Skip to content

Commit f3e6851

Browse files
committed
feat(doc-truthing): in-repo DOC-04/05 guard + ADR-019 record (Refs #175 #176 #260)
Drive two in-repo children of the #175 epic index to a concrete state. DOC-16 (Refs #176): tools/check-doc-truthing.sh — a toolchain-free bash guard that locks the doc-truthing invariant DOC-04/05 established. It fails if any over-claiming doc (README, BACKEND-IMPLEMENTATION, COMPILER-CAPABILITIES, ALPHA-1-RELEASE-NOTES) loses its authoritative banner pointing at docs/CAPABILITY-MATRIX.adoc, if the matrix stops self-declaring primacy or loses its "What AffineScript is NOT" section, or if STATE.a2ml drops its mirror keys. Checks banner *presence* (not phrase blocklists) so it is green on the current truthed tree and has no false positives on the "production-ready" strings that legitimately appear inside negating banners. Wired into `just check` (guard recipe) and CI (ci.yml build job), mirroring tools/check-no-extension-ts.sh. Converts the DOC-08/09 MONITOR posture from external-bot-only to a first-class in-repo gate. ADR-019 (Refs #260): docs/decisions/0019-compiler-distribution.adoc — a canonical per-file decision record (matching docs/decisions/0022) for the already-settled, already-shipped compiler-distribution decision (Releases-canonical binaries + thin Deno/JSR shim, S1..S4 merged, JSR publish live). Cross-linked from SETTLED-DECISIONS.adoc, PACKAGING.adoc, and the TECH-DEBT INT-04 row. Ledger: TECH-DEBT.adoc gains the DOC-16 row and the ADR-019 cross-links. Per the epic's update protocol these are Refs (never Closes) on #175 — only the owner closes the index. https://claude.ai/code/session_01E3R1oZhGUKfTYeTSVJVTcn
1 parent efa0339 commit f3e6851

7 files changed

Lines changed: 289 additions & 7 deletions

File tree

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,14 @@ jobs:
7575
# rationale + recovery instructions.
7676
run: ./tools/check-no-extension-ts.sh
7777

78+
- name: Issue #176 — block status-doc truthing re-drift
79+
# docs/CAPABILITY-MATRIX.adoc is the single authoritative status
80+
# doc. This guard fails if the over-claiming docs lose their
81+
# banners pointing back at it, if the matrix stops self-declaring
82+
# primacy / loses its anti-over-claim section, or if STATE.a2ml
83+
# stops flagging itself as a mirror (DOC-01..09). Toolchain-free.
84+
run: ./tools/check-doc-truthing.sh
85+
7886
- name: Check formatting
7987
run: opam exec -- dune build @fmt
8088

docs/PACKAGING.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ scoped automation token) — a deliberate exception to Deno-first.
6666
#181 says "publish *compiler* + runtime". The runtime is the JS
6767
packages above. The **compiler is a native OCaml binary** — not a
6868
JSR/npm package. The one-way-door fork (#260) was decided
69-
(**ADR-019**): *Releases-canonical, dual-channel*.
69+
(**ADR-019**): *Releases-canonical, dual-channel*. Full decision record:
70+
link:decisions/0019-compiler-distribution.adoc[docs/decisions/0019-compiler-distribution.adoc].
7071

7172
* *Canonical artifact.* `.github/workflows/release.yml` (#260 S2), on a
7273
`v*` tag, builds `affinescript-{linux-x64,macos-x64,macos-arm64}`

docs/TECH-DEBT.adoc

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,21 @@ ABI coordination model (four axes: spec authority / coordinated
114114
landing / test parity / conflict resolution). Both filed in
115115
META.a2ml; ADR-020 lands first as the first test of ADR-021's
116116
protocol. Both require owner ratification. |*PROPOSED 2026-05-23*
117+
|DOC-16 |In-repo enforcement of the DOC-04/05 truthing invariant —
118+
`tools/check-doc-truthing.sh` (toolchain-free bash) fails if any
119+
over-claiming doc loses its `CAPABILITY-MATRIX.adoc` banner, if the
120+
matrix stops self-declaring primacy or loses its "What AffineScript is
121+
NOT" section, or if `STATE.a2ml` drops its mirror keys. Wired into
122+
`just check` (the `guard` recipe) and CI (`ci.yml` build job). Converts
123+
the DOC-08/09 MONITOR posture from external-bot-only to a first-class
124+
in-repo gate. |*DONE 2026-05-30*
117125
|===
118126

119127
MONITOR enforcement (DOC-08/09): any PR re-introducing backend-breadth,
120128
"production-ready", or stdlib-percentage over-claims must be rejected.
129+
As of DOC-16 (2026-05-30) the banner/mirror half of this is enforced
130+
in-repo by `tools/check-doc-truthing.sh`; the external Hypatia/gitbot
131+
DOC rules remain the catch-all for novel over-claim phrasings.
121132

122133
== Section B — CORE (compiler soundness / completeness)
123134

@@ -347,8 +358,9 @@ allocates a length-prefixed AS string and byte-copies from the
347358
WASI buffer.** Real-host main-invoke = S6 (WIT export lifting)
348359
|INT-04 |Publish to JSR/npm |S2 |#181 packaging READY (dry-run green,
349360
manual workflow); compiler-binary distribution decided = **ADR-019**
350-
(#260, Releases + thin Deno/JSR shim, staged S1..S4) — S1/S2/S3
351-
merged (#283/#284/#285); S4 done — INT-10 closed (#282).
361+
(#260, Releases + thin Deno/JSR shim, staged S1..S4; per-file record
362+
link:decisions/0019-compiler-distribution.adoc[decisions/0019-compiler-distribution.adoc])
363+
— S1/S2/S3 merged (#283/#284/#285); S4 done — INT-10 closed (#282).
352364
**First JSR publish landed 2026-05-20: `@hyperpolymath/affinescript@0.1.2`
353365
LIVE** (cross-runtime Deno+Bun+Node, MPL-2.0, sibling .d.ts for
354366
fast-check). `@hyperpolymath/affine-js` + `@hyperpolymath/affinescript-tea`
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// SPDX-FileCopyrightText: 2026 hyperpolymath (Jonathan D.A. Jewell) <j.d.a.jewell@open.ac.uk>
3+
= ADR-019: Compiler distribution — GitHub Releases binaries + thin Deno/JSR shim
4+
Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
5+
:toc:
6+
:toclevels: 2
7+
:icons: font
8+
9+
Status:: Accepted
10+
Date:: 2026-05-19 (accepted); implementation S1–S4 landed 2026-05-20
11+
Issue:: https://github.com/hyperpolymath/affinescript/issues/260[INT-04 #260] (split from https://github.com/hyperpolymath/affinescript/issues/181[#181])
12+
Series:: Settled-decisions ledger; companion entry in `.machine_readable/6a2/META.a2ml [[adr]] id="ADR-019"`; narrative in `docs/specs/SETTLED-DECISIONS.adoc` §"Compiler Distribution".
13+
14+
== Context
15+
16+
INT-04 (#181) is "publish compiler + runtime". The two halves have very
17+
different shapes:
18+
19+
* The *runtime* JS packages (`@hyperpolymath/affine-js`,
20+
`@hyperpolymath/affinescript-tea`) are ordinary JSR/npm packages — their
21+
publish path is bounded packaging-prep (`docs/PACKAGING.adoc`) and was
22+
already ready/independent of this decision.
23+
* The *compiler* is a native OCaml binary. It is **not** a JSR/npm package,
24+
so "publish the compiler" has no off-the-shelf answer. How consumers — and
25+
in particular `affinescript-lsp` (INT-10) — install it is a one-way-door
26+
contract: it sets the install surface, the checksum/trust story, and the
27+
LSP's compiler-resolution path.
28+
29+
This was escalated as a design fork (issue #260, AskUserQuestion 2026-05-19)
30+
rather than guessed, because it is irreversible-ish and outside the
31+
packaging-prep scope.
32+
33+
== Decision
34+
35+
=== Option survey
36+
37+
The fork presented four options:
38+
39+
(1) **GitHub Releases binaries.** Per-platform artifacts via the existing
40+
`release.yml` (`v*` tags) + an install script / fetch-pin.
41+
42+
(2) **Guix/Nix channel.** Aligns with the repo's primary packaging
43+
(`guix.scm` / `flake.nix`); reproducible, but a narrower audience and no
44+
`deno`/`npm` ergonomics for the LSP-installer consumers.
45+
46+
(3) **Thin JSR/npm shim.** A small package that, on first run, downloads a
47+
pinned, checksummed prebuilt binary and execs it — `deno`/`npm`
48+
ergonomics without shipping OCaml source.
49+
50+
(4) **Combination** of the above.
51+
52+
=== Chosen: (4) — *Releases-canonical, dual-channel*
53+
54+
The owner chose the combination, with GitHub Releases as the **single
55+
canonical artifact source** and a thin Deno/JSR shim as the ergonomic front
56+
door:
57+
58+
* *Canonical artifact.* `.github/workflows/release.yml`, on a `v*` tag,
59+
builds per-platform compiler binaries and a `SHA256SUMS` manifest, both
60+
attached to the GitHub Release. The Release is the one source of truth;
61+
Guix/Nix and any later npm tail are **additive fetch-derivations over it**,
62+
not separate producers.
63+
64+
* *Ergonomic front door.* A thin Deno/JSR package
65+
`@hyperpolymath/affinescript` downloads the host-platform binary from the
66+
pinned Release, verifies it against the `SHA256SUMS` checksum embedded in
67+
that shim version, then caches and execs it. HTTPS-only, no secrets, one
68+
version+checksum pinned per shim release (no floating fetch).
69+
70+
* *LSP path.* `affinescript-lsp` (INT-10) resolves the compiler via
71+
`AFFINESCRIPT_COMPILER` → `affinescript` on `PATH` → the
72+
`@hyperpolymath/affinescript` shim. Consuming the shim is what unblocks
73+
INT-10; the LSP does no bespoke bundling.
74+
75+
* *npm tail deferred.* An npm publish is added only if an npm-native consumer
76+
needs it, mirroring the `affine-vscode` runtime exception. It is **not**
77+
wired by this ADR.
78+
79+
== Staging
80+
81+
Tracked in `docs/TECH-DEBT.adoc` (INT-04 / INT-10); all four stages have
82+
landed:
83+
84+
[cols="1,4,1"]
85+
|===
86+
|Stage |Content |Status
87+
88+
|S1 |This ADR + plan; file INT-10. No code. |DONE
89+
|S2 |`release.yml` per-platform binary build + `SHA256SUMS` matrix |DONE (#283/#284/#285)
90+
|S3 |The shim package — download + checksum-verify + cache + exec + tests; publish owner-gated via the existing manual JSR workflow |DONE
91+
|S4 |Wire INT-10 `affinescript-lsp` onto the shim |DONE (#282)
92+
|===
93+
94+
First JSR publish landed 2026-05-20: `@hyperpolymath/affinescript@0.1.2`
95+
LIVE (cross-runtime Deno + Bun + Node, MPL-2.0, sibling `.d.ts` for
96+
JSR fast-check). The shim pins all three v0.1.1 binaries by SHA256, so the
97+
resolution path executes end-to-end. Compiler version-string drift was fixed
98+
in lock-step via `lib/version.ml` (single source of truth) + a release.yml
99+
tag-time bake step (#297/#300).
100+
101+
== Consequences
102+
103+
* The install contract is *Releases-first*. Renaming the `SHA256SUMS`
104+
manifest or the per-platform asset names is an ABI change for every shim
105+
version and for Guix/Nix derivations downstream — do not rename without
106+
amending this ADR and the shim's pinned spec.
107+
* Browsers and Cloudflare Workers are out of scope for the shim by
108+
construction (fetch → save to disk → exec a native binary cannot run in a
109+
sandboxed JS runtime). The shim's runtime-compatibility matrix is therefore
110+
Deno ✅ / Bun ✅ / Node ✅ / Workers ❌ / Browsers ❌ (see `.claude/CLAUDE.md`
111+
Runtime Exemptions).
112+
* Reproducible-build consumers are served by the additive Guix/Nix
113+
derivations layered over the same Release artifact, not by a competing
114+
artifact source.
115+
116+
== References
117+
118+
* Issue: https://github.com/hyperpolymath/affinescript/issues/260 (INT-04
119+
compiler distribution), split from
120+
https://github.com/hyperpolymath/affinescript/issues/181[#181].
121+
* Unblocks: https://github.com/hyperpolymath/affinescript/issues/282[INT-10
122+
#282] (`affinescript-lsp` distribution).
123+
* `docs/specs/SETTLED-DECISIONS.adoc` §"Compiler Distribution: GitHub
124+
Releases Binaries + Thin Deno/JSR Shim (ADR-019)" — the narrative twin of
125+
this record.
126+
* `docs/PACKAGING.adoc` §"The compiler itself — decided (ADR-019 / #260)".
127+
* `docs/TECH-DEBT.adoc` INT-04 / INT-10 rows.
128+
* `.machine_readable/6a2/META.a2ml [[adr]] id="ADR-019"` — the machine record.
129+
* `.github/workflows/release.yml` (S2 producer), `.github/workflows/publish-jsr.yml`
130+
(owner-gated shim publish), `packages/affinescript-cli/` (the shim).
131+
132+
== Standing rule
133+
134+
This decision is *settled and implemented*. Do not reopen without amending
135+
this ADR. The companion `SETTLED-DECISIONS.adoc` section and the `META.a2ml`
136+
ADR-019 record must move in lock-step with any amendment.

docs/specs/SETTLED-DECISIONS.adoc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,5 +456,6 @@ tests, publish owner-gated via the existing manual JSR workflow; *S4*
456456
wire INT-10 `affinescript-lsp` onto the shim.
457457

458458
This decision is settled; do not reopen without amending the ADR. Full
459-
ADR in `.machine_readable/6a2/META.a2ml` (ADR-019); ledger #260 /
460-
INT-10 in `docs/TECH-DEBT.adoc`.
459+
ADR in `.machine_readable/6a2/META.a2ml` (ADR-019) and the per-file
460+
decision record link:../decisions/0019-compiler-distribution.adoc[docs/decisions/0019-compiler-distribution.adoc];
461+
ledger #260 / INT-10 in `docs/TECH-DEBT.adoc`.

justfile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,14 @@ fmt:
8787
# Run all checks (lint + test + regression guards)
8888
check: lint test guard
8989

90-
# Issue #35 Phase 3 regression guard — fails if extension.ts reappears
91-
# under editors/vscode/src or any face's vscode extension dir
90+
# Regression guards:
91+
# - Issue #35 Phase 3: fails if extension.ts reappears under
92+
# editors/vscode/src or any face's vscode extension dir.
93+
# - Issue #176 (DOC-01..09): fails if the status-doc truthing banners
94+
# re-drift (authoritative matrix pointers + STATE.a2ml mirror keys).
9295
guard:
9396
./tools/check-no-extension-ts.sh
97+
./tools/check-doc-truthing.sh
9498

9599
# ── Compiler subcommands ──────────────────────────────────────────────────────
96100

tools/check-doc-truthing.sh

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# Copyright (c) 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
4+
#
5+
# Doc-truthing re-drift guard for issue #176 (DOC-01..09).
6+
#
7+
# Authoritative feature-readiness status lives in docs/CAPABILITY-MATRIX.adoc.
8+
# Several historical/architectural docs are known to over-claim (backend
9+
# breadth, "production-ready", stdlib percentage). DOC-04/05 neutralised them
10+
# with an *authoritative banner* that points every reader back at the matrix,
11+
# and made the matrix self-declare its primacy + carry an anti-over-claim
12+
# section.
13+
#
14+
# Until now that invariant was enforced only by the external Hypatia/gitbot
15+
# review bots (the MONITOR posture in issue #176). This guard makes it a
16+
# first-class, in-repo, toolchain-free gate: it fails if any of the banner
17+
# pointers, the matrix's self-declaration, the anti-over-claim section, or the
18+
# STATE.a2ml mirror keys are removed — i.e. if the docs start to re-drift.
19+
#
20+
# It deliberately checks *banner presence*, not phrase blocklists: the word
21+
# "production-ready" legitimately appears inside the negating banners and in
22+
# clearly-marked future-roadmap sections, so a naive phrase grep would
23+
# false-positive. Presence-of-the-correction is the durable invariant.
24+
#
25+
# Wired into:
26+
# - just check (via the `guard` recipe in justfile)
27+
# - CI (.github/workflows/ci.yml, build job)
28+
#
29+
# Run from anywhere; it cd's to the repo root itself.
30+
31+
set -euo pipefail
32+
33+
cd "$(dirname "$0")/.."
34+
35+
# The single authoritative status document.
36+
MATRIX="docs/CAPABILITY-MATRIX.adoc"
37+
38+
# Docs that historically over-claim and MUST carry a pointer back at $MATRIX
39+
# (DOC-04). Each must mention CAPABILITY-MATRIX.adoc somewhere in its body.
40+
BANNERED_DOCS=(
41+
"README.adoc"
42+
"docs/architecture/BACKEND-IMPLEMENTATION.md"
43+
"docs/reference/COMPILER-CAPABILITIES.md"
44+
"docs/history/ALPHA-1-RELEASE-NOTES.md"
45+
)
46+
47+
# The machine-readable mirror (DOC-05): it follows the matrix, it does not
48+
# lead. These keys assert that contract in-band.
49+
STATE_FILE=".machine_readable/6a2/STATE.a2ml"
50+
STATE_KEYS=(
51+
"authoritative-status-doc"
52+
"drift-flag"
53+
)
54+
55+
fail=0
56+
note() { printf '%s\n' "$*" >&2; }
57+
58+
# --- 1. The matrix itself exists and asserts its own primacy ----------------
59+
if [ ! -f "$MATRIX" ]; then
60+
note "ERROR: authoritative status doc is missing: $MATRIX"
61+
note " This file is the single source of truth for feature readiness"
62+
note " (DOC-01, issue #176). Restore it or amend this guard."
63+
fail=1
64+
else
65+
if ! grep -q "single authoritative source" "$MATRIX"; then
66+
note "ERROR: $MATRIX no longer self-declares primacy."
67+
note " Expected the phrase 'single authoritative source' (DOC-01)."
68+
fail=1
69+
fi
70+
# DOC-08/09: the anti-over-claim section must survive.
71+
if ! grep -q "What AffineScript is NOT" "$MATRIX"; then
72+
note "ERROR: $MATRIX lost its 'What AffineScript is NOT' section."
73+
note " That section is the anti-over-claim record (DOC-08/09,"
74+
note " issue #176). Do not delete it."
75+
fail=1
76+
fi
77+
fi
78+
79+
# --- 2. Every over-claiming doc still points back at the matrix (DOC-04) -----
80+
for doc in "${BANNERED_DOCS[@]}"; do
81+
if [ ! -f "$doc" ]; then
82+
note "ERROR: bannered doc is missing: $doc"
83+
note " If it was intentionally removed, drop it from BANNERED_DOCS"
84+
note " in this guard. Otherwise restore the file + its banner."
85+
fail=1
86+
continue
87+
fi
88+
if ! grep -q "CAPABILITY-MATRIX.adoc" "$doc"; then
89+
note "ERROR: $doc no longer points at the authoritative status matrix."
90+
note " DOC-04 requires an authoritative banner referencing"
91+
note " docs/CAPABILITY-MATRIX.adoc so readers are not misled by"
92+
note " this doc's historical over-claims (issue #176)."
93+
fail=1
94+
fi
95+
done
96+
97+
# --- 3. STATE.a2ml still declares itself a mirror, not a leader (DOC-05) -----
98+
if [ ! -f "$STATE_FILE" ]; then
99+
note "ERROR: machine-readable state file is missing: $STATE_FILE"
100+
fail=1
101+
else
102+
for key in "${STATE_KEYS[@]}"; do
103+
if ! grep -q "$key" "$STATE_FILE"; then
104+
note "ERROR: $STATE_FILE lost the '$key' key."
105+
note " DOC-05 requires STATE.a2ml to flag that it MIRRORS the"
106+
note " capability matrix and does not lead it (issue #176)."
107+
fail=1
108+
fi
109+
done
110+
fi
111+
112+
if [ "$fail" -ne 0 ]; then
113+
note ""
114+
note "Doc-truthing guard failed. The status docs are drifting back toward"
115+
note "over-claiming. See docs/CAPABILITY-MATRIX.adoc (authoritative) and"
116+
note "docs/TECH-DEBT.adoc section A (DOC-01..09) for the contract."
117+
exit 1
118+
fi
119+
120+
echo "OK: doc-truthing banners intact (matrix primacy + DOC-04 banners + DOC-05 mirror keys)."

0 commit comments

Comments
 (0)