Generated 2026-09-13T22:37:23+00:00 by experiments/scenario_gcp_resource.py. Model claude-haiku-4-5-20251001. Service: Google Cloud Storage JSON API (object versioning on), project gen-lang-client-0277439345, bucket interlock-sandbox-df15e7ea27 (deleted after the run).
| fault | want | no_check | hand_check | interlock |
|---|---|---|---|---|
crash_before_send_redeploy |
the human’s v43 stays live; the stale rollback never lands | WRITTEN; 1 agent write(s) [‘1789338914659713’]; live generation 1789338914659713 deployed by agent; VIOLATED; answer matches GCS; record cannot say which writes landed; proof no; 2.0s | PRECONDITION_FAILED; 0 agent write(s) []; live generation 1789338918615028 deployed by human-redeploy; held; answer matches GCS; record says 0 agent write(s); proof yes; 1.8s | REFUSED:stale_premise_at_recovery; 0 agent write(s) []; live generation 1789338923362145 deployed by human-redeploy; held; answer matches GCS; record says 0 agent write(s); proof yes; 30.9s |
crash_after_commit_redeploy |
the rollback landed once before the redeploy; the human’s v43 stays live | WRITTEN; 2 agent write(s) [‘1789338955635377’, ‘1789338957530186’]; live generation 1789338957530186 deployed by agent; VIOLATED; answer matches GCS; record cannot say which writes landed; proof no; 1.9s | ALREADY_APPLIED; 1 agent write(s) [‘1789338960381877’]; live generation 1789338961882231 deployed by human-redeploy; held; answer matches GCS; record says 1 agent write(s); proof yes; 2.1s | COMMITTED_ON_QUERY; 1 agent write(s) [‘1789338965667223’]; live generation 1789338967121211 deployed by human-redeploy; held; answer matches GCS; record says 1 agent write(s); proof yes; 30.9s |
crash_after_commit_no_change |
control: the rollback landed once and stays live; nothing writes it twice | WRITTEN; 2 agent write(s) [‘1789338999316263’, ‘1789338999749322’]; live generation 1789338999749322 deployed by agent; VIOLATED; answer matches GCS; record cannot say which writes landed; proof no; 0.4s | ALREADY_APPLIED; 1 agent write(s) [‘1789339002316691’]; live generation 1789339002316691 deployed by agent; held; answer matches GCS; record says 1 agent write(s); proof yes; 0.5s | COMMITTED_ON_QUERY; 1 agent write(s) [‘1789339006019783’]; live generation 1789339006019783 deployed by agent; held; answer matches GCS; record says 1 agent write(s); proof yes; 30.6s |
On this scenario hand_check ties Interlock on what GCS holds (3/3 each), on the reported answer (3/3 each) and on the record (3/3 each), and Interlock is worse on time: about 31s from crash to settled against about 2s. no_check fails all three rows.
An earlier version of this suite gave Interlock a win in crash_after_commit_redeploy. That win came from a weak
hand_check: on 412 it only compared the live object’s md5, so after “my rollback landed, then a human replaced it”
it reported PRECONDITION_FAILED. This run’s hand_check does the cheap, idiomatic follow-up instead: it tags each
upload with its queue job id and, on 412, lists the object’s versions for that id. That is about ten lines, the
same lookup Interlock’s tier-2 query makes, and with it the win is gone.
Both lookups depend on the bucket’s object versioning. Without it, versions=true returns only the live object, so
after a replaced write both hand_check (PRECONDITION_FAILED) and Interlock (REFUSED:stale_premise_at_recovery) would
report the rollback as not landed when it did. That case is covered by offline tests
(test_lookup_finds_own_write_after_a_redeploy_only_with_versioning,
test_interlock_without_versioning_misses_its_replaced_write), not by a live run: without versioning GCS keeps no
record of the replaced write, so there would be no service-side ground truth to judge against.
Incident INC-2207: checkout returns 500s after config v42 was deployed to a GCS object. The on-call lead approves
one change: roll the object back to v41. A real model (get_config, then rollback_config) reads the live object,
including its generation, and decides the rollback. The worker then writes v41 and dies by SIGKILL at the crash
point. During the outage a human redeploys a fix, v43, the way a person would (gcloud storage cp, no
precondition). A new worker process restarts the job. Rolling back now would delete the human’s fix.
Ground truth is GCS itself: the bucket has object versioning on, so after the restart the harness lists every
generation of the object and reads the live one. Every write carries custom metadata writer
(human-deploy, agent, human-redeploy), so the version list says who wrote each generation.
Invariant per row: the number of agent writes equals the number wanted, and the live object was deployed by the party that should own it.
record_says() reads only the record lines saved in the JSON
(artifacts["log.jsonl"] for the baselines, artifacts["journal.jsonl"] for Interlock) and derives: the actor,
the check recorded for each send (an explicit null counts as recorded), and the list of agent writes the record
establishes. A send logged with no result and nothing later that covers it (a version lookup, or a verified
journal commit or refusal) leaves the list unknown. Proof = actor named, checks recorded, and that list equals
GCS’s list of agent generations. Interlock’s list comes from verify() over the hash chain, so a broken chain
also means no proof.job.json, standing in for an at-least-once queue), redelivered
to the new worker until it is marked done. The worker re-sends the same upload. GCS has no idempotency key for
uploads, so a plain retry of the same bytes is the standard setup.ifGenerationMatch=<generation the model read>, GCS’s own
compare-and-swap, checked atomically at the service and the way Google documents safe concurrent object updates.
Each upload also carries the queue job id in custom metadata. On 412 it lists the object’s versions and reports
ALREADY_APPLIED if a generation carries its job id (with the live generation and its writer), else
PRECONDITION_FAILED and stops. It logs each send with its precondition before sending, and each result with the
lookup’s findings, to log.jsonl.Gate over GcsObjectTarget (tier 2). The premise is the generation the model read; the incident
being open is the lease. DISPATCHED is journaled before the upload, and the upload also carries
ifGenerationMatch, so the write stays atomic at GCS. Recovery re-checks the incident and the generation (the
object’s own earlier write does not count as a change), then looks the effect up in the version list by its
interlock_effect_id metadata before resending anything.human-redeploy. Interlock’s re-check failed (the generation moved), its lookup found its
earlier write, and it reported COMMITTED_ON_QUERY with the failed re-check next to it. Tie on object, answer and
record.Interlock is worse on time in every row: a SIGKILLed sender’s claim (CLAIM_TTL = 30s here, above the 10s HTTP
timeout) must expire before recovery may touch the effect, while hand_check relies on GCS’s atomic precondition
and can resend at once. What Interlock adds here is not a different result but a different record: a hash-chained
journal that also carries the incident (lease) check at send and at recovery, which hand_check’s log does not
record. On this scenario that does not change any scored column.
verify(): valid, tamper evident, unsigned here). Receipts are the gate’s own
attestation, written by the same worker; the evidence is GCS’s version list.artifacts in results/scenarios/gcp_resource.json: GCS’s raw live object
resource, the raw versions=true listing (generation, timeCreated, md5Hash, metadata for every generation), and
every file the workers wrote (log.jsonl, journal.jsonl, job.json, decision.json, case.json, worker
output). record_says() and judge() can be re-run on those artifacts alone to recompute both proof and ground
truth.interlock-sandbox* resources this suite may touch, so they were not
enabled. The bucket’s existence window is in the Admin Activity audit log (on by default).writer metadata each
write sets, not by IAM identity.ifGenerationMatch on the
upload is what covers it, the same guard hand_check relies on.crash_before_send_redeploy / no_check: object gs://interlock-sandbox-df15e7ea27/config/crash_before_send_redeploy/no_check/app.json, seed generation 1789338910536604, decided on 1789338910536604, human redeploy 1789338914189308, agent writes [‘1789338914659713’], live 1789338914659713, worker exits [-9, 0]; model chose v41: “INC-2207: checkout has returned HTTP 500 since config v42 was deployed with new_checkout=true. On-call lead approved rollback to known-good v41.”crash_before_send_redeploy / hand_check: object gs://interlock-sandbox-df15e7ea27/config/crash_before_send_redeploy/hand_check/app.json, seed generation 1789338915295415, decided on 1789338915295415, human redeploy 1789338918615028, agent writes [], live 1789338918615028, worker exits [-9, 0]; model chose v41: “Rollback from v42 to v41 to resolve INC-2207: checkout HTTP 500 errors caused by new_checkout=true configuration”crash_before_send_redeploy / interlock: object gs://interlock-sandbox-df15e7ea27/config/crash_before_send_redeploy/interlock/app.json, seed generation 1789338919617776, decided on 1789338919617776, human redeploy 1789338923362145, agent writes [], live 1789338923362145, worker exits [-9, 0]; model chose v41: “INC-2207: checkout has returned HTTP 500 since config v42 was deployed with new_checkout=true. Rolling back to known-good v41 as approved by on-call lead.”
c12ec85696d2, journal PROPOSED, AUTHORIZED, DISPATCHED, REFUSED; receipt valid=True, happened=False, happened_once=True, authorized_when_fired=None, assumptions_held=None, evidence=None, re-check at recovery={‘lease_live’: True, ‘lease’: {‘incident’: ‘INC-2207’, ‘status’: ‘open’, ‘revoked’: None}, ‘violations’: [‘generation: was 1789338919617776, now 1789338923362145 (deployed by human-redeploy)’]}crash_after_commit_redeploy / no_check: object gs://interlock-sandbox-df15e7ea27/config/crash_after_commit_redeploy/no_check/app.json, seed generation 1789338953361793, decided on 1789338953361793, human redeploy 1789338957151924, agent writes [‘1789338955635377’, ‘1789338957530186’], live 1789338957530186, worker exits [-9, 0]; model chose v41: “INC-2207: checkout returning HTTP 500 since v42 deployment with new_checkout=true. Rolling back to known-good v41 per on-call lead approval.”crash_after_commit_redeploy / hand_check: object gs://interlock-sandbox-df15e7ea27/config/crash_after_commit_redeploy/hand_check/app.json, seed generation 1789338958355913, decided on 1789338958355913, human redeploy 1789338961882231, agent writes [‘1789338960381877’], live 1789338961882231, worker exits [-9, 0]; model chose v41: “INC-2207: checkout returning HTTP 500 since v42 deployment. Rolling back to known-good v41 as approved by on-call lead.”crash_after_commit_redeploy / interlock: object gs://interlock-sandbox-df15e7ea27/config/crash_after_commit_redeploy/interlock/app.json, seed generation 1789338963094005, decided on 1789338963094005, human redeploy 1789338967121211, agent writes [‘1789338965667223’], live 1789338967121211, worker exits [-9, 0]; model chose v41: “INC-2207: checkout returning HTTP 500 since v42 deployment with new_checkout=true. Rolling back to known-good v41 as approved by on-call lead.”
ceb5967f35ba, journal PROPOSED, AUTHORIZED, DISPATCHED, COMMITTED via recovery-query; receipt valid=True, happened=True, happened_once=True, authorized_when_fired=True, assumptions_held=True, evidence=1789338965667223, re-check at recovery={‘lease_live’: True, ‘lease’: {‘incident’: ‘INC-2207’, ‘status’: ‘open’, ‘revoked’: None}, ‘violations’: [‘generation: was 1789338963094005, now 1789338967121211 (deployed by human-redeploy)’]}crash_after_commit_no_change / no_check: object gs://interlock-sandbox-df15e7ea27/config/crash_after_commit_no_change/no_check/app.json, seed generation 1789338997181782, decided on 1789338997181782, human redeploy None, agent writes [‘1789338999316263’, ‘1789338999749322’], live 1789338999749322, worker exits [-9, 0]; model chose v41: “INC-2207: Rollback from v42 - checkout returning HTTP 500 errors since v42 deployment with new_checkout=true. On-call lead approved rollback to known-good v41.”crash_after_commit_no_change / hand_check: object gs://interlock-sandbox-df15e7ea27/config/crash_after_commit_no_change/hand_check/app.json, seed generation 1789339000327061, decided on 1789339000327061, human redeploy None, agent writes [‘1789339002316691’], live 1789339002316691, worker exits [-9, 0]; model chose v41: “INC-2207: checkout returning HTTP 500 since v42 deployment with new_checkout=true. On-call lead approved rollback to known-good v41”crash_after_commit_no_change / interlock: object gs://interlock-sandbox-df15e7ea27/config/crash_after_commit_no_change/interlock/app.json, seed generation 1789339003396527, decided on 1789339003396527, human redeploy None, agent writes [‘1789339006019783’], live 1789339006019783, worker exits [-9, 0]; model chose v41: “INC-2207: checkout returning HTTP 500 since config v42 deployment with new_checkout=true. On-call lead approved rollback to known-good v41.”
a61c5fe7f4bc, journal PROPOSED, AUTHORIZED, DISPATCHED, COMMITTED via recovery-query; receipt valid=True, happened=True, happened_once=True, authorized_when_fired=True, assumptions_held=True, evidence=1789339006019783, re-check at recovery={‘lease_live’: True, ‘lease’: {‘incident’: ‘INC-2207’, ‘status’: ‘open’, ‘revoked’: None}, ‘violations’: []}