Skip to content

feat(verdict): write-expiry groundwork — expired taxonomy, contract docs, upstream ask#13

Merged
FelineStateMachine merged 6 commits into
mainfrom
feat/write-expiry-groundwork
Jul 19, 2026
Merged

feat(verdict): write-expiry groundwork — expired taxonomy, contract docs, upstream ask#13
FelineStateMachine merged 6 commits into
mainfrom
feat/write-expiry-groundwork

Conversation

@FelineStateMachine

@FelineStateMachine FelineStateMachine commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Part of #12. Closes #14.

Server-enforced write expiry needs a surface that does not exist at the pinned alpha — verified from source: at the jazz alpha.53 publish commit, the SealedBatchSubmission envelope is {batch_id, mode, target_branch_name, batch_digest, members, captured_frontier} with no expiry or deadline field anywhere in the submission path, and the only expired the codebase knows is the transport's unauthenticated-response code for rejected JWTs. The batch-fate code space at the pin is four codes — permission_denied, transaction_conflict, permissions_head_missing, invalid_batch_submission — an open string on BatchFate::Rejected, not a closed enum. This PR lands everything that is safe and useful ahead of enforcement:

  • Taxonomy: the registry now classifies the full observed code space. transaction_conflict and permissions_head_missing are registered permanent, each pinned by a harness test that provokes it end to end (tests/write_conflict_test.ts): a losing concurrent transaction is final, deterministic across reconnects, batch-correlated, and never rebased; a store deployed without a permissions head fails closed on both verdict surfaces, and publishing permissions later does not revisit the fate. invalid_batch_submission stays unregistered (transient by default): every trigger is an envelope invariant the client runtime computes itself, and the one author-reachable shape — committing an empty transaction — throws synchronously before any batch exists, so no rejection carrying it can be observed from the public API; the registry doc comment records this. expired remains registered permanent and inert until a store emits it.
  • Docs: the write-verdicts page carries the four-code taxonomy table, records the re-derivability boundary at the pin (a transactional batch committed offline is never re-adjudicated; waiting on it fails with a runtime error, not a verdict), and states the expiry contract — client-asserted deadline declared outside the payload, arrival-time enforcement with a two-minute skew tolerance, honest-client threat model, and the expiresAt + 2 × tolerance soundness rule — mechanism-neutral: it binds whichever component enforces it. The page states plainly that nothing enforces it at the pinned alpha and that the enforcement design is in progress at the node layer.
  • Design record: docs/upstream-write-expiry.md is the reference spec for the vendor surface lofi would prefer if jazz offered it (recorded as upstream(jazz): expiresAt on the sync envelope with an engine-enforced expired verdict lofi#145; upstream wishes are labeled reference issues, nothing is filed). Its wire sketch is source-accurate: expires_at covered by a v2 digest manifest, the rejection code needing no wire change, captured_frontier compatibility-only.

Evidence grading. Every load-bearing claim here is source-verified (jazz repo at the alpha.53 publish commit, cross-checked against the pinned binaries by hash) or behaviorally observed in the harness; nothing rests on binary string inference. Where the two prior audits of the code space disagreed (one code vs three), both were artifacts of their method — a JS-artifact grep and an under-specified strings pattern; source enumeration settles it at four.

Checklist items of #12 that remain blocked on enforcement and keep the issue open:

deno task check clean; deno task test 64/64; deno task publish:dry succeeds.

A write refused because it arrived past its declared deadline is final:
waiting or re-sending only arrives later still. The pinned alpha never
emits the code, so the entry is inert until a store does.
Cleartext client-asserted deadline, arrival-time enforcement with a
two-minute skew tolerance, the honest-client threat model, and the
soundness rule that makes local compensation safe — stated as the
contract for when enforcement exists, with today's behavior (deadlines
ignored) called out plainly.
Optional expiresAt on the batch cleartext envelope, engine-side
arrival-time comparison at the sync node, and an expired rejection
through the existing batch-settlement path with the three properties
permission_denied already satisfies.
…sing as permanent

The pinned alpha's store emits four rejection codes through the batch-
settlement path: permission_denied, transaction_conflict,
permissions_head_missing, and invalid_batch_submission. The two newly
registered codes are pinned by harness tests that provoke them end to end:
a losing concurrent transaction (final, deterministic, never rebased) and a
write against a store whose schema was deployed without a permissions head
(both verdict surfaces, fate unaffected by a later permissions publish).
invalid_batch_submission stays unregistered: every trigger is an envelope
invariant the client runtime computes itself, so no rejection carrying it
can be observed from the public API; the registry documents why.

Refs #14
…ontract

The taxonomy table now lists the store's full rejection-code space with per-
code semantics, records the transactional-offline boundary on the
re-derivability property, and states the expiry contract independent of the
enforcing component. The deadline's placement is motivated by what
enforcement may read (the declared deadline and an arrival clock, never
application payloads), matching lofi's trusted-server threat model.
…#145

The document records the vendor surface lofi would prefer if jazz offered
it; upstream wishes are labeled reference issues, and node-layer enforcement
is designed separately against the same contract. The wire-level sketch now
states the envelope fields and digest coverage precisely: no expiry field
exists in the submission path today, the batch digest covers only the member
manifest (so covering expires_at needs a v2 manifest), captured_frontier is
compatibility-only, and the rejection code is an open string.
@FelineStateMachine
FelineStateMachine merged commit 622044f into main Jul 19, 2026
2 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.

feat(verdict): classify transaction_conflict and invalid_batch_submission

1 participant