Prepare accepted layer v0.8 publication and preserve immutable ADR history

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
tegwick 2026-10-11 02:06:56 +02:00
parent bfef142d4c
commit 5c41564f04
5 changed files with 307 additions and 2 deletions

View file

@ -303,7 +303,17 @@
"source_path": "canon/architecture/adr-002-custodian-agent-runtime-design.md",
"canonical_path": "adr/custodian-agent-runtime/v1/index.html",
"revision_path": "adr/custodian-agent-runtime/v1/revisions/{revision}/index.html",
"review_interval": "6m"
"review_interval": "6m",
"historical_revisions": [
{
"revision": "accepted-1",
"source_path": "docs/evidence/2026-10-11-adr-currency-review/adr-002-accepted-1.md",
"source_digest": "6aef66cd5cf71a48f5b4e14401dc19a755e2b182445b272d5952df0eb0dea8ec",
"source_revision": "4039c9d1c08c92014ecc0a65dda63cc73ba187bb",
"rendered_page_path": "docs/evidence/2026-10-11-adr-currency-review/adr-002-accepted-1.html",
"rendered_page_digest": "ea4eae05b62d50cd8204a47c1a4f3e3e0beda571880ed1de93ccbb17987d84aa"
}
]
},
{
"id": "CUST-ADR-003",
@ -319,7 +329,17 @@
"source_path": "canon/architecture/adr-004-connectivity-first-network-posture.md",
"canonical_path": "adr/custodian-connectivity-first/v1/index.html",
"revision_path": "adr/custodian-connectivity-first/v1/revisions/{revision}/index.html",
"review_interval": "6m"
"review_interval": "6m",
"historical_revisions": [
{
"revision": "accepted-1",
"source_path": "docs/evidence/2026-10-11-adr-currency-review/adr-004-accepted-1.md",
"source_digest": "3b68adfa6ab329e73f857cf691dc405136d2d66a0135e2c37c236aabe4659557",
"source_revision": "4039c9d1c08c92014ecc0a65dda63cc73ba187bb",
"rendered_page_path": "docs/evidence/2026-10-11-adr-currency-review/adr-004-accepted-1.html",
"rendered_page_digest": "3db1b0f5c50543ff0fb022ea67f1d85b30f8ff691eaa1a2443220b7474b54de7"
}
]
},
{
"id": "CUST-ADR-005",
@ -551,6 +571,16 @@
"source_path": "canon/standards/security-layer-model_v0.7.md",
"canonical_path": "standards/security-layer-model/v0.7/index.html",
"revision_path": "standards/security-layer-model/v0.7/revisions/{revision}/index.html",
"review_interval": "3m",
"lifecycle": "superseded",
"successor": "/standards/security-layer-model/v0.8/"
},
{
"id": "netkingdom-security-layer-model-v0.8",
"source_repo": "net-kingdom",
"source_path": "canon/standards/security-layer-model_v0.8.md",
"canonical_path": "standards/security-layer-model/v0.8/index.html",
"revision_path": "standards/security-layer-model/v0.8/revisions/{revision}/index.html",
"review_interval": "3m"
},
{

View file

@ -259,6 +259,12 @@
"source_path": "canon/standards/credential-management_v0.2.md",
"source_repo": "net-kingdom"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
"source_path": "canon/standards/emission-cadence-security-profile_v0.1.md",
"source_repo": "net-kingdom"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
@ -271,18 +277,36 @@
"source_path": "canon/standards/iam-profile_v0.3.md",
"source_repo": "net-kingdom"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
"source_path": "canon/standards/iam-profile_v0.4.md",
"source_repo": "net-kingdom"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
"source_path": "canon/standards/playbook-capability-contract_v0.1.md",
"source_repo": "net-kingdom"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
"source_path": "canon/standards/playbook-capability-contract_v0.2.md",
"source_repo": "net-kingdom"
},
{
"disposition": "published",
"reason": "Published through an explicit publication.json document entry.",
"source_path": "canon/standards/posture-feedback_v0.1.md",
"source_repo": "net-kingdom"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
"source_path": "canon/standards/security-case-types_v0.1.md",
"source_repo": "net-kingdom"
},
{
"disposition": "excluded",
"reason": "Superseded before publication by security-layer-model v0.2; retained as source history.",
@ -325,6 +349,12 @@
"source_path": "canon/standards/security-layer-model_v0.7.md",
"source_repo": "net-kingdom"
},
{
"disposition": "published",
"reason": "Published through an explicit publication.json document entry.",
"source_path": "canon/standards/security-layer-model_v0.8.md",
"source_repo": "net-kingdom"
},
{
"disposition": "published",
"reason": "Published through an explicit publication.json document entry.",
@ -373,6 +403,12 @@
"source_path": "docs/adr/ADR-0008-object-storage-sts-credential-vending.md",
"source_repo": "net-kingdom"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
"source_path": "docs/adr/ADR-0009-expanded-mode-keycloak-federation-topology.md",
"source_repo": "net-kingdom"
},
{
"disposition": "published",
"reason": "Published through an explicit publication.json document entry.",
@ -409,6 +445,24 @@
"source_path": "docs/adr/ADR-0015-netkingdom-railiance-workload-packaging-and-relational-platform.md",
"source_repo": "net-kingdom"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
"source_path": "docs/adr/ADR-0016-mfa-user-preference-and-workload-step-up.md",
"source_repo": "net-kingdom"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
"source_path": "docs/adr/ADR-0017-root-grant-custody-and-account-authority.md",
"source_repo": "net-kingdom"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
"source_path": "docs/architecture/expanded-mode-admission-requirements.md",
"source_repo": "net-kingdom"
},
{
"disposition": "published",
"reason": "Published through an explicit publication.json document entry.",
@ -589,6 +643,12 @@
"source_path": "docs/adr/ADR-0009-netkingdom-security-layer-interaction.md",
"source_repo": "railiance-master"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
"source_path": "docs/adr/ADR-0010-taxonomy-is-an-enumerated-layer.md",
"source_repo": "railiance-master"
},
{
"disposition": "published",
"reason": "Published through an explicit publication.json document entry.",
@ -613,6 +673,18 @@
"source_path": "docs/adr/ADR-0003-decisions-live-in-the-repo.md",
"source_repo": "railiance-platform"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
"source_path": "docs/adr/ADR-0004-service-assurance-disclosure.md",
"source_repo": "railiance-platform"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
"source_path": "docs/adr/ADR-0005-demand-gated-stateful-services.md",
"source_repo": "railiance-platform"
},
{
"disposition": "excluded",
"reason": "Directory index, not an architecture decision record.",
@ -889,6 +961,12 @@
"source_path": "canon/standards/credential-management_v0.1.md",
"source_repo": "the-custodian"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
"source_path": "canon/standards/delegated-owner-review_v0.1.md",
"source_repo": "the-custodian"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
@ -997,6 +1075,12 @@
"source_path": "canon/standards/schemas/work-records/spine.schema.json",
"source_repo": "the-custodian"
},
{
"disposition": "metadata-pending",
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
"source_path": "canon/standards/task-wait-qualifiers_v0.1.md",
"source_repo": "the-custodian"
},
{
"disposition": "unsupported-format",
"reason": "Inventoried governing source is not Markdown and has no renderer yet.",

View file

@ -1,5 +1,6 @@
from __future__ import annotations
import hashlib
import importlib.util
import json
import datetime as dt
@ -275,5 +276,90 @@ class PublicationTest(unittest.TestCase):
build_site.build(manifest, root / "site")
class HistoricalRevisionTest(unittest.TestCase):
def prepare(self, root):
manifest = _fixture(root)
data = json.loads(manifest.read_text())
repo = root / "canon-repo"
source = repo / "canon/standards/example.md"
source.write_text(source.read_text().replace(
'owner: example-owner', 'owner: example-owner\ndecided_by: Original Founder\ndate: "2026-08-18"'))
with mock.patch.object(build_site, "_source_revision", return_value="a" * 40):
build_site.build(manifest, root / "served", as_of=dt.date(2026, 8, 18))
original_page = (root / "served/standards/example/v1/revisions/draft-1/index.html").read_bytes()
history = repo / "docs/evidence/original.md"
history.parent.mkdir(parents=True)
history.write_bytes(source.read_bytes())
page = history.with_suffix(".html")
page.write_bytes(original_page)
data["documents"][0]["historical_revisions"] = [{
"revision": "draft-1", "source_path": "docs/evidence/original.md",
"source_digest": hashlib.sha256(history.read_bytes()).hexdigest(),
"source_revision": "a" * 40,
"rendered_page_path": "docs/evidence/original.html",
"rendered_page_digest": hashlib.sha256(original_page).hexdigest(),
}]
manifest.write_text(json.dumps(data))
source.write_text(source.read_text().replace('revision: "draft-1"', 'revision: "draft-2"')
+ "\nDated factual annotation; no new authority.\n")
return manifest, source, page, original_page
def test_clean_build_preserves_exact_served_revision_and_new_annotation(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
manifest, source, _page, original = self.prepare(root)
output = root / "clean"
with mock.patch.object(build_site, "_source_revision", return_value="b" * 40):
build_site.build(manifest, output, as_of=dt.date(2026, 8, 18))
old = output / "standards/example/v1/revisions/draft-1/index.html"
self.assertEqual(original, old.read_bytes())
self.assertNotIn(b"Dated factual annotation", old.read_bytes())
self.assertIn("Dated factual annotation",
(output / "standards/example/v1/index.html").read_text())
self.assertTrue((output / "standards/example/v1/revisions/draft-2/index.html").is_file())
meta, _ = build_site.split_frontmatter(source.read_text())
self.assertEqual("Original Founder", meta["decided_by"])
self.assertEqual("2026-08-18", meta["date"])
def test_changed_historical_page_fails_before_replacing_output(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
manifest, _source, page, _original = self.prepare(root)
page.write_bytes(page.read_bytes() + b"changed")
with self.assertRaisesRegex(RuntimeError, "rendered page digest mismatch"):
build_site.build(manifest, root / "clean")
self.assertFalse((root / "clean").exists())
def test_changed_historical_source_and_provenance_are_rejected(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
manifest, _source, page, _original = self.prepare(root)
history = page.with_suffix(".md")
history.write_bytes(history.read_bytes() + b"changed")
with self.assertRaisesRegex(RuntimeError, "source digest mismatch"):
build_site.build(manifest, root / "clean")
def test_existing_immutable_history_refuses_different_page_bytes(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
manifest, _source, _page, original = self.prepare(root)
old = root / "clean/standards/example/v1/revisions/draft-1/index.html"
old.parent.mkdir(parents=True)
old.write_bytes(original + b"changed")
with self.assertRaisesRegex(RuntimeError, "already has different bytes"):
build_site.build(manifest, root / "clean")
self.assertEqual(original + b"changed", old.read_bytes())
def test_historical_source_cannot_escape_owner_repository(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
manifest, _source, _page, _original = self.prepare(root)
data = json.loads(manifest.read_text())
data["documents"][0]["historical_revisions"][0]["source_path"] = "../publication.json"
manifest.write_text(json.dumps(data))
with self.assertRaisesRegex(ValueError, "inside source repository"):
build_site.build(manifest, root / "clean")
if __name__ == "__main__":
unittest.main()

View file

@ -111,6 +111,8 @@ def load_manifest(path: Path) -> dict[str, Any]:
document["canonical_path"],
document["revision_path"].replace("{revision}", "revision"),
*document.get("legacy_paths", []),
*(document["revision_path"].format(revision=h["revision"])
for h in document.get("historical_revisions", [])),
):
path_value = _safe_path(raw)
if path_value in seen:
@ -119,6 +121,44 @@ def load_manifest(path: Path) -> dict[str, Any]:
return manifest
def _restore_historical_revisions(
document: dict[str, Any], repo: Path, output: Path, current_revision: str,
) -> None:
"""Retain explicitly pinned served revision bytes even in a clean build."""
for historical in document.get("historical_revisions", []):
revision = historical["revision"]
if revision == current_revision:
raise ValueError("historical revision must differ from current revision")
source = (repo / historical["source_path"]).resolve()
page = (repo / historical["rendered_page_path"]).resolve()
for artifact in (source, page):
if repo not in artifact.parents or not artifact.is_file():
raise ValueError("historical artifact must be a file inside source repository")
source_bytes, page_bytes = source.read_bytes(), page.read_bytes()
if hashlib.sha256(source_bytes).hexdigest() != historical["source_digest"]:
raise RuntimeError("historical source digest mismatch")
if hashlib.sha256(page_bytes).hexdigest() != historical["rendered_page_digest"]:
raise RuntimeError("historical rendered page digest mismatch")
meta, _ = split_frontmatter(source_bytes.decode("utf-8"))
if meta.get("id") != document["id"] or meta.get("revision") != revision:
raise ValueError("historical source id/revision mismatch")
page_text = page_bytes.decode("utf-8")
source_revision = SOURCE_REVISION.search(page_text)
source_digest = SOURCE_DIGEST.search(page_text)
expected_revision = historical["source_revision"]
if not CLEAN_GIT_REVISION.fullmatch(expected_revision):
raise ValueError("historical source revision must be a clean Git commit")
if (not source_revision or not source_digest
or html.unescape(source_revision.group(1)) != expected_revision
or html.unescape(source_digest.group(1)) != historical["source_digest"]):
raise RuntimeError("historical page/source provenance mismatch")
target = output / _safe_path(document["revision_path"].format(revision=revision))
if target.exists() and target.read_bytes() != page_bytes:
raise RuntimeError("immutable historical revision already has different bytes")
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(page_bytes)
def _redirect(target: str, title: str) -> str:
escaped = html.escape(target, quote=True)
return (
@ -214,6 +254,7 @@ def build(
revision = meta.get("revision") or meta.get("version")
if not revision:
raise ValueError(f"{source}: revision or version is required")
_restore_historical_revisions(document, repo, temporary, revision)
source_revision = _source_revision(repo, source)
source_digest = hashlib.sha256(source.read_bytes()).hexdigest()
reviewed = meta.get("last_reviewed") or meta.get("updated")

View file

@ -0,0 +1,64 @@
---
id: PNEX-WP-0004
type: workplan
title: "Publish the accepted security-layer v0.8 guardrails"
domain: infotech
repo: policy-nexus
status: active
owner: codex
topic_slug: custodian
created: "2026-10-11"
updated: "2026-10-11"
flavor: implementation
depends_on: []
---
Native human memo `infd-20261010-security-layer-v08` revision2 accepted exact
proposed source103a635f; confirmed approval8432d0dc. NetKingdom accepted artifact
3d829856 is committed in0e17d956ab45f8c4a0163b70749490c414e54e67.
This is new requested publication demand, not a residual or duplicate checker
migration. GH-WP-0004-T11 is source-complete; hosting is this owner's task.
## Register accepted source and preserve history
```task
id: PNEX-WP-0004-T01
status: done
priority: high
```
Register accepted-1 v0.8 and superseded currentv0.7 notice; retain immutable
historicalv0.7 source/page bytes. Full local73document build succeeded. Inventory
refresh adds13 previously unreviewed discovered records as metadata-pending,
changes no existing disposition and publishes none of those13. Exact memo/source
and native acceptance receipts stay with NetKingdom and Custodian evidence.
## Produce a verified source-bound publisher candidate
```task
id: PNEX-WP-0004-T02
status: progress
priority: high
```
Retain immutable fetched source revision lock, content/manifest hashes and full
release verification. Local build alone is not a served publication. Current
currency gate honestly reports CUST-ADR-002/004 stale; owner technical review
must establish their truthful status, not blindly bump dates or suppress them.
Publish/verify the candidate through documented owner CI; retain any failed
checks and exact release output before proposing native activation.
## Serve and verify the accepted guardrails
```task
id: PNEX-WP-0004-T03
status: todo
priority: high
```
Prepare exact hosting revision/image and rollback, native resource/headroom
preflight and source-bound deployment diff. Obtain required scoped supervisor
disposition if hosting activation is outside the existing source-publication
approval. Execute through owner path; independently verify v0.8 accepted page,
old route supersession notice and immutableoldrevision survival. No permission
or PDP/runtime capability is introduced by publishing static guardrails.