Found while counting publication helpers for #508 (fsatomic). Pre-existing on main before that PR and unchanged by it — filed rather than fixed inside a refactor.
What is verified
catalog_transaction::collect_bundle_files decides whether an agent-directory child is state plane (skip) or declaration plane (hash into the bundle) with an exact-name list plus exactly one prefix:
let state = matches!(
name_text,
"resources" | "archive" | "inbox" | "status"
| "harness-context" | "harness-state"
| ".harness-state.seq" | ".harness-state.lock" | ".harness-context.lock"
) || name_text.starts_with(crate::status::TMP_STAGING_PREFIX);
(src/catalog_transaction.rs:2017-2028, applied only at relative_to_bundle.components().count() == 1.) Anything not matched falls through to add_regular — i.e. into the declaration bundle whose hash is rootSha256 — or, for a Prepared source, to the "prepared catalog contains state-plane path" bail.
is_writer_temporary does not cover these: its WRITER_TEMP_PREFIXES are the three declaration-plane writer temps (.agent.kdl.presentation-, .agent.kdl.publish-, .catalog-apply-file-, src/catalog_transaction.rs:31-35).
So .status.tmp-<pid>-<counter> is deliberately special-cased, and these depth-1 names are not:
.harness-state.tmp-<pid>-<counter> — the harness-state record stages beside itself in the agent directory (src/harness_state.rs, write_json_atomic(path, record, &dir, ".harness-state")). Every live seat rewrites that record on transitions plus a refresh cadence, so this window is real rather than theoretical.
driver-diagnostic and its .driver-diagnostic.tmp-<pid>-<counter> staging — driver_diagnostic::path is agent_dir.join("driver-diagnostic") (src/driver_diagnostic.rs:399-401), depth 1, and the record itself is absent from the list, not just its staging file.
Not affected, for the record: .message.tmp- (inside inbox/archive/resources, all skipped wholesale), .ctx.tmp- (context_dir is <agent>/resources/context), .harness-context.tmp- (stages in the catalog control plane, outside agents, by HC-R05).
The intent is documented by tests/catalog_apply.rs::harness_runtime_records_never_change_the_declaration_snapshot, which asserts rootSha256 is unchanged for exactly the five names in the list — so the list is the contract, and the two above are outside it.
What needs the owner's read
The consequence per case, which is why this is a report and not a patch:
- For
Current sources: does a staged .harness-state.tmp-… or a published driver-diagnostic actually change rootSha256, making the declaration digest depend on runtime timing? That is what the code path suggests, and it would make catalog digest/catalog snapshot nondeterministic under load.
- For
Prepared sources: the same names would bail! instead.
- Is
driver-diagnostic's absence deliberate (a record intended to travel with the declaration) or an oversight from when the record was added?
A fix has an obvious shape — have the state-plane test enumerate every record and staging prefix the state plane publishes, and have the walker consult the writers' own prefix constants (status::TMP_STAGING_PREFIX is now such a constant after #508, and message has the same shape) rather than a hand-maintained literal list. But which names belong in the state plane is a decision for whoever owns the declaration/state split, not a refactor's judgement call.
Posted on behalf of @schickling
| field |
value |
agent_identity |
dev3.direct.omp.43sz6ujq |
session |
dev3.43sz6ujq |
agent_persona |
generalist |
agent_supervisor |
unavailable |
agent_tool |
OMP |
agent_tool_version |
18.1.7 |
agent_runtime |
OMP 18.1.7 |
tooling_profile |
dotfiles@39a19af |
Found while counting publication helpers for #508 (
fsatomic). Pre-existing onmainbefore that PR and unchanged by it — filed rather than fixed inside a refactor.What is verified
catalog_transaction::collect_bundle_filesdecides whether an agent-directory child is state plane (skip) or declaration plane (hash into the bundle) with an exact-name list plus exactly one prefix:(
src/catalog_transaction.rs:2017-2028, applied only atrelative_to_bundle.components().count() == 1.) Anything not matched falls through toadd_regular— i.e. into the declaration bundle whose hash isrootSha256— or, for aPreparedsource, to the"prepared catalog contains state-plane path"bail.is_writer_temporarydoes not cover these: itsWRITER_TEMP_PREFIXESare the three declaration-plane writer temps (.agent.kdl.presentation-,.agent.kdl.publish-,.catalog-apply-file-,src/catalog_transaction.rs:31-35).So
.status.tmp-<pid>-<counter>is deliberately special-cased, and these depth-1 names are not:.harness-state.tmp-<pid>-<counter>— theharness-staterecord stages beside itself in the agent directory (src/harness_state.rs,write_json_atomic(path, record, &dir, ".harness-state")). Every live seat rewrites that record on transitions plus a refresh cadence, so this window is real rather than theoretical.driver-diagnosticand its.driver-diagnostic.tmp-<pid>-<counter>staging —driver_diagnostic::pathisagent_dir.join("driver-diagnostic")(src/driver_diagnostic.rs:399-401), depth 1, and the record itself is absent from the list, not just its staging file.Not affected, for the record:
.message.tmp-(insideinbox/archive/resources, all skipped wholesale),.ctx.tmp-(context_diris<agent>/resources/context),.harness-context.tmp-(stages in the catalog control plane, outsideagents, by HC-R05).The intent is documented by
tests/catalog_apply.rs::harness_runtime_records_never_change_the_declaration_snapshot, which assertsrootSha256is unchanged for exactly the five names in the list — so the list is the contract, and the two above are outside it.What needs the owner's read
The consequence per case, which is why this is a report and not a patch:
Currentsources: does a staged.harness-state.tmp-…or a publisheddriver-diagnosticactually changerootSha256, making the declaration digest depend on runtime timing? That is what the code path suggests, and it would makecatalog digest/catalog snapshotnondeterministic under load.Preparedsources: the same names wouldbail!instead.driver-diagnostic's absence deliberate (a record intended to travel with the declaration) or an oversight from when the record was added?A fix has an obvious shape — have the state-plane test enumerate every record and staging prefix the state plane publishes, and have the walker consult the writers' own prefix constants (
status::TMP_STAGING_PREFIXis now such a constant after #508, andmessagehas the same shape) rather than a hand-maintained literal list. But which names belong in the state plane is a decision for whoever owns the declaration/state split, not a refactor's judgement call.Posted on behalf of @schickling
agent_identitysessionagent_personaagent_supervisoragent_toolagent_tool_versionagent_runtimetooling_profile