Skip to content

Commit fff81ff

Browse files
committed
Add A2ML Record Dialect specification + conformance vectors
Specifies the TOML-like [section]/key=value surface that ~435 estate descriptile files (STATE/META/ECOSYSTEM/AGENTIC/NEUROSYM/PLAYBOOK/ANCHOR) already use but which was never formally defined. Grounded in a measured survey of the deployed corpus (1,798 .a2ml files), not the abandoned .scm-era specs. The record dialect is a second concrete surface over the SAME typed core as the existing markup dialect (SPEC.adoc section 3): it reuses the validation modes, base invariants, Base Record vocabulary, profile mechanism, and canonical hash form unchanged. Not a rival format -- a co-equal reader. This is a structural fix for the "three coexisting format generations, no deprecation story" finding. Adds: - a2ml/RECORD-DIALECT-SPEC.adoc -- normative spec (RFC 2119, EBNF, core mapping, R0/R1/R2 conformance classes, normative divergences from TOML, security, versioning). Status: Draft -- not yet ratified, wired into CI, or exercised by a reference reader (see the spec's Appendix D). - a2ml/tests/record-dialect/ -- executable conformance-vector suite: 8 valid + 12 invalid vectors, each mapped to a spec clause and required class, plus a dogfooded record-dialect MANIFEST.a2ml and a README. Regenerated .machine_readable/REGISTRY.a2ml with scripts/build-registry.sh (the sanctioned generator; the file is not hand-edited). This updates the a2ml/ home source_hash for the new files and incidentally re-pins the k9-svc/ home hash, which was already drifted on main since 6df21b1 (the registry was not regenerated after that commit changed k9-svc files). The drift gate (build-registry.sh --check) passes after regeneration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112RkdER2wtwHdNmbEhThUz
1 parent 6df21b1 commit fff81ff

24 files changed

Lines changed: 1222 additions & 2 deletions

.machine_readable/REGISTRY.a2ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ name = "A2ML — Attested Markup Language"
3636
stream = "foundation"
3737
home = "a2ml/"
3838
canonical_doc = "a2ml/README.adoc"
39-
source_hash = "sha256:5f070c778ad112229878e2d551b564e2ec885d2239c6f348c0a96b700091d84a"
39+
source_hash = "sha256:8177d3a56c342d99383c26ef5ac481842dc8adfb885499d75b490729ecad7dfb"
4040
route = "the typed/verified machine-readable document format"
4141

4242
[[spec]]
@@ -45,7 +45,7 @@ name = "K9 Self-Validating Components"
4545
stream = "foundation"
4646
home = "k9-svc/"
4747
canonical_doc = "k9-svc/README.adoc"
48-
source_hash = "sha256:2fa2fe6394de60ac7da8672dcfeb21aa65919a7df83e8cf9ee9b82b2260ab0a8"
48+
source_hash = "sha256:cf2afa97e93597731bdb18f8c56beba3fd553c77e19c02ad9515a919cc4bec00"
4949
route = "self-validating components with embedded contracts + deploy logic"
5050

5151
[[spec]]

a2ml/RECORD-DIALECT-SPEC.adoc

Lines changed: 821 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# SPDX-License-Identifier: CC-BY-SA-4.0
2+
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
#
4+
# MANIFEST.a2ml — conformance-vector index for the A2ML Record Dialect.
5+
# This file is itself a record-dialect document (spec RECORD-DIALECT-SPEC.adoc):
6+
# it dogfoods the format it indexes and MUST parse at R0.
7+
8+
[metadata]
9+
suite = "a2ml-record-dialect"
10+
spec = "RECORD-DIALECT-SPEC.adoc"
11+
spec-version = "1.0.0"
12+
generated-by = "authored"
13+
last-updated = "2026-07-03"
14+
15+
[legend]
16+
expect = "one of: parse | validate | reject"
17+
class = "lowest conformance class at which the expectation is REQUIRED (R0|R1|R2)"
18+
note = "spec section(s) the vector exercises"
19+
20+
# ----------------------------------------------------------------------------
21+
# VALID vectors — MUST parse (R0); MUST validate (R1) where a profile is declared.
22+
# ----------------------------------------------------------------------------
23+
24+
[[vector]]
25+
name = "valid/minimal.a2ml"
26+
expect = "parse"
27+
class = "R0"
28+
spec_section = "5.3, 4.1"
29+
note = "One section, one quoted-string entry — the smallest well-formed document."
30+
31+
[[vector]]
32+
name = "valid/all-scalar-forms.a2ml"
33+
expect = "parse"
34+
class = "R0"
35+
spec_section = "4.1-4.5, D-3"
36+
note = "String, multi-line string, integer with _ separator, float with exponent, booleans."
37+
38+
[[vector]]
39+
name = "valid/arrays-and-inline-tables.a2ml"
40+
expect = "parse"
41+
class = "R0"
42+
spec_section = "4.6, 4.7, 2.3"
43+
note = "Multi-line array with interior comment and trailing comma; array of inline tables."
44+
45+
[[vector]]
46+
name = "valid/dotted-sections.a2ml"
47+
expect = "parse"
48+
class = "R0"
49+
spec_section = "5.4, 6, 7.1"
50+
note = "Dotted key-paths up to four segments; uppercase segment (categories.CHS)."
51+
52+
[[vector]]
53+
name = "valid/array-of-tables.a2ml"
54+
expect = "parse"
55+
class = "R0"
56+
spec_section = "5.3, 6"
57+
note = "Repeated [[entry]] headers build an ordered array of tables."
58+
59+
[[vector]]
60+
name = "valid/quoted-keys.a2ml"
61+
expect = "parse"
62+
class = "R0"
63+
spec_section = "5.5, 3"
64+
note = "Quoted key required for a key containing '-' as first char (\"--browser\")."
65+
66+
[[vector]]
67+
name = "valid/comments-and-preamble.a2ml"
68+
expect = "parse"
69+
class = "R0"
70+
spec_section = "5.1, 2.3"
71+
note = "SPDX/copyright preamble comments; trailing comments after values and headers."
72+
73+
[[vector]]
74+
name = "valid/state-profile.a2ml"
75+
expect = "validate"
76+
class = "R1"
77+
spec_section = "5.2, 9.5, 10"
78+
note = "Declares @profile(id=a2ml/state); carries the three required sections — validates."
79+
80+
# ----------------------------------------------------------------------------
81+
# INVALID vectors — MUST be rejected at the stated class (and every class above).
82+
# ----------------------------------------------------------------------------
83+
84+
[[vector]]
85+
name = "invalid/bare-string-value.a2ml"
86+
expect = "reject"
87+
class = "R0"
88+
spec_section = "4.8, D-2"
89+
note = "Unquoted non-numeric, non-boolean scalar (x = hello) — no bare strings."
90+
91+
[[vector]]
92+
name = "invalid/single-quoted-string.a2ml"
93+
expect = "reject"
94+
class = "R0"
95+
spec_section = "D-5"
96+
note = "Single-quoted 'literal string' does not exist in the record dialect."
97+
98+
[[vector]]
99+
name = "invalid/native-date.a2ml"
100+
expect = "reject"
101+
class = "R0"
102+
spec_section = "D-1, 4.8"
103+
note = "Unquoted date literal — dates are quoted strings, no native date-time."
104+
105+
[[vector]]
106+
name = "invalid/hex-integer.a2ml"
107+
expect = "reject"
108+
class = "R0"
109+
spec_section = "D-3, 4.3"
110+
note = "0x/0o/0b integer bases are not part of the reduced number grammar."
111+
112+
[[vector]]
113+
name = "invalid/leading-dot-float.a2ml"
114+
expect = "reject"
115+
class = "R0"
116+
spec_section = "D-3, 4.4"
117+
note = "Leading-dot float (.5) is not valid; a float MUST have a leading digit."
118+
119+
[[vector]]
120+
name = "invalid/triple-single-quote.a2ml"
121+
expect = "reject"
122+
class = "R0"
123+
spec_section = "D-5, 4.2"
124+
note = "'''…''' multi-line literal string does not exist; only \"\"\"…\"\"\"."
125+
126+
[[vector]]
127+
name = "invalid/unterminated-string.a2ml"
128+
expect = "reject"
129+
class = "R0"
130+
spec_section = "4.1"
131+
note = "String value with no closing quote before end of line."
132+
133+
[[vector]]
134+
name = "invalid/profile-after-section.a2ml"
135+
expect = "reject"
136+
class = "R1"
137+
spec_section = "5.1, 5.2"
138+
note = "@profile declared after a section header — all declarations MUST precede sections."
139+
140+
[[vector]]
141+
name = "invalid/duplicate-key.a2ml"
142+
expect = "reject"
143+
class = "R1"
144+
spec_section = "9.1, D-7"
145+
note = "Same key twice in one table — warning at R0, error at R1."
146+
147+
[[vector]]
148+
name = "invalid/reopened-section.a2ml"
149+
expect = "reject"
150+
class = "R1"
151+
spec_section = "9.2, D-7"
152+
note = "A standard section path opened twice ([a] … [a])."
153+
154+
[[vector]]
155+
name = "invalid/unknown-directive.a2ml"
156+
expect = "reject"
157+
class = "R1"
158+
spec_section = "10"
159+
note = "A base document permits no directive but @profile; @frobnicate is rejected."
160+
161+
[[vector]]
162+
name = "invalid/state-missing-required-section.a2ml"
163+
expect = "reject"
164+
class = "R1"
165+
spec_section = "9.5, 10"
166+
note = "Declares a2ml/state but omits the required maintenance-status section."
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// SPDX-License-Identifier: CC-BY-SA-4.0
2+
= A2ML Record Dialect — Conformance Vectors
3+
:icons: font
4+
:toc: macro
5+
6+
toc::[]
7+
8+
This directory is the *normative test* for
9+
link:../../RECORD-DIALECT-SPEC.adoc[the A2ML Record Dialect specification]. A
10+
reference validator conforms to a class (R0/R1/R2, spec §1.4) if and only if it
11+
produces the expected verdict for every vector whose required class is at or below
12+
the class under test.
13+
14+
== Layout
15+
16+
[cols="1,3",options="header"]
17+
|===
18+
| Path | Contents
19+
20+
| `vectors/valid/`
21+
| Documents that MUST parse (R0) and, where they declare a profile, MUST validate
22+
(R1) against the definitions in `a2ml/profiles/`.
23+
24+
| `vectors/invalid/`
25+
| Documents that MUST be rejected. Each is paired in `MANIFEST.a2ml` with the spec
26+
clause it violates and the lowest class (R0/R1/R2) at which rejection is
27+
REQUIRED.
28+
29+
| `MANIFEST.a2ml`
30+
| The machine-readable index of every vector: expected verdict, required class,
31+
and the spec section exercised. It is itself a record-dialect document, so it
32+
doubles as a valid vector (the suite dogfoods the dialect).
33+
|===
34+
35+
== Expected-verdict model
36+
37+
Each vector has an `expect` of `parse`, `validate`, or `reject`:
38+
39+
* `parse` — R0 MUST build a record tree without error. (Profile-free or
40+
profile-inert.)
41+
* `validate` — R0 MUST parse AND R1 MUST validate against the declared profile.
42+
* `reject` — the validator MUST error at or below `class`; a validator at a higher
43+
class MUST also reject (obligations accumulate downward, spec §1.4).
44+
45+
A vector marked `reject` at `class = R0` is a *syntax* error every conforming
46+
implementation catches. A vector marked `reject` at `class = R1` is a
47+
*well-formedness / profile* error that a pure R0 reader is permitted to accept
48+
(with a warning where the spec says so).
49+
50+
== Running the suite (once a reference validator exists)
51+
52+
The validator is not yet wired (spec Appendix D). The intended contract, so the
53+
suite is runnable the moment the reader lands:
54+
55+
[source,sh]
56+
----
57+
# For each valid vector: exit 0, tree emitted.
58+
# For each invalid vector: non-zero exit, error cites the manifest's clause.
59+
a2ml validate --dialect record --class R1 vectors/valid/<name>.a2ml # → 0
60+
a2ml validate --dialect record --class R1 vectors/invalid/<name>.a2ml # → non-0
61+
----
62+
63+
The CI job that drives this MUST fail loudly on any mismatch — never the
64+
`|| echo SKIP` pattern. A green run of this suite is the precondition (spec §15,
65+
Appendix D) for moving the specification from `Draft` to `Stable`.
66+
67+
== Adding a vector
68+
69+
. Add the `.a2ml` file under `valid/` or `invalid/`.
70+
. Add a matching `[[vector]]` block to `MANIFEST.a2ml` with `name`, `expect`,
71+
`class`, `spec_section`, and a one-line `note`.
72+
. Keep each vector minimal — it should exercise exactly one clause so a failure
73+
localises to one rule.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# INVALID (R0): bare (unquoted) string value. Spec 4.8 / D-2 — no bare strings.
3+
[metadata]
4+
name = hello
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# INVALID (R1): duplicate key in one table. Spec 9.1 / D-7 — warn at R0, error at R1.
3+
[metadata]
4+
version = "1.0.0"
5+
version = "2.0.0"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# INVALID (R0): hexadecimal integer. Spec D-3 / 4.3 — decimal only, no 0x/0o/0b.
3+
[metadata]
4+
mask = 0xFF
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# INVALID (R0): leading-dot float. Spec D-3 / 4.4 — a float MUST have a leading digit.
3+
[metadata]
4+
ratio = .5
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# INVALID (R0): unquoted native date literal. Spec D-1 / 4.8 — dates are quoted strings.
3+
[metadata]
4+
last-updated = 2026-05-15
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
# INVALID (R1): @profile declared after a section header. Spec 5.1 / 5.2 —
3+
# all profile declarations MUST precede the first section.
4+
[metadata]
5+
name = "example"
6+
7+
@profile(id=a2ml/state)

0 commit comments

Comments
 (0)