R-291: assert that a tag one reader accepts stays visible to the other
c0 asked whether R-290's widening of tagsIn reaches past outcome-coverage's loose counter, which would zero its unparsed count for the wrong reason and retire a canary. It does not -- 22 beats from each reader on CLEAN_GROUND.md, nothing that tagsIn accepts invisible to the other guard -- but "does not today" decays quietly, so it is now a test importing both real functions rather than a copy of either pattern. Mutation-checked: widening tagsIn to accept [CUS Spot] turns it red. Measuring it found something the question did not ask about, recorded in the log: for [cus: ...], [CUS : ...] and [ CUS: ...] the two guards disagree -- tagsIn reads the tag, beatLikeIn calls it unparseable -- because beatsIn's strict half kept the case-sensitive literal. The build stops, which is the safe direction, but names the wrong thing. outcome-coverage.mjs is c0's file and the call is theirs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ae8179bd9b
commit
ed851b76e3
@@ -6840,3 +6840,29 @@ starts from a clean corpus rather than from an exception list.
|
||||
pattern**: widen the reader for the spellings you thought of, and add a looser counter that
|
||||
names the one you did not. A marker nothing reads is worse than no marker, because whoever
|
||||
wrote it believes the thing is declared.
|
||||
|
||||
## R-291 — the canary c0 asked about, asserted rather than remembered
|
||||
|
||||
R-290 widened `tagsIn`. c0 asked the right question about it: `outcome-coverage`'s
|
||||
`beatLikeIn` names the tags its own reader cannot parse, and if the widened `tagsIn` reaches
|
||||
past that loose pattern, the unparsed count drops to zero for the wrong reason and stops
|
||||
being a canary.
|
||||
|
||||
**It does not.** Measured on both real readers rather than by copying either pattern:
|
||||
CLEAN_GROUND.md gives 22 beats from `tagsIn`, 22 from `beatsIn`, 0 unparsed. Across spellings,
|
||||
everything `tagsIn` accepts is seen by the other guard — parsed, or named. `[CUS Spot]` and
|
||||
`[CUS= Spot]` are refused by both strict readers and still named by the counter.
|
||||
|
||||
"Does not today" is exactly the kind of claim that decays without anyone noticing, so it is a
|
||||
test: **a tag one reader accepts is never invisible to the other.** It imports both real
|
||||
functions, so neither pattern is duplicated and the test cannot agree with a stale copy.
|
||||
Mutation-checked — widening `tagsIn` to accept `[CUS Spot]`, past the counter, turns it red.
|
||||
|
||||
**What the measurement found that the question did not ask about.** For `[cus: …]`,
|
||||
`[CUS : …]` and `[ CUS: …]` the two guards now *disagree*: `tagsIn` reads the tag and checks
|
||||
it for reachability, while `beatLikeIn` reports it unparseable and fails the build, because
|
||||
`beatsIn`'s own strict half kept the case-sensitive literal R-290 removed from `tagsIn`. Not
|
||||
silence — the build stops, which is the safe direction — but the message names the wrong
|
||||
thing, and the fix is the one check-rollable already made for the cast: read through the other
|
||||
guard's function rather than a second regex. That file is c0's and the call is theirs; it is
|
||||
recorded here so it is a decision rather than something nobody wrote down.
|
||||
|
||||
@@ -32,6 +32,8 @@ import { ROLES, TRADES, INDUCTION, TIERS, TRADE_BANDS, CHARACTERISTIC_DICE }
|
||||
import { STARTER_AUTHORITY, STARTER_GROUPS, STARTER_CASE, STARTER_TEAM }
|
||||
from "./scenario-starter.mjs";
|
||||
import { castMarkersIn, castLikeIn } from "./declared-cast.mjs";
|
||||
import { tagsIn } from "./check-scenarios.mjs";
|
||||
import { beatsIn, beatLikeIn } from "./outcome-coverage.mjs";
|
||||
|
||||
let passed = 0, failed = 0;
|
||||
// Async tests are awaited in order rather than fired off — a failure that lands after
|
||||
@@ -819,6 +821,36 @@ test("a cast-shaped marker the reader cannot parse is named, not skipped", () =>
|
||||
assert.deepEqual(castLikeIn("`<!-- cast = x -->`"), [], "prose in backticks is not an oddity");
|
||||
});
|
||||
|
||||
/* ── R-290: two readers of the same tag must not lose one between them ───────────────
|
||||
check-scenarios' `tagsIn` decides which beats are checked for skills and reachability;
|
||||
outcome-coverage's `beatsIn` decides which are checked for stated outcomes, with
|
||||
`beatLikeIn` naming what it could not parse. Two strict readers of one format, written a
|
||||
week apart by two sessions.
|
||||
|
||||
c0 asked the right question when R-290 widened `tagsIn`: if the widening reaches past its
|
||||
loose counter, that counter drops to zero for the wrong reason and stops being a canary.
|
||||
It does not — the widened pattern is a subset of the loose one — but "does not today" is
|
||||
the kind of claim that decays silently, so it is asserted rather than remembered. The
|
||||
invariant is the weakest one that keeps the canary alive: a tag `tagsIn` accepts must be
|
||||
visible to the other guard, either parsed or named. Never to neither. */
|
||||
|
||||
test("a tag one reader accepts is never invisible to the other", () => {
|
||||
const seen = t => beatsIn(t).length + beatLikeIn(t).length;
|
||||
for (const tag of ["[CUS: Spot — what it gets]", "[cus: Spot — what it gets]",
|
||||
"[CUS : Spot — x]", "[ CUS: Spot — x]", "[CUS:Spot]"]) {
|
||||
assert.equal(tagsIn(tag).length, 1, `tagsIn no longer reads ${tag}`);
|
||||
assert.ok(seen(tag) >= 1,
|
||||
`${tag} is read by tagsIn and invisible to the outcome guard — widening one reader past `
|
||||
+ `the other's counter is how a tag gets checked for reachability and never for whether `
|
||||
+ `the beat says what it does`);
|
||||
}
|
||||
// and the counter still has teeth: shapes the strict readers refuse are still named
|
||||
for (const tag of ["[CUS Spot]", "[CUS= Spot]"]) {
|
||||
assert.equal(tagsIn(tag).length, 0);
|
||||
assert.equal(beatLikeIn(tag).length, 1, `${tag} is refused by both readers and named by neither`);
|
||||
}
|
||||
});
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
await runAll();
|
||||
|
||||
Reference in New Issue
Block a user