test(40): capture a launcher minted by today's mint as the pre-phase fixture - #43
Conversation
Issue #40's first acceptance criterion, verbatim: "A launcher minted by today's mint is committed as a fixture, in a commit that changes nothing else, before either phase begins." This commit is that criterion. It adds exactly one file and touches no code, no template and no test, so the fixture provably predates both phases. Capturing it after phase 2 would assert the new emitter against the new parser, which passes whether or not backwards compatibility survived. Provenance — reproducible at the base commit: base: 154b9b6 (origin/main) build: cargo build --release -p launch-scaffolder invocation: ./target/release/launch-scaffolder mint \ examples/stapeln.launcher.fixture.a2ml --stdout result: rc=0, 471 lines, @a2ml-metadata block at lines 5-22 The input config examples/stapeln.launcher.fixture.a2ml is already committed, so nothing outside the repository is needed to reproduce it. Committed 100644: the fixture is read by tests, never executed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WPSJ7fBhVAMcpSffCBWUDo Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Advanced Run ID: No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📜 Recent review details⏰ Context from checks skipped due to timeout. (11)
🧰 Additional context used🪛 Shellcheck (0.11.0)crates/launcher-common/tests/fixtures/metadata_block/minted-2026-09-22_stapeln-launcher.sh[warning] 56-56: APP_PORT appears unused. Verify use (or export if used externally). (SC2034) [style] 94-94: See if you can use ${variable//search/replace} instead. (SC2001) [warning] 315-315: Declare and assign separately to avoid masking return values. (SC2155) 🔇 Additional comments (1)
📝 SummarySummary by CodeRabbit
WalkthroughThis change adds a generated Bash launcher fixture for the Stapeln app. The script includes metadata, platform detection, server start and stop logic, browser opening, Linux desktop integration and removal, help output, and command-line mode dispatch. ChangesLauncher fixture script
Priority: ⬇️ Low Estimated code review effort: 4 (Complex) | ~40 minutes Change: Other Sequence Diagram(s)sequenceDiagram
participant User
participant LauncherScript
participant ServerProcess
participant URLProbe
participant Browser
User->>LauncherScript: run --auto
LauncherScript->>ServerProcess: start selected startup command
LauncherScript->>URLProbe: poll URL with curl
URLProbe-->>LauncherScript: reachable or timeout
LauncherScript->>Browser: open configured URL
Merge Risk: ⚪ Minimal · up to This adds a raw launcher fixture without changing shipped runtime behavior. No current merge-blocking risk is established; it is ready to merge after normal checks. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. I minted a launcher with a careful hop, Comment |
| APP_PORT="4010" | ||
| WAIT_SECONDS="15" | ||
|
|
||
| PID_FILE="/tmp/stapeln-server.pid" |
| WAIT_SECONDS="15" | ||
|
|
||
| PID_FILE="/tmp/stapeln-server.pid" | ||
| LOG_FILE="/tmp/stapeln-server.log" |
|
🤖 Completed: Fix pre-merge checks in PR #43 — View commit |
Phase 1 of #40: a compat reader that accepts the `@launcher-deed` dialect alongside the `@a2ml-metadata` block every launcher minted to date carries. **The emitter is untouched.** Phase 2 switches `mint`, gated on a tagged release carrying this — per the owner's ruling that "baked" means a tagged release, not a merge to `main`. Stacked on #43, which supplies the fixture. GitHub retargets this to `main` when #43 merges. ## ⚠ This repo has no Rust CI, so a green check here proves nothing Measured: `.github/workflows/` holds six files — `codeql.yml`, `governance.yml`, `hypatia-scan.yml`, `labels.yml`, `label-triage.yml`, `push-email-notify.yml` — and `grep -rn 'cargo test\|cargo nextest'` over that directory returns **zero**. No workflow builds or tests the crates. Whatever goes green on this PR is governance and scanning, not Rust. So the local run below is the only real evidence, and it is quoted rather than summarised. Filed as #45, with acceptance criteria, per the standing rule that a new finding is an issue, not a merge blocker. ``` running 66 tests (lib) test result: ok. 66 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out running 5 tests (tests/deed_corpus.rs) test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out running 4 tests (tests/round_trip.rs) test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ``` `cargo clippy --all-targets -- -D warnings` is clean. ## A passing suite proves nothing until a mutant dies #40 asks for exactly this. Eight mutants, each applied to a clean tree and reverted after: | # | mutation | result | |---|---|---| | A | legacy (backwards-compat) arm returns `Ok(None)` | **8 tests red**, incl. `the_committed_legacy_fixture_still_parses` | | B | deed arm returns `Ok(None)` | **8 tests red**, incl. `a_deed_dialect_block_parses` | | C | `app-display` mapped to the deed's `:name` instead of `:display` | `both_dialects_flatten_to_the_same_values` **red**; `both_dialects_agree_on_what_todays_emitter_produces` **red** | | D | `node.head != "praxis-deed"` check removed | `a_deed_block_that_is_not_a_praxis_deed_is_rejected` **red** | | E | `:standards` string-count strictness check removed | **survived at first — see below** | | F | `:beholding-chora` requirement removed | `a_deed_block_without_beholding_chora_is_rejected` **red** | | G | `rewrite_scalar`'s deed guard removed | `rewrite_scalar_refuses_a_deed_block_rather_than_corrupting_it` **red** | | H | both-dialects rejection falls through to legacy | `a_script_carrying_both_dialects_is_rejected` **red** | **Mutant E survived the first pass, and that was a real gap.** `Value::str_list()` skips non-strings *silently*, so a symbol or integer smuggled into `:standards` would vanish from the compliance claim. The reader compares `str_list().len()` against `as_list().len()` for exactly that — but nothing tested it, so the comparison was dead code that would have shipped looking correct. Added `a_non_string_entry_in_standards_is_reported_not_silently_dropped`; mutant E now dies. That test exists *because* the mutant survived, which is the whole argument for running them. Worth recording: removing the both-dialects arm outright (mutant H's first form) does not compile — the match is exhaustiveness-checked. H was re-run as a semantic mutant instead, which is the honest test. ## Criterion 5 — the round trip, delivered not deferred `mint → parse → realign → parse`, on both forms, in `crates/launcher-common/tests/round_trip.rs`. No CLI needed: `realign` has no rewrite path in phase 1 — `cmd_realign.rs:156` calls `template::render` and `:172` writes the result — and `template::render` is public, so the test drives the identical call `realign_one` makes. - `mint_parse_realign_parse_is_stable_for_the_legacy_form` — also asserts the two renders are byte-identical, which is what `Outcome::Unchanged` (`cmd_realign.rs:163`) is decided on. If that ever stops holding, `realign` rewrites every launcher on every run. - `both_dialects_agree_on_what_todays_emitter_produces` — the deed sample is **generated from the live emitter's own output**, not typed by hand, so it moves when the emitter moves. This is the "on both forms" half. - `realign_cannot_edit_a_deed_launcher_in_place` - `phase_one_mint_emits_the_legacy_markers_and_not_the_deed_ones` — states the no-emitter-change claim as a test, so phase 2 has to delete it deliberately. The generator carries a guard that panics if `mint` ever emits a scalar the deed dialect has no slot for — silently dropping one is precisely the phase-2 regression this change exists to prevent. Verified non-vacuous: removing `generator` from that match list turns two tests red with the intended message. ## Why the deed form is not re-scanned here The `#` prefix is stripped and the text handed to `deed::parse`, the normative grammar. A second s-expression scanner in this file would be cheaper today and would manufacture a second DEED grammar that nothing forces to agree with `deed.rs` — it would keep passing while being wrong the moment the normative grammar moves. Delegating gets the tab check, escape handling, `#u5` literals, `#t`/`#f`, and list-vs-clause disambiguation for free. `the_real_deed_grammar_is_the_one_enforcing_the_block` proves the delegation is real: a literal tab inside the block must produce the `HTAB` diagnostic, a rule only the real grammar knows. A hand-rolled scanner here would accept it. ## Scope ``` $ git diff --name-only origin/main crates/launcher-common/src/metadata_block.rs crates/launcher-common/tests/fixtures/metadata_block/minted-2026-09-22_stapeln-launcher.sh (#43) crates/launcher-common/tests/round_trip.rs ``` Zero `.tera` files. Zero diff in `cmd_config.rs`, `config.rs`, `discovery.rs` and `template.rs` — `config.rs`/`discovery.rs` are standards#960, not this. `is_deed()` is derived from the captured marker line rather than stored as a struct field, so there is no API break and it cannot desync from `raw_lines`. The new marker deliberately does not say `a2ml`: A2ML is retired, and a marker carrying the name would propagate it into every launcher minted from here on. ## One unrelated observation `cargo fmt --check` is red on `main` today, independent of this PR — rustfmt collapses a five-line `app_license` insert in `template.rs` to one. Running `cargo fmt --all` in any PR silently pulls that into the diff; reverted here so the "no emitter change" claim stays clean. Not fixed here — it is not this PR's to carry. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WPSJ7fBhVAMcpSffCBWUDo Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Phase 2 of #40. Phase 1 shipped the compat reader; this converts the emitter, so `mint` now writes `@launcher-deed` instead of the retired `@a2ml-metadata` form. Closes the emitter half of #40. Follows #43 (the pre-phase fixture), which this change depends on and which made the strongest test here possible. ## It is a pure dialect change, and that is what makes the test strong The compliance data is byte-identical in both dialects. So the central test is not "the new block parses" — it is that the new emitter's flattened output is **equal to the committed pre-phase fixture's**, field for field: ```rust assert_eq!(legacy.scalars, minted.scalars); assert_eq!(legacy.lists, minted.lists); ``` A launcher minted today and one minted before the change carry the same values. That is the compat promise, asserted against an artefact rather than a reconstruction. ## The escape filter is the exact inverse of its consumer, deliberately no stricter `deedstr` was written against `deed.rs`'s `lex_string`, not against intuition: | character | grammar | filter | |---|---|---| | `"` `\` `\n` `\t` | the only four legal escapes | escaped | | other `< U+0020` | rejected by the lexer | **reported as an error** | | **U+007F (DEL)** | **accepted** — the check is `< 0x20` | **passed through** | A filter rejecting U+007F would be stricter than the parser it feeds — the estate's most-recurring trap, a guard asking a different question than its consumer. CR is the opposite case: it has *no* legal spelling in a deed string at all, so it is an error rather than a silent drop. Refusing to mint beats minting a launcher whose own metadata cannot be read back. ⚠ The tab case reaches furthest. `deed::parse` rejects a literal HTAB **anywhere in the document**, tested on the raw text before lexing — so a tab surviving into a value would not corrupt one field, it would make the whole block unparseable. The emitted template is verified to contain zero literal tabs. Registered as a Tera filter rather than pre-escaping the context, so the escape applies at exactly the emission sites and every other interpolation in the script keeps its raw value. (Tera autoescaping is HTML-shaped and does not fire for `.sh` in any case — without this, a display name holding one `"` closes the string early.) ## `:standards` — measured, not assumed An earlier review flagged that the emitted block claims compliance with `launcher-standard.adoc` while D73-C converted that standard to `.deed`, so a fresh mint might fail AC4's currency gate. Measured instead: - `docs/UX-standards/launcher-standard.adoc` **exists** on `standards` `main` (it was the `.a2ml` that was deleted), as does `LM-LA-LIFECYCLE-STANDARD.adoc`. - `scripts/check-launcher-standard-currency.sh:34-37` states verbatim that `launcher-standard.adoc` is *"a DIFFERENT document (the human-readable UX standard) and is deliberately NOT checked here."* - The gate's canonical file is `launcher-standard_praxis.deed` at `0.4.0`. So the block is correct as emitted and the gate exempts it by design. No change needed, and no owner ruling required. ⚠ `:schema-version` is the **grammar** version (`1.0.0`); `:standard-version` is the **document** version, read live from the real deed via `standard.rs:100-108` — not hardcoded, and not interchangeable with the former. ## `config set` now refuses on freshly-minted launchers — by design, and owner-ruled `cmd_set` calls `rewrite_scalar`, which declines to edit a DEED form in place. Phase 2 therefore changes observable CLI behaviour: `config set` hard-fails on every launcher minted from here on. **Put to the owner and ruled: leave it refusing.** A deed block is a generated artefact; in-place scalar surgery on it is how you get a launcher whose metadata no longer parses. The config file stays the single source of truth and re-mint is the only edit path. Zero new code — the refusal is already covered by a test. `cmd_config.rs`'s user-facing strings are corrected to match: the "no `@a2ml-metadata` block found" message named only the retired dialect while the reader has accepted both since phase 1. ## Four mutants killed A passing suite proves nothing until a mutant dies. | mutant | result | |---|---| | **A** `deed_escape` made the identity function | 4 tests red | | **B** emitter drops one compliance standard | 1 test red — the paired control alone, proving it checks the **data**, not merely that two things parsed | | **C** emitter reverts to the legacy markers | 4 tests red | | **D** emitter changes the `:generator` value | 2 tests red | | clean restore | 72/72 — so every red above is attributable to the mutant, not ambient state | ⭐ **Mutant D found something worth reporting.** `both_dialects_agree_on_what_todays_emitter_produces` stayed **green** under it, because it generates its legacy leg *from* the deed the emitter just produced — so under a uniform value change both legs move together and the equality still holds. It proves the transform, never the value. That is not a defect (it exists to prove cross-dialect agreement, and it does), but it means the committed fixtures are the only thing anchoring the emitter to a known-good value set. Hence the second commit. ## Two committed fixtures, one facing each way - `minted-2026-09-22_stapeln-launcher.sh` (legacy, from #43) — proves a launcher minted **before** the change still reads. **Not edited by this PR.** - `minted-2026-09-23_stapeln-launcher-deed.sh` (new) — proves a launcher minted **during** it still reads after some later tightening of the DEED grammar. `deed::parse` is shared with the estate and will keep moving; launchers already written will not. Both captured from the real emitter, neither hand-typed, neither derived from the other. ## ⚠ The two red phase-1 guards were inverted deliberately `round_trip.rs` carried `phase_one_mint_emits_the_legacy_markers_and_not_the_deed_ones`, whose own doc comment reads *"Stated as a test rather than left to the diff, so phase 2 has to delete this line deliberately."* It did its job. Both it and `mint_parse_realign_parse_is_stable_for_the_legacy_form` now assert the phase-2 contract, pointing the other way, so a revert has to delete them just as deliberately. Two **further** tests in that file had gone green-but-degenerate rather than red — subtler, and easy to miss. Since `mint()` now returns a deed, their variable named `legacy` held a *deed* block, so "both dialects agree" was comparing deed against deed. The legs are inverted. ## Verification ``` cargo test --workspace -> 72 + 5 + 6 = 83 passed, 0 failed cargo clippy --workspace --all-targets -> rc=0, zero warnings rustfmt --check on all touched files -> 0 diffs ``` ⚠ **`launch-scaffolder` has no Rust CI** (#45) — six workflows, not one runs cargo, so 18 `.rs` files and 59+ `#[test]` fns have never executed in CI. The local output above is the evidence for this PR, and that is a statement of what is *not* proven here, not a claim of coverage. Incidental: this also cures one pre-existing `rustfmt` drift in `template.rs` (the `app_license` chain) that was already red on `main`, in a file this PR is editing anyway. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WPSJ7fBhVAMcpSffCBWUDo --------- Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
#40's criteria are all implemented in code shipped by PRs #43, #44 and #46. What the issue still asks for is the write-up: the measurements, taken on a named commit, with the numbers attached. Recorded here: * the fixture predates both phases, and still carries artefacts no post-phase emitter can produce; * PR #44 touched only metadata_block.rs and round_trip.rs — the template is absent from its file list, which is the "phase 1 shipped alone" claim, checked rather than remembered; * the phase-1 fixture test after phase 2, including the one assertion that had to change and why that is not a compatibility break: every value assertion is untouched and still passes, and the restored phase-1/2-era assertion fails naming exactly the four fields #41 started enforcing; * the eight round-trip tests and what each pins; * the mutant kill — reverting the legacy arm of parse_from_text to `return Ok(None)` fails 11 tests, reverting that restores 98 passing. Two things are recorded as still open rather than quietly omitted: #45 AC4 (actions.lock could not be generated here) and the `--version` gap between the generated launcher and the standard's (required-modes). Closes #40 Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Closes the first acceptance criterion of #40, and nothing else.
Why this is its own PR, not the first commit of phase 1
#36 was squash-merged. A squash collapses a branch into one commit, so "a
commit that changes nothing else" would stop existing the moment phase 1
landed on top of it. The criterion is checked with
git diff --name-onlyagainst
main, so the fixture has to reachmainas its own commit —which means its own PR.
The ordering is the whole point of the criterion. From #40:
Provenance — reproducible from this repository alone
The fixture is the real output of the real binary at the base commit. It was
not hand-filled from the template.
The input,
examples/stapeln.launcher.fixture.a2ml, is already committed, sothere is no external input to lose.
What it captures
Verification
git show --stat HEAD→ 1 file changed, 471 insertions. No code, notemplate, no test.
100644— the fixture is read by tests, never executed, so it isdeliberately not marked executable.
anywhere in a deed, so the phase-1 bridge could not accept this block if it
carried one.
Scope
No test asserts this fixture yet — that is criterion 2, and it belongs to
phase 1. Shipping the assertion here would mean the fixture and its test
arrive together, which is exactly the coupling criterion 1 exists to prevent.
Out of scope per #40:
config.rs,discovery.rs,cmd_config.rs(tracked ashyperpolymath/standards#960), and the dated audit records underdocs/ruleset-audit-2026-04-10/.🤖 Generated with Claude Code
https://claude.ai/code/session_01WPSJ7fBhVAMcpSffCBWUDo