Skip to content

[Language design] Model typed cross-Script transaction roles without runtime linkage #10

Closed

Description

@a19q3

Summary

Design a CKB-native source and metadata model for typed transaction roles across independently compiled Scripts.

The goal is to let one CellScript entry declare the foreign inputs, outputs, dependencies, and peer Script identities that it relies on in the same atomic CKB transaction—without adding an ELF linker, runtime calls, or a shared hidden state model.

This work should begin only after the artifact-only composition boundary in #9 is specified well enough to expose real role and conflict requirements.

Problem

CellScript can already:

  • inspect transaction and Script Group views through typed SourceView;
  • construct and compare Script/ScriptRef values;
  • import schemas and pure helpers at compile time;
  • bind package interfaces and deployment identities;
  • express explicit local input/output transitions.

What it cannot express as one typed protocol contract is:

“This action is valid only when a foreign Script consumes these typed inputs, creates these typed outputs, and independently accepts the same transaction under this exact or compatible identity.”

Applications can hand-code individual syscall checks, but the relationship is not a first-class role graph. It is therefore missing from interface compatibility, builder generation, ProofPlan, audit output, and protocol-level transaction composition.

Argent's closed/open ICC and consumes/emits/observes model demonstrate the product value of making the application graph source-level. CellScript must adapt the idea to CKB's Cell Model and Script Groups rather than copying Kaspa covenant semantics.

CKB-native model

CKB already executes independent Lock and Type Script Groups against the same transaction. The language feature should describe and verify relationships between those groups; it should not call one Script from another.

flowchart TB
    SRC["CellScript protocol/role declarations"] --> A["Compile artifact A obligations"]
    SRC --> B["Compile artifact B obligations"]
    SRC --> PB["Emit ProtocolBundle role graph"]

    TX["One atomic CKB transaction"] --> GA["Script Group A"]
    TX --> GB["Script Group B"]

    GA --> VA["Artifact A validates its observable facts"]
    GB --> VB["Artifact B validates its observable facts"]

    VA --> OK{"Both independently accept?"}
    VB --> OK
    OK -- "yes" --> ACCEPT["Transaction valid"]
    OK -- "no" --> REJECT["Transaction rejected"]
Loading

Required role classes

The design should separate these concepts rather than overloading ordinary action parameters:

Local lifecycle roles

  • inputs controlled by the current Script Group;
  • outputs created or updated under the current Script identity;
  • existing consume, create, transition, and bounded lifecycle rules.

Closed foreign roles

A peer role bound to:

  • exact package/interface hash;
  • exact serialized state schema;
  • exact deployment and Script identity policy;
  • fixed Lock/Type role;
  • explicit transaction or group source;
  • fixed or bounded cardinality.

This is the CKB analogue of a statically linked peer application, but it remains an independently executed Script.

Open foreign roles

A peer role whose implementation is chosen at transaction construction time, subject to an explicit interface/schema/effect contract and an on-chain-verifiable Script identity binding.

Open roles must not rely on a Registry lookup as consensus proof. See the separate interface-bound Script-handle issue that will build on this role model.

Dependency roles

  • code or data CellDeps;
  • HeaderDeps;
  • spawned VM/IPC verifier contracts;
  • read-only configuration Cells.

Dependency roles remain distinct from consumed inputs and created outputs.

Possible source shape

This issue must not preselect final syntax. A strawman only illustrates the semantic information:

protocol Settlement {
    closed input order_before: OrderState
        from transaction
        script order_v1

    open input asset_before: FungibleAsset
        interface fungible_v1

    output order_after: OrderState
        verified_by order_v1

    output asset_after: FungibleAsset
        same_implementation_as asset_before
}

action settle(...) for Settlement {
    transition order_before -> order_after

    verification
        require asset_after.amount == asset_before.amount
}

The accepted design may use a manifest rather than new language syntax. The non-negotiable part is the typed role graph and its evidence, not the spelling.

Role graph and obligations

graph LR
    ENTRY["Current entry"] --> LI["Local input role"]
    ENTRY --> LO["Local output role"]
    ENTRY --> FI["Foreign input role"]
    ENTRY --> FO["Foreign output role"]
    ENTRY --> DEP["CellDep/HeaderDep role"]

    FI --> ID["Script/interface identity"]
    FI --> SCHEMA["Typed schema decode"]
    FO --> CORR["Output correspondence"]
    FO --> PEER["Peer independently executes"]
    DEP --> POLICY["Read-only identity policy"]
Loading

For each role, metadata must record:

  • stable role identity and source span;
  • local/foreign/dependency classification;
  • transaction versus group-relative source;
  • cardinality;
  • Lock/Type/CellDep role;
  • Script and deployment identity policy;
  • interface and schema requirements;
  • readable fields and required relations;
  • whether the current Script checks the fact on chain;
  • which peer artifact independently enforces its own transition;
  • builder assumptions that remain off chain.

Soundness rules

  1. Presence is not authorization. Observing a peer input proves only that it is present and matches the declared identity.
  2. Shape is not behavior. Decoding a peer state does not prove that the peer intended the observer's transition.
  3. Builder construction is not consensus proof. Every claimed on-chain fact needs generated verifier coverage.
  4. Peer success is independent. The bundle must execute each participating Script Group; one Script cannot claim another has accepted without evidence.
  5. Closed and open roles cannot collapse. An exact implementation dependency must not silently become an open interface slot.
  6. Transaction-wide sources are explicit. A role must not accidentally widen from Script Group to the complete transaction.
  7. Cardinality is executable. A bound in metadata is insufficient without runtime count enforcement.
  8. Interface identity must be bound. Package/Registry metadata alone cannot identify the Script bytes executing on chain.

Implementation phases

Phase 0 — use cases and threat model

Select at least three concrete multi-Script protocols, for example:

  • token/order atomic settlement;
  • factory/child Type ID launch;
  • DAO proposal plus authorization Lock;
  • shared pool plus independent token Type Scripts.

For each, document current manual checks and the intended role graph.

Phase 1 — metadata-only role graph

This phase must not advertise on-chain coverage.

Phase 2 — closed roles

  • bind exact peer artifacts, interfaces, schemas, deployments, and Scripts;
  • generate current-Script observation checks;
  • construct and dry-run all peer Script Groups through ProtocolBundle;
  • add negative fixtures for identity, source, cardinality, schema, and output mismatches.

Phase 3 — open roles

  • add only after the interface-bound Script handle has a complete consensus binding;
  • preserve exact runtime-selected implementation across declared continuations;
  • ensure both observer and peer independently validate the transaction;
  • add substitution, downgrade, and Registry-spoofing adversarial tests.

Acceptance criteria

  • An accepted RFC defines local, closed foreign, open foreign, and dependency roles.
  • At least three real protocol fixtures justify the model.
  • Roles are canonical metadata and public-interface entries.
  • Every claimed check maps to typed semantics, ProofPlan, lowering, and machine evidence.
  • ProtocolBundle builds and dry-runs every participating Script Group against identical transaction bytes.
  • Closed roles bind exact interface, deployment, and Script identities.
  • Open roles cannot ship before an on-chain-verifiable interface-handle contract exists.
  • Transaction versus Script Group scope is explicit and tested.
  • Fixed and bounded cardinality rules are executable.
  • Negative tests cover missing, extra, reordered, substituted, incompatible, and non-executed peer roles.
  • Generated builder, CLI, LSP, VS Code, Registry, checker, and docs agree.
  • No ELF-linker or cross-Script-call claim is introduced.

Non-goals

  • synchronous contract calls;
  • shared mutable VM memory;
  • compiling several Scripts into one ELF;
  • Kaspa covenant IDs or P2SH template receipts;
  • trusting Registry availability during consensus execution;
  • inferring peer behavior from matching state shape;
  • adding actor/mailbox terminology to CellScript;
  • hiding transaction roles behind ordinary generic values.

Dependencies

References

Activity

  1. a19q3 commented on Aug 27, 2026

    @a19q3
    CollaboratorAuthor

    Research update: Argent ICC confirms the model, but also sharpens what one CKB Script can claim

    Comment audit

    There were no prior comments on this issue at the time of review.

    The relevant Argent PRs have little substantive review discussion. The most useful primary source is the repository's explicit ICC semantics document:

    https://github.com/argent-lang/argent/blob/6a627da4abdd53e71fdb71da0f711ccc96424eb6/docs/icc-semantics.md

    It states two rules that should be normative here:

    • ICC is not a call from one contract to another;
    • presence of a peer input/output is not authorization by that peer.

    CKB-specific interpretation

    CKB already provides the atomic composition mechanism: the node groups inputs and outputs by exact Script and executes each relevant Lock/Type Script Group against the same transaction.

    Therefore a CellScript role declaration can prove only facts observable and checked by the current artifact, such as:

    • a transaction/group Cell is present;
    • its Lock/Type Script matches a declared policy;
    • its data decodes under a declared schema;
    • its cardinality and relation to another observed Cell satisfy current-script checks;
    • a local input/output transition is discharged by the current Script.

    It cannot, from inside the current CKB-VM program, call a syscall that says “peer artifact B has accepted.” Peer success is a property of whole-transaction validation. ProtocolBundle can dry-run and record every group off chain; CKB consensus makes the transaction valid only if all groups pass on chain.

    Metadata should therefore distinguish:

    • checked_by_current_script;
    • required_peer_script_group;
    • checked_by_bundle_dry_run;
    • guaranteed_only_by_ckb_consensus_if_the_peer_group_is_present.

    Do not collapse these into one verified boolean.

    Closed versus open roles

    Argent's closed/open split remains useful:

    • Closed role: exact peer artifact/interface/deployment/Script identity is part of the compiled contract.
    • Open role: implementation is selected at transaction construction time under an interface/receipt policy.

    The audit supports shipping closed roles first. Open roles must wait for #11 because a raw CKB Script and a Registry interface name do not prove that the executing code satisfies that interface.

    For a closed role, exact identity should include at least:

    • Lock versus Type role;
    • transaction-wide versus group source;
    • complete Script fields and hash_type policy;
    • exact data schema/codec;
    • package interface hash;
    • checked artifact and target/profile identity;
    • deployment/code CellDep identity;
    • fixed/bounded cardinality;
    • which fields the current Script actually reads and constrains.

    Presence, observation, and authorization

    A useful role taxonomy is:

    • Local owned input/output: current Script controls the lifecycle.
    • Foreign observed input/output: current Script verifies declared facts but does not own peer authority.
    • Required peer group: bundle/transaction must include and execute a named peer Script Group.
    • Dependency role: CellDep/HeaderDep/configuration, never confused with consumed state.
    • Builder-only role: construction/signing/fee/change assumption, explicitly non-consensus.

    This prevents an observed foreign Cell from accidentally acquiring consume/create authority in the type system.

    Source qualification is mandatory

    The official CKB source model distinguishes Input, Output, CellDep, HeaderDep, GroupInput, and GroupOutput:

    https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0046-syscalls-summary/0046-syscalls-summary.md

    Every role must preserve that distinction through AST, IR, typed semantics, ProofPlan, ABI, builder, and checker. “Input role” is not precise enough because transaction Input and GroupInput have different authority/visibility implications.

    Recommended staging

    1. Metadata-only role graph with honest checked/required/builder classifications.
    2. Exact closed roles using fixed cardinality and existing Script/data checks.
    3. ProtocolBundle construction and all-group dry-run on identical transaction bytes.
    4. Bounded roles only after [Candidate][Consensus runtime] Implement executable bounded Cell-group consumption #7/[Candidate][Consensus runtime] Implement executable bounded output-plan correspondence #8 execute positively.
    5. Open roles only after exact interface-bound handles in [Language/ABI] Bind runtime Script values to verified package interfaces #11.
    6. Upgrade-aware roles only after the transactional policy in [Toolchain][Upgrade] Add a transactional package, interface, and deployment upgrade plan #20.

    Acceptance additions

    • Peer Cell present with matching schema but wrong Script: reject.
    • Matching Script/data present outside the declared source view: reject or ignore exactly as specified.
    • Peer group omitted from ProtocolBundle dry-run: bundle evidence invalid.
    • Current Script's observation check removed while peer still passes: current artifact/checker rejects.
    • Peer Script fails while current Script passes: whole transaction/bundle fails, but current artifact metadata does not falsely claim it checked peer behavior.
    • Same interface name with different hash: reject.
    • Closed role substituted with a compatible-but-not-exact artifact: reject.
    • Open role Registry record valid but transaction Script unbound: reject.
    • Foreign observed value cannot be consumed/replaced through local lifecycle operations.
    • Role cardinality bound exists in metadata but runtime count is absent: production fail-closed.

    Conclusion

    The issue's direction is sound. The most important clarification is evidence attribution: typed roles describe relationships across independently executed Script Groups, while each generated artifact may claim only the checks it performs. Whole-transaction acceptance belongs to CKB consensus and ProtocolBundle evidence, not to a fictional cross-Script call.

  2. a19q3 commented on Sep 11, 2026

    @a19q3
    CollaboratorAuthor

    Completed for the exact closed-role 0.30 scope in ee6dbfb4 and exercised by the exact four-artifact ProtocolBundle/CKB-VM corpus through a2ee77aa. Closed roles bind source, schema, interface, artifact, deployment, Script identity, ownership, and evidence attribution without claiming runtime linkage between peer Scripts. Runtime-selected open roles have been split into post-v1 issue #29, dependent on compatible-open handle issue #28. Ledger/docs record the split and remaining_work=[] at cdc5b53c. No TAC or release workflow was invoked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions