docs: improvise-xps8 describes a bug that does not exist — turn it into a guard

Review findings on PR #30, all three applied.

1 [P1]. The claim that the handshake watchdog overwrites a genuine mid-session
disconnect 8s later is FALSE on any tree that has PR #21. `close` routes BOTH
arms through `setShellFailure` (app.js:248-250), which clears the timer
synchronously (app.js:165-169).

It was believed because it was measured honestly — against a tree predating
PR #21 — and then reported as "surviving" that PR on the strength of a
simulation that re-created only the `opened` flag and not the other half of the
fix, the routing. A simulated fix that is not the fix you named reproduces a
bug the real code does not have, and reads exactly like a confirmation.

Rather than delete the claim, execute the property it denied: the
open-then-dropped test now runs past HANDSHAKE_TIMEOUT_MS + 2s and asserts
"sent no document" never appears. Proved discriminating by removing
`setShellFailure`'s `clearTimeout` — the assertion then fails with exactly the
message xps8 described. gotcha 48 and the spec's comment block corrected; the
ticket is cited in neither. Recorded as
doctrine(a_simulated_fix_must_be_the_fix_you_named), the sibling of
an_injected_fault_must_be_the_fault_you_named that fails the other way: a wrong
fault proves a check that cannot fire, a wrong simulated fix proves a bug that
does not exist.

2 [P1]. The previous commit created the contradiction it was meant to remove:
design-principles recorded the measured ~2.2s while kb.pl still said "under 2
seconds". The ambiguity between per-binary and wall clock is what let it drift,
so both now state two numbers — no single binary over 2s, wall clock is the
sum, measured 1309 tests / 33 binaries / ~2.2s / longest 1.42s — in kb.pl,
design-principles and repo-map alike. Also 16 -> 17 in doctrine(binding_dump_diff).

3 [P2]. The new audit exclusion was unanchored, so `improvise-gp` was a prefix
match that would silently exclude a real future ticket. Anchored `^…$`. It
immediately caught the example id the explanatory paragraph itself spelled out
— a recipe is code, twice over.

CLAUDE.md: declined, with evidence. Both copies of the duplicated tail sit
inside `<!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:ca08a54f -->`
markers written by `bd setup claude` and `bd setup codex` and refreshed against
that hash. A process change there is one tool invocation, not two hand edits,
and a pointer makes `bd setup <recipe> --check` report it modified. Recorded as
kb_issue_found(claude_md_tail_is_generated_not_duplicated) so it is not
re-proposed.

kb.pl: doctrine 54->55, kb_issue_found 11->12; invariant 51, decision 47,
location 34 unchanged. kb_consistent passes.

cargo test --workspace: 1309 passed, 0 failed, 2 ignored.
harness (Playwright): 12 passed, 3 skipped (webkit has no OPFS).
Dead-ID audit with the anchored recipe: 157 tokens, all resolve.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Edward Langley
2026-09-20 01:11:46 -07:00
parent 34e5bd5902
commit 6f9efcf2e8
5 changed files with 88 additions and 45 deletions
+4 -2
View File
@@ -105,11 +105,11 @@ invariant(the_persist_trigger_is_an_observation,'improvise-worker-server persist
invariant(opfs_is_run_but_bindings_is_not,'harness/ (improvise-vvl3/xf1) executes improvise_io::storage::contract::storage_contract against a real OpfsFs in a real dedicated Web Worker under Chromium and Firefox, gated by the browser job in CI; two bugs fell out of the first run. Playwrights WebKit provides no OPFS at all (getDirectory rejects with UnknownError), so xf1s three-engine criterion is met for two of three and the project is kept so it starts the day that build gains OPFS. harness/tests/shell.spec.js runs web/app.js under CHROMIUM ONLY -- engine-independent logic, and routeWebSocket is slower across three. Never instantiated by any harness: bindings.rs.').
doctrine(bug_first,'write a failing test demonstrating the bug before fixing it').
doctrine(unit_over_e2e,'prefer small unit tests over integration/e2e').
doctrine(suite_speed,'workspace suite must stay under 2 seconds').
doctrine(suite_speed,'Two numbers, because the ambiguity between them is what let this drift: NO SINGLE TEST BINARY may exceed 2 seconds, and the WORKSPACE WALL CLOCK is the sum of them. Measured at 22d43b2: 1309 tests over 33 binaries, ~2.2s wall, longest single binary 1.42s -- so the workspace is already past 2s and the rule that bites is the per-binary one. Re-MEASURE rather than re-assert: cargo test --workspace prints a "finished in" per binary. Excludes compilation, and excludes harness/ (Playwright, minutes, its own CI job).').
doctrine(coverage,'aim ~80% line/branch on logic; skip ratatui render paths').
doctrine(test_contract,'test the observable contract, not the internal storage').
doctrine(proptest_cases,'keep proptest at default 256 cases; constrain strategies instead of brute force').
doctrine(binding_dump_diff,'when refactoring keymap construction, dump every (mode,key,modifier) lookup for all 16 modes before and after and diff: most bindings have no direct test, so a green suite is not evidence').
doctrine(binding_dump_diff,'when refactoring keymap construction, dump every (mode,key,modifier) lookup for all 17 ModeKeys before and after and diff: most bindings have no direct test, so a green suite is not evidence').
doctrine(derive_then_verify,'when classifying many items from source, extract facts mechanically and reason in Prolog, but VERIFY the extraction: multi-line method chains and free-function helpers defeated naive regexes and mis-classified SetCell, SortData and ApplyAndClearDrill as view-only. A line-oriented awk over effect.rs likewise reports 30 Model effects where brace-matching the impl blocks gives the true 29 -- HelpPageSet is ViewOnly and the running variable bleeds across the #[cfg(test)] boundary.').
doctrine(mentions_are_not_edges,'never bulk-add dependency edges from prose mentions; direction must be read per pair, and blind addition can invert real edges or create cycles').
doctrine(kb_is_code,'the knowledge base is executable, so audit it like code: check rules actually reject what they should, and check location/3 symbols still exist in the tree').
@@ -139,6 +139,7 @@ doctrine(a_documented_door_with_no_caller_is_the_dangerous_kind,'reduce_full was
doctrine(the_audience_follows_the_optimistic_fold,'a relay to other clients is ToOthers exactly when the asker folded the command optimistically; otherwise ToAll. A Command::Model is never folded optimistically, so every relay out of apply_model is ToAll. Copying apply_switch ToOthers into apply_model left the asker own slice naming the outgoing view while its server mirror named the incoming one -- and a test asserting through a what-do-the-others-hear helper cannot see it, because that helper looks away from the asker by construction.').
doctrine(half_a_lockstep_fix_is_worse_than_none,'resetting the server mirrors without telling the clients makes the DIFF believe they are already current, so it sends nothing: stale FOREVER rather than until the next scroll. When a fix has a server half and a client half, shipping one is not partial progress, it is a worse bug than the original.').
doctrine(force_is_a_recomposition_not_a_bypass,'force never applies a batch whose basis is stale; it re-composes the same INTENT against the current state and submits a fresh batch with a current basis. A mechanism whose only purpose is to violate the invariant is not a feature, it is the invariant deleted with extra steps.').
doctrine(a_simulated_fix_must_be_the_fix_you_named,'The sibling of an_injected_fault_must_be_the_fault_you_named, one level up, and it fails in the opposite direction: an injected FAULT that is not the one named proves a check that does not fire; a simulated FIX that is not the one named proves a BUG THAT DOES NOT EXIST. Worked example: the harness agent measured the 8s watchdog overwrite honestly, on a tree predating PR #21, then reported it "survives improvise-taxx, confirmed by simulating the opened flag". PR #21 is TWO changes -- the flag AND routing both close arms through setShellFailure, which clears the timer. Simulating half of it reproduced the bug and read exactly like a confirmation. improvise-xps8 was filed on that report, propagated into repo-map gotcha 48 and a spec comment, and was closed INVALID. The repair is not a deletion: the property is now executed by a test that outlasts HANDSHAKE_TIMEOUT_MS, proved discriminating by removing setShellFailures clearTimeout and watching "sent no document" appear.').
doctrine(an_order_test_needs_two_histories,'a single-process test cannot tell \"sorted correctly\" from \"hashed that way today, on this machine\". Compare against the insertion-ordered structure itself, or build the same logical data by two DIFFERENT insertion histories and require each to list its own. Corollary of check_the_checker.').
doctrine(a_helper_that_builds_the_world_the_code_wants_tests_nothing,'improvise_eval FABRICATED a _Measure item per feature name, so it proved the emitter and gp_tree::eval agree against a model that could not exist. The fix is not to delete such a test -- it checks real things -- but to state its scope and assert the PRECONDITION separately against a real fixture. Pair a refusal test with an accepting one, or the refusal can pass by refusing everything.').
doctrine(a_session_local_widget_gets_a_cache_and_no_projection,'a Projection is addressed to a widget on EVERY client, which is right exactly when the change is addressed to NO ONE session. A half-open dialogue belongs to precisely the session that opened it, so WizardCache is built and drawn locally with no Projection. Same argument that removed AcceptWizardModel from ModelCommand, applied to rendering rather than to mutation.').
@@ -367,6 +368,7 @@ groom_check(unrecorded_dep,'prose naming another issue is a CANDIDATE, never a f
groom_check(stale_citation,'file:line references rot; resolve each against the tree and prefer symbol names when correcting').
groom_check(dissolved_premise,'grep for the pattern an issue targets before working it -- improvise-ont described 11 call sites of a pattern that no longer occurs anywhere').
groom_check(dissolved_premise_test_gap,'a test-gap ticket rots differently from a code ticket: the gap can be closed by someone else work without anyone touching the ticket. Re-run each item against the test names before working it -- 2 of 7yx five items were already covered, by jc8 and by an import fix.').
kb_issue_found(claude_md_tail_is_generated_not_duplicated,'The ~47-line beads tail is byte-identical in AGENTS.md and CLAUDE.md, which reads as the duplication doctrine(a_fact_stated_twice_is_changed_by_grep) forbids. It is not: both copies sit inside <!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:ca08a54f --> markers, written by bd setup codex and bd setup claude respectively, and refreshed by bd against that content hash. A process change there is one tool invocation, not two hand edits, and replacing either copy with a pointer makes bd setup <recipe> --check report it modified and the next run re-add it. Checked 2026-09-20 after a doc review proposed the pointer; declining is the right answer and this fact exists so the next reviewer does not re-propose it.').
kb_issue_found(host_rpc_shims,'sleep/delay/console_log/js_eval/future/await_any/future_cancel were the MCP runtime own predicates, captured into this file by a saveSession. Removed: not project knowledge, and the source of an overwriting warning on every consult. If saveSession re-adds them, strip them again.').
kb_issue_found(may_depend_on_type_confusion,'may_depend_on(A,B) :- crate(A,C,D), \\+forbidden(A,B). C and D were singletons and B was never constrained, so may_depend_on(improvise_core, banana) succeeded. forbidden/2 second argument is a CONCERN, not a crate. Replaced by may_use/2 over an explicit concern/1, plus real crate_depends_on/2 facts read from the Cargo.toml files.').
kb_issue_found(before_fixing_dead_guard,'before_fixing(A,B) :- A=C, doctrine(bug_first,B). A=C bound A to a fresh variable and constrained nothing, so the rule succeeded for any first argument. Now requires work_done(Bug,bug_fix,_).').