kb: editing a shell script corrupts the instances already running it
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

bash reads a script incrementally rather than loading it whole, so editing a
file while an instance executes it shifts the byte offsets underneath that
instance. Two background pr-reviews watchers, started ten minutes earlier and
sleeping in their poll loops, both died with 'syntax error near unexpected
token do' the moment I appended a paragraph to the usage text. bash -n
confirmed the file itself was fine.

The failure is in the RUNNING instance, not the file, so it is invisible to
every check you would think to run afterwards. And worse than a crash here:
each watcher exited having printed only the EARLIER review, so the wait it
existed to perform silently did not happen. I re-ran both cleanly before
merging rather than counting them.

Closes improvise-hsoe, improvise-4dpj and improvise-26dm from PR #96.
Backlog: 32 open.

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-21 11:04:36 -07:00
parent d7bfcbbd93
commit 26456c698e
2 changed files with 4 additions and 3 deletions
+3 -3
View File
@@ -198,10 +198,10 @@
{"_type":"issue","id":"improvise-pby","title":"2.2 Write the README","description":"Replace existing README.md with new one under 250 lines. Sections in order: Title, one-sentence pitch, inline demo GIF, why this exists (Lotus Improv reference), quick start, key bindings, installation (Nix/crates.io/prebuilt - no Homebrew), codebase overview, expectations disclaimer, license. No badges, TOC, or contributing guide.","status":"closed","priority":2,"issue_type":"task","assignee":"Edward Langley","owner":"el-github@elangley.org","created_at":"2026-04-09T04:07:32Z","created_by":"Edward Langley","updated_at":"2026-07-30T23:06:31Z","closed_at":"2026-04-09T07:02:52Z","close_reason":"README written with all 10 required sections, committed. License updated to Apache-2.0 per user request.","dependency_count":0,"dependent_count":2,"comment_count":0}
{"_type":"issue","id":"improvise-ihv","title":"2.1 Add Nix tooling for asciinema, VHS, and cargo-dist","description":"Modify flake.nix to add pkgs.asciinema and pkgs.vhs to dev shell nativeBuildInputs. Add cargo-dist if packaged in nixpkgs. Add runtime deps for VHS (ttyd, ffmpeg) if needed. Verify tools work via nix develop --command. Add nix run app or shell script for demo recording workflow.","status":"closed","priority":2,"issue_type":"task","assignee":"Edward Langley","owner":"el-github@elangley.org","created_at":"2026-04-09T04:07:30Z","created_by":"Edward Langley","updated_at":"2026-07-30T23:06:31Z","closed_at":"2026-04-09T07:30:19Z","close_reason":"asciinema, vhs, cargo-dist added to flake.nix dev shell and verified. cargo-dist wrapper enables 'cargo dist' subcommand. scripts/record-demo.sh added.","dependency_count":0,"dependent_count":3,"comment_count":0}
{"_type":"issue","id":"improvise-yk4","title":"Phase 2: README and demo artifacts","description":"The main launch work. README is 80% of the launch, demo artifacts are 20%. Covers Nix tooling for asciinema/VHS, writing the README, creating demo GIF and asciinema casts, and verifying all artifacts.","status":"closed","priority":2,"issue_type":"feature","assignee":"spot","owner":"el-github@elangley.org","created_at":"2026-04-09T04:05:49Z","created_by":"Edward Langley","updated_at":"2026-04-14T07:47:43Z","closed_at":"2026-04-14T07:47:43Z","close_reason":"All 5 subtasks closed; artifacts verified present and tracked in git (README.md, docs/demo.gif, docs/demo.tape, docs/casts/*.cast).","dependencies":[{"issue_id":"improvise-yk4","depends_on_id":"improvise-abz","type":"blocks","created_at":"2026-04-08T23:37:37Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-yk4","depends_on_id":"improvise-d4w","type":"blocks","created_at":"2026-04-08T23:37:36Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-yk4","depends_on_id":"improvise-ihv","type":"blocks","created_at":"2026-04-08T23:37:34Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-yk4","depends_on_id":"improvise-odx","type":"blocks","created_at":"2026-04-08T23:37:35Z","created_by":"Edward Langley","metadata":"{}"},{"issue_id":"improvise-yk4","depends_on_id":"improvise-pby","type":"blocks","created_at":"2026-04-08T23:37:36Z","created_by":"Edward Langley","metadata":"{}"}],"dependency_count":5,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-hsoe","title":"check-evidence cannot see a duplicate heading, and three exist","description":"Found by the Wave I agent while merging PR #92, which had accumulated TWO entries headed `### a_window_narrows_the_cells_and_neither_the_totals_nor_the_widths` — the original, describing a fixture that no longer existed and repeating a claim the adversarial review had measured false, plus the superseding one. A reader hitting the first would have taken it as current.\n\n--check-evidence CANNOT SEE THIS. It verifies that a heading names a test that runs, and a duplicate heading names one just as well. Both copies pass.\n\n## Three already on main, verified 2026-09-21\n ### a_layout_change_moves_the_layout_version_and_no_record (test-evidence/command.md)\n ### the_two_front_ends_draw_the_same_column_header (test-evidence/tui.md)\n ### the_whole_frame_is_unchanged (test-evidence/tui.md)\n\nReproduce:\n grep -rhoE '^### [A-Za-z_:][A-Za-z_0-9:]*$' context/test-evidence/*.md | sort | uniq -d\n\n## Why it matters beyond tidiness\ncontext/test-evidence records WHY a test can fail. Two entries under one heading means one of them is stale by construction — they cannot both describe the current test — and nothing indicates which. That is the same class as an evidence entry whose mechanism measurement contradicts (doctrine an_evidence_entry_that_measurement_contradicts_is_worse_than_none): a reader gets a confident wrong answer rather than no answer.\n\n## Acceptance Criteria\n- --check-evidence refuses a duplicate heading, naming the file and both line numbers. It should refuse rather than warn, per the precedent set for ambiguous leaf names in improvise-rm4s.\n- Prove it by injection: add a second copy of an existing heading and watch the checker fail by name.\n- Resolve the three above. Each needs a read to decide which copy is current, or whether they describe genuinely different aspects and want distinct names.\n- Consider whether the same check should apply ACROSS files, not only within one: a heading is keyed to a test name, and test names are unique workspace-wide once module-qualified (improvise-rm4s), so two files claiming the same test is also a defect.\n","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T12:39:28Z","created_by":"Edward Langley","updated_at":"2026-09-21T12:39:28Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-hsoe","title":"check-evidence cannot see a duplicate heading, and three exist","description":"Found by the Wave I agent while merging PR #92, which had accumulated TWO entries headed `### a_window_narrows_the_cells_and_neither_the_totals_nor_the_widths` — the original, describing a fixture that no longer existed and repeating a claim the adversarial review had measured false, plus the superseding one. A reader hitting the first would have taken it as current.\n\n--check-evidence CANNOT SEE THIS. It verifies that a heading names a test that runs, and a duplicate heading names one just as well. Both copies pass.\n\n## Three already on main, verified 2026-09-21\n ### a_layout_change_moves_the_layout_version_and_no_record (test-evidence/command.md)\n ### the_two_front_ends_draw_the_same_column_header (test-evidence/tui.md)\n ### the_whole_frame_is_unchanged (test-evidence/tui.md)\n\nReproduce:\n grep -rhoE '^### [A-Za-z_:][A-Za-z_0-9:]*$' context/test-evidence/*.md | sort | uniq -d\n\n## Why it matters beyond tidiness\ncontext/test-evidence records WHY a test can fail. Two entries under one heading means one of them is stale by construction — they cannot both describe the current test — and nothing indicates which. That is the same class as an evidence entry whose mechanism measurement contradicts (doctrine an_evidence_entry_that_measurement_contradicts_is_worse_than_none): a reader gets a confident wrong answer rather than no answer.\n\n## Acceptance Criteria\n- --check-evidence refuses a duplicate heading, naming the file and both line numbers. It should refuse rather than warn, per the precedent set for ambiguous leaf names in improvise-rm4s.\n- Prove it by injection: add a second copy of an existing heading and watch the checker fail by name.\n- Resolve the three above. Each needs a read to decide which copy is current, or whether they describe genuinely different aspects and want distinct names.\n- Consider whether the same check should apply ACROSS files, not only within one: a heading is keyed to a test name, and test names are unique workspace-wide once module-qualified (improvise-rm4s), so two files claiming the same test is also a defect.\n","status":"closed","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T12:39:28Z","created_by":"Edward Langley","updated_at":"2026-09-21T18:04:26Z","closed_at":"2026-09-21T18:04:26Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-swre","title":"grid_of recomputes an unchanged grid every frame; memoise on the tiers that already exist","description":"Split out of improvise-jmjz on the adversarial review of PR #92, which closed jmjz on its own acceptance criterion (fuse the passes) and argued this is the separate, larger win.\n\ngrid_of recomputes the whole grid from scratch EVERY FRAME, even when nothing moved. After jmjz that is exactly one pass rather than three — 95ms at 18,000 cells, 1.32s at 90,000 — but the common case during scrolling, panel toggling and cursor movement is that the grid has not changed at all.\n\n## Why this is cheap to do here\nThe invalidation signal ALREADY EXISTS and is maintained for other reasons:\n- document_generation, layout_version, record_versions (gotcha 40, ADR 001's three tiers)\n- improvise-mcq4's view_shapes / view_frame observation\n\nA GridBody memo keyed on those takes an unchanged grid to roughly zero.\n\n## Why the remaining pass cannot be narrowed instead\nRuled out twice over, and the review measured it: totals sum EVERY row including ones a viewport scrolled past (gotcha 44), and widths are content-adaptive over every row. The benchmark's windowing ratio stayed 1.2-1.4x precisely because the surviving pass is the one a viewport cannot narrow. improvise-i5tk's decline stands.\n\n## Acceptance Criteria\n- A memo on the projected grid body keyed on the existing invalidation tiers; no NEW tier invented, because a second description of \"has the document changed\" is the disease (see the reasoning behind gotcha 39 and 45, where the trigger is the counter moving rather than a table of command variants).\n- Benchmarked: an unchanged grid across frames should approach zero, and the first frame must not regress.\n- The negative arm matters as much as the positive: a test that a grid which DID change is recomputed. A memo that never invalidates is fast and wrong, and it is the failure mode nothing will notice.\n\n## Second-order, much bigger, not this ticket\n~5us per cell at 18k is dominated by evaluate_aggregated doing a matching_values set intersection per cell (gotcha 18). An incremental aggregate index is the deeper fix. 90,000 cells at 1.32s remains unusable; that case is labelled pathological and memoisation helps it most.\n","status":"open","priority":3,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-21T11:30:15Z","created_by":"Edward Langley","updated_at":"2026-09-21T11:30:15Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-4dpj","title":"The RecordsNormal-to-Normal arm of settle_normal_mode has zero coverage, and it is live","description":"Two gaps in settle_normal_mode's coverage, both live code with zero tests. The second was raised by ChatGPT Codex on PR #91 and is recorded here because the behaviour turned out CORRECT and only the guard is missing.\n\n## Gap 1 — the RecordsNormal-to-Normal arm (found by injection)\nReplacing settle_normal_mode's guard with a one-way version — matching only AppMode::Normal, dropping the RecordsNormal arm — leaves THE ENTIRE WORKSPACE SUITE PASSING.\n\nThe existing crossing test drives the LAYOUT both ways, but both branches end with `enter-mode normal`, so the mode handed to the settle is AppMode::Normal in BOTH directions. It discriminates \"settles at all\" and \"settles against the ending layout\", but not \"settles from either mode\".\n\nThe arm is live, proved reachable in production:\n\n records mode -\u003e apply_effects([SetAxis { \"_Index\", Axis::None }])\n layout.is_records_mode() == false, mode was RecordsNormal\n -\u003e settle yields AppMode::Normal\n\nOne extra case in the existing loop — seed the mode as RecordsNormal, cross OUT — closes it, and the one-way injection must then fail by name.\n\n## Gap 2 — the SwitchView path, raised by Codex and VERIFIED CORRECT\nCodex reviewed PR #91 at c186909 (round 1, the per-effect resolution) and pointed out that SwitchViewAtCursor emits [SwitchView(target), ChangeMode(Normal)]: open the view panel over a records view, select a pivot view, and round 1 would leave RecordsNormal over the pivot grid, with the inverse leaving Normal over records.\n\nRound 2 (a10e95e) moved the settle to ONE call after rebuild_layout at the end of apply_effects, so it resolves against the POST-batch layout. Verified on main at ce895ff with a throwaway probe driving a real `:` line that crosses records-ness within one batch:\n\n start_records=true -\u003e ended_records=false mode=Normal\n start_records=false -\u003e ended_records=true mode=RecordsNormal\n\nCorrect in both directions. So the behaviour Codex named is fixed.\n\nTHE GUARD IS STILL MISSING. Codex asked specifically for \"a regression that switches between records and pivot views through the view panel\", and no such test exists. The path it names is different from the one the crossing test drives (toggle-records-mode, not SwitchView), and records-ness is auto-detected from the axes (gotcha 1), so a view switch can change it.\n\n## Acceptance Criteria\n- The gap-1 case added to the existing loop; the one-way-guard injection fails by name.\n- A regression driving SwitchViewAtCursor over the view panel, records view to pivot view AND back, asserting the mode matches the grid the batch ends on.\n- Both must fail if the settle is moved back before rebuild_layout.\n","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T11:29:18Z","created_by":"Edward Langley","updated_at":"2026-09-21T17:13:17Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-26dm","title":"The fourth length-not-completeness list: cases.len() == 13 in ui/effect.rs","description":"improvise-5ubs is closed for the two CYCLIC all() functions, by reading serde's\nderive-generated variant list out of its \"unknown variant ... expected one of\"\nmessage and diffing it against the names the cycle reached. That closes the\nAppMode and Rejection halves and the mechanism generalises to any serde enum.\n\nIt does NOT reach the FOURTH instance 5ubs records in its notes:\nlayout_geometry_effects_actually_change_document_config in\ncrates/improvise-command/src/ui/effect.rs, whose assert_eq!(cases.len(), 13) is\nover a hand-built list of EFFECTS, not of enum variants. serde has nothing to\nsay about it: an effect is a struct implementing a trait, so there is no derived\nenumeration to diff against.\n\nThe general statement stands and is the reason this is worth its own ticket:\ndoctrine(a_list_checked_for_length_reads_like_a_list_checked_for_completeness).\nA count catches a case being DELETED from the list and never one being ABSENT\nfrom it, and the two are written identically as an assert_eq over a number. The\nadversarial review of PR #78 added a fourteenth pub struct declaring\nEffectKind::Model(ModelChange::LayoutGeometry), pointed at by nothing, and the\nwhole suite stayed green.\n\nWhat would close it: an enumeration of the effects the compiler maintains. The\ncandidates are a proc-macro over the impl blocks, or inventory-style\nregistration, or reading the Debug names off a derived list the way\nevery_model_command_names_the_effect_of_the_same_name already does for the\nModelCommand mapping -- that last one is the closest existing precedent in this\nrepo and is worth looking at first.\n\nNot fixed in wave I because ui/effect.rs was another wave's ground that round,\nand because a fourth hand-rolled attempt in a fourth place is the disease 5ubs\nnames.\n","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T11:09:56Z","created_by":"Edward Langley","updated_at":"2026-09-21T11:09:56Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-4dpj","title":"The RecordsNormal-to-Normal arm of settle_normal_mode has zero coverage, and it is live","description":"Two gaps in settle_normal_mode's coverage, both live code with zero tests. The second was raised by ChatGPT Codex on PR #91 and is recorded here because the behaviour turned out CORRECT and only the guard is missing.\n\n## Gap 1 — the RecordsNormal-to-Normal arm (found by injection)\nReplacing settle_normal_mode's guard with a one-way version — matching only AppMode::Normal, dropping the RecordsNormal arm — leaves THE ENTIRE WORKSPACE SUITE PASSING.\n\nThe existing crossing test drives the LAYOUT both ways, but both branches end with `enter-mode normal`, so the mode handed to the settle is AppMode::Normal in BOTH directions. It discriminates \"settles at all\" and \"settles against the ending layout\", but not \"settles from either mode\".\n\nThe arm is live, proved reachable in production:\n\n records mode -\u003e apply_effects([SetAxis { \"_Index\", Axis::None }])\n layout.is_records_mode() == false, mode was RecordsNormal\n -\u003e settle yields AppMode::Normal\n\nOne extra case in the existing loop — seed the mode as RecordsNormal, cross OUT — closes it, and the one-way injection must then fail by name.\n\n## Gap 2 — the SwitchView path, raised by Codex and VERIFIED CORRECT\nCodex reviewed PR #91 at c186909 (round 1, the per-effect resolution) and pointed out that SwitchViewAtCursor emits [SwitchView(target), ChangeMode(Normal)]: open the view panel over a records view, select a pivot view, and round 1 would leave RecordsNormal over the pivot grid, with the inverse leaving Normal over records.\n\nRound 2 (a10e95e) moved the settle to ONE call after rebuild_layout at the end of apply_effects, so it resolves against the POST-batch layout. Verified on main at ce895ff with a throwaway probe driving a real `:` line that crosses records-ness within one batch:\n\n start_records=true -\u003e ended_records=false mode=Normal\n start_records=false -\u003e ended_records=true mode=RecordsNormal\n\nCorrect in both directions. So the behaviour Codex named is fixed.\n\nTHE GUARD IS STILL MISSING. Codex asked specifically for \"a regression that switches between records and pivot views through the view panel\", and no such test exists. The path it names is different from the one the crossing test drives (toggle-records-mode, not SwitchView), and records-ness is auto-detected from the axes (gotcha 1), so a view switch can change it.\n\n## Acceptance Criteria\n- The gap-1 case added to the existing loop; the one-way-guard injection fails by name.\n- A regression driving SwitchViewAtCursor over the view panel, records view to pivot view AND back, asserting the mode matches the grid the batch ends on.\n- Both must fail if the settle is moved back before rebuild_layout.\n","status":"closed","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T11:29:18Z","created_by":"Edward Langley","updated_at":"2026-09-21T18:04:27Z","closed_at":"2026-09-21T18:04:27Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-26dm","title":"The fourth length-not-completeness list: cases.len() == 13 in ui/effect.rs","description":"improvise-5ubs is closed for the two CYCLIC all() functions, by reading serde's\nderive-generated variant list out of its \"unknown variant ... expected one of\"\nmessage and diffing it against the names the cycle reached. That closes the\nAppMode and Rejection halves and the mechanism generalises to any serde enum.\n\nIt does NOT reach the FOURTH instance 5ubs records in its notes:\nlayout_geometry_effects_actually_change_document_config in\ncrates/improvise-command/src/ui/effect.rs, whose assert_eq!(cases.len(), 13) is\nover a hand-built list of EFFECTS, not of enum variants. serde has nothing to\nsay about it: an effect is a struct implementing a trait, so there is no derived\nenumeration to diff against.\n\nThe general statement stands and is the reason this is worth its own ticket:\ndoctrine(a_list_checked_for_length_reads_like_a_list_checked_for_completeness).\nA count catches a case being DELETED from the list and never one being ABSENT\nfrom it, and the two are written identically as an assert_eq over a number. The\nadversarial review of PR #78 added a fourteenth pub struct declaring\nEffectKind::Model(ModelChange::LayoutGeometry), pointed at by nothing, and the\nwhole suite stayed green.\n\nWhat would close it: an enumeration of the effects the compiler maintains. The\ncandidates are a proc-macro over the impl blocks, or inventory-style\nregistration, or reading the Debug names off a derived list the way\nevery_model_command_names_the_effect_of_the_same_name already does for the\nModelCommand mapping -- that last one is the closest existing precedent in this\nrepo and is worth looking at first.\n\nNot fixed in wave I because ui/effect.rs was another wave's ground that round,\nand because a fourth hand-rolled attempt in a fourth place is the disease 5ubs\nnames.\n","status":"closed","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T11:09:56Z","created_by":"Edward Langley","updated_at":"2026-09-21T18:04:28Z","closed_at":"2026-09-21T18:04:28Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-e8no","title":"A wide numeric category now refuses a fit that used to ignore it","description":"improvise-zv8t forces every document category to `Forced::Categorical`, which is\nright (a `Year` category with items 2024/2025 is a coordinate, not a number).\nThe cost: a WIDE numeric category one-hots past `MAX_FEATURES` and refuses the\nwhole fit, where before it encoded as one numeric feature and a fit that never\nsplit on it succeeded.\n\nMeasured on 100 records with a 100-item numeric `Year` beside a `Cost` that\npredicts `Score` on its own:\n- pre-zv8t: fit lands on `IF(Cost \u003c 48.015...)`; `Year` never appears.\n- post-zv8t: preflight refuses at 101 features.\n\nPinned by `a_wide_numeric_category_now_refuses_a_fit_that_used_to_ignore_it`\nin crates/improvise-gp/src/fit.rs, and argued in context/code/gp.md section 11.\n\nThe workaround the refusal names works: mark the category a Label and the\npre-zv8t feature set is restored exactly. What is lost is the case where the\nuser wants neither -- a wide numeric category left alone while the fit uses the\nmeasures.\n\n## Steps to Reproduce\n\nBuild a model with a Regular category `Year` holding 100 numeric items\n(1900..1999), a Label category for record identity, and two measures `Cost` and\n`Score` where Score = 4000 if Cost \u003e 50 else 1000. Put `Year` on Axis::Row and\n_Measure on Axis::Page. Run `fit_measure(.., \"Score\", ..)`.\n\nPre-zv8t it lands on `IF(Cost \u003c 48.015..)`. Post-zv8t it is refused with\n\"would encode 101 features from 100 records\". The fixture is\n`wide_category_beside_a_good_measure()` in crates/improvise-gp/src/fit.rs.\n\n## Acceptance Criteria\n\nThe honest fix is a budget that can decline A COLUMN rather than the whole\ntable: `encoded_width` (and the exact check beside it) would drop the widest\ncategory and report it as a `Dataset::warnings` notice -- gotcha 37(a), it comes\nback as DATA and nothing prints. That keeps the zv8t decision intact (a category\nis still never inferred numeric) and changes only what the budget does when it\ncannot afford one.\n\nDo NOT close this by making the forcing conditional on cardinality: that is\ninference by another name, and makes the ENCODING depend on a budget rather than\non what the document says.\n","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T10:55:32Z","created_by":"Edward Langley","updated_at":"2026-09-21T10:55:39Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-sxsf","title":"cargo test -- --list cannot name the binary, so two crates could share a full test path","description":"MEASURED 2026-09-21 (PR #88). Re-verified latent: 1444 names, zero duplicate full paths, zero duplicate (crate, path) pairs.\n\nTHE PROPOSED FIX IS DECLINED ON TWO INDEPENDENT GROUNDS, and the second is the interesting one:\n\n1. COST: 45.2s against 2.4s for the one workspace call (11 members, warm) — 18x. And --check-evidence needs the --ignored twin, so the honest figure is 22 cargo invocations.\n\n2. IT DOES NOT CLOSE THE CLASS IT NAMES. A package is not a binary. The improvise crate alone has FIVE integration test binaries beside its lib, and an integration test's top-level fn lists with NO MODULE PATH AT ALL — 60 of 1444 names are bare. So '-p improvise' prefixes all six identically and tests/a.rs::foo still cannot be told from tests/b.rs::foo.\n\nAny real fix is per-TARGET (--lib, --test \u003cname\u003e), not per-package. Whoever takes this should start there.\n\nWorth noting how this was found: the agent set out to time the per-package fix and discovered it does not work while reading the target list. The cost measurement was the excuse for reading it; the correctness finding was the payoff — an argument for measuring declines rather than asserting them.","status":"open","priority":3,"issue_type":"bug","owner":"el-github@elangley.org","created_at":"2026-09-21T04:49:16Z","created_by":"Edward Langley","updated_at":"2026-09-21T05:23:48Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"improvise-8shu","title":"Constrain the .improv grammar generator's strategy; improvise-io's lib binary is the run's outlier","description":"Split out of improvise-8ec3, whose second half (stop pinning one machine's number) is done. This is the first half, left open.\n\nimprovise-io's lib binary is an order of magnitude above every other binary in the run on every machine it has been measured on: 1.40-1.45s over three runs here (vs a ~0.06s median), 3.70-5.24s on the machine that filed 8ec3. The ratio is the portable signal; the seconds are not.\n\nIts slow half is the grammar-walking generator in persistence/parse.rs plus the .improv roundtrip proptests at the default 256 cases. A grammar walk with no size bound generates deeply nested documents whose cost is not uniform across cases, so a few cases dominate.\n\ndoctrine(proptest_cases) rules out the cheap fix: constrain the strategy, do NOT lower the count below 256.\n\nAcceptance: improvise-io's lib binary is no longer an order of magnitude above the run's median, with the case count still 256 and the properties still covering groups, formulas, views, hidden items and pipe-quoting edges. Report the before/after ratio, not seconds.","status":"closed","priority":3,"issue_type":"task","owner":"el-github@elangley.org","created_at":"2026-09-21T04:14:01Z","created_by":"Edward Langley","updated_at":"2026-09-21T05:15:42Z","closed_at":"2026-09-21T05:15:42Z","dependency_count":0,"dependent_count":0,"comment_count":0}
+1
View File
@@ -300,6 +300,7 @@ doctrine(cargo_tree_shows_dev_edges_by_default,'Asking whether a feature leaked
doctrine(an_unreachability_claim_is_only_as_wide_as_the_routes_enumerated,'PR #98 predicted the composition-time mode drift itself and declared it unreachable BECAUSE the toggle route (R) is a stranding decline in the browser. True of that route. A VIEW SWITCH reached the same drift, because records-ness is auto-detected from the axes, so a switch changes it with nothing in the batch saying \"records\" -- found by ChatGPT Codex. The ENUMERATION of routes is the part nothing checks, so an unreachability argument is evidence about the routes you thought of and about nothing else. Same shape as a_mitigation_that_names_the_wrong_file_is_not_a_mitigation. The remedy is to close the CLASS by observation rather than to patch the route.').
doctrine(a_precondition_derived_from_its_own_expectation_guards_nothing,'A loop test whose precondition is computed from the same tuple element as the assertion cannot be relaxed one iteration at a time: relaxing the assertion relaxes the guard too, and the test then fails for the wrong reason -- which reads exactly like the bug. Assert the two as SEPARATE facts. Corollary for check_the_checker over a loop: an injection that stops the loop at iteration 1 leaves every later iteration untested, so each direction must be reached deliberately. Found by the PR #98 author in its own bidirectional test while proving the second direction.').
doctrine(a_pipe_discards_the_exit_code_you_meant_to_read,'FOURTH occurrence in one campaign, and the last one was in a tool written TO BE the reliable answer. scripts/pr-watch once read tr exit status through a pipe. An orchestrator probe reported EXIT=0 from a checker that had exited 1, because the pipeline ended in tail. Another read PIPESTATUS in zsh, where the array is lowercase pipestatus. And scripts/pr-reviews --wait correctly exited 1 on a timeout while the invocation \`... | tail -20; echo\` reported 0 -- so a review window that had EXPIRED read as a review that had ARRIVED, which is the dangerous direction. In bash and zsh the exit status of a pipeline is the LAST command, so any \`cmd | head\`, \`| tail\`, \`| grep\` discards it. Redirect to a file and read the file, or check PIPESTATUS[0] in bash / pipestatus[1] in zsh -- and note the two shells differ, which is its own trap. A gate whose status you sampled through a pipe has told you about the pager.').
doctrine(editing_a_shell_script_corrupts_the_instances_already_running_it,'bash reads a script INCREMENTALLY rather than loading it whole, so editing a file while an instance is executing it shifts the byte offsets underneath that instance. Two background pr-reviews watchers, started ten minutes earlier and sleeping in their poll loops, both died with \"syntax error near unexpected token do\" the moment the orchestrator appended a paragraph to the usage text -- and the script itself was syntactically fine, which bash -n confirmed. The failure is in the RUNNING instance, not the file, so it is invisible to every check you would think to run afterwards. Before editing a script, ask what is still executing it; a long-poll watcher is exactly the shape that will be. Worse in this case than a crash: the watcher exited having printed only the EARLIER review, so the wait it was supposed to perform silently did not happen.').
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.').