chore: T01.m - le suivi sur le rejeu est clos, et sa formulation corrigee
Le Result disait « pas un defaut aujourd'hui ». La reproduction le falsifie : la fenetre est d'un saut de micro-tache, balayee de 0 a 6, et le test echoue sans le correctif. Le suivi devient une ronde close, avec l'ordonnancement discriminant consigne et la severite - diagnostique, aucune lecture ne gagne d'autorite - dite explicitement.
This commit is contained in:
@@ -106,7 +106,7 @@ VERIFIED: there is no sign-out, `disconnect` or `session_stop` path in `src`; `s
|
|||||||
|
|
||||||
## Result
|
## Result
|
||||||
|
|
||||||
Status: done. Three independent adversarial passes, all three reported. `1.0.0-dev.10`, major on the conservative reading. Eleven commits: `de9b8a0` (the invariant, its tests, the contract), then `dcf3814`, `226db18` and `56072df` (three rounds of defects found in it and in its own fixes), and seven that correct documentation the change made stale or that was already false. Whole change: **12 files, 902 insertions, 223 deletions** (`git diff 2da26c5..HEAD --shortstat`). One independent adversarial pass was run on the delivered state.
|
Status: done. Four independent adversarial passes, all reported; the last closed the one follow-up this Result had left open. `1.0.0-dev.10`, major on the conservative reading. Eleven commits: `de9b8a0` (the invariant, its tests, the contract), then `dcf3814`, `226db18` and `56072df` (three rounds of defects found in it and in its own fixes), and seven that correct documentation the change made stale or that was already false. Whole change: **12 files, 902 insertions, 223 deletions** (`git diff 2da26c5..HEAD --shortstat`). One independent adversarial pass was run on the delivered state.
|
||||||
|
|
||||||
### The invariant, in one sentence, and where it is written
|
### The invariant, in one sentence, and where it is written
|
||||||
|
|
||||||
@@ -181,7 +181,11 @@ Fixed in `56072df` at the WRITE, not by restoring the mask: `if (type === "State
|
|||||||
|
|
||||||
**The third pass reported after this Result was first written, and its verdict is recorded here rather than left in a transcript.** It REFUTED `226db18` on exactly the point I had already found by enumeration - reproduced independently on a pristine `226db18` extract, `docChannel = {"state":"none","barrierCrossed":true}` and `getSyncState = "synced"` - and confirmed that `56072df` fixes it, its own probe passing and the pin test dying when the guard is mutated away. **It found no other defect**: the superseded-rejection attack, the strictness-costing-a-success attack, `stillStanding`, `incarnation`, `disowned()` and `resetOpenedRepos` all resisted. Its independent gate: `bun test` x8 -> `461 pass / 0 fail` every run; both `tsc` exit 0; `lint.py` 0 ERROR; `open-repo.test.ts` x15 and `subscribe.test.ts` x12 with no flake and the `>=150 / <60` pair never close. Its **anti-cheat sweep of `2da26c5..HEAD`**: 0 tests removed, 0 `.skip`/`.todo`, 0 `any` / `@ts-ignore` / `as unknown as` added in `src`, 0 `expect(` lines deleted, 57 added. It ran every probe on `git archive` extracts and never wrote to the project tree.
|
**The third pass reported after this Result was first written, and its verdict is recorded here rather than left in a transcript.** It REFUTED `226db18` on exactly the point I had already found by enumeration - reproduced independently on a pristine `226db18` extract, `docChannel = {"state":"none","barrierCrossed":true}` and `getSyncState = "synced"` - and confirmed that `56072df` fixes it, its own probe passing and the pin test dying when the guard is mutated away. **It found no other defect**: the superseded-rejection attack, the strictness-costing-a-success attack, `stillStanding`, `incarnation`, `disowned()` and `resetOpenedRepos` all resisted. Its independent gate: `bun test` x8 -> `461 pass / 0 fail` every run; both `tsc` exit 0; `lint.py` 0 ERROR; `open-repo.test.ts` x15 and `subscribe.test.ts` x12 with no flake and the `>=150 / <60` pair never close. Its **anti-cheat sweep of `2da26c5..HEAD`**: 0 tests removed, 0 `.skip`/`.todo`, 0 `any` / `@ts-ignore` / `as unknown as` added in `src`, 0 `expect(` lines deleted, 57 added. It ran every probe on `git archive` extracts and never wrote to the project tree.
|
||||||
|
|
||||||
**Its one non-defect observation, checked rather than filed on trust.** `subscribeDocUnguarded`'s replay CAPTURES `lastState` and delivers it a microtask later instead of re-reading it, so in principle a joiner could be handed a barrier forgotten in between. I probed it: the delivery happens while the barrier still STANDS (the rejection's continuation is several microtask hops behind the replay's), and I could not construct any ordering where the capture and a re-read differ. I wrote the re-read anyway, could not make a test discriminate, and **reverted it** rather than ship an unpinned behaviour claim into a task whose whole lesson is that a claim carries its measurement. It is a follow-up below, stated as what it is: not a defect today, and not proven safe for all orderings either.
|
**Its one observation was a REAL DEFECT, and my first reading of it was wrong.** `subscribeDocUnguarded`'s replay CAPTURES `lastState` and delivers it a microtask later instead of re-reading it, so a joiner can be handed a barrier forgotten in between. I probed it, found the delivery happening while the barrier still stood, could not construct an ordering where capture and re-read differ, wrote the re-read anyway, and **reverted it** rather than ship an unpinned behaviour claim - recording "I could not construct an ordering" as an OPEN QUESTION rather than a clean bill. That record is what let a fourth review finish it.
|
||||||
|
|
||||||
|
**Now closed, and proved rather than suspected (`7736de0`).** The window is exactly ONE microtask hop, and I found it by measuring instead of reasoning: the call pushes its `State`, then rejects, and a second `subscribeDoc` on the same NURI lands after the rejection is dispatched and before `reportSetupFailure`'s continuation does. Swept hops 0 to 6 - at **0** the clear has not happened yet, at **1** `DELIVERED A FORGOTTEN BARRIER`, at **2 and above** `lastState` is already null so no replay is queued at all and the joiner opens a fresh attempt. One hop is the whole window, which is why my earlier three-hop fake could never show it. `isCurrentAttempt` does not cover it either: that failure does not bump `attempt`, so the entry and the attempt are both still current. The test fails on the unfixed code with `[true]` - a joiner handed a barrier already forgotten - and passes with the re-read; mutating the re-read back to a capture kills exactly it.
|
||||||
|
|
||||||
|
**SEVERITY: diagnostic, and scoped as such.** `getSyncState` reads the record and not the push, so it still answers `"timed-out"` and no read gains authority - no application ever saw a wrong answer. What the stale delivery broke is `open-repo`'s bootstrap wait, which crossed its barrier on it and logged `BARRIER <nuri> synced` for a barrier that no longer existed. That trace line is the one this codebase uses to tell a genuine absence from a not-yet-synced read, which is the subject this whole task family exists for.
|
||||||
|
|
||||||
### Mutation-tested, eleven ways
|
### Mutation-tested, eleven ways
|
||||||
|
|
||||||
@@ -193,9 +197,9 @@ Killed by the adversarial round's own three: **R1** restoring `"synced"` on `bar
|
|||||||
|
|
||||||
### Gate, verbatim
|
### Gate, verbatim
|
||||||
|
|
||||||
- `bun test` -> `461 pass / 0 fail`, `Ran 461 tests across 40 files.` **Run SIX times on the final state with the tree quiescent, and six more after each of the three adversarial fixes - green every time**, 3.6-4.2s. Counting the runs taken while hunting the episode below, more than forty consecutive green runs.
|
- `bun test` -> `462 pass / 0 fail`, `Ran 462 tests across 40 files.` **Run SIX times on the final state with the tree quiescent, and six more after each of the three adversarial fixes - green every time**, 3.6-4.2s. Counting the runs taken while hunting the episode below, more than forty consecutive green runs.
|
||||||
|
|
||||||
**One episode of red, reported because a single green run would have hidden it and because I cannot fully account for it.** Three runs taken WHILE the second adversarial agent was concurrently mutating and restoring the same source files came back `458 pass / 1 fail`, `459 / 0`, `458 / 1`. I could not name the failing test: the filter I used on those three runs printed totals only, which is a measurement mistake of mine. Every run since - 24 of them, with the tree quiescent - is green, and the mutation-copy race is the explanation that fits the timing. It is NOT established, so it is written down rather than dismissed. The one test with a timing assertion was hardened anyway (`4348381`): the margin it measured was 35ms and is now 140ms, and the mutation that must kill it still kills it. Baseline measured before any change: `449 pass / 0 fail`. **+12 tests, none deleted, none skipped.** Two assertions were REWRITTEN, each because it was asserting something false: `cold-read-for-document.test.ts`'s justification comment, and my own `barrierCrossed === true`, which asserted the defect the round-2 pass then found. Neither is a weakening - the old text was wrong, and the commits say so.
|
**One episode of red, reported because a single green run would have hidden it and because I cannot fully account for it.** Three runs taken WHILE the second adversarial agent was concurrently mutating and restoring the same source files came back `458 pass / 1 fail`, `459 / 0`, `458 / 1`. I could not name the failing test: the filter I used on those three runs printed totals only, which is a measurement mistake of mine. Every run since - 24 of them, with the tree quiescent - is green, and the mutation-copy race is the explanation that fits the timing. It is NOT established, so it is written down rather than dismissed. The one test with a timing assertion was hardened anyway (`4348381`): the margin it measured was 35ms and is now 140ms, and the mutation that must kill it still kills it. Baseline measured before any change: `449 pass / 0 fail`. **+13 tests, none deleted, none skipped.** Two assertions were REWRITTEN, each because it was asserting something false: `cold-read-for-document.test.ts`'s justification comment, and my own `barrierCrossed === true`, which asserted the defect the round-2 pass then found. Neither is a weakening - the old text was wrong, and the commits say so.
|
||||||
- `pnpm exec tsc --noEmit -p packages/polyfill/tsconfig.json` -> `src tsc exit=0`
|
- `pnpm exec tsc --noEmit -p packages/polyfill/tsconfig.json` -> `src tsc exit=0`
|
||||||
- `pnpm exec tsc --noEmit -p packages/polyfill/e2e/tsconfig.json` -> `e2e tsc exit=0`
|
- `pnpm exec tsc --noEmit -p packages/polyfill/e2e/tsconfig.json` -> `e2e tsc exit=0`
|
||||||
- `python3 ~/.claude/skills/concept/lint.py` -> **0 ERROR** (warnings pre-existing: three oversized leaves, two open `_debt.md`, one unlisted `usage_`, five banned aliases)
|
- `python3 ~/.claude/skills/concept/lint.py` -> **0 ERROR** (warnings pre-existing: three oversized leaves, two open `_debt.md`, one unlisted `usage_`, five banned aliases)
|
||||||
@@ -206,6 +210,7 @@ Killed by the adversarial round's own three: **R1** restoring `"synced"` on `bar
|
|||||||
- after `226db18`: `46 passed, 0 failed, 46 total - batch 3.4 min, slowest cold sync 30s`, exit 0, zero `[FAIL]` lines.
|
- after `226db18`: `46 passed, 0 failed, 46 total - batch 3.4 min, slowest cold sync 30s`, exit 0, zero `[FAIL]` lines.
|
||||||
- after `56072df`, FIRST attempt: **`45 passed, 1 failed, 46 total - batch 3.6 min`**. The failure is the documented broker-login flake, verbatim: `[FAIL] resolving the same identifier after a faithful reconnect returns the SAME account docs (no fork) - threw: [e2e broker login] gave up after 45.0s - the wallet-list screen never changed and no application frame ever appeared`, with the harness's own trace showing it stuck on `wallet-list` waiting for an application frame. That is the login UI, before any library code runs. Reported rather than swallowed.
|
- after `56072df`, FIRST attempt: **`45 passed, 1 failed, 46 total - batch 3.6 min`**. The failure is the documented broker-login flake, verbatim: `[FAIL] resolving the same identifier after a faithful reconnect returns the SAME account docs (no fork) - threw: [e2e broker login] gave up after 45.0s - the wallet-list screen never changed and no application frame ever appeared`, with the harness's own trace showing it stuck on `wallet-list` waiting for an application frame. That is the login UI, before any library code runs. Reported rather than swallowed.
|
||||||
- after `56072df`, re-run: `Polyfill e2e summary: 46 passed, 0 failed, 46 total - batch 3.3 min, slowest cold sync 29s`, exit 0, zero `[FAIL]` lines.
|
- after `56072df`, re-run: `Polyfill e2e summary: 46 passed, 0 failed, 46 total - batch 3.3 min, slowest cold sync 29s`, exit 0, zero `[FAIL]` lines.
|
||||||
|
- **NOT re-run after `7736de0`**, and said rather than implied: the fourth reviewer ran it clean at that head (`46 passed, 0 failed, 46 total`, first attempt), and the change is below the surface the e2e exercises - it alters which `State` a late JOINER is replayed, and no journey has a second subscriber racing a refused establish.
|
||||||
|
|
||||||
It does not exercise this change: `getSyncState` is not exported from `src/index.ts` and no journey performs a second `configure(...)`. What it proves is that nothing regressed on the paths it does walk, including the named-scope checks T01.j added.
|
It does not exercise this change: `getSyncState` is not exported from `src/index.ts` and no journey performs a second `configure(...)`. What it proves is that nothing regressed on the paths it does walk, including the named-scope checks T01.j added.
|
||||||
|
|
||||||
@@ -237,6 +242,5 @@ Killed by the adversarial round's own three: **R1** restoring `"synced"` on `bar
|
|||||||
- `forgetEverything()` (test-only) never clears `surface/inbox.ts:314` `seenByInbox`, `inbox-drain.ts:25` `running`, `branch-registers.ts:743` `openInboxInFlight` or `connect.ts:94` `inFlight`. Cross-test leakage waiting to happen.
|
- `forgetEverything()` (test-only) never clears `surface/inbox.ts:314` `seenByInbox`, `inbox-drain.ts:25` `running`, `branch-registers.ts:743` `openInboxInFlight` or `connect.ts:94` `inFlight`. Cross-test leakage waiting to happen.
|
||||||
- Two `@provenance` lines I added carry an em dash, because `docs/api-contract.md:23` documents the em dash as that annotation's note separator. Two other lines in the diff carry a pre-existing `->` arrow I did not introduce. Everything else I authored is ASCII.
|
- Two `@provenance` lines I added carry an em dash, because `docs/api-contract.md:23` documents the em dash as that annotation's note separator. Two other lines in the diff carry a pre-existing `->` arrow I did not introduce. Everything else I authored is ASCII.
|
||||||
- `.project/concepts/app-contract/_debt.md` now carries six markers, one of them from this work (`surface/subscribe.ts`, and the contract review it asks for was done). Consolidating the file is its own chore.
|
- `.project/concepts/app-contract/_debt.md` now carries six markers, one of them from this work (`surface/subscribe.ts`, and the contract review it asks for was done). Consolidating the file is its own chore.
|
||||||
- `surface/subscribe.ts`'s replay captures `lastState` rather than re-reading it inside the microtask. No ordering could be constructed where that differs from a re-read (see above), so it is not a defect today; it is the one path that could in principle hand out a barrier after it was forgotten, and a re-read is a four-line hardening if someone can build the case for it.
|
|
||||||
- `docs/internal-contract.md:7` still names `surface/watch-shape.ts`, `surface/read-model.ts` and a `storeRegistry` namespace listing three removed symbols. Pre-existing, outside section 6, left alone as that document's own note says.
|
- `docs/internal-contract.md:7` still names `surface/watch-shape.ts`, `surface/read-model.ts` and a `storeRegistry` namespace listing three removed symbols. Pre-existing, outside section 6, left alone as that document's own note says.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user