Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 38 additions & 16 deletions 1-formats/deed/mappings/launcher-standard-to-praxis-deed.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -334,25 +334,44 @@ surface and must follow. They are listed so that "the translation is done" is
never mistaken for "the consumers are done" — `implemented` does not imply
`wired` (AGENTS.md §6).

. **`launch-scaffolder`** — `crates/launcher-common/src/standard.rs` parses the
standard with `toml::from_str` and bakes a vendored copy via `include_str!()`.
⚠ **There is no Rust `.deed` parser in the estate.** `deed-ecosystem/rs` is
the unmigrated A2ML markdown crate (`[package] name = "a2ml"`, with
`Block::Heading` / `Block::Paragraph`) and is not a starting point. The work
is a purpose-built recursive-descent parser in `launcher-common`; the
acceptance test is that the old TOML and the new deed parse to an **equal**
`Standard` value, and the vendored copy is made byte-identical to the
committed deed with a drift test asserting it. `[resolution]` joins the
required-section set — the vendored copy's omission of it is the live defect
this closes.
. **`launch-scaffolder`** — ✅ **DONE 2026-09-22** (D73-C part 2). This item was
written while `crates/launcher-common/src/standard.rs` parsed the standard with
`toml::from_str` over a vendored `include_str!()` copy, and while it was true
that **no Rust `.deed` parser existed in the estate**. Both of those facts have
since changed; the original wording survives in git history.
+
Measured 2026-09-22: `crates/launcher-common/src/deed.rs` is a purpose-built
recursive-descent DEED parser (37,039 B), exported as `pub mod deed;`
(`lib.rs:23`), with `pub mod standard;` (`lib.rs:30`) reading
`launcher-standard_praxis.deed` on top of it. The vendored copy at
`launch-scaffolder/standards/launcher-standard_praxis.deed` is **byte-identical
to the canonical file** (`diff -q` reports no difference; 13,803 B,
`:standard-version "0.4.0"`, `:standard-date "2026-09-22"`), so the
`[resolution]`-omission defect this item described is closed.
+
⚠ Still true, and still the reason `deed-ecosystem/rs` was not the starting
point: it remains the unmigrated A2ML markdown crate (`[package] name =
"a2ml"`, with `Block::Heading` / `Block::Paragraph`). Leave it alone —
`deed-ecosystem#67`.
. **`trigger`** — `scripts/trigger-launcher.sh`, `dev-notes/HANDOVER.adoc`,
`docs/IMPLEMENTATION-STATUS.adoc` name the standard. Textual.

Out of scope, recorded so it is not lost: **10 downstream `*.launcher.a2ml`
files** under `metadatastician/` and `_ESOTERIC_GROUP _SET/`. These are
Out of scope, recorded so it is not lost: the downstream `*.launcher.a2ml`
descriptors under `metadatastician/` and `_ESOTERIC_GROUP _SET/`. These are
per-application *instances* of the metadata block, a different family from this
standard, and they need their own spec before anything touches them.

⚠ This was first recorded as **10 files**. That is an undercount by more than
5×. Measured 2026-09-22 (`rg --files -g '*.launcher.a2ml'`, excluding
`.claude/worktrees/` and `developer/worktrees/`): **23 distinct descriptors
across 52 on-disk paths** — the path count exceeds the descriptor count because
several repos are cloned more than once — and **39 of those paths cite the
deleted `launcher-standard.a2ml`**. Two of the 23 (`_DATABASE`,
`_ESOTERIC_GROUP`) are set-level rather than per-application, which is why an
app-name census returns 21. The figure decays as clones are added, so it is
dated rather than stated as standing fact; `hyperpolymath/standards#960` carries
the decision on what happens to these descriptors.

== 8. What this spec does not claim

* That the `praxis-deed` head is the only defensible choice. `repo-deed` would
Expand All @@ -361,6 +380,9 @@ standard, and they need their own spec before anything touches them.
names that exact failure.
* That the `:priority` step of 10 is meaningful. It is insertion headroom and
nothing else; only the relative order is semantic.
* That the vendored `launch-scaffolder` copy is now correct. It is not: it is
still v0.2.0, still missing `[resolution]`, and still `.a2ml`. That is §7
item 1 and it is not done.
* ⚠ **WITHDRAWN 2026-09-22.** This spec previously claimed the vendored
`launch-scaffolder` copy was "still v0.2.0, still missing `[resolution]`, and
still `.a2ml`". Measured, that is false in all three parts: the vendored file
is `standards/launcher-standard_praxis.deed`, 13,803 B, **byte-identical to
the canonical deed**, at `:standard-version "0.4.0"`. D73-C part 2 closed it.
See §7 item 1.
256 changes: 256 additions & 0 deletions scripts/check-launcher-standard-currency.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
#
# check-launcher-standard-currency.sh -- fail if a repo claims compliance with a
# launcher standard filename or version that no longer exists upstream.
#
# standards#960 acceptance criterion 4.
#
# WHY THIS EXISTS
# ---------------
# `launcher/launcher-standard.a2ml` was DELETED from `standards` on 2026-09-22
# (#952, be6c9580) and replaced by `launcher/launcher-standard_praxis.deed`.
# Nothing told the downstream repos. A census on 2026-09-22 found 21 distinct
# `*.launcher.a2ml` descriptors, plus `trigger/scripts/trigger-launcher.sh`,
# still declaring "Compliant with launcher-standard.a2ml v0.3.0" -- a filename
# that is gone and a version that no longer exists. Both claims read as
# compliance and neither is checkable, which is the vacuous-gate pattern the
# estate rules against.
#
# THE TWO DEFECTS ARE INDEPENDENT
# -------------------------------
# A reference fails on the FILENAME or on the VERSION, separately:
#
# launcher-standard.a2ml any version -> FAIL (retired filename)
# launcher-standard_praxis.deed v0.3.0 -> FAIL (stale version)
# launcher-standard.a2ml v0.4.0 -> FAIL (filename only)
# launcher-standard_praxis.deed v0.4.0 -> PASS
#
# A reference carrying no version token is checked on the filename alone; that
# is not a defect in itself, because plenty of prose names the standard without
# pinning it.
#
# `launcher-standard.adoc` is a DIFFERENT document (the human-readable UX
# standard) and is deliberately NOT checked here. It declares no version of its
# own, so a version claim against it is a separate defect and belongs in its
# own gate rather than being smuggled into this one.
#
# ANTI-DRIFT: THE EXPECTED VERSION IS NOT A MAGIC NUMBER
# ------------------------------------------------------
# CURRENT_VERSION below is a default for consumer repos, which do not carry the
# standard. It is NOT the authority. `scripts/tests/` asserts that this default
# equals `:standard-version` in the real deed, so bumping the standard without
# updating this script turns `standards`' own CI red. A checker whose expected
# value can silently drift from the thing it checks is worse than no checker.
# Consumers that DO vendor the deed should pass `--standard <path>` and read the
# version from the artefact instead of trusting this default.
#
# HISTORICAL RECORDS ARE ALLOWLISTED BY PATH, NEVER BY CONTENT
# ------------------------------------------------------------
# standards#960 AC1 exempts "a dated historical record". That exemption is a
# PATH allowlist (see ALLOWLIST below), not a content heuristic: a heuristic
# that spares any line mentioning "formerly" or "translated from" would spare
# the live defect too, because that is exactly how a stale descriptor comment
# is worded.
#
# EXIT CODES
# 0 no defects
# 1 at least one defect
# 2 usage error, or a seeded mutant survived --self-test
set -uo pipefail

CANONICAL_FILE="launcher-standard_praxis.deed"
RETIRED_FILE="launcher-standard.a2ml"
CURRENT_VERSION="0.4.0"
SELF_TEST_TMP=""

# Path globs exempt as historical records. Matched against the repo-relative
# path. Each entry is justified; do not add one without a reason.
ALLOWLIST=(
'docs/audits/*' # dated audit records (AC1's exemption)
'1-formats/deed/mappings/*' # the .a2ml -> .deed conversion record; naming both is its job
'dev-notes/*' # working notes, not compliance claims
'*HANDOVER*' # handover documents record prior state
'*CHANGELOG*' # a changelog that cannot name the old file is useless
'launcher/launcher-standard_praxis.deed' # the canon itself; its ;; header records its own provenance
'scripts/check-launcher-standard-currency.sh' # this file
'scripts/tests/check-launcher-standard-currency-test.sh'
)

usage() {

Check warning on line 81 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXM&open=AaDJPndd5eDccUsDAiXM&pullRequest=983
cat <<USAGE
Usage: check-launcher-standard-currency.sh [options]

--root DIR tree to scan (default: .)
--standard FILE read the expected version from a deed's :standard-version
--expect-version VER expected version, overrides --standard and the default
--self-test run the seeded-mutant self-test and exit
-h, --help this message

Expected version resolution order:
--expect-version > --standard > built-in default (${CURRENT_VERSION})
USAGE
}

# Read :standard-version out of a praxis deed. The DEED grammar carries TWO
# versions and they are not interchangeable: :schema-version is the GRAMMAR
# (1.0.0) and :standard-version is the DOCUMENT. Reading the first yields a
# number that looks like a newer spec, so the resulting error reads as an
# upgrade rather than as drift.
read_standard_version() {
local f="$1" v
[ -r "$f" ] || { echo "ERROR: cannot read standard file: $f" >&2; return 2; }

Check failure on line 103 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXN&open=AaDJPndd5eDccUsDAiXN&pullRequest=983
v=$(command grep -m1 -oE ':standard-version[[:space:]]+"[0-9]+\.[0-9]+\.[0-9]+"' "$f" \
| command grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
[ -n "$v" ] || { echo "ERROR: no :standard-version in $f" >&2; return 2; }

Check failure on line 106 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXO&open=AaDJPndd5eDccUsDAiXO&pullRequest=983
printf '%s' "$v"
}

is_allowlisted() {
local path="$1" glob
for glob in "${ALLOWLIST[@]}"; do
# shellcheck disable=SC2053
[[ "$path" == $glob ]] && return 0
done
return 1
}

# Scan a tree. Prints one defect per line; returns 1 if any were found.
scan() {
local root="$1" expect="$2" defects=0 hit file lineno text rel found

while IFS= read -r hit; do
file="${hit%%:*}"; hit="${hit#*:}"
lineno="${hit%%:*}"; text="${hit#*:}"
rel="${file#"$root"/}"; rel="${rel#./}"
is_allowlisted "$rel" && continue

if [[ "$text" == *"$RETIRED_FILE"* ]]; then
printf 'DEFECT retired-filename %s:%s names %s (deleted upstream 2026-09-22, #952)\n' \
"$rel" "$lineno" "$RETIRED_FILE"
defects=$((defects + 1))
fi

if [[ "$text" =~ launcher-standard(\.a2ml|_praxis\.deed)[^0-9]{0,24}v?([0-9]+\.[0-9]+\.[0-9]+) ]]; then
found="${BASH_REMATCH[2]}"
if [ "$found" != "$expect" ]; then

Check failure on line 137 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXP&open=AaDJPndd5eDccUsDAiXP&pullRequest=983
printf 'DEFECT stale-version %s:%s claims v%s, current is v%s\n' \
"$rel" "$lineno" "$found" "$expect"
defects=$((defects + 1))
fi
fi
done < <(command grep -rInF -e "$RETIRED_FILE" -e "$CANONICAL_FILE" "$root" --exclude-dir=.git 2>/dev/null)

[ "$defects" -eq 0 ] && return 0

Check failure on line 145 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXQ&open=AaDJPndd5eDccUsDAiXQ&pullRequest=983
printf '\n%s defect(s).\n' "$defects"
return 1
}

# Seeded-mutant self-test. A passing check proves nothing until a mutant dies,
# so every defect class gets a mutant AND there is a clean negative control.
self_test() {
local rc=0 out seeded=0
# NOTE: a `trap ... RETURN` here would NOT be scoped to this function -- bash
# installs it globally, so it fires on every later function return and, since
# $tmp is local, dies under `set -u`. Use a global + EXIT trap instead.
SELF_TEST_TMP="$(mktemp -d)"
trap 'rm -rf "${SELF_TEST_TMP:-}"' EXIT
local tmp="$SELF_TEST_TMP"

mkdir -p "$tmp/docs/audits"
# mutant 1: retired filename, no version
printf '# Compliant with %s\n' "$RETIRED_FILE" > "$tmp/m1.toml"; seeded=$((seeded+1))
# mutant 2: canonical filename, stale version
printf '# Compliant with %s v0.3.0\n' "$CANONICAL_FILE" > "$tmp/m2.toml"; seeded=$((seeded+1))
# mutant 3: retired filename WITH the current version -- filename must fail on its own
printf '# Compliant with %s v%s\n' "$RETIRED_FILE" "$CURRENT_VERSION" > "$tmp/m3.toml"; seeded=$((seeded+1))
# negative control: fully current, must NOT be reported
printf '# Compliant with %s v%s\n' "$CANONICAL_FILE" "$CURRENT_VERSION" > "$tmp/clean.toml"
# allowlist control: the worst mutant, under a dated-audit path -- must NOT be reported
printf '# Compliant with %s v0.1.0\n' "$RETIRED_FILE" > "$tmp/docs/audits/old-2026-05-26.adoc"

if [ "$seeded" -eq 0 ]; then

Check failure on line 173 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXR&open=AaDJPndd5eDccUsDAiXR&pullRequest=983
echo "SELF-TEST ERROR: zero fixtures seeded -- the self-test is vacuous." >&2
return 2
fi

out="$(scan "$tmp" "$CURRENT_VERSION")" || rc=1

local fail=0
check_detects() {

Check warning on line 181 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXT&open=AaDJPndd5eDccUsDAiXT&pullRequest=983
if ! printf '%s' "$out" | command grep -q "$1"; then

Check warning on line 182 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXS&open=AaDJPndd5eDccUsDAiXS&pullRequest=983
echo "SELF-TEST FAIL: mutant survived -- expected to detect: $1" >&2
fail=1
fi
}
check_absent() {

Check warning on line 187 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXV&open=AaDJPndd5eDccUsDAiXV&pullRequest=983
if printf '%s' "$out" | command grep -q "$1"; then

Check warning on line 188 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXU&open=AaDJPndd5eDccUsDAiXU&pullRequest=983
echo "SELF-TEST FAIL: false positive on: $1" >&2
fail=1
fi
}

check_detects 'retired-filename m1.toml'
check_detects 'stale-version m2.toml'
check_detects 'retired-filename m3.toml'
check_absent 'm3.toml.*stale-version' # m3 is current; only the filename is wrong
check_absent 'clean.toml'
check_absent 'docs/audits'

if [ "$rc" -ne 1 ]; then

Check failure on line 201 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXW&open=AaDJPndd5eDccUsDAiXW&pullRequest=983
echo "SELF-TEST FAIL: scan returned 0 with mutants present." >&2
fail=1
fi

if [ "$fail" -ne 0 ]; then

Check failure on line 206 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXX&open=AaDJPndd5eDccUsDAiXX&pullRequest=983
echo "--- scan output was ---" >&2
printf '%s\n' "$out" >&2
return 2
fi

printf 'self-test: %s mutants killed, 2 controls clean, OK\n' "$seeded"
return 0
}

main() {
local root="." standard="" expect="" do_self_test=0
while [ $# -gt 0 ]; do

Check failure on line 218 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXY&open=AaDJPndd5eDccUsDAiXY&pullRequest=983
case "$1" in
--root) root="${2:-}"; shift 2 ;;
--standard) standard="${2:-}"; shift 2 ;;
--expect-version) expect="${2:-}"; shift 2 ;;
--self-test) do_self_test=1; shift ;;
-h|--help) usage; return 0 ;;
*) echo "ERROR: unknown argument: $1" >&2; usage >&2; return 2 ;;

Check warning on line 225 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXZ&open=AaDJPndd5eDccUsDAiXZ&pullRequest=983
esac
done

[ "$do_self_test" -eq 1 ] && { self_test; return $?; }

Check failure on line 229 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXa&open=AaDJPndd5eDccUsDAiXa&pullRequest=983

if [ -z "$expect" ] && [ -n "$standard" ]; then

Check failure on line 231 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXb&open=AaDJPndd5eDccUsDAiXb&pullRequest=983

Check failure on line 231 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXc&open=AaDJPndd5eDccUsDAiXc&pullRequest=983
expect="$(read_standard_version "$standard")" || return 2
fi
[ -z "$expect" ] && expect="$CURRENT_VERSION"

Check failure on line 234 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXd&open=AaDJPndd5eDccUsDAiXd&pullRequest=983

if [ ! -d "$root" ]; then

Check failure on line 236 in scripts/check-launcher-standard-currency.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaDJPndd5eDccUsDAiXe&open=AaDJPndd5eDccUsDAiXe&pullRequest=983
echo "ERROR: --root is not a directory: $root" >&2
return 2
fi

echo "check-launcher-standard-currency: canonical=${CANONICAL_FILE} expected=v${expect}"
if scan "$root" "$expect"; then
echo "No stale launcher-standard references."
return 0
fi
cat >&2 <<REMEDY

Cure: name ${CANONICAL_FILE} at v${expect}.
The standard lives at launcher/launcher-standard_praxis.deed in
hyperpolymath/standards. Clause citations are s-expression heads --
write (runtime ...), not [runtime] or the old section-sign notation.
REMEDY
return 1
}

main "$@"
Loading
Loading