From 560c3e58fa6d5420012e95ff6c349c02b645a0ab Mon Sep 17 00:00:00 2001 From: David Charlot Date: Tue, 13 Oct 2026 03:06:10 -0400 Subject: [PATCH 1/2] aac, sandbox: five citations named sections their SPECs do not have MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code cites normative text to say which rule it enforces. These five cited sections that do not exist, so the citation read as authority it did not have. None of the rules was missing — every one is written down, just not where the code said: aac §3.3 (3 sites) the rule is §3 item 3 ("rendering its cell as visually inactive **in place**") and §3.1 item 3 ("MUST keep its position and MUST be rendered as visually inactive and not selectable"). aac/SPEC.md has §3 and §3.1 and no §3.2 or §3.3, and the SPEC's own cross-references already use the form this commit adopts — line 54 reads "(§3 item 3, §3.1)". sandbox §6.5 the receipt's storage line is `spec/receipts.md`: "Storage activity is its own line … it does **not** enter the per-device-class energy sum of §6.2". §6 has 6.1-6.4. sandbox §12.1 the deterministic-first cascade is `spec/build.md`, under "## Deterministic-first (the cascade)". §12 exists and records the tier in the build receipt, but has no subsections. So each citation now points where the rule actually is. Sections of a `spec/*.md` subfile are not sections of `SPEC.md`, and are cited by file name. Found by checking every `SPEC §N` in the tree against the headings of the SPEC.md beside it — 1454 citations across 15 standards, and these five were the only ones that did not resolve. The commit after this one makes that a check, because all three were written long before anyone read them. The same family as #190, where `jcp-runtime` cited `SPEC §4.10` in six places and §4.10 did not exist. Both workspaces check clean with `--all-targets`. Co-Authored-By: Claude Opus 5 --- aac/aac-rs/crates/aac-core/src/obf.rs | 2 +- aac/aac-rs/crates/aac-web/src/actions.rs | 2 +- aac/aac-rs/crates/aac-web/src/components/grid_cell.rs | 2 +- sandbox/sandbox-rs/crates/sandbox-build/src/lib.rs | 3 ++- sandbox/sandbox-rs/crates/sandbox-exec-wasm/src/lib.rs | 3 ++- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/aac/aac-rs/crates/aac-core/src/obf.rs b/aac/aac-rs/crates/aac-core/src/obf.rs index aafd7bbc5..78e03821e 100644 --- a/aac/aac-rs/crates/aac-core/src/obf.rs +++ b/aac/aac-rs/crates/aac-core/src/obf.rs @@ -33,7 +33,7 @@ //! button (message banking), travel in the file: the photo as the button's //! image, the recording as its `sound`, each as a `data:` URI when the //! export is given their bytes ([`ObfExportOptions::media`]). -//! - A word hidden in place (SPEC §3.3) is a button with `hidden: true`. +//! - A word hidden in place (SPEC §2.1, §3 item 3) is a button with `hidden: true`. //! - AuraSense extensions: `ext_aurasense_locked` (board and button), //! `ext_aurasense_word_class` (the button's part of speech, from the core //! vocabulary) and `ext_aurasense_provenance` (where each piece came from: diff --git a/aac/aac-rs/crates/aac-web/src/actions.rs b/aac/aac-rs/crates/aac-web/src/actions.rs index 781fb3abb..a2c99ea0e 100644 --- a/aac/aac-rs/crates/aac-web/src/actions.rs +++ b/aac/aac-rs/crates/aac-web/src/actions.rs @@ -93,7 +93,7 @@ pub fn handle_cell_action( }) }) }); - // A hidden word keeps its place but cannot be chosen (SPEC §3.3), unless + // A hidden word keeps its place but cannot be chosen (SPEC §3.1 item 3), unless // hidden words are shown for now: a press there does nothing at all (Help's // message stays, words being said go on). A word with no word in the // person's language yet is hidden in place too, shown or not. diff --git a/aac/aac-rs/crates/aac-web/src/components/grid_cell.rs b/aac/aac-rs/crates/aac-web/src/components/grid_cell.rs index ec905e31e..108ca4527 100644 --- a/aac/aac-rs/crates/aac-web/src/components/grid_cell.rs +++ b/aac/aac-rs/crates/aac-web/src/components/grid_cell.rs @@ -10,7 +10,7 @@ //! always its words. //! //! A photo the person added takes the symbol's place (media.rs). A hidden -//! word (SPEC §3.3) keeps its place but looks like an empty one and cannot +//! word (SPEC §3.1 item 3) keeps its place but looks like an empty one and cannot //! be chosen, unless hidden words are shown for now; in Edit mode it shows //! faded, and every cell, and every empty place, opens the cell editor //! (editing.rs). diff --git a/sandbox/sandbox-rs/crates/sandbox-build/src/lib.rs b/sandbox/sandbox-rs/crates/sandbox-build/src/lib.rs index d16adf8a9..248f8c2dd 100644 --- a/sandbox/sandbox-rs/crates/sandbox-build/src/lib.rs +++ b/sandbox/sandbox-rs/crates/sandbox-build/src/lib.rs @@ -225,7 +225,8 @@ pub struct BuildTask { pub steps: Vec, } -/// The cascade tier that resolved a step (deterministic-first, SPEC §12.1). +/// The cascade tier that resolved a step (deterministic-first, `spec/build.md`; +/// the receipt that records it is SPEC §12). /// A build never invokes a frontier model *inside* the sandbox — execution is /// the deterministic verification — so the only tiers seen here are the cheap /// ones: a content-addressed cache hit, or a fresh deterministic execution. diff --git a/sandbox/sandbox-rs/crates/sandbox-exec-wasm/src/lib.rs b/sandbox/sandbox-rs/crates/sandbox-exec-wasm/src/lib.rs index 437006f73..6aaae5984 100644 --- a/sandbox/sandbox-rs/crates/sandbox-exec-wasm/src/lib.rs +++ b/sandbox/sandbox-rs/crates/sandbox-exec-wasm/src/lib.rs @@ -515,7 +515,8 @@ mod tests { .iter() .any(|e| matches!(e, sandbox_attest::JournalEntry::HostCall { name, .. } if name == "storage_read"))); - // The brokered I/O is metered as a storage line (SPEC §6.5): one write + + // The brokered I/O is metered as a storage line (`spec/receipts.md`, + // storage activity — its own axis, outside §6.2's per-device sum): one write + // one read ⇒ 2 ops, 7 bytes read ("payload"), an honest estimate. let storage = report.storage.expect("storage activity was metered"); assert_eq!(storage.iops, 2); -- 2.54.0 (Apple Git-157) From 4b05fd80e1248bd5d1ee83bcb740164aeb3e6181 Mon Sep 17 00:00:00 2001 From: David Charlot Date: Tue, 13 Oct 2026 03:07:23 -0400 Subject: [PATCH 2/2] ci: a citation to a section that does not exist is now a failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit before this fixed five of them. They were written long before anyone read them, and the one in #190 — `jcp-runtime` citing `SPEC §4.10` in six places, with no §4.10 — survived a gate-enforcing, tested mechanism for months. Reading the tree once finds these; reading it every time is a check. `scripts/check-spec-refs.py` resolves every `SPEC §N` in a standard's `*.rs` against the numbered headings of the `SPEC.md` beside it. 1454 citations across 15 standards resolve today. Three decisions worth stating, because each is a way the check could have been written to find nothing: - Parents are not synthesised. A cited `§4` needs its own `§4` heading; a `§4.10` heading does not satisfy it. Otherwise a standard citing a section it never wrote is excused by a child it happens to have. - A citation resolves against the SPEC.md of its own directory. Every cross-standard citation in the tree today is a self-reference that names its standard for the reader ("JouleClaw SPEC §1.3", inside jouleclaw/), so this is right for all of them; the header says what to do instead if that ever stops being true, rather than loosening the check. - Sections of a `spec/*.md` subfile are not sections of `SPEC.md`. The self-test asserts the check can fail, in the four ways that matter: an unresolvable citation fails, both heading spellings (`## 4.` and `## §4`) are accepted, a child heading does not satisfy its parent, and one standard's section does not excuse another's citation. A regex that quietly stops matching would otherwise find nothing and exit 0 — the failure mode this repo has already met in gated and skipped tests. The workflow carries no `paths:` filter, deliberately. The check is a second of Python with no toolchain, and a correct filter would have to name every standard's `**/*.rs` and every `SPEC.md`; `standards-build.yml` records that `**`-prefixed globs do not match on this forge and that a gate which quietly fails to fire is worse than none, since it reports nothing and that reads as green. This is `check-wai-section-refs.py`'s complement, not a duplicate: that one checks WAI markdown against WAI markdown, this one checks every standard's code against its spec. Co-Authored-By: Claude Opus 5 --- .github/workflows/spec-refs.yml | 45 ++++++++++++++ scripts/check-spec-refs-selftest.sh | 70 +++++++++++++++++++++ scripts/check-spec-refs.py | 94 +++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 .github/workflows/spec-refs.yml create mode 100755 scripts/check-spec-refs-selftest.sh create mode 100755 scripts/check-spec-refs.py diff --git a/.github/workflows/spec-refs.yml b/.github/workflows/spec-refs.yml new file mode 100644 index 000000000..4ac3cd8db --- /dev/null +++ b/.github/workflows/spec-refs.yml @@ -0,0 +1,45 @@ +name: spec citations resolve + +# Every `SPEC §N` a standard's code cites is a section that standard's SPEC.md +# has. See the header of scripts/check-spec-refs.py for why: a citation to a +# section that does not exist reads as authority it does not have, and the three +# instances found so far were each discovered by reading, long after they were +# written. +# +# Deliberately NO `paths:` filter, for two reasons. +# +# The check is one Python pass over the tree — about a second, no toolchain, no +# cargo — so running it on every change costs less than describing when to run +# it. And a correct filter here would have to name every standard's `**/*.rs` +# plus every `SPEC.md`; `standards-build.yml` records that `**`-prefixed globs +# do not match on this forge the way they do elsewhere, and that a gate which +# quietly fails to fire is worse than no gate, because it reports nothing and +# everyone reads that as green. An unfiltered cheap check cannot fail that way. +# +# The self-test runs first and on its own line. A check that cannot fail is +# decoration, and this one would be easy to break into permanent silence — a +# regex that stops matching finds nothing and exits 0. + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + pull_request: + push: + branches: + - main + +jobs: + spec-refs: + name: Code cites no section its SPEC lacks + runs-on: linux + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: The check can fail (self-test) + run: bash scripts/check-spec-refs-selftest.sh + + - name: Every SPEC citation resolves + run: python3 scripts/check-spec-refs.py diff --git a/scripts/check-spec-refs-selftest.sh b/scripts/check-spec-refs-selftest.sh new file mode 100755 index 000000000..67e7de4aa --- /dev/null +++ b/scripts/check-spec-refs-selftest.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# +# check-spec-refs-selftest.sh — check-spec-refs.py can fail. +# +# A check that cannot fail is decoration. This builds scratch standards under a +# temporary root and asserts that the check +# 1. passes when every cited section is a heading; +# 2. fails when a cited section is not a heading (the case it exists for); +# 3. accepts both heading spellings, `## 4.` and `## §4`; +# 4. does NOT synthesise parents: a `## 4.10` heading alone must not satisfy a +# `SPEC §4` citation, which is how a standard citing a section it never +# wrote would otherwise pass; +# 5. resolves a citation against the SPEC.md of the standard the file sits in, +# so one standard's section cannot excuse another's citation; +# 6. ignores `*.rs` under a `target/` directory, which is build output. +# Nothing here reads the real tree, and nothing needs a network. +set -uo pipefail + +here=$(cd "$(dirname "$0")" && pwd) +check=$here/check-spec-refs.py +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT +fails=0 + +# mk +mk() { + mkdir -p "$1/$2/$(dirname "$4")" + printf '%s\n' "$3" > "$1/$2/SPEC.md" + printf '%s\n' "$5" > "$1/$2/$4" +} + +want() { # want