feat(verdict): write-expiry groundwork — expired taxonomy, contract docs, upstream ask#13
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
SealedBatchSubmissionenvelope 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 onlyexpiredthe 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 onBatchFate::Rejected, not a closed enum. This PR lands everything that is safe and useful ahead of enforcement:transaction_conflictandpermissions_head_missingare 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_submissionstays 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.expiredremains registered permanent and inert until a store emits it.expiresAt + 2 × tolerancesoundness 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.docs/upstream-write-expiry.mdis 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_atcovered by a v2 digest manifest, the rejection code needing no wire change,captured_frontiercompatibility-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:
tests/write_verdict_test.tsforexpired(late write rejected, batch-id correlation, determinism across reconnects) — impossible until something can reject a late write.deno task checkclean;deno task test64/64;deno task publish:drysucceeds.