From a4bd05fa46a08c6a4a6a8d263aac3a602c5be872 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" Date: Thu, 15 Oct 2026 18:20:44 +0100 Subject: [PATCH] feat(hypatia-rules): identity coherence and SHA pinning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reifies the two finding classes from the 2026-10-15 CodeRabbit estate sweep into scanner rules, so the next occurrence is caught by the scanner instead of by a reviewer bot: - HYP-UUID-001 rewritten for ADR-008 as amended by D320: estate UUIDs are UUIDv8 profile C over registered domains (check-uuid-v8.sh is the gate), v5/v7 literals are rejected, a preimage identifying a different repository is a violation, and Julia [deps] mappings must use the package's own declared uuid (the CompositionalDA oracle finding). Supersedes the v7 rule from #1063. - HYP-S010 clade-identity-coherence (new): a CLADE.a2ml must declare THIS repository — owner, canonical-name, forges, born — and carry the recomputable profile-C mint for that declaration, with CLAUDE.md in agreement. Codifies the odraz #19 lesson: the fix direction is an owner ruling, and a reviewer bot must not align the uuid to template residue. - HYP-S011 uses-ref-sha-pinned (new): every workflow uses: ref is a full commit SHA, tags only as comments (the choreographic-types #33 finding). States the content-level invariant PI001-PI005 assume but never state; auto-fixable via the actions.lock. README table updated (eleven rules). Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe --- hypatia-rules/README.adoc | 10 ++-- hypatia-rules/clade-identity-coherence.a2ml | 54 +++++++++++++++++++ hypatia-rules/uses-ref-sha-pinned.a2ml | 46 ++++++++++++++++ hypatia-rules/uuid-v7-estate-conformance.a2ml | 17 ------ hypatia-rules/uuid-v8-estate-conformance.a2ml | 22 ++++++++ 5 files changed, 128 insertions(+), 21 deletions(-) create mode 100644 hypatia-rules/clade-identity-coherence.a2ml create mode 100644 hypatia-rules/uses-ref-sha-pinned.a2ml delete mode 100644 hypatia-rules/uuid-v7-estate-conformance.a2ml create mode 100644 hypatia-rules/uuid-v8-estate-conformance.a2ml diff --git a/hypatia-rules/README.adoc b/hypatia-rules/README.adoc index 7cf87ac46..f338fefae 100644 --- a/hypatia-rules/README.adoc +++ b/hypatia-rules/README.adoc @@ -1,9 +1,9 @@ // SPDX-License-Identifier: CC-BY-SA-4.0 = Standards-Specific Hypatia Rules -:status: Draft v0.3.0 -:updated: 2026-08-24 +:status: Draft v0.4.0 +:updated: 2026-10-15 -Eight Hypatia rules specific to the standards-repo dogfooding loop. +Eleven Hypatia rules specific to the standards-repo dogfooding loop. Each rule is defined in A2ML, consumes VeriSimDB octads or the repo file tree, and emits Groove `compliance.finding.new` signals. @@ -18,7 +18,9 @@ tree, and emits Groove `compliance.finding.new` signals. | HYP-S005 | `crg-overclaim-detector` | Alert when a self-declared CRG grade lacks v2.0 evidence artefacts | | HYP-S007 | `profile-drift-detector` | Flag a descriptiles file whose content drifts from its declared A2ML `@profile` | | HYP-S008 | `workflow-allowlist-gap` | Flag a workflow `uses:` an action/reusable not permitted by the repo Actions allowlist (would `startup_failure`) | -| HYP-UUID-001 | `uuid-v7-estate-conformance` | Reject non-v7 UUID literals and untracked legacy identifiers across both estates | +| HYP-S010 | `clade-identity-coherence` | A CLADE.a2ml must declare THIS repository (not template residue) and carry the recomputable profile-C mint for that declaration, with CLAUDE.md in agreement | +| HYP-S011 | `uses-ref-sha-pinned` | Every workflow `uses:` ref is a full commit SHA; version tags and moving refs are findings | +| HYP-UUID-001 | `uuid-v8-estate-conformance` | Enforce ADR-008 (amended D320): UUIDv8 profile C over registered domains, no v5/v7 literals, self-consistent Julia dependency uuid mappings | The CRG rule pair (S001 + S005) together enforce grade-honesty: S001 catches backwards moves, S005 catches forwards-overshoots. Both read from diff --git a/hypatia-rules/clade-identity-coherence.a2ml b/hypatia-rules/clade-identity-coherence.a2ml new file mode 100644 index 000000000..7fc12c644 --- /dev/null +++ b/hypatia-rules/clade-identity-coherence.a2ml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: MPL-2.0 +# HYP-S010 — CLADE Identity Coherence +# Fires when a repository's CLADE.a2ml identity block does not cohere. +# +# Root cause seen 2026-10-15 on metadatastician/odraz (PR #19): the repo's +# CLADE.a2ml was copied verbatim from the template, so its [identity] block +# declared rsr-template-repo (owner, canonical-name, prefixed-name, forges, +# born) while the minted uuid was recomputed for metadatastician/odraz — +# two true facts pointing at different repositories. A reviewer bot then +# aligned the uuid to the stale declaration, which was the wrong direction: +# the declaration was the residue. Every CLADE must declare THIS repository +# and carry the recomputable profile-C mint for that declaration, with +# CLAUDE.md in agreement. This rule reifies the coherence invariant so the +# direction of the fix is not a judgement call made under review pressure. + +@rule(version="1.0"): +id: HYP-S010 +name: "CLADE identity coherence" +description: "The [identity]/[forges] block of .machine_readable/descriptiles/CLADE.a2ml must declare THIS repository, and its uuid must be the recomputable UUIDv8 profile-C mint for the declared owner/name (domain gv-clade-index)" +severity: high +category: IdentityIntegrity +auto_fixable: false # the direction of the fix (declaration vs preimage) is an owner ruling, not a rewrite +source: standards/hypatia-rules + +@scanner(type="file-tree"): +target_repo: "any estate repo carrying .machine_readable/descriptiles/CLADE.a2ml" +reference: "docs/decisions/ADR-008-uuid-v8-estate-standard.adoc" +@end + +@parameters: +profile_c_domains: docs/decisions/ADR-008-profile-c-domains.json +identity_domains: [gv-clade-index, julia-package-identity] +template_canonical_name: rsr-template-repo +template_repo: hyperpolymath/rsr-template-repo +@end + +@must: +# the declaration is THIS repo, not the template it was instantiated from +- "primary-owner + canonical-name identify the repository the file lives in (canonical-name == rsr-template-repo outside hyperpolymath/rsr-template-repo is template residue)" +- "forges.github names the repository's actual home; empty rather than the template's forge" +- "born does not predate the repository's first commit" +# the uuid is recomputable from the declaration +- "uuid == profile-C(domain=gv-clade-index, name=github.com//)" +- "the recorded preimage comment, the derivation note, and the uuid agree" +# both metadata documents agree +- "CLAUDE.md's identity line carries the same uuid, preimage, forge, and born as CLADE.a2ml" +@end + +@query(engine="vcl"): +clade_identity(scan="repo-tree", file=".machine_readable/descriptiles/CLADE.a2ml") + |> where("not declares_this_repository(identity, repo)") + |> or_where("uuid != profile_c_mint('gv-clade-index', identity.primary_owner, identity.canonical_name)") + |> or_where("disagrees_with(claude_md_identity_line)") + |> emit("compliance.finding.new", severity=high) diff --git a/hypatia-rules/uses-ref-sha-pinned.a2ml b/hypatia-rules/uses-ref-sha-pinned.a2ml new file mode 100644 index 000000000..eb51ea9e9 --- /dev/null +++ b/hypatia-rules/uses-ref-sha-pinned.a2ml @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: MPL-2.0 +# HYP-S011 — Workflow uses refs are SHA-pinned +# Fires when a `.github/workflows` `uses:` reference resolves to a version +# tag (or any moving ref) instead of a full commit SHA. +# +# Root cause seen 2026-10-15 on hyperpolymath/choreographic-types (PR #33): +# a governance-repair PR restored `actions/checkout@v7.0.1` — a tag — in +# codeql.yml and proofs.yml, while the estate's pinning policy +# (sha_pinning_required) and the actions.lock both speak in SHAs. A tag can +# be re-pointed upstream at any time, which silently changes what the +# workflow executes; the lock's identity checks (HYPATIA PI002/PI004) +# compare SHA-to-comment, so a tag ref sidesteps them entirely. +# +# Tags remain welcome as *comments* (`@3d3c42e5… # v7.0.1`) — the ref is +# the SHA, the comment is the label. This rule enforces the content-level +# invariant that PI001-PI005 assume but never state. + +@rule(version="1.0"): +id: HYP-S011 +name: "Workflow uses refs are SHA-pinned" +description: "Every `uses:` ref in .github/workflows must resolve to a full commit SHA (40 hex chars); version tags and moving refs are rejected at content level" +severity: medium +category: CiResilience +auto_fixable: true # rewriting @vX.Y.Z to the locked SHA is mechanical when the lock carries the entry +source: standards/hypatia-rules + +@scanner(type="repo-tree"): +dir: ".github/workflows" +glob: "*.{yml,yaml}" +@end + +@parameters: +exempt_refs: [docker://] # container refs use digests, not SHAs +@end + +@must: +- "every `uses:` ref is a full commit SHA (^[0-9a-f]{40}$), with the tag kept only as a trailing comment" +- "a ref matching ^v?\d+(\.\d+)*$ (a version tag) or ^[a-z]+$ that is not a SHA (a moving ref: stable, latest, main) is a finding" +- "the SHA agrees with the repo's actions.lock entry for the same action (defers to PI004 for the comparison itself)" +- "local reusable workflows (`./…`) are exempt — they resolve within the repo" +@end + +@query(engine="vcl"): +workflow_uses(scan="repo-tree", dir=".github/workflows") + |> where("ref !~ '^[0-9a-f]{40}$' and ref !~ '^\\./' and ref !~ '^docker://'") + |> emit("compliance.finding.new", severity=medium, fix="resolve the tag to its commit SHA via the actions.lock and pin the ref, keeping the tag as a comment") diff --git a/hypatia-rules/uuid-v7-estate-conformance.a2ml b/hypatia-rules/uuid-v7-estate-conformance.a2ml deleted file mode 100644 index de5cceb4f..000000000 --- a/hypatia-rules/uuid-v7-estate-conformance.a2ml +++ /dev/null @@ -1,17 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 -; HYP-UUID-001 — estate UUID policy enforcement. - -(rule - (id "HYP-UUID-001") - (name "Estate UUIDs are version 7") - (severity high) - (category identity-integrity) - (source "docs/UUID-V7-ESTATE-STANDARD.adoc") - (applies-to ("hyperpolymath" "metadatastician")) - (must - "all newly generated UUIDs use an approved UUID v7 API" - "all UUID literals have version nibble 7 and RFC 9562 variant 10" - "legacy values have an auditable migration alias and explicit type" - "unknown and unmeasured repositories are not reported as compliant") - (gate "scripts/check-uuid-v7.sh") - (remediation "migrate with a reviewed mapping; do not hash or copy legacy UUIDs")) diff --git a/hypatia-rules/uuid-v8-estate-conformance.a2ml b/hypatia-rules/uuid-v8-estate-conformance.a2ml new file mode 100644 index 000000000..83768e639 --- /dev/null +++ b/hypatia-rules/uuid-v8-estate-conformance.a2ml @@ -0,0 +1,22 @@ +; SPDX-License-Identifier: MPL-2.0 +; HYP-UUID-001 — estate UUID policy enforcement (ADR-008, amended by D320). +; Supersedes uuid-v7-estate-conformance.a2ml: the estate standardised on v7 +; (#1063) and was amended to strict UUIDv8 profile C by owner ruling D320 +; (2026-10-07). The gates run strict, version 8 only. + +(rule + (id "HYP-UUID-001") + (name "Estate UUIDs are version 8, profile C, over registered domains") + (severity high) + (category identity-integrity) + (source "docs/decisions/ADR-008-uuid-v8-estate-standard.adoc (amendment D320)") + (applies-to ("hyperpolymath" "metadatastician")) + (must + "all newly generated UUIDs are UUIDv8 profile C: bytes 0..15 of SHA-256(domain \":\" name), version nibble 8, RFC 9562 variant 10" + "the domain is registered in docs/decisions/ADR-008-profile-c-domains.json and the preimage identifies the artifact itself, never another repository" + "all UUID literals have version nibble 8 and RFC 9562 variant 10; v5 and v7 literals are rejected by the strict gate" + "a Julia [deps] mapping that names an estate package uses that package's own declared uuid, so every Project.toml in the repo is self-consistent" + "legacy values have an auditable migration alias and explicit type" + "unknown and unmeasured repositories are not reported as compliant") + (gate "scripts/check-uuid-v8.sh") + (remediation "re-mint from the registered domain and the artifact's own preimage; never hash-copy legacy UUIDs and never retain a preimage that identifies a different repository"))