Skip to content

Add support for typed entry payloads - #72

Merged
vigneshwerv merged 9 commits into
devfrom
worktree/add-typed-batch-entries
Aug 11, 2026
Merged

Add support for typed entry payloads#72
vigneshwerv merged 9 commits into
devfrom
worktree/add-typed-batch-entries

Conversation

@vigneshwerv

Copy link
Copy Markdown
Contributor

No description provided.

@snoble

snoble commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Written by Claude, in Steven's session. My words and judgement, not his.

I pushed two commits to this branch:

1. §2.3a — common fields were derived from the operation. getFields exposed only the entry fields the source operation bound, so a payload's surface was whatever CLI version generated that operation. Before the fix, across the committed fixtures:

field template edge-case
posted 9/9 1/5
description 0/9 1/5
tags, groups, conditions 0/9 0/5

So tags, groups and conditions were reachable from no payload at all. §2.3a says not to derive the set — the operation is a codegen input, not the transport. COMMON_ENTRY_FIELDS now adds any of the five the operation did not bind. lines stays out.

Visible proof it mattered: tests/template-schema/ has two operation sets from different CLI generations, and their payloads had different surfaces for the same entry type. A test asserted that as correct. It now asserts the opposite.

2. integration-tests was red on every run, unrelated to your code. The step dies at Install Fragment CLI: the workflow caches /home/linuxbrew/.linuxbrew under a fixed key and the runner disables auto-update, so it restores a months-old Homebrew that cannot pour a current bottle (unknown install step: run). Added brew update, rotated the cache key. This blocks every PR in the repo, not just this one.

Fixtures regenerated — batch-ledger-entries.test.ts imports its builders from them, so stale ones meant those tests ran against pre-fix code. 74 passing; the 13 failures are the credential-gated integration tests, unchanged.


One call I made that may be wrong. FixedValuesV1 does not expose description/tags because its operation pins them to literals. I applied §2.3's rule for parameters ("fixed by the operation… MUST NOT become a caller-supplied field") to entry fields. But §2.3a is unconditional, so a strict reading makes this non-conforming.

The two readings differ in capability: under mine, an operation can guarantee an entry always carries a given tag; under the strict one, a caller can override it. The spec does not say which, and all four SDKs should agree — worth one sentence in shared-spec §2.3a rather than deciding here.

Also still missing: node vendors none of the shared spec/conformance/ fixtures. §5 makes that a MUST for every SDK.

vigneshwerv and others added 8 commits August 11, 2026 13:37
Two independent fixes.

## Spec 2.3a: common fields are not the operation's to decide

`getFields` walked the source operation's `entry` object and exposed the fields it
bound to a variable, so a payload's surface was whatever the CLI version that
generated the operation happened to write. Measured across the committed fixtures:

| field | template client | edge-case client |
| --- | --- | --- |
| posted | 9/9 | 1/5 |
| description | 0/9 | 1/5 |
| tags, groups, conditions | 0/9 | 0/5 |

So `tags`, `groups` and `conditions` were reachable from no payload at all, and
`description` from one. That is what spec 2.3a forbids in as many words: the
operation is a codegen input, never the transport, and a payload travels as an
`AddLedgerEntryInput`, so what the operation binds places no limit on what the
payload may carry. Deriving the set invents a restriction the API does not have and
moves a payload's surface whenever the CLI changes -- the spec 2.6 breakage the
whole design is meant to prevent.

`COMMON_ENTRY_FIELDS` now names the five, with the types `LedgerEntryInput`
declares, and `getFields` appends any the operation did not already bind. Appended
rather than interleaved, so the operation's own fields keep their source order.
`lines` stays excluded: it cannot be combined with an entry that has a `type`.

Two existing tests asserted the old rule and have been updated, and a new one
derives a payload from an operation binding none of the five and asserts all five
are still settable -- which is what proves they are not derived.

## Homebrew

`integration-tests` has been failing on every run since the formula moved:

    ==> Pouring ca-certificates--2026-07-16.all.bottle.2.tar.gz
    ##[error]unknown install step: run
    You have disabled automatic updates and have not updated today.

The workflow caches `/home/linuxbrew/.linuxbrew` under a fixed key, so it restores
whatever Homebrew was current when that key was first written, and the runner
disables auto-update. A months-old Homebrew cannot pour a current bottle. Added
`brew update` before installing, which is Homebrew's own advice in that message,
and rotated the cache key so the poisoned cache cannot persist.

The step dies before any test runs, so this was never about the PR's code -- but it
does mean `addLedgerEntries commits a batch of typed payloads` has still never
executed anywhere.
The committed clients under `tests/fixtures/` are generated output, and the
previous commit changed the generator without regenerating them. They were stale in
a way that mattered: `batch-ledger-entries.test.ts` imports its builders from them,
so those tests were exercising pre-fix code, and `template-schema-client.test.ts`
reads them as text and passed only because of it.

Regenerated with `scripts/update-test-schema.sh`'s codegen steps. Every payload now
carries the five common fields:

| field | template client | edge-case client |
| --- | --- | --- |
| posted | 9/9 | 5/5 |
| description | 9/9 | 4/5 |
| tags | 9/9 | 4/5 |
| groups | 9/9 | 5/5 |
| conditions | 9/9 | 5/5 |

The 4/5 is `FixedValuesV1`, whose operation pins `description` and `tags` to
literals. A field the operation fixes is still posted but stays out of the caller's
hands, which is the same rule spec 2.3 applies to parameters — exposing it would let
a caller override a value the operation deliberately set.

Two tests asserted the behaviour this stack is removing, and both passed only
against the stale fixtures:

- `exposes the fields each operation set binds, and no others` asserted that the
  plain operation set's payload has no `tags`, `groups` or `conditions`. Those two
  operation sets come from different CLI generations, which is exactly the drift
  spec 2.3a exists to hide from callers, so the test now asserts the opposite: the
  same surface whichever generation produced it.
- `binds different fields per entry type within one operation set` asserted
  `CardSettleV1` has no `conditions` because its operation does not bind them. Now
  it does, like its siblings.

74 passing; the 13 failures are the credential-gated integration tests, unchanged
from before this work.
The rebase conflicted only in generated output, which is resolved by
regenerating rather than merging: the schema descriptions that landed on dev
via #74 now flow through generated/, dist/ and the fixture clients.
An entry type whose Ledger Lines the Schema does not fix takes them from the
caller, and `lines` is not a common field, so a payload for it could only ever
post an entry with no Lines. Generate none, and say which entry type it was and
what to reach for instead -- `addLedgerEntries` takes a raw AddLedgerEntryInput
alongside typed payloads.

Also stop carrying values the operation fixes. They are encoded in the Schema
the operation was generated from, so the API derives them from there. Dropping
them costs a caller nothing, since every common field is settable anyway.
Three defects, all silent:

- A `typeVersion` bound to a variable posted 1, discarding the version the
  caller of the single-entry operation chose. A payload names one version and
  posts it, so generate none and say why, as with lines.
- Conflict detection compared the entry field two operations write rather than
  the field a caller writes, so `ledger: {ik}` and `ledger: {id}` at one
  identity deduplicated silently despite exposing different payload fields.
- The builderBody test helper looked for a terminator that block-bodied
  builders do not have, so an assertion against one would have read most of the
  file and passed for the wrong reason.

Also: parametersMode is a discriminated union, so a payload carries only what
its case has and the renderers narrow instead of re-checking optional fields;
entry members are lines rather than a joined string that had to be re-split to
indent; and the test helper's misnamed `parameters` option is `entryFields`,
which is what it always was.
@vigneshwerv
vigneshwerv force-pushed the worktree/add-typed-batch-entries branch from aa08aa6 to 7e09683 Compare August 11, 2026 19:26
The other three SDKs expose a payload's parameters as fields alongside the
common ones, and so do this SDK's own generated single-entry methods
(`PostUserFundsAccount({ ik, ledgerIk, amount })`). Nesting them made one entry
type read two different ways depending on which method you reached for.

Nesting had avoided the collision a flat namespace allows, so handle it the way
§2.5 describes instead: the first occupant of a name keeps it, a later parameter
is suffixed, and it still posts under the name the Schema knows it by. Unlike
Python's, TypeScript's failure mode here is loud -- a duplicate property in a
generated type is a compile error -- so the escape only has to be correct, not
defensive.
@vigneshwerv
vigneshwerv merged commit 56d68e1 into dev Aug 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants