Skip to content

fix!: repoint published schema namespace to a controlled URI root - #939

Merged
Brad-Edwards merged 5 commits into
devfrom
908-gov-944-schema-namespace-uri
Jul 28, 2026
Merged

fix!: repoint published schema namespace to a controlled URI root#939
Brad-Edwards merged 5 commits into
devfrom
908-gov-944-schema-namespace-uri

Conversation

@Brad-Edwards

@Brad-Edwards Brad-Edwards commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Three related fixes on the identity-cutover surface, found by pulling one thread.

1. The published schema namespace pointed at a domain we do not own.

The canonical root was https://raes.dev/schemas/, adopted during the identity cutover in #921. raes.dev is registered to a third party — as was aces.dev before it, which belongs to the Auto Care Association (ACES = Aftermarket Catalog Exchange Standard) and redirects to their site.

Using a domain the project does not control forfeits the one property the URI form of $id exists to provide: that the identifier is unambiguously ours to assign. It also left #908 blocked on OBL-908-REMOTE-IDENTITIES-1, an obligation to "provision raes.dev" that cannot be discharged.

Per the JSON Schema specification, $id is an identifier and imposes no behaviour — "JSON Schema does not guarantee that a schema with an HTTP URL identifier is actually resolvable at such URL." Official guidance is to use a domain you control when serving schemas over HTTP, and a non-locatable URI otherwise. Nothing in this repo dereferences $id.

The root is now https://raesystem.github.io/rae/, bound to this repository's own GitHub organisation: uniquely controlled, no DNS obligation, and matching the Pages path if the schemas are ever served. The alternative considered was urn:uuid: per schema — unimpeachable but opaque across 84 contracts.

2. The identity gate was guaranteed to go stale on every release.

check_identity_cutover pinned CHANGELOG.md by whole-file digest, but release-please rewrites that file on every release, inserting each new section above the existing ones. The pin goes stale by construction.

It did at v2.0.0. PR #937 back-merged the release into dev with the required verify check failing, and it was merged anyway — dev has been red on this gate since 2026-07-27.

generated-release-history replaces the whole-file pin with one that matches how the file is actually maintained: the classified tail is pinned exactly, and everything written above it is held to the live-tree rule of zero retired identity occurrences. This is stricter than what it replaces — the old rule permitted any content anywhere so long as the digest was re-recorded, which is precisely the re-record-and-move-on habit that let the override stand. Editing or truncating the tail still fails closed.

3. Nothing checked the text that lands in the changelog.

Fixing (2) leaves the failure in the right place but still late: a retired name in a commit subject would pass every check on its own PR and surface days later as a red release PR. The guard that already runs on PRs into dev now rejects retired naming in the title and in BREAKING CHANGE: footers.

The text it checks is the text that actually reaches the changelog. This repo squashes with squash_merge_commit_title=PR_TITLE and squash_merge_commit_message=COMMIT_MESSAGES, so that is the PR title plus the commit messages — not the PR body. The workflow supplies the commit messages via a read-only API call written to a file, never shell-interpolated. Body prose is deliberately not checked: explaining the retired name is legitimate — this description does it — and prose outside a footer never reaches the changelog.

Changes

  • 84 published $id values, the semantic-invariant profile URI, and the concept authority reference repointed (133 occurrences across 91 files).
  • _RAES_SEMANTIC_INVARIANT_PROFILE_URI and the Literal annotations in raes_contracts updated to match.
  • Schema-publication content hashes refreshed for all 84 affected entries, with a change-ledger summary on each.
  • New generated-release-history record class in check_identity_cutover; manifest schema moves to historical-identity-records/v3.
  • New pr-title-retired-identity rule in check_pr_title, with the pattern held equivalent to the gate's by test rather than imported (the guard is stdlib-only by design).
  • Both identity corrections recorded as ADR-096 amendments with adr-index.yaml entries; the index identity digest is re-pinned.

Historical records under docs/decisions/ are otherwise untouched, per the ADR-096 boundary.

Verification

  • All tools/check_*.py gates pass, including check_identity_cutover — which fails on dev today.
  • Identity gate adversarially tested: retired identity in the generated head, edited tail, truncated tail, and wrong tail occurrence count each fail closed; a simulated future release passes.
  • PR guard tested for both directions, including that surfaces, interfaces, and namespaces do not trip the word-boundary-anchored rule.
  • Full Python suite: 5132 passed, 1 skipped.

Follow-up, not fixed here

  • OBL-908-REMOTE-IDENTITIES-1 on ACES naming audit: 2020 live occurrences remain after the rename (full inventory) #908 is premised on provisioning raes.dev. There is no domain to provision; it should be closed as invalid. The SonarCloud and Ground Control project-identifier halves are unaffected by this PR.
  • dev has enforce_admins: false, which is what allowed a failing required check to be merged. With a single maintainer, enabling it would block all merges, so this PR does not touch it.

Refs #908

The canonical published-schema URI root was `https://raes.dev/schemas/`,
carried over from the identity cutover in #921. That domain is registered to
a third party, as was the root it replaced. Using an uncontrolled domain in
`$id` gives up the only property the URI form buys — that the identifier is
unambiguously ours to assign — and left issue #908 blocked on provisioning a
domain the project cannot obtain.

Repoint every published `$id`, the semantic-invariant profile URI, and the
concept authority reference at `https://raesystem.github.io/rae/`, which is
bound to the repository's own GitHub organisation. `$id` is an identifier and
is not required to resolve, so this carries no DNS or hosting obligation;
the path matches the org's GitHub Pages layout if the schemas are ever served.

Refresh the schema-publication content hashes for the 84 affected schemas and
record the correction as an amendment to ADR-096.

Also re-pin the CHANGELOG.md identity digest, which went stale on dev when
release-please appended the 2.0.0 notes. The appended content introduces no
retired identity occurrences; the count is unchanged at 297.

BREAKING CHANGE: published schema `$id` values move from
`https://raes.dev/schemas/` to `https://raesystem.github.io/rae/schemas/`.
Consumers pinning or caching schema identifiers must update them.

Refs #908
`check_identity_cutover` pinned CHANGELOG.md by whole-file digest, but
release-please rewrites that file on every release, inserting each new section
above the existing ones. The pin therefore goes stale at every release by
construction. It did at v2.0.0: PR #937 back-merged the release into `dev` with
the required `verify` check failing, and the failure was overridden rather than
fixed, leaving `dev` red on this gate.

Add a `generated-release-history` record class that matches how the file is
actually maintained. It pins the classified tail exactly and holds everything
written above it to the live-tree rule of zero retired identity occurrences, so
newly generated content carries no historical exemption. Editing or truncating
the tail still fails closed.

This is strictly stricter than the whole-file pin it replaces: the old rule
allowed any content anywhere in the file so long as the digest was re-recorded,
which is exactly the re-record-and-move-on habit that let the override stand.

Manifest schema moves to historical-identity-records/v3.

Refs #908
The identity gate holds the generated head of CHANGELOG.md to zero retired
identity occurrences, but nothing checked the text that lands there. A feature
PR into `dev` is squash-merged, so its title becomes the commit release-please
writes into the changelog, and any BREAKING CHANGE footer becomes a changelog
entry. A retired name in either surfaced days later as a red release PR, far
from the change that caused it.

Check both at the point they are authored, in the existing PR title guard that
already runs on PRs targeting `dev` from the trusted base ref.

Body prose is deliberately not checked. Explaining the retired name is
legitimate — this PR's own description does it — and only the title and
breaking-change footers reach the changelog.

The pattern is duplicated in text form rather than imported, because
check_pr_title is stdlib-only by design and check_identity_cutover pulls in
PyYAML. A test asserts the two patterns agree on the same samples so they
cannot drift.

Refs #908
The previous commit checked the PR body for BREAKING CHANGE footers. That is
the wrong text. This repository squashes with
squash_merge_commit_title=PR_TITLE and squash_merge_commit_message=COMMIT_MESSAGES,
so the squash commit body release-please parses is the concatenated commit
messages; the PR body never reaches it.

Supply the PR's commit messages to the guard from the workflow and check those.
The messages are fetched with a read-only token and written to a file rather
than passed as an argument, so untrusted PR data is never shell-interpolated.
The PR body is still checked, so the rule holds if the squash setting changes.

Refs #908
Two CI failures on this branch.

`ruff format` rejected tests/test_runtime_contracts.py. The namespace rewrite
lengthened a URI past the 120-column limit and I only linted the files I edited
by hand, not the ones the rewrite touched.

`title-guard` failed with "unrecognized arguments: --commit-messages-file". The
workflow file comes from the PR head but the checker is deliberately taken from
the base ref, so on the PR that introduces the flag the base copy cannot accept
it yet. Probe for support before passing it, matching the bootstrap shape the
workflow already uses for a missing checker. Commit-message footer enforcement
becomes active from the next PR onward; title enforcement is unaffected now.

Refs #908
@Brad-Edwards
Brad-Edwards merged commit ae11cd0 into dev Jul 28, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant