The per-machine lock's generator field is never refreshed — every apply receipt on this fleet names the wrong binary
Trying to answer "which forjar version produced this apply receipt?" retroactively, I found the only version field in a machine lock is the header's generator, and it is preserved from whenever the file was first created. Measured across paiml/infra's state dir, 2026-09-16:
| lock |
generator: says |
generated_at: |
binary that ACTUALLY ran that apply (event log) |
| intel |
forjar 0.1.0 |
2026-09-16T02:11:15Z |
1.30.0 (r-ab35e7e5f928) |
| gx10 |
forjar 1.13.1 |
2026-09-16T02:12:41Z |
1.30.0 (r-ab49ff0c7517) |
| yoga |
forjar 1.1.1 |
2026-09-15T09:19:01Z |
1.30.0 (r-73f8cc5cbc7d) |
| lambda-labs |
forjar 1.4.0 |
2026-09-14T16:47:03Z |
1.30.0 (r-3da7029e1237) |
| mini |
forjar 1.6.1 |
2026-09-14T17:16:17Z |
1.30.0 (r-3f0d731dbfb7) |
Not one is right, and the errors span 0.1.0 to 1.13.1 — they look like the version each lock was first created at, years of releases ago.
Root cause. finalize_machine (src/core/executor/machine_b.rs:37) refreshes the timestamp and saves:
lock.generated_at = eventlog::now_iso8601();
if cfg.config.policy.lock_file {
state::save_lock(cfg.state_dir, lock)?;
}
It never assigns lock.generator. The only non-test writer is src/core/state/stamp/mod.rs:340 (lock.generator.clone_from(&generator)), which is the GlobalLock stamp path, not the per-machine lock. So a machine lock is deserialized, its generated_at moved forward, and its original generator written straight back out.
Why it went unnoticed. src/cli/lock_audit.rs:98 is the check that looks at this field, and it asserts only lock.generator.starts_with("forjar"). forjar 0.1.0 passes. The audit for this field cannot fail on the thing that is wrong with it — a green that has no red.
Consequence. Apply provenance is unverifiable from the lock. forjar history --json does carry a correct per-event forjar_version, so the information exists; it just never reaches the receipt anyone reads. A fleet trying to audit "which applies ran under the old exit-code semantics, before the 1.31.0 pin" has to reconstruct it from the event log, and any lock older than its event-log retention cannot be stamped at all.
Suggested fix. Assign lock.generator = format!("forjar {}", env!("CARGO_PKG_VERSION")) in finalize_machine alongside generated_at, and tighten lock_audit to compare against the running version — reporting a mismatch as information (a lock written by an older forjar is normal and worth knowing), not as a failure.
Falsifier for the fix: take a lock whose generator is forjar 0.1.0, apply with the current binary, assert the header now names the current version. That test is red against today's tree.
Found while auditing paiml/infra's 1.31.0 pin. Related: #579 (apply/plan should refuse when the running binary does not match the manifest pin) — same theme, that forjar does not currently tie an apply to the binary that performed it.
The per-machine lock's
generatorfield is never refreshed — every apply receipt on this fleet names the wrong binaryTrying to answer "which forjar version produced this apply receipt?" retroactively, I found the only version field in a machine lock is the header's
generator, and it is preserved from whenever the file was first created. Measured across paiml/infra's state dir, 2026-09-16:generator:saysgenerated_at:r-ab35e7e5f928)r-ab49ff0c7517)r-73f8cc5cbc7d)r-3da7029e1237)r-3f0d731dbfb7)Not one is right, and the errors span 0.1.0 to 1.13.1 — they look like the version each lock was first created at, years of releases ago.
Root cause.
finalize_machine(src/core/executor/machine_b.rs:37) refreshes the timestamp and saves:It never assigns
lock.generator. The only non-test writer issrc/core/state/stamp/mod.rs:340(lock.generator.clone_from(&generator)), which is the GlobalLock stamp path, not the per-machine lock. So a machine lock is deserialized, itsgenerated_atmoved forward, and its originalgeneratorwritten straight back out.Why it went unnoticed.
src/cli/lock_audit.rs:98is the check that looks at this field, and it asserts onlylock.generator.starts_with("forjar").forjar 0.1.0passes. The audit for this field cannot fail on the thing that is wrong with it — a green that has no red.Consequence. Apply provenance is unverifiable from the lock.
forjar history --jsondoes carry a correct per-eventforjar_version, so the information exists; it just never reaches the receipt anyone reads. A fleet trying to audit "which applies ran under the old exit-code semantics, before the 1.31.0 pin" has to reconstruct it from the event log, and any lock older than its event-log retention cannot be stamped at all.Suggested fix. Assign
lock.generator = format!("forjar {}", env!("CARGO_PKG_VERSION"))infinalize_machinealongsidegenerated_at, and tightenlock_auditto compare against the running version — reporting a mismatch as information (a lock written by an older forjar is normal and worth knowing), not as a failure.Falsifier for the fix: take a lock whose
generatorisforjar 0.1.0, apply with the current binary, assert the header now names the current version. That test is red against today's tree.Found while auditing paiml/infra's 1.31.0 pin. Related: #579 (apply/plan should refuse when the running binary does not match the manifest pin) — same theme, that forjar does not currently tie an apply to the binary that performed it.