Skip to content

Commit db22cd5

Browse files
feat: canonical A2ML dialect = TOML (warn-by-default, enforce flag) (#53)
Estate decision (2026-07-06): **one canonical `.a2ml` dialect = TOML** (`key = value` / `[section]`). The validator was deliberately polyglot (accepted TOML, S-expr, *and* YAML-ish identity) — which is exactly how the estate drifted into three dialects under one extension. ## Change - **Check 5 (canonical dialect):** flags repo-specific files still written as YAML (`key:`) or S-expression (`(form ...)`). **Warning by default** so the estate migrates incrementally; **error** when the new input `enforce-canonical-dialect: true`. - AI-manifest templates are **exempt** (they're template-sourced; converted upstream in `rsr-template-repo`/`standards`, not per-repo — otherwise every repo drowns in warnings for files it can't fix locally). - README now **declares TOML canonical**. Verified: `bash -n` clean, `action.yml` parses, env var wired (`INPUT_ENFORCE_CANONICAL_DIALECT`). ## The ratchet 1. **This PR** — warn on non-TOML, canon documented. Non-breaking. ✅ 2. Convert the `*-AI-MANIFEST.a2ml` + `contractiles/*` templates in `rsr-template-repo`/`standards` to TOML. 3. Propagate templates to repos. 4. Flip `enforce-canonical-dialect: true` estate-wide → hard enforcement, one type everywhere. First consumer already converted its own files: `hybrid-automation-router` (PR #82). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7e1cd7e commit db22cd5

3 files changed

Lines changed: 46 additions & 0 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ RSR (Rhodium Standard Repository) projects to declare machine-readable
1212
metadata, AI agent instructions, and project state. This action scans
1313
for `.a2ml` files and validates their structure and required fields.
1414

15+
# Canonical dialect: TOML
16+
17+
Estate decision (2026-07-06): the **one canonical A2ML dialect is TOML**
18+
(`key = value` / `[section]`). Files written in YAML (`key:`) or S-expression
19+
(`(form ...)`) are non-canonical and should be converted. Check 5 flags them as
20+
a **warning** by default; set `enforce-canonical-dialect: true` to make it a
21+
hard **error** once a repository's `.a2ml` files are all converted. AI-manifest
22+
templates are exempt from this check until they are converted upstream in
23+
`rsr-template-repo` / `standards`.
24+
1525
# Checks Performed
1626

1727
1. **SPDX header** — Verifies `SPDX-License-Identifier` is present in

action.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ inputs:
2929
will fail on any validation issue. Defaults to false.
3030
required: false
3131
default: 'false'
32+
enforce-canonical-dialect:
33+
description: >-
34+
When true, files written in a non-canonical A2ML dialect (YAML or
35+
S-expression instead of canonical TOML) are reported as errors rather
36+
than warnings. Defaults to false so the estate can migrate incrementally.
37+
Canonical A2ML dialect: TOML (key = value / [section]).
38+
required: false
39+
default: 'false'
3240
paths-ignore:
3341
description: >-
3442
Newline-separated path fragments to skip. Each line is matched as a
@@ -69,6 +77,7 @@ runs:
6977
env:
7078
INPUT_PATH: ${{ inputs.path }}
7179
INPUT_STRICT: ${{ inputs.strict }}
80+
INPUT_ENFORCE_CANONICAL_DIALECT: ${{ inputs.enforce-canonical-dialect }}
7281
INPUT_PATHS_IGNORE: ${{ inputs.paths-ignore }}
7382
run: |
7483
"${GITHUB_ACTION_PATH}/validate-a2ml.sh"

validate-a2ml.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ set -euo pipefail
2626

2727
SCAN_PATH="${INPUT_PATH:-.}"
2828
STRICT="${INPUT_STRICT:-false}"
29+
ENFORCE_DIALECT="${INPUT_ENFORCE_CANONICAL_DIALECT:-false}"
2930
PATHS_IGNORE_RAW="${INPUT_PATHS_IGNORE:-}"
3031
GITHUB_OUTPUT_FILE="${GITHUB_OUTPUT:-/dev/null}"
3132

@@ -278,6 +279,32 @@ validate_a2ml() {
278279
fi
279280
fi
280281
done < "$file"
282+
283+
# --- Check 5: Canonical dialect (TOML) ---
284+
# Estate canon (2026-07-06): .a2ml is ONE dialect — TOML (`key = value` /
285+
# `[section]`). Flag repo-specific files still written as YAML (`key:`) or
286+
# S-expression (`(form ...)`). AI manifests are template-sourced (converted
287+
# upstream in rsr-template-repo/standards), so they are exempt here to avoid
288+
# estate-wide noise before that conversion lands. Warning by default so the
289+
# estate can migrate incrementally; set enforce_canonical_dialect=true to
290+
# make it a hard error once conversion is complete.
291+
if [[ "$is_manifest" == "false" ]]; then
292+
local _sexpr _toml _yaml _dialect=""
293+
_sexpr=$(grep -cE '^[[:space:]]*\([a-zA-Z]' "$file" 2>/dev/null || true)
294+
_toml=$(grep -cE '^[[:space:]]*(\[[^]]+\]|[A-Za-z0-9_.-]+[[:space:]]*=)' "$file" 2>/dev/null || true)
295+
_yaml=$(grep -cE '^[[:space:]]*[A-Za-z0-9_-]+:[[:space:]]' "$file" 2>/dev/null || true)
296+
if [[ "${_sexpr:-0}" -gt 2 && "${_toml:-0}" -eq 0 ]]; then
297+
_dialect="S-expression"
298+
elif [[ "${_yaml:-0}" -gt 2 && "${_toml:-0}" -le 1 ]]; then
299+
_dialect="YAML"
300+
fi
301+
if [[ -n "$_dialect" ]]; then
302+
local _sev="warning"
303+
[[ "$ENFORCE_DIALECT" == "true" ]] && _sev="error"
304+
report_issue "$_sev" "$file" "1" \
305+
"Non-canonical A2ML dialect ($_dialect). Canonical A2ML dialect is TOML (key = value / [section]); convert this file."
306+
fi
307+
fi
281308
}
282309

283310
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)