kb+docs: the Pages artifact is reaped by a foreign successful deploy
CI / Build all bundles (push) Has been cancelled
CI / Test and lint (host) (push) Has been cancelled
CI / wasm32 check (browser bundle crates) (push) Has been cancelled
CI / Browser JS unit tests (node --test) (push) Has been cancelled
CI / Knowledge base consistency (push) Has been cancelled
CI / Lint the harness crate (push) Has been cancelled
CI / Browser harness (OPFS contract + shell) (push) Has been cancelled
CI / Deploy to GitHub Pages (push) Has been cancelled

improvise-864a diagnosed over all 100 push-to-main runs rather than the three
the ticket named — and the three-run framing was mine, a sampling artefact
that made a two-day, sixteen-failure pattern look like a concurrency race
between queued deploys. 12/12 artifact-not-found failures had another run's
SUCCESSFUL deploy complete inside their own upload-to-deploy window, against
2/56 successes.

The decisive observation was a negative one already sitting in a log: a run
seeing its own harness-serve and wasm-bundles but not its own github-pages.
Same run, same token, same call, same retention — which kills flakiness,
eventual consistency, retention, token scope and a v3/v4 mismatch together.

One clause of the ticket's own correction is falsified: PR #46's guard aimed
at the right quantity for the wrong reason. It still must not ship, because
the association is not a mechanism and one run survived a foreign deploy in
its own window.

improvise-ijql closed additive. The author argued against the precedent I
pointed it at and was right: a provenance line qualifies an answer the run
gave, a disclaimer qualifies one it did not, and exit 0 is what CI branches
on. The rule is now stated over the exit code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
This commit is contained in:
Edward Langley
2026-09-20 22:24:32 -07:00
parent 9b22965fdf
commit ada16d211a
3 changed files with 14 additions and 4 deletions
+5
View File
@@ -263,6 +263,10 @@ doctrine(a_bug_can_hide_a_bug_and_the_tell_is_a_failing_precondition,'The review
doctrine(a_list_checked_for_length_reads_like_a_list_checked_for_completeness,'assert_eq!(cases.len(), 13) carried the comment \"a fourteenth declaring LayoutGeometry must come here and say so\". A reviewer added a fourteenth pub struct declaring that kind, pointed at by nothing, and the 1439-test suite stayed green. Both checks are written as an assert_eq over a number and are indistinguishable at the call site, but a length check asserts only that the list HAS NOT SHRUNK. Completeness needs something that makes the new case unrepresentable-unless-declared -- an exhaustive match, or a derivation off the thing being enumerated. Fourth instance of the improvise-5ubs shape, and it appeared INSIDE the PR that found 5ubs.').
doctrine(it_broke_no_other_test_answers_observability_not_correctness,'The sibling of a_measurement_that_a_change_is_free_is_not_evidence_that_it_is_correct, one level up and in the same PR. Fixing project::grid_in to honour prune_empty for a non-active view broke exactly ONE test -- the probe written that same hour -- and that was offered as evidence nothing depended on the old behaviour. It is equally consistent with NOTHING HAVING TESTED IT, which is what it was. The honest answer came from asking WHY instead: the unpruned projection was internally consistent (seed and steady diff both went through grid_in, so they agreed with each other) and externally wrong (a browser client on a non-active view was shown phantom rows its own config prunes). The author was right, and right for a reason not yet established when they said it -- the difference between those two states is the entire value of the review loop.').
doctrine(a_property_test_over_two_configs_must_make_the_configs_actually_differ,'Writing the improvise-t6db host-view-independence property -- grid_in(app_on_A, \"B\") == grid_in(app_on_B, \"B\") -- the first version PASSED ON THE BUGGY TREE. It made the two views differ by turning prune_empty OFF in the second: plenty of differing config, and completely vacuous, because with nothing to prune the two code paths agree. The fixture must contain an ACTUALLY EMPTY ROW and leave BOTH views pruning. A test written the first way looks thorough and asserts nothing -- this repo characteristic defect arriving inside the very test meant to close it. Same trap named in improvise-iot6, whose fix needs the same non-vacuous fixture.').
doctrine(a_gate_that_can_exit_zero_answers_every_question_it_gates,'A flag ADDS a question; it never selects one away. A disclaimer line saying a check did not run still exits 0, and exit 0 is what CI branches on and what a human reads as clean -- so the rule is stated over the EXIT CODE rather than over the log: a tree with a long comment run must not pass ANY invocation that can exit 0. improvise-ijql: --check-evidence early-returned past the comment cap, and three agents hit it in one campaign. Note the orchestrator pointed at the improvise-rm4s precedent (where the fix was to make the success line NAME ITS SOURCE) and the author argued against it correctly: a provenance line qualifies an answer the run GAVE, a disclaimer qualifies one it DID NOT. --self-test and --list-doctests are excluded by decision, not exempted -- the first scans no tree, the second renders no verdict.').
doctrine(a_dispatch_bug_is_invisible_to_unit_self_tests,'When every unit in a file is correct and the defect is in main() argument dispatch, no fixture-level table can reach the wiring -- the guard must INVOKE THE PROGRAM, as a subprocess over a temporary tree. improvise-ijql was exactly that shape: every function in check-comment-runs.py was right, and the early return in main() was the bug. Its guard needs both arms: restore the early return and the clean-tree case must fail, AND force the cap to always report a violation, or an always-failing cap satisfies the first arm.').
doctrine(job_metadata_outlives_the_artifacts_it_describes,'An artifact-lifetime incident is still diagnosable after the artifacts 404: run, job, step and log APIs retain about 90 days where artifacts here retain 1. So a post-hoc artifact COUNT is not evidence about the incident, it is a reading of retention -- improvise-864a 0/1/0 table could not have been produced by expiry at all, since the newest-finalized expires LAST and expiry can only yield 0/1/1 or 0/0/1. Corollary that did the real work: widen the sample before theorising. The ticket framing of \"two of three artifacts\" was a SAMPLING ARTEFACT that made a two-day, sixteen-failure pattern look like a three-run concurrency race, which is the wrong shape entirely.').
doctrine(a_control_you_already_have_is_cheaper_than_the_hypothesis_you_were_about_to_test,'Diagnosing improvise-864a, the single observation that eliminated five competing explanations at once was a NEGATIVE one already sitting in a log: a run lookup reporting \"Found 2 artifact(s)\" -- its own harness-serve and wasm-bundles present, its own github-pages absent. Same run, same token, same call, same retention, so service flakiness, eventual consistency, retention, token scope and a v3/v4 backend mismatch all die together, since any of them would have taken the siblings too. The agent nearly did not fetch that log, having already established the timeline. Before testing the next hypothesis, ask what is already in hand that would discriminate.').
work_done('improvise-8tk',bug_fix,'ZZ/:wq quit even when the save failed, silently discarding the model. Save now aborts the effect batch on failure; save_to_current_path/1 returns Result<String,String>; three tests in ui/effect.rs.').
work_done('improvise-907',bug_fix,'Import wizard step machine redesign: validation_error() gate at ReviewProposals (no-category and over-limit), history-stack previous() bound to Left on every step, and Done made a real confirmation step holding the built workbook. Superseded improvise-gfq, improvise-o1c, improvise-c6j.').
work_done('improvise-6sq',wont_do,'Merging EditOrDrill and EnterEditAtCursorCmd into one EnterEdit{mode} is not safe: they differ in WHEN the cell value is read, not in a mode parameter. Added ui/app.rs::tests::i_and_o_diverge_on_an_empty_records_grid as executable evidence.').
@@ -369,6 +373,7 @@ decision('improvise-kc7n',an_origin_check_is_not_authentication,'It answers what
decision('improvise-x7cq',every_check_job_gates_the_deploy_is_a_gate,'pages-deploy must \`needs\` every other job in ci.yml. Stating the rule was not enough: a zz-new-check: job added during review of PR #83 was ungated and nothing noticed, and README carried a second copy of the job list with nothing holding it to the first. scripts/check-ci-graph.py reads the job keys and the needs list off indentation, TRACKING WHICH JOB A needs: BELONGS TO -- taking the first one seen would read another job the deploy list and pass, which is one of its five self-test graphs. README now states the rule and lists no jobs.').
decision('improvise-mcq4',cursor_frame_is_one_view_on_geometry_and_every_view_on_cell_data,'The LayoutGeometry arm hashes only the active view frame -- that is the held-key path the optimisation exists for, and other views cannot reframe there because view_mut() writes only the active view, with AddCategory/RemoveCategory/AddItem/RemoveItem already covered by view_shapes. The CellData arm hashes EVERY view, because a cell write can prune a row out of a view the host is not on. Measured: 0 percent on the held-key path, +5.7 percent on an 18k two-view SetCell, linear in view count and accepted. Absolute stated rather than only the ratio: the PR still roughly doubles held-key latency on large pruned documents, 3.04ms to 5.69ms at 18k, and 56ms at 90,000 cells is 3.4 frames.').
decision('improvise-ni6k',cursor_invalidation_is_observed_not_declared,'No effect calls reset_cursor. apply_effects diffs the grid frame after every model effect, INSIDE the loop, because a batch that reframes and then places the cursor loses the placement otherwise (four records-mode tests pin that position; two after the arms were split). Total over EffectKind::Model(_) by construction rather than by a maintained list -- the thirteen per-effect calls it replaced are gone, and the \"a geometry effect that forgets to reset is invisible in both directions\" hazard is closed for model effects. Totality remains conditional on CLASSIFICATION, which nothing makes total: a ViewOnly effect that renumbers the grid is invisible to all of it.').
decision('improvise-864a',pages_artifact_reaped_by_a_foreign_successful_deploy,'Measured over 100 push-to-main runs 2026-09-19..2026-09-21: 12 of 12 artifact-not-found deploy failures had ANOTHER run SUCCESSFUL Pages deployment complete inside their own upload-to-deploy window, against 2 of 56 successes. Direction proved by the oldest run of the 17:47Z batch failing BEFORE the next deploy started and before any deployment existed, since deploy-pages fetches artifact metadata before creating one. NOT queue depth, NOT supersession, NOT retention, NOT the action deleting it. PR #46 tip-of-main guard aimed at the right quantity for the WRONG REASON and still must not ship: the association is not a mechanism, and 5f871f7 survived a foreign deploy inside its own window. Reproduce deliberately before fixing.').
wire_carries(command,both_directions,'user intent up, projection commands down').
wire_carries(snapshot,on_connect_only,'improvise-q08: full ViewState + render cache filled from current viewport').
wire_carries(command,both_directions,'user intent client->server, projections server->client').