Skip to content

Commit ddea64f

Browse files
committed
feat(registry): add verifiable spec-pointer registry for language-coupled specs
Generalise the hub's registry surface so specs whose source-of-truth lives in another repo (language/service-coupled) are held as VERIFIED POINTERS, never copies — keeping SSOT intact and avoiding normative-text duplication. - REGISTRY.a2ml (schema spec-registry.v1): pointer registry distinct from SATELLITES.a2ml (absorbed repos). Self-describing field set ratified by owner 2026-06-03 — 7 base fields (name, canonical_url, owning_repo, version_pin, source_hash, conformance_level, last_synced) plus provenance fields (source_hash_algo, spec_kind, media_type, lineage). source_hash is the anti-drift anchor; sentinel PENDING-FIRST-SYNC is used rather than fabricating a hash before upstream lands. - Three pointers for the AffineScript v2 standards (SSOT = hyperpolymath/affinescript): affine-spec, affex-manifest (format_version tracked independently of version_pin), affmap-provenance (own pointer per owner decision). .affcite documented as a CodeCite-profile A2ML artefact, not a separate pointer. - HYP-S006 registry-pointer-staleness: recomputes each pointer's source_hash against canonical_url and flags drift. FLAG-ONLY (strategy: review) — re-pin is an owner decision. Rejects md5/sha1 per estate security policy. - SATELLITES.a2ml + hypatia-rules/README.adoc: scope-boundary cross-references. Upstream .affine/.affex v2 spec text lands in a separate affinescript PR (SSOT); writing it here would violate SSOT and is out of scope.
1 parent 0b96f61 commit ddea64f

4 files changed

Lines changed: 268 additions & 2 deletions

File tree

REGISTRY.a2ml

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# SPDX-License-Identifier: AGPL-3.0-or-later
2+
# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell
3+
# REGISTRY.a2ml -- Verifiable Spec-Pointer Registry for hyperpolymath/standards
4+
#
5+
# WHY THIS EXISTS (and why it is NOT SATELLITES.a2ml)
6+
# ---------------------------------------------------
7+
# SATELLITES.a2ml registers repositories that were *absorbed* into this hub as
8+
# subdirectories -- their normative text now lives HERE. This registry is the
9+
# opposite case: specs whose source-of-truth deliberately lives ELSEWHERE
10+
# because they are coupled to a language, runtime, or service whose release
11+
# cadence the standards repo must not own.
12+
#
13+
# For those, the standards repo holds a POINTER, never a copy. Duplicating the
14+
# normative text would create two sources of truth and guarantee drift. Each
15+
# pointer records a `source_hash` of the upstream spec; hypatia (HYP-S006)
16+
# recomputes that hash against the canonical_url and flags any mismatch as
17+
# staleness. That hash is the anti-drift mechanism -- the registry is
18+
# verifiable, not merely descriptive.
19+
#
20+
# First user of this registry: the AffineScript v2 standards (.affine source
21+
# documents / faces, .affex face-interop manifest, .affmap provenance), whose
22+
# SSOT is hyperpolymath/affinescript. The standards repo only points at them.
23+
24+
[registry]
25+
version = "1.0.0"
26+
schema = "spec-registry.v1"
27+
created = "2026-06-03"
28+
owner = "Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>"
29+
description = "Verifiable pointers to language/service-coupled specs whose SSOT lives in another repo. Pointers, never copies."
30+
31+
[registry.relationship]
32+
# How this file relates to its sibling.
33+
companion = "SATELLITES.a2ml"
34+
distinction = "SATELLITES = absorbed repos (text lives here). REGISTRY = external SSOT (text lives upstream, we hold a verified pointer)."
35+
overlap = "none -- a given spec appears in exactly one of the two files."
36+
37+
# ============================================================================
38+
# SCHEMA -- self-describing field set for every [[registry.pointers]] entry.
39+
# Field set ratified by owner 2026-06-03 (7 base fields + provenance fields).
40+
# This block is documentation for humans and a contract for hypatia's loader.
41+
# ============================================================================
42+
43+
[registry.schema]
44+
id = "spec-registry.v1"
45+
# The seven base fields (owner directive 2026-06-03).
46+
required = [
47+
"name", # stable registry key for the spec
48+
"canonical_url", # upstream source-of-truth location (the file hypatia hashes)
49+
"owning_repo", # repo that authors + versions the spec (org/repo)
50+
"version_pin", # upstream version/tag this pointer is pinned to
51+
"source_hash", # hash of the upstream spec content (anti-drift anchor)
52+
"conformance_level",# normative | draft | proposed | experimental
53+
"last_synced", # ISO-8601 timestamp hypatia last verified the hash (or "never")
54+
]
55+
# Provenance fields (owner-approved extension 2026-06-03).
56+
provenance = [
57+
"source_hash_algo", # e.g. sha256 -- makes the anti-drift check unambiguous
58+
"spec_kind", # language-coupled | service-coupled (heterogeneous registry)
59+
"media_type", # consistency with SATELLITES media-types
60+
"lineage", # lineage convention key, consistency with the .affine lineage
61+
]
62+
# Operational fields (not part of the ratified set, but the loader tolerates them).
63+
optional = [
64+
"sync_status", # awaiting-upstream | verified | drift-detected
65+
"format_version", # for regenerable artefacts whose format bumps independently
66+
"notes",
67+
]
68+
69+
[registry.schema.source_hash_format]
70+
# A source_hash value is "<algo>:<hex>". When the upstream spec has not yet
71+
# landed, source_hash is the sentinel below and sync_status = "awaiting-upstream".
72+
# NEVER fabricate a hash -- an unknown hash is recorded as the sentinel.
73+
sentinel-unsynced = "PENDING-FIRST-SYNC"
74+
preferred-algo = "sha256"
75+
banned-algos = ["md5", "sha1"] # estate security policy: no MD5/SHA1
76+
77+
[registry.policy]
78+
ssot = "A pointer's canonical_url is the ONLY normative copy. The standards repo MUST NOT re-author or mirror the spec text."
79+
anti-drift = "hypatia HYP-S006 recomputes source_hash against canonical_url; mismatch => staleness finding."
80+
flag-only = "Registry findings are :review (flag), never :auto_execute -- consistent with estate no-automated-edit posture."
81+
versioning = "language-coupled specs pin version_pin to the language tag; regenerable artefacts ALSO carry format_version, bumped independently."
82+
83+
# ============================================================================
84+
# POINTERS
85+
# ============================================================================
86+
# AffineScript v2 standards. SSOT = hyperpolymath/affinescript (NOT here).
87+
# Spec text is authored ONCE in AsciiDoc in that repo; faces govern how
88+
# AffineScript SOURCE is written, not how the SPECS are written. Multi-face
89+
# examples live upstream, never in this registry.
90+
#
91+
# source_hash is the sentinel until the separate affinescript PR lands the v2
92+
# spec text (see "Upstream work" note at the foot of this file). On first
93+
# successful sync hypatia replaces the sentinel and sets last_synced.
94+
95+
[[registry.pointers]]
96+
name = "affine-spec"
97+
spec_kind = "language-coupled"
98+
owning_repo = "hyperpolymath/affinescript"
99+
canonical_url = "https://github.com/hyperpolymath/affinescript/blob/main/spec/affine.adoc"
100+
version_pin = "v2.0.0"
101+
source_hash = "PENDING-FIRST-SYNC"
102+
source_hash_algo = "sha256"
103+
conformance_level = "draft" # v2 proposed direction; promote to normative when upstream lands
104+
media_type = "application/vnd.affinescript.affine"
105+
lineage = "affinescript:affine@2"
106+
last_synced = "never"
107+
sync_status = "awaiting-upstream"
108+
notes = "Source documents / faces. Defines faces, canonical-lowering invariant, canonical islands, idiom packs, mimicry bindings, project face policy."
109+
110+
[[registry.pointers]]
111+
name = "affex-manifest"
112+
spec_kind = "language-coupled"
113+
owning_repo = "hyperpolymath/affinescript"
114+
canonical_url = "https://github.com/hyperpolymath/affinescript/blob/main/spec/affex.adoc"
115+
version_pin = "v2.0.0" # language version this manifest format targets
116+
format_version = "2" # affex format version -- tracked INDEPENDENTLY (owner 2026-06-03)
117+
format_version_tracking = "independent"
118+
source_hash = "PENDING-FIRST-SYNC"
119+
source_hash_algo = "sha256"
120+
conformance_level = "draft"
121+
media_type = "application/vnd.affinescript.affex"
122+
lineage = "affinescript:affex@2"
123+
last_synced = "never"
124+
sync_status = "awaiting-upstream"
125+
notes = "Face-interop manifest. Derived, regenerable, source-authoritative artefact: declaration heads not full bodies. Because it is regenerable, format_version may bump freely without moving version_pin."
126+
127+
[[registry.pointers]]
128+
name = "affmap-provenance"
129+
spec_kind = "language-coupled"
130+
owning_repo = "hyperpolymath/affinescript"
131+
canonical_url = "https://github.com/hyperpolymath/affinescript/blob/main/spec/affmap.adoc"
132+
version_pin = "v2.0.0"
133+
source_hash = "PENDING-FIRST-SYNC"
134+
source_hash_algo = "sha256"
135+
conformance_level = "draft"
136+
media_type = "application/vnd.affinescript.affmap"
137+
lineage = "affinescript:affmap@2"
138+
last_synced = "never"
139+
sync_status = "awaiting-upstream"
140+
# Owner 2026-06-03: .affmap gets its OWN pointer now (not folded under affex),
141+
# so its provenance format has an independent source_hash / staleness signal.
142+
notes = "Provenance format. Own registry entry (owner decision) for independent staleness tracking."
143+
144+
# ----------------------------------------------------------------------------
145+
# Citation profile note (NOT a pointer):
146+
# .affcite.a2ml is an A2ML document under the CodeCite citation profile
147+
# (consistent with the .affex sketch that names pixi.affcite.a2ml). Citation
148+
# CONTENTS live in that A2ML artefact inside the AffineScript repo, not in
149+
# .affex and not here. It is therefore not a separate spec pointer.
150+
# ----------------------------------------------------------------------------
151+
152+
# ============================================================================
153+
# Upstream work (tracked, NOT done from this repo)
154+
# ============================================================================
155+
# A separate PR in hyperpolymath/affinescript lands the v2 spec text these
156+
# pointers reference, using the lineage convention:
157+
# * .affine v2 -- faces, canonical-lowering invariant, canonical islands,
158+
# idiom packs, mimicry bindings, project face policy.
159+
# * .affex v2 -- derived, regenerable manifest; source-authoritative;
160+
# declaration heads not full bodies.
161+
# Writing that spec text HERE would violate SSOT and is out of scope. Once it
162+
# lands, run hypatia HYP-S006 to populate source_hash + last_synced.
163+
164+
### End of REGISTRY.a2ml

SATELLITES.a2ml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
#
55
# This file registers all satellite repositories that orbit the standards hub.
66
# Satellites inherit enforcement policies, language rules, and governance from here.
7+
#
8+
# SCOPE BOUNDARY (see also: REGISTRY.a2ml)
9+
# ----------------------------------------
10+
# This file is for repos ABSORBED into the hub -- their normative text lives
11+
# HERE, as subdirectories. For specs whose source-of-truth deliberately lives
12+
# in ANOTHER repo (language/service-coupled: e.g. the AffineScript v2
13+
# .affine/.affex/.affmap standards), the hub holds a VERIFIED POINTER, not a
14+
# copy. Those live in REGISTRY.a2ml (schema spec-registry.v1), with hypatia
15+
# HYP-S006 verifying each pointer's source_hash against upstream to catch drift.
16+
# A given spec appears in exactly one of the two files, never both.
717

818
[satellites]
919
version = "2.0.0"

hypatia-rules/README.adoc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
:status: Draft v0.2.0
44
:updated: 2026-04-18
55

6-
Five Hypatia rules specific to the standards-repo dogfooding loop.
6+
Six Hypatia rules specific to the standards-repo dogfooding loop.
77
Each rule is defined in A2ML, consumes VeriSimDB octads, and emits
88
Groove `compliance.finding.new` signals.
99

@@ -16,12 +16,21 @@ Groove `compliance.finding.new` signals.
1616
| HYP-S003 | `proof-freshness` | Alert when a proof hasn't been re-verified in > 30 days |
1717
| HYP-S004 | `rsr-self-compliance` | Validate standards repo against its own RSR definition |
1818
| HYP-S005 | `crg-overclaim-detector` | Alert when a self-declared CRG grade lacks v2.0 evidence artefacts |
19+
| HYP-S006 | `registry-pointer-staleness` | Flag a `REGISTRY.a2ml` pointer whose `source_hash` drifts from its upstream `canonical_url` |
1920

2021
The CRG rule pair (S001 + S005) together enforce grade-honesty: S001
2122
catches backwards moves, S005 catches forwards-overshoots. Both read from
2223
`crg-grade` octads in VeriSimDB, but S005 also scans the repo file tree for
2324
evidence artefacts required by CRG v2.0 (STRICT).
2425

26+
HYP-S006 is the anti-drift mechanism for `REGISTRY.a2ml`: language/service-
27+
coupled specs (first user: the AffineScript v2 `.affine` / `.affex` / `.affmap`
28+
standards) keep their source-of-truth in another repo, so the standards repo
29+
holds only a verified pointer. The rule recomputes each pointer's `source_hash`
30+
against its `canonical_url` and flags any mismatch. It is FLAG-ONLY
31+
(`strategy: review`) — re-pinning a pointer is an owner decision, never an
32+
auto-fix.
33+
2534
== Implementation
2635

2736
Rules live as `.a2ml` files in this directory. They are consumed by
@@ -41,10 +50,11 @@ enabled = true
4150

4251
Rules read from:
4352
- VeriSimDB `crg-grade` octads (HYP-S001, HYP-S005)
44-
- Repo file tree via Hypatia's scanner (HYP-S002, HYP-S004, HYP-S005)
53+
- Repo file tree via Hypatia's scanner (HYP-S002, HYP-S004, HYP-S005, HYP-S006)
4554
- VeriSimDB `proof-status` octads (HYP-S003)
4655
- `rhodium-standard-repositories/RSR.adoc` (HYP-S004)
4756
- `rsr-template-repo/docs/governance/CRG-AUDIT-TEMPLATE.adoc` (HYP-S005 reference)
57+
- `REGISTRY.a2ml` spec pointers + upstream `canonical_url` fetch (HYP-S006)
4858

4959
And emit:
5060
- Groove `compliance.finding.new` signals with the rule's ID
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# SPDX-License-Identifier: AGPL-3.0-or-later
2+
# HYP-S006 — Registry Pointer Staleness
3+
# Flags a REGISTRY.a2ml spec-pointer whose recorded source_hash no longer
4+
# matches the upstream spec at its canonical_url. This is the anti-drift
5+
# mechanism for language/service-coupled specs whose SSOT lives in another
6+
# repo (the standards repo holds a verified pointer, never a copy).
7+
8+
@rule(version="1.0"):
9+
id: HYP-S006
10+
name: "Registry pointer staleness"
11+
description: "Detect REGISTRY.a2ml pointers whose source_hash drifts from the upstream spec at canonical_url"
12+
severity: high
13+
category: SourceOfTruth
14+
auto_fixable: false # FLAG-ONLY: re-pinning a pointer is an owner decision, never auto
15+
source: standards/hypatia-rules
16+
17+
@parameters:
18+
registry_file: "REGISTRY.a2ml"
19+
schema: "spec-registry.v1"
20+
# A pointer still on the sentinel hash with sync_status=awaiting-upstream is
21+
# NOT drift -- it is a not-yet-landed upstream. Treat as info, not high.
22+
sentinel_unsynced: "PENDING-FIRST-SYNC"
23+
# How long a sentinel pointer may sit unsynced before it is itself a finding.
24+
unsynced_grace_days: 30
25+
@end
26+
27+
@scanner(type="file-tree"):
28+
find:
29+
- glob: "REGISTRY.a2ml"
30+
- glob: "**/REGISTRY.a2ml"
31+
@end
32+
33+
@logic(engine="built-in"):
34+
# For each [[registry.pointers]] entry:
35+
# 1. read source_hash, source_hash_algo, canonical_url, sync_status.
36+
# 2. fetch canonical_url (the upstream spec file) and recompute the hash
37+
# with source_hash_algo (sha256+; md5/sha1 are rejected per security policy).
38+
# 3. compare:
39+
# - recomputed == recorded -> ok (refresh last_synced)
40+
# - recorded == sentinel_unsynced -> awaiting-upstream:
41+
# * upstream now exists -> emit registry.pointer.ready (info)
42+
# * still absent > grace -> emit registry.pointer.unsynced (low)
43+
# - recomputed != recorded (both real) -> emit registry.pointer.stale (high)
44+
steps:
45+
- parse_pointers: "registry.pointers"
46+
- reject_weak_algo: "source_hash_algo in [md5, sha1]" # security policy
47+
- fetch_canonical: "canonical_url"
48+
- recompute_hash: "source_hash_algo"
49+
- compare_to_recorded: "source_hash"
50+
- classify_and_emit
51+
@end
52+
53+
@action:
54+
emit_signal: compliance.finding.new
55+
strategy: review # never :auto_execute -- mirrors the estate flag-only posture
56+
message_template: "Registry pointer '{name}' is STALE: upstream {canonical_url} hashes {recomputed} but pointer pins {source_hash} (algo {source_hash_algo}). Re-pin is an owner decision."
57+
recipe: none
58+
halt_on_violation: false
59+
@end
60+
61+
@signals:
62+
# Emitted variants so downstream pipelines can route by severity.
63+
- name: registry.pointer.stale
64+
severity: high
65+
meaning: "recorded hash != recomputed upstream hash"
66+
- name: registry.pointer.unsynced
67+
severity: low
68+
meaning: "still on sentinel hash beyond unsynced_grace_days"
69+
- name: registry.pointer.ready
70+
severity: info
71+
meaning: "sentinel pointer's upstream now exists; ready for first sync"
72+
@end
73+
74+
@notes:
75+
# Weak-algo rejection is a hard fail, not a warning: a pointer that claims to
76+
# be verified by md5/sha1 is treated as UNVERIFIED, because those hashes do not
77+
# meet the estate's no-MD5/SHA1-for-security requirement.
78+
# Re-pinning (updating source_hash + version_pin + last_synced) is deliberately
79+
# NOT auto_fixable: changing what the standards repo points at is an owner call.
80+
@end
81+
82+
@end

0 commit comments

Comments
 (0)