Skip to content

feat(gateway): write-once public demand release ledger (kits K4a) - #543

Draft
LamaSu wants to merge 1 commit into
feat/kits-registry-k1from
feat/kits-release-ledger-k4a
Draft

LamaSu wants to merge 1 commit into
feat/kits-registry-k1from
feat/kits-release-ledger-k4a

Conversation

@LamaSu

@LamaSu LamaSu commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Summary

Kits K4a: the write-once public demand release ledger. Each closed period's PublicOpportunityRelease (built by #365's buildPublicRelease) is recorded exactly once, and served only after it verifies.

A release digest proves the record is unmodified. It does not prove who built it, or that its period was released only once. That is astra's weakest link on #397 (112e): "Callers must obtain the record from the write-once release ledger." PX-13 assigns once-per-period to the publisher (kits).

Draft, stacked on #511 (which stacks on #397). It stays a draft until both merge (steward #5665).

Design

  • Storage: <root>/releases/<YYYY-MM>.json on the Kit registry's durable volume (K1's root). No table (no schema change) and no cache.
  • Write-once: each file is created exclusively (a temp file, then link; EEXIST means another writer won) and is never overwritten.
    • The same record again answers created: false.
    • A different record for a released period answers 409 release_period_taken.
  • The entry: {schema, period, publishedAt, publisher, approvedSet, release}, as canonical JSON. approvedSet is the approved-set snapshot the record was built with, normalized as feat(spec): Capability Kit manifest identity, OperatorBindingDTO and OpportunityDTO v0 contracts (interface-only; not before wave D) #397's approvedSnapshot, so a consumer verifies against the set approved at release time.
  • Verification is feat(spec): Capability Kit manifest identity, OperatorBindingDTO and OpportunityDTO v0 contracts (interface-only; not before wave D) #397's own: demandAggregatesFromRelease(release, approvedSet, asOf) runs before writing and on every read. A read also requires strict UTF-8, the exact canonical bytes, matching periods (file name, entry and record) and a canonical approved-set snapshot. A failing entry is never served, and the listing skips it.
  • One copy at publish: publish verifies and stores ONE plain copy of its input.
  • Root confinement, as K1:
    • O_NOFOLLOW reads, and an lstat-checked directory;
    • a probe for O_NOFOLLOW and hard links before the first write (503 registry_unsupported_fs);
    • a runtime without O_NOFOLLOW refuses reads.
  • Privacy: the publisher stays in the entry and the audit log, and no read returns it.

Routes

Route What
GET /api/kits/demand/releases every released period that verifies, newest first: {period, digest, aggregateCount, publishedAt}
GET /api/kits/demand/releases/:period {period, release, approvedSet, publishedAt}; 404 if unreleased, 400 if malformed, 500 if the stored entry fails verification
  • Both need an authenticated caller, like every /api/kits path. Making them public is an api-gate and policy decision for the operator.
  • There is no publish route: the server-side producer calls ledger.publish. painpoints is scoping that side (#5725).

Tests (Spark, at 576f8ef)

Check Result
release-ledger.test.ts 38 passed
gateway vitest 209 files, 3816 passed / 13 skipped
gateway tsc --noEmit 0
mutation pass 19 of 20 killed (the survivor is equivalent: lstat reports a symlink as not a directory)
secret scan 0

The tests cover:

  • once per period, including concurrent writers from several instances;
  • every refusal at publish;
  • tampered entries (bytes, canonical form, period and name, approved set, ISO time, truncation, invalid UTF-8);
  • symlinks and a missing O_NOFOLLOW;
  • the size cap, the single-copy rule, and the routes.

Follow-ups

🤖 Generated with Claude Code

https://claude.ai/code/session_015zYzsSSFUHbPV5DNxssX4A

PX-13 requires each period to be released exactly once, and #397's
demandAggregatesFromRelease expects its caller to take the record from a
write-once ledger. #365's buildPublicRelease builds the record. Its digest
proves the record is unmodified, not who built it or that its period was
released only once (astra 112e's weakest link on #397).

- services/release-ledger.ts stores <root>/releases/<YYYY-MM>.json on the
  Kit registry's durable volume (K1).
  - Each entry is created exclusively (a temp file plus a hard link; EEXIST
    means another writer won) and is never overwritten.
  - The same record again answers created:false. A different record for a
    released period is refused (409 release_period_taken).
  - Each entry keeps the approved-set snapshot the record was built with, so
    a consumer can verify against the set approved at release time.
- Verification is #397's demandAggregatesFromRelease, run before writing and
  on every read. Reads also require strict UTF-8, the exact canonical bytes
  and matching periods. Reads use O_NOFOLLOW, the directory is lstat-checked,
  and K1's capability probe runs first. publish verifies and stores one
  plain copy of its input.
- routes/kit-releases.ts adds GET /api/kits/demand/releases and
  GET /api/kits/demand/releases/:period. Like every /api/kits path, they need
  an authenticated caller. There is no publish route: the producer calls the
  ledger server-side.
- 38 tests. Mutations: 19 of 20 killed. The survivor is equivalent: lstat
  reports a symlink as not a directory.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zYzsSSFUHbPV5DNxssX4A

This branch has not been deployed

No deployments
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