Summary
Add a reusable verify-decisions workflow (workflow_call, org-only guard like the other reusables) that checks a repository's decisions/ folder against the shared decision-record front-matter shape, so the convention is enforceable in any Cratis repository on the day it opts in, independent of corpus distribution.
Current behaviour
- This repository ships seven
workflow_call reusables (cleanup-pr-artifacts.yml, propagate-copilot-instructions.yml, sync-copilot-instructions.yml, trigger-documentation-build.yml, update-packages.yml, verify-no-work-records.yml, verify-release-intent.yml). The two governance gates, verify-no-work-records and verify-release-intent, are the organisation's proven way to put a rule in front of every PR.
- No
verify-decisions workflow exists (the string appears nowhere in the repository).
- Decision records across the organisation have no shared checker: Cratis/Components keeps four tracked records under
Documentation/decisions/ with Starlight-only front matter and status in body prose; other repositories keep decisions in .agents/PROJECT.md or in untracked work files. Nothing verifies that an accepted record names a decider and a date, that supersession pointers are two-way, or that the check a record claims to be enforced by still exists.
- Corpus profiles cannot carry a checker (passive skills forbid scripts and hooks), so a checker as a reusable workflow is the only distribution path that reaches repositories today.
Desired behaviour
.github/workflows/verify-decisions.yml, workflow_call, with inputs:
decisions_path (default decisions)
schema_path (default: the schema vendored in this repository under .github/scripts/decision-record.frontmatter.schema.json)
require_census (boolean, default true)
The job runs .github/scripts/verify-decisions.mjs (Node, no network) which checks, per file under decisions_path:
- the filename prefix agrees with the front-matter
status (proposed · returned · accepted · rejected · deferred · superseded);
- an
accepted, rejected, deferred or superseded record carries a decided date and a decider that resolves to a named actor (a bare role fails);
superseded-by and supersedes point both ways;
stage: verified carries at least one evidence entry;
- every
enforced-by path resolves in the repository (a workflow file is an acceptable target);
- the index's census line agrees with the files (when
require_census);
- the front matter validates against the schema.
Checker conventions: exit 0 and print counts (records parsed, links resolved, checks run) when clean; exit 1 with rule id, file and line per defect; exit 2 with the missing prerequisite when it cannot run (no decisions_path, unreadable schema, git ls-files failure). --self-test builds a temporary fixture with one planted defect per rule plus a clean control and asserts exactly those defects; a workflow like verify-ai-profile-subscription-update.yml runs node --check, node --test and --self-test on every change to the script.
Rule 1 of the org guard applies: if: ${{ github.repository_owner == 'Cratis' }} as in propagate-copilot-instructions.yml:45.
Acceptance
Done when:
- The workflow and script exist;
README.md documents the three inputs and the wrapper a repository adds.
verify-decisions.mjs --self-test passes and is run by CI on changes to the script.
- One repository with tracked decision records (Cratis/Components is the candidate) runs the wrapper green after adding the front matter fields (tracked in that repository).
Verify by:
node .github/scripts/verify-decisions.mjs --self-test # prints "self-test: 7 planted, 7 red, control 0"
node .github/scripts/verify-decisions.mjs --decisions-path tests/fixtures/decisions-clean # exit 0, prints counts
node .github/scripts/verify-decisions.mjs --decisions-path does-not-exist # exit 2, names the prerequisite
Mutation proof: in the clean fixture, delete the file named by one record's enforced-by; the run exits 1 naming rule 5 and the record.
Out of scope
The decision-record skill and vocabulary (Cratis/AI); repository-specific wrappers; making the check required (an org-admin ruleset action).
Dependencies
Source: AI-Learning F-75. Depends on: D-0001, D-0002 (decision ids for where records live and the single record format); the verify-no-work-records allowlist amendment in this repository (adopting repositories otherwise fight that gate); the exit-2/self-test convention issue in this repository (same conventions).
Summary
Add a reusable
verify-decisionsworkflow (workflow_call, org-only guard like the other reusables) that checks a repository'sdecisions/folder against the shared decision-record front-matter shape, so the convention is enforceable in any Cratis repository on the day it opts in, independent of corpus distribution.Current behaviour
workflow_callreusables (cleanup-pr-artifacts.yml,propagate-copilot-instructions.yml,sync-copilot-instructions.yml,trigger-documentation-build.yml,update-packages.yml,verify-no-work-records.yml,verify-release-intent.yml). The two governance gates,verify-no-work-recordsandverify-release-intent, are the organisation's proven way to put a rule in front of every PR.verify-decisionsworkflow exists (the string appears nowhere in the repository).Documentation/decisions/with Starlight-only front matter and status in body prose; other repositories keep decisions in.agents/PROJECT.mdor in untracked work files. Nothing verifies that an accepted record names a decider and a date, that supersession pointers are two-way, or that the check a record claims to be enforced by still exists.Desired behaviour
.github/workflows/verify-decisions.yml,workflow_call, with inputs:decisions_path(defaultdecisions)schema_path(default: the schema vendored in this repository under.github/scripts/decision-record.frontmatter.schema.json)require_census(boolean, defaulttrue)The job runs
.github/scripts/verify-decisions.mjs(Node, no network) which checks, per file underdecisions_path:status(proposed · returned · accepted · rejected · deferred · superseded);accepted,rejected,deferredorsupersededrecord carries adecideddate and adeciderthat resolves to a named actor (a bare role fails);superseded-byandsupersedespoint both ways;stage: verifiedcarries at least oneevidenceentry;enforced-bypath resolves in the repository (a workflow file is an acceptable target);require_census);Checker conventions: exit
0and print counts (records parsed, links resolved, checks run) when clean; exit1with rule id, file and line per defect; exit2with the missing prerequisite when it cannot run (nodecisions_path, unreadable schema,git ls-filesfailure).--self-testbuilds a temporary fixture with one planted defect per rule plus a clean control and asserts exactly those defects; a workflow likeverify-ai-profile-subscription-update.ymlrunsnode --check,node --testand--self-teston every change to the script.Rule 1 of the org guard applies:
if: ${{ github.repository_owner == 'Cratis' }}as inpropagate-copilot-instructions.yml:45.Acceptance
Done when:
README.mddocuments the three inputs and the wrapper a repository adds.verify-decisions.mjs --self-testpasses and is run by CI on changes to the script.Verify by:
Mutation proof: in the clean fixture, delete the file named by one record's
enforced-by; the run exits1naming rule 5 and the record.Out of scope
The decision-record skill and vocabulary (Cratis/AI); repository-specific wrappers; making the check required (an org-admin ruleset action).
Dependencies
Source: AI-Learning F-75. Depends on: D-0001, D-0002 (decision ids for where records live and the single record format); the
verify-no-work-recordsallowlist amendment in this repository (adopting repositories otherwise fight that gate); the exit-2/self-test convention issue in this repository (same conventions).