kb: serde's derive is the one enumeration the compiler maintains
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
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-5ubs is solved, and not the way anyone proposed. Closing a length-not-completeness check needs an enumeration INDEPENDENT of the derivation, and stable Rust has none for an enum carrying data: variant_count is nightly, strum::EnumIter cannot construct a variant holding a MinibufferConfig. Serde can — the Deserialize derive builds the complete variant-name list from the enum definition and publishes it in its unknown-variant message. variants.rs reads it back and diffs it against what the cycle reached. The dependency on serde's message format is made loud rather than silent: it panics naming the message if the split fails and asserts the parsed list has more than one name, so a format change cannot degrade into a vacuous pass. Verified by reading it rather than taking the report. Also: a consolidation's frozen behaviour is a date, not a decision. A consolidation must preserve what it moves or its agreement test cannot tell 'both ends agree' from 'both ends changed together' — so the rule is frozen for the duration of the move, and afterwards a single home reads as decided. improvise-8qlr froze name-only group coalescing; the test still pinning it justified itself with 'because that is what src/ui/grid.rs uses', a clause 8qlr had already made false. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
This commit is contained in:
@@ -262,7 +262,6 @@ doctrine(a_narrowing_optimisation_owes_the_correctness_test_its_predecessor_pass
|
||||
doctrine(a_count_in_an_assertion_is_not_a_totality_guard,'assert_eq!(cases.len(), 13) with a comment reading \"a fourteenth declaring LayoutGeometry must come here and say so\" is false: a reviewer added a fourteenth pub struct declaring that kind, pointed at by nothing, and the whole 1439-test suite stayed green. Adding a variant is compiler-forced only where an EXISTING ARM MUST POINT AT THE NEW THING -- answering for it is forced, being reached is not. Same shape as improvise-5ubs (a cyclic-successor all() omitting a variant nothing points back to) and it appeared INSIDE the PR that found 5ubs. A count over a hand-written list asserts that the list has not shrunk, never that it is complete.').
|
||||
doctrine(a_measurement_that_a_change_is_free_is_not_evidence_that_it_is_correct,'The sharpest lesson of the backlog campaign, in the author own words: \"I asked what does this cost, got nothing, and reported it as though it had also answered is this right.\" improvise-mcq4 narrowed an all-views frame observation to the active view, measured the narrowing at 12.41ms to 12.56ms -- free -- and kept it as \"the honest shape\". It cost a multi-client invalidation no test named: a CellData write pruning a row out of another client view became invisible, and gotcha 24 says that is strictly worse than doing nothing, because the diff then believes the bystander is current and it stays stale permanently. A performance measurement answers ONE question. Running it does not discharge the other, and a change that costs nothing is the easiest kind to keep for bad reasons, since there is no budget forcing anyone to justify it.').
|
||||
doctrine(a_bug_can_hide_a_bug_and_the_tell_is_a_failing_precondition,'The reviewer P1 probe for improvise-mcq4 would not run: its precondition -- that the bystander grid really loses a row -- failed. Cause was a SECOND bug: project::grid_in builds a NON-ACTIVE view with GridLayout::new, which does not apply prune_empty, so the bystander projected grid never pruned. Measured: 3 rows via grid_in against 2 via with_frozen_records. The first bug was real and logically reachable and COMPLETELY UNOBSERVABLE because the second one masked it. Fixing grid_in is what made the P1 demonstrable; the P1 test is what found grid_in. So a precondition that will not hold is not a broken test to be relaxed -- it is a finding. Third instance of the GridLayout::new versus with_frozen_records trap (view_frame was the second): improvise-t6db.').
|
||||
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.').
|
||||
@@ -276,6 +275,8 @@ doctrine(prose_that_calls_a_change_free_is_a_claim_and_is_checkable,'The sibling
|
||||
doctrine(forcing_by_name_while_budgeting_by_index_defeats_the_budget,'improvise-zv8t made fit_measure force header[..category_columns] to Forced::Categorical keyed by NAME, while encoded_width decides category-ness by INDEX. A document may legally name a regular category and a raw measure the same thing (a_category_and_a_measure_of_the_same_name_are_refused ships that fixture), so the MEASURE column was forced categorical by name match while the preflight still counted it numeric by index: measured 41 against 80 at 40 records. no_ambiguous_column_names ran AFTER preprocess, so the expansion the preflight exists to prevent was allocated and only then refused -- guard_the_cost_not_the_result defeated by an inconsistency the fix itself introduced, NOT the bounded approximation a_budget_check_is_advisory_or_exact_and_must_say_which licenses. Repair is a data dependency, not a moved line: as_categories runs the check and returns Result<Overrides>, so the Overrides preprocess needs cannot exist until the header is accepted. Guard is the SIGNATURE -- reordering the two statements is error[E0425], verified. Deliberate deletion of the check from as_categories is still possible and is stated rather than implied.').
|
||||
doctrine(a_mitigation_that_names_the_wrong_file_is_not_a_mitigation,'PR #91 deferred a wire-level gap on the ground that \"keys.rs emits no ChangeMode at all\". True, and irrelevant: keys.rs holds only BrowserKey, and the LIVE browser key resolver is improvise_protocol::resolve, re-exported as improvise_wasm_client::resolve -- which emits Command::ChangeMode(AppMode::Normal) at five sites. So the browser front end kept the very bug the PR fixed for the TUI, and the suite was green over a divergence the PR itself created. The sentence was in context/, which design-principles section 6 names as exactly the place a reason must not be wrong, because nothing can find it later. Before writing \"X does not do Y\" as the reason a gap may be deferred, GREP FOR Y rather than for X -- the claim that matters is about the behaviour, and the file you happen to have open is not the only place it can live. The orchestrator copied the same false clause into the bead it filed.').
|
||||
doctrine(an_example_that_works_for_another_reason_defends_nothing,'improvise-sihr resolved AppMode::Normal against a pre-batch layout (gotcha 23) and defended the staleness in prose with the case of toggle-records-mode, which does work in both directions. It works for a DIFFERENT REASON: ToggleRecordsMode emits change_mode(RecordsNormal) explicitly on entry, and on exit ViewBack restores the mode SwitchView pushed, so normal_mode() answer is overwritten in both directions and the staleness is neutralised there rather than exercised. The case that would actually test it -- a batch that CROSSES records-ness and then sets Normal -- was untested, and regressed: from a records grid, \":toggle-records-mode . enter-mode normal\" landed in RecordsNormal where it was right before. An example chosen because it passes is not evidence; ask WHY it passes.').
|
||||
doctrine(a_list_checked_for_length_reads_like_a_list_checked_for_completeness,'assert_eq!(cases.len(), N) catches a case being DELETED from a list and never one being ABSENT from it, and the two are written identically. Four instances here: AppMode::all(), Rejection::all(), and twice inside one PR. A cyclic total after() does NOT close it -- answering after() FOR a new variant is compiler-forced, pointing an existing arm AT it is not, so a variant nothing names is never walked (all().len() came back 17 against 18, suite green). Closing it needs an enumeration INDEPENDENT of the derivation, and stable Rust has none for an enum carrying data: variant_count is nightly, and strum EnumIter cannot construct a variant holding a MinibufferConfig. SERDE CAN -- the Deserialize derive builds the complete variant-name list from the enum definition and publishes it in its unknown-variant message. improvise-5ubs reads it back in crates/improvise-protocol/src/variants.rs (cfg(test)) and diffs it against what the cycle reached. The dependency on serde message format is made LOUD rather than silent: it panics naming the message if the split fails, and asserts the parsed list has more than one name, so a format change cannot degrade into a vacuous pass.').
|
||||
doctrine(a_consolidations_frozen_behaviour_is_a_date_not_a_decision,'A consolidation must PRESERVE the behaviour it moves, or its agreement test cannot tell \"both ends agree\" from \"both ends changed together\" -- so the moved rule is frozen FOR THE DURATION OF THE MOVE. Afterwards a single home reads as decided, and nothing in the tree says otherwise. improvise-8qlr froze name-only group coalescing because both prior copies did it; improvise-h0iv later changed it once for both ends, and found the test still pinning the old rule justifying itself with \"because that is what src/ui/grid.rs uses\" -- a clause 8qlr had already made false, since grid.rs calls group_run_heads now. When a consolidation lands, say in context/ WHICH of its behaviours were preserved for evidence rather than on merit; otherwise the next reader inherits a frozen date as a decision.').
|
||||
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.').
|
||||
|
||||
Reference in New Issue
Block a user