Skip to content

feat(40): read both the legacy and DEED launcher metadata dialects - #44

Merged
hyperpolymath merged 1 commit into
feat/40-metadata-block-fixturefrom
feat/40-compat-deed-reader
Sep 22, 2026
Merged

hyperpolymath merged 1 commit into
feat/40-metadata-block-fixturefrom
feat/40-compat-deed-reader

Conversation

@hyperpolymath

@hyperpolymath hyperpolymath commented Sep 22, 2026 •

Copy link
Copy Markdown
Owner

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.ai/code/session_01WPSJ7fBhVAMcpSffCBWUDo

Phase 1 of launch-scaffolder#40: the reader accepts a `@launcher-deed`
block alongside the `@a2ml-metadata` one every launcher minted to date
carries. The emitter is untouched — phase 2 switches `mint`, gated on a
tagged release carrying this.

Both dialects flatten to the same `MetadataBlock`, so every existing
caller is dialect-blind. That equality is asserted directly rather than
via two parallel assertion lists: if the scalars and lists match, no
caller can tell the forms apart.

The DEED form is not re-scanned here. The `#` comment 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. A test proves the delegation is real: a tab
inside the block must produce the `HTAB` diagnostic, a rule only the
real grammar knows.

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.

`rewrite_scalar` refuses a DEED block rather than editing it. Its
scanner looks for `key = "value"` and a quoted span; handed a deed form
it would match nothing, or worse, the wrong quotes. An error is the
honest outcome — the caller never gets a corrupted launcher.

A script carrying both marker pairs is rejected rather than silently
preferring one: two blocks mean two answers to the same question.

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>
@coderabbitai

coderabbitai Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: e29efbd2-20e4-4e79-a05d-a3471b518f92

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hyperpolymath

Copy link
Copy Markdown
Owner Author

⚠ Do not read this PR's two green checks as a pass — 14 checks that run on #43 never run here.

Measured just now:

PR base checks reported
#43 main 16
#44 (this one) feat/40-metadata-block-fixture 2 (CodeRabbit "review skipped", GitGuardian)

The cause is in .github/workflows/governance.yml on main:

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

A PR whose base is a feature branch does not match pull_request: branches: [main, master], so the entire governance suite — and CodeRabbit, which reports "reviews are disabled for this base branch" — simply never fires. An absent check reads as ABSENT, not as failing, which is exactly the shape that makes a stacked PR look cleaner than a direct one.

Two consequences, both benign but worth stating rather than discovering:

  1. This resolves itself on merge of test(40): capture a launcher minted by today's mint as the pre-phase fixture #43. GitHub auto-retargets feat(40): read both the legacy and DEED launcher metadata dialects #44 to main, and the full suite then runs against it before it can land. Nothing needs doing here.
  2. It compounds #45 — there is no Rust CI at all, so even the 16 checks on test(40): capture a launcher minted by today's mint as the pre-phase fixture #43 never compile or test this crate. The cargo test / cargo clippy output quoted in the PR body above is local, and remains the only evidence that this code runs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WPSJ7fBhVAMcpSffCBWUDo

@hyperpolymath

Copy link
Copy Markdown
Owner Author

Correction to my previous comment — "nothing needs doing here" was wrong

My earlier note ended by saying this PR's missing checks self-resolve on merge of #43, so nothing needs doing here. The first half is right; the second is not, and the merge procedure gains steps because of it. Correcting it rather than leaving a false reassurance standing.

What I re-measured

claim verdict
merging #43 auto-retargets this PR to main ✅ holds — delete_branch_on_merge is true on this repo, so feat/40-metadata-block-fixture is deleted on merge, and GitHub retargets dependent PRs when their base branch is deleted
…and the 16 checks then run ❌ does not follow

governance.yml declares pull_request: with no types:, so it takes the default activity set — opened, synchronize, reopened. A base-branch change is the edited activity, which that default excludes. The PR flips to base main and the check count can stay at 2, which looks exactly like the thing this comment exists to warn about: absent, not passing.

It is worse than "14 of 16 missing"

The two checks that do run here are CodeRabbit and GitGuardian Security Checks — both third-party GitHub Apps, neither of which honours a workflow base filter. Every first-party workflow in this repo that triggers on pull_request filters to main/master:

# governance.yml        pull_request: branches: [main, master]
# codeql.yml            pull_request: branches: [main]
# hypatia-scan.yml      pull_request: branches: [main, master]

So the accurate statement is: no CI defined in this repository has run against this PR at all. The two green checks are entirely external.

⚠ And CodeRabbit's check reports conclusion SUCCESS while its own review text says "Review skipped: reviews are disabled for this base branch." A skipped review reporting green is the same ABSENT-as-PASS shape one layer up.

Merge procedure for this PR

  1. Merge test(40): capture a launcher minted by today's mint as the pre-phase fixture #43 first.
  2. Confirm this PR retargeted: gh pr view 44 --json baseRefName. If it still names the feature branch, set it: gh pr edit 44 --base main.
  3. Fire a synchronize so the workflows actually evaluate — gh pr update-branch 44 (a merge, not a force-push), or any push to the head branch. A close/reopen also works.
  4. Require checks = 16, all terminal-success, before merging. A count of 2 means step 3 did not take effect — not that the PR is clean. Green is count > 0 and all-success and the suite is the expected size.

One consequence of step 3

If #43 lands as a squash, this branch's head still carries c9cc76a, so after update-branch the three-dot diff will show 3 files rather than 2 — the fixture reappears in the comparison. The content is identical, so there is no conflict, but the "2 files changed, +683/−18" line in this PR's body goes stale at that moment. Nothing to fix in the code; just don't read the changed file count as a regression.

Unchanged from my previous comment

Issue #45 still stands and still compounds this: there is no Rust CI in this repository at all, so even the full 16 checks on main do not run cargo test. The cargo test / cargo clippy output quoted in this PR's body is local, and remains the only evidence the code runs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WPSJ7fBhVAMcpSffCBWUDo

@hyperpolymath
hyperpolymath merged commit c0b1276 into feat/40-metadata-block-fixture Sep 22, 2026
2 checks passed
@hyperpolymath
hyperpolymath deleted the feat/40-compat-deed-reader branch September 22, 2026 22:56
arena-ai-coding-agent Bot pushed a commit that referenced this pull request Sep 25, 2026
#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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant