Skip to content

Expose every common entry field, and unbreak the Homebrew CI step - #73

Merged
snoble merged 2 commits into
worktree/add-typed-batch-entriesfrom
claude/fix-2-3a-common-fields
Aug 6, 2026
Merged

Expose every common entry field, and unbreak the Homebrew CI step#73
snoble merged 2 commits into
worktree/add-typed-batch-entriesfrom
claude/fix-2-3a-common-fields

Conversation

@snoble

@snoble snoble commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Written by Claude, running in Steven's session — these are Claude's words and judgements, not Steven's.

Stacked on #72. Two independent fixes, found by reviewing #72 against the shared spec with the fragment-ruby implementation as a lens.

1. §2.3a was inverted

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 that operation happened to write. Measured across the committed fixtures, before the fix:

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

tags, groups and conditions were reachable from no payload at all. The spec forbids this in as many words:

Do not derive this set from the operation. … one generation binds tags, groups and conditions while another binds typeVersion instead, and neither binds description. The operation is a codegen input, never the transport… Deriving the set would invent a restriction the API does not have and would move a payload's surface whenever the CLI changed.

That last clause was already true in this repo: tests/template-schema/ carries two operation sets from different CLI generations, and their payloads had different surfaces for the same entry type. A test asserted that as correct.

COMMON_ENTRY_FIELDS now names the five with the types LedgerEntryInput declares, appended after the operation's own fields so source order is untouched. lines stays excluded — it cannot be combined with an entry that has a type.

One deliberate exception: a field the operation pins to a literal stays out of the caller's hands. FixedValuesV1 pins description and tags, so it is 4/5 above. It still posts them; exposing them would let a caller override a value the operation deliberately set, which is the rule §2.3 already applies to parameters.

2. integration-tests has been red on every run

Not #72's fault — the step dies before any test runs:

==> 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 — Homebrew's own advice in that message — and rotated the cache key so the poisoned cache cannot persist.

This is repo-wide, not PR-specific: it will block every node-client PR until fixed.

Tests

Three asserted the old rule and now assert the new one; the one that matters is new: derive a payload from an operation binding none of the five, and assert all five are still settable. That is what proves they are not derived.

The committed tests/fixtures/generated-*-client.ts are regenerated — they are the actual generated output, batch-ledger-entries.test.ts imports its builders from them, and leaving them stale would have meant those tests exercising pre-fix code.

74 passing. The 13 failures are the credential-gated integration tests, unchanged from before this work — including addLedgerEntries commits a batch of typed payloads, which has never executed anywhere.

Not included

No x-fragment-experimental header. fragment-ruby found addLedgerEntries is gated behind one, but per Steven that requirement is being removed, so node should not grow a flag it will immediately have to drop.

Still outstanding from my review and not in this PR: node vendors none of the shared spec/conformance/ fixtures, which §5 makes a MUST. That is the remaining gap.

🤖 Generated with Claude Code

snoble added 2 commits August 6, 2026 16:41
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.
@snoble

snoble commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Possible non-conformance: pinned common fields

Flagging a judgement call in this PR rather than leaving it to be discovered.

FixedValuesV1 does not expose description or tags, because its operation pins them to literals:

description: "posted by the nightly sweep"
tags: [{ key: "source", value: "sweep" }]

That is why the edge-case client is 4/5 rather than 5/5 on those two fields. Every other payload in both fixtures is 5/5.

Why I did it this way. §2.3 says a parameter bound to something other than a variable "MUST be skipped. The value is fixed by the operation and MUST NOT become a caller-supplied field." I applied the same reasoning to entry fields: the value is still posted, but exposing it would let a caller override something the operation deliberately set.

Why it may be wrong. §2.3a is unconditional — "A payload MUST expose all of" — and lists the seven without carving out fields the operation pins. Read strictly, FixedValuesV1 is non-conforming, and the fix would be to let the caller-supplied value win over the pinned one.

What settles it. One sentence in shared-spec/typed-batch-entries.md §2.3a saying whether a pinned entry field stays pinned or becomes overridable. Whichever way it goes, all four SDKs should agree, and right now the spec does not say — so this is worth raising there rather than deciding per-SDK.

Worth noting the two readings differ in what a caller can actually do, not just in shape: under mine, an operation can guarantee an entry always carries a particular tag; under the strict reading, it cannot.

Also still outstanding and not addressed here: node vendors none of the shared spec/conformance/ fixtures, which §5 makes a MUST for every SDK.

@snoble
snoble merged commit aa08aa6 into worktree/add-typed-batch-entries Aug 6, 2026
1 check failed
@snoble
snoble deleted the claude/fix-2-3a-common-fields branch August 6, 2026 23:52
vigneshwerv added a commit that referenced this pull request Aug 11, 2026
The runner's preinstalled Homebrew is months old and auto-update is disabled
there, but formula definitions come fresh from the JSON API. A current
ca-certificates bottle cannot be poured by that Homebrew -- it fails with
`unknown install step: run` -- and the existing cache key restores the same
stale Homebrew on every run.

Bump the cache key and update Homebrew before installing. Ported from #73,
which fixed this on another branch.
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.

1 participant