Repository navigation
roadmap event carrier: sequence the snapshot close and answer its standing; one history-order authority - #13232
Conversation
…lt, answer its standing, one history-order authority
Every snapshot-owning carrier operation (roadmap_event_append, roadmap_events_read,
roadmap_standings_read, roadmap_events_read_all, roadmap_bound_result_append) now removes its
private worktree inside a match on the operation's own result and answers
CarrierClosed<T> { result, close }: the result unchanged, and a typed CarrierSnapshotClose
(SnapshotNotOpened | SnapshotClosed | SnapshotCloseFailed { worktree, exit_code, stderr })
beside it. No caller drops it: reports state a leak after their text, the record entry
states it on a refusal, and the belt tick's exit fails naming each worktree left behind.
Callers with no surface match the standing by name in one function that says why.
roadmap_event_parent_admitted, roadmap_event_record_cli and the issue detail route no
longer compare a standing's key string; they ask roadmap_history_unordered_reason.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ere, and name the capability that would Review 75464: the serve comment claimed the belt tick states serve's leaks (it sees only its own snapshots), and the page deferral named a trigger its own function discards. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rktrees and names the ones that outlived a tick Review 75464 and the parent ruling: the callers with no surface for a failed snapshot removal (serve, the daily page, the launch-time binding commit, a successful record) were an uncounted degradation. Snapshots are now created under one path prefix, and the belt tick reads the repository's worktree registry for them, writes a census receipt beside its own, and marks a snapshot present on two consecutive ticks as a leak. The tick refuses only when the census cannot be read or its receipt cannot be written. Nothing is removed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…onValue arms The floor refused NonFoldResidueRosterDiverged on a wildcard arm over JsonValue in snapshot_census_prior_decode. The arms are now named, so no residue row is needed, and a receipt whose snapshots hold a non-string has its own control. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
RulingNO-LAND #13232 at The exact head is mergeable/CLEAN, and run The carrier-close and history-authority portions are sound. The new census does not yet establish what it claims, and its durable wire is not a total receipt. Approved: carrier close sequencing
The surface decisions are also coherent:
The use of Blocker 1: two consecutive sightings do not establish a leakThe census currently defines: and justifies that with “no carrier operation runs that long.” But that fact is not established anywhere in the model. Carrier commands use A valid execution is therefore: The acknowledged repository-wide population makes this easier to reach: one instance can take both censuses while another instance’s request remains active. The two observations may also be arbitrarily close if ticks are invoked manually. The code nevertheless writes the path into The inverse problem also remains: a no-surface caller can receive
It is not an accounting of the Required repairChoose one honest contract:
Also resolve the subject mismatch:
A control must keep a still-active snapshot out of the Blocker 2: the census wire is not a total receiptThe encoder stores several representations of the same facts: But the only decoder reads {
"schema": "roadmap-event-snapshot-census/v1",
"snapshot_count": 0,
"snapshots": ["/tmp/gunbc-roadmap-event-snapshot.a"],
"leaked_count": 99,
"leaked": [],
"prior": "absent"
}The test called a receipt round trip is therefore only a projection round trip for the There is also a direct absorbing fallback: An existing zero-length or torn receipt is not absence. This contradicts the stated policy that an unreadable prior is recorded as unreadable rather than silently becoming an empty history. fileciteturn664file0L2-L2 Required repairMake the durable artifact one authority. Preferably:
Required reds include:
Blocker 3: the tick’s authority text is now false
The implementation has changed, but the declared exit contract has not. fileciteturn660file0L2-L2 Update the entrypoint authority to name: and both new exit causes. This is the same class previously fixed when dogfood-start refusal was added to the tick tail. Rulings on the stated choicesSeparate receipt file: approved. A Literal Unreadable previous census does not fail the tick: approved. This is diagnostic continuity, not permission for a safety-sensitive action. The current receipt must say it was blind, and empty/malformed existing bytes must remain Known result-return close failure fails the tick: approved. The exact failure is available synchronously, the append result remains intact, and retry is idempotent. A detected aged/leaked path does not fail the tick: approved while the census is reporting only. It must not become removal permission without owner/quiescence evidence. Reapproval bar
The close sequencing and history-order consolidation should remain unchanged. |
…f a leak claim, a total receipt Side-chat ruling on #13232 (NO-LAND at 61e81c2). The close sequencing and the history-order authority are unchanged. - Two sightings do not establish a leak: carrier commands carry no deadline and the census observes no owner. The population is now present_across_censuses, stated as a diagnostic candidate and never grounds for removal. The no-surface callers' comments say a failed removal itself is recorded nowhere. - One subject: snapshots are created under a namespace derived from the layout's own worktree path, so an instance's census and receipt describe that instance's snapshots only. - The receipt is total and single-authority: no stored counts, every member decoded, and a receipt for another namespace, with a duplicate or out-of-namespace path, or whose present-across list is not derivable from its own populations is refused. An existing empty file is unreadable, not absent. - The belt entrypoint's authority text names the third persisted artifact and both new exit causes. - test/manual/snapshot_census_wet drives the real pass (previous read, registry list, write) six times on a throwaway repository and applies the tick join to the real passes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
RulingNO-LAND #13232 at The exact head is mergeable/CLEAN, and run The close sequencing and shared history-order authority remain approved. The reworked receipt and wet path also satisfy most of the prior bar. One structural blocker remains: the supposed per-instance namespace is neither injective nor prefix-disjoint. What is now closedThe census now honestly reports The committed wet driver exercises the actual effectful pass: across consecutive runs and applies the real tick-exit join to the resulting pass. I accept the absence of a whole live-dashboard tick run: The belt authority now names the third persisted artifact and both new failure causes. fileciteturn706file0L2-L2 Blocker: two different instances can still share—or overlap—one namespaceThe namespace is rendered as: and the census selects worktrees using The comment claims doubling underscore before replacing slash makes the encoding injective. It does not. The codewords are: Those are not uniquely decodable. Exact collisionThese two distinct instance roots produce distinct carrier worktrees: Both render to the same namespace suffix: A’s carrier snapshots and B’s carrier snapshots are therefore indistinguishable to either census. Each per-instance receipt can count the other instance’s worktrees. Prefix overlapThe problem also exists without an exact collision because ownership is tested with Namespace B begins with namespace A. A’s census therefore selects B’s snapshots as its own. The enrolled test checks only: That particular pair happens not to collide. It does not establish either injectivity or prefix separation. fileciteturn702file0L2-L2 This reopens the exact subject mismatch the revision was intended to close: It can also make one instance’s Required repairDerive a namespace that is both:
A fixed-width structural digest of the complete A genuinely prefix-free escaped encoding is also acceptable. The census should preferably validate the complete carrier-created path shape—namespace plus the expected Add discriminating controls for: and: Then feed snapshots from both pairs through the real porcelain decoder and require each census to select only its exact instance population. Reapproval barKeep the current The full live-dashboard tick-tail run remains a deployment receipt, not a merge blocker. |
Side-chat ruling on #13232 (NO-LAND at cc584b6; everything else approved and unchanged). The namespace folded layout.worktree's separators into one token, which was neither injective (/srv/a_/b and /srv/a/_b folded alike) nor prefix-free (/srv/a's namespace was a prefix of /srv/a/roadmap-events.foo's). It is now the stem plus a fixed-width structural digest of the complete worktree path. Ownership is the whole carrier-created shape -- the namespace followed by exactly mktemp's suffix -- in the census selection and in receipt validation, not a prefix test. Both pairs are controls through the real porcelain decoder. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Follow-up to #13075 (the side chat's carrier-wide cleanup note). Two things, one PR.
1. The snapshot close is sequenced, and its outcome is answered
gunbc.roadmap.roadmap_event_carrier's snapshot-owning operations closed their private worktree withlet cleanup = roadmap_event_snapshot_close(...)followed byresult. Nothing consumed the binding.Now, in
roadmap_event_append,roadmap_events_read,roadmap_standings_read,roadmap_events_read_allandroadmap_bound_result_append:CarrierClosed<T> { result, close }.resultis exactly what it was.closeis a newCarrierSnapshotClose:SnapshotNotOpened | SnapshotClosed | SnapshotCloseFailed { worktree, exit_code, stderr }.roadmap_bound_result_appendis changed too: #13075's match returnedresulton both arms, which sequenced the close but reported nothing.A cleanup failure never rewrites a result. Where it is stated (ruled by bold-bee-114: typed standing beside the result, the caller decides, never dropped with
_):gunbc.roadmap_belt_tick_clibelt_tick_tail_after_result_returnbelt_tick_exit_with_snapshot_closesfails the tick naming each worktree left behind by the result-return pass (its whole-log read and each bound append); a tick that already failed keeps its code and reason and gains the leak after it.roadmap_event_cliroadmap_event_standing_report,roadmap_result_returnedresult_returned_reportcarrier_report_with_snapshot_close).roadmap_event_cliroadmap_event_record_cli, on a refusalroadmap_event_record_exit).test.manual.issue_command_wetSites with no surface of their own, and the census that reports for them
Each consumes the standing in one named function with an exhaustive match and the reason in its leading comment:
roadmap_event_cliroadmap_event_record_exit, on success. The exit is the append's result andExitSuccesscarries no text; a nonzero exit would tell the caller a recorded event was not recorded, and a retry would append a second one.gunbc.roadmap_served_observationserved_standings_after_snapshot_close. The daily page renders standings or why they are unobserved.roadmap_attempt_request_bindingrequest_binding_history_after_snapshot_close. The launch-time commit answers committed or refused, and a leaked scratch worktree is not a reason to refuse a launch.gunbc.roadmap_serveserve_history_after_snapshot_close,serve_all_events_after_snapshot_close,serve_append_after_snapshot_close(issue detail, inline assignment context, assign, comment, comment readback). An HTTP body is the client's answer, and serve writes no operator log.A failed removal at these sites is recorded nowhere. What exists beside them is a census, added after review 75464 and reworked to the side-chat rulings on this PR (NO-LAND at
61e81c25, then atcc584b6eon the namespace alone). It isgunbc.roadmap.roadmap_event_snapshot_census:shell.Mktemp.DirWithTemplateat/tmp/gunbc-roadmap-event-snapshot.<digest>.<mktemp suffix>, where the digest is a fixed-width structural digest of the layout's complete worktree path (roadmap_event_snapshot_namespace). Every namespace has one length, so none is a prefix of another. Ownership is the whole path shape (roadmap_event_snapshot_path_owned: the namespace followed by exactly mktemp's suffix), in the census selection and in receipt validation. The digest is FNV-1a 64 (std.content_hash), so distinctness between instance paths is a hash property, not a proof.git worktree list --porcelain -zover the carrier's repository, decodes it with the existingextdeps.git.worktree_enumerationdecoder, keeps the worktrees in the instance's namespace, reads the previous receipt, and writesroadmap-event-snapshot-census.jsonin the instance's receipts directory.present_across_censuses). It does not claim any of them leaked: carrier commands carry no deadline and the census observes no owner, so a snapshot seen twice may be an operation still running. The module says that list is a diagnostic candidate and never grounds for removal.SnapshotCloseFailedwhose worktree is removed or pruned before the next census appears nowhere.schema,observed_at,namespace,snapshots,present_across_censuses,prior(the previous population as read, or absent, or unreadable with its reason). The decoder reads every member and refuses a receipt for another namespace, one with a duplicate or out-of-namespace path, and one whose present-across list is not exactly derivable from its own two populations. An existing empty file is unreadable; absent is only a file that is not there.Still true and worth knowing:
/tmp, wheremktemp -dhonouredTMPDIR.v2;v1(which carried counts) was only ever on this branch and is refused as unknown.2. One history-order authority
roadmap_event_parent_admittedandroadmap_event_record_clidecided "this history cannot be ordered" by comparingroadmap_node_standing_key(...)to"forked"/"history_incomplete". Both now askgunbc.roadmap.roadmap_event_logroadmap_history_unordered_reason, the exhaustive match onRoadmapNodeStandingfrom #13075.A third site made the same string comparison and was not in the brief: the issue detail route in
gunbc.roadmap_serve. It is changed the same way. No string-key ordering decision remains;roadmap_node_standing_keyis still used for CSS classes and in witnesses, which is what it is for.The record entry's refusal text changed from
…a history that is <key>: <text>to…a history that cannot be ordered: <text>.Evidence
Run locally with
gunbc run(scratch driver ANDing the claims, not committed).Claims, all passing, with the three existing carrier claims and the two existing result-return claims that touch the changed pass:
roadmap_event_carrier_witness_testa_failed_snapshot_removal_is_its_own_standing_and_names_the_worktreea_report_keeps_its_answer_and_states_a_leaked_snapshot_after_itthe_record_exit_keeps_a_success_and_states_a_leak_on_a_refusalan_append_is_refused_on_a_history_the_order_authority_cannot_orderroadmap_result_returned_witness_testa_snapshot_left_behind_by_the_result_return_fails_the_tick_beside_its_own_exitroadmap_event_snapshot_census_witness_testthe_census_of_an_instance_is_exactly_its_own_snapshots_in_gits_list(through the real porcelain decoder: two instances on one repository; the pairs/srv/a_/bvs/srv/a/_band/srv/avs/srv/a/roadmap-events.foo, each census holding only its own; worktrees at the namespace plus a longer, shorter or non-mktemp name not counted)a_snapshot_seen_at_two_censuses_is_present_across_them_and_not_called_a_leak(the ruling's control: the wire has no leak member)the_census_receipt_is_total_and_a_self_contradicting_one_is_refused(full round trip; reds for an existing empty file, a present-across path not in the current population, a present-across list missing a path, a duplicate, an out-of-namespace path in either population, another instance's namespace, an undecodable prior, a non-string path, the v1 schema, non-JSON)the_tick_refuses_on_an_unreadable_census_and_not_on_a_snapshot_present_across_censusesMutations: five planted defects in the close/authority code and five mutation runs over the reworked census (no namespace filter, a clean exit over an unreadable census, an empty file read as absent, the present-across check skipped, every snapshot called present-across, the duplicate check skipped, the namespace check skipped). Each turned the corresponding claims red and no unrelated ones. Restoring the folded namespace with prefix-only ownership turns the scope claim red.
Wet control, committed and run (again on the digest namespace):
test/manual/snapshot_census_weton a throwaway repository with real git. It runsroadmap_snapshot_census_at— the whole pass: previous receipt read from disk, registry listed, receipt written — six times, each reading the receipt the last one wrote, with snapshots created the carrier's way for two layouts sharing the repository. Observed, equal to expected:What is not shown by execution:
roadmap_snapshot_census_for_instanceisroadmap_snapshot_census_atwith the layout and path taken from the instance and nothing else; the wet control runs the latter and appliesbelt_tick_exit_with_snapshot_censusto its real results.belt_tick_tail_after_result_returnitself needs a live dashboard instance and was not run.gunbc run, bindings execute eagerly in order, including an unused one (probed with twoFilesystem.Writes). So the previouslet cleanup = …did run in that realization, and I have no red for it. The sequencing change is by construction.test/manual/issue_command_wetdrives the real append and read and now fails on an unremoved snapshot, but I did not run it.an_append_is_refused_on_a_history_the_order_authority_cannot_order, the incomplete-history case is the discriminating one; a forked history is refused by the generation check on its own.Not changed
roadmap_event_private_snapshotcan still leave themktempdirectory behind whengit worktree addrefuses. That is a different shape (no worktree was registered) and is not addressed here.🤖 Generated with Claude Code