From 0ee21bfbc87582bb21032f167c75baaab525860b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 12:04:35 -0500 Subject: [PATCH] docs(readme): census the three concurrency-scenario shapes honestly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three corrections to the methodology paragraph, all of the same kind: it stated as fact things that hold for some scenarios and not others. "Run the real scripts as real concurrent processes" is true of seed scenario 7 and half-true of 8a; 8b, prune 12 and publish 6-8 spawn nothing. A reader who stopped at that topic sentence would take away "spawn real concurrent processes", which is the instinct that produced #3. The paragraph now leads with the three shapes actually in use and says which scenarios take each: a genuine race whose invariant holds under any interleaving; a PATH stub that places the interference inside the window; and a synthetic stand-in for the other side where the artefact is itself the contract. That third shape — publish-snapshot-selftest.sh's held .reading-* marker — was unmentioned, so the README implied that suite stubs something it does not. Its own defence (the marker IS the contract between the two sides, and racing a real slow consumer would make the suite's runtime the thing under test) is a good reason and now sits beside the other two. "Where more than one guard could catch a fault, the scenario asserts which one did" was written as description when it is a target: seed scenario 9 does not, which is exactly why #5's mutation survives it. Stated as the rule plus its one live exception — a rule asserted as fact with a known counterexample is the same defect as the sentence this paragraph replaced. --- README.md | 49 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 132b226..45d826b 100644 --- a/README.md +++ b/README.md @@ -370,26 +370,43 @@ Every suite runs the actual script, not a reimplementation of its logic, and every fix scenario is paired with a control that reproduces the bug — a scenario that passes either way proves nothing. -The concurrency scenarios run the real scripts as real concurrent processes, -but they never race for the interleaving. The interfering step is placed -inside the window by stubbing, on `PATH`, a command the code under test calls -at a known point: `prune-cache-selftest.sh` scenario 12 stubs `du`, so the -pass's own measurement publishes a reader marker strictly between its check -and its unlink; `seed-target-dir-selftest.sh` scenarios 8a and 8b stub `cp`, -so the consumer's own clone is what rotates the snapshot underneath it, or -what loses a subtree of its own source, strictly inside the identity window. -Placement is then a fact rather than a scheduling outcome — and each stub -asserts that it fired, because a scenario whose interference silently did not -happen passes for the wrong reason. +The concurrency scenarios take one of three shapes, and none of them races +for the interleaving its assertion depends on. + +**A genuine race whose asserted invariant holds under any interleaving.** +`seed-target-dir-selftest.sh` scenario 7 starts two real seeds on one cache +key and asserts only what must be true whichever of them wins the rename. + +**A `PATH` stub on a command the code under test calls at a known point**, +which places the interference inside the window rather than hoping it lands +there. `prune-cache-selftest.sh` scenario 12 stubs `du`, so the pass's own +measurement publishes a reader marker strictly between its check and its +unlink; `seed-target-dir-selftest.sh` scenarios 8a and 8b stub `cp`, so the +consumer's own clone is what rotates the snapshot underneath it, or what +loses a subtree of its own source, strictly inside the identity window. 8a +does start a second real process — the actual `publish-snapshot.sh` — but the +stub is what fixes where its swap lands; the concurrency is incidental to the +determinism. Every stub asserts that it fired, because a scenario whose +interference silently did not happen passes for the wrong reason. + +**A synthetic stand-in for the other side, where that artefact *is* the +contract.** `publish-snapshot-selftest.sh` scenarios 6 to 8 hold a +`.reading-*` marker instead of running a slow consumer: the marker is the +whole agreement between reader and publisher, so holding one is being a +reader, and racing a real one would make the suite's runtime the thing under +test. Gating the interfering step on *observed progress* of the step it interferes -with was the earlier answer here, and it is not one: seeing that a walk has +with was an earlier answer here, and it is not one: seeing that a walk has started says nothing about where it will be when the interference lands, so -the assertion downstream held only some of the time (issue #3). +the assertion downstream held only some of the time (issue #3). No scenario +does it any more. -Where more than one guard could catch a fault, the scenario asserts *which* -one did — otherwise deleting the guard under test leaves the suite green -because a sibling fires in its place. +Where more than one guard could catch a fault, a scenario should assert +*which* one did — otherwise deleting the guard under test leaves the suite +green because a sibling fires in its place. Scenarios 8a and 8b of the seed +suite do; scenario 9 of the same suite does not yet, which is why a mutation +survives it (issue #5). The action YAML holds no logic beyond wiring; everything testable lives in `scripts/`. A composite action needs `shell: bash` on every `run:` step, and