Replace instruction arguments with a single data type node - #142
Conversation
Instructions describe their serialised data with one optional type node, exactly like accounts and events: data replaces arguments and extraArguments, instructionArgumentNode is deleted, and contextual defaults flow through the inject/provide pattern with lexical scoping and the self-contained rule made normative. Resolvers are removed in favour of namespaced plugins with graceful degradation — refine a declared shape or mark an honest absence, never a fake value. instructionRemainingAccountsNode declares a named client input via identifier, the byte-delta and condition unions lose their resolver members, and instructionArgumentLinkNode is removed since values inside a type node are reached by path expressions, not links.
trevor-cortex
left a comment
There was a problem hiding this comment.
This PR completes the data-unification wave (#131, part of #102): instructionNode.arguments/extraArguments are replaced by a single optional data: TypeNode, instructionArgumentNode and its satellite machinery (instructionArgumentLinkNode, resolverValueNode, resolverDependency, instructionRemainingAccountsValue) are deleted, contextual defaults move to the inject/provide pattern with newly-normative lexical-scoping and self-contained rules, and instructionRemainingAccountsNode becomes a named client input with plugin-based resolution. Node/union counts, tests, examples, and generated artifacts (spec.json, docs/) all line up — schema.json is untouched, which is expected since it's still the placeholder.
The design is coherent and the changeset does a good job spelling out the upgrade lens. major bump matches the CONTRIBUTING semver policy. One consistency issue worth resolving before (or right after) merge, plus a couple of non-blocking notes:
1. defaultValueStrategy semantics got stricter in the migration (inline comment). The deleted instructionArgumentNode documented defaultValueStrategy as "when absent, optional is assumed", but structFieldTypeNode says it's "Required when defaultValue is set" — and most of the converted examples in this PR set a defaultValue without a strategy. Either the examples or the structFieldTypeNode doc needs to give; details inline.
2. argumentValueNode naming (non-blocking question). With arguments gone as a concept, argumentValueNode now "refers to a value within the data of the surrounding instruction" via a path — the name is the last remnant of the old model. Since this wave is already maximally breaking, was a rename (e.g. dataValueNode) considered? Fine to keep for familiarity/ecosystem continuity, just flagging that the window to rename cheaply is now.
3. Changeset remaining-accounts snippet (cosmetic). The second diff block in the changeset starts from the new positional-identifier signature (instructionRemainingAccountsNode('authorities', {) and only adds plugins — a reader migrating from v1 sees a "before" state that never existed. Since the identifier change is described in its own bullet this is arguably fine as a staged illustration, but worth a second look. Also trivial: that snippet uses dependsOn: ['data.multisig'] while the docs example uses data.hasMultisig — different examples, just noting in case they were meant to match.
For subsequent reviewers:
- The normative additions (lexical scoping on
providedNode/injectedValueNode, the self-contained rule) are stated consistently in three places (InjectedValueNode.ts,ProvidedNode.ts,InstructionNode.tsprovidesdocs) — worth a read to confirm the wording can't be interpreted divergently. TheprovidedNode"transparent pipe / family check at the injection point" model composes cleanly with the new rules. - Verify CI's artifact-lockstep check passed —
docs/andspec.jsonare machine output here, so any review nit on examples should be fixed insrc/**/*.examples.tsand regenerated. - The new tests in
tests/spec.test.tspin the removals and the new shapes well (including union member lists and attribute ordering oninstructionRemainingAccountsNode).
argumentValueNode becomes dataValueNode and accountFieldValueNode becomes accountDataValueNode — with arguments gone as a concept, both names describe what the nodes do: refer to a value at a path within anchored data, mirroring the data attributes and interpolation roots. The structFieldTypeNode defaultValueStrategy docs adopt the soft rule the deleted instructionArgumentNode carried (only relevant when defaultValue is set; absent means optional), resolving the inconsistency the migration had silently promoted. The changeset's plugin illustration becomes a plain v2 snippet and gains the rename bullet.
trevor-cortex
left a comment
There was a problem hiding this comment.
Re-review after the follow-up commits. All three points from my previous review are resolved:
defaultValueStrategysemantics —structFieldTypeNodenow adopts the soft rule the deletedinstructionArgumentNodecarried ("Only relevant whendefaultValueis set … When absent,optionalis assumed"), consistently across source,spec.json, and the generated doc. The converted examples that set a contextualdefaultValuewithout a strategy are now valid as written, and the lens needs no strategy synthesis. ✅- Naming —
argumentValueNode→dataValueNodeand (bonus)accountFieldValueNode→accountDataValueNode. Both names now describe the path-into-anchored-data semantics, and the rename is propagated everywhere I checked: all five affected unions, examples, docs, tests, and the changeset's upgrade notes. ✅ - Changeset snippet — the remaining-accounts block is now a plain
// v2:illustration rather than a diff from a state that never existed, anddependsOn: ['data.hasMultisig']now matches the docs example. ✅
Two trivial leftovers, neither blocking:
- Grammar typo in
instructionByteDeltaValueUniondocs: "andataValueNode" → "adataValueNode" (inline comment). Fix inInstructionNodeUnions.tsand regenerate — it currently flows intospec.jsonanddocs/InstructionByteDeltaValue.md. - In
src/spec/nodes/contextualValueNodes/AccountDataValueNode.examples.ts(~line 5, unchanged so not anchorable), the first example title still reads "Create an account field value node from an account name and a field path" — stale after theaccountDataValueNoderename; also surfaces as the heading in the generateddocs/contextualValueNodes/AccountDataValueNode.md.
For subsequent reviewers: the new tests/spec.test.ts blocks pin the removals, the union member lists, and the instructionRemainingAccountsNode identifier-first shape precisely — good regression coverage for the whole wave. Nothing else changed my prior assessment; the normative scoping wording remains consistent across InjectedValueNode, ProvidedNode, and InstructionNode.provides.
The instructionByteDeltaValue union docs read 'a dataValueNode' rather than the article the mechanical rename left behind, and the accountDataValueNode example title drops the stale 'account field value node' wording. Both flow into spec.json and the generated docs.
This PR implements #131, the last of the data-unification wave: instructions describe their serialised data with one optional type node, exactly like accounts (#137) and events.
data?: TypeNodereplacesarguments/extraArguments;instructionArgumentNodeis deleted (it wasstructFieldTypeNodeplus argument-only machinery). Absentdata= no bytes. Contextual defaults use inject/provide: field defaults carryinjectedValueNodes, the instruction'sprovidessupplies contextual values. Two rules are now normative oninjectedValueNode/providedNode/provides: lexical scoping (nearest enclosingprovideswins, shadowing allowed — relevant forsubInstructions) and the self-contained rule (every key resolves in scope or has afallback), which makes "can this instruction be built from its IDL alone?" statically checkable.Resolvers are gone, plugins take over with graceful degradation: refine a declared shape (remaining-accounts input +
codama.jsResolverplugin) or mark an honest absence (account with nodefaultValue+ plugin) — the standard never carries a fake value.instructionRemainingAccountsNodenow declares a named client input viaidentifier(same model asinstructionAccountNode); the byte-delta and condition unions lose their resolver members;resolverDependencyandinstructionRemainingAccountsValueunions are deleted.instructionArgumentLinkNoderemoved: link nodes point at nodes on the tree; values inside a type node are reached by path expressions (#141) — a different paradigm. An "instruction + path" definition-link can return additively if a need appears.Follow-up from review:
argumentValueNode→dataValueNodeandaccountFieldValueNode→accountDataValueNode— with arguments gone as a concept, both names describe what the nodes do (a value at a path within anchored data), mirroring thedataattributes and interpolation roots. AndstructFieldTypeNode.defaultValueStrategyadopts the soft rule the deletedinstructionArgumentNodecarried: only relevant whendefaultValueis set (a strategy without a default is meaningless); when absent,optionalis assumed — so the lens needs no strategy synthesis.Counts: 90 nodes (−3), 31 unions (−2). Examples rewritten throughout (instruction walkthroughs, all four contextual-value showcases now model inject/provide, remaining-accounts plugin form, display/discriminator hosts).
Upgrade lens notes (for
upgradeV1ToV2): arguments → struct fields one-to-one (plain defaults carry over with their strategy as-is; contextual defaults become provide/inject pairs with the field identifier as key); emptyarguments→ omitdata;extraArguments/resolvers → plugins under a codama-owned namespace;remainingAccounts.value: argumentValueNode(x)→identifier: x; resolver-valued byte deltas with no expressible value → drop the node, attach the plugin to the instruction; kind renamesargumentValueNode→dataValueNode(itsname→path) andaccountFieldValueNode→accountDataValueNode(itsfield→path).Closes #131 (manual close — default branch is
1.x). Part of #102.