From 7c51a7b1856dc0f42bc776432852ce77a2ebe4c7 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 11 Oct 2026 02:25:29 +0200 Subject: [PATCH] Preserve complete served policy history in clean release builds Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade --- .../complete-history-validation.json | 30 + .../content-delta.json | 147 ++ .../content-review.json | 101 ++ .../immutable-audit.json | 798 +++++++++ .../live-baseline.json | 119 ++ .../public-baseline-manifest.json | 1230 ++++++++++++++ .../source-exports.json | 555 +++++++ .../transition-failure.txt | 76 + .../README.md | 9 + .../pages/ACT-ADR-001.html | 247 +++ .../pages/ACT-ADR-002.html | 437 +++++ .../pages/ACT-ADR-003.html | 294 ++++ .../pages/ACT-ADR-004.html | 224 +++ .../pages/ACT-ADR-005.html | 249 +++ .../pages/ACT-ADR-006.html | 228 +++ .../pages/ACT-ADR-007.html | 226 +++ .../pages/CUST-ADR-001.html | 257 +++ .../pages/CUST-ADR-002.html | 242 +++ .../pages/CUST-ADR-003.html | 248 +++ .../pages/CUST-ADR-004.html | 236 +++ .../pages/CUST-ADR-005.html | 225 +++ .../pages/CUST-ADR-006.html | 253 +++ .../pages/CUST-ADR-007.html | 279 ++++ .../pages/CUST-ADR-010.html | 253 +++ .../pages/CUST-ADR-011.html | 284 ++++ .../pages/CUST-ADR-012.html | 248 +++ .../pages/NK-ADR-0006.html | 219 +++ .../pages/NK-ADR-0007.html | 230 +++ .../pages/NK-ADR-0008.html | 216 +++ .../pages/NK-ADR-0010.html | 221 +++ .../pages/NK-ADR-0011.html | 227 +++ .../pages/NK-ADR-0012.html | 227 +++ .../pages/NK-ADR-0013.html | 241 +++ .../pages/NK-ADR-0014.html | 225 +++ .../pages/NK-ADR-0015.html | 229 +++ .../pages/RINFRA-ADR-0005.html | 211 +++ .../pages/RISK-F-0001.html | 245 +++ .../pages/RISK-F-0008.html | 274 ++++ .../pages/RISK-METHOD-DEPENDENCIES.html | 238 +++ .../pages/RISK-METHOD-DISCLOSURE.html | 232 +++ .../pages/RISK-METHOD-REVIEW.html | 244 +++ .../pages/RISK-METHOD-SEVERITY.html | 256 +++ .../pages/RISK-METHOD-VERIFICATION.html | 222 +++ .../pages/RMASTER-ADR-0001.html | 235 +++ .../pages/RMASTER-ADR-0002.html | 218 +++ .../pages/RMASTER-ADR-0003.html | 216 +++ .../pages/RMASTER-ADR-0004.html | 217 +++ .../pages/RMASTER-ADR-0005.html | 205 +++ .../pages/RMASTER-ADR-0006.html | 207 +++ .../pages/RMASTER-ADR-0007.html | 213 +++ .../pages/RMASTER-ADR-0008.html | 218 +++ .../pages/RMASTER-ADR-0009.html | 214 +++ .../pages/RPLAT-ADR-0001.html | 209 +++ .../pages/RPLAT-ADR-0002.html | 212 +++ .../pages/RPLAT-ADR-0003.html | 211 +++ .../pages/canon-autonomy-lanes.html | 222 +++ .../pages/canon-contrib-convention.html | 273 ++++ .../canon-project-repository-flavor.html | 279 ++++ .../pages/canon-work-record-types.html | 246 +++ .../canon-workplan-terminology-fleet.html | 236 +++ .../pages/coulomb-estate-architecture.html | 274 ++++ .../pages/net-kingdom-architecture.html | 244 +++ .../pages/netkingdom-iam-profile-v0.3.html | 319 ++++ .../netkingdom-posture-feedback-v0.1.html | 224 +++ .../netkingdom-security-layer-model-v0.7.html | 461 ++++++ ...om-security-scenario-composition-v0.1.html | 239 +++ .../pages/netkingdom-security-zones-v0.1.html | 320 ++++ .../pages/netkingdom-tenancy-posture.html | 496 ++++++ .../pages/ops-warden-adr-0001.html | 216 +++ .../pages/ops-warden-adr-0002.html | 216 +++ .../pages/ops-warden-adr-0003.html | 218 +++ .../pages/ops-warden-adr-0004.html | 216 +++ .../pages/ops-warden-adr-0005.html | 214 +++ .../pages/ops-warden-adr-0007.html | 217 +++ .../pages/ops-warden-adr-0008.html | 215 +++ .../pages/ops-warden-adr-0009.html | 215 +++ .../pages/ops-warden-adr-0010.html | 218 +++ .../pages/policy-nexus-adr-0001.html | 214 +++ .../pages/policy-nexus-architecture.html | 248 +++ .../pages/railiance-architecture.html | 246 +++ .../pages/state-hub-architecture.html | 247 +++ .../publication-manifest.json | 1230 ++++++++++++++ .../receipt.json | 1017 ++++++++++++ .../source-verification-errors.json | 1 + .../sources/ACT-ADR-001.md | 191 +++ .../sources/ACT-ADR-002.md | 360 ++++ .../sources/ACT-ADR-003.md | 342 ++++ .../sources/ACT-ADR-004.md | 160 ++ .../sources/ACT-ADR-005.md | 207 +++ .../sources/ACT-ADR-006.md | 142 ++ .../sources/ACT-ADR-007.md | 128 ++ .../sources/CUST-ADR-001.md | 247 +++ .../sources/CUST-ADR-002.md | 126 ++ .../sources/CUST-ADR-003.md | 207 +++ .../sources/CUST-ADR-004.md | 147 ++ .../sources/CUST-ADR-005.md | 124 ++ .../sources/CUST-ADR-006.md | 278 ++++ .../sources/CUST-ADR-007.md | 324 ++++ .../sources/CUST-ADR-010.md | 271 +++ .../sources/CUST-ADR-011.md | 290 ++++ .../sources/CUST-ADR-012.md | 273 ++++ .../sources/NK-ADR-0006.md | 108 ++ .../sources/NK-ADR-0007.md | 154 ++ .../sources/NK-ADR-0008.md | 94 ++ .../sources/NK-ADR-0010.md | 108 ++ .../sources/NK-ADR-0011.md | 139 ++ .../sources/NK-ADR-0012.md | 124 ++ .../sources/NK-ADR-0013.md | 177 ++ .../sources/NK-ADR-0014.md | 183 +++ .../sources/NK-ADR-0015.md | 178 ++ .../sources/RINFRA-ADR-0005.md | 69 + .../sources/RISK-F-0001.md | 226 +++ .../sources/RISK-F-0008.md | 364 +++++ .../sources/RISK-METHOD-DEPENDENCIES.md | 139 ++ .../sources/RISK-METHOD-DISCLOSURE.md | 144 ++ .../sources/RISK-METHOD-REVIEW.md | 199 +++ .../sources/RISK-METHOD-SEVERITY.md | 207 +++ .../sources/RISK-METHOD-VERIFICATION.md | 112 ++ .../sources/RMASTER-ADR-0001.md | 143 ++ .../sources/RMASTER-ADR-0002.md | 74 + .../sources/RMASTER-ADR-0003.md | 67 + .../sources/RMASTER-ADR-0004.md | 71 + .../sources/RMASTER-ADR-0005.md | 41 + .../sources/RMASTER-ADR-0006.md | 52 + .../sources/RMASTER-ADR-0007.md | 113 ++ .../sources/RMASTER-ADR-0008.md | 104 ++ .../sources/RMASTER-ADR-0009.md | 83 + .../sources/RPLAT-ADR-0001.md | 68 + .../sources/RPLAT-ADR-0002.md | 86 + .../sources/RPLAT-ADR-0003.md | 68 + .../sources/canon-autonomy-lanes.md | 102 ++ .../sources/canon-contrib-convention.md | 161 ++ .../canon-project-repository-flavor.md | 282 ++++ .../sources/canon-work-record-types.md | 243 +++ .../canon-workplan-terminology-fleet.md | 147 ++ .../sources/coulomb-estate-architecture.md | 230 +++ .../sources/net-kingdom-architecture.md | 152 ++ .../sources/netkingdom-iam-profile-v0.3.md | 482 ++++++ .../netkingdom-posture-feedback-v0.1.md | 109 ++ .../netkingdom-security-layer-model-v0.7.md | 1396 ++++++++++++++++ ...gdom-security-scenario-composition-v0.1.md | 142 ++ .../sources/netkingdom-security-zones-v0.1.md | 352 ++++ .../sources/netkingdom-tenancy-posture.md | 1447 +++++++++++++++++ .../sources/ops-warden-adr-0001.md | 90 + .../sources/ops-warden-adr-0002.md | 90 + .../sources/ops-warden-adr-0003.md | 95 ++ .../sources/ops-warden-adr-0004.md | 85 + .../sources/ops-warden-adr-0005.md | 79 + .../sources/ops-warden-adr-0007.md | 100 ++ .../sources/ops-warden-adr-0008.md | 94 ++ .../sources/ops-warden-adr-0009.md | 101 ++ .../sources/ops-warden-adr-0010.md | 109 ++ .../sources/policy-nexus-adr-0001.md | 74 + .../sources/policy-nexus-architecture.md | 161 ++ .../sources/railiance-architecture.md | 151 ++ .../sources/state-hub-architecture.md | 161 ++ ...security-layer-v08-publication-proposal.md | 58 + publication.json | 6 + tests/test_publication_transition.py | 49 + tests/test_published_history.py | 66 + tools/build_site.py | 66 + tools/verify_publication_transition.py | 66 + 162 files changed, 37466 insertions(+) create mode 100644 docs/evidence/2026-10-11-hosting-preparation/complete-history-validation.json create mode 100644 docs/evidence/2026-10-11-hosting-preparation/content-delta.json create mode 100644 docs/evidence/2026-10-11-hosting-preparation/content-review.json create mode 100644 docs/evidence/2026-10-11-hosting-preparation/immutable-audit.json create mode 100644 docs/evidence/2026-10-11-hosting-preparation/live-baseline.json create mode 100644 docs/evidence/2026-10-11-hosting-preparation/public-baseline-manifest.json create mode 100644 docs/evidence/2026-10-11-hosting-preparation/source-exports.json create mode 100644 docs/evidence/2026-10-11-hosting-preparation/transition-failure.txt create mode 100644 docs/evidence/2026-10-11-served-publication-history/README.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-001.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-002.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-003.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-004.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-005.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-006.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-007.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-001.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-002.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-003.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-004.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-005.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-006.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-007.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-010.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-011.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-012.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0006.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0007.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0008.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0010.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0011.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0012.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0013.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0014.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0015.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RINFRA-ADR-0005.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RISK-F-0001.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RISK-F-0008.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-DEPENDENCIES.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-DISCLOSURE.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-REVIEW.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-SEVERITY.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-VERIFICATION.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0001.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0002.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0003.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0004.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0005.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0006.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0007.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0008.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0009.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RPLAT-ADR-0001.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RPLAT-ADR-0002.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/RPLAT-ADR-0003.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/canon-autonomy-lanes.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/canon-contrib-convention.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/canon-project-repository-flavor.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/canon-work-record-types.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/canon-workplan-terminology-fleet.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/coulomb-estate-architecture.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/net-kingdom-architecture.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-iam-profile-v0.3.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-posture-feedback-v0.1.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-security-layer-model-v0.7.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-security-scenario-composition-v0.1.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-security-zones-v0.1.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-tenancy-posture.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0001.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0002.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0003.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0004.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0005.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0007.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0008.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0009.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0010.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/policy-nexus-adr-0001.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/policy-nexus-architecture.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/railiance-architecture.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/pages/state-hub-architecture.html create mode 100644 docs/evidence/2026-10-11-served-publication-history/publication-manifest.json create mode 100644 docs/evidence/2026-10-11-served-publication-history/receipt.json create mode 100644 docs/evidence/2026-10-11-served-publication-history/source-verification-errors.json create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-001.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-002.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-003.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-004.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-005.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-006.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-007.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-001.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-002.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-003.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-004.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-005.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-006.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-007.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-010.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-011.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-012.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0006.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0007.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0008.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0010.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0011.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0012.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0013.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0014.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0015.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RINFRA-ADR-0005.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RISK-F-0001.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RISK-F-0008.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-DEPENDENCIES.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-DISCLOSURE.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-REVIEW.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-SEVERITY.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-VERIFICATION.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0001.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0002.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0003.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0004.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0005.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0006.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0007.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0008.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0009.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RPLAT-ADR-0001.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RPLAT-ADR-0002.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/RPLAT-ADR-0003.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/canon-autonomy-lanes.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/canon-contrib-convention.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/canon-project-repository-flavor.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/canon-work-record-types.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/canon-workplan-terminology-fleet.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/coulomb-estate-architecture.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/net-kingdom-architecture.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-iam-profile-v0.3.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-posture-feedback-v0.1.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-security-layer-model-v0.7.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-security-scenario-composition-v0.1.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-security-zones-v0.1.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-tenancy-posture.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0001.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0002.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0003.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0004.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0005.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0007.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0008.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0009.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0010.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/policy-nexus-adr-0001.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/policy-nexus-architecture.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/railiance-architecture.md create mode 100644 docs/evidence/2026-10-11-served-publication-history/sources/state-hub-architecture.md create mode 100644 docs/releases/2026-10-11-security-layer-v08-publication-proposal.md create mode 100644 tests/test_publication_transition.py create mode 100644 tests/test_published_history.py create mode 100644 tools/verify_publication_transition.py diff --git a/docs/evidence/2026-10-11-hosting-preparation/complete-history-validation.json b/docs/evidence/2026-10-11-hosting-preparation/complete-history-validation.json new file mode 100644 index 0000000..bdfd5d5 --- /dev/null +++ b/docs/evidence/2026-10-11-hosting-preparation/complete-history-validation.json @@ -0,0 +1,30 @@ +{ + "schema_version": 1, + "policy_tests_passed": 45, + "new_history_regressions": 7, + "new_transition_regressions": 5, + "original_owner_sources_git_show_verified": 72, + "previous_current_immutable_urls_captured_http200": 72, + "previous_current_immutable_urls_byte_exact_preserved": 72, + "clean_candidate_documents": 73, + "reviewed_semantic_changed_ids": [ + "CUST-ADR-002", + "CUST-ADR-004", + "RISK-F-0008", + "RMASTER-ADR-0006", + "canon-work-record-types", + "canon-workplan-terminology-fleet", + "net-kingdom-architecture", + "netkingdom-security-layer-model-v0.7", + "netkingdom-security-layer-model-v0.8", + "netkingdom-tenancy-posture", + "ops-warden-adr-0004" + ], + "three_minimal_owner_publication_revision_bumps": { + "netkingdom-tenancy-posture": "draft-14 -> draft-15", + "net-kingdom-architecture": "draft-3 -> draft-4", + "RISK-F-0008": "published-1 -> published-2" + }, + "new_revision_public_urls_prior_status": 404, + "limits": "Staged-source behavior verification against exact current HEAD exports plus three explicit revision-only overlays, not final committed CI source lock, OCI image, inventory provenance, security scan or native deployment approval. All original failed transition evidence is retained." +} diff --git a/docs/evidence/2026-10-11-hosting-preparation/content-delta.json b/docs/evidence/2026-10-11-hosting-preparation/content-delta.json new file mode 100644 index 0000000..9780f53 --- /dev/null +++ b/docs/evidence/2026-10-11-hosting-preparation/content-delta.json @@ -0,0 +1,147 @@ +{ + "added": [ + { + "canonical_path": "standards/security-layer-model/v0.8/index.html", + "currency": "current", + "id": "netkingdom-security-layer-model-v0.8", + "last_reviewed": "2026-10-10", + "lifecycle": "active", + "owner": "gate-house", + "review_due": "2027-01-10", + "revision": "accepted-1", + "revision_path": "standards/security-layer-model/v0.8/revisions/accepted-1/index.html", + "source_digest": "3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b", + "source_path": "canon/standards/security-layer-model_v0.8.md", + "source_repo": "net-kingdom", + "source_revision": "0e17d956ab45f8c4a0163b70749490c414e54e67", + "status": "accepted", + "title": "NetKingdom Security Layer Model v0.8" + } + ], + "existing_changes": [ + { + "id": "netkingdom-tenancy-posture", + "delta": { + "source_digest": { + "served": "27d6878c68310619877de516000d9af78d5456a92572a709884ed052d765b876", + "candidate": "795944f3f7735f58c5447b57bd3f65766a6e8d7d54f75586f9f269d4f6de5c56" + } + }, + "removed": false + }, + { + "id": "canon-work-record-types", + "delta": { + "source_digest": { + "served": "1a40de294be332ee713cdaab532a32a83718f3952c339fbff835ecee899a8dbc", + "candidate": "ab5d9de07fd899fc5334c9912571ed3d6748f2ee4bd0c9f81c5606c4727024d1" + }, + "revision": { + "served": "accepted-1", + "candidate": "accepted-2" + } + }, + "removed": false + }, + { + "id": "canon-workplan-terminology-fleet", + "delta": { + "source_digest": { + "served": "199812632ac342ca3231498e545e3701e10c191ca144035b36a23c1a0b717712", + "candidate": "431d36f40d90bcd40a640c16b583095ad35757675f78ddcea382e6f1b2e23710" + }, + "revision": { + "served": "accepted-1", + "candidate": "accepted-2" + } + }, + "removed": false + }, + { + "id": "net-kingdom-architecture", + "delta": { + "source_digest": { + "served": "b9f89f706a801d54961fc67d725ca433b7c3b6a083c54793de6e8b60d1dd42a8", + "candidate": "d57021a85db6b69b58d21f6be258a57ec86deecdfff30395412657cf273dd9e7" + } + }, + "removed": false + }, + { + "id": "RMASTER-ADR-0006", + "delta": { + "source_digest": { + "served": "9839d8a4a4617b745cfacab6a0fb72157e88cddf53f40226d8246a64864de533", + "candidate": "5d0454384e1a3f31939aaa4e3d4225ede73c3669a2d86b15efb4b56803e01690" + }, + "revision": { + "served": "accepted-2", + "candidate": "accepted-3" + } + }, + "removed": false + }, + { + "id": "ops-warden-adr-0004", + "delta": { + "source_digest": { + "served": "5db38dcb754af1ba639f4ceca056df842bc7b91fa48dd8bad21611aee29f682d", + "candidate": "e379f47fb56214ff0cc6abcddaaad33c6969cf386e11abeed105b4fef953c5a0" + }, + "revision": { + "served": "1", + "candidate": "2" + } + }, + "removed": false + }, + { + "id": "CUST-ADR-002", + "delta": { + "source_digest": { + "served": "6aef66cd5cf71a48f5b4e14401dc19a755e2b182445b272d5952df0eb0dea8ec", + "candidate": "b860b02b1e8f47b914d41c6ecf67f451e86326ed51c7c53dcc6b7870fc889e42" + }, + "revision": { + "served": "accepted-1", + "candidate": "accepted-2" + } + }, + "removed": false + }, + { + "id": "CUST-ADR-004", + "delta": { + "source_digest": { + "served": "3b68adfa6ab329e73f857cf691dc405136d2d66a0135e2c37c236aabe4659557", + "candidate": "4f9978b40c104600c0ccb981a8ee52376a5d3003da4786752029af524b7dede7" + }, + "revision": { + "served": "accepted-1", + "candidate": "accepted-2" + } + }, + "removed": false + }, + { + "id": "netkingdom-security-layer-model-v0.7", + "delta": { + "lifecycle": { + "served": "active", + "candidate": "superseded" + } + }, + "removed": false + }, + { + "id": "RISK-F-0008", + "delta": { + "source_digest": { + "served": "8bf47414a9f958afb57649f087a2617f91a8f2aeaa4f34d04d8fb3a0f453e840", + "candidate": "1762d479f1ea572bacdb8ea7612b36b342fb6f87c21e5a4a92732c74215865b1" + } + }, + "removed": false + } + ] +} diff --git a/docs/evidence/2026-10-11-hosting-preparation/content-review.json b/docs/evidence/2026-10-11-hosting-preparation/content-review.json new file mode 100644 index 0000000..cd86eba --- /dev/null +++ b/docs/evidence/2026-10-11-hosting-preparation/content-review.json @@ -0,0 +1,101 @@ +[ + { + "id": "netkingdom-tenancy-posture", + "source_repo": "net-kingdom", + "source_path": "canon/standards/tenancy-posture_v0.1.md", + "served_revision": "draft-14", + "current_revision": "draft-14", + "served_status": "proposed", + "current_status": "proposed", + "same_revision_source_conflict": true, + "diff": "--- served-original\n+++ current-owner-source\n@@ -784,7 +784,7 @@\n | Service | Current | Notes |\n |---|---|---|\n | `tenant-engine` | `I1 A0 E1 P n/a R0 V0` | Acting identity is caller-supplied; unauthorised read paths set the A minimum; E2-shaped child-table controls are not evidenced; SQLite is outside P; no erasure or availability evidence. This corrects draft-7, which quoted A2/E2 despite its own minimum/evidence rules. |\n-| `audit-core` | `I1 A2 E2 P1 R2 V0` | Bounded adversarial run `WH-ENG-20260822-AUDIT-E2-03` passed all three calibrated cross-tenant probes over ten operations, so E2 is evidenced as of 2026-08-22. This establishes only that the attempted attacks did not work. The 24-hour facility baseline requires review or replacement by `2026-08-23T22:10:25Z`. Its 30-day retention and erasure horizon remain declared and published. |\n+| `audit-core` | `I1 A2 E2 P1 R2 V1` | Bounded adversarial run `WH-ENG-20260822-AUDIT-E2-03` passed all three calibrated cross-tenant probes over ten operations, so E2 is evidenced as of 2026-08-22. This establishes only that the attempted attacks did not work. The 24-hour facility baseline requires review or replacement by `2026-08-23T22:10:25Z`. Its 30-day retention and erasure horizon remain declared and published. V1 is the 2026-09-24 restart exercise: receiver recreate about 7s, platform-pg primary recreate 22s from pod termination to the next accept, chain intact. Review 2026-12-31. |\n | `flex-auth` | `I1 A0 E1 P n/a R n/a V0` | Enables A3 for consumers. `/v1/check` authenticates no caller; E2 is implemented but not evidenced. |\n | `platform-pg` (provider) | `I0 A0 E0 P n/a R2 V1` | Provides P1; backup/restore and single-node recovery are evidenced. Provides no tenant boundary by itself. |\n | `apps-pg` (provider) | `I0 A0 E0 P n/a R0 V0` | Zeros are structural, except R0/V0 are live gaps: no backup and no recovery evidence. |\n" + }, + { + "id": "canon-work-record-types", + "source_repo": "the-custodian", + "source_path": "canon/standards/work-record-types_v0.1.md", + "served_revision": "accepted-1", + "current_revision": "accepted-2", + "served_status": "accepted", + "current_status": "accepted", + "same_revision_source_conflict": false, + "diff": "--- served-original\n+++ current-owner-source\n@@ -4,16 +4,17 @@\n title: \"Work Record Types & Identity (Fleet) v0.1\"\n domain: custodian\n status: accepted\n-version: \"0.1\"\n+version: \"0.2\"\n owner: the-custodian\n-revision: \"accepted-1\"\n-last_reviewed: \"2026-08-31\"\n+revision: \"accepted-2\"\n+last_reviewed: \"2026-09-14\"\n review_interval: 6m\n created: \"2026-07-20\"\n-updated: \"2026-08-23\"\n+updated: \"2026-09-14\"\n scope: fleet\n related_workplans:\n - CUST-WP-0060\n+ - STATE-WP-0092\n supersedes: none\n machine_copy: canon/standards/work-record-types.yaml\n ---\n@@ -24,6 +25,9 @@\n > `research/WorkOrchestrationArchitectureDraft.md` v0.2 (founder-reviewed\n > 2026-07-20). Extends \u2014 does not replace \u2014\n > `workplan-terminology-fleet_v0.1.md` and ADR-001/ADR-005.\n+>\n+> Flavor and residual default-exclusion ratified 2026-09-14 by founder\n+> instruction to execute STATE-WP-0092. Flavor is orthogonal to kind.\n \n ## Purpose\n \n@@ -148,7 +152,27 @@\n an evidence link). Profiles are YAML in repos, in the same reconciliation\n loop.\n \n-## Residuals (role, not kind)\n+## Flavor (bucket, not kind)\n+\n+**Flavor** is a closed list on **workplan** and **task**, orthogonal to\n+kind and to lifecycle `status`. It says *what kind of demand this is*,\n+not whether it is open.\n+\n+| Flavor | Meaning |\n+| --- | --- |\n+| `planning` | analysis, design, policy, sequencing |\n+| `implementation` | building the accepted requirement |\n+| `refactoring` | internal change that does not add capability |\n+| `extension` | additive change to an existing capability |\n+| `residual` | leftover after a parent finished; not deemed as valuable as the main functional and non-functional requirements |\n+\n+Unset flavor is **not** residual. Historic records without `flavor:` stay\n+relevant until backfill classifies them (`CUST-WP-0072`). Do not\n+auto-classify.\n+\n+Unknown flavor values fail closed (rejected on write; consistency WARN).\n+\n+### Residuals (flavor, plus provenance role)\n \n A **residual** is work intentionally left after a workplan (or large task\n bundle) finishes. It is **not** a registered kind \u2014 inventing a parallel\n@@ -156,29 +180,55 @@\n prose is a process defect. Residuals must be **live work records** so the\n hub can list them across repos and domains.\n \n+On workplans and tasks the live classification is `flavor: residual`.\n+`origin: residual` (or `handoff`) on intakes and promoted children remains\n+**provenance**, not the default-view switch.\n+\n | Residual shape | Capture as | Required links |\n | --- | --- | --- |\n | Small Green/Blue follow-up, parkable | **intake** (e.g. AWQ / `*-IN-*`) | `origin: residual`, `origin_ref: ` |\n-| Multi-step / dependency-bearing | **workplan** (spawn next WP) | body or frontmatter names parent WP; optional `origin`/`origin_ref` when promoted from intake |\n+| Multi-step / dependency-bearing | **workplan** with `flavor: residual` | body or frontmatter names parent WP; `origin`/`origin_ref` when spawned from a finished parent |\n | Needs founder choice | **decision** | same origin fields when created from residual intake |\n | Needs founder time window | **engagement** | same |\n | Persistent gap / risk | **register-entry** (risk, tech debt, \u2026) | cross-link parent WP in notes |\n \n+**Default views omit residuals.** State Hub open-workplan lists\n+(`/state/summary` `open_workplans`, `/state/next_steps`, `/state/deps`,\n+dashboard open widgets) and agent \u201cshould we implement this?\u201d **do not\n+consider** `flavor: residual` unless an explicit include is set\n+(`include_residuals=true` or `flavor=residual`). Residuals are not equal\n+open demand. Totals still report `residual_open` so the count cannot hide.\n+\n+**Promotion** is a first-class transition: `flavor: residual` \u2192\n+`planning` | `implementation` | `refactoring` | `extension` when **demand**\n+or **risk** meets the leftover. Record `flavor_promotion_reason:\n+demand | risk` and optional `flavor_promoted_from: residual` in the file,\n+then `fix-consistency`. After promotion the record is no longer residual\n+and re-enters the default view.\n+\n **Close discipline for finished workplans:**\n \n 1. Short residual **narrative** may remain under the closing task (human\n context).\n 2. **Before** `status: finished`, each residual that is still actionable\n- becomes a live record (intake and/or child workplan). Prose alone is not\n- the backlog.\n+ becomes a live record (intake and/or child workplan with\n+ `flavor: residual`). Prose alone is not the backlog.\n 3. Progress milestone may name the handoff ids.\n 4. `statehub fix-consistency` so the index (`WORK-RECORDS.md`, hub) reflects\n the handoff.\n \n-**Fleet list (future CLI):** filter open/vetted/routed intakes (and ready/\n-active workplans) with `origin \u2208 {residual, handoff}` and optional\n-`topic_id` / `repo` / `origin_ref`. Implementation home for that query is\n-**state-hub** (`statehub residuals` / API), not domain repos.\n+**Fleet list:** filter `flavor: residual` on workplans/tasks and intakes\n+with `origin \u2208 {residual, handoff}` via State Hub (`GET /workplans/?flavor=residual`,\n+`GET /state/summary?include_residuals=true`). Implementation home is\n+**state-hub**, not domain-local residual files.\n+\n+### `depends_on` (workplan frontmatter)\n+\n+Workplan frontmatter `depends_on` is the canonical list of **workplan ids**\n+this plan waits on. State Hub indexes those edges (C-20). Legacy keys\n+`depends_on_workplans` and `depends_on_tasks` remain aliases until a\n+legacy-meter retires them. Task-block `depends_on` continues to name\n+sibling tasks; it is not a substitute for workplan-level edges.\n \n ## Source files, index, and views\n \n@@ -241,3 +291,17 @@\n - `canon/standards/workplan-terminology-fleet_v0.1.md`\n - `canon/standards/autonomy-lanes_v0.1.md`\n - `state-hub/docs/task-flow-engine-spec.md`\n+\n+## Historical Custodian identifier compatibility (2026-09-09)\n+\n+Preserve the source-bound suffix workplans `CUST-WP-0000b`, `CUST-WP-0000c`,\n+`CUST-WP-0010b`, and archived `CUST-WP-0017-LEGACY` / `CUST-WP-0053-LEGACY`.\n+The machine registry enumerates the observed suffixed task IDs and the 38\n+parent-local aliases in CUST-WP-0006, CUST-WP-0007, CUST-WP-0030 and CUST-WP-0031.\n+These local aliases require an existing managed UUID and are collision-checked\n+within their recorded parent. Qualified task IDs remain globally checked.\n+Unknown aliases and missing historical bindings refuse ordinary registration;\n+new tasks must use the canonical qualified convention. Compatibility does not\n+rename source records, replace existing UUIDs, reactivate finished work or\n+retire duplicate projection rows. The broader historical reconciliation remains\n+CUST-IN-0017; validation evidence is in the 2026-09-09 assessment.\n" + }, + { + "id": "canon-workplan-terminology-fleet", + "source_repo": "the-custodian", + "source_path": "canon/standards/workplan-terminology-fleet_v0.1.md", + "served_revision": "accepted-1", + "current_revision": "accepted-2", + "served_status": "accepted", + "current_status": "accepted", + "same_revision_source_conflict": false, + "diff": "--- served-original\n+++ current-owner-source\n@@ -4,19 +4,20 @@\n title: \"Workplan Terminology (Fleet) v0.1\"\n domain: custodian\n status: accepted\n-version: \"0.2\"\n+version: \"0.3\"\n owner: the-custodian\n-revision: \"accepted-1\"\n-last_reviewed: \"2026-08-31\"\n+revision: \"accepted-2\"\n+last_reviewed: \"2026-09-14\"\n review_interval: 6m\n created: \"2026-07-08\"\n-updated: \"2026-07-20\"\n+updated: \"2026-09-14\"\n scope: fleet\n related_workplans:\n - CUST-WP-0055\n - STATE-WP-0054\n - STATE-WP-0069\n - CUST-WP-0060\n+ - STATE-WP-0092\n supersedes: none\n ---\n \n@@ -62,6 +63,19 @@\n intake kind (state-hub suggestions table: read-only legacy pending\n close-out). This file keeps its `_v0.1` name for reference stability; the\n frontmatter `version` field is authoritative.\n+\n+## Flavor and depends_on (v0.3 addendum, STATE-WP-0092)\n+\n+Workplans and tasks may carry **flavor**: `planning`, `implementation`,\n+`refactoring`, `extension`, or `residual`. Flavor is a bucket, not a kind\n+and not a status. `residual` leftovers are live records but are **not**\n+equal to main functional/non-functional demand. Default State Hub open\n+views omit them until demand or risk promotes the flavor.\n+\n+Workplan frontmatter `depends_on` is the canonical list of blocker\n+workplan ids. Prefer that spelling over `depends_on_workplans`.\n+\n+Normative detail: `work-record-types_v0.1.md` \u00a7 Flavor.\n \n Use **workplan** in human-facing prose: `SCOPE.md`, `AGENTS.md`, `INTENT.md`,\n README, workplan bodies, operator runbooks, and agent session guidance.\n" + }, + { + "id": "net-kingdom-architecture", + "source_repo": "net-kingdom", + "source_path": "docs/architecture/net-kingdom_v0.1.md", + "served_revision": "draft-3", + "current_revision": "draft-3", + "served_status": "proposed", + "current_status": "proposed", + "same_revision_source_conflict": true, + "diff": "--- served-original\n+++ current-owner-source\n@@ -114,6 +114,7 @@\n | `netkingdom-tenancy-posture` | proposed | Graduated multi-tenancy posture. |\n | `netkingdom-iam-profile-v0.3` | accepted | Current provider-neutral identity contract. |\n | `netkingdom-posture-feedback-v0.1` | proposed | Governed posture feedback. |\n+| `netkingdom-emission-cadence-security-profile-v0.1` | proposed | NetKingdom evidence-cadence obligations; owner-instance migration pending. |\n | `netkingdom-security-layer-model-v0.7` | accepted | Current security-layer interaction model. |\n | `netkingdom-security-scenario-composition-v0.1` | proposed | Security scenario composition. |\n | `netkingdom-security-zones-v0.1` | proposed | Security-zone vocabulary and boundaries. |\n" + }, + { + "id": "RMASTER-ADR-0006", + "source_repo": "railiance-master", + "source_path": "docs/adr/ADR-0006-reef-production-admission.md", + "served_revision": "accepted-2", + "current_revision": "accepted-3", + "served_status": "accepted", + "current_status": "accepted", + "same_revision_source_conflict": false, + "diff": "--- served-original\n+++ current-owner-source\n@@ -2,9 +2,9 @@\n id: RMASTER-ADR-0006\n title: \"Reef Production Admission\"\n status: accepted\n-revision: \"accepted-2\"\n+revision: \"accepted-3\"\n owner: railiance-master\n-last_reviewed: \"2026-08-29\"\n+last_reviewed: \"2026-09-21\"\n review_interval: 6m\n ---\n \n@@ -50,3 +50,75 @@\n - `production-approved` is not permission to publish a listener. See\n ADR-0008.\n - `production-approved` is not an authorization decision. See ADR-0009.\n+\n+## Revision accepted-3 (2026-09-21): the change path\n+\n+The founder decided the Kubernetes change gate on 2026-09-21, exercising\n+`GOVERN @ estate` (the-custodian\n+`docs/kubernetes-change-gate-decision.md`). Terms follow SecurityCanon Mode\n+of Authority v0.2.0, which is still a draft. The text above is left as\n+accepted-2 recorded it. This section adds one consequence and does not change\n+the Decision.\n+\n+**Added consequence: `production-approved` also fixes the change path.**\n+\n+| Readiness state of the target | Path for a change | `Activation` |\n+|---|---|---|\n+| `declared`, `installed`, `verified` | Direct `ADMINISTER @ realm:kubernetes` | `APPROVED`: founder approval of the construction plan |\n+| `production-approved` | `CONSTRUCT @ `, reconciled by ArgoCD | `APPROVED` for the change; the merge is the gate |\n+| `production-approved`, emergency | Direct `ADMINISTER @ realm:kubernetes` | `BREAK_GLASS`, recorded, then reconciled back into the manifest repository |\n+\n+The reason is the `EvidenceBoundary` dimension. A direct apply is\n+`target-audited`: only the realm being changed holds the evidence. A change\n+through the manifest repository adds `external-audited` evidence, which\n+outlives both the actor and the realm. This is a statement about evidence,\n+not about trust in any actor.\n+\n+**Still not an authorization decision.** The change path is a quality gate on\n+a change. It does not say whether an actor may perform an action; that remains\n+`access-engine`'s question (ADR-0009, Decision 5). `production-approved`\n+still MUST NOT be read as permission to act, and it does not make any engine a\n+party to the path. `BREAK_GLASS` is an activation value, not a mode and not a\n+grant.\n+\n+**Tiering axis.** The tier follows readiness state, not the canon's\n+`Environment` dimension. railiance01 is a single cluster, so every workload on\n+it shares one `Environment`, and that dimension cannot separate the tiers.\n+This repository's own model agrees: it has no `Environment` dimension. The\n+nearest concept is the reef, which `docs/reef-substrate-model.md` treats as\n+the environment or substrate boundary. railiance01 carries one reef,\n+`reef-railiance`, so the reef cannot separate the tiers either. Readiness is\n+recorded per binding (a rail hosted on a reef, a `rapp` bound to one), which\n+is the axis that does.\n+\n+**Transition.** `rapp-policy-nexus` is `production-approved` but not yet\n+managed by ArgoCD. Until 2026-12-21 its changes keep `activation=APPROVED` by\n+founder plan approval, and each records that its target is production-tier.\n+From its ArgoCD onboarding, due by 2026-12-21, the production row applies.\n+The onboarding is owned by `railiance-platform`, and enforcement in the\n+executor by `ops-mason`. Neither is work in this repository.\n+\n+**Open points referred to the custodian, not resolved here.** The decision\n+sits on this ADR and the readiness contract, and four of its terms reach past\n+what they define:\n+\n+1. **Objects with no readiness state.** The decision puts platform objects\n+ that carry no readiness state in the production tier. The readiness\n+ contract defines states only for hosted rails and bound `rapp`s, and\n+ nothing here maps a Kubernetes object or namespace to a binding. The\n+ default is the founder's rule. It is not derivable from this contract.\n+2. **Evidence lapse.** The contract returns a binding to its last proven state\n+ when evidence fails or expires. Read with the table, a lapse would move a\n+ `production-approved` target back to the direct-apply row. The decision\n+ does not say whether a lapse loosens the change path.\n+3. **`deprecated`.** The table covers four of the five states. It does not\n+ place `deprecated`.\n+4. **The Tooling sentence.** The decision says the Kubernetes API \"stays a\n+ Tooling contact, owned by `rail-kubernetes`\". ADR-0009, Decision 4 keeps\n+ statute \u00a720.3 unset: this repository will not imply a mapping of rails\n+ onto Taxonomy, Tooling, Engine or Staff. This revision therefore does not\n+ adopt that sentence. The custodian and `gate-house` should decide whether\n+ it is a \u00a720.3 mapping or only a description of the contact.\n+\n+None of the four blocks the consequence above. Each needs a ruling before this\n+ADR states more than the table.\n" + }, + { + "id": "ops-warden-adr-0004", + "source_repo": "ops-warden", + "source_path": "docs/adr/ADR-0004-agent-read-boundary-on-high-risk-lanes.md", + "served_revision": "1", + "current_revision": "2", + "served_status": "accepted", + "current_status": "accepted", + "same_revision_source_conflict": false, + "diff": "--- served-original\n+++ current-owner-source\n@@ -5,15 +5,15 @@\n domain: infotech\n repo: ops-warden\n status: accepted\n-version: \"1.0\"\n-revision: \"1\"\n+version: \"1.1\"\n+revision: \"2\"\n owner: ops-warden\n binds: \"ops-warden; any agent runtime calling warden access\"\n created: \"2026-07-20\"\n-updated: \"2026-08-18\"\n-last_reviewed: \"2026-08-18\"\n+updated: \"2026-09-04\"\n+last_reviewed: \"2026-09-04\"\n review_interval: 6m\n-enforced_by: \"src/warden/access.py (exit 7); OpenBao policy agent-high-risk-boundary\"\n+enforced_by: \"src/warden/access.py + src/warden/cli.py (exit 7); OpenBao policy agent-high-risk-boundary\"\n supersedes: \"\"\n successor: \"\"\n ---\n@@ -48,6 +48,13 @@\n its work \u2014 `--out`, `--exec`, `--wrap` and `--fingerprint` all remain available.\n It is blocked from doing its work *in a way that writes the secret into a transcript*.\n \n+**Revision 2 also recognizes the issued coding-agent subject.** When\n+`WARDEN_POLICY_SUBJECT` is the KeyCape-issued\n+`service:codex:railiance-platform` identity, the same refusal applies without\n+`WARDEN_AGENT_ID`. The legacy variable remains a fail-toward-safety fallback.\n+Ops-warden reads this identity marker; it does not validate the token or decide\n+whether the principal is authorized.\n+\n **The boundary is enforced at the credential store as well as at the CLI.** The\n OpenBao policy `agent-high-risk-boundary` denies data-read on those paths for agent\n tokens, allowing metadata and capabilities only. A control that lives solely in our\n@@ -73,10 +80,12 @@\n **`risk: high` becomes a load-bearing catalog field** rather than documentation.\n Classifying a lane now changes runtime behaviour, so it must be set deliberately.\n \n-**We accept that `--unsafe-stdout` still exists for humans.** The boundary keys on\n-`WARDEN_AGENT_ID`, so an agent that does not set it is not caught. That is a known\n-limit: this ADR raises the floor for cooperating runtimes and hardens the store\n-behind them; it does not claim to stop a determined caller.\n+**We accept that `--unsafe-stdout` still exists for humans.** The CLI recognizes the\n+issued subject when the runtime presents it and otherwise relies on\n+`WARDEN_AGENT_ID`; both markers remain caller-visible and therefore advisory. This\n+ADR raises the floor for cooperating runtimes and hardens the store behind them; it\n+does not claim to stop a determined caller. The OpenBao\n+`agent-high-risk-boundary` policy is the enforced control.\n \n ## Related\n \n" + }, + { + "id": "CUST-ADR-002", + "source_repo": "the-custodian", + "source_path": "canon/architecture/adr-002-custodian-agent-runtime-design.md", + "served_revision": "accepted-1", + "current_revision": "accepted-2", + "served_status": "accepted", + "current_status": "accepted", + "same_revision_source_conflict": false, + "diff": "--- served-original\n+++ current-owner-source\n@@ -4,9 +4,11 @@\n title: \"Custodian Agent Runtime \u2014 v0.1 Bootstrap Design\"\n status: accepted\n owner: the-custodian\n-revision: \"accepted-1\"\n-last_reviewed: \"2026-03-12\"\n+revision: \"accepted-2\"\n+last_reviewed: \"2026-10-11\"\n review_interval: 6m\n+reviewed_by: \"codex \u2014 technical source/currency review\"\n+review_scope: \"Historical decision retained; current factual and authority scope clarified, no new grant\"\n decided_by: Bernd Worsch\n date: \"2026-03-12\"\n tags: [\"architecture\", \"agent-runtime\", \"llm\", \"ooda\", \"bounded-agency\"]\n@@ -18,7 +20,44 @@\n \n Accepted.\n \n-## Context\n+## Technical currency review \u2014 2026-10-11\n+\n+The accepted decision remains the **v0.1 bootstrap architecture for the\n+standalone package under `runtime/`**, not a grant to every coding agent or\n+an assertion that a privileged autonomous runtime is deployed. Current source\n+still implements D1\u2013D5: this package location, a single synchronous CLI cycle,\n+configurable `llm-connect` with `claude-code` default, three explicit HTTP write\n+classes, and a fenced JSON action plan plus a session note. All 32 existing\n+offline runtime tests pass with HTTP calls mocked. That verifies the described\n+prototype behavior; it proves no live authorization, runtime isolation,\n+credential boundary, semantic correctness of task updates, or rollout state.\n+\n+D4's original permission is limited to that bootstrap's named low-impact\n+coordination operations. It is neither an approval token nor a Codex autopilot\n+grant, and it does not override owner-source work records, API authorization,\n+work-record lanes or agent-specific supervision. Current governance is\n+`docs/agent-autonomy-decision.md` (founder, 2026-09-28): new scopes start\n+supervised and autopilot requires a separate explicit scoped grant and verified\n+runtime enforcement. The inspected `custodian-codex` supervision record remains\n+supervised, with no autopilot grant and interactive isolation not yet migrated.\n+This review changes no such authority or record.\n+\n+The March dependency/deployment statements below are historical context.\n+The review does not reaffirm that every deployment has Claude Code, that the\n+bootstrap is scheduled or deployed, or that its narrow operation list provides\n+credential isolation. API addresses and live change procedures belong to\n+`docs/agent-environment-orientation.md`; task/workplan sources follow ADR-001\n+and the later accepted ADR-012. The code's direct task-status HTTP branch is\n+prototype implementation evidence, not permission to override file-backed\n+owner records. Any widening of D4 or promotion remains a separate decision.\n+\n+The original 2026-03-12 decision and accepted-1 bytes are retained in\n+`docs/evidence/2026-10-11-adr-currency-review/adr-002-accepted-1.md`.\n+`accepted-2` is this dated factual/scope annotation, not a new human adoption\n+of an expanded action surface. The detailed source hashes and test receipt\n+are in `docs/evidence/2026-10-11-adr-currency-review/review.json`.\n+\n+## Context (historical decision basis)\n \n CUST-WP-0001 requires a first working skeleton of the Custodian as an acting\n agent: a loop that observes project state, reasons about it, and executes\n" + }, + { + "id": "CUST-ADR-004", + "source_repo": "the-custodian", + "source_path": "canon/architecture/adr-004-connectivity-first-network-posture.md", + "served_revision": "accepted-1", + "current_revision": "accepted-2", + "served_status": "accepted", + "current_status": "accepted", + "same_revision_source_conflict": false, + "diff": "--- served-original\n+++ current-owner-source\n@@ -4,9 +4,11 @@\n title: \"Connectivity-First Network Posture for Custodian Infrastructure\"\n status: accepted\n owner: the-custodian\n-revision: \"accepted-1\"\n-last_reviewed: \"2026-03-26\"\n+revision: \"accepted-2\"\n+last_reviewed: \"2026-10-11\"\n review_interval: 6m\n+reviewed_by: \"codex \u2014 technical source/currency review\"\n+review_scope: \"Historical decision retained; current factual and authority scope clarified, no new grant\"\n decided_by: Bernd Worsch\n date: \"2026-03-26\"\n tags: [\"architecture\", \"network\", \"ops-bridge\", \"connectivity\", \"isolation\", \"security-posture\"]\n@@ -18,7 +20,60 @@\n \n Accepted.\n \n-## Context\n+## Technical currency review \u2014 2026-10-11\n+\n+The accepted decision remains a **transport-connectivity posture**: needed\n+connections use named, observable, managed paths with deliberate lifecycle\n+continuity. Persistent transport is compatible with independently limited\n+credentials, authenticated callers and resource-side authorization. It grants\n+neither access to every connected component nor authority to bypass isolation\n+required by the current security model. No blanket trust, permanent credential\n+permission or autonomous privileged execution is adopted by this review.\n+\n+The March Context, Rationale and deployment consequences below are retained\n+as the original decision's historical basis, not current environment facts or\n+a present security assurance. Specifically:\n+\n+- The central State Hub is on railiance01. The workstation address\n+ `http://127.0.0.1:8000` is a managed local forward; remote in-cluster callers\n+ use `http://10.43.68.154:8000`. The old remote-to-workstation reverse tunnel\n+ `http://127.0.0.1:18000` is retired. An optional local relay at :18080 is a\n+ separate explicitly enabled route, not the primary hub.\n+- OpsBridge owns observable connectivity, not credentials or authorization.\n+ Its INTENT explicitly disclaims credential-authority and security-policy\n+ ownership. Its older SCOPE still describes a workstation-local Hub and must\n+ not be used as current topology proof. Use the Custodian environment\n+ orientation and current owner records before selecting a route.\n+- Observability and a read-model design are **not proof that unauthorized\n+ writes, lateral movement or credential exposure are prevented**. The old\n+ narrow-write rationale is not a current control assurance. Source authority\n+ is governed by accepted ADR-001/ADR-012, and concrete service security is\n+ governed by its owners and accepted NetKingdom security-layer v0.8.\n+- SSH encryption protects its transport; it does not guarantee that a\n+ forwarded application is authenticated, that listener binding exposes no\n+ service, or that credentials cannot reach a process or log. The original\n+ \u201cno inbound ports exposed\u201d and age/SOPS phrases are not estate-wide proof.\n+ Current custody routing and secret-output rules remain mandatory.\n+- Build mode and absence of customers do not waive tenant boundaries, service\n+ admission, credential isolation or exact live-change approval. The\n+ agent-specific supervision decision of 2026-09-28 governs privileged\n+ execution; the Kubernetes change gate of 2026-09-21 governs its change path.\n+ Those existing decisions, not this transport ADR, supply authority.\n+\n+These clarifications apply existing accepted governance rather than replacing\n+the connectivity-first decision with a new isolation-first mandate. Changing\n+the transport posture or granting new authority would require a separate\n+proposal and human disposition. No fresh live topology or isolation audit was\n+performed by this source review; environment facts above retain the evidence\n+dates and ownership in `docs/agent-environment-orientation.md`.\n+\n+The original 2026-03-26 decision and accepted-1 bytes are retained in\n+`docs/evidence/2026-10-11-adr-currency-review/adr-004-accepted-1.md`.\n+`accepted-2` is this dated factual/scope annotation; the original decision\n+author and date remain history. Detailed source hashes and review findings\n+are in `docs/evidence/2026-10-11-adr-currency-review/review.json`.\n+\n+## Context (historical decision basis)\n \n The Custodian infrastructure spans multiple machines: a primary workstation, a\n shared compute node (COULOMBCORE), and Railiance cluster nodes. Agents running\n" + }, + { + "id": "RISK-F-0008", + "source_repo": "risk-nexus", + "source_path": "findings/RISK-F-0008-audit-retention-legal-basis-assumed.md", + "served_revision": "published-1", + "current_revision": "published-1", + "served_status": "accepted", + "current_status": "accepted", + "same_revision_source_conflict": true, + "diff": "--- served-original\n+++ current-owner-source\n@@ -42,12 +42,12 @@\n escalation_answered_by: the-custodian\n escalation_act: rule\n decision: \"identity in audit records: opaque subject ids preferred, agent identifiers where possible, operator credentials only where necessary, policy decisions tracked to the responsible party; zone-level privacy guarantees may raise the floor\"\n-outstanding: \"a defensible retention period per category (waits on audit-core's co-residency horizon), and the trigger list for buying an external answer\"\n+outstanding: \"whether the stated target periods are achievable under platform-pg co-residency, and whether a keyed commitment restores erasability\"\n determination: RISK-REG-0001\n-last_checked: \"2026-08-20T21:36:44Z\"\n-next_check: \"2026-08-20T21:36:44Z\"\n-cadence: instant\n-clean_streak: 0\n+last_checked: \"2026-09-22T06:13:33Z\"\n+next_check: \"2026-09-22T14:13:33Z\"\n+cadence: 8h\n+clean_streak: 2\n waiting_on:\n - who: audit-core\n what: \"does a keyed commitment restore erasability without breaking chain verification; what is the platform-pg co-residency horizon\"\n@@ -57,6 +57,7 @@\n default_at: \"2026-11-17\"\n graded_by: risk-nexus\n ruling: RISK-RULING-2026-08-19-C\n+checked_by: \"worsch\"\n ---\n \n # RISK-F-0008 \u2014 the exemption nobody has established\n@@ -362,3 +363,20 @@\n co-residency horizon that decides whether the stated retention periods are\n achievable. An accepted risk still gets checked.\n - **2026-08-20** \u2014 not clean: Trigger list ruled: no external determination in build mode; accepted with the legal policy set as the compensating control. Cadence instant \u2192 instant; checked again immediately.\n+- **2026-09-01** \u2014 not clean: the regulatory record now states target periods per category; the remaining gap is whether platform-pg co-residency can achieve them, while the keyed-commitment question is unchanged. Grade and acceptance hold. Cadence instant \u2192 instant; checked again immediately.\n+- **2026-09-02** \u2014 clean check: acceptance, RISK-REG-0001, and the audit-core keyed-commitment wait unchanged. Cadence instant \u2192 1h (1 clean in a row); next check 2026-09-02 09:11Z.\n+\n+## Evidence inspection \u2014 2026-09-05 (review remains due)\n+\n+[RISK-V-0003](../docs/verifications/2026-09-05-open-findings-source-review.md)\n+records a source/inbox inspection. The technical owner still documents the\n+cleartext-hash limitation; no new answer to the existing wait appeared in the\n+fetched risk-nexus inbox. The report now displays this record's existing\n+accepter, ending condition, determination and review date as a recorded\n+acceptance obligation, rather than inventing a fix plan to fill `fix_tracking`.\n+\n+This is not a clean check or a renewed acceptance. Whether the real-person or\n+counterparty condition has fired, and the substantive regulatory review, remain\n+unestablished here. The overdue review, existing grade and acceptance terms are\n+preserved; absence of a new inbox answer cannot certify the condition false.\n+- **2026-09-22** \u2014 clean check: Acceptance review: ending conditions not observed (no counterparty demand on record; no new real-person data intake reported); railiance 2026-09-09 message concerned F-0010 only; audit-core wait runs to 2026-11-17. Cadence 1h \u2192 8h (2 clean in a row); next check 2026-09-22 14:13Z.\n" + } +] diff --git a/docs/evidence/2026-10-11-hosting-preparation/immutable-audit.json b/docs/evidence/2026-10-11-hosting-preparation/immutable-audit.json new file mode 100644 index 0000000..29e1070 --- /dev/null +++ b/docs/evidence/2026-10-11-hosting-preparation/immutable-audit.json @@ -0,0 +1,798 @@ +{ + "baseline_documents": 72, + "candidate_documents": 73, + "immutable_results": [ + { + "id": "netkingdom-tenancy-posture", + "revision_path": "standards/tenancy-posture/v0.1/revisions/draft-14/index.html", + "old_source_digest": "27d6878c68310619877de516000d9af78d5456a92572a709884ed052d765b876", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "404f0bc2699040d0a065d557dc02232c025b1e340a77c64d645464bca2745b01", + "candidate_exists": true, + "candidate_html_sha256": "66525eb12b28a488ecca9c42e9788f3c19133848e990c8c1ec19765cf7c251d1", + "retained_byte_exact": false + }, + { + "id": "canon-autonomy-lanes", + "revision_path": "standards/autonomy-lanes/v0.1/revisions/accepted-1/index.html", + "old_source_digest": "106cb0c99f73983199c9f1d8491143aa2a2eb41ee450a02ec7968d3763f30399", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "aec0623d3e2a81d6141120906e97efbae941f7b2b5704ad42634484702932b13", + "candidate_exists": true, + "candidate_html_sha256": "5a9ccc831d60225e76a8ff3053034360d6672f6891cd5f6aaaa5bb0e66e160af", + "retained_byte_exact": false + }, + { + "id": "canon-contrib-convention", + "revision_path": "standards/contribution-convention/v0.1/revisions/accepted-1/index.html", + "old_source_digest": "6dfc1d7b2b82ab18228a84660c1c7dff607aad1d9da37ac12084cdd2ab8ae112", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "0b8aa643a908ba780ce231cd5628d10035697004d2ac512b9d60c32077cd936e", + "candidate_exists": true, + "candidate_html_sha256": "aeb3e5fd208fb245d29c116a5d194bdb71eeb69a335829c292e9be85951ee1ee", + "retained_byte_exact": false + }, + { + "id": "canon-project-repository-flavor", + "revision_path": "standards/project-repository-flavor/v0.1/revisions/accepted-1/index.html", + "old_source_digest": "61889b2f18a82a08b66ef90d758efe5c028a4f52aced1721a23e69c6f24a8224", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "cc5cdbaa875338b08a7ffed743121df16f002ada0b5a70268d32448ead57b2eb", + "candidate_exists": true, + "candidate_html_sha256": "dffddd9b2bdb30756593c493b31b5a888adedf3c3758e7edd8f62d9a33b46e5c", + "retained_byte_exact": false + }, + { + "id": "canon-work-record-types", + "revision_path": "standards/work-record-types/v0.1/revisions/accepted-1/index.html", + "old_source_digest": "1a40de294be332ee713cdaab532a32a83718f3952c339fbff835ecee899a8dbc", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "43cf02d4b768194c482fc09a0eda11c62bfd3d4250da0175a9f0012f68b1fec4", + "candidate_exists": false, + "candidate_html_sha256": null, + "retained_byte_exact": false + }, + { + "id": "canon-workplan-terminology-fleet", + "revision_path": "standards/workplan-terminology/v0.1/revisions/accepted-1/index.html", + "old_source_digest": "199812632ac342ca3231498e545e3701e10c191ca144035b36a23c1a0b717712", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "3fffb8c59107797e97db55bf8ee23280ddc2ca0ef1ea1edc1547c3b2c3bef3c0", + "candidate_exists": false, + "candidate_html_sha256": null, + "retained_byte_exact": false + }, + { + "id": "coulomb-estate-architecture", + "revision_path": "architecture/coulomb-estate/v0.1/revisions/draft-3/index.html", + "old_source_digest": "3b4f93acdedc6f6d1a1c737c3327d9d55061506c5a879be09e6b0a2ea7f5f9bc", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "bac4d0ac08eaf5a64d8e0b3363e091e2426de99a2e5054b6812c650084dd3dd8", + "candidate_exists": true, + "candidate_html_sha256": "97ddf92ff7a0815f3ed27287f094eaf49013247e1d62cdcd01c1856a761ec7c0", + "retained_byte_exact": false + }, + { + "id": "railiance-architecture", + "revision_path": "architecture/railiance/v0.1/revisions/draft-3/index.html", + "old_source_digest": "77de319978dfad02f40658bbeb96db46b246a46f1abd6a45f5b1269563de5568", + "old_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "http_status": 200, + "served_html_sha256": "4bfde5ce9935572f1b0c38368877605d35a30b8d822ad7735697462aa4e5318e", + "candidate_exists": true, + "candidate_html_sha256": "4a05fb79c324aeee1e7056f9da5ad78e18c4bfdaabd777d311e767d9f8b64b7e", + "retained_byte_exact": false + }, + { + "id": "net-kingdom-architecture", + "revision_path": "architecture/net-kingdom/v0.1/revisions/draft-3/index.html", + "old_source_digest": "b9f89f706a801d54961fc67d725ca433b7c3b6a083c54793de6e8b60d1dd42a8", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "827f197303b1074e022dd60af8e9e0245d01b6785bc039e21cb7c527805f7010", + "candidate_exists": true, + "candidate_html_sha256": "1d0fd228cfb3cd7c3e04dbf5071cbdce4d64939c30a586311128746da771d0f3", + "retained_byte_exact": false + }, + { + "id": "state-hub-architecture", + "revision_path": "architecture/state-hub/v0.1/revisions/draft-3/index.html", + "old_source_digest": "e7104b2182612efe90c9a12168757506402490229814d454e94917ae62d2bfac", + "old_source_revision": "803bb95e1d071f188f4202d53a235f8721a8ecdc", + "http_status": 200, + "served_html_sha256": "0430e83028f33b966e72f637f7b12acbcfa58a60810c0f6aaa2cfe3afc253c41", + "candidate_exists": true, + "candidate_html_sha256": "1281c6c65e37a6fd62051f9c36ccc528119bf4eb2c32b69bdd3e3d3c4b6904a6", + "retained_byte_exact": false + }, + { + "id": "policy-nexus-architecture", + "revision_path": "architecture/policy-nexus/v0.1/revisions/draft-1/index.html", + "old_source_digest": "179cbb86bca95f71f46f51ca1971ff1c274eed7d900adf0672b537d4bcc5b480", + "old_source_revision": "c1b60f322e3bcddff3cece618ece2bd3686169e1", + "http_status": 200, + "served_html_sha256": "1fe8e9ddfee222c1d3604f5a37a3b34b1274344dfb5233b1d9f52c98aaa944e6", + "candidate_exists": true, + "candidate_html_sha256": "ab06698c1ccddd1d465a23a98418cde1fd1ee82afc45085400dfa59bbd70595a", + "retained_byte_exact": false + }, + { + "id": "policy-nexus-adr-0001", + "revision_path": "adr/addressing-and-permanence/v1/revisions/accepted-1/index.html", + "old_source_digest": "a28668fb4b8b6c5ec8c94baac000061276d85ef1849ec7ab8d132b913dbfe3be", + "old_source_revision": "c1b60f322e3bcddff3cece618ece2bd3686169e1", + "http_status": 200, + "served_html_sha256": "883a91470d4dcf5d1eb0f02e769081c97c2be060379235757e27a7f59c9eb824", + "candidate_exists": true, + "candidate_html_sha256": "b097d2a93b1b68e5e0a084e245d17ba549c6d7745892816525dfbf469a8fb730", + "retained_byte_exact": false + }, + { + "id": "RMASTER-ADR-0001", + "revision_path": "adr/railiance-repository-prefix/v1/revisions/accepted-1/index.html", + "old_source_digest": "b9c993ded8d79d6f871dba9cf08a320b2d619609a448ad3d02b632f5b6f76497", + "old_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "http_status": 200, + "served_html_sha256": "f005c4645fd09709ea4fb6ed24e080e6dc8dd6975e3a87ccc495b6516195ac18", + "candidate_exists": true, + "candidate_html_sha256": "4e21ca2c5969caa7b5d6a3884ef08fa7ef095d8a135e54196ba4bacc21582c2d", + "retained_byte_exact": false + }, + { + "id": "RMASTER-ADR-0002", + "revision_path": "adr/railiance-rail-kubernetes-wave-1-boundary/v1/revisions/accepted-1/index.html", + "old_source_digest": "7e1fc5aedd7294192d8702a22b9f205e5bae20793836fdc48c0c071d10d7ab9d", + "old_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "http_status": 200, + "served_html_sha256": "bf60c98f5cb2e2e01e4c03bc8adb0059f041cfc894f2d1e5e086f2b7e1912c64", + "candidate_exists": true, + "candidate_html_sha256": "8f606d840935de029139c75de4e504cd006cb5e06074abfd45a0e2992dca69b7", + "retained_byte_exact": false + }, + { + "id": "RMASTER-ADR-0003", + "revision_path": "adr/railiance-rapp-first-wave-selection/v1/revisions/accepted-1/index.html", + "old_source_digest": "28135e94758b6935518d2e83458c1e607deeabb341879b605eef6529b3168cbc", + "old_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "http_status": 200, + "served_html_sha256": "baab701edeb557dd1c971a802952b9f038ddeb8b88c92324b338988e6261ec7a", + "candidate_exists": true, + "candidate_html_sha256": "56c97200598f98f1d8813ff1eb633586839a2c5a79d023a98b7899cd3d04caef", + "retained_byte_exact": false + }, + { + "id": "RMASTER-ADR-0004", + "revision_path": "adr/railiance-first-wave-reef-rollout/v1/revisions/accepted-1/index.html", + "old_source_digest": "36ec3aad5082ceff685d66e091c0a24b52abfc65b36595d00dce2b52a7250f4e", + "old_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "http_status": 200, + "served_html_sha256": "39274457b44e6ce0f4f5493272ad0022821ce86c190d42a0ed02d6d0131e637e", + "candidate_exists": true, + "candidate_html_sha256": "5e4f6f82d2bbc18c1a6347d371649026f4d199745c685695b752a3a869b8e8a1", + "retained_byte_exact": false + }, + { + "id": "RMASTER-ADR-0005", + "revision_path": "adr/railiance-derived-rail-composition/v1/revisions/accepted-1/index.html", + "old_source_digest": "e02982ce54691cf1589ac3d04012b9b9282f9eb54ed4b8f2f0544371f9b87f3e", + "old_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "http_status": 200, + "served_html_sha256": "9bc8dea9ae3fdc3a97ca78f388ef992d9966040e253fb7cc983b5efca78c4728", + "candidate_exists": true, + "candidate_html_sha256": "67f14e707247d126d22f906b8d3342549b9fe69ad15aa8d02f3bf6d42f7f412b", + "retained_byte_exact": false + }, + { + "id": "RMASTER-ADR-0006", + "revision_path": "adr/railiance-reef-production-admission/v1/revisions/accepted-2/index.html", + "old_source_digest": "9839d8a4a4617b745cfacab6a0fb72157e88cddf53f40226d8246a64864de533", + "old_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "http_status": 200, + "served_html_sha256": "051b11d1f82a21775aa4328b39385d43649ddf7a267cb12d691c17feff07161b", + "candidate_exists": false, + "candidate_html_sha256": null, + "retained_byte_exact": false + }, + { + "id": "RMASTER-ADR-0007", + "revision_path": "adr/railiance-rapp-declaration-contract/v1/revisions/accepted-2/index.html", + "old_source_digest": "b1185388ded53188dc624fbda9bee517dd3ac80b35e881cd4a7962531bec52ff", + "old_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "http_status": 200, + "served_html_sha256": "fdb4bedcc9120932e8c2792956c603f9fa45a6b06ebe30c92b7661c62cd0a0e6", + "candidate_exists": true, + "candidate_html_sha256": "a5bb562cf37626deae56120acdde9d5c0c33fe76e8357746353cdf738c674f16", + "retained_byte_exact": false + }, + { + "id": "RMASTER-ADR-0008", + "revision_path": "adr/railiance-private-by-default-exposure/v1/revisions/accepted-2/index.html", + "old_source_digest": "bd97ec8ee663bb416fae53c8cfe29c7066e4fd3a51a5bc364895297e6eb994b7", + "old_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "http_status": 200, + "served_html_sha256": "5c913da6b1e0c223bbe197d50f0f71dbf2e9c283ab7193758cf4567e059047ba", + "candidate_exists": true, + "candidate_html_sha256": "0a62d8368b0e4216985987b689faa5651ae14332664ecfbf54425845f07adf6d", + "retained_byte_exact": false + }, + { + "id": "ACT-ADR-001", + "revision_path": "adr/activity-core-event-bridge/v1/revisions/accepted-2/index.html", + "old_source_digest": "57ecf490465e71cc4970133301cc315748b1d6ee65caac3690472ff88f3aab06", + "old_source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "http_status": 200, + "served_html_sha256": "ebbe7111cc8fe75943cac3b8df625eed6675c268fb2d78de90de7766427ba032", + "candidate_exists": true, + "candidate_html_sha256": "351f848c282870a3dd750ba2021ca593d13999880518b4347de11481a6ea833f", + "retained_byte_exact": false + }, + { + "id": "ACT-ADR-002", + "revision_path": "adr/activity-core-definition-format/v1/revisions/accepted-2/index.html", + "old_source_digest": "1670e1616094bc6f2c0c7d47b2e019c02d5870b22b564ef83afa24caff197bab", + "old_source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "http_status": 200, + "served_html_sha256": "4f77070a5ceacc7463d6e570d650649149d934b865b069cf303a829e8823fb2c", + "candidate_exists": true, + "candidate_html_sha256": "4724343520b13411034fdcb440c433ad916685488fc9d2595935fdd2bc26371f", + "retained_byte_exact": false + }, + { + "id": "ACT-ADR-003", + "revision_path": "adr/activity-core-rule-instruction-model/v1/revisions/accepted-2/index.html", + "old_source_digest": "f1017730074727bcb0c68309c445ffb219ef647c8c5604adbfff76c4aa3f9d66", + "old_source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "http_status": 200, + "served_html_sha256": "99ff7ee81f42d460105a4dadc69ddd369094bde64617cb07bf850458ff0f2400", + "candidate_exists": true, + "candidate_html_sha256": "d71ec09ce482561ae38e1a01ba910340e4abf687fe1850ccf2a8f2262ab66fe8", + "retained_byte_exact": false + }, + { + "id": "ACT-ADR-004", + "revision_path": "adr/activity-core-producer-trust-boundary/v1/revisions/accepted-2/index.html", + "old_source_digest": "92da58f58ee3dbb62233161a0fc0b7780920f81ec37fdd40d476be354f24e0eb", + "old_source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "http_status": 200, + "served_html_sha256": "da014cb4991ecb8f03458016b69595a7ef82cece8a2a1f301b9a65140c6db5fe", + "candidate_exists": true, + "candidate_html_sha256": "eb29d356b52eefc6117d43aa3d23c85ca23947a84890f5f2d37c100e91f8eb93", + "retained_byte_exact": false + }, + { + "id": "ACT-ADR-005", + "revision_path": "adr/activity-core-ops-runs-vs-work-records/v1/revisions/accepted-2/index.html", + "old_source_digest": "984208262a0f6b67a6dc94cb078ebd0bcddc95d0b21a694d7e207677c10b471f", + "old_source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "http_status": 200, + "served_html_sha256": "fb1afdaa34eed2c923d451a0ab119079abfaf3924c452bcd8bb9ab84b0b047bf", + "candidate_exists": true, + "candidate_html_sha256": "2ee5c97ecd32e5c2a34521610fac51b871796a8380f648f9e06efc3caea37519", + "retained_byte_exact": false + }, + { + "id": "ops-warden-adr-0001", + "revision_path": "adr/ops-warden-catalog-pointer-layer/v1/revisions/1/index.html", + "old_source_digest": "7df0bb364276e382cbee9383e7e67d399b0ac1b0353246e0ab323e629e732a6d", + "old_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "http_status": 200, + "served_html_sha256": "1c0fb4914894de9aae6fa9549d5b6e0ae2dcb401cb50e70febb481a93cf5acdf", + "candidate_exists": true, + "candidate_html_sha256": "70e4b7518b82a732339440890ae76bba813f0ad69b4d045ab98e352447469665", + "retained_byte_exact": false + }, + { + "id": "ops-warden-adr-0002", + "revision_path": "adr/ops-warden-conduit-not-broker/v1/revisions/1/index.html", + "old_source_digest": "7dcc31732d774ddf2c98636b69ee12e2d74034836ed0def81b7e06461309b53a", + "old_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "http_status": 200, + "served_html_sha256": "3484b554239c617d438325ab8e87a744ac24797f6e665cb2da9562060841b352", + "candidate_exists": true, + "candidate_html_sha256": "11fd3dc0cf8b6e79b815770577d55b1e63f329c1a3e23f219456d466adeb9edd", + "retained_byte_exact": false + }, + { + "id": "ops-warden-adr-0003", + "revision_path": "adr/ops-warden-cover-gaps/v1/revisions/1/index.html", + "old_source_digest": "45b47c02afa575fbfe5be980e426c331a1d99bb9cd9c7a8de80bf8e319469f81", + "old_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "http_status": 200, + "served_html_sha256": "6deb88e23100298841f7022f1ab81376862073293ab1edefe9e4b6ad32e45163", + "candidate_exists": true, + "candidate_html_sha256": "1dc4689947f8944bb6efe876172f7c8135f1cfab97a1ef3fd9c56ea4433bd735", + "retained_byte_exact": false + }, + { + "id": "ops-warden-adr-0004", + "revision_path": "adr/ops-warden-high-risk-read-boundary/v1/revisions/1/index.html", + "old_source_digest": "5db38dcb754af1ba639f4ceca056df842bc7b91fa48dd8bad21611aee29f682d", + "old_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "http_status": 200, + "served_html_sha256": "830b886a57017d5e5feb1e39289bc13eec389dacc2f0072812ff45e76f735590", + "candidate_exists": false, + "candidate_html_sha256": null, + "retained_byte_exact": false + }, + { + "id": "ops-warden-adr-0005", + "revision_path": "adr/ops-warden-implement-narrowly/v1/revisions/1/index.html", + "old_source_digest": "31eafe4d8d9362a3446739d63c3af83fd9138cfdc6ad120086312a67dc0d27d0", + "old_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "http_status": 200, + "served_html_sha256": "4ea223b2f0105372a3d6868486480ce549926808658aaa1606dbde510f7ddb78", + "candidate_exists": true, + "candidate_html_sha256": "79ad8e9c2182eff9b20359d5701b7884d34fe346353ef2fad349cee5fda58045", + "retained_byte_exact": false + }, + { + "id": "CUST-ADR-001", + "revision_path": "adr/custodian-workplans-as-repo-artefacts/v1/revisions/accepted-2/index.html", + "old_source_digest": "183023ee57bae9c29e726fec5ee0361633fe3a2b182436a57869ae4b2a27af24", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "c8f76626fcdaf5d777b966082620071c7b005ebe85c8dcef2f9c899521edf28e", + "candidate_exists": true, + "candidate_html_sha256": "c1d30034131f2fa0ed9c7335530ee442fdd7fc7a4e4c5bcdae31aae11f4fdc4f", + "retained_byte_exact": false + }, + { + "id": "CUST-ADR-002", + "revision_path": "adr/custodian-agent-runtime/v1/revisions/accepted-1/index.html", + "old_source_digest": "6aef66cd5cf71a48f5b4e14401dc19a755e2b182445b272d5952df0eb0dea8ec", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "ea4eae05b62d50cd8204a47c1a4f3e3e0beda571880ed1de93ccbb17987d84aa", + "candidate_exists": true, + "candidate_html_sha256": "ea4eae05b62d50cd8204a47c1a4f3e3e0beda571880ed1de93ccbb17987d84aa", + "retained_byte_exact": true + }, + { + "id": "CUST-ADR-003", + "revision_path": "adr/custodian-materialized-derived-state/v1/revisions/accepted-2/index.html", + "old_source_digest": "fcb49719e2b85b9120c0bd5bebd5e82748ca713f16b44951c028b60205514e2b", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "bca06c973dcf18494b7b57071b00af5f37b2eac231f30cfac771ad49698d09d0", + "candidate_exists": true, + "candidate_html_sha256": "4ce241adbfdcff1e15ea51746a4cc6660287b4a9429b805da96bfa0aecb8001f", + "retained_byte_exact": false + }, + { + "id": "CUST-ADR-004", + "revision_path": "adr/custodian-connectivity-first/v1/revisions/accepted-1/index.html", + "old_source_digest": "3b68adfa6ab329e73f857cf691dc405136d2d66a0135e2c37c236aabe4659557", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "3db1b0f5c50543ff0fb022ea67f1d85b30f8ff691eaa1a2443220b7474b54de7", + "candidate_exists": true, + "candidate_html_sha256": "3db1b0f5c50543ff0fb022ea67f1d85b30f8ff691eaa1a2443220b7474b54de7", + "retained_byte_exact": true + }, + { + "id": "CUST-ADR-005", + "revision_path": "adr/custodian-cross-repo-workplans/v1/revisions/accepted-1/index.html", + "old_source_digest": "13195a721d0e579715f5f39ca6f72b5e49c583611e6ca089e6d4618708ae917f", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "61804eedae4fbc82ddf86bd17a994f1e2291290d39af3d8a212b503eab0f359d", + "candidate_exists": true, + "candidate_html_sha256": "3b648cc46f33ec1078e42011bb4530833a5e4cbed7010fc2f4e5d34efb292958", + "retained_byte_exact": false + }, + { + "id": "CUST-ADR-006", + "revision_path": "adr/custodian-canon-federation/v1/revisions/accepted-1/index.html", + "old_source_digest": "a454df0e1d227f99ebb36c4abd45c76cc12579086d34f0c0ccfccfd7f4790823", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "e10fdd888936a997cb91d6e5df8b801f2e7be1bbd170e82087faf1d37bf6d15b", + "candidate_exists": true, + "candidate_html_sha256": "992cebe70a9bfdc7e6c7e55c5d5c68d9a7ddef85784b9c0e70cbe66abf084b99", + "retained_byte_exact": false + }, + { + "id": "CUST-ADR-007", + "revision_path": "adr/custodian-workplan-identity/v1/revisions/accepted-2/index.html", + "old_source_digest": "1169f0c1f485a2bb7241a8f64f3167c060c04f83341b12586fd9be5c2b3693f8", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "bdf9f9522a48278f36cb86cd1f79765c97ab23b7abef94b5de2dada77dfc9e84", + "candidate_exists": true, + "candidate_html_sha256": "89de424e7e1f5e845e0d403a2fbdcde8857785168403e3ac1e2e91d612b24f6f", + "retained_byte_exact": false + }, + { + "id": "CUST-ADR-010", + "revision_path": "adr/custodian-hub-authority/v1/revisions/draft-2/index.html", + "old_source_digest": "5979da20799118259fc19246f51e8cc8f2c0c1be3d414318bd51d5a27d9ad565", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "83eeac4536266033882082332ffa150a8df69ad7b5f6bd8bd02d1ce587844ddd", + "candidate_exists": true, + "candidate_html_sha256": "412a30d486f3941df2a67367ee5facbfa4bef1884a932ab0565b566800e9154b", + "retained_byte_exact": false + }, + { + "id": "CUST-ADR-011", + "revision_path": "adr/custodian-federated-namespaces/v1/revisions/draft-2/index.html", + "old_source_digest": "f94f429c72f6cfd01ee83f1e5689d2d10ae52d7588d7cbd3ca40aca7eef46fb0", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "b4b023c51266d152adc41d126566c55fdb6bf303047ca93d0585a4f60f8194c8", + "candidate_exists": true, + "candidate_html_sha256": "45b931e7ff0611c34dc011383446450e76108fee8a69818262e4f2f98d3972ed", + "retained_byte_exact": false + }, + { + "id": "RPLAT-ADR-0001", + "revision_path": "adr/railiance-s3-platform-service-boundary/v1/revisions/1.0/index.html", + "old_source_digest": "63697581401b53a8437835c2bb8b40f8054cc40d0bc7a2972f83a4a10377f6ba", + "old_source_revision": "62423fd0925d75f5a2b27034044dc1080823d341", + "http_status": 200, + "served_html_sha256": "1fd07544a0a0f59087c5cbae908a2b883455e40b81bdb062305c33c646812708", + "candidate_exists": true, + "candidate_html_sha256": "8d9b19fb364f46edb78024170d5fa7334c1f8874e40badcf0ecf514ab560ee9f", + "retained_byte_exact": false + }, + { + "id": "RPLAT-ADR-0002", + "revision_path": "adr/railiance-placement-policy-ownership/v1/revisions/1.0/index.html", + "old_source_digest": "cfc0ad202c2eeeefd963127ff1defa127c715c001ecc684ab8759733bd8fb9f8", + "old_source_revision": "62423fd0925d75f5a2b27034044dc1080823d341", + "http_status": 200, + "served_html_sha256": "6abef6687e89a35352aa283d930c2dfdfd566cd4881a1859fdc7597214431be8", + "candidate_exists": true, + "candidate_html_sha256": "a236962f1493b7bbd0679b0897c80e4a3db0e1ae85958c074065e2771985a820", + "retained_byte_exact": false + }, + { + "id": "RPLAT-ADR-0003", + "revision_path": "adr/railiance-decisions-live-in-the-repo/v1/revisions/1.0/index.html", + "old_source_digest": "9b12ab6aa0e6f9eba03465782c35d6ff4683b682191599e38f4f9614cde9fa1b", + "old_source_revision": "62423fd0925d75f5a2b27034044dc1080823d341", + "http_status": 200, + "served_html_sha256": "ac49cddc344d6937dfdb0ba89f15bcad96bca1c6c360b0cc1c1e5632c69560e5", + "candidate_exists": true, + "candidate_html_sha256": "41a11ad7c4450ced6831e7565daf6879263bed7b588cd1ef4fec75a2ef15e68c", + "retained_byte_exact": false + }, + { + "id": "netkingdom-iam-profile-v0.3", + "revision_path": "standards/iam-profile/v0.3/revisions/accepted-1/index.html", + "old_source_digest": "6c47b596ea145fa197c9665081c84902482d90f5bb94d185c1223af65279be1d", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "b020f882093fbb0fa8290d4c09be665c3ed3b157522ec2f7231652e525019875", + "candidate_exists": true, + "candidate_html_sha256": "4413eb2f14bd8452019b600962fbc90838c1bd7aed6ef882800306a1e00394ec", + "retained_byte_exact": false + }, + { + "id": "NK-ADR-0006", + "revision_path": "adr/netkingdom-recursive-multi-tenant-identity-authorization/v1/revisions/1/index.html", + "old_source_digest": "e92a43649bb6e14e53ec62ecc819405bf3a44bda9557487f7177402f107bbd13", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "97070487ddd5d14bd85e68ae2e02f2512a3ad997352b384c0df344be48b3394c", + "candidate_exists": true, + "candidate_html_sha256": "bff0fd703bda76b2179e6a19d6bea0d345a90f5515f015db64f4b5aebf2a4b71", + "retained_byte_exact": false + }, + { + "id": "NK-ADR-0007", + "revision_path": "adr/netkingdom-security-orchestration-boundary/v1/revisions/1/index.html", + "old_source_digest": "b4fcff8448f07aca1fcb6908618bdb19f6c0e7dce25d4c5c8b85eec2f175233b", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "15056fedbd58e65e7c2b16cb5ea6d1c5f23cd23b1162c59e3e29500f8075ff3c", + "candidate_exists": true, + "candidate_html_sha256": "22555214d2bad8a972c6e1448de5621b4a639702f14ac234c33c1406b48dfdc9", + "retained_byte_exact": false + }, + { + "id": "NK-ADR-0008", + "revision_path": "adr/netkingdom-object-storage-sts-credential-vending/v1/revisions/1/index.html", + "old_source_digest": "f47276f4953f62b783397ee7fb1d3693da060103247e425a9a5b40d019fb4272", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "6a9a2b2249de33ff1342e027829f6cf2ce55dd067758c1cb63c8d2cd8e528f9b", + "candidate_exists": true, + "candidate_html_sha256": "2f23c90506a33941e510d9c1808c43d5e9ff989cfcec1b2c328a9f0aa9c8441b", + "retained_byte_exact": false + }, + { + "id": "NK-ADR-0010", + "revision_path": "adr/netkingdom-orchestration-dependency-intent/v1/revisions/1/index.html", + "old_source_digest": "b7c4f6a13f2f5add08bd03cb39c4f18ca25b202747dde883a309d1b3eaa1f571", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "be883fd0ad350fd0bb7991304fa29056485e1a9f95a61f8472e6b335261c4922", + "candidate_exists": true, + "candidate_html_sha256": "830595ef5922e2206cd654dd1c11d0889680a124132cf5264233bff59f1a8e07", + "retained_byte_exact": false + }, + { + "id": "NK-ADR-0011", + "revision_path": "adr/netkingdom-iam-profile-governance/v1/revisions/1/index.html", + "old_source_digest": "b5c7fd1e78026063b4a2ca4017202512d4f94ab5e12dc8498a34d04d3a48c7a9", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "3d75a914645d4c7bd2aaea7c9ebb3d1d22d491e1ebdd3cc84a8a3c7ad44ede1b", + "candidate_exists": true, + "candidate_html_sha256": "a9a3794ab00acaafdccf691623b23f8833eb4434922cdf16145876ea14cb41fa", + "retained_byte_exact": false + }, + { + "id": "NK-ADR-0012", + "revision_path": "adr/netkingdom-playbook-capability-ownership/v1/revisions/1/index.html", + "old_source_digest": "e269fabfc376f97f2a03ea66e34059d54016a445a7f30b351a6774b65c96c2ee", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "f64f93bcc0d527e5a782a599bf4e61374fcac9ded2c1a1e9003176de374a03fa", + "candidate_exists": true, + "candidate_html_sha256": "43e9f9dbd66f174bf8c3f27ef7257a825f3022aef12f6931be18deace5639645", + "retained_byte_exact": false + }, + { + "id": "NK-ADR-0013", + "revision_path": "adr/netkingdom-tenant-onboarding-taxonomy/v1/revisions/2/index.html", + "old_source_digest": "3a6030a8958176a902942ffd29154104ba3441a09d06edf3deaeadc7291ee0d4", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "c0b6997e551dba96b07eea3e78fca10f1760104494143606847fa74ccfdde53a", + "candidate_exists": true, + "candidate_html_sha256": "fb203f3ff1a51c0e798b0cdc1a85000ab575d677264a855f4fb5acacc6ba6cbe", + "retained_byte_exact": false + }, + { + "id": "NK-ADR-0014", + "revision_path": "adr/netkingdom-tenant-capability-ownership/v1/revisions/1/index.html", + "old_source_digest": "843f7a65f0fc145d08a73e364bc9a1dee0f7b8af934abf1584ee39dffa903ee0", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "6d2c05eb1fd48b27d2236ca7006e593bf8747df47e72952c947cbcda9ea0456f", + "candidate_exists": true, + "candidate_html_sha256": "7d8e851bd80e0b887d0d6e84b0ea2c82a78cb1f7a738e36f6a21b4f4557896f3", + "retained_byte_exact": false + }, + { + "id": "NK-ADR-0015", + "revision_path": "adr/netkingdom-railiance-workload-packaging/v1/revisions/1/index.html", + "old_source_digest": "2000985ef211aeedd3656e15cedb289e655526c2dcc4a161a64ffc27f0289db1", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "826db77b95bccd3bbb169808aebab17f6a511405b03d4d05c04c79be2e49f3aa", + "candidate_exists": true, + "candidate_html_sha256": "602d683697bd6d29eef48b447e5d07f11432b90c03e33c6cdc1572b3bfff0eff", + "retained_byte_exact": false + }, + { + "id": "RINFRA-ADR-0005", + "revision_path": "adr/railiance-k3s-api-tunnel-only/v1/revisions/accepted-1/index.html", + "old_source_digest": "4d93643d00d19b9d7c46843cd9be674c24e4cd613dc8e780c1684d97a6a9030a", + "old_source_revision": "f3e8bf3ab4b1dafa4d0b251b88ab2ef23a30c37b", + "http_status": 200, + "served_html_sha256": "ce55375217dd70e253f422ad6ed29006d99a64fec64ea16b2d6544ddd812654e", + "candidate_exists": true, + "candidate_html_sha256": "c9c7bd1061a2c2a8f37bcb126f19fcbc4bd8892bb64acef638b96efec0dc2a75", + "retained_byte_exact": false + }, + { + "id": "ACT-ADR-006", + "revision_path": "adr/activity-core-glas-profile-execution/v1/revisions/accepted-1/index.html", + "old_source_digest": "88041e9eb3a0f91fdd9da46f9813d7304b5ba58e62f9d28ea8f63c0a7b055b63", + "old_source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "http_status": 200, + "served_html_sha256": "fd7e88b98f1da488e38e52cdbf6fc08cd3e2e5862c9ad7a00f56fcee1da77daf", + "candidate_exists": true, + "candidate_html_sha256": "d4fda9ccae399c8c9fe5c263aa0266ea13e3da6d43e59c00c6dbf84853168942", + "retained_byte_exact": false + }, + { + "id": "ACT-ADR-007", + "revision_path": "adr/activity-core-bounded-operations/v1/revisions/accepted-1/index.html", + "old_source_digest": "75f42047b51293f7240f8a8c3ec02bf025a47fe6627f106fc4ef22fb1be7459d", + "old_source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "http_status": 200, + "served_html_sha256": "2c41e74508a38ba0ab9e711c7fd4052d3cbb837a15b10f1e43ca11329d10058a", + "candidate_exists": true, + "candidate_html_sha256": "ab43dc9fc3e57da6f21ebb271ba1f5190fecb396724f51c55b492502dd2f4ec2", + "retained_byte_exact": false + }, + { + "id": "ops-warden-adr-0007", + "revision_path": "adr/ops-warden-build-stage-credential-disclosure/v1/revisions/1/index.html", + "old_source_digest": "868f953688988b11ce48f4141833bbca51abdb4e86d5b495a8a905002830d7b0", + "old_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "http_status": 200, + "served_html_sha256": "a6ad7d6fb4fadc21e6e6755225745733780b13755b28d4d706c614b9269003aa", + "candidate_exists": true, + "candidate_html_sha256": "22213fb8cb2bfe3ed79bf3b47733bd005338b17c88fca4c0bf08391d30ad6415", + "retained_byte_exact": false + }, + { + "id": "ops-warden-adr-0008", + "revision_path": "adr/ops-warden-grade-disclosure-path/v1/revisions/1/index.html", + "old_source_digest": "2e22863ba592802cceb40b32d395168b42ace747741f5188307505eaa89ff764", + "old_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "http_status": 200, + "served_html_sha256": "b0129596a06884b2d34a74adfac4b08e3bd2e1af6ab8856d5bd4d50b4496a66c", + "candidate_exists": true, + "candidate_html_sha256": "1c462afdcdd736006fb7f484b381ce24c898282cf70353e0269a29d1713bbad9", + "retained_byte_exact": false + }, + { + "id": "ops-warden-adr-0009", + "revision_path": "adr/ops-warden-security-zones-consumer/v1/revisions/1/index.html", + "old_source_digest": "73fff22177b4bec2c56ff737e06e4225eb02d39669be3ea1b723906245f878b8", + "old_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "http_status": 200, + "served_html_sha256": "533a14b26081a1d88fa9c2fb0c7ea9bfde542e452cc989a68b5da33b0e024510", + "candidate_exists": true, + "candidate_html_sha256": "4ce260d625d1a0e867b740da6b859743e7ae5863522a95c1be823699ea851a43", + "retained_byte_exact": false + }, + { + "id": "ops-warden-adr-0010", + "revision_path": "adr/ops-warden-staff-layer/v1/revisions/1/index.html", + "old_source_digest": "064455bcb2870abf8e243f5a8c154e50bfc1c537cfba7996a792f9e314dd78ae", + "old_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "http_status": 200, + "served_html_sha256": "91727be45934f64eb5f98cb995fcea9f76bb219e1c6bec197e8937f8ea82f6b1", + "candidate_exists": true, + "candidate_html_sha256": "e94876e29a3c787949ba17f2c1717ce6cc3ec78b52a560174bfda39692f6176d", + "retained_byte_exact": false + }, + { + "id": "RMASTER-ADR-0009", + "revision_path": "adr/railiance-netkingdom-security-layer-interaction/v1/revisions/accepted-1/index.html", + "old_source_digest": "c2f81c9718715fc08ea7a2e3021d0d3159958ace23b60a16430020637b9f57f2", + "old_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "http_status": 200, + "served_html_sha256": "0a91e1d4fac1ebda5dc383c9f750d307dc29ef81789ac26d3c2659d58666b897", + "candidate_exists": true, + "candidate_html_sha256": "243ce8e88610ef88056d47c2bd5ee92b535b743a30ec1590a9aead13895ff11b", + "retained_byte_exact": false + }, + { + "id": "CUST-ADR-012", + "revision_path": "adr/custodian-projection-source-overlay/v1/revisions/1.0/index.html", + "old_source_digest": "b5e8582459f546ae789ad5fd62f458454aa19997b520e32b6b9f792d6af55987", + "old_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "http_status": 200, + "served_html_sha256": "63488f677010582fcbdd157fd6c10b2fa6e765db3c605605d1177e57e231f4ff", + "candidate_exists": true, + "candidate_html_sha256": "00bd1b554e41605c361759e3bea8c74ddd336c54a3ecadbc127dac86620d88ef", + "retained_byte_exact": false + }, + { + "id": "netkingdom-posture-feedback-v0.1", + "revision_path": "standards/posture-feedback/v0.1/revisions/0.1/index.html", + "old_source_digest": "dd2628b9f0c2a662ac44af22657d91918da228f307ebc7ceb09fc856162985db", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "54c14e25e018593d83df5f4b69a8316c0f1082460f94e5a0fbc80ecf61089d3b", + "candidate_exists": true, + "candidate_html_sha256": "7ad388d0f4b320db48366f1ff77a476fb5276e44911b3d1b10747e955eae79c0", + "retained_byte_exact": false + }, + { + "id": "netkingdom-security-layer-model-v0.7", + "revision_path": "standards/security-layer-model/v0.7/revisions/0.7/index.html", + "old_source_digest": "8155e7b123be1e84377d8278525b1bad8961007b6bb8ff012dd01ba24ff8065b", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "7d7a38a8c3b9fff65b46280fcd5723eff77dc9d0784f871bb35f718bc4e8c0e7", + "candidate_exists": true, + "candidate_html_sha256": "7d7a38a8c3b9fff65b46280fcd5723eff77dc9d0784f871bb35f718bc4e8c0e7", + "retained_byte_exact": true + }, + { + "id": "netkingdom-security-scenario-composition-v0.1", + "revision_path": "standards/security-scenario-composition/v0.1/revisions/0.1/index.html", + "old_source_digest": "17b715d78e04470a0f83b8bc8c7313ab16e13e9e741d5ec445172d9008fce937", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "2e401b2c8948ca983e7cd364faeea28b1d0bb6963a156e9b8e0e0a04961bf1c9", + "candidate_exists": true, + "candidate_html_sha256": "04d30ddc03b140d9972be5428533c66df9a197cfee0b2e0d286957c76c5a39fd", + "retained_byte_exact": false + }, + { + "id": "netkingdom-security-zones-v0.1", + "revision_path": "standards/security-zones/v0.1/revisions/0.1/index.html", + "old_source_digest": "32e71e9c0d6946bb14099eb66193822da26f199de0e11d8488464207f3bd9906", + "old_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "http_status": 200, + "served_html_sha256": "b8c7ce8198edbb637657ed4194356453aa3c3794c5592ebcee4c157a68a9a2b1", + "candidate_exists": true, + "candidate_html_sha256": "d6d4880830a57c138ef76cd0eb7e19b12bf65ba44ff069c30afc4914d71a8c9e", + "retained_byte_exact": false + }, + { + "id": "RISK-F-0001", + "revision_path": "findings/flex-auth-unauthenticated-check/v1/revisions/published-1/index.html", + "old_source_digest": "b2d1d0c526d5c9b5729b8353877bb9f29096667145f1498cfde73c9711206fda", + "old_source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "http_status": 200, + "served_html_sha256": "adfc18f51f4defb529c5529669eb33bf283a4d9c6da78f26bc8cd73cddad85b7", + "candidate_exists": true, + "candidate_html_sha256": "87d59a33ce04acafd52b90e58adeaf067996b18e13405aa6e3f3f329c4425c0d", + "retained_byte_exact": false + }, + { + "id": "RISK-F-0008", + "revision_path": "findings/audit-retention-legal-basis/v1/revisions/published-1/index.html", + "old_source_digest": "8bf47414a9f958afb57649f087a2617f91a8f2aeaa4f34d04d8fb3a0f453e840", + "old_source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "http_status": 200, + "served_html_sha256": "c6aee9543f8ef21964b8fe0cd966dfc4ac49b17958d258bef6b580210be08656", + "candidate_exists": true, + "candidate_html_sha256": "d15c81268f430a502be843e6a24ab69a9ddd5ace4280c49b499efaa5f0f84217", + "retained_byte_exact": false + }, + { + "id": "RISK-METHOD-SEVERITY", + "revision_path": "methods/risk-severity/v1/revisions/adopted-1/index.html", + "old_source_digest": "f064c591aeef9425bbfbd5fa2aae659abb0ef83b8586848300077f41dd4ffaab", + "old_source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "http_status": 200, + "served_html_sha256": "8073cd40f67197fe093ce1173bcc0a9ce1e7170c157dd88726997fff1cb791e6", + "candidate_exists": true, + "candidate_html_sha256": "5adcb8d703a836011dbd9db475318fcbb9c2dc4d4a2d8b88be885fdd1f300958", + "retained_byte_exact": false + }, + { + "id": "RISK-METHOD-DISCLOSURE", + "revision_path": "methods/risk-disclosure/v1/revisions/adopted-1/index.html", + "old_source_digest": "bd825a87736c8fb019d4224c3ef4fe3882b8f3028035225b4b8ff8cad62fdb81", + "old_source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "http_status": 200, + "served_html_sha256": "f0e2eaabe65543725f54b4701ea3a1efdbc936006ee9d309650feb21cbc593ca", + "candidate_exists": true, + "candidate_html_sha256": "444b7dfe892602458ffeedaaf90df4d41b11bdd936ee77f0db487d38d858f414", + "retained_byte_exact": false + }, + { + "id": "RISK-METHOD-REVIEW", + "revision_path": "methods/risk-review/v1/revisions/adopted-1/index.html", + "old_source_digest": "8f873d105bf6bd711a1a15ebd739b2abe97365b4e854be9d1c82e3ff3b162cfd", + "old_source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "http_status": 200, + "served_html_sha256": "64e707283c19bc46bf41b03c266ee6b625824696dcfc35dc83333b43e1662f8e", + "candidate_exists": true, + "candidate_html_sha256": "9625ea73467252e6f947eaf26e4c72c5ebd60e1a152614b049a54f96b49c1af1", + "retained_byte_exact": false + }, + { + "id": "RISK-METHOD-VERIFICATION", + "revision_path": "methods/risk-verification/v1/revisions/adopted-1/index.html", + "old_source_digest": "562b6eb3031ffe35e712c5e11e35aae23ce84a90e0090438dc89513f9685052b", + "old_source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "http_status": 200, + "served_html_sha256": "e6219a88daef48fd715d7c8092d2db6e5eda8f4d44dcbf9a0b7d975227237b92", + "candidate_exists": true, + "candidate_html_sha256": "88cc89b0cdcfd3b06b9ce18255046e68eee26a7c4ee148cd613ccf7707ef2b71", + "retained_byte_exact": false + }, + { + "id": "RISK-METHOD-DEPENDENCIES", + "revision_path": "methods/risk-dependencies/v1/revisions/adopted-1/index.html", + "old_source_digest": "a5e7b42043b0bb366add9bbc5f606e134ef5b0454532480203d5d87099296240", + "old_source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "http_status": 200, + "served_html_sha256": "24f29c04622f13156a1c6d69c0f98843e00d2d12cdbf506bf6a260d67b00d4e4", + "candidate_exists": true, + "candidate_html_sha256": "e249ffb8ccdeeef49ebf1b0e2d48b1c4a9a169574be37d6344aae2b770066f83", + "retained_byte_exact": false + } + ] +} diff --git a/docs/evidence/2026-10-11-hosting-preparation/live-baseline.json b/docs/evidence/2026-10-11-hosting-preparation/live-baseline.json new file mode 100644 index 0000000..e79a7a2 --- /dev/null +++ b/docs/evidence/2026-10-11-hosting-preparation/live-baseline.json @@ -0,0 +1,119 @@ +{ + "captured_at": "2026-10-11T00:16:33.889261+00:00", + "record_type": "read-only-pre-adoption", + "cluster_mutated": false, + "secret_values_observed": false, + "nodes": [ + { + "name": "239.62.205.92.host.secureserver.net", + "addresses": [ + { + "address": "92.205.62.239", + "type": "InternalIP" + }, + { + "address": "2a00:1169:111:6d70::", + "type": "InternalIP" + }, + { + "address": "239.62.205.92.host.secureserver.net", + "type": "Hostname" + } + ], + "cpu_allocatable": "4" + } + ], + "applications": [ + { + "name": "railiance-apps-root", + "target_revision": "main", + "namespace": "argocd", + "health": "Healthy", + "sync": "OutOfSync" + } + ], + "deployment": { + "image": "forgejo.coulomb.social/coulomb/policy-nexus@sha256:23df0ab3dc80d2a92b8058eeb572c222ceb05d17434f173a24142b2450ab3cea", + "generation": 8, + "ready": 1, + "observed_generation": 8, + "replicas": 1, + "strategy": { + "rollingUpdate": { + "maxSurge": 1, + "maxUnavailable": 0 + }, + "type": "RollingUpdate" + }, + "resources": { + "limits": { + "cpu": "100m", + "memory": "64Mi" + }, + "requests": { + "cpu": "10m", + "memory": "32Mi" + } + }, + "release_metadata": { + "meta.helm.sh/release-name": "policy-nexus", + "meta.helm.sh/release-namespace": "policy-nexus", + "policy.coulomb.social/publication-manifest-digest": "09f9f7edcc46e31d4b3f73b62d16fdf17808ae1f893f312242b7b9e2037319e0", + "policy.coulomb.social/source-inventory-digest": "9658803bc79d19cef1a9c842d1a8c161f1998d59ce6e4bc4df3c88aba2f4c9c4", + "policy.coulomb.social/source-set-digest": "03f04902a3559099433795453829d75470aa1490dd243b42e7703b6eab87772f", + "argocd.argoproj.io/tracking-id": null + }, + "managed_by": "Helm" + }, + "helm_history": [ + { + "revision": 1, + "status": "superseded", + "chart": "policy-nexus-0.1.0" + }, + { + "revision": 2, + "status": "superseded", + "chart": "policy-nexus-0.2.0" + }, + { + "revision": 3, + "status": "superseded", + "chart": "policy-nexus-0.1.0" + }, + { + "revision": 4, + "status": "superseded", + "chart": "policy-nexus-0.2.0" + }, + { + "revision": 5, + "status": "superseded", + "chart": "policy-nexus-0.2.0" + }, + { + "revision": 6, + "status": "superseded", + "chart": "policy-nexus-0.2.0" + }, + { + "revision": 7, + "status": "superseded", + "chart": "policy-nexus-0.2.0" + }, + { + "revision": 8, + "status": "deployed", + "chart": "policy-nexus-0.2.0" + } + ], + "helm_release_inputs": { + "image": { + "digest": "sha256:23df0ab3dc80d2a92b8058eeb572c222ceb05d17434f173a24142b2450ab3cea", + "repository": "forgejo.coulomb.social/coulomb/policy-nexus" + }, + "publicationManifestDigest": "09f9f7edcc46e31d4b3f73b62d16fdf17808ae1f893f312242b7b9e2037319e0", + "sourceInventoryDigest": "9658803bc79d19cef1a9c842d1a8c161f1998d59ce6e4bc4df3c88aba2f4c9c4", + "sourceSetDigest": "03f04902a3559099433795453829d75470aa1490dd243b42e7703b6eab87772f" + } +} diff --git a/docs/evidence/2026-10-11-hosting-preparation/public-baseline-manifest.json b/docs/evidence/2026-10-11-hosting-preparation/public-baseline-manifest.json new file mode 100644 index 0000000..05aa68a --- /dev/null +++ b/docs/evidence/2026-10-11-hosting-preparation/public-baseline-manifest.json @@ -0,0 +1,1230 @@ +{ + "documents": [ + { + "canonical_path": "standards/tenancy-posture/v0.1/index.html", + "currency": "current", + "id": "netkingdom-tenancy-posture", + "last_reviewed": "2026-08-23", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-23", + "revision": "draft-14", + "revision_path": "standards/tenancy-posture/v0.1/revisions/draft-14/index.html", + "source_digest": "27d6878c68310619877de516000d9af78d5456a92572a709884ed052d765b876", + "source_path": "canon/standards/tenancy-posture_v0.1.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "proposed", + "title": "NetKingdom Tenancy Posture v0.1" + }, + { + "canonical_path": "standards/autonomy-lanes/v0.1/index.html", + "currency": "current", + "id": "canon-autonomy-lanes", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-1", + "revision_path": "standards/autonomy-lanes/v0.1/revisions/accepted-1/index.html", + "source_digest": "106cb0c99f73983199c9f1d8491143aa2a2eb41ee450a02ec7968d3763f30399", + "source_path": "canon/standards/autonomy-lanes_v0.1.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Autonomy Lanes (Fleet) v0.1" + }, + { + "canonical_path": "standards/contribution-convention/v0.1/index.html", + "currency": "current", + "id": "canon-contrib-convention", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-1", + "revision_path": "standards/contribution-convention/v0.1/revisions/accepted-1/index.html", + "source_digest": "6dfc1d7b2b82ab18228a84660c1c7dff607aad1d9da37ac12084cdd2ab8ae112", + "source_path": "canon/standards/contribution-convention_v0.1.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Contribution Convention v0.1" + }, + { + "canonical_path": "standards/project-repository-flavor/v0.1/index.html", + "currency": "current", + "id": "canon-project-repository-flavor", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-1", + "revision_path": "standards/project-repository-flavor/v0.1/revisions/accepted-1/index.html", + "source_digest": "61889b2f18a82a08b66ef90d758efe5c028a4f52aced1721a23e69c6f24a8224", + "source_path": "canon/standards/project-repository-flavor_v0.1.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Project Repository Flavor (prj-) v0.1" + }, + { + "canonical_path": "standards/work-record-types/v0.1/index.html", + "currency": "current", + "id": "canon-work-record-types", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-1", + "revision_path": "standards/work-record-types/v0.1/revisions/accepted-1/index.html", + "source_digest": "1a40de294be332ee713cdaab532a32a83718f3952c339fbff835ecee899a8dbc", + "source_path": "canon/standards/work-record-types_v0.1.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Work Record Types & Identity (Fleet) v0.1" + }, + { + "canonical_path": "standards/workplan-terminology/v0.1/index.html", + "currency": "current", + "id": "canon-workplan-terminology-fleet", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-1", + "revision_path": "standards/workplan-terminology/v0.1/revisions/accepted-1/index.html", + "source_digest": "199812632ac342ca3231498e545e3701e10c191ca144035b36a23c1a0b717712", + "source_path": "canon/standards/workplan-terminology-fleet_v0.1.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Workplan Terminology (Fleet) v0.1" + }, + { + "canonical_path": "architecture/coulomb-estate/v0.1/index.html", + "currency": "current", + "id": "coulomb-estate-architecture", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "draft-3", + "revision_path": "architecture/coulomb-estate/v0.1/revisions/draft-3/index.html", + "source_digest": "3b4f93acdedc6f6d1a1c737c3327d9d55061506c5a879be09e6b0a2ea7f5f9bc", + "source_path": "canon/architecture/coulomb-estate_v0.1.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "proposed", + "title": "Coulomb estate architecture" + }, + { + "canonical_path": "architecture/railiance/v0.1/index.html", + "currency": "current", + "id": "railiance-architecture", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-02-28", + "revision": "draft-3", + "revision_path": "architecture/railiance/v0.1/revisions/draft-3/index.html", + "source_digest": "77de319978dfad02f40658bbeb96db46b246a46f1abd6a45f5b1269563de5568", + "source_path": "docs/architecture/railiance_v0.1.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "proposed", + "title": "Railiance architecture" + }, + { + "canonical_path": "architecture/net-kingdom/v0.1/index.html", + "currency": "current", + "id": "net-kingdom-architecture", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-28", + "revision": "draft-3", + "revision_path": "architecture/net-kingdom/v0.1/revisions/draft-3/index.html", + "source_digest": "b9f89f706a801d54961fc67d725ca433b7c3b6a083c54793de6e8b60d1dd42a8", + "source_path": "docs/architecture/net-kingdom_v0.1.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "proposed", + "title": "NetKingdom architecture" + }, + { + "canonical_path": "architecture/state-hub/v0.1/index.html", + "currency": "current", + "id": "state-hub-architecture", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "state-hub", + "review_due": "2027-02-28", + "revision": "draft-3", + "revision_path": "architecture/state-hub/v0.1/revisions/draft-3/index.html", + "source_digest": "e7104b2182612efe90c9a12168757506402490229814d454e94917ae62d2bfac", + "source_path": "docs/architecture/state-hub_v0.1.md", + "source_repo": "state-hub", + "source_revision": "803bb95e1d071f188f4202d53a235f8721a8ecdc", + "status": "proposed", + "title": "State Hub architecture" + }, + { + "canonical_path": "architecture/policy-nexus/v0.1/index.html", + "currency": "current", + "id": "policy-nexus-architecture", + "last_reviewed": "2026-08-18", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-18", + "revision": "draft-1", + "revision_path": "architecture/policy-nexus/v0.1/revisions/draft-1/index.html", + "source_digest": "179cbb86bca95f71f46f51ca1971ff1c274eed7d900adf0672b537d4bcc5b480", + "source_path": "docs/architecture/policy-nexus_v0.1.md", + "source_repo": "policy-nexus", + "source_revision": "c1b60f322e3bcddff3cece618ece2bd3686169e1", + "status": "proposed", + "title": "Policy Nexus architecture" + }, + { + "canonical_path": "adr/addressing-and-permanence/v1/index.html", + "currency": "current", + "id": "policy-nexus-adr-0001", + "last_reviewed": "2026-08-18", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-18", + "revision": "accepted-1", + "revision_path": "adr/addressing-and-permanence/v1/revisions/accepted-1/index.html", + "source_digest": "a28668fb4b8b6c5ec8c94baac000061276d85ef1849ec7ab8d132b913dbfe3be", + "source_path": "docs/adr/ADR-0001-addressing-and-permanence.md", + "source_repo": "policy-nexus", + "source_revision": "c1b60f322e3bcddff3cece618ece2bd3686169e1", + "status": "accepted", + "title": "Policy addressing and permanence" + }, + { + "canonical_path": "adr/railiance-repository-prefix/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0001", + "last_reviewed": "2026-07-25", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-01-25", + "revision": "accepted-1", + "revision_path": "adr/railiance-repository-prefix/v1/revisions/accepted-1/index.html", + "source_digest": "b9c993ded8d79d6f871dba9cf08a320b2d619609a448ad3d02b632f5b6f76497", + "source_path": "docs/adr/ADR-0001-repository-prefix-architecture.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "Repository Prefix Architecture" + }, + { + "canonical_path": "adr/railiance-rail-kubernetes-wave-1-boundary/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0002", + "last_reviewed": "2026-07-25", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-01-25", + "revision": "accepted-1", + "revision_path": "adr/railiance-rail-kubernetes-wave-1-boundary/v1/revisions/accepted-1/index.html", + "source_digest": "7e1fc5aedd7294192d8702a22b9f205e5bae20793836fdc48c0c071d10d7ab9d", + "source_path": "docs/adr/ADR-0002-rail-kubernetes-wave-1-boundary.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "Wave 1 rail-kubernetes Boundary" + }, + { + "canonical_path": "adr/railiance-rapp-first-wave-selection/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0003", + "last_reviewed": "2026-07-25", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-01-25", + "revision": "accepted-1", + "revision_path": "adr/railiance-rapp-first-wave-selection/v1/revisions/accepted-1/index.html", + "source_digest": "28135e94758b6935518d2e83458c1e607deeabb341879b605eef6529b3168cbc", + "source_path": "docs/adr/ADR-0003-rapp-first-wave-selection.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "First-Wave rapp Selection" + }, + { + "canonical_path": "adr/railiance-first-wave-reef-rollout/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0004", + "last_reviewed": "2026-07-26", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-01-26", + "revision": "accepted-1", + "revision_path": "adr/railiance-first-wave-reef-rollout/v1/revisions/accepted-1/index.html", + "source_digest": "36ec3aad5082ceff685d66e091c0a24b52abfc65b36595d00dce2b52a7250f4e", + "source_path": "docs/adr/ADR-0004-first-wave-reef-rollout.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "First-Wave reef Rollout" + }, + { + "canonical_path": "adr/railiance-derived-rail-composition/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0005", + "last_reviewed": "2026-07-26", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-01-26", + "revision": "accepted-1", + "revision_path": "adr/railiance-derived-rail-composition/v1/revisions/accepted-1/index.html", + "source_digest": "e02982ce54691cf1589ac3d04012b9b9282f9eb54ed4b8f2f0544371f9b87f3e", + "source_path": "docs/adr/ADR-0005-derived-rail-composition.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "Derived Rail Composition" + }, + { + "canonical_path": "adr/railiance-reef-production-admission/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0006", + "last_reviewed": "2026-08-29", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-02-28", + "revision": "accepted-2", + "revision_path": "adr/railiance-reef-production-admission/v1/revisions/accepted-2/index.html", + "source_digest": "9839d8a4a4617b745cfacab6a0fb72157e88cddf53f40226d8246a64864de533", + "source_path": "docs/adr/ADR-0006-reef-production-admission.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "Reef Production Admission" + }, + { + "canonical_path": "adr/railiance-rapp-declaration-contract/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0007", + "last_reviewed": "2026-08-23", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-02-23", + "revision": "accepted-2", + "revision_path": "adr/railiance-rapp-declaration-contract/v1/revisions/accepted-2/index.html", + "source_digest": "b1185388ded53188dc624fbda9bee517dd3ac80b35e881cd4a7962531bec52ff", + "source_path": "docs/adr/ADR-0007-rapp-declaration-contract.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "Rapp Declaration Contract" + }, + { + "canonical_path": "adr/railiance-private-by-default-exposure/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0008", + "last_reviewed": "2026-08-29", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-02-28", + "revision": "accepted-2", + "revision_path": "adr/railiance-private-by-default-exposure/v1/revisions/accepted-2/index.html", + "source_digest": "bd97ec8ee663bb416fae53c8cfe29c7066e4fd3a51a5bc364895297e6eb994b7", + "source_path": "docs/adr/ADR-0008-private-by-default-exposure.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "Private-by-default Exposure" + }, + { + "canonical_path": "adr/activity-core-event-bridge/v1/index.html", + "currency": "current", + "id": "ACT-ADR-001", + "last_reviewed": "2026-05-14", + "lifecycle": "active", + "owner": "activity-core", + "review_due": "2026-11-14", + "revision": "accepted-2", + "revision_path": "adr/activity-core-event-bridge/v1/revisions/accepted-2/index.html", + "source_digest": "57ecf490465e71cc4970133301cc315748b1d6ee65caac3690472ff88f3aab06", + "source_path": "docs/adr/adr-001-event-bridge-architecture.md", + "source_repo": "activity-core", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "status": "accepted", + "title": "Activity-Core as Coulomb Org Event Bridge" + }, + { + "canonical_path": "adr/activity-core-definition-format/v1/index.html", + "currency": "current", + "id": "ACT-ADR-002", + "last_reviewed": "2026-05-14", + "lifecycle": "active", + "owner": "activity-core", + "review_due": "2026-11-14", + "revision": "accepted-2", + "revision_path": "adr/activity-core-definition-format/v1/revisions/accepted-2/index.html", + "source_digest": "1670e1616094bc6f2c0c7d47b2e019c02d5870b22b564ef83afa24caff197bab", + "source_path": "docs/adr/adr-002-definition-format.md", + "source_repo": "activity-core", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "status": "accepted", + "title": "Markdown-as-Definition Format for Event Types and ActivityDefinitions" + }, + { + "canonical_path": "adr/activity-core-rule-instruction-model/v1/index.html", + "currency": "current", + "id": "ACT-ADR-003", + "last_reviewed": "2026-05-14", + "lifecycle": "active", + "owner": "activity-core", + "review_due": "2026-11-14", + "revision": "accepted-2", + "revision_path": "adr/activity-core-rule-instruction-model/v1/revisions/accepted-2/index.html", + "source_digest": "f1017730074727bcb0c68309c445ffb219ef647c8c5604adbfff76c4aa3f9d66", + "source_path": "docs/adr/adr-003-rule-instruction-model.md", + "source_repo": "activity-core", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "status": "accepted", + "title": "Rule vs. Instruction Model and Expression DSL" + }, + { + "canonical_path": "adr/activity-core-producer-trust-boundary/v1/index.html", + "currency": "current", + "id": "ACT-ADR-004", + "last_reviewed": "2026-06-26", + "lifecycle": "active", + "owner": "activity-core", + "review_due": "2026-12-26", + "revision": "accepted-2", + "revision_path": "adr/activity-core-producer-trust-boundary/v1/revisions/accepted-2/index.html", + "source_digest": "92da58f58ee3dbb62233161a0fc0b7780920f81ec37fdd40d476be354f24e0eb", + "source_path": "docs/adr/adr-004-producer-trust-boundary.md", + "source_repo": "activity-core", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "status": "accepted", + "title": "The Producer Trust Boundary \u2014 Guardrails and Error-Correction for Untrusted Output" + }, + { + "canonical_path": "adr/activity-core-ops-runs-vs-work-records/v1/index.html", + "currency": "current", + "id": "ACT-ADR-005", + "last_reviewed": "2026-08-03", + "lifecycle": "active", + "owner": "activity-core", + "review_due": "2027-02-03", + "revision": "accepted-2", + "revision_path": "adr/activity-core-ops-runs-vs-work-records/v1/revisions/accepted-2/index.html", + "source_digest": "984208262a0f6b67a6dc94cb078ebd0bcddc95d0b21a694d7e207677c10b471f", + "source_path": "docs/adr/adr-005-ops-runs-vs-dev-work-records.md", + "source_repo": "activity-core", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "status": "accepted", + "title": "Ops runs vs development work records \u2014 claim queue and plane split" + }, + { + "canonical_path": "adr/ops-warden-catalog-pointer-layer/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0001", + "last_reviewed": "2026-08-18", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2027-02-18", + "revision": "1", + "revision_path": "adr/ops-warden-catalog-pointer-layer/v1/revisions/1/index.html", + "source_digest": "7df0bb364276e382cbee9383e7e67d399b0ac1b0353246e0ab323e629e732a6d", + "source_path": "docs/adr/ADR-0001-catalog-is-a-pointer-layer.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0001 \u2014 The routing catalog is a pointer layer, never a second copy" + }, + { + "canonical_path": "adr/ops-warden-conduit-not-broker/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0002", + "last_reviewed": "2026-08-18", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2027-02-18", + "revision": "1", + "revision_path": "adr/ops-warden-conduit-not-broker/v1/revisions/1/index.html", + "source_digest": "7dcc31732d774ddf2c98636b69ee12e2d74034836ed0def81b7e06461309b53a", + "source_path": "docs/adr/ADR-0002-conduit-not-broker.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0002 \u2014 ops-warden is a transparent conduit, never a secret broker" + }, + { + "canonical_path": "adr/ops-warden-cover-gaps/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0003", + "last_reviewed": "2026-08-18", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2027-02-18", + "revision": "1", + "revision_path": "adr/ops-warden-cover-gaps/v1/revisions/1/index.html", + "source_digest": "45b47c02afa575fbfe5be980e426c331a1d99bb9cd9c7a8de80bf8e319469f81", + "source_path": "docs/adr/ADR-0003-cover-gaps-never-silently-own-them.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0003 \u2014 Cover gaps, but never silently own them" + }, + { + "canonical_path": "adr/ops-warden-high-risk-read-boundary/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0004", + "last_reviewed": "2026-08-18", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2027-02-18", + "revision": "1", + "revision_path": "adr/ops-warden-high-risk-read-boundary/v1/revisions/1/index.html", + "source_digest": "5db38dcb754af1ba639f4ceca056df842bc7b91fa48dd8bad21611aee29f682d", + "source_path": "docs/adr/ADR-0004-agent-read-boundary-on-high-risk-lanes.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0004 \u2014 High-risk lanes refuse raw value streaming to agent sessions" + }, + { + "canonical_path": "adr/ops-warden-implement-narrowly/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0005", + "last_reviewed": "2026-08-18", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2027-02-18", + "revision": "1", + "revision_path": "adr/ops-warden-implement-narrowly/v1/revisions/1/index.html", + "source_digest": "31eafe4d8d9362a3446739d63c3af83fd9138cfdc6ad120086312a67dc0d27d0", + "source_path": "docs/adr/ADR-0005-implement-narrowly-route-broadly.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0005 \u2014 Implement one lane narrowly, route everything else" + }, + { + "canonical_path": "adr/custodian-workplans-as-repo-artefacts/v1/index.html", + "currency": "current", + "id": "CUST-ADR-001", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-2", + "revision_path": "adr/custodian-workplans-as-repo-artefacts/v1/revisions/accepted-2/index.html", + "source_digest": "183023ee57bae9c29e726fec5ee0361633fe3a2b182436a57869ae4b2a27af24", + "source_path": "canon/architecture/adr-001-workplans-as-repo-artefacts.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Workplans and Work Items Are Repository Artefacts" + }, + { + "canonical_path": "adr/custodian-agent-runtime/v1/index.html", + "currency": "current", + "id": "CUST-ADR-002", + "last_reviewed": "2026-03-12", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2026-09-12", + "revision": "accepted-1", + "revision_path": "adr/custodian-agent-runtime/v1/revisions/accepted-1/index.html", + "source_digest": "6aef66cd5cf71a48f5b4e14401dc19a755e2b182445b272d5952df0eb0dea8ec", + "source_path": "canon/architecture/adr-002-custodian-agent-runtime-design.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Custodian Agent Runtime \u2014 v0.1 Bootstrap Design" + }, + { + "canonical_path": "adr/custodian-materialized-derived-state/v1/index.html", + "currency": "current", + "id": "CUST-ADR-003", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-2", + "revision_path": "adr/custodian-materialized-derived-state/v1/revisions/accepted-2/index.html", + "source_digest": "fcb49719e2b85b9120c0bd5bebd5e82748ca713f16b44951c028b60205514e2b", + "source_path": "canon/architecture/adr-003-materialized-derived-state.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Materialized Derived State with Fingerprint Invalidation for Repo-Sourced Data" + }, + { + "canonical_path": "adr/custodian-connectivity-first/v1/index.html", + "currency": "current", + "id": "CUST-ADR-004", + "last_reviewed": "2026-03-26", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2026-09-26", + "revision": "accepted-1", + "revision_path": "adr/custodian-connectivity-first/v1/revisions/accepted-1/index.html", + "source_digest": "3b68adfa6ab329e73f857cf691dc405136d2d66a0135e2c37c236aabe4659557", + "source_path": "canon/architecture/adr-004-connectivity-first-network-posture.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Connectivity-First Network Posture for Custodian Infrastructure" + }, + { + "canonical_path": "adr/custodian-cross-repo-workplans/v1/index.html", + "currency": "current", + "id": "CUST-ADR-005", + "last_reviewed": "2026-06-22", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2026-12-22", + "revision": "accepted-1", + "revision_path": "adr/custodian-cross-repo-workplans/v1/revisions/accepted-1/index.html", + "source_digest": "13195a721d0e579715f5f39ca6f72b5e49c583611e6ca089e6d4618708ae917f", + "source_path": "canon/architecture/adr-005-cross-repo-workplans-project-repos.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Cross-Repo Workplans Live in Dedicated Project Repos" + }, + { + "canonical_path": "adr/custodian-canon-federation/v1/index.html", + "currency": "current", + "id": "CUST-ADR-006", + "last_reviewed": "2026-08-17", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-17", + "revision": "accepted-1", + "revision_path": "adr/custodian-canon-federation/v1/revisions/accepted-1/index.html", + "source_digest": "a454df0e1d227f99ebb36c4abd45c76cc12579086d34f0c0ccfccfd7f4790823", + "source_path": "canon/architecture/adr-006-canon-federation-concept-ownership.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Canon Federation and Concept Ownership Across InfoTech and Commerce" + }, + { + "canonical_path": "adr/custodian-workplan-identity/v1/index.html", + "currency": "current", + "id": "CUST-ADR-007", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-2", + "revision_path": "adr/custodian-workplan-identity/v1/revisions/accepted-2/index.html", + "source_digest": "1169f0c1f485a2bb7241a8f64f3167c060c04f83341b12586fd9be5c2b3693f8", + "source_path": "canon/architecture/adr-007-workplan-identity-and-repo-worker-topology.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Workplan Identity Uniqueness, Single Registrar, and Repo Worker Topology" + }, + { + "canonical_path": "adr/custodian-hub-authority/v1/index.html", + "currency": "current", + "id": "CUST-ADR-010", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "draft-2", + "revision_path": "adr/custodian-hub-authority/v1/revisions/draft-2/index.html", + "source_digest": "5979da20799118259fc19246f51e8cc8f2c0c1be3d414318bd51d5a27d9ad565", + "source_path": "canon/architecture/adr-010-hub-authority-and-local-cache-model.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "proposed", + "title": "Hub Authority, Local Cache, and the Two Kinds of Hub Data" + }, + { + "canonical_path": "adr/custodian-federated-namespaces/v1/index.html", + "currency": "current", + "id": "CUST-ADR-011", + "last_reviewed": "2026-08-17", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-17", + "revision": "draft-2", + "revision_path": "adr/custodian-federated-namespaces/v1/revisions/draft-2/index.html", + "source_digest": "f94f429c72f6cfd01ee83f1e5689d2d10ae52d7588d7cbd3ca40aca7eef46fb0", + "source_path": "canon/architecture/adr-011-federated-namespaces-and-reconciliation-limits.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "proposed", + "title": "Federated Namespaces: Four Planes, Declared Posture, and the Limits of Reconciliation" + }, + { + "canonical_path": "adr/railiance-s3-platform-service-boundary/v1/index.html", + "currency": "current", + "id": "RPLAT-ADR-0001", + "last_reviewed": "2026-08-17", + "lifecycle": "active", + "owner": "railiance-platform", + "review_due": "2027-02-17", + "revision": "1.0", + "revision_path": "adr/railiance-s3-platform-service-boundary/v1/revisions/1.0/index.html", + "source_digest": "63697581401b53a8437835c2bb8b40f8054cc40d0bc7a2972f83a4a10377f6ba", + "source_path": "docs/adr/ADR-0001-s3-platform-service-boundary.md", + "source_repo": "railiance-platform", + "source_revision": "62423fd0925d75f5a2b27034044dc1080823d341", + "status": "accepted", + "title": "ADR-0001 \u2014 S3 owns platform services, not the substrate beneath them" + }, + { + "canonical_path": "adr/railiance-placement-policy-ownership/v1/index.html", + "currency": "current", + "id": "RPLAT-ADR-0002", + "last_reviewed": "2026-08-17", + "lifecycle": "active", + "owner": "railiance-platform", + "review_due": "2027-02-17", + "revision": "1.0", + "revision_path": "adr/railiance-placement-policy-ownership/v1/revisions/1.0/index.html", + "source_digest": "cfc0ad202c2eeeefd963127ff1defa127c715c001ecc684ab8759733bd8fb9f8", + "source_path": "docs/adr/ADR-0002-placement-policy-ownership.md", + "source_repo": "railiance-platform", + "source_revision": "62423fd0925d75f5a2b27034044dc1080823d341", + "status": "proposed", + "title": "ADR-0002 \u2014 S3 owns the placement rule; the package repo owns the number" + }, + { + "canonical_path": "adr/railiance-decisions-live-in-the-repo/v1/index.html", + "currency": "current", + "id": "RPLAT-ADR-0003", + "last_reviewed": "2026-08-17", + "lifecycle": "active", + "owner": "railiance-platform", + "review_due": "2027-02-17", + "revision": "1.0", + "revision_path": "adr/railiance-decisions-live-in-the-repo/v1/revisions/1.0/index.html", + "source_digest": "9b12ab6aa0e6f9eba03465782c35d6ff4683b682191599e38f4f9614cde9fa1b", + "source_path": "docs/adr/ADR-0003-decisions-live-in-the-repo.md", + "source_repo": "railiance-platform", + "source_revision": "62423fd0925d75f5a2b27034044dc1080823d341", + "status": "accepted", + "title": "ADR-0003 \u2014 Decisions that bind others live in docs/adr, not only in the State Hub" + }, + { + "canonical_path": "standards/iam-profile/v0.3/index.html", + "currency": "current", + "id": "netkingdom-iam-profile-v0.3", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "accepted-1", + "revision_path": "standards/iam-profile/v0.3/revisions/accepted-1/index.html", + "source_digest": "6c47b596ea145fa197c9665081c84902482d90f5bb94d185c1223af65279be1d", + "source_path": "canon/standards/iam-profile_v0.3.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "NetKingdom IAM Profile v0.3" + }, + { + "canonical_path": "adr/netkingdom-recursive-multi-tenant-identity-authorization/v1/index.html", + "currency": "current", + "id": "NK-ADR-0006", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-recursive-multi-tenant-identity-authorization/v1/revisions/1/index.html", + "source_digest": "e92a43649bb6e14e53ec62ecc819405bf3a44bda9557487f7177402f107bbd13", + "source_path": "docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "Recursive Multi-Tenant Identity and Authorization Architecture" + }, + { + "canonical_path": "adr/netkingdom-security-orchestration-boundary/v1/index.html", + "currency": "current", + "id": "NK-ADR-0007", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-security-orchestration-boundary/v1/revisions/1/index.html", + "source_digest": "b4fcff8448f07aca1fcb6908618bdb19f6c0e7dce25d4c5c8b85eec2f175233b", + "source_path": "docs/adr/ADR-0007-security-orchestration-boundary.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "Security Orchestration Boundary" + }, + { + "canonical_path": "adr/netkingdom-object-storage-sts-credential-vending/v1/index.html", + "currency": "current", + "id": "NK-ADR-0008", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-object-storage-sts-credential-vending/v1/revisions/1/index.html", + "source_digest": "f47276f4953f62b783397ee7fb1d3693da060103247e425a9a5b40d019fb4272", + "source_path": "docs/adr/ADR-0008-object-storage-sts-credential-vending.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "Object Storage STS Credential Vending Boundary" + }, + { + "canonical_path": "adr/netkingdom-orchestration-dependency-intent/v1/index.html", + "currency": "current", + "id": "NK-ADR-0010", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-orchestration-dependency-intent/v1/revisions/1/index.html", + "source_digest": "b7c4f6a13f2f5add08bd03cb39c4f18ca25b202747dde883a309d1b3eaa1f571", + "source_path": "docs/adr/ADR-0010-orchestration-vs-dependency-self-coherent-intent.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "Orchestration vs Dependency, and Self-Coherent Intent" + }, + { + "canonical_path": "adr/netkingdom-iam-profile-governance/v1/index.html", + "currency": "current", + "id": "NK-ADR-0011", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-iam-profile-governance/v1/revisions/1/index.html", + "source_digest": "b5c7fd1e78026063b4a2ca4017202512d4f94ab5e12dc8498a34d04d3a48c7a9", + "source_path": "docs/adr/ADR-0011-iam-profile-ownership-and-version-governance.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "NetKingdom IAM Profile Ownership And Version Governance" + }, + { + "canonical_path": "adr/netkingdom-playbook-capability-ownership/v1/index.html", + "currency": "current", + "id": "NK-ADR-0012", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-playbook-capability-ownership/v1/revisions/1/index.html", + "source_digest": "e269fabfc376f97f2a03ea66e34059d54016a445a7f30b351a6774b65c96c2ee", + "source_path": "docs/adr/ADR-0012-playbook-capability-contract-ownership.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "Playbook Capability Contract Ownership" + }, + { + "canonical_path": "adr/netkingdom-tenant-onboarding-taxonomy/v1/index.html", + "currency": "current", + "id": "NK-ADR-0013", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "2", + "revision_path": "adr/netkingdom-tenant-onboarding-taxonomy/v1/revisions/2/index.html", + "source_digest": "3a6030a8958176a902942ffd29154104ba3441a09d06edf3deaeadc7291ee0d4", + "source_path": "docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "Tenant Onboarding Grouping Taxonomy" + }, + { + "canonical_path": "adr/netkingdom-tenant-capability-ownership/v1/index.html", + "currency": "current", + "id": "NK-ADR-0014", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-tenant-capability-ownership/v1/revisions/1/index.html", + "source_digest": "843f7a65f0fc145d08a73e364bc9a1dee0f7b8af934abf1584ee39dffa903ee0", + "source_path": "docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "Tenant Capability Roles, Carrying Mechanism, and Tenant-Engine Ownership" + }, + { + "canonical_path": "adr/netkingdom-railiance-workload-packaging/v1/index.html", + "currency": "current", + "id": "NK-ADR-0015", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-railiance-workload-packaging/v1/revisions/1/index.html", + "source_digest": "2000985ef211aeedd3656e15cedb289e655526c2dcc4a161a64ffc27f0289db1", + "source_path": "docs/adr/ADR-0015-netkingdom-railiance-workload-packaging-and-relational-platform.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "NetKingdom Railiance Workload Packaging and Relational Platform" + }, + { + "canonical_path": "adr/railiance-k3s-api-tunnel-only/v1/index.html", + "currency": "current", + "id": "RINFRA-ADR-0005", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "railiance-infra", + "review_due": "2027-02-22", + "revision": "accepted-1", + "revision_path": "adr/railiance-k3s-api-tunnel-only/v1/revisions/accepted-1/index.html", + "source_digest": "4d93643d00d19b9d7c46843cd9be674c24e4cd613dc8e780c1684d97a6a9030a", + "source_path": "docs/adr/ADR-005-k3s-api-tunnel-only.md", + "source_repo": "railiance-infra", + "source_revision": "f3e8bf3ab4b1dafa4d0b251b88ab2ef23a30c37b", + "status": "accepted", + "title": "k3s API is tunnel-only" + }, + { + "canonical_path": "adr/activity-core-glas-profile-execution/v1/index.html", + "currency": "current", + "id": "ACT-ADR-006", + "last_reviewed": "2026-08-21", + "lifecycle": "active", + "owner": "activity-core", + "review_due": "2027-02-21", + "revision": "accepted-1", + "revision_path": "adr/activity-core-glas-profile-execution/v1/revisions/accepted-1/index.html", + "source_digest": "88041e9eb3a0f91fdd9da46f9813d7304b5ba58e62f9d28ea8f63c0a7b055b63", + "source_path": "docs/adr/adr-006-glas-profile-execution.md", + "source_repo": "activity-core", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "status": "accepted", + "title": "Profile-driven execution selection over the ops_run pull queue" + }, + { + "canonical_path": "adr/activity-core-bounded-operations/v1/index.html", + "currency": "current", + "id": "ACT-ADR-007", + "last_reviewed": "2026-08-23", + "lifecycle": "active", + "owner": "activity-core", + "review_due": "2027-02-23", + "revision": "accepted-1", + "revision_path": "adr/activity-core-bounded-operations/v1/revisions/accepted-1/index.html", + "source_digest": "75f42047b51293f7240f8a8c3ec02bf025a47fe6627f106fc4ef22fb1be7459d", + "source_path": "docs/adr/adr-007-bounded-operations.md", + "source_repo": "activity-core", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "status": "accepted", + "title": "Code-registered bounded operations are the only local mutation exception" + }, + { + "canonical_path": "adr/ops-warden-build-stage-credential-disclosure/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0007", + "last_reviewed": "2026-08-19", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2027-02-19", + "revision": "1", + "revision_path": "adr/ops-warden-build-stage-credential-disclosure/v1/revisions/1/index.html", + "source_digest": "868f953688988b11ce48f4141833bbca51abdb4e86d5b495a8a905002830d7b0", + "source_path": "docs/adr/ADR-0007-build-stage-stops-at-credential-disclosure.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0007 \u2014 Build-stage permissiveness stops at credential disclosure" + }, + { + "canonical_path": "adr/ops-warden-grade-disclosure-path/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0008", + "last_reviewed": "2026-08-21", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2027-02-21", + "revision": "1", + "revision_path": "adr/ops-warden-grade-disclosure-path/v1/revisions/1/index.html", + "source_digest": "2e22863ba592802cceb40b32d395168b42ace747741f5188307505eaa89ff764", + "source_path": "docs/adr/ADR-0008-grade-the-path-not-the-field.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0008 \u2014 A lane's risk grade covers every field its path discloses" + }, + { + "canonical_path": "adr/ops-warden-security-zones-consumer/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0009", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2026-11-22", + "revision": "1", + "revision_path": "adr/ops-warden-security-zones-consumer/v1/revisions/1/index.html", + "source_digest": "73fff22177b4bec2c56ff737e06e4225eb02d39669be3ea1b723906245f878b8", + "source_path": "docs/adr/ADR-0009-adopt-security-zones-as-a-consumer.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0009 \u2014 Adopt security-zones v0.1 as a consumer" + }, + { + "canonical_path": "adr/ops-warden-staff-layer/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0010", + "last_reviewed": "2026-08-28", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2026-11-28", + "revision": "1", + "revision_path": "adr/ops-warden-staff-layer/v1/revisions/1/index.html", + "source_digest": "064455bcb2870abf8e243f5a8c154e50bfc1c537cfba7996a792f9e314dd78ae", + "source_path": "docs/adr/ADR-0010-ops-warden-is-staff.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0010 \u2014 ops-warden is Staff: lanes, not rules, and one declared engine gap" + }, + { + "canonical_path": "adr/railiance-netkingdom-security-layer-interaction/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0009", + "last_reviewed": "2026-08-29", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-02-28", + "revision": "accepted-1", + "revision_path": "adr/railiance-netkingdom-security-layer-interaction/v1/revisions/accepted-1/index.html", + "source_digest": "c2f81c9718715fc08ea7a2e3021d0d3159958ace23b60a16430020637b9f57f2", + "source_path": "docs/adr/ADR-0009-netkingdom-security-layer-interaction.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "NetKingdom Security-Layer Interaction Boundary" + }, + { + "canonical_path": "adr/custodian-projection-source-overlay/v1/index.html", + "currency": "current", + "id": "CUST-ADR-012", + "last_reviewed": "2026-08-25", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-25", + "revision": "1.0", + "revision_path": "adr/custodian-projection-source-overlay/v1/revisions/1.0/index.html", + "source_digest": "b5e8582459f546ae789ad5fd62f458454aa19997b520e32b6b9f792d6af55987", + "source_path": "canon/architecture/adr-012-projection-source-and-preliminary-overlay.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "What the Hub Projects: Forge as Projection Source, Working Copies as Preliminary Overlay" + }, + { + "canonical_path": "standards/posture-feedback/v0.1/index.html", + "currency": "current", + "id": "netkingdom-posture-feedback-v0.1", + "last_reviewed": "2026-08-23", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2026-11-23", + "revision": "0.1", + "revision_path": "standards/posture-feedback/v0.1/revisions/0.1/index.html", + "source_digest": "dd2628b9f0c2a662ac44af22657d91918da228f307ebc7ceb09fc856162985db", + "source_path": "canon/standards/posture-feedback_v0.1.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "proposed", + "title": "NetKingdom Posture Feedback v0.1" + }, + { + "canonical_path": "standards/security-layer-model/v0.7/index.html", + "currency": "current", + "id": "netkingdom-security-layer-model-v0.7", + "last_reviewed": "2026-08-28", + "lifecycle": "active", + "owner": "gate-house", + "review_due": "2026-11-28", + "revision": "0.7", + "revision_path": "standards/security-layer-model/v0.7/revisions/0.7/index.html", + "source_digest": "8155e7b123be1e84377d8278525b1bad8961007b6bb8ff012dd01ba24ff8065b", + "source_path": "canon/standards/security-layer-model_v0.7.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "NetKingdom Security Layer Model v0.7" + }, + { + "canonical_path": "standards/security-scenario-composition/v0.1/index.html", + "currency": "current", + "id": "netkingdom-security-scenario-composition-v0.1", + "last_reviewed": "2026-08-23", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2026-11-23", + "revision": "0.1", + "revision_path": "standards/security-scenario-composition/v0.1/revisions/0.1/index.html", + "source_digest": "17b715d78e04470a0f83b8bc8c7313ab16e13e9e741d5ec445172d9008fce937", + "source_path": "canon/standards/security-scenario-composition_v0.1.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "proposed", + "title": "NetKingdom Security Scenario Composition v0.1" + }, + { + "canonical_path": "standards/security-zones/v0.1/index.html", + "currency": "current", + "id": "netkingdom-security-zones-v0.1", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "zone-engine", + "review_due": "2026-11-22", + "revision": "0.1", + "revision_path": "standards/security-zones/v0.1/revisions/0.1/index.html", + "source_digest": "32e71e9c0d6946bb14099eb66193822da26f199de0e11d8488464207f3bd9906", + "source_path": "canon/standards/security-zones_v0.1.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "proposed", + "title": "NetKingdom Security Zones v0.1" + }, + { + "canonical_path": "findings/flex-auth-unauthenticated-check/v1/index.html", + "currency": "current", + "id": "RISK-F-0001", + "last_reviewed": "2026-08-20", + "lifecycle": "active", + "owner": "risk-nexus", + "review_due": "2027-02-20", + "revision": "published-1", + "revision_path": "findings/flex-auth-unauthenticated-check/v1/revisions/published-1/index.html", + "source_digest": "b2d1d0c526d5c9b5729b8353877bb9f29096667145f1498cfde73c9711206fda", + "source_path": "findings/RISK-F-0001-flex-auth-unauthenticated-check.md", + "source_repo": "risk-nexus", + "source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "status": "fixed", + "title": "flex-auth /v1/check authenticates no caller" + }, + { + "canonical_path": "findings/audit-retention-legal-basis/v1/index.html", + "currency": "current", + "id": "RISK-F-0008", + "last_reviewed": "2026-08-20", + "lifecycle": "active", + "owner": "risk-nexus", + "review_due": "2027-02-20", + "revision": "published-1", + "revision_path": "findings/audit-retention-legal-basis/v1/revisions/published-1/index.html", + "source_digest": "8bf47414a9f958afb57649f087a2617f91a8f2aeaa4f34d04d8fb3a0f453e840", + "source_path": "findings/RISK-F-0008-audit-retention-legal-basis-assumed.md", + "source_repo": "risk-nexus", + "source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "status": "accepted", + "title": "The legal basis for retaining audit facts against an erasure request has been assumed, never established" + }, + { + "canonical_path": "methods/risk-severity/v1/index.html", + "currency": "current", + "id": "RISK-METHOD-SEVERITY", + "last_reviewed": "2026-08-20", + "lifecycle": "active", + "owner": "risk-nexus", + "review_due": "2027-02-20", + "revision": "adopted-1", + "revision_path": "methods/risk-severity/v1/revisions/adopted-1/index.html", + "source_digest": "f064c591aeef9425bbfbd5fa2aae659abb0ef83b8586848300077f41dd4ffaab", + "source_path": "docs/method/severity.md", + "source_repo": "risk-nexus", + "source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "status": "adopted", + "title": "Severity: how bad, how likely, and what is not a risk at all" + }, + { + "canonical_path": "methods/risk-disclosure/v1/index.html", + "currency": "current", + "id": "RISK-METHOD-DISCLOSURE", + "last_reviewed": "2026-08-20", + "lifecycle": "active", + "owner": "risk-nexus", + "review_due": "2027-02-20", + "revision": "adopted-1", + "revision_path": "methods/risk-disclosure/v1/revisions/adopted-1/index.html", + "source_digest": "bd825a87736c8fb019d4224c3ef4fe3882b8f3028035225b4b8ff8cad62fdb81", + "source_path": "docs/method/disclosure.md", + "source_repo": "risk-nexus", + "source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "status": "adopted", + "title": "Disclosure: publish now, hold, or restrict" + }, + { + "canonical_path": "methods/risk-review/v1/index.html", + "currency": "current", + "id": "RISK-METHOD-REVIEW", + "last_reviewed": "2026-08-20", + "lifecycle": "active", + "owner": "risk-nexus", + "review_due": "2027-02-20", + "revision": "adopted-1", + "revision_path": "methods/risk-review/v1/revisions/adopted-1/index.html", + "source_digest": "8f873d105bf6bd711a1a15ebd739b2abe97365b4e854be9d1c82e3ff3b162cfd", + "source_path": "docs/method/review.md", + "source_repo": "risk-nexus", + "source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "status": "adopted", + "title": "Review and expiry: what happens when nobody looks" + }, + { + "canonical_path": "methods/risk-verification/v1/index.html", + "currency": "current", + "id": "RISK-METHOD-VERIFICATION", + "last_reviewed": "2026-08-20", + "lifecycle": "active", + "owner": "risk-nexus", + "review_due": "2027-02-20", + "revision": "adopted-1", + "revision_path": "methods/risk-verification/v1/revisions/adopted-1/index.html", + "source_digest": "562b6eb3031ffe35e712c5e11e35aae23ce84a90e0090438dc89513f9685052b", + "source_path": "docs/method/verification.md", + "source_repo": "risk-nexus", + "source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "status": "adopted", + "title": "What this register may verify for itself, and what it must take from owners" + }, + { + "canonical_path": "methods/risk-dependencies/v1/index.html", + "currency": "current", + "id": "RISK-METHOD-DEPENDENCIES", + "last_reviewed": "2026-08-20", + "lifecycle": "active", + "owner": "risk-nexus", + "review_due": "2027-02-20", + "revision": "adopted-1", + "revision_path": "methods/risk-dependencies/v1/revisions/adopted-1/index.html", + "source_digest": "a5e7b42043b0bb366add9bbc5f606e134ef5b0454532480203d5d87099296240", + "source_path": "docs/method/dependencies.md", + "source_repo": "risk-nexus", + "source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "status": "adopted", + "title": "Waiting: how this register depends on other people without becoming a queue" + } + ], + "generated_as_of": "2026-08-31", + "schema_version": 1 +} diff --git a/docs/evidence/2026-10-11-hosting-preparation/source-exports.json b/docs/evidence/2026-10-11-hosting-preparation/source-exports.json new file mode 100644 index 0000000..09f8bfb --- /dev/null +++ b/docs/evidence/2026-10-11-hosting-preparation/source-exports.json @@ -0,0 +1,555 @@ +[ + { + "repo": "net-kingdom", + "path": "canon/standards/iam-profile_v0.3.md", + "sha256": "6c47b596ea145fa197c9665081c84902482d90f5bb94d185c1223af65279be1d", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "canon/standards/posture-feedback_v0.1.md", + "sha256": "dd2628b9f0c2a662ac44af22657d91918da228f307ebc7ceb09fc856162985db", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "canon/standards/security-layer-model_v0.7.md", + "sha256": "8155e7b123be1e84377d8278525b1bad8961007b6bb8ff012dd01ba24ff8065b", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "canon/standards/security-layer-model_v0.8.md", + "sha256": "3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "canon/standards/security-scenario-composition_v0.1.md", + "sha256": "17b715d78e04470a0f83b8bc8c7313ab16e13e9e741d5ec445172d9008fce937", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "canon/standards/security-zones_v0.1.md", + "sha256": "32e71e9c0d6946bb14099eb66193822da26f199de0e11d8488464207f3bd9906", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "canon/standards/tenancy-posture_v0.1.md", + "sha256": "38e7500a487cf65a5c7cf479852e4fa2cbcf0915e6d7218bcbaa96dae1f2e6ce", + "kind": "reviewed-stage", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md", + "sha256": "e92a43649bb6e14e53ec62ecc819405bf3a44bda9557487f7177402f107bbd13", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "docs/adr/ADR-0007-security-orchestration-boundary.md", + "sha256": "b4fcff8448f07aca1fcb6908618bdb19f6c0e7dce25d4c5c8b85eec2f175233b", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "docs/adr/ADR-0008-object-storage-sts-credential-vending.md", + "sha256": "f47276f4953f62b783397ee7fb1d3693da060103247e425a9a5b40d019fb4272", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "docs/adr/ADR-0010-orchestration-vs-dependency-self-coherent-intent.md", + "sha256": "b7c4f6a13f2f5add08bd03cb39c4f18ca25b202747dde883a309d1b3eaa1f571", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "docs/adr/ADR-0011-iam-profile-ownership-and-version-governance.md", + "sha256": "b5c7fd1e78026063b4a2ca4017202512d4f94ab5e12dc8498a34d04d3a48c7a9", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "docs/adr/ADR-0012-playbook-capability-contract-ownership.md", + "sha256": "e269fabfc376f97f2a03ea66e34059d54016a445a7f30b351a6774b65c96c2ee", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md", + "sha256": "3a6030a8958176a902942ffd29154104ba3441a09d06edf3deaeadc7291ee0d4", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md", + "sha256": "843f7a65f0fc145d08a73e364bc9a1dee0f7b8af934abf1584ee39dffa903ee0", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "docs/adr/ADR-0015-netkingdom-railiance-workload-packaging-and-relational-platform.md", + "sha256": "2000985ef211aeedd3656e15cedb289e655526c2dcc4a161a64ffc27f0289db1", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "docs/architecture/net-kingdom_v0.1.md", + "sha256": "db86e509552e92e3e235799dabe08a0c93506e7ef8402cbfbae01b7a1f526d81", + "kind": "reviewed-stage", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "docs/evidence/2026-10-11-layer-v07-immutable-publication/security-layer-model-v0.7.html", + "sha256": "7d7a38a8c3b9fff65b46280fcd5723eff77dc9d0784f871bb35f718bc4e8c0e7", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "net-kingdom", + "path": "docs/evidence/2026-10-11-layer-v07-immutable-publication/security-layer-model-v0.7.md", + "sha256": "8155e7b123be1e84377d8278525b1bad8961007b6bb8ff012dd01ba24ff8065b", + "kind": "clean-head", + "source_head": "f4436dadd5adbedb8b74114a76e8c8ab8ae91b1d" + }, + { + "repo": "policy-nexus", + "path": "docs/adr/ADR-0001-addressing-and-permanence.md", + "sha256": "a28668fb4b8b6c5ec8c94baac000061276d85ef1849ec7ab8d132b913dbfe3be", + "kind": "clean-head", + "source_head": "6d2c7eef779c823731e6f793b3adcf44afddd183" + }, + { + "repo": "policy-nexus", + "path": "docs/architecture/policy-nexus_v0.1.md", + "sha256": "179cbb86bca95f71f46f51ca1971ff1c274eed7d900adf0672b537d4bcc5b480", + "kind": "clean-head", + "source_head": "6d2c7eef779c823731e6f793b3adcf44afddd183" + }, + { + "repo": "railiance-master", + "path": "docs/adr/ADR-0001-repository-prefix-architecture.md", + "sha256": "b9c993ded8d79d6f871dba9cf08a320b2d619609a448ad3d02b632f5b6f76497", + "kind": "clean-head", + "source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb" + }, + { + "repo": "railiance-master", + "path": "docs/adr/ADR-0002-rail-kubernetes-wave-1-boundary.md", + "sha256": "7e1fc5aedd7294192d8702a22b9f205e5bae20793836fdc48c0c071d10d7ab9d", + "kind": "clean-head", + "source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb" + }, + { + "repo": "railiance-master", + "path": "docs/adr/ADR-0003-rapp-first-wave-selection.md", + "sha256": "28135e94758b6935518d2e83458c1e607deeabb341879b605eef6529b3168cbc", + "kind": "clean-head", + "source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb" + }, + { + "repo": "railiance-master", + "path": "docs/adr/ADR-0004-first-wave-reef-rollout.md", + "sha256": "36ec3aad5082ceff685d66e091c0a24b52abfc65b36595d00dce2b52a7250f4e", + "kind": "clean-head", + "source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb" + }, + { + "repo": "railiance-master", + "path": "docs/adr/ADR-0005-derived-rail-composition.md", + "sha256": "e02982ce54691cf1589ac3d04012b9b9282f9eb54ed4b8f2f0544371f9b87f3e", + "kind": "clean-head", + "source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb" + }, + { + "repo": "railiance-master", + "path": "docs/adr/ADR-0006-reef-production-admission.md", + "sha256": "5d0454384e1a3f31939aaa4e3d4225ede73c3669a2d86b15efb4b56803e01690", + "kind": "clean-head", + "source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb" + }, + { + "repo": "railiance-master", + "path": "docs/adr/ADR-0007-rapp-declaration-contract.md", + "sha256": "b1185388ded53188dc624fbda9bee517dd3ac80b35e881cd4a7962531bec52ff", + "kind": "clean-head", + "source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb" + }, + { + "repo": "railiance-master", + "path": "docs/adr/ADR-0008-private-by-default-exposure.md", + "sha256": "bd97ec8ee663bb416fae53c8cfe29c7066e4fd3a51a5bc364895297e6eb994b7", + "kind": "clean-head", + "source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb" + }, + { + "repo": "railiance-master", + "path": "docs/adr/ADR-0009-netkingdom-security-layer-interaction.md", + "sha256": "c2f81c9718715fc08ea7a2e3021d0d3159958ace23b60a16430020637b9f57f2", + "kind": "clean-head", + "source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb" + }, + { + "repo": "railiance-master", + "path": "docs/architecture/railiance_v0.1.md", + "sha256": "77de319978dfad02f40658bbeb96db46b246a46f1abd6a45f5b1269563de5568", + "kind": "clean-head", + "source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb" + }, + { + "repo": "state-hub", + "path": "docs/architecture/state-hub_v0.1.md", + "sha256": "e7104b2182612efe90c9a12168757506402490229814d454e94917ae62d2bfac", + "kind": "clean-head", + "source_head": "cccc27ce44928e579627aa86ccee38ae4fb1ca10" + }, + { + "repo": "the-custodian", + "path": "canon/architecture/adr-001-workplans-as-repo-artefacts.md", + "sha256": "183023ee57bae9c29e726fec5ee0361633fe3a2b182436a57869ae4b2a27af24", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/architecture/adr-002-custodian-agent-runtime-design.md", + "sha256": "b860b02b1e8f47b914d41c6ecf67f451e86326ed51c7c53dcc6b7870fc889e42", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/architecture/adr-003-materialized-derived-state.md", + "sha256": "fcb49719e2b85b9120c0bd5bebd5e82748ca713f16b44951c028b60205514e2b", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/architecture/adr-004-connectivity-first-network-posture.md", + "sha256": "4f9978b40c104600c0ccb981a8ee52376a5d3003da4786752029af524b7dede7", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/architecture/adr-005-cross-repo-workplans-project-repos.md", + "sha256": "13195a721d0e579715f5f39ca6f72b5e49c583611e6ca089e6d4618708ae917f", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/architecture/adr-006-canon-federation-concept-ownership.md", + "sha256": "a454df0e1d227f99ebb36c4abd45c76cc12579086d34f0c0ccfccfd7f4790823", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/architecture/adr-007-workplan-identity-and-repo-worker-topology.md", + "sha256": "1169f0c1f485a2bb7241a8f64f3167c060c04f83341b12586fd9be5c2b3693f8", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/architecture/adr-010-hub-authority-and-local-cache-model.md", + "sha256": "5979da20799118259fc19246f51e8cc8f2c0c1be3d414318bd51d5a27d9ad565", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/architecture/adr-011-federated-namespaces-and-reconciliation-limits.md", + "sha256": "f94f429c72f6cfd01ee83f1e5689d2d10ae52d7588d7cbd3ca40aca7eef46fb0", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/architecture/adr-012-projection-source-and-preliminary-overlay.md", + "sha256": "b5e8582459f546ae789ad5fd62f458454aa19997b520e32b6b9f792d6af55987", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/architecture/coulomb-estate_v0.1.md", + "sha256": "3b4f93acdedc6f6d1a1c737c3327d9d55061506c5a879be09e6b0a2ea7f5f9bc", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/standards/autonomy-lanes_v0.1.md", + "sha256": "106cb0c99f73983199c9f1d8491143aa2a2eb41ee450a02ec7968d3763f30399", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/standards/contribution-convention_v0.1.md", + "sha256": "6dfc1d7b2b82ab18228a84660c1c7dff607aad1d9da37ac12084cdd2ab8ae112", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/standards/project-repository-flavor_v0.1.md", + "sha256": "61889b2f18a82a08b66ef90d758efe5c028a4f52aced1721a23e69c6f24a8224", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/standards/work-record-types_v0.1.md", + "sha256": "ab5d9de07fd899fc5334c9912571ed3d6748f2ee4bd0c9f81c5606c4727024d1", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "canon/standards/workplan-terminology-fleet_v0.1.md", + "sha256": "431d36f40d90bcd40a640c16b583095ad35757675f78ddcea382e6f1b2e23710", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "docs/evidence/2026-10-11-adr-currency-review/adr-002-accepted-1.html", + "sha256": "ea4eae05b62d50cd8204a47c1a4f3e3e0beda571880ed1de93ccbb17987d84aa", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "docs/evidence/2026-10-11-adr-currency-review/adr-002-accepted-1.md", + "sha256": "6aef66cd5cf71a48f5b4e14401dc19a755e2b182445b272d5952df0eb0dea8ec", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "docs/evidence/2026-10-11-adr-currency-review/adr-004-accepted-1.html", + "sha256": "3db1b0f5c50543ff0fb022ea67f1d85b30f8ff691eaa1a2443220b7474b54de7", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "the-custodian", + "path": "docs/evidence/2026-10-11-adr-currency-review/adr-004-accepted-1.md", + "sha256": "3b68adfa6ab329e73f857cf691dc405136d2d66a0135e2c37c236aabe4659557", + "kind": "clean-head", + "source_head": "dcdec162d61398f0def4990957455d45a74a1b01" + }, + { + "repo": "activity-core", + "path": "docs/adr/adr-001-event-bridge-architecture.md", + "sha256": "57ecf490465e71cc4970133301cc315748b1d6ee65caac3690472ff88f3aab06", + "kind": "clean-head", + "source_head": "fd84f2b6518ff17509930499099ec2a6692d088c" + }, + { + "repo": "activity-core", + "path": "docs/adr/adr-002-definition-format.md", + "sha256": "1670e1616094bc6f2c0c7d47b2e019c02d5870b22b564ef83afa24caff197bab", + "kind": "clean-head", + "source_head": "fd84f2b6518ff17509930499099ec2a6692d088c" + }, + { + "repo": "activity-core", + "path": "docs/adr/adr-003-rule-instruction-model.md", + "sha256": "f1017730074727bcb0c68309c445ffb219ef647c8c5604adbfff76c4aa3f9d66", + "kind": "clean-head", + "source_head": "fd84f2b6518ff17509930499099ec2a6692d088c" + }, + { + "repo": "activity-core", + "path": "docs/adr/adr-004-producer-trust-boundary.md", + "sha256": "92da58f58ee3dbb62233161a0fc0b7780920f81ec37fdd40d476be354f24e0eb", + "kind": "clean-head", + "source_head": "fd84f2b6518ff17509930499099ec2a6692d088c" + }, + { + "repo": "activity-core", + "path": "docs/adr/adr-005-ops-runs-vs-dev-work-records.md", + "sha256": "984208262a0f6b67a6dc94cb078ebd0bcddc95d0b21a694d7e207677c10b471f", + "kind": "clean-head", + "source_head": "fd84f2b6518ff17509930499099ec2a6692d088c" + }, + { + "repo": "activity-core", + "path": "docs/adr/adr-006-glas-profile-execution.md", + "sha256": "88041e9eb3a0f91fdd9da46f9813d7304b5ba58e62f9d28ea8f63c0a7b055b63", + "kind": "clean-head", + "source_head": "fd84f2b6518ff17509930499099ec2a6692d088c" + }, + { + "repo": "activity-core", + "path": "docs/adr/adr-007-bounded-operations.md", + "sha256": "75f42047b51293f7240f8a8c3ec02bf025a47fe6627f106fc4ef22fb1be7459d", + "kind": "clean-head", + "source_head": "fd84f2b6518ff17509930499099ec2a6692d088c" + }, + { + "repo": "ops-warden", + "path": "docs/adr/ADR-0001-catalog-is-a-pointer-layer.md", + "sha256": "7df0bb364276e382cbee9383e7e67d399b0ac1b0353246e0ab323e629e732a6d", + "kind": "clean-head", + "source_head": "eaabb4216186f4fee2d830376a381740b140087c" + }, + { + "repo": "ops-warden", + "path": "docs/adr/ADR-0002-conduit-not-broker.md", + "sha256": "7dcc31732d774ddf2c98636b69ee12e2d74034836ed0def81b7e06461309b53a", + "kind": "clean-head", + "source_head": "eaabb4216186f4fee2d830376a381740b140087c" + }, + { + "repo": "ops-warden", + "path": "docs/adr/ADR-0003-cover-gaps-never-silently-own-them.md", + "sha256": "45b47c02afa575fbfe5be980e426c331a1d99bb9cd9c7a8de80bf8e319469f81", + "kind": "clean-head", + "source_head": "eaabb4216186f4fee2d830376a381740b140087c" + }, + { + "repo": "ops-warden", + "path": "docs/adr/ADR-0004-agent-read-boundary-on-high-risk-lanes.md", + "sha256": "e379f47fb56214ff0cc6abcddaaad33c6969cf386e11abeed105b4fef953c5a0", + "kind": "clean-head", + "source_head": "eaabb4216186f4fee2d830376a381740b140087c" + }, + { + "repo": "ops-warden", + "path": "docs/adr/ADR-0005-implement-narrowly-route-broadly.md", + "sha256": "31eafe4d8d9362a3446739d63c3af83fd9138cfdc6ad120086312a67dc0d27d0", + "kind": "clean-head", + "source_head": "eaabb4216186f4fee2d830376a381740b140087c" + }, + { + "repo": "ops-warden", + "path": "docs/adr/ADR-0007-build-stage-stops-at-credential-disclosure.md", + "sha256": "868f953688988b11ce48f4141833bbca51abdb4e86d5b495a8a905002830d7b0", + "kind": "clean-head", + "source_head": "eaabb4216186f4fee2d830376a381740b140087c" + }, + { + "repo": "ops-warden", + "path": "docs/adr/ADR-0008-grade-the-path-not-the-field.md", + "sha256": "2e22863ba592802cceb40b32d395168b42ace747741f5188307505eaa89ff764", + "kind": "clean-head", + "source_head": "eaabb4216186f4fee2d830376a381740b140087c" + }, + { + "repo": "ops-warden", + "path": "docs/adr/ADR-0009-adopt-security-zones-as-a-consumer.md", + "sha256": "73fff22177b4bec2c56ff737e06e4225eb02d39669be3ea1b723906245f878b8", + "kind": "clean-head", + "source_head": "eaabb4216186f4fee2d830376a381740b140087c" + }, + { + "repo": "ops-warden", + "path": "docs/adr/ADR-0010-ops-warden-is-staff.md", + "sha256": "064455bcb2870abf8e243f5a8c154e50bfc1c537cfba7996a792f9e314dd78ae", + "kind": "clean-head", + "source_head": "eaabb4216186f4fee2d830376a381740b140087c" + }, + { + "repo": "railiance-platform", + "path": "docs/adr/ADR-0001-s3-platform-service-boundary.md", + "sha256": "63697581401b53a8437835c2bb8b40f8054cc40d0bc7a2972f83a4a10377f6ba", + "kind": "clean-head", + "source_head": "8c152d012a44664f51969f2cb3456e28a3a0e5ff" + }, + { + "repo": "railiance-platform", + "path": "docs/adr/ADR-0002-placement-policy-ownership.md", + "sha256": "cfc0ad202c2eeeefd963127ff1defa127c715c001ecc684ab8759733bd8fb9f8", + "kind": "clean-head", + "source_head": "8c152d012a44664f51969f2cb3456e28a3a0e5ff" + }, + { + "repo": "railiance-platform", + "path": "docs/adr/ADR-0003-decisions-live-in-the-repo.md", + "sha256": "9b12ab6aa0e6f9eba03465782c35d6ff4683b682191599e38f4f9614cde9fa1b", + "kind": "clean-head", + "source_head": "8c152d012a44664f51969f2cb3456e28a3a0e5ff" + }, + { + "repo": "railiance-infra", + "path": "docs/adr/ADR-005-k3s-api-tunnel-only.md", + "sha256": "4d93643d00d19b9d7c46843cd9be674c24e4cd613dc8e780c1684d97a6a9030a", + "kind": "clean-head", + "source_head": "ef64e324b61e9aa9b9ed03aad7dd2101915057ca" + }, + { + "repo": "risk-nexus", + "path": "docs/method/dependencies.md", + "sha256": "a5e7b42043b0bb366add9bbc5f606e134ef5b0454532480203d5d87099296240", + "kind": "clean-head", + "source_head": "7095f328e6639fd0033c90296ebc73e8acb76c9c" + }, + { + "repo": "risk-nexus", + "path": "docs/method/disclosure.md", + "sha256": "bd825a87736c8fb019d4224c3ef4fe3882b8f3028035225b4b8ff8cad62fdb81", + "kind": "clean-head", + "source_head": "7095f328e6639fd0033c90296ebc73e8acb76c9c" + }, + { + "repo": "risk-nexus", + "path": "docs/method/review.md", + "sha256": "8f873d105bf6bd711a1a15ebd739b2abe97365b4e854be9d1c82e3ff3b162cfd", + "kind": "clean-head", + "source_head": "7095f328e6639fd0033c90296ebc73e8acb76c9c" + }, + { + "repo": "risk-nexus", + "path": "docs/method/severity.md", + "sha256": "f064c591aeef9425bbfbd5fa2aae659abb0ef83b8586848300077f41dd4ffaab", + "kind": "clean-head", + "source_head": "7095f328e6639fd0033c90296ebc73e8acb76c9c" + }, + { + "repo": "risk-nexus", + "path": "docs/method/verification.md", + "sha256": "562b6eb3031ffe35e712c5e11e35aae23ce84a90e0090438dc89513f9685052b", + "kind": "clean-head", + "source_head": "7095f328e6639fd0033c90296ebc73e8acb76c9c" + }, + { + "repo": "risk-nexus", + "path": "findings/RISK-F-0001-flex-auth-unauthenticated-check.md", + "sha256": "b2d1d0c526d5c9b5729b8353877bb9f29096667145f1498cfde73c9711206fda", + "kind": "clean-head", + "source_head": "7095f328e6639fd0033c90296ebc73e8acb76c9c" + }, + { + "repo": "risk-nexus", + "path": "findings/RISK-F-0008-audit-retention-legal-basis-assumed.md", + "sha256": "b1b6ea241f82be48df427af915227dad2fe9a90b970b8a03fb4b39a0ab3a821c", + "kind": "reviewed-stage", + "source_head": "7095f328e6639fd0033c90296ebc73e8acb76c9c" + } +] diff --git a/docs/evidence/2026-10-11-hosting-preparation/transition-failure.txt b/docs/evidence/2026-10-11-hosting-preparation/transition-failure.txt new file mode 100644 index 0000000..64e2771 --- /dev/null +++ b/docs/evidence/2026-10-11-hosting-preparation/transition-failure.txt @@ -0,0 +1,76 @@ +RISK-F-0008: unreviewed semantic change +RMASTER-ADR-0006: unreviewed semantic change +canon-work-record-types: unreviewed semantic change +canon-workplan-terminology-fleet: unreviewed semantic change +net-kingdom-architecture: unreviewed semantic change +netkingdom-tenancy-posture: unreviewed semantic change +ops-warden-adr-0004: unreviewed semantic change +netkingdom-tenancy-posture: old immutable bytes changed +canon-autonomy-lanes: old immutable bytes changed +canon-contrib-convention: old immutable bytes changed +canon-project-repository-flavor: old immutable bytes changed +canon-work-record-types: old immutable revision absent or unsafe +canon-workplan-terminology-fleet: old immutable revision absent or unsafe +coulomb-estate-architecture: old immutable bytes changed +railiance-architecture: old immutable bytes changed +net-kingdom-architecture: old immutable bytes changed +state-hub-architecture: old immutable bytes changed +policy-nexus-architecture: old immutable bytes changed +policy-nexus-adr-0001: old immutable bytes changed +RMASTER-ADR-0001: old immutable bytes changed +RMASTER-ADR-0002: old immutable bytes changed +RMASTER-ADR-0003: old immutable bytes changed +RMASTER-ADR-0004: old immutable bytes changed +RMASTER-ADR-0005: old immutable bytes changed +RMASTER-ADR-0006: old immutable revision absent or unsafe +RMASTER-ADR-0007: old immutable bytes changed +RMASTER-ADR-0008: old immutable bytes changed +ACT-ADR-001: old immutable bytes changed +ACT-ADR-002: old immutable bytes changed +ACT-ADR-003: old immutable bytes changed +ACT-ADR-004: old immutable bytes changed +ACT-ADR-005: old immutable bytes changed +ops-warden-adr-0001: old immutable bytes changed +ops-warden-adr-0002: old immutable bytes changed +ops-warden-adr-0003: old immutable bytes changed +ops-warden-adr-0004: old immutable revision absent or unsafe +ops-warden-adr-0005: old immutable bytes changed +CUST-ADR-001: old immutable bytes changed +CUST-ADR-003: old immutable bytes changed +CUST-ADR-005: old immutable bytes changed +CUST-ADR-006: old immutable bytes changed +CUST-ADR-007: old immutable bytes changed +CUST-ADR-010: old immutable bytes changed +CUST-ADR-011: old immutable bytes changed +RPLAT-ADR-0001: old immutable bytes changed +RPLAT-ADR-0002: old immutable bytes changed +RPLAT-ADR-0003: old immutable bytes changed +netkingdom-iam-profile-v0.3: old immutable bytes changed +NK-ADR-0006: old immutable bytes changed +NK-ADR-0007: old immutable bytes changed +NK-ADR-0008: old immutable bytes changed +NK-ADR-0010: old immutable bytes changed +NK-ADR-0011: old immutable bytes changed +NK-ADR-0012: old immutable bytes changed +NK-ADR-0013: old immutable bytes changed +NK-ADR-0014: old immutable bytes changed +NK-ADR-0015: old immutable bytes changed +RINFRA-ADR-0005: old immutable bytes changed +ACT-ADR-006: old immutable bytes changed +ACT-ADR-007: old immutable bytes changed +ops-warden-adr-0007: old immutable bytes changed +ops-warden-adr-0008: old immutable bytes changed +ops-warden-adr-0009: old immutable bytes changed +ops-warden-adr-0010: old immutable bytes changed +RMASTER-ADR-0009: old immutable bytes changed +CUST-ADR-012: old immutable bytes changed +netkingdom-posture-feedback-v0.1: old immutable bytes changed +netkingdom-security-scenario-composition-v0.1: old immutable bytes changed +netkingdom-security-zones-v0.1: old immutable bytes changed +RISK-F-0001: old immutable bytes changed +RISK-F-0008: old immutable bytes changed +RISK-METHOD-SEVERITY: old immutable bytes changed +RISK-METHOD-DISCLOSURE: old immutable bytes changed +RISK-METHOD-REVIEW: old immutable bytes changed +RISK-METHOD-VERIFICATION: old immutable bytes changed +RISK-METHOD-DEPENDENCIES: old immutable bytes changed diff --git a/docs/evidence/2026-10-11-served-publication-history/README.md b/docs/evidence/2026-10-11-served-publication-history/README.md new file mode 100644 index 0000000..40b8239 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/README.md @@ -0,0 +1,9 @@ +# Observed publisher history, 2026-10-11 + +This is a publisher receipt of bytes actually delivered by policy.coulomb.social, not a replacement for owner canon or a new acceptance decision. All 72 current immutable revision URLs from the delivered publication manifest returned HTTP200. Their HTML is preserved under `pages/`; original owner Markdown is separately retained under `sources/`. The receipt binds both byte hashes, original owner repository/path, the exact source commit embedded in the served HTML, and delivered immutable URL/revision. Original Markdown was obtained using `git show :` for each record; every one of 72 source hashes matched both the served HTML and delivered manifest. + +The delivered manifest's current source revision may differ from immutable HTML's original source revision when a later owner HEAD renders the canonical URL. The receipt records both; it does not rewrite historical provenance to a current HEAD. The archived Markdown is historical evidence, not the current owner source selected for canonical publication. New builds verify receipt/manifest/source/HTML hashes, identity/revision and provenance, then restore exact old pages before rendering current canonical content. A changed owner source under the same revision refuses; a new current revision retains the old path. + +Existing NetKingdom v0.7 and Custodian ADR002/004 owner archival snapshots remain authoritative owner evidence and are retained. Their rendered bytes agree with the publisher receipt. No owner source decision, status, authorship, original acceptance date, enforcement grant or live hosting state is changed by importing this archive. + +Limit: this capture covers every current immutable URL in the delivered 72-document manifest. It does not assert that no earlier URL absent from that manifest has ever existed. Additional already served historical paths must be imported as additional reviewed receipts when identified; existing explicit owner historical revisions are preserved independently. This archive is publicly served policy content and exact original public source, not credentials or customer data. diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-001.html b/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-001.html new file mode 100644 index 0000000..c72f448 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-001.html @@ -0,0 +1,247 @@ + + + + +Activity-Core as Coulomb Org Event Bridge + +
ACT-ADR-001 accepted · accepted-2 activity-core reviewed 2026-05-14generated from canonical source — do not edit

Activity-Core as Coulomb Org Event Bridge

Source: activity-core · docs/adr/adr-001-event-bridge-architecture.md · b72fdb5452bff51a867a0316edb994723b35f268

Review due: 2026-11-14

Status

+

Accepted.

+
+

Context

+

The Coulomb organization's set of repositories, services, and deployments is growing beyond what a single person can coordinate manually. The state hub tracks cross-domain state but has no mechanism to automatically respond to it. Recurring maintenance (dependency scans, SBOM staleness checks, consistency audits) is implemented as bespoke cron jobs baked into individual services — scattered, hard to audit, and impossible to govern from a single vantage point.

+

Three forces drive the need for a dedicated orchestration layer:

+
  1. Scale: as the repo count grows, manual coordination becomes the bottleneck.
  2. Reactivity: org-level events (new repo registered, CVE published, deployment completed) should trigger coordinated responses without human intervention.
  3. Separation of concerns: the state hub is a read model and should remain one. It must not accumulate automation logic to avoid becoming a God object.
+
+

Decision

+

activity-core is the org-wide Event Bridge for the Coulomb organization.

+

Its responsibility is exactly three things:

+
  1. Receive events — time-based (cron, one-off scheduled) and domain events (NATS, Gitea webhooks, state hub lifecycle signals).
  2. Evaluate rules and instructions — given event payload and resolved context, determine what work must be created.
  3. Emit task sets — publish structured task creation requests to issue-core.
+

It does not execute work. It does not track task lifecycle. It does not manage projects.

+

Boundary rules

+
ConcernOwner
Cross-org task scheduling and reactive automationactivity-core
Task lifecycle (create, assign, track, close)issue-core
Project and initiative management (phased, completion-gated)project-core (future)
Repository capability profilingrepo-scoping
Cross-domain coordination statestate hub
Execution of automatable tasksTemporal workers (per-repo)
+

Event type registry

+

Event types are declared by publishers as markdown definition files (see ACT-ADR-002). Governance is publisher-declared by default: a publisher registers its event types by committing definition files to the event-types registry. In production environments, a curator gate can be enabled — registry entries must be reviewed before the runtime accepts events of that type. This is a configuration flag per runtime scope (dev, staging, prod), not a hard-coded rule.

+

State hub relationship

+

The state hub delegates automation to activity-core rather than implementing it internally. Concretely:

+
  • Maintenance jobs currently baked into the state hub (consistency sync, SBOM staleness checks) are migrated to ActivityDefinitions in activity-core.
  • The state hub becomes a publisher of lifecycle events on NATS (org.workstream.created, org.decision.resolved, org.repo.registered, etc.).
  • The state hub does not subscribe to activity-core's output directly; it reads task state from issue-core when needed.
+

This preserves the state hub as a read model and makes activity-core the single home for automation policy.

+

rules-core: module-first

+

The rule and instruction evaluation engine starts as src/activity_core/rules/ — a module with a clean internal boundary (no imports from Temporal, Postgres, or FastAPI within the module). Extraction to a standalone rules-core repository happens when a second consumer (e.g. state hub governance, project-core) needs the engine. This follows the same discipline as the task-flow-engine extraction plan (CUST-TFE-SCOPE).

+

NATS as org infrastructure

+

NATS JetStream is promoted from an activity-core internal component to org-wide event bus infrastructure. It runs as a standalone service (not bundled in activity-core's docker-compose) with its own lifecycle. All services that publish or subscribe to org events do so via NATS streams.

+

issue-core integration

+

activity-core communicates with issue-core via a task emission adapter — an abstraction layer that, in the initial implementation, calls issue-core's REST API. The adapter interface is defined now; the transport can migrate to NATS subscription (issue-core subscribes to task.spawned events) once issue-core adds that capability. This avoids hardcoding REST coupling throughout the codebase.

+

Webhook receiver

+

A new HTTP endpoint within activity-core accepts inbound webhooks from Gitea (and later GitHub, other services). It normalises payloads to the canonical EventEnvelope format, validates against the event type registry, and publishes to NATS. This runs alongside the existing FastAPI api.py.

+

Domain assignment

+

activity-core and issue-core are assigned to the capabilities domain — the same domain as repo-scoping. These are org-wide infrastructure tools that serve all domains equally, not artefacts of any single project or custodian's personal workflow. issue-core is explicitly disassociated from the markitect domain.

+
+

Trigger types

+

Three trigger types are supported:

+
TypeDescriptionTemporal mechanism
cronRecurring schedule (5-field cron + timezone + misfire policy)Temporal Schedule (implemented WP-0002)
eventReact to a named event type on NATSTemporal workflow started by Event Router
scheduledOne-off at a future datetimeTemporal Schedule with remaining_actions: 1
+

scheduled is a new trigger type added in WP-0003.

+
+

Consequences

+

Immediate

+
  • activity-core's INTENT.md and SCOPE.md are rewritten to reflect this architecture.
  • task_spawn_log is the local spawn audit trail; authoritative work-item lifecycle state lives downstream.
  • A task emission adapter (src/activity_core/issue_sink.py) owns downstream creation. The unused TaskExecutorWorkflow and task_instances compatibility surface was retired by ACTIVITY-WP-0035.
+

Medium term

+
  • State hub adds NATS publishing to its lifecycle operations.
  • Gitea webhook receiver added to activity-core as a new HTTP router.
  • Existing state hub maintenance crons are migrated to ActivityDefinitions.
  • issue-facade is renamed issue-core and re-registered under the capabilities domain.
+

Long term

+
  • rules-core extracted as a standalone package when a second consumer appears.
  • project-core created (depends on task-flow-engine extraction) for multi-phase initiative management — explicitly out of scope for activity-core.
  • NATS gets its own operational runbook and monitoring as org infrastructure.
+
+

Alternatives Considered

+

State hub absorbs activity-core functionality: rejected — turns the state hub into a God object, violates the read-model boundary, and makes automation logic impossible to test independently.

+

Per-repo automation (GitHub Actions style): rejected — cross-repo coordination requires a single vantage point that can see all repos; per-repo actions can't express org-level triggers or context.

+

Activity-core as a thin Temporal wrapper only: rejected — without the event type registry and rule model, it's just a scheduler. The governance and introspection properties are the point.

+

Separate rules-core from day one: rejected — premature extraction adds dependency management overhead before a second consumer exists. Module-first with a clean boundary costs nothing and preserves the extraction option.

+
+
ACT-ADR-001 · accepted-2 · acceptedactivity-core · docs/adr/adr-001-event-bridge-architecture.md · b72fdb5452bff51a867a0316edb994723b35f268
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-002.html b/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-002.html new file mode 100644 index 0000000..8e0468b --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-002.html @@ -0,0 +1,437 @@ + + + + +Markdown-as-Definition Format for Event Types and ActivityDefinitions + +
ACT-ADR-002 accepted · accepted-2 activity-core reviewed 2026-05-14generated from canonical source — do not edit

Markdown-as-Definition Format for Event Types and ActivityDefinitions

Source: activity-core · docs/adr/adr-002-definition-format.md · b72fdb5452bff51a867a0316edb994723b35f268

Review due: 2026-11-14

Status

+

Accepted.

+
+

Context

+

Event type schemas and ActivityDefinition rules need to be understood and authored by three distinct audiences simultaneously: humans reviewing and debugging automation, agents creating and modifying definitions at runtime, and machines parsing and evaluating them. Traditional approaches split these concerns — schemas go in JSON Schema or YAML, documentation goes in a wiki, logic goes in code — and they drift apart. A bug in a rule requires cross-referencing three places to understand intent, check the schema, and read the condition.

+

The Custodian ecosystem already uses markdown files with YAML frontmatter as the authoritative format for workplans, ADRs, SCOPE.md, and INTENT.md — all understood by humans and agents without additional tooling. The same pattern should apply here.

+
+

Decision

+

Event type definitions and ActivityDefinitions are markdown files where machine- parseable structure (frontmatter YAML and fenced definition blocks) is embedded within human-readable narrative. Intent, schema, logic, and debugging notes live in one file.

+

Event Type Definition Files

+

Location: event-types/{namespace}.{event-name}.md within the activity-core repo (or a registered event-types registry repo if volumes justify separation).

+

Naming convention: {publisher-domain}.{noun}.{verb}.md, e.g.:

+
  • org.repo.registered.md
  • org.security.cve.published.md
  • org.workstream.completed.md
+

Structure:

+
---
+id: org.repo.registered
+type: event-type
+version: "1.0"
+publisher: the-custodian/state-hub
+governance: publisher-declared   # publisher-declared | curated
+status: active                   # active | deprecated | draft
+introduced: "2026-05-14"
+---
+
+# Event: org.repo.registered
+
+## Intent
+
+One-paragraph statement of why this event exists and what it signals.
+Written for an agent or human who has never seen it before.
+
+## When Published
+
+Bulleted list of the exact conditions under which the publisher fires this event.
+Be precise — ambiguity here causes missed or duplicate activations.
+
+## Attributes
+
+| Attribute | Type | Required | Description |
+|---|---|---|---|
+| `repo_slug` | string | yes | URL-safe repository identifier |
+| `domain` | string | yes | Domain slug the repo is assigned to |
+| `tags` | string[] | no | Capability tags set at registration time |
+| `registered_at` | datetime | yes | ISO 8601 UTC timestamp |
+
+## Example Payload
+
+​```json
+{
+  "id": "evt-7f3a1b2c",
+  "type": "org.repo.registered",
+  "version": "1.0",
+  "timestamp": "2026-05-14T10:00:00Z",
+  "publisher": "the-custodian/state-hub",
+  "attributes": {
+    "repo_slug": "new-python-service",
+    "domain": "railiance",
+    "tags": ["python-service", "fastapi"],
+    "registered_at": "2026-05-14T10:00:00Z"
+  }
+}
+​```
+
+## Consumer Notes
+
+Guidance for agents and humans writing rules against this event type:
+- Which attributes are safe for instruction prompts (trusted fields)
+- Common misuses or gotchas
+- Related events that are often used together
+
+## Debugging
+
+What to check when an activity that subscribes to this event does not fire:
+- How to verify the event was published (NATS subject, log entry)
+- How to inspect the event payload in the registry
+- Common schema validation failures
+

Attribute Types

+

The type system for event attributes is intentionally small:

+
TypeNotes
stringUTF-8 string
integer64-bit signed integer
float64-bit float
booleantrue / false
datetimeISO 8601 UTC string in payload, parsed to datetime in evaluator
uuidString in payload, validated as UUID v4
string[]JSON array of strings
integer[]JSON array of integers
objectFreeform JSON object — cannot be used in rule conditions; instruction-only
+

object type attributes are available to instructions but excluded from rule conditions deliberately — rules must be deterministic and schema-validatable.

+

ActivityDefinition Files

+

Location: activity-definitions/{slug}.md within the repo that owns the automation. For org-wide automations: activity-core/activity-definitions/. For domain-specific automations: {domain-repo}/activity-definitions/.

+

Structure:

+
---
+id: ACT-DEF-onboard-python-repo
+type: activity-definition
+version: "1.0"
+status: active
+trigger:
+  type: event                        # event | cron | scheduled
+  event_type: org.repo.registered    # for type: event
+  # cron: "0 9 * * 1"               # for type: cron (5-field, UTC)
+  # timezone: "Europe/Berlin"        # optional, cron only
+  # misfire_policy: skip             # skip | catchup | compress (cron only)
+  # at: "2026-06-01T09:00:00Z"      # for type: scheduled (one-off)
+context_sources:
+  - type: repo-scoping
+    query: repo_profile
+    bind_to: context.repo_profile
+  - type: state-hub
+    query: domain_summary
+    bind_to: context.domain_summary
+governance: publisher-declared
+owner: custodian-agent
+created: "2026-05-14"
+---
+
+# ActivityDefinition: Onboard New Python Service
+
+## Purpose
+
+One paragraph. What does this automation do and why does it exist? What problem
+would accumulate if this automation were turned off?
+
+## Trigger
+
+Which event type fires this activity, and under what conditions does it apply?
+Cross-reference the event type definition file.
+
+## Context Sources
+
+What context is resolved before rules are evaluated? Explain what each source
+provides and why it is needed.
+
+## Rules
+
+Each rule is a fenced block tagged `rule`. Rules are evaluated in order; all
+matching rules fire (not first-match-only). See ACT-ADR-003 for the expression
+language specification.
+
+​```rule
+id: create-sbom-scan
+condition: '"python-service" in event.attributes.tags'
+action:
+  task_template: tasks/sbom-initial-scan.md
+  target_repo: event.attributes.repo_slug
+  priority: high
+  labels: ["onboarding", "security"]
+​```
+
+​```rule
+id: create-scope-generation
+condition: '"python-service" in event.attributes.tags and context.repo_profile.scope_md_exists == false'
+action:
+  task_template: tasks/generate-scope-md.md
+  target_repo: event.attributes.repo_slug
+  priority: medium
+  labels: ["onboarding", "documentation"]
+​```
+
+## Instructions
+
+Instructions are evaluated after all rules. An instruction asks an LLM to decide
+what additional tasks (if any) to create. See ACT-ADR-003 for safety requirements.
+
+​```instruction
+id: domain-specific-onboarding
+condition: 'event.attributes.domain != "test_domain_v2"'
+trusted_fields:
+  - event.attributes.repo_slug
+  - event.attributes.domain
+  - event.attributes.tags
+model: claude-sonnet-4-6
+review_advisory: false
+prompt: |
+  A new repository has been registered in the Coulomb organization.
+
+  Repository: {event.attributes.repo_slug}
+  Domain: {event.attributes.domain}
+  Tags: {event.attributes.tags}
+
+  Based on the domain's current standards and the repository profile above,
+  determine what additional domain-specific onboarding tasks should be created
+  beyond the standard SBOM scan and SCOPE.md generation. Return an empty list
+  if no additional tasks are warranted.
+output_schema: tasks/task-template-list-schema.json
+​```
+
+## Task Templates
+
+References to task template files used in rule actions. Each template is a
+separate markdown file under `tasks/` that defines the task title, description
+template, default labels, and default assignee logic.
+
+- `tasks/sbom-initial-scan.md`
+- `tasks/generate-scope-md.md`
+
+## Notes
+
+Operational notes, edge cases, and context that does not fit elsewhere.
+
+## Debugging
+
+Checklist for when this ActivityDefinition fires but produces unexpected output:
+
+1. Was the triggering event published with the correct type and attributes?
+2. Do the rule conditions evaluate as expected? (Use `make eval-rule` with a fixture)
+3. Is issue-core reachable and configured for the target domain?
+4. For instructions: check the audit log for the model response and output validation result.
+
+## Change History
+
+- v1.0 (2026-05-14): Initial definition
+

Governance model

+

The governance field on an event type definition determines how the registry runtime handles it:

+
ValueBehaviour
publisher-declaredAccepted immediately on publish; no review required
curatedHeld in pending state until a curator approves via registry API
+

The runtime checks the environment's curator gate configuration — not just the file's governance field. An environment configured with curator_gate: disabled treats all event types as publisher-declared regardless of the field value. An environment with curator_gate: required treats all event types as curated regardless of the field value. The field is the publisher's declared preference; the environment config is the enforcement point.

+

This means:

+
  • Dev / integration: curator_gate: disabled — developers and agents iterate freely; new event types take effect immediately.
  • Staging / production: curator_gate: required — all new event types queue for curator review before the runtime accepts events of that type.
+

File as source of truth

+

Following CUST-ADR-001 (Workplans as Repository Artefacts), definition files are the canonical source of truth. The activity-core runtime indexes them into its database on startup and via a sync command. The database is a queryable cache, not the origin. A definition deleted from the filesystem is disabled at next sync.

+

Task Templates

+

Task templates are separate markdown files (tasks/{slug}.md) referenced from ActivityDefinition action blocks. They define:

+
---
+id: tasks/sbom-initial-scan
+type: task-template
+---
+# Task: Run Initial SBOM Scan
+
+## Title template
+`Run SBOM scan — {target_repo}`
+
+## Description template
+Initial SBOM scan required for newly registered repository `{target_repo}`.
+Run: `make ingest-sbom REPO={target_repo} SCAN=1`
+
+## Default labels
+["sbom", "security", "automated"]
+
+## Default assignee
+None (unassigned)
+

This keeps task content editable separately from the routing logic in ActivityDefinitions.

+
+

Consequences

+
  • A new event-types/ directory in activity-core (and eventually a shared registry) holds all org event type definitions.
  • A new activity-definitions/ directory in activity-core holds org-wide automations.
  • Domain repos may hold their own activity-definitions/ for domain-specific automations, scanned by activity-core at sync time.
  • The runtime requires a parser for the rule and instruction fenced blocks.
  • SCOPE.md for activity-core must be updated to list these directories.
+
+

Alternatives Considered

+

Pure JSON Schema for event types, separate wiki for docs: rejected — documentation and schema diverge immediately; agents must cross-reference two systems to author a rule correctly.

+

OpenAPI / AsyncAPI specification: rejected — those formats are excellent for API and broker documentation but not designed for co-locating operational intent and debugging guidance. They are also less readable for non-specialists.

+

Code-only (Python dataclasses for event schemas, Python functions for rules): rejected — requires code deployment for any definition change; agents cannot modify definitions without write access to the codebase; non-technical stakeholders cannot review or understand automation policies.

+
+
ACT-ADR-002 · accepted-2 · acceptedactivity-core · docs/adr/adr-002-definition-format.md · b72fdb5452bff51a867a0316edb994723b35f268
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-003.html b/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-003.html new file mode 100644 index 0000000..2862503 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-003.html @@ -0,0 +1,294 @@ + + + + +Rule vs. Instruction Model and Expression DSL + +
ACT-ADR-003 accepted · accepted-2 activity-core reviewed 2026-05-14generated from canonical source — do not edit

Rule vs. Instruction Model and Expression DSL

Source: activity-core · docs/adr/adr-003-rule-instruction-model.md · b72fdb5452bff51a867a0316edb994723b35f268

Review due: 2026-11-14

Status

+

Accepted.

+
+

Context

+

ActivityDefinitions need two distinct evaluation modes to cover the full range of automation scenarios in the Coulomb org:

+

Deterministic cases: "if this repo has tag python-service AND has no SBOM in the last 30 days, create a scan task." The condition is fully expressible as a boolean predicate over known attributes. The output is fixed by the template. No ambiguity, no LLM required, fully testable.

+

Judgement cases: "a new repository has been registered — based on its domain and profile, determine what domain-specific onboarding tasks are appropriate." The right answer depends on context that is expensive to encode as explicit rules. An LLM is a better evaluator than a rule tree, but introduces non-determinism, cost, and a new attack surface (prompt injection via event payload).

+

Conflating these two modes into one mechanism produces a system that is either too rigid (rules only) or too unpredictable (LLM everywhere). The two modes need different evaluation pipelines, testing strategies, and audit trails.

+
+

Decision

+

Two named, distinct evaluation modes: Rule and Instruction.

+

Terminology is deliberate. A Rule is deterministic and mechanical — it applies or it does not. An Instruction is contextual and interpretive — it guides an LLM agent to make a judgement call. Both are expressed as fenced blocks in ActivityDefinition markdown files (see ACT-ADR-002).

+

Rules

+

A Rule has two parts: a condition (boolean predicate) and one or more actions (task template references).

+

Condition expression language

+

The condition is a single-line string expression evaluated by a sandboxed AST walker — never exec() or eval(). The evaluator walks the parsed AST and whitelist-checks every node type before executing. Unknown node types raise an UnsafeExpression error at parse time, not at evaluation time.

+

Available operations:

+
CategorySyntaxExample
Equality==, !=event.type == "org.repo.registered"
Comparison>, <, >=, <=event.attributes.sbom_age_days > 30
Membershipin, not in"python-service" in event.attributes.tags
Booleanand, or, nota and (b or not c)
Grouping( )(a or b) and c
Lengthlen(x)len(event.attributes.affected_repos) > 0
Existencex is None, x is not Noneevent.attributes.domain is not None
+

Attribute access follows dot notation on the event object and the context object (populated by context sources declared in the ActivityDefinition):

+
  • event.id — UUID string
  • event.type — event type identifier
  • event.version — event type version
  • event.timestamp — ISO 8601 datetime string
  • event.publisher — publisher identifier
  • event.attributes.{name} — typed attribute per event type schema
  • context.{source}.{field} — resolved context data
+

Explicitly forbidden (evaluator rejects at parse time):

+
  • Function calls other than len() and None tests
  • Attribute access on arbitrary Python objects
  • String interpolation or formatting
  • Any control flow (if, for, while, lambda)
  • Import statements
  • Assignments
+

Design rationale: the expression language is intentionally small. Anything complex enough to need more than this belongs in an Instruction, not a Rule. When a rule condition becomes difficult to express, that is a signal that the case requires LLM judgement, not a signal that the DSL needs more features.

+

Actions

+

A Rule's action block specifies:

+
action:
+  task_template: "Run SBOM rescan for {context.repo.repo_slug}"
+  target_repo: context.repo.repo_slug
+  priority: medium
+  labels: ["sbom", "security", "{context.repo.repo_slug}"]
+  due_in_days: 7
+

action.task_template is the emitted task title template. It is not a path to a repo-local file. Older design notes and the legacy tasks/*.md directory use "task template" for materialized task-body templates; that is a separate legacy surface. To avoid surprise, new rule actions should treat task_template as title_template semantics until the field can be renamed in a schema-breaking revision.

+

Action fields accept two deterministic rendering forms:

+
  • Whole-field paths: if the whole string is a path like context.repo.repo_slug or event.attributes.repo_slug, the rendered value keeps the original scalar/list/object shape from that path. This is the correct form for target_repo and other fields that should not become prose.
  • Scalar placeholders: strings may include {context.foo} or {event.foo} placeholders. Each placeholder must resolve to a scalar. Lists and objects are rejected rather than stringified, which prevents accidental JSON blobs or untrusted text from being embedded into task titles.
+

Unsafe action cases are rejected:

+
  • Any action path outside context.* or event.*.
  • Any path containing calls, indexing, arithmetic, filters, or boolean logic.
  • Placeholder values that resolve to lists or objects.
  • for_each values that are not a whole-field context.* or event.* path to a list.
  • bind_as names that are not simple identifiers.
+

Per-item rule expansion is explicit:

+
for_each: context.repos.repos
+bind_as: repo
+condition: 'context.repo.sbom_age_days > 30'
+action:
+  task_template: Run SBOM rescan for {context.repo.repo_slug}
+  target_repo: context.repo.repo_slug
+  priority: medium
+  labels: ["sbom", "security", "automated"]
+

The weekly SBOM staleness definition is the canonical pattern. The State Hub bulk resolver exposes all repository entries at context.repos.repos, the rule binds each item as context.repo, and the strict staleness definition is context.repo.sbom_age_days > 30. Thirty days exactly is not stale; thirty-one days is stale.

+

Evaluation semantics

+
  • All rules in an ActivityDefinition are evaluated; all matching rules fire (not first-match-only). There is no implicit ordering beyond the file order, which is documented in the ActivityDefinition for human clarity.
  • A rule whose condition raises an error during evaluation is skipped and logged as rule_error; other rules still fire. This prevents a single malformed rule from silencing an entire ActivityDefinition.
  • An empty condition (omitted condition field) evaluates to true — the rule always fires when the trigger fires.
+

Instructions

+

An Instruction defers the task-creation decision to an LLM. It specifies what context to provide, how to frame the prompt, and what output schema to enforce.

+

Structure

+
# in an instruction fenced block:
+id: {slug}
+condition: '{expression}'          # optional pre-filter (Rule DSL); runs before LLM
+trusted_fields:                    # REQUIRED — explicit allowlist of payload fields
+  - event.attributes.repo_slug     # safe to interpolate into prompt
+  - event.attributes.domain
+  - event.attributes.tags
+model: claude-sonnet-4-6
+review_advisory: false             # true | false — advisory evidence, not a gate
+prompt: |
+  {prompt template — only trusted_fields may be interpolated}
+output_schema: {path to JSON schema file}
+

Trusted fields and prompt injection protection

+

The trusted_fields list is required and enforced at parse time. Any field not listed is unavailable to the prompt template. The template engine raises UntrustedFieldError if the prompt references a field not in trusted_fields.

+

The rationale: event payloads may contain free-text from untrusted sources — commit messages, issue titles, CVE descriptions, repo descriptions. Interpolating these directly into a prompt creates a prompt injection surface. Trusted fields are those whose values are validated by the event type schema (typed attributes like slugs, domain names, tag lists) and cannot carry arbitrary instruction text by construction.

+

Fields of type object (freeform JSON) are never eligible for trusted_fields even if listed — the evaluator rejects this at parse time.

+

Output schema enforcement

+

The LLM response is validated against output_schema using JSON Schema validation. If validation fails, the instruction retries once with the schema error appended to the prompt. If the second attempt also fails, the instruction records an instruction_output_error audit event and emits no tasks. Tasks are never created from unvalidated output.

+

Structured output mode (tool_use / JSON mode) is used where the model supports it. The output schema must define List[TaskSpec] or a compatible envelope.

+

review_advisory: true

+

ACTIVITY-WP-0035 selected advisory-only semantics because no downstream owner currently exposes an acknowledged proposal/decision/release contract. The instruction's task/report evidence is marked review_advisory=true and review_gate_applied=false so an operator can distinguish review-worthy output without interpreting it as held for approval.

+

The legacy input name review_required is accepted during migration and normalized to review_advisory; new definitions must not use it. activity-core does not route proposals to a pending-review queue and does not own review lifecycle state. A future hold/release design requires a named downstream owner, an idempotent release reference, an ADR update, and fail-closed emission tests.

+

Evaluation semantics

+
  • Instructions are evaluated after all rules in the ActivityDefinition.
  • The optional condition field on an instruction uses the same Rule DSL as a first-pass filter — if the condition is false, the LLM is not called. This avoids LLM cost for events that clearly do not need instruction judgement.
  • Instructions are not first-match-only; all instructions whose conditions pass fire. An ActivityDefinition may have zero instructions.
+

Audit trail

+

Every task emission records:

+
FieldRuleInstruction
source_type"rule""instruction"
source_idrule id from definitioninstruction id from definition
source_versionActivityDefinition versionActivityDefinition version
triggering_event_idevent UUIDevent UUID
condition_matchedexpression stringexpression string (pre-filter)
prompt_hash—SHA-256 of rendered prompt
model—model ID used
output_validated—true / false
review_advisory—true / false; no gate applied
+

The audit trail is written to the task_spawn_log table in activity-core's database and referenced from the task record in issue-core.

+

The rendered prompt and provider response are deliberately not persisted. The prompt hash proves equality when an authorized operator can reconstruct the same input, but the audit contract does not promise reconstruction after source definitions or upstream event retention have changed. activity_runs retains the definition version and bounded context snapshot; reports may retain allowlisted route/usage metadata. Prompts, messages, tool output, credential fields, and provider blobs are excluded from run, progress, and public API evidence.

+

Testing strategy

+

Rules: every rule can and should be unit-tested with fixture event payloads. A test helper evaluate_rule(condition_str, event_fixture) returns bool and raises on syntax errors. Tests live alongside ActivityDefinition files: activity-definitions/{slug}.test.json — a list of {event, expected_rules_fired} fixtures.

+

Instructions: instructions cannot be deterministically unit-tested. Instead:

+
  • Sample evaluations are collected: given a fixture event, record the LLM response.
  • Samples are committed to activity-definitions/{slug}.samples/ for human review.
  • Output schema validation is unit-tested independently of the LLM call.
  • Prompt injection resistance is tested by including injection strings in fixture event payloads and asserting they do not appear in the rendered prompt.
+

rules-core module boundary

+

The rule evaluator and instruction executor live in src/activity_core/rules/. Within this module:

+
  • No imports from temporalio, sqlalchemy, fastapi, or any activity-core application code.
  • Public surface: evaluate_condition(expr: str, event: EventEnvelope, context: dict) -> bool and execute_instruction(instr: InstructionDef, event: EventEnvelope, context: dict) -> List[TaskSpec].
  • The module is independently importable and testable without starting the Temporal worker or Postgres.
+

This boundary makes future extraction to rules-core a packaging exercise, not a refactor.

+
+

Consequences

+
  • The ActivityDefinition Pydantic model gains rules: List[RuleDef] and instructions: List[InstructionDef] fields. The current implicit "always create tasks" behaviour is replaced by explicit rule blocks.
  • A new RuleEvaluator class (AST walker) is added to src/activity_core/rules/.
  • A new InstructionExecutor class handles prompt rendering, LLM call, output validation, and review-required audit metadata. Pending review queue routing remains a future issue-core integration.
  • Integration tests for rule evaluation use fixture JSON; no running Temporal required.
  • The task_spawn_log table is added to the Postgres schema (new Alembic migration).
  • ActivityDefinition files that omit both rules and instructions are valid (they fire with no output) — this supports future placeholder definitions.
+
+

Alternatives Considered

+

OPA / Rego for rule conditions: powerful, well-established policy language, supports complex logic. Rejected — Rego's learning curve is high for non-specialists; agents rarely produce correct Rego without fine-tuning; it adds a runtime dependency. The simple AST-walker DSL covers the realistic condition complexity for this org.

+

Rules as Python lambdas: maximum expressiveness. Rejected — arbitrary code execution in a rule condition is a serious security surface, especially in an org-wide event loop. Code deployment required for any rule change; agents cannot write rules without code write access.

+

LLM for all conditions (no Rule/Instruction split): simpler model, more flexible. Rejected — non-deterministic for cases that are deterministic; expensive for high-frequency events like cron ticks; impossible to unit-test; audit trail for deterministic rules becomes murky.

+

Instructions only, no Rules: allows arbitrary LLM judgement for everything. Rejected — LLM cost for every event, latency, and non-determinism are unacceptable for high-frequency maintenance automations. Many cases (SBOM staleness check, tag-based routing) are fully deterministic and should stay that way.

+
+
ACT-ADR-003 · accepted-2 · acceptedactivity-core · docs/adr/adr-003-rule-instruction-model.md · b72fdb5452bff51a867a0316edb994723b35f268
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-004.html b/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-004.html new file mode 100644 index 0000000..65889a0 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-004.html @@ -0,0 +1,224 @@ + + + + +The Producer Trust Boundary — Guardrails and Error-Correction for Untrusted Output + +
ACT-ADR-004 accepted · accepted-2 activity-core reviewed 2026-06-26generated from canonical source — do not edit

The Producer Trust Boundary — Guardrails and Error-Correction for Untrusted Output

Source: activity-core · docs/adr/adr-004-producer-trust-boundary.md · b72fdb5452bff51a867a0316edb994723b35f268

Review due: 2026-12-26

Status

+

Accepted.

+
+

Context

+

On 2026-06-26 the scheduled daily WSJF triage instruction fired on time, called llm-connect successfully, and produced a long ranked recommendation list — but the JSON broke at char 5268 (~rank 8–9 of ~16), failing schema validation. Because the report was validated and consumed as a single monolithic JSON document, one malformed delimiter discarded the entire run, including the 7 perfectly good recommendations the model had already emitted. The scheduling and runtime layers were healthy; the failure was entirely at the seam where free-form model output meets a strict consumer.

+

This is not a one-off bug, it is a recurring class. activity-core has a trust boundary wherever generative or human-authored output meets strict deterministic consumers: the JSON Schema validator, the task emitter, and any classic compute pipeline downstream. The producers on the other side of that boundary — LLMs, agents, and humans — are all untrusted producers. Their output may be:

+
  • erroneous — hallucination, truncation at a token limit, drift, type slips, typos, a missing delimiter; or
  • malicious — prompt injection, crafted payloads, or oversized / deeply-nested structures intended to exhaust or confuse the consumer.
+

The pre-existing design treated producer output optimistically: parse the whole document, validate the whole document, and on any failure discard the whole document (preserving only a bounded diagnostic preview). That gives zero error locality — the blast radius of any single defect is the entire activation.

+
+

Decision

+

Treat the producer→consumer seam as an explicit, adversarial trust boundary, and place guardrails plus error-correction tooling at that boundary rather than letting raw producer output flow into deterministic consumers.

+

Two non-fail-fast postures

+

When hard-failing on a problem is undesirable, there are two sound strategies, and they compose:

+
  • A) Trust but handle exceptions (optimistic / reactive). Consume the output as-is; on exception, catch → repair → retry → or quarantine. Cheap on the happy path; blast radius depends entirely on how granular the catch is. Best when failures are rare and locally recoverable. Risk: failures surface late, possibly after partial side effects.
  • B) Verify and mitigate (defensive / proactive). Validate, sanitize, clamp, and normalize the output to a known-good shape before it enters the pipeline — drop bad items, coerce types, bound sizes/depth, allow-list references — so the consumer only ever sees clean input. Higher upfront cost, smaller blast radius, no partial side effects. Best when failures are common or consequences are high.
+

Governing principles

+
  1. Push verification to the boundary; keep the interior strict. Apply posture B at the producer→consumer boundary; keep posture A for residual exceptions inside the verified core. Never relax the interior schema to absorb producer sloppiness.
  2. Make error locality match the unit of work. One bad recommendation must cost one recommendation, not the whole report. Structuring the payload so each item is independently parseable and validatable is the highest-leverage change.
  3. Quarantine, never silently drop. Invalid units are preserved as bounded, provenance-tagged artifacts (index, error, raw snippet, reason) so they can be debugged or replayed. Degraded-but-usable is reported distinctly from total loss.
  4. Both human and agent input get the same rigor. Guardrails are producer-agnostic: the same count / length / depth caps and reference allow-lists apply whether the producer is an LLM, an agent, or a human.
+

What this means concretely in activity-core

+

Implemented in src/activity_core/rules/executor.py:

+
  • Strict-structure-only schema. The daily-triage output schema is strict on per-item structure (required [rank, candidate, action, why], typed wsjf) and carries maxItems as a producer hint — never as a hard whole-document reject, which would reproduce the very blast-radius failure (ACT-ADR-002 governs the schema format; schemas/daily-triage-report.json).
  • Item-granular recovery (posture B). When whole-document parse + one retry fail, _resilient_report recovers individually-parseable recommendation objects via a brace/quote-aware scanner (_extract_object_spans) that works for both pretty-printed and NDJSON output, attempts a best-effort _try_repair on a truncated tail, validates each recovered object against the item schema, and keeps the valid ones. Survivors are emitted with output_validated=true, partial=true, and review_advisory=true (review_gate_applied=false).
  • Producer guardrails (_partition_items, applied on both the recovery and the happy path). Per recommendation: structural type → schema → structural caps (_MAX_DEPTH, _MAX_STRING_LEN) → reference allow-list → count cap (top-N by maxItems). The first failing check quarantines the item with provenance and a reason (malformed / schema / guardrail / allow_list / over_limit).
  • Reference allow-list. A recommendation whose candidate is not in the set of known ids is quarantined. The set is sourced from resolved context (context["known_candidates"], via _allow_list_from_context); the check is inert until a context resolver populates it, so the capability ships now and activates with a one-line resolver change.
+

Where each posture sits

+
LayerPostureMechanism
Schema / contractBstrict per-item structure; maxItems as hint
Whole-document parseAtolerant parse + single retry
Failed parseBitem-granular recovery + repair + quarantine
Per-item screeningBschema + depth/length caps + allow-list + count cap
Emitted report—partial / quarantined_* provenance; never silent
+
+

Consequences

+
  • A single malformed or oversized item no longer discards an entire activation; the daily-triage run that failed on 2026-06-26 would now deliver its 7 valid recommendations and quarantine the broken tail.
  • Reports gain a partial / quarantined_* vocabulary; downstream report sinks and reviewers can distinguish degraded-but-usable from total loss.
  • Guardrail thresholds (_MAX_DEPTH, _MAX_STRING_LEN, maxItems, the allow-list) are policy knobs that will need tuning; they are intentionally conservative defaults, not a finished calibration.
  • Known retention gap (follow-on): LLMConnectClient.complete() still returns only content, discarding finish_reason/usage, and the total-loss artifact caps raw output below realistic break points. Capturing those signals so failures stay debuggable is tracked as a retention fix, not closed by this ADR.
+
+

Alternatives considered

+
  • Hard-enforce maxItems in the validator. Rejected: a hard reject of an over-count document reproduces the whole-document blast radius. Mitigation (keep top-N, quarantine the rest) is preferred.
  • Relax the schema to accept anything. Rejected: violates principle 1; pushes malformed data into downstream consumers.
  • Retry-until-valid only (pure posture A). Rejected as the sole strategy: the 2026-06-26 failure recurred across both the initial attempt and the retry, so retry alone does not bound the blast radius.
+
+

References

+
  • ACT-ADR-002 — markdown-as-definition format and output schema governance.
  • ACT-ADR-003 — Rule vs. Instruction model; the Instruction prompt-injection surface this boundary complements on the output side.
  • workplans/ACTIVITY-WP-0016-llm-output-robustness-trust-boundary.md — the implementing workplan.
+
ACT-ADR-004 · accepted-2 · acceptedactivity-core · docs/adr/adr-004-producer-trust-boundary.md · b72fdb5452bff51a867a0316edb994723b35f268
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-005.html b/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-005.html new file mode 100644 index 0000000..99e9a52 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-005.html @@ -0,0 +1,249 @@ + + + + +Ops runs vs development work records — claim queue and plane split + +
ACT-ADR-005 accepted · accepted-2 activity-core reviewed 2026-08-03generated from canonical source — do not edit

Ops runs vs development work records — claim queue and plane split

Source: activity-core · docs/adr/adr-005-ops-runs-vs-dev-work-records.md · b72fdb5452bff51a867a0316edb994723b35f268

Review due: 2027-02-03

Status

+

Accepted (2026-08-03).

+
+

Context

+

The fleet has two healthy patterns that were forced into one vague “task” idea:

+
  1. Development / coordination work — structured workplans and tasks as repo files, indexed by State Hub on coulombcore (fix-consistency, UUIDv7 write-back). This matches ADR-001 and work-record-types_v0.1.md and is how humans and coding agents ship product.
+
  1. Operations / scheduled automation — activity-core Temporal schedules (when / what / where), rein-aharness execution (how), completion events (idempotence). Instances of “today’s FI brief” or “this prune fire” are ephemeral runs, not multi-day workplan bodies.
+

Practice collapsed (2) into broken paths:

+
PathFailure
Emit activity_task_spawn progress onlyAppend-only; not claimable; schedule can “succeed” while nothing runs
ISSUE_SINK_TYPE=rest → issue-core → ForgejoSpam + 503s; issue-core INTENT forbids origin of fleet work
Host systemd timers dual-clocked with TemporalShadow scheduler; weak self-healing
+

issue-core’s correct role is a connector facade over external trackers (Forgejo, GitHub, Jira, …) so agents need not know each backend. It is not the origin of work records and not the default internal ops queue. Gitea is out of scope for this fleet; the self-hosted forge is Forgejo.

+

Canon already allows a DB-only exception for “runtime operations data (logs, metrics, run histories, token events)” (work-record-types_v0.1.md). This ADR names that exception for ops runs.

+

This is also the precise meaning of INTENT's no-task-lifecycle boundary: ops_run claim/lease/outcome is durable runtime delivery state, not work-item state. It may not grow assignment, commitments, dependencies, project phases, or manually managed task status.

+
+

Decision

+

1. Two planes for “work,” one vocabulary for people

+
PlaneArtefactHomeLifecycle
A — Development / coordinationWork records (workplan, task, intake, …)Repo files + State Hub (coulombcore)File-backed, reviewable, multi-day
B — Recurrence controlActivity definitions + Temporal schedulesactivity-core (railiance)Git definitions; DB schedule state
C — Ops executionops_run (runtime claim object)activity-core DB + claim APIopen → claimed → succeeded \failed \expired
D — External collabTracker issuesissue-core optional projection to Forgejo (and others)Never default for internal automation
+

Coding agents orient on Plane A via State Hub. Automation orients on Planes B+C. Humans outside the fleet may see Plane D only when deliberately projected.

+

2. activity-core owns the ops_run claim queue

+

When a definition emits a TaskSpec for internal fleet work:

+
  1. Write ops_run row (durable, claimable) with idempotency key (e.g. fi-daily:2026-08-04 for once-per-day briefs).
  2. Optionally dual-write activity_task_spawn progress for existing observers.
  3. Do not open a Forgejo issue.
  4. Do not create a workplan task file for that day’s fire.
+

Claim API (implemented in ACTIVITY-WP-0026 and hardened in ACTIVITY-WP-0036):

+
  • POST /ops-runs/claim — lease next open run matching labels / worker id
  • POST /ops-runs/{id}/complete — succeeded + completion metadata
  • POST /ops-runs/{id}/fail — failed + retry policy
  • GET /ops-runs?state=open — operator visibility
+

Worker mutation credentials are bound to one configured queue worker_id; the request body cannot assert a different claim owner. Heartbeat, completion, and failure lock the row and require its lease deadline to remain strictly in the future. Operator/SSO credentials provide visibility and explicit administration, but do not act as a normal worker identity.

+

activity-core remains when / what / where only: it does not run domain LLM sessions or hold tenant git credentials.

+

3. rein-aharness owns how (approach selection + execute)

+
  • Continuous claim loop (service / Deployment), not dual wall-clock timers as source of truth.
  • Host systemd timers become break-glass after cutover.
  • Approach selection at claim time (minimal table v1):
+
Labels / definition familyApproach
mail / mail-intakedeterministic adapter (+ optional triage)
research-brief / fi-dailystructured llm-connect (fi-research-brief)
binky rhythmstructured llm-connect (brief-daily)
agent-sessionpersona + tool profile session
unknownrefuse; do not invent
+
  • Success posts domain completion event (e.g. fi_daily_brief) so resolvers set due=false, and closes the ops_run.
+

4. State Hub remains the work-record read model

+
  • Stays on coulombcore; railiance peers via edge relay / ops-bridge.
  • May project open/failed ops runs for fleet ops UI (read-only), but is not the claim authority.
  • Development workplans/tasks continue file + fix-consistency only.
+

5. issue-core is Forgejo (and multi-tracker) facade only

+
  • Forgejo is the self-hosted forge; do not plan for Gitea as a product.
  • Optional project / link of an existing work-record UUID to an external issue when collaboration needs it.
  • Authenticated POST /issues/ means “create/link external tracker work,” never “spawn fleet automation.”
  • Internal automation must not use issue-core as the ops claim queue unless a future design adds an internal-only backend with projection hard-off for automation labels — out of scope unless a later ADR says otherwise.
+

6. Promotion path ops → dev

+

When an ops run discovers multi-day product work (e.g. “collect Kimi K3 under new quota”), the executor or human promotes an intake / workplan in the domain repo (Plane A). Ops runs never become permanent fake workplans.

+
+

Consequences

+

Positive

+
  • Self-healing recurrence: schedule → claimable row → claim → complete/fail.
  • Aligns issue-core INTENT with practice; stops Forgejo spam path.
  • Keeps activity-core thin; keeps rein-aharness as sole session/credential shell.
  • Preserves the good dev loop (files + State Hub).
+

Negative / cost

+
  • New schema + API + migrator in activity-core.
  • rein-aharness must leave issue-core-only poll as primary for ops.
  • Temporary dual-write and dual timers until cutover proven.
+

Non-goals

+
  • Replacing workplans for development.
  • Running domain briefs inside activity-core workers.
  • Global ISSUE_SINK_TYPE=rest to Forgejo.
  • Gitea support or migration paths.
+
+

Topology

+
coulombcore:  State Hub + work-record registry
+railiance:    activity-core (Temporal + ops_run queue)
+              rein-aharness (claim loop + approach + llm-connect)
+              domain checkouts
+forgejo:      optional external collab via issue-core projection only
+
+

Implementation stack

+
WorkplanOwnerRole
ACTIVITY-WP-0026activity-coreops_run schema, claim API, emit path, dual-write
REIN-A-0002rein-aharnessclaim loop, approach table, FI/Binky cutover
ISSUE-WP-0006issue-coreForgejo-only language; projection boundary; no ops queue
STATE-WP-0078state-hubRead projection of ops_run for ops UI (optional consume)
+
+

References

+
  • docs/recurring-automations-playbook.md
  • docs/task-emission-consumer-contract.md
  • docs/issue-core-emission-boundary.md
  • ACTIVITY-WP-0022, ACTIVITY-WP-0023 (G2)
  • the-custodian/canon/standards/work-record-types_v0.1.md
  • issue-core/INTENT.md (work-record boundary)
  • state-hub/docs/cluster-operating-model.md (coulombcore primary)
+
ACT-ADR-005 · accepted-2 · acceptedactivity-core · docs/adr/adr-005-ops-runs-vs-dev-work-records.md · b72fdb5452bff51a867a0316edb994723b35f268
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-006.html b/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-006.html new file mode 100644 index 0000000..e6d8c03 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-006.html @@ -0,0 +1,228 @@ + + + + +Profile-driven execution selection over the ops_run pull queue + +
ACT-ADR-006 accepted · accepted-1 activity-core reviewed 2026-08-21generated from canonical source — do not edit

Profile-driven execution selection over the ops_run pull queue

Source: activity-core · docs/adr/adr-006-glas-profile-execution.md · ea6fb0e92a25825b42e00b2f85aa23cf50605294

Review due: 2027-02-21

Status

+

Accepted (2026-08-21). Hub decision 147beec6-7fe7-4837-8e3a-4264a240379d ("Glas contract 1.0 makes execution constellation selection explicit").

+
+

Context

+

ACT-ADR-005 gave internal scheduled automation a claimable ops_run plane. What an ops_run said about how to execute was approach_hint — free text, interpreted by the claiming executor at claim time.

+

That binding is too late. Live failure, railiance01 2026-08-17:

+
state=failed  attempt=1  claim_owner=rein-aharness@railiance01
+title=Run SBOM rescan for binect-js
+failure=no approach matched labels/definition;
+        labels=['sbom','security','automated']
+

The run was claimed and leased, then died because nothing could execute it. A claim was consumed to discover the request was unroutable.

+

glas-harness GLAS-WP-0004 closed contract 1.0 and proved it by running one bounded task through two different reins (rein-aharness, rein-openweights) via an explicit harness_profile_ref, with real commits and verified sandbox destruction. Meanwhile every activity-core claim in the week to 2026-08-21 was rein-aharness@railiance01 — a coupling that is now avoidable rather than inherent.

+

Two shapes were considered: keep the pull queue and carry the profile as payload, or have activity-core call the Glas gateway directly. glas-harness, which owns the contract, advised the first for the initial migration.

+
+

Decision

+

1. The queue stays; the payload changes

+

activity-core continues to insert ops_run(open) and executors continue to claim under lease. Scheduling topology does not change. What changes is that a queued run carries an exact versioned harness_profile_ref plus the correlation / assignment / role / duty / goal / resource-envelope references. The claiming executor passes that request into Glas, which resolves or refuses it before sandbox creation.

+

This changes the execution contract without simultaneously changing the scheduling topology — one variable at a time. A direct activity-core → Glas call may be evaluated later, independently, if pull delivery stops meeting operational needs.

+

2. harness_profile_ref is authoritative; approach_hint is legacy

+

The two coexist with distinct semantics, not as fallbacks for one another:

+
  • harness_profile_ref is the authoritative execution-constellation selector.
  • approach_hint remains only a legacy activity/definition-matching hint while producers and consumers migrate.
+

approach_hint must not override, synthesize, or fall back from an absent or invalid harness_profile_ref on governed execution. A missing profile is an error, never an invitation to guess from a hint. Once inventory shows no caller depends on approach_hint for runtime selection, that use is deprecated.

+

3. activity-core does not mirror the profile catalog

+

The catalog in glas-harness is authoritative. activity-core must not maintain an independent list, which would drift and produce a second, disagreeing opinion about what is executable.

+

Consequence, accepted knowingly: Glas exposes deterministic validation through its package and CLI but no network validation service today, so activity-core cannot remotely validate a profile ref at emission time. We therefore validate only what is local and structural — that a ref is present and well-formed for governed execution — and rely on the execution-side Glas resolver as the mandatory fail-closed check.

+

This is weaker than refusing at emission, and deliberately so: an unreachable or absent validation service must never downgrade into "emit anything". If emit-time remote validation is later required, it needs a separately scoped Glas API and its own decision — not a mirrored catalogue here.

+

Even unvalidated at emit, this is strictly better than the 2026-08-17 failure: refusal happens deterministically before sandbox creation rather than after a claim and lease were consumed.

+

4. activity-core does not become an executor

+

We emit an authorized, profile-named request and record normalized evidence. We do not select reins, provision sandboxes, acquire credentials, or run any inner agentic loop. Glas owns profile resolution and the outer loop; the rein owns its inner loop; sand-boxer owns isolation. This is SCOPE drift risk #1 ("convenience execution") and this ADR does not relax it.

+

The attribution references we carry (assignment_ref, role_ref, duty_ref, goal_refs, resource_envelope_refs) are passed through, not authored here. Their vocabulary belongs to info-tech-canon; activity-core must not invent org roles (ACTIVITY-WP-0029 responsibility map).

+

ACT-ADR-007's bounded-operation exception does not alter this decision. A code-registered fixed maintenance operation is not a Glas/rein execution constellation, and the exception cannot be used to run an agent loop or execute an ops_run inside activity-core.

+
+

Consequences

+
  • ops_runs grows harness_profile_ref and the attribution refs; the emission path, queue projection, and run artefacts carry them.
  • Definitions declare a profile; rules pass it through.
  • Unroutable requests fail before sandbox creation instead of at claim time.
  • activity-core is no longer coupled to one rein in practice.
  • Emit-time validation remains a known gap, owned by a future Glas API.
  • Implementation: ACTIVITY-WP-0032.
+
ACT-ADR-006 · accepted-1 · acceptedactivity-core · docs/adr/adr-006-glas-profile-execution.md · ea6fb0e92a25825b42e00b2f85aa23cf50605294
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-007.html b/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-007.html new file mode 100644 index 0000000..fe8cb3c --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ACT-ADR-007.html @@ -0,0 +1,226 @@ + + + + +Code-registered bounded operations are the only local mutation exception + +
ACT-ADR-007 accepted · accepted-1 activity-core reviewed 2026-08-23generated from canonical source — do not edit

Code-registered bounded operations are the only local mutation exception

Source: activity-core · docs/adr/adr-007-bounded-operations.md · ea6fb0e92a25825b42e00b2f85aa23cf50605294

Review due: 2027-02-23

Status

+

Accepted (2026-08-23) for ACTIVITY-WP-0035.

+
+

Context

+

The Event Bridge principle says activity-core answers when, what, and where and does not execute domain work. Production nevertheless contains three useful operations whose complete outcome is a small scheduled maintenance action:

+
  • ingest at most three Repo Manager-selected immutable sources into SBOM Nexus;
  • prune Forgejo package versions under live-image protection; and
  • invoke the fixed CNPG Option A backup tool for an explicit target list.
+

Historically the latter two ran through a generic shell context resolver. That made a mutating subprocess look like a read and left extension policy to convention. Removing the operations would recreate bespoke cron; accepting arbitrary shell would make activity-core a general executor.

+
+

Decision

+

1. Bounded operations are a narrow implementation exception

+

The governing when/what/where responsibility does not gain a general “how.” A bounded operation is allowed only when the operation itself is the declared automation outcome and all admission requirements below are code-reviewable.

+

The initial allowlist is exactly:

+
  1. sbom_nexus_ingest
  2. forgejo_package_prune
  3. cnpg_option_a_backup
+

Adding an operation requires updating the code-owned registry, this ADR (or a successor), tests, credential route, and evidence contract. A string in a definition cannot register an operation.

+

2. Admission is fail closed during file sync

+

Each registry entry declares:

+
  • source type and query;
  • whether mutation intent is apply or dry_run and that it is explicit;
  • fixed or maximum target bounds;
  • canonical implementation and allowed configuration;
  • idempotency and Temporal retry semantics;
  • maximum execution timeout;
  • credential owner/route; and
  • mandatory non-secret evidence mode.
+

Unknown shell queries are refused unless separately registered as read-only. Known operation queries with missing, malformed, or over-limit safety fields are refused before database projection or Temporal schedule reconciliation.

+

3. Context resolution is read-only

+

The workflow first resolves and freezes context. Mutations then run in an explicit bounded-operation stage and merge only normalized outcomes into the snapshot before evidence and rule/instruction evaluation.

+

SBOM selection remains a read in the context phase; its fixed selection is the input to the operation stage. Package prune and backup have no discovery read inside activity-core and bind a pending marker until their operation completes.

+

4. Retry behavior is operation-specific

+
  • SBOM ingest uses stable per-run/per-repository idempotency keys and heartbeat checkpoints, so Activity retries resume the frozen batch.
  • Forgejo prune and CNPG backup have no activity-core-verifiable remote idempotency receipt. Their operation activity therefore has one Temporal attempt; a failure remains visible for operator reconciliation rather than risking an automatic second mutation.
+

This does not preclude future safe retries after the platform tools expose a durable operation receipt.

+

5. Evidence is mandatory and bounded

+

Every operation must produce an allowlisted summary through a configured report/evidence sink. Raw subprocess output, tokens, provider payloads, archive URLs, kubeconfigs, and credential material are not evidence.

+
+

Rejected alternatives

+
  • Keep mutating shell resolvers. Rejected because resolution should be a replayable read and the generic dispatcher hides mutation admission.
  • Generic command activity. Rejected because command text/path from a definition is remote code execution by configuration.
  • Move every operation to a rein. Rejected for these fixed platform operations; it adds an agent execution constellation without judgement or repository work. Operations that exceed this ADR's bounds do belong there.
  • Remove all local operations. Rejected because it recreates scattered cron and loses Temporal/evidence guarantees for established maintenance.
+
+

Consequences

+
  • Definition parsing gains a central operation-policy validator.
  • The workflow gains an explicit operation stage.
  • The generic shell resolver becomes read-only.
  • Existing definitions migrate without widening targets or permissions.
  • The registry is intentionally small and architectural review is required to expand it.
+
ACT-ADR-007 · accepted-1 · acceptedactivity-core · docs/adr/adr-007-bounded-operations.md · ea6fb0e92a25825b42e00b2f85aa23cf50605294
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-001.html b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-001.html new file mode 100644 index 0000000..20717bd --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-001.html @@ -0,0 +1,257 @@ + + + + +Workplans and Work Items Are Repository Artefacts + +
CUST-ADR-001 accepted · accepted-2 the-custodian reviewed 2026-08-31generated from canonical source — do not edit

Workplans and Work Items Are Repository Artefacts

Source: the-custodian · canon/architecture/adr-001-workplans-as-repo-artefacts.md · d3c6f13d7aed9784b6f31c23d2b2777668493ba5

Review due: 2027-02-28

Status

+

Accepted 2026-02-28.

+

Amended 2026-08-31 to distinguish file-backed work records from hub-native records, identify the Forge default branch as the central projection baseline, and align identity, lifecycle, reconciliation, and closure with ADR-007, ADR-010, ADR-011, ADR-012, and the work-record standards. The central decision is unchanged.

+
+

Context

+

During early State Hub development, workstreams and tasks were created directly in PostgreSQL through bootstrap APIs. This made a database the origin of durable work rather than a projection of repository-owned artefacts. The pattern was convenient, but it made work difficult to audit, review, recover, and carry between generations of tooling.

+

The original decision correctly moved durable workplans and their work items into repositories. It overstated the consequence, however, by saying that the entire Hub database and every item that matters for coordination must be reconstructible from repository files. Later decisions established two different persistence classes:

+
  • file-backed records, whose durable meaning originates in a repository;
  • hub-native records, such as append-only progress and runtime facts, whose durable meaning originates in the Hub.
+

Later decisions also established that a central projection reads the pushed default branch in Forgejo. An arbitrary workstation checkout is a workspace, not the shared baseline. Unpushed local work may be represented only as an explicit preliminary overlay.

+
+

Decision

+

1. File-backed work originates in the owning repository

+

Workplans and their file-backed work items MUST originate as repository artefacts in the repository that owns the work. The Hub indexes and projects those artefacts but is not their origin.

+

This includes the workplan, embedded tasks, declared dependencies, residual handoffs, and durable governed decision artefacts when those are represented as files. No authoritative field of a file-backed record may exist solely in a Hub database.

+

The owning repository is identified by repository identity, not merely by a domain or topic. Domains and topics classify work; they do not own its source.

+

2. Authority is explicit per persistence class

+

Every record contract must declare whether the record is file-backed or hub-native. The same record must not be writable as authoritative in both places.

+
  • For a file-backed record, the repository artefact is authoritative and the Hub row is derived state.
  • For a hub-native record, the Hub is authoritative. Progress events, messages, token events, run history, and similar runtime facts are not made fictional repository records merely to satisfy a rebuild slogan.
+

The word decision is used by more than one subsystem. Architecture decisions and other governed decision artefacts remain files. A runtime decision event may be hub-native only where its schema says so explicitly; it does not replace the governed artefact.

+

This replaces the original blanket rejection of a hybrid architecture. The architecture is hybrid by declared record class, never ambiguous within one record.

+

3. Forge is the central projection baseline

+

For the shared central view, the source is the pushed default-branch state held by Forgejo, as decided by ADR-012. Every projected record must be attributable to its source repository, path, and commit.

+

A working copy remains the authoring workspace. Unpushed work may appear in the Hub only as an attributed preliminary overlay. It must not silently replace or be presented as the Forge-derived baseline. When its commit reaches Forge, the baseline absorbs it and the overlay retires.

+

4. Mutations of file-backed state are file-first

+

Creating or changing a file-backed work record means changing its repository artefact first. A convenience tool or API is conformant only when it performs a governed repository mutation and leaves a reviewable file and commit. Writing only the projected database row is not a durable update.

+

Direct Hub APIs remain valid for hub-native records. They may also provide diagnostics or propose repository patches, but a successful database PATCH is not evidence that a file-backed status changed.

+

5. The rebuild guarantee applies to the projection, not the whole database

+

Given a repository and a specific Forge commit, the system must be able to reconcile that repository's file-derived projection so that it is equivalent to the records derived from that commit. Reconciliation must be idempotent, verifiable, and scoped per repository; a fleet operation is iteration over the same per-repository operation.

+

Reconciliation retires file-derived records that no longer derive. It does not delete hub-native history attached to them. It must refuse and report a repository whose records exist only in the projection until those records have an explicit disposition. A Forge rebuild does not reconstruct preliminary overlays and must disclose their retirement before proceeding.

+

Therefore the file-derived projection is disposable. The database as a whole is not disposable when it also contains hub-native facts.

+

6. Identity and lifecycle are delegated contracts

+

This ADR does not define a second identity or lifecycle schema.

+
  • Workplan and task identity follow ADR-007 as amended by ADR-011: global identity is namespace-aware, and derivable Hub identifiers are governed by that contract. Existing historical identifiers are grandfathered according to its migration rules.
  • Work-record kinds, locations, lifecycle values, and residual handling follow canon/standards/work-record-types_v0.1.md and canon/standards/workplan-terminology-fleet_v0.1.md.
+

New normative text uses workplan and work record. workstream remains only as a metered compatibility term for legacy database and API surfaces.

+

7. Reconciliation belongs at the repository boundary

+

Repo Manager owns discovery, parsing, identity checks, and reconciliation for file-backed records. State Hub owns the projection and hub-native records. The implementation may distribute fetch and parse work, but it must preserve that authority boundary.

+

Legacy create_workstream, create_task, workstation-driven sync-workplans, and similarly named commands are not normative interfaces. They are conformant only if their current implementation satisfies decisions 3 and 4; otherwise they are transitional or retired.

+
+

Workplan closure

+

A workplan cannot be marked finished merely by changing its Hub row. Before closure, the responsible worker must:

+
  1. review the source file and the projected view for unfinished tasks;
  2. record completed and cancelled outcomes in the source task blocks using the canonical task lifecycle;
  3. turn every actionable carry-forward item into a live work record with origin: residual and an origin_ref, or into another explicitly linked workplan;
  4. set the source workplan to finished, update it, and commit the result;
  5. publish the commit to Forge for the shared baseline and verify that the projection reconciles to that commit.
+

A local reconciliation before push may expose preliminary state, but it does not complete step 5.

+

Automated stale-task cleanup must report file/projection drift. It may not silently cancel a file-backed task only in the database. An automated repair may change such a task only through the same governed repository mutation and commit path as any other file-backed change. Cleanup of hub-native records is governed by their own retention contract.

+
+

Consequences

+

Positive

+
  • Durable work remains inspectable, reviewable, and recoverable through Git.
  • The central view has an exact repository and commit provenance instead of reflecting whichever workstation synced most recently.
  • Reconciliation can be exercised per repository without destroying progress history or other hub-native evidence.
  • Multiple contributors share a published baseline while retaining an honest representation of preliminary work.
  • Service ownership is clearer: repositories own file-backed truth; the Hub owns runtime facts and projection services.
+

Negative

+
  • File-backed changes require a repository mutation and normally a push before they become shared baseline state.
  • Forge availability affects projection freshness, although it does not prevent local authoring.
  • Preliminary, stale, and retired projection states must be visible in APIs and user interfaces.
  • Git merge conflicts become the explicit conflict mechanism for simultaneous edits to the same authoritative artefact.
  • Tools that previously corrected only database state must be changed to emit a diagnostic or perform a governed repository update.
+
+

Migration

+

No bulk rename, UUID rewrite, or historical file rewrite is required by this amendment. Existing identifiers and legacy terminology retain the grandfathering rules of ADR-007, ADR-011, and the terminology standard.

+

Implementations must audit and retire DB-first creation, closure, and stale-task cleanup paths. Existing file-backed projection rows without a Forge source commit are migration state: they must be matched to a repository artefact or explicitly dispositioned before reset. Forge-derived reconciliation and preliminary overlays are implemented under ADR-012 rather than duplicated here.

+

The accepted-1 publication remains the immutable historical revision. This document is accepted-2.

+
+

Alternatives considered

+

Database-first with export. Rejected. Export can be skipped, making the reviewable artefact secondary and stale.

+

Files only, with no projection database. Rejected. Cross-repository queries, runtime views, and append-only operational facts need indexed services.

+

Treat every Hub record as reconstructible from Git. Rejected. This either loses runtime truth during rebuild or creates artificial files whose only purpose is mirroring a database.

+

Let working copies push authoritative projection rows. Rejected by ADR-012. It makes the baseline depend on the last workstation to reconcile and cannot be audited against a shared commit.

+
+
CUST-ADR-001 · accepted-2 · acceptedthe-custodian · canon/architecture/adr-001-workplans-as-repo-artefacts.md · d3c6f13d7aed9784b6f31c23d2b2777668493ba5
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-002.html b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-002.html new file mode 100644 index 0000000..029635b --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-002.html @@ -0,0 +1,242 @@ + + + + +Custodian Agent Runtime — v0.1 Bootstrap Design + +
CUST-ADR-002 accepted · accepted-1 the-custodian reviewed 2026-03-12generated from canonical source — do not edit

Custodian Agent Runtime — v0.1 Bootstrap Design

Source: the-custodian · canon/architecture/adr-002-custodian-agent-runtime-design.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb

Review due: 2026-09-12

Status

+

Accepted.

+
+

Context

+

CUST-WP-0001 requires a first working skeleton of the Custodian as an acting agent: a loop that observes project state, reasons about it, and executes bounded write operations — without human interaction for each step.

+

The dependencies (llm-connect, operational Railiance infra) are now resolved. This ADR captures the five key architectural decisions for the v0.1 bootstrap.

+
+

Decisions

+

D1 — Location: runtime/ inside the-custodian (not a new repo)

+

Decision: The runtime lives under the-custodian/runtime/ as a standalone Python package (pyproject.toml, own venv) rather than a new repository.

+

Rationale: The runtime is tightly coupled to canon (reads constitution, memory) and the state-hub (its primary coordination layer). A separate repo adds friction with no v0.1 benefit. The existing runtime/ scaffold confirms the original intent. Extraction to its own repo is deferred to when the runtime has stable boundaries and multiple consumers.

+

D2 — OODA loop: single-pass synchronous CLI

+

Decision: One run() call = one complete Observe → Orient → Decide → Act cycle. Entry point is a CLI (agent.py) invoked manually or by a cron job.

+
Observe  — HTTP GET to state-hub: state summary or domain summary
+Orient   — Load constitution + build structured LLM context prompt
+Decide   — Single LLM call (via llm-connect) returns a JSON action plan
+Act      — Execute only sanctioned write operations from the plan
+

Rationale: Async event loops and daemons add operational complexity that v0.1 doesn't need. A single-pass CLI is testable, debuggable, and can be scheduled externally. The transition to an event-driven loop is Phase 2.

+

D3 — LLM backend: ClaudeCodeAdapter by default

+

Decision: The runtime uses llm_connect.ClaudeCodeAdapter as its default LLM backend (shells out to claude --print). Provider is configurable via --llm flag to support gemini, openrouter, or openai.

+

Rationale: ClaudeCodeAdapter requires no API key and honours the Local-First value (V2). All current deployments have Claude Code available. The llm-connect abstraction means switching providers is a one-line change.

+

D4 — Action constraint: three sanctioned write operations only

+

Decision: The agent may execute exactly three state-hub write operations without human approval:

+
  1. add_progress_event — append an observation to the event log
  2. update_task_status — mark a task done/in_progress (reversible)
  3. flag_for_human — raise an intervention flag (escalation, not action)
+

All other operations (create workstream, record decision, resolve decision, write to canon) require human approval before execution.

+

Rationale: Constitution §3/§4 require bounded agency. The three operations are either append-only (progress events), reversible (task status), or explicitly escalating (flag). They cannot produce irreversible harm.

+

D5 — LLM response format: JSON block in markdown

+

Decision: The LLM is prompted to return a Markdown response with a fenced ```json block containing the structured action plan:

+
{
+  "observations": ["..."],
+  "progress_events": [
+    {"summary": "...", "workstream_id": "...", "event_type": "note"}
+  ],
+  "tasks_to_update": [
+    {"task_id": "...", "status": "done"}
+  ],
+  "tasks_to_flag": [
+    {"task_id": "...", "note": "..."}
+  ]
+}
+

The surrounding Markdown is preserved as a human-readable reasoning trace and written to memory/working/ as a session note.

+

Rationale: JSON blocks are robust to extraction (delimited), LLMs produce them reliably with clear instructions, and the surrounding prose gives Bernd an auditable reasoning trace without requiring a separate reasoning step.

+
+

Consequences

+
  • runtime/ becomes a standalone Python package; make agent-run DOMAIN=x invokes it.
  • The runtime has no DB schema changes and no new API endpoints — it is a pure client of the existing state-hub HTTP API.
  • Autonomous actions are limited to append-only writes and escalations. Any expansion of the action surface requires a new ADR and human approval.
  • The v0.1 loop is single-user (Bernd). Multi-agent expansion is Phase 2+.
+
+

Deferred

+
  • Async event loop / daemon mode (Phase 2)
  • RAG over canon (Phase 1 roadmap item)
  • Tool adapters beyond state-hub HTTP (planned in runtime/tool_adapters/)
  • Deployment on Railiance k3s as a scheduled CronJob
+
CUST-ADR-002 · accepted-1 · acceptedthe-custodian · canon/architecture/adr-002-custodian-agent-runtime-design.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-003.html b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-003.html new file mode 100644 index 0000000..dfc62ab --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-003.html @@ -0,0 +1,248 @@ + + + + +Materialized Derived State with Fingerprint Invalidation for Repo-Sourced Data + +
CUST-ADR-003 accepted · accepted-2 the-custodian reviewed 2026-08-31generated from canonical source — do not edit

Materialized Derived State with Fingerprint Invalidation for Repo-Sourced Data

Source: the-custodian · canon/architecture/adr-003-materialized-derived-state.md · d3c6f13d7aed9784b6f31c23d2b2777668493ba5

Review due: 2027-02-28

Status

+

Accepted, and partially superseded by ADR-012 (accepted 2026-08-25). Decision 2's fingerprint composition is invalidated in part; decision 5's rebuild principle is given a concrete source and a required operation. See the notes on each.

+
+

Context

+

The Custodian State Hub is a read model (CQRS terminology) — its data is fully derivable from canonical sources that live in repositories and the filesystem. No state-hub data is authoritative; it is always a derived view of what the repos contain.

+

Several categories of data fit this description:

+
DataCanonical sourceState-hub table
SBOM dependenciesuv.lock, package-lock.json, etc.sbom_entries
Third-party service declarationstpsc.yamltpsc_entries
Provided capabilitiesSCOPE.md capability blockscapability_catalog
DoI compliance tier14 criteria across repo files + DBdoi_cache
Workplan task statusworkplans/*.mdtasks
+

Early implementations either recomputed this data on every request (too slow) or ingested it once without invalidation (stale data goes undetected). Neither is acceptable for a system designed to give accurate, fast orientation.

+

The doi_cache table, introduced in CUST-WP-0024, demonstrated a pattern that solves both problems. This ADR formalises that pattern and mandates its use for all repo-sourced derived data.

+
+

Pattern Name

+

Materialized Derived State with Fingerprint Invalidation.

+

This pattern is known under several names in the literature:

+
  • Materialized View (SQL standard, PostgreSQL) — the stored result of a query or computation, refreshed on demand when source data changes.
  • Derived Data Store (Kleppmann, Designing Data-Intensive Applications, Ch. 3 & 11) — a system whose entire dataset can be rebuilt from upstream sources; it is never the source of truth.
  • Read Model / Projection (CQRS / Event Sourcing) — a pre-computed view maintained alongside a write model, rebuilt when relevant events occur.
  • Fingerprint-based / Content-addressed invalidation — analogous to HTTP ETags: a cache entry is valid as long as a composite hash/timestamp of its inputs matches the stored value.
+

The State Hub already documents itself as a read model. This ADR extends that principle to specify how the read model stays fresh.

+
+

Decision

+

1. All repo-sourced derived data MUST be materialised in the DB

+

Data computed from repository files or repo records must be stored in a dedicated table rather than recomputed per request. Direct computation on every API call is only permissible for development tooling or when explicitly forced by the caller.

+

2. Each materialised table MUST carry a fingerprint column

+

The fingerprint is a deterministic string encoding all inputs that affect the computed result. It is compared on each read; if unchanged, the stored result is returned without recomputation. If changed, the result is recomputed and the stored value is updated.

+

Fingerprint composition rules:

+
  • Include the updated_at timestamp of every DB record that feeds the computation (repo record, related domain, goals, snapshots).
  • Include the mtime (filesystem modification time) of every file that feeds the computation (SCOPE.md, CLAUDE.md, lockfiles, tpsc.yaml, etc.).
+

Invalidated in part 2026-08-25 by ADR-012 decisions 1 and 2. Filesystem mtime is not a property of the source. It differs between machines, changes on a fresh clone, and says nothing about content — so a fingerprint built from it describes one workstation's filesystem rather than the repository. Under ADR-012 the projection derives from the forge, and the commit that produced a record is both the correct input and the auditable one. This was not merely theoretical drift. git_fingerprint for the-custodian held the repository's initial commit while last_state_synced_at was minutes old: the field meant to identify what a projection reflects was wrong by the entire history of the repository, and nothing noticed. Replace mtime inputs with the source commit.

+
  • Join all components with | as a pipe-separated string — no hashing needed since the string is compared by equality, not transmitted to clients.
  • If a file is absent, encode filename:absent rather than omitting it, so file creation also triggers invalidation.
+

Reference implementation: state-hub/api/doi_engine.py::compute_fingerprint()

+

3. Every materialised endpoint MUST support ?force_refresh=true

+

Callers must always be able to bypass the cache and trigger a fresh computation. This is the escape hatch for debugging, post-ingest verification, and scheduled background refresh jobs.

+

4. Writes to source data SHOULD update the repo record's updated_at

+

Operations that change source data (SBOM ingest, TPSC ingest, capability ingest) must ensure managed_repos.updated_at is refreshed so the fingerprint detects the change on the next read. Where data lives in a related table (e.g. tpsc_snapshots), the fingerprint must include that table's max(snapshot_at) directly rather than relying on the repo record.

+

5. The DB is never the source of truth — the rebuild principle holds

+

Per ADR-001, the state-hub must be rebuildable from scratch by re-ingesting all canonical sources. Materialised tables are caches, not records of authority. They may be wiped and repopulated at any time without data loss.

+

Given concrete form 2026-08-25 by ADR-012 decision 7. This principle was correct and, until now, never exercised — an untested rebuild path is an assumption rather than a capability, and this one was believed for long enough that a divergence survived seven weeks behind it. ADR-012 requires the reconstruction to exist as a routine operation, scoped per repository, sourced from the forge, and verifiable against it. The claim "without data loss" also needs its precondition stated: it holds only while the rule immediately below does. On 2026-08-25, 111 work records existed only in the hub, so a rebuild at that moment would have destroyed them. ADR-012 therefore requires reset to refuse, per repository, when records have no counterpart in the forge.

+

This means:

+
  • No materialised table may be the only copy of any information.
  • Schema migrations that wipe a materialised table are safe and expected.
  • Background jobs that periodically re-ingest all repos are valid and encouraged.
+
+

Consequences

+

Positive

+
  • Fast reads in steady state — after the first computation, subsequent reads hit the DB with no filesystem or subprocess overhead.
  • Accurate on change — fingerprint invalidation ensures stale data is never silently served; the cache refreshes exactly when needed.
  • Debuggable — force_refresh=true and checked_at timestamps make it easy to see when a value was last computed and to trigger a recheck.
  • Consistent with the read model principle — the pattern makes explicit what was always implied: state-hub data is derived, not authoritative.
+

Negative / Trade-offs

+
  • First-call latency — cache misses are expensive (filesystem reads, subprocess calls, HTTP self-calls). Mitigated by pre-warming caches at startup or after ingest.
  • Fingerprint completeness — if a new input is added to a computation and not added to the fingerprint, stale results will be silently returned. The fingerprint must be kept in sync with the computation.
  • Filesystem dependency — file mtimes are volatile (e.g. git checkout rewrites mtimes). In practice this means a cache miss after every checkout, not a correctness problem.
+
+

Implementation Checklist

+

When adding a new category of repo-sourced derived data:

+
  • [ ] Create a _cache or _snapshots table with fingerprint and checked_at columns.
  • [ ] Implement compute_fingerprint(repo, ...) in the relevant module.
  • [ ] Add ?force_refresh=true query parameter to the read endpoint.
  • [ ] Ensure the ingest script (or write path) touches managed_repos.updated_at or includes a related table's max(timestamp) in the fingerprint.
  • [ ] Verify the cache can be wiped and repopulated without data loss.
  • [ ] Document which inputs are included in the fingerprint in a comment alongside compute_fingerprint.
+
+

Current Implementations

+
Derived dataTableFingerprint inputsForce-refresh
DoI compliance tierdoi_cacherepo.updated_at, max(tpsc_snapshots.snapshot_at), max(repo_goals.updated_at), mtime(SCOPE.md), mtime(CLAUDE.md), mtime(tpsc.yaml)?force_refresh=true
+
+

Planned Applications

+
Derived dataTable (proposed)Notes
SBOM summary statssbom_cacheFingerprint: max(sbom_snapshots.snapshot_at)
Capability declarationscapability_cacheFingerprint: mtime(SCOPE.md), repo.updated_at
Workplan status summaryAlready handled by consistency checkerFingerprint: workplan file mtimes
+
+
CUST-ADR-003 · accepted-2 · acceptedthe-custodian · canon/architecture/adr-003-materialized-derived-state.md · d3c6f13d7aed9784b6f31c23d2b2777668493ba5
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-004.html b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-004.html new file mode 100644 index 0000000..753df7a --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-004.html @@ -0,0 +1,236 @@ + + + + +Connectivity-First Network Posture for Custodian Infrastructure + +
CUST-ADR-004 accepted · accepted-1 the-custodian reviewed 2026-03-26generated from canonical source — do not edit

Connectivity-First Network Posture for Custodian Infrastructure

Source: the-custodian · canon/architecture/adr-004-connectivity-first-network-posture.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb

Review due: 2026-09-26

Status

+

Accepted.

+
+

Context

+

The Custodian infrastructure spans multiple machines: a primary workstation, a shared compute node (COULOMBCORE), and Railiance cluster nodes. Agents running on remote machines need to reach the state-hub API and MCP server, which live on the workstation. Human operators and agents also need to reach remote services (k3s API, Gitea, Temporal) from the workstation.

+

Two network postures were considered for how these components communicate:

+

Option A — Connectivity-first: Components are connected by default via controlled, observable access paths (reverse SSH tunnels managed by ops-bridge). Isolation is added selectively where there is a specific threat model or compliance reason to do so.

+

Option B — Isolation-first (zero-trust): No component trusts any other by default. Every connection requires mutual authentication, short-lived credentials, and explicit authorisation at the point of use. Connectivity is earned, not assumed.

+

This decision is architectural policy — it governs how ops-bridge tunnels are designed, how agent-to-hub communication works, and how new infrastructure components are onboarded.

+
+

Decision

+

Connectivity-first, with isolation as a deliberate option.

+

The default posture for Custodian infrastructure is: components that need to work together are connected. Access paths are explicit, observable, and managed (via ops-bridge), but they are persistent by default rather than ephemeral. Isolation is introduced where there is a specific, articulated reason — not as a blanket policy applied uniformly.

+
+

Rationale

+

1. Scale and team size

+

The infrastructure is operated by a single human and a bounded set of automation agents. The overhead of zero-trust (credential rotation, SPIFFE/SPIRE identity, mTLS everywhere, short-lived tokens per request) is disproportionate for this scale. It would consume significant operational complexity without a commensurate security return.

+

2. Observability over perimeter hardening

+

The primary security control at this scale is observability: knowing what connected, when, from where, and what it did. ops-bridge provides this — every tunnel is named, actor-attributed, health-checked, and audited. A perimeter of invisible short-lived connections would actually reduce observability.

+

3. The threat model does not require zero-trust today

+

The main threats are:

+
  • A runaway agent consuming resources (mitigated by nproc/memory cgroups)
  • A compromised workload reaching state-hub and corrupting state (mitigated by the read-model design of state-hub — write surface is narrow and sanctioned)
  • An external attacker reaching internal services (mitigated by the tunnels being reverse SSH — no inbound ports exposed)
+

Zero-trust would address a different threat model: lateral movement between hostile tenants, or untrusted code running in the same environment as sensitive data. That is not the current situation.

+

4. Degrade-gracefully requires persistent connectivity

+

The Custodian's foundational value of local-first, degrade-gracefully requires that agents can orient themselves even when some connections are slow or partially degraded. Ephemeral connectivity (zero-trust tokens, service mesh sidecars) introduces additional failure modes that conflict with graceful degradation. Persistent SSH tunnels with auto-reconnect are more resilient to intermittent conditions.

+

5. Isolation remains the right choice in specific cases

+

Connectivity-first does not mean no isolation. The following cases call for explicit isolation and are handled separately:

+
  • Tenant separation (when/if multi-user or multi-org) — each tenant gets its own network segment
  • Privileged execution — CI runners and agent actions with write access to production systems run in ephemeral, isolated environments (per the Privileged Execution Control standard)
  • Secrets — credentials are never transmitted over tunnels in plaintext; age-encrypted at rest, SOPS for config
+
+

Consequences

+

Immediate

+
  • ops-bridge tunnels are persistent (max_attempts: 0, auto-reconnect) and are treated as infrastructure, not one-off connections
  • Agents on remote machines check tunnel health at session start and restore dropped tunnels before accessing state-hub (documented in global CLAUDE.md)
  • New infrastructure components are onboarded with a named tunnel entry in ~/.config/bridge/tunnels.yaml — not ad-hoc SSH commands
+

Deferred

+
  • If the infrastructure grows to multi-tenant or multi-operator, this decision should be revisited. At that point, isolation-first becomes proportionate.
  • If a security audit identifies a specific lateral movement risk, targeted isolation (network policy, mTLS for that service) is the response — not a wholesale posture change.
+
+

Alternatives Rejected

+

Zero-trust / isolation-first

+

Rejected for current scale. The operational overhead (credential lifecycle, service mesh, mutual TLS) is disproportionate, observability would decrease, and the threat model does not require it. Noted for re-evaluation at multi- tenant scale.

+

VPN (WireGuard / Tailscale)

+

Considered briefly. VPN would solve the connectivity problem but introduces a persistent network layer that all traffic traverses, reducing the explicitness of individual access paths. ops-bridge tunnels are per-service and per-actor, which gives better observability and blast-radius control. VPN is not ruled out as a future complement but is not the primary approach.

+

Ad-hoc SSH (no ops-bridge)

+

The pre-ops-bridge approach. Rejected because it has no health checks, no actor attribution, no audit log, and requires manual intervention to restore. ops-bridge formalises the same SSH tunnel pattern with operational discipline.

+
CUST-ADR-004 · accepted-1 · acceptedthe-custodian · canon/architecture/adr-004-connectivity-first-network-posture.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-005.html b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-005.html new file mode 100644 index 0000000..cb8edec --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-005.html @@ -0,0 +1,225 @@ + + + + +Cross-Repo Workplans Live in Dedicated Project Repos + +
CUST-ADR-005 accepted · accepted-1 the-custodian reviewed 2026-06-22generated from canonical source — do not edit

Cross-Repo Workplans Live in Dedicated Project Repos

Source: the-custodian · canon/architecture/adr-005-cross-repo-workplans-project-repos.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb

Review due: 2026-12-22

Status

+

Accepted.

+
+

Context

+

ADR-001 established that workplans and work items originate as files in the repository that owns them, so the State Hub can rebuild its coordination state from repo-owned files alone. The repo-classification redesign (CUST-WP-0050) takes the next step: it makes the repo the primary anchor for a workplan (workstreams.repo_id becomes required) and derives the market-domain from the repo's .repo-classification.yaml rather than maintaining a separate topic/domain spine. Repos are the most stable, git-managed entities in the ecosystem; binding to them is the most durable anchor available.

+

This raises an unavoidable question: what anchors a genuinely cross-repo workplan? Some efforts coordinate change across many repositories — ecosystem migrations, the FOS hub bootstrap (CUST-WP-0025), or CUST-WP-0050 itself, which touches ~70 repos. If every workplan must bind to exactly one repo:

+
  • binding it to one arbitrary product repo misrepresents the work and pollutes that repo's history with coordination it does not own;
  • leaving it unbound reintroduces the hub-only orphan that ADR-001 forbids;
  • modelling it as an array of repo_ids breaks the "one stable anchor, clear ownership, clean lifecycle" property and complicates the rebuild principle.
+
+

Decision

+

**A complex cross-repo workplan gets its own dedicated project repo.**

+
  • The project repo is a real, git-managed repository. It owns the coordination workplan, its tasks, its decisions, and any cross-cutting artefacts. It is the required repo anchor for that workplan, satisfying the repo-primary-anchor rule without distorting any single product repo.
  • The project repo is classified under the Repo Classification Standard, normally category: project. Its domain/tags describe the effort, not any one product.
  • Implementation still happens in the product repos. Changes land via per-repo workplans and PRs in the repos being modified. The project repo coordinates and references that work (via dependency edges / links); it does not own product code.
  • On completion, the project repo is retired to archive — not deleted. Its durable results live on in the product repos it modified (the merged changes are the outcome). The archived project repo remains as an immutable provenance record of the coordination, consistent with the append-only-memory value.
+

The project repo's completion record MUST list the product repos it modified and link to the merged PRs/commits, so the trail survives archival.

+
+

Lifecycle

+
draft → active → completed → archived
+
  • active — work in progress; workplan status: active; repo live in Gitea and registered in the Hub.
  • completed — all tasks done; completion record written (modified repos + links).
  • archived — repo archived in Gitea and status: archived in the Hub. The workplan moves to workplans/archived/ per the workplan convention. Results persist in the product repos; the project repo is read-only history.
+
+

Naming

+

Project repos MUST use the prj-<kebab-slug> prefix.

+

Exact naming, required files (GOAL.md instead of durable-product INTENT.md), authority boundary, residual handoff, and archive procedure are defined in:

+

canon/standards/project-repository-flavor_v0.1.md

+

That standard closes the naming open question previously deferred to CUST-WP-0050 (D1a). The illustrative proj- form mentioned in earlier drafts is not used for new repositories.

+
+

Consequences

+
  • Pro: every workplan — including cross-repo ones — has a stable, git-managed anchor; no hub-only orphans; the rebuild principle (ADR-001) holds.
  • Pro: the classification standard applies uniformly; project repos are just repos with category: project.
  • Pro: clean, explicit lifecycle; results are never lost on retirement because they live in the modified product repos.
  • Con: proliferation of short-lived repos; requires discipline around the naming and archival convention.
  • Con: cross-references between the project repo and the product repos it modified must be recorded deliberately, or the provenance trail degrades after archival.
  • Con: judgement is required on when an effort is "complex enough" to merit a project repo versus a single-repo workplan; small cross-cutting changes should not spawn a repo.
+
+

Alternatives Considered

+
  • Bind to a "lead" product repo. Rejected: distorts that repo's history and creates ambiguous ownership.
  • Keep an optional hub-only topic for cross-repo coordination. Rejected: reintroduces the soft, non-git-managed spine that CUST-WP-0050 removes and ADR-001 discourages.
  • Multi-anchor workplan (array of repo_ids, no primary). Rejected: breaks single-anchor simplicity, ownership clarity, and lifecycle modelling.
+
+
CUST-ADR-005 · accepted-1 · acceptedthe-custodian · canon/architecture/adr-005-cross-repo-workplans-project-repos.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-006.html b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-006.html new file mode 100644 index 0000000..3d013bf --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-006.html @@ -0,0 +1,253 @@ + + + + +Canon Federation and Concept Ownership Across InfoTech and Commerce + +
CUST-ADR-006 accepted · accepted-1 the-custodian reviewed 2026-08-17generated from canonical source — do not edit

Canon Federation and Concept Ownership Across InfoTech and Commerce

Source: the-custodian · canon/architecture/adr-006-canon-federation-concept-ownership.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb

Review due: 2027-02-17

Status

+

Accepted 2026-08-17. All seven ownership questions are resolved (see Resolutions); content may now move under CFED-WP-0001.

+
+

Context

+

The ecosystem currently runs two independent canons in the same market domain (infotech), with no cross-reference in either direction:

+
  • info-tech-canon — InfoTechCanon, v0.6.0, status service-baseline. Kernel + 12 models + 3 standards, with declared concept ownership per model, an orthogonality rule ("standards can import but not redefine each other"), and a live CLI/JSON/API surface built on infospace-bench.
  • identity-canon — documentation-only research repo, category research. Three IDENTITY-WP-* workplans, all finished; no active work since the commercial-identity research pass. ~60 concepts in canon/CanonicalGlossary.md.
+

Two problems follow.

+

Concept-ownership collision. InfoTechCanonOrganizationModel (:55) declares ownership of Actor, Person, Organization, OrganizationalUnit, Team, Group, Role, Position, Membership, Assignment, Responsibility, Authority, Accountability. InfoTechCanonAccessControlModel (:106) declares Subject, Principal, AccessRole, Permission, .... identity-canon independently defines Actor, Natural Person, Collective Actor, Organization, Group, Role, Membership Relationship, Authenticated Subject, Authorization Principal. Two canons claim the same concepts — exactly what InfoTechCanon's orthogonality rule exists to prevent.

+

An unowned gap. InfoTechCanonAccessControlModel (:214, "Boundary with Identity and Authentication") explicitly pushes identity provisioning, authentication factors, identity proofing, and account lifecycle out of scope, and :41 does the same for generic organization modelling. So Account, Identity Record, Identifier, Credential, Claim, Persona, Tenant, Realm, Synonymity Assertion, Assurance Level belong to no model at all.

+

Business semantics are accumulating in the wrong places. Roughly a third of identity-canon's glossary is not identity but counterparty/commercial modelling (Legal Entity, Beneficial Owner, Customer, Vendor, Commercial Commitment, Payment Mandate, Pipeline Pursuit, Counterparty Assurance Gradient, ...) — hence the repo's government secondary domain. Independently, info-tech-canon/demand/CapabilityProvisionEconomics.md (status accepted, 2026-08-15) records procurement and economics demand arriving in ITC-CAP from consumer resource-control, domain financials. Two unrelated donors pushing commercial semantics into technical canon is a domain boundary, not a coincidence. InfoTechCanon's own Purpose/Demand extension names this ScopePressure.

+
+

Decision

+

1. Three canons, federated by declared ownership.

+
CanonOwnsRepo
Custodian canonecosystem-normative governance: constitution, values, standards, ADRs, chartersthe-custodian/canon/
InfoTechCanonsemantics of information-processing systemsinfo-tech-canon
CommerceCanoncounterparty and commercial-relationship semanticscommerce-canon
+

Canons import but do not redefine each other's concepts, applying InfoTechCanon's existing orthogonality rule one level up.

+

2. identity-canon is renamed to commerce-canon, in place. Git history, State Hub registration, .repo-classification.yaml, and the finished IDENTITY-WP-* workplans are retained as provenance for the commercial content that stays. Identity content emigrates; nothing is archived. Structure follows info-tech-canon (canon.yaml, infospace/ with kernel/models/standards, assimilation/, mappings/, profiles/) per InfoTechCanonRepositoryLayoutStandard.

+

3. Identity becomes an InfoTechCanon model, at infospace/models/identity/InfoTechCanonIdentityModel.md (itc-ident), importing rather than redefining upstream concepts. This fills the gap InfoTechCanonAccessControlModel:214 leaves open.

+

4. Concept ownership is assigned as follows.

+

Imported by itc-ident, owned by itc-org: Actor · Natural Person → Person · Artificial Agent → Agent · Organization · Group · Role · Membership Relationship → Membership

+

Imported by itc-ident, owned by itc-access: Authenticated Subject → Subject · Authorization Principal → Principal

+

This follows identity-canon's own design principles: P1 makes Actor the participation root (owned upstream by itc-org), and P6 "Keep Authorization Projections Separate" already treats subject/principal as projections rather than identity-owned definitions.

+

Owned by itc-ident (new): Account · Service Account · Identity Record · Identifier · Scoped Identifier · Pseudonymous Identifier · Credential · Claim · Profile · Persona · Tenant · Realm · Synonymity Assertion · Lifecycle State · the actor-linking relationship taxonomy (Relationship, Affiliation, Following, Representation, Delegation, Administration, Trust) · convenience terms User, Subscriber

+

Owned by commerce-canon: Legal Entity · Legal Person · Beneficial Owner · Beneficial Ownership Relationship · Beneficial Ownership Exemption · Customer · Vendor · Commercial Relationship · Commercial Commitment · Payment Instrument Reference · Payment Mandate · Pipeline Pursuit · Commercial Record · Counterparty Assurance Gradient · Reputation Signal · Performance Evidence · Registry Identifier · Proxy Commercial Identifier · convenience terms Reputation, Customer Account

+

Owned by itc-evid, the evidence model (see R3, R5, R7): Evidence · Evidence Source · Adjudication Outcome

+

Identifier subtypes demonstrate the intended pattern: itc-ident owns Identifier; commerce-canon owns Registry Identifier and Proxy Commercial Identifier as specializations of it.

+

5. CommerceCanon grows by demand signal, not speculative authoring. New content enters through the mechanism InfoTechCanon already runs — a demand signal with named consumer evidence (see demand/). Plausible future consumers (fin-hub, target-revenue, adaptive-pricing, qonto-assistant) must pull; the canon does not push.

+

6. The Federated Organization Standard stays in Custodian canon. canon/standards/federated-organization-standard_v1.0.md is ecosystem-normative organizational architecture, human-gated — not commercial vocabulary. If it ever moves, it becomes an InfoTechCanon organization standard, not a CommerceCanon one.

+
+

Resolutions

+

The six collisions listed at draft time, resolved 2026-08-17. Two were settled by evidence already present in the models rather than by argument.

+

R1 — Scope: owned by itc-ident. There is no head-on collision: itc-access owns ResourceScope (:717, "the boundary within which access applies"), a narrower concept, not a general Scope. itc-ident owns the general concept, keeping it with Tenant and Realm — Tenant is defined as "an administrative or isolation scope", so separating it from its genus would split a definition from the concept it depends on. itc-access keeps ResourceScope as a refinement.

+

If landscape or information-space later need general scoping, promote Scope to the kernel on that demand signal, not pre-emptively.

+

R2 — Assurance Level: owned by itc-ident, distinct from governance assurance. A false collision. itc-gov owns AssuranceCase (:1178, a structured argument that a claim is justified) and AssuranceConclusion (:1184). identity's Assurance Level is NIST SP 800-63-4 IAL/AAL/FAL — graded confidence metadata on credentials, bindings, and federation assertions. They share an English word and nothing else. Both models carry a disambiguation note, because the word will keep causing this.

+

Design principle P12 ("Distinguish Assurance Dimensions") carries over: IAL, AAL, and FAL must not be collapsed into a single "trust level" on an account.

+

R3 — Evidence and Evidence Source are a general pair, owned together, and not by commerce. They are not competing definitions of one concept:

+
  • Evidence Source — an addressable information container: a document, file, or other artifact identifiable by URI.
  • Evidence — a distinct information item, textual or descriptive, drawn from a source: a quotation, an extracted value, a specific assertion.
+

Both may carry commentary. Which evidence is captured from a source depends on the interest being served.

+

Worked example: an invoice PDF is an Evidence Source; the amount, the issuer, and the due date are separate Evidence items within it. The common electronic- invoicing pattern of an XML embedding inside a signed PDF is exactly this structure — evidence pre-extracted and bound to its source so the extraction is itself tamper-evident.

+

The pair is domain-neutral (it extends to criminal, regulatory, and scientific evidence). Commerce, identity, and governance all use it; none owns it. Consequently itc-gov no longer owns Evidence; it imports it.

+

R4 — Relationship Tuple: owned by itc-access. Already modelled there (:549, under PolicyEvaluationEntity beside AuthorizationRequest, AuthorizationDecision, DecisionReason, EvaluationContext). identity-canon's own entry agrees: "Relationship tuples are not canonical identity roots. They project from actors, accounts, memberships, and delegations into authorization domains." itc-ident must not define it.

+

R5 — Adjudication Outcome: follows R3, owned with the evidence pair. Not itc-access AuthorizationDecision (a PDP allow/deny, :907) and not itc-gov Decision (a governance choice point). The concept is general rather than commercial: an arbitration award, court judgment, or regulatory consent order is evidence in employment, licensing, or compliance disputes as much as in commercial ones. Commerce is a consumer, not the owner.

+

Structurally it is Evidence — the outcome asserted — sourced from an Evidence Source such as the judgment document.

+

The assurance_tier dimension splits accordingly: the evidence model owns a general evidence-strength dimension; commerce-canon owns the Counterparty Assurance Gradient as its named four-tier application of it.

+

R6 — Community and Household extend itc-org; Family is a separate concept area. identity-canon defines "Family Or Household" as one entry. That conflation is rejected.

+

Community and Household are collective actors and slot under itc-org's existing CollectiveActor (:363, beside Person, HumanActor, NonHumanActor), honouring P4 ("Model Collective Actors Without Collapsing Them").

+

Family does not. Family carries substantial structure — kinship, guardianship, dependency, care, and legal, biological, and social parenthood — which changes over time and is subject to interpretation. Modelling it as one more collective actor is the specific mistake most family-oriented software makes, and it is why such software generally models families badly. It gets its own concept area.

+

Scope discipline applies: the family area is seeded, not authored. Record the concept, the privacy sensitivity already flagged in identity-canon ("may have legal implications outside the canon's scope"), and the open modelling questions. Do not build it out inside CFED-WP-0001; it grows on demand signal like any other canon content.

+

R7 — the evidence pair lives in a dedicated model, itc-evid. A new InfoTechCanon model at infospace/models/evidence/ owns Evidence, Evidence Source, Adjudication Outcome, and the general evidence-strength dimension.

+

itc-gov, itc-ident, and commerce-canon import it. Three named consumers existed before the model did, which is the demand signal the canon requires.

+

Rejected alternative: leaving both with itc-gov as incumbent owner of Evidence. That is cheaper and preserves the locality of the Policy-Control-Evidence chain pattern (:1391), but it would force identity and commerce to import "governance" in order to describe an invoice line item — mis-signalling evidence as a governance sub-topic when it is domain-neutral.

+

itc-gov retains AssuranceCase, AssuranceConclusion, Audit, and the Policy-Control-Evidence pattern, now expressed over imported evidence concepts.

+
+

Consequences

+

Positive. Every concept gains exactly one owner. The identity gap that itc-access explicitly declines is filled. Commercial semantics get a home before they accrete further into technical canon. A dormant research repo with no inbound references becomes a canon with declared consumers. A second canon tests whether InfoTechCanonRepositoryLayoutStandard is a real standard or merely InfoTechCanon's own shape described back to itself.

+

Negative. This is a concept-ownership reconciliation, not a file move: the first ~15 glossary entries must be rewritten as imports. Three canons cost more coordination than one. The identity-canon name disappears from tooling, bookmarks, and any external reference.

+

Risks. CommerceCanon could repeat identity-canon's failure mode — dormant, zero consumers, drifting — if it launches as a scaffold. Mitigated by decision 2 (it opens holding real, research-backed content) and decision 5 (growth requires consumer evidence).

+
+

References

+
  • ADR-001 — workplans originate as repo files; hub is a read model
  • ADR-005 — cross-repo workplans live in dedicated project repos
  • info-tech-canon/infospace/models/organization/InfoTechCanonOrganizationModel.md:55
  • info-tech-canon/infospace/models/access-control/InfoTechCanonAccessControlModel.md:106, :214
  • info-tech-canon/infospace/models/governance/InfoTechCanonGovernanceModel.md:107
  • info-tech-canon/demand/CapabilityProvisionEconomics.md
  • identity-canon/canon/CanonicalGlossary.md, canon/DesignPrinciples.md
+
CUST-ADR-006 · accepted-1 · acceptedthe-custodian · canon/architecture/adr-006-canon-federation-concept-ownership.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-007.html b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-007.html new file mode 100644 index 0000000..efbc315 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-007.html @@ -0,0 +1,279 @@ + + + + +Workplan Identity Uniqueness, Single Registrar, and Repo Worker Topology + +
CUST-ADR-007 accepted · accepted-2 the-custodian reviewed 2026-08-31generated from canonical source — do not edit

Workplan Identity Uniqueness, Single Registrar, and Repo Worker Topology

Source: the-custodian · canon/architecture/adr-007-workplan-identity-and-repo-worker-topology.md · d3c6f13d7aed9784b6f31c23d2b2777668493ba5

Review due: 2027-02-28

Status

+

Accepted 2026-08-17. Identifier uniqueness, the registrar model, lifecycle protection, and worker topology are settled.

+

Remediation of existing collisions (§ Migration) remains an open ruling. It is disruptive, touches six repositories, and no active work depends on it — all five duplicated identifiers are finished.

+
+

Context

+

On 2026-08-16, STATE-WP-0080 was found registered twice, in two different databases, with two different workplan UUIDs and two disjoint sets of task UUIDs:

+
RegistrarWorkplan UUIDReachable at 127.0.0.1:8000
Workstation hub (make api, local postgres)03f38314yes
A second instance, over a second databasebbfce36a404
+

The second instance is not identifiable from the commit author. custodian-sync@railiance.local is a hardcoded default git identity in scripts/consistency_check.py:1899 (GIT_SYNC_USER_NAME / GIT_SYNC_USER_EMAIL), so every machine running fix-consistency commits under that name. The discriminator is the timezone: sync commits appear under both +0000 and +0200 (the workstation's offset), which is independent evidence of two machines writing to one repository.

+

Both write their IDs into the same git-tracked workplan file, so each sync overwrites the other's IDs and the file flip-flops on every round trip. The same commit (ff909e1, "renormalize lifecycle state [auto]") also promoted the workplan proposed → active without human review.

+

A fleet scan of 955 hub workplans (525 with parseable PREFIX-WP-NNNN identifiers) found this is not an isolated incident. Two distinct identity defects are live:

+

Prefix reuse across repositories:

+
CUST-WP-      → state-hub, the-custodian
+RAIL-BS-WP-   → railiance-bootstrap, railiance-cluster
+RAILIANCE-WP- → railiance-apps, railiance-forge, railiance-platform, railiance-telemetry
+

PRJ-WP- is a latent fourth: statehub register derives it from the prj- flavor marker, so every project repository would collide (see RMGR-WP-0004).

+

Running-number reuse:

+
CUST-WP-0000       the-custodian ×2
+CUST-WP-0010       the-custodian ×2
+CUST-WP-0045       the-custodian ×2
+RAILIANCE-WP-0015  railiance-platform, railiance-apps
+RAILIANCE-WP-0016  railiance-platform ×2, railiance-apps
+

RAILIANCE-WP-0015 is actively cited in operational memory as the cnpg-backup gate in railiance-apps; a second record of the same name in railiance-platform makes that citation ambiguous.

+

The C-26 consistency check already flags prefix nonconformance within a repo against a canonical prefix, but nothing enforces uniqueness across repos, and nothing prevents number reuse.

+
+

Decision

+

1. A workplan identifier is globally unique. PREFIX-WP-NNNN names exactly one workplan across the entire fleet, for all time.

+

Amended 2026-08-17 by ADR-011 decision 2. Uniqueness and forward-only allocation are namespace-scoped, not global; global identity is the pair (namespace, identifier), written PREFIX-WP-NNNN@namespace when foreign. Global sequential allocation would require a central coordinator — the exact dependency federation must survive. Everything below holds unchanged within a namespace, which is where all current work sits.

+
  • A workplan prefix is owned by exactly one repository. No two repositories may use the same prefix.
  • A running number is never reused within a prefix, including after a workplan is cancelled, archived, or deleted. Numbers are allocated forward only.
  • Prefixes are derived from the project or repository identity, never from a flavor marker or category (PRJ-WP- is invalid by construction).
+

2. Hub identifiers stored in repository files must be derivable, not database-local. The defect is structural: a database-local key is stored in a shared artifact, so each database overwrites the other's value on every sync. It also inverts ADR-001 — a file carrying a hub's private key is the file holding hub state.

+

Target state (C2). state_hub_workstream_id and state_hub_task_id become deterministic: UUIDv5 derived from the workplan identifier. Per ADR-011 decision 3 the derivation input is the pair (namespace, identifier), not the identifier alone — deriving from the identifier alone would make two forks holding unrelated work under the same number compute the same UUID. Every instance computes the same value independently, writeback becomes idempotent, and any number of hub instances may coexist without coordination. The field shape is unchanged, so consumers keep working; only the provenance of the value changes.

+

This has a hard prerequisite: deterministic derivation from a non-unique identifier would manufacture collisions rather than remove them. Two project repos sharing PRJ-WP- would compute the same UUID for different workplans. Decision 1 must therefore be enforced before derivation ships.

+

Interim state (A). Until derivation lands, exactly one instance writes hub identifiers into repository files. Other instances may read, project, and serve, but must not mint workplan or task UUIDs into git-tracked files.

+

Corrected 2026-08-17, superseded by ADR-010 decisions 1–3. This decision originally described workstation hubs as "development read replicas". That was wrong on both counts: the workstation instance was not a replica, and it was the larger of the two, holding 306 more workplans than the primary. The two instances were peer databases. ADR-010 establishes the central hub as authoritative and local instances as rebuildable caches, which is what makes this interim rule coherent.

+

The interim is policy, enforced by discipline, and it has a real cost: registration requires connectivity to the registrar, so disconnected work cannot register. That cost is accepted only until C2 removes the need for it, at which point the number of hub instances becomes an availability choice rather than a correctness constraint.

+

Rejected. Collapsing to a single shared database (workstation hubs pointing at the production database) would also make identifiers consistent by construction, but it eliminates offline capability — cutting directly against STATE-WP-0068 (offline write buffer and edge relay) — and couples all local work to tunnel availability.

+

Ownership. Both the interim guard and the derivation belong to repo-manager under decision 747011c6, which already places file-backed record indexing and reconciliation there. Building either in state-hub would invest in a component being retired under STATE-WP-0079.

+

Migration scope for C2: 758 workplan files across the fleet currently carry these fields.

+

Amended 2026-08-21 — derivation scope for C2. ADR-011 decision 7 keys derivation on (namespace, identifier), which separates forks. It does not separate collisions inside one namespace, and the ecosystem's posture is N1 — a single implied namespace. A fleet scan on 2026-08-21 found 20 reused identifiers across 48 files, all within that one namespace, so the prerequisite above is not satisfied by ADR-011 alone. Ruled: C2 derives only for live records; archived records keep their minted identifiers, frozen. This is what reconciles § Migration option 2 — under which historical files keep colliding identifiers — with the uniqueness derivation requires. Of the 20, only five collide among live files, so the remediation surface is 11 files rather than 48. Two consequences follow, and both are load-bearing: 1. Un-archiving a record with a frozen identifier is a collision hazard. A record returning to live status must be checked against the live namespace before it is re-derived, and renumbered if it clashes. 2. Derivation is not retroactive. Existing live records keep their minted UUIDs until they are re-derived deliberately; C2 changes provenance for new and re-registered records, not the whole corpus at once. Rejected: treating a repository as the namespace. That would make the collisions vanish by construction, but it redefines the term ADR-011 decision 1 fixes as "a fleet instance, a client deployment, an autonomous domain", and ADR-011 decision 9 warns specifically against reading N-plane movement into claims it does not support.

+

3. Lifecycle status is not automatically promoted. An automated normalization pass may report drift; it may not move a workplan from proposed to active. proposed means awaiting human review, and an automation that promotes it destroys the meaning of the review gate.

+

4. Repository manipulation is performed by a worker agent in that repository. This is the default topology.

+
  • A worker acting in repo X owns changes to repo X.
  • Multiple independent top-level workers inside a single repository are an exception, requiring an explicit reason, not a routine mode of operation.
  • Concurrent independent writers are what turned a two-registrar bug into repeated git divergence.
+

5. Project (prj-) repositories may act across their participating repositories. When work is governed by a project repo, its tasks may direct changes across every repository the project names, through the project's work agent, where that is more efficient than delegating.

+

This is a deliberate, scoped exception to decision 4: the project repo already owns cross-repo sequencing and its SCOPE.md names its participants, so its authority is declared rather than ad hoc. It does not license a worker in an arbitrary repository to reach into others.

+
+

Consequences

+

Positive. Workplan identifiers become citable without qualification. Hub IDs stop flip-flopping in git. The proposed status regains meaning. Cross-repo authority becomes something a repository declares rather than something any session assumes.

+

Negative. Existing collisions must be remediated (see below), which is disruptive. Workstation sessions lose the ability to register workplans directly and must route through the registrar or a worker in the owning repo. Prefix allocation needs a fleet-level registry, which is new machinery.

+

Enforcement. Prefix ownership, uniqueness, and forward-only numbering are repository standards, so they belong to Repo Manager under decision 747011c6 (RMGR-WP-0004), not to a hub. Canon defines the rule; Repo Manager checks it.

+
+

Migration — needs a separate ruling

+

Three prefixes and five identifiers are already colliding. Remediation options, in increasing cost:

+
  1. Freeze and forward-fix. Accept existing collisions as historical, enforce uniqueness only for new workplans. Cheapest; leaves RAILIANCE-WP-0015 permanently ambiguous.
  2. Renumber the live collisions only. Fix identifiers that are still cited or active; leave finished/archived duplicates alone.
  3. Full renaming. Give railiance-apps, railiance-forge, railiance-platform, railiance-telemetry distinct prefixes, likewise railiance-bootstrap/railiance-cluster and state-hub's legacy CUST-WP- files. Touches six repositories and every inbound reference.
+

Ruled 2026-08-17: option 2. Renumber live collisions; leave finished/archived duplicates as historical record.

+

The live renumber list is empty

+

Verified against the fleet scan. All five duplicated identifiers are finished:

+
CUST-WP-0000       the-custodian ×2      finished
+CUST-WP-0010       the-custodian ×2      finished
+CUST-WP-0045       the-custodian ×2      finished
+RAILIANCE-WP-0015  apps, platform        finished
+RAILIANCE-WP-0016  apps, platform ×2     finished
+

No workplan in a proposed, ready, active, blocked, or backlog state shares an identifier with another. Option 2 therefore requires no renumbering today. Historical duplicates stay, including the RAILIANCE-WP-0015 ambiguity between railiance-apps and railiance-platform; citations of it must name the repository.

+

The structural cause is not historical

+

Option 2 governs remediation. It does not exempt anything from decision 1, which is accepted canon: one prefix, one repository, forward-only numbering.

+

Three shared prefixes are still in use across seven repositories, and each is a single number line being allocated from concurrently:

+
RAIL-BS-WP-    bootstrap  8, 9
+               cluster    7, 10, 11, 12, 13, 14
+
+RAILIANCE-WP-  platform   5, 8-17 (16 twice — an internal duplicate)
+               apps       15, 16          <- already collided with platform
+               forge      2
+               telemetry  1
+
+CUST-WP-       the-custodian  50 plans
+               state-hub       4 legacy plans (canonical prefix is STATE-WP)
+

RAIL-BS- and RAILIANCE- are actively growing — RAIL-BS-WP-0014 (ready), RAILIANCE-WP-0002 (ready), RAILIANCE-WP-0001 (proposed), all created 2026-08-11 or later. The RAILIANCE-WP-0015/0016 collisions were not a historical accident; they are what concurrent allocation from a shared sequence produces, and it will recur at the next concurrent allocation.

+

CUST- is dormant on the state-hub side — four legacy plans, one in backlog — and needs no split, only a prefix-ownership assertion.

+

Prefix assignments

+

RAIL-BS-WP- is retired (2026-08-17). Neither repository keeps it: railiance-cluster adopts RCLUSTER-WP- for active and future plans; railiance-bootstrap adopts RBS-WP- for future plans. Finished and archived files keep RAIL-BS-WP- as historical record, consistent with option 2.

+

Migrating plans keep their running numbers — the prefix changes, the number does not. This preserves traceability and cannot violate forward-only allocation, because neither new prefix has prior history. railiance-bootstrap begins at RBS-WP-0010, above its historical maximum, leaving the lower range free should its finished plans ever be adopted into the new prefix.

+

RAILIANCE-WP- should follow the same pattern — retired rather than awarded to one repository, since it names a family rather than a repository and so fails decision 1 for the same reason PRJ-WP- does. Assignment of the four successor prefixes is outstanding.

+

Execution of each rename belongs to a worker in the owning repository under decision 4. RMGR-WP-0004-T09 records assignments and the numbering rule; it does not perform renames.

+

Consequence. Prefix ownership must be assigned for all three shared prefixes before the next workplan is created in the affected repositories. This is forward conformance under decision 1, not migration, and is tracked as RMGR-WP-0004-T09. Renaming the historical files is explicitly not required — that would be option 3, which was rejected.

+
+

References

+
  • Decision 747011c6 — repository standards belong to Repo Manager
  • ADR-001 — workplans originate as repo files; hub is a read model
  • RMGR-WP-0004 — repository standards conformance and governed scaffolding
  • STATE-WP-0080 — register scaffolding handoff
  • Fleet scan 2026-08-16: 955 hub workplans, 525 parseable identifiers, 3 reused prefixes, 5 reused identifiers
+
CUST-ADR-007 · accepted-2 · acceptedthe-custodian · canon/architecture/adr-007-workplan-identity-and-repo-worker-topology.md · d3c6f13d7aed9784b6f31c23d2b2777668493ba5
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-010.html b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-010.html new file mode 100644 index 0000000..550fba8 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-010.html @@ -0,0 +1,253 @@ + + + + +Hub Authority, Local Cache, and the Two Kinds of Hub Data + +
CUST-ADR-010 proposed · draft-2 the-custodian reviewed 2026-08-31generated from canonical source — do not edit

Hub Authority, Local Cache, and the Two Kinds of Hub Data

Source: the-custodian · canon/architecture/adr-010-hub-authority-and-local-cache-model.md · d3c6f13d7aed9784b6f31c23d2b2777668493ba5

Review due: 2027-02-28

Status

+

Proposed, and partially superseded by ADR-012 (accepted 2026-08-25). Decisions 1, 5 and 6 are sharpened or given a mechanism there; see the notes on each below. Everything else in this ADR remains in force.

+
+

Context

+

Investigating a workplan-identifier flip-flop (ADR-007) revealed that two State Hub instances were operating as peer databases, not as a primary and a replica:

+
AddressInstanceWorkplans
127.0.0.1:8000 (IPv4)local uvicorn on docker postgres955
[::1]:8000 (IPv6)ssh tunnel state-hub-primary → railiance01649
+

Both listen on port 8000, separated only by IP family, so every tool defaulting to 127.0.0.1 reached the local instance while believing it was the primary.

+

Mechanism identified 2026-08-24 by CUST-WP-0067. This ADR recorded the shared port as the hazard but not why it was silent, which is the part that let it persist. An ssh -L forward with no bind address listens on every loopback family, and ExitOnForwardFailure=yes fires only when every requested bind fails. The IPv4 bind lost to the local uvicorn, the IPv6 bind succeeded, and the tunnel therefore reported success while serving an address nothing resolved to by default. The collision was declared in ~/.config/bridge/tunnels.yaml, and ops-bridge guarded only tunnel against tunnel, so a non-bridge listener was invisible to it. Fixed by pinning local forwards to 127.0.0.1, so a contested port fails loudly (ops-bridge commit 2213847). A shared port is recoverable; a shared port that reports healthy is not.

+

Measured divergence — 320 records exist locally but not on the primary:

+
backed by a file that exists on disk    288    fully rebuildable
+no backing file recorded                 28    not rebuildable as-is
+same filename, different UUID             4    duplicate registration
+

No file was missing for any of the 288. The divergence is therefore almost entirely redundant: it can be discarded and rebuilt from repositories without losing anything.

+

ADR-001 already says work originates as repository files and the hub is a read model. The two-peer-database situation contradicts that in practice: a second database accumulated 306 net records of its own.

+
+

Decision

+

1. One authoritative hub, deriving from repository files. The central State Hub — running on railiance — is authoritative. It is authoritative as a reading of the repositories, not as a second place data lives. Repository files remain the source of truth (ADR-001).

+

Sharpened 2026-08-25 by ADR-012 decision 1. "A reading of the repositories" never said which copy of them, and the honest answer was neither the forge nor any particular working copy: the projection derived from whichever checkout last ran the sync. The hub holds no repository files at all and never reads one. ADR-012 names the forge as the projection source.

+

2. A local hub is a cache, never a database. Local instances hold a rebuildable projection. A cache may be discarded and reconstructed from the repositories at any time, and losing it must never lose work.

+

This replaces the peer-database arrangement. It is also why the divergence is tractable: a divergent database is a merge problem, a stale cache is a refresh problem.

+

Corrected 2026-08-24 by CUST-WP-0067. Those two shapes are not exhaustive, and repository records took a third one. Measured that day: 122 repositories on the cache, 78 on central, zero central-only. A strict subset in the cache's favour is neither a merge problem nor a refresh problem — refreshing the cache would have destroyed the 44 extra records rather than reconciling them, and central held no path to re-derive repositories it had never been told existed. The third shape: cache-only records whose authoritative source exists and is reachable, but was never introduced to central. Its remedy is re-derivation from source — not refresh, not merge. Of the 44, 43 had a working copy, all 43 were pushed, and 34 carried a classification file; nothing was unrecoverable, but nothing would have recovered itself either. Onboarding by date showed a clean break: central's repository registrations stopped at 2026-07-08 while the workstation kept accepting them.

+

3. Local work requires no hub at all. Repository files are self-describing — identifier, status, tasks, all in frontmatter. Working in a repository requires reading files, not querying an index. A cache is an optimization for cross-repo questions, never a prerequisite for doing work.

+

4. Hub data is classified by origin, and the two kinds have opposite rules.

+
File-derivedHub-native
Examplesworkplans, tasks, statuses, dependenciesprogress events, decisions, inbox messages, token events
Source of truththe repository filethe hub
Offline behaviourwrite the file and commit — the commit is the writebuffer locally, replay when reachable
Conflict modelnone; conflicts are git conflicts, resolved in gitnone; append-only merges regardless of order
Central accepts pushes?no — it derivesyes
+

Neither kind needs a hub-side conflict model. That is the point of the split: if central derives file-backed state, it cannot hold a conflicting version of it — it re-derives whatever git settles on. Two people editing one workplan is a git conflict and belongs to git.

+

5. Central derives file-backed state; it does not accept pushes of it. "Authoritative" means authoritative reading, so nothing may inject derived state directly. Hub-native records are the exception and keep a real write path.

+

Sharpened 2026-08-25 by ADR-012 decision 6. This was policy, not practice: nothing derived, and the workstation pushed everything. ADR-012 retires push-based sync as the primary path so that "central derives" becomes true rather than aspirational.

+

6. Preliminary until confirmed. Locally registered data and uncommitted repository state are preliminary until the central service has seen them. Mitigation is by changing the repository files and the local cache — never by editing central to match a local view.

+

Given a mechanism 2026-08-25 by ADR-012 decisions 3 and 4. "Preliminary" was named here but never built, so in practice locally registered data was indistinguishable from derived state once it arrived. It is now a labelled overlay within the same projection — explicitly not a second store — and it retires when the commit carrying it reaches the forge. The prohibition on editing central to match a local view is unchanged.

+

Combined with ADR-007 decision 2 (identifiers derived from PREFIX-WP-NNNN), "preliminary" largely stops mattering: a cache computes the same identifier central will, so offline-registered data is already correct on arrival and needs confirmation rather than reconciliation.

+

7. Every record has exactly one authoritative hub. The State Hub retirement splits one hub into several. Multiple central hubs are permitted only under this rule: the owning hub is determined by the record's repository and domain. Without it, the same peer-database divergence recurs at larger scale.

+

8. Cache reads are advisory and must carry their age. Cross-repo answers from a cache are advisory and should be presented with staleness. For the repository an agent is working in, the file is truth and the cache is never consulted for correctness.

+
+

Orphan disposition

+

The 28 records with no backing file are the only ones a cache rebuild would drop. They fall into three classes, to be separated before any rebuild:

+
  1. Broken links — a file exists but backing_filename was never recorded. RMGR-WP-0004 is one: the workplan file exists and is committed. These are metadata repairs, not data loss, and are likely the largest class.
  2. Live hub-first records — proposed, ready, or backlog with no file, in activity-core, core-hub, hub-core, issue-core, ops-hub, prj-forgejo-org-refactor, railiance-enablement, railiance-infra, reef-railiance. Each needs a repository file written or an explicit drop. These are ADR-001 violations and must not be preserved as hub-only records.
  3. Closed hub-first records — finished or archived with no file. Retain as historical provenance where cheap; do not reconstruct plans that are done.
+

A cache rebuild enforces ADR-001 retroactively: the only casualties are records that broke it.

+
+

Consequences

+

Positive. The divergence becomes discardable rather than mergeable. Offline work is fully supported without a write buffer for file-backed state — the git commit is the write. No hub-side conflict model is needed for either data kind. Authority stops being a policy claim and becomes a structural property.

+

Negative. The 28 orphans require case-by-case disposition before a rebuild. Any consumer that treats a local hub as authoritative must be corrected. The one-hub-per-record rule constrains the retirement's hub split.

+

Rescoping. STATE-WP-0068 (offline write buffer and edge relay) is scoped as a single mechanism. Under decision 4, most of what it buffers does not need buffering — only the append-only hub-native stream does. Its scope should be re-examined before more is built on it; this may reduce work rather than add it.

+

Correction to ADR-007. Decision 2 there calls the workstation instance a "development read replica". It was neither a replica nor smaller — it held 306 more workplans than the primary. Superseded by decisions 1–3 here.

+
+

Implementation

+

Owned by repo-manager for file-derived state (decision 747011c6; it already owns repository representation, file-backed record indexing, and reconciliation) and by hub-core for hub-native records. Tracked under RMGR-WP-0005.

+
+

References

+
  • ADR-001 — workplans originate as repo files; hub is a read model
  • ADR-007 — identifier uniqueness, derived identifiers, worker topology
  • Decision 747011c6 — repository standards belong to Repo Manager
  • RMGR-WP-0005 — registrar consolidation and deterministic identifiers
  • STATE-WP-0068 — offline write buffer and edge relay (rescope candidate)
  • Divergence measurement, 2026-08-17: 955 local / 649 primary / 320 local-only
+
+

Outcome (2026-08-24)

+

Added by CUST-WP-0067. This ADR read as though its remediation had landed. It had not: the two-instance condition it measured on 2026-08-17 was still live seven weeks later, and had continued to accumulate records the whole time. An ADR that describes a fix in the present tense is easily mistaken for a fix that happened — worth stating plainly for the ones that follow.

+

The peer-database arrangement is now resolved, by deletion rather than by reconciliation:

+
  • The local hub instance is retired. Rather than making two instances coexist safely, the second one was removed. Exactly one process binds 127.0.0.1:8000 and it is the tunnel to central. Nothing remains that could impersonate the primary, and no call site needed editing — retiring the impersonator made the existing 127.0.0.1:8000 defaults correct.
  • Decision 3 carried the argument. Because local work requires no hub at all, and Repo Manager already maintains a file-derived index, the local instance was redundant rather than load-bearing.
  • Repo Manager gained the write path it was already assigned. hub-record-authority.yaml gives it managed_repos as file-derived, but it exposed no command for it; the only working path lived in the State Hub repo and defaulted to a local address. rmgr repo-onboard closes that, and refuses to onboard a repository whose backing file is uncommitted, unpushed, or has no upstream — a record whose source is only local cannot be re-derived, which is the failure this ADR exists to prevent.
  • 33 of the 44 were re-derived onto central, taking it from 78 to 111 repositories. The remaining 11 carry written dispositions rather than guessed values, per the orphan-disposition principle above.
+

One cause sat deeper than the topology: the hub resolved its classification allowed-values file from three hardcoded developer-workstation checkouts, so in a container every classification write failed. Repository classification could only be written from a workstation. That is a second instance of this ADR's own theme — authority that depends on where a process happens to run is not authority — and is why "central derives" had never been achievable for this record type.

+
+

Outcome (2026-08-28)

+

Added by CUST-WP-0068. The 2026-08-24 outcome closed the repository divergence. The work-record divergence this ADR originally measured — 955 local / 649 primary — remained, because the retired instance's database was still load-bearing. That is now closed.

+
  • Central holds 1167 workplans. Records that existed only in the cache were re-derived from their files, renamed onto the canonical scheme, or given a written disposition (docs/recovery/cache-only-disposition-2026-08-28.md).
  • No open work record exists only in the cache. Remaining cache-only slugs are aliases of recovered records, clay-borg product files (not workplans), or prefix-migration residue.
  • The cache database is discarded. Final dump ~/backups/state-hub-cache-2026-08-28.dump. Container infra-postgres-1 and volume infra_pg_data removed. Port 5432 is free.
  • The local instance is no longer load-bearing for any record type. Decision 3 is now true in operation, not only in argument.
+
CUST-ADR-010 · draft-2 · proposedthe-custodian · canon/architecture/adr-010-hub-authority-and-local-cache-model.md · d3c6f13d7aed9784b6f31c23d2b2777668493ba5
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-011.html b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-011.html new file mode 100644 index 0000000..99baff5 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-011.html @@ -0,0 +1,284 @@ + + + + +Federated Namespaces + +
CUST-ADR-011 proposed · draft-2 the-custodian reviewed 2026-08-17generated from canonical source — do not edit

Federated Namespaces

Source: the-custodian · canon/architecture/adr-011-federated-namespaces-and-reconciliation-limits.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb

Review due: 2027-02-17

Status

+

Proposed, draft-2. Amends ADR-007 decisions 1 and 2; extends ADR-010 decision 4; adopts the plane/ladder/posture form and the accuracy-not-altitude conformance rule from ADR-008 (Multi-Tenancy Framework).

+
+

Context

+

The ecosystem must be federation-capable — able to work without a live link to central services — and chain-of-command efficient. federated-organization-standard_v1.0 already resolves the organizational half:

+

global coherence is achieved through policy, escalation, and shared protocols rather than through monolithic control (:51) bounded autonomy plus disciplined coordination (:86)

+

Authority is a policy layer; availability is a mechanism. Distributed version control makes the same separation: an integration branch is privileged by agreement, never by a technical property.

+

One accepted decision violates this. ADR-007 decision 1 requires running numbers that are globally unique and allocated forward only. Global sequential allocation requires a coordinator — precisely the central dependency federation must survive. Two disconnected forks each allocating RCLUSTER-WP-0015 is a certainty, not an edge case.

+

This is not hypothetical. Instance-per-client tenancy (CUST-WP-0058) already makes every client deployment a fork of the stack; the business model committed to forking before the identifier scheme could express it.

+

Why this draft changed form

+

Draft-1 stated federation as a set of binary rules — notably "records fork, effects do not". That is a single verdict where a graduated declaration belongs, and it is the same conflation ADR-008 §03 diagnoses for multi-tenancy: "'Is this multi-tenant?' is treated as one question. It is four, and they are independent."

+

"Is this federation-capable?" is likewise not one question. A namespace can be strong on identity and incapable of disconnected operation; it can operate disconnected and have no way to reconcile afterwards. Draft-1 could not say so.

+

This draft reuses ADR-008's vocabulary rather than inventing a parallel one. Two competing maturity vocabularies in one canon would be its own collision.

+
+

Decision

+

1. Records live in a namespace. A namespace is a federated branch of the ecosystem — a fleet instance, a client deployment, an autonomous domain that may operate disconnected. It is the unit of sovereignty in FOS terms and the unit of divergence here.

+

2. Federation is four orthogonal planes.

+
PlaneQuestionVocabulary owner
Namespace NHow are records named and disambiguated across branches?this ADR
Autonomy UCan work proceed with no link to central services?repo-manager
Reconciliation RWhat happens when branches meet again?repo-manager
Substrate SDoes this namespace hold independently diverging operational state?railiance-platform
+

3. Each plane carries a ladder. Higher is stronger, not better. The right level is the one a namespace can evidence and its risk warrants.

+
N0  no namespace concept; identifiers assume one global space
+N1  namespace implied by deployment, never recorded
+N2  namespace declared; identifiers namespace-scoped; qualification available
+N3  N2 + lineage recorded, and derived identifiers keyed on (namespace, identifier)
+
+U0  routine work requires the central service
+U1  degraded read-only; writes blocked while disconnected
+U2  work proceeds locally: file-derived state written to files, hub-native buffered
+U3  U2 + exercised — a dated disconnection drill with evidence
+
+R0  no defined path; divergence is unrecoverable
+R1  manual, per-record comparison and hand resolution
+R2  append-only events plus derived state: union and re-derive, deterministic
+R3  R2 + lineage-aware; distinguishes divergence from collision automatically
+
+S0  shares operational substrate with another namespace — cannot fork safely
+S1  shared substrate, namespace-keyed
+S2  dedicated substrate, no independent state divergence yet
+S3  independent operational substrate with diverged state — the fork is real
+

4. A namespace declares a posture vector, in the repository rather than the hub, consistent with ADR-010 decision 4 (file-derived state) and ADR-008 decision 5.1.

+
federation:
+  current:  { N: 1, U: 1, R: 1, S: 1 }
+  target:   { N: 3, U: 2, R: 2, S: 1 }
+  reviewed: "2026-08-18"
+  gap:
+    U: "Registration requires the registrar under ADR-007 decision 2 interim.
+        Lifts when derived identifiers land (RMGR-WP-0005-T03)."
+

The ecosystem's own posture today is N1 U1 R1 S1 — one implied namespace, disconnected registration blocked, reconciliation manual, substrate shared. Declaring that is conformant. Claiming better is not.

+

5. Conformance is accuracy, not altitude. Adopted verbatim in force from ADR-008 §06: a namespace is conformant when its declared posture is accurate, its target recorded, and it claims no level it cannot evidence. It is non-conformant when it overclaims, at any altitude.

+

Declaring S0 is conformant. Concealing S0 is not. A repository may join at any posture; it may not join silently. This matters more here than it does for tenancy: the cost of discovering a true posture during reconciliation is the whole difference between T2 and T3.

+

6. Identifiers are unique within a namespace, qualified across. ADR-007 decision 1 is amended: uniqueness and forward-only allocation are namespace-scoped. Global identity is the pair (namespace, identifier).

+
RCLUSTER-WP-0015              this namespace — the common case, unchanged
+RCLUSTER-WP-0015@railiance01  a foreign namespace, explicitly qualified
+

Unqualified always means "in my own namespace", exactly as main does against origin/main. A suffix preserves existing sorting, grep, and tooling; a prefix would break them.

+

7. Derived identifiers key on the pair. ADR-007 decision 2 (C2) derives UUIDv5 from the identifier. Under federation the input must be (namespace, identifier). Deriving from the identifier alone would make two forks holding unrelated work under the same number compute the same UUID — a collision manufactured by the fix. C2 is not yet built, so this is free now and expensive later.

+

8. The planes couple at their upper levels. State the coupling; do not use it to argue the planes are one.

+
  • R3 requires N3 — distinguishing divergence from collision requires lineage.
  • A namespace may not sustain U2 or above while below R2. Operating disconnected without deterministic reconciliation accumulates divergence that cannot later be merged. Autonomy without reconciliation is not federation; it is an unplanned fork.
  • T3 is entailed by S3 in any participant (decision 10).
+

9. Anti-overclaim. This is the most misusable fact in the framework, and it is the direct analogue of ADR-008's P0 → P1 → P2 warning.

+

Adopting namespace-qualified identifiers is movement on the N plane only. It buys disambiguation, citability, and coordinator-free allocation. It does not raise autonomy, reconciliation, or substrate independence by one step. A system at N3 U0 R0 S0 cannot operate disconnected and cannot reconcile.

+

No namespace may be described as "federation-capable", "independently operable", or "able to run disconnected" on N-plane evidence alone. Such a claim requires declared U and R levels with evidence behind them.

+

Commercially, client-facing claims about instance independence must cite the S and U levels, never the identifier scheme. Instance-per-client tenancy is sold on substrate independence; the qualifier does not supply it.

+

10. The reconciliation tier is derived from participants' postures, not judged after the fact.

+
any participant at S3                      -> T3   (regardless of other planes)
+all participants S<=S2 and R>=R2           -> T2   feasible
+any participant below R2                   -> T1   at best; manual thereafter
+

This is the practical payoff of declaring postures: the cost of rejoining is computable before a fork is created, rather than discovered when reconciliation is attempted.

+

11. Coexistence is the normal case; merging is the exception. The qualifier is primarily a coexistence mechanism, not merge preparation. Most forks — client instances above all — are never intended to return. Permanent parallel operation is a first-class outcome, not a failure to reconcile.

+

12. Evidence. A claimed level requires evidence, per ADR-008 §13.

+
LevelEvidence
N2namespace declared in the repo; qualified references resolve
N3lineage fields present; a derived identifier reproduced by an independent implementation
U2work completed with central unreachable, committed, later accepted without rework
U3dated disconnection drill record
R2replaying the union of events reproduces identical derived state
R3a collision and a divergence, correctly distinguished, in test
S2 / S3substrate inventory naming the stateful systems and whether their state has diverged
+

13. Guard. Declared postures regress silently. Posture is checked by the same consistency machinery that checks repository standards (RMGR-WP-0004-T02, rmgr conform), so a namespace that drops below its declared level is a reported defect rather than a discovery. A regression found by guarding is a defect; a regression declared in advance is a decision.

+
+

Reconciliation tiers, and where automation stops

+
TierSituationMechanismAutomatable
T0 Forknamespace created, lineage recordedmetadatayes
T1 Syncpull canon, plans, or knowledge from another namespaceappend events, re-derivelargely
T2 Reintegrationrecords diverged, no participant above S2union events, re-derive, human-resolve genuine contradictionspartly
T3 Amalgamationany participant at S3governed programme — see belowno
+

T3 is not a merge. It resembles the merger or acquisition of distinct companies far more than a version-control merge, and must be planned as such:

+
  • due diligence — inventory both sides, item by item;
  • survivor decisions per subsystem — keep, move, replace, retire;
  • migration with evidence, and decommissioning of what does not survive;
  • governance integration — reconciling two bodies of canon is a human act, never automated;
  • identity, credential, and data custody consolidation.
+

T3 may legitimately end without integration. Permanent coexistence, or divestment, are valid outcomes. Real mergers most often fail at systems integration; an architecture that assumes eventual convergence is lying about its own limits.

+

Known cost

+

prj-state-hub-retirement is a T3 reconciliation inside a single organization — two hub implementations, distinct infrastructure. It required a dedicated project repository, a 425-item disposition inventory (SHR-INV-0001), staged migration with strangler adapters, and explicit retirement gates. That is the realistic cost of T3 under the most favourable conditions possible: one owner, one canon, one credential authority.

+

Nothing here reduces that cost. Declared postures make it visible before a fork is created rather than discovered when reconciliation is attempted.

+
+

Consequences

+

Positive. Disconnected operation stops requiring a coordinator for identity. ADR-007 decision 1's contradiction is resolved rather than tolerated. A namespace mid-journey can describe itself honestly instead of being non-conformant. Reconciliation cost becomes computable in advance. Client instances become expressible.

+

Negative. Every namespace gains a posture to declare and keep accurate, and tooling must learn qualified references. Decision 8's coupling rule constrains disconnected operation to namespaces that can reconcile — deliberately.

+

Deferred. Merge machinery for T2 is not specified here and should not be built until a real fork needs it. T3 needs no machinery by definition; it needs a project repository and human judgement.

+

Amended. ADR-007 decision 1 — uniqueness is namespace-scoped. ADR-007 decision 2 — derivation input becomes (namespace, identifier).

+
+

Open question

+

What is the namespace boundary in practice? Candidates: a fleet instance (railiance01), a client deployment, or an FOS autonomous domain. FOS's recursion — "from projects to companies to foundation-like umbrella structures" — suggests namespaces nest, which would make qualification relative rather than absolute, closer to DNS than to a flat handle. The notation survives either answer, so implementation is not blocked, but the data model is unfinished until it is settled.

+
+

References

+
  • canon/standards/federated-organization-standard_v1.0.md — bounded autonomy, escalation, sovereignty by default, rebuildability
  • ADR-001 — workplans originate as repo files
  • ADR-007 — identifier uniqueness and derived identifiers (amended here)
  • ADR-008 — Multi-Tenancy Framework; source of the plane/ladder/posture form and the accuracy-not-altitude conformance rule
  • ADR-010 — hub authority, local cache, and the two kinds of hub data
  • CUST-WP-0058 — instance-per-client tenancy
  • SHR-INV-0001 — 425-item disposition inventory, T3 cost evidence
  • RMGR-WP-0004-T02 — rmgr conform, the guard machinery
+
CUST-ADR-011 · draft-2 · proposedthe-custodian · canon/architecture/adr-011-federated-namespaces-and-reconciliation-limits.md · 4039c9d1c08c92014ecc0a65dda63cc73ba187bb
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-012.html b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-012.html new file mode 100644 index 0000000..7c2b042 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/CUST-ADR-012.html @@ -0,0 +1,248 @@ + + + + +What the Hub Projects + +
CUST-ADR-012 accepted · 1.0 the-custodian reviewed 2026-08-25generated from canonical source — do not edit

What the Hub Projects

Source: the-custodian · canon/architecture/adr-012-projection-source-and-preliminary-overlay.md · 6475a2e1ca590682888b77ce32ea056fd02e272a

Review due: 2027-02-25

Status

+

Accepted 2026-08-25 by Bernd Worsch. Supersedes ADR-010 decision 1's phrase "authoritative as a reading of the repositories" by making the reading concrete, and implements decision 6's unbuilt notion of "preliminary".

+
+

Context

+

ADR-001 says work originates as repository files and the hub is a read model. ADR-010 says central is authoritative as a reading of the repositories. Both leave one question unanswered, and it turns out to be the load-bearing one:

+

Which repository files? The forge, or a working copy?

+

Verified on 2026-08-25, the answer today is neither.

+

Central runs one FastAPI pod on railiance01 with a CNPG database. Inspected from inside, it holds /app — its own source — and nothing else. No clone tree, no repository mount; the sweep hostPath that would give it one is disabled. The hub never reads a repository. It cannot see the forge, and it cannot see a workstation.

+

What actually happens is the inverse of the ADR language: fix-consistency and registrar-reconcile run on a workstation, read files in /home/<user>/<repo>, and POST the result over a tunnel. The host_paths field records this plainly — 117 repositories carry a path on bnt-lap001, a laptop.

+

So the projection derives from whichever checkout most recently ran the sync. That is a third thing, distinct from both the forge and any particular working copy, and it is written down nowhere.

+

Why this is not academic

+

Every failure investigated under CUST-WP-0067 and CUST-WP-0068 is downstream of it:

+
  • A local hub and central both served port 8000, and every default reached the local one for seven weeks. Neither could be distinguished by what it projected.
  • 50 repository records pointed at a retired forge, so the sync could not match the checkout and silently registered nothing.
  • Duplicate registrations accumulated: the same file registered twice under two identifiers, because two environments each believed they were projecting it.
  • git_fingerprint — the field that should say which commit a projection reflects — still holds the initial commit for the-custodian, while last_state_synced_at is minutes old. There is no reliable way to ask central what state it is a projection of.
+

The forcing function

+

With one contributor, "last writer wins" is invisible, because there is only one writer. With several, each pushes a projection of a different repository state into one shared read model, and each will look locally consistent while disagreeing with the others. That is the peer-database problem ADR-010 diagnosed, relocated one layer up and made harder to see.

+

The window to fix this cheaply is before the second contributor, not after.

+
+

Decision

+

1. The forge is the projection source. Central derives its projection from Forgejo — the pushed state of the default branch — not from any working copy. "Authoritative as a reading of the repositories" now names a specific reading: what the forge holds.

+

Forgejo runs in the same cluster as central, so this is an in-cluster fetch. No tunnel, no cross-network credentials, and central already carries a git binary.

+

2. Every projected record carries the commit it derived from. A projection without provenance cannot be audited, and the existing git_fingerprint proves the point by being wrong and silent about it. Records must reference the commit that produced them, and a projection whose commit is older than the forge's head must be visibly stale rather than quietly current.

+

3. Unpushed local work is a preliminary overlay, never the baseline. A working copy may contribute records the forge does not yet hold. They are marked preliminary, attributed to their environment, and never silently merged into the derived baseline. When the commit that carries them reaches the forge, the baseline absorbs them and the overlay entry retires.

+

This implements ADR-010 decision 6, which named "preliminary" but left it unbuilt.

+

4. The overlay is a label, not a second store. Preliminary records live in the same projection, flagged. They are not a parallel database and not a local hub. Every local projection this ecosystem has had was eventually mistaken for authority; the defence is that there is nothing separate to mistake.

+

5. A working copy is a workspace, not a source of authority. Deleting a checkout after pushing must lose nothing and invalidate nothing. Validation of a projection must not require a local clone of the repository being validated.

+

6. Push-based sync is retired as the primary path. Workstation tooling stops being how records reach central. It may continue to propose preliminary records under decision 3, but the baseline is derived, not received. This closes the gap where "central derives" was policy while "the laptop pushes" was practice.

+

7. The projection must be resettable from the forge, as a routine operation. There must be a supported way to reconcile the projection — for one repository or for the whole fleet — against what Forgejo holds: create what is missing, update what differs, and retire what no longer derives.

+

Amended 2026-08-25, before implementation. This decision originally said discard and rebuild. That operation cannot exist, and the reason is a good one. progress_events, tasks, decisions and review_contracts all reference workplans with ON DELETE RESTRICT, and 1067 of 1075 workplans carry at least one such reference. Deleting a workplan would mean deleting the hub-native records attached to it — which ADR-010 decision 4 forbids, and which the schema refuses to allow. The database is enforcing this ADR's own boundary one layer down. A progress event recording work on a workplan is a fact that happened; destroying it to tidy a derived projection would be losing hub-native truth to fix a derived-state problem, which is exactly backwards. Retirement achieves what reset is for — the projection converges on the forge, and records that no longer derive stop appearing as live work — without erasing anything that only the hub holds. Reset reconciles; it does not destroy.

+

This is the decision that makes the others checkable rather than merely stated. A read model that cannot be rebuilt from its source is not a projection; it is a database with a projection's reputation, and the difference only becomes visible on the day someone needs to rebuild it. ADR-010 decision 2 already asserts that a cache "may be discarded and reconstructed from the repositories at any time" — that claim has never been executed, and an untested rebuild path is an assumption, not a capability.

+

Three properties make it real rather than ceremonial:

+
  • Routine, not emergency. It should be run deliberately and often enough that it is known to work, not discovered under pressure. A reset that has never been performed is indistinguishable from one that does not work.
  • Per repository is the unit. Not a convenience over a fleet-wide operation — the repository is the unit of reconstruction, and the fleet-wide form is defined as iteration over it.
+

This follows from the source. Each repository is a separate forge repository with its own history and its own head commit, so its projection is derivable in isolation and provable in isolation: fetch that repository, rebuild its records, compare against that repository's head. Nothing about that requires knowing the state of any other repository, and a design that made it require so would be inventing a dependency the source does not have.

+

It also bounds the blast radius, which is what makes decision 7's "routine" achievable. A rebuild that can only run fleet-wide is an operation nobody runs casually, and therefore an operation nobody runs — which is precisely how ADR-010's never-executed reconstruction claim came to be believed. Scoped to one repository, a rebuild is small enough to be ordinary, and ordinary is the only state in which it stays known-working.

+

It is also the only form that composes with the refusals below. A repository holding projection-only records can be held back and dispositioned while every healthy repository around it is rebuilt; an all-or-nothing reset is blocked entirely by a single bad repository, which in practice means it is blocked permanently.

+

With contributors, per-repository scope is what keeps one person's rebuild from touching another person's records.

+

The fleet-wide form must therefore be a loop over the per-repository form, sharing one implementation. The rarely-used dangerous path and the frequently used safe path must be the same code, so the rare one is exercised by the common one rather than trusted on the strength of never having been run.

+
  • Idempotent and verifiable. A reset followed by a reset produces the same projection, and the result can be compared against the forge to show it matches. Derived identifiers (ADR-007) are what make this possible: the same commit yields the same record identities every time.
+

Retirement must be visible, not silent. A retired record states that the forge no longer derives it, and remains inspectable — including from whatever hub-native history is still attached. A record that merely disappears from a listing is indistinguishable from one that was never there.

+

Reset does not restore the preliminary overlay. Overlay records exist precisely because the forge does not hold them, so a rebuild from the forge cannot reproduce them and must not pretend to. Reset therefore discards preliminary state, and must say so plainly before it runs.

+

A reset must refuse when records exist only in the projection. If the hub holds records with no counterpart in the forge, rebuilding destroys them. That is not hypothetical: as of 2026-08-25, 111 work records existed only in a retired local database, and a rebuild at that moment would have erased them. The reset path must detect that condition and stop, naming what would be lost, rather than proceed and report success.

+

The refusal is evaluated per repository, in keeping with the scope above. A repository whose records all exist in the forge is rebuildable regardless of what any other repository holds, and a fleet-wide run must skip and report the repositories it refuses rather than abort the whole pass. Otherwise one unresolved repository blocks reconstruction everywhere, and the capability decays back into the untested assumption this decision exists to prevent.

+

8. Formal git review stays optional. Deriving from the default branch gives a shared baseline without requiring pull requests. Review can be adopted per repository where it earns its keep; this ADR neither mandates nor forbids it.

+
+

Consequences

+

Positive. The hub becomes provably a projection: reconcilable on demand against its source, and therefore knowable to be one. Truth becomes checkable by anyone, from anywhere, without a clone. Multiple contributors share one baseline instead of overwriting each other's views. Provenance becomes auditable — every record can name its commit. The "push then delete the working copy" case simply works. The distinction between committed and uncommitted work becomes visible in the model rather than a matter of who ran which command last.

+

Negative. Git becomes load-bearing for the hub: Forgejo availability now affects projection freshness. A derive loop needs a cadence, and freshness becomes a property to monitor rather than assume. Unpushed work becomes explicitly second-class — which is its honest status, but it will feel like a restriction to a solo developer used to local-first behaviour.

+

Migration. The sweep hostPath — central reading a node-local clone tree — is a half-measure toward this decision and was disabled pending "governed remote reconciliation" while railiance01 checkouts still targeted Gitea. As of 2026-08-25 all 79 node checkouts track Forgejo, so that stated blocker has cleared. Sweep should be evaluated as a stepping stone or retired in favour of a direct forge fetch, not left dormant with an obsolete justification.

+

Unresolved. This ADR does not settle the derive cadence, whether central clones or uses the Forgejo API, how preliminary records are surfaced in the dashboard and MCP, or what happens to a preliminary record whose commit never arrives. Nor does it settle how hub-native records — progress events, decisions, inbox messages, which ADR-010 decision 4 classes as originating in the hub — survive a reset. They are not forge-derived and must not be destroyed by a rebuild of forge-derived state; the boundary needs drawing before reset is built. Those belong to implementation.

+
+

Relationship to prior decisions

+
  • ADR-001 — unchanged. Work still originates as repository files; this ADR says which copy of them the hub reads.
  • ADR-010 — decisions 1 and 5 are sharpened, not reversed: central still derives and still does not accept pushes of derived state. Decision 6's "preliminary" gains a mechanism. The local-cache-versus-database framing stands.
  • ADR-003 — partially superseded. Decision 2 composes fingerprints from filesystem mtime, which is a property of one workstation rather than of the source; under decision 1 here the input is the commit. Decision 5 already stated the rebuild principle correctly but had never been exercised; decision 7 here makes it an operation with a source, a scope and a verification.
  • ADR-007 — derived identifiers become more valuable here: a forge-derived projection and a preliminary overlay compute the same identifier for the same record, so absorbing an overlay entry needs no reconciliation.
+
+

References

+
  • ADR-001 — workplans originate as repo files; hub is a read model
  • ADR-010 — hub authority, local cache, and the two kinds of hub data
  • ADR-007 — identifier uniqueness and derived identifiers
  • CUST-WP-0067 — hub target resolution; retired the impersonating local instance
  • CUST-WP-0068 — cache-only work-record recovery; surfaced the stale git_fingerprint and the duplicate registrations
  • Verification, 2026-08-25: central pod holds no repository files; sweep disabled; 117 repositories record a laptop path; the-custodian git_fingerprint is the initial commit while last_state_synced_at is current
+
CUST-ADR-012 · 1.0 · acceptedthe-custodian · canon/architecture/adr-012-projection-source-and-preliminary-overlay.md · 6475a2e1ca590682888b77ce32ea056fd02e272a
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0006.html b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0006.html new file mode 100644 index 0000000..7ef4492 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0006.html @@ -0,0 +1,219 @@ + + + + +Recursive Multi-Tenant Identity and Authorization Architecture + +
NK-ADR-0006 accepted · 1 net-kingdom reviewed 2026-08-22generated from canonical source — do not edit

Recursive Multi-Tenant Identity and Authorization Architecture

Source: net-kingdom · docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2027-02-22

Status: Accepted Date: 2026-05-17 Deciders: Bernd Worsch

+

Context

+

The Coulomb platform is being built from the same repositories and services that will later support other use cases. This creates a recursive architecture problem: Coulomb needs to use the shared identity, security, policy, and deployment capabilities, while those capabilities are themselves part of the infrastructure being built.

+

If this recursion is left implicit, the first internal use case can drift into being treated as the platform root of trust. That would make future multi-tenant use harder, blur operational authority, and make secure bootstrap/recovery decisions harder to reason about.

+

NetKingdom already owns identity and security architecture concerns. key-cape provides a lightweight IAM implementation of the NetKingdom IAM Profile. Keycloak remains the expanded production IAM option. privacyIDEA is relevant for MFA/token lifecycle. flex-auth is emerging as the canonical authorization control plane and practical reference implementation of CARING authorization semantics. Topaz is the most likely first delegated authorization runtime behind flex-auth.

+
+

Decision

+

We will document and implement the platform security architecture as a recursive multi-tenant architecture with three explicit planes:

+
  • Bootstrap plane - establishes the first trusted runtime and recovery authority before normal platform services exist.
  • Platform control plane - operates shared identity, MFA, secrets, authorization, policy, audit, and explanation services.
  • Tenant plane - runs Coulomb and future workloads under scoped tenant authority.
+

Coulomb will be treated as the first internal/reference tenant, not as the platform root of trust.

+

NetKingdom will own the canonical security architecture and standards. Railiance will own deployment layering and orchestration boundaries. flex-auth will own the canonical authorization interface and CARING-based policy/decision model. Topaz will be the first delegated PDP runtime, with other authorization engines treated as adapters where useful.

+
+

Consequences

+
  • Architecture documentation must separate platform-root authority from tenant administration, even for Coulomb.
  • Workplans for identity, authorization, and bootstrapping must include explicit tenant and control-plane boundaries.
  • Bootstrap design must include trust-state transitions and recovery procedures rather than assuming the final IAM service already exists.
  • flex-auth should model tenants, platform resources, CARING descriptors, decision envelopes, and runtime adapters in a provider-neutral way.
  • key-cape and Keycloak should be treated as implementations of the IAM Profile, not as the canonical source of resource authorization semantics.
  • A future orchestration repo may be useful, but only to coordinate safe sequencing across Railiance and NetKingdom capabilities. It must not bypass Railiance stack ownership.
+
+

Alternatives Considered

+

Treat Coulomb As The Platform Root

+

This is simpler during early development but creates long-term coupling between one internal use case and the shared platform. It makes later multi-tenant operation and secure bootstrap harder.

+

Put All Security Semantics Into Keycloak

+

Keycloak is useful for expanded IAM and can provide authorization features, but making it the canonical model would make lightweight mode and future authorization backends harder to support. The preferred model keeps identity provider concerns separate from canonical authorization semantics.

+

Create An Orchestration Repo Immediately

+

A dedicated orchestration repo may become appropriate. Creating it before we define trust states and repo boundaries would risk encoding accidental sequence logic too early. The immediate step is to document the state machine and update workplans.

+
+

Follow-Up

+
  • Refine bootstrapping around explicit trust-state transitions.
  • Add tenant/control-plane language to flex-auth authorization workplans.
  • Define the first production Topaz integration boundary for flex-auth.
  • Decide when key-cape is sufficient and when Keycloak expanded mode is required.
  • Decide what, if anything, should live in a future orchestration repo.
+
NK-ADR-0006 · 1 · acceptednet-kingdom · docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md · ce198fc2905687ea90a2892346b6860281ac87f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0007.html b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0007.html new file mode 100644 index 0000000..471f6a1 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0007.html @@ -0,0 +1,230 @@ + + + + +Security Orchestration Boundary + +
NK-ADR-0007 accepted · 1 net-kingdom reviewed 2026-08-22generated from canonical source — do not edit

Security Orchestration Boundary

Source: net-kingdom · docs/adr/ADR-0007-security-orchestration-boundary.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2027-02-22

Status: Accepted Date: 2026-05-18 Refined: 2026-05-21 (meta-orchestration layer — see below) Deciders: Bernd Worsch, Codex

+

Context

+

The recursive platform security architecture needs careful sequencing: host trust, cluster trust, bootstrap secrets, runtime secret authority, runtime identity, runtime authorization, tenant onboarding, and readiness verification.

+

That sequencing crosses NetKingdom and Railiance ownership boundaries. NetKingdom owns the canonical security architecture, IAM Profile, credential/bootstrap standards, and authorization semantics. Railiance owns deployment layering for infrastructure, clusters, platform services, and applications. OpenBao adds an important runtime-secret authority to the platform control plane, but it does not change those ownership boundaries.

+

Creating a dedicated orchestration repo too early would risk encoding temporary bootstrap order and accidental stack assumptions as a permanent interface. Leaving every sequence implicit would also be risky: platform root actions, OpenBao initialization, policy import, and tenant onboarding must be auditable and repeatable.

+
+

Decision

+

Security orchestration will stay in Railiance playbooks for now.

+

NetKingdom will define the trust-state model, readiness checks, policy semantics, OpenBao boundaries, and tenant/control-plane rules. Railiance playbooks will own the concrete deployment sequencing across railiance-infra, railiance-cluster, railiance-platform, and railiance-apps.

+

A dedicated orchestration repo is deferred until the sequencing surface is stable enough to justify its own product boundary. If created later, it must coordinate safe sequencing and readiness reporting; it must not own security policy semantics or bypass Railiance stack ownership.

+
+

Consequences

+
  • NK-WP-0006 is implemented as architecture, standards, ADRs, and workplan constraints rather than a new repo.
  • OpenBao bootstrap, unseal/recovery, audit, backup, and workload-secret delivery belong in Railiance platform playbooks, governed by NetKingdom standards.
  • Cross-repo readiness should be reported as checks against explicit trust states, not as a hidden imperative script.
  • A future orchestration repo needs a new ADR before creation.
+
+

Future Repo Trigger

+

Revisit a dedicated orchestration repo only if at least two of these are true:

+
  • multiple Railiance deployments need the same security sequencing interface;
  • readiness reporting becomes a reusable artifact consumed by operators, agents, or CI;
  • rollback and recovery workflows need a cross-repo state machine that no single Railiance layer can own cleanly;
  • tenant onboarding becomes a repeatable workflow spanning identity, flex-auth, Topaz, OpenBao, object storage, and application repos.
+
+

Alternatives Considered

+

Create A Dedicated Orchestration Repo Now

+

This would give sequencing a visible home, but it would probably encode unstable details before OpenBao runtime operations, flex-auth/Topaz policy import, and tenant onboarding have enough implementation feedback.

+

Put Orchestration In NetKingdom

+

NetKingdom owns the security model, but it should not become the deployment repo for every stack layer. This would blur architecture ownership with platform deployment ownership.

+

Leave Sequencing Entirely Informal

+

This avoids premature structure but leaves bootstrap and runtime trust transitions too dependent on operator memory. The accepted approach keeps the sequence explicit while leaving concrete deployment in the Railiance stack.

+
+

Refinement (2026-05-21): Meta-Orchestration Layer

+

The original decision left "what NetKingdom does about orchestration" defined only negatively (it does not own deployment mechanics). This refinement names the positive role. It sharpens, and does not overturn, the accepted decision: execution stays in Railiance.

+

There are two distinct layers:

+
  • Railiance — execution orchestration (the "how"). A library of scenario playbooks plus the tools that run them. Each playbook provisions a slice of the landscape, ships sensible defaults, and exposes parameters for tuning. Multiple playbooks exist for multiple scenarios.
  • NetKingdom — meta-orchestration (the "what" and "who"). NetKingdom does not re-implement deployment mechanics. It (1) selects the services and playbooks a given scenario requires, (2) decides parametrization where tuning is warranted and otherwise relies on playbook defaults, and (3) holds the responsibility map — which element (a Railiance layer, an external provider, a tenant-owned piece) owns what across the whole IT landscape.
+

The relationship is architect ↔ contractor (or conductor ↔ players): NetKingdom composes the score and assigns parts; Railiance executes them. This is the same discipline used elsewhere — the IAM Profile is the contract while key-cape/Keycloak are implementations, and the State Hub is a read model over execution. Meta-orchestration is a decide/compose layer over Railiance's execute layer.

+

This binds directly to the capability ladder (docs/platform-identity-security-architecture.md → Capability Progression): the ladder is the menu, a scenario selects a subset of tiers, and meta-orchestration is the act of binding that subset to playbooks + parameters + a responsibility map, producing a turn-key landscape.

+

New Dependency — Playbook / Capability Contract

+

For NetKingdom to select and parametrize reliably, Railiance playbooks must publish a declared interface: the capability each playbook provisions, its parameters (with defaults and constraints), and the responsibility it claims. This catalog is the orchestration-layer analog of the IAM Profile. Without it, meta-orchestration composes against implicit behavior and breaks when a playbook changes. Establishing this contract is the prerequisite for any concrete meta-orchestration work.

+

Effect on the Future Repo Trigger

+

Meta-orchestration logic now has a clear home (NetKingdom) regardless of whether a dedicated execution-orchestration repo is later created under the Future Repo Trigger above. A future repo, if created, would host reusable execution sequencing — not the scenario-composition and responsibility-mapping role, which remains NetKingdom's.

+
NK-ADR-0007 · 1 · acceptednet-kingdom · docs/adr/ADR-0007-security-orchestration-boundary.md · ce198fc2905687ea90a2892346b6860281ac87f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0008.html b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0008.html new file mode 100644 index 0000000..cbe354c --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0008.html @@ -0,0 +1,216 @@ + + + + +Object Storage STS Credential Vending Boundary + +
NK-ADR-0008 accepted · 1 net-kingdom reviewed 2026-08-22generated from canonical source — do not edit

Object Storage STS Credential Vending Boundary

Source: net-kingdom · docs/adr/ADR-0008-object-storage-sts-credential-vending.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2027-02-22

Status: Accepted Date: 2026-05-18 Deciders: Bernd Worsch, Codex

+

Context

+

NetKingdom needs a canonical pattern for issuing short-lived object-storage credentials to platform and tenant workloads. The first known consumer is artifact-store, but the pattern must work for future S3-compatible consumers without making each application repo own identity, authorization, root object-store credentials, or backend-specific STS differences.

+

The backend landscape is not uniform. AWS S3, Ceph RGW, and MinIO/AIStor can use web-identity STS-style flows. Cloudflare R2 exposes temporary credentials through a provider API or local signing with parent access material. OpenBao is now part of the Railiance platform stack as runtime secret authority, but it is not an identity provider or authorization policy engine.

+
+

Decision

+

NetKingdom will define a provider-neutral credential-vending interface backed by provider-native temporary credential mechanisms where possible.

+

The trust path is:

+
  1. IAM Profile token proves the actor or workload.
  2. flex-auth decides whether the actor may receive credentials for the requested protected system, tenant, bucket, prefix, action set, TTL, and assurance level.
  3. The credential-vending service exchanges the approved request with the backend-specific temporary credential mechanism.
  4. OpenBao stores parent credentials, broker configuration, lease metadata, and audit evidence where useful, but it does not replace flex-auth authorization.
  5. Consumers receive normalized temporary credentials containing access key id, secret access key, session token, and expiration.
+
+

Consequences

+
  • artifact-store needs temporary credential support, especially AWS_SESSION_TOKEN and refresh behavior, before it can fully consume the production vending pattern.
  • Backend-specific differences are isolated in the vending service, not leaked into application policy.
  • OpenBao remains runtime secret infrastructure and audit support; it does not become the object-storage policy source.
  • Provider-native STS is preferred when available because it gives the storage backend direct lease/expiration semantics.
  • Cloudflare R2 requires a broker path that protects parent access material, most likely through OpenBao custody.
+
+

Alternatives Considered

+

Give Applications Long-Lived Access Keys

+

This is simple but leaves applications holding durable credentials and pushes policy into ad hoc bucket configuration. It is acceptable only as a transitional bridge with scoped credentials and explicit rotation.

+

Put Object-Storage Policy In Keycloak Or key-cape

+

Identity providers can assert who the actor is and coarse groups or roles, but they should not become the canonical source of bucket, prefix, action, TTL, and explanation semantics.

+

Use OpenBao As The Credential Vending Policy Engine

+

OpenBao is valuable for secret custody, broker configuration, leases, and audit records. Making it the policy decision point would duplicate flex-auth, blur the platform/tenant boundary, and make authorization semantics backend-specific.

+

Require One Backend Everywhere

+

A single backend would simplify implementation but does not match the platform direction. Railiance and NetKingdom need a stable security interface across AWS, self-hosted S3-compatible stores, and Cloudflare R2-like APIs.

+
NK-ADR-0008 · 1 · acceptednet-kingdom · docs/adr/ADR-0008-object-storage-sts-credential-vending.md · ce198fc2905687ea90a2892346b6860281ac87f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0010.html b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0010.html new file mode 100644 index 0000000..b3a20a9 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0010.html @@ -0,0 +1,221 @@ + + + + +Orchestration vs Dependency, and Self-Coherent Intent + +
NK-ADR-0010 accepted · 1 net-kingdom reviewed 2026-08-22generated from canonical source — do not edit

Orchestration vs Dependency, and Self-Coherent Intent

Source: net-kingdom · docs/adr/ADR-0010-orchestration-vs-dependency-self-coherent-intent.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2027-02-22

Status: Accepted (repo classification subject to ongoing refinement) Date: 2026-05-21 Deciders: Bernd Worsch, Codex

+

Context

+

While aligning the ecosystem's INTENT.md files, two relationships that had been blurred turned out to be fundamentally different, and a content principle for intent emerged. Both are foundational enough that future interface and boundary refinements should be measured against them.

+

NetKingdom performs meta-orchestration (ADR-0007): it selects, parametrizes, and assigns responsibility across an IT landscape. But "things NetKingdom meta-orchestrates" is not the same as "things NetKingdom depends on," and the two had been conflated.

+
+

Decision

+

Principle 1 — Orchestration is not dependency

+

NetKingdom relates to other repositories in two distinct ways:

+
  • Orchestrated — the repo provides a service that holds resources NetKingdom must manage: users, roles, scopes, policies, credentials, infrastructure resources, and the like. NetKingdom composes, parametrizes, and holds responsibility for those resources.
  • Dependency — NetKingdom uses the repo as a tool to provide its own interface, without managing resources the tool holds.
+

The defining question: does the repo provide a service holding resources that NetKingdom needs to orchestrate?

+
  • Yes → orchestrated.
  • No, it is merely used → dependency.
+

Worked examples:

+
  • railiance-fabric is a tool NetKingdom uses to provide an interface; it holds no NetKingdom-managed resources → dependency.
  • railiance-infra, railiance-cluster, railiance-platform define and hold resources → orchestrated.
  • An IAM directory (users, groups) or a policy store (roles, scopes, policies) holds exactly the resource kinds in the criterion → orchestrated.
+

This classification is applied now (see the responsibility map) and will be refined as interfaces and boundaries mature. Borderline cases are expected.

+

Principle 2 — Intent is self-coherent

+

Every repository's INTENT.md describes that repository's own purpose and direction, abstractly and stably. Therefore:

+
  • It must not define itself in terms of NetKingdom.
  • It must not reference the intent of sister projects.
  • It must not even encode dependencies — dependencies are more concrete and less stable than intent should be.
+

Intent is the most abstract, most stable layer. Relationships — orchestration, dependency, interfaces, boundaries — are recorded outside intent: in NetKingdom's responsibility map, architecture docs, ADRs, and interface contracts. This keeps every repo's intent free of external reference points, so it stays stable while the interfaces and boundaries between repos are refined over time.

+
+

Consequences

+
  • The earlier idea of adding a "place in the NetKingdom-orchestrated landscape" block to downstream INTENT.md files is rejected. It would violate Principle 2.
  • Cross-repo INTENT.md work becomes: ensure each orchestrated repo has a self-coherent intent — author one where missing, and remove external references (to NetKingdom or sister projects) where present.
  • The orchestration/dependency relationship and the per-repo responsibility map live in net-kingdom, not in the downstream repos.
  • A responsibility-map artifact in net-kingdom enumerates, per orchestrated repo, which resources NetKingdom manages: docs/responsibility-map.md.
  • ADR-0007's meta-orchestration layer is unchanged; this ADR clarifies what NetKingdom orchestrates versus merely uses.
+
+

Alternatives Considered

+

Treat every related repo uniformly

+

Simpler, but it conflates "manages the resources this service holds" with "uses this tool," which produces an incoherent responsibility map and tempts downstream repos to encode NetKingdom into their intent.

+

Record relationships inside each repo's intent

+

Convenient for a reader of a single repo, but it couples intents to each other and to NetKingdom, making the most-stable layer the least stable. Relationships belong in interface contracts and the responsibility map.

+
NK-ADR-0010 · 1 · acceptednet-kingdom · docs/adr/ADR-0010-orchestration-vs-dependency-self-coherent-intent.md · ce198fc2905687ea90a2892346b6860281ac87f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0011.html b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0011.html new file mode 100644 index 0000000..846a28b --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0011.html @@ -0,0 +1,227 @@ + + + + +NetKingdom IAM Profile Ownership And Version Governance + +
NK-ADR-0011 accepted · 1 net-kingdom reviewed 2026-08-22generated from canonical source — do not edit

NetKingdom IAM Profile Ownership And Version Governance

Source: net-kingdom · docs/adr/ADR-0011-iam-profile-ownership-and-version-governance.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2027-02-22

Status: Accepted Date: 2026-05-22 Deciders: Bernd Worsch, Codex

+

Context

+

The IAM Profile is the identity contract that applications, flex-auth, key-cape, Keycloak, and bootstrap identity tooling all target. It defines the OIDC discovery, flow, token, claim, assurance, tenant, and conformance requirements that make lightweight and expanded identity modes interchangeable at the application boundary.

+

A draft IAM Profile v0.1 existed in the-custodian canon with an all-hubs scope. That draft captured useful material: OIDC discovery, Authorization Code + PKCE, service-account tokens, required claims, token lifecycle, emergency access, and local-development behavior. However, NetKingdom now owns the platform identity domain. SCOPE.md names the NetKingdom IAM Profile as an in-scope, versioned standard, and ADR-0006 requires key-cape and Keycloak to be implementations of the profile rather than the canonical source of authorization semantics.

+

The v0.1 draft also used hub-specific scope and role vocabulary. That made sense for the Custodian hub landscape, but the core NetKingdom profile must be platform-neutral so it can serve tenant, service, application, and agent use cases without encoding one downstream system's scope names.

+
+

Decision

+

NetKingdom is the canonical owner of the IAM Profile.

+

The profile is versioned under canon/standards/ in this repository. The first canonical NetKingdom version is canon/standards/iam-profile_v0.2.md.

+

The relationship to the earlier the-custodian draft is:

+
  • the-custodian IAM Profile v0.1 is superseded as a core/platform standard;
  • NetKingdom owns the provider-neutral core profile;
  • downstream systems may define hub-, tenant-, or application-specific scopes and roles as extensions, but those extensions must map back to the core identity and authorization input contract;
  • key-cape lightweight mode and Keycloak expanded mode are interchangeable implementations of the same profile;
  • flex-auth consumes the profile as normative identity input and must not re-derive identity facts from provider-specific state.
+
+

Versioning

+

The IAM Profile uses explicit document versions:

+
  • Patch/editorial changes clarify wording, examples, or non-normative guidance without changing the token contract.
  • Minor versions add optional claims, optional flows, or additional conformance checks that existing implementations can pass unchanged.
  • Major or breaking versions change required claims, claim meanings, validation rules, flow requirements, assurance semantics, tenant semantics, or token acceptance rules.
+

Every versioned profile file remains immutable enough for downstream references to cite. New versions are added as new files rather than rewriting historical versions in place, except for clearly editorial fixes that do not affect semantics.

+
+

Breaking-Change Governance

+

A breaking profile change requires:

+
  1. a new ADR or ADR refinement that explains the change and migration path;
  2. a new versioned profile document;
  3. an update to the executable conformance suite;
  4. a coexistence window that lets at least one previous supported profile version and the new version be accepted where practical;
  5. notification in workplans or interface docs for known consumers, especially key-cape, Keycloak/expanded-mode work, flex-auth, and application integration docs.
+

Breaking changes include:

+
  • removing or renaming a required claim;
  • changing the meaning, type, or allowed values of required claims such as tenant, principal_type, roles, groups, scope/scp, or assurance;
  • changing accepted issuer, audience, or signing validation rules;
  • weakening PKCE, MFA/assurance, local-development rejection, or emergency-access requirements;
  • moving authorization decisions into an identity provider instead of flex-auth.
+
+

Consequences

+
  • canon/standards/iam-profile_v0.2.md is the canonical profile.
  • the-custodian's v0.1 draft should carry a relocation/deprecation note pointing to this repository.
  • Hub-specific scopes such as hub:*, ops:*, and fin:* are downstream extensions, not core profile vocabulary.
  • key-cape and Keycloak must emit or normalize to the same claim contract before applications and flex-auth consume tokens.
  • The conformance suite in tools/iam-profile-conformance/ is the executable contract for implementations.
+
+

Alternatives Considered

+

Keep The Custodian Draft As Canonical

+

The draft is useful, but keeping ownership there would conflict with NetKingdom's repository scope and with ADR-0006's responsibility split. It would also leave the profile coupled to Custodian hub vocabulary.

+

Make Keycloak The Reference Provider

+

Keycloak is the expanded-mode implementation and remains important for enterprise federation. Making it the reference provider would make lightweight mode, local bootstrap, and future identity adapters secondary to one implementation. The accepted model keeps providers interchangeable behind the profile.

+

Put Scope And Role Vocabulary In The Core Profile

+

A shared vocabulary is useful, but core identity must stay stable across applications and tenants. Downstream systems can define extension scopes and roles as long as they map to the core claim shapes and flex-auth decision inputs.

+
NK-ADR-0011 · 1 · acceptednet-kingdom · docs/adr/ADR-0011-iam-profile-ownership-and-version-governance.md · ce198fc2905687ea90a2892346b6860281ac87f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0012.html b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0012.html new file mode 100644 index 0000000..3a72411 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0012.html @@ -0,0 +1,227 @@ + + + + +Playbook Capability Contract Ownership + +
NK-ADR-0012 accepted · 1 net-kingdom reviewed 2026-08-22generated from canonical source — do not edit

Playbook Capability Contract Ownership

Source: net-kingdom · docs/adr/ADR-0012-playbook-capability-contract-ownership.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2027-02-22

Status: Accepted Date: 2026-05-22 Deciders: Bernd Worsch, Codex

+

Context

+

ADR-0007 refined NetKingdom's orchestration role into a meta-orchestration layer. NetKingdom selects the services and playbooks a scenario needs, decides which parameters may be tuned, and holds the responsibility map. Railiance remains the execution-orchestration layer: Railiance playbooks provision and converge the actual infrastructure, cluster, platform services, and application layers.

+

That split requires a stable interface. If a Railiance playbook only describes behavior implicitly, NetKingdom cannot safely compose it into a scenario, compare it with another playbook, or know which parameter changes are safe. The IAM Profile provides the precedent: the consumer that needs a stable contract defines the contract, and providers conform to it.

+
+

Decision

+

NetKingdom owns the Playbook Capability Contract schema and vocabulary. Railiance owns playbook implementation and publishes one conformant declaration per playbook.

+

The first canonical contract is canon/standards/playbook-capability-contract_v0.1.md, backed by the machine-readable schema in canon/schemas/playbook-capability-declaration_v0.1.schema.json and the validator in tools/playbook-capability-contract/.

+

The contract is NetKingdom-owned with Railiance co-design:

+
  • NetKingdom defines the schema, controlled vocabulary, trust-state language, parameter-sensitivity rules, and conformance criteria.
  • Railiance authors and maintains declarations beside the playbooks they describe.
  • Railiance execution stays unchanged. The declaration never becomes the playbook runner.
  • NetKingdom meta-orchestration consumes declarations to select, parametrize, sequence, and build responsibility maps for scenarios.
+

ADR-0007 remains unchanged: execution stays in Railiance.

+
+

Versioning

+

The contract uses explicit document versions:

+
  • Patch/editorial changes clarify wording or examples without changing declaration semantics.
  • Minor versions add optional fields, vocabulary entries, or validator warnings that existing declarations can ignore.
  • Breaking versions change required fields, field meanings, allowed vocabulary, parameter-sensitivity semantics, trust-state semantics, or catalog consumption rules.
+

Declarations carry metadata.contract_version. A catalog may accept more than one contract version during a migration window, but must report the version used for each selected playbook.

+
+

Breaking-Change Governance

+

A breaking change requires:

+
  1. an ADR or ADR refinement explaining the change and migration path;
  2. a new versioned standard and schema;
  3. an updated validator;
  4. a coexistence window for the previous supported version where practical;
  5. notice to known declaration publishers, especially Railiance repos.
+

Breaking changes include:

+
  • removing or renaming required fields;
  • changing capability ids or resource-kind vocabulary;
  • changing trust-state meanings;
  • changing which parameter sensitivities are tenant-tunable;
  • changing catalog selection or override semantics;
  • moving execution responsibility out of Railiance into NetKingdom.
+
+

Consequences

+
  • Playbook declaration files live beside Railiance playbooks, normally at capabilities/playbooks/*.yaml.
  • NetKingdom can validate declarations before consuming them.
  • A playbook interface change becomes visible and versioned instead of an implicit break.
  • The responsibility map can be assembled from declarations, while Railiance keeps execution ownership.
+
+

Alternatives Considered

+

Put The Contract In Railiance

+

Railiance owns execution, so this is tempting. But NetKingdom is the consumer that needs stable scenario composition and responsibility-map inputs. Keeping the contract in NetKingdom mirrors the IAM Profile pattern and keeps scenario semantics close to the responsibility map.

+

Make Declarations Free-Form Documentation

+

Free-form docs are readable but not safely composable. NetKingdom needs a validator and controlled vocabulary so a playbook change cannot silently break a scenario.

+

Build A Dedicated Execution-Orchestration Repo Now

+

ADR-0007 explicitly defers that. The contract is useful now and does not require a new runner or repo boundary.

+
NK-ADR-0012 · 1 · acceptednet-kingdom · docs/adr/ADR-0012-playbook-capability-contract-ownership.md · ce198fc2905687ea90a2892346b6860281ac87f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0013.html b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0013.html new file mode 100644 index 0000000..bf1dfa5 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0013.html @@ -0,0 +1,241 @@ + + + + +Tenant Onboarding Grouping Taxonomy + +
NK-ADR-0013 accepted · 2 net-kingdom reviewed 2026-08-22generated from canonical source — do not edit

Tenant Onboarding Grouping Taxonomy

Source: net-kingdom · docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2027-02-22

Status: Accepted Date: 2026-07-23 Amended: 2026-08-22 (current classification versus historical identifier segment) Deciders: Bernd Worsch, Codex

+

Context

+

canon/standards/iam-profile_v0.2.md's "Tenant Claim" section lists four suggested (not exhaustive) tenant identifiers: tenant:platform, tenant:coulomb, tenant:sandbox:<name>, tenant:customer:<name>.

+

Separately, an unratified exploration (docs/princedom-isolation-exploration.md) proposes a non-exclusive capability-role model for tenants: PLTF (operates the platform), IAM (organizes its own users/secrets), VEN (provides apps/services to others), CUS (consumes apps/services from PLTF or VEN tenants) — one tenant can hold multiple roles simultaneously.

+

Binky Hedgehog GmbH is being onboarded as the platform's first tenant outside tenant:coulomb (key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md), forcing a concrete identifier decision. Binky already exercises both CUS (consumes qonto-assistant) and, per binky-control/INTENT.md, will exercise VEN (packages and sells offers built on the ecosystem to real external customers) — it is not purely a "customer" in the role sense.

+

Using a capability-role word (customer, vendor, ...) as the tenant grouping segment collides with that separate role dimension: a tenant labeled tenant:customer:binky would carry a stale, misleading label the moment it also starts acting as a vendor, and tenant identifiers are expensive to rename once tokens, OpenBao paths, and downstream config reference them.

+
+

Decision

+

Adopt an onboarding-risk / entity-shape grouping taxonomy for the tenant identifier's second segment, replacing the sandbox/customer suggested identifiers in iam-profile_v0.2.md's "Tenant Claim" section:

+
tenant:<grouping>:<name>
+
+trial        - test/trial/showcase tenants only
+friendly     - known, easily reached, tolerant of experimentation/instability
+single       - one-person business entities (freelance consultants)
+small        - up to 10 employees at time of onboarding (attoo)
+medium       - up to 100 employees (attoo)
+large        - up to 1000 employees (attoo)
+enterprise   - 1001+ employees (attoo)
+consumer     - private individuals
+family       - a legal family
+community    - a non-legal group of people
+association  - a legal association of people
+agentic      - financially enabled AI entities
+

The taxonomy has two deliberately different uses:

+
  • At creation, the identifier's grouping segment records the tenant's onboarding-time classification. The complete identifier is immutable, so this segment is historical after creation.
  • The tenant record's grouping field records current classification. It may change as the entity changes and is authoritative for present-day policy, including guardrails and spend ceilings.
+

No consumer may parse the identifier's middle segment and treat it as current grouping. Consumers needing current grouping MUST read it from tenant-engine. Identifier creation still validates the segment against this vocabulary; historical does not mean free-form or optional.

+

Grouping is deliberately orthogonal to capability role (PLTF/IAM/VEN/CUS, subsequently ratified by ADR-0014): grouping describes what kind of entity a tenant is and its current onboarding-risk classification; role describes what it does on the platform. Both are carried as tenant metadata, but never conflated into the immutable identifier segment — that conflation is exactly what this ADR avoids.

+

tenant:platform and tenant:coulomb remain reserved, ungrouped identifiers outside this taxonomy: tenant:platform is the control-plane root, not a business entity being onboarded; tenant:coulomb is the reference tenant established by ADR-0006, predating this taxonomy, and none of the twelve groupings meaningfully describe "the platform building itself." The taxonomy applies to tenants onboarded from here forward. (This sub-point completes an open question raised during KEY-WP-0004 drafting and is Codex's reasoned proposal — flagged for Bernd's explicit confirmation rather than assumed settled.)

+

First application: Binky Hedgehog GmbH maps to friendly — known, reachable, tolerant of early instability — giving tenant:friendly:binky (key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md).

+
+

Scope and Governance Classification

+

Per ADR-0011's breaking-change governance, this does not meet the breaking-change bar: it doesn't add, remove, or rename a required claim; it doesn't change tenant's type or validation rule (still an opaque string); it doesn't affect acceptance of any previously-issued token (tenant:platform, tenant:coulomb tokens remain valid as-is, and no tenant:customer:* or tenant:sandbox:* tokens have been issued yet). It only replaces non-normative suggested-identifier guidance for tenants onboarded going forward.

+

Per ADR-0011's own versioning rule ("New versions are added as new files... except for clearly editorial fixes that do not affect semantics"), this qualifies as an editorial update to iam-profile_v0.2.md's Tenant Claim section — not a new versioned profile document.

+
+

Consequences

+
  • canon/standards/iam-profile_v0.2.md's "Tenant Claim" section needs a follow-up edit replacing the sandbox/customer suggested identifiers with this taxonomy. Not made by this ADR itself — tracked as follow-up so the change is reviewable on its own.
  • key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md already reflects this decision (tenant:friendly:binky).
  • Future tenant onboarding work should classify a tenant against this list before minting an identifier, rather than reaching for a role word.
  • Tenant identifiers never change when current grouping changes. The middle segment is creation-time history; tenant-engine is authoritative for the current grouping value.
  • Policy and commercial consumers, including spend-ceiling resolution, MUST query tenant-engine and MUST NOT derive current grouping by splitting a tenant identifier.
  • The capability-role model (PLTF/IAM/VEN/CUS) remains a separate dimension, now ratified by ADR-0014. Role metadata is carried alongside — not instead of — current grouping and the historical identifier segment.
  • tenant:platform and tenant:coulomb are reserved outside the taxonomy, pending Bernd's explicit confirmation (see Decision).
+
+

Alternatives Considered

+

Keep using capability-role words as the tenant grouping

+

Rejected: tenants are already known to hold multiple roles at once (Binky is CUS now, VEN later); a role-word grouping would need renaming as a tenant's roles evolve, which tenant identifiers are specifically expensive to do once tokens, OpenBao paths, and config reference them.

+

Retrofit tenant:coulomb into the new taxonomy

+

Considered tenant:friendly:coulomb or similar. Rejected for now: Coulomb is structurally the reference tenant from ADR-0006, predating this taxonomy, and none of the twelve entity-shape groupings describe "the platform's own ecosystem-development tenant." Revisit if a future grouping is ever added that genuinely fits it.

+

Do nothing / keep the four original suggested identifiers

+

Rejected: tenant:customer:binky was the working default going into Binky's onboarding despite the role-collision problem above; the fleet needs this resolved before the first non-Coulomb tenant goes live, not after.

+
+

Follow-Up

+
  • Edit canon/standards/iam-profile_v0.2.md's Tenant Claim section to replace the old suggested identifiers with this taxonomy (separate, reviewable change).
  • Confirm the tenant:platform/tenant:coulomb reserved/ungrouped treatment explicitly.
  • ADR-0014 and the Tenant Engine Boundary Contract define how capability-role metadata is carried alongside grouping.
  • Keep tenant-engine's identifier parser vocabulary-validating for creation and lookup compatibility, but do not expose parsed grouping as current classification.
+
NK-ADR-0013 · 2 · acceptednet-kingdom · docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md · ce198fc2905687ea90a2892346b6860281ac87f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0014.html b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0014.html new file mode 100644 index 0000000..ce5ae47 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0014.html @@ -0,0 +1,225 @@ + + + + +Tenant Capability Roles, Carrying Mechanism, and Tenant-Engine Ownership + +
NK-ADR-0014 accepted · 1 net-kingdom reviewed 2026-08-22generated from canonical source — do not edit

Tenant Capability Roles, Carrying Mechanism, and Tenant-Engine Ownership

Source: net-kingdom · docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2027-02-22

Status: Accepted Date: 2026-07-23 Deciders: Bernd Worsch, Codex

+

Context

+

ADR-0013 introduced the tenant onboarding grouping taxonomy (trial/friendly/single/.../agentic), deliberately orthogonal to a separate, unratified capability-role model sketched in docs/princedom-isolation-exploration.md: PLTF (operates the platform), IAM (organizes its own users/auth/secrets), VEN (provides apps/services to others), CUS (consumes apps/services from PLTF or VEN tenants) — non-exclusive, a tenant may hold several at once.

+

That exploration left open where capability roles actually live (a per-token claim vs. a registry), who owns them, how they're granted or revoked, and how this interacts with the IAM Profile's existing roles claim — which is a per-subject claim ("coarse identity roles" for the human/service/agent holding the token), a different concept from a per-tenant capability fact. Conflating the two would be a category error: roles: ["VEN"] on a token would ambiguously mean "this subject has vendor-role" vs. "this subject's tenant is a vendor."

+

No existing service owns tenant-as-an-entity facts (existence, grouping, capability roles, plan/subscription state) as a queryable resource. user-engine's own boundary contract (canon/standards/user-engine-boundary-contract_v0.1.md) explicitly scopes user-engine to consuming tenant identifiers and storing tenant-scoped records, not owning tenant identity or capability facts.

+

Bernd's direction (2026-07-23):

+
  • Implement the previously-discussed hybrid carrying approach: cache a tenant's capability roles on the token for ordinary decisions, but require a live check for critical/high-stakes actions.
  • Role grants are usually tied to a payment plan — most concretely, IAM means the tenant has its own dedicated key-cape/Keycloak instance for isolation, scale, and performance, which is itself a paid capability.
  • trial-grouped tenants may hold any capability role without restriction, specifically so the platform can showcase, test, and explore every role. Safety for trial tenants comes from resource guardrails (spend limits defaulting to zero budget, entity/action count limits), not from role gating — guardrail design itself is future work, not this ADR.
  • A new service, tenant-engine, will be built (Bernd) as the owner of this domain, organized beside user-engine rather than inside it — smaller, single-purpose services are easier to reason about and drift less, matching the fleet's existing convention (activity-core, audit-core, user-engine, and others).
+
+

Decision

+
  1. Capability-role vocabulary ratified as core NetKingdom vocabulary: PLTF, IAM, VEN, CUS, non-exclusive. IAM specifically means: the tenant operates its own dedicated IAM implementation instance (lightweight key-cape or expanded Keycloak) rather than sharing the platform's, for isolation/scale/performance — not "any tenant that happens to have users."
+
  1. tenant-engine is the canonical owner of tenant-domain facts: tenant existence, grouping (ADR-0013), capability roles (this ADR), plan/ subscription assignment, and — reserved for future design, not built now — guardrail/quota policy. It is a new, separate service, not a module inside user-engine. Its ownership boundary is defined in the companion contract, canon/standards/tenant-engine-boundary-contract_v0.1.md.
+
  1. Carrying mechanism: hybrid cache + live re-validation. tenant-engine is the single source of truth. key-cape stamps a cached, optional tenant_roles claim onto issued tokens at issuance time, sourced from tenant-engine (added to the IAM Profile as a new optional claim — canon/standards/iam-profile_v0.3.md). Consumers may trust the cached claim for ordinary decisions. flex-auth MUST re-validate live against tenant-engine — never trust the cached claim alone — before authorizing privileged or high-stakes actions, using the same threshold class the profile already defines for assurance.level >= aal2 (privileged, destructive, platform-root, secret, credential-vending flows). This bounds staleness risk for ordinary actions to a token's short lifetime (5–30 minutes for service/agent tokens, per the profile's Token Lifecycle table) while guaranteeing freshness exactly where it matters most.
+
  1. Role governance is plan-linked. Granting a role is normally a consequence of a tenant's payment-plan state in tenant-engine, not a separate manual workflow — starting with IAM. tenant-engine records which plan grants which role(s); adaptive-pricing remains the source of plan/pricing-model definitions, tenant-engine owns the tenant's current plan/subscription assignment, referenced by id, never duplicated locally. Whether VEN needs an approval gate beyond payment (reselling access carries legal/compliance exposure a payment alone doesn't cover) is not resolved by this ADR — left to tenant-engine's own workplan.
+
  1. Trial tenants may hold any capability role, unrestricted. The trial grouping's purpose (showcase, test, explore) requires demonstrating every role. Safety is enforced through resource guardrails instead: trial tenants default to a spend budget of zero, with entity and action count limits to follow. Guardrail policy design (exact limits, enforcement point, override process) is real, near-term future work, reserved as tenant-engine's to own once designed — not specified by this ADR.
+
  1. Grouping and capability role are independent axes recorded on the same tenant record in tenant-engine. Neither constrains the other except where a future guardrail policy explicitly says so.
+
+

Consequences

+
  • canon/standards/iam-profile_v0.3.md adds the optional tenant_roles claim and folds in ADR-0013's tenant-identifier vocabulary update (both non-breaking per ADR-0011's own minor-version rule — no existing implementation is invalidated by either change). Supersedes iam-profile_v0.2.md.
  • tenant-engine becomes a new repository with its own workplans (Bernd). canon/standards/tenant-engine-boundary-contract_v0.1.md defines its ownership boundary now, before code exists — the same sequencing user-engine's contract followed.
  • flex-auth policy packages gating high-stakes actions must add a tenant-engine live-lookup step; they cannot trust tenant_roles alone for those decisions.
  • key-cape needs a tenant-engine integration at token-issuance time to source the cached claim — tracked in key-cape's own workplans, not here.
  • docs/platform-identity-security-architecture.md's Tenant Model section is updated to reflect the grouping + role split and tenant-engine's role (companion change alongside this ADR).
  • Guardrail/quota policy is named as required near-term work and given an owner (tenant-engine), but is explicitly not designed by this ADR.
+
+

Alternatives Considered

+

Token-claim-only, no live re-validation

+

Rejected: staleness would be unbounded within a token's lifetime for genuinely high-stakes actions. A stale VEN grant surviving a plan downgrade or cancellation is not an acceptable risk for money-movement or credential-vending flows — exactly the class the profile already treats as requiring the strongest assurance.

+

Registry-only, no cached claim

+

Rejected: every ordinary request would pay a tenant-engine round-trip even for non-critical checks, adding latency and a hard runtime dependency for every consumer, not just the ones handling privileged actions.

+

Restrict role eligibility by tenant grouping (e.g., trial cannot hold VEN)

+

Rejected per Bernd's direction: trial tenants exist specifically to showcase every role. Resource guardrails are the intended safety mechanism instead, keeping the two axes (grouping, role) independent.

+

Put tenant-role/plan storage inside user-engine

+

Rejected. user-engine's own boundary contract scopes it to consuming tenant identifiers and storing tenant-scoped user records, not owning tenant-as-an-entity facts. A dedicated service avoids coupling a security-critical, high-frequency lookup (used by flex-auth on every privileged decision, and by key-cape on every token issuance) to user-engine's much larger surface (registration flows, factor models, family dataspace onboarding) that has nothing to do with tenant capability state.

+
+

Follow-Up

+
  • tenant-engine repository creation and its own workplan (Bernd).
  • key-cape integration: source tenant_roles from tenant-engine at token issuance.
  • flex-auth policy package updates: live tenant-engine re-validation gate for privileged actions.
  • Guardrail/quota policy design for trial (and eventually all) tenants: spend limits, entity/action count limits, enforcement point, override process.
  • Resolve whether VEN needs an approval gate beyond payment-plan state.
+
NK-ADR-0014 · 1 · acceptednet-kingdom · docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md · ce198fc2905687ea90a2892346b6860281ac87f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0015.html b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0015.html new file mode 100644 index 0000000..61e6c96 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/NK-ADR-0015.html @@ -0,0 +1,229 @@ + + + + +NetKingdom Railiance Workload Packaging and Relational Platform + +
NK-ADR-0015 accepted · 1 net-kingdom reviewed 2026-08-22generated from canonical source — do not edit

NetKingdom Railiance Workload Packaging and Relational Platform

Source: net-kingdom · docs/adr/ADR-0015-netkingdom-railiance-workload-packaging-and-relational-platform.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2027-02-22

Status: Accepted Date: 2026-08-11 Deciders: Bernd Worsch, Claude

+

Context

+

NetKingdom's runtime services are deployed today outside the Railiance reef/rail/rapp model. tenant-engine and user-engine run on the coulomb substrate with digest-pinned images (forgejo.coulomb.social/coulomb/{tenant,user}-engine@sha256:…), but their Kubernetes manifests live in this repo at sso-mfa/k8s/<service>/runtime.yaml — a canon repo holding runtime YAML — applied imperatively, with verify-t0*.sh scripts as verification. Neither service declares railiance/app.toml, appears in reef-railiance/bindings/rapps.yaml, or is reconciled by a GitOps controller.

+

The decision to bring NetKingdom under Railiance governance forces two questions this ADR settles.

+

Packaging granularity. railiance-master/docs/repository-axes.md maps rapp-* to "one Railiance-managed workload" and states that a rapp "does not replace the responsibility repo that owns the broader domain. It is explicitly about managed wrapping, not ownership." Precedent exists in two shapes: rapp-qonto (ownership_repo: qonto-assistant, one workload, thin package repo pointing back at the domain repo) and rapp-postgres (ownership_repo: railiance-platform, one workload with a consumers: list and a consumer_contract). The open question was whether NetKingdom's engines are one workload or several.

+

Relational storage. The engines have diverged. user-engine already runs CloudNative PG (kind: Cluster in its own namespace) with NetworkPolicies and a migrations/ surface. tenant-engine runs SQLite on a 1 Gi RWO PVC, chosen in TEN-WP-0004 for expedience. railiance-platform (S3) declares CloudNative PG the canonical database operator, and rapp-postgres packages it with a database-per-consumer boundary unit and OpenBao dynamic credentials. Separately, rail-kubernetes's wave-1 contract explicitly states the rail must not assume "a generic persistent-storage contract" — so a per-workload PVC is unsupported by the rail regardless of which database is chosen.

+

TEN-WP-0005 was drafted against PostgreSQL and implemented against SQLite, because T05 must roll out against the runtime that exists. That workplan's TenantStore Protocol was kept as an explicit seam for this decision.

+
+

Decision

+

1. Separate rapp-* repos per engine

+

Each deployed NetKingdom engine gets its own managed workload package repo:

+
rapp-tenant-engine    ownership_repo: tenant-engine
+rapp-user-engine      ownership_repo: user-engine
+

Following rapp-qonto's shape: the rapp owns Railiance packaging, rail compatibility, workload-specific smoke/health checks, rollout and rollback expectations, secret references, and dependency declarations. The engine repo retains domain ownership; net-kingdom retains canon.

+

This follows the one-workload rule rather than working around it. The engines are already independent on every axis a rapp declaration must state: separate namespaces, separate image digests, separate release cadence, and (until decision 2 lands) different storage. A single rapp-netkingdom would have to declare one workload_identity, one rollout_contract, and one rollback_contract across services that genuinely differ.

+

The decisive property is independent rollback: rolling back a bad tenant-engine revision must not force a user-engine revision change. A single rapp would make that either impossible or fictional.

+

Rollout ordering between the engines is a declared constraint between rapps, not a reason to merge them: tenant-engine before user-engine, following the dependency direction.

+

2. secrets-engine is not packaged as a rapp

+

secrets-engine has no deployed workload — no Containerfile, no Makefile, no Kubernetes manifests. It is a catalog/policy/workflow layer over OpenBao, whose packaging already belongs to rapp-openbao with custody and lane policy in railiance-platform.

+

A rapp wraps a workload; secrets-engine has none to wrap. Revisit only if it becomes a runtime service, at which point this decision reopens for that repo alone and not for the packaging model.

+

3. CloudNative PG is the default relational platform for production

+

Production NetKingdom services requiring relational storage use CloudNative PG via rapp-postgres, consuming the database-per-consumer boundary unit and the openbao-dynamic-database-credential lane, with tenant-keying per business-app-service-contract_v0.1 section 1.3.

+

Per-workload SQLite-on-a-PVC is not a production pattern. It remains acceptable for local development and tests.

+

tenant-engine migrates from SQLite to cnpg. Its TenantStore Protocol makes this a backend swap behind an existing seam rather than a rewrite; the lifecycle semantics proven in TEN-WP-0005 (atomic compare-and-swap, durable idempotency receipts, forward-only migration) are the conformance bar the PostgreSQL backend must meet, and its store-conformance suite is already parametrised across backends to enforce exactly that.

+
+

Consequences

+
  • Two new rapp-* repos to create, each requiring a declarations/rapp.yaml, a binding in reef-railiance/bindings/rapps.yaml, and its own readiness evidence progression (declared → installed → verified → production-approved).
  • Per-binding evidence multiplies, but per-reef evidence does not — the reef-production-readiness-contract puts substrate, ingress, storage, network, and backup evidence on the reef, once. What multiplies is the critical-workload gate (threat model, negative authorization tests, rollback rules, residual-risk owner), which identity-plane services warrant individually. A shared NetKingdom threat model may be referenced by both bindings rather than duplicated.
  • Runtime manifests move out of net-kingdom/sso-mfa/k8s/ into the respective rapp repos. This repo stops holding runtime YAML and returns to canon, standards, and ADRs.
  • tenant-engine requires a data migration from SQLite to cnpg, including migration of existing tenants, grants, plan assignments, and idempotency receipts. TEN-WP-0005-T05's rollout plan is affected: it currently targets the SQLite runtime.
  • NetKingdom services become subject to the railiance/app.toml staged promotion contract (Stage 1 local → Stage 2 canary → Stage 3 production), including declared rollback commands and health endpoints. Digest-pinned images already satisfy digest_policy = "required".
  • The reef must accept or mitigate its single-server and shared-control-plane risk for each NetKingdom binding, as it must for rapp-qonto. The reef name and a high criticality label are not evidence.
+
+

Alternatives Considered

+

One rapp-netkingdom for all associated repos. Rejected. It would reduce binding-evidence count and match the current coupled deployment (both engines' manifests share one tree; flex-auth runs per-consumer instances flex-auth-tenant-engine and flex-auth-user-engine). But it contradicts the one-workload rule in repository-axes.md, and would force a single rollback contract across independently versioned services. A rollback contract that depends on which service failed is not a contract. This alternative would be correct only if the engines were always promoted and rolled back as one atomic cutover — which their separate digests and release cadences contradict.

+

Keep SQLite for tenant-engine, standardise later. Rejected. It leaves the two engines operationally dissimilar, which is the opposite of the intent, and rail-kubernetes does not support the per-workload persistent-storage contract the PVC depends on. Deferring also grows the migration: every tenant, grant, and receipt written between now and the cutover is data to move.

+

A rapp per NetKingdom concern with a consumers: list, mirroring rapp-postgres. Rejected as a category error. rapp-postgres's consumers are consumers of one workload (PostgreSQL); NetKingdom's engines are distinct workloads, not consumers of a shared one.

+
+

Follow-Up

+
  • Create rapp-tenant-engine and rapp-user-engine; move runtime manifests out of net-kingdom/sso-mfa/k8s/.
  • Add both bindings to reef-railiance/bindings/rapps.yaml at declared.
  • Add railiance/app.toml to tenant-engine and user-engine.
  • Open a tenant-engine workplan for the cnpg backend and data migration; reconcile with TEN-WP-0005-T05, whose rollout currently targets SQLite.
  • Confirm whether secrets-engine is intended to remain a non-deployed control layer. This ADR assumes it is.
  • Record the NetKingdom-wide threat model that both bindings will reference.
+
NK-ADR-0015 · 1 · acceptednet-kingdom · docs/adr/ADR-0015-netkingdom-railiance-workload-packaging-and-relational-platform.md · ce198fc2905687ea90a2892346b6860281ac87f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RINFRA-ADR-0005.html b/docs/evidence/2026-10-11-served-publication-history/pages/RINFRA-ADR-0005.html new file mode 100644 index 0000000..56d619c --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RINFRA-ADR-0005.html @@ -0,0 +1,211 @@ + + + + +k3s API is tunnel-only + +
RINFRA-ADR-0005 accepted · accepted-1 railiance-infra reviewed 2026-08-22generated from canonical source — do not edit

k3s API is tunnel-only

Source: railiance-infra · docs/adr/ADR-005-k3s-api-tunnel-only.md · f3e8bf3ab4b1dafa4d0b251b88ab2ef23a30c37b

Review due: 2027-02-22

Status: Accepted Date: 2026-08-15 Deciders: implementation of RAIL-HO-WP-0009-T04 Workplans: RAIL-HO-WP-0009

+

Context

+

Operator addresses on this network rotate with the ISP lease. A public UFW allowlist for 6443/tcp is therefore a treadmill:

+
  • miss a rotation and kubectl breaks mid-session
  • leave the old grant standing and it becomes a grant to a stranger
  • hand-add the new address and the declaration drifts again
+

That cycle produced this workplan. The live allowlist drifted by hand during the session that was fixing allowlist drift, and again before the next session (89.244.90.248 appeared undeclared). On 2026-08-15 the workstation egress address was 85.132.220.102 — already on the revoked list as a "historic" operator address.

+

docs/deploy-stack.md already documents API access over the ops-bridge SSH tunnel for CoulombCore (k3s-api-coulombcore, local port 16443). The same tunnel already exists for Railiance01 (k3s-api-railiance01, local port 16444). SSH itself stays public, so the host remains recoverable.

+
+

Decision

+

The public k3s API allowlist is empty. Operator and agent kubectl access uses the ops-bridge tunnels:

+
ClusterTunnelLocal portRemote
CoulombCorek3s-api-coulombcore164436443
Railiance01k3s-api-railiance01164446443
+
bridge up k3s-api-railiance01
+# kubeconfig server: https://127.0.0.1:16444
+

Trade: every operator kubectl action depends on ops-bridge. That is accepted. A rotating public allowlist is the worse dependency.

+

Emergency break-glass remains SSH: ssh railiance01 -- sudo k3s kubectl …. Do not re-open 6443/tcp to Anywhere.

+
+

Consequences

+
  • k3s_api_allowed_sources stays [].
  • Former public grants live in k3s_api_revoked_sources so a firewall-tagged converge deletes them.
  • Goss asserts the 6443 allowlist size is exactly the declared length (zero) and that no revoked address remains.
  • Amending this ADR is required before adding any new public 6443 source.
+
RINFRA-ADR-0005 · accepted-1 · acceptedrailiance-infra · docs/adr/ADR-005-k3s-api-tunnel-only.md · f3e8bf3ab4b1dafa4d0b251b88ab2ef23a30c37b
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RISK-F-0001.html b/docs/evidence/2026-10-11-served-publication-history/pages/RISK-F-0001.html new file mode 100644 index 0000000..7f3a3a0 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RISK-F-0001.html @@ -0,0 +1,245 @@ + + + + +flex-auth /v1/check authenticates no caller + +
RISK-F-0001 fixed · published-1 risk-nexus reviewed 2026-08-20generated from canonical source — do not edit

flex-auth /v1/check authenticates no caller

The estate's authorization oracle authenticated no caller for as long as the endpoint existed. Found by reading, not by monitoring; fixed in two days.

Source: risk-nexus · findings/RISK-F-0001-flex-auth-unauthenticated-check.md · a13d954f8597fd92201746e2d52f031a5a88d969

Review due: 2027-02-20

What is true

+

POST /v1/check and POST /v1/batch_check authenticate no caller. Any workload with network reach to the ClusterIP Service can assert any subject and any tenant and receive an authoritative allow.

+

flex-auth is the estate's authorization oracle. Every service that delegates a decision to it is relying on an answer that anyone able to reach the pod can obtain for any identity they care to name.

+

Self-reported by flex-auth as A0 on their own inbound surface, in their Tenancy Posture review. Their words: "flex-auth is the estate's authorization oracle and it trusts its callers completely."

+
+

How it was found

+

Not by a probe, an incident, or an alert. By flex-auth assessing themselves against the Tenancy Posture A ladder during a review they were asked to do — and their own note says they did not know they were carrying it.

+

That provenance matters for triage: nothing was watching for this, and nothing would have found it. It has presumably been true for as long as the endpoint has existed.

+
+

Exposure, as far as the reporter stated it

+
  • The Service is ClusterIP, so reach requires a workload inside the cluster.
  • No claim was made that network policy restricts which workloads can reach it, and this record does not assume one. If a default-deny NetworkPolicy fronts the service, that materially changes the exposure and should be verified rather than inferred — flex-auth did not state it either way, and I have not checked, because doing so would be reporting on a system I do not own.
+
+

What makes it worse than a single service's defect

+

A false allow from this endpoint is not confined to flex-auth. It is the answer other services act on. tenant-engine separately reports that its own mutations are authorized by flex-auth and that direct authority over its rows would mean "privilege escalation across NetKingdom rather than data tampering confined to one store". The same reasoning applies to a forged allow.

+
+

Owner and state

+

flex-auth owns the fix and has tracked it as FLEX-WP-0015-T02, to ship through the staged-promotion path rather than a direct apply. They classify it as the only urgent item of their five follow-ups. Nothing is asked of them by this record beyond what they have already committed to.

+
+

What this repo is asked to decide

+
  1. Severity. Not the reporter's to set.
  2. Disclosure. Build mode is currently public-by-default, and this is precisely the class of finding where that stops being obviously right — a live authorization bypass in the service every other service trusts. The controlled-disclosure scheme this repo anticipates does not exist yet, so the choice today is publish or hold, with no mechanism between them.
  3. Escalation. Whether this reaches the operator personally. The candidate triggers in INTENT include "anything exposing real tenant data" — this exposes the decision that governs access to it, which may or may not be the same thing, and that judgement is this repo's.
+
+ +

Register ruling — 2026-08-19

+

critical (I4 × L3, no fidelity modifier), embargoed until FLEX-WP-0015-T02 ships, escalated to the operator on trigger 1.

+

The question this finding put — whether governing access to tenant data counts as exposing it — is answered yes. An authorization oracle that can be forged is not one step removed from the data; it is the step.

+

Impact is I4 because a forged allow does not stay here: it is the answer other services act on, and tenant-engine has stated what direct authority over its rows would mean. Likelihood is L3 — the normal working set, inside the cluster, no additional step — and the register follows the reporter in neither assuming a default-deny NetworkPolicy nor assuming its absence.

+

No fidelity modifier: this endpoint answers honestly about a caller it never checked. The false-record hazard lives in RISK-F-0002's constraint, where a consumer of this endpoint would begin signing records asserting an authorization that was never made. That constraint binds this finding's remediation: ops-warden's policy.enabled must not be turned on until /v1/check authenticates its callers, and the ordering is

+
flex-auth warn-only -> ops-warden gate presents its SA token -> logs clean
+                    -> flex-auth fail-closed -> ops-warden policy.enabled: true
+

Nothing further is asked of flex-auth beyond what they have committed to, except one fact only they can supply: is there a default-deny NetworkPolicy in front of the Service? It is the single fact that would most change this grade, and it is the first question at review.

+

Reasoning: docs/rulings/2026-08-19-first-grading.md.

+
+

Reviews

+
  • 2026-08-19 — graded. Next review 2026-08-26 (critical → 7 days). Open at review: the NetworkPolicy question; whether FLEX-WP-0015-T02 has moved; whether the embargo still holds.
+
+

Re-grade and close — 2026-08-19 (same day)

+

Corrected from critical to high, and closed as fixed. Both changes come from messages that were already in this repo's inbox when the first grade was set. The register graded before it read them.

+

The exposure was narrower than graded. flex-auth answered the NetworkPolicy question on 2026-08-18: both production Deployments ship a NetworkPolicy in the same manifest, and it is narrower than default-deny — ingress restricted to one namespaceSelector plus one podSelector on port 8080, egress empty. In force since before the period the A0 describes. So the reachable set was never "any pod in the cluster"; it was the single paired workload per Deployment.

+

That is L2, not L3. Impact stays I4 — what a forged allow reaches does not change — so the grade is high. flex-auth also corrected their own earlier phrasing to ops-warden in the same message, unprompted, and that correction is why the fact reached this register at all.

+

Three caveats flex-auth asked to be recorded rather than taken from them, and they are why the grade did not fall further: label selectors are network position, not identity; the policy could not bind the asserted resource.system, which is what made cross-system impersonation possible; and enforcement depends on a CNI they could not verify from a cluster where kubectl returned Unauthorized. They said so rather than letting a manifest stand in for a probe.

+

It is fixed. On 2026-08-19 flex-auth reported both production Deployments enforcing ADR-0004 TokenReview, with live unbound-request probes returning 401 rather than a decision, on both the user-engine and tenant-engine pins. FLEX-WP-0015 is finished and tenancy.current.A is 2. That is a probe against the running system, which is the standard docs/method/review.md sets for closing: something concrete read, not something been told.

+

Disclosure flips to public. The embargo condition was "FLEX-WP-0015-T02 ships to production" and it is met. Handover to policy-nexus is the next step and is not done yet.

+

The escalation is withdrawn without being sent. It was pending-operator for roughly four hours, and the fix landed first. Withdrawing it is correct — escalating a fixed defect makes the operator the queue for history — but the register does not get to be pleased about it. The escalation would have been sent on facts that were already stale, and only luck put the fix on the same day.

+

What this cost, recorded because it is the register's own defect. The NetworkPolicy answer arrived 2026-08-18. The fix notice arrived 2026-08-19 at 12:35. The first grading ran at 21:14 the same day, on neither. Reading the inbox is now step 0 of grading and question 0 of every review — see docs/method/review.md — and this finding is the case that bought it.

+
+

Reviews

+
  • 2026-08-19 — re-graded high, closed fixed, disclosure public, escalation withdrawn. Remaining: handover to policy-nexus; the CNI enforcement question is flex-auth's and no longer this finding's.
  • 2026-08-20 — not clean: Publication handover requested; publication front-matter applied and the wait on policy-nexus typed. Cadence instant → instant; checked again immediately.
+
RISK-F-0001 · published-1 · fixedrisk-nexus · findings/RISK-F-0001-flex-auth-unauthenticated-check.md · a13d954f8597fd92201746e2d52f031a5a88d969
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RISK-F-0008.html b/docs/evidence/2026-10-11-served-publication-history/pages/RISK-F-0008.html new file mode 100644 index 0000000..ef0392f --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RISK-F-0008.html @@ -0,0 +1,274 @@ + + + + +The legal basis for retaining audit facts against an erasure request has been assumed, never established + +
RISK-F-0008 accepted · published-1 risk-nexus reviewed 2026-08-20generated from canonical source — do not edit

The legal basis for retaining audit facts against an erasure request has been assumed, never established

The estate retains personal data in audit records on grounds nobody had established. Published as a question, because it is one.

Source: risk-nexus · findings/RISK-F-0008-audit-retention-legal-basis-assumed.md · a13d954f8597fd92201746e2d52f031a5a88d969

Review due: 2027-02-20

What is true

+

audit-core holds audit evidence across tenants, targets R2 on the Tenancy Posture retention ladder, and has declared R4 — verified erasure — unreachable by design. The technical reasoning is sound and documented (audit-core/docs/erasure-and-audit.md, framework Decision 4.5.3): crypto-shredding would destroy the evidence the service exists to hold, and their integrity chain commits to a SHA-256 of the cleartext record, which survives key destruction as a confirmation oracle against low-entropy audit rows. Destroying a key does not erase content a surviving commitment can still be tested against.

+

The consequence is that if an Article 17 request arrives naming a data subject in the audit trail, audit-core has no mechanism. The answer would rest on audit evidence being exempt — legal obligation, or legitimate interest in fraud and security investigation.

+

Those grounds are ordinary. Nobody in this estate has actually reached them. audit-core routed the question here on 2026-08-18 rather than absorbing it, saying plainly that they are not competent to answer it and that they have been assuming it. §19.11 of the framework says the same in its own words: the legal basis for retaining audit facts remains a risk/legal question outside the framework.

+
+

Why this repo owns it

+

This is the first finding where fix_owner is risk-nexus.

+

INTENT.md moved regulatory intake here from policy-nexus on 2026-08-17, precisely because deciding what a rule demands of us is a judgement about risk rather than an act of publishing. audit-core routed it by both available routes and asked for an owner rather than an opinion. Refusing it would be this repo declining its own remit.

+

What this repo owns is the record: what the source says, when, and what therefore is or is not established. It does not own legal advice — INTENT.md is explicit — and it does not own the redesign. If the basis does not hold, audit-core owns encrypt-then-hash at accept time, and that is not retrofittable onto events already accepted.

+
+

The three questions, as asked

+
  1. On what basis does the estate retain personal data inside audit records against an erasure request, and does that basis hold for the categories audit-core stores?
  2. Does it hold across the full 30-day recoverable window and beyond, given that at P1 the real erasure horizon is the maximum across every co-resident on platform-pg, not the value audit-core declares?
  3. If it does not hold, R4 is urgent rather than theoretical, and the answer is a substantial redesign with a long lead time.
+
+

Register ruling — 2026-08-19

+

medium today (I3 × L2), high at production, public, escalated on trigger 2.

+

I3: an unmet retention obligation in the audit store crosses from a technical question to an obligation with an outside counterparty, and the remediation is a non-retrofittable redesign rather than a patch. L2: no request has arrived and the estate holds no real data subject's records yet, but the trigger is somebody else's to pull and needs no foothold here.

+

production_rescore: true. The likelihood of an Article 17 request is a function of having real users; that is exactly what production means.

+

Escalation, trigger 2 — "creates or reveals an obligation with an outside counterparty". It reveals one. The estate cannot decide unilaterally that this obligation is small, and the operator is the only party who can commission an answer that is more than an assumption. The ask is narrow: authorise someone to establish the basis, or record that the estate knowingly runs on the assumption and for how long.

+

Disclosure public. Nothing here shortens a path to a defect: it is a question about a legal basis, published as a question. audit-core's technical reasoning is already written down and worth reading.

+
+

How it got here

+

Ruled a note on 2026-08-19 (RISK-N-0002) on the reasoning that no obligation exists yet. That ruling was made without reading audit-core's message, which had been in this repo's inbox since 2026-08-18 and asks specifically for an owner. The note was wrong on the second floor test: recording this does change a decision, because the redesign it might force cannot be retrofitted and therefore has to be decided early or not at all.

+

RISK-N-0002 is superseded by this record.

+
+

Reviews

+
  • 2026-08-19 — promoted from note, graded, escalated. Open at review: has the basis been established or the assumption recorded; has anything changed about what categories audit-core stores.
+
+

Suggested disposition — 2026-08-20, proposed by risk-nexus

+

Offered because this repo owns the finding and the operator asked for a direction. It is not legal advice, and this repo cannot make it into one: what follows is a route to an answer and a hedge against the answer being no.

+

The reframe: the expensive thing is not the legal question

+

audit-core asks whether the exemption holds. That question is cheap to answer badly and expensive to answer properly, and the temptation is to schedule the proper version and wait.

+

But the cost of a "no" is not fixed — it grows daily. The remedy they name, encrypt-then-hash at accept time, cannot be retrofitted onto events already accepted. Every day the estate accepts events under the current scheme, the un-erasable set grows by one day. So the decision that actually needs taking now is not "is it exempt" but "do we keep manufacturing records we could never erase while we find out".

+

That splits the finding into two decisions with very different prices.

+

1. Establish the basis internally, now, for the cost of an afternoon

+

Not a legal opinion — a written determination that says which ground is being relied on, for which category of data, and for how long. Today the estate has no such document; that is the whole finding.

+

The shape it should take, per category of personal data in the audit trail:

+
CategoryLikely groundThe part that is actually arguable
Operator and agent identifiersArt 6(1)(f) legitimate interest in security, with Recital 49 squarely on pointlittle — this is the ordinary case
Counterparty or end-user identifiers in event payloadsArt 17(3)(e), defence of legal claims; Art 6(1)(f)duration, not existence
Commercial records that happen to pass through auditArt 17(3)(b) plus German §257 HGB / §147 AO retentionscope — retention duties cover books and invoices, not application logs generally
+

Where such determinations usually fail is not the ground. It is the retention period: a blanket "we keep audit forever under legitimate interest" is much weaker than "we keep these fields for N months because X". That lands precisely on audit-core's question 2 — the real horizon being the maximum across every co-resident on platform-pg rather than the declared value.

+

Recording the determination converts an assumption into a position that can be argued with. That is what this register exists to produce, and it does not require a lawyer to write down.

+

2. Stop the un-erasable set from growing — a cheaper hedge than the redesign

+

audit-core's stated obstacle is precise and correct: their chain commits to SHA-256(cleartext), audit records are low-entropy, so the retained hash survives key destruction as a confirmation oracle. Guess, hash, compare.

+

The oracle exists because the commitment is over cleartext with no secret in it. A keyed commitment removes it: replace the digest with an HMAC (or a hash over record plus a high-entropy per-subject salt) where the key or salt lives outside the audit store and is destroyable per subject.

+

What that buys, and why it is cheaper than the redesign they costed:

+
  • Destroying the per-subject key makes the commitment untestable — no guess can be confirmed. That is crypto-shredding restored, which their analysis correctly found unavailable under a plain hash.
  • The integrity chain still verifies. It chains over commitment values, and those persist after key destruction; what is lost is the ability to re-derive a commitment from cleartext, which is exactly what erasure means.
  • It is a change at accept time only. No re-processing of stored events, no new storage layer, no change to the read path.
+

This is a suggestion to audit-core, not an instruction, and they own whether it is sound — they know their chain and this repo does not. The claim worth testing with them is narrow: does a keyed commitment restore erasability without breaking chain verification? If yes, the expensive redesign becomes a contingency rather than a plan, and the daily accrual stops.

+

3. Buy the real answer only when something triggers it

+

An external determination costs money and needs a real question. Propose three triggers, any of which fires it:

+
  • the estate first holds a real person's data;
  • a counterparty contract requires a stated erasure position;
  • an actual Art 17 request arrives.
+

Until one fires, the internal determination plus the hedge is a proportionate posture, and severity_at_production: high plus production_rescore: true already guarantee this is re-read before production completes.

+

What this repo would record if the operator agrees

+

status: accepted with the determination attached, escalation answered as rule, and the review kept at 90 days. The finding stays open and visible until the determination exists — an accepted risk with no written basis is the same assumption it started as, wearing a different word.

+

Also worth saying, because it is the cheapest fix of all

+

Every field of personal data that never enters the audit trail is a field with no erasure question. Where an opaque subject identifier would carry the same evidentiary weight as a name or an address, the identifier is strictly better, and that is a audit-core design choice available today at no legal cost.

+
+

Operator decision — 2026-08-20: minimise the identity, keep the accountability

+

The custodian ruled on what goes into an audit record, which is the half of this finding that shrinks the question rather than answering it:

+
  1. Opaque subject identifiers are preferred. Where an opaque id carries the same evidentiary weight as a name or an address, it is the id that goes in.
  2. Agent identifiers where possible. Agents act; attribute to the acting agent identity rather than to a person behind it.
  3. Operator credentials only where necessary. Not as a convenience, not as a default — where the record genuinely requires the operator.
  4. Policy decisions are tracked to the responsible party. Accountability is preserved by linking a decision to who is answerable for it, not by retaining personal data in the record itself.
  5. Zone guarantees may raise the floor. If a zone establishes additional privacy, pseudonymity or anonymity guarantees, those apply — the current level is not a permanent ceiling. That work is zone-engine's (ZONE-WP-0001), and this finding should be re-read when a zone lands one.
+

Why this is more than a preference. Personal data that never enters the audit trail has no erasure question, no exemption to establish, and nothing to argue about with a regulator. Points 1-3 shrink the population the legal basis has to cover; point 4 is what stops that shrinking from costing accountability, which is the usual objection to minimising an audit log.

+

It also changes the shape of the accrual problem. The un-erasable set still grows daily, but each day's records now carry less that would need erasing — so the cost of a "no" answer falls with every event accepted under the new rule rather than rising.

+

What is still outstanding, and stays escalated:

+
  • The written determination of the retention basis — which ground, for which category, for how long. risk-nexus owns writing it; it needs no further authorisation and is scheduled into the next workplan.
  • The trigger list for buying an external answer (first real person's data, first counterparty contract requiring a stated position, first Art 17 request). Proposed, not yet ruled on.
+

The escalation is therefore partially-answered, not closed. make check will keep listing it.

+

Routed to audit-core on 2026-08-20, together with the keyed-commitment question — which remains theirs to judge, because they know their chain.

+
+

The determination exists — 2026-08-20

+

docs/regulatory/RISK-REG-0001 (audit-retention-basis.md). The estate now has a written position rather than an assumption, which was this finding's substance.

+

What it says, in short: Art 6(1)(f) with Art 32 for operator and agent audit records; Art 17(3)(e) for records evidencing a counterparty transaction; Art 17(3)(b) only where a commercial or tax retention duty independently applies, and not extended to application logs generally.

+

The weak part is duration, not existence, and the record says so rather than sounding confident. A position of the form "we keep audit forever because it is audit" is the one that fails; a period per category is what holds. The estate does not have one yet, and the reason is audit-core's own question 2 — at P1 the real horizon is the maximum across every co-resident on platform-pg, not the declared value. That infrastructure fact is the most likely point of failure in the whole position.

+

The operator's minimisation ruling improves this materially: it shrinks the category whose retention is hardest to justify, leaving mostly the row where the ground is strong. A weak argument avoided by holding less data beats a strong one relied upon.

+

The finding stays open. What remains is a retention period per category, which waits on the co-residency horizon, and the trigger list for buying an external determination. The record is reviewed every 90 days with this finding, or immediately on any trigger.

+
  • 2026-08-20 — not clean: The determination now exists: RISK-REG-0001 states the grounds per category and names duration as the weak point. Cadence instant → instant; checked again immediately.
+
+

Operator decision — 2026-08-20: no external determination, and a policy set instead

+

Ruled: the estate will not buy an external determination while it is building. The internal determination (RISK-REG-0001) stands as the recorded position, and the finding moves to accepted — deliberately carried, with a named accepter and a condition that ends it.

+

That is not the same as the trigger list being rejected. The triggers survive as what ends the acceptance: a real person's data, or a counterparty requiring a stated position. What was declined is spending money in advance of either.

+

The compensating control is the thing that makes this defensible. Rather than defer the question, the operator directed that the estate define and keep a set of legal policies for reuse, because future work contexts will need specific positions in place and should retrieve them rather than research them.

+

docs/regulatory/policies/ now catalogues thirteen, keyed by activation condition. Two of them turned out to be already active and unowned: commercial and tax retention (RISK-POL-0009), and the e-invoicing receiving obligation (RISK-POL-0012), live since 2025 with no system in the estate named as the receiving point.

+

Finding an unnoticed live obligation in the first hour of building the catalogue is the argument for having built it. The reason this repo exists is that regulation was previously "consulted and discarded"; a set that answers "what applies if we do X" before anyone does X is the opposite of that.

+

Still open under the acceptance, and unchanged by this ruling: audit-core on whether a keyed commitment restores erasability, and the platform-pg co-residency horizon that decides whether the stated retention periods are achievable. An accepted risk still gets checked.

+
  • 2026-08-20 — not clean: Trigger list ruled: no external determination in build mode; accepted with the legal policy set as the compensating control. Cadence instant → instant; checked again immediately.
+
RISK-F-0008 · published-1 · acceptedrisk-nexus · findings/RISK-F-0008-audit-retention-legal-basis-assumed.md · a13d954f8597fd92201746e2d52f031a5a88d969
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-DEPENDENCIES.html b/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-DEPENDENCIES.html new file mode 100644 index 0000000..5739518 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-DEPENDENCIES.html @@ -0,0 +1,238 @@ + + + + +Waiting + +
RISK-METHOD-DEPENDENCIES adopted · adopted-1 risk-nexus reviewed 2026-08-20generated from canonical source — do not edit

Waiting

Source: risk-nexus · docs/method/dependencies.md · a13d954f8597fd92201746e2d52f031a5a88d969

Review due: 2027-02-20

By 2026-08-20 the register had accumulated nine waits in four days, one of them four hops deep: RISK-F-0003's embargo waited on RISK-F-0009, which waited on railiance-platform fixing a deny set, which waited on someone verifying it against live OpenBao, which waited on a credential nobody has.

+

Nothing in that chain is anyone's fault, and every link was individually reasonable. That is exactly why it needs a rule: deep dependencies are not built deliberately, they accrete one sensible step at a time.

+

The principle

+

The register never waits to decide. It decides, and revises when told.

+

A wait is a refinement pending, not a decision pending. If the register cannot act until someone answers, the register has made that person's silence into its own paralysis — and INTENT.md is explicit that a register nobody acts on is worse than none.

+
+

The four rules

+

1. Every wait is typed

+

No record may say "waiting on X". A wait carries six things:

+
waiting_on:
+  - who: tenant-engine
+    what: "confirm or correct the unfiltered events() read; open fix tracking"
+    since: "2026-08-19"
+    would_change: "grade rises if the log carries payload rather than metadata"
+    default: "grade stands as recorded; absent fix tracking noted as a stall"
+    default_at: "2026-09-03"
+

would_change is the discipline. If nobody can say what the answer would change, there is nothing being waited for, and the wait should be deleted rather than carried.

+

2. Depth one

+

A record may wait on at most one other record, and never on a record that is itself waiting.

+

When the chain would go deeper, the far end is cut: the record takes its own default and says which unresolved thing it declined to wait for. Two hops is the point at which nobody can see the whole line any more, and a wait nobody can see is indistinguishable from a thing that was dropped.

+

Applied 2026-08-20: RISK-F-0009's embargo condition was "verified against live OpenBao", which no one in the estate can currently do. It now lifts on railiance-platform reporting the coverage, with live verification recorded as a refinement rather than a condition. That cut the RISK-F-0003 chain from four hops to two.

+

3. Defaults are dates, and defaults are pessimistic

+

Every wait resolves on a date whether or not anyone answers. The default is the reading the stated facts already support — never a hold, never a downgrade earned by silence.

+

This is what removes the incentive to wait. Silence does not buy an owner a softer grade or a quieter register; it costs them the grade the evidence supports, which is usually the one they would want corrected. Answering is how a grade improves, and that is the right shape for the incentive.

+

The register says so in advance, to the owner, in writing. A default nobody was warned about is an ambush, not a rule.

+

4. A condition naming somebody else's action carries a date beside it

+

"Embargo lifts when X ships" is a dependency with no end. "Lifts when X ships, or is re-decided on 2026-09-20" terminates.

+

Re-decided is not the same as lifted — the re-decision may extend the hold with a fresh reason. What it may not do is extend by default, which is how holds quietly become permanent.

+
+

What this does not solve

+

Some dependencies are real and cannot be defaulted away. Nobody can verify an OpenBao policy without a token, and no rule here conjures one.

+

What the rules do is stop that from propagating: the register grades on what is stated, records what it could not verify, and keeps its own position independent of the blockage. docs/method/verification.md bounds what this repo can establish itself, and every grade resting on a document rather than a probe says so on its face.

+
+

Waits outlive statuses

+

A finding that is fixed can still owe something. RISK-F-0001 was closed on 2026-08-19 and is still waiting on policy-nexus for the publication entry that turns disclosure: public into an actual address.

+

So the waiting list is built from every finding, not from the watched ones. A closed record with an open obligation is precisely the thing that goes quiet, because nothing is prompting anyone to look at it any more.

+
+

A wait is not a findings-only idea

+

Adopting a rule is nobody's finding. Registering a canon kind is nobody's finding. Publishing a document is nobody's finding. All three are obligations someone owes, and each one that lived outside the mechanism was invisible to it.

+

So waits attach to any record this repo keeps — findings, regulatory records, workplans — and make check reports them together. RISK-WP-0001 carries two: the escalation rule that is producing decisions while still status: proposed, and the canon question about what kind of thing a finding is.

+

The first of those has the most uncomfortable default in the register, and it points inward: if the rule is not adopted by 2026-09-17, it is recorded as de facto in force but unratified, and every escalation sent under it says so on its face. That is worse than either adopting or rejecting it, which is the point — a draft that quietly governs is the thing this register was built to notice.

+
+

Where the waits are visible

+

make check reports every open wait with its age, its owner and its default date, flags any default that has come due, and flags any wait that points at a record which is itself waiting — a depth-two violation, caught by tooling rather than by someone noticing.

+
RISK-METHOD-DEPENDENCIES · adopted-1 · adoptedrisk-nexus · docs/method/dependencies.md · a13d954f8597fd92201746e2d52f031a5a88d969
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-DISCLOSURE.html b/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-DISCLOSURE.html new file mode 100644 index 0000000..9f947b7 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-DISCLOSURE.html @@ -0,0 +1,232 @@ + + + + +Disclosure + +
RISK-METHOD-DISCLOSURE adopted · adopted-1 risk-nexus reviewed 2026-08-20generated from canonical source — do not edit

Disclosure

Source: risk-nexus · docs/method/disclosure.md · a13d954f8597fd92201746e2d52f031a5a88d969

Review due: 2027-02-20

Whether and when a finding is published. policy-nexus is the surface; this document decides what it is handed.

+

What disclosure is not

+

A finding file in this repo is not a publication. This repo is a private checkout on a private forge. Holding a finding means not routing it to policy-nexus; it does not mean hiding it from the estate, from the owning repo, or from the operator. Every state below is fully visible internally.

+
+

The states

+
StateMeaningEntry condition
publicPublished through policy-nexus now.The finding is fixed, or reading it gives no one an advantage they do not already have.
embargoedHeld, with a stated condition that lifts it.Live, unfixed, and the text would help someone reach the defect faster than they could without it.
restrictedHeld with no expected lift.Publication would remain harmful after the fix — third-party material, a named person, or a credential-shaped detail that survives remediation.
+

There is no fourth state and no unset after grading. A finding whose disclosure has not been decided is an ungraded finding.

+
+

Embargo is a record, not a silence

+

INTENT.md requires the record that a delay was deliberate rather than a document quietly going missing. An embargoed finding therefore carries:

+
disclosure: embargoed
+embargo_condition: "FLEX-WP-0015-T02 ships to production"
+embargo_since: "2026-08-19"
+embargo_review: "2026-08-26"
+

embargo_condition must be an event someone can observe, not a mood. "Until it is safer" is not a condition. embargo_review follows the finding's severity interval from docs/method/review.md; when it passes, the embargo is re-decided, not extended by default.

+

An embargo that has outlived two consecutive reviews without its condition moving is itself a finding — the remediation has stalled, and the hold is now doing the work the fix was supposed to do.

+
+

The build-mode deferral, re-taken

+

INTENT.md recorded controlled disclosure as deferred to production, reasoning that build mode has no users to expose. RISK-F-0001 arrived and tested it: a live authorization bypass in the service every other service trusts, with publish-or-forget as the only available choice.

+

The deferral is narrowed, not kept and not abandoned.

+

What was right about it: build mode does have no consumers to protect, and building an embargo mechanism — timed release, staged notification, coordinated disclosure with third parties — before there is anyone to coordinate with would be machinery for its own sake.

+

What was wrong about it: it conflated the mechanism with the decision. The argument for publishing in build mode is that there are no users to expose. That argument says nothing about attackers, and RISK-F-0001 is exactly the class where the two come apart — the finding names an unauthenticated decision surface and the service that carries it. Publishing that while it is live helps precisely one kind of reader.

+

So the ruling is:

+
  1. Build-mode default stays publish. Architecture, method, fixed findings, and findings whose exposure is already bounded go out. The estate publishing what it knows is wrong remains the norm and does not need a case made for it each time.
  2. Live-and-reachable is the exception, and it exists now. A finding that is unfixed and whose text shortens the path to the defect is embargoed until the fix lands. That is the missing middle INTENT.md said did not exist. It costs one front-matter field and a line in REGISTER.md.
  3. The mechanism stays deferred. No timed release, no coordinated disclosure protocol, no notification tiers. Those wait for real users, as originally reasoned. What is not deferred is the decision, because RISK-F-0001 demonstrated the decision is needed before the machinery is.
+

This is a decision of this repo, taken 2026-08-19 with RISK-F-0001, RISK-F-0002 and RISK-F-0003 in hand rather than hypothetically. It is revisable, and the production transition is the scheduled moment to revisit it.

+
+

Test for "shortens the path"

+

Ask: does the finding tell a reader something that materially reduces the work of reaching the defect, beyond what reading the repo would give them?

+
  • A file path and line number in a private repo — no, that is already there.
  • "This surface authenticates nobody, here is its cluster address" — yes.
  • "These five named lanes vend real secret values without the boundary firing" — yes.
  • "This system had no backups configured" — no, once backups exist; yes, while they do not, because it names when destruction is unrecoverable.
+

When the answer is genuinely unclear, embargo and re-decide at the review. The cost of a wrong embargo is a delayed publication; the cost of a wrong publish is not recoverable.

+
+

Publication happens elsewhere

+

A public finding is handed to policy-nexus under its publication contract and gets a permanent address there. This repo never serves it and never edits it after handover; corrections go through the same route as the original.

+
+

The standing route, when an embargo lifts

+

RISK-WP-0002-T03. Written down because publication will arrive in a trickle as conditions clear, not as a batch, and a route improvised each time is a route that eventually is not taken.

+
  1. The check that lifts the embargo records it. make checked on the finding, with the lift as the reason. An embargo lifting is never a clean check — something moved.
  2. The finding gets publication front-matter, in the shape policy-nexus already requires of everyone: owner, revision, last_reviewed, review_interval. No body rewrite.
  3. This repo asks policy-nexus for an entry, giving source_repo, source_path, a proposed canonical_path under findings/<id>/<version>/, and a one-line subtitle. Addressing and permanence are theirs (POLICY-NEXUS-WP-0001); this repo does not invent a scheme.
  4. publication: published is recorded back on the finding, with the URL. A finding that says public but has no address is a claim, not a publication — the same class of error as a backup nobody has restored from.
+

The contract publishes a file from the owning repo, so what is handed over is exactly what a reader gets. That makes the whole-versus-summary decision (T01) a decision about what a finding file contains, not about how it is rendered.

+
RISK-METHOD-DISCLOSURE · adopted-1 · adoptedrisk-nexus · docs/method/disclosure.md · a13d954f8597fd92201746e2d52f031a5a88d969
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-REVIEW.html b/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-REVIEW.html new file mode 100644 index 0000000..36030ad --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-REVIEW.html @@ -0,0 +1,244 @@ + + + + +Review and expiry + +
RISK-METHOD-REVIEW adopted · adopted-1 risk-nexus reviewed 2026-08-20generated from canonical source — do not edit

Review and expiry

Source: risk-nexus · docs/method/review.md · a13d954f8597fd92201746e2d52f031a5a88d969

Review due: 2027-02-20

INTENT.md: a finding that has sat untouched past its review date is itself a finding. Silence is not resolution. This document makes that operable.

+

The cadence ladder

+

Operator ruling, 2026-08-20. Intervals are not set by severity. They are earned by outcomes.

+

A finding is checked, and the check comes back clean or it does not. Clean climbs one rung. Anything wrong drops straight back to the bottom.

+
RungWait before the next check
instantnow, and again immediately until a check comes back clean
1hone hour
8height hours
24hone day
48htwo days
96hfour days
7done week
14dtwo weeks
1moone month
1qone quarter — the ceiling; nothing is ever checked less often than this
+

Two rules and one escape:

+
  • Climb on clean. One rung per clean check, never two.
  • Reset on anything wrong. Not a slide down one rung — straight to instant. A matter that has just moved has no track record, whatever it had before.
  • The operator may defer. An instant finding can be deferred to a stated future date by explicit operator decision, recorded as deferred_to. That is the only way out of the bottom rung other than a clean check, and it is a decision with a name on it rather than a check quietly not happening.
+

Who checked, and when anyone last did

+

RISK-WP-0005-T06. Two defences against the rung telling a lie:

+
  • checked_by on every check. record_check.py writes it. A rung earned by nobody in particular is visible as such.
  • A heartbeat. If nothing anywhere in the register has been checked for two days, make check says so before anything else. A 1q rung means "stable for a quarter" and "nobody looked for a quarter", and those read identically from the outside — the heartbeat is what separates them.
+

The rung is the signal

+

This is the point of the design, not a side effect. The cadence a finding sits on is a statement about how stable the estate has been on that matter.

+

RISK-F-0002 at 1q (9) says the signing gate has been examined ten times over three months and nothing has moved. RISK-F-0002 at instant (0) says something changed within the day. The number carries information that no severity does — severity says how bad it would be, the rung says how settled it is — and the two are independent. A low finding that keeps resetting is telling you something a critical one at the ceiling is not.

+

It is also self-correcting in the direction that matters. Anything volatile gets attention often, automatically, without anyone deciding it deserves it; anything genuinely quiet stops consuming attention, without anyone deciding to stop looking. Neither of those judgements has to be made by a person who might be wrong or busy.

+

What "clean" means

+

A check is clean when nothing about the assessment moved: the grade still holds, every stated blocker is still true, the fix state is unchanged, the disclosure state is still right, and no new fact has arrived.

+

A check is not clean when any of those moved — including when they moved in a good direction. RISK-F-0001 being fixed is not a clean check; it is a large change, and the next check comes immediately. Good news resets the ladder exactly like bad news, because the ladder measures stillness, not health.

+

Starting position

+

Every finding starts at instant. A register with no check history has no grounds to wait, and the first clean check is what buys the first hour.

+

The whole register sat at instant (0) on 2026-08-20, which is correct and temporary: everything in it had been graded, re-graded or ruled on within the preceding day.

+
+

What a review is

+

Five questions, answered in writing on the finding. It takes minutes; it is not an investigation.

+
  1. Has the inbox said anything? Read the messages addressed to this repo before anything else. This is question zero because on 2026-08-19 the register graded RISK-F-0001 critical while two messages sat unread in its own inbox — one narrowing the exposure, one reporting the fix. Both changed the grade. A register that does not read its own inbox is guessing with a straight face.
  2. Is the grade still right? Re-read impact and likelihood against what has changed. New facts move the grade in both directions.
  3. Is the blocker still true? This is the one RISK-F-0002 bought with evidence: a blocker is a claim about the world at a date. Its own stated blocker — "flex-auth is not deployed yet" — was invalidated within a day by RISK-F-0001, and nothing would have re-checked it. Every review re-checks every stated blocker.
  4. Has the fix moved? Read the owner's tracking record, not our memory of it. Confirm the record still exists and still refers to this defect.
  5. Is the disclosure state still right? An embargo whose condition has been met is published; an embargo whose condition has not moved in two reviews is a stall.
+

The finding gets a dated line under ## Reviews, last_reviewed is updated, and review_by is pushed by one interval. A review that changes nothing still writes the line — "checked, nothing moved" is the evidence that the silence was observed rather than accidental.

+
+

When a check is missed

+

Overdue is not a status change on the finding. It is a fact about this repo, and it surfaces in three places:

+
  • make check lists it under "Checks due", with how late it is and which rung it is on.
  • REGISTER.md shows the next check as due.
  • A finding sitting at the bottom rung for more than fourteen days with no movement fires escalation trigger 5. Bottom rung means it keeps failing or keeps being skipped; fourteen days of that is a stall whichever it is.
+

The register does not auto-escalate severity for lateness and does not auto-close anything. Both would be the register lying about its own state to make a number look better.

+
+

The production re-score

+

Every finding carries severity_at_production alongside severity (docs/method/severity.md). Where the two differ, the finding is flagged production_rescore: true.

+

On the day any part of the estate declares production readiness, every flagged finding is re-scored before that declaration completes. This is not a review date — it is an event, and it fires regardless of where the review dates happen to sit.

+

Until then, make check lists the flagged findings so the size of that obligation is visible rather than discovered on the day.

+
+

Front-matter this adds

+
last_checked: "2026-08-20T05:40:00Z"
+next_check: "2026-08-20T06:40:00Z"
+cadence: 1h
+clean_streak: 1
+production_rescore: true
+deferred_to: ""        # only by explicit operator decision
+

next_check is what the nag reads, and it is an absolute moment rather than a duration, so nothing has to recompute an interval to know whether a check is late. The rungs run in hours as well as days, so it carries a time.

+
+

Closing a finding

+

A finding leaves open for exactly one of:

+
  • fixed — the owner's record shows the defect gone, and this repo has read something concrete rather than been told. Publication follows if the disclosure state was embargoed.
  • accepted — the estate is deliberately carrying it. Requires who accepted it, why, and what ends the acceptance. accepted is not closed: it stays on the ladder forever, and it climbs like anything else.
  • mitigated — the live gap is closed but the finding is not. RISK-F-0003 is the case: the boundary now fires, and the omission that let it not fire is still there. Stays watched.
  • withdrawn — the finding was wrong, or the defect never existed. Say which.
+

Any status the tooling does not recognise keeps the finding watched, and the unrecognised word is reported. RISK-F-0003 arrived as mitigated on 2026-08-20, before that word existed here, and dropped silently out of make check — a finding vanishing from the nag because someone used an unfamiliar word is precisely the failure this register exists to prevent. The tooling now fails loud instead of quiet.

+

There is no stale, no wontfix and no silent expiry. A finding that nobody will fix and nobody will accept stays open and keeps arriving in the nag, because that is the true state.

+
RISK-METHOD-REVIEW · adopted-1 · adoptedrisk-nexus · docs/method/review.md · a13d954f8597fd92201746e2d52f031a5a88d969
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-SEVERITY.html b/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-SEVERITY.html new file mode 100644 index 0000000..33b5462 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-SEVERITY.html @@ -0,0 +1,256 @@ + + + + +Severity + +
RISK-METHOD-SEVERITY adopted · adopted-1 risk-nexus reviewed 2026-08-20generated from canonical source — do not edit

Severity

Source: risk-nexus · docs/method/severity.md · a13d954f8597fd92201746e2d52f031a5a88d969

Review due: 2027-02-20

This is risk-nexus's judgement instrument. It is not canon, it is not a standard, and it binds nobody else. It exists so that two findings graded a month apart are graded the same way, and so that a grade can be argued with.

+

It was written against three real findings (RISK-F-0001, RISK-F-0002, RISK-F-0003) and it must keep grading those three sensibly or it is wrong.

+

The two axes

+

Impact — what happens if it goes wrong once

+
BandNameTest
I1negligibleConfined to one component. No data leaves it, no record is falsified, no recovery is lost.
I2limitedOne system's data or availability. Recoverable. Confined to a single tenant, actor or lane.
I3seriousCrosses a boundary — tenant, system, or trust — or removes recoverability for one system.
I4severeCrosses the estate. What is compromised here propagates to everything that trusts it, or the data loss is unbounded.
+

Impact is scored at one occurrence, not at the worst imaginable campaign. "An attacker who already owns the cluster could do this too" is not an impact argument.

+

Likelihood — how far anyone has to reach

+
BandNameTest
L1remoteRequires access nobody currently holds and no ordinary process grants.
L2possibleRequires a foothold the estate does grant somewhere — an in-cluster workload, an agent session, a scoped token.
L3likelyReachable from inside the normal working set with no additional step.
L4presentNo barrier at all, or it is already happening.
+

Likelihood is about reach, not about intent or about whether anyone has bothered. risk-nexus does not model attackers; it models what the system permits.

+

Where the reporter has not established exposure, the finding says so and the grade uses the band the stated facts support — not the worst case, and not zero. RISK-F-0001 explicitly declines to assume a default-deny NetworkPolicy exists; the grade must decline with it, and the unverified fact becomes a review item rather than a silent assumption in either direction.

+
+

The grid

+
L1L2L3L4
I4mediumhighcriticalcritical
I3lowmediumhighcritical
I2lowlowmediumhigh
I1notelowlowmedium
+

Four severities: low, medium, high, critical. note is not a severity; see the floor.

+
+

The fidelity modifier

+

A control that lies is one impact band worse than the same control absent.

+

Apply +1 impact band (capped at I4) when the failure mode produces a false record rather than no record: an attestation that a check passed when nothing checked, an audit line asserting an authorization that was never made, a green signal derived from an unreachable test.

+

The reasoning is RISK-F-0002's and the register adopts it: an absent control is a gap you can find by looking; a lying control is a gap that survives looking, because the evidence you would look at is the thing that is wrong. Only one of the two states misleads the person investigating afterwards.

+

The modifier applies to the state being scored. A finding that describes both states — control absent today, control lying if switched on in the wrong order — gets two scores and one of them is the register's headline; see "Which state is scored".

+
+

Which state is scored

+

The headline severity is the state of the world today. A hazard that would be created by a future action is not the headline, because a register that scores hypotheticals stops describing the estate.

+

The hazard is not lost. It is recorded on the finding as a named constraint with its own grade, and it attaches to whatever action would trigger it — usually another finding's remediation. RISK-F-0002 is the worked example: the gate being off is today (headline), the gate being switched on while the oracle is forgeable is a constraint on RISK-F-0001's fix, graded separately and higher.

+

If the constraint's grade is higher than the headline, the finding says so in its ruling. A reader must not be able to come away with the low number and miss the high one.

+
+

Build mode

+

Every finding is graded twice:

+
  • severity — today, in build mode, with today's likelihood.
  • severity_at_production — the same impact, with likelihood re-read for a system carrying real users and real tenant data.
+

Build mode legitimately lowers both axes, for different reasons: likelihood, where the reach itself depends on a production deployment that has not happened; and impact, where the data that would be exposed does not exist yet. What it must never lower is severity_at_production — the defect does not improve because the calendar has not reached it.

+

Amended 2026-08-19 (RISK-WP-0001-T07). This paragraph originally said build mode was a likelihood input and never an impact one. Grading the unverified tenant boundary broke that: what build mode changes there is the consequence of an occurrence, not the reach of it. The instrument was wrong on first hard use and is corrected rather than worked around.

+

Where the two grades differ, the production transition is a mandatory re-score. docs/method/review.md binds the review date to it, so the re-score is a scheduled event and not somebody's memory.

+
+

Non-adversarial findings

+

Likelihood is written as reach because most findings are about someone getting somewhere. Where a finding is about loss, corruption or outage — no backup, no recovery path, an eviction-prone deployment — there is no attacker to model.

+

For those, likelihood reads as the chance of the triggering event inside one review interval: L1 would be surprising, L2 is an ordinary failure the estate has seen before, L3 is expected in the normal course of running, L4 is already happening. Impact is unchanged: what is lost, and whether it comes back.

+

Added 2026-08-19 (RISK-WP-0001-T07). Forced by RISK-F-0006, where the defect is an absent backup and the reach reading produced nonsense.

+
+

Live incidents

+

Everything above assumes a latent defect — something reachable that nobody is currently reaching. When someone is, three things change:

+
  • Likelihood is L4. The band means "already happening" and this is what it is for.
  • Impact is scored on what has occurred plus what is still reachable, not on the worst case. An incident in progress has facts; use them.
  • The grade is provisional and expected to move. File first, grade within the hour, re-grade as facts arrive. docs/method/intake.md has the rest, including the 72-hour clock that first_observed starts.
+
+

The floor

+

INTENT.md: if a finding would not change anyone's decision, it is a note, not a risk. Concretely, a register entry requires both:

+
  1. An owner who could act. Some repo, or the operator, can do something about it. No actor, no entry.
  2. A decision that changes. Recording it alters what someone does, when they do it, or what they must not do first.
+

Fails either test → it is a note in notes/, not a finding in findings/. Notes are not graded, not reviewed, and not published. They exist so that "we saw it" survives without inflating the register.

+

An I1/L1 cell is note in the grid for the same reason: something that is both negligible and unreachable is a thing we know, not a risk we carry.

+

Two things the floor does not exclude:

+
  • Known and deliberate. RISK-F-0002 is a decision somebody made on purpose. It still passes the floor, because it changes what may be switched on and in what order. Deliberate is not the same as tracked.
  • Omission-shaped. RISK-F-0003 is a default that silently produces ungoverned lanes. The individual lane is small; the default is not.
+
+

Provenance is a grading input

+

All four defects known to this register were found by repos reading their own code against a ladder, within days of each other. None was found by monitoring.

+

Where a finding's provenance is "we happened to look", the register does not get to assume that similar defects would have been caught. That raises likelihood for the class, not for the instance, and it belongs in the ruling's reasoning rather than in a modifier — the register grades what is filed, and notes when the filing was luck.

+
+

Recording a grade

+

The finding's front-matter carries:

+
severity: critical              # headline, today
+severity_at_production: critical
+impact: I4                      # band, before modifiers
+likelihood: L3
+fidelity_modifier: false        # true if +1 applied, with the reason in the ruling
+

and the ruling section states impact, likelihood, any modifier, and the one sentence that would have to become false for the grade to change.

+
RISK-METHOD-SEVERITY · adopted-1 · adoptedrisk-nexus · docs/method/severity.md · a13d954f8597fd92201746e2d52f031a5a88d969
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-VERIFICATION.html b/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-VERIFICATION.html new file mode 100644 index 0000000..2fa3f10 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RISK-METHOD-VERIFICATION.html @@ -0,0 +1,222 @@ + + + + +What this register may verify for itself, and what it must take from owners + +
RISK-METHOD-VERIFICATION adopted · adopted-1 risk-nexus reviewed 2026-08-20generated from canonical source — do not edit

What this register may verify for itself, and what it must take from owners

Source: risk-nexus · docs/method/verification.md · a13d954f8597fd92201746e2d52f031a5a88d969

Review due: 2027-02-20

RISK-WP-0004-T05 asked what this repo can legitimately establish itself, because two gradings were resting on file comparison. The answer was found by trying, on 2026-08-20, rather than by reasoning about it.

+

The rule

+

A register may look. It may not touch, and it may not conclude on a system's behalf.

+

Verification here means: running a read-only command a reporter has already named, or that answers a question the register itself wrote down as open, and recording exactly what came back.

+

Permitted:

+
  • read-only reads of live state (kubectl get, bao policy read, an HTTP probe of a documented endpoint);
  • comparing what is deployed against what a repo said is deployed;
  • recording the discrepancy and routing it as a question.
+

Not permitted:

+
  • any write, apply, patch, restart or rotation — including one that would obviously improve things;
  • concluding what a defect means for a system this repo does not own. The system stays authoritative about itself (INTENT.md);
  • verifying instead of asking. The owner is asked first; verification settles what an owner cannot see or has invited someone to check.
+

rapp-postgres declined to check the NetworkPolicy on flex-auth's behalf, saying that would be reporting on a system they do not own. That was right for a reporter. This register is the downstream party whose grade depended on the answer, and flex-auth explicitly named the command and asked for someone with credentials to run it. Those two positions are compatible.

+
+

What this host can actually reach — established 2026-08-20

+
TargetResultConsequence
Kubernetes (railiance01)reads work — kubectl get ns, get networkpolicy -o jsonLive cluster state is verifiable by this register
OpenBao (bao.coulomb.social)403 permission denied on token lookup and policy readPolicy claims are not verifiable here; RISK-F-0009 rests on file comparison
+

The asymmetry is worth stating plainly: the register can check what the cluster admits, and cannot check what the secret store permits. Every grade touching an OpenBao policy is therefore a grade on a document, and says so.

+

That is not a gap to close by acquiring credentials. A risk register holding production secret-store access has traded a verification problem for a much worse one. If OpenBao claims need verifying, the right answer is for the owner to run the read and report it, which is what ops-warden did — the obstacle there was an expired token, not the arrangement.

+
+

A file is not a safe proxy for the server

+

Established 2026-08-21, by evidence rather than by caution. ops-warden ran the live OpenBao read that RISK-F-0009 had been graded without, and the deployed policy differs from the committed file. No lane maps to the drifted path, so nothing was exposed — but the general claim is now proven rather than suspected.

+

Consequences this register accepts:

+
  • Every grade made off a checkout is a grade on a document, and says so on its face. RISK-F-0009 does.
  • A verification that reads a file is a weaker artifact than one that reads a server, and the record must not blur them. RISK-V-0001 reads a server; the OpenBao comparison in RISK-F-0009 reads a file.
  • Where only the owner can reach the server, the owner's probe is the evidence and the register says whose it is. That is not a lesser standard — it is the correct one, given verification.md's own limits.
+

The corollary is uncomfortable and worth stating: drift between file and server is invisible to anyone reading files, which is most of this estate's tooling, including fix_tracker.py. What that tool reads is what a repo recorded, and a record can be as stale as any other claim — as four self-reported stale blockers in twelve hours demonstrated on 2026-08-21.

+
+

Recording a verification

+

One file per verification in docs/verifications/, RISK-V-NNNN, stating the command, the raw result, what it confirms, what it contradicts, and what it does not establish. It names the findings it bears on, and those findings link back.

+

A verification is evidence, not a ruling. It can move a grade; it does not close a finding on its own, and it never speaks for the owning repo.

+
+

Verification and the cadence ladder

+

A verification that contradicts something is a check that is not clean: the affected findings reset to instant. A verification that confirms what was already recorded is clean, and may be exactly the evidence a rung is built on.

+

The first one (RISK-V-0001) did both — it confirmed the ingress claim RISK-F-0001 was graded on, contradicted the egress claim in the same message, and surfaced a third policy nobody had mentioned that bears on RISK-F-0002.

+
RISK-METHOD-VERIFICATION · adopted-1 · adoptedrisk-nexus · docs/method/verification.md · a13d954f8597fd92201746e2d52f031a5a88d969
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0001.html b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0001.html new file mode 100644 index 0000000..27f85e7 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0001.html @@ -0,0 +1,235 @@ + + + + +Repository Prefix Architecture + +
RMASTER-ADR-0001 accepted · accepted-1 railiance-master reviewed 2026-07-25generated from canonical source — do not edit

Repository Prefix Architecture

Source: railiance-master · docs/adr/ADR-0001-repository-prefix-architecture.md · debbc13b4018f20db0997516b99afc1cc21084d7

Review due: 2027-01-25

Date: 2026-07-25 Status: Accepted

+

Context

+

Railiance already has a meaningful set of ownership repos such as railiance-infra, railiance-cluster, railiance-platform, railiance-enablement, railiance-apps, railiance-forge, and railiance-fabric.

+

That structure is useful, but it does not by itself capture all of the dimensions Railiance now needs.

+

In particular, the architecture needs clear source-controlled homes for:

+
  • workload execution contracts across different operations architectures,
  • managed workload packages as first-class repos,
  • and concrete substrate boundaries such as named servers, workstations, or grouped environments.
+

Without that separation, Kubernetes-specific workload semantics remain mixed into railiance-cluster, workload wrappers remain mixed into ownership repos, and concrete substrates remain under-described.

+

Railiance also operates inside a wider ecosystem around Railiance, Net Kingdom, and the Helix Forge software factory. That wider context needs architecture that can support both exploratory operation and production-grade evolution without leaving key responsibilities ambiguous.

+
+

Decision

+

Railiance adopts four canonical repository families:

+
  1. railiance-* for ownership and responsibility areas
  2. rail-* for workload execution contracts
  3. rapp-* for Railiance-managed workload packages
  4. reef-* for durable substrate boundaries
+

The current implementation wave is explicitly centered on rail-kubernetes as the default base rail. Additional rails are introduced only when a concrete workload has a sound reason to run on a distinct execution architecture.

+

rail-knative is the first expected follow-on rail, motivated by the need to support qonto-assistent, but it should follow the rail-kubernetes boundary cleanup rather than bypass it.

+
+

Meaning Of Each Family

+

railiance-*

+

Owns major architectural responsibilities, shared policies, and durable layer boundaries.

+

rail-*

+

Owns how workloads run on a specific execution architecture such as Kubernetes, Knative, KEDA, Fission, or Nuclio.

+

In the current phase, rail-kubernetes is the default path for platform services and managed applications.

+

rapp-*

+

Owns the managed workload package for one service or application, whether it is an internal workload or a wrapped upstream product.

+

Its role is to provide the managed wrapper and scaffolding needed to run that workload in the Railiance and Net Kingdom context. It does not replace responsibility ownership.

+

reef-*

+

Owns the concrete substrate reality where rails and rapps are bound, such as a named server, cluster, workstation, or grouped substrate class.

+

A reef is about purpose-bound compute resources, not merely about individual machines.

+
+

Consequences

+

Positive

+
  • Ownership, execution, workload identity, and substrate identity become separate concerns.
  • Railiance can support multiple execution architectures without forcing all workload semantics into railiance-cluster.
  • Platform services and applications can both become first-class managed workload packages where appropriate.
  • Concrete substrates gain a clear architectural home without overloading the ownership repos.
  • The framework gains a disciplined default path for introducing new rails instead of proliferating them speculatively.
+

Required Follow-On Work

+
  • Define the initial rail-* contracts, starting with rail-kubernetes.
  • Evolve the current railiance/app.toml and overlay pattern into a rail-aware, eventually rail-neutral workload packaging contract.
  • Identify which current workloads should become rapp-* repos.
  • Extend railiance-fabric so rails, rapps, and reefs become graph-native.
  • Define when mixed-rail reefs are acceptable and when clearer substrate separation should be preferred.
+

Constraints

+
  • reef-* must not become a default one-repo-per-machine pattern.
  • Generic logic stays in the appropriate ownership repo.
  • A named machine gets its own reef only when that machine is itself a durable substrate boundary.
  • rapp-* repos must not become shadow ownership repos.
  • Transitional substrate labels should not be canonized before the pattern is operationally stable.
+
+

Current Interpretation For Existing Repos

+
  • railiance-* repos remain the primary ownership axis.
  • Current workload-execution logic in railiance-cluster is a candidate to migrate into rail-kubernetes.
  • Current workload wrappers inside railiance-apps, railiance-platform, or railiance-forge may evolve into rapp-* repos over time.
  • Current named substrates such as COULOMBCORE, RAILIANCE01, and WORKSTATION may justify reef-* repos when they represent real operational boundaries.
  • A reef may host more than one rail in early or mixed environments, but production-critical substrates should prefer clearer purpose and primary-rail boundaries unless a mixed design is justified.
+
+

Notes

+

This ADR defines the repository taxonomy. It does not yet mandate a full migration or rename of existing repos. Migration should happen when it produces clearer ownership and lower ambiguity, not merely for naming purity.

+
RMASTER-ADR-0001 · accepted-1 · acceptedrailiance-master · docs/adr/ADR-0001-repository-prefix-architecture.md · debbc13b4018f20db0997516b99afc1cc21084d7
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0002.html b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0002.html new file mode 100644 index 0000000..1556574 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0002.html @@ -0,0 +1,218 @@ + + + + +Wave 1 rail-kubernetes Boundary + +
RMASTER-ADR-0002 accepted · accepted-1 railiance-master reviewed 2026-07-25generated from canonical source — do not edit

Wave 1 rail-kubernetes Boundary

Source: railiance-master · docs/adr/ADR-0002-rail-kubernetes-wave-1-boundary.md · debbc13b4018f20db0997516b99afc1cc21084d7

Review due: 2027-01-25

Date: 2026-07-25 Status: Accepted

+

Context

+

Railiance wants rail-* repos to represent workload execution contracts rather than abstract naming ideas.

+

Today, the concrete Kubernetes workload contract already exists, but it is embedded in railiance-cluster. That repo currently owns both:

+
  • the S2 Kubernetes substrate
  • and the generic workload lifecycle and tooling that run on top of it
+

That mixed ownership is the main blocker to introducing the first real rail.

+
+

Decision

+

Railiance will treat rail-kubernetes as the wave-1 base rail.

+

The boundary is:

+
  • railiance-cluster owns the Kubernetes substrate and its prerequisites
  • rail-kubernetes owns the generic workload-on-Kubernetes execution contract
+

Workload-specific helper flows currently living in railiance-cluster are temporary exceptions and must be rehomed rather than carried forward as part of the generic rail.

+
+

Consequences

+

Positive

+
  • The first real rail gains a concrete ownership boundary.
  • railiance-cluster can return to a clean S2 substrate identity.
  • Future rails such as rail-knative get a stable base boundary to extend from.
  • rapp-* repos gain a clearer execution-contract home for Kubernetes-managed workloads.
+

Required Follow-On Work

+
  • Create the detailed rail-kubernetes boundary contract and migration map.
  • Split generic lifecycle docs, schema, examples, and Stage 1-3 tooling out of railiance-cluster.
  • Leave a compatibility path so current users of the cluster repo are not broken during the migration.
  • Rehome workload-specific helpers that do not belong in either boundary.
+

Constraints

+
  • rail-kubernetes must not take ownership of cluster bootstrap, operators, or kubeconfig management.
  • railiance-cluster must not continue as the owner of generic workload promotion semantics after the split.
  • rail-knative should follow this cleanup, not bypass it.
+
+

Notes

+

This ADR does not require all current files to move immediately.

+

It requires the ownership line to be explicit now, so practical repo separation can proceed without ambiguity.

+
RMASTER-ADR-0002 · accepted-1 · acceptedrailiance-master · docs/adr/ADR-0002-rail-kubernetes-wave-1-boundary.md · debbc13b4018f20db0997516b99afc1cc21084d7
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0003.html b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0003.html new file mode 100644 index 0000000..893a6e0 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0003.html @@ -0,0 +1,216 @@ + + + + +First-Wave rapp Selection + +
RMASTER-ADR-0003 accepted · accepted-1 railiance-master reviewed 2026-07-25generated from canonical source — do not edit

First-Wave rapp Selection

Source: railiance-master · docs/adr/ADR-0003-rapp-first-wave-selection.md · debbc13b4018f20db0997516b99afc1cc21084d7

Review due: 2027-01-25

Date: 2026-07-25 Status: Accepted

+

Context

+

Railiance wants rapp-* repos to represent managed workload packages rather than new ownership layers.

+

The current workload surfaces already suggest several candidates:

+
  • OpenBao in railiance-platform
  • the forge workload in railiance-forge
  • multiple S5 workloads in railiance-apps
+

But they are not equally ready for first extraction.

+
+

Decision

+

The first-wave rapp-* order is:

+
  1. rapp-openbao
  2. rapp-vergabe-teilnahme
  3. later rapp-forgejo after the forge runtime target stabilizes
+

The forge workload is explicitly deferred from the first wave because current Gitea operation and future Forgejo direction are both active realities.

+
+

Consequences

+

Positive

+
  • The first rapp-* extraction proves the third-party platform-service wrapper model.
  • The second proves the user-facing self-built app wrapper model.
  • The deferred forge wrapper avoids immediate rename churn and packaging ambiguity.
+

Required Follow-On Work

+
  • Write repo-local extraction work for rapp-openbao.
  • Write repo-local extraction work for rapp-vergabe-teilnahme.
  • Reassess the forge wrapper when the production package target is no longer split between current Gitea and future Forgejo.
+

Constraints

+
  • rapp-* repos must stay packaging-focused and must not become shadow ownership repos.
  • Shared S3 policy stays in railiance-platform.
  • Shared S5 release patterns stay in railiance-apps.
+
+

Notes

+

This ADR chooses sequence, not a mandatory destination for every workload in the ecosystem.

+
RMASTER-ADR-0003 · accepted-1 · acceptedrailiance-master · docs/adr/ADR-0003-rapp-first-wave-selection.md · debbc13b4018f20db0997516b99afc1cc21084d7
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0004.html b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0004.html new file mode 100644 index 0000000..b3a8426 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0004.html @@ -0,0 +1,217 @@ + + + + +First-Wave reef Rollout + +
RMASTER-ADR-0004 accepted · accepted-1 railiance-master reviewed 2026-07-26generated from canonical source — do not edit

First-Wave reef Rollout

Source: railiance-master · docs/adr/ADR-0004-first-wave-reef-rollout.md · debbc13b4018f20db0997516b99afc1cc21084d7

Review due: 2027-01-26

Date: 2026-07-25 Status: Accepted

+

Context

+

Railiance now has a reef model, but it needs a concrete first rollout.

+

The current substrate reality is not uniform:

+
  • Railiance01 is the clearest new-workload home
  • CoulombCore is still active but transitional
  • the workstation role is operator edge compute rather than server substrate
+

At the same time, S1 ownership is ambiguous because railiance-hosts and railiance-infra duplicate each other.

+
+

Decision

+

The first-wave reef rollout is:

+
  1. reef-railiance
  2. reef-coulombcore
  3. reef-ops-workstations
+

railiance-infra is the canonical S1 ownership repo going forward.

+

railiance-hosts is treated as predecessor or duplicate state to be retired, frozen, or reduced later rather than as a second canonical S1 architecture home.

+
+

Consequences

+

Positive

+
  • Railiance gets a grouped home-reef seed whose name remains stable as more home servers are added.
  • Transitional CoulombCore reality is acknowledged without being treated as the long-term preferred production pattern.
  • Operator edge compute is modeled without defaulting to one repo per machine.
  • Reef planning now rests on one canonical S1 ownership line.
+

Required Follow-On Work

+
  • Create repo-local rollout work for the first reef repos.
  • Define how additional Railiance home servers join reef-railiance and what future split criteria would justify narrower reefs.
  • Plan the railiance-hosts cleanup direction relative to railiance-infra.
+

Constraints

+
  • Multi-rail reefs are acceptable early, especially on transitional substrates.
  • Production-grade separation should still prefer clearer purpose and primary rail boundaries as criticality rises.
  • Transitional substrate nicknames should remain provisional until the pattern is stable enough to canonize.
+
+

Notes

+

This ADR chooses the first rollout set. It does not require that every future substrate be modeled the same way.

+
RMASTER-ADR-0004 · accepted-1 · acceptedrailiance-master · docs/adr/ADR-0004-first-wave-reef-rollout.md · debbc13b4018f20db0997516b99afc1cc21084d7
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0005.html b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0005.html new file mode 100644 index 0000000..20dbc34 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0005.html @@ -0,0 +1,205 @@ + + + + +Derived Rail Composition + +
RMASTER-ADR-0005 accepted · accepted-1 railiance-master reviewed 2026-07-26generated from canonical source — do not edit

Derived Rail Composition

Source: railiance-master · docs/adr/ADR-0005-derived-rail-composition.md · debbc13b4018f20db0997516b99afc1cc21084d7

Review due: 2027-01-26

Date: 2026-07-26 Status: Accepted

+

Context

+

Knative provides distinct workload activation and revision semantics but runs on Kubernetes. Treating it as an unrelated peer rail would duplicate generic workload lifecycle and substrate assumptions.

+
+

Decision

+

Railiance distinguishes base and derived rails.

+

rail-kubernetes is the default base rail for platform workloads. rail-knative is a derived rail that inherits a versioned rail-kubernetes common contract and overrides only Knative-specific activation, revision, autoscaling, traffic, cold-start, and rollback semantics.

+

The detailed contract is docs/rail-composition-contract.md.

+
+

Consequences

+
  • New platform workloads continue to use rail-kubernetes unless a specialized rail is justified.
  • Derived rails declare base-rail compatibility rather than copying lifecycle contracts.
  • Fabric and conformance tooling must understand rail dependency and readiness.
  • Knative installation stays with the S2 substrate owner.
+
RMASTER-ADR-0005 · accepted-1 · acceptedrailiance-master · docs/adr/ADR-0005-derived-rail-composition.md · debbc13b4018f20db0997516b99afc1cc21084d7
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0006.html b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0006.html new file mode 100644 index 0000000..89c0782 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0006.html @@ -0,0 +1,207 @@ + + + + +Reef Production Admission + +
RMASTER-ADR-0006 accepted · accepted-2 railiance-master reviewed 2026-08-29generated from canonical source — do not edit

Reef Production Admission

Source: railiance-master · docs/adr/ADR-0006-reef-production-admission.md · 5ffd7d1b40d56249f490a318e728047fd3517c4c

Review due: 2027-02-28

Date: 2026-07-26 Status: Accepted

+

Context

+

Fabric topology can say that a reef hosts a rail or binds a workload, but that does not demonstrate capacity, isolation, recoverability, or approval for a critical internet-facing service.

+
+

Decision

+

Rail and rapp bindings use explicit readiness states: declared, installed, verified, production-approved, and deprecated.

+

Critical workloads require machine-readable conformance evidence plus explicit acceptance of residual risks that cannot be automated. Mixed-rail reefs use defined split triggers.

+

The detailed contract is docs/reef-production-readiness-contract.md.

+

Who may reach a listener is a different axis: ADR-0008. Production admission does not imply a public surface. A public surface requires this admission and an exposure grant.

+

Neither admission nor exposure is an authorization decision. Whether an actor may perform an action on a resource is access-engine (ADR-0009). production-approved MUST NOT be read as permission to act.

+
+

Consequences

+
  • hosts_rail and binds_rapp no longer imply deployability.
  • reef-railiance may host Knative in wave 2, but Qonto cannot be called production-approved solely from that declaration.
  • Repeated evidence collection should become functional automation.
  • production-approved is not permission to publish a listener. See ADR-0008.
  • production-approved is not an authorization decision. See ADR-0009.
+
RMASTER-ADR-0006 · accepted-2 · acceptedrailiance-master · docs/adr/ADR-0006-reef-production-admission.md · 5ffd7d1b40d56249f490a318e728047fd3517c4c
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0007.html b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0007.html new file mode 100644 index 0000000..62b96e3 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0007.html @@ -0,0 +1,213 @@ + + + + +Rapp Declaration Contract + +
RMASTER-ADR-0007 accepted · accepted-2 railiance-master reviewed 2026-08-23generated from canonical source — do not edit

Rapp Declaration Contract

Source: railiance-master · docs/adr/ADR-0007-rapp-declaration-contract.md · 51747ca793d830c42bd86ce0ca43e34db9e8a1c1

Review due: 2027-02-23

Date: 2026-08-13 Status: Accepted

+

Context

+

RMASTER-WP-0017 through RMASTER-WP-0019 established the four-axis model and materialized the first family repos. The model held up. Its enforcement did not.

+

A 2026-08-11 survey by railiance-platform found that the three live rapp.yaml files were mutually unreadable: rollout, smoke, and rollback contracts used different shapes; metadata that both rails carry consistently appeared in only one rapp; reef-railiance bound_rapps listed rapp-qonto only, while rapp-openbao and rapp-postgres were already live on the same reef. docs/repo-family-bootstrap-contract.md named fields in prose and could not catch any of this.

+

The same survey treated rapp grouping as something that might be derived from Forgejo organizations or from State Hub domains. Neither works. A repo lives in exactly one Forgejo org, so org:repo is one-to-many. A repo may legitimately contribute to more than one rapp, so rapp:repo is many-to-many. A many-to-many grouping cannot be derived from a one-to-many one. Domains fail in both directions. The three dimensions also change at different speeds.

+

Canon OAS P1 requires independent perspectives to be separate orthogonal dimensions. Decision d07ee5f9 already recorded this from the railiance-platform side; this ADR is the framework record.

+
+

Decision

+
  1. Bounded rapp context is its own dimension. It is derived neither from Forgejo organizations nor from State Hub domains. Grouping is a first-class declaration, not a projection of some other axis.
+
  1. Two cardinalities stay distinct. Repos to rapps is many-to-many: a repo may appear in the composition.member_repos of more than one rapp. Deployables to rapps is one-to-one: every running deployable has exactly one rapp that owns its rollout. The coverage question — does every live deployable belong to exactly one rapp? — is well-formed only if these stay distinct.
+
  1. Granularity is grouped-by-bounded-context. One rapp per cohesive group that deploys, versions, and rolls back together, not one rapp per deployable. Grouping is legitimate only where members share rollout and rollback fate. A single-repo rapp is the one-member case of the same composition block, not a second shape.
+
  1. The schema is normative. schemas/rapp.schema.json, schemas/rail.schema.json, and schemas/reef.schema.json define the shapes. Framework prose cites those files. It does not restate their fields. Reef bound_rapps is a derived projection of rapp.bound_reefs, not a hand-maintained registry.
+
  1. Coverage includes every managed running deployable. Application, operational, and tooling runtimes participate in the same exactly-one-rapp invariant when they are installed, scheduled, or otherwise operated as a managed deployable. This includes a managed one-shot Job; it does not turn a human command or approval act into a workload. Human access, credential patterns, broker actions, one-off operational acts, and infrastructure resources that are not workloads retain their native actor, lane, activity, or resource identity.
+

A running deployable that predates rapp extraction is migration debt. Until an authoritative declaration claims it, workload-based controls report it as unknown; they do not infer a rapp from its repository, namespace, path, labels, or apparent owner. A subject explicitly established as not being a workload is not-applicable. unknown and not-applicable are different outcomes and omission must not collapse them.

+

Railiance Master remains the sole owner of the normative rapp vocabulary and schemas. Consumer catalogs may store explicit references and integration owners may resolve them, but neither creates a parallel declaration surface or copies rapp metadata as another source of truth.

+

The detailed shapes, including the single normative form of the rollout, smoke, and rollback contracts, live in the schema files and schemas/README.md.

+
+

Consequences

+
  • Drift across family declarations fails in tools/validate-family-declarations.py instead of accumulating in prose.
  • railiance-platform RAILIANCE-WP-0015-T02 can converge rapp-openbao and rapp-postgres onto one shape. Migration belongs to the owning repos; this ADR does not move any declaration.
  • reef-railiance must stop treating bound_rapps: [rapp-qonto] as source of truth. The list is already stale.
  • Three further rapp-* repos (rapp-secrets-engine, rapp-tenant-engine, rapp-user-engine) carry the family prefix and no declaration. They are visible to the validator as undeclared and must be declared, renamed, or retired by their owners.
  • Operational and tooling deployables are not exempt from family coverage. Existing pre-rapp runtimes may continue during migration, but their workload identity remains visibly unknown to controls until declared.
  • Runtime inventory is still required to prove universal coverage. Repository discovery alone cannot establish that every running unit has exactly one authoritative rapp.
  • Calling the validator from fix-consistency still waits on the-custodian admitting the family prefixes into the classification standard. That sequencing is not this repo's.
+
RMASTER-ADR-0007 · accepted-2 · acceptedrailiance-master · docs/adr/ADR-0007-rapp-declaration-contract.md · 51747ca793d830c42bd86ce0ca43e34db9e8a1c1
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0008.html b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0008.html new file mode 100644 index 0000000..c8488c7 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0008.html @@ -0,0 +1,218 @@ + + + + +Private-by-default Exposure + +
RMASTER-ADR-0008 accepted · accepted-2 railiance-master reviewed 2026-08-29generated from canonical source — do not edit

Private-by-default Exposure

Source: railiance-master · docs/adr/ADR-0008-private-by-default-exposure.md · 5ffd7d1b40d56249f490a318e728047fd3517c4c

Review due: 2027-02-28

Date: 2026-08-15 Status: Accepted

+

Context

+

ADR-0006 says a topology binding is not permission to run a workload in production. It does not say who may reach a listener. A working deploy, a hosts_rail / binds_rapp line, or an Ingress object has been enough to put something on the public internet.

+

Family readiness vocabularies are deliberately not unified (schemas/README.md). Reef lifecycle_state has no production-approved. Rapp readiness_state has no production-approved either. Exposure cannot be derived from those enums.

+

Live public surfaces already exist on reef-railiance. They must be named as grants, not silently grandfathered and not taken down by this decision.

+

Ratified 2026-08-15 under RMASTER-WP-0023-T01.

+
+

Decision

+

New reefs, rails, and rapps are unreachable from the public internet until they are production-safe and explicitly granted. The field name is exposure. Missing field means private.

+
PostureMeaningWho may reach it
privateNo extra listener we controlin-cluster DNS, same-reef services
operatorSame listener as private, plus a named ops-bridge / SSH tunneloperator and approved agents
publicInternet listener we publishanyone the Ingress / DNS / UFW surface allows
+

operator is an access annotation, not a different packet posture. It does not open a host port or a public Ingress.

+

Default for a new reef, rail, or rapp is private. Use operator only for a named admin or API surface that must be reachable before admission (k3s API, OpenBao UI). Do not prefer operator as the debug default.

+

public requires both an ADR-0006 binding of production-approved and an explicit grant. A deploy, a binding line, or an Ingress object is not a grant. Do not key public off rapp readiness_state and do not add production-approved to the rapp enum for this purpose.

+

A reef public surface (host port or public DNS we publish) is a substrate grant, not reef lifecycle_state. A rapp cannot be public on a reef that has not granted a public surface. Conflicting declarations fail closed.

+

The shared vocabulary is one enum. It is not one schema shape and not one admission check:

+
FamilyWhat the field meansWhat makes public legal
rapp-*intended consumer-facing listenerbinding production-approved + grant
rail-*which listener classes the rail may emitrail may emit public Ingress only when a bound grant exists
reef-*host ports and public DNS we publishsubstrate grant
+

A grant is accepted residual risk, not a self-serve wish. Required fields: hostname or port, reason, approved_on, residual-risk owner.

+

6443 / the k3s API is not a grantable public surface.

+

This contract covers listeners we control: host ports, Ingress, Service types, and public DNS we publish. Provider-native internet APIs (Scaleway S3 and other provider-delegated endpoints) are outside this enum.

+

Ops-bridge is the normal path to a shielded thing.

+

The detailed per-family reading lives in docs/exposure-posture-contract.md.

+
+

Consequences

+
  • ADR-0006 still answers "may this binding run in production?" This ADR answers "who may reach the listener?" Do not merge the axes.
  • Neither question is an authorization decision. Whether an actor may perform an action is access-engine (ADR-0009). exposure: public MUST NOT be read as permission to act.
  • The three readiness enums stay distinct on purpose.
  • Family schemas grow an additive exposure field. Rapp data_classification: public is a different field and must not be reused as the posture name.
  • Implementation stays in the owning repos. This ADR does not install NetworkPolicy, UFW, Ingress, or tunnels.
  • Existing public surfaces on reef-railiance remain up until named as grants. This ADR is not a shutdown plan.
  • CoulombCore host inventory and Q7 / Goss reaction stay outside this decision.
+
RMASTER-ADR-0008 · accepted-2 · acceptedrailiance-master · docs/adr/ADR-0008-private-by-default-exposure.md · 5ffd7d1b40d56249f490a318e728047fd3517c4c
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0009.html b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0009.html new file mode 100644 index 0000000..8c46c9e --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RMASTER-ADR-0009.html @@ -0,0 +1,214 @@ + + + + +NetKingdom Security-Layer Interaction Boundary + +
RMASTER-ADR-0009 accepted · accepted-1 railiance-master reviewed 2026-08-29generated from canonical source — do not edit

NetKingdom Security-Layer Interaction Boundary

Source: railiance-master · docs/adr/ADR-0009-netkingdom-security-layer-interaction.md · 51747ca793d830c42bd86ce0ca43e34db9e8a1c1

Review due: 2027-02-28

Date: 2026-08-29 Status: Accepted

+

Context

+

NetKingdom Security Layer Model v0.7 is accepted. Section 20 restates Railiance workload-operation definitions owned by this repository and states consumption rules every Railiance consumer of NetKingdom security owes. Companion v0.2 §9 is the operative form of the same boundary.

+

This repository had declared the four axes and the workload coverage rule in its own voice, but had no machine-readable layer declaration, no recorded assent to §20, and no framework contract that bound rails, rapps, and reefs to those consumption rules. Admission (ADR-0006) and exposure (ADR-0008) were live and were not demarcated from authorization.

+

Statute §20.4: an interaction boundary between two frameworks is owned by neither alone. Changes to §20 require this repository's assent for the axis definitions and glas-harness assent for the session and tool-policy seam.

+

Ratified 2026-08-29 under RMASTER-WP-0026.

+
+

Decision

+
  1. This repository is Taxonomy of Railiance workload operations. The machine-readable declaration is layer.yaml. It is not a NetKingdom §4 catalog row. It is not PEP-shaped. It holds no Tooling-layer client.
+
  1. Statute §20.1 restates our definitions and does not author them. Workload, the four axes, and the rule that rein-* is not a fifth axis remain this repository's. NetKingdom may cite them; it may not redefine them without our assent.
+
  1. Statute §20.2 is the consumption constitution for every Railiance consumer of NetKingdom security. The detailed contract is docs/netkingdom-security-consumption-contract.md.
+
  1. Statute §20.3 remains unset. This repository will not imply a mapping of rails, rapps, reefs, or ownership onto Taxonomy, Tooling, Engine, or Staff. The five questions are tracked, unanswered, in docs/netkingdom-axis-layer-open-questions.md.
+
  1. Admission, exposure, and authorization stay three questions. ADR-0006 answers whether a binding may run in production. ADR-0008 answers who may reach a listener we control. access-engine answers whether an actor may perform an action. production-approved and exposure: public are not authorization decisions.
+
  1. Changes to this boundary require this repository's assent for the axis definitions. Changes that touch the glas-harness seam require glas-harness assent as well.
+
+

Consequences

+
  • Rails, rapps, and reefs consume access-engine, approval-engine, secrets-engine, and audit-core. They do not grow local substitutes.
  • This repository does not host a PDP, an approval store, a credential plane, an evidence archive, or an actuation surface.
  • PEP stance maps belong in the repositories that cause protected side effects, inventoried in statute §13.1, not here.
  • Observation-in-production and automatic containment remain estate-wide zeros. Framework plans must not assume they exist.
  • gate-house can cite this ADR as this repository's own-voice declaration and §20 assent, rather than a review note about us.
+
+

Notes

+

This ADR does not amend ADR-0001 through ADR-0008. It adds the security consumption axis those records did not have to name.

+
RMASTER-ADR-0009 · accepted-1 · acceptedrailiance-master · docs/adr/ADR-0009-netkingdom-security-layer-interaction.md · 51747ca793d830c42bd86ce0ca43e34db9e8a1c1
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RPLAT-ADR-0001.html b/docs/evidence/2026-10-11-served-publication-history/pages/RPLAT-ADR-0001.html new file mode 100644 index 0000000..2bc6cea --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RPLAT-ADR-0001.html @@ -0,0 +1,209 @@ + + + + +ADR-0001 — S3 owns platform services, not the substrate beneath them + +
RPLAT-ADR-0001 accepted · 1.0 railiance-platform reviewed 2026-08-17generated from canonical source — do not edit

ADR-0001 — S3 owns platform services, not the substrate beneath them

Source: railiance-platform · docs/adr/ADR-0001-s3-platform-service-boundary.md · 56d516e10cdad6691f254ec9cb11f11ea364f7de

Review due: 2027-02-17

Context

+

railiance-platform is S3 on the OAS Stack: the shared services several applications depend on — PostgreSQL, secrets, cache, object storage. The layers around it are S1 railiance-infra (OS and host concerns), S2 railiance-cluster (Kubernetes runtime, ingress), S4 railiance-enablement (tooling and CI), S5 railiance-apps (workloads).

+

This boundary has been stated in SCOPE.md and in ADR-003 of railiance-infra since the five-repo split, and it has been tested twice. RAIL-PL-WP-0001 existed to extract platform services out of S2 subcharts. On 2026-08-17 POLICY-NEXUS-WP-0001 assigned this repo "the substrate — DNS, TLS, ingress, hosting" for policy.coulomb.social, which would move the boundary back the other way.

+

The pressure is predictable and will recur: S3 is the layer that looks like it owns infrastructure, because it owns things that feel infrastructural. Recording the rule as an ADR rather than as a line in SCOPE.md gives future requests something to be answered against.

+
+

Decision

+

**S3 owns shared platform services and the interfaces consumers bind to. It does not own DNS, TLS, Kubernetes ingress, host configuration, or CI.** A request that would place any of those here is redirected to S1, S2 or S4 rather than absorbed, even when this repo is the closest available owner and even when absorbing it would be faster.

+

Where S3 has a genuine part in such a request — an object-storage bucket, a credential lane, a database — it supplies that part and co-signs the rest. It does not become the owner of the whole by being the first repo asked.

+
+

Consequences

+
  • POLICY-NEXUS-WP-0001 T04 needs splitting: ingress and TLS to S2, DNS to S1 or the reef boundary, storage and credentials here. Routed as RPF-WP-0018 finding F1.
  • A request this repo declines does not thereby become nobody's. Declining carries an obligation to name the owner, which is why F1 names two.
  • The rule cuts against this repo's convenience in the common case. That is what makes it a rule rather than a preference.
  • rapp-openbao and rapp-postgres are the same principle applied inward: the deployable package left, custody and policy stayed. See docs/rapp-openbao-boundary.md and docs/rapp-postgres-boundary.md.
+
+

Alternatives considered

+

Accept the substrate assignment as written. Fastest, and the requester had already resolved it with the operator. Rejected: it re-imports the coupling RAIL-PL-WP-0001 spent a workplan removing, and a boundary that yields to whoever asks most recently is not a boundary.

+

Own ingress for S3-adjacent services only. A narrower version, and it fails on the first argument about what counts as adjacent. The line has to be drawn where it can be checked.

+
RPLAT-ADR-0001 · 1.0 · acceptedrailiance-platform · docs/adr/ADR-0001-s3-platform-service-boundary.md · 56d516e10cdad6691f254ec9cb11f11ea364f7de
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RPLAT-ADR-0002.html b/docs/evidence/2026-10-11-served-publication-history/pages/RPLAT-ADR-0002.html new file mode 100644 index 0000000..ffeebbb --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RPLAT-ADR-0002.html @@ -0,0 +1,212 @@ + + + + +ADR-0002 — S3 owns the placement rule; the package repo owns the number + +
RPLAT-ADR-0002 proposed · 1.0 railiance-platform reviewed 2026-08-17generated from canonical source — do not edit

ADR-0002 — S3 owns the placement rule; the package repo owns the number

Source: railiance-platform · docs/adr/ADR-0002-placement-policy-ownership.md · 56d516e10cdad6691f254ec9cb11f11ea364f7de

Review due: 2027-02-17

Context

+

An earlier draft of net-kingdom/canon/standards/tenancy-posture_v0.1.md §8.2 proposed that database placement policy — dedicated versus shared, and when that changes — be owned by railiance-platform, co-signed by adaptive-pricing. tenant-engine raised the same gap independently on 2026-08-16: both patterns are live on railiance01, neither is written down, and each new service copies whichever neighbour it looked at.

+

The complication is that this repo no longer holds the specs. RAILIANCE-WP-0012 and RAILIANCE-WP-0015 moved the deployable surface to the rapp-* repos. platform-pg's instances, max_connections, memory limit and retention are rapp-postgres's cluster CR. Tenancy Posture §19.8 nonetheless asks this repo for platform-pg's declared maximum size — a question one hop from where its answer lives.

+

Accepting ownership without stating this would produce either an answer we cannot substantiate or a quiet non-answer.

+
+

Decision

+

Accept placement ownership, split along rule and number.

+

This repo owns the rule: the default placement level, what overrides it, who is accountable for each workload's placement, that a shared cluster must publish a ceiling and an overflow target before its third consumer, that retention has a floor and a ceiling, and that a consumer's erasure horizon on shared substrate is disclosed rather than inherited silently.

+

The package repo owns the number: what the ceiling actually is, which resource binds first, what the retention values are, and the validator that enforces them where the CR lives.

+

Placement ownership per workload sits with the consuming repo; this repo co-signs. The consumer knows its latency requirement, erasure horizon and compliance position; S3 knows the substrate. A placement change needs both. Putting the decision solely here would recreate the problem §8.2 exists to avoid — an operations-shaped repo holding a commercial decision alone.

+

adaptive-pricing declined the standing co-signature on 2026-08-17. The replacement is binding and machine-reconcilable: it publishes typed tier minimums; this policy validates placement against them. Ownership stays here.

+
+

Consequences

+
  • docs/placement-policy.md is the operative document; this ADR is the decision behind it.
  • §19.8 is answered for platform-pg by rapp-postgres ADR-0004: four consumers, with platform-pg-2 named as the still-absent overflow target. Rule P-4.1 still requires apps-pg to declare its own ceiling before its third consumer.
  • This repo owes apps-pg's ceiling under its own rule. A policy whose author is exempt is not a policy.
  • §8.1 requires triggers to be monitored. This repo has no monitoring plane, so the connection-and-memory-ceiling trigger is recorded unmonitored pending railiance-telemetry. Declaring a ceiling is a partial substitute — a written threshold is checkable by a human at provisioning, which beats a limit discovered by an OOM kill.
  • adaptive-pricing's decline is recorded as single policy ownership plus a mandatory typed constraint join, not as an absent signature.
+
+

Alternatives considered

+

Decline ownership; route it to rapp-postgres. They hold the specs and the operational knowledge. Rejected: placement is a cross-cluster question and rapp-postgres owns one package. A policy owned by one substrate's operator cannot govern movement between substrates.

+

Accept whole, including the numbers. Rejected: it would either re-import the deployable surface this repo deliberately gave up, or produce numbers restated here that drift from the CR — a second source of truth for exactly the values a consumer must be able to trust.

+
RPLAT-ADR-0002 · 1.0 · proposedrailiance-platform · docs/adr/ADR-0002-placement-policy-ownership.md · 56d516e10cdad6691f254ec9cb11f11ea364f7de
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/RPLAT-ADR-0003.html b/docs/evidence/2026-10-11-served-publication-history/pages/RPLAT-ADR-0003.html new file mode 100644 index 0000000..10a9696 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/RPLAT-ADR-0003.html @@ -0,0 +1,211 @@ + + + + +ADR-0003 — Decisions that bind others live in docs/adr, not only in the State Hub + +
RPLAT-ADR-0003 accepted · 1.0 railiance-platform reviewed 2026-08-17generated from canonical source — do not edit

ADR-0003 — Decisions that bind others live in docs/adr, not only in the State Hub

Source: railiance-platform · docs/adr/ADR-0003-decisions-live-in-the-repo.md · 56d516e10cdad6691f254ec9cb11f11ea364f7de

Review due: 2027-02-17

Context

+

This repo recorded decisions with the State Hub's record_decision() and wrote governing content as prose in docs/ — 24 files on 2026-08-17, none carrying a status, owner, revision or review date. It held no ADRs at all.

+

Two things made that a defect rather than a style.

+

The hub is a read model. The estate's standing rule is that local files are the source of truth and the hub reflects them. A decision that exists only as a hub record inverts that for the one class of content where it matters most.

+

policy-nexus publishes canon and ADRs only, globbing docs/adr/*.md from each repo. A repo with no ADRs is invisible to the estate's publication surface no matter how much governing content it holds. This repo owns S3 boundary rules, the credential-lane model and the consumption-mode gate — all binding on other repos, all unpublishable.

+

The gap was structural, not an oversight: nothing in the session protocol ever told anyone to write an ADR here.

+
+

Decision

+

A decision that binds anyone outside this repo is recorded as an ADR in docs/adr/, with the frontmatter in docs/adr/README.md, before or alongside any hub record. The hub record points at the ADR; it does not replace it.

+

Runbooks and procedures stay as prose in docs/ and stay unpublished. The test is not importance — several runbooks are critical — but whether the document decides something for someone else.

+
+

Consequences

+
  • docs/adr/ exists, with a convention, a template and this repo's first three ADRs.
  • Existing prose is not converted wholesale. Content is promoted when it is next touched, or when a consumer needs to cite it.
  • last_reviewed and review_interval are mandatory here, which is stricter than the estate norm — of 69 ADRs across 19 repos on 2026-08-17, 21 had YAML frontmatter and 2 had any notion of a review date. POLICY-NEXUS-WP-0001 T05 cannot compute staleness without those fields and must not invent them, since inventing them would make the publication a second source of truth, which is that workplan's own top risk. Routed as RPF-WP-0018 finding F5.
  • Session protocol guidance should follow, so the next agent writes an ADR without being told. Not done in this ADR.
+
+

Alternatives considered

+

Keep decisions in the hub and have policy-nexus read it. Rejected on both sides: it would make a read model authoritative, and it would give the publication surface a source that no repo can diff or review.

+

Add frontmatter to all 24 existing docs/ files. Rejected. Most are runbooks that should not be published, and stamping them with a status would assert a decision that was never made.

+
RPLAT-ADR-0003 · 1.0 · acceptedrailiance-platform · docs/adr/ADR-0003-decisions-live-in-the-repo.md · 56d516e10cdad6691f254ec9cb11f11ea364f7de
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/canon-autonomy-lanes.html b/docs/evidence/2026-10-11-served-publication-history/pages/canon-autonomy-lanes.html new file mode 100644 index 0000000..c59a452 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/canon-autonomy-lanes.html @@ -0,0 +1,222 @@ + + + + +Autonomy Lanes (Fleet) v0.1 + +
canon-autonomy-lanes accepted · accepted-1 the-custodian reviewed 2026-08-31generated from canonical source — do not edit

Autonomy Lanes (Fleet) v0.1

Source: the-custodian · canon/standards/autonomy-lanes_v0.1.md · 4b951be3947f9457cc091a7d359d9232646b4c93

Review due: 2027-02-28

Ratified by founder 2026-07-20 (CUST-WP-0060-T01, hub decision f4640f9e). Promotes the binky-control AutonomyPolicy lane model to fleet canon, unchanged in substance. binky-control's AutonomyPolicy.md remains the company-level policy instance; this standard makes the lane vocabulary and enforcement rules fleet-wide.

+

The rule

+

If the responsible human is unavailable, the system must either continue safely, prepare the next decision, or explicitly defer with evidence. It must not silently idle.

+

Valid states: proceeding | prepared for review | deferred by policy. Invalid state: waiting because unsure. Uncertainty triggers research, comparison, preparation, or risk classification — not paralysis. "Ask the human" is never the default.

+
+

Lanes

+
LaneNameAgent mayHuman involvement
greenSafe autonomousresearch, summarize, classify, draft, organize, test, preparenone (human-on-the-loop via logs)
blueReversible executioninternal docs, backlog updates, local scripts, reports, local commitsnone, but logged
yellowDeferred approvalprepare external emails, invoices, offers, releases, paymentsasync approval via decision records
orangeTime-window approvalcustomer/legal/accounting/banking actions needing business hoursengagement records, batched into command days
redHuman-onlysigning, strategic commitments, bank/credential changes, sensitive legal/HR/securityalways; agents prepare packages only
+

Optimization target: maximize green+blue throughput, queue yellow, batch orange, protect red from automation. Risk-tiered gating (in-the-loop for irreversible actions, on-the-loop for recoverable ones) is the only oversight pattern that scales.

+
+

Lane as a spine field

+

lane is a mandatory field on every work record (see work-record-types_v0.1.md) and on every agent instance manifest. The chain is closed end-to-end:

+
work record.lane → view routing (autopilot / decision / engagement)
+                 → harness tool profile (enforced allow-list)
+                 → ops-warden credential lane (catalog access)
+

Declaring a lane an item does not hold is a policy violation the harness must catch (tool profile refuses the run).

+
+

Approval packages (yellow and above)

+

An agent hitting a lane boundary prepares a complete decision package, never a bare question: recommended action, reason, evidence links, risk level, reversibility, deadline (needed_by), attention cost, fallback if no response, options.

+
+

Human attention is a WIP-limited workstation

+

The responsible human's capacity is modeled like any lane capacity: attention budget (minutes/day, max decisions/day, max deep topics/day) and availability as first-class state (available | evening_window | unavailable | office_hour_day), with an explicit escalation allow-list when unavailable (critical security incident, payment failure, legal deadline). Yellow routes to the decision view, orange to the engagement view, both subject to that capacity.

+

Time-boxing is hub-owned: needed_by / fallback_if_no_response windows are watched by the hub → NATS → activity-core; a lapsed window raises the escalation or executes the declared fallback — never a human re-reading a file.

+
+

Raising autonomy

+

Autonomy per area is raised only after the system proves reliable there, by explicit human decision recorded as a decision work record. Lane assignments are auditable history; lowering a lane needs no approval.

+
+

References

+
  • binky-control/AutonomyPolicy.md (origin instance)
  • canon/standards/work-record-types_v0.1.md
  • research/2026-07-19-work-orchestration-best-practices.md §2
+
canon-autonomy-lanes · accepted-1 · acceptedthe-custodian · canon/standards/autonomy-lanes_v0.1.md · 4b951be3947f9457cc091a7d359d9232646b4c93
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/canon-contrib-convention.html b/docs/evidence/2026-10-11-served-publication-history/pages/canon-contrib-convention.html new file mode 100644 index 0000000..ec65d3b --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/canon-contrib-convention.html @@ -0,0 +1,273 @@ + + + + +Contribution Convention v0.1 + +
canon-contrib-convention accepted · accepted-1 the-custodian reviewed 2026-08-31generated from canonical source — do not edit

Contribution Convention v0.1

Source: the-custodian · canon/standards/contribution-convention_v0.1.md · 4b951be3947f9457cc091a7d359d9232646b4c93

Review due: 2027-02-28

Purpose

+

This document defines the canonical convention for tracking upstream contributions across all custodian-ecosystem repositories. A contribution is any intentional engagement with an external upstream project: a bug report, feature request, extension-point proposal, or pull request.

+

Contributions are tracked as Markdown artifacts with typed YAML frontmatter, committed to the repository that authors them, and indexed in the State Hub DB. This enables the Custodian to maintain a full audit trail of all upstream engagement across all six project domains.

+
+

Artifact Types

+
CodeNameDescription
brBug ReportA defect reported to an upstream project
frFeature RequestA capability requested from an upstream project
epExtension-Point ProposalA proposed extension point in an upstream project
uprUpstream PRA pull request submitted or planned for upstream
+
+

Directory Layout

+
<repo-root>/
+  contrib/
+    bug-reports/       # BR artifacts
+    feature-requests/  # FR artifacts
+    extension-points/  # EP artifacts
+    upstream-prs/      # UPR artifacts
+

Each artifact file follows the naming pattern:

+
<YYYY-MM-DD>--<org>--<repo>--<slug>.md
+

Examples:

+
  • contrib/bug-reports/2026-03-01--observablehq--framework--plot-tooltip-flicker.md
  • contrib/upstream-prs/2026-02-26--observablehq--framework--toc-sidebar-inject.md
  • contrib/extension-points/EP-CUST-002--anthropic--claude-code--hook-on-tool-result.md
+
+

Frontmatter Schema

+

All contribution artifacts share a common base frontmatter:

+
---
+id: <type>-<YYYY-MM-DD>--<org>--<repo>--<slug>   # unique string ID
+type: br | fr | ep | upr                           # required
+target_org: <github-org-or-owner>                  # required
+target_repo: <repository-name>                     # required
+title: "Human-readable title"                      # required
+status: draft | submitted | acknowledged | accepted | rejected | merged | withdrawn
+domain: infotech | agents | communication | ...   # current sector domain; see Repo Classification Standard §6
+related_workstream: <workstream-slug>              # optional
+state_hub_contribution_id: <uuid>                  # set once registered in State Hub
+created: "YYYY-MM-DD"
+updated: "YYYY-MM-DD"
+---
+

BR — Bug Report (additional fields)

+
upstream_version: "1.2.3"          # version where bug was observed
+reproduction_steps: |
+  1. …
+  2. …
+expected: "What should happen"
+actual: "What actually happens"
+workaround: "Any known workaround"  # optional
+upstream_issue_url: "https://…"     # optional — link once filed
+

FR — Feature Request (additional fields)

+
motivation: "Why this feature is needed"
+proposed_api: |
+  # Sketch of the proposed interface
+alternatives: "What we do instead today"
+upstream_issue_url: "https://…"     # optional
+

EP — Extension-Point Proposal (additional fields)

+
ep_id: EP-<DOMAIN>-NNN              # e.g. EP-RAIL-007
+location: "src/file.ts:42"          # file:line in upstream where hook would live
+proposed_interface: |
+  # Interface or callback signature sketch
+rationale: "Why this extension point is valuable"
+upstream_issue_url: "https://…"     # optional
+

UPR — Upstream PR (additional fields)

+
local_component_path: "relative/path/in/this/repo"
+target_upstream_file: "path/in/upstream/repo"
+upstream_pr_url: "https://…"        # set once submitted
+draft_pr_body: |
+  ## Summary
+  …
+
+  ## Motivation
+  …
+
+

ID Schemes

+
  • BR / FR / UPR: <type>-<YYYY-MM-DD>--<org>--<repo>--<slug> — date-prefixed slug
  • EP: EP-<DOMAIN>-NNN where <DOMAIN> is uppercase (CUST, RAIL, MARK, etc.) and NNN is a zero-padded three-digit sequence per domain (EP-CUST-001, EP-CUST-002, …). This scheme is an extension of the Railiance EP convention defined in the staged-promotion-lifecycle workstream; this canon document is the authoritative master spec.
+
+

Status Lifecycle

+
draft → submitted → acknowledged → accepted → merged
+                ↘             ↘
+              rejected      withdrawn
+
  • draft: artifact created locally, not yet sent upstream
  • submitted: issue filed or PR opened upstream
  • acknowledged: upstream team has responded
  • accepted: upstream has agreed to the proposal
  • rejected: upstream has declined
  • merged: PR merged / fix released
  • withdrawn: we decided not to pursue
+
+

Relationship to State Hub

+

Once an artifact is registered via register_contribution() MCP tool or POST /contributions/ API, the State Hub assigns a UUID and returns it. That UUID is written back into state_hub_contribution_id in the frontmatter.

+

The State Hub is a read/cache layer — the Markdown file is always the authoritative source of truth. If the DB is reset, contributions can be re-ingested from the files.

+
canon-contrib-convention · accepted-1 · acceptedthe-custodian · canon/standards/contribution-convention_v0.1.md · 4b951be3947f9457cc091a7d359d9232646b4c93
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/canon-project-repository-flavor.html b/docs/evidence/2026-10-11-served-publication-history/pages/canon-project-repository-flavor.html new file mode 100644 index 0000000..650f068 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/canon-project-repository-flavor.html @@ -0,0 +1,279 @@ + + + + +Project Repository Flavor (prj-) v0.1 + +
canon-project-repository-flavor accepted · accepted-1 the-custodian reviewed 2026-08-31generated from canonical source — do not edit

Project Repository Flavor (prj-) v0.1

Source: the-custodian · canon/standards/project-repository-flavor_v0.1.md · 4b951be3947f9457cc091a7d359d9232646b4c93

Review due: 2027-02-28

Purpose

+

Define the durable project repository flavor used for complex cross-repository efforts: naming, required files, authority boundary, lifecycle, residual handoff, and archive procedure.

+

This standard implements and completes ADR-005 (Cross-Repo Workplans Live in Dedicated Project Repos). It does not replace the Repo Classification Standard: project repositories still use category: project in .repo-classification.yaml.

+
+

When to use a project repository

+

Create a project repository when all of the following hold:

+
  1. The work coordinates change across two or more functional repositories.
  2. Binding the coordination workplan to any single product/tooling repo would misrepresent ownership or pollute that repo's history.
  3. The effort has a bounded outcome with explicit success and retirement conditions (not an open-ended product roadmap).
+

Do not create a project repository for:

+
  • single-repo workplans;
  • small cross-cutting fixes that fit one lead repo's workplan plus dependency links;
  • durable products, libraries, or services (those use INTENT.md and ordinary product/tooling/research categories);
  • long-lived domain charters (those remain under the-custodian/canon/projects/).
+
+

Naming

+
ElementConvention
Git / local directoryprj-<kebab-slug>
Hub repo slugsame as directory name
Workplan prefixshort uppercase prefix derived from the project, ending in -WP- (example: SHR-WP- for State Hub Retirement)
Classificationcategory: project
+

Canonical prefix is prj-. ADR-005's illustrative proj- form is superseded for new repositories. Do not invent parallel prefixes (project-, xrepo-, …).

+

Slug rules:

+
  • lowercase kebab-case;
  • outcome-oriented (prj-state-hub-retirement), not a random ticket id;
  • stable for the life of the project (rename only with an explicit migration note in history/).
+
+

Authority boundary

+
ConcernOwner
Project goal, success gates, retirement conditionsproject repo (GOAL.md)
Architecture decisions, sequencing, dependency map, migration ledger, risks, consolidated evidenceproject repo
Cross-repo workplans that coordinate the effortproject repo (workplans/)
Production implementation, tests, deployablesparticipating functional repositories
Per-repo implementation workplans and local evidenceeach functional repository
Durable post-project standards and ADRspermanent homes (the-custodian canon, or the owning product/tooling repo)
+

Hard rules:

+
  1. Project repositories MUST NOT host production implementation code for the systems being changed.
  2. Project workplans link child workplans by stable ID; they MUST NOT copy or replace child task lists.
  3. The hub remains a read/index layer; work records originate as files (ADR-001).
  4. Results of the project live primarily in the modified functional repositories and promoted canon. The project repo is coordination + provenance.
+
+

Required files

+

At registration / first meaningful commit:

+
PathRequiredRole
GOAL.mdyesOutcome, invariants, success gates, project retirement conditions. Replaces INTENT.md for this flavor.
SCOPE.mdyesProject authority, participating repos, in/out of scope, work-record rule.
README.mdyesShort entry point linking GOAL.md, SCOPE.md, genesis/history, active workplans.
AGENTS.mdyesAgent session protocol for this project (orient → work → close; hub integration).
.repo-classification.yamlyescategory: project plus domain / capability tags for the effort.
workplans/yesAt least one foundation workplan; prefix unique to the project.
history/recommendedGenesis and significant milestones (why the project exists).
WORK-RECORDS.mdgeneratedHub index; do not hand-edit.
.custodian-brief.mdgeneratedOffline orientation brief.
+

GOAL.md (not INTENT.md)

+

Durable product and tooling repositories explain why they exist with INTENT.md. Project repositories are temporary and outcome-bounded; they use GOAL.md instead.

+

GOAL.md MUST include:

+
  1. Outcome — what success looks like in one short section.
  2. Invariants — rules that must hold during the project.
  3. Success gates — checkable conditions for “goal achieved”.
  4. Project retirement — conditions under which this repository may be archived (usually: gates met + residuals handed off + durable decisions promoted).
+

Recommended frontmatter:

+
---
+repo: prj-<slug>
+repo_flavor: project
+project_status: draft | active | completed | archived
+started: "YYYY-MM-DD"
+reviewed: "YYYY-MM-DD"
+---
+

Anti-pattern: shipping both INTENT.md and GOAL.md in a prj- repo. If a durable intent emerges, promote it into a permanent repository's INTENT.md (or canon) and keep the project goal as the temporary outcome document.

+

Relation to durable INTENT.md

+
Repository kindPurpose document
product / tooling / research / business / experimentalINTENT.md
project (prj-)GOAL.md only
+

Tooling that scaffolds repositories (e.g. statehub register) MUST treat GOAL.md + repo_flavor: project as sufficient purpose documentation for prj- repos and MUST NOT require inventing an INTENT.md that pretends the project is a permanent product.

+
+

Lifecycle

+
draft → active → completed → archived
+
StatusMeaningRepo / hub signals
draftConvention and foundation still forming; may not yet be registeredproject_status: draft; workplans often proposed / ready
activeCoordination and child implementation in progressproject_status: active; at least one workplan active or ready
completedAll success gates have accepted evidence; residuals have live owners outside this repoproject_status: completed; project workplans finished or cancelled with rationale
archivedRepository is read-only provenanceforge archive flag; hub repo/workplan archived; no normal writes
+

Promotion rules:

+
  • draft → active when GOAL.md / SCOPE.md are reviewable and the foundation workplan is ready or active.
  • active → completed only when every GOAL.md success gate has accepted evidence and residuals are live work records elsewhere (see below).
  • completed → archived after the completion record is written, durable decisions are promoted, and a final consistency/link check passes.
+
+

Residuals before completion

+

Actionable leftovers MUST NOT live only in finished workplan prose or SCOPE.md. Before any project workplan is set to finished, and again before project_status: completed, each residual becomes a live work record outside the closing narrative:

+
  • intake with origin: residual + origin_ref: <WP-id>, or
  • a next workplan / decision / engagement / register-entry in a permanent repo.
+

Canon: work-record-types_v0.1.md § Residuals. Fleet residual listing is a State Hub concern, not a project-local queue file.

+
+

Completion record

+

Before archival, the project repository MUST contain a completion record (recommended path: history/YYYY-MM-DD-completion.md) that lists:

+
  1. deployed or merged revisions that realize the outcome;
  2. every participating repository touched, with links to merged PRs/commits or child workplan IDs;
  3. promoted durable artefacts (canon paths, ADRs, standards);
  4. migration and retirement evidence pointers (counts, cutover windows, backups);
  5. residual work-record IDs and their owning repos;
  6. rollback expiry or remaining operational obligations, if any.
+

Without this record, do not archive.

+
+

Archive procedure

+
  1. Verify all success gates and retirement conditions in GOAL.md.
  2. Confirm residuals are live and owned outside this repository.
  3. Promote remaining durable decisions/standards to permanent homes; leave pointers from the project repo.
  4. Finish or cancel project workplans; move finished files to workplans/archived/YYMMDD-<id>-<slug>.md per workplan convention.
  5. Write the completion record.
  6. Set project_status: archived in GOAL.md frontmatter.
  7. Run statehub fix-consistency (or equivalent) so the hub reflects finished workplans and the archived status.
  8. Archive the repository in the forge (read-only); do not delete it.
  9. Optionally mark the hub registration archived / read-only.
+

Archive means read-only provenance, never deletion. Historical evidence and coordination narrative remain queryable.

+
+

Workplan and agent conventions

+
  • Workplan files: workplans/<PREFIX>-WP-NNNN-<slug>.md with fleet status vocabulary (proposed … archived).
  • Session start order for agents: GOAL.md → SCOPE.md → genesis/history → active workplans (see project AGENTS.md).
  • Progress and task status follow ordinary State Hub / fix-consistency rules.
  • Ad-hoc low-risk fixes may use workplans/ADHOC-YYYY-MM-DD.md with canonical id {PREFIX}-WP-ADHOC-YYYY-MM-DD; anything multi-step belongs in a normal workplan.
+
+

Minimal layout example

+
prj-example-cutover/
+  GOAL.md
+  SCOPE.md
+  README.md
+  AGENTS.md
+  .repo-classification.yaml
+  history/
+    2026-08-09-genesis.md
+  workplans/
+    EX-WP-0001-foundation.md
+
+

Reference instance

+

The first repository under this standard is prj-state-hub-retirement (State Hub retirement / hub-core consolidation). Use it as the structural reference when scaffolding later prj- repos; do not copy its domain-specific content.

+
+

Conformance checklist

+

A repository conforms to the prj- flavor when:

+
  • [ ] Named prj-<slug> and classified category: project.
  • [ ] Has GOAL.md with outcome, invariants, success gates, and retirement.
  • [ ] Has no durable-product INTENT.md (or documents an explicit temporary exception during migration of a mis-scaffolded repo).
  • [ ] Has SCOPE.md, README.md, AGENTS.md, and .repo-classification.yaml.
  • [ ] Hosts coordination workplans only; implementation lives in child repos.
  • [ ] Links child workplans by ID rather than duplicating their tasks.
  • [ ] Defines archive/completion discipline consistent with this standard.
  • [ ] On completion, writes a completion record and archives read-only.
+
+
canon-project-repository-flavor · accepted-1 · acceptedthe-custodian · canon/standards/project-repository-flavor_v0.1.md · 4b951be3947f9457cc091a7d359d9232646b4c93
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/canon-work-record-types.html b/docs/evidence/2026-10-11-served-publication-history/pages/canon-work-record-types.html new file mode 100644 index 0000000..c7f38af --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/canon-work-record-types.html @@ -0,0 +1,246 @@ + + + + +Work Record Types & Identity (Fleet) v0.1 + +
canon-work-record-types accepted · accepted-1 the-custodian reviewed 2026-08-31generated from canonical source — do not edit

Work Record Types & Identity (Fleet) v0.1

Source: the-custodian · canon/standards/work-record-types_v0.1.md · 4b951be3947f9457cc091a7d359d9232646b4c93

Review due: 2027-02-28

Ratified by founder 2026-07-20 (CUST-WP-0060-T01, hub decision f4640f9e). Source: research/WorkOrchestrationArchitectureDraft.md v0.2 (founder-reviewed 2026-07-20). Extends — does not replace — workplan-terminology-fleet_v0.1.md and ADR-001/ADR-005.

+

Purpose

+

Define work record as the umbrella term for every identified, lifecycle-bearing coordination artefact in the fleet; register the closed list of work-record kinds, their id schemes and abstract lifecycles; and fix the two-layer identity rule. This is the backbone convention for all work — planning, development, testing, operations, security & compliance, controlling, billing, marketing, sales — under one conceptual framework (different demands are met by flow profiles and kind-specific fields, never by parallel ontologies).

+
+

Core definition

+

A work record is any identified, lifecycle-bearing coordination artefact. A task is one kind of work record — the smallest executable unit. Every work record, regardless of kind, carries the conventional spine:

+
FieldConvention
idcanonical name per the type registry below
uuidUUIDv7, hub-assigned at first index, written back to the source file
kindone of the registered kinds
laneautonomy lane (autonomy-lanes_v0.1.md)
statusabstract lifecycle of the kind (below)
owneragent identity (agt-…) or human
repoowning repo (ADR-001/ADR-005 anchor)
tagspolicy + derived tags (§ Tags)
created / updateddates (UUIDv7 supplies fine-grained internal time)
+

Kind-specific fields (budget, evidence, counterparty, deadline_pressure, attention_cost, …) extend the spine; they never replace it.

+
+

Kind registry (closed list)

+
KindId schemeMeaningAbsorbs / legacy
`workplan`{PREFIX}-WP-NNNN · {PREFIX}-WP-ADHOC-YYYY-MM-DDstructured, dependency-bearing body of work; repository-qualified daily container for small same-day tasksunqualified ADHOC-YYYY-MM-DD; MASON-0001 (grandfathered)
`task`{WP-ID}-TNNsmallest executable unitunqualified ADHOC-YYYY-MM-DD-TNN; MASON-0001-TNN (grandfathered); issue-core issues become external projections only
`intake`{PREFIX}-IN-NNNNspark: idea, finding, directive, requesthub suggestions (legacy, read-only close-out); AWQ-NNN (grandfathered)
`decision`{PREFIX}-DEC-YYYY-NNNapproval package + resolutionDEC-YYYY-NNN (binky, grandfathered as-is)
`engagement`{PREFIX}-ENG-YYYY-NNNscheduled external interaction (counterparty, prepared material, time window)OH-YYYY-NNN (grandfathered)
`register-entry`existing schemes: RISK-NNN, RISK-F-NNNN, RISK-REG-NNNN, TD-…, CAP-…, CCR-YYYY-NNNN, ADR-NNNrisks/findings, regulatory records, technical debt, capability requests, credential change requests, architecture decisionsunchanged semantics, brought under the spine
+

Rules:

+
  1. Prefix grammar: {PREFIX} is one or more uppercase alphanumeric segments, each beginning with a letter, joined by hyphens. Both compact prefixes such as CUST and repository-derived prefixes such as RAPP-OPENBAO or RAIL-K8S are canonical. The final -WP- token remains the unambiguous kind separator.
  2. The registry is closed. An id pattern not registered in work-record-types.yaml fails CI and fix-consistency (the sidetrack detector). New kinds/prefixes are added by canon PR — deliberately cheap (one YAML entry + one schema) so the legitimate path is easier than any workaround.
  3. Grandfathering: existing ids are never renamed. Legacy prefixes map to kinds in the registry; retirement of a scheme (never of records) follows the legacy-meter discipline of workplan-terminology-fleet_v0.1.md.
+

Compatibility correction 2026-08-23: State Hub message 24504ce7-4f73-47da-a150-629b07b37073 exposed that the machine pattern did not accept the already owner-registered RAPP-OPENBAO-WP prefix. The grammar now matches ADR-007's repository-derived prefix model; prefix ownership and uniqueness remain separate registry checks and are not weakened by this syntax correction.

+

The same compatibility review initially registered unqualified ADHOC-YYYY-MM-DD containers. A same-day collision between Activity Core and Net Kingdom then demonstrated that this form violates ADR-007's fleet-namespace uniqueness rule and cannot be used with deterministic UUID derivation. New daily records therefore use {PREFIX}-WP-ADHOC-YYYY-MM-DD; the filename remains workplans/ADHOC-YYYY-MM-DD.md. Existing unqualified ids are grandfathered and are never silently re-derived or disambiguated. Ad-hoc containers remain only for low-risk work completed directly.

+

Risk Nexus findings (RISK-F-*) and regulatory records (RISK-REG-*) are registered as variants of register-entry, not as parallel work kinds. Their lifecycles remain owned by the register convention. RISK-N-* notes are deliberately not registered: a note has no lifecycle, so it does not meet the core definition of a work record. A note becomes part of the backbone only when an event promotes its substance into a lifecycle-bearing kind.

+
+

Identity layering

+
  • UUIDv7 is primary for bookkeeping, relations, dependencies, and history. It is stable across renames, migrations, and terminology transitions.
  • Canonical names are primary for humans and agents — UI, CLI, API output, prose, commits. UUIDs stay inspectable on demand but are never required reading.
  • The hub writes the UUID back into the source file at first index (pattern: today's state_hub_*_id fields).
  • A repeated source block with the same canonical id and the same UUID is one record with duplicate source occurrences. Index it once, retain every source location as diagnostic provenance, and report the duplication for governed cleanup. The repetition must not mint a second record. The same canonical id with different UUIDs is an identity collision and fails closed.
+
+

Abstract lifecycles (canon-fixed, minimal)

+
KindLifecycle
task`wait → todo → progress → done \cancel` (InfoTechCanon)
workplanproposed → ready → active → blocked → backlog → finished → archived
intake`open → vetted → routed → closed(promoted \declined \absorbed)`
decision`prepared → resolved(approved \rejected \revised \deferred)`
engagementqueued → prepared → done
register-entryper existing register conventions (unchanged)
+

Promotion is a first-class transition: intake.routed → workplan | task | decision | engagement, executed by one CLI/MCP call that writes both artefacts, sets promoted_to / origin back-links, and re-syncs. Manual transcription of an intake item into other kinds is a process defect.

+

Kind-specific rigor lives in flow profiles, not extra states: named task-flow-engine workstation definitions whose entry/exit assertions encode per-work-kind gates (e.g. compliance tasks cannot exit progress without an evidence link). Profiles are YAML in repos, in the same reconciliation loop.

+
+

Residuals (role, not kind)

+

A residual is work intentionally left after a workplan (or large task bundle) finishes. It is not a registered kind — inventing a parallel “residuals queue” or parking leftovers only in SCOPE.md / finished-file prose is a process defect. Residuals must be live work records so the hub can list them across repos and domains.

+
Residual shapeCapture asRequired links
Small Green/Blue follow-up, parkableintake (e.g. AWQ / *-IN-*)origin: residual, origin_ref: <parent-WP-id>
Multi-step / dependency-bearingworkplan (spawn next WP)body or frontmatter names parent WP; optional origin/origin_ref when promoted from intake
Needs founder choicedecisionsame origin fields when created from residual intake
Needs founder time windowengagementsame
Persistent gap / riskregister-entry (risk, tech debt, …)cross-link parent WP in notes
+

Close discipline for finished workplans:

+
  1. Short residual narrative may remain under the closing task (human context).
  2. Before status: finished, each residual that is still actionable becomes a live record (intake and/or child workplan). Prose alone is not the backlog.
  3. Progress milestone may name the handoff ids.
  4. statehub fix-consistency so the index (WORK-RECORDS.md, hub) reflects the handoff.
+

Fleet list (future CLI): filter open/vetted/routed intakes (and ready/ active workplans) with origin ∈ {residual, handoff} and optional topic_id / repo / origin_ref. Implementation home for that query is state-hub (statehub residuals / API), not domain repos.

+
+

Source files, index, and views

+
  1. Any repo file is a potential source of work records. Records are authored as schema-valid YAML blocks in context — thematic live documents, meeting notes, design docs, triage logs. The schema is the contract, not the location. Workplans keep dedicated files.
  2. A generated per-repo work-record index references every registered record in its source location (transclusion-style; markitect-family tooling). It is the "everything across all files" orientation view and a concurrency cross-check.
  3. Every working list is a generated view ([auto]-marked) over indexed records — autopilot views, decision queues, engagement batches, briefs. Hand-maintained completed-logs and queue tails are retired.
  4. Ordering of views is lane + priority + age by default; dependency capture optional; WSJF optional and situational (overload triage only).
+
+

Tags

+

Two classes, both in tags, never in status:

+
  • Derived tags — computed, read-only in files: stalled, stale, overdue-decision, budget-breached, orphaned.
  • Policy tags — assigned under tagging policies: automatable, needs-human, unclear-implementation, red-adjacent, compliance-relevant, residual (optional badge when origin is not used).
+

States migrate badly; tags migrate trivially. Anything situational is a tag.

+

Origin values (intake and promoted children): use stable strings, not free prose. For residuals prefer origin: residual (or handoff) and origin_ref: <parent canonical id> (e.g. BINKY-WP-0006). Other established origins (e.g. legacy-suggestion:<uuid>, mail-triage ids) remain valid.

+
+

Budgets

+

Guardrail envelopes resolve programmatically at run start: workplan-frontmatter override → per-repo budgets.yaml (lane/default envelopes) → global best-guess default. Nothing runs unbounded. Budget resolution and enforcement is automation infrastructure, never agentic effort; metering via token events; breach ⇒ budget-breached + escalation.

+
+

Reconciliation loop (normative)

+
author (any repo file) → CI schema validation → commit
+      → fix-consistency: register/sync, UUID write-back
+      → hub read model → generated index + views → NATS events → activity-core
+

The hub remains a read model per ADR-001; runtime operations data (logs, metrics, run histories, token events) is the sanctioned DB-only exception.

+
+

References

+
  • research/WorkOrchestrationArchitectureDraft.md (v0.2)
  • research/2026-07-19-work-orchestration-infrastructure-survey.md
  • research/2026-07-19-work-orchestration-best-practices.md
  • canon/architecture/adr-001-workplans-as-repo-artefacts.md, adr-005-…
  • canon/standards/workplan-terminology-fleet_v0.1.md
  • canon/standards/autonomy-lanes_v0.1.md
  • state-hub/docs/task-flow-engine-spec.md
+
canon-work-record-types · accepted-1 · acceptedthe-custodian · canon/standards/work-record-types_v0.1.md · 4b951be3947f9457cc091a7d359d9232646b4c93
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/canon-workplan-terminology-fleet.html b/docs/evidence/2026-10-11-served-publication-history/pages/canon-workplan-terminology-fleet.html new file mode 100644 index 0000000..3f13d54 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/canon-workplan-terminology-fleet.html @@ -0,0 +1,236 @@ + + + + +Workplan Terminology (Fleet) v0.1 + +
canon-workplan-terminology-fleet accepted · accepted-1 the-custodian reviewed 2026-08-31generated from canonical source — do not edit

Workplan Terminology (Fleet) v0.1

Source: the-custodian · canon/standards/workplan-terminology-fleet_v0.1.md · 4b951be3947f9457cc091a7d359d9232646b4c93

Review due: 2027-02-28

Purpose

+

Define workplan as the canonical fleet term for repo-backed deliverable work indexed by State Hub. Preserve explicit legacy bridges where APIs, events, generated fields, or historical documents still say workstream, until metered retirement criteria are met.

+

This standard complements:

+
  • ADR-001 — workplans originate as repo files; the hub indexes them.
  • STATE-WP-0054 — State Hub compatibility layer and legacy-meter.
  • STATE-WP-0069 — State Hub legacy interface retirement (child plan).
  • CUST-WP-0055 — fleet-wide coordination and prose migration.
+

Authoritative interface matrix for State Hub: state-hub/docs/workplan-terminology-transition.md.

+
+

Canonical term

+
ConceptFleet termMeaning
Repo-backed body of workworkplanMarkdown file under workplans/ per ADR-001
Workplan file id{PREFIX}-WP-NNNNe.g. CUST-WP-0055, STATE-WP-0069
Hub index rowworkplan (product term)DB entity keyed by UUID; may still use legacy column names internally
Task within a workplantaskid: {WP}-TNN in workplan file blocks
+
+

Work-record umbrella (v0.2 addendum, CUST-WP-0060)

+

The workplan and task definitions above are unchanged. Since v0.2 they are two kinds of the fleet umbrella term work record — any identified, lifecycle-bearing coordination artefact. The full kind registry (workplan, task, intake item, decision, engagement, register entry), the conventional spine, identity layering (UUIDv7 internal, canonical names external), and grandfathered legacy id schemes (AWQ-, DEC-, OH-) are normative in work-record-types_v0.1.md (+ machine registry work-record-types.yaml). Autonomy-lane vocabulary is normative in autonomy-lanes_v0.1.md. "Suggestion" is a legacy bridge name for the intake kind (state-hub suggestions table: read-only legacy pending close-out). This file keeps its _v0.1 name for reference stability; the frontmatter version field is authoritative.

+

Use workplan in human-facing prose: SCOPE.md, AGENTS.md, INTENT.md, README, workplan bodies, operator runbooks, and agent session guidance.

+
+

Legacy bridges (keep until retired)

+

Do not mass-rename these in the CUST-WP-0055 migration window:

+
SurfaceCanonicalLegacy bridgeRetirement owner
Workplan frontmatter link(implicit workplan UUID)state_hub_workstream_idSeparate bridge-field migration
REST/MCP parametersworkplan_idworkstream_idSTATE-WP-0069 + legacy-meter
REST routes/workplans//workstreams/STATE-WP-0069
MCP procedurescreate_workplan, etc.create_workstream, etc.STATE-WP-0069
State Hub completion eventorg.statehub.workplan.completedorg.statehub.workstream.completedSTATE-WP-0069 T05
activity-core catalog (custodian-era)org.statehub.workplan.completedorg.workstream.completedCUST-WP-0055 T03
Archived workplan proseworkplan (when editing)historical "workstream" textGrandfathered; header note only
Grandfathered filenames—paths containing workstreamADR-001 non-rename policy
+

Do not introduce a bare org.workplan.completed event subject. State Hub uses the org.statehub.* namespace.

+
+

Event subjects (normative)

+
SubjectStatusNotes
org.statehub.workplan.completedpreferredEmitted on workplan finish; subscribe here
org.statehub.workstream.completedlegacy (metered)Dual-published today; retire per legacy-meter
org.workstream.completedlegacy (catalog)activity-core custodian-era type; align to State Hub subject
+
+

Agent and documentation rules

+
  1. Write workplan in instructions, orientation steps, and error messages aimed at humans or coding agents.
  2. Mention workstream only in an explicit compatibility footnote, e.g.: > Legacy term: State Hub still exposes /workstreams and workstream_id > aliases until legacy-meter retires them (STATE-WP-0069).
  3. Leave unchanged in code or docs when the string is a wire key, route, legacy-meter registry entry, or state_hub_workstream_id frontmatter field.
  4. Prefer GET /workplans/ and workplan_id in new examples and scripts.
+

Regenerated agent files (state-hub/scripts/update_agent_instruction_files.py) should follow rules 1–2. Templates live under state-hub/scripts/project_rules/*.template.

+
+

Retirement rule (unchanged)

+

A legacy interface may be removed only when all are true:

+
  • registered in State Hub legacy-meter;
  • replacement reference verified;
  • no manual hold;
  • zero measured calls in the review window.
+

State Hub owns usage evidence; activity-core may run weekly review activities. See state-hub/docs/workplan-terminology-transition.md.

+
+

Verification

+

Fleet terminology drift is measured with:

+
python tools/scan_workstream_terminology.py
+python tools/scan_workstream_terminology.py --repo <slug> --json
+

Baseline artefact (2026-07-08): docs/evidence/workstream-terminology-baseline-20260708.json.

+

Exit criteria and scan exclusions: CUST-WP-0055 T08.

+
+

Out of scope (this standard)

+
  • Renaming state_hub_workstream_id in workplan frontmatter.
  • Database table or ORM model renames (completed in STATE-WP-0065).
  • Rewriting archived workplan bodies in bulk (CUST-WP-0055 T07).
+
+

References

+
  • canon/architecture/adr-001-workplans-as-repo-artefacts.md
  • state-hub/docs/workplan-terminology-transition.md
  • state-hub/workplans/STATE-WP-0054-workplan-terminology-transition-legacy-meter.md
  • state-hub/workplans/STATE-WP-0069-workplan-terminology-legacy-retirement.md
  • workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md
+
canon-workplan-terminology-fleet · accepted-1 · acceptedthe-custodian · canon/standards/workplan-terminology-fleet_v0.1.md · 4b951be3947f9457cc091a7d359d9232646b4c93
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/coulomb-estate-architecture.html b/docs/evidence/2026-10-11-served-publication-history/pages/coulomb-estate-architecture.html new file mode 100644 index 0000000..5f6b50a --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/coulomb-estate-architecture.html @@ -0,0 +1,274 @@ + + + + +Coulomb estate architecture + +
coulomb-estate-architecture proposed · draft-3 the-custodian reviewed 2026-08-31generated from canonical source — do not edit

Coulomb estate architecture

Source: the-custodian · canon/architecture/coulomb-estate_v0.1.md · d3c6f13d7aed9784b6f31c23d2b2777668493ba5

Review due: 2027-02-28

About this document

+

This is the first-wave estate map. It describes how the Coulomb / Custodian estate is put together: canons, hubs, rails, and publication. System-level arc42 documents (Railiance, NetKingdom, State Hub, Policy Nexus) live in their owning repos. Chapter 9 lists estate ADRs; it does not paste them.

+
+

01Introduction and Goals

+

the-custodian holds meaning, boundaries, and continuity for a local-first agent estate. Implementation lives in product repos. Coordination state is a read-model of repository files, not the origin of those files.

+

1.1 Requirements Overview

+
  • Work, decisions, and canon originate as files in the owning repo.
  • A publication surface keeps governing documents at permanent URLs.
  • Runtime (Rails, rApps, identity, storage) is consumed from platform packages, not reimplemented in the custodian.
  • Cross-repo work is coordinated, not owned, by a dedicated project repo when it does not belong to one product.
+

1.2 Quality Goals

+
  1. Rebuildability — State Hub can be reconstructed from registered repository files (ADR-001).
  2. Concept ownership — canons import, they do not redefine (ADR-006).
  3. Permanence of published policy addresses (policy-nexus ADR-0001).
  4. Honest currency — stale documents are visibly stale.
+

1.3 Stakeholders

+
RoleConcern
OperatorWhat must be discussed in person; ratification.
the-custodianCanon, values, constitution, estate ADRs.
Product reposImplementation and per-repo ADRs.
railiance-platformSubstrate: DNS, TLS, ingress, hosting.
policy-nexusPublication only.
info-tech-canonSemantic model, not this estate's building blocks.
+
+

02Architecture Constraints

+

N/A in this revision — local-first files, no second source of truth, stdlib-preferring tooling, single-node rail availability. To be written as first-wave complete.

+
+

03System Scope and Context

+

In: estate canon (constitution, standards, architecture ADRs), the federation of canons, the publication contract, the hub-as-read-model rule, and the map of first-wave systems.

+

Out: product implementation, InfoTechCanon's landscape model, regulatory intake (risk-nexus), and per-system deployment detail (those belong in the system arc42).

+

3.1 Business Context

+

The estate is a set of repositories that together run Coulomb products and the agent work-factory. Readers need one map of what governs what. Owners need a place that is not also the implementation.

+

3.2 Technical Context

+
NeighbourInterface
Owning git reposSource of workplans, ADRs, canon.
State HubDerived index / cache of those files.
policy.coulomb.socialGenerated publication of canon and ADRs.
Railiance reefRuntime for hubs, rApps, Forgejo.
NetKingdomIdentity, tenancy, IAM profile.
info-tech-canonImported semantics, not estate structure.
+
+

04Solution Strategy

+
  • Files first. Workplans and ADRs are markdown in git. The hub rebuilds from them (ADR-001, ADR-003).
  • One registrar. Workplan identifiers are globally unique; this workstation is not the registrar (ADR-007).
  • Three canons, federated. Custodian (governance), InfoTechCanon (information-system semantics), CommerceCanon (counterparty semantics). They import, they do not redefine (ADR-006).
  • Publish, do not author. policy-nexus reads owning repos and emits static addresses. It never writes back.
  • Project repos for cross-repo work (prj-*), not an unbound hub workplan (ADR-005).
+
+

05Building Block View

+

5.1 Level 1 – System/Top-Level

+
                    ┌─────────────────────────┐
+                    │     the-custodian       │
+                    │  constitution, values,  │
+                    │  estate ADRs, memory    │
+                    └────────────┬────────────┘
+           ┌─────────────────────┼─────────────────────┐
+           ▼                     ▼                     ▼
+   ┌───────────────┐    ┌────────────────┐    ┌─────────────────┐
+   │ info-tech-    │    │ commerce-canon │    │ net-kingdom     │
+   │ canon         │    │                │    │ (identity /     │
+   │ (semantics)   │    │                │    │  tenancy)       │
+   └───────────────┘    └────────────────┘    └────────┬────────┘
+                                                       │
+   ┌───────────────┐    ┌────────────────┐             │
+   │ state-hub     │◄───│ product repos  │◄────────────┘
+   │ (read model)  │    │ + project repos│
+   └───────────────┘    └────────┬───────┘
+                                 │
+                    ┌────────────┴────────────┐
+                    ▼                         ▼
+           ┌────────────────┐        ┌─────────────────┐
+           │ railiance      │        │ policy-nexus    │
+           │ (reef, rApps)  │        │ (publication)   │
+           └────────────────┘        └─────────────────┘
+

5.2 Level 2 – Key Components

+

N/A in this revision.

+

5.3 Level 3 – Internal Structure (as needed)

+

N/A in this revision.

+
+

06Runtime View

+

N/A — estate coordination is file sync plus hub rebuild, not a single runtime scenario. System runtimes belong in their own arc42.

+
+

07Deployment View

+

N/A — Railiance owns where things run. This document names the substrate; it does not map nodes.

+
+

08Cross-Cutting Concepts

+

N/A in this revision. Concept ownership and the hub/cache split are already in §4 and the ADRs in §9.

+
+

09Architecture Decisions

+

Estate ADRs in the-custodian/canon/architecture/. Status is the source front-matter.

+
IdStatusDecision
CUST-ADR-001acceptedFile-backed work originates in repositories; the Hub projects it and separately owns declared Hub-native records.
CUST-ADR-002acceptedCustodian agent runtime design.
CUST-ADR-003acceptedHub state is derived and fingerprint-invalidated.
CUST-ADR-004acceptedConnectivity-first network posture.
CUST-ADR-005acceptedCross-repo work lives in a prj-* repo.
CUST-ADR-006acceptedThree canons; import, do not redefine.
CUST-ADR-007acceptedNamespace-aware work-record identity, deterministic Hub ids, and repository worker topology.
adr-008supersededRelocated to NetKingdom Tenancy Posture.
CUST-ADR-010proposedTwo kinds of hub data; local cache vs authority.
CUST-ADR-011proposedFederated namespaces and reconciliation limits.
CUST-ADR-012acceptedForge is the projection source; unpushed working-copy state is preliminary.
+

Related and published elsewhere: policy-nexus ADR-0001; Tenancy Posture and IAM Profile v0.3 plus the current NetKingdom security standards; NetKingdom ADR-0006–0008 and 0010–0015; railiance-master ADR-0001–0009; activity-core ACT-ADR-001–007; ops-warden ADR-0001–0005 and 0007–0010; railiance-platform RPLAT-ADR-0001–0003.

+

Unresolved WP-0003 conflicts (hosts/infra duplicate ADR-003/004, coulomb-social ADR-0002 partial supersession) are not listed as current.

+
+

10Quality Requirements

+

N/A in this revision — rebuildability and currency already have mechanical checks (fix-consistency, make currency).

+
+

11Risks and Technical Debt

+

N/A in this revision. Known residuals are the Forge-derived reset and preliminary-overlay implementation under ADR-012. Markitect arc42-v1 is not registered yet.

+
+

12Glossary

+
TermMeaning
EstateThe set of Coulomb / Custodian repos, canons, hubs, and rails.
CanonGoverning documents owned by one of the three federated canons.
Read modelA derived index. Never the origin of work or decisions.
Publication entryOne explicit object in policy-nexus publication.json.
First-wave completeChapters 1, 3, 4, 5.1, 9 and 12 are real; others real or N/A.
Project repoA prj-* repo that coordinates cross-repo work (ADR-005).
+
coulomb-estate-architecture · draft-3 · proposedthe-custodian · canon/architecture/coulomb-estate_v0.1.md · d3c6f13d7aed9784b6f31c23d2b2777668493ba5
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/net-kingdom-architecture.html b/docs/evidence/2026-10-11-served-publication-history/pages/net-kingdom-architecture.html new file mode 100644 index 0000000..97ff62c --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/net-kingdom-architecture.html @@ -0,0 +1,244 @@ + + + + +NetKingdom architecture + +
net-kingdom-architecture proposed · draft-3 net-kingdom reviewed 2026-08-31generated from canonical source — do not edit

NetKingdom architecture

Source: net-kingdom · docs/architecture/net-kingdom_v0.1.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3

Review due: 2027-02-28

About this document

+

First-wave arc42 for NetKingdom: the estate's identity and tenancy security core. Chapter 9 lists governing ADRs and standards; it does not paste them.

+
+

01Introduction and Goals

+

NetKingdom is the open security core for DevSecOps on Kubernetes. It owns identity, tenancy posture, and the contracts that flex-auth, key-cape, tenant-engine, and railiance workloads implement.

+

1.1 Requirements Overview

+
  • One IAM profile, versioned, owned here.
  • Tenancy described as graduated axes, not a single on/off switch.
  • Workload packaging and credential vending have explicit boundaries.
+

1.2 Quality Goals

+
  1. Provider-neutral identity contract.
  2. Recursive multi-tenant authorization that implementers can declare.
  3. Honest about what is not there yet (Tenancy Posture).
+

1.3 Stakeholders

+
RoleConcern
net-kingdomCanon owner for identity and tenancy.
flex-auth / key-cape / tenant-engineImplementers of the contracts.
railiance-masterWorkload packaging on the rail.
the-custodianFederation; does not redefine these concepts.
+
+

02Architecture Constraints

+

N/A for this stub.

+
+

03System Scope and Context

+

In: IAM profile, tenancy posture, tenant/user-engine boundaries, credential management, playbook capability contract, NetKingdom ADRs. Out: publication (policy-nexus), rail runtime (railiance), estate work-factory (the-custodian).

+

3.1 Business Context

+

Security here is dynamic and adversarial. The system exists so implementers share one contract instead of copying a neighbour.

+

3.2 Technical Context

+

Consumers: flex-auth, key-cape, tenant-engine, audit-core, rApps. Published today: Tenancy Posture /standards/tenancy-posture/v0.1/.

+
+

04Solution Strategy

+

N/A for this stub — recursive multi-tenant identity (ADR-0006) and the IAM profile ownership rule (ADR-0011) are the spine.

+
+

05Building Block View

+

5.1 Level 1 – System/Top-Level

+

N/A for this stub.

+
+

06Runtime View

+

N/A for this stub.

+
+

07Deployment View

+

N/A for this stub.

+
+

08Cross-Cutting Concepts

+

N/A for this stub.

+
+

09Architecture Decisions

+
Publication idStatusDecision or standard
netkingdom-tenancy-postureproposedGraduated multi-tenancy posture.
netkingdom-iam-profile-v0.3acceptedCurrent provider-neutral identity contract.
netkingdom-posture-feedback-v0.1proposedGoverned posture feedback.
netkingdom-security-layer-model-v0.7acceptedCurrent security-layer interaction model.
netkingdom-security-scenario-composition-v0.1proposedSecurity scenario composition.
netkingdom-security-zones-v0.1proposedSecurity-zone vocabulary and boundaries.
NK-ADR-0006acceptedRecursive multi-tenant identity and authorization.
NK-ADR-0007acceptedSecurity orchestration boundary.
NK-ADR-0008acceptedObject-storage STS credential vending.
NK-ADR-0010acceptedOrchestration, dependency, and self-coherent intent.
NK-ADR-0011acceptedIAM Profile ownership and version governance.
NK-ADR-0012acceptedPlaybook capability-contract ownership.
NK-ADR-0013acceptedTenant onboarding grouping taxonomy.
NK-ADR-0014acceptedTenant capability roles and tenant-engine ownership.
NK-ADR-0015acceptedRailiance workload packaging and relational platform.
+

Custodian ADR-008 is superseded by Tenancy Posture and is not current.

+
+

10Quality Requirements

+

N/A for this stub.

+
+

11Risks and Technical Debt

+

N/A for this stub. The IAM Profile publication id is now globally qualified.

+
+

12Glossary

+
TermMeaning
IAM ProfileProvider-neutral OIDC contract owned here.
Tenancy PostureGraduated axes for describing multi-tenancy.
Tenant-engineLifecycle and capability roles for tenants.
+
net-kingdom-architecture · draft-3 · proposednet-kingdom · docs/architecture/net-kingdom_v0.1.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-iam-profile-v0.3.html b/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-iam-profile-v0.3.html new file mode 100644 index 0000000..bdbc92c --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-iam-profile-v0.3.html @@ -0,0 +1,319 @@ + + + + +NetKingdom IAM Profile v0.3 + +
netkingdom-iam-profile-v0.3 accepted · accepted-1 net-kingdom reviewed 2026-08-22generated from canonical source — do not edit

NetKingdom IAM Profile v0.3

Source: net-kingdom · canon/standards/iam-profile_v0.3.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3

Review due: 2027-02-22

Minor version. Per ADR-0011's versioning rule, this adds an optional claim and clarifies non-normative guidance — no required claim, validation rule, or previously-issued token is invalidated. Existing v0.2 implementations remain conformant; tenant_roles and the revised Tenant Claim guidance are additive.

+

Purpose

+

The NetKingdom IAM Profile is the provider-neutral OIDC contract that identity implementations issue and applications consume.

+

It defines:

+
  • OIDC discovery and endpoint requirements;
  • Authorization Code + PKCE for human login;
  • service-account and workload identity token requirements;
  • human, service, and agent principal representation;
  • tenant, tenant-grouping, and platform-boundary claims;
  • tenant capability roles and their carrying mechanism;
  • explicit assurance evidence;
  • the identity-to-authorization claim contract consumed by flex-auth;
  • local-development and emergency-access behavior;
  • executable conformance expectations.
+

Applications target this profile, not a concrete identity provider. key-cape is the lightweight implementation. Keycloak is the expanded-mode implementation. Both are interchangeable at the application and authorization boundary when they conform to this document.

+
+

Ownership

+

NetKingdom owns the core/platform profile. See ADR-0011.

+

Downstream systems may define extension scopes, roles, resource names, and tenant policy vocabularies. Those extensions are not part of the core profile unless a future version explicitly adopts them. Extension vocabularies must map back to the core claims in this document before flex-auth or applications consume them.

+
+

Design Principles

+
  • Consumers trust signed OIDC tokens, not provider-specific sessions.
  • Identity providers assert identity and authentication evidence; they do not make final resource authorization decisions.
  • The same profile works in lightweight key-cape mode and expanded Keycloak mode.
  • Tenancy is explicit. tenant:platform is distinct from tenant planes such as tenant:coulomb and from later tenants grouped per ADR-0013.
  • A tenant's onboarding grouping (ADR-0013) and its capability roles (ADR-0014) are independent axes. Neither is encoded in the other; a tenant's roles may change without renaming its identifier.
  • Human, service, and agent principals are distinguishable.
  • Assurance evidence is explicit enough for flex-auth policy.
  • Local-development issuers are useful but never accepted by production.
  • Emergency access is auditable, time-bounded, and reviewable.
+
+

Discovery Contract

+

Every IAM Profile implementation MUST expose OIDC discovery at:

+
GET <issuer>/.well-known/openid-configuration
+

The discovery response MUST include:

+
FieldRequirement
issuerExact issuer identifier used in tokens
authorization_endpointRequired for human Authorization Code + PKCE
token_endpointRequired for token exchange and service accounts
jwks_uriRequired for signature validation
userinfo_endpointRequired when userinfo is supported by the flow
scopes_supportedMUST include openid; SHOULD include profile and email
response_types_supportedMUST include code
grant_types_supportedMUST include authorization_code; MUST include client_credentials or a documented workload-token exchange for service identities
id_token_signing_alg_values_supportedMUST include the implementation signing algorithm; RS256 is required for v0.2+ conformance
code_challenge_methods_supportedMUST include S256
+

The response SHOULD include end_session_endpoint where logout is supported and claims_supported listing the core claims below.

+

Consumers MUST discover endpoints and key material from the issuer metadata instead of hardcoding provider-specific paths.

+
+

Required Flows

+

Human Interactive Flow

+

Human users authenticate with Authorization Code + PKCE.

+

Required properties:

+
  • PKCE with S256 is mandatory for browser and CLI clients.
  • Implicit flow is not part of the profile.
  • MFA or equivalent strong assurance is mandatory for privileged, destructive, platform-root, and emergency access in production.
  • Access tokens are short-lived.
  • Refresh tokens are allowed only for trusted clients with explicit rotation and revocation.
+

Service Account Flow

+

Service-to-service traffic uses client credentials or a deployment's documented workload identity token-exchange equivalent.

+

Required properties:

+
  • Service subjects are stable and named for service plus environment.
  • Secrets or workload credentials are delivered through the credential-management standard, not plaintext configuration.
  • Tokens include an audience that identifies the target service.
  • Tokens carry principal_type: service.
  • Service accounts receive only required scopes and roles.
  • Credentials are rotated and never shared between environments.
+

Agent Principal Flow

+

Agents are automation principals that may act autonomously or under delegated authority.

+

Required properties:

+
  • Tokens carry principal_type: agent.
  • Tokens include an agent object with id and mode.
  • agent.mode is autonomous or delegated.
  • Delegated agents MUST identify the delegating actor using actor_sub or an equivalent act.sub claim.
  • Agent tokens MUST carry the tenant they operate within.
  • Agent tokens MUST include assurance evidence for both the agent credential and any delegated human authority when policy needs it.
+
+

Core Claims

+

Access tokens accepted by production consumers MUST provide the following claims after provider mapping or normalization:

+
ClaimTypeMeaning
issstringOIDC issuer URL or issuer identifier
substringStable subject identifier unique within iss
audstring or arrayIntended audience; MUST include the receiving service
expnumberExpiry timestamp
iatnumberIssued-at timestamp
nbfnumberNot-before timestamp, recommended for production tokens
jtistringToken identifier, recommended for audit and replay controls
tenantstringTenant identifier such as tenant:platform or tenant:friendly:binky
principal_typestringhuman, service, or agent
groupsarrayGroup memberships, possibly empty
rolesarrayCoarse identity roles for the subject, possibly empty
scope or scpstring or arrayGranted OAuth scopes
assuranceobjectAuthentication and credential assurance evidence
+

Recommended human claims:

+
ClaimMeaning
preferred_usernameHuman-readable username
emailContact identity
nameDisplay name
+

Recommended service claims:

+
ClaimMeaning
azp or client_idAuthorized client/service identifier
serviceObject naming the service and environment
+

Recommended agent claims:

+
ClaimMeaning
agent.idStable agent identifier
agent.modeautonomous or delegated
actor_sub or act.subDelegating subject for delegated agents
+

Optional claims (new in v0.3):

+
ClaimTypeMeaning
tenant_rolesarrayCached tenant capability roles (PLTF/IAM/VEN/CUS), possibly empty. See "Tenant Roles" below — this is a point-in-time cache, not the authoritative source.
+

Role Claim

+

The canonical subject-level role claim is roles, an array of strings. This is distinct from tenant_roles (below) — a subject's own coarse identity roles are not the same fact as which capability roles the subject's tenant holds.

+

Expanded-mode Keycloak deployments may also expose provider-native roles such as realm_access.roles, but conforming tokens consumed by flex-auth or applications MUST either emit roles directly or pass through a normalizing adapter that produces roles.

+

Scope Vocabulary

+

The core profile defines only OAuth/OIDC base scopes:

+
ScopeMeaning
openidRequired for OIDC login
profileBasic profile claims
emailEmail claim where appropriate
offline_accessRefresh-token capable access where explicitly allowed
+

Hub-, application-, and resource-specific scopes such as hub:*, ops:*, fin:*, or storage actions are downstream extensions. They are valid only when the consuming system defines them and maps them to flex-auth resource/action semantics.

+
+

Tenant Claim

+

tenant is required for every token accepted by profile consumers.

+

Tenant identifiers follow tenant:<grouping>:<name>, where <grouping> is one of the taxonomy ratified by ADR-0013 at identifier creation:

+
trial        - test/trial/showcase tenants only
+friendly     - known, easily reached, tolerant of experimentation/instability
+single       - one-person business entities (freelance consultants)
+small        - up to 10 employees at time of onboarding (attoo)
+medium       - up to 100 employees (attoo)
+large        - up to 1000 employees (attoo)
+enterprise   - 1001+ employees (attoo)
+consumer     - private individuals
+family       - a legal family
+community    - a non-legal group of people
+association  - a legal association of people
+agentic      - financially enabled AI entities
+

tenant:platform and tenant:coulomb remain reserved, ungrouped identifiers outside this taxonomy: tenant:platform is the platform control-plane tenant, not a business entity being onboarded; tenant:coulomb is the first internal/reference tenant established by ADR-0006, predating this taxonomy. Tenant administration for tenant:coulomb or any grouped tenant must never imply platform-root authority.

+

Subjects may have access to multiple tenants, but a token used for a request MUST identify the tenant context for that request. If a client needs to switch tenant context, it obtains a new token or uses an approved token-exchange flow that records the target tenant.

+

The grouping segment is an immutable record of the tenant's onboarding-time onboarding-risk / entity-shape classification. It remains vocabulary-valid but becomes historical if the tenant's classification later changes. The authoritative current grouping is the grouping field held by tenant-engine; consumers MUST NOT split tenant and treat its middle segment as current policy input. Changing current grouping never renames the tenant. Neither the historical segment nor current grouping gates which capability roles (below) a tenant may hold — see Tenant Roles.

+
+

Tenant Roles

+

Tenant capability roles are a separate fact from the grouping above and from the subject-level roles claim: PLTF, IAM, VEN, CUS (ratified by ADR-0014), non-exclusive — a tenant may hold several simultaneously, and holding one does not require or restrict any grouping.

+

Source of truth: tenant-engine (canon/standards/tenant-engine-boundary-contract_v0.1.md), not this profile and not any token. tenant-engine records role grants/revocations, their link (if any) to a plan/subscription, and emits domain events on change.

+

Carrying mechanism — hybrid, not claim-only:

+
  • key-cape (or Keycloak) MAY stamp a cached tenant_roles claim onto an issued token at issuance time, sourced from tenant-engine.
  • Consumers MAY trust the cached claim for ordinary, non-privileged decisions.
  • Consumers MUST NOT trust the cached claim for privileged, destructive, platform-root, secret, credential-vending, or otherwise assurance.level >= aal2-class decisions. Those decisions MUST query tenant-engine live for current role state before authorizing the action.
  • This bounds staleness for ordinary actions to the issuing token's lifetime (5-30 minutes for service/agent tokens, see Token Lifecycle) while guaranteeing freshness exactly where a stale grant (e.g. VEN surviving a plan cancellation) would matter most.
+

trial-grouped tenants may hold any capability role without restriction — the grouping exists to showcase, test, and explore every role. Safety for trial tenants is enforced through tenant-engine-owned resource guardrails (spend limits, entity/action counts — reserved, not yet specified), not through role gating.

+
+

Assurance Evidence

+

The canonical assurance claim is assurance.

+

It is an object with these fields:

+
FieldTypeMeaning
levelstringaal0, aal1, aal2, aal3, or break_glass
methodsarrayAuthentication methods, e.g. pwd, otp, webauthn, client_secret, workload_identity, upstream_mfa
mfabooleanWhether the authentication included multiple factors or equivalent upstream evidence
sourcestringProvider of the evidence, e.g. key-cape, keycloak, privacyidea, entra, local-identity
atnumberAuthentication time, recommended
+

Level meanings:

+
LevelMeaning
aal0Local/dev or unauthenticated bootstrap evidence; never production privileged
aal1Single-factor or service credential evidence
aal2MFA or equivalent strong upstream assurance
aal3Phishing-resistant or hardware-backed assurance
break_glassTime-bounded emergency access with post-event review
+

Privileged, destructive, platform-root, secret, credential-vending, and emergency flows require aal2 or stronger unless a policy explicitly permits a narrower service or workload identity path. Emergency access MUST use break_glass and short token lifetimes. This is the same threshold class that gates when tenant_roles MUST be re-validated live against tenant-engine rather than trusted from the token — see Tenant Roles.

+

Provider-native claims such as acr and amr may be present, but consumers use assurance as the normalized profile claim.

+
+

Identity To Authorization Contract

+

flex-auth consumes IAM Profile tokens as normative identity input. flex-auth MUST NOT re-derive identity, tenant, group, role, or assurance facts from provider-specific session state.

+

The profile guarantees these inputs for authorization decision envelopes:

+
Decision inputSource claim
Subjectsub
Issueriss
Audienceaud
Tenanttenant
Principal typeprincipal_type
Groupsgroups
Subject rolesroles
Tenant capability rolestenant_roles (cached); tenant-engine live lookup required for high-stakes decisions
Scopesscope or scp
Assuranceassurance
Authorized clientazp or client_id, where present
Agent/delegation contextagent, actor_sub, or act, where present
Token lifetime/audit idsiat, nbf, exp, jti, where present
+

Authorization decisions are made by flex-auth and its delegated PDP adapters. Identity providers may assert roles or scopes, but those claims are inputs to policy, not final permission to act on a resource.

+
+

Token Lifecycle

+

Recommended production defaults:

+
TokenLifetimeNotes
Human access token5-15 minutesShort-lived bearer token
Refresh token8-12 hoursRotated and revoked on logout or suspicion
Service token5-30 minutesReissued by client credentials or workload identity
Agent token5-30 minutesShorter when delegated or platform-scoped
Emergency token5-15 minutesRequires incident/review record
+

Consumers MUST reject expired tokens and tokens with invalid issuer, audience, signature, nbf, or algorithm. Clock skew tolerance SHOULD be small, normally no more than 60 seconds.

+

JWKS material may be cached, but consumers MUST tolerate key rotation by refreshing JWKS when a token uses an unknown kid.

+
+

Local Development Profile

+

A local file-backed provider may be used for development, tests, and bootstrap contexts where the full platform is unavailable.

+

It MUST:

+
  • expose OIDC discovery;
  • issue signed JWTs;
  • support deterministic test users and service accounts;
  • use local-only issuer URLs or a clearly local issuer identifier;
  • mark tokens as local/development through issuer, audience, or assurance evidence;
  • be rejected by production consumers.
+

Production consumers MUST reject:

+
  • issuer local-identity;
  • http:// issuers;
  • loopback issuers such as localhost or 127.0.0.1;
  • tokens with assurance.level: aal0;
  • tokens where the environment marks the issuer as local/dev.
+
+

Emergency And Break-Glass Access

+

Emergency access is allowed only as a break-glass path.

+

Requirements:

+
  • Emergency identities are disabled by default.
  • Activation requires an incident, decision, or human-recorded review reference.
  • Tokens are short-lived and carry the emergency role.
  • Tokens carry assurance.level: break_glass.
  • Every emergency action emits an audit/progress/incident event.
  • Emergency access is reviewed after use and then disabled again.
+

Emergency access MUST NOT bypass audit logging or flex-auth policy.

+
+

Conformance

+

An implementation conforms to IAM Profile v0.3 when it passes the executable conformance suite in:

+
tools/iam-profile-conformance/
+

The suite validates:

+
  • discovery document completeness;
  • PKCE S256 advertisement and rejection of authorization requests that omit a code challenge;
  • JWKS structure and key ids;
  • token issuer, audience, expiry, nbf, iat, and RS256 signature;
  • tenant, principal type, groups, roles, scopes, and assurance claim shape;
  • tenant_roles claim shape when present (array of ratified role strings);
  • agent and delegated-agent claim shape;
  • local-development issuer rejection in production mode.
+

Conformance must be runnable against both key-cape lightweight issuers and Keycloak expanded-mode issuers. Implementations may add provider adapters, but the token consumed by applications and flex-auth must match the core claim contract above. tenant_roles conformance does not require an implementation to emit the claim (it is optional); when emitted, it must match the ratified vocabulary.

+
+

Validation Checklist

+

A service or implementation is profile-ready when:

+
  • it reads OIDC discovery rather than hardcoding endpoints;
  • it validates issuer, audience, expiry, nbf, algorithm, and signature;
  • it refreshes JWKS on unknown kid;
  • it supports Authorization Code + PKCE for human login;
  • it supports service-account or workload identity tokens;
  • it emits tenant, principal_type, groups, roles, scope/scp, and assurance;
  • it uses the ADR-0013 grouping vocabulary for new tenant identifiers;
  • if it consumes tenant_roles, it treats the claim as a cache and re-validates live against tenant-engine before any aal2-class decision;
  • it maps provider-native claims into the canonical core claims;
  • it rejects local-development issuers in production;
  • it logs emergency access with a durable audit trail;
  • flex-auth receives identity facts from the profile, not from provider-specific sessions.
+
netkingdom-iam-profile-v0.3 · accepted-1 · acceptednet-kingdom · canon/standards/iam-profile_v0.3.md · d4e57e63126d2cca1d381c025170e4b1f678c3f3
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-posture-feedback-v0.1.html b/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-posture-feedback-v0.1.html new file mode 100644 index 0000000..7b9ff91 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-posture-feedback-v0.1.html @@ -0,0 +1,224 @@ + + + + +NetKingdom Posture Feedback v0.1 + +
netkingdom-posture-feedback-v0.1 proposed net-kingdom reviewed 2026-08-23generated from canonical source — do not edit

NetKingdom Posture Feedback v0.1

Source: net-kingdom · canon/standards/posture-feedback_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2026-11-23

01Purpose

+

This contract is the first bounded C6 feedback mechanism. It turns explicit posture review dates, evidence freshness, implemented-but-unevidenced controls, and declared gaps into deterministic remediation proposals.

+

It does not modify a posture level, policy, declaration, workplan, State Hub, or runtime. Human or separately governed automation decides whether a proposal becomes work.

+
+

02Deterministic time

+

Evaluation requires an explicit RFC 3339 as_of timestamp. Wall-clock time is never read implicitly. A date-only review_due remains current through that calendar date in UTC and becomes overdue on the following UTC date.

+

A non-negative horizon in days identifies items due soon. Changing the horizon changes the report digest and is therefore visible.

+
+

03Owner resolution

+

The evaluator routes only from authoritative declaration fields:

+
  • posture review, gaps, and implemented controls: responsible_repo;
  • evidence replacement: evidence_freshness.<level>.responsible_repo;
  • security-zone review: zones.responsible_party.
+

If the relevant field is absent, owner resolution is unknown. The evaluator must not infer ownership from the service name, repository path, Git remote, previous work, or another policy subject.

+
+

04Finding classes

+
FindingTriggerSeverity
posture-review-overdueas_of is after tenancy.review_duehigh
posture-review-due-soonreview is within the horizonmedium
zone-review-overdueas_of is after zones.review_duehigh
zone-review-due-soonzone review is within the horizonmedium
evidence-freshness-unknowna current adversarial level has no complete freshness entryhigh
evidence-expiredas_of is after valid_untilhigh
evidence-due-soonevidence expires within the horizonmedium
implemented-not-evidencedan implemented level is above currentmedium
declared-gapa non-empty tenancy.gap entry existslow
+

The review horizon does not generate a due-soon finding for an item already overdue or expired. Exact equality with a timestamp is still valid; expiry is strictly as_of > valid_until.

+

Current adversarial levels are E2, R4, and V2–V4. This vocabulary comes from Tenancy Posture §13. Mechanical evidence is evaluated for expiry only when its declaration explicitly supplies valid_until.

+
+

05Proposal and safety boundary

+

Every finding receives a stable id derived from its source declaration, service, finding class, control, and due value. It contains the authoritative owner or unknown, current evidence state, reason, and recommended action. For declarations under the workspace containing this repository, the source is normalized to <repo>/<path> so absolute checkout locations do not perturb the identity. This source normalization identifies an input only; it is never an ownership inference.

+

Every report declares:

+
automation:
+  mode: proposal-only
+  external_write_permitted: false
+  policy_mutation_permitted: false
+  declaration_mutation_permitted: false
+

Expired or unknown evidence does not silently inherit freshness and does not silently downgrade a level. It makes the uncertainty visible for governed review. Consumers that use the report as an admission gate may fail closed on high findings, but that is a separate owner decision.

+
+

06Exit behavior

+

The CLI emits a report conforming to posture-feedback-report_v0.1.schema.json. --fail-on high exits non-zero when at least one high-severity finding exists; medium includes medium and high; low includes every finding; none reports without a finding-based failure. Invalid declarations always exit non-zero.

+
netkingdom-posture-feedback-v0.1 · · proposednet-kingdom · canon/standards/posture-feedback_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-security-layer-model-v0.7.html b/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-security-layer-model-v0.7.html new file mode 100644 index 0000000..eadc612 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-security-layer-model-v0.7.html @@ -0,0 +1,461 @@ + + + + +NetKingdom Security Layer Model v0.7 + +
netkingdom-security-layer-model-v0.7 accepted gate-house reviewed 2026-08-28generated from canonical source — do not edit

NetKingdom Security Layer Model v0.7

Source: net-kingdom · canon/standards/security-layer-model_v0.7.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2026-11-28

01Purpose

+

This standard states how NetKingdom's IT-security estate is layered, and what each layer may and may not do. It answers one question:

+

Given a repository, which layer is it in, and what does that permit it to own?

+

The layers are distinguished by determinism and by the kind of artifact the layer produces, not by technical tier, deployment topology, or team.

+

It is not an org chart, not a network model, not a deployment topology, and not a dependency graph. It does not assign work, and it does not replace any repository's boundary contract; it constrains what such a contract may claim.

+

What changed in v0.7. v0.6 announced a rule it never wrote: §1 and §15 said the standard separates human and agent principals inside Staff, and §3.4 was byte-identical to v0.5. kings-guard found it and put it correctly — a rule stated about a standard in its own change log is not a rule, which is §11's own principle turned on the standard. §3.4 is now written.

+

The rest are collisions between rules written for the clean case: §6.4's first obligation forbade what its third obligation blesses, and its second forbade the session-bound allow §9.7.1 permits. §9.4's atomicity was described as closing a threat it does not close. §19 graded the document it lived in. And §20 records the interaction boundary with Railiance operations, on definitions from railiance-master rather than inference. §15 records the change list.

+

What changed in v0.6. An independent assessment against industry practice (net-kingdom/history/2026-08-29-layering-standard-assessment.md) found the model sound as a layering constitution and incomplete as a self-healing one: cognition, authority, and execution are specified, but the two verbs that close a healing loop — observe in production and actuate through a deterministic surface — are pending, and one is unstaffed. It also found the Engine layer untyped, so that "we need an engine for X" drifts toward "X now decides", and the enforcement point unnamed.

+

v0.6 types the engines (§3.3), names the enforcement point (§6.4), replaces the containment assignment with an actuation surface held at zero (§9.2), separates human and agent principals inside Staff (§3.4), puts time into the model (§9.7), requires the Taxonomy artifacts that make §6.2 compileable rather than reviewable (§17), and composes the sibling standards it had only cited (§18). Section numbers below §14 are unchanged: the estate cites them.

+

What changed in v0.5. All four reviewing repositories returned findings on v0.4, and one contested a rule. §9.3 was wrong: it collapsed engine reachable but degraded with engine not reachable at all, and the second case has no evaluator in the path to express anything. §9.1 collapsed no route exists with route exists under a declared gap, which would have forced a false "pending" onto a production capability. §11 claimed mechanical checkability for a rule that cannot be checked in prose. §13 filed two opposite conformance states in one table and recorded proposed owners as owners. §9.6 needed the load-bearing distinction it implied but never drew. §15 records the change list.

+

What changed in v0.4. audit-core assented to the approval evidence half and corrected the rationale twice. v0.3 rested §9.4 on that repository's INTENT principle 6, which is an aspiration; the shipped bound in its docs/integrity.md is weaker and conditional. More consequentially, no append-only archive can prove omission at source — a suppressed revocation leaves the chain intact — which is now stated as an estate-wide doctrine constraint (§9.6) rather than left implicit. audit-core was also referenced as an owner in v0.3 without appearing in the §4 catalog at all; it is catalogued here, as an Engine, on its own declaration. §15 records the change list.

+

What changed in v0.3. Two engines were seeded to own concepts v0.2 recorded as unowned: approval-engine takes the approval object that §13 left homeless, and maturity-engine takes graded progression — closing a §9.1 defect in gate-house's own catalog claim, which asserted conformance review with no engine to act through. §15 records the change list. v0.3 is proposed: the two new engines are seeded by owner direction and have no other side to assent yet, and the evidence half of the approval split needs audit-core's assent.

+

What changed in v0.2. v0.1 was assented to by all three repositories whose boundaries moved, and each returned a finding. v0.1 had one lane for a Staff repository that legitimately touches Tooling — read-only diagnostics — which is narrower than the estate as it actually stands, and a rule with no lane for a real sanctioned case is satisfied by relabelling rather than by closing the gap. v0.1 also catalogued a capability (§4, containment) that §5 forbade discharging, and applied its reconstructability test to engines but not to the doctrine gate-house feeds them. §15 records the full change list.

+
+

02Authority and conformance

+
Fact or ruleAuthority
The layers, their definitions, and the rules between themThis standard, owned by gate-house
Which layer a given repository is inThis standard, §4 catalog
What a repository owns within its layerThat repository's INTENT.md and boundary contract
Whether a specific request is permittedaccess-engine — never this standard
Whether a Tooling contact is sanctionedThe declaring repository, under the shapes in §5, reviewable by gate-house
Security doctrine and invariantsgate-house
Publicationnet-kingdom canon
Whether an invariant is watched in practicethe observing repository's own report — never this standard, and never §12's diagram
+

A repository conforms when its INTENT.md declares its layer, its claims fall within that layer's permissions (§3), and its Tooling contacts take one of the sanctioned shapes in §5 or are declared as gaps under §5.3.

+

No estate argument may cite observation that has not happened. §12 lists kings-guard against the loop's fourth step, and that repository has reported that it has never observed a real event. Until it reports otherwise, no assessment, review, or decision in this estate may treat an invariant as being watched in practice on the strength of the diagram. Lifted here from §12 so it cannot be lost in a summary.

+
+

03The layers

+
LayerCharacterProducesDeterministic
Taxonomycross-cutting languageterms, semantic contracts, standardsn/a — describes
Toolinginfrastructure and statedata structures, persistenceyes
Enginesinterfaces for a modeled conceptAPIs, contractsyes
Staffmanagement, operations, change, controllingspecifications, decisions, workplans, tasksno
+

3.1 Taxonomy

+

Cross-cutting language. Taxonomy repositories define terms and semantic contracts so the other layers interoperate without integration by interpretation. They own no runtime position and no state any layer depends on.

+

info-tech-canon holds ecosystem-wide semantic contracts. NetKingdom-specific security architecture — including this standard — is net-kingdom canon's.

+

3.2 Tooling

+

Deterministic infrastructure: data structures, persistence, and the consistent, performant, scalable keeping of state. Much of it is third-party.

+

3.3 Engines

+

Deterministic APIs for a modeled concept — a user, a tenant, a zone, a secret, an access rule. An engine's defining property is that the same authoritative input state yields the same result. Engines are where the estate's deterministic guarantees live, and therefore where every enforcement boundary MUST sit.

+

A repository whose core function is inference or judgment fails this test by construction and is Staff, however much of its work happens at runtime.

+

Engines are typed. "Engine" is one layer but four roles, and collapsing them hides different failure modes. Every §4 Engine row carries a role:

+
RoleMeaningOutage means
PDPrenders the authorization decision — access-engine, and only it (§6)consumer residue (§9.3)
PIPsupplies facts a decision consumes as claims — user, tenant, zone, approval, maturityinput degradation, engine's own fallback (§9.3)
Evidencerecords what happened and proves integrity of what it holds — audit-coreMUST NOT block the operation being recorded — a default, not a property; see below
Lifecyclea deterministic API over Tooling it owns — secrets-enginethe owning engine's failure semantics
+

The roles are why "we need an engine for X" does not mean "X now decides". A new engine is a PIP unless this standard is amended to say otherwise, and §6 means it can never be a second PDP.

+

The Evidence row's outage rule is an estate trade, not a property of evidence engines. Choosing availability there means accepting that a compromised source can suppress a record and that detection is the answer (§9.6). The opposite shape — do not proceed unless an independent custodian already holds the record — is the only one that puts evidence outside the actor's blast radius before the act. The estate has not needed it, so it is not ruled out by a table cell: an operation whose control genuinely requires independent recording before effect is a declared exception, raised when needed. Raised by audit-core against its own row.

+

The industry vocabulary is deliberately mirrored here — PDP, PIP, PEP as in NIST ZTA and XACML — because it is how the estate talks to the outside and how a PEP is stopped from quietly becoming a PDP. The determinism cut in §3 stays primary where the two disagree.

+

3.4 Staff

+

Interactive and non-deterministic. Staff is the management layer: operations, change, innovation, and controlling. It works through agentic capability — assistants and autonomous agents — and its artifacts are specifications, decisions, workplans, and tasks.

+

Staff repositories MUST NOT hold state that another layer depends on at runtime, and MUST NOT render or cache any decision an Engine is responsible for.

+

Acting at runtime does not make a repository an Engine. Being agentic makes it Staff, and §5 governs how it acts.

+

Two principals, one layer. Humans and agents are both non-deterministic and both Staff, so they share the layer's permissions. They do not share blast radius. Four rules bind the agent principal specifically:

+
  1. No standing credential. An agent holds no long-lived credential of its own. Authority is issued per task, time-bounded under §9.7, and attributable to the principal on whose behalf it acts.
  2. Tool use is a conduit or an Engine API. An agent acts through §5.2 — the owner's tool under the caller's identity, presenting no authority of its own — or through an engine. There is no third route. Tool availability is not permission: a callable tool means the operation exists, not that this actor may invoke it.
  3. Agent memory is not a state plane. Agent memory, tool-call traces, and prompt caches are the agent's own. They MUST NOT become state another layer depends on at runtime unless catalogued as Tooling in §4, which subjects them to §5 like anything else, and to §5's sunset.
  4. Every agent action is reconstructable as the caller's action, bounded by §9.6 — the archive shows the actions it received, not that it received all of them.
+

Session semantics — session loops, tool policy, harness routing, model selection — are not governed here. They belong to glas-harness and its rein-* backends (§20). This standard governs what an agent may be authorized to do; glas-harness governs how an agent session is conducted. Rule 2 is the seam between them, and neither side may treat its own half as sufficient.

+

v0.6 claimed these rules in its change log and did not write them. Found by kings-guard, which is the repository they bind hardest and which offered to assent to them sight-unseen.

+
+

04Layer catalog

+
RepositoryLayerRoleOwns
info-tech-canonTaxonomy—ecosystem-wide semantic contracts and terminology
net-kingdomTaxonomy—NetKingdom standards of record; publication
key-capeTooling—packaged identity tooling; IAM profile; authentication
OpenBaoTooling—secret storage, leases, PKI, dynamic secret engines
user-engineEnginePIPusers, accounts, memberships
tenant-engineEnginePIPtenant-as-an-entity facts
zone-engineEnginePIPzone identity and membership — offline reference conformance per its 2026-08-23 disposition
secrets-engineEngineLifecyclecredential abstraction, custody, lifecycle
audit-coreEngineEvidenceaudit event custody, retention, integrity verification, export — explicitly not a decision point (§9.6)
access-engineEnginePDPthe policy decision — the only decision point (§6)
approval-engineEnginePIPthe approval object — durable, authenticated, consumable, atomically supersedable (§9.4)
maturity-engineEnginePIPgraded progression against declared criteria and evidence; the gap register; capability readiness (§9.5)
gate-houseStaff—security doctrine, authority context, curriculum; conformance review — through maturity-engine (§9.5)
ops-masonStaffPEP-shapedbuilding and tearing down access routes and perimeters
ops-wardenStaffPEP-shapedoperational access lanes, stewardship, runbooks; SSH certificate issuance — declared-gap (§9.1, §13)
kings-guardStaff—adaptive defence and judgment; observation of Staff-reachable sources — identity and secret observation pending; proposes containment, which it does not own (§9.2)
whitehat-securityStaff—offensive validation
+

An actuation surface — reduce authority, require step-up, isolate a workload — is catalogued nowhere because it does not exist. See §9.2: it is an Engine concept held at zero, not a Staff capability.

+

access-engine is the ruled name for the repository currently called flex-auth; both denote the same authority until the governed rename completes. Execution conditions for that rename are recorded in its migration decision, not here.

+
+

05The binding rule

+

Staff never touches Tooling directly. It acts only through Engine APIs.

+

A Staff repository MUST NOT hold a direct client for a Tooling-layer system — no direct database connection, no direct OpenBao client, no direct cluster mutation — outside the shapes below. This is the architectural form of no privilege from cognition, and it is deliberately mechanically checkable.

+

Scope. "Tooling-layer system" means a system catalogued as Tooling in §4. Infrastructure the estate runs but has not catalogued — the State Hub, llm-connect, and similar — is outside this rule, because a rule that silently covered them would put every Staff repository in undeclared violation on adoption day: they all write progress events. Such clients SHOULD be recorded in the repository's declaration as non-Tooling for completeness of the check, and the way to bring one under §5 is to catalogue it in §4, deliberately.

+

Raised by ops-warden, which held clients for both and declined to resolve the scope question on gate-house's behalf.

+

The carve-out sunsets. It is a pressure valve, and a valve left open becomes a second persistence plane under the Staff layer — which §3.4 forbids in spirit. Three rules bound it: every non-Tooling client MUST be listed in the repository's declaration; an uncatalogued store that another layer reads MUST, within two review intervals, either be catalogued as Tooling in §4 or be declared a gap under §5.3; and a Staff-owned event bus or memory store MUST NOT become the estate's de facto state plane. Today's instances are the State Hub and llm-connect; tomorrow's are agent memory, tool-call traces, and prompt caches (§3.4).

+

Three shapes are sanctioned. Everything else is a violation.

+

5.1 Read-only diagnostic observation

+

A Staff repository MAY read Tooling state for diagnostics where the owning engine exposes no equivalent. It MUST be declared in the repository's INTENT.md. It grants no write, and it is an engine gap to close, not a standing arrangement.

+

5.2 Conduit

+

A Staff repository MAY run the owner's tool under the caller's identity, supplying no authority of its own. The test is the supplied-authority property: the conduit MUST NOT present its own credential, MUST NOT widen what the caller could already do, and MUST be reconstructable as the caller's action in audit.

+

A conduit that presents its own token is not a conduit; it is §5.3 or a violation. This shape MUST be declared, and the no-authority property SHOULD be covered by a test.

+

The reconstructability requirement is an audit-dependent claim and is therefore bounded by §9.6: the archive shows the conduit actions it received, not that it received all of them.

+

5.3 Declared engine gap

+

Where a Staff repository must contact Tooling directly and no engine exposes the capability, it MUST declare the contact rather than take an exemption. A declared gap carries, machine-readably:

+
FieldMeaning
capabilitywhat the contact does
intended_ownerthe engine that should own it
blocked_onwhy it cannot move today
reviewa date, not "when convenient"
+

A declared gap is tracked non-conformance, not conformance. It does not expire on its own and it is not a licence to add more. It exists because a rule offering no lane for a real sanctioned case gets satisfied by relabelling rather than by closing the gap — and a tracked gap is visible, whereas a relabelled one is not.

+

Prior art: ops-warden runs equivalent machinery for delegated lanes (27 catalog entries carrying delegation:, queryable via warden route gaps), and has offered it as reusable.

+

No fourth "operator of third-party Tooling" shape. It has been proposed, on the argument that someone must operate OpenBao and every operational necessity otherwise looks like a gap. Declined: §5.3 already sanctions the operation while keeping it visible, and a clean "operator" shape would convert a tracked gap into a permanent allowance — the relabelling failure this standard exists to prevent. A permanent operational necessity is a declared gap whose review interval keeps returning, which is the correct amount of friction. If the review becomes ceremonial, that is an argument for closing the gap, not for renaming it.

+
+

06One decision point

+

access-engine is the only policy decision point in NetKingdom. No other repository, in any layer, may render or cache authorization decisions.

+

First ruled in zone-engine/INTENT.md §5 — "flex-auth is the policy decision point. It stays the only one." The failure mode, from the same source: "It becomes a second decision point… it would arrive as a small convenience."

+

6.1 Compiled data that determines an outcome is still deciding

+

A registry, cache, or schema that resolves a result before the engine runs has decided early. Provenance MUST remain reconstructable from the engine's decision record.

+

6.2 Doctrine reaches the decision as an input, or it is not applied

+

This rule binds gate-house on the same terms. An authority ceiling, mandate constraint, or operating-mode restriction that determines an outcome MUST reach the decision either as an input claim on the request or as a rule in the versioned policy package, so that its application is reconstructable from the decision record.

+

Doctrine that influences outcomes by any other route is a second decision point wearing an author's hat. This is not a limit on gate-house's authorship; it is what keeps that authorship auditable at decision time.

+

6.3 No Staff repository may host a decision point

+

A deterministic authority boundary inside a non-deterministic layer contradicts the invariant the estate is built on. gate-house was re-cut on this ground.

+

6.4 The enforcement point

+

The standard has been precise about the decision and silent about the gate. A decision that nothing refuses to proceed without is advice.

+

A PEP is any runtime that causes a protected side effect. It is a shape, not a repository: ops-warden issuing a certificate, ops-mason opening a route, and any protected system acting on a verdict are all PEP-shaped. Being PEP-shaped does not move a repository out of its layer.

+

Four obligations, and they are normative:

+
  1. No side effect without a decision record, or a recorded stance. A PEP MUST NOT perform the protected action unless it holds a decision from access-engine identifying the request it was rendered for, or its declared §9.3 stance for the applicable scope permits proceeding without one and the application of that stance is recorded in place of the decision. The second limb is stricter than silence, not looser: a fail-open result is metadata, never an absent record. ops-warden ca.py writes the zone, the failure mode, and a decision id present only where a decision was rendered.
+

v0.6's unqualified form made the shipped stance §9.3 sanctions into a violation — the same defect as v0.5's §9.1, a rule written for the clean case producing a false result on the adjacent case already sanctioned elsewhere. Raised by ops-warden, which is the reference shape for limb two.

+
  1. No recaching of the verdict beyond its own binding. A stored verdict replayed outside the decision's stated binding and lifetime is a second decision point deciding early (§6.1). Within them it is the decision being used as issued — a session-bound allow under §9.7.1 is used across later requests by construction, and v0.6 forbade what §9.7.1 permits.
+

The test is mechanical, not a matter of implementer judgement: replay is permitted iff the canonical request digest matches and the decision's lifetime holds. access-engine computes that digest over normalised subject, action, resource, and context, and it is already in every decision binding. A retry after a transport failure is therefore the same request; a different resource is not.

+

Negative caching is permitted, narrowly. A cached DENY cannot manufacture authority — §8's asymmetry holds — and protects against retry storms. It is permitted where the refusal is itself recorded against the request that was refused (obligation 4), and where the cache lifetime is declared alongside the stance map. A stale deny is an availability failure and will be misdiagnosed as a policy one, so it must be visible as what it is. Ruled explicitly because it is the first thing an implementer under load reaches for. Raised by access-engine.

+
  1. A declared unreachable-engine stance (§9.3): total, per zone or equivalent scope, no implicit default, no per-call discretion, published rather than held in code comments or in a dataclass default. ops-warden ADR-0009 and its pep-stance.yaml are the reference shape.
+

The published map MUST equal the shipped behaviour, and that equality SHOULD be asserted by a test. A published map free to drift from the code is worse than none, because it invites reliance it cannot support. Raised by ops-warden, which found its own map unpublished while being cited as the reference for this obligation.

+
  1. Reconstructability, bounded by §9.6.
+

Every PEP-shaped consumer MUST publish its stance map at a path named in its layer declaration, and those maps MUST be inventoried in the §13.1 register until maturity-engine can hold them. §9.3 is otherwise a ruling with no register behind it, and "z0–z2 and unknown fail open" becomes the estate's real policy without anyone having compiled it into a versioned package.

+

v0.6 named a register that did not exist — a requirement whose register is missing is a capability catalogued without a surface, by §9.1's own logic. Raised independently by ops-warden and access-engine; §13.1 now exists, and its first inventory has one row, which is itself the finding.

+

Raised by the 2026-08-29 independent assessment: NIST ZTA splits decide from enforce, and this standard had only the first half.

+
+

07Relationship to the Active Secrets Management Canon

+
Staff       interactive, non-deterministic   ≈  Cognitive Plane
+Engines     deterministic APIs               ≈  Authority Plane
+Tooling     deterministic state              ≈  Execution Plane
+Taxonomy    cross-cutting language
+

Cognition proposes. Authority disposes. Infrastructure executes. is therefore NetKingdom's layering rule, not only its security maxim. §5 and §6 are that principle applied to repositories rather than to requests.

+
+

08Vocabulary demarcations

+
TermBelongs toNot
access laneops-warden, ops-mason (Staff) — how a worker reaches a hostthe decision whether they may
access ruleaccess-engine (Engine) — whether an actor may actthe route by which they arrive
control planeEngine layera Staff repository's self-description
doctrinegate-housea lane owner's runbook
runbookthe Staff repository stewarding the lanea substitute for doctrine
posturekings-guard publishes; gate-house defines its authority meaning; access-engine renders ita privilege source
+

Posture carries an asymmetry that MUST hold: adaptive systems may reduce authority, require step-up, or request containment. They MUST NOT probabilistically manufacture additional authority.

+

The asymmetry is what bounds the damage when observation is incomplete (§9.6): suppressed evidence can only prevent a tightening that should have happened, never engineer a loosening. That is an argument for keeping it absolute rather than situational.

+
+

09Capability assignment

+

9.1 The catalog may not assign what the rules forbid discharging

+

A Staff repository MUST NOT be catalogued in §4 as owning a capability it cannot discharge under these rules. Two marks distinguish the two ways that happens, and they are not interchangeable:

+
MarkMeaning
pendingNo route exists. No engine exposes the capability, the repository makes no Tooling contact, and the capability is zero — not degraded.
declared-gapA route exists through a §5.3 declared gap. The capability works and is tracked, with an intended owner and a review date in §13.
+

v0.4 had only pending, which forced a false choice. ops-warden holds production-verified SSH certificate issuance through a declared OpenBao contact; marking it pending would have told readers the repository does not do the one thing it demonstrably does daily, while leaving it unmarked left §4 disagreeing with §13. Neither is acceptable, and the defect was in this section rather than in the catalog.

+

pending was written for kings-guard's containment — no route, capability zero — and remains correct there. declared-gap is the case §5.3 was added to sanction. Raised by ops-warden.

+

Both marks apply per capability, not per repository. A repository may hold one capability outright, another under a declared gap, and a third pending.

+

9.2 Actuation does not exist, and containment is not Staff's to own

+

Self-healing needs four verbs: observe, evaluate, decide, actuate. Observation is kings-guard and is unstaffed (§12). Evaluation is maturity-engine and is seeded. Decision is access-engine and works. Actuation has no surface at all, and a model with no actuation surface describes a diagnosis machine rather than a healing one.

+

v0.5 marked containment pending against kings-guard, which was the right mark on the wrong repository. Containment is not a Staff capability that happens to lack a route: reduce authority, require step-up, isolate a workload are authority-changing operations, and under §6 an authority-changing operation is rendered by an Engine and enforced by a PEP (§6.4). A Staff repository proposes containment; it never performs it.

+

The actuation surface is therefore an Engine concept — likely a small surface on access-engine together with runtime PEPs — carrying the same reconstructability rules as any other decision: a containment action is a decision record, not a side channel.

+

It is unowned and held at zero. access-engine is recorded in §13 as a proposed owner and has explicitly not reviewed it (FLEX-DEC-2026-002). No repository may be catalogued as owning containment until the surface exists — §9.1 applied to the estate's most operationally tempting gap, and the standard's own medicine.

+

Until then kings-guard proposes and judges, its containment claim stays at zero rather than degraded, and no argument may assume the estate can contain anything automatically.

+

9.3 Degraded mode: two failure cases, two owners

+

v0.4 collapsed two failures into one rule. They have different owners because one has an evaluator in the path and the other does not.

+

Input degradation — the engine's. Where access-engine is reachable but cannot reach its own inputs, the deterministic fail to reduced authority default belongs to the engine. This keeps the decision at the decision point and keeps the fallback deterministic, which a Staff-layer fallback could never be.

+

Engine unreachable — necessarily the consumer's. Where access-engine is not reachable at all, it applies nothing, because it is not running. Whatever happens next is the consumer's behaviour by construction: fail-open is not expressible by a policy decision point, since there is no evaluator in the path to express it. A standard that assigns this to the engine assigns it to nobody.

+

The consumer's residue is bounded rather than free. A protected system MUST declare its unreachable-engine stance ahead of time, per zone or equivalent scope, and that stance MUST be auditable and total — no implicit default, no per-call discretion. ops-warden ADR-0009 already satisfies this: a total per-zone map, open for z0–z2 and unknown, closed for z3-critical, replacing the global policy.enabled / policy.fail_closed switches it superseded.

+

Unchanged: engine-unavailable is not grounds for a Staff break-glass path. The distinction is whether an engine is there to ask. A bypass around a reachable engine is a second decision point, and an incident is when an attacker most wants that shortcut. A consumer choosing its declared behaviour when there is no engine to ask is not a bypass; it is the only thing left.

+

Contested by flex-auth (FLEX-DEC-2026-002), which has held since 2026-08-19 that fail-open is not expressible by a PDP, and which noted v0.4 collided with shipped behaviour in a repository that had assented to this standard.

+

9.4 Approvals are an engine concept, not a Staff or audit concern

+

The approval object — durable, authenticated entries, distinct-approver counting, atomic supersession, single consumption, revocation without holder cooperation — is owned by approval-engine.

+

It is not Staff's: §3.4 forbids Staff holding state another layer depends on at runtime. It is not the decision point's: an evaluator that owns the object it evaluates is self-dealing. It is not the audit fabric's: an approval needs mutable, in-path, current-state semantics, and an append-only archive is built for the opposite property.

+

access-engine consumes approvals as input claims under §6.2 and never mutates them. Every issuance, use, supersession, and revocation is emitted to audit-core: the operative state and the evidence record are different artifacts with different owners.

+

The evidence guarantee is bounded, and the bound is audit-core's docs/integrity.md, not its INTENT principle 6. An in-database hash chain detects a rewritten payload only if the attacker does not also recompute the suffix — which a database owner can. Detection against that class requires the external chain-head attestation, and even with it the store is not WORM, object lock, or archival custody. tamper_evidence is therefore conditional on live preconditions, not a property of the store at rest, and approval events receive exactly the guarantee every other source receives.

+

Emission atomicity is approval-engine's obligation. An approval MUST NOT be issued, consumed, superseded, or revoked without the corresponding event being durably queued in the same transaction.

+

The queue MUST be local. The durable queue MUST live in approval-engine's own transactional store, and no synchronous dependency on audit-core may sit inside the state-change transaction. With a genuine local outbox, fail-closed triggers only when approval-engine's own store is unavailable — where the change could not have been recorded anyway — and an audit-core outage does not block a revocation. Satisfying the requirement by emitting synchronously to audit-core inside the transaction is also atomic, and turns an audit outage into an inability to revoke: the operation least tolerable to block during an incident, and the same coupling this section rejects for reads. Raised by audit-core. audit-core reports what it received and does not imply it is everything that happened; without atomic emission the evidence half is silently incomplete and nothing detects the gap. This is a condition of audit-core's assent (AUDIT-IN-0001) and belongs in approval-engine's contract before the evidence half is treated as load-bearing.

+

audit-core MUST NOT expose an approval-validity query. Records, yes; a verdict on whether an approval is still valid, never — a consumer branching on that answer would route an authorization decision through the audit fabric, which is what this section exists to prevent. Callers needing current state ask approval-engine.

+

9.5 Graded progression is an engine concept

+

Maturity — how far a subject has progressed against declared criteria and submitted evidence — is owned by maturity-engine. Given the same criteria and the same evidence it MUST return the same level; that determinism is what makes it an Engine rather than an opinion.

+

The division with Staff: gate-house judges and proposes; maturity-engine computes and remembers. Interpretation is inference and stays Staff. A criterion that cannot be evaluated by rule is not yet a criterion.

+

This closes a defect in v0.2's own catalog: gate-house was assigned conformance review with no engine to act through, which is exactly the §9.1 problem raised against the containment claim. Staff acts only through Engine APIs, including gate-house.

+

A maturity level MUST NOT be compiled into registry content. Until access-engine's decision provenance carries a registry-snapshot digest — a gap it self-declared in §13 — a level reaching a decision through the registry is not reconstructable from the decision record. Levels arrive as request claims or as versioned policy rules. Same constraint, and same reason, as zone stance.

+

A maturity level MUST NOT gate a decision directly. Under §6.1, compiled data that determines an outcome is still deciding. If a level determines whether an action is permitted, it MUST reach access-engine as an input claim or a versioned policy rule under §6.2, never by a consumer branching on a fetched level.

+

Approvals and maturity are deliberate opposites — a closed binary state machine against an open graded ladder — and neither engine may drift toward the other.

+

9.6 Evidence proves alteration and truncation, not omission at source

+

An append-only archive with a verified hash chain proves that records were not altered or truncated after arrival. It cannot prove that a record was never sent. Against a compromised or buggy source, a suppressed event leaves the chain perfectly intact and verification reports intact.

+

This bound is estate-wide. Statements of the form "the audit record proves it happened" are unsound; the sound form is "the archive proves the records it holds were not altered or truncated after arrival". Its mirror is equally unsound: absence of a record is not evidence of non-occurrence, and no control may read it as such.

+

Load-bearing versus attributive evidence. The atomicity obligation attaches to the first, not to both:

+
KindTestObligation
Load-bearinga control's soundness depends on the event being present or absent — an approval revocation, a containment action, a denialemission MUST be atomic with the state change (§9.4)
Attributivethe event supports forensic reconstruction and attribution, and no control branches on its presenceatomicity SHOULD be sought; where it is deliberately traded away, the trade MUST be declared and completeness MUST NOT be claimed
+

Where a repository deliberately makes emission non-atomic — ops-warden's # audit must not block signing is the estate's live example, chosen so that an audit-store failure cannot remove production host access — the trade is legitimate for attributive evidence, MUST be declared where the trail is documented, and MUST NOT be described in terms that imply completeness. The availability argument is real in both directions: making it atomic gives the estate's operational access lane a new dependency on its own evidence store.

+

Consequence for adaptive systems. Suppression does not degrade observation neutrally, it biases it optimistic, and silently: an event never emitted is never evaluated, so no finding is raised and the last posture stands. A confidence score computed from the richness of the record in hand cannot express doubt about the completeness of the stream — a well-formed observation from a 90%-suppressed stream scores high. That is this section's failure reproduced one layer up, in the consumer.

+

Two things follow.

+

Which control covers which threat. v0.6 read as though emission atomicity closed this section's opening sentence. It does not, and the decomposition is owed to the reader:

+
ThreatCovered byWhen
Accidental omission — process dies between mutation and emitemission atomicity, local outbox (§9.4)prevented
Adversarial omission — a compromised source declines to insert, deletes before drain, or drains to nowherecadence and reconciliationdetected, after the fact
Adversarial omission at a compromised source—nothing in this model prevents it
+

The outbox sits inside the blast radius of the component whose compromise this section posits, so it makes emission atomic against crash and partial failure and nothing more. That residual is real and is stated rather than implied. Raised by audit-core, correcting a remedy it had itself proposed.

+
  1. The §8 asymmetry bounds the damage, and this is its clearest payoff. Because an adaptive system may only reduce authority and never manufacture it, suppression can only prevent a tightening that should have happened. It cannot be used to engineer a loosening. The harm is a missed reduction, not an invented privilege — which is an argument for keeping the asymmetry absolute.
  2. Silence is a signal, and for load-bearing evidence it is the only control in its class. A source of attributive evidence SHOULD declare an expected emission cadence; a source of load-bearing evidence MUST. A drop below the declared rate is a finding in its own right — the stream observed, not only its contents — and needs no Tooling contact, because the source publishes its own stream.
+

Rate monitoring is the wrong form for rare events, and rare is exactly where the stakes are highest: the most valuable event to suppress is the negative one, and revocations, denials, and containment actions are infrequent by nature. A source emitting a handful of revocations a month has no rate to drop below, and suppression is indistinguishable from a quiet month. For low-volume load-bearing classes the required form is therefore positive reconciliation or a heartbeat: compare the source's own state transitions against the evidence engine's event count per class and treat divergence as a finding, or assert nothing to report as a signed positive claim that can itself go missing. Rate monitoring never produces a claim that can be missing; a heartbeat does. GH-WP-0002-T04 is the reference instance. Raised by audit-core.

+

Raised by audit-core against its own principle; extended by kings-guard from its own evaluator and confidence model.

+

9.7 Decisions have a lifetime

+

A single decision point deciding on stale claims is a single decision point deciding wrongly. The model has had no temporal law, and the approval race in §16 was its first symptom.

+
  1. Every allow has an explicit lifetime — a TTL, or a binding to a session or obligation that ends. An allow with no stated end is a standing grant, and standing grants are what this estate exists to remove.
  2. Revocation and supersession have a visibility deadline, and its shape differs by role. A PEP has one boundary and MUST state one deadline. A PDP MUST state a deadline per input class, because a decision is a join over sources with unrelated refresh behaviour — approval-claim freshness, registry snapshot cadence, policy package activation, directory ETag. A single number at a PDP is either a fiction or the worst case, and the worst case is the slowest and least visible input. "Eventually" is not a stance; an unstated deadline is an unbounded replay window.
+

A consequence worth naming: a stated deadline for a fact carried by a registry snapshot is unfalsifiable while decision provenance holds no snapshot digest, since nobody can determine afterwards which snapshot a decision read. The deadline and the digest are one gap seen from two sides, which promotes access-engine's self-declared provenance gap (§13) from housekeeping to a conformance prerequisite. Raised by access-engine against its own backlog.

+
  1. Consumption is a state change, never an inference. An approval is consumed by a mutation in approval-engine (§9.4). It MUST NOT be inferred from the existence of a decision record — the decision precedes the action and the action precedes consumption, so a decision record proves an intent to act, not an act.
  2. Three failure modes are named, and each needs an owner: an allow rendered then never consumed; a double consumption by racing callers; consumption after the authorized action has already failed. Neither engine closes these alone. Recorded in §16 and GH-WP-0002-T06.
+

9.8 Partition is not one-dimensional

+

§9.3 handles engine unreachable. A real estate spends most of its incident time in the band between reachable and gone: partial PIP reachability, clock skew across a decision and its enforcement, and two consumers with different declared stances seeing different worlds at the same moment.

+

Two rules hold today, and the rest is open (§16). A PEP MUST resolve its own stance from its declared map without consulting another consumer — divergent views are expected and are not a coordination problem to be solved at enforcement time. And where clock skew could extend a lifetime under §9.7, the shorter reading governs.

+
+

10Changing layer

+

A repository's layer is not permanent. zone-engine changed layer in practice when its runtime hypothesis was falsified.

+

A layer change MUST be recorded as a decision, MUST update the repository's INTENT.md, and MUST obtain assent from the repositories whose boundaries move. A repository MUST NOT acquire a new layer's permissions by gradual practice.

+

"No gradual practice" needs a check rather than a sentence. A layer change MUST carry six artifacts, written from the zone-engine case that the procedure should have been derived from in the first place:

+
ArtifactWhy
before/after INTENT.mdthe declaration is the conformance surface (§11)
client inventorywhat the repository holds against Tooling, before and after
gap inventorywhich §5.3 gaps close, open, or transfer
assent listevery repository whose boundary moves
state-migration decisionwhat happens to live state and to consumers reading it
permission freezeno new permissions of the target layer are exercised until the cut completes
+

The freeze is the one that makes the rule checkable: a repository mid-change holds its old permissions, not the union of both.

+
+

11Conformance

+

Conformance has four states, and the distinction between the last two is the point:

+
StateMeaning
Conformingno Tooling contact, or only §5.1/§5.2 shapes, declared
Blocked-cleanthe capability does not exist because no engine exposes it, and the repository makes no Tooling contact — §9.1 pending, and not a non-conformance
Declared gapa §5.3 contact with owner, blocker, and review date — tracked non-conformance
Undeclared violationanything else — a finding
+

Blocked-clean is not a lesser state than conforming. A repository that declined a break-glass path and left a capability at zero has complied at cost; a repository that quietly opened a direct client and declared nothing has not. Any downstream scoring — maturity-engine included (§9.5) — MUST NOT rank the first below the second. Raised by kings-guard, whose three gaps are all of this kind and which would otherwise have been graded down three times for having taken the standard seriously.

+

Who must declare. A repository the estate authors declares its layer in its own INTENT.md. For a component the estate catalogues but does not author — third-party or vendored, such as OpenBao — the §4 catalog row is the declaration, and no INTENT.md obligation attaches. A rule that assigns an obligation the holder cannot discharge is the §9.1 defect applied to conformance rather than capability.

+

A layer stated about a repository by another repository is not a declaration. Review notes, catalog rows, and correspondence record an intent to adopt; only the repository's own file conforms.

+

Declaration form. Because prose cannot distinguish a declaration from a transcribed review, a declaration MUST carry a machine-readable form: a layer: key in the INTENT.md frontmatter, or an equivalent declaration file. Without it this section asserts a property it cannot deliver — the defect this standard has now corrected three times elsewhere. ops-warden has implemented a reference form (layer.yaml, a conformance script, and a test covering the §5.2 no-authority property) and offered it to the repositories that have yet to declare. Raised by audit-core, which noted that flex-auth's conforming declaration is legible as one only by following its decision trail.

+

Mechanically checkable:

+
  • every estate-authored repository in §4 carries a machine-readable layer declaration;
  • every direct Tooling client in a Staff repository maps to a declared §5.1, §5.2, or §5.3 entry, and non-Tooling clients are recorded so the check is total;
  • no repository other than access-engine exposes an authorization decision surface;
  • no §4 capability is catalogued without an engine surface, a pending mark, or a declared-gap mark.
+

Requires review: whether claims stay inside layer permissions; whether compiled or cached data has become an early decision (§6.1); whether doctrine is reaching decisions as declared inputs (§6.2); whether the §8 vocabulary is used correctly.

+
+

12The conformance loop

+

Doctrine no engine implements is fiction. The loop is normative, not aspirational:

+
gate-house asserts an invariant
+      → the engines implement it, or declare a gap
+      → whitehat-security tries to break it
+      → kings-guard observes it in operation
+      → findings return to gate-house as doctrine change
+

A finding that a rule is unsatisfiable is a success of this loop, not a failure of the reporting repository. Four of this standard's five versions exist because a reviewing repository used it.

+

Step four is currently aspiration. kings-guard has disclosed that it has never observed anything in operation: the pilot is specified and scaffolded, every input is a hand-built fixture, and no test has met a real event. Until it reports otherwise, no argument in this estate may assume an invariant is being watched in practice because §12 lists a repository against that step.

+
+

13Open gaps

+

Two different things are recorded here, and they are opposite conformance states (§11). A declared contact means the repository touches Tooling because no engine exposes the capability. An unowned capability means no route exists and the repository makes no contact at all. Reading them as one list would grade restraint as though it were non-conformance.

+

An intended owner is a proposal to the named repository, not an assignment onto it. §2 keeps ownership in the repository's own INTENT.md, so the register distinguishes proposed from assented.

+
GapStateDeclared byOwnerOwner status
SSH-CA signing write (VaultCA, bao kv put)declared-contactops-wardensecrets-engineproposed
Authentication / assurance evidenceunowned-capabilitykings-guardidentity layer + audit-coreaccess-engine declined
Secret-use evidenceunowned-capabilitykings-guardsecrets-engineproposed
Actuation / containment surfaceunowned-capabilitygate-house (estate-wide)access-engine + runtime enginesproposed
Identity and secret observationunowned-capabilitykings-guardas aboveproposed
Stance-map register had no implementationdeclared-contactops-warden, access-enginegate-houseresolved in §13.1
Registry-snapshot digest in decision provenancedeclared-contactflex-authflex-authself-declared
Approval storage and lifecycle—flex-authapproval-engineassigned (§9.4)
Approval evidence—gate-houseaudit-coreassented (AUDIT-IN-0001)
Approval evidence custody stronger than the shipped bound — WORM, object lock, transparency logunowned-capabilityaudit-core—unassigned
Emission atomicity for approval state changes—audit-coreapproval-engineassigned (§9.4)
Non-atomic audit emission on the SSH signing lanedeclared-contactops-wardenops-wardenself-declared, attributive (§9.6)
+

The actuation row is no longer attributed to kings-guard. §9.2 ruled that containment is not a Staff capability lacking a route, so kings-guard is not its declarer: the gap is estate-wide and blocks every repository's ability to act. Raised by kings-guard, which asked not to carry a row for a capability the standard had just ruled was never theirs.

+

access-engine declined authentication and assurance evidence (FLEX-DEC-2026-002): it consumes assurance claims as input and never redefines them, so evidence of authentication belongs to the identity layer and audit-core. It owns evidence of the decision, which it already emits. The containment surface is recorded as proposed and remains pending under §9.2.

+

Whether approvals warrant custody stronger than every other source is doctrine work not yet done; until it is, approval evidence carries the same guarantee as any other source and §9.6 bounds what may be claimed from it.

+

What is normative here, and what is a snapshot. Three rules are part of this standard and survive wherever the register lives:

+
  1. the two marks — pending and declared-gap (§9.1);
  2. the owner-status rule — a proposed owner is not an assigned one (§2);
  3. the scoring rule — blocked-clean MUST NOT rank below conforming (§11).
+

The table above is a snapshot, not statute. It moves into maturity-engine as soon as that engine can store state, and the state and owner-status columns MUST survive the migration. A standard that is also a backlog keeps attracting findings that belong in the register, and its review interval is far slower than the register's real rate of change.

+

13.1 PEP stance-map register

+

Every PEP-shaped consumer publishes an unreachable-engine stance map (§6.4, obligation 3). This is the inventory until maturity-engine can hold it.

+
ConsumerStance mapShape
ops-wardenops-warden/pep-stance.yamltotal per-zone; open z0–z2 and unknown, closed z3-critical; test asserts the published map equals the shipped default (ADR-0009)
ops-mason—not published; catalogued PEP-shaped in §4
+

One row is the finding. The aggregate of consumer stances is the estate's real authorization behaviour, and it is currently one published map and one absence. access-engine has noted it is the repository positioned to notice when that aggregate diverges from what the policy packages say — which it cannot do while the register is nearly empty.

+
+

14Adoption

+

Status is accepted, on the owner's decision of 2026-08-29.

+

Two things that acceptance does and does not mean, kept apart because ops-warden asked for the distinction:

+
Boundary assentgiven by the four repositories below, at the version named in each record, and undisturbed since
Revision revieweach of the four reviewed v0.6 and returned findings; every change in v0.7 is the adopted remedy of a finding they raised
Not claimedno repository has reviewed v0.7 as text. The first revision review will confirm or correct it
+

Accepting a standard nobody has re-read is a deliberate call: the estate learns more from using it than from another round of prose refinement, and the changes in v0.7 were requested rather than invented. Findings against the accepted text remain welcome and are §12's normal business, not an exception.

+
RepositoryRecordOutcome
flex-authFLEX-DEC-2026-001assent to all three items; one self-declared non-conformance; two rename conditions
kings-guardKG-DEC-2026-001assent; declined the offered §5 relaxation; raised §9.1
ops-wardenADR-0010assent to all three; veto not exercised; offered the §5.3 amendment
audit-coreAUDIT-IN-0001assent to the evidence half with conditions; corrected the rationale twice; raised §9.6
+

Adoption for a repository means its INTENT.md declares its layer, its ownership claims fall inside that layer, its Tooling contacts are declared under §5, and any shared boundary has been assented to by the other side.

+

Adoption status as of 2026-08-29: seven of sixteen estate-authored §4 repositories have declared in their own voice — gate-house, flex-auth, kings-guard, ops-warden, audit-core, approval-engine, maturity-engine. The remaining nine — info-tech-canon, net-kingdom, key-cape, user-engine, tenant-engine, zone-engine, secrets-engine, ops-mason, whitehat-security — carry a layering review note authored by gate-house and have not answered it. Those notes state a layer but do not constitute a declaration, and this standard does not claim estate-wide adoption on their basis. Declaration requests are open as intakes in each.

+
+

15Change log

+

v0.1 → v0.2:

+
  1. §5 restructured into three sanctioned shapes. Added §5.2 conduit (ops-warden's question, ruled) and §5.3 declared engine gap (ops-warden's amendment, accepted).
  2. §6.2 added — doctrine must reach the decision as an input claim or a versioned policy rule (flex-auth's boundary drawn back, accepted).
  3. §9 added — the catalog may not assign a capability the rules forbid discharging; containment marked pending; degraded-mode fallback ruled into the engine (kings-guard's finding).
  4. §11 restructured — conformance now has three states, distinguishing a tracked gap from an undeclared violation.
  5. §12 made normative, with the explicit statement that an unsatisfiability finding is a success of the loop.
  6. §13 added — open gaps register, including the unowned approval storage and lifecycle capability.
  7. §4 catalog gained the pending mark and ops-warden's SSH certificate lane.
+

v0.2 → v0.3:

+
  1. §9.4 added — approvals assigned to approval-engine, with the operative state and the evidence record separated between it and audit-core.
  2. §9.5 added — graded progression assigned to maturity-engine, closing the §9.1 defect in gate-house's own conformance-review claim, and carrying the guardrail that a level may never gate a decision directly.
  3. §4 catalog gained both engines; gate-house's conformance-review claim now names the engine it acts through.
  4. §13 register updated: the approval hole is assigned, two new entries added.
+

v0.6 → v0.7, from four reviews:

+
  1. §3.4 is written. v0.6 announced the human/agent principal separation in §1 and §15 and left §3.4 byte-identical to v0.5 — a silent edit failure. A rule stated about a standard in its own change log is not a rule. Found by kings-guard. The same failure had also dropped two §16 entries, restored here.
  2. §6.4 obligation 1 rewritten — it forbade what obligation 3 blesses. A PEP may proceed under its declared §9.3 stance provided the application of that stance is recorded in place of the decision. Stricter than v0.6 where it counts: a fail-open result is metadata, never silence. Raised by ops-warden.
  3. §6.4 obligation 2 rewritten — it forbade the session-bound allow §9.7.1 permits. Scoped to replay outside the decision's own binding and lifetime, with the canonical request digest as the mechanical test, and negative caching ruled permitted where the refusal is recorded and the cache lifetime declared. Raised by access-engine.
  4. §6.4 obligation 3 gained the requirement that the published stance map equal shipped behaviour, asserted by test. §13.1 now exists as the register §6.4 mandated and v0.6 did not implement.
  5. §9.6 gained a threat decomposition — atomicity prevents accidental omission; cadence and reconciliation detect the adversarial case after the fact; nothing prevents it at a compromised source. Raised by audit-core against its own proposed remedy.
  6. §9.6 cadence is now MUST for load-bearing sources, with positive reconciliation or a heartbeat as the required form for low-volume classes, because rate monitoring fails exactly where the stakes are highest.
  7. §9.7.2 splits by role — a PDP states a deadline per input class, a PEP one at its boundary. Promotes access-engine's provenance gap to a conformance prerequisite.
  8. §3.3's Evidence row is stated as an estate trade rather than a property, leaving independent-recording-before-effect raisable as a declared exception.
  9. §17 moves the decision-record schema to access-engine, which argued it against its own interest; kings-guard drafts the emission-cadence schema.
  10. §13 no longer attributes the actuation gap to kings-guard; it is estate-wide. §19 removed — a verdict inside a standard grades the document it lives in. §17/§18 demoted from H1 to H2.
  11. §20 added — the Railiance interaction boundary, on railiance-master's definitions, including that rein-* is not a fifth axis.
+

v0.5 → v0.6, from the independent assessment of 2026-08-29:

+
  1. §3.3 types the engines — PDP, PIP, Evidence, Lifecycle, with a role column in §4. A new engine is a PIP unless this standard says otherwise, so "we need an engine for X" cannot drift into "X now decides".
  2. §6.4 names the enforcement point — a PEP shape with four obligations: no side effect without a decision record, no local recaching of the verdict, a declared unreachable-engine stance, reconstructability. The standard had the decision and not the gate.
  3. §9.2 replaced — containment was marked pending against the wrong repository. Actuation is an Engine concept, unowned, held at zero; Staff proposes containment and never performs it.
  4. §3.4 separates the two Staff principals — human and agent share the layer but not blast radius: no standing credential, conduit or engine API only, agent memory is not a state plane, every action reconstructable as the caller's.
  5. §9.7 puts time into the model — explicit lifetimes, revocation visibility deadlines, consumption as a state change never inferred, and the three race modes named. §9.8 states what holds under partition and leaves the rest open.
  6. §17 requires the Taxonomy artifacts — claim, decision-record, gap-record, and emission-cadence schemas — without which §6.2 and §11 are reviewable but not compileable. Ownership proposed, not assigned.
  7. §18 composes the sibling standards — how zone stance, tenancy posture, and a credential lifecycle event each enter a decision as a claim. They were cited in frontmatter and nowhere in the rules.
  8. §5 gained a sunset on the uncatalogued-infrastructure carve-out, and §5.3 declines a proposed fourth "operator of third-party Tooling" shape: it would convert a tracked gap into a permanent allowance.
  9. §10 gained the six artifacts a layer change must carry, written from the zone-engine case, including a permission freeze during the cut.
  10. §2 lifts the observation rule — no estate argument may cite observation that has not happened. §13 separates its three normative rules from the table, which is now a snapshot due to move into maturity-engine.
  11. §16 the approval custody question is decided: no, rather than left open. §19 records the fitness verdict, including that the estate can propose and decide but cannot yet watch or act.
+

v0.4 → v0.5, all from review findings:

+
  1. §9.1 split into two marks — pending (no route, capability zero) and declared-gap (route exists under §5.3, capability works and is tracked). v0.4's single mark would have forced a false pending onto ops-warden's production SSH issuance. Raised by ops-warden.
  2. §9.3 rewritten — input degradation is the engine's; engine-unreachability is necessarily the consumer's, bounded by a declared, auditable, total stance. Contested by flex-auth: fail-open is not expressible by a PDP, and v0.4 collided with ops-warden ADR-0009.
  3. §5 gained a scope rule — "Tooling-layer system" means a §4 Tooling row; uncatalogued infrastructure is outside §5 and recorded rather than policed. Without it every Staff repository was in undeclared violation for writing progress events. Raised by ops-warden.
  4. §9.4 requires a local outbox — no synchronous dependency on audit-core inside the state-change transaction, so an audit outage cannot block a revocation. Raised by audit-core.
  5. §9.5 forbids compiling maturity levels into registry content until decision provenance carries a registry-snapshot digest. Raised by flex-auth.
  6. §9.6 gained the load-bearing / attributive distinction, the mirror rule that absence is not evidence of non-occurrence, the optimistic-bias consequence for adaptive systems, and silence-as-signal. Raised by kings-guard on top of audit-core's original.
  7. §11 gained a fourth state — blocked-clean, which MUST NOT rank below conforming — and a machine-readable declaration form. Raised by kings-guard and audit-core.
  8. §13 gained state and owner-status columns — declared-contact versus unowned-capability, proposed versus assented owner. access-engine's decline of authentication evidence is recorded. Raised by kings-guard and flex-auth.
  9. §8 records the asymmetry's payoff under incomplete observation; §12 records that its fourth step is unstaffed; §14 corrects the adoption arithmetic and the status contradiction.
+

Amended in place while proposed, 2026-08-28: §11 gained the who-must-declare rule after a conformance sweep found the standard required an INTENT.md declaration from OpenBao, which the estate does not author; and §14 gained the honest adoption count.

+

v0.3 → v0.4:

+
  1. §4 catalog gained audit-core as an Engine, on its own declaration. v0.3 named it as an owner in §9.4 and §13 without cataloguing it — a §11 defect in the standard itself, raised by audit-core.
  2. §9.4 evidence rationale rewritten to cite audit-core's shipped docs/integrity.md bound rather than its INTENT principle 6, and to state that tamper_evidence is conditional on live preconditions.
  3. §9.4 gained emission atomicity as approval-engine's obligation, and the prohibition on audit-core exposing an approval-validity query.
  4. §9.6 added — evidence proves alteration and truncation, not omission at source. Estate-wide; the sound and unsound forms of the claim are stated.
  5. §13 — evidence half recorded as assented with conditions; two new gaps: stronger approval custody (unassigned) and emission atomicity (approval-engine).
+
+

16Open questions

+
  • ~~Whether approvals warrant archival custody stronger than every other audit source.~~ Decided (§13): no. Approval evidence carries the same bound as every other source. The acute risk for approvals is omission — a suppressed revocation — and archival custody does not address omission at all; emission atomicity with a local outbox (§9.4) and a detection surface (GH-WP-0002-T04) do. Leaving it open while calling the evidence half load-bearing created a promise the archive cannot cash. If a future requirement genuinely needs WORM or a transparency log, that is a different store with a different owner, raised then.
  • Whether SSH certificate issuance evidence is load-bearing or attributive (§9.6). Ruled attributive here on the argument that no control branches on the presence of a signing record; ops-warden asked for the ruling and the trade is genuinely two-sided, so it is flagged rather than settled.
  • Who marks an approval consumed, and at what point relative to the decision (§9.4). flex-auth notes the decision precedes the action and the action precedes consumption, so an allow rendered against an approval then never consumed, or consumed twice by a racing caller, is a gap neither engine closes alone. Needed before FLEX-WP-0017 T05.
  • Whether other §4 repositories are missing layer declarations; audit-core flagged its own absence and asked whether the catalog needs the same correction elsewhere.
  • Whether the gap register migrates from this standard into maturity-engine once that engine exists, leaving the standard to state the rules only.
  • Whether Tooling warrants subdivision between third-party and homegrown.
  • How a future role-engine divides responsibility with access-engine.
  • Whether declared gaps need an estate-wide register rather than per-repository declarations; ops-warden's warden route gaps is candidate machinery.
  • Whether non-security repositories adopt the same model. The determinism cut is not security-specific; if non-security Staff also may not hold runtime-dependent state, the estate gets one constitution rather than a security ghetto.
  • The rest of §9.8: split brain, partial PIP reachability, and clock skew beyond the two rules stated.
  • Publication integrity of the Taxonomy layer itself. This standard demands reconstructability of decisions while its own publication path has no digest, freeze, or rollback discipline.
  • The fitness verdict formerly at §19 now lives in net-kingdom/history/2026-08-29-layering-standard-assessment.md. A grade inside a standard of record becomes normative by adjacency and ages against the text it grades. Raised by access-engine. Its two substantive points remain live: observation in production is unstaffed (§12) and actuation has no surface (§9.2).
  • The working companion (net-kingdom/SECURITY-COMPANION.md, v0.2, root of the repository for onboarding) is the operative form of this statute. The statute governs on disagreement, and a disagreement is a finding. The v0.1 gap access-engine found — publish your stance map, but nowhere saying where, and no inventory obligation — is fixed in v0.2 §5.3.
  • How the Railiance operational axes meet this model beyond §20's first statement, which is deliberately minimal.
+
+

17Taxonomy artifacts

+

§6.2 says doctrine reaches a decision as an input claim or a versioned policy rule. As prose that is a rule a reviewer can apply. As an interface it does not exist, because nothing defines what a claim is. §11 calls itself mechanically checkable while resting on that gap.

+

Four artifacts are therefore required, owned by Taxonomy and versioned like any standard:

+
ArtifactContents
request-claim schemaidentity, tenant, zone stance, posture, approval, maturity, assurance — each with its issuer and freshness rule
~~decision-record schema~~moved to access-engine — see below
gap-record schemathe §5.3 fields — capability, intended_owner, blocked_on, review — plus the §13 state and owner-status
emission-cadence declarationthe expected rate a source publishes, so silence is a finding (§9.6)
+

Until these exist, §6.2 and §11 are reviewable but not compileable, and every engine invents its own claim shape at its own boundary.

+

The decision-record schema is not Taxonomy's. A decision record is the PDP's output artifact — the one thing in the estate only access-engine produces — and §2 keeps ownership in the producing repository's own INTENT.md. Taxonomy authoring the schema for an artifact only one engine emits would invert the ownership rule this standard applies everywhere else. access-engine publishes it as a contract; Taxonomy holds only the shared field vocabulary the claim schema references.

+

Raised by access-engine against its own interest — the same §2 argument it used to decline authentication evidence, applied where it takes work on rather than off. Symmetry of that kind is what makes the ownership rule credible.

+

The emission-cadence declaration has a drafter. kings-guard is its only consumer, cannot implement silence-as-signal without it, and has offered to draft it against qonto-assistant and hand it to whichever Taxonomy repository takes ownership — rather than inventing a local shape, which is the drift §17 exists to prevent. Accepted as a draft; ownership still rests with Taxonomy.

+

Ownership is proposed, not assigned. info-tech-canon holds ecosystem-wide semantic contracts and net-kingdom holds NetKingdom standards of record; the split between them for these four artifacts is theirs to draw, and §2 keeps ownership in the owning repository's INTENT.md. Neither has assented.

+
+

18Composition with the sibling standards

+

The related-standards list has been frontmatter and little else. If the following sentences cannot be written, the list is decoration — so they are written here rather than in the siblings.

+

Zone stance (security-zones_v0.1). A zone answers which scrutiny a workload has qualified for; membership is zone-engine's. The effect of a zone on a decision belongs in a versioned access-engine policy package, never in registry content — that ruling is zone-engine's §5, and §6.1 is its generalization. Zone stance therefore enters a decision as a claim on the request or a rule in the package, and a decision that turned on a zone must name the package version that read it.

+

Tenancy posture (tenancy-posture_v0.1). Posture is a bounded security-state input, published by its owner and never a privilege source (§8). It enters as a claim, carries its own freshness, and the §8 asymmetry binds it: posture may tighten a decision and may never loosen one. A posture too stale to trust is a missing claim, and a missing claim is not permission.

+

Credential lifecycle (credential-management_v0.2). Issuance, rotation, and revocation are secrets-engine's and OpenBao's, downstream of a decision — a credential is an artifact of authority, never its source. A lifecycle event becomes an input to a later decision as a claim (this credential is current, this lease is bound to this task), never a side channel that changes an outcome without appearing in the decision record. Revocation visibility is bounded by §9.7.

+

Each of the three composes the same way, which is the point: facts arrive as claims, effects live in versioned policy, and anything that changes an outcome appears in the decision record.

+
+

20The Railiance interaction boundary

+

Operations is not NetKingdom's. Workload operations are organized by Railiance, whose framework repository is railiance-master, and NetKingdom provides the security and approval framework those operations consume. This section states the boundary as it stands today. It is expected to evolve, and it is written here so that evolution is visible rather than inferred.

+

Definitions are railiance-master's and are restated, not authored, here.

+

20.1 What Railiance organizes

+

A workload is a managed running deployable. Human commands, credential patterns, broker actions, approvals, and infrastructure resources that are not themselves deployables are not workloads — which is why an approval object (§9.4) is not a Railiance axis and never becomes one.

+

Every workload is operated through four composable axes, each answering a different question about the same workload:

+
PrefixAxisQuestion
railiance-*ownershipWho owns this capability?
rail-*execution contractHow does this workload run?
rapp-*managed packageWhat exactly is packaged and operated?
reef-*substrateWhere is it bound, and as what operational reality?
+

rein-* is not a fifth axis. Reins are glas-harness agent-harness backends; the name echoes rail-* analogically, not taxonomically. Agentic session semantics — session loops, tool policy, harness routing, model selection — belong to glas-harness. When a rein is installed and operated as a managed service it is a workload like any other, packaged and bound through the four axes above.

+

20.2 What holds today

+

For any Railiance consumer of NetKingdom security, without exception:

+
  1. Authorization decisions come from access-engine and from nowhere else (§6).
  2. Approvals are objects in approval-engine, consumed as claims (§9.4).
  3. Credentials are materialized by secrets-engine after a decision, never as a substitute for one.
  4. Evidence goes to audit-core under the bound in §9.6.
  5. Anything causing a protected side effect is PEP-shaped and owes the four obligations in §6.4 — including a published unreachable-engine stance in the §13.1 register.
+

20.3 What is not settled

+

The mapping between the axes and this model is deliberately thin, because guessing it would be worse than admitting it:

+
  • A rapp-* is the most likely resource a decision is rendered about, but nothing states its identity form in a request claim.
  • A rail-* describes how a workload runs and is therefore where PEP shape is most likely to live — but §6.4 obligations attach to repositories, and a rail is a contract, so whether a rail can carry an obligation is unwritten.
  • A reef-* answers where a workload is bound, which is adjacent to a security zone (security-zones_v0.1) without being one. zone-engine records that a reef capping availability for everything bound to it is a canon composition problem. That composition is unwritten.
  • The railiance-* ownership axis names who owns a capability, which is adjacent to the principal a decision is rendered for. Adjacent is not equal, and no rule connects them.
  • glas-harness and reins hold tool policy and session semantics for agents, while §3.4 rule 2 holds that an agent acts only through a conduit or an engine API. Those two must compose, and neither side may treat its own half as sufficient. That seam is the most consequential of the five, because it is where "tool availability is not permission" is actually enforced or lost.
+

20.4 How this boundary changes

+

An interaction boundary between two frameworks is owned by neither alone. Changes to §20 require assent from railiance-master for the axis definitions and from glas-harness for the session and tool-policy seam, on the same terms as any other boundary in this standard (§10). NetKingdom states what a consumer owes; it does not define what a rail, rapp, reef, or rein is.

+
netkingdom-security-layer-model-v0.7 · · acceptednet-kingdom · canon/standards/security-layer-model_v0.7.md · ce198fc2905687ea90a2892346b6860281ac87f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-security-scenario-composition-v0.1.html b/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-security-scenario-composition-v0.1.html new file mode 100644 index 0000000..989b320 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-security-scenario-composition-v0.1.html @@ -0,0 +1,239 @@ + + + + +NetKingdom Security Scenario Composition v0.1 + +
netkingdom-security-scenario-composition-v0.1 proposed net-kingdom reviewed 2026-08-23generated from canonical source — do not edit

NetKingdom Security Scenario Composition v0.1

Source: net-kingdom · canon/standards/security-scenario-composition_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2026-11-23

01Purpose

+

This contract defines the deterministic, plan-only boundary between a requested NetKingdom capability set and the independently owned playbook entry points that can realize it. It consumes conformant Playbook Capability Contract v0.1 declarations and produces an owner-routed responsibility, trust, parameter, and readiness handoff.

+

Composition answers what is selected, in which trust order, with which safe parameters, and who must execute and evidence it. It does not run a playbook, mint a credential, infer authority, or declare a runtime ready.

+
+

02Authority boundary

+
  • NetKingdom owns scenario intent, provider selection, parameter-policy checks, trust sequencing, and the composed responsibility map.
  • The declaration owner owns the playbook or stable entry point, execution, rollback, and readiness evidence.
  • Railiance retains deployment execution for Railiance-owned declarations.
  • A composed plan is not authorization to execute. Any approval, custody, credential, or change-window gate named by an owner remains in force.
+
+

03Scenario input

+

Scenario files conform to canon/schemas/security-scenario_v0.1.schema.json:

+
id: scenario:c0-local-identity-reference
+authority: netkingdom
+initial_trust:
+  - bare_host_trust
+requires:
+  capabilities:
+    - c0.bootstrap-identity
+providers:
+  c0.bootstrap-identity: net-kingdom.local-identity
+parameter_overrides:
+  net-kingdom.local-identity:
+    bootstrap_username: bootstrap-admin
+

authority uses the Playbook Capability Contract vocabulary: platform, netkingdom, or tenant. Parameter sensitivity and tuning-authority rules are applied before a plan is emitted.

+

initial_trust lists trust states established outside this composition. The composer never assumes an initial trust state. A required state must be present there or be satisfied by an earlier selected declaration.

+

providers pins a required capability to an exact declaration id. A pin is mandatory when more than one valid declaration provides the capability. A pin may not name an unrequested capability or a declaration that does not provide the keyed capability.

+
+

04Fail-closed selection rules

+

Composition fails when any of the following is true:

+
  • a declaration is invalid or declaration ids are duplicated;
  • a capability is unknown, duplicated, or has no provider;
  • multiple providers match and the scenario does not pin one;
  • a provider pin does not match the requested capability;
  • an override targets an unselected declaration or unknown parameter;
  • an override violates type, constraint, sensitivity, or tuning authority;
  • a required parameter has neither a default nor an override;
  • a required trust state cannot be established without a cycle or inference.
+

Selection order never resolves ambiguity. Filesystem order, catalog order, lexical order, and prior deployment state are not provider authority.

+
+

05Trust sequencing

+

The composer starts only with the scenario's explicit initial_trust set. It then selects the lexically first eligible declaration, where eligible means all of that declaration's required trust states have already been established. After the step, and only for composition purposes, the declaration's satisfied states become available to later steps.

+

Lexical ordering makes independent eligible steps reproducible; it does not grant one provider precedence during selection. If no remaining declaration is eligible, composition fails and reports the unresolved trust states.

+

Readiness checks attached to a satisfied state are obligations for the owning executor. They are not marked satisfied merely because the plan contains them.

+
+

06Composition output

+

A successful output has apiVersion: netkingdom.io/security-scenario-composition/v0.1 and kind: SecurityScenarioComposition. It contains:

+
  • the requested capabilities and exact selected declaration ids;
  • effective parameter values with their source, sensitivity, and tuning authority;
  • ordered execution handoffs containing owner, repository, entry point, required trust, produced trust, and readiness obligations;
  • a flattened responsibility map attributable to declaration ids;
  • the final planned trust-state set;
  • an explicit execution.permitted: false boundary.
+

The output is non-secret planning material. Declarations and scenarios must use secret references rather than secret values as required by the Playbook Capability Contract.

+
+

07Conformance

+

Use the canonical tool:

+
python3 tools/security-scenario-composer/security_scenario_composer.py \
+  --scenario <scenario.yaml> <declaration.yaml> [<declaration.yaml> ...]
+

Exit zero means the declarations and scenario compose deterministically. It does not mean the plan was executed or its readiness evidence was observed.

+
netkingdom-security-scenario-composition-v0.1 · · proposednet-kingdom · canon/standards/security-scenario-composition_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-security-zones-v0.1.html b/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-security-zones-v0.1.html new file mode 100644 index 0000000..49bea2c --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-security-zones-v0.1.html @@ -0,0 +1,320 @@ + + + + +NetKingdom Security Zones v0.1 + +
netkingdom-security-zones-v0.1 proposed zone-engine reviewed 2026-08-22generated from canonical source — do not edit

NetKingdom Security Zones v0.1

Source: net-kingdom · canon/standards/security-zones_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2026-11-22

01Purpose

+

A security zone is a named workload-admission standard. It answers which scrutiny a workload has qualified for; control-owner policy then answers what a particular control does in that zone. A zone is not a repository label, a credential lane, a network segment, a reef, or a temporary exception.

+

This standard is a sibling of tenancy-posture_v0.1. It owns zone identity, membership, admission, resolution, and the time-boxed exception lifecycle. flex-auth remains the only PDP for decisions it renders. Every other control continues to be owned and evaluated at its existing enforcement point.

+
+

02Authority and conformance

+

Authority is split deliberately:

+
Fact or ruleAuthority
Workload identity and requested membershipWorkload's responsible repo
Zone identities and admission standardsThis standard, owned by zone-engine
Per-zone stanceOwner of the control that renders the effect
Failure behavior when a dependency is unavailableOwner of the PEP
Publicationnet-kingdom canon
+

Conformance is accuracy, not altitude. A lower zone declared with evidence is conformant. A stricter zone that the workload cannot evidence is not. Changing membership to evade a control is not an exception mechanism.

+

The workload is the sole zone policy subject. It includes independently governed application, automation, maintenance, and operational/control-plane execution units. The workload executing a tunnel, credential broker, policy service, or maintenance operation requires identity; the tunnel, grant, lane, pattern, action, human, or agent does not become a workload merely because a control observes it. Those retain native typed identity and caller/resource context.

+
+

03Resolution is authoritative

+

The stable workload_identity.name is the join key. Runtime principals, resources, credential lanes, and controls reference it explicitly. A resolver MUST NOT infer membership from repository ownership, paths, lane types, actor classes, environment, criticality, reefs, organization posture, or naming conventions.

+

For a managed deployable, the authoritative cross-repository reference is the Repo Manager v1 tuple (rapp_id, workload_identity.name), optionally narrowed by deployable. A catalog also declares whether its subject is workload applicable. Resolution returns both the reference result and admission result:

+
  • satisfied — the workload has an authoritative identity and evidence meeting the declared zone's floor; effective_zone is the declared zone;
  • unsatisfied — identity and membership are declared, but evidence is below the admission floor; effective_zone is unknown;
  • unknown — identity, membership, or a required floor/evidence input cannot be resolved for a workload-applicable subject; effective_zone is unknown;
  • not-applicable — the owning catalog explicitly establishes that the subject is an action, actor, lane, pattern, or resource rather than a workload; no zone is resolved and the control uses that subject's native identity.
+

unknown is a result, not a zone. A control MUST declare an explicit treatment for it. That treatment may deny, escalate, or use a reviewed build-stage rule; it never manufactures membership and never grants an exception.

+
+

04Zone catalog

+

The initial catalog follows the estate's existing M0–M3 maturity ladder and adds one non-monotone continuity band required by ops-warden ADR-0006.

+
Zone idAdmission floorEnvironment/data boundaryPurpose
z0-experimentalM0 promotion evidenceSynthetic only; no real credential or user dataExperiments where advisory controls preserve iteration speed
z1-operationalM1 promotion evidenceProduction secret handling for at most internal dataEarly operational workloads with an owned front door
z2-protectedM2 promotion evidenceprod; at most confidential dataProduction workloads requiring review, SLO/on-call, and incident readiness
z3-criticalM3 promotion evidenceprod; at most restricted dataCritical or regulated workloads with the strongest normal failure behavior
z2-continuityM2 plus dependency/recovery evidenceprod; at most confidential dataFoundational access or recovery workloads where fail-closed dependency cycles would cause an outage
+

z2-continuity is a sibling of z2-protected, not a relaxation of its admission floor. It exists because enforcement stance is not monotone: a tunnel or credential-issuance path can require production scrutiny while deliberately remaining fail-open for one availability-sensitive control. Placement on a reef is never evidence for any zone.

+

4.1 Context floor

+

The declared zone must be at least as strict as the workload context requires. The context floor is the maximum of every resolved input:

+
criticalityMinimum maturity
lowM0
mediumM1
highM2
criticalM3
+

Data-class floors are consumed from the canon-owned classification mapping; the current synthetic → M0, internal → M1, confidential → M2, and restricted → M3 mapping is already used by ops-warden. An absent or unresolved floor makes admission unknown. In particular, public is disclosure policy and synthetic is data origin: they are not aliases, and this standard does not invent a floor while info-tech-canon's ruling is pending.

+

organization_posture may select a versioned control profile, but it never changes identity, membership, or admission.

+
+

05Stance and failure-mode model

+

Every owner-qualified control publishes a total mapping over all zone ids plus unknown. There is no implicit default.

+
  • enforced — the control's deny or escalation effect changes the outcome;
  • advisory — the control evaluates fully and records the outcome it would have produced, but does not block;
  • exempt — the control is deliberately not evaluated for this zone and emits the versioned exemption reason.
+

Failure mode is separate and PEP-side:

+
  • fail_closed — an unavailable or invalid evaluator produces the control's safe blocking/escalation outcome;
  • fail_open — the PEP proceeds and records that the control was unavailable.
+

exempt has no failure mode. A local control with no remote dependency uses fail_closed to describe evaluator errors. Changing stance is a policy-package change owned by the control owner, not a membership change.

+

5.1 Initial build-stage control profile

+

This profile is the v0.1 proposal for the first consumer. It is not stored in a workload's zones: declaration. flex-auth owns the pre-sign rows; ops-warden owns the other rows and every PEP failure mode.

+
Zoneflex-auth pre-sign stance / ops-warden PEPagent high-risk read boundarywarden plan zone rule
z0-experimentaladvisory / fail_openenforced / fail_closedadvisory; evaluator failure cannot produce autonomous
z1-operationaladvisory / fail_openenforced / fail_closedadvisory; evaluator failure cannot produce autonomous
z2-protectedenforced / fail_openenforced / fail_closedenforced; minimum founder_required when the zone rule matches
z3-criticalenforced / fail_closedenforced / fail_closedenforced; minimum founder_required when the zone rule matches
z2-continuityenforced / fail_openenforced / fail_closedenforced; minimum founder_required when the zone rule matches
unknownadvisory / fail_open under the versioned build profileenforced / fail_closedenforced; never autonomous from zone evidence
+

The unknown pre-sign treatment is an explicit organization-build policy, not a permissive membership default. It must change through a versioned control profile when the organization posture graduates.

+

The agent read boundary stays enforced in every zone: build-stage permissiveness does not extend to disclosing high-risk credentials. For a missing lane risk:

+
  • z0-experimental may resolve to standard only when admission proves the lane can expose synthetic material exclusively;
  • z1-operational and z2-protected resolve to at least high;
  • z3-critical resolves to critical, treated by the boundary as at least high; and
  • unresolved membership resolves to at least high.
+

An explicit grade always remains preferable. accepted is an acceptance record, not a risk grade.

+
+

06Declaration in tenancy.yaml

+

For a single-service declaration, zones: is a sibling of tenancy: and workload_identity. For a services: declaration, both workload_identity and zones occur inside the same service entry. A multi-service file MUST NOT use a top-level zones: block.

+

Every managed running deployable has an authoritative rapp-*/declarations/rapp.yaml. Its workload_identity.declaration_ref points to that declaration, and consuming catalogs reference it using the Repo Manager v1 tuple. A pre-rapp deployable is migration debt and resolves unknown. An independently governed operational execution unit that is not a managed deployable may declare directly in its responsible repo's tenancy.yaml; this does not turn a human action or infrastructure resource into a fictional rapp or workload.

+
schema_version: "0.1"
+framework: netkingdom-tenancy-posture
+service: ops-bridge-tunnel
+role: operational-access-path
+workload_identity:
+  name: ops-bridge-tunnel
+  kind: operational-control-plane
+  responsible_repo: ops-bridge
+  identity_bindings:
+    - scheme: ssh-certificate
+      authority: ops-warden
+      subject: agt-ops-bridge
+      principal_type: agent
+      environment: prod
+tenancy:
+  # tenancy-posture_v0.1 content omitted
+zones:
+  standard: security-zones_v0.1
+  membership: z2-continuity
+  responsible_party: ops-bridge
+  justification: foundational tunnel path must retain availability under PDP loss
+  context:
+    maturity: M2
+    criticality: high
+    data_classification: confidential
+  evidence:
+    - ref: docs/evidence/ops-bridge-tunnel-zone.md
+      supports: [M2, continuity-dependency, recovery]
+  reviewed: "2026-08-22"
+  review_due: "2026-11-22"
+

The zones: block contains only membership evidence. It MUST NOT contain control stance, failure mode, organization posture, or exceptions.

+

Required fields are:

+
  • standard — exactly security-zones_v0.1;
  • membership — one zone id from §4;
  • responsible_party — the party answering for this membership;
  • justification — why the zone fits the workload's actual context;
  • context — the evidenced maturity, criticality, and data_classification used for admission. A managed workload's latter two values must agree with its resolved rapp projection; n/a requires an evidence-backed reason;
  • evidence — one or more references and the admission facts each supports;
  • reviewed and review_due — ISO dates, with review due after review.
+

Permanent membership changes are reviewed source changes. A change to a lower floor also records its reason and approver in the change review. Temporary relaxation uses an exception and never changes membership.

+
+

07Compilation and resolved view

+

Compilation produces a workload-addressable resolved record. At minimum it contains:

+
workload_id: ops-bridge-tunnel
+workload_ref:
+  applicability: applicable
+  rapp_id: null                 # required for a managed deployable
+  name: ops-bridge-tunnel
+  deployable: null              # optional for a managed deployable
+identity_binding: ssh-certificate/ops-warden/agt-ops-bridge
+declared_zone: z2-continuity
+admission: satisfied
+effective_zone: z2-continuity
+membership_revision: sha256:<digest>
+guarantees:
+  - authoritative-workload-identity
+  - explicit-zone-membership
+  - non-inferred-resolution
+  - enforcement-time-exception-expiry
+controls:
+  - id: flex-auth/pre-sign
+    policy_owner: flex-auth
+    stance: enforced
+    pep_owner: ops-warden
+    failure_mode: fail_open
+    policy_ref: <versioned-package>
+

The membership_revision covers the authoritative workload binding, zones: block, and source revision. Control results include their policy/profile version and any active exception id and expiry. This is the machine-readable answer to “which zone is this workload in, and what applies there?” It may be compiled into existing consumer artifacts; it is not a synchronous zone-engine lookup.

+

For managed deployables, compilation consumes the exact Repo Manager reference projection:

+
workload_ref:
+  applicability: applicable
+  rapp_id: rapp-issue-core
+  name: issue-core
+  deployable: issue-core  # optional
+

The owning catalog uses applicability: not-applicable for a native non-workload subject. Absence of either applicability or an expected reference is unknown, not not-applicable. Zone-engine consumes these outcomes; it does not parse a path or repository name to repair them.

+

For flex-auth's pre-sign control, the governed workload is the target of the certificate or grant, so the compiler writes workload_id, security_zone, security_zone_admission, and security_zone_revision on the resource attributes. Caller identity remains in the subject. A control that governs the requesting workload must declare that role explicitly and use separately named caller-workload attributes.

+

The dormant trust_zone: platform constant is not security-zone membership and MUST be retired before adoption. The new concept is named security_zone; the two fields must not coexist as competing zone sources.

+
+

08Membership-change observability

+

A membership change becomes effective only through a reviewed declaration and a newly compiled artifact. The compiler emits the source and membership revision, rejects ambiguous identities, and reports additions, removals, and changes against the preceding snapshot. Controls expose the membership revision in their decision or verdict evidence.

+

A zone that can be widened by editing an unversioned runtime label is not conformant.

+
+

09Time-boxed exceptions

+

The normative lifecycle is the ZONE-WP-0001-T04 decision in zone-engine/docs/exception-lifecycle-2026-08-22.md: only the control owner's designated authority grants a named-workload, named-zone, named-control relaxation within a declared maximum duration. Enforcement applies it only for not_before <= now < not_after; invalid or unevaluable records are inactive, expiry restores the base rule automatically, and no minted credential, lease, or session may outlive the exception.

+

Exceptions live with the versioned control policy or PEP configuration and are evaluated where their effects occur. This requires no zone-engine runtime.

+
+

10Adoption

+

Net-kingdom published this standard at revision 337484a. Adoption requires:

+
  1. flex-auth and ops-warden accept the initial control profile or publish a versioned replacement with total zone and unknown coverage;
  2. at least two workload owners declare authoritative identities and zones;
  3. a third consumer compiles or reads the resolved view; and
  4. ops-warden retires policy.enabled and the dormant trust_zone constant in the same migration.
+

All four gates were met on 2026-08-22. The owning zone-engine repository records the exact consumer revisions, tests, resolved membership digests, and live caller decision in docs/evidence/security-zone-adoption-2026-08-22.md.

+
netkingdom-security-zones-v0.1 · · proposednet-kingdom · canon/standards/security-zones_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-tenancy-posture.html b/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-tenancy-posture.html new file mode 100644 index 0000000..1aa5d33 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/netkingdom-tenancy-posture.html @@ -0,0 +1,496 @@ + + + + +NetKingdom Tenancy Posture v0.1 + +
netkingdom-tenancy-posture proposed · draft-14 net-kingdom reviewed 2026-08-23generated from canonical source — do not edit

NetKingdom Tenancy Posture v0.1

A framework for describing, holding and improving multi-tenancy — including where we are not there yet.

Source: net-kingdom · canon/standards/tenancy-posture_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8

Review due: 2027-02-23

Status

+

Proposed, draft-13; ratification-ready. Relocated from the-custodian/canon/architecture on 2026-08-17: multi-tenancy is part of the IT-security framework NetKingdom provides, so this framework belongs in NetKingdom canon beside the IAM Profile and the tenant-engine boundary contract, not in the work-factory canon.

+
  • draft-1 proposed a single model with fixed characteristics. Rejected: it could not describe a repo that is not there yet.
  • draft-2 reframed to graduated levels per axis. Externally corroborated (§16), but four of its statements were wrong and one thing it needed was missing.
  • draft-3 applied those corrections, added the retention axis, and recorded an adoption stance.
  • draft-4 closed the two gaps draft-3 left open: R4 had no mechanism beyond waiting, and the noisy-neighbour evidence artifact asserted something shared infrastructure cannot provide.
  • draft-5 relocated to NetKingdom and renamed the dimensions from planes to axes, because the word was already taken (§0).
  • draft-6 applied tenant-engine's review: five changes, including an axis that did not fit its data shape.
  • draft-7 applies audit-core, railiance-platform and flex-auth. Eleven further changes, two of them corrections to statements this document made as fact about other repos. Every posture I guessed was too generous, on every repo that has now self-reported.
  • draft-8 applies adaptive-pricing's review, the last of the six, and the consistency review across all declarations. It adds the missing availability axis, a canonical declaration schema, explicit authority for tier assurance, retention/placement coupling, downgrade propagation, and honest sanctioned customer language. It also corrects the distinction between an implemented control and an evidenced current level.
+
  • draft-9 answers zone-engine's ZONE-WP-0001-T01. It rules that enforcement stance is not a seventh axis (Decision 5.6) while reserving zones: in tenancy.yaml so the estate keeps one declaration surface, and it records the reef/P/V reconciliation as an open defect of this document rather than of the repo that noticed it (Decision 8.4).
  • draft-10 answers zone-engine's ZONE-WP-0001-T03. It keeps the workload as the sole security-zone policy subject, makes operational and control-plane execution units part of that term, and requires unresolved workload identity or membership to remain unknown without zone inference (Decision 5.6.1). It also closes the textual reef/P boundary while leaving the substrate-provider declaration and mechanical V join as implementation work (Decision 8.4.2).
  • draft-11 makes that ruling declarable. A zones: block now requires an authoritative workload_identity beside it, including for non-rapp operational workloads; multi-service files carry both per service. Missing identity remains absence rather than a guessed join (Decision 5.6.2).
  • draft-12 reconciles that declaration with RMGR-ADR-004 and the published security-zones_v0.1 proposal. Managed deployables use their authoritative rapp declaration and the exact Repo Manager reference tuple; independently governed operational execution units that are not managed deployables may declare locally. Native actions, actors, lanes, patterns, and resources are explicitly not-applicable, while omitted or unresolved workload references remain unknown (Decision 5.6.2).
  • draft-13 advances the audit-core worked example from E1 to E2 after bounded adversarial run WH-ENG-20260822-AUDIT-E2-03 supplied the artifact required by §13.2. The claim remains explicitly bounded and freshness-dated: the attempted cross-tenant attacks did not work; this is not a universal isolation proof.
  • draft-14 makes evidence freshness and remediation ownership declarable. Adversarial evidence may now carry its observation and expiry timestamps, bounded scope, responsible repository, and replacement action. A separate proposal-only evaluator treats absent authoritative owner or freshness as unknown; it does not infer either or mutate the declared posture.
+

Reviewed by all six. The score: six repos found three live defects in their own code by reading the ladders — tenant-engine's unfiltered event accessor, audit-core's unfiltered read path, flex-auth's unauthenticated /v1/check — and railiance-platform found apps-pg running with no backup configured at all while writing its §10.2 disclosure. The framework changed to fit the repos; no repo was told to fabricate a posture.

+

Informed by five external research digests plus their index in the-custodian/research/2026-08-17-adr008-*, which carry full citations for the external claims made here.

+
+

00Terminology: axes, not planes

+

docs/platform-identity-security-architecture.md — accepted, 2026-07-23 — already uses plane for a trust and deployment layer: the bootstrap plane, the platform control plane, and tenant planes. That meaning is established, ratified, and owned by this repo.

+

Drafts 1–4 of this document, written elsewhere, used plane for something different: an independent dimension of concern. Two incompatible senses of one word inside one canon is exactly the concept-ownership collision the estate has been careful about elsewhere, and the newcomer yields.

+

This framework therefore describes six axes. They are orthogonal to NetKingdom's planes, not a subdivision of them:

+
  • A plane is where something runs and what trust it carries — bootstrap, platform control, tenant.
  • An axis is which property of tenancy is being described — identity, authorization, enforcement, placement, retention, availability.
+

A workload in the tenant plane has a position on all six axes. A platform control plane service does too. The two vocabularies compose and neither replaces the other.

+

The rename is also an improvement. A posture vector is literally a point in six-dimensional space, and "axis" says that where "plane" did not.

+
+

01Context

+

Drafts 1–4 opened by claiming the estate "has never written down what it is building". Relocation proved that wrong, and the correction is worth keeping visible: docs/platform-identity-security-architecture.md has described the trust model, the tenant model and a capability progression since 2026-07-23. The accurate claim is narrower — what was missing is a way to say how far a given service has got, and to hold several answers at once. Seven documents cover slices of the subject and none of them does that:

+
DocumentCoversStatus
iam-profile_v0.3 (NetKingdom)Tenant identifier shape, tenant_roles claim, staleness rulesRatified
tenant-engine-boundary-contract_v0.1 (NetKingdom)Who owns tenant records, roles, plan assignmentRatified
business-app-service-contract_v0.1 §1 (Custodian)Business apps: instance-per-client, tenant-keyed dataRatified
rapp-postgres ADR-0001Consumer + tenant isolation in PostgreSQLProposed, governs one repo
rapp-postgres ADR-0002Per-consumer retention and the erasure horizonProposed, governs one repo
shared-platform-relational-storage_v0.1The stacked-boundary gapRouted 2026-08-10, still unratified
platform-identity-security-architecture (NetKingdom)Trust model, planes, tenant model, capability progressionAccepted 2026-07-23
+

This document is downstream of that architecture and must not restate it. It answers one question the architecture leaves open: given the model, where is this particular service today, and how would anyone know?

+

Four failures existed when drafting began.

+

The gap was diagnosed once and the fix stalled. The v0.1 draft was written to fill this hole and has sat unratified in neither canon directory. §20 attaches a ratification path so this one does not join it.

+

Placement was owned by nobody. user-engine-pg and target-revenue-pg are dedicated; apps-pg, net-kingdom-pg, platform-pg, state-hub-db and forgejo-db are shared. Both live, neither written down. tenant-engine raised this with railiance-platform on 2026-08-16. Draft-8 resolves the authority split in §8.2.

+

Two contradictory defaults were already ratified. Business apps get instance-per-client; platform services pool. Nothing says which shape a new service takes, and no definition separates the categories. Decision 4.4.1 now supplies the default; §19.4 retains the missing classification rule.

+

There is no honest way to describe a repo that is not there yet. The estate absorbs repos with weak or absent tenant separation. Today such a repo is simply non-conformant, leaving it two bad options: misrepresent its posture, or stay outside the framework.

+
+

02What this document is

+

A framework, not a model. It specifies no single correct implementation. It supplies terminology (§3, §4), a declaration (§5), a conformance rule (§6), methodology (§12), and evidence definitions (§13).

+

A service is conformant when its declared posture is accurate and its trajectory recorded. A service is non-conformant when it claims a level it cannot evidence — regardless of how high or low that level is.

+
+

03Six orthogonal axes

+

"Is this multi-tenant?" is treated as one question. It is six, and they are independent:

+
AxisQuestionVocabulary owner
Identity (I)How is a tenant named and validated?tenant-engine / IAM Profile
Authorization (A)How is a request bound to the tenants it may act for?flex-auth
Enforcement (E)Where, mechanically, is the tenant boundary enforced?This framework
Placement (P)Which substrate holds a tenant's data?railiance-platform
Retention (R)How long does data persist, and how is it erased?The storage platform; policy by the consumer
Availability (V)What failure can the complete service path survive, and within what recovery objective?The delivering service; substrate facts by its providers
+

Conflation produces errors today. rapp-postgres's PostgresConsumer carries tenantIsolation: consumer-service-boundary — an E-axis fact in a P-axis artifact, reading as though storage enforces something it does not. The "dedicated versus shared" argument mixes P (capacity, blast radius) with E (correctness).

+

The axes are separated precisely so each may sit at a different level.

+

Decision 3.1: every document, declaration and plan tier that says "isolation" MUST name which axis it means.

+

Decision 3.2: the axes couple at their tops and the couplings MUST be stated where they apply, not used to argue the axes are one:

+
  • E4 is reachable only at P3 or above.
  • R's erasure horizon is bounded below by P — on shared substrate, a consumer's horizon is the instance maximum (§4.5).
  • R4 by key destruction is bounded by the key boundary, which is an E-axis property. Shredding a single tenant's data requires the application to encrypt under a per-tenant key before writing; the storage platform cannot supply it. Reaching the top of the retention ladder is not a retention project.
  • V composes as the minimum across the critical request path, not the maximum of its components. A replicated application on a single-instance database is not V2. A tested degraded mode may remove a dependency from that path, but the bypass itself is part of the V evidence.
+

Decision 3.3 — scope. The P and R ladders describe a service's primary datastore. The V ladder describes the service's complete critical request path, including providers it synchronously depends on. Caches, search indices, message queues and background jobs are named leak surfaces in the external baselines and are assessed separately, not silently covered by a datastore level. A declaration names material secondary stores and asynchronous paths as exceptions rather than implying that one vector proves them safe.

+
+

04Graduated levels

+

Each axis carries an ordered ladder. Higher is stronger, not better: the right level is the one a service can evidence and its risk warrants.

+

4.1 Identity (I)

+
+
Identityaxis I
I0No tenant concept. Data not attributable to a tenant.
I1A local tenant notion exists but is not canonical, or the tenant is taken from the request rather than from a verified token.
I2Canonical identifiers, bound at the identity provider and carried as a verified claim, and verified by this service on its own inbound calls.
I3I2 plus capability roles honoured, with live tenant-engine re-query for privileged, destructive, credential-vending or aal2-class decisions.
—Ladder ends at I3.
+
+

I1 now explicitly absorbs request-supplied tenant identifiers. "Never trust client-supplied tenant IDs without validation" is a named anti-pattern; a service reading the tenant from a header is at I1 however canonical the string.

+

An axis is assessed on a service's own inbound surface, never on its authority over the concept. tenant-engine is the source of existence for tenant records and is nonetheless at I1, because it takes the acting identity from the request body rather than from a verified token. Draft-5 conflated these by naming the authority inside the I2 definition, which made the level describing canonical identity unclaimable by the service that provides it. Corrected on tenant-engine's review — a reader would otherwise assume the authority must be at I2 by definition.

+

business-app-service-contract §2.1 sets app-local accounts as the v1 baseline for business apps — a sanctioned low level with recorded triggers for moving up. That is the pattern this framework generalises.

+

4.2 Authorization (A)

+
+
Authorizationaxis A
A0No authorization, or tenant context not carried.
A1Ad-hoc checks scattered through handlers.
A2A single local authorization boundary; tenant context bound once, centrally.
A3Decisions delegated to flex-auth as PDP, with live re-query where the IAM Profile requires it.
A4A3 over a standard PDP interface (OpenID AuthZEN Authorization API 1.0), so the decision point is swappable and the enforcement point is not coupled to one engine's request shape.
+
+

This ladder describes enforcement points. A decision point cannot occupy A3 — "delegated to flex-auth" is not something flex-auth can do. A service that is a PDP declares two numbers: its own inbound level, and the maximum it enables for consumers. flex-auth reads A0, enables A3 — accurate, and considerably more alarming than A3, which is the point. Raised by flex-auth, whose absence from the §5 worked examples was this surfacing implicitly.

+

A4 is new. The specification reached Final in January 2026 and Keycloak shipped experimental support in May; the argument for it is interoperability — a swappable decision point and an enforcement point not coupled to one engine's request shape.

+

Correction from flex-auth's review: earlier drafts also justified A4 as ending the copying of action strings between repos. It does not. AuthZEN standardises the envelope — subject, action, resource, context, endpoint — and deliberately does not standardise the action vocabulary or the policy language. At A4, tenant.guardrail.set still has to be agreed and still gets copied. Those are two problems with different fixes, and the cheaper one is not A4: flex-auth's registry already carries action definitions per system and could serve them read-only. The vocabulary argument is withdrawn.

+

Internal service-to-service calls are in scope for this axis. "Skipping tenant validation for internal services" is a named anti-pattern and our estate is mostly internal calls.

+

Correction from flex-auth's review: earlier drafts asserted that flex-auth calls tenant-engine synchronously on the authorization path. That is not true. The adapter is built and complete and has no non-test caller, so the IAM Profile's live re-query exists and is unwired — which is also why flex-auth cannot reach I3. Built-and-unwired is the worst of the three states because it reads as capability.

+

The requirement, narrowed on their proposal because the original was too strong to be met and would have made tenant-engine a hard availability dependency of every decision in the estate:

+

Tenant context MUST be carried on every internal hop and MUST NOT be re-derived from a service identity. It MUST be revalidated against tenant-engine at least once per request chain — at the service that holds or mutates the tenant's data, or before a privileged, destructive, credential-vending or aal2-class decision, whichever comes first. A hop that neither holds tenant data nor makes such a decision may carry the context without revalidating it.

+

And carrying tenant context is worthless without an authenticated hop to carry it over. flex-auth found this in itself: it carries tenant context faithfully and cannot distinguish "user-engine asking on behalf of tenant X" from "any pod asking on behalf of tenant X".

+

4.3 Enforcement (E)

+
+
Enforcementaxis E
E0None. Data not tenant-keyed; separation incidental or absent.
E1Data tenant-keyed, filtering applied per query at call sites.
E2Filtering centralised at a single service-side choke point binding authenticated identity to permitted tenants.
E3E2 plus platform-assisted filtering: row-level security keyed on a tenant GUC set transaction-locally, or an equivalent enforced data-access layer.
E4Structural: the credential a workload holds cannot address another tenant's data at all. Requires per-tenant credentials and per-tenant substrate.
+
+

Correction from draft-2. Draft-2 described E3 as something "the application cannot trivially route around". That is false and it was this document overclaiming in exactly the way §6 prohibits. Any session can re-issue SET on a custom GUC, so an attacker with SQL execution can reset the tenant and read across the boundary. What E3 buys is precise, and the ladder must say so:

+
ThreatE1E2E3E4
A developer forgets a tenant predicate✗✓✓✓
A new code path bypasses the choke point✗✗✓✓
SQL injection reaching the connection✗✗✗✓
The application process is compromised✗✗✗✓
+

E3 is a strong control against accident — the common case, and the one that causes real breaches — and no control at all against compromise. Only E4 holds against both, because the credential itself cannot address another tenant's data.

+

Correction: E3 layers on E2, it does not replace it. External practice treats application-layer and database-layer filtering as complementary. A service that dropped its choke point on reaching E3 would be worse off, since E3 fails open under injection. Claiming E3 therefore requires the E2 evidence artifact as well.

+

Correction: the GUC is set transaction-locally. Draft-2 said "at pool checkout", which is session scope and the wrong instrument. Under a pooler in statement mode, SET leaks between clients and returns other tenants' rows — a failure that appears only under production concurrency and produces no error. Use SET LOCAL inside an explicit transaction.

+

Platform enforcement is a platform obligation. Reaching E3 requires the storage platform to offer the mechanism: provisioned policies, a documented GUC contract, and a probe. Where a consumer wants E3 and the platform has not supplied it, the gap is the platform's. §19.6 asks rapp-postgres to define that contract, which must carry FORCE ROW LEVEL SECURITY on every tenant table (without it the table owner bypasses policies silently, and ADR-0001 already established that our migration role owns the tables it creates), no BYPASSRLS on leased roles, SECURITY INVOKER for ordinary logic, and an EXPLAIN comparison because RLS disables functional indexes built on non-leakproof functions.

+

Not all data is tenant-keyed, and the ladder must not pretend otherwise. A registry whose rows are the tenants has no per-tenant predicate to scope a policy by; enforcing one would break the service's function rather than secure it. tenant-engine's tenants table is the worked example — key-cape enumerates it at token issuance and flex-auth queries it live, both of which are cross-tenant reads by design.

+

A service with mixed data shapes declares E-level plus a registry exception: the level its tenant-keyed tables hold, and a named list of tables excluded because they are registries rather than tenant data. The exception is part of the claim and is reviewable; an unnamed exception is an overclaim. Without this, mixed-shape services either overclaim or stay at E2 permanently, and tenant-engine declined to claim E3 on precisely that reasoning.

+

Default expectation for a new platform service: E2 at first serve, E3 recorded as target. Services whose cross-tenant exposure would be a reportable breach SHOULD target E3 or above.

+

4.4 Placement (P)

+
+
Placementaxis P
P0Shares a database with another consumer.
P1Database per consumer, shared cluster.
P2Dedicated cluster per consumer.
P3Dedicated cluster per tenant.
P4P3 plus separate region or jurisdiction.
+
+

Enforcement and placement are independent axes. Plotted together, with where each service actually sits — parenthesised entries are targets or defaults rather than current positions, and — marks a cell the coupling in §3.2 makes unreachable:

+
Enforcement →
E4
business app
E3
target
E2
tenant-engine
audit-core
E1
absorbed repo
E0
P0
P1
P2
P3
P4
Where a service sits todayTarget or defaultUnreachable at this placement
+

P0 → P1 → P2 is movement along the horizontal axis only. Those steps buy consumer isolation, capacity predictability, independent retention and a smaller operational blast radius. They do not raise the tenant boundary by one step. Only P3 makes E4 reachable. This is the most misusable fact in the framework and §11 governs how it may be described.

+

Decision 4.4.1: P1 is the default for platform services; P3 for client-facing business apps, as already ratified. A service unsure which it is must resolve that first (§19.4).

+

Decision 4.4.2 — placement scopes to data substrate. Identity-provider placement (realm-per-tenant versus Organizations) is the same silo/pool decision on a different substrate, is live in our estate, and is undecided. Realm-per-tenant carries a stated ceiling around 5–20 tenants, far below our target. Recorded here as a parallel question (§19.7), not folded into P.

+

4.5 Retention and erasure (R)

+

New in draft-3. Implemented abstractly by the storage platform for any dataset; policy is built on top of that interface by the consumer or its governance layer. Reference implementation: rapp-postgres ADR-0002.

+
+
Retentionaxis R
R0No retention or deletion position. Data kept indefinitely by default; no deletion path exists.
R1Platform default retention applies (N=30 days). The consumer has declared no requirement.
R2Retention declared as N days per dataset; the erasure horizon is published, and the consumer makes no promise shorter than it.
R3Policy-driven deletion: the consumer or its governance layer declares what is due, the platform sweeps whole datasets on that instruction and evidences each run.
R4Verified erasure: data proven unrecoverable across live storage, backups and derived copies, by one of the two routes below.
+
+

R4 has two routes and a service MUST name which one it uses.

+
RouteMechanismCost
Horizon-elapsedWait out the published erasure horizon; the data ages out of every retained copy.Available to everyone, proves little, and the wait is set by a co-resident's retention requirement rather than your own.
Key-destroyedEncrypt per entity, then destroy the key. Retained copies survive but are unreadable.Requires per-entity keys, strong encryption, and an auditable destruction record. Immediate.
+

Decision 4.5.3 — key destruction is not sufficient on its own. The key-destroyed route requires that no retained commitment reveals the erased content. Found by audit-core, and it is a general defect rather than a fact about them:

+
  • A SHA-256 over a canonical record whose fields are low-entropy — event type, actor, tenant, subject, timestamp — is a confirmation oracle. Anyone holding the hash can guess the payload, hash the guess, and confirm a match. Destroying the key does not make the content unrecoverable while that hash survives.
  • Shreddability is not retrofittable onto an integrity chain that commits to cleartext. It has to be built as encrypt-then-hash at accept time, with the chain committing to ciphertext. Retrofitting means rewriting the chain — the exact thing a tamper-evident log exists to make detectable.
+

So a service claiming R4 by key destruction must show that its retained commitments — hashes, chains, indexes, search keys — do not reveal what was erased. The remedies are an HMAC under a per-subject key that dies with the key, or a per-record salt destroyed alongside it. audit-core cannot reach R4 under its current design and targets R2; a fleet R4 target must exempt it explicitly.

+

Regulatory standing of the key-destroyed route, stated carefully because overclaiming here is worse than anywhere else in this framework. Data protection authorities have accepted key destruction as erasure where physical deletion would be manifestly disproportionate, and the practice is recognised under conditions — strong encryption, irreversible destruction, and an auditable record of it. The EDPB has not formally endorsed it as Article 17 erasure. A service reaching R4 by key destruction is making a defensible claim, not a settled one, and must say so rather than reporting a clean "deleted".

+

Three further properties.

+

The erasure horizon is the interval between deleting data and it ceasing to be recoverable from anything the platform holds. Deleting a row does not remove it from yesterday's backup. With an N-day window, deleted data remains recoverable for N days. That is the difference between "deleted" and "erased" and the estate had never written it down.

+

On shared substrate, retention is not per-consumer. Physical backup is instance-wide — one WAL stream, one window — so the instance retention is derived as the maximum across co-resident consumers, and every consumer's horizon is that maximum. A consumer declaring 7 days beside one declaring 90 gets 90. This is the retention analogue of ADR-0001's blast-radius disclosure: state the coupling rather than imply an isolation that is not there.

+

Retention is therefore a placement trigger. A consumer needing a horizon shorter than the instance floor cannot have one at P1. It moves to P2 for a reason with nothing to do with performance — which is exactly why it needs recording, since nobody looks for a retention argument when reviewing placement.

+

Decision 4.5.4 — a retention promise binds both R and P. A tier making a retention claim records an R minimum and a maximum erasure horizon in days. It also requires P2 or above unless its provider contract guarantees that the shared-substrate horizon stays within that maximum and rejects or notifies before a co-resident change would extend it. A bare R2 minimum is insufficient: at P1 another consumer can change the promise without changing the tier or its holder.

+

Deletion splits mechanism from policy. The platform deletes whole datasets on instruction and records an opaque policy reference it never interprets, so every deletion traces to what authorised it. Rows are not a dataset: row expiry is the consumer's own DML under its migration lease. Dropping a consumer's whole database is an operator-gated offboarding step, never a scheduled one.

+

4.6 Availability (V)

+

New in draft-8. adaptive-pricing found that §11 required availability claims to map to a minimum level while the framework supplied no availability vocabulary. Placement is not a substitute: a dedicated cluster can still be a single instance on a single node.

+
+
Availabilityaxis V
V0No availability or recovery position. Recovery is untested or depends on improvisation.
V1Restart or recreate recovery in one failure domain is documented and exercised. Interruption is expected; this is recovery, not failover.
V2Redundant instances provide automated service failover, with measured RTO/RPO; a shared failure domain or critical dependency may remain.
V3The complete critical path survives loss of one declared failure domain, with measured RTO/RPO from an exercise.
V4The complete critical path survives regional loss through tested multi-region failover, with measured RTO/RPO.
+
+

Decision 4.6.1 — V is end-to-end. A service declares the minimum across the components and synchronous providers required to serve the operation. An application with three replicas over a V1 database is V1. A status page or replica count is not evidence of a higher level.

+

Decision 4.6.2 — availability claims name the operation. A read-only degraded mode and a mutation path may have different V levels. Decision 5.2 applies: declare the paths and quote the minimum unless the customer-facing claim explicitly and unambiguously names the narrower operation.

+
+

05The posture vector

+

A service states one level per axis, plus a target, review dates, evidence and any exceptions. current is the highest evidenced level; a control present in code but still awaiting the evidence required by §13 goes in implemented, not in current:

+
schema_version: "0.1"
+framework: netkingdom-tenancy-posture
+service: example-service
+role: tenant-data-service
+tenancy:
+  current:     { I: 2, A: 3, E: 2, P: 1, R: 1, V: 1 }
+  implemented: { E: 3 }
+  target:      { I: 2, A: 3, E: 3, P: 1, R: 2, V: 2 }
+  reviewed: "2026-08-17"
+  review_due: "2027-02-17"
+  service_class: interactive
+  gap:
+    E: "RLS is implemented; the §13 E3 probe is still absent."
+    R: "Retention declared; erasure horizon not yet published to consumers."
+    V: "Automated failover is not implemented or exercised."
+evidence:
+  A3: "docs/evidence/authorization-denial.md"
+  E2: "docs/evidence/cross-tenant-review.md"
+  P1: "rapp-postgres/docs/evidence/isolation-2026-08-10.md"
+

Placement exceptions. Draft-2 assigned one P level per service, which cannot express the vertically partitioned model — most tenants pooled, some dedicated — that §11's isolation tiers require. A tier requiring P2 bought by three tenants would put the service at two levels at once, forcing an over- or under-claim. Placement is therefore declared as a default plus exceptions:

+
  placement_exceptions:
+    - tenants: ["tenant:enterprise:*"]
+      P: 3
+      reason: "isolation tier; see adaptive-pricing tier definition"
+

A service with exceptions must be able to say which tenants are on which substrate. That mapping is a first-class artifact, not archaeology.

+

Decision 5.5 — a provider declares what it makes reachable, not where it sits. The six ladders describe a consumer of infrastructure. They describe a provider of it badly, and railiance-platform's review demonstrated how badly: apps-pg is I0 A0 E0 because a database has no tenant concept, carries no tenant claim and applies no tenant predicate. Those zeros are structural, not weak — the cluster is exactly as strong as its consumers make it.

+

The sharp case is OpenBao at E0. Literally correct, and actively misleading: the mechanism in place is credential-scoped structural separation — E4 machinery — pointed at a consumer boundary rather than a tenant one. A reader scanning a column of E values would rank it below a service doing per-query filtering in application code, inverting the real security position.

+

So a platform service additionally declares, per axis, the level available now, the maximum it can make reachable, and what a consumer must do to reach it. For apps-pg: E4 unreachable (shared credential per consumer, no per-tenant credential), E3 conditional on the GUC contract, R2 blocked on a backup target, V1 at most on the single-node rail. That is the sentence a consumer actually needs, and no arrangement of the consumer ladders produces it.

+

A provider's own P is n/a, not a number. apps-pg provides P1; it is not at P1, and writing P: 1 there would later read as an isolation claim.

+

Decision 5.6 — enforcement stance is not a seventh axis, and zones: is reserved in this file. zone-engine asked whether enforcement stance — whether a given control is enforced, advisory or exempt in a given band of the estate — should fold in here rather than become a second standard. It should not, for a reason that is structural rather than territorial.

+

Every one of the six ladders is monotone: higher is stronger, and higher is what a service wants. That assumption is load-bearing throughout. §12's improve step moves a service up. §12's guard checks that none is below what it declared. §6 has to make a special allowance for a level that is permanently low by design, and §12's guard is told not to nag it — the allowance exists because low-is-normal is the exception here.

+

Enforcement stance is not monotone. The correct stance for a bootstrap lane is deliberately and permanently below the top rung, and the top rung is sometimes the wrong answer outright: ops-warden's ADR-0006 is exactly the finding that a fail-closed authorization gate on the SSH lane the tunnels depend on is not a stronger position, it is an outage. A ladder whose top is sometimes wrong is an enumeration, not a ladder, and putting one inside this vector would break current/target/gap, the guard, and §6 for the six that are.

+

§8.3 already refused an axis for a weaker reason than this one — that a QoS level would be an unenforced claim. Enforcement stance is enforced. It fails the other half of the same test.

+

There is a second, sharper reason. §6's conformance rule is accuracy, not altitude, and it works because this framework is descriptive: it never blocks anything by itself. Prescription enters only through §11 and Decision 8.2, where a requirer — never the declaring repo — sets a minimum level and the two are machine-reconciled. Enforcement stance is prescriptive by nature. Fold it in as an axis and an accurately declared exempt becomes conformant and exempt: a conformance rule that hands out the exemption it exists to audit. The declarer must not be the party that sets the stance.

+

So the split is the one flex-auth already argued to zone-engine: membership is data and is declared; stance is a rule and belongs to the control's owner. Membership is posture-shaped and behaves like a level. Stance behaves like a tier minimum under Decision 8.2 — asserted elsewhere, joined by machine.

+

What canon rules, and it is binding on the sibling standard:

+
  • A security-zone standard is a separate document in this family, drafted by zone-engine and published in NetKingdom canon beside this one and the *-engine boundary contracts. It carries over §6 verbatim and §13's evidence discipline.
  • Zone membership is declared in tenancy.yaml, under a reserved top-level zones: key, sibling to tenancy: and provider: — not inside tenancy.current. Decision 5.4 makes this file the repo's single posture declaration surface, and a second root file would recreate the divergence §5.4 was written to end. One file, one review cadence, one validator; two standards, because the two have different owners and different conformance semantics.
  • organization_posture (ops-warden WP-0029) does not belong in this file at all, under either key. It is a fleet-wide, time-varying scalar describing the estate, not a property of the declaring service, and a per-repo copy of a global would go stale in as many places as there are repos. It is an input to stance selection and should be read by the zone model, not absorbed into a declaration.
+

Decision 5.6.1 — the workload remains the sole zone policy subject, and absence resolves to unknown. Here workload means an independently governed execution unit that performs application, automation, or operational control-plane work and can be attributed at enforcement time to both a responsible party and an authoritative workload identity. The independently governed executing unit behind operator-driven or automated SSH access, tunnel operation, credential brokering, policy compilation or enforcement, or maintenance is a workload when such a unit exists. The observed access, tunnel, grant, lane, pattern, or action is not itself a workload. A human or agent identity remains caller context; it does not replace the workload whose execution is being governed.

+

A repository, credential lane, grant template, pattern, or software package is not an alternative zone policy subject. A broker runtime is a workload; the grants and lanes it handles retain native resource identity and may explicitly be not-applicable to workload resolution. Every managed running deployable, including operational and tooling runtimes, has one authoritative rapp-*/declarations/rapp.yaml under RMGR-ADR-004. A pre-rapp managed runtime is migration debt and resolves unknown. An independently governed operational execution unit that is not a managed deployable may declare directly in its responsible repo's tenancy.yaml. The declaration surface broadens to cover real workloads; the subject model does not broaden merely to totalize an incomplete registry.

+

When a workload-applicable subject lacks an authoritative workload identity, when its reference is absent or ambiguous, or when that identity has no authoritative zone membership, the resolved membership MUST be unknown. It MUST NOT be inferred from a repository owner, credential path, lane type, actor class, environment, criticality, reef, organization posture, or a default zone. A control owner MAY define an explicit, reviewable fail-safe treatment for unknown — including denial, escalation, or a build-stage rule — but that treatment is stance, not membership. unknown never silently inherits a permissive zone or exception.

+

Decision 5.6.2 — zone membership requires an authoritative workload binding in the same declaration entry. A bare service name is a label, not identity evidence. Any single-service declaration carrying zones: MUST also carry workload_identity; in a layer repo using services:, both fields live on the same service entry. Top-level zones: is not valid for a multi-service file, because it would make membership ambiguous.

+

The binding states:

+
  • name — the stable workload id, exactly equal to the declaration's service;
  • kind — application, platform service, automation, operational control plane, or maintenance job;
  • responsible_repo — the repository accountable for the identity and zone declaration;
  • one or more identity_bindings, each naming the identity scheme, authoritative issuer or registry, exact subject, IAM Profile principal type, and optional environment and evidence; and
  • for a managed deployable, a declaration_ref to its authoritative rapp declaration. Consuming catalogs reference it with the exact Repo Manager v1 tuple (rapp_id, workload_identity.name) and optional deployable.
+

An identity binding uses principal_type: service or agent. A human identity may still be required as actor or delegation context, but cannot be the sole workload identity. A managed application, operational runtime, or tooling runtime references its rapp declaration. Only an independently governed operational execution unit that is not a managed deployable declares directly in its responsible repo's tenancy.yaml; native actions and resources do not acquire a fictional workload or rapp merely to enter policy.

+

The stable workload id is the join key. Credential lanes, grants, controls, and compiled policy resources reference that id explicitly; compilers MUST NOT recover it by parsing a credential path or repository name. Multiple runtime principals may bind to one workload when environments or mechanisms differ, but the bindings must be unique and remain owner-reviewed. If no authoritative binding matches the runtime principal, Decision 5.6.1 returns unknown.

+
service: ops-bridge-tunnel
+role: operational-access-path
+workload_identity:
+  name: ops-bridge-tunnel
+  kind: operational-control-plane
+  responsible_repo: ops-bridge
+  identity_bindings:
+    - scheme: ssh-certificate
+      authority: ops-warden
+      subject: agt-ops-bridge
+      principal_type: agent
+      environment: prod
+zones:
+  standard: security-zones_v0.1
+  membership: z2-continuity
+  responsible_party: ops-bridge
+  justification: foundational tunnel path must retain availability under PDP loss
+  context:
+    maturity: M2
+    criticality: high
+    data_classification: confidential
+  evidence:
+    - ref: docs/evidence/ops-bridge-tunnel-zone.md
+      supports: [M2, continuity-dependency, recovery]
+  reviewed: "2026-08-22"
+  review_due: "2026-11-22"
+

Worked examples after applying the evidence rule and minimum-across-paths rule consistently:

+
ServiceCurrentNotes
tenant-engineI1 A0 E1 P n/a R0 V0Acting identity is caller-supplied; unauthorised read paths set the A minimum; E2-shaped child-table controls are not evidenced; SQLite is outside P; no erasure or availability evidence. This corrects draft-7, which quoted A2/E2 despite its own minimum/evidence rules.
audit-coreI1 A2 E2 P1 R2 V0Bounded adversarial run WH-ENG-20260822-AUDIT-E2-03 passed all three calibrated cross-tenant probes over ten operations, so E2 is evidenced as of 2026-08-22. This establishes only that the attempted attacks did not work. The 24-hour facility baseline requires review or replacement by 2026-08-23T22:10:25Z. Its 30-day retention and erasure horizon remain declared and published.
flex-authI1 A0 E1 P n/a R n/a V0Enables A3 for consumers. /v1/check authenticates no caller; E2 is implemented but not evidenced.
platform-pg (provider)I0 A0 E0 P n/a R2 V1Provides P1; backup/restore and single-node recovery are evidenced. Provides no tenant boundary by itself.
apps-pg (provider)I0 A0 E0 P n/a R0 V0Zeros are structural, except R0/V0 are live gaps: no backup and no recovery evidence.
adaptive-pricing observatoryI0 A0 E0 P n/a R n/a V0Local, unauthenticated, single-user analysis surface; not a production service.
A newly absorbed repoI1 A1 E1 P0 R0 V0Conformant if declared, with a recorded path.
+

Decision 5.1: the posture vector is declared in the repo, not in the hub, consistent with local-files-are-source-of-truth.

+

Decision 5.2 — declare per path, quote the minimum. A service whose mutations are authorized and whose reads are not is at the reads' level. The quoted number is the minimum across paths; the per-path detail is declared beside it.

+

Draft-6 required only the minimum, on tenant-engine's review. audit-core then showed why that is insufficient on its own: a bare minimum destroys signal, because E3-write/E1-read declares identically to E1/E1. Bare per-path invites "our write path is E3", which is the sentence §6 exists to stop. Both, related explicitly, is the rule.

+

Two services found this shape in themselves within a day of each other — tenant-engine (writes authorized, three read routes not) and audit-core (write path tenant-filtered, read path not filtered at all). Most services enforce harder on write than read, so this is the common case, not the corner.

+

Decision 5.3 — n/a is a level, and it is conformant. P0 presupposes a shared database and R0 presupposes retained data. A service holding nothing at rest — flex-auth runs with its registry and policy baked read-only into the image and no decision log persisted — is neither. A datastore outside a ladder's substrate vocabulary, such as tenant-engine's current SQLite PVC, also uses n/a rather than inventing a level. Without an admissible n/a, a missing rung forces the fabrication §6 prohibits, which is precisely what draft-1 was rejected for. n/a is declared with a stated reason.

+

Decision 5.4 — the vector lives at tenancy.yaml in the repo root. Draft-6 said "in the repo" and not where or in what shape, which left §12's guard needing per-repo archaeology. flex-auth adopted tenancy.yaml speculatively; adopted here as the convention. A repo representing one service uses the single-service form above. A layer repo uses the schema's services list in the same root file — one vector per service, never an average. The normative schema is canon/schemas/tenancy-posture_v0.1.schema.json; prose documents may explain a declaration but do not replace it. The schema carries current, implemented, target, reviewed, review_due, gap, placement_exceptions, service_class (§8.3), per-path detail (§5.2), and provider reachability (§5.5), plus the workload identity prerequisite for zone membership (§5.6.2). It also permits responsible_repo for authoritative posture routing and evidence_freshness for machine-readable evidence observation, expiry, scope, owner, and remediation metadata. Their absence remains valid declaration syntax; feedback resolution must report unknown, never infer them from a directory, service name, or previous owner. From the net-kingdom repo, owners validate one or more declarations with uv run tools/tenancy-posture/validate.py <path>...; the validator applies the JSON Schema and the evidence, date, implemented/current and provider-range rules that JSON Schema alone cannot express.

+
+

06Conformance is accuracy, not altitude

+

A service is conformant when its declared posture is accurate, its target is recorded, and it does not claim a level it cannot evidence. It is non-conformant when it overclaims — at any altitude.

+
  • Declaring E0 is conformant. Concealing E0 is not.
  • A repo may be absorbed at any posture. It may not be absorbed silently.
  • No service is blocked from the estate for being low on a ladder. Services MAY be blocked from specific work — serving a tenant grouping, holding a data class, carrying a plan tier — by requirements expressed as minimum levels.
  • Downgrading is permitted and must be declared. A regression found by guarding is a defect; a regression declared in advance is a decision.
  • A low level may be permanent by design, and the declaration must be able to say so. flex-auth is I1 and always will be: a decision point evaluates the claims it is handed, and verifying its own inputs would make it the identity provider its scope refuses to be. A target equal to current with a reason is a settled position, not a stalled trajectory, and §12's guard must not nag it as though it were one.
+

Decision 6.1 — downgrades propagate. Before a planned downgrade of a current level or a provider's available level, the declaring repo MUST resolve the tier definitions and consumers that reference it. A downgrade below a recorded minimum blocks the change until the claim is changed, the workload is moved, or the affected owner explicitly accepts the gap. An unplanned regression is an incident and triggers the same notifications. Updating tenancy.yaml without notifying dependants is declaration drift, not a completed downgrade.

+

Without the axis separation, "not rigorous about tenant separation" is one verdict a repo passes or fails. With it, the same repo is I1 A1 E1 P0 R0 V0 with a path — a plan, not an indictment.

+
+

07Portability across placement levels

+

Movement between P levels must be operational, not a rebuild:

+
  • Connect by injected credential only — no cluster, host, namespace or database name in source.
  • Own a whole database, never tables inside someone else's.
  • Idempotent schema creation.
  • No cross-database joins or co-location assumptions.
+

Decision 7.1: mandatory at P1 and above. At P3, SHOULD rather than MUST — a per-client instance that never moves is not misconformant for naming its own database.

+
+

08Placement triggers

+

Recorded at provisioning time: noisy neighbour on a latency-critical path; a compliance or residency requirement; a plan tier requiring a higher minimum; an erasure horizon that no longer fits (§4.5); connection or memory ceiling reached.

+

Decision 8.1: triggers MUST be monitored, not merely recorded. A trigger in a YAML comment nobody re-reads is documentation, not control.

+

Decision 8.2 — split authority, machine-reconciled. railiance-platform owns the placement rule; the package repo owns the substrate numbers and enforcement; the consuming repo owns its workload requirements; adaptive-pricing owns any tier minimum. adaptive-pricing declined a standing co-signature and the framework accepts the replacement: typed tier minima are joined to consumer and provider declarations at tier definition and whenever one changes. A machine-checkable constraint must not depend on somebody remembering to collect a signature.

+

Decision 8.2.1 — trigger monitoring has an owner. The provider monitors capacity ceilings and co-residency; the consumer monitors latency, compliance and erasure requirements; adaptive-pricing monitors tier-definition changes. The placement owner reconciles those signals. A trigger marked unmonitored is an explicit gap and cannot support a customer assurance claim.

+

8.3 Service class — a placement input, never a priority

+

A latency-critical consumer and a batch consumer can share an instance today with nothing distinguishing them. tenant-engine sits on flex-auth's synchronous authorization path and chose a 5s statement timeout for that reason; audit-core, co-resident, is not latency-critical. Nothing prioritises between them.

+

The framework does not add a QoS axis, because the platform cannot enforce one. Community PostgreSQL has no resource governor: no per-role CPU or I/O priority, no resource queues, no workload classes. Those exist in EDB's enterprise variant, in Greenplum, and in SQL Server — not in what we run. A declared priority level would therefore be an unenforced claim sitting in a declaration, which is precisely what retiring tenantIsolation was about. An axis implies graduation and enforcement; this has neither.

+

Decision 8.3.1 — co-residents are equal. On shared substrate no consumer's query yields to another's. A consumer whose latency requirement cannot survive an unprioritised neighbour must escalate to P2. That is the honest mechanism and it is the only one we have.

+

Decision 8.3.2 — service class is declared anyway, as a category rather than a level: latency-critical, interactive, or batch. It buys three things, none of which is priority:

+
  • A placement input. Mixing latency-critical with batch on one instance is a recognised mismatch. It may still be the right call — it is right today — but it should be a decision, not an accident of who was provisioned when.
  • A trigger. A latency-critical consumer acquiring a batch co-resident is a recorded placement trigger under §8, on the same footing as noisy neighbour.
  • An acceptance criterion for evidence. The noisy-neighbour artifact in §13 asks whether measured degradation is acceptable; without a declared class that word has no referent. Degradation tolerable for batch may be an outage for latency-critical.
+

Decision 8.3.3 — class mixture must be visible. The platform reports which classes are co-resident. An unenforceable risk that nobody can see is strictly worse than one that is stated.

+

8.4 Substrate location is not evidence — and reefs are not reconciled with P or V

+

Decision 8.4.1 — location is not evidence of any property of the workload on it. Three repos have now written this rule locally in three vocabularies: railiance-master's topology is not readiness, zone-engine's placement is not posture, and §3.1 here, which requires every use of "isolation" to name its axis. They are one rule. Stated once: the substrate a workload sits on is never, by itself, evidence for a level on any ladder in this framework. Binding to a reef, being on a dedicated instance, or naming a rail proves placement and nothing else. Other repos should cite this rather than restate it.

+

Decision 8.4.2 — the reef taxonomy and this framework are not reconciled, and that is this document's defect. zone-engine asked whether canon should reconcile reefs with the posture axes, on the assumption that this was a scope question for the zone model. It is not: the unreconciled pair is not zone ↔ reef, it is reef ↔ P and V, and it belongs to canon.

+

repo-manager owns substrate placement (reef-railiance, reef-storage) with an explicit residual-risk acceptance attached to a binding. §7 and §8 of this document presuppose that placement is fully described by the P ladder. It is not. P grades tenant data isolation within a datastore; a reef is a named compute substrate carrying an accepted residual risk. The P ladder has no rung meaning "single node, shared control plane, risk accepted", and inventing one would be the fabrication §6 prohibits.

+

A reef binding therefore satisfies none of the P couplings in Decision 3.2 by itself. It records the compute substrate and its accepted residual risk; it does not establish a database-per-consumer or database-per-tenant boundary, a per-tenant credential or encryption boundary, or an erasure horizon. Those remain properties of the workload and data provider declarations. The binding does participate in V, because availability composes across the complete critical path and the substrate can impose a ceiling.

+

The live consequence is on V, not P. §4.6 already warns that "a dedicated cluster can still be a single instance on a single node", and Decision 4.6.1 makes V the minimum across the synchronous path. reef-railiance is single-node with a shared control plane, which caps V for every workload bound to it regardless of that workload's own replica count — which Decision 4.6.1 already says is not evidence. Nothing today joins the reef's facts to a consumer's V declaration, so a rapp can declare V2 accurately by its own reading and be wrong by this document's own composition rule.

+

This is railiance-platform's provider-declaration finding (Decision 5.5) generalised one layer down. A reef is a provider and has nowhere to state what it makes reachable. Tracked as NK-WP-0027; the fix is canon's, and the zone model is not blocked on it.

+

The known escalation short of P2 is gateway-level prioritisation — ordering submissions in a connection proxy by the requesting tenant's current consumption. It is real, it is where the industry puts this when it must, and it is new infrastructure we do not run. Recorded as the option, not adopted.

+
+

09Credentials as a tenancy control

+

Short-lived leased credentials re-read at connection checkout, with overlap-first rotation, bound the residual risk at every E level below E4: a leaked credential expires rather than persisting. Stronger than the industry norm of a long-lived per-service secret.

+

Decision 9.1: static long-lived database credentials are not a sanctioned path for any service above E0.

+

Decision 9.2 — the rule extends to consumer-facing credentials. Draft-6 named database access only. audit-core pointed out that its ingest credentials are static long-lived bearer tokens, rotated by publishing a second alongside the first — and that the argument applies with more force to the credential that actually carries the tenant claim than to the one that reaches the database behind it. Read as an accidental omission; it was. Consumer-facing credentials are named in. Where a service cannot yet meet this, it is a stated gap rather than a silent exclusion.

+
+

10Blast radius must be published

+

Decision 10.1: every platform holding consumer data MUST publish, in concrete terms, what a leaked runtime credential can and cannot reach at the levels it operates. rapp-postgres ADR-0001 §5 is the reference. Where the model cannot provide a guarantee, the platform says so and names the escalation.

+

Decision 10.2 — quotas are disclosed, not discovered. The same obligation extends from what a leaked credential can reach to what the platform will refuse to do for you. Every consumer MUST be told, at provisioning, the throttles and quotas enforced against it — connection limits, statement timeouts, idle-transaction timeouts — and told again when they change. A consumer learning its statement timeout by hitting it in production is a disclosure failure, not a consumer bug. This is how tenant-engine was provisioned, by good practice rather than by rule; the rule now exists.

+
+

11Commercial expression

+
  • 11.1 Plan tiers are expressed internally as typed assurance requirements. A tier may require E3 P2 R2 V2 and a maximum erasure horizon; it need not print those labels anywhere customer-facing.
  • 11.2 Marketing and product language is free. No requirement to expose level labels or this document. "Dedicated infrastructure", "isolated tenancy", "private instance" all remain available.
  • 11.3 The constraint is on evidence, not vocabulary. A customer-facing isolation, availability or retention claim must map to a minimum level the delivering service actually holds, recorded once when the tier is defined. The review is internal and happens at tier definition — not per campaign.
  • 11.4 Two hard lines, because these reach contracts and compliance questionnaires:
  • A claim that another tenant cannot reach the customer's data requires E4.
  • A claim that deleted data is gone requires R4, or an erasure horizon disclosed alongside it. Where R4 is reached by key destruction, the claim is defensible but not settled law (§4.5) — it may be made, and it may not be made in language that implies a regulator has blessed it.
  • A claim that service survives loss of a zone requires V3; regional-loss language requires V4. "High availability" without a named failure and measured recovery objective is not an assurance claim this framework can evidence.
  • 11.5 — sanctioned honest language. The strong prohibitions above must not leave a commercial writer with only silence:
  • E3 may be described as database-backed defence against an omitted tenant filter; it must not be paraphrased as "another tenant cannot reach".
  • P2 may be described as a dedicated service database cluster with an independent capacity and restore boundary; it is not tenant-dedicated.
  • R2 may state the declared retention and published erasure horizon.
  • V1 may state exercised restart recovery in one failure domain and must say that interruption and single-domain loss remain.
  • 11.6 — authority and reconciliation. The tier definition is authoritative for the minimum and customer wording. tenancy.yaml is authoritative for the delivering service's current level; provider declarations are authoritative for what infrastructure makes available. None is derived by copying another. Approval joins them and fails closed on a missing, stale or insufficient declaration. A performance-differentiated tier requires P2 or an enforceable resource governor; service class alone grants no priority.
+
+

12Methodology — analyze, establish, improve, guard

+

Analyze. Assess a repo against the ladders; produce tenancy.current with reasoning recorded. Applies to new and absorbed services alike.

+

Establish. Declare the target and gap. The target is set by data class, tenant groupings served and plan tiers carried — not by ambition.

+

Improve. Move one axis at a time. Raising P while leaving E untouched is the characteristic misstep.

+

Guard. Verify continuously that the declared posture holds — against the service's own declaration, not a universal maximum. Nobody must prove every service is at E4; the check is that none is below what it declared.

+

Regression found by guarding is a defect; regression declared in advance is a decision. The estate has been bitten twice by silent pin rollbacks producing ordinary-looking 403s and 404s rather than errors. Posture regression looks the same — an RLS context leak returns correct-looking rows for the wrong tenant. Guarding must be designed for invisible failure, not for crashes.

+
+

13Evidence per level

+

Decision 13.1: a current level is claimed only with its evidence artifact present. This turns §6's accuracy rule from an honour system into a check. implemented records a control observed in code or configuration whose required artifact is still absent; it never satisfies a tier minimum.

+

Decision 13.1a — the floor needs no artifact, only a reason. Found independently by audit-core and flex-auth: the table below defines artifacts from I2, A2, E1, P1, R2 upward and none below, so a literal 13.1 made the lowest rungs unclaimable — including §5's own worked example of a conformant absorbed repo, I1 A1 E1 P0 R0, which could not satisfy it on any axis. A rule that forbids the declaration §6 exists to permit is a defect in the rule.

+

At I0/I1, A0/A1, E0, P0, R0/R1, V0, or n/a, a declaration requires a stated reason, not an artifact. Evidence is what stops you overclaiming, and there is nothing to overclaim at those floors.

+

Decision 13.4 — an artifact must assert something achievable. Draft-3's noisy-neighbour evidence required proof that a saturating consumer "does not breach" another's allowance. Shared infrastructure cannot provide that; the risk is inherent and cannot be wholly removed. An artifact that can only fail, or that passes by being run gently enough, is an overclaim wearing the costume of evidence. Where a property cannot be guaranteed, the artifact measures and records it instead.

+

Decision 13.2 — evidence is of two kinds, and conflating them is an overclaim. Mechanical evidence is a structural assertion a machine can make and belongs in CI. Adversarial evidence is semantic, requires setting up separate tenant contexts and comparing responses, and carries a review date rather than a green build. Cross-tenant findings are the category external testing practice identifies as needing human review. A passing CI run is not E2 evidence.

+

Decision 13.5 — freshness and ownership are explicit inputs. A declaration may attach evidence_freshness.<level> to an evidence key. An adversarial entry requires observed_at, valid_until, responsible_repo, scope, and remediation; a mechanical entry may omit valid_until when the artifact is continuously re-established by the referenced revision or CI control. The timestamps use RFC 3339 and the responsible repository is the authority for replacement evidence.

+

The feedback evaluator does not parse prose for dates, infer ownership from a file path, or silently extend a validity window. A current adversarial claim without freshness metadata resolves to freshness unknown. An expired artifact resolves to freshness expired. Neither automatically rewrites the declared level: the evaluator emits a deterministic owner-routed remediation proposal so review remains observable and controlled. The proposal contract is posture-feedback_v0.1; it performs no State Hub write or policy mutation.

+
LevelEvidenceKind
I2Identifiers validated against the vocabulary; rejection test for a malformed id; binding shown to come from a verified tokenMechanical
I3Live re-query demonstrated on an aal2-class path; cached-claim path shown unused thereMechanical
A2Choke point identified; test that an unbound request is refusedMechanical
A3Live decision with a denial observed at the endpoint, not only at the decision surfaceMechanical
A4Decision served over the standard interface; a second PDP substituted without PEP change, with the decision differences between the two recorded — substitution proves interface portability, not decision equivalenceMechanical
E1Every tenant-owned table carries the tenant keyMechanical
E2Choke point identified; identity bound to tenant A demonstrably cannot read tenant BAdversarial, with a review date
E3FORCE ROW LEVEL SECURITY on every tenant table; no BYPASSRLS on leased roles; probe that a session without the GUC reads nothing; probe that a wrong GUC reads nothing; EXPLAIN comparisonMechanical
E4Per-tenant credential demonstrated unable to connect to another tenant's substrateMechanical
P1–P4Provisioning declaration plus the platform's isolation probesMechanical
Shared P1–P2 capacity assuranceA recorded baseline of per-consumer resource usage; a run in which one consumer saturates its declared allowance; evidence that the governance controls bind (the greedy consumer is held at its limits) and that the degradation co-residents experience is measured, recorded and judged acceptable against each one's declared service class (§8.3); the aggregate headroom at time of measurementAdversarial, load-generated, with a review date
R2Declared retention rendered; erasure horizon published and reported in the operator surfaceMechanical
R3Sweep evidence records: timestamp, dataset, identifiers removed, authorising policy referenceMechanical
R4Erasure demonstrated across live data, backups and derived copies within the horizonAdversarial
V1Critical dependencies enumerated; restart/recreate recovery exercised; interruption and measured recovery time recordedMechanical exercise
V2One instance terminated while traffic continues or recovers automatically; measured RTO/RPO and remaining shared failure domains recordedAdversarial, failure-injected
V3Declared failure domain removed in an exercise; complete critical path and degraded modes observed against RTO/RPOAdversarial, failure-injected
V4Region made unavailable in an exercise; traffic and state recover in the alternate region against RTO/RPOAdversarial, failure-injected
+

The P1–P4 artifact proves the declared placement topology. The shared-capacity artifact is additional: it is required before a P1/P2 service can claim that a noisy-neighbour control binds, that the trigger is actively guarded, or that a customer performance assurance survives co-residency. It is not required merely to report the true topology as P1 or P2. No such capacity artifact exists in the estate today, so §11 requires P2 or an enforceable governor for a performance-differentiated tier.

+

Decision 13.3: the tenant-boundary E2/E3 and noisy-neighbour artifacts do not exist anywhere in the estate today. rapp-postgres runs 19 adversarial probes, all against the consumer boundary, none against the tenant boundary inside a consumer. Externally, what this framework calls a tenant boundary failure is Broken Object Level Authorization — OWASP API1, top of the API Security Top 10 since that list launched, and the most commonly exploited API vulnerability in published assessments. We have no coverage for the highest-ranked risk in our class of system. §19.3 records the owner.

+
+

14Adoption stance — structure, not tooling

+

Decision 14.1: external research is design input. This estate adopts published standards and structural patterns; it does not adopt tooling unless that tooling is an established industry standard with broad application. Everything else is built ground-up, so it can be optimised and refactored as the estate sees fit.

+
ClassStance
Security baselines (OWASP Multi-Tenant Security Cheat Sheet, API Security Top 10)Adopt as the external reference our ladders answer to
Standards bodies (OpenID AuthZEN 1.0)Adopt — this is what A4 is
Reference taxonomies (Azure tenancy models, AWS SaaS Lens, cell architecture)Adopt as structure
Engine behaviour (PostgreSQL RLS mechanics)Facts, not tooling
Third-party analyzers and test frameworksDo not adopt. Take their rule taxonomies as checklists for probes we write ourselves
+

The practical effect is small and good: rapp-postgres already owns a ground-up probe harness — bash and psql, no dependency tree — that found four real defects in its own provisioning SQL. The evidence artifacts in §13 become new probes in a tool we control. One idea worth reimplementing from the external survey is policy-diff classification: labelling a change to an enforcement policy as safe or breaking before it lands.

+
+

15Alternatives considered

+

One fixed model with a single set of characteristics (draft-1). Rejected: cannot describe a repo that is not there yet, forcing absorbed repos to misrepresent their posture or stay outside. A framework that can only describe its own end state is not a framework.

+

A maturity model with a single overall level. Rejected: collapses the axis separation. A service strong on identity and weak on enforcement has a specific, actionable gap; one composite score hides it and invites averaging.

+

Prohibiting row-level security (draft-2's inherited position). Rejected in draft-2, refined in draft-3: RLS is a real rung against the common threat. The error was never RLS — it was describing E3 in E4's language.

+

Schema-per-consumer in one database. Rejected: pg_catalog is readable per-database, so every co-resident enumerates every other's table and column names regardless of grants. Retained as a describable state, never a target.

+

Mandating E4 for everyone. Rejected: the tenant taxonomy includes consumer (private individuals) and family. A cluster per private individual is economically impossible; the taxonomy is itself evidence pooling is required.

+

Per-consumer physical backup retention. Rejected: CNPG retention is a property of the instance's WAL archive. There is no mechanism, and claiming it would be a fabricated guarantee. Hence the derived maximum in §4.5.

+

Platform-scheduled row expiry. Rejected: requires the platform to hold DML authority over consumer schemas and interpret consumer data semantics, both forbidden by ADR-0001. The consumer's migration lease is the correct instrument.

+

Leaving each repo to its own model. Rejected: the status quo, which produced two contradictory ratified defaults and an unowned placement question.

+
+

16Held against outside practice

+

The graduated reframe is corroborated, not invented here. Microsoft's tenancy-model guidance states it almost verbatim: "Instead of viewing isolation as a discrete property, consider it a spectrum. You can deploy components of your architecture that are more isolated or less isolated than other components in the same architecture." The same guidance derives our E↔P coupling independently — shared deployment means enforcement lives in application code; dedicated deployment means it is structural.

+

Stronger than typical. Most multi-tenancy literature models one boundary, tenant-to-tenant. This estate has two stacked boundaries: platform-service to platform-service, and tenant to tenant inside a consumer. Naming them separately and refusing to enforce both with one mechanism is uncommon and correct. Graduated per-axis levels also beat the silo/pool/bridge trichotomy, which is approximately our P axis with the other four missing — which is why it cannot express "pooled infrastructure, structurally enforced boundary".

+

Weaker than typical. The pool model's standard mitigation is a verified enforcement layer every service is demonstrably routed through. We have the concept and none of the verification (§13.3).

+

Adopted without naming it. Short-lived leased credentials re-read at checkout beat the long-lived-secret norm. §9 promotes it to a tenancy control.

+

Still unexplored. Neither P nor R describes a cell — a slice of infrastructure with a fixed maximum size, sized so one cell's failure is survivable and cell count scales linearly. platform-pg is, in these terms, an uncapped cell: §17 computes a ceiling and nothing enforces it (§19.8).

+

Sources: the five research digests in the-custodian/research/2026-08-17-adr008-*, which carry full citations for the claims in this section.

+
+

17Scaling demands

+

Derived from the live platform-pg specification. Connection arithmetic is exact; the per-backend memory estimate remains unmeasured and is explicitly a gap in rapp-postgres ADR-0004.

+
instances:        1              (no HA; single-node rail)
+max_connections:  100
+memory limit:     1Gi
+per consumer:     14 connections (12 runtime + 2 migration)
+

The hard connection bound is roughly six declarations; the enforceable operational ceiling is four. Seven declarations request 98 of 100 connections before CNPG's instance manager, metrics exporter and reserved slots. Every one of them is politely inside its declared 14-connection allowance; the instance still fails. ADR-0004 sets four because memory is expected to bind first and fails by OOM-killing every co-resident rather than refusing one connection.

+

That distinction matters because our governance addresses the wrong shape. Per-consumer connection_limit, statement_timeout and idle_in_transaction_session_timeout guard well against one greedy consumer. They do nothing about the aggregate of many modest ones, which is the second and less intuitive noisy-neighbour failure and the one this number describes. Two workload consumers plus the isolation probe occupy three of the four declared slots. The next workload request must trigger measurement and the overflow decision before admission.

+

Memory likely binds first. 100 backends against 1Gi is ~10MB per backend. Connection exhaustion errors clearly; memory pressure OOM-kills and degrades every co-resident at once.

+

E3 and pooling. Corrected from draft-2, which had this backwards. Transaction-scoped context (SET LOCAL inside an explicit transaction) is what makes E3 safe under a pooler. Statement-level pooling is what breaks it, serving other tenants' rows under concurrency with no error. E3 constrains which pooling mode is available, not whether pooling is available.

+

Retention consumes the volume. WAL accumulates with the window, and §4.5 makes the window the maximum across consumers. A consumer declaring a long retention extends everyone's horizon and everyone's storage draw against a 20Gi volume.

+

Restore time couples all consumers. Physical backup is instance-wide, so a consumer's RTO is a function of total instance size, not its own.

+

platform-pg is V1. instances: 1 on a single-node rail provides exercised restart recovery and no failover. P1 describes its consumer placement and says nothing about this availability fact; the new V axis carries it.

+
+

18Consequences

+
  • The estate gains one vocabulary and a way to be honest about partial adoption.
  • Absorbed repos get a described state and a path instead of a failing grade.
  • tenantIsolation in PostgresConsumer is revealed as a mislabelled field.
  • The verification problem becomes tractable: guard against declaration.
  • Draft-2's RLS prohibition is reversed and its E3 description corrected; rapp-postgres acquires an obligation to define and offer the mechanism.
  • Adding a consumer with long retention silently extends everyone's erasure horizon. This must reach the consumer review checklist, not only this document.
  • A service selling an isolation tier must maintain a tenant→substrate mapping it does not have today.
  • Availability becomes an end-to-end, evidenced property rather than an inference from replica count or placement.
  • Nothing here changes a running system.
+
+

19Review resolutions and residual questions

+
  1. tenantIsolation field — resolved. rapp-postgres retired it. A consumer declaration asks for mechanisms; posture lives in the consumer's tenancy.yaml.
  2. Placement ownership — resolved. §8.2 records the split. The policy has one owner; typed tier requirements replace the declined commercial co-signature.
  3. E2, E3 and noisy-neighbour evidence — owned as of 2026-08-17 by whitehat-security (WHITEHAT-WP-0001), an independent adversarial evidence facility seeded for this purpose. audit-core and tenant-engine were right to decline it as fleet-scope work; the answer was a home of its own rather than a volunteer.
+

Owned by NetKingdom — corrected 2026-08-17; an earlier revision of this section proposed otherwise on independence grounds and was overruled. Offensive security is security work and belongs with the repo that owns security. The facility is framed offensively rather than as a conformance checker: it is pointed at infrastructure we choose, our own estate among them, and conformance testing is one use of a general capability.

+

The residual tension is recorded rather than resolved: NetKingdom owns this framework and the facility that tests conformance to it, so those findings are NetKingdom assessing NetKingdom. The mitigation is that findings leave for risk-nexus, under the-custodian, rather than being closed in place. Proportionate, not perfect. Revisit if conformance findings start getting quietly closed.

+

Two consequences land back here. Cadence is now a security parameter, not a schedule — for any control whose guarantee is detection rather than prevention, the interval between probe runs is the exposure window, and rapp-postgres ADR-0003 leaves that number to the facility. And a passing suite is not proof of isolation; it is proof that the attacks attempted did not work. §13's evidence artifacts should be read with that distinction, because a green run recorded as "E2 verified" would be exactly the overclaim §6 prohibits.

+
  1. Business app vs platform service — open. Custodian canon: a classification rule. Candidate: reuse repo-classification-standard_v1.0.
  2. Tier → minimum level mapping — policy resolved, implementation open. adaptive-pricing owns typed minima and wording; tenant-engine owns plan assignment by id. Current tiers make no assurance claims.
  3. The E3 mechanism — resolved. rapp-postgres ADR-0003 publishes the GUC contract with the FORCE/BYPASSRLS/SECURITY INVOKER/EXPLAIN requirements.
  4. Identity-provider placement — open. Owner of key-cape: realm-per-tenant or Organizations? Realm-per-tenant's ~5–20 tenant ceiling is below our target.
  5. Cell sizing — resolved for platform-pg. rapp-postgres ADR-0004 sets four consumers and names absent overflow target platform-pg-2; measurement and provisioning remain live gaps.
  6. Retention floor and ceiling — resolved as policy. Both exist; requests outside them fail validation and the package repo owns the numbers.
  7. Engine neutrality — open. The P ladder rests on a PostgreSQL property. State it engine-specifically and say so, or abstract it and risk a non-Postgres implementation that silently differs?
  8. Erasure versus audit — framework resolved. audit-core: crypto-shredding a tenant's audit records destroys the evidence the service exists to hold, and ADR-0001 §2 deliberately built the role model so history could not be rewritten. The usual resolution separates the fact of an event, retained, from its personal payload, encrypted per subject and shreddable. Raised because a naive "R4 everywhere" target would instruct the audit service to destroy its own evidence. audit-core targets R2 and is explicitly not a fleet R4 target. The legal basis for retaining audit facts remains a risk/legal question outside this framework.
  9. Quality of service — resolved 2026-08-17. Co-residents are equal; a declared service class informs placement but never grants priority. See §8.3. The question asked whether to add a QoS dimension; the answer is no, and the reason is that we could not enforce one.
+

Tenant grouping ambiguity — resolved outside this framework. ADR-0013 revision 2 makes the identifier segment immutable onboarding-time history and the tenant-engine record authoritative for current grouping. Consumers do not parse current policy or spend-ceiling inputs from tenant:<grouping>:<name>.

+
+

20Ratification path

+
  1. Reviewed by tenant-engine, flex-auth, audit-core, rapp-postgres, railiance-platform and adaptive-pricing against §19. Complete in draft-8.
  2. Each publishes its own posture vector (§5) as part of review. The framework is validated by whether it can describe them accurately — if a repo cannot express itself in these six ladders, the ladders are wrong and this document changes, not the repo. Complete in draft-8; all six root declarations validate against the canonical schema.
  3. On acceptance, supersedes the routing of rapp-postgres/docs/canon-drafts/shared-platform-relational-storage_v0.1-draft.md, whose §§3–8 are absorbed here. That draft is withdrawn rather than left pending.
  4. On acceptance, rapp-postgres ADR-0001 through ADR-0004 move to accepted and are annotated as the PostgreSQL implementation of the E, P, R and shared-capacity rules.
+
netkingdom-tenancy-posture · draft-14 · proposednet-kingdom · canon/standards/tenancy-posture_v0.1.md · ce198fc2905687ea90a2892346b6860281ac87f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0001.html b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0001.html new file mode 100644 index 0000000..068bb19 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0001.html @@ -0,0 +1,216 @@ + + + + +ADR-0001 — The routing catalog is a pointer layer, never a second copy + +
ops-warden-adr-0001 accepted · 1 ops-warden reviewed 2026-08-18generated from canonical source — do not edit

ADR-0001 — The routing catalog is a pointer layer, never a second copy

Source: ops-warden · docs/adr/ADR-0001-catalog-is-a-pointer-layer.md · 35aff380a33f51a512c1e1b42d52d1dc0d95930f

Review due: 2027-02-18

Status

+

Accepted. Decided during WARDEN-WP-0010 (access routing charter), enforced in code since WARDEN-WP-0011. Restated here because it binds repos other than ops-warden and had, until now, no address they could cite.

+
+

Context

+

registry/routing/catalog.yaml tells a worker which subsystem owns a credential need and where the authoritative procedure lives. The obvious temptation, every time someone uses it, is to add the procedure itself: the reader is already here, the steps are short, and one more copy seems cheaper than a second lookup.

+

That temptation is the failure mode. A copied procedure is correct on the day it is written and silently wrong afterwards, because the owner changes theirs without knowing ours exists. The estate has already paid for this once, between an ADR and its published page — fixed by generating the page from the markdown rather than maintaining both.

+

The catalog is consulted precisely when someone is about to touch a credential. Being confidently wrong there is worse than being absent.

+
+

Decision

+

For any subsystem ops-warden does not own, a catalog entry carries identifiers and pointers only — owner_repo, subsystem, wiki_ref, canon_ref, need_keywords, and the secret-free handoff metadata warden access needs.

+

Authored procedure is permitted only where warden_executes: true. A steps: block and a cert_command: may exist on the SSH certificate lane and nowhere else, because that is the one lane ops-warden actually owns. Rotation steps: are the narrow exception and describe what the owner does, recorded because rotation guidance had no other home; they are still pointers in spirit and must not grow into a runnable substitute for the owner's tooling.

+

No secret material in this file, ever.

+

This is enforced, not merely documented. tests/test_routing.py fails any non-SSH entry carrying a steps block, and checks that every wiki_ref anchor resolves to a real section. A rule that is only written down is a rule that erodes.

+
+

Consequences

+

Accepted cost. Two lookups instead of one. A worker who wants the procedure follows the pointer. We consider a correct second hop cheaper than a stale first one.

+

Anchors must resolve. Because the entry is only a pointer, a broken pointer is a total failure rather than a cosmetic one. Hence the anchor test — which has already caught a real break (ADHOC-2026-08-11-T01, a stale rapp-qonto-keycape-client anchor).

+

Other repos are bound by this. When another repo asks us to add or rename a lane, we add the pointer and decline to absorb the procedure. That has been exercised: on 2026-08-11 railiance-platform asked ops-warden to rename an active lane, and the answer was to cross-reference the id from their CCR rather than have this repo carry a second identity for the same thing.

+

It constrains what this repo may usefully become. ops-warden cannot grow into a documentation site for other people's credential procedures, however often that is asked for. The value of the catalog is that a reader knows it points at truth rather than at a copy of truth.

+
+
ops-warden-adr-0001 · 1 · acceptedops-warden · docs/adr/ADR-0001-catalog-is-a-pointer-layer.md · 35aff380a33f51a512c1e1b42d52d1dc0d95930f
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0002.html b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0002.html new file mode 100644 index 0000000..afb03ed --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0002.html @@ -0,0 +1,216 @@ + + + + +ADR-0002 — ops-warden is a transparent conduit, never a secret broker + +
ops-warden-adr-0002 accepted · 1 ops-warden reviewed 2026-08-18generated from canonical source — do not edit

ADR-0002 — ops-warden is a transparent conduit, never a secret broker

Source: ops-warden · docs/adr/ADR-0002-conduit-not-broker.md · 35aff380a33f51a512c1e1b42d52d1dc0d95930f

Review due: 2027-02-18

Status

+

Accepted. Decided during WARDEN-WP-0014 (operator access assist), tightened by WARDEN-WP-0026 (disclosure hygiene).

+
+

Context

+

warden access is the operator front door for every credential need in the estate. For lanes marked exec_capable it does more than advise: it runs the owner's tool and returns the value. Anything that fetches secrets on request looks like a broker, and the gravity toward becoming one is strong — a broker is more convenient at every individual call site.

+

The distinction is not stylistic. A broker holds authority; a conduit borrows the caller's. Only one of those creates a new thing worth attacking.

+
+

Decision

+

ops-warden runs the owner's tool with the caller's own identity, and takes no custody of the value. The caller's credentials do the work. ops-warden holds nothing after the command returns, stores nothing, and caches nothing.

+

Forbidden: a standing broker. ops-warden must not hold its own long-lived secret-read credential in order to serve values to callers who could not have fetched them themselves. If the caller lacks authority, the correct outcome is a denial from the owner's system — not a fetch performed on their behalf by a more privileged intermediary.

+

The test is a question: could the caller have run this themselves? If yes, we are a conduit and may proxy. If no, proxying is privilege laundering and is refused.

+

Owner-native front doors outrank the proxy. Where an owner has shipped their own exec surface — secrets-engine exec, the railiance-platform credential broker — we route there and do not proxy. The proxy is a fallback for lanes nobody fronts yet, not a preferred path. This is why whynot-design-npm-publish and ops-warden-warden-sign-token are native rather than interim.

+

The value must not land somewhere it will be logged. Sanctioned transports are --out (mode-0600 file), --exec (child process env), and --wrap (a single-use OpenBao wrapping token). Streaming to a non-terminal stdout is refused without an explicit --unsafe-stdout, which exists for interactive humans only.

+
+

Consequences

+

ops-warden never becomes a credential store, and gains no value by being compromised beyond the SSH CA it already holds. This is the whole point. An attacker who owns ops-warden gets the SSH signing lane — serious, bounded, and already the thing this repo is hardened around — not a key to every secret in the estate.

+

Some requests cannot be served, and that is the correct answer. When a caller lacks authority, ops-warden routes and explains rather than fetching. This reads as unhelpfulness at the moment it happens; it is the property that makes the front door safe to point every agent at.

+

Every proxied fetch is auditable and attributable to the caller, because it ran as them. audit.jsonl records metadata only — never values, guarded in code.

+

The --unsafe-stdout escape hatch is a known liability. It exists because humans in terminals legitimately need to see values. It is also exactly the shape of the 2026-07-16 disclosure, where a value reached a captured stdout. ADR-0004 constrains it further for agent sessions.

+
+
ops-warden-adr-0002 · 1 · acceptedops-warden · docs/adr/ADR-0002-conduit-not-broker.md · 35aff380a33f51a512c1e1b42d52d1dc0d95930f
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0003.html b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0003.html new file mode 100644 index 0000000..f989510 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0003.html @@ -0,0 +1,218 @@ + + + + +ADR-0003 — Cover gaps, but never silently own them + +
ops-warden-adr-0003 accepted · 1 ops-warden reviewed 2026-08-18generated from canonical source — do not edit

ADR-0003 — Cover gaps, but never silently own them

Source: ops-warden · docs/adr/ADR-0003-cover-gaps-never-silently-own-them.md · 35aff380a33f51a512c1e1b42d52d1dc0d95930f

Review due: 2027-02-18

Status

+

Accepted. Stated as INTENT §9, made structural by WARDEN-WP-0030 (delegation register).

+
+

Context

+

ops-warden owns exactly one lane: SSH certificate issuance. It nonetheless fronts around eleven credential lanes as a caller-identity proxy, because no other component fronts them yet and a worker blocked on a credential is a worker blocked.

+

Covering a gap is legitimate and this repo intends to keep doing it. The failure is subtler: a cover that is never recorded as a cover becomes ownership by default. Nobody decides to permanently own another component's lane. It happens because the interim arrangement worked, nobody wrote down that it was interim, and the intended owner never learned they were expected to build a front door.

+

By August 2026 the primitive to hand a lane back existed and was proven — exec_owner / exec_command, used by secrets-engine for npm publish and by the railiance-platform credential broker for warden-sign — and was used by 2 of 24 lanes. The other twenty-two had no record of who should own them.

+
+

Decision

+

Every catalog entry carries a delegation: block, with a mode: of:

+
modeMeaning
permanentOurs forever. SSH certificate issuance, and nothing else
nativeThe owner has a front door; we route to it and execute nothing
interimWe are covering a gap. Requires intended_owner: and blocked_on:
+

interim without an intended_owner is not permitted. If we cannot name who should own it, we have not understood the lane well enough to be fronting it.

+

blocked_on: must name a specific condition, not a mood. "No front door yet" is not a blocker; "secrets-engine has not confirmed whether exec --catalog generalizes over arbitrary OpenBao lanes (asked 2026-08-11, msg 7d55d332)" is. A blocker with a question and a date can be chased. A blocker without one is an excuse with a timestamp.

+

The interim set is queryable: warden route gaps lists it with review dates and staleness. A cover that nobody can enumerate is a cover nobody will retire.

+

A blocker is a claim about the world at a date, and expires. reviewed: is bumped only on a real re-check, never inherited. This was learned the hard way: RISK-F-0001 invalidated one of our blockers within a day and nothing would have re-checked it.

+
+

Consequences

+

Retiring a cover is a normal, expected event rather than a renegotiation. When an owner ships their front door the lane flips interim → native. This has happened twice and both were routine.

+

Other repos can see what we are holding for them. The register is why key-cape and user-engine were able to confirm or decline lanes in August 2026 — the question was answerable because it had been written down. One of those answers was "not ours", which is a legitimate and useful outcome.

+

We accept looking worse than we are. warden route gaps publishes a list of things this repo is doing that it would rather not be doing. That is the intent: the alternative is a repo that looks clean because nobody counted.

+

This register is not a risk register. Interim lanes are tracked work with an owner and a date, not defects. They do not get bulk-filed into risk-nexus, which needs to stay small enough to read. Defects go there; gaps stay here.

+
+
ops-warden-adr-0003 · 1 · acceptedops-warden · docs/adr/ADR-0003-cover-gaps-never-silently-own-them.md · 35aff380a33f51a512c1e1b42d52d1dc0d95930f
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0004.html b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0004.html new file mode 100644 index 0000000..2399b22 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0004.html @@ -0,0 +1,216 @@ + + + + +ADR-0004 — High-risk lanes refuse raw value streaming to agent sessions + +
ops-warden-adr-0004 accepted · 1 ops-warden reviewed 2026-08-18generated from canonical source — do not edit

ADR-0004 — High-risk lanes refuse raw value streaming to agent sessions

Source: ops-warden · docs/adr/ADR-0004-agent-read-boundary-on-high-risk-lanes.md · 35aff380a33f51a512c1e1b42d52d1dc0d95930f

Review due: 2027-02-18

Status

+

Accepted. Decided during WARDEN-WP-0026 (credential disclosure hygiene), in response to a real disclosure on 2026-07-16.

+
+

Context

+

On 2026-07-16 a secret value reached a captured stdout. The mechanism was ordinary: bao kv get -field=X in an agent session. Nothing was misconfigured and nobody misused a tool. The value was read correctly, by an authorized caller, using the documented command — and an agent session records its stdout, so the value landed in a transcript that outlives the shell.

+

This is a structural mismatch, not a mistake to train away. Agent sessions are logged by design; that is what makes them reviewable. A human at a terminal sees a value and it scrolls away. An agent "seeing" a value writes it into a durable context that may be stored, replayed, or sent to an inference provider.

+

Guidance alone will not fix it. The command is correct, it is in every runbook, and the next agent that needs the value will reach for it.

+
+

Decision

+

When WARDEN_AGENT_ID is set and the catalog lane is risk: high, ops-warden refuses to stream the raw value and exits 7. The agent is not blocked from doing its work — --out, --exec, --wrap and --fingerprint all remain available. It is blocked from doing its work in a way that writes the secret into a transcript.

+

The boundary is enforced at the credential store as well as at the CLI. The OpenBao policy agent-high-risk-boundary denies data-read on those paths for agent tokens, allowing metadata and capabilities only. A control that lives solely in our own CLI is a control that ends the moment someone calls bao directly.

+

Verification must not require a read. To check a lane, use bao token capabilities — allow/deny — never a read of the value. This is the specific habit the disclosure taught us to break.

+

Exposure is reportable without reading. warden taint <catalog-id> reports KV v2 custom_metadata (exposed_at, exposed_version) and touches no secret data.

+
+

Consequences

+

Agents can still do everything they could before, by a different route. --exec covers nearly every real case: the child process gets the value in its environment, the agent never sees it. The friction is deliberate and small.

+

Exit 7 is a contract other runtimes depend on. It is a distinguishable code, not a generic failure, so a caller can tell "refused by boundary" from "lane broken" and retry correctly. Changing it is a breaking change to every agent runtime.

+

risk: high becomes a load-bearing catalog field rather than documentation. Classifying a lane now changes runtime behaviour, so it must be set deliberately.

+

We accept that --unsafe-stdout still exists for humans. The boundary keys on WARDEN_AGENT_ID, so an agent that does not set it is not caught. That is a known limit: this ADR raises the floor for cooperating runtimes and hardens the store behind them; it does not claim to stop a determined caller.

+
+
ops-warden-adr-0004 · 1 · acceptedops-warden · docs/adr/ADR-0004-agent-read-boundary-on-high-risk-lanes.md · 35aff380a33f51a512c1e1b42d52d1dc0d95930f
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0005.html b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0005.html new file mode 100644 index 0000000..2032cad --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0005.html @@ -0,0 +1,214 @@ + + + + +ADR-0005 — Implement one lane narrowly, route everything else + +
ops-warden-adr-0005 accepted · 1 ops-warden reviewed 2026-08-18generated from canonical source — do not edit

ADR-0005 — Implement one lane narrowly, route everything else

Source: ops-warden · docs/adr/ADR-0005-implement-narrowly-route-broadly.md · 35aff380a33f51a512c1e1b42d52d1dc0d95930f

Review due: 2027-02-18

Status

+

Accepted. The founding charter decision, taken 2026-06-18 (history/2026-06-18-access-routing-intent-shift-assessment.md).

+
+

Context

+

ops-warden began as an SSH certificate manager. It then became the place workers asked when they did not know where a credential came from — which is a real need, and the obvious way to serve it is to start fetching credentials.

+

Down that path is a component that issues SSH certificates, vends API keys, brokers tokens, and holds authority over all of them: a single point whose compromise is total. NetKingdom's architecture deliberately separates identity (key-cape), authorization (flex-auth), and secrets (OpenBao). A helpful front door that absorbed all three would quietly undo that separation, one convenience at a time.

+
+

Decision

+

ops-warden executes exactly one lane with its own authority: SSH certificate issuance for adm/agt/atm actors. warden_executes: true appears on one catalog entry and is expected to stay that way.

+

For every other need it routes, and where the lane is exec_capable it may assist by proxying as the caller under ADR-0002. Routing is not a lesser service — it is the service. Knowing which subsystem owns a need, and being right about it, is what this repo sells.

+

Scope growth is tested by ownership, not by usefulness. "Would this be handy in ops-warden?" is the wrong question and almost always answers yes. The right question is "does ops-warden have the authority to own this, permanently?" If the answer is no, the correct outcome is a pointer, or an interim cover recorded under ADR-0003.

+
+

Consequences

+

The blast radius stays bounded and known. Compromising ops-warden yields the SSH signing lane. That is worth defending well precisely because it is the only thing here.

+

We say no to requests that would be easy to say yes to. warden secret, warden login, warden bao, warden tunnel do not exist and must not be invented; the agent instructions name them as anti-patterns because agents keep reaching for them. Each would be a day's work and a permanent widening.

+

Being useful therefore depends on the pointers being right, which is the whole weight behind ADR-0001's anchor enforcement and the catalog's review dates. A router that routes wrongly is worse than no router.

+

It leaves real gaps visible rather than filled. Six workload lanes and three tenant lanes are covered interim because secrets-engine and tenant-engine have not shipped front doors. Under this ADR that is the correct state, tracked under ADR-0003, and not a signal that ops-warden should absorb them.

+
+
ops-warden-adr-0005 · 1 · acceptedops-warden · docs/adr/ADR-0005-implement-narrowly-route-broadly.md · 35aff380a33f51a512c1e1b42d52d1dc0d95930f
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0007.html b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0007.html new file mode 100644 index 0000000..a848e6e --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0007.html @@ -0,0 +1,217 @@ + + + + +ADR-0007 — Build-stage permissiveness stops at credential disclosure + +
ops-warden-adr-0007 accepted · 1 ops-warden reviewed 2026-08-19generated from canonical source — do not edit

ADR-0007 — Build-stage permissiveness stops at credential disclosure

Source: ops-warden · docs/adr/ADR-0007-build-stage-stops-at-credential-disclosure.md · 4e267179db741b27a3e62f81f753cd9752c97412

Review due: 2027-02-19

Status

+

Accepted 2026-08-19, alongside grading the last 14 ungraded catalog lanes.

+
+

Context

+

ADR-0006 deferred a global fail-closed authorization gate because uniform enforcement across an estate under deep refactor hardens the access needed to perform the refactor. The organization's declared posture is build (WP-0029), and the operator has confirmed the estate need not be tight yet.

+

That is correct, and it is also the kind of principle that quietly generalises past its warrant. Read loosely, "we are in build stage" argues for relaxing every control, including the ones that stop a credential landing in a logged agent transcript. Those are not the same class of control, and the difference is not severity — it is cost.

+

RISK-F-0003 made the distinction concrete. ADR-0004 reads as a categorical rule: high-risk lanes refuse raw value streaming to agent sessions. The implementation was risk == "high" against an optional field, so 14 of 27 lanes never reached the control at all — five of them exec_capable. The control had not been relaxed by anyone's decision. It had simply never been reached, which is worse, because nothing announced it.

+
+

Decision

+

Build-stage permissiveness applies to controls that gate work. It does not apply to controls that prevent credential disclosure.

+

The test is friction, not severity:

+
  • A control that can block a legitimate operation — a fail-closed authorization gate, an enforcement stance — is a candidate for relaxation while the organization is in build, and ADR-0006 scopes that relaxation to zones.
  • A control that redirects how a value moves without preventing the work — the agent read-boundary, which refuses raw stdout but leaves --out, --exec, --wrap and --fingerprint fully available — is not relaxed by build posture, because relaxing it buys nothing. Nobody is unblocked by it.
+

The asymmetry that settles it: a blocked operation is recovered by retrying. A credential written into a logged transcript is not recovered by rotation — rotation limits the damage, it does not unwrite the log. The 2026-07-16 disclosure is the case in point.

+

Therefore, regardless of organization_posture:

+
  1. Every catalog lane carries an explicit risk grade. Absence is not a grade, and a lane that omits it is a defect, not a default.
  2. Grading is done on merit, per lane. This decision is not licence to grade everything high — an over-broad grade is its own inaccuracy, and tenancy-posture §6's accuracy, not altitude applies to this field too.
  3. Minimum credential-handling standards — the read-boundary, the safe fetch transports, the no-secret audit guard — hold in every posture.
+
+

Consequences

+

We accept the grading cost, now and on every new lane. That is the point: WARDEN-WP-0032-T06 makes an ungraded lane impossible rather than merely discouraged, because a rule enforced by remembering is not enforced.

+

We reject "build stage" as a general argument in credential-handling discussions. It is a real and useful argument about gating, and citing it against a disclosure control is a category error this record exists to name.

+

We note what this decision is not. It does not set severity for RISK-F-0003 — that is risk-nexus's. It does not make ops-warden the judge of other repos' controls. And it does not survive contact with a zone model that says otherwise: when zone-engine defines admission standards, a zone may legitimately require more than this floor. It may not require less.

+
+
ops-warden-adr-0007 · 1 · acceptedops-warden · docs/adr/ADR-0007-build-stage-stops-at-credential-disclosure.md · 4e267179db741b27a3e62f81f753cd9752c97412
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0008.html b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0008.html new file mode 100644 index 0000000..fe4d585 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0008.html @@ -0,0 +1,215 @@ + + + + +ADR-0008 — A lane's risk grade covers every field its path discloses + +
ops-warden-adr-0008 accepted · 1 ops-warden reviewed 2026-08-21generated from canonical source — do not edit

ADR-0008 — A lane's risk grade covers every field its path discloses

Source: ops-warden · docs/adr/ADR-0008-grade-the-path-not-the-field.md · 4e267179db741b27a3e62f81f753cd9752c97412

Review due: 2027-02-21

Status

+

Accepted 2026-08-21, after secrets-engine found two under-graded lanes while reviewing ops-warden's own catalog metadata.

+
+

Context

+

ADR-0007 requires every catalog lane to carry an explicit risk grade. It does not say what the grade is of, and the omission turned out to matter.

+

The catalog describes a lane by a single fetch_command naming a single field — bao kv get -field=ISSUE_CORE_API_KEY <path>. Grading followed that description. But the unit of disclosure is not the field, it is the path: bao kv get without -field returns every key stored there, and an agent session that discloses one field has disclosed all of them.

+

On 2026-08-19, grading all 27 lanes, ops-warden graded issue-core-ingestion-api-key and reuse-surface-hub-write-token as standard — "ordinary internal workload secrets". Both grades read only the headline field. CCR-2026-0002 records a deliberate decision to keep GITEA_BACKEND_TOKEN at the first path; CCR-2026-0005 declares a dual-consumer webhook HMAC at the second. Neither is recovered by rotating the credential the lane is named after.

+

Three details make this worth a record rather than a fix:

+
  • The evidence was already ours. The field sets were in the CCRs the catalog already cites as authoritative. This was not missing data; it was unread data.
  • A test held the error still. test_high_risk_lanes_classified asserted issue-core-ingestion-api-key was not high. A first grading pass had marked it high, the test contradicted it, and the test was believed. A test that encodes a judgement defends that judgement from correction.
  • Another repo found it. secrets-engine graded both high independently while drafting catalog entries whose schema records fields. A schema that names the field set makes the right grade obvious; ours did not have one.
+
+

Decision

+

A lane's risk grade is a property of its path, and must cover the union of everything a read of that path would disclose.

+
  1. Where the field set is known, the catalog records it as fields, with the authority it came from.
  2. The grade is argued against the most damaging field, not the named one.
  3. Where the field set is unknown, that is stated — never assumed to be one field. An unverified field set is a reason to grade conservatively, matching the inter-hub-bootstrap-ssh precedent under ADR-0007.
  4. Establishing a field set must not be done by reading the secret. Use the owning CCR, the owner's catalog, or bao kv metadata. bao kv get on a high-risk path is the 2026-07-16 vector and is forbidden by ADR-0004 for agent sessions regardless of intent.
+
+

Consequences

+

ADR-0007 is unchanged and still governs: every lane carries an explicit grade, and absence fails safe. This record says what that grade must account for.

+

Grading gets more expensive: it now requires knowing what is at a path, not just what the lane is called. That cost is the point — the cheap version produced two wrong answers in one pass and is the reason this exists.

+

A test that asserts a grade is asserting a judgement. When a grade is disputed, re-argue it from evidence before trusting the test that encodes it.

+
+
ops-warden-adr-0008 · 1 · acceptedops-warden · docs/adr/ADR-0008-grade-the-path-not-the-field.md · 4e267179db741b27a3e62f81f753cd9752c97412
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0009.html b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0009.html new file mode 100644 index 0000000..b2b68a1 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0009.html @@ -0,0 +1,215 @@ + + + + +ADR-0009 — Adopt security-zones v0.1 as a consumer + +
ops-warden-adr-0009 accepted · 1 ops-warden reviewed 2026-08-22generated from canonical source — do not edit

ADR-0009 — Adopt security-zones v0.1 as a consumer

Source: ops-warden · docs/adr/ADR-0009-adopt-security-zones-as-a-consumer.md · 4e267179db741b27a3e62f81f753cd9752c97412

Review due: 2026-11-22

Status

+

Accepted 2026-08-22 after zone-engine completed ZONE-WP-0001-T03/T05 and published the declaration, compilation, stance, and failure-mode contract in canon revision 337484a; zone-engine's reference compiler is revision 9b6ada7.

+
+

Context

+

ADR-0006 rejected a repo-wide policy.enabled switch because one boolean plus one fail_closed value made flex-auth a uniform dependency of every signing path, including continuity paths needed to repair that dependency. It deferred the replacement to zone-engine rather than designing an estate model here.

+

The owning model now exists. A zone is an evidenced workload-admission fact; control stance remains with the control owner, and dependency failure behavior remains with the PEP. Membership resolves only through an authoritative workload identity. Missing identity, membership, admission evidence, or a required floor is unknown, never an inferred permissive zone.

+
+

Decision

+

Ops-warden adopts security-zones_v0.1 and accepts its initial build-stage rows for the controls ops-warden owns:

+
  • the pre-sign PEP fails open for z0-experimental, z1-operational, z2-protected, z2-continuity, and build-profile unknown; it fails closed for z3-critical;
  • the agent high-risk read boundary remains enforced and fail-closed in every zone and for unknown;
  • warden plan never derives autonomous authority from unknown zone evidence.
+

The implementation follows four rules:

+
  1. policy.enabled and the global policy.fail_closed setting are retired and rejected by configuration loading. The PEP chooses failure behavior from a total per-zone map.
  2. The existing compiled flex-auth registry is the resource-membership carrier. Actor resources receive workload_id, security_zone, security_zone_admission, and security_zone_revision. The dormant trust_zone: platform constant is removed; it is not repurposed.
  3. Workload joins are explicit. Managed deployables use Repo Manager's exact (rapp_id, workload_identity.name, deployable?) tuple. Independent operational workloads use their owner-reviewed tenancy.yaml. Catalog owners distinguish not-applicable from applicable-but-unknown; no path or repository-name inference is allowed.
  4. A fail-open signing result is metadata, not silence. Signature and unified audit records carry the selected zone, failure mode, outcome, and decision id when one exists.
+

Ops-warden itself declares z1-operational. That is an accuracy decision: the workload has M1 evidence and does not yet have the SLO history, on-call rotation, or exercised recovery evidence needed for z2 admission.

+
+

Consequences

+

The global flip and its failure cycle no longer exist. An unknown target remains observable and follows the versioned build profile without manufacturing membership. A future organization-posture graduation changes the versioned control profile, not each workload declaration.

+

The flex-auth policy package still owns pre-sign stance. Ops-warden can compile and send the membership attributes, handle allow/audit_only/deny, and apply the correct PEP failure mode; it does not write flex-auth's Rego rows.

+

Catalog coverage is intentionally honest at adoption: exact references resolve where authoritative declarations exist, applicable lanes without one report unknown with a reason, and generic actions/patterns are explicitly not-applicable. Resolution coverage improves by adding owner declarations, never by adding heuristics here.

+
+
ops-warden-adr-0009 · 1 · acceptedops-warden · docs/adr/ADR-0009-adopt-security-zones-as-a-consumer.md · 4e267179db741b27a3e62f81f753cd9752c97412
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0010.html b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0010.html new file mode 100644 index 0000000..f46abe7 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/ops-warden-adr-0010.html @@ -0,0 +1,218 @@ + + + + +ADR-0010 — ops-warden is Staff + +
ops-warden-adr-0010 accepted · 1 ops-warden reviewed 2026-08-28generated from canonical source — do not edit

ADR-0010 — ops-warden is Staff

Source: ops-warden · docs/adr/ADR-0010-ops-warden-is-staff.md · 4e267179db741b27a3e62f81f753cd9752c97412

Review due: 2026-11-28

Status

+

Accepted 2026-08-28, answering intake WARDEN-IN-0001 from gate-house, which carries decision GH-DEC-2026-001. The standard being adopted — net-kingdom/canon/standards/security-layer-model_v0.1.md — is proposed, and was proposed pending assent from flex-auth, kings-guard, and ops-warden. This ADR is ops-warden's half of that assent.

+
+

Context

+

The estate acquired overlapping claims to the same responsibility, most visibly two repositories describing themselves as the authorization control plane. The layer model resolves the overlap by layering repositories on determinism — Taxonomy, Tooling, Engines, Staff — and by two rules: Staff never touches Tooling directly (§5), and access-engine is the only policy decision point (§6).

+

ops-warden is assigned Staff. Two demarcations follow that touch this repository: the security curriculum it had been carrying belongs to gate-house, and the words access lane and access rule are bound to different owners.

+

Full reasoning: history/2026-08-28-security-layer-model-assent.md.

+
+

Decision

+

1. ops-warden is Staff and declares it. INTENT.md carries the layer label and the §5 invariant. ops-warden holds no state another layer depends on at runtime and renders no authorization decision — it consumes them.

+

2. Lanes, not rules. ops-warden owns how a worker reaches a host: SSH certificate issuance, the routing catalog, warden access, warden plan, cert_command. It never owns whether a worker may — that is access-engine (today flex-auth), and ops-warden neither renders nor caches that decision. This restates what ADR-0002 and ADR-0005 already bind; it is recorded here because the demarcation is now normative estate-wide and other repositories rely on ops-warden holding to it. The ruled rename flex-auth → access-engine is assented to; ops-warden asks only for a window in which both names resolve.

+

3. Doctrine goes to gate-house; runbooks stay here. ops-warden does not restate security doctrine, the authority model, or the curriculum. It references gate-house's. It keeps everything operational about the lanes it stewards: which subsystem owns which need, how to obtain a credential lane by lane, and conformance evidence for its own lanes. .claude/rules/credential-routing.md is runbook, not curriculum, and stays inlined in this and every other repository.

+

4. One declared engine gap, not an exemption. src/warden/vault.py (VaultCA) is a direct OpenBao client performing a write from a Staff repository. It is a §5 non-conformance. ops-warden declares it rather than arguing it away:

+
  • intended owner: secrets-engine (credential abstraction, custody, lifecycle)
  • blocked on: no engine exposes an SSH certificate signing surface
  • review: with this ADR, every 3 months
+

Until that surface exists, ops-warden continues to sign — refusing to would remove production host access to close a documentation gap — and reports the position as open. warden desk's bao kv put is declared on the same terms. taint.py is metadata-only observation, declared under §5's read-only allowance. proxy.py supplies no authority of its own: it runs the owner's tool under the caller's identity and is governed by ADR-0002.

+

This is ADR-0003 turned inward. ops-warden has required an intended owner and a blocker on 27 catalog lanes it holds for other repositories; it holds itself to the same record.

+
+

Consequences

+

ops-warden's conformance under §10 is declared non-conformant with a tracked closure path, not clean. That is the accurate state and it is the state that gets fixed, because it names an owner who can fix it.

+

An amendment to §5 has been offered to gate-house — a second sanctioned shape alongside read-only diagnostics: a declared engine gap carrying intended owner, blocker, and review date, machine-readable so §10 can tell a tracked gap from an undeclared violation. It is offered, not assumed; §5 stays gate-house's to write. If gate-house declines it, ops-warden's position is a plain non-conformance and is reported as one.

+

The NetKingdom Security Literacy section stops being a prose second source for registry/routing/catalog.yaml, which ADR-0001 had already ruled against for catalog procedure.

+
+
ops-warden-adr-0010 · 1 · acceptedops-warden · docs/adr/ADR-0010-ops-warden-is-staff.md · 4e267179db741b27a3e62f81f753cd9752c97412
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/policy-nexus-adr-0001.html b/docs/evidence/2026-10-11-served-publication-history/pages/policy-nexus-adr-0001.html new file mode 100644 index 0000000..25607cc --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/policy-nexus-adr-0001.html @@ -0,0 +1,214 @@ + + + + +Policy addressing and permanence + +
policy-nexus-adr-0001 accepted · accepted-1 the-custodian reviewed 2026-08-18generated from canonical source — do not edit

Policy addressing and permanence

Source: policy-nexus · docs/adr/ADR-0001-addressing-and-permanence.md · 6515ed9ef8499cb3de3397f0ae3993cc71440599

Review due: 2027-02-18

  • Status: accepted
  • Date: 2026-08-18
  • Owner: the-custodian
+

Decision

+

A document has one stable current address and immutable revision addresses:

+
/<kind>/<document>/<version>/
+/<kind>/<document>/<version>/revisions/<revision>/
+

<kind> is one of standards, architecture, constitution, or adr. <document> is a kebab-case slug that is unique on the site. The owner-facing rules live in docs/publication-contract.md.

+

The publication manifest records both. Existing public paths become permanent redirect aliases; the first is /tenancy-posture.html. No URL is derived from a checkout path, branch name, build number or hosting implementation.

+

The current address advances only when the owning source publishes a new revision. A revision address is write-once: the builder records the exact git commit (or commit plus working-tree marker) and the full source-content digest. It refuses to replace that revision with different content. Committing the same bytes may update the current page's provenance but does not rewrite the already-published revision page. The generated publication manifest makes the source repository, path, revision and digest machine-readable.

+

Superseded and withdrawn documents are never deleted. The current page gains a status banner and link to its successor; every historical revision continues to resolve. “Withdrawn” means retained and visibly non-current. Deletion is not a lifecycle state.

+

Source Git history remains the authority for versions that predate this site. Once a revision is published, the built revision page is also retained by the hosting artifact/release. Rollback republishes a previous complete build; it does not rebuild old pages from a changed working tree.

+
+

Permanence promise

+

A published URL is expected to resolve indefinitely. Breaking that promise requires an explicit the-custodian decision plus a redirect/export plan. A DNS, hosting or repository migration does not qualify: those must preserve paths.

+

This repo owns addressing, rendering and currency. It does not decide whether a draft is accepted; ratification belongs to the canon owner/the-custodian.

+
+

Publication scope

+

Canon standards, architecture, and constitution are in scope. values, tpsc, and projects are out until their owner marks individual documents as governing. Per-repo ADRs are in scope through explicit manifest entries. Workplans, evidence, runbooks and general docs are not.

+
+

Consequences

+
  • Builds fail if a source disappears, an id differs, a path collides, or an immutable revision would change; stale output is not silently called fresh.
  • Pages show status, revision, owner, last review and exact source revision.
  • Availability remains restart recovery on the single-node rail. This contract promises stable addressing, not a high-availability SLA.
+
policy-nexus-adr-0001 · accepted-1 · acceptedpolicy-nexus · docs/adr/ADR-0001-addressing-and-permanence.md · 6515ed9ef8499cb3de3397f0ae3993cc71440599
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/policy-nexus-architecture.html b/docs/evidence/2026-10-11-served-publication-history/pages/policy-nexus-architecture.html new file mode 100644 index 0000000..20fce95 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/policy-nexus-architecture.html @@ -0,0 +1,248 @@ + + + + +Policy Nexus architecture + +
policy-nexus-architecture proposed · draft-1 the-custodian reviewed 2026-08-18generated from canonical source — do not edit

Policy Nexus architecture

Source: policy-nexus · docs/architecture/policy-nexus_v0.1.md · a8fb62a3bcbff3daf03e5d3d56c3ca3a5bd991f8

Review due: 2027-02-18

About this document

+

This document follows the arc42 template for the publication surface at policy.coulomb.social. It is the first-wave architecture document this repository is allowed to author. Other first-wave systems are written in their owning repos.

+
+

01Introduction and Goals

+

Policy Nexus exists so estate canon and architecture decisions have a permanent, current, addressable public home. Before this system, governing documents were served from disposable artifact URLs.

+

1.1 Requirements Overview

+
  • Publish canon (standards, architecture, constitution) and per-repo ADRs from the repositories that own them.
  • Keep one stable current URL and one immutable revision URL per document (ADR-0001).
  • Fail closed when a source disappears or an immutable revision would change.
  • Show status, owner, revision, and review currency on every page.
+

1.2 Quality Goals

+
  1. Permanence of published addresses.
  2. Honesty about staleness.
  3. Source-of-truth stays upstream — this system never writes back.
  4. Builds are deterministic and stdlib-only.
+

1.3 Stakeholders

+
RoleConcern
the-custodianWhat counts as canon; ratification.
Source-repo ownersTheir document is published accurately.
railiance-platform / rapp-policy-nexusSubstrate, Helm, promotion.
Readers inside and outside the estateA URL that still resolves.
+
+

02Architecture Constraints

+

N/A for this stub — first-wave complete will record stdlib-only rendering, single-node rail availability, and the no-CMS rule.

+
+

03System Scope and Context

+

In: addressing, rendering, currency, the public static site. Out: ratification, legal advice, regulatory intake (risk-nexus), editing surfaces, search.

+

3.1 Business Context

+

Source owners publish governing documents. This system makes those documents findable and durable. It does not decide what is current canon.

+

3.2 Technical Context

+
  • Inputs: publication.json, sibling or fetched git checkouts, source-inventory.json.
  • Outputs: static HTML, publication-manifest.json, an OCI image.
  • Neighbours: owning source repos, Forgejo Actions, rapp-policy-nexus, railiance-apps, the reef Traefik/cert-manager substrate.
+
+

04Solution Strategy

+

N/A for this stub — pull-on-schedule ingestion, explicit manifest entries, stdlib renderer. To be written as first-wave complete.

+
+

05Building Block View

+

5.1 Level 1 – System/Top-Level

+

N/A for this stub — builder, inventory auditor, currency checker, OCI artifact, Helm package in rapp-policy-nexus.

+

5.2 Level 2 – Key Components

+

N/A for this stub.

+

5.3 Level 3 – Internal Structure (as needed)

+

N/A for this stub.

+
+

06Runtime View

+

N/A — the published site is static files. Daily source-pull and promotion are operational scenarios for a later revision.

+
+

07Deployment View

+

N/A for this stub — see WP-0001-T04. Single-node restart recovery, not a high-availability SLA.

+
+

08Cross-Cutting Concepts

+

N/A for this stub.

+
+

09Architecture Decisions

+
IdStatusDecisionSource
policy-nexus-adr-0001acceptedStable current address + immutable revision address; no deletiondocs/adr/ADR-0001-addressing-and-permanence.md
+

Later publish ADRs from WP-0003 land here. local and unresolved conflict rows do not.

+
+

10Quality Requirements

+

N/A for this stub — currency failure is already a build/CI failure.

+
+

11Risks and Technical Debt

+

N/A for this stub. Known residual: Markitect arc42-v1 schema is not registered yet (WP-0002-T02).

+
+

12Glossary

+
TermMeaning
Current addressThe stable URL for the document as it now stands.
Revision addressWrite-once URL for one source digest.
Publication entryOne object in publication.json. Discovery is not publication.
First-wave completeChapters 1, 3, 4, 5.1, 9 and 12 are real; others real or N/A.
+
policy-nexus-architecture · draft-1 · proposedpolicy-nexus · docs/architecture/policy-nexus_v0.1.md · a8fb62a3bcbff3daf03e5d3d56c3ca3a5bd991f8
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/railiance-architecture.html b/docs/evidence/2026-10-11-served-publication-history/pages/railiance-architecture.html new file mode 100644 index 0000000..306ce47 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/railiance-architecture.html @@ -0,0 +1,246 @@ + + + + +Railiance architecture + +
railiance-architecture proposed · draft-3 railiance-master reviewed 2026-08-31generated from canonical source — do not edit

Railiance architecture

Source: railiance-master · docs/architecture/railiance_v0.1.md · 5ffd7d1b40d56249f490a318e728047fd3517c4c

Review due: 2027-02-28

About this document

+

First-wave arc42 for the Railiance runtime substrate. Deeper chapters belong to follow-on work in this repo. Chapter 9 lists the ADRs this repo already publishes; it does not paste them.

+
+

01Introduction and Goals

+

Railiance-master is the authoritative source for Railiance framework architecture: repo families, workload models, and substrate boundaries that implementation repos must not invent locally.

+

1.1 Requirements Overview

+
  • Name the rails, reefs, and rApps and who owns each boundary.
  • Keep those decisions in docs/adr/ with publication-grade metadata.
  • Consume platform packages; do not fork identity or tenancy.
+

1.2 Quality Goals

+
  1. Reviewable boundary decisions.
  2. Private-by-default exposure until admission.
  3. Derived rails compose; they do not fork policy.
+

1.3 Stakeholders

+
RoleConcern
railiance-masterFramework language and first-wave rApp set.
railiance-platformS3, placement, substrate services.
railiance-appsProduction digest bindings.
NetKingdomIdentity and tenancy posture of workloads.
+
+

02Architecture Constraints

+

N/A for this stub.

+
+

03System Scope and Context

+

In: rails, reefs, rApp packaging, admission, exposure defaults. Out: tenant identity semantics (NetKingdom), publication of policy (policy-nexus), OS baseline (railiance-hosts).

+

3.1 Business Context

+

Implementation repos solve immediate cluster problems. This system holds the shared meaning so those repos do not drift.

+

3.2 Technical Context

+

Neighbours: railiance-platform, railiance-apps, rapp-* packages, the reef (Traefik, cert-manager), Forgejo, NetKingdom, policy-nexus.

+
+

04Solution Strategy

+

N/A for this stub — repository-prefix architecture and rapp-first wave are already in the ADRs in §9.

+
+

05Building Block View

+

5.1 Level 1 – System/Top-Level

+

N/A for this stub.

+
+

06Runtime View

+

N/A for this stub.

+
+

07Deployment View

+

N/A for this stub.

+
+

08Cross-Cutting Concepts

+

N/A for this stub.

+
+

09Architecture Decisions

+

Published on policy.coulomb.social from this repo:

+
IdStatusDecision
RMASTER-ADR-0001acceptedRepository prefix architecture
RMASTER-ADR-0002acceptedWave 1 rail-kubernetes boundary
RMASTER-ADR-0003acceptedFirst-wave rapp selection
RMASTER-ADR-0004acceptedFirst-wave reef rollout
RMASTER-ADR-0005acceptedDerived rail composition
RMASTER-ADR-0006acceptedReef production admission
RMASTER-ADR-0007acceptedRapp declaration contract
RMASTER-ADR-0008acceptedPrivate-by-default exposure
RMASTER-ADR-0009acceptedNetKingdom security-layer interaction
+

Also published from railiance-platform: RPLAT-ADR-0001 (S3 platform services), RPLAT-ADR-0002 (placement rule), RPLAT-ADR-0003 (decisions live in the repo).

+

Unresolved: identical accepted ADR-003/004 copies in railiance-hosts and railiance-infra. Not listed as current here until those owners rule.

+
+

10Quality Requirements

+

N/A for this stub.

+
+

11Risks and Technical Debt

+

N/A for this stub.

+
+

12Glossary

+
TermMeaning
RailA composed runtime path.
ReefThe production admission environment.
rAppA packaged workload bound by a declaration contract.
+
railiance-architecture · draft-3 · proposedrailiance-master · docs/architecture/railiance_v0.1.md · 5ffd7d1b40d56249f490a318e728047fd3517c4c
diff --git a/docs/evidence/2026-10-11-served-publication-history/pages/state-hub-architecture.html b/docs/evidence/2026-10-11-served-publication-history/pages/state-hub-architecture.html new file mode 100644 index 0000000..f450685 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/pages/state-hub-architecture.html @@ -0,0 +1,247 @@ + + + + +State Hub architecture + +
state-hub-architecture proposed · draft-3 state-hub reviewed 2026-08-31generated from canonical source — do not edit

State Hub architecture

Source: state-hub · docs/architecture/state-hub_v0.1.md · da30ce6723f9c5e6fdd994c78bb646986e819d3f

Review due: 2027-02-28

About this document

+

First-wave arc42 for State Hub, the estate's live coordination read-model. This service is in active retirement planning; new permanent ownership should not land here. Chapter 9 points at the estate ADRs that still bind it.

+
+

01Introduction and Goals

+

State Hub is a queryable, auditable memory of work: domains, repos, workplans, tasks, decisions, progress. Files remain the origin. The hub is derived state (custodian ADR-001, ADR-003).

+

It remains operational until retirement gates in prj-state-hub-retirement are met. Replacement ownership is moving toward repo-manager and hub-core.

+

1.1 Requirements Overview

+
  • Rebuild coordination state from registered repository files.
  • Deterministic UUIDv5 work-record identifiers (ADR-007). Any instance may reconcile the same repository; the canonical record id produces the same database key and writeback bytes on every instance.
  • Preserve compatibility; do not take new permanent architectural ownership.
+

1.2 Quality Goals

+
  1. Rebuildability from git.
  2. Hub never becomes the origin of work.
  3. Extraction paths stay open.
+

1.3 Stakeholders

+
RoleConcern
state-hubLive read-model during retirement.
the-custodianEstate rules the hub must not invert.
repo-managerIncoming consistency / repo representation.
product reposWorkplan files the hub indexes.
+
+

02Architecture Constraints

+

N/A for this stub — retirement program is the binding constraint.

+
+

03System Scope and Context

+

In: indexing workplans/tasks/decisions, consistency rebuild, query API and dashboard used today. Out: being the source of work items; new cross-domain capabilities; publication of policy (policy-nexus).

+

3.1 Business Context

+

Files are excellent for canon and provenance. The estate still needs a live query surface while retirement proceeds.

+

3.2 Technical Context

+

Inputs: workplan markdown via fix-consistency. Outputs: HTTP/MCP APIs. Neighbours: every registered repo, activity-core (ops runs), policy-nexus (does not index the hub).

+
+

04Solution Strategy

+

N/A for this stub. The strategy is already in the estate ADRs: files first, materialized derived state, single registrar, local cache vs authority (ADR-010, proposed).

+
+

05Building Block View

+

5.1 Level 1 – System/Top-Level

+

N/A for this stub.

+
+

06Runtime View

+

N/A for this stub.

+
+

07Deployment View

+

N/A for this stub.

+
+

08Cross-Cutting Concepts

+

N/A for this stub.

+
+

09Architecture Decisions

+

Estate-wide binding decisions live in the-custodian and are listed on the estate map. Service-local implementation decisions live in docs/adr/ and do not supersede the estate decisions:

+
Estate ADRStatusWhy it binds this system
CUST-ADR-001acceptedFile-backed work originates in repositories; the Hub projects it. Published /adr/custodian-workplans-as-repo-artefacts/v1/.
CUST-ADR-003acceptedHow derived state invalidates; ADR-012 supplies commit provenance.
CUST-ADR-007acceptedNamespace-aware identity and deterministic work-record UUIDs.
CUST-ADR-010proposedTwo kinds of hub data.
CUST-ADR-011proposedNamespace and reconciliation limits.
CUST-ADR-012acceptedForge projection baseline and preliminary overlays.
+
Local ADRStatusScope
STATE-ADR-001acceptedRepository identity and recovery contract for canonical-name changes.
+

Do not treat a State Hub /decisions row as the published ADR.

+
+

10Quality Requirements

+

N/A for this stub.

+
+

11Risks and Technical Debt

+

N/A for this stub. Residual: this workstation cannot mint registrar UUIDs.

+
+

12Glossary

+
TermMeaning
Read modelDerived index; never the origin.
RegistrarThe single instance allowed to mint workplan UUIDs.
RetirementCoordinated move of capabilities out of this repo.
+
state-hub-architecture · draft-3 · proposedstate-hub · docs/architecture/state-hub_v0.1.md · da30ce6723f9c5e6fdd994c78bb646986e819d3f
diff --git a/docs/evidence/2026-10-11-served-publication-history/publication-manifest.json b/docs/evidence/2026-10-11-served-publication-history/publication-manifest.json new file mode 100644 index 0000000..05aa68a --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/publication-manifest.json @@ -0,0 +1,1230 @@ +{ + "documents": [ + { + "canonical_path": "standards/tenancy-posture/v0.1/index.html", + "currency": "current", + "id": "netkingdom-tenancy-posture", + "last_reviewed": "2026-08-23", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-23", + "revision": "draft-14", + "revision_path": "standards/tenancy-posture/v0.1/revisions/draft-14/index.html", + "source_digest": "27d6878c68310619877de516000d9af78d5456a92572a709884ed052d765b876", + "source_path": "canon/standards/tenancy-posture_v0.1.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "proposed", + "title": "NetKingdom Tenancy Posture v0.1" + }, + { + "canonical_path": "standards/autonomy-lanes/v0.1/index.html", + "currency": "current", + "id": "canon-autonomy-lanes", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-1", + "revision_path": "standards/autonomy-lanes/v0.1/revisions/accepted-1/index.html", + "source_digest": "106cb0c99f73983199c9f1d8491143aa2a2eb41ee450a02ec7968d3763f30399", + "source_path": "canon/standards/autonomy-lanes_v0.1.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Autonomy Lanes (Fleet) v0.1" + }, + { + "canonical_path": "standards/contribution-convention/v0.1/index.html", + "currency": "current", + "id": "canon-contrib-convention", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-1", + "revision_path": "standards/contribution-convention/v0.1/revisions/accepted-1/index.html", + "source_digest": "6dfc1d7b2b82ab18228a84660c1c7dff607aad1d9da37ac12084cdd2ab8ae112", + "source_path": "canon/standards/contribution-convention_v0.1.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Contribution Convention v0.1" + }, + { + "canonical_path": "standards/project-repository-flavor/v0.1/index.html", + "currency": "current", + "id": "canon-project-repository-flavor", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-1", + "revision_path": "standards/project-repository-flavor/v0.1/revisions/accepted-1/index.html", + "source_digest": "61889b2f18a82a08b66ef90d758efe5c028a4f52aced1721a23e69c6f24a8224", + "source_path": "canon/standards/project-repository-flavor_v0.1.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Project Repository Flavor (prj-) v0.1" + }, + { + "canonical_path": "standards/work-record-types/v0.1/index.html", + "currency": "current", + "id": "canon-work-record-types", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-1", + "revision_path": "standards/work-record-types/v0.1/revisions/accepted-1/index.html", + "source_digest": "1a40de294be332ee713cdaab532a32a83718f3952c339fbff835ecee899a8dbc", + "source_path": "canon/standards/work-record-types_v0.1.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Work Record Types & Identity (Fleet) v0.1" + }, + { + "canonical_path": "standards/workplan-terminology/v0.1/index.html", + "currency": "current", + "id": "canon-workplan-terminology-fleet", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-1", + "revision_path": "standards/workplan-terminology/v0.1/revisions/accepted-1/index.html", + "source_digest": "199812632ac342ca3231498e545e3701e10c191ca144035b36a23c1a0b717712", + "source_path": "canon/standards/workplan-terminology-fleet_v0.1.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Workplan Terminology (Fleet) v0.1" + }, + { + "canonical_path": "architecture/coulomb-estate/v0.1/index.html", + "currency": "current", + "id": "coulomb-estate-architecture", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "draft-3", + "revision_path": "architecture/coulomb-estate/v0.1/revisions/draft-3/index.html", + "source_digest": "3b4f93acdedc6f6d1a1c737c3327d9d55061506c5a879be09e6b0a2ea7f5f9bc", + "source_path": "canon/architecture/coulomb-estate_v0.1.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "proposed", + "title": "Coulomb estate architecture" + }, + { + "canonical_path": "architecture/railiance/v0.1/index.html", + "currency": "current", + "id": "railiance-architecture", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-02-28", + "revision": "draft-3", + "revision_path": "architecture/railiance/v0.1/revisions/draft-3/index.html", + "source_digest": "77de319978dfad02f40658bbeb96db46b246a46f1abd6a45f5b1269563de5568", + "source_path": "docs/architecture/railiance_v0.1.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "proposed", + "title": "Railiance architecture" + }, + { + "canonical_path": "architecture/net-kingdom/v0.1/index.html", + "currency": "current", + "id": "net-kingdom-architecture", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-28", + "revision": "draft-3", + "revision_path": "architecture/net-kingdom/v0.1/revisions/draft-3/index.html", + "source_digest": "b9f89f706a801d54961fc67d725ca433b7c3b6a083c54793de6e8b60d1dd42a8", + "source_path": "docs/architecture/net-kingdom_v0.1.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "proposed", + "title": "NetKingdom architecture" + }, + { + "canonical_path": "architecture/state-hub/v0.1/index.html", + "currency": "current", + "id": "state-hub-architecture", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "state-hub", + "review_due": "2027-02-28", + "revision": "draft-3", + "revision_path": "architecture/state-hub/v0.1/revisions/draft-3/index.html", + "source_digest": "e7104b2182612efe90c9a12168757506402490229814d454e94917ae62d2bfac", + "source_path": "docs/architecture/state-hub_v0.1.md", + "source_repo": "state-hub", + "source_revision": "803bb95e1d071f188f4202d53a235f8721a8ecdc", + "status": "proposed", + "title": "State Hub architecture" + }, + { + "canonical_path": "architecture/policy-nexus/v0.1/index.html", + "currency": "current", + "id": "policy-nexus-architecture", + "last_reviewed": "2026-08-18", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-18", + "revision": "draft-1", + "revision_path": "architecture/policy-nexus/v0.1/revisions/draft-1/index.html", + "source_digest": "179cbb86bca95f71f46f51ca1971ff1c274eed7d900adf0672b537d4bcc5b480", + "source_path": "docs/architecture/policy-nexus_v0.1.md", + "source_repo": "policy-nexus", + "source_revision": "c1b60f322e3bcddff3cece618ece2bd3686169e1", + "status": "proposed", + "title": "Policy Nexus architecture" + }, + { + "canonical_path": "adr/addressing-and-permanence/v1/index.html", + "currency": "current", + "id": "policy-nexus-adr-0001", + "last_reviewed": "2026-08-18", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-18", + "revision": "accepted-1", + "revision_path": "adr/addressing-and-permanence/v1/revisions/accepted-1/index.html", + "source_digest": "a28668fb4b8b6c5ec8c94baac000061276d85ef1849ec7ab8d132b913dbfe3be", + "source_path": "docs/adr/ADR-0001-addressing-and-permanence.md", + "source_repo": "policy-nexus", + "source_revision": "c1b60f322e3bcddff3cece618ece2bd3686169e1", + "status": "accepted", + "title": "Policy addressing and permanence" + }, + { + "canonical_path": "adr/railiance-repository-prefix/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0001", + "last_reviewed": "2026-07-25", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-01-25", + "revision": "accepted-1", + "revision_path": "adr/railiance-repository-prefix/v1/revisions/accepted-1/index.html", + "source_digest": "b9c993ded8d79d6f871dba9cf08a320b2d619609a448ad3d02b632f5b6f76497", + "source_path": "docs/adr/ADR-0001-repository-prefix-architecture.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "Repository Prefix Architecture" + }, + { + "canonical_path": "adr/railiance-rail-kubernetes-wave-1-boundary/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0002", + "last_reviewed": "2026-07-25", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-01-25", + "revision": "accepted-1", + "revision_path": "adr/railiance-rail-kubernetes-wave-1-boundary/v1/revisions/accepted-1/index.html", + "source_digest": "7e1fc5aedd7294192d8702a22b9f205e5bae20793836fdc48c0c071d10d7ab9d", + "source_path": "docs/adr/ADR-0002-rail-kubernetes-wave-1-boundary.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "Wave 1 rail-kubernetes Boundary" + }, + { + "canonical_path": "adr/railiance-rapp-first-wave-selection/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0003", + "last_reviewed": "2026-07-25", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-01-25", + "revision": "accepted-1", + "revision_path": "adr/railiance-rapp-first-wave-selection/v1/revisions/accepted-1/index.html", + "source_digest": "28135e94758b6935518d2e83458c1e607deeabb341879b605eef6529b3168cbc", + "source_path": "docs/adr/ADR-0003-rapp-first-wave-selection.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "First-Wave rapp Selection" + }, + { + "canonical_path": "adr/railiance-first-wave-reef-rollout/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0004", + "last_reviewed": "2026-07-26", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-01-26", + "revision": "accepted-1", + "revision_path": "adr/railiance-first-wave-reef-rollout/v1/revisions/accepted-1/index.html", + "source_digest": "36ec3aad5082ceff685d66e091c0a24b52abfc65b36595d00dce2b52a7250f4e", + "source_path": "docs/adr/ADR-0004-first-wave-reef-rollout.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "First-Wave reef Rollout" + }, + { + "canonical_path": "adr/railiance-derived-rail-composition/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0005", + "last_reviewed": "2026-07-26", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-01-26", + "revision": "accepted-1", + "revision_path": "adr/railiance-derived-rail-composition/v1/revisions/accepted-1/index.html", + "source_digest": "e02982ce54691cf1589ac3d04012b9b9282f9eb54ed4b8f2f0544371f9b87f3e", + "source_path": "docs/adr/ADR-0005-derived-rail-composition.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "Derived Rail Composition" + }, + { + "canonical_path": "adr/railiance-reef-production-admission/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0006", + "last_reviewed": "2026-08-29", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-02-28", + "revision": "accepted-2", + "revision_path": "adr/railiance-reef-production-admission/v1/revisions/accepted-2/index.html", + "source_digest": "9839d8a4a4617b745cfacab6a0fb72157e88cddf53f40226d8246a64864de533", + "source_path": "docs/adr/ADR-0006-reef-production-admission.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "Reef Production Admission" + }, + { + "canonical_path": "adr/railiance-rapp-declaration-contract/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0007", + "last_reviewed": "2026-08-23", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-02-23", + "revision": "accepted-2", + "revision_path": "adr/railiance-rapp-declaration-contract/v1/revisions/accepted-2/index.html", + "source_digest": "b1185388ded53188dc624fbda9bee517dd3ac80b35e881cd4a7962531bec52ff", + "source_path": "docs/adr/ADR-0007-rapp-declaration-contract.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "Rapp Declaration Contract" + }, + { + "canonical_path": "adr/railiance-private-by-default-exposure/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0008", + "last_reviewed": "2026-08-29", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-02-28", + "revision": "accepted-2", + "revision_path": "adr/railiance-private-by-default-exposure/v1/revisions/accepted-2/index.html", + "source_digest": "bd97ec8ee663bb416fae53c8cfe29c7066e4fd3a51a5bc364895297e6eb994b7", + "source_path": "docs/adr/ADR-0008-private-by-default-exposure.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "Private-by-default Exposure" + }, + { + "canonical_path": "adr/activity-core-event-bridge/v1/index.html", + "currency": "current", + "id": "ACT-ADR-001", + "last_reviewed": "2026-05-14", + "lifecycle": "active", + "owner": "activity-core", + "review_due": "2026-11-14", + "revision": "accepted-2", + "revision_path": "adr/activity-core-event-bridge/v1/revisions/accepted-2/index.html", + "source_digest": "57ecf490465e71cc4970133301cc315748b1d6ee65caac3690472ff88f3aab06", + "source_path": "docs/adr/adr-001-event-bridge-architecture.md", + "source_repo": "activity-core", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "status": "accepted", + "title": "Activity-Core as Coulomb Org Event Bridge" + }, + { + "canonical_path": "adr/activity-core-definition-format/v1/index.html", + "currency": "current", + "id": "ACT-ADR-002", + "last_reviewed": "2026-05-14", + "lifecycle": "active", + "owner": "activity-core", + "review_due": "2026-11-14", + "revision": "accepted-2", + "revision_path": "adr/activity-core-definition-format/v1/revisions/accepted-2/index.html", + "source_digest": "1670e1616094bc6f2c0c7d47b2e019c02d5870b22b564ef83afa24caff197bab", + "source_path": "docs/adr/adr-002-definition-format.md", + "source_repo": "activity-core", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "status": "accepted", + "title": "Markdown-as-Definition Format for Event Types and ActivityDefinitions" + }, + { + "canonical_path": "adr/activity-core-rule-instruction-model/v1/index.html", + "currency": "current", + "id": "ACT-ADR-003", + "last_reviewed": "2026-05-14", + "lifecycle": "active", + "owner": "activity-core", + "review_due": "2026-11-14", + "revision": "accepted-2", + "revision_path": "adr/activity-core-rule-instruction-model/v1/revisions/accepted-2/index.html", + "source_digest": "f1017730074727bcb0c68309c445ffb219ef647c8c5604adbfff76c4aa3f9d66", + "source_path": "docs/adr/adr-003-rule-instruction-model.md", + "source_repo": "activity-core", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "status": "accepted", + "title": "Rule vs. Instruction Model and Expression DSL" + }, + { + "canonical_path": "adr/activity-core-producer-trust-boundary/v1/index.html", + "currency": "current", + "id": "ACT-ADR-004", + "last_reviewed": "2026-06-26", + "lifecycle": "active", + "owner": "activity-core", + "review_due": "2026-12-26", + "revision": "accepted-2", + "revision_path": "adr/activity-core-producer-trust-boundary/v1/revisions/accepted-2/index.html", + "source_digest": "92da58f58ee3dbb62233161a0fc0b7780920f81ec37fdd40d476be354f24e0eb", + "source_path": "docs/adr/adr-004-producer-trust-boundary.md", + "source_repo": "activity-core", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "status": "accepted", + "title": "The Producer Trust Boundary \u2014 Guardrails and Error-Correction for Untrusted Output" + }, + { + "canonical_path": "adr/activity-core-ops-runs-vs-work-records/v1/index.html", + "currency": "current", + "id": "ACT-ADR-005", + "last_reviewed": "2026-08-03", + "lifecycle": "active", + "owner": "activity-core", + "review_due": "2027-02-03", + "revision": "accepted-2", + "revision_path": "adr/activity-core-ops-runs-vs-work-records/v1/revisions/accepted-2/index.html", + "source_digest": "984208262a0f6b67a6dc94cb078ebd0bcddc95d0b21a694d7e207677c10b471f", + "source_path": "docs/adr/adr-005-ops-runs-vs-dev-work-records.md", + "source_repo": "activity-core", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "status": "accepted", + "title": "Ops runs vs development work records \u2014 claim queue and plane split" + }, + { + "canonical_path": "adr/ops-warden-catalog-pointer-layer/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0001", + "last_reviewed": "2026-08-18", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2027-02-18", + "revision": "1", + "revision_path": "adr/ops-warden-catalog-pointer-layer/v1/revisions/1/index.html", + "source_digest": "7df0bb364276e382cbee9383e7e67d399b0ac1b0353246e0ab323e629e732a6d", + "source_path": "docs/adr/ADR-0001-catalog-is-a-pointer-layer.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0001 \u2014 The routing catalog is a pointer layer, never a second copy" + }, + { + "canonical_path": "adr/ops-warden-conduit-not-broker/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0002", + "last_reviewed": "2026-08-18", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2027-02-18", + "revision": "1", + "revision_path": "adr/ops-warden-conduit-not-broker/v1/revisions/1/index.html", + "source_digest": "7dcc31732d774ddf2c98636b69ee12e2d74034836ed0def81b7e06461309b53a", + "source_path": "docs/adr/ADR-0002-conduit-not-broker.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0002 \u2014 ops-warden is a transparent conduit, never a secret broker" + }, + { + "canonical_path": "adr/ops-warden-cover-gaps/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0003", + "last_reviewed": "2026-08-18", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2027-02-18", + "revision": "1", + "revision_path": "adr/ops-warden-cover-gaps/v1/revisions/1/index.html", + "source_digest": "45b47c02afa575fbfe5be980e426c331a1d99bb9cd9c7a8de80bf8e319469f81", + "source_path": "docs/adr/ADR-0003-cover-gaps-never-silently-own-them.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0003 \u2014 Cover gaps, but never silently own them" + }, + { + "canonical_path": "adr/ops-warden-high-risk-read-boundary/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0004", + "last_reviewed": "2026-08-18", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2027-02-18", + "revision": "1", + "revision_path": "adr/ops-warden-high-risk-read-boundary/v1/revisions/1/index.html", + "source_digest": "5db38dcb754af1ba639f4ceca056df842bc7b91fa48dd8bad21611aee29f682d", + "source_path": "docs/adr/ADR-0004-agent-read-boundary-on-high-risk-lanes.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0004 \u2014 High-risk lanes refuse raw value streaming to agent sessions" + }, + { + "canonical_path": "adr/ops-warden-implement-narrowly/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0005", + "last_reviewed": "2026-08-18", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2027-02-18", + "revision": "1", + "revision_path": "adr/ops-warden-implement-narrowly/v1/revisions/1/index.html", + "source_digest": "31eafe4d8d9362a3446739d63c3af83fd9138cfdc6ad120086312a67dc0d27d0", + "source_path": "docs/adr/ADR-0005-implement-narrowly-route-broadly.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0005 \u2014 Implement one lane narrowly, route everything else" + }, + { + "canonical_path": "adr/custodian-workplans-as-repo-artefacts/v1/index.html", + "currency": "current", + "id": "CUST-ADR-001", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-2", + "revision_path": "adr/custodian-workplans-as-repo-artefacts/v1/revisions/accepted-2/index.html", + "source_digest": "183023ee57bae9c29e726fec5ee0361633fe3a2b182436a57869ae4b2a27af24", + "source_path": "canon/architecture/adr-001-workplans-as-repo-artefacts.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Workplans and Work Items Are Repository Artefacts" + }, + { + "canonical_path": "adr/custodian-agent-runtime/v1/index.html", + "currency": "current", + "id": "CUST-ADR-002", + "last_reviewed": "2026-03-12", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2026-09-12", + "revision": "accepted-1", + "revision_path": "adr/custodian-agent-runtime/v1/revisions/accepted-1/index.html", + "source_digest": "6aef66cd5cf71a48f5b4e14401dc19a755e2b182445b272d5952df0eb0dea8ec", + "source_path": "canon/architecture/adr-002-custodian-agent-runtime-design.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Custodian Agent Runtime \u2014 v0.1 Bootstrap Design" + }, + { + "canonical_path": "adr/custodian-materialized-derived-state/v1/index.html", + "currency": "current", + "id": "CUST-ADR-003", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-2", + "revision_path": "adr/custodian-materialized-derived-state/v1/revisions/accepted-2/index.html", + "source_digest": "fcb49719e2b85b9120c0bd5bebd5e82748ca713f16b44951c028b60205514e2b", + "source_path": "canon/architecture/adr-003-materialized-derived-state.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Materialized Derived State with Fingerprint Invalidation for Repo-Sourced Data" + }, + { + "canonical_path": "adr/custodian-connectivity-first/v1/index.html", + "currency": "current", + "id": "CUST-ADR-004", + "last_reviewed": "2026-03-26", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2026-09-26", + "revision": "accepted-1", + "revision_path": "adr/custodian-connectivity-first/v1/revisions/accepted-1/index.html", + "source_digest": "3b68adfa6ab329e73f857cf691dc405136d2d66a0135e2c37c236aabe4659557", + "source_path": "canon/architecture/adr-004-connectivity-first-network-posture.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Connectivity-First Network Posture for Custodian Infrastructure" + }, + { + "canonical_path": "adr/custodian-cross-repo-workplans/v1/index.html", + "currency": "current", + "id": "CUST-ADR-005", + "last_reviewed": "2026-06-22", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2026-12-22", + "revision": "accepted-1", + "revision_path": "adr/custodian-cross-repo-workplans/v1/revisions/accepted-1/index.html", + "source_digest": "13195a721d0e579715f5f39ca6f72b5e49c583611e6ca089e6d4618708ae917f", + "source_path": "canon/architecture/adr-005-cross-repo-workplans-project-repos.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Cross-Repo Workplans Live in Dedicated Project Repos" + }, + { + "canonical_path": "adr/custodian-canon-federation/v1/index.html", + "currency": "current", + "id": "CUST-ADR-006", + "last_reviewed": "2026-08-17", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-17", + "revision": "accepted-1", + "revision_path": "adr/custodian-canon-federation/v1/revisions/accepted-1/index.html", + "source_digest": "a454df0e1d227f99ebb36c4abd45c76cc12579086d34f0c0ccfccfd7f4790823", + "source_path": "canon/architecture/adr-006-canon-federation-concept-ownership.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Canon Federation and Concept Ownership Across InfoTech and Commerce" + }, + { + "canonical_path": "adr/custodian-workplan-identity/v1/index.html", + "currency": "current", + "id": "CUST-ADR-007", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "accepted-2", + "revision_path": "adr/custodian-workplan-identity/v1/revisions/accepted-2/index.html", + "source_digest": "1169f0c1f485a2bb7241a8f64f3167c060c04f83341b12586fd9be5c2b3693f8", + "source_path": "canon/architecture/adr-007-workplan-identity-and-repo-worker-topology.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "Workplan Identity Uniqueness, Single Registrar, and Repo Worker Topology" + }, + { + "canonical_path": "adr/custodian-hub-authority/v1/index.html", + "currency": "current", + "id": "CUST-ADR-010", + "last_reviewed": "2026-08-31", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-28", + "revision": "draft-2", + "revision_path": "adr/custodian-hub-authority/v1/revisions/draft-2/index.html", + "source_digest": "5979da20799118259fc19246f51e8cc8f2c0c1be3d414318bd51d5a27d9ad565", + "source_path": "canon/architecture/adr-010-hub-authority-and-local-cache-model.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "proposed", + "title": "Hub Authority, Local Cache, and the Two Kinds of Hub Data" + }, + { + "canonical_path": "adr/custodian-federated-namespaces/v1/index.html", + "currency": "current", + "id": "CUST-ADR-011", + "last_reviewed": "2026-08-17", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-17", + "revision": "draft-2", + "revision_path": "adr/custodian-federated-namespaces/v1/revisions/draft-2/index.html", + "source_digest": "f94f429c72f6cfd01ee83f1e5689d2d10ae52d7588d7cbd3ca40aca7eef46fb0", + "source_path": "canon/architecture/adr-011-federated-namespaces-and-reconciliation-limits.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "proposed", + "title": "Federated Namespaces: Four Planes, Declared Posture, and the Limits of Reconciliation" + }, + { + "canonical_path": "adr/railiance-s3-platform-service-boundary/v1/index.html", + "currency": "current", + "id": "RPLAT-ADR-0001", + "last_reviewed": "2026-08-17", + "lifecycle": "active", + "owner": "railiance-platform", + "review_due": "2027-02-17", + "revision": "1.0", + "revision_path": "adr/railiance-s3-platform-service-boundary/v1/revisions/1.0/index.html", + "source_digest": "63697581401b53a8437835c2bb8b40f8054cc40d0bc7a2972f83a4a10377f6ba", + "source_path": "docs/adr/ADR-0001-s3-platform-service-boundary.md", + "source_repo": "railiance-platform", + "source_revision": "62423fd0925d75f5a2b27034044dc1080823d341", + "status": "accepted", + "title": "ADR-0001 \u2014 S3 owns platform services, not the substrate beneath them" + }, + { + "canonical_path": "adr/railiance-placement-policy-ownership/v1/index.html", + "currency": "current", + "id": "RPLAT-ADR-0002", + "last_reviewed": "2026-08-17", + "lifecycle": "active", + "owner": "railiance-platform", + "review_due": "2027-02-17", + "revision": "1.0", + "revision_path": "adr/railiance-placement-policy-ownership/v1/revisions/1.0/index.html", + "source_digest": "cfc0ad202c2eeeefd963127ff1defa127c715c001ecc684ab8759733bd8fb9f8", + "source_path": "docs/adr/ADR-0002-placement-policy-ownership.md", + "source_repo": "railiance-platform", + "source_revision": "62423fd0925d75f5a2b27034044dc1080823d341", + "status": "proposed", + "title": "ADR-0002 \u2014 S3 owns the placement rule; the package repo owns the number" + }, + { + "canonical_path": "adr/railiance-decisions-live-in-the-repo/v1/index.html", + "currency": "current", + "id": "RPLAT-ADR-0003", + "last_reviewed": "2026-08-17", + "lifecycle": "active", + "owner": "railiance-platform", + "review_due": "2027-02-17", + "revision": "1.0", + "revision_path": "adr/railiance-decisions-live-in-the-repo/v1/revisions/1.0/index.html", + "source_digest": "9b12ab6aa0e6f9eba03465782c35d6ff4683b682191599e38f4f9614cde9fa1b", + "source_path": "docs/adr/ADR-0003-decisions-live-in-the-repo.md", + "source_repo": "railiance-platform", + "source_revision": "62423fd0925d75f5a2b27034044dc1080823d341", + "status": "accepted", + "title": "ADR-0003 \u2014 Decisions that bind others live in docs/adr, not only in the State Hub" + }, + { + "canonical_path": "standards/iam-profile/v0.3/index.html", + "currency": "current", + "id": "netkingdom-iam-profile-v0.3", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "accepted-1", + "revision_path": "standards/iam-profile/v0.3/revisions/accepted-1/index.html", + "source_digest": "6c47b596ea145fa197c9665081c84902482d90f5bb94d185c1223af65279be1d", + "source_path": "canon/standards/iam-profile_v0.3.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "NetKingdom IAM Profile v0.3" + }, + { + "canonical_path": "adr/netkingdom-recursive-multi-tenant-identity-authorization/v1/index.html", + "currency": "current", + "id": "NK-ADR-0006", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-recursive-multi-tenant-identity-authorization/v1/revisions/1/index.html", + "source_digest": "e92a43649bb6e14e53ec62ecc819405bf3a44bda9557487f7177402f107bbd13", + "source_path": "docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "Recursive Multi-Tenant Identity and Authorization Architecture" + }, + { + "canonical_path": "adr/netkingdom-security-orchestration-boundary/v1/index.html", + "currency": "current", + "id": "NK-ADR-0007", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-security-orchestration-boundary/v1/revisions/1/index.html", + "source_digest": "b4fcff8448f07aca1fcb6908618bdb19f6c0e7dce25d4c5c8b85eec2f175233b", + "source_path": "docs/adr/ADR-0007-security-orchestration-boundary.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "Security Orchestration Boundary" + }, + { + "canonical_path": "adr/netkingdom-object-storage-sts-credential-vending/v1/index.html", + "currency": "current", + "id": "NK-ADR-0008", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-object-storage-sts-credential-vending/v1/revisions/1/index.html", + "source_digest": "f47276f4953f62b783397ee7fb1d3693da060103247e425a9a5b40d019fb4272", + "source_path": "docs/adr/ADR-0008-object-storage-sts-credential-vending.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "Object Storage STS Credential Vending Boundary" + }, + { + "canonical_path": "adr/netkingdom-orchestration-dependency-intent/v1/index.html", + "currency": "current", + "id": "NK-ADR-0010", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-orchestration-dependency-intent/v1/revisions/1/index.html", + "source_digest": "b7c4f6a13f2f5add08bd03cb39c4f18ca25b202747dde883a309d1b3eaa1f571", + "source_path": "docs/adr/ADR-0010-orchestration-vs-dependency-self-coherent-intent.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "Orchestration vs Dependency, and Self-Coherent Intent" + }, + { + "canonical_path": "adr/netkingdom-iam-profile-governance/v1/index.html", + "currency": "current", + "id": "NK-ADR-0011", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-iam-profile-governance/v1/revisions/1/index.html", + "source_digest": "b5c7fd1e78026063b4a2ca4017202512d4f94ab5e12dc8498a34d04d3a48c7a9", + "source_path": "docs/adr/ADR-0011-iam-profile-ownership-and-version-governance.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "NetKingdom IAM Profile Ownership And Version Governance" + }, + { + "canonical_path": "adr/netkingdom-playbook-capability-ownership/v1/index.html", + "currency": "current", + "id": "NK-ADR-0012", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-playbook-capability-ownership/v1/revisions/1/index.html", + "source_digest": "e269fabfc376f97f2a03ea66e34059d54016a445a7f30b351a6774b65c96c2ee", + "source_path": "docs/adr/ADR-0012-playbook-capability-contract-ownership.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "Playbook Capability Contract Ownership" + }, + { + "canonical_path": "adr/netkingdom-tenant-onboarding-taxonomy/v1/index.html", + "currency": "current", + "id": "NK-ADR-0013", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "2", + "revision_path": "adr/netkingdom-tenant-onboarding-taxonomy/v1/revisions/2/index.html", + "source_digest": "3a6030a8958176a902942ffd29154104ba3441a09d06edf3deaeadc7291ee0d4", + "source_path": "docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "Tenant Onboarding Grouping Taxonomy" + }, + { + "canonical_path": "adr/netkingdom-tenant-capability-ownership/v1/index.html", + "currency": "current", + "id": "NK-ADR-0014", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-tenant-capability-ownership/v1/revisions/1/index.html", + "source_digest": "843f7a65f0fc145d08a73e364bc9a1dee0f7b8af934abf1584ee39dffa903ee0", + "source_path": "docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "Tenant Capability Roles, Carrying Mechanism, and Tenant-Engine Ownership" + }, + { + "canonical_path": "adr/netkingdom-railiance-workload-packaging/v1/index.html", + "currency": "current", + "id": "NK-ADR-0015", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2027-02-22", + "revision": "1", + "revision_path": "adr/netkingdom-railiance-workload-packaging/v1/revisions/1/index.html", + "source_digest": "2000985ef211aeedd3656e15cedb289e655526c2dcc4a161a64ffc27f0289db1", + "source_path": "docs/adr/ADR-0015-netkingdom-railiance-workload-packaging-and-relational-platform.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "NetKingdom Railiance Workload Packaging and Relational Platform" + }, + { + "canonical_path": "adr/railiance-k3s-api-tunnel-only/v1/index.html", + "currency": "current", + "id": "RINFRA-ADR-0005", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "railiance-infra", + "review_due": "2027-02-22", + "revision": "accepted-1", + "revision_path": "adr/railiance-k3s-api-tunnel-only/v1/revisions/accepted-1/index.html", + "source_digest": "4d93643d00d19b9d7c46843cd9be674c24e4cd613dc8e780c1684d97a6a9030a", + "source_path": "docs/adr/ADR-005-k3s-api-tunnel-only.md", + "source_repo": "railiance-infra", + "source_revision": "f3e8bf3ab4b1dafa4d0b251b88ab2ef23a30c37b", + "status": "accepted", + "title": "k3s API is tunnel-only" + }, + { + "canonical_path": "adr/activity-core-glas-profile-execution/v1/index.html", + "currency": "current", + "id": "ACT-ADR-006", + "last_reviewed": "2026-08-21", + "lifecycle": "active", + "owner": "activity-core", + "review_due": "2027-02-21", + "revision": "accepted-1", + "revision_path": "adr/activity-core-glas-profile-execution/v1/revisions/accepted-1/index.html", + "source_digest": "88041e9eb3a0f91fdd9da46f9813d7304b5ba58e62f9d28ea8f63c0a7b055b63", + "source_path": "docs/adr/adr-006-glas-profile-execution.md", + "source_repo": "activity-core", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "status": "accepted", + "title": "Profile-driven execution selection over the ops_run pull queue" + }, + { + "canonical_path": "adr/activity-core-bounded-operations/v1/index.html", + "currency": "current", + "id": "ACT-ADR-007", + "last_reviewed": "2026-08-23", + "lifecycle": "active", + "owner": "activity-core", + "review_due": "2027-02-23", + "revision": "accepted-1", + "revision_path": "adr/activity-core-bounded-operations/v1/revisions/accepted-1/index.html", + "source_digest": "75f42047b51293f7240f8a8c3ec02bf025a47fe6627f106fc4ef22fb1be7459d", + "source_path": "docs/adr/adr-007-bounded-operations.md", + "source_repo": "activity-core", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "status": "accepted", + "title": "Code-registered bounded operations are the only local mutation exception" + }, + { + "canonical_path": "adr/ops-warden-build-stage-credential-disclosure/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0007", + "last_reviewed": "2026-08-19", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2027-02-19", + "revision": "1", + "revision_path": "adr/ops-warden-build-stage-credential-disclosure/v1/revisions/1/index.html", + "source_digest": "868f953688988b11ce48f4141833bbca51abdb4e86d5b495a8a905002830d7b0", + "source_path": "docs/adr/ADR-0007-build-stage-stops-at-credential-disclosure.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0007 \u2014 Build-stage permissiveness stops at credential disclosure" + }, + { + "canonical_path": "adr/ops-warden-grade-disclosure-path/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0008", + "last_reviewed": "2026-08-21", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2027-02-21", + "revision": "1", + "revision_path": "adr/ops-warden-grade-disclosure-path/v1/revisions/1/index.html", + "source_digest": "2e22863ba592802cceb40b32d395168b42ace747741f5188307505eaa89ff764", + "source_path": "docs/adr/ADR-0008-grade-the-path-not-the-field.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0008 \u2014 A lane's risk grade covers every field its path discloses" + }, + { + "canonical_path": "adr/ops-warden-security-zones-consumer/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0009", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2026-11-22", + "revision": "1", + "revision_path": "adr/ops-warden-security-zones-consumer/v1/revisions/1/index.html", + "source_digest": "73fff22177b4bec2c56ff737e06e4225eb02d39669be3ea1b723906245f878b8", + "source_path": "docs/adr/ADR-0009-adopt-security-zones-as-a-consumer.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0009 \u2014 Adopt security-zones v0.1 as a consumer" + }, + { + "canonical_path": "adr/ops-warden-staff-layer/v1/index.html", + "currency": "current", + "id": "ops-warden-adr-0010", + "last_reviewed": "2026-08-28", + "lifecycle": "active", + "owner": "ops-warden", + "review_due": "2026-11-28", + "revision": "1", + "revision_path": "adr/ops-warden-staff-layer/v1/revisions/1/index.html", + "source_digest": "064455bcb2870abf8e243f5a8c154e50bfc1c537cfba7996a792f9e314dd78ae", + "source_path": "docs/adr/ADR-0010-ops-warden-is-staff.md", + "source_repo": "ops-warden", + "source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "status": "accepted", + "title": "ADR-0010 \u2014 ops-warden is Staff: lanes, not rules, and one declared engine gap" + }, + { + "canonical_path": "adr/railiance-netkingdom-security-layer-interaction/v1/index.html", + "currency": "current", + "id": "RMASTER-ADR-0009", + "last_reviewed": "2026-08-29", + "lifecycle": "active", + "owner": "railiance-master", + "review_due": "2027-02-28", + "revision": "accepted-1", + "revision_path": "adr/railiance-netkingdom-security-layer-interaction/v1/revisions/accepted-1/index.html", + "source_digest": "c2f81c9718715fc08ea7a2e3021d0d3159958ace23b60a16430020637b9f57f2", + "source_path": "docs/adr/ADR-0009-netkingdom-security-layer-interaction.md", + "source_repo": "railiance-master", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "status": "accepted", + "title": "NetKingdom Security-Layer Interaction Boundary" + }, + { + "canonical_path": "adr/custodian-projection-source-overlay/v1/index.html", + "currency": "current", + "id": "CUST-ADR-012", + "last_reviewed": "2026-08-25", + "lifecycle": "active", + "owner": "the-custodian", + "review_due": "2027-02-25", + "revision": "1.0", + "revision_path": "adr/custodian-projection-source-overlay/v1/revisions/1.0/index.html", + "source_digest": "b5e8582459f546ae789ad5fd62f458454aa19997b520e32b6b9f792d6af55987", + "source_path": "canon/architecture/adr-012-projection-source-and-preliminary-overlay.md", + "source_repo": "the-custodian", + "source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "status": "accepted", + "title": "What the Hub Projects: Forge as Projection Source, Working Copies as Preliminary Overlay" + }, + { + "canonical_path": "standards/posture-feedback/v0.1/index.html", + "currency": "current", + "id": "netkingdom-posture-feedback-v0.1", + "last_reviewed": "2026-08-23", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2026-11-23", + "revision": "0.1", + "revision_path": "standards/posture-feedback/v0.1/revisions/0.1/index.html", + "source_digest": "dd2628b9f0c2a662ac44af22657d91918da228f307ebc7ceb09fc856162985db", + "source_path": "canon/standards/posture-feedback_v0.1.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "proposed", + "title": "NetKingdom Posture Feedback v0.1" + }, + { + "canonical_path": "standards/security-layer-model/v0.7/index.html", + "currency": "current", + "id": "netkingdom-security-layer-model-v0.7", + "last_reviewed": "2026-08-28", + "lifecycle": "active", + "owner": "gate-house", + "review_due": "2026-11-28", + "revision": "0.7", + "revision_path": "standards/security-layer-model/v0.7/revisions/0.7/index.html", + "source_digest": "8155e7b123be1e84377d8278525b1bad8961007b6bb8ff012dd01ba24ff8065b", + "source_path": "canon/standards/security-layer-model_v0.7.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "accepted", + "title": "NetKingdom Security Layer Model v0.7" + }, + { + "canonical_path": "standards/security-scenario-composition/v0.1/index.html", + "currency": "current", + "id": "netkingdom-security-scenario-composition-v0.1", + "last_reviewed": "2026-08-23", + "lifecycle": "active", + "owner": "net-kingdom", + "review_due": "2026-11-23", + "revision": "0.1", + "revision_path": "standards/security-scenario-composition/v0.1/revisions/0.1/index.html", + "source_digest": "17b715d78e04470a0f83b8bc8c7313ab16e13e9e741d5ec445172d9008fce937", + "source_path": "canon/standards/security-scenario-composition_v0.1.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "proposed", + "title": "NetKingdom Security Scenario Composition v0.1" + }, + { + "canonical_path": "standards/security-zones/v0.1/index.html", + "currency": "current", + "id": "netkingdom-security-zones-v0.1", + "last_reviewed": "2026-08-22", + "lifecycle": "active", + "owner": "zone-engine", + "review_due": "2026-11-22", + "revision": "0.1", + "revision_path": "standards/security-zones/v0.1/revisions/0.1/index.html", + "source_digest": "32e71e9c0d6946bb14099eb66193822da26f199de0e11d8488464207f3bd9906", + "source_path": "canon/standards/security-zones_v0.1.md", + "source_repo": "net-kingdom", + "source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "status": "proposed", + "title": "NetKingdom Security Zones v0.1" + }, + { + "canonical_path": "findings/flex-auth-unauthenticated-check/v1/index.html", + "currency": "current", + "id": "RISK-F-0001", + "last_reviewed": "2026-08-20", + "lifecycle": "active", + "owner": "risk-nexus", + "review_due": "2027-02-20", + "revision": "published-1", + "revision_path": "findings/flex-auth-unauthenticated-check/v1/revisions/published-1/index.html", + "source_digest": "b2d1d0c526d5c9b5729b8353877bb9f29096667145f1498cfde73c9711206fda", + "source_path": "findings/RISK-F-0001-flex-auth-unauthenticated-check.md", + "source_repo": "risk-nexus", + "source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "status": "fixed", + "title": "flex-auth /v1/check authenticates no caller" + }, + { + "canonical_path": "findings/audit-retention-legal-basis/v1/index.html", + "currency": "current", + "id": "RISK-F-0008", + "last_reviewed": "2026-08-20", + "lifecycle": "active", + "owner": "risk-nexus", + "review_due": "2027-02-20", + "revision": "published-1", + "revision_path": "findings/audit-retention-legal-basis/v1/revisions/published-1/index.html", + "source_digest": "8bf47414a9f958afb57649f087a2617f91a8f2aeaa4f34d04d8fb3a0f453e840", + "source_path": "findings/RISK-F-0008-audit-retention-legal-basis-assumed.md", + "source_repo": "risk-nexus", + "source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "status": "accepted", + "title": "The legal basis for retaining audit facts against an erasure request has been assumed, never established" + }, + { + "canonical_path": "methods/risk-severity/v1/index.html", + "currency": "current", + "id": "RISK-METHOD-SEVERITY", + "last_reviewed": "2026-08-20", + "lifecycle": "active", + "owner": "risk-nexus", + "review_due": "2027-02-20", + "revision": "adopted-1", + "revision_path": "methods/risk-severity/v1/revisions/adopted-1/index.html", + "source_digest": "f064c591aeef9425bbfbd5fa2aae659abb0ef83b8586848300077f41dd4ffaab", + "source_path": "docs/method/severity.md", + "source_repo": "risk-nexus", + "source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "status": "adopted", + "title": "Severity: how bad, how likely, and what is not a risk at all" + }, + { + "canonical_path": "methods/risk-disclosure/v1/index.html", + "currency": "current", + "id": "RISK-METHOD-DISCLOSURE", + "last_reviewed": "2026-08-20", + "lifecycle": "active", + "owner": "risk-nexus", + "review_due": "2027-02-20", + "revision": "adopted-1", + "revision_path": "methods/risk-disclosure/v1/revisions/adopted-1/index.html", + "source_digest": "bd825a87736c8fb019d4224c3ef4fe3882b8f3028035225b4b8ff8cad62fdb81", + "source_path": "docs/method/disclosure.md", + "source_repo": "risk-nexus", + "source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "status": "adopted", + "title": "Disclosure: publish now, hold, or restrict" + }, + { + "canonical_path": "methods/risk-review/v1/index.html", + "currency": "current", + "id": "RISK-METHOD-REVIEW", + "last_reviewed": "2026-08-20", + "lifecycle": "active", + "owner": "risk-nexus", + "review_due": "2027-02-20", + "revision": "adopted-1", + "revision_path": "methods/risk-review/v1/revisions/adopted-1/index.html", + "source_digest": "8f873d105bf6bd711a1a15ebd739b2abe97365b4e854be9d1c82e3ff3b162cfd", + "source_path": "docs/method/review.md", + "source_repo": "risk-nexus", + "source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "status": "adopted", + "title": "Review and expiry: what happens when nobody looks" + }, + { + "canonical_path": "methods/risk-verification/v1/index.html", + "currency": "current", + "id": "RISK-METHOD-VERIFICATION", + "last_reviewed": "2026-08-20", + "lifecycle": "active", + "owner": "risk-nexus", + "review_due": "2027-02-20", + "revision": "adopted-1", + "revision_path": "methods/risk-verification/v1/revisions/adopted-1/index.html", + "source_digest": "562b6eb3031ffe35e712c5e11e35aae23ce84a90e0090438dc89513f9685052b", + "source_path": "docs/method/verification.md", + "source_repo": "risk-nexus", + "source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "status": "adopted", + "title": "What this register may verify for itself, and what it must take from owners" + }, + { + "canonical_path": "methods/risk-dependencies/v1/index.html", + "currency": "current", + "id": "RISK-METHOD-DEPENDENCIES", + "last_reviewed": "2026-08-20", + "lifecycle": "active", + "owner": "risk-nexus", + "review_due": "2027-02-20", + "revision": "adopted-1", + "revision_path": "methods/risk-dependencies/v1/revisions/adopted-1/index.html", + "source_digest": "a5e7b42043b0bb366add9bbc5f606e134ef5b0454532480203d5d87099296240", + "source_path": "docs/method/dependencies.md", + "source_repo": "risk-nexus", + "source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "status": "adopted", + "title": "Waiting: how this register depends on other people without becoming a queue" + } + ], + "generated_as_of": "2026-08-31", + "schema_version": 1 +} diff --git a/docs/evidence/2026-10-11-served-publication-history/receipt.json b/docs/evidence/2026-10-11-served-publication-history/receipt.json new file mode 100644 index 0000000..4af1a08 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/receipt.json @@ -0,0 +1,1017 @@ +{ + "schema_version": "policy-nexus-published-history/v1", + "origin": "https://policy.coulomb.social", + "captured_at": "2026-10-11", + "publication_manifest_path": "publication-manifest.json", + "publication_manifest_digest": "09f9f7edcc46e31d4b3f73b62d16fdf17808ae1f893f312242b7b9e2037319e0", + "documents": [ + { + "id": "netkingdom-tenancy-posture", + "revision": "draft-14", + "revision_path": "standards/tenancy-posture/v0.1/revisions/draft-14/index.html", + "source_repo": "net-kingdom", + "source_path": "canon/standards/tenancy-posture_v0.1.md", + "source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8", + "source_digest": "27d6878c68310619877de516000d9af78d5456a92572a709884ed052d765b876", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/netkingdom-tenancy-posture.html", + "rendered_page_digest": "404f0bc2699040d0a065d557dc02232c025b1e340a77c64d645464bca2745b01", + "archived_source_path": "sources/netkingdom-tenancy-posture.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "canon-autonomy-lanes", + "revision": "accepted-1", + "revision_path": "standards/autonomy-lanes/v0.1/revisions/accepted-1/index.html", + "source_repo": "the-custodian", + "source_path": "canon/standards/autonomy-lanes_v0.1.md", + "source_revision": "4b951be3947f9457cc091a7d359d9232646b4c93", + "source_digest": "106cb0c99f73983199c9f1d8491143aa2a2eb41ee450a02ec7968d3763f30399", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/canon-autonomy-lanes.html", + "rendered_page_digest": "aec0623d3e2a81d6141120906e97efbae941f7b2b5704ad42634484702932b13", + "archived_source_path": "sources/canon-autonomy-lanes.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "canon-contrib-convention", + "revision": "accepted-1", + "revision_path": "standards/contribution-convention/v0.1/revisions/accepted-1/index.html", + "source_repo": "the-custodian", + "source_path": "canon/standards/contribution-convention_v0.1.md", + "source_revision": "4b951be3947f9457cc091a7d359d9232646b4c93", + "source_digest": "6dfc1d7b2b82ab18228a84660c1c7dff607aad1d9da37ac12084cdd2ab8ae112", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/canon-contrib-convention.html", + "rendered_page_digest": "0b8aa643a908ba780ce231cd5628d10035697004d2ac512b9d60c32077cd936e", + "archived_source_path": "sources/canon-contrib-convention.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "canon-project-repository-flavor", + "revision": "accepted-1", + "revision_path": "standards/project-repository-flavor/v0.1/revisions/accepted-1/index.html", + "source_repo": "the-custodian", + "source_path": "canon/standards/project-repository-flavor_v0.1.md", + "source_revision": "4b951be3947f9457cc091a7d359d9232646b4c93", + "source_digest": "61889b2f18a82a08b66ef90d758efe5c028a4f52aced1721a23e69c6f24a8224", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/canon-project-repository-flavor.html", + "rendered_page_digest": "cc5cdbaa875338b08a7ffed743121df16f002ada0b5a70268d32448ead57b2eb", + "archived_source_path": "sources/canon-project-repository-flavor.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "canon-work-record-types", + "revision": "accepted-1", + "revision_path": "standards/work-record-types/v0.1/revisions/accepted-1/index.html", + "source_repo": "the-custodian", + "source_path": "canon/standards/work-record-types_v0.1.md", + "source_revision": "4b951be3947f9457cc091a7d359d9232646b4c93", + "source_digest": "1a40de294be332ee713cdaab532a32a83718f3952c339fbff835ecee899a8dbc", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/canon-work-record-types.html", + "rendered_page_digest": "43cf02d4b768194c482fc09a0eda11c62bfd3d4250da0175a9f0012f68b1fec4", + "archived_source_path": "sources/canon-work-record-types.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "canon-workplan-terminology-fleet", + "revision": "accepted-1", + "revision_path": "standards/workplan-terminology/v0.1/revisions/accepted-1/index.html", + "source_repo": "the-custodian", + "source_path": "canon/standards/workplan-terminology-fleet_v0.1.md", + "source_revision": "4b951be3947f9457cc091a7d359d9232646b4c93", + "source_digest": "199812632ac342ca3231498e545e3701e10c191ca144035b36a23c1a0b717712", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/canon-workplan-terminology-fleet.html", + "rendered_page_digest": "3fffb8c59107797e97db55bf8ee23280ddc2ca0ef1ea1edc1547c3b2c3bef3c0", + "archived_source_path": "sources/canon-workplan-terminology-fleet.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "coulomb-estate-architecture", + "revision": "draft-3", + "revision_path": "architecture/coulomb-estate/v0.1/revisions/draft-3/index.html", + "source_repo": "the-custodian", + "source_path": "canon/architecture/coulomb-estate_v0.1.md", + "source_revision": "d3c6f13d7aed9784b6f31c23d2b2777668493ba5", + "source_digest": "3b4f93acdedc6f6d1a1c737c3327d9d55061506c5a879be09e6b0a2ea7f5f9bc", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/coulomb-estate-architecture.html", + "rendered_page_digest": "bac4d0ac08eaf5a64d8e0b3363e091e2426de99a2e5054b6812c650084dd3dd8", + "archived_source_path": "sources/coulomb-estate-architecture.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "railiance-architecture", + "revision": "draft-3", + "revision_path": "architecture/railiance/v0.1/revisions/draft-3/index.html", + "source_repo": "railiance-master", + "source_path": "docs/architecture/railiance_v0.1.md", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "source_digest": "77de319978dfad02f40658bbeb96db46b246a46f1abd6a45f5b1269563de5568", + "delivered_manifest_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "rendered_page_path": "pages/railiance-architecture.html", + "rendered_page_digest": "4bfde5ce9935572f1b0c38368877605d35a30b8d822ad7735697462aa4e5318e", + "archived_source_path": "sources/railiance-architecture.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "net-kingdom-architecture", + "revision": "draft-3", + "revision_path": "architecture/net-kingdom/v0.1/revisions/draft-3/index.html", + "source_repo": "net-kingdom", + "source_path": "docs/architecture/net-kingdom_v0.1.md", + "source_revision": "d4e57e63126d2cca1d381c025170e4b1f678c3f3", + "source_digest": "b9f89f706a801d54961fc67d725ca433b7c3b6a083c54793de6e8b60d1dd42a8", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/net-kingdom-architecture.html", + "rendered_page_digest": "827f197303b1074e022dd60af8e9e0245d01b6785bc039e21cb7c527805f7010", + "archived_source_path": "sources/net-kingdom-architecture.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "state-hub-architecture", + "revision": "draft-3", + "revision_path": "architecture/state-hub/v0.1/revisions/draft-3/index.html", + "source_repo": "state-hub", + "source_path": "docs/architecture/state-hub_v0.1.md", + "source_revision": "da30ce6723f9c5e6fdd994c78bb646986e819d3f", + "source_digest": "e7104b2182612efe90c9a12168757506402490229814d454e94917ae62d2bfac", + "delivered_manifest_source_revision": "803bb95e1d071f188f4202d53a235f8721a8ecdc", + "rendered_page_path": "pages/state-hub-architecture.html", + "rendered_page_digest": "0430e83028f33b966e72f637f7b12acbcfa58a60810c0f6aaa2cfe3afc253c41", + "archived_source_path": "sources/state-hub-architecture.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "policy-nexus-architecture", + "revision": "draft-1", + "revision_path": "architecture/policy-nexus/v0.1/revisions/draft-1/index.html", + "source_repo": "policy-nexus", + "source_path": "docs/architecture/policy-nexus_v0.1.md", + "source_revision": "a8fb62a3bcbff3daf03e5d3d56c3ca3a5bd991f8", + "source_digest": "179cbb86bca95f71f46f51ca1971ff1c274eed7d900adf0672b537d4bcc5b480", + "delivered_manifest_source_revision": "c1b60f322e3bcddff3cece618ece2bd3686169e1", + "rendered_page_path": "pages/policy-nexus-architecture.html", + "rendered_page_digest": "1fe8e9ddfee222c1d3604f5a37a3b34b1274344dfb5233b1d9f52c98aaa944e6", + "archived_source_path": "sources/policy-nexus-architecture.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "policy-nexus-adr-0001", + "revision": "accepted-1", + "revision_path": "adr/addressing-and-permanence/v1/revisions/accepted-1/index.html", + "source_repo": "policy-nexus", + "source_path": "docs/adr/ADR-0001-addressing-and-permanence.md", + "source_revision": "6515ed9ef8499cb3de3397f0ae3993cc71440599", + "source_digest": "a28668fb4b8b6c5ec8c94baac000061276d85ef1849ec7ab8d132b913dbfe3be", + "delivered_manifest_source_revision": "c1b60f322e3bcddff3cece618ece2bd3686169e1", + "rendered_page_path": "pages/policy-nexus-adr-0001.html", + "rendered_page_digest": "883a91470d4dcf5d1eb0f02e769081c97c2be060379235757e27a7f59c9eb824", + "archived_source_path": "sources/policy-nexus-adr-0001.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RMASTER-ADR-0001", + "revision": "accepted-1", + "revision_path": "adr/railiance-repository-prefix/v1/revisions/accepted-1/index.html", + "source_repo": "railiance-master", + "source_path": "docs/adr/ADR-0001-repository-prefix-architecture.md", + "source_revision": "debbc13b4018f20db0997516b99afc1cc21084d7", + "source_digest": "b9c993ded8d79d6f871dba9cf08a320b2d619609a448ad3d02b632f5b6f76497", + "delivered_manifest_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "rendered_page_path": "pages/RMASTER-ADR-0001.html", + "rendered_page_digest": "f005c4645fd09709ea4fb6ed24e080e6dc8dd6975e3a87ccc495b6516195ac18", + "archived_source_path": "sources/RMASTER-ADR-0001.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RMASTER-ADR-0002", + "revision": "accepted-1", + "revision_path": "adr/railiance-rail-kubernetes-wave-1-boundary/v1/revisions/accepted-1/index.html", + "source_repo": "railiance-master", + "source_path": "docs/adr/ADR-0002-rail-kubernetes-wave-1-boundary.md", + "source_revision": "debbc13b4018f20db0997516b99afc1cc21084d7", + "source_digest": "7e1fc5aedd7294192d8702a22b9f205e5bae20793836fdc48c0c071d10d7ab9d", + "delivered_manifest_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "rendered_page_path": "pages/RMASTER-ADR-0002.html", + "rendered_page_digest": "bf60c98f5cb2e2e01e4c03bc8adb0059f041cfc894f2d1e5e086f2b7e1912c64", + "archived_source_path": "sources/RMASTER-ADR-0002.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RMASTER-ADR-0003", + "revision": "accepted-1", + "revision_path": "adr/railiance-rapp-first-wave-selection/v1/revisions/accepted-1/index.html", + "source_repo": "railiance-master", + "source_path": "docs/adr/ADR-0003-rapp-first-wave-selection.md", + "source_revision": "debbc13b4018f20db0997516b99afc1cc21084d7", + "source_digest": "28135e94758b6935518d2e83458c1e607deeabb341879b605eef6529b3168cbc", + "delivered_manifest_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "rendered_page_path": "pages/RMASTER-ADR-0003.html", + "rendered_page_digest": "baab701edeb557dd1c971a802952b9f038ddeb8b88c92324b338988e6261ec7a", + "archived_source_path": "sources/RMASTER-ADR-0003.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RMASTER-ADR-0004", + "revision": "accepted-1", + "revision_path": "adr/railiance-first-wave-reef-rollout/v1/revisions/accepted-1/index.html", + "source_repo": "railiance-master", + "source_path": "docs/adr/ADR-0004-first-wave-reef-rollout.md", + "source_revision": "debbc13b4018f20db0997516b99afc1cc21084d7", + "source_digest": "36ec3aad5082ceff685d66e091c0a24b52abfc65b36595d00dce2b52a7250f4e", + "delivered_manifest_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "rendered_page_path": "pages/RMASTER-ADR-0004.html", + "rendered_page_digest": "39274457b44e6ce0f4f5493272ad0022821ce86c190d42a0ed02d6d0131e637e", + "archived_source_path": "sources/RMASTER-ADR-0004.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RMASTER-ADR-0005", + "revision": "accepted-1", + "revision_path": "adr/railiance-derived-rail-composition/v1/revisions/accepted-1/index.html", + "source_repo": "railiance-master", + "source_path": "docs/adr/ADR-0005-derived-rail-composition.md", + "source_revision": "debbc13b4018f20db0997516b99afc1cc21084d7", + "source_digest": "e02982ce54691cf1589ac3d04012b9b9282f9eb54ed4b8f2f0544371f9b87f3e", + "delivered_manifest_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "rendered_page_path": "pages/RMASTER-ADR-0005.html", + "rendered_page_digest": "9bc8dea9ae3fdc3a97ca78f388ef992d9966040e253fb7cc983b5efca78c4728", + "archived_source_path": "sources/RMASTER-ADR-0005.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RMASTER-ADR-0006", + "revision": "accepted-2", + "revision_path": "adr/railiance-reef-production-admission/v1/revisions/accepted-2/index.html", + "source_repo": "railiance-master", + "source_path": "docs/adr/ADR-0006-reef-production-admission.md", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "source_digest": "9839d8a4a4617b745cfacab6a0fb72157e88cddf53f40226d8246a64864de533", + "delivered_manifest_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "rendered_page_path": "pages/RMASTER-ADR-0006.html", + "rendered_page_digest": "051b11d1f82a21775aa4328b39385d43649ddf7a267cb12d691c17feff07161b", + "archived_source_path": "sources/RMASTER-ADR-0006.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RMASTER-ADR-0007", + "revision": "accepted-2", + "revision_path": "adr/railiance-rapp-declaration-contract/v1/revisions/accepted-2/index.html", + "source_repo": "railiance-master", + "source_path": "docs/adr/ADR-0007-rapp-declaration-contract.md", + "source_revision": "51747ca793d830c42bd86ce0ca43e34db9e8a1c1", + "source_digest": "b1185388ded53188dc624fbda9bee517dd3ac80b35e881cd4a7962531bec52ff", + "delivered_manifest_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "rendered_page_path": "pages/RMASTER-ADR-0007.html", + "rendered_page_digest": "fdb4bedcc9120932e8c2792956c603f9fa45a6b06ebe30c92b7661c62cd0a0e6", + "archived_source_path": "sources/RMASTER-ADR-0007.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RMASTER-ADR-0008", + "revision": "accepted-2", + "revision_path": "adr/railiance-private-by-default-exposure/v1/revisions/accepted-2/index.html", + "source_repo": "railiance-master", + "source_path": "docs/adr/ADR-0008-private-by-default-exposure.md", + "source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "source_digest": "bd97ec8ee663bb416fae53c8cfe29c7066e4fd3a51a5bc364895297e6eb994b7", + "delivered_manifest_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "rendered_page_path": "pages/RMASTER-ADR-0008.html", + "rendered_page_digest": "5c913da6b1e0c223bbe197d50f0f71dbf2e9c283ab7193758cf4567e059047ba", + "archived_source_path": "sources/RMASTER-ADR-0008.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ACT-ADR-001", + "revision": "accepted-2", + "revision_path": "adr/activity-core-event-bridge/v1/revisions/accepted-2/index.html", + "source_repo": "activity-core", + "source_path": "docs/adr/adr-001-event-bridge-architecture.md", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "source_digest": "57ecf490465e71cc4970133301cc315748b1d6ee65caac3690472ff88f3aab06", + "delivered_manifest_source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "rendered_page_path": "pages/ACT-ADR-001.html", + "rendered_page_digest": "ebbe7111cc8fe75943cac3b8df625eed6675c268fb2d78de90de7766427ba032", + "archived_source_path": "sources/ACT-ADR-001.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ACT-ADR-002", + "revision": "accepted-2", + "revision_path": "adr/activity-core-definition-format/v1/revisions/accepted-2/index.html", + "source_repo": "activity-core", + "source_path": "docs/adr/adr-002-definition-format.md", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "source_digest": "1670e1616094bc6f2c0c7d47b2e019c02d5870b22b564ef83afa24caff197bab", + "delivered_manifest_source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "rendered_page_path": "pages/ACT-ADR-002.html", + "rendered_page_digest": "4f77070a5ceacc7463d6e570d650649149d934b865b069cf303a829e8823fb2c", + "archived_source_path": "sources/ACT-ADR-002.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ACT-ADR-003", + "revision": "accepted-2", + "revision_path": "adr/activity-core-rule-instruction-model/v1/revisions/accepted-2/index.html", + "source_repo": "activity-core", + "source_path": "docs/adr/adr-003-rule-instruction-model.md", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "source_digest": "f1017730074727bcb0c68309c445ffb219ef647c8c5604adbfff76c4aa3f9d66", + "delivered_manifest_source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "rendered_page_path": "pages/ACT-ADR-003.html", + "rendered_page_digest": "99ff7ee81f42d460105a4dadc69ddd369094bde64617cb07bf850458ff0f2400", + "archived_source_path": "sources/ACT-ADR-003.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ACT-ADR-004", + "revision": "accepted-2", + "revision_path": "adr/activity-core-producer-trust-boundary/v1/revisions/accepted-2/index.html", + "source_repo": "activity-core", + "source_path": "docs/adr/adr-004-producer-trust-boundary.md", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "source_digest": "92da58f58ee3dbb62233161a0fc0b7780920f81ec37fdd40d476be354f24e0eb", + "delivered_manifest_source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "rendered_page_path": "pages/ACT-ADR-004.html", + "rendered_page_digest": "da014cb4991ecb8f03458016b69595a7ef82cece8a2a1f301b9a65140c6db5fe", + "archived_source_path": "sources/ACT-ADR-004.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ACT-ADR-005", + "revision": "accepted-2", + "revision_path": "adr/activity-core-ops-runs-vs-work-records/v1/revisions/accepted-2/index.html", + "source_repo": "activity-core", + "source_path": "docs/adr/adr-005-ops-runs-vs-dev-work-records.md", + "source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "source_digest": "984208262a0f6b67a6dc94cb078ebd0bcddc95d0b21a694d7e207677c10b471f", + "delivered_manifest_source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "rendered_page_path": "pages/ACT-ADR-005.html", + "rendered_page_digest": "fb1afdaa34eed2c923d451a0ab119079abfaf3924c452bcd8bb9ab84b0b047bf", + "archived_source_path": "sources/ACT-ADR-005.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ops-warden-adr-0001", + "revision": "1", + "revision_path": "adr/ops-warden-catalog-pointer-layer/v1/revisions/1/index.html", + "source_repo": "ops-warden", + "source_path": "docs/adr/ADR-0001-catalog-is-a-pointer-layer.md", + "source_revision": "35aff380a33f51a512c1e1b42d52d1dc0d95930f", + "source_digest": "7df0bb364276e382cbee9383e7e67d399b0ac1b0353246e0ab323e629e732a6d", + "delivered_manifest_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "rendered_page_path": "pages/ops-warden-adr-0001.html", + "rendered_page_digest": "1c0fb4914894de9aae6fa9549d5b6e0ae2dcb401cb50e70febb481a93cf5acdf", + "archived_source_path": "sources/ops-warden-adr-0001.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ops-warden-adr-0002", + "revision": "1", + "revision_path": "adr/ops-warden-conduit-not-broker/v1/revisions/1/index.html", + "source_repo": "ops-warden", + "source_path": "docs/adr/ADR-0002-conduit-not-broker.md", + "source_revision": "35aff380a33f51a512c1e1b42d52d1dc0d95930f", + "source_digest": "7dcc31732d774ddf2c98636b69ee12e2d74034836ed0def81b7e06461309b53a", + "delivered_manifest_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "rendered_page_path": "pages/ops-warden-adr-0002.html", + "rendered_page_digest": "3484b554239c617d438325ab8e87a744ac24797f6e665cb2da9562060841b352", + "archived_source_path": "sources/ops-warden-adr-0002.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ops-warden-adr-0003", + "revision": "1", + "revision_path": "adr/ops-warden-cover-gaps/v1/revisions/1/index.html", + "source_repo": "ops-warden", + "source_path": "docs/adr/ADR-0003-cover-gaps-never-silently-own-them.md", + "source_revision": "35aff380a33f51a512c1e1b42d52d1dc0d95930f", + "source_digest": "45b47c02afa575fbfe5be980e426c331a1d99bb9cd9c7a8de80bf8e319469f81", + "delivered_manifest_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "rendered_page_path": "pages/ops-warden-adr-0003.html", + "rendered_page_digest": "6deb88e23100298841f7022f1ab81376862073293ab1edefe9e4b6ad32e45163", + "archived_source_path": "sources/ops-warden-adr-0003.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ops-warden-adr-0004", + "revision": "1", + "revision_path": "adr/ops-warden-high-risk-read-boundary/v1/revisions/1/index.html", + "source_repo": "ops-warden", + "source_path": "docs/adr/ADR-0004-agent-read-boundary-on-high-risk-lanes.md", + "source_revision": "35aff380a33f51a512c1e1b42d52d1dc0d95930f", + "source_digest": "5db38dcb754af1ba639f4ceca056df842bc7b91fa48dd8bad21611aee29f682d", + "delivered_manifest_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "rendered_page_path": "pages/ops-warden-adr-0004.html", + "rendered_page_digest": "830b886a57017d5e5feb1e39289bc13eec389dacc2f0072812ff45e76f735590", + "archived_source_path": "sources/ops-warden-adr-0004.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ops-warden-adr-0005", + "revision": "1", + "revision_path": "adr/ops-warden-implement-narrowly/v1/revisions/1/index.html", + "source_repo": "ops-warden", + "source_path": "docs/adr/ADR-0005-implement-narrowly-route-broadly.md", + "source_revision": "35aff380a33f51a512c1e1b42d52d1dc0d95930f", + "source_digest": "31eafe4d8d9362a3446739d63c3af83fd9138cfdc6ad120086312a67dc0d27d0", + "delivered_manifest_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "rendered_page_path": "pages/ops-warden-adr-0005.html", + "rendered_page_digest": "4ea223b2f0105372a3d6868486480ce549926808658aaa1606dbde510f7ddb78", + "archived_source_path": "sources/ops-warden-adr-0005.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "CUST-ADR-001", + "revision": "accepted-2", + "revision_path": "adr/custodian-workplans-as-repo-artefacts/v1/revisions/accepted-2/index.html", + "source_repo": "the-custodian", + "source_path": "canon/architecture/adr-001-workplans-as-repo-artefacts.md", + "source_revision": "d3c6f13d7aed9784b6f31c23d2b2777668493ba5", + "source_digest": "183023ee57bae9c29e726fec5ee0361633fe3a2b182436a57869ae4b2a27af24", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/CUST-ADR-001.html", + "rendered_page_digest": "c8f76626fcdaf5d777b966082620071c7b005ebe85c8dcef2f9c899521edf28e", + "archived_source_path": "sources/CUST-ADR-001.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "CUST-ADR-002", + "revision": "accepted-1", + "revision_path": "adr/custodian-agent-runtime/v1/revisions/accepted-1/index.html", + "source_repo": "the-custodian", + "source_path": "canon/architecture/adr-002-custodian-agent-runtime-design.md", + "source_revision": "4039c9d1c08c92014ecc0a65dda63cc73ba187bb", + "source_digest": "6aef66cd5cf71a48f5b4e14401dc19a755e2b182445b272d5952df0eb0dea8ec", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/CUST-ADR-002.html", + "rendered_page_digest": "ea4eae05b62d50cd8204a47c1a4f3e3e0beda571880ed1de93ccbb17987d84aa", + "archived_source_path": "sources/CUST-ADR-002.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "CUST-ADR-003", + "revision": "accepted-2", + "revision_path": "adr/custodian-materialized-derived-state/v1/revisions/accepted-2/index.html", + "source_repo": "the-custodian", + "source_path": "canon/architecture/adr-003-materialized-derived-state.md", + "source_revision": "d3c6f13d7aed9784b6f31c23d2b2777668493ba5", + "source_digest": "fcb49719e2b85b9120c0bd5bebd5e82748ca713f16b44951c028b60205514e2b", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/CUST-ADR-003.html", + "rendered_page_digest": "bca06c973dcf18494b7b57071b00af5f37b2eac231f30cfac771ad49698d09d0", + "archived_source_path": "sources/CUST-ADR-003.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "CUST-ADR-004", + "revision": "accepted-1", + "revision_path": "adr/custodian-connectivity-first/v1/revisions/accepted-1/index.html", + "source_repo": "the-custodian", + "source_path": "canon/architecture/adr-004-connectivity-first-network-posture.md", + "source_revision": "4039c9d1c08c92014ecc0a65dda63cc73ba187bb", + "source_digest": "3b68adfa6ab329e73f857cf691dc405136d2d66a0135e2c37c236aabe4659557", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/CUST-ADR-004.html", + "rendered_page_digest": "3db1b0f5c50543ff0fb022ea67f1d85b30f8ff691eaa1a2443220b7474b54de7", + "archived_source_path": "sources/CUST-ADR-004.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "CUST-ADR-005", + "revision": "accepted-1", + "revision_path": "adr/custodian-cross-repo-workplans/v1/revisions/accepted-1/index.html", + "source_repo": "the-custodian", + "source_path": "canon/architecture/adr-005-cross-repo-workplans-project-repos.md", + "source_revision": "4039c9d1c08c92014ecc0a65dda63cc73ba187bb", + "source_digest": "13195a721d0e579715f5f39ca6f72b5e49c583611e6ca089e6d4618708ae917f", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/CUST-ADR-005.html", + "rendered_page_digest": "61804eedae4fbc82ddf86bd17a994f1e2291290d39af3d8a212b503eab0f359d", + "archived_source_path": "sources/CUST-ADR-005.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "CUST-ADR-006", + "revision": "accepted-1", + "revision_path": "adr/custodian-canon-federation/v1/revisions/accepted-1/index.html", + "source_repo": "the-custodian", + "source_path": "canon/architecture/adr-006-canon-federation-concept-ownership.md", + "source_revision": "4039c9d1c08c92014ecc0a65dda63cc73ba187bb", + "source_digest": "a454df0e1d227f99ebb36c4abd45c76cc12579086d34f0c0ccfccfd7f4790823", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/CUST-ADR-006.html", + "rendered_page_digest": "e10fdd888936a997cb91d6e5df8b801f2e7be1bbd170e82087faf1d37bf6d15b", + "archived_source_path": "sources/CUST-ADR-006.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "CUST-ADR-007", + "revision": "accepted-2", + "revision_path": "adr/custodian-workplan-identity/v1/revisions/accepted-2/index.html", + "source_repo": "the-custodian", + "source_path": "canon/architecture/adr-007-workplan-identity-and-repo-worker-topology.md", + "source_revision": "d3c6f13d7aed9784b6f31c23d2b2777668493ba5", + "source_digest": "1169f0c1f485a2bb7241a8f64f3167c060c04f83341b12586fd9be5c2b3693f8", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/CUST-ADR-007.html", + "rendered_page_digest": "bdf9f9522a48278f36cb86cd1f79765c97ab23b7abef94b5de2dada77dfc9e84", + "archived_source_path": "sources/CUST-ADR-007.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "CUST-ADR-010", + "revision": "draft-2", + "revision_path": "adr/custodian-hub-authority/v1/revisions/draft-2/index.html", + "source_repo": "the-custodian", + "source_path": "canon/architecture/adr-010-hub-authority-and-local-cache-model.md", + "source_revision": "d3c6f13d7aed9784b6f31c23d2b2777668493ba5", + "source_digest": "5979da20799118259fc19246f51e8cc8f2c0c1be3d414318bd51d5a27d9ad565", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/CUST-ADR-010.html", + "rendered_page_digest": "83eeac4536266033882082332ffa150a8df69ad7b5f6bd8bd02d1ce587844ddd", + "archived_source_path": "sources/CUST-ADR-010.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "CUST-ADR-011", + "revision": "draft-2", + "revision_path": "adr/custodian-federated-namespaces/v1/revisions/draft-2/index.html", + "source_repo": "the-custodian", + "source_path": "canon/architecture/adr-011-federated-namespaces-and-reconciliation-limits.md", + "source_revision": "4039c9d1c08c92014ecc0a65dda63cc73ba187bb", + "source_digest": "f94f429c72f6cfd01ee83f1e5689d2d10ae52d7588d7cbd3ca40aca7eef46fb0", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/CUST-ADR-011.html", + "rendered_page_digest": "b4b023c51266d152adc41d126566c55fdb6bf303047ca93d0585a4f60f8194c8", + "archived_source_path": "sources/CUST-ADR-011.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RPLAT-ADR-0001", + "revision": "1.0", + "revision_path": "adr/railiance-s3-platform-service-boundary/v1/revisions/1.0/index.html", + "source_repo": "railiance-platform", + "source_path": "docs/adr/ADR-0001-s3-platform-service-boundary.md", + "source_revision": "56d516e10cdad6691f254ec9cb11f11ea364f7de", + "source_digest": "63697581401b53a8437835c2bb8b40f8054cc40d0bc7a2972f83a4a10377f6ba", + "delivered_manifest_source_revision": "62423fd0925d75f5a2b27034044dc1080823d341", + "rendered_page_path": "pages/RPLAT-ADR-0001.html", + "rendered_page_digest": "1fd07544a0a0f59087c5cbae908a2b883455e40b81bdb062305c33c646812708", + "archived_source_path": "sources/RPLAT-ADR-0001.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RPLAT-ADR-0002", + "revision": "1.0", + "revision_path": "adr/railiance-placement-policy-ownership/v1/revisions/1.0/index.html", + "source_repo": "railiance-platform", + "source_path": "docs/adr/ADR-0002-placement-policy-ownership.md", + "source_revision": "56d516e10cdad6691f254ec9cb11f11ea364f7de", + "source_digest": "cfc0ad202c2eeeefd963127ff1defa127c715c001ecc684ab8759733bd8fb9f8", + "delivered_manifest_source_revision": "62423fd0925d75f5a2b27034044dc1080823d341", + "rendered_page_path": "pages/RPLAT-ADR-0002.html", + "rendered_page_digest": "6abef6687e89a35352aa283d930c2dfdfd566cd4881a1859fdc7597214431be8", + "archived_source_path": "sources/RPLAT-ADR-0002.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RPLAT-ADR-0003", + "revision": "1.0", + "revision_path": "adr/railiance-decisions-live-in-the-repo/v1/revisions/1.0/index.html", + "source_repo": "railiance-platform", + "source_path": "docs/adr/ADR-0003-decisions-live-in-the-repo.md", + "source_revision": "56d516e10cdad6691f254ec9cb11f11ea364f7de", + "source_digest": "9b12ab6aa0e6f9eba03465782c35d6ff4683b682191599e38f4f9614cde9fa1b", + "delivered_manifest_source_revision": "62423fd0925d75f5a2b27034044dc1080823d341", + "rendered_page_path": "pages/RPLAT-ADR-0003.html", + "rendered_page_digest": "ac49cddc344d6937dfdb0ba89f15bcad96bca1c6c360b0cc1c1e5632c69560e5", + "archived_source_path": "sources/RPLAT-ADR-0003.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "netkingdom-iam-profile-v0.3", + "revision": "accepted-1", + "revision_path": "standards/iam-profile/v0.3/revisions/accepted-1/index.html", + "source_repo": "net-kingdom", + "source_path": "canon/standards/iam-profile_v0.3.md", + "source_revision": "d4e57e63126d2cca1d381c025170e4b1f678c3f3", + "source_digest": "6c47b596ea145fa197c9665081c84902482d90f5bb94d185c1223af65279be1d", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/netkingdom-iam-profile-v0.3.html", + "rendered_page_digest": "b020f882093fbb0fa8290d4c09be665c3ed3b157522ec2f7231652e525019875", + "archived_source_path": "sources/netkingdom-iam-profile-v0.3.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "NK-ADR-0006", + "revision": "1", + "revision_path": "adr/netkingdom-recursive-multi-tenant-identity-authorization/v1/revisions/1/index.html", + "source_repo": "net-kingdom", + "source_path": "docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md", + "source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8", + "source_digest": "e92a43649bb6e14e53ec62ecc819405bf3a44bda9557487f7177402f107bbd13", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/NK-ADR-0006.html", + "rendered_page_digest": "97070487ddd5d14bd85e68ae2e02f2512a3ad997352b384c0df344be48b3394c", + "archived_source_path": "sources/NK-ADR-0006.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "NK-ADR-0007", + "revision": "1", + "revision_path": "adr/netkingdom-security-orchestration-boundary/v1/revisions/1/index.html", + "source_repo": "net-kingdom", + "source_path": "docs/adr/ADR-0007-security-orchestration-boundary.md", + "source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8", + "source_digest": "b4fcff8448f07aca1fcb6908618bdb19f6c0e7dce25d4c5c8b85eec2f175233b", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/NK-ADR-0007.html", + "rendered_page_digest": "15056fedbd58e65e7c2b16cb5ea6d1c5f23cd23b1162c59e3e29500f8075ff3c", + "archived_source_path": "sources/NK-ADR-0007.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "NK-ADR-0008", + "revision": "1", + "revision_path": "adr/netkingdom-object-storage-sts-credential-vending/v1/revisions/1/index.html", + "source_repo": "net-kingdom", + "source_path": "docs/adr/ADR-0008-object-storage-sts-credential-vending.md", + "source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8", + "source_digest": "f47276f4953f62b783397ee7fb1d3693da060103247e425a9a5b40d019fb4272", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/NK-ADR-0008.html", + "rendered_page_digest": "6a9a2b2249de33ff1342e027829f6cf2ce55dd067758c1cb63c8d2cd8e528f9b", + "archived_source_path": "sources/NK-ADR-0008.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "NK-ADR-0010", + "revision": "1", + "revision_path": "adr/netkingdom-orchestration-dependency-intent/v1/revisions/1/index.html", + "source_repo": "net-kingdom", + "source_path": "docs/adr/ADR-0010-orchestration-vs-dependency-self-coherent-intent.md", + "source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8", + "source_digest": "b7c4f6a13f2f5add08bd03cb39c4f18ca25b202747dde883a309d1b3eaa1f571", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/NK-ADR-0010.html", + "rendered_page_digest": "be883fd0ad350fd0bb7991304fa29056485e1a9f95a61f8472e6b335261c4922", + "archived_source_path": "sources/NK-ADR-0010.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "NK-ADR-0011", + "revision": "1", + "revision_path": "adr/netkingdom-iam-profile-governance/v1/revisions/1/index.html", + "source_repo": "net-kingdom", + "source_path": "docs/adr/ADR-0011-iam-profile-ownership-and-version-governance.md", + "source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8", + "source_digest": "b5c7fd1e78026063b4a2ca4017202512d4f94ab5e12dc8498a34d04d3a48c7a9", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/NK-ADR-0011.html", + "rendered_page_digest": "3d75a914645d4c7bd2aaea7c9ebb3d1d22d491e1ebdd3cc84a8a3c7ad44ede1b", + "archived_source_path": "sources/NK-ADR-0011.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "NK-ADR-0012", + "revision": "1", + "revision_path": "adr/netkingdom-playbook-capability-ownership/v1/revisions/1/index.html", + "source_repo": "net-kingdom", + "source_path": "docs/adr/ADR-0012-playbook-capability-contract-ownership.md", + "source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8", + "source_digest": "e269fabfc376f97f2a03ea66e34059d54016a445a7f30b351a6774b65c96c2ee", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/NK-ADR-0012.html", + "rendered_page_digest": "f64f93bcc0d527e5a782a599bf4e61374fcac9ded2c1a1e9003176de374a03fa", + "archived_source_path": "sources/NK-ADR-0012.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "NK-ADR-0013", + "revision": "2", + "revision_path": "adr/netkingdom-tenant-onboarding-taxonomy/v1/revisions/2/index.html", + "source_repo": "net-kingdom", + "source_path": "docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md", + "source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8", + "source_digest": "3a6030a8958176a902942ffd29154104ba3441a09d06edf3deaeadc7291ee0d4", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/NK-ADR-0013.html", + "rendered_page_digest": "c0b6997e551dba96b07eea3e78fca10f1760104494143606847fa74ccfdde53a", + "archived_source_path": "sources/NK-ADR-0013.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "NK-ADR-0014", + "revision": "1", + "revision_path": "adr/netkingdom-tenant-capability-ownership/v1/revisions/1/index.html", + "source_repo": "net-kingdom", + "source_path": "docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md", + "source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8", + "source_digest": "843f7a65f0fc145d08a73e364bc9a1dee0f7b8af934abf1584ee39dffa903ee0", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/NK-ADR-0014.html", + "rendered_page_digest": "6d2c05eb1fd48b27d2236ca7006e593bf8747df47e72952c947cbcda9ea0456f", + "archived_source_path": "sources/NK-ADR-0014.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "NK-ADR-0015", + "revision": "1", + "revision_path": "adr/netkingdom-railiance-workload-packaging/v1/revisions/1/index.html", + "source_repo": "net-kingdom", + "source_path": "docs/adr/ADR-0015-netkingdom-railiance-workload-packaging-and-relational-platform.md", + "source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8", + "source_digest": "2000985ef211aeedd3656e15cedb289e655526c2dcc4a161a64ffc27f0289db1", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/NK-ADR-0015.html", + "rendered_page_digest": "826db77b95bccd3bbb169808aebab17f6a511405b03d4d05c04c79be2e49f3aa", + "archived_source_path": "sources/NK-ADR-0015.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RINFRA-ADR-0005", + "revision": "accepted-1", + "revision_path": "adr/railiance-k3s-api-tunnel-only/v1/revisions/accepted-1/index.html", + "source_repo": "railiance-infra", + "source_path": "docs/adr/ADR-005-k3s-api-tunnel-only.md", + "source_revision": "f3e8bf3ab4b1dafa4d0b251b88ab2ef23a30c37b", + "source_digest": "4d93643d00d19b9d7c46843cd9be674c24e4cd613dc8e780c1684d97a6a9030a", + "delivered_manifest_source_revision": "f3e8bf3ab4b1dafa4d0b251b88ab2ef23a30c37b", + "rendered_page_path": "pages/RINFRA-ADR-0005.html", + "rendered_page_digest": "ce55375217dd70e253f422ad6ed29006d99a64fec64ea16b2d6544ddd812654e", + "archived_source_path": "sources/RINFRA-ADR-0005.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ACT-ADR-006", + "revision": "accepted-1", + "revision_path": "adr/activity-core-glas-profile-execution/v1/revisions/accepted-1/index.html", + "source_repo": "activity-core", + "source_path": "docs/adr/adr-006-glas-profile-execution.md", + "source_revision": "ea6fb0e92a25825b42e00b2f85aa23cf50605294", + "source_digest": "88041e9eb3a0f91fdd9da46f9813d7304b5ba58e62f9d28ea8f63c0a7b055b63", + "delivered_manifest_source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "rendered_page_path": "pages/ACT-ADR-006.html", + "rendered_page_digest": "fd7e88b98f1da488e38e52cdbf6fc08cd3e2e5862c9ad7a00f56fcee1da77daf", + "archived_source_path": "sources/ACT-ADR-006.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ACT-ADR-007", + "revision": "accepted-1", + "revision_path": "adr/activity-core-bounded-operations/v1/revisions/accepted-1/index.html", + "source_repo": "activity-core", + "source_path": "docs/adr/adr-007-bounded-operations.md", + "source_revision": "ea6fb0e92a25825b42e00b2f85aa23cf50605294", + "source_digest": "75f42047b51293f7240f8a8c3ec02bf025a47fe6627f106fc4ef22fb1be7459d", + "delivered_manifest_source_revision": "b72fdb5452bff51a867a0316edb994723b35f268", + "rendered_page_path": "pages/ACT-ADR-007.html", + "rendered_page_digest": "2c41e74508a38ba0ab9e711c7fd4052d3cbb837a15b10f1e43ca11329d10058a", + "archived_source_path": "sources/ACT-ADR-007.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ops-warden-adr-0007", + "revision": "1", + "revision_path": "adr/ops-warden-build-stage-credential-disclosure/v1/revisions/1/index.html", + "source_repo": "ops-warden", + "source_path": "docs/adr/ADR-0007-build-stage-stops-at-credential-disclosure.md", + "source_revision": "4e267179db741b27a3e62f81f753cd9752c97412", + "source_digest": "868f953688988b11ce48f4141833bbca51abdb4e86d5b495a8a905002830d7b0", + "delivered_manifest_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "rendered_page_path": "pages/ops-warden-adr-0007.html", + "rendered_page_digest": "a6ad7d6fb4fadc21e6e6755225745733780b13755b28d4d706c614b9269003aa", + "archived_source_path": "sources/ops-warden-adr-0007.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ops-warden-adr-0008", + "revision": "1", + "revision_path": "adr/ops-warden-grade-disclosure-path/v1/revisions/1/index.html", + "source_repo": "ops-warden", + "source_path": "docs/adr/ADR-0008-grade-the-path-not-the-field.md", + "source_revision": "4e267179db741b27a3e62f81f753cd9752c97412", + "source_digest": "2e22863ba592802cceb40b32d395168b42ace747741f5188307505eaa89ff764", + "delivered_manifest_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "rendered_page_path": "pages/ops-warden-adr-0008.html", + "rendered_page_digest": "b0129596a06884b2d34a74adfac4b08e3bd2e1af6ab8856d5bd4d50b4496a66c", + "archived_source_path": "sources/ops-warden-adr-0008.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ops-warden-adr-0009", + "revision": "1", + "revision_path": "adr/ops-warden-security-zones-consumer/v1/revisions/1/index.html", + "source_repo": "ops-warden", + "source_path": "docs/adr/ADR-0009-adopt-security-zones-as-a-consumer.md", + "source_revision": "4e267179db741b27a3e62f81f753cd9752c97412", + "source_digest": "73fff22177b4bec2c56ff737e06e4225eb02d39669be3ea1b723906245f878b8", + "delivered_manifest_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "rendered_page_path": "pages/ops-warden-adr-0009.html", + "rendered_page_digest": "533a14b26081a1d88fa9c2fb0c7ea9bfde542e452cc989a68b5da33b0e024510", + "archived_source_path": "sources/ops-warden-adr-0009.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "ops-warden-adr-0010", + "revision": "1", + "revision_path": "adr/ops-warden-staff-layer/v1/revisions/1/index.html", + "source_repo": "ops-warden", + "source_path": "docs/adr/ADR-0010-ops-warden-is-staff.md", + "source_revision": "4e267179db741b27a3e62f81f753cd9752c97412", + "source_digest": "064455bcb2870abf8e243f5a8c154e50bfc1c537cfba7996a792f9e314dd78ae", + "delivered_manifest_source_revision": "8afcc9c32170d76f6d5d02ea153967ed5c6fc4d5", + "rendered_page_path": "pages/ops-warden-adr-0010.html", + "rendered_page_digest": "91727be45934f64eb5f98cb995fcea9f76bb219e1c6bec197e8937f8ea82f6b1", + "archived_source_path": "sources/ops-warden-adr-0010.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RMASTER-ADR-0009", + "revision": "accepted-1", + "revision_path": "adr/railiance-netkingdom-security-layer-interaction/v1/revisions/accepted-1/index.html", + "source_repo": "railiance-master", + "source_path": "docs/adr/ADR-0009-netkingdom-security-layer-interaction.md", + "source_revision": "51747ca793d830c42bd86ce0ca43e34db9e8a1c1", + "source_digest": "c2f81c9718715fc08ea7a2e3021d0d3159958ace23b60a16430020637b9f57f2", + "delivered_manifest_source_revision": "5ffd7d1b40d56249f490a318e728047fd3517c4c", + "rendered_page_path": "pages/RMASTER-ADR-0009.html", + "rendered_page_digest": "0a91e1d4fac1ebda5dc383c9f750d307dc29ef81789ac26d3c2659d58666b897", + "archived_source_path": "sources/RMASTER-ADR-0009.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "CUST-ADR-012", + "revision": "1.0", + "revision_path": "adr/custodian-projection-source-overlay/v1/revisions/1.0/index.html", + "source_repo": "the-custodian", + "source_path": "canon/architecture/adr-012-projection-source-and-preliminary-overlay.md", + "source_revision": "6475a2e1ca590682888b77ce32ea056fd02e272a", + "source_digest": "b5e8582459f546ae789ad5fd62f458454aa19997b520e32b6b9f792d6af55987", + "delivered_manifest_source_revision": "f9435cd605cc5b3cb0f2e957ce6287d9f3129aac", + "rendered_page_path": "pages/CUST-ADR-012.html", + "rendered_page_digest": "63488f677010582fcbdd157fd6c10b2fa6e765db3c605605d1177e57e231f4ff", + "archived_source_path": "sources/CUST-ADR-012.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "netkingdom-posture-feedback-v0.1", + "revision": "0.1", + "revision_path": "standards/posture-feedback/v0.1/revisions/0.1/index.html", + "source_repo": "net-kingdom", + "source_path": "canon/standards/posture-feedback_v0.1.md", + "source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8", + "source_digest": "dd2628b9f0c2a662ac44af22657d91918da228f307ebc7ceb09fc856162985db", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/netkingdom-posture-feedback-v0.1.html", + "rendered_page_digest": "54c14e25e018593d83df5f4b69a8316c0f1082460f94e5a0fbc80ecf61089d3b", + "archived_source_path": "sources/netkingdom-posture-feedback-v0.1.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "netkingdom-security-layer-model-v0.7", + "revision": "0.7", + "revision_path": "standards/security-layer-model/v0.7/revisions/0.7/index.html", + "source_repo": "net-kingdom", + "source_path": "canon/standards/security-layer-model_v0.7.md", + "source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8", + "source_digest": "8155e7b123be1e84377d8278525b1bad8961007b6bb8ff012dd01ba24ff8065b", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/netkingdom-security-layer-model-v0.7.html", + "rendered_page_digest": "7d7a38a8c3b9fff65b46280fcd5723eff77dc9d0784f871bb35f718bc4e8c0e7", + "archived_source_path": "sources/netkingdom-security-layer-model-v0.7.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "netkingdom-security-scenario-composition-v0.1", + "revision": "0.1", + "revision_path": "standards/security-scenario-composition/v0.1/revisions/0.1/index.html", + "source_repo": "net-kingdom", + "source_path": "canon/standards/security-scenario-composition_v0.1.md", + "source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8", + "source_digest": "17b715d78e04470a0f83b8bc8c7313ab16e13e9e741d5ec445172d9008fce937", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/netkingdom-security-scenario-composition-v0.1.html", + "rendered_page_digest": "2e401b2c8948ca983e7cd364faeea28b1d0bb6963a156e9b8e0e0a04961bf1c9", + "archived_source_path": "sources/netkingdom-security-scenario-composition-v0.1.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "netkingdom-security-zones-v0.1", + "revision": "0.1", + "revision_path": "standards/security-zones/v0.1/revisions/0.1/index.html", + "source_repo": "net-kingdom", + "source_path": "canon/standards/security-zones_v0.1.md", + "source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8", + "source_digest": "32e71e9c0d6946bb14099eb66193822da26f199de0e11d8488464207f3bd9906", + "delivered_manifest_source_revision": "9781102e2971d762ae42fdd5085a6647afd1cd66", + "rendered_page_path": "pages/netkingdom-security-zones-v0.1.html", + "rendered_page_digest": "b8c7ce8198edbb637657ed4194356453aa3c3794c5592ebcee4c157a68a9a2b1", + "archived_source_path": "sources/netkingdom-security-zones-v0.1.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RISK-F-0001", + "revision": "published-1", + "revision_path": "findings/flex-auth-unauthenticated-check/v1/revisions/published-1/index.html", + "source_repo": "risk-nexus", + "source_path": "findings/RISK-F-0001-flex-auth-unauthenticated-check.md", + "source_revision": "a13d954f8597fd92201746e2d52f031a5a88d969", + "source_digest": "b2d1d0c526d5c9b5729b8353877bb9f29096667145f1498cfde73c9711206fda", + "delivered_manifest_source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "rendered_page_path": "pages/RISK-F-0001.html", + "rendered_page_digest": "adfc18f51f4defb529c5529669eb33bf283a4d9c6da78f26bc8cd73cddad85b7", + "archived_source_path": "sources/RISK-F-0001.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RISK-F-0008", + "revision": "published-1", + "revision_path": "findings/audit-retention-legal-basis/v1/revisions/published-1/index.html", + "source_repo": "risk-nexus", + "source_path": "findings/RISK-F-0008-audit-retention-legal-basis-assumed.md", + "source_revision": "a13d954f8597fd92201746e2d52f031a5a88d969", + "source_digest": "8bf47414a9f958afb57649f087a2617f91a8f2aeaa4f34d04d8fb3a0f453e840", + "delivered_manifest_source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "rendered_page_path": "pages/RISK-F-0008.html", + "rendered_page_digest": "c6aee9543f8ef21964b8fe0cd966dfc4ac49b17958d258bef6b580210be08656", + "archived_source_path": "sources/RISK-F-0008.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RISK-METHOD-SEVERITY", + "revision": "adopted-1", + "revision_path": "methods/risk-severity/v1/revisions/adopted-1/index.html", + "source_repo": "risk-nexus", + "source_path": "docs/method/severity.md", + "source_revision": "a13d954f8597fd92201746e2d52f031a5a88d969", + "source_digest": "f064c591aeef9425bbfbd5fa2aae659abb0ef83b8586848300077f41dd4ffaab", + "delivered_manifest_source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "rendered_page_path": "pages/RISK-METHOD-SEVERITY.html", + "rendered_page_digest": "8073cd40f67197fe093ce1173bcc0a9ce1e7170c157dd88726997fff1cb791e6", + "archived_source_path": "sources/RISK-METHOD-SEVERITY.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RISK-METHOD-DISCLOSURE", + "revision": "adopted-1", + "revision_path": "methods/risk-disclosure/v1/revisions/adopted-1/index.html", + "source_repo": "risk-nexus", + "source_path": "docs/method/disclosure.md", + "source_revision": "a13d954f8597fd92201746e2d52f031a5a88d969", + "source_digest": "bd825a87736c8fb019d4224c3ef4fe3882b8f3028035225b4b8ff8cad62fdb81", + "delivered_manifest_source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "rendered_page_path": "pages/RISK-METHOD-DISCLOSURE.html", + "rendered_page_digest": "f0e2eaabe65543725f54b4701ea3a1efdbc936006ee9d309650feb21cbc593ca", + "archived_source_path": "sources/RISK-METHOD-DISCLOSURE.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RISK-METHOD-REVIEW", + "revision": "adopted-1", + "revision_path": "methods/risk-review/v1/revisions/adopted-1/index.html", + "source_repo": "risk-nexus", + "source_path": "docs/method/review.md", + "source_revision": "a13d954f8597fd92201746e2d52f031a5a88d969", + "source_digest": "8f873d105bf6bd711a1a15ebd739b2abe97365b4e854be9d1c82e3ff3b162cfd", + "delivered_manifest_source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "rendered_page_path": "pages/RISK-METHOD-REVIEW.html", + "rendered_page_digest": "64e707283c19bc46bf41b03c266ee6b625824696dcfc35dc83333b43e1662f8e", + "archived_source_path": "sources/RISK-METHOD-REVIEW.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RISK-METHOD-VERIFICATION", + "revision": "adopted-1", + "revision_path": "methods/risk-verification/v1/revisions/adopted-1/index.html", + "source_repo": "risk-nexus", + "source_path": "docs/method/verification.md", + "source_revision": "a13d954f8597fd92201746e2d52f031a5a88d969", + "source_digest": "562b6eb3031ffe35e712c5e11e35aae23ce84a90e0090438dc89513f9685052b", + "delivered_manifest_source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "rendered_page_path": "pages/RISK-METHOD-VERIFICATION.html", + "rendered_page_digest": "e6219a88daef48fd715d7c8092d2db6e5eda8f4d44dcbf9a0b7d975227237b92", + "archived_source_path": "sources/RISK-METHOD-VERIFICATION.md", + "original_source_verification": "git-show-exact-commit-and-path" + }, + { + "id": "RISK-METHOD-DEPENDENCIES", + "revision": "adopted-1", + "revision_path": "methods/risk-dependencies/v1/revisions/adopted-1/index.html", + "source_repo": "risk-nexus", + "source_path": "docs/method/dependencies.md", + "source_revision": "a13d954f8597fd92201746e2d52f031a5a88d969", + "source_digest": "a5e7b42043b0bb366add9bbc5f606e134ef5b0454532480203d5d87099296240", + "delivered_manifest_source_revision": "d65aaf7484cf8d01510fa6ae0ee267f434549ff6", + "rendered_page_path": "pages/RISK-METHOD-DEPENDENCIES.html", + "rendered_page_digest": "24f29c04622f13156a1c6d69c0f98843e00d2d12cdbf506bf6a260d67b00d4e4", + "archived_source_path": "sources/RISK-METHOD-DEPENDENCIES.md", + "original_source_verification": "git-show-exact-commit-and-path" + } + ] +} diff --git a/docs/evidence/2026-10-11-served-publication-history/source-verification-errors.json b/docs/evidence/2026-10-11-served-publication-history/source-verification-errors.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/source-verification-errors.json @@ -0,0 +1 @@ +[] diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-001.md b/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-001.md new file mode 100644 index 0000000..d39ca39 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-001.md @@ -0,0 +1,191 @@ +--- +id: ACT-ADR-001 +type: architecture-decision-record +title: "Activity-Core as Coulomb Org Event Bridge" +status: accepted +owner: activity-core +revision: "accepted-2" +last_reviewed: "2026-05-14" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-05-14" +scope: cross-repo +affects: + - activity-core + - the-custodian/state-hub + - issue-facade (→ issue-core) + - repo-scoping +tags: ["architecture", "event-bridge", "activity-core", "orchestration", "event-loop"] +--- + +# ACT-ADR-001: Activity-Core as Coulomb Org Event Bridge + +## Status + +Accepted. + +## Context + +The Coulomb organization's set of repositories, services, and deployments is growing +beyond what a single person can coordinate manually. The state hub tracks cross-domain +state but has no mechanism to automatically respond to it. Recurring maintenance +(dependency scans, SBOM staleness checks, consistency audits) is implemented as +bespoke cron jobs baked into individual services — scattered, hard to audit, and +impossible to govern from a single vantage point. + +Three forces drive the need for a dedicated orchestration layer: + +1. **Scale**: as the repo count grows, manual coordination becomes the bottleneck. +2. **Reactivity**: org-level events (new repo registered, CVE published, deployment + completed) should trigger coordinated responses without human intervention. +3. **Separation of concerns**: the state hub is a read model and should remain one. + It must not accumulate automation logic to avoid becoming a God object. + +## Decision + +**activity-core is the org-wide Event Bridge for the Coulomb organization.** + +Its responsibility is exactly three things: +1. **Receive events** — time-based (cron, one-off scheduled) and domain events (NATS, + Gitea webhooks, state hub lifecycle signals). +2. **Evaluate rules and instructions** — given event payload and resolved context, + determine what work must be created. +3. **Emit task sets** — publish structured task creation requests to issue-core. + +It does not execute work. It does not track task lifecycle. It does not manage projects. + +### Boundary rules + +| Concern | Owner | +|---|---| +| Cross-org task scheduling and reactive automation | **activity-core** | +| Task lifecycle (create, assign, track, close) | **issue-core** | +| Project and initiative management (phased, completion-gated) | **project-core** (future) | +| Repository capability profiling | **repo-scoping** | +| Cross-domain coordination state | **state hub** | +| Execution of automatable tasks | Temporal workers (per-repo) | + +### Event type registry + +Event types are declared by publishers as markdown definition files (see ACT-ADR-002). +Governance is **publisher-declared by default**: a publisher registers its event types +by committing definition files to the event-types registry. In production environments, +a curator gate can be enabled — registry entries must be reviewed before the runtime +accepts events of that type. This is a configuration flag per runtime scope (dev, +staging, prod), not a hard-coded rule. + +### State hub relationship + +The state hub **delegates automation to activity-core** rather than implementing it +internally. Concretely: + +- Maintenance jobs currently baked into the state hub (consistency sync, SBOM + staleness checks) are migrated to ActivityDefinitions in activity-core. +- The state hub becomes a **publisher** of lifecycle events on NATS + (`org.workstream.created`, `org.decision.resolved`, `org.repo.registered`, etc.). +- The state hub does not subscribe to activity-core's output directly; it reads + task state from issue-core when needed. + +This preserves the state hub as a read model and makes activity-core the single +home for automation policy. + +### rules-core: module-first + +The rule and instruction evaluation engine starts as `src/activity_core/rules/` — a +module with a clean internal boundary (no imports from Temporal, Postgres, or FastAPI +within the module). Extraction to a standalone `rules-core` repository happens when a +**second consumer** (e.g. state hub governance, project-core) needs the engine. This +follows the same discipline as the task-flow-engine extraction plan (CUST-TFE-SCOPE). + +### NATS as org infrastructure + +NATS JetStream is promoted from an activity-core internal component to **org-wide +event bus infrastructure**. It runs as a standalone service (not bundled in +activity-core's docker-compose) with its own lifecycle. All services that publish +or subscribe to org events do so via NATS streams. + +### issue-core integration + +activity-core communicates with issue-core via a **task emission adapter** — an +abstraction layer that, in the initial implementation, calls issue-core's REST API. +The adapter interface is defined now; the transport can migrate to NATS subscription +(issue-core subscribes to `task.spawned` events) once issue-core adds that capability. +This avoids hardcoding REST coupling throughout the codebase. + +### Webhook receiver + +A new HTTP endpoint within activity-core accepts inbound webhooks from Gitea (and +later GitHub, other services). It normalises payloads to the canonical EventEnvelope +format, validates against the event type registry, and publishes to NATS. This runs +alongside the existing FastAPI `api.py`. + +### Domain assignment + +activity-core and issue-core are assigned to the **`capabilities`** domain — the +same domain as repo-scoping. These are org-wide infrastructure tools that serve all +domains equally, not artefacts of any single project or custodian's personal workflow. +issue-core is explicitly disassociated from the markitect domain. + +## Trigger types + +Three trigger types are supported: + +| Type | Description | Temporal mechanism | +|---|---|---| +| `cron` | Recurring schedule (5-field cron + timezone + misfire policy) | Temporal Schedule (implemented WP-0002) | +| `event` | React to a named event type on NATS | Temporal workflow started by Event Router | +| `scheduled` | One-off at a future datetime | Temporal Schedule with `remaining_actions: 1` | + +`scheduled` is a new trigger type added in WP-0003. + +## Consequences + +### Immediate + +- activity-core's `INTENT.md` and `SCOPE.md` are rewritten to reflect this architecture. +- `task_spawn_log` is the local **spawn audit trail**; authoritative work-item + lifecycle state lives downstream. +- A task emission adapter (`src/activity_core/issue_sink.py`) owns downstream + creation. The unused `TaskExecutorWorkflow` and `task_instances` compatibility + surface was retired by ACTIVITY-WP-0035. + +### Medium term + +- State hub adds NATS publishing to its lifecycle operations. +- Gitea webhook receiver added to activity-core as a new HTTP router. +- Existing state hub maintenance crons are migrated to ActivityDefinitions. +- issue-facade is renamed issue-core and re-registered under the `capabilities` domain. + +### Long term + +- rules-core extracted as a standalone package when a second consumer appears. +- project-core created (depends on task-flow-engine extraction) for multi-phase + initiative management — explicitly out of scope for activity-core. +- NATS gets its own operational runbook and monitoring as org infrastructure. + +## Alternatives Considered + +**State hub absorbs activity-core functionality**: rejected — turns the state hub into +a God object, violates the read-model boundary, and makes automation logic impossible +to test independently. + +**Per-repo automation (GitHub Actions style)**: rejected — cross-repo coordination +requires a single vantage point that can see all repos; per-repo actions can't express +org-level triggers or context. + +**Activity-core as a thin Temporal wrapper only**: rejected — without the event type +registry and rule model, it's just a scheduler. The governance and introspection +properties are the point. + +**Separate rules-core from day one**: rejected — premature extraction adds dependency +management overhead before a second consumer exists. Module-first with a clean boundary +costs nothing and preserves the extraction option. + +## Related + +- ACT-ADR-002 — Event type and ActivityDefinition definition format +- ACT-ADR-003 — Rule vs. Instruction model and DSL +- CUST-ADR-001 — Workplans as repository artefacts (canon/architecture/) +- CUST-TFE-SCOPE-2026-000001 — task-flow-engine extraction plan (canon/projects/) +- activity-core INTENT.md (to be written) +- activity-core WP-0003 (to be written) diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-002.md b/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-002.md new file mode 100644 index 0000000..80a2b17 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-002.md @@ -0,0 +1,360 @@ +--- +id: ACT-ADR-002 +type: architecture-decision-record +title: "Markdown-as-Definition Format for Event Types and ActivityDefinitions" +status: accepted +owner: activity-core +revision: "accepted-2" +last_reviewed: "2026-05-14" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-05-14" +scope: cross-repo +affects: + - activity-core + - any event publisher registering event types +tags: ["architecture", "format", "event-type", "activity-definition", "markdown", "documentation"] +--- + +# ACT-ADR-002: Markdown-as-Definition Format + +## Status + +Accepted. + +## Context + +Event type schemas and ActivityDefinition rules need to be understood and authored +by three distinct audiences simultaneously: humans reviewing and debugging automation, +agents creating and modifying definitions at runtime, and machines parsing and +evaluating them. Traditional approaches split these concerns — schemas go in JSON +Schema or YAML, documentation goes in a wiki, logic goes in code — and they drift +apart. A bug in a rule requires cross-referencing three places to understand intent, +check the schema, and read the condition. + +The Custodian ecosystem already uses markdown files with YAML frontmatter as the +authoritative format for workplans, ADRs, SCOPE.md, and INTENT.md — all understood +by humans and agents without additional tooling. The same pattern should apply here. + +## Decision + +**Event type definitions and ActivityDefinitions are markdown files** where machine- +parseable structure (frontmatter YAML and fenced definition blocks) is embedded within +human-readable narrative. Intent, schema, logic, and debugging notes live in one file. + +### Event Type Definition Files + +**Location**: `event-types/{namespace}.{event-name}.md` within the activity-core repo +(or a registered event-types registry repo if volumes justify separation). + +**Naming convention**: `{publisher-domain}.{noun}.{verb}.md`, e.g.: +- `org.repo.registered.md` +- `org.security.cve.published.md` +- `org.workstream.completed.md` + +**Structure**: + +```markdown +--- +id: org.repo.registered +type: event-type +version: "1.0" +publisher: the-custodian/state-hub +governance: publisher-declared # publisher-declared | curated +status: active # active | deprecated | draft +introduced: "2026-05-14" +--- + +# Event: org.repo.registered + +## Intent + +One-paragraph statement of why this event exists and what it signals. +Written for an agent or human who has never seen it before. + +## When Published + +Bulleted list of the exact conditions under which the publisher fires this event. +Be precise — ambiguity here causes missed or duplicate activations. + +## Attributes + +| Attribute | Type | Required | Description | +|---|---|---|---| +| `repo_slug` | string | yes | URL-safe repository identifier | +| `domain` | string | yes | Domain slug the repo is assigned to | +| `tags` | string[] | no | Capability tags set at registration time | +| `registered_at` | datetime | yes | ISO 8601 UTC timestamp | + +## Example Payload + +​```json +{ + "id": "evt-7f3a1b2c", + "type": "org.repo.registered", + "version": "1.0", + "timestamp": "2026-05-14T10:00:00Z", + "publisher": "the-custodian/state-hub", + "attributes": { + "repo_slug": "new-python-service", + "domain": "railiance", + "tags": ["python-service", "fastapi"], + "registered_at": "2026-05-14T10:00:00Z" + } +} +​``` + +## Consumer Notes + +Guidance for agents and humans writing rules against this event type: +- Which attributes are safe for instruction prompts (trusted fields) +- Common misuses or gotchas +- Related events that are often used together + +## Debugging + +What to check when an activity that subscribes to this event does not fire: +- How to verify the event was published (NATS subject, log entry) +- How to inspect the event payload in the registry +- Common schema validation failures +``` + +### Attribute Types + +The type system for event attributes is intentionally small: + +| Type | Notes | +|---|---| +| `string` | UTF-8 string | +| `integer` | 64-bit signed integer | +| `float` | 64-bit float | +| `boolean` | true / false | +| `datetime` | ISO 8601 UTC string in payload, parsed to datetime in evaluator | +| `uuid` | String in payload, validated as UUID v4 | +| `string[]` | JSON array of strings | +| `integer[]` | JSON array of integers | +| `object` | Freeform JSON object — cannot be used in rule conditions; instruction-only | + +`object` type attributes are available to instructions but excluded from rule +conditions deliberately — rules must be deterministic and schema-validatable. + +### ActivityDefinition Files + +**Location**: `activity-definitions/{slug}.md` within the repo that owns the automation. +For org-wide automations: `activity-core/activity-definitions/`. +For domain-specific automations: `{domain-repo}/activity-definitions/`. + +**Structure**: + +```markdown +--- +id: ACT-DEF-onboard-python-repo +type: activity-definition +version: "1.0" +status: active +trigger: + type: event # event | cron | scheduled + event_type: org.repo.registered # for type: event + # cron: "0 9 * * 1" # for type: cron (5-field, UTC) + # timezone: "Europe/Berlin" # optional, cron only + # misfire_policy: skip # skip | catchup | compress (cron only) + # at: "2026-06-01T09:00:00Z" # for type: scheduled (one-off) +context_sources: + - type: repo-scoping + query: repo_profile + bind_to: context.repo_profile + - type: state-hub + query: domain_summary + bind_to: context.domain_summary +governance: publisher-declared +owner: custodian-agent +created: "2026-05-14" +--- + +# ActivityDefinition: Onboard New Python Service + +## Purpose + +One paragraph. What does this automation do and why does it exist? What problem +would accumulate if this automation were turned off? + +## Trigger + +Which event type fires this activity, and under what conditions does it apply? +Cross-reference the event type definition file. + +## Context Sources + +What context is resolved before rules are evaluated? Explain what each source +provides and why it is needed. + +## Rules + +Each rule is a fenced block tagged `rule`. Rules are evaluated in order; all +matching rules fire (not first-match-only). See ACT-ADR-003 for the expression +language specification. + +​```rule +id: create-sbom-scan +condition: '"python-service" in event.attributes.tags' +action: + task_template: tasks/sbom-initial-scan.md + target_repo: event.attributes.repo_slug + priority: high + labels: ["onboarding", "security"] +​``` + +​```rule +id: create-scope-generation +condition: '"python-service" in event.attributes.tags and context.repo_profile.scope_md_exists == false' +action: + task_template: tasks/generate-scope-md.md + target_repo: event.attributes.repo_slug + priority: medium + labels: ["onboarding", "documentation"] +​``` + +## Instructions + +Instructions are evaluated after all rules. An instruction asks an LLM to decide +what additional tasks (if any) to create. See ACT-ADR-003 for safety requirements. + +​```instruction +id: domain-specific-onboarding +condition: 'event.attributes.domain != "test_domain_v2"' +trusted_fields: + - event.attributes.repo_slug + - event.attributes.domain + - event.attributes.tags +model: claude-sonnet-4-6 +review_advisory: false +prompt: | + A new repository has been registered in the Coulomb organization. + + Repository: {event.attributes.repo_slug} + Domain: {event.attributes.domain} + Tags: {event.attributes.tags} + + Based on the domain's current standards and the repository profile above, + determine what additional domain-specific onboarding tasks should be created + beyond the standard SBOM scan and SCOPE.md generation. Return an empty list + if no additional tasks are warranted. +output_schema: tasks/task-template-list-schema.json +​``` + +## Task Templates + +References to task template files used in rule actions. Each template is a +separate markdown file under `tasks/` that defines the task title, description +template, default labels, and default assignee logic. + +- `tasks/sbom-initial-scan.md` +- `tasks/generate-scope-md.md` + +## Notes + +Operational notes, edge cases, and context that does not fit elsewhere. + +## Debugging + +Checklist for when this ActivityDefinition fires but produces unexpected output: + +1. Was the triggering event published with the correct type and attributes? +2. Do the rule conditions evaluate as expected? (Use `make eval-rule` with a fixture) +3. Is issue-core reachable and configured for the target domain? +4. For instructions: check the audit log for the model response and output validation result. + +## Change History + +- v1.0 (2026-05-14): Initial definition +``` + +### Governance model + +The `governance` field on an event type definition determines how the registry +runtime handles it: + +| Value | Behaviour | +|---|---| +| `publisher-declared` | Accepted immediately on publish; no review required | +| `curated` | Held in `pending` state until a curator approves via registry API | + +The runtime checks the **environment's curator gate configuration** — not just the +file's governance field. An environment configured with `curator_gate: disabled` +treats all event types as `publisher-declared` regardless of the field value. +An environment with `curator_gate: required` treats all event types as `curated` +regardless of the field value. The field is the publisher's declared preference; +the environment config is the enforcement point. + +This means: +- **Dev / integration**: `curator_gate: disabled` — developers and agents iterate + freely; new event types take effect immediately. +- **Staging / production**: `curator_gate: required` — all new event types queue + for curator review before the runtime accepts events of that type. + +### File as source of truth + +Following CUST-ADR-001 (Workplans as Repository Artefacts), definition files are +the canonical source of truth. The activity-core runtime indexes them into its +database on startup and via a sync command. The database is a queryable cache, +not the origin. A definition deleted from the filesystem is disabled at next sync. + +### Task Templates + +Task templates are separate markdown files (`tasks/{slug}.md`) referenced from +ActivityDefinition action blocks. They define: + +```markdown +--- +id: tasks/sbom-initial-scan +type: task-template +--- +# Task: Run Initial SBOM Scan + +## Title template +`Run SBOM scan — {target_repo}` + +## Description template +Initial SBOM scan required for newly registered repository `{target_repo}`. +Run: `make ingest-sbom REPO={target_repo} SCAN=1` + +## Default labels +["sbom", "security", "automated"] + +## Default assignee +None (unassigned) +``` + +This keeps task content editable separately from the routing logic in +ActivityDefinitions. + +## Consequences + +- A new `event-types/` directory in activity-core (and eventually a shared registry) + holds all org event type definitions. +- A new `activity-definitions/` directory in activity-core holds org-wide automations. +- Domain repos may hold their own `activity-definitions/` for domain-specific + automations, scanned by activity-core at sync time. +- The runtime requires a parser for the `rule` and `instruction` fenced blocks. +- SCOPE.md for activity-core must be updated to list these directories. + +## Alternatives Considered + +**Pure JSON Schema for event types, separate wiki for docs**: rejected — documentation +and schema diverge immediately; agents must cross-reference two systems to author +a rule correctly. + +**OpenAPI / AsyncAPI specification**: rejected — those formats are excellent for +API and broker documentation but not designed for co-locating operational intent +and debugging guidance. They are also less readable for non-specialists. + +**Code-only (Python dataclasses for event schemas, Python functions for rules)**: +rejected — requires code deployment for any definition change; agents cannot modify +definitions without write access to the codebase; non-technical stakeholders cannot +review or understand automation policies. + +## Related + +- ACT-ADR-001 — Event Bridge Architecture +- ACT-ADR-003 — Rule vs. Instruction model and DSL +- CUST-ADR-001 — Workplans as repository artefacts diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-003.md b/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-003.md new file mode 100644 index 0000000..d535dbe --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-003.md @@ -0,0 +1,342 @@ +--- +id: ACT-ADR-003 +type: architecture-decision-record +title: "Rule vs. Instruction Model and Expression DSL" +status: accepted +owner: activity-core +revision: "accepted-2" +last_reviewed: "2026-05-14" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-05-14" +scope: cross-repo +affects: + - activity-core + - rules-core (future extraction) +tags: ["architecture", "rules", "instructions", "dsl", "llm", "safety", "evaluation"] +--- + +# ACT-ADR-003: Rule vs. Instruction Model and Expression DSL + +## Status + +Accepted. + +## Context + +ActivityDefinitions need two distinct evaluation modes to cover the full range +of automation scenarios in the Coulomb org: + +**Deterministic cases**: "if this repo has tag `python-service` AND has no SBOM +in the last 30 days, create a scan task." The condition is fully expressible as a +boolean predicate over known attributes. The output is fixed by the template. No +ambiguity, no LLM required, fully testable. + +**Judgement cases**: "a new repository has been registered — based on its domain +and profile, determine what domain-specific onboarding tasks are appropriate." The +right answer depends on context that is expensive to encode as explicit rules. An +LLM is a better evaluator than a rule tree, but introduces non-determinism, cost, +and a new attack surface (prompt injection via event payload). + +Conflating these two modes into one mechanism produces a system that is either +too rigid (rules only) or too unpredictable (LLM everywhere). The two modes +need different evaluation pipelines, testing strategies, and audit trails. + +## Decision + +**Two named, distinct evaluation modes: Rule and Instruction.** + +Terminology is deliberate. A **Rule** is deterministic and mechanical — it applies +or it does not. An **Instruction** is contextual and interpretive — it guides an +LLM agent to make a judgement call. Both are expressed as fenced blocks in +ActivityDefinition markdown files (see ACT-ADR-002). + +### Rules + +A Rule has two parts: a **condition** (boolean predicate) and one or more +**actions** (task template references). + +#### Condition expression language + +The condition is a single-line string expression evaluated by a sandboxed +AST walker — never `exec()` or `eval()`. The evaluator walks the parsed AST +and whitelist-checks every node type before executing. Unknown node types +raise an `UnsafeExpression` error at parse time, not at evaluation time. + +**Available operations**: + +| Category | Syntax | Example | +|---|---|---| +| Equality | `==`, `!=` | `event.type == "org.repo.registered"` | +| Comparison | `>`, `<`, `>=`, `<=` | `event.attributes.sbom_age_days > 30` | +| Membership | `in`, `not in` | `"python-service" in event.attributes.tags` | +| Boolean | `and`, `or`, `not` | `a and (b or not c)` | +| Grouping | `( )` | `(a or b) and c` | +| Length | `len(x)` | `len(event.attributes.affected_repos) > 0` | +| Existence | `x is None`, `x is not None` | `event.attributes.domain is not None` | + +**Attribute access** follows dot notation on the `event` object and the `context` +object (populated by context sources declared in the ActivityDefinition): + +- `event.id` — UUID string +- `event.type` — event type identifier +- `event.version` — event type version +- `event.timestamp` — ISO 8601 datetime string +- `event.publisher` — publisher identifier +- `event.attributes.{name}` — typed attribute per event type schema +- `context.{source}.{field}` — resolved context data + +**Explicitly forbidden** (evaluator rejects at parse time): +- Function calls other than `len()` and `None` tests +- Attribute access on arbitrary Python objects +- String interpolation or formatting +- Any control flow (`if`, `for`, `while`, `lambda`) +- Import statements +- Assignments + +**Design rationale**: the expression language is intentionally small. Anything +complex enough to need more than this belongs in an Instruction, not a Rule. +When a rule condition becomes difficult to express, that is a signal that the +case requires LLM judgement, not a signal that the DSL needs more features. + +#### Actions + +A Rule's action block specifies: + +```yaml +action: + task_template: "Run SBOM rescan for {context.repo.repo_slug}" + target_repo: context.repo.repo_slug + priority: medium + labels: ["sbom", "security", "{context.repo.repo_slug}"] + due_in_days: 7 +``` + +`action.task_template` is the emitted task title template. It is not a path to a +repo-local file. Older design notes and the legacy `tasks/*.md` directory use +"task template" for materialized task-body templates; that is a separate legacy +surface. To avoid surprise, new rule actions should treat `task_template` as +`title_template` semantics until the field can be renamed in a schema-breaking +revision. + +Action fields accept two deterministic rendering forms: + +- Whole-field paths: if the whole string is a path like + `context.repo.repo_slug` or `event.attributes.repo_slug`, the rendered value + keeps the original scalar/list/object shape from that path. This is the + correct form for `target_repo` and other fields that should not become prose. +- Scalar placeholders: strings may include `{context.foo}` or `{event.foo}` + placeholders. Each placeholder must resolve to a scalar. Lists and objects are + rejected rather than stringified, which prevents accidental JSON blobs or + untrusted text from being embedded into task titles. + +Unsafe action cases are rejected: + +- Any action path outside `context.*` or `event.*`. +- Any path containing calls, indexing, arithmetic, filters, or boolean logic. +- Placeholder values that resolve to lists or objects. +- `for_each` values that are not a whole-field `context.*` or `event.*` path to + a list. +- `bind_as` names that are not simple identifiers. + +Per-item rule expansion is explicit: + +```yaml +for_each: context.repos.repos +bind_as: repo +condition: 'context.repo.sbom_age_days > 30' +action: + task_template: Run SBOM rescan for {context.repo.repo_slug} + target_repo: context.repo.repo_slug + priority: medium + labels: ["sbom", "security", "automated"] +``` + +The weekly SBOM staleness definition is the canonical pattern. The State Hub +bulk resolver exposes all repository entries at `context.repos.repos`, the rule +binds each item as `context.repo`, and the strict staleness definition is +`context.repo.sbom_age_days > 30`. Thirty days exactly is not stale; thirty-one +days is stale. + +#### Evaluation semantics + +- All rules in an ActivityDefinition are evaluated; **all matching rules fire** + (not first-match-only). There is no implicit ordering beyond the file order, + which is documented in the ActivityDefinition for human clarity. +- A rule whose condition raises an error during evaluation is skipped and logged + as `rule_error`; other rules still fire. This prevents a single malformed rule + from silencing an entire ActivityDefinition. +- An empty condition (omitted `condition` field) evaluates to `true` — the rule + always fires when the trigger fires. + +### Instructions + +An Instruction defers the task-creation decision to an LLM. It specifies what +context to provide, how to frame the prompt, and what output schema to enforce. + +#### Structure + +```yaml +# in an instruction fenced block: +id: {slug} +condition: '{expression}' # optional pre-filter (Rule DSL); runs before LLM +trusted_fields: # REQUIRED — explicit allowlist of payload fields + - event.attributes.repo_slug # safe to interpolate into prompt + - event.attributes.domain + - event.attributes.tags +model: claude-sonnet-4-6 +review_advisory: false # true | false — advisory evidence, not a gate +prompt: | + {prompt template — only trusted_fields may be interpolated} +output_schema: {path to JSON schema file} +``` + +#### Trusted fields and prompt injection protection + +The `trusted_fields` list is **required** and enforced at parse time. Any field +not listed is unavailable to the prompt template. The template engine raises +`UntrustedFieldError` if the prompt references a field not in `trusted_fields`. + +The rationale: event payloads may contain free-text from untrusted sources — +commit messages, issue titles, CVE descriptions, repo descriptions. Interpolating +these directly into a prompt creates a prompt injection surface. Trusted fields +are those whose values are validated by the event type schema (typed attributes +like slugs, domain names, tag lists) and cannot carry arbitrary instruction text +by construction. + +Fields of type `object` (freeform JSON) are **never eligible** for `trusted_fields` +even if listed — the evaluator rejects this at parse time. + +#### Output schema enforcement + +The LLM response is validated against `output_schema` using JSON Schema validation. +If validation fails, the instruction retries once with the schema error appended +to the prompt. If the second attempt also fails, the instruction records an +`instruction_output_error` audit event and emits no tasks. Tasks are **never +created from unvalidated output**. + +Structured output mode (tool_use / JSON mode) is used where the model supports +it. The output schema must define `List[TaskSpec]` or a compatible envelope. + +#### `review_advisory: true` + +ACTIVITY-WP-0035 selected advisory-only semantics because no downstream owner +currently exposes an acknowledged proposal/decision/release contract. The +instruction's task/report evidence is marked `review_advisory=true` and +`review_gate_applied=false` so an operator can distinguish review-worthy output +without interpreting it as held for approval. + +The legacy input name `review_required` is accepted during migration and +normalized to `review_advisory`; new definitions must not use it. activity-core +does **not** route proposals to a pending-review queue and does not own review +lifecycle state. A future hold/release design requires a named downstream owner, +an idempotent release reference, an ADR update, and fail-closed emission tests. + +#### Evaluation semantics + +- Instructions are evaluated **after** all rules in the ActivityDefinition. +- The optional `condition` field on an instruction uses the same Rule DSL as + a first-pass filter — if the condition is false, the LLM is not called. + This avoids LLM cost for events that clearly do not need instruction judgement. +- Instructions are **not** first-match-only; all instructions whose conditions + pass fire. An ActivityDefinition may have zero instructions. + +### Audit trail + +Every task emission records: + +| Field | Rule | Instruction | +|---|---|---| +| `source_type` | `"rule"` | `"instruction"` | +| `source_id` | rule `id` from definition | instruction `id` from definition | +| `source_version` | ActivityDefinition version | ActivityDefinition version | +| `triggering_event_id` | event UUID | event UUID | +| `condition_matched` | expression string | expression string (pre-filter) | +| `prompt_hash` | — | SHA-256 of rendered prompt | +| `model` | — | model ID used | +| `output_validated` | — | `true` / `false` | +| `review_advisory` | — | `true` / `false`; no gate applied | + +The audit trail is written to the `task_spawn_log` table in activity-core's database +and referenced from the task record in issue-core. + +The rendered prompt and provider response are deliberately not persisted. The +prompt hash proves equality when an authorized operator can reconstruct the +same input, but the audit contract does not promise reconstruction after source +definitions or upstream event retention have changed. `activity_runs` retains +the definition version and bounded context snapshot; reports may retain +allowlisted route/usage metadata. Prompts, messages, tool output, credential +fields, and provider blobs are excluded from run, progress, and public API +evidence. + +### Testing strategy + +**Rules**: every rule can and should be unit-tested with fixture event payloads. +A test helper `evaluate_rule(condition_str, event_fixture)` returns `bool` and +raises on syntax errors. Tests live alongside ActivityDefinition files: +`activity-definitions/{slug}.test.json` — a list of `{event, expected_rules_fired}` +fixtures. + +**Instructions**: instructions cannot be deterministically unit-tested. Instead: +- Sample evaluations are collected: given a fixture event, record the LLM response. +- Samples are committed to `activity-definitions/{slug}.samples/` for human review. +- Output schema validation is unit-tested independently of the LLM call. +- Prompt injection resistance is tested by including injection strings in fixture + event payloads and asserting they do not appear in the rendered prompt. + +### rules-core module boundary + +The rule evaluator and instruction executor live in `src/activity_core/rules/`. +Within this module: + +- **No imports from** `temporalio`, `sqlalchemy`, `fastapi`, or any activity-core + application code. +- Public surface: `evaluate_condition(expr: str, event: EventEnvelope, context: dict) -> bool` + and `execute_instruction(instr: InstructionDef, event: EventEnvelope, context: dict) -> List[TaskSpec]`. +- The module is independently importable and testable without starting the Temporal + worker or Postgres. + +This boundary makes future extraction to `rules-core` a packaging exercise, not a refactor. + +## Consequences + +- The `ActivityDefinition` Pydantic model gains `rules: List[RuleDef]` and + `instructions: List[InstructionDef]` fields. The current implicit "always create + tasks" behaviour is replaced by explicit rule blocks. +- A new `RuleEvaluator` class (AST walker) is added to `src/activity_core/rules/`. +- A new `InstructionExecutor` class handles prompt rendering, LLM call, output + validation, and review-required audit metadata. Pending review queue routing + remains a future issue-core integration. +- Integration tests for rule evaluation use fixture JSON; no running Temporal required. +- The `task_spawn_log` table is added to the Postgres schema (new Alembic migration). +- ActivityDefinition files that omit both `rules` and `instructions` are valid + (they fire with no output) — this supports future placeholder definitions. + +## Alternatives Considered + +**OPA / Rego for rule conditions**: powerful, well-established policy language, +supports complex logic. Rejected — Rego's learning curve is high for non-specialists; +agents rarely produce correct Rego without fine-tuning; it adds a runtime dependency. +The simple AST-walker DSL covers the realistic condition complexity for this org. + +**Rules as Python lambdas**: maximum expressiveness. Rejected — arbitrary code +execution in a rule condition is a serious security surface, especially in an +org-wide event loop. Code deployment required for any rule change; agents cannot +write rules without code write access. + +**LLM for all conditions (no Rule/Instruction split)**: simpler model, more +flexible. Rejected — non-deterministic for cases that are deterministic; expensive +for high-frequency events like cron ticks; impossible to unit-test; audit trail +for deterministic rules becomes murky. + +**Instructions only, no Rules**: allows arbitrary LLM judgement for everything. +Rejected — LLM cost for every event, latency, and non-determinism are unacceptable +for high-frequency maintenance automations. Many cases (SBOM staleness check, +tag-based routing) are fully deterministic and should stay that way. + +## Related + +- ACT-ADR-001 — Event Bridge Architecture +- ACT-ADR-002 — Definition format (where rule/instruction blocks live) +- CUST-TFE-SCOPE-2026-000001 — task-flow-engine extraction (analogue pattern) +- `src/activity_core/rules/` — implementation home diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-004.md b/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-004.md new file mode 100644 index 0000000..13d1d51 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-004.md @@ -0,0 +1,160 @@ +--- +id: ACT-ADR-004 +type: architecture-decision-record +title: "The Producer Trust Boundary — Guardrails and Error-Correction for Untrusted Output" +status: accepted +owner: activity-core +revision: "accepted-2" +last_reviewed: "2026-06-26" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-06-26" +scope: cross-repo +affects: + - activity-core + - rules-core (future extraction) +tags: ["architecture", "llm", "safety", "validation", "guardrails", "trust-boundary", "resilience"] +--- + +# ACT-ADR-004: The Producer Trust Boundary + +## Status + +Accepted. + +## Context + +On 2026-06-26 the scheduled daily WSJF triage instruction fired on time, called +llm-connect successfully, and produced a long ranked recommendation list — but +the JSON broke at char 5268 (~rank 8–9 of ~16), failing schema validation. Because +the report was validated and consumed as a single monolithic JSON document, one +malformed delimiter discarded the **entire** run, including the 7 perfectly good +recommendations the model had already emitted. The scheduling and runtime layers +were healthy; the failure was entirely at the seam where free-form model output +meets a strict consumer. + +This is not a one-off bug, it is a recurring class. activity-core has a **trust +boundary** wherever generative or human-authored output meets strict deterministic +consumers: the JSON Schema validator, the task emitter, and any classic compute +pipeline downstream. The producers on the other side of that boundary — **LLMs, +agents, and humans** — are all *untrusted producers*. Their output may be: + +- **erroneous** — hallucination, truncation at a token limit, drift, type slips, + typos, a missing delimiter; or +- **malicious** — prompt injection, crafted payloads, or oversized / deeply-nested + structures intended to exhaust or confuse the consumer. + +The pre-existing design treated producer output optimistically: parse the whole +document, validate the whole document, and on any failure discard the whole +document (preserving only a bounded diagnostic preview). That gives **zero error +locality** — the blast radius of any single defect is the entire activation. + +## Decision + +Treat the producer→consumer seam as an explicit, adversarial **trust boundary**, +and place guardrails plus error-correction tooling *at that boundary* rather than +letting raw producer output flow into deterministic consumers. + +### Two non-fail-fast postures + +When hard-failing on a problem is undesirable, there are two sound strategies, and +they **compose**: + +- **A) Trust but handle exceptions** (optimistic / reactive). Consume the output + as-is; on exception, catch → repair → retry → or quarantine. Cheap on the happy + path; blast radius depends entirely on how granular the catch is. Best when + failures are rare and locally recoverable. Risk: failures surface late, possibly + after partial side effects. +- **B) Verify and mitigate** (defensive / proactive). Validate, sanitize, clamp, + and normalize the output to a known-good shape *before* it enters the pipeline — + drop bad items, coerce types, bound sizes/depth, allow-list references — so the + consumer only ever sees clean input. Higher upfront cost, smaller blast radius, + no partial side effects. Best when failures are common or consequences are high. + +### Governing principles + +1. **Push verification to the boundary; keep the interior strict.** Apply posture + **B** at the producer→consumer boundary; keep posture **A** for residual + exceptions inside the verified core. Never relax the interior schema to absorb + producer sloppiness. +2. **Make error locality match the unit of work.** One bad recommendation must + cost one recommendation, not the whole report. Structuring the payload so each + item is independently parseable and validatable is the highest-leverage change. +3. **Quarantine, never silently drop.** Invalid units are preserved as bounded, + provenance-tagged artifacts (`index`, `error`, `raw` snippet, `reason`) so they + can be debugged or replayed. Degraded-but-usable is reported distinctly from + total loss. +4. **Both human and agent input get the same rigor.** Guardrails are + producer-agnostic: the same count / length / depth caps and reference + allow-lists apply whether the producer is an LLM, an agent, or a human. + +### What this means concretely in activity-core + +Implemented in `src/activity_core/rules/executor.py`: + +- **Strict-structure-only schema.** The daily-triage output schema is strict on + per-item *structure* (`required [rank, candidate, action, why]`, typed `wsjf`) + and carries `maxItems` as a producer *hint* — never as a hard whole-document + reject, which would reproduce the very blast-radius failure (ACT-ADR-002 governs + the schema format; `schemas/daily-triage-report.json`). +- **Item-granular recovery (posture B).** When whole-document parse + one retry + fail, `_resilient_report` recovers individually-parseable recommendation objects + via a brace/quote-aware scanner (`_extract_object_spans`) that works for both + pretty-printed and NDJSON output, attempts a best-effort `_try_repair` on a + truncated tail, validates each recovered object against the item schema, and + keeps the valid ones. Survivors are emitted with `output_validated=true`, + `partial=true`, and `review_advisory=true` (`review_gate_applied=false`). +- **Producer guardrails (`_partition_items`, applied on both the recovery and the + happy path).** Per recommendation: structural type → schema → structural caps + (`_MAX_DEPTH`, `_MAX_STRING_LEN`) → reference allow-list → count cap (top-N by + `maxItems`). The first failing check quarantines the item with provenance and a + `reason` (`malformed` / `schema` / `guardrail` / `allow_list` / `over_limit`). +- **Reference allow-list.** A recommendation whose `candidate` is not in the set of + known ids is quarantined. The set is sourced from resolved context + (`context["known_candidates"]`, via `_allow_list_from_context`); the check is + inert until a context resolver populates it, so the capability ships now and + activates with a one-line resolver change. + +### Where each posture sits + +| Layer | Posture | Mechanism | +|-------|---------|-----------| +| Schema / contract | B | strict per-item structure; `maxItems` as hint | +| Whole-document parse | A | tolerant parse + single retry | +| Failed parse | B | item-granular recovery + repair + quarantine | +| Per-item screening | B | schema + depth/length caps + allow-list + count cap | +| Emitted report | — | `partial` / `quarantined_*` provenance; never silent | + +## Consequences + +- A single malformed or oversized item no longer discards an entire activation; + the daily-triage run that failed on 2026-06-26 would now deliver its 7 valid + recommendations and quarantine the broken tail. +- Reports gain a `partial` / `quarantined_*` vocabulary; downstream report sinks + and reviewers can distinguish degraded-but-usable from total loss. +- Guardrail thresholds (`_MAX_DEPTH`, `_MAX_STRING_LEN`, `maxItems`, the + allow-list) are policy knobs that will need tuning; they are intentionally + conservative defaults, not a finished calibration. +- **Known retention gap (follow-on):** `LLMConnectClient.complete()` still returns + only `content`, discarding `finish_reason`/`usage`, and the total-loss artifact + caps raw output below realistic break points. Capturing those signals so + failures stay debuggable is tracked as a retention fix, not closed by this ADR. + +## Alternatives considered + +- **Hard-enforce `maxItems` in the validator.** Rejected: a hard reject of an + over-count document reproduces the whole-document blast radius. Mitigation (keep + top-N, quarantine the rest) is preferred. +- **Relax the schema to accept anything.** Rejected: violates principle 1; pushes + malformed data into downstream consumers. +- **Retry-until-valid only (pure posture A).** Rejected as the sole strategy: the + 2026-06-26 failure recurred across both the initial attempt and the retry, so + retry alone does not bound the blast radius. + +## References + +- ACT-ADR-002 — markdown-as-definition format and output schema governance. +- ACT-ADR-003 — Rule vs. Instruction model; the Instruction prompt-injection + surface this boundary complements on the output side. +- `workplans/ACTIVITY-WP-0016-llm-output-robustness-trust-boundary.md` — the + implementing workplan. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-005.md b/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-005.md new file mode 100644 index 0000000..9e46170 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-005.md @@ -0,0 +1,207 @@ +--- +id: ACT-ADR-005 +type: architecture-decision-record +title: "Ops runs vs development work records — claim queue and plane split" +status: accepted +owner: activity-core +revision: "accepted-2" +last_reviewed: "2026-08-03" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-08-03" +scope: cross-repo +affects: + - activity-core + - rein-aharness + - state-hub + - issue-core + - domain repos (Binky, freedom-intelligence, …) +tags: + - architecture + - activity-core + - ops-run + - work-records + - rein-aharness + - issue-core + - forgejo +supersedes_notes: > + Clarifies ACTIVITY-WP-0022 sink practice and G2 executor gap from + ACTIVITY-WP-0023. Does not reverse WP-0022 (no default external tracker issues). +--- + +# ACT-ADR-005: Ops runs vs development work records + +## Status + +**Accepted** (2026-08-03). + +## Context + +The fleet has two healthy patterns that were forced into one vague “task” idea: + +1. **Development / coordination work** — structured workplans and tasks as + **repo files**, indexed by **State Hub** on **coulombcore** (`fix-consistency`, + UUIDv7 write-back). This matches ADR-001 and + `work-record-types_v0.1.md` and is how humans and coding agents ship product. + +2. **Operations / scheduled automation** — activity-core Temporal schedules + (when / what / where), rein-aharness execution (how), completion events + (idempotence). Instances of “today’s FI brief” or “this prune fire” are + **ephemeral runs**, not multi-day workplan bodies. + +Practice collapsed (2) into broken paths: + +| Path | Failure | +| ---- | ------- | +| Emit `activity_task_spawn` progress only | Append-only; **not claimable**; schedule can “succeed” while nothing runs | +| `ISSUE_SINK_TYPE=rest` → issue-core → Forgejo | Spam + 503s; issue-core INTENT forbids origin of fleet work | +| Host systemd timers dual-clocked with Temporal | Shadow scheduler; weak self-healing | + +issue-core’s **correct** role is a **connector facade** over external trackers +(Forgejo, GitHub, Jira, …) so agents need not know each backend. It is **not** +the origin of work records and **not** the default internal ops queue. +**Gitea is out of scope** for this fleet; the self-hosted forge is **Forgejo**. + +Canon already allows a DB-only exception for “runtime operations data (logs, +metrics, run histories, token events)” (`work-record-types_v0.1.md`). This ADR +names that exception for **ops runs**. + +This is also the precise meaning of INTENT's no-task-lifecycle boundary: +`ops_run` claim/lease/outcome is durable runtime delivery state, not work-item +state. It may not grow assignment, commitments, dependencies, project phases, +or manually managed task status. + +## Decision + +### 1. Two planes for “work,” one vocabulary for people + +| Plane | Artefact | Home | Lifecycle | +| ----- | -------- | ---- | --------- | +| **A — Development / coordination** | Work records (`workplan`, `task`, `intake`, …) | Repo files + State Hub (coulombcore) | File-backed, reviewable, multi-day | +| **B — Recurrence control** | Activity definitions + Temporal schedules | activity-core (railiance) | Git definitions; DB schedule state | +| **C — Ops execution** | **`ops_run`** (runtime claim object) | **activity-core DB + claim API** | open → claimed → succeeded \| failed \| expired | +| **D — External collab** | Tracker issues | **issue-core** optional projection to **Forgejo** (and others) | Never default for internal automation | + +Coding agents orient on **Plane A** via State Hub. +Automation orients on **Planes B+C**. +Humans outside the fleet may see **Plane D** only when deliberately projected. + +### 2. activity-core owns the ops_run claim queue + +When a definition emits a TaskSpec for internal fleet work: + +1. Write **`ops_run`** row (durable, claimable) with idempotency key + (e.g. `fi-daily:2026-08-04` for once-per-day briefs). +2. Optionally dual-write `activity_task_spawn` progress for existing observers. +3. Do **not** open a Forgejo issue. +4. Do **not** create a workplan task file for that day’s fire. + +Claim API (implemented in ACTIVITY-WP-0026 and hardened in +ACTIVITY-WP-0036): + +- `POST /ops-runs/claim` — lease next open run matching labels / worker id +- `POST /ops-runs/{id}/complete` — succeeded + completion metadata +- `POST /ops-runs/{id}/fail` — failed + retry policy +- `GET /ops-runs?state=open` — operator visibility + +Worker mutation credentials are bound to one configured queue `worker_id`; +the request body cannot assert a different claim owner. Heartbeat, completion, +and failure lock the row and require its lease deadline to remain strictly in +the future. Operator/SSO credentials provide visibility and explicit +administration, but do not act as a normal worker identity. + +activity-core remains **when / what / where** only: it does **not** run +domain LLM sessions or hold tenant git credentials. + +### 3. rein-aharness owns how (approach selection + execute) + +- Continuous **claim loop** (service / Deployment), not dual wall-clock timers + as source of truth. +- Host systemd timers become **break-glass** after cutover. +- **Approach selection** at claim time (minimal table v1): + + | Labels / definition family | Approach | + | -------------------------- | -------- | + | mail / mail-intake | deterministic adapter (+ optional triage) | + | research-brief / fi-daily | structured llm-connect (`fi-research-brief`) | + | binky rhythm | structured llm-connect (`brief-daily`) | + | agent-session | persona + tool profile session | + | unknown | refuse; do not invent | + +- Success posts **domain completion event** (e.g. `fi_daily_brief`) so + resolvers set `due=false`, and closes the `ops_run`. + +### 4. State Hub remains the work-record read model + +- Stays on **coulombcore**; railiance peers via edge relay / ops-bridge. +- May **project** open/failed ops runs for fleet ops UI (read-only), but is + **not** the claim authority. +- Development workplans/tasks continue file + `fix-consistency` only. + +### 5. issue-core is Forgejo (and multi-tracker) facade only + +- **Forgejo** is the self-hosted forge; do not plan for **Gitea** as a product. +- Optional **project / link** of an existing work-record UUID to an external + issue when collaboration needs it. +- Authenticated `POST /issues/` means “create/link **external** tracker work,” + never “spawn fleet automation.” +- Internal automation **must not** use issue-core as the ops claim queue unless + a future design adds an **internal-only** backend with projection **hard-off** + for automation labels — out of scope unless a later ADR says otherwise. + +### 6. Promotion path ops → dev + +When an ops run discovers multi-day product work (e.g. “collect Kimi K3 under +new quota”), the executor or human **promotes** an `intake` / workplan in the +domain repo (Plane A). Ops runs never become permanent fake workplans. + +## Consequences + +### Positive + +- Self-healing recurrence: schedule → claimable row → claim → complete/fail. +- Aligns issue-core INTENT with practice; stops Forgejo spam path. +- Keeps activity-core thin; keeps rein-aharness as sole session/credential shell. +- Preserves the good dev loop (files + State Hub). + +### Negative / cost + +- New schema + API + migrator in activity-core. +- rein-aharness must leave issue-core-only poll as primary for ops. +- Temporary dual-write and dual timers until cutover proven. + +### Non-goals + +- Replacing workplans for development. +- Running domain briefs inside activity-core workers. +- Global `ISSUE_SINK_TYPE=rest` to Forgejo. +- Gitea support or migration paths. + +## Topology + +```text +coulombcore: State Hub + work-record registry +railiance: activity-core (Temporal + ops_run queue) + rein-aharness (claim loop + approach + llm-connect) + domain checkouts +forgejo: optional external collab via issue-core projection only +``` + +## Implementation stack + +| Workplan | Owner | Role | +| -------- | ----- | ---- | +| ACTIVITY-WP-0026 | activity-core | ops_run schema, claim API, emit path, dual-write | +| REIN-A-0002 | rein-aharness | claim loop, approach table, FI/Binky cutover | +| ISSUE-WP-0006 | issue-core | Forgejo-only language; projection boundary; no ops queue | +| STATE-WP-0078 | state-hub | Read projection of ops_run for ops UI (optional consume) | + +## References + +- `docs/recurring-automations-playbook.md` +- `docs/task-emission-consumer-contract.md` +- `docs/issue-core-emission-boundary.md` +- ACTIVITY-WP-0022, ACTIVITY-WP-0023 (G2) +- `the-custodian/canon/standards/work-record-types_v0.1.md` +- `issue-core/INTENT.md` (work-record boundary) +- `state-hub/docs/cluster-operating-model.md` (coulombcore primary) diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-006.md b/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-006.md new file mode 100644 index 0000000..0f5c8af --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-006.md @@ -0,0 +1,142 @@ +--- +id: ACT-ADR-006 +type: architecture-decision-record +title: "Profile-driven execution selection over the ops_run pull queue" +status: accepted +owner: activity-core +revision: "accepted-1" +last_reviewed: "2026-08-21" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-08-21" +scope: cross-repo +affects: + - activity-core + - glas-harness + - rein-aharness + - sand-boxer +tags: + - architecture + - activity-core + - ops-run + - glas-harness + - execution +supersedes_notes: > + Extends ACT-ADR-005. Does not change the ops_run claim/lease plane; it + changes what a queued run says about how it should execute. +--- + +# ACT-ADR-006: Profile-driven execution selection over the ops_run pull queue + +## Status + +**Accepted** (2026-08-21). Hub decision `147beec6-7fe7-4837-8e3a-4264a240379d` +("Glas contract 1.0 makes execution constellation selection explicit"). + +## Context + +ACT-ADR-005 gave internal scheduled automation a claimable `ops_run` plane. +What an ops_run said about *how* to execute was `approach_hint` — free text, +interpreted by the claiming executor at claim time. + +That binding is too late. Live failure, railiance01 2026-08-17: + +``` +state=failed attempt=1 claim_owner=rein-aharness@railiance01 +title=Run SBOM rescan for binect-js +failure=no approach matched labels/definition; + labels=['sbom','security','automated'] +``` + +The run was claimed and leased, *then* died because nothing could execute it. +A claim was consumed to discover the request was unroutable. + +`glas-harness` GLAS-WP-0004 closed contract 1.0 and proved it by running one +bounded task through two different reins (`rein-aharness`, `rein-openweights`) +via an explicit `harness_profile_ref`, with real commits and verified sandbox +destruction. Meanwhile every activity-core claim in the week to 2026-08-21 was +`rein-aharness@railiance01` — a coupling that is now avoidable rather than +inherent. + +Two shapes were considered: keep the pull queue and carry the profile as +payload, or have activity-core call the Glas gateway directly. glas-harness, +which owns the contract, advised the first for the initial migration. + +## Decision + +### 1. The queue stays; the payload changes + +activity-core continues to insert `ops_run(open)` and executors continue to +claim under lease. Scheduling topology does not change. What changes is that a +queued run carries an exact **versioned `harness_profile_ref`** plus the +correlation / assignment / role / duty / goal / resource-envelope references. +The claiming executor passes that request into Glas, which resolves or refuses +it **before sandbox creation**. + +This changes the execution contract without simultaneously changing the +scheduling topology — one variable at a time. A direct activity-core → Glas +call may be evaluated later, independently, if pull delivery stops meeting +operational needs. + +### 2. `harness_profile_ref` is authoritative; `approach_hint` is legacy + +The two coexist with **distinct semantics**, not as fallbacks for one another: + +- `harness_profile_ref` is the authoritative execution-constellation selector. +- `approach_hint` remains only a legacy activity/definition-matching hint while + producers and consumers migrate. + +`approach_hint` must **not** override, synthesize, or fall back from an absent +or invalid `harness_profile_ref` on governed execution. A missing profile is an +error, never an invitation to guess from a hint. Once inventory shows no caller +depends on `approach_hint` for runtime selection, that use is deprecated. + +### 3. activity-core does not mirror the profile catalog + +The catalog in glas-harness is authoritative. activity-core must not maintain +an independent list, which would drift and produce a second, disagreeing +opinion about what is executable. + +Consequence, accepted knowingly: Glas exposes deterministic validation through +its package and CLI but **no network validation service** today, so +activity-core cannot remotely validate a profile ref at emission time. We +therefore validate only what is local and structural — that a ref is present +and well-formed for governed execution — and rely on the **execution-side Glas +resolver as the mandatory fail-closed check**. + +This is weaker than refusing at emission, and deliberately so: an unreachable +or absent validation service must never downgrade into "emit anything". If +emit-time remote validation is later required, it needs a separately scoped +Glas API and its own decision — not a mirrored catalogue here. + +Even unvalidated at emit, this is strictly better than the 2026-08-17 failure: +refusal happens deterministically before sandbox creation rather than after a +claim and lease were consumed. + +### 4. activity-core does not become an executor + +We emit an authorized, profile-named request and record normalized evidence. +We do not select reins, provision sandboxes, acquire credentials, or run any +inner agentic loop. Glas owns profile resolution and the outer loop; the rein +owns its inner loop; `sand-boxer` owns isolation. This is SCOPE drift risk #1 +("convenience execution") and this ADR does not relax it. + +The attribution references we carry (`assignment_ref`, `role_ref`, `duty_ref`, +`goal_refs`, `resource_envelope_refs`) are **passed through**, not authored +here. Their vocabulary belongs to `info-tech-canon`; activity-core must not +invent org roles (ACTIVITY-WP-0029 responsibility map). + +ACT-ADR-007's bounded-operation exception does not alter this decision. A +code-registered fixed maintenance operation is not a Glas/rein execution +constellation, and the exception cannot be used to run an agent loop or execute +an `ops_run` inside activity-core. + +## Consequences + +- `ops_runs` grows `harness_profile_ref` and the attribution refs; the emission + path, queue projection, and run artefacts carry them. +- Definitions declare a profile; rules pass it through. +- Unroutable requests fail before sandbox creation instead of at claim time. +- activity-core is no longer coupled to one rein in practice. +- Emit-time validation remains a known gap, owned by a future Glas API. +- Implementation: **ACTIVITY-WP-0032**. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-007.md b/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-007.md new file mode 100644 index 0000000..93a45f3 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ACT-ADR-007.md @@ -0,0 +1,128 @@ +--- +id: ACT-ADR-007 +type: architecture-decision-record +title: "Code-registered bounded operations are the only local mutation exception" +status: accepted +state_hub_decision_id: "2b1f0c01-1d6f-4fdc-b3e7-537ce0a0a1f8" +owner: activity-core +revision: "accepted-1" +last_reviewed: "2026-08-23" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-08-23" +scope: repo +affects: + - activity-core + - sbom-nexus + - railiance-platform +tags: + - architecture + - activity-core + - bounded-operation + - execution-boundary +--- + +# ACT-ADR-007: Code-registered Bounded Operations + +## Status + +**Accepted** (2026-08-23) for ACTIVITY-WP-0035. + +## Context + +The Event Bridge principle says activity-core answers when, what, and where and +does not execute domain work. Production nevertheless contains three useful +operations whose complete outcome is a small scheduled maintenance action: + +- ingest at most three Repo Manager-selected immutable sources into SBOM Nexus; +- prune Forgejo package versions under live-image protection; and +- invoke the fixed CNPG Option A backup tool for an explicit target list. + +Historically the latter two ran through a generic `shell` context resolver. +That made a mutating subprocess look like a read and left extension policy to +convention. Removing the operations would recreate bespoke cron; accepting +arbitrary shell would make activity-core a general executor. + +## Decision + +### 1. Bounded operations are a narrow implementation exception + +The governing when/what/where responsibility does not gain a general “how.” A +bounded operation is allowed only when the operation itself is the declared +automation outcome and all admission requirements below are code-reviewable. + +The initial allowlist is exactly: + +1. `sbom_nexus_ingest` +2. `forgejo_package_prune` +3. `cnpg_option_a_backup` + +Adding an operation requires updating the code-owned registry, this ADR (or a +successor), tests, credential route, and evidence contract. A string in a +definition cannot register an operation. + +### 2. Admission is fail closed during file sync + +Each registry entry declares: + +- source type and query; +- whether mutation intent is `apply` or `dry_run` and that it is explicit; +- fixed or maximum target bounds; +- canonical implementation and allowed configuration; +- idempotency and Temporal retry semantics; +- maximum execution timeout; +- credential owner/route; and +- mandatory non-secret evidence mode. + +Unknown `shell` queries are refused unless separately registered as read-only. +Known operation queries with missing, malformed, or over-limit safety fields +are refused before database projection or Temporal schedule reconciliation. + +### 3. Context resolution is read-only + +The workflow first resolves and freezes context. Mutations then run in an +explicit bounded-operation stage and merge only normalized outcomes into the +snapshot before evidence and rule/instruction evaluation. + +SBOM selection remains a read in the context phase; its fixed selection is the +input to the operation stage. Package prune and backup have no discovery read +inside activity-core and bind a pending marker until their operation completes. + +### 4. Retry behavior is operation-specific + +- SBOM ingest uses stable per-run/per-repository idempotency keys and heartbeat + checkpoints, so Activity retries resume the frozen batch. +- Forgejo prune and CNPG backup have no activity-core-verifiable remote + idempotency receipt. Their operation activity therefore has one Temporal + attempt; a failure remains visible for operator reconciliation rather than + risking an automatic second mutation. + +This does not preclude future safe retries after the platform tools expose a +durable operation receipt. + +### 5. Evidence is mandatory and bounded + +Every operation must produce an allowlisted summary through a configured +report/evidence sink. Raw subprocess output, tokens, provider payloads, archive +URLs, kubeconfigs, and credential material are not evidence. + +## Rejected alternatives + +- **Keep mutating shell resolvers.** Rejected because resolution should be a + replayable read and the generic dispatcher hides mutation admission. +- **Generic command activity.** Rejected because command text/path from a + definition is remote code execution by configuration. +- **Move every operation to a rein.** Rejected for these fixed platform + operations; it adds an agent execution constellation without judgement or + repository work. Operations that exceed this ADR's bounds do belong there. +- **Remove all local operations.** Rejected because it recreates scattered cron + and loses Temporal/evidence guarantees for established maintenance. + +## Consequences + +- Definition parsing gains a central operation-policy validator. +- The workflow gains an explicit operation stage. +- The generic `shell` resolver becomes read-only. +- Existing definitions migrate without widening targets or permissions. +- The registry is intentionally small and architectural review is required to + expand it. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-001.md b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-001.md new file mode 100644 index 0000000..6e0083b --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-001.md @@ -0,0 +1,247 @@ +--- +id: CUST-ADR-001 +type: architecture-decision-record +title: "Workplans and Work Items Are Repository Artefacts" +status: accepted +owner: the-custodian +revision: "accepted-2" +last_reviewed: "2026-08-31" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-02-28" +tags: ["architecture", "state-hub", "workplans", "source-of-truth", "rebuild-principle", "forgejo", "projection"] +--- + +# ADR-001: Workplans and Work Items Are Repository Artefacts + +## Status + +Accepted 2026-02-28. + +Amended 2026-08-31 to distinguish file-backed work records from hub-native +records, identify the Forge default branch as the central projection baseline, +and align identity, lifecycle, reconciliation, and closure with ADR-007, +ADR-010, ADR-011, ADR-012, and the work-record standards. The central decision +is unchanged. + +## Context + +During early State Hub development, workstreams and tasks were created directly +in PostgreSQL through bootstrap APIs. This made a database the origin of durable +work rather than a projection of repository-owned artefacts. The pattern was +convenient, but it made work difficult to audit, review, recover, and carry +between generations of tooling. + +The original decision correctly moved durable workplans and their work items +into repositories. It overstated the consequence, however, by saying that the +entire Hub database and every item that matters for coordination must be +reconstructible from repository files. Later decisions established two +different persistence classes: + +- **file-backed records**, whose durable meaning originates in a repository; +- **hub-native records**, such as append-only progress and runtime facts, whose + durable meaning originates in the Hub. + +Later decisions also established that a central projection reads the pushed +default branch in Forgejo. An arbitrary workstation checkout is a workspace, +not the shared baseline. Unpushed local work may be represented only as an +explicit preliminary overlay. + +## Decision + +### 1. File-backed work originates in the owning repository + +Workplans and their file-backed work items **MUST originate as repository +artefacts in the repository that owns the work**. The Hub indexes and projects +those artefacts but is not their origin. + +This includes the workplan, embedded tasks, declared dependencies, residual +handoffs, and durable governed decision artefacts when those are represented as +files. No authoritative field of a file-backed record may exist solely in a Hub +database. + +The owning repository is identified by repository identity, not merely by a +domain or topic. Domains and topics classify work; they do not own its source. + +### 2. Authority is explicit per persistence class + +Every record contract must declare whether the record is file-backed or +hub-native. The same record must not be writable as authoritative in both +places. + +- For a **file-backed record**, the repository artefact is authoritative and + the Hub row is derived state. +- For a **hub-native record**, the Hub is authoritative. Progress events, + messages, token events, run history, and similar runtime facts are not made + fictional repository records merely to satisfy a rebuild slogan. + +The word `decision` is used by more than one subsystem. Architecture decisions +and other governed decision artefacts remain files. A runtime decision event may +be hub-native only where its schema says so explicitly; it does not replace the +governed artefact. + +This replaces the original blanket rejection of a hybrid architecture. The +architecture is hybrid **by declared record class**, never ambiguous within one +record. + +### 3. Forge is the central projection baseline + +For the shared central view, the source is the pushed default-branch state held +by Forgejo, as decided by ADR-012. Every projected record must be attributable +to its source repository, path, and commit. + +A working copy remains the authoring workspace. Unpushed work may appear in the +Hub only as an attributed preliminary overlay. It must not silently replace or +be presented as the Forge-derived baseline. When its commit reaches Forge, the +baseline absorbs it and the overlay retires. + +### 4. Mutations of file-backed state are file-first + +Creating or changing a file-backed work record means changing its repository +artefact first. A convenience tool or API is conformant only when it performs a +governed repository mutation and leaves a reviewable file and commit. Writing +only the projected database row is not a durable update. + +Direct Hub APIs remain valid for hub-native records. They may also provide +diagnostics or propose repository patches, but a successful database PATCH is +not evidence that a file-backed status changed. + +### 5. The rebuild guarantee applies to the projection, not the whole database + +Given a repository and a specific Forge commit, the system must be able to +reconcile that repository's file-derived projection so that it is equivalent to +the records derived from that commit. Reconciliation must be idempotent, +verifiable, and scoped per repository; a fleet operation is iteration over the +same per-repository operation. + +Reconciliation retires file-derived records that no longer derive. It does not +delete hub-native history attached to them. It must refuse and report a +repository whose records exist only in the projection until those records have +an explicit disposition. A Forge rebuild does not reconstruct preliminary +overlays and must disclose their retirement before proceeding. + +Therefore the **file-derived projection is disposable**. The database as a +whole is not disposable when it also contains hub-native facts. + +### 6. Identity and lifecycle are delegated contracts + +This ADR does not define a second identity or lifecycle schema. + +- Workplan and task identity follow ADR-007 as amended by ADR-011: global + identity is namespace-aware, and derivable Hub identifiers are governed by + that contract. Existing historical identifiers are grandfathered according + to its migration rules. +- Work-record kinds, locations, lifecycle values, and residual handling follow + `canon/standards/work-record-types_v0.1.md` and + `canon/standards/workplan-terminology-fleet_v0.1.md`. + +New normative text uses `workplan` and `work record`. `workstream` remains only +as a metered compatibility term for legacy database and API surfaces. + +### 7. Reconciliation belongs at the repository boundary + +Repo Manager owns discovery, parsing, identity checks, and reconciliation for +file-backed records. State Hub owns the projection and hub-native records. The +implementation may distribute fetch and parse work, but it must preserve that +authority boundary. + +Legacy `create_workstream`, `create_task`, workstation-driven +`sync-workplans`, and similarly named commands are not normative interfaces. +They are conformant only if their current implementation satisfies decisions +3 and 4; otherwise they are transitional or retired. + +## Workplan closure + +A workplan cannot be marked `finished` merely by changing its Hub row. Before +closure, the responsible worker must: + +1. review the source file and the projected view for unfinished tasks; +2. record completed and cancelled outcomes in the source task blocks using the + canonical task lifecycle; +3. turn every actionable carry-forward item into a live work record with + `origin: residual` and an `origin_ref`, or into another explicitly linked + workplan; +4. set the source workplan to `finished`, update it, and commit the result; +5. publish the commit to Forge for the shared baseline and verify that the + projection reconciles to that commit. + +A local reconciliation before push may expose preliminary state, but it does +not complete step 5. + +Automated stale-task cleanup must report file/projection drift. It may not +silently cancel a file-backed task only in the database. An automated repair may +change such a task only through the same governed repository mutation and +commit path as any other file-backed change. Cleanup of hub-native records is +governed by their own retention contract. + +## Consequences + +### Positive + +- Durable work remains inspectable, reviewable, and recoverable through Git. +- The central view has an exact repository and commit provenance instead of + reflecting whichever workstation synced most recently. +- Reconciliation can be exercised per repository without destroying progress + history or other hub-native evidence. +- Multiple contributors share a published baseline while retaining an honest + representation of preliminary work. +- Service ownership is clearer: repositories own file-backed truth; the Hub + owns runtime facts and projection services. + +### Negative + +- File-backed changes require a repository mutation and normally a push before + they become shared baseline state. +- Forge availability affects projection freshness, although it does not prevent + local authoring. +- Preliminary, stale, and retired projection states must be visible in APIs and + user interfaces. +- Git merge conflicts become the explicit conflict mechanism for simultaneous + edits to the same authoritative artefact. +- Tools that previously corrected only database state must be changed to emit a + diagnostic or perform a governed repository update. + +## Migration + +No bulk rename, UUID rewrite, or historical file rewrite is required by this +amendment. Existing identifiers and legacy terminology retain the grandfathering +rules of ADR-007, ADR-011, and the terminology standard. + +Implementations must audit and retire DB-first creation, closure, and stale-task +cleanup paths. Existing file-backed projection rows without a Forge source +commit are migration state: they must be matched to a repository artefact or +explicitly dispositioned before reset. Forge-derived reconciliation and +preliminary overlays are implemented under ADR-012 rather than duplicated here. + +The `accepted-1` publication remains the immutable historical revision. This +document is `accepted-2`. + +## Alternatives considered + +**Database-first with export.** Rejected. Export can be skipped, making the +reviewable artefact secondary and stale. + +**Files only, with no projection database.** Rejected. Cross-repository queries, +runtime views, and append-only operational facts need indexed services. + +**Treat every Hub record as reconstructible from Git.** Rejected. This either +loses runtime truth during rebuild or creates artificial files whose only +purpose is mirroring a database. + +**Let working copies push authoritative projection rows.** Rejected by ADR-012. +It makes the baseline depend on the last workstation to reconcile and cannot be +audited against a shared commit. + +## Related + +- ADR-003 — materialized and derived state +- ADR-005 — cross-repository work ownership +- ADR-007 — workplan identity and repository worker topology +- ADR-010 — Hub authority and local cache model +- ADR-011 — namespace-aware federation and reconciliation limits +- ADR-012 — Forge projection source and preliminary overlay +- `canon/standards/work-record-types_v0.1.md` — work-record kinds, lifecycle, + residuals, and reconciliation +- `canon/standards/workplan-terminology-fleet_v0.1.md` — canonical terminology +- `canon/values/foundational_values_v0.1.md` — local-first operation, + auditability, and reversibility diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-002.md b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-002.md new file mode 100644 index 0000000..b95e2bd --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-002.md @@ -0,0 +1,126 @@ +--- +id: CUST-ADR-002 +type: architecture-decision-record +title: "Custodian Agent Runtime — v0.1 Bootstrap Design" +status: accepted +owner: the-custodian +revision: "accepted-1" +last_reviewed: "2026-03-12" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-03-12" +tags: ["architecture", "agent-runtime", "llm", "ooda", "bounded-agency"] +--- + +# ADR-002: Custodian Agent Runtime — v0.1 Bootstrap Design + +## Status + +Accepted. + +## Context + +CUST-WP-0001 requires a first working skeleton of the Custodian as an acting +agent: a loop that observes project state, reasons about it, and executes +bounded write operations — without human interaction for each step. + +The dependencies (llm-connect, operational Railiance infra) are now resolved. +This ADR captures the five key architectural decisions for the v0.1 bootstrap. + +## Decisions + +### D1 — Location: `runtime/` inside the-custodian (not a new repo) + +**Decision:** The runtime lives under `the-custodian/runtime/` as a standalone +Python package (`pyproject.toml`, own venv) rather than a new repository. + +**Rationale:** The runtime is tightly coupled to canon (reads constitution, +memory) and the state-hub (its primary coordination layer). A separate repo +adds friction with no v0.1 benefit. The existing `runtime/` scaffold confirms +the original intent. Extraction to its own repo is deferred to when the +runtime has stable boundaries and multiple consumers. + +### D2 — OODA loop: single-pass synchronous CLI + +**Decision:** One `run()` call = one complete Observe → Orient → Decide → Act +cycle. Entry point is a CLI (`agent.py`) invoked manually or by a cron job. + +``` +Observe — HTTP GET to state-hub: state summary or domain summary +Orient — Load constitution + build structured LLM context prompt +Decide — Single LLM call (via llm-connect) returns a JSON action plan +Act — Execute only sanctioned write operations from the plan +``` + +**Rationale:** Async event loops and daemons add operational complexity that +v0.1 doesn't need. A single-pass CLI is testable, debuggable, and can be +scheduled externally. The transition to an event-driven loop is Phase 2. + +### D3 — LLM backend: ClaudeCodeAdapter by default + +**Decision:** The runtime uses `llm_connect.ClaudeCodeAdapter` as its default +LLM backend (shells out to `claude --print`). Provider is configurable via +`--llm` flag to support `gemini`, `openrouter`, or `openai`. + +**Rationale:** `ClaudeCodeAdapter` requires no API key and honours the +Local-First value (V2). All current deployments have Claude Code available. +The llm-connect abstraction means switching providers is a one-line change. + +### D4 — Action constraint: three sanctioned write operations only + +**Decision:** The agent may execute exactly three state-hub write operations +without human approval: +1. `add_progress_event` — append an observation to the event log +2. `update_task_status` — mark a task done/in_progress (reversible) +3. `flag_for_human` — raise an intervention flag (escalation, not action) + +All other operations (create workstream, record decision, resolve decision, +write to canon) require human approval before execution. + +**Rationale:** Constitution §3/§4 require bounded agency. The three operations +are either append-only (progress events), reversible (task status), or +explicitly escalating (flag). They cannot produce irreversible harm. + +### D5 — LLM response format: JSON block in markdown + +**Decision:** The LLM is prompted to return a Markdown response with a +fenced ```json block containing the structured action plan: + +```json +{ + "observations": ["..."], + "progress_events": [ + {"summary": "...", "workstream_id": "...", "event_type": "note"} + ], + "tasks_to_update": [ + {"task_id": "...", "status": "done"} + ], + "tasks_to_flag": [ + {"task_id": "...", "note": "..."} + ] +} +``` + +The surrounding Markdown is preserved as a human-readable reasoning trace +and written to `memory/working/` as a session note. + +**Rationale:** JSON blocks are robust to extraction (delimited), LLMs produce +them reliably with clear instructions, and the surrounding prose gives Bernd +an auditable reasoning trace without requiring a separate reasoning step. + +## Consequences + +- `runtime/` becomes a standalone Python package; `make agent-run DOMAIN=x` + invokes it. +- The runtime has no DB schema changes and no new API endpoints — it is a + pure client of the existing state-hub HTTP API. +- Autonomous actions are limited to append-only writes and escalations. Any + expansion of the action surface requires a new ADR and human approval. +- The v0.1 loop is single-user (Bernd). Multi-agent expansion is Phase 2+. + +## Deferred + +- Async event loop / daemon mode (Phase 2) +- RAG over canon (Phase 1 roadmap item) +- Tool adapters beyond state-hub HTTP (planned in `runtime/tool_adapters/`) +- Deployment on Railiance k3s as a scheduled CronJob diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-003.md b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-003.md new file mode 100644 index 0000000..ad3c46d --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-003.md @@ -0,0 +1,207 @@ +--- +id: CUST-ADR-003 +type: architecture-decision-record +title: "Materialized Derived State with Fingerprint Invalidation for Repo-Sourced Data" +status: accepted +owner: the-custodian +revision: "accepted-2" +last_reviewed: "2026-08-31" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-03-20" +tags: ["architecture", "state-hub", "caching", "read-model", "materialized-view", "derived-state"] +--- + +# ADR-003: Materialized Derived State with Fingerprint Invalidation + +## Status + +Accepted, and **partially superseded by `ADR-012`** (accepted 2026-08-25). +Decision 2's fingerprint composition is invalidated in part; decision 5's rebuild +principle is given a concrete source and a required operation. See the notes on +each. + +## Context + +The Custodian State Hub is a **read model** (CQRS terminology) — its data is +fully derivable from canonical sources that live in repositories and the +filesystem. No state-hub data is authoritative; it is always a derived view +of what the repos contain. + +Several categories of data fit this description: + +| Data | Canonical source | State-hub table | +|---|---|---| +| SBOM dependencies | `uv.lock`, `package-lock.json`, etc. | `sbom_entries` | +| Third-party service declarations | `tpsc.yaml` | `tpsc_entries` | +| Provided capabilities | `SCOPE.md` `capability` blocks | `capability_catalog` | +| DoI compliance tier | 14 criteria across repo files + DB | `doi_cache` | +| Workplan task status | `workplans/*.md` | `tasks` | + +Early implementations either recomputed this data on every request (too slow) +or ingested it once without invalidation (stale data goes undetected). Neither +is acceptable for a system designed to give accurate, fast orientation. + +The `doi_cache` table, introduced in CUST-WP-0024, demonstrated a pattern that +solves both problems. This ADR formalises that pattern and mandates its use +for all repo-sourced derived data. + +## Pattern Name + +**Materialized Derived State with Fingerprint Invalidation.** + +This pattern is known under several names in the literature: + +- **Materialized View** (SQL standard, PostgreSQL) — the stored result of a + query or computation, refreshed on demand when source data changes. +- **Derived Data Store** (Kleppmann, *Designing Data-Intensive Applications*, + Ch. 3 & 11) — a system whose entire dataset can be rebuilt from upstream + sources; it is never the source of truth. +- **Read Model / Projection** (CQRS / Event Sourcing) — a pre-computed view + maintained alongside a write model, rebuilt when relevant events occur. +- **Fingerprint-based / Content-addressed invalidation** — analogous to HTTP + ETags: a cache entry is valid as long as a composite hash/timestamp of its + inputs matches the stored value. + +The State Hub already documents itself as a read model. This ADR extends that +principle to specify *how* the read model stays fresh. + +## Decision + +### 1. All repo-sourced derived data MUST be materialised in the DB + +Data computed from repository files or repo records must be stored in a +dedicated table rather than recomputed per request. Direct computation on +every API call is only permissible for development tooling or when explicitly +forced by the caller. + +### 2. Each materialised table MUST carry a `fingerprint` column + +The fingerprint is a deterministic string encoding all inputs that affect the +computed result. It is compared on each read; if unchanged, the stored result +is returned without recomputation. If changed, the result is recomputed and +the stored value is updated. + +**Fingerprint composition rules:** +- Include the `updated_at` timestamp of every DB record that feeds the + computation (repo record, related domain, goals, snapshots). +- Include the `mtime` (filesystem modification time) of every file that feeds + the computation (`SCOPE.md`, `CLAUDE.md`, lockfiles, `tpsc.yaml`, etc.). + +> **Invalidated in part 2026-08-25 by `ADR-012` decisions 1 and 2.** Filesystem +> `mtime` is not a property of the source. It differs between machines, changes +> on a fresh clone, and says nothing about content — so a fingerprint built from +> it describes one workstation's filesystem rather than the repository. Under +> `ADR-012` the projection derives from the forge, and the commit that produced +> a record is both the correct input and the auditable one. +> +> This was not merely theoretical drift. `git_fingerprint` for `the-custodian` +> held the repository's *initial* commit while `last_state_synced_at` was minutes +> old: the field meant to identify what a projection reflects was wrong by the +> entire history of the repository, and nothing noticed. Replace `mtime` inputs +> with the source commit. +- Join all components with `|` as a pipe-separated string — no hashing needed + since the string is compared by equality, not transmitted to clients. +- If a file is absent, encode `filename:absent` rather than omitting it, so + file creation also triggers invalidation. + +**Reference implementation:** `state-hub/api/doi_engine.py::compute_fingerprint()` + +### 3. Every materialised endpoint MUST support `?force_refresh=true` + +Callers must always be able to bypass the cache and trigger a fresh +computation. This is the escape hatch for debugging, post-ingest verification, +and scheduled background refresh jobs. + +### 4. Writes to source data SHOULD update the repo record's `updated_at` + +Operations that change source data (SBOM ingest, TPSC ingest, capability +ingest) must ensure `managed_repos.updated_at` is refreshed so the +fingerprint detects the change on the next read. Where data lives in a +related table (e.g. `tpsc_snapshots`), the fingerprint must include that +table's `max(snapshot_at)` directly rather than relying on the repo record. + +### 5. The DB is never the source of truth — the rebuild principle holds + +Per ADR-001, the state-hub must be rebuildable from scratch by re-ingesting +all canonical sources. Materialised tables are **caches**, not records of +authority. They may be wiped and repopulated at any time without data loss. + +> **Given concrete form 2026-08-25 by `ADR-012` decision 7.** This principle was +> correct and, until now, never exercised — an untested rebuild path is an +> assumption rather than a capability, and this one was believed for long enough +> that a divergence survived seven weeks behind it. `ADR-012` requires the +> reconstruction to exist as a routine operation, scoped per repository, sourced +> from the forge, and verifiable against it. +> +> The claim "without data loss" also needs its precondition stated: it holds only +> while the rule immediately below does. On 2026-08-25, 111 work records existed +> only in the hub, so a rebuild at that moment would have destroyed them. +> `ADR-012` therefore requires reset to refuse, per repository, when records have +> no counterpart in the forge. +This means: +- No materialised table may be the only copy of any information. +- Schema migrations that wipe a materialised table are safe and expected. +- Background jobs that periodically re-ingest all repos are valid and + encouraged. + +## Consequences + +### Positive + +- **Fast reads in steady state** — after the first computation, subsequent + reads hit the DB with no filesystem or subprocess overhead. +- **Accurate on change** — fingerprint invalidation ensures stale data is + never silently served; the cache refreshes exactly when needed. +- **Debuggable** — `force_refresh=true` and `checked_at` timestamps make it + easy to see when a value was last computed and to trigger a recheck. +- **Consistent with the read model principle** — the pattern makes explicit + what was always implied: state-hub data is derived, not authoritative. + +### Negative / Trade-offs + +- **First-call latency** — cache misses are expensive (filesystem reads, + subprocess calls, HTTP self-calls). Mitigated by pre-warming caches at + startup or after ingest. +- **Fingerprint completeness** — if a new input is added to a computation and + not added to the fingerprint, stale results will be silently returned. The + fingerprint must be kept in sync with the computation. +- **Filesystem dependency** — file mtimes are volatile (e.g. `git checkout` + rewrites mtimes). In practice this means a cache miss after every checkout, + not a correctness problem. + +## Implementation Checklist + +When adding a new category of repo-sourced derived data: + +- [ ] Create a `_cache` or `_snapshots` table with `fingerprint` and + `checked_at` columns. +- [ ] Implement `compute_fingerprint(repo, ...)` in the relevant module. +- [ ] Add `?force_refresh=true` query parameter to the read endpoint. +- [ ] Ensure the ingest script (or write path) touches `managed_repos.updated_at` + or includes a related table's `max(timestamp)` in the fingerprint. +- [ ] Verify the cache can be wiped and repopulated without data loss. +- [ ] Document which inputs are included in the fingerprint in a comment + alongside `compute_fingerprint`. + +## Current Implementations + +| Derived data | Table | Fingerprint inputs | Force-refresh | +|---|---|---|---| +| DoI compliance tier | `doi_cache` | `repo.updated_at`, `max(tpsc_snapshots.snapshot_at)`, `max(repo_goals.updated_at)`, `mtime(SCOPE.md)`, `mtime(CLAUDE.md)`, `mtime(tpsc.yaml)` | `?force_refresh=true` | + +## Planned Applications + +| Derived data | Table (proposed) | Notes | +|---|---|---| +| SBOM summary stats | `sbom_cache` | Fingerprint: `max(sbom_snapshots.snapshot_at)` | +| Capability declarations | `capability_cache` | Fingerprint: `mtime(SCOPE.md)`, `repo.updated_at` | +| Workplan status summary | Already handled by consistency checker | Fingerprint: workplan file mtimes | + +## Related + +- ADR-001: Workplans and Work Items Are Repository Artefacts +- ADR-002: Custodian Agent Runtime Design +- `state-hub/api/doi_engine.py` — reference implementation +- `state-hub/api/models/doi_cache.py` — reference schema +- `state-hub/migrations/versions/k8f9a0b1c2d3_doi_cache.py` — reference migration diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-004.md b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-004.md new file mode 100644 index 0000000..5299dc1 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-004.md @@ -0,0 +1,147 @@ +--- +id: CUST-ADR-004 +type: architecture-decision-record +title: "Connectivity-First Network Posture for Custodian Infrastructure" +status: accepted +owner: the-custodian +revision: "accepted-1" +last_reviewed: "2026-03-26" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-03-26" +tags: ["architecture", "network", "ops-bridge", "connectivity", "isolation", "security-posture"] +--- + +# ADR-004: Connectivity-First Network Posture for Custodian Infrastructure + +## Status + +Accepted. + +## Context + +The Custodian infrastructure spans multiple machines: a primary workstation, a +shared compute node (COULOMBCORE), and Railiance cluster nodes. Agents running +on remote machines need to reach the state-hub API and MCP server, which live +on the workstation. Human operators and agents also need to reach remote +services (k3s API, Gitea, Temporal) from the workstation. + +Two network postures were considered for how these components communicate: + +**Option A — Connectivity-first:** Components are connected by default via +controlled, observable access paths (reverse SSH tunnels managed by ops-bridge). +Isolation is added selectively where there is a specific threat model or +compliance reason to do so. + +**Option B — Isolation-first (zero-trust):** No component trusts any other by +default. Every connection requires mutual authentication, short-lived +credentials, and explicit authorisation at the point of use. Connectivity is +earned, not assumed. + +This decision is architectural policy — it governs how ops-bridge tunnels are +designed, how agent-to-hub communication works, and how new infrastructure +components are onboarded. + +## Decision + +**Connectivity-first, with isolation as a deliberate option.** + +The default posture for Custodian infrastructure is: components that need to +work together are connected. Access paths are explicit, observable, and managed +(via ops-bridge), but they are persistent by default rather than ephemeral. +Isolation is introduced where there is a specific, articulated reason — not as +a blanket policy applied uniformly. + +## Rationale + +### 1. Scale and team size + +The infrastructure is operated by a single human and a bounded set of +automation agents. The overhead of zero-trust (credential rotation, SPIFFE/SPIRE +identity, mTLS everywhere, short-lived tokens per request) is disproportionate +for this scale. It would consume significant operational complexity without +a commensurate security return. + +### 2. Observability over perimeter hardening + +The primary security control at this scale is **observability**: knowing what +connected, when, from where, and what it did. ops-bridge provides this — every +tunnel is named, actor-attributed, health-checked, and audited. A perimeter of +invisible short-lived connections would actually reduce observability. + +### 3. The threat model does not require zero-trust today + +The main threats are: +- A runaway agent consuming resources (mitigated by nproc/memory cgroups) +- A compromised workload reaching state-hub and corrupting state (mitigated by + the read-model design of state-hub — write surface is narrow and sanctioned) +- An external attacker reaching internal services (mitigated by the tunnels + being reverse SSH — no inbound ports exposed) + +Zero-trust would address a different threat model: lateral movement between +hostile tenants, or untrusted code running in the same environment as sensitive +data. That is not the current situation. + +### 4. Degrade-gracefully requires persistent connectivity + +The Custodian's foundational value of **local-first, degrade-gracefully** +requires that agents can orient themselves even when some connections are slow +or partially degraded. Ephemeral connectivity (zero-trust tokens, service mesh +sidecars) introduces additional failure modes that conflict with graceful +degradation. Persistent SSH tunnels with auto-reconnect are more resilient to +intermittent conditions. + +### 5. Isolation remains the right choice in specific cases + +Connectivity-first does not mean no isolation. The following cases call for +explicit isolation and are handled separately: + +- **Tenant separation** (when/if multi-user or multi-org) — each tenant gets + its own network segment +- **Privileged execution** — CI runners and agent actions with write access to + production systems run in ephemeral, isolated environments (per the + Privileged Execution Control standard) +- **Secrets** — credentials are never transmitted over tunnels in plaintext; + age-encrypted at rest, SOPS for config + +## Consequences + +### Immediate + +- ops-bridge tunnels are **persistent** (max_attempts: 0, auto-reconnect) and + are treated as infrastructure, not one-off connections +- Agents on remote machines check tunnel health at session start and restore + dropped tunnels before accessing state-hub (documented in global CLAUDE.md) +- New infrastructure components are onboarded with a named tunnel entry in + `~/.config/bridge/tunnels.yaml` — not ad-hoc SSH commands + +### Deferred + +- If the infrastructure grows to multi-tenant or multi-operator, this decision + should be revisited. At that point, isolation-first becomes proportionate. +- If a security audit identifies a specific lateral movement risk, targeted + isolation (network policy, mTLS for that service) is the response — not a + wholesale posture change. + +## Alternatives Rejected + +### Zero-trust / isolation-first + +Rejected for current scale. The operational overhead (credential lifecycle, +service mesh, mutual TLS) is disproportionate, observability would decrease, +and the threat model does not require it. Noted for re-evaluation at multi- +tenant scale. + +### VPN (WireGuard / Tailscale) + +Considered briefly. VPN would solve the connectivity problem but introduces +a persistent network layer that all traffic traverses, reducing the +explicitness of individual access paths. ops-bridge tunnels are per-service +and per-actor, which gives better observability and blast-radius control. +VPN is not ruled out as a future complement but is not the primary approach. + +### Ad-hoc SSH (no ops-bridge) + +The pre-ops-bridge approach. Rejected because it has no health checks, no +actor attribution, no audit log, and requires manual intervention to restore. +ops-bridge formalises the same SSH tunnel pattern with operational discipline. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-005.md b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-005.md new file mode 100644 index 0000000..004921b --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-005.md @@ -0,0 +1,124 @@ +--- +id: CUST-ADR-005 +type: architecture-decision-record +title: "Cross-Repo Workplans Live in Dedicated Project Repos" +status: accepted +owner: the-custodian +revision: "accepted-1" +last_reviewed: "2026-06-22" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-06-22" +tags: ["architecture", "state-hub", "workplans", "cross-repo", "project-repo", "source-of-truth", "lifecycle"] +--- + +# ADR-005: Cross-Repo Workplans Live in Dedicated Project Repos + +## Status + +Accepted. + +## Context + +ADR-001 established that workplans and work items originate as files in the +repository that owns them, so the State Hub can rebuild its coordination state +from repo-owned files alone. The repo-classification redesign (`CUST-WP-0050`) +takes the next step: it makes the **repo the primary anchor** for a workplan +(`workstreams.repo_id` becomes required) and **derives** the market-domain from +the repo's `.repo-classification.yaml` rather than maintaining a separate +`topic`/`domain` spine. Repos are the most stable, git-managed entities in the +ecosystem; binding to them is the most durable anchor available. + +This raises an unavoidable question: **what anchors a genuinely cross-repo +workplan?** Some efforts coordinate change across many repositories — ecosystem +migrations, the FOS hub bootstrap (`CUST-WP-0025`), or `CUST-WP-0050` itself, +which touches ~70 repos. If every workplan must bind to exactly one repo: + +- binding it to one arbitrary product repo misrepresents the work and pollutes + that repo's history with coordination it does not own; +- leaving it unbound reintroduces the hub-only orphan that ADR-001 forbids; +- modelling it as an array of `repo_id`s breaks the "one stable anchor, clear + ownership, clean lifecycle" property and complicates the rebuild principle. + +## Decision + +**A complex cross-repo workplan gets its own dedicated *project repo*.** + +- The project repo is a real, git-managed repository. It owns the coordination + workplan, its tasks, its decisions, and any cross-cutting artefacts. It is the + required `repo` anchor for that workplan, satisfying the repo-primary-anchor + rule without distorting any single product repo. +- The project repo is classified under the Repo Classification Standard, normally + `category: project`. Its `domain`/tags describe the effort, not any one product. +- **Implementation still happens in the product repos.** Changes land via + per-repo workplans and PRs in the repos being modified. The project repo + *coordinates and references* that work (via dependency edges / links); it does + not own product code. +- **On completion, the project repo is retired to archive — not deleted.** Its + durable results live on in the product repos it modified (the merged changes + are the outcome). The archived project repo remains as an immutable provenance + record of the coordination, consistent with the append-only-memory value. + +The project repo's completion record MUST list the product repos it modified and +link to the merged PRs/commits, so the trail survives archival. + +## Lifecycle + +``` +draft → active → completed → archived +``` + +- **active** — work in progress; workplan `status: active`; repo live in Gitea + and registered in the Hub. +- **completed** — all tasks done; completion record written (modified repos + + links). +- **archived** — repo archived in Gitea and `status: archived` in the Hub. The + workplan moves to `workplans/archived/` per the workplan convention. Results + persist in the product repos; the project repo is read-only history. + +## Naming + +Project repos MUST use the `prj-` prefix. + +Exact naming, required files (`GOAL.md` instead of durable-product +`INTENT.md`), authority boundary, residual handoff, and archive procedure are +defined in: + +`canon/standards/project-repository-flavor_v0.1.md` + +That standard closes the naming open question previously deferred to +`CUST-WP-0050` (D1a). The illustrative `proj-` form mentioned in earlier drafts +is not used for new repositories. + +## Consequences + +- **Pro:** every workplan — including cross-repo ones — has a stable, + git-managed anchor; no hub-only orphans; the rebuild principle (ADR-001) holds. +- **Pro:** the classification standard applies uniformly; project repos are just + repos with `category: project`. +- **Pro:** clean, explicit lifecycle; results are never lost on retirement + because they live in the modified product repos. +- **Con:** proliferation of short-lived repos; requires discipline around the + naming and archival convention. +- **Con:** cross-references between the project repo and the product repos it + modified must be recorded deliberately, or the provenance trail degrades after + archival. +- **Con:** judgement is required on *when* an effort is "complex enough" to merit + a project repo versus a single-repo workplan; small cross-cutting changes + should not spawn a repo. + +## Alternatives Considered + +- **Bind to a "lead" product repo.** Rejected: distorts that repo's history and + creates ambiguous ownership. +- **Keep an optional hub-only topic for cross-repo coordination.** Rejected: + reintroduces the soft, non-git-managed spine that `CUST-WP-0050` removes and + ADR-001 discourages. +- **Multi-anchor workplan (array of repo_ids, no primary).** Rejected: breaks + single-anchor simplicity, ownership clarity, and lifecycle modelling. + +## Related + +- ADR-001 — Workplans and Work Items Are Repository Artefacts +- `CUST-WP-0050` — Repo Classification & State Hub Registration Redesign (D1) +- `canon/standards/repo-classification-standard_v1.0.md` diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-006.md b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-006.md new file mode 100644 index 0000000..b42c7ff --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-006.md @@ -0,0 +1,278 @@ +--- +id: CUST-ADR-006 +type: architecture-decision-record +title: "Canon Federation and Concept Ownership Across InfoTech and Commerce" +status: accepted +owner: the-custodian +revision: "accepted-1" +last_reviewed: "2026-08-17" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-08-16" +accepted: "2026-08-17" +tags: ["architecture", "canon", "concept-ownership", "info-tech-canon", "commerce-canon", "identity", "orthogonality"] +--- + +# ADR-006: Canon Federation and Concept Ownership Across InfoTech and Commerce + +## Status + +Accepted 2026-08-17. All seven ownership questions are resolved (see +Resolutions); content may now move under `CFED-WP-0001`. + +## Context + +The ecosystem currently runs two independent canons in the same market domain +(`infotech`), with **no cross-reference in either direction**: + +- `info-tech-canon` — InfoTechCanon, v0.6.0, status `service-baseline`. Kernel + + 12 models + 3 standards, with declared concept ownership per model, an + orthogonality rule ("standards can import but not redefine each other"), and a + live CLI/JSON/API surface built on `infospace-bench`. +- `identity-canon` — documentation-only research repo, category `research`. Three + `IDENTITY-WP-*` workplans, all `finished`; no active work since the + commercial-identity research pass. ~60 concepts in + `canon/CanonicalGlossary.md`. + +Two problems follow. + +**Concept-ownership collision.** `InfoTechCanonOrganizationModel` (`:55`) +declares ownership of `Actor, Person, Organization, OrganizationalUnit, Team, +Group, Role, Position, Membership, Assignment, Responsibility, Authority, +Accountability`. `InfoTechCanonAccessControlModel` (`:106`) declares `Subject, +Principal, AccessRole, Permission, ...`. identity-canon independently defines +`Actor, Natural Person, Collective Actor, Organization, Group, Role, Membership +Relationship, Authenticated Subject, Authorization Principal`. Two canons claim +the same concepts — exactly what InfoTechCanon's orthogonality rule exists to +prevent. + +**An unowned gap.** `InfoTechCanonAccessControlModel` (`:214`, "Boundary with +Identity and Authentication") explicitly pushes identity provisioning, +authentication factors, identity proofing, and account lifecycle out of scope, +and `:41` does the same for generic organization modelling. So `Account, +Identity Record, Identifier, Credential, Claim, Persona, Tenant, Realm, +Synonymity Assertion, Assurance Level` belong to no model at all. + +**Business semantics are accumulating in the wrong places.** Roughly a third of +identity-canon's glossary is not identity but counterparty/commercial modelling +(`Legal Entity, Beneficial Owner, Customer, Vendor, Commercial Commitment, +Payment Mandate, Pipeline Pursuit, Counterparty Assurance Gradient, ...`) — +hence the repo's `government` secondary domain. Independently, +`info-tech-canon/demand/CapabilityProvisionEconomics.md` (status `accepted`, +2026-08-15) records procurement and economics demand arriving in `ITC-CAP` from +consumer `resource-control`, domain `financials`. Two unrelated donors pushing +commercial semantics into technical canon is a domain boundary, not a +coincidence. InfoTechCanon's own Purpose/Demand extension names this +`ScopePressure`. + +## Decision + +**1. Three canons, federated by declared ownership.** + +| Canon | Owns | Repo | +|---|---|---| +| Custodian canon | ecosystem-normative governance: constitution, values, standards, ADRs, charters | `the-custodian/canon/` | +| InfoTechCanon | semantics of information-processing systems | `info-tech-canon` | +| CommerceCanon | counterparty and commercial-relationship semantics | `commerce-canon` | + +Canons import but do not redefine each other's concepts, applying InfoTechCanon's +existing orthogonality rule one level up. + +**2. `identity-canon` is renamed to `commerce-canon`, in place.** Git history, +State Hub registration, `.repo-classification.yaml`, and the finished +`IDENTITY-WP-*` workplans are retained as provenance for the commercial content +that stays. Identity content emigrates; nothing is archived. Structure follows +`info-tech-canon` (`canon.yaml`, `infospace/` with kernel/models/standards, +`assimilation/`, `mappings/`, `profiles/`) per +`InfoTechCanonRepositoryLayoutStandard`. + +**3. Identity becomes an InfoTechCanon model**, at +`infospace/models/identity/InfoTechCanonIdentityModel.md` (`itc-ident`), +importing rather than redefining upstream concepts. This fills the gap +`InfoTechCanonAccessControlModel:214` leaves open. + +**4. Concept ownership is assigned as follows.** + +*Imported by `itc-ident`, owned by `itc-org`:* +`Actor` · `Natural Person` → `Person` · `Artificial Agent` → `Agent` · +`Organization` · `Group` · `Role` · `Membership Relationship` → `Membership` + +*Imported by `itc-ident`, owned by `itc-access`:* +`Authenticated Subject` → `Subject` · `Authorization Principal` → `Principal` + +This follows identity-canon's own design principles: P1 makes `Actor` the +participation root (owned upstream by `itc-org`), and P6 "Keep Authorization +Projections Separate" already treats subject/principal as projections rather +than identity-owned definitions. + +*Owned by `itc-ident` (new):* +`Account` · `Service Account` · `Identity Record` · `Identifier` · +`Scoped Identifier` · `Pseudonymous Identifier` · `Credential` · `Claim` · +`Profile` · `Persona` · `Tenant` · `Realm` · `Synonymity Assertion` · +`Lifecycle State` · the actor-linking relationship taxonomy +(`Relationship`, `Affiliation`, `Following`, `Representation`, `Delegation`, +`Administration`, `Trust`) · convenience terms `User`, `Subscriber` + +*Owned by `commerce-canon`:* +`Legal Entity` · `Legal Person` · `Beneficial Owner` · +`Beneficial Ownership Relationship` · `Beneficial Ownership Exemption` · +`Customer` · `Vendor` · `Commercial Relationship` · `Commercial Commitment` · +`Payment Instrument Reference` · `Payment Mandate` · `Pipeline Pursuit` · +`Commercial Record` · `Counterparty Assurance Gradient` · `Reputation Signal` · +`Performance Evidence` · `Registry Identifier` · +`Proxy Commercial Identifier` · convenience terms `Reputation`, +`Customer Account` + +*Owned by `itc-evid`, the evidence model (see R3, R5, R7):* +`Evidence` · `Evidence Source` · `Adjudication Outcome` + +Identifier subtypes demonstrate the intended pattern: `itc-ident` owns +`Identifier`; `commerce-canon` owns `Registry Identifier` and `Proxy Commercial +Identifier` as specializations of it. + +**5. CommerceCanon grows by demand signal, not speculative authoring.** New +content enters through the mechanism InfoTechCanon already runs — a demand +signal with named consumer evidence (see `demand/`). Plausible future consumers +(`fin-hub`, `target-revenue`, `adaptive-pricing`, `qonto-assistant`) must pull; +the canon does not push. + +**6. The Federated Organization Standard stays in Custodian canon.** +`canon/standards/federated-organization-standard_v1.0.md` is ecosystem-normative +organizational architecture, human-gated — not commercial vocabulary. If it ever +moves, it becomes an InfoTechCanon organization standard, not a CommerceCanon one. + +## Resolutions + +The six collisions listed at draft time, resolved 2026-08-17. Two were settled +by evidence already present in the models rather than by argument. + +**R1 — `Scope`: owned by `itc-ident`.** There is no head-on collision: +`itc-access` owns `ResourceScope` (`:717`, "the boundary within which access +applies"), a narrower concept, not a general `Scope`. `itc-ident` owns the +general concept, keeping it with `Tenant` and `Realm` — `Tenant` is defined as +"an administrative or isolation scope", so separating it from its genus would +split a definition from the concept it depends on. `itc-access` keeps +`ResourceScope` as a refinement. + +If `landscape` or `information-space` later need general scoping, promote +`Scope` to the kernel **on that demand signal**, not pre-emptively. + +**R2 — `Assurance Level`: owned by `itc-ident`, distinct from governance +assurance.** A false collision. `itc-gov` owns `AssuranceCase` (`:1178`, a +structured argument that a claim is justified) and `AssuranceConclusion` +(`:1184`). identity's `Assurance Level` is NIST SP 800-63-4 IAL/AAL/FAL — +graded confidence metadata on credentials, bindings, and federation assertions. +They share an English word and nothing else. Both models carry a disambiguation +note, because the word will keep causing this. + +Design principle P12 ("Distinguish Assurance Dimensions") carries over: IAL, +AAL, and FAL must not be collapsed into a single "trust level" on an account. + +**R3 — `Evidence` and `Evidence Source` are a general pair, owned together, and +not by commerce.** They are not competing definitions of one concept: + +- **Evidence Source** — an addressable information container: a document, file, + or other artifact identifiable by URI. +- **Evidence** — a distinct information item, textual or descriptive, drawn from + a source: a quotation, an extracted value, a specific assertion. + +Both may carry commentary. Which evidence is captured from a source depends on +the interest being served. + +Worked example: an invoice PDF is an Evidence Source; the amount, the issuer, +and the due date are separate Evidence items within it. The common electronic- +invoicing pattern of an XML embedding inside a signed PDF is exactly this +structure — evidence pre-extracted and bound to its source so the extraction is +itself tamper-evident. + +The pair is domain-neutral (it extends to criminal, regulatory, and scientific +evidence). Commerce, identity, and governance all **use** it; none owns it. +Consequently `itc-gov` no longer owns `Evidence`; it imports it. + +**R4 — `Relationship Tuple`: owned by `itc-access`.** Already modelled there +(`:549`, under `PolicyEvaluationEntity` beside `AuthorizationRequest`, +`AuthorizationDecision`, `DecisionReason`, `EvaluationContext`). identity-canon's +own entry agrees: "Relationship tuples are not canonical identity roots. They +project from actors, accounts, memberships, and delegations into authorization +domains." `itc-ident` must not define it. + +**R5 — `Adjudication Outcome`: follows R3, owned with the evidence pair.** Not +`itc-access` `AuthorizationDecision` (a PDP allow/deny, `:907`) and not `itc-gov` +`Decision` (a governance choice point). The concept is general rather than +commercial: an arbitration award, court judgment, or regulatory consent order is +evidence in employment, licensing, or compliance disputes as much as in +commercial ones. Commerce is a consumer, not the owner. + +Structurally it is **Evidence** — the outcome asserted — sourced from an +Evidence Source such as the judgment document. + +The `assurance_tier` dimension splits accordingly: the evidence model owns a +general evidence-strength dimension; `commerce-canon` owns the +`Counterparty Assurance Gradient` as its named four-tier application of it. + +**R6 — `Community` and `Household` extend `itc-org`; `Family` is a separate +concept area.** identity-canon defines "Family Or Household" as one entry. That +conflation is rejected. + +`Community` and `Household` are collective actors and slot under `itc-org`'s +existing `CollectiveActor` (`:363`, beside `Person`, `HumanActor`, +`NonHumanActor`), honouring P4 ("Model Collective Actors Without Collapsing +Them"). + +`Family` does not. Family carries substantial structure — kinship, guardianship, +dependency, care, and legal, biological, and social parenthood — which changes +over time and is subject to interpretation. Modelling it as one more collective +actor is the specific mistake most family-oriented software makes, and it is why +such software generally models families badly. It gets its own concept area. + +Scope discipline applies: the family area is **seeded, not authored**. Record the +concept, the privacy sensitivity already flagged in identity-canon ("may have +legal implications outside the canon's scope"), and the open modelling +questions. Do not build it out inside `CFED-WP-0001`; it grows on demand signal +like any other canon content. + +**R7 — the evidence pair lives in a dedicated model, `itc-evid`.** A new +InfoTechCanon model at `infospace/models/evidence/` owns `Evidence`, +`Evidence Source`, `Adjudication Outcome`, and the general evidence-strength +dimension. + +`itc-gov`, `itc-ident`, and `commerce-canon` import it. Three named consumers +existed before the model did, which is the demand signal the canon requires. + +Rejected alternative: leaving both with `itc-gov` as incumbent owner of +`Evidence`. That is cheaper and preserves the locality of the +Policy-Control-Evidence chain pattern (`:1391`), but it would force identity and +commerce to import "governance" in order to describe an invoice line item — +mis-signalling evidence as a governance sub-topic when it is domain-neutral. + +`itc-gov` retains `AssuranceCase`, `AssuranceConclusion`, `Audit`, and the +Policy-Control-Evidence pattern, now expressed over imported evidence concepts. + +## Consequences + +**Positive.** Every concept gains exactly one owner. The identity gap that +`itc-access` explicitly declines is filled. Commercial semantics get a home +before they accrete further into technical canon. A dormant research repo with +no inbound references becomes a canon with declared consumers. A second canon +tests whether `InfoTechCanonRepositoryLayoutStandard` is a real standard or +merely InfoTechCanon's own shape described back to itself. + +**Negative.** This is a concept-ownership reconciliation, not a file move: the +first ~15 glossary entries must be rewritten as imports. Three canons cost more +coordination than one. The `identity-canon` name disappears from tooling, +bookmarks, and any external reference. + +**Risks.** CommerceCanon could repeat identity-canon's failure mode — dormant, +zero consumers, drifting — if it launches as a scaffold. Mitigated by decision 2 +(it opens holding real, research-backed content) and decision 5 (growth requires +consumer evidence). + +## References + +- ADR-001 — workplans originate as repo files; hub is a read model +- ADR-005 — cross-repo workplans live in dedicated project repos +- `info-tech-canon/infospace/models/organization/InfoTechCanonOrganizationModel.md:55` +- `info-tech-canon/infospace/models/access-control/InfoTechCanonAccessControlModel.md:106`, `:214` +- `info-tech-canon/infospace/models/governance/InfoTechCanonGovernanceModel.md:107` +- `info-tech-canon/demand/CapabilityProvisionEconomics.md` +- `identity-canon/canon/CanonicalGlossary.md`, `canon/DesignPrinciples.md` diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-007.md b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-007.md new file mode 100644 index 0000000..dfb4769 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-007.md @@ -0,0 +1,324 @@ +--- +id: CUST-ADR-007 +type: architecture-decision-record +title: "Workplan Identity Uniqueness, Single Registrar, and Repo Worker Topology" +status: accepted +owner: the-custodian +revision: "accepted-2" +last_reviewed: "2026-08-31" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-08-16" +accepted: "2026-08-17" +tags: ["architecture", "workplans", "identity", "state-hub", "repo-manager", "agents", "registrar"] +--- + +# ADR-007: Workplan Identity Uniqueness, Single Registrar, and Repo Worker Topology + +## Status + +Accepted 2026-08-17. Identifier uniqueness, the registrar model, lifecycle +protection, and worker topology are settled. + +**Remediation of existing collisions (§ Migration) remains an open ruling.** It +is disruptive, touches six repositories, and no active work depends on it — all +five duplicated identifiers are `finished`. + +## Context + +On 2026-08-16, `STATE-WP-0080` was found registered **twice, in two different +databases**, with two different workplan UUIDs and two disjoint sets of task +UUIDs: + +| Registrar | Workplan UUID | Reachable at `127.0.0.1:8000` | +|---|---|---| +| Workstation hub (`make api`, local postgres) | `03f38314` | yes | +| A second instance, over a second database | `bbfce36a` | **404** | + +The second instance is *not* identifiable from the commit author. +`custodian-sync@railiance.local` is a hardcoded default git identity in +`scripts/consistency_check.py:1899` (`GIT_SYNC_USER_NAME` / +`GIT_SYNC_USER_EMAIL`), so every machine running `fix-consistency` commits under +that name. The discriminator is the timezone: sync commits appear under both +`+0000` and `+0200` (the workstation's offset), which is independent evidence of +two machines writing to one repository. + +Both write their IDs into the same git-tracked workplan file, so each sync +overwrites the other's IDs and the file flip-flops on every round trip. The same +commit (`ff909e1`, "renormalize lifecycle state [auto]") also promoted the +workplan `proposed` → `active` without human review. + +A fleet scan of 955 hub workplans (525 with parseable `PREFIX-WP-NNNN` +identifiers) found this is not an isolated incident. Two distinct identity +defects are live: + +**Prefix reuse across repositories:** + +```text +CUST-WP- → state-hub, the-custodian +RAIL-BS-WP- → railiance-bootstrap, railiance-cluster +RAILIANCE-WP- → railiance-apps, railiance-forge, railiance-platform, railiance-telemetry +``` + +`PRJ-WP-` is a latent fourth: `statehub register` derives it from the `prj-` +flavor marker, so every project repository would collide (see `RMGR-WP-0004`). + +**Running-number reuse:** + +```text +CUST-WP-0000 the-custodian ×2 +CUST-WP-0010 the-custodian ×2 +CUST-WP-0045 the-custodian ×2 +RAILIANCE-WP-0015 railiance-platform, railiance-apps +RAILIANCE-WP-0016 railiance-platform ×2, railiance-apps +``` + +`RAILIANCE-WP-0015` is actively cited in operational memory as the cnpg-backup +gate in `railiance-apps`; a second record of the same name in +`railiance-platform` makes that citation ambiguous. + +The `C-26` consistency check already flags prefix nonconformance *within* a repo +against a canonical prefix, but nothing enforces uniqueness *across* repos, and +nothing prevents number reuse. + +## Decision + +**1. A workplan identifier is globally unique.** `PREFIX-WP-NNNN` names exactly +one workplan across the entire fleet, for all time. + +> **Amended 2026-08-17 by `ADR-011` decision 2.** Uniqueness and forward-only +> allocation are **namespace-scoped**, not global; global identity is the pair +> `(namespace, identifier)`, written `PREFIX-WP-NNNN@namespace` when foreign. +> Global sequential allocation would require a central coordinator — the exact +> dependency federation must survive. Everything below holds unchanged **within** +> a namespace, which is where all current work sits. + +- A workplan prefix is owned by exactly one repository. No two repositories may + use the same prefix. +- A running number is never reused within a prefix, including after a workplan is + cancelled, archived, or deleted. Numbers are allocated forward only. +- Prefixes are derived from the *project or repository identity*, never from a + flavor marker or category (`PRJ-WP-` is invalid by construction). + +**2. Hub identifiers stored in repository files must be derivable, not +database-local.** The defect is structural: a database-local key is stored in a +shared artifact, so each database overwrites the other's value on every sync. +It also inverts ADR-001 — a file carrying a hub's private key is the file +holding hub state. + +*Target state (C2).* `state_hub_workstream_id` and `state_hub_task_id` become +**deterministic**: UUIDv5 derived from the workplan identifier. Per `ADR-011` +decision 3 the derivation input is the pair `(namespace, identifier)`, not the +identifier alone — deriving from the identifier alone would make two forks +holding unrelated work under the same number compute the same UUID. Every instance computes the same value independently, writeback +becomes idempotent, and any number of hub instances may coexist without +coordination. The field shape is unchanged, so consumers keep working; only the +provenance of the value changes. + +This has a hard prerequisite: deterministic derivation from a **non-unique** +identifier would manufacture collisions rather than remove them. Two project +repos sharing `PRJ-WP-` would compute the same UUID for different workplans. +Decision 1 must therefore be enforced before derivation ships. + +*Interim state (A).* Until derivation lands, exactly one instance writes hub +identifiers into repository files. Other instances may read, project, and serve, +but must not mint workplan or task UUIDs into git-tracked files. + +> **Corrected 2026-08-17, superseded by `ADR-010` decisions 1–3.** This decision +> originally described workstation hubs as "development read replicas". That was +> wrong on both counts: the workstation instance was not a replica, and it was +> the *larger* of the two, holding 306 more workplans than the primary. The two +> instances were peer databases. `ADR-010` establishes the central hub as +> authoritative and local instances as rebuildable caches, which is what makes +> this interim rule coherent. + +The interim is policy, enforced by discipline, and it has a real cost: +registration requires connectivity to the registrar, so disconnected work cannot +register. That cost is accepted only until C2 removes the need for it, at which +point the number of hub instances becomes an availability choice rather than a +correctness constraint. + +*Rejected.* Collapsing to a single shared database (workstation hubs pointing at +the production database) would also make identifiers consistent by construction, +but it eliminates offline capability — cutting directly against `STATE-WP-0068` +(offline write buffer and edge relay) — and couples all local work to tunnel +availability. + +*Ownership.* Both the interim guard and the derivation belong to `repo-manager` +under decision `747011c6`, which already places file-backed record indexing and +reconciliation there. Building either in `state-hub` would invest in a component +being retired under `STATE-WP-0079`. + +Migration scope for C2: **758 workplan files** across the fleet currently carry +these fields. + +> **Amended 2026-08-21 — derivation scope for C2.** `ADR-011` decision 7 keys +> derivation on `(namespace, identifier)`, which separates *forks*. It does not +> separate collisions inside one namespace, and the ecosystem's posture is `N1` +> — a single implied namespace. A fleet scan on 2026-08-21 found **20 reused +> identifiers across 48 files**, all within that one namespace, so the +> prerequisite above is not satisfied by `ADR-011` alone. +> +> Ruled: **C2 derives only for live records; archived records keep their minted +> identifiers, frozen.** This is what reconciles § Migration option 2 — under +> which historical files keep colliding identifiers — with the uniqueness +> derivation requires. Of the 20, only **five** collide among live files, so the +> remediation surface is 11 files rather than 48. +> +> Two consequences follow, and both are load-bearing: +> +> 1. **Un-archiving a record with a frozen identifier is a collision hazard.** +> A record returning to live status must be checked against the live +> namespace before it is re-derived, and renumbered if it clashes. +> 2. **Derivation is not retroactive.** Existing live records keep their minted +> UUIDs until they are re-derived deliberately; C2 changes provenance for new +> and re-registered records, not the whole corpus at once. +> +> Rejected: treating a *repository* as the namespace. That would make the +> collisions vanish by construction, but it redefines the term `ADR-011` +> decision 1 fixes as "a fleet instance, a client deployment, an autonomous +> domain", and `ADR-011` decision 9 warns specifically against reading N-plane +> movement into claims it does not support. + +**3. Lifecycle status is not automatically promoted.** An automated +normalization pass may report drift; it may not move a workplan from `proposed` +to `active`. `proposed` means awaiting human review, and an automation that +promotes it destroys the meaning of the review gate. + +**4. Repository manipulation is performed by a worker agent in that +repository.** This is the default topology. + +- A worker acting in repo X owns changes to repo X. +- Multiple independent top-level workers inside a single repository are an + **exception**, requiring an explicit reason, not a routine mode of operation. +- Concurrent independent writers are what turned a two-registrar bug into + repeated git divergence. + +**5. Project (`prj-`) repositories may act across their participating +repositories.** When work is governed by a project repo, its tasks may direct +changes across every repository the project names, through the project's work +agent, where that is more efficient than delegating. + +This is a deliberate, scoped exception to decision 4: the project repo already +owns cross-repo sequencing and its `SCOPE.md` names its participants, so its +authority is declared rather than ad hoc. It does not license a worker in an +arbitrary repository to reach into others. + +## Consequences + +**Positive.** Workplan identifiers become citable without qualification. +Hub IDs stop flip-flopping in git. The `proposed` status regains meaning. +Cross-repo authority becomes something a repository declares rather than +something any session assumes. + +**Negative.** Existing collisions must be remediated (see below), which is +disruptive. Workstation sessions lose the ability to register workplans directly +and must route through the registrar or a worker in the owning repo. Prefix +allocation needs a fleet-level registry, which is new machinery. + +**Enforcement.** Prefix ownership, uniqueness, and forward-only numbering are +repository standards, so they belong to Repo Manager under decision `747011c6` +(`RMGR-WP-0004`), not to a hub. Canon defines the rule; Repo Manager checks it. + +## Migration — needs a separate ruling + +Three prefixes and five identifiers are already colliding. Remediation options, +in increasing cost: + +1. **Freeze and forward-fix.** Accept existing collisions as historical, enforce + uniqueness only for new workplans. Cheapest; leaves `RAILIANCE-WP-0015` + permanently ambiguous. +2. **Renumber the live collisions only.** Fix identifiers that are still cited or + active; leave `finished`/`archived` duplicates alone. +3. **Full renaming.** Give `railiance-apps`, `railiance-forge`, + `railiance-platform`, `railiance-telemetry` distinct prefixes, likewise + `railiance-bootstrap`/`railiance-cluster` and `state-hub`'s legacy `CUST-WP-` + files. Touches six repositories and every inbound reference. + +**Ruled 2026-08-17: option 2.** Renumber live collisions; leave +`finished`/`archived` duplicates as historical record. + +### The live renumber list is empty + +Verified against the fleet scan. All five duplicated identifiers are `finished`: + +```text +CUST-WP-0000 the-custodian ×2 finished +CUST-WP-0010 the-custodian ×2 finished +CUST-WP-0045 the-custodian ×2 finished +RAILIANCE-WP-0015 apps, platform finished +RAILIANCE-WP-0016 apps, platform ×2 finished +``` + +No workplan in a `proposed`, `ready`, `active`, `blocked`, or `backlog` state +shares an identifier with another. **Option 2 therefore requires no renumbering +today.** Historical duplicates stay, including the `RAILIANCE-WP-0015` ambiguity +between `railiance-apps` and `railiance-platform`; citations of it must name the +repository. + +### The structural cause is not historical + +Option 2 governs *remediation*. It does not exempt anything from decision 1, +which is accepted canon: one prefix, one repository, forward-only numbering. + +Three shared prefixes are still in use across seven repositories, and each is a +**single number line being allocated from concurrently**: + +```text +RAIL-BS-WP- bootstrap 8, 9 + cluster 7, 10, 11, 12, 13, 14 + +RAILIANCE-WP- platform 5, 8-17 (16 twice — an internal duplicate) + apps 15, 16 <- already collided with platform + forge 2 + telemetry 1 + +CUST-WP- the-custodian 50 plans + state-hub 4 legacy plans (canonical prefix is STATE-WP) +``` + +`RAIL-BS-` and `RAILIANCE-` are actively growing — `RAIL-BS-WP-0014` (`ready`), +`RAILIANCE-WP-0002` (`ready`), `RAILIANCE-WP-0001` (`proposed`), all created +2026-08-11 or later. The `RAILIANCE-WP-0015`/`0016` collisions were not a +historical accident; they are what concurrent allocation from a shared sequence +produces, and it will recur at the next concurrent allocation. + +`CUST-` is dormant on the `state-hub` side — four legacy plans, one in +`backlog` — and needs no split, only a prefix-ownership assertion. + +### Prefix assignments + +`RAIL-BS-WP-` is **retired** (2026-08-17). Neither repository keeps it: +`railiance-cluster` adopts `RCLUSTER-WP-` for active and future plans; +`railiance-bootstrap` adopts `RBS-WP-` for future plans. Finished and archived +files keep `RAIL-BS-WP-` as historical record, consistent with option 2. + +Migrating plans keep their running numbers — the prefix changes, the number does +not. This preserves traceability and cannot violate forward-only allocation, +because neither new prefix has prior history. `railiance-bootstrap` begins at +`RBS-WP-0010`, above its historical maximum, leaving the lower range free should +its finished plans ever be adopted into the new prefix. + +`RAILIANCE-WP-` should follow the same pattern — retired rather than awarded to +one repository, since it names a family rather than a repository and so fails +decision 1 for the same reason `PRJ-WP-` does. Assignment of the four successor +prefixes is outstanding. + +Execution of each rename belongs to a worker in the owning repository under +decision 4. `RMGR-WP-0004-T09` records assignments and the numbering rule; it +does not perform renames. + +**Consequence.** Prefix ownership must be assigned for all three shared prefixes +before the next workplan is created in the affected repositories. This is +forward conformance under decision 1, not migration, and is tracked as +`RMGR-WP-0004-T09`. Renaming the *historical* files is explicitly not required — +that would be option 3, which was rejected. + +## References + +- Decision `747011c6` — repository standards belong to Repo Manager +- ADR-001 — workplans originate as repo files; hub is a read model +- `RMGR-WP-0004` — repository standards conformance and governed scaffolding +- `STATE-WP-0080` — register scaffolding handoff +- Fleet scan 2026-08-16: 955 hub workplans, 525 parseable identifiers, + 3 reused prefixes, 5 reused identifiers diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-010.md b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-010.md new file mode 100644 index 0000000..04e4184 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-010.md @@ -0,0 +1,271 @@ +--- +id: CUST-ADR-010 +type: architecture-decision-record +title: "Hub Authority, Local Cache, and the Two Kinds of Hub Data" +status: proposed +owner: the-custodian +revision: "draft-2" +last_reviewed: "2026-08-31" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-08-17" +tags: ["architecture", "state-hub", "repo-manager", "hub-core", "authority", "cache", "offline", "adr-001"] +--- + +# ADR-010: Hub Authority, Local Cache, and the Two Kinds of Hub Data + +## Status + +Proposed, and **partially superseded by `ADR-012`** (accepted 2026-08-25). +Decisions 1, 5 and 6 are sharpened or given a mechanism there; see the notes on +each below. Everything else in this ADR remains in force. + +## Context + +Investigating a workplan-identifier flip-flop (`ADR-007`) revealed that two +State Hub instances were operating as **peer databases**, not as a primary and a +replica: + +| Address | Instance | Workplans | +|---|---|---| +| `127.0.0.1:8000` (IPv4) | local uvicorn on docker postgres | 955 | +| `[::1]:8000` (IPv6) | ssh tunnel `state-hub-primary` → railiance01 | 649 | + +Both listen on port 8000, separated only by IP family, so every tool defaulting +to `127.0.0.1` reached the local instance while believing it was the primary. + +> **Mechanism identified 2026-08-24 by `CUST-WP-0067`.** This ADR recorded the +> shared port as the hazard but not why it was *silent*, which is the part that +> let it persist. An `ssh -L` forward with no bind address listens on every +> loopback family, and `ExitOnForwardFailure=yes` fires only when **every** +> requested bind fails. The IPv4 bind lost to the local uvicorn, the IPv6 bind +> succeeded, and the tunnel therefore reported success while serving an address +> nothing resolved to by default. The collision was declared in +> `~/.config/bridge/tunnels.yaml`, and `ops-bridge` guarded only tunnel against +> tunnel, so a non-bridge listener was invisible to it. +> +> Fixed by pinning local forwards to `127.0.0.1`, so a contested port fails +> loudly (`ops-bridge` commit `2213847`). A shared port is recoverable; a shared +> port that reports healthy is not. + +Measured divergence — 320 records exist locally but not on the primary: + +```text +backed by a file that exists on disk 288 fully rebuildable +no backing file recorded 28 not rebuildable as-is +same filename, different UUID 4 duplicate registration +``` + +**No file was missing for any of the 288.** The divergence is therefore almost +entirely redundant: it can be discarded and rebuilt from repositories without +losing anything. + +`ADR-001` already says work originates as repository files and the hub is a read +model. The two-peer-database situation contradicts that in practice: a second +database accumulated 306 net records of its own. + +## Decision + +**1. One authoritative hub, deriving from repository files.** The central State +Hub — running on railiance — is authoritative. It is authoritative *as a reading +of the repositories*, not as a second place data lives. Repository files remain +the source of truth (`ADR-001`). + +> **Sharpened 2026-08-25 by `ADR-012` decision 1.** "A reading of the +> repositories" never said *which* copy of them, and the honest answer was +> neither the forge nor any particular working copy: the projection derived from +> whichever checkout last ran the sync. The hub holds no repository files at all +> and never reads one. `ADR-012` names the forge as the projection source. + +**2. A local hub is a cache, never a database.** Local instances hold a +rebuildable projection. A cache may be discarded and reconstructed from the +repositories at any time, and losing it must never lose work. + +This replaces the peer-database arrangement. It is also why the divergence is +tractable: a divergent database is a merge problem, a stale cache is a refresh +problem. + +> **Corrected 2026-08-24 by `CUST-WP-0067`.** Those two shapes are not +> exhaustive, and repository records took a third one. Measured that day: +> 122 repositories on the cache, 78 on central, **zero central-only**. A strict +> subset in the cache's favour is neither a merge problem nor a refresh problem +> — refreshing the cache would have *destroyed* the 44 extra records rather than +> reconciling them, and central held no path to re-derive repositories it had +> never been told existed. +> +> The third shape: **cache-only records whose authoritative source exists and is +> reachable, but was never introduced to central.** Its remedy is re-derivation +> from source — not refresh, not merge. Of the 44, 43 had a working copy, all 43 +> were pushed, and 34 carried a classification file; nothing was unrecoverable, +> but nothing would have recovered itself either. +> +> Onboarding by date showed a clean break: central's repository registrations +> stopped at 2026-07-08 while the workstation kept accepting them. + +**3. Local work requires no hub at all.** Repository files are self-describing — +identifier, status, tasks, all in frontmatter. Working in a repository requires +reading files, not querying an index. A cache is an optimization for cross-repo +questions, never a prerequisite for doing work. + +**4. Hub data is classified by origin, and the two kinds have opposite rules.** + +| | File-derived | Hub-native | +|---|---|---| +| Examples | workplans, tasks, statuses, dependencies | progress events, decisions, inbox messages, token events | +| Source of truth | the repository file | the hub | +| Offline behaviour | write the file and commit — the commit *is* the write | buffer locally, replay when reachable | +| Conflict model | **none**; conflicts are git conflicts, resolved in git | **none**; append-only merges regardless of order | +| Central accepts pushes? | no — it derives | yes | + +Neither kind needs a hub-side conflict model. That is the point of the split: if +central derives file-backed state, it cannot hold a conflicting version of it — +it re-derives whatever git settles on. Two people editing one workplan is a git +conflict and belongs to git. + +**5. Central derives file-backed state; it does not accept pushes of it.** +"Authoritative" means authoritative *reading*, so nothing may inject derived +state directly. Hub-native records are the exception and keep a real write path. + +> **Sharpened 2026-08-25 by `ADR-012` decision 6.** This was policy, not +> practice: nothing derived, and the workstation pushed everything. `ADR-012` +> retires push-based sync as the primary path so that "central derives" becomes +> true rather than aspirational. + +**6. Preliminary until confirmed.** Locally registered data and uncommitted +repository state are preliminary until the central service has seen them. +Mitigation is by changing the repository files and the local cache — never by +editing central to match a local view. + +> **Given a mechanism 2026-08-25 by `ADR-012` decisions 3 and 4.** "Preliminary" +> was named here but never built, so in practice locally registered data was +> indistinguishable from derived state once it arrived. It is now a labelled +> overlay within the same projection — explicitly not a second store — and it +> retires when the commit carrying it reaches the forge. The prohibition on +> editing central to match a local view is unchanged. + +Combined with `ADR-007` decision 2 (identifiers derived from `PREFIX-WP-NNNN`), +"preliminary" largely stops mattering: a cache computes the same identifier +central will, so offline-registered data is already correct on arrival and needs +confirmation rather than reconciliation. + +**7. Every record has exactly one authoritative hub.** The State Hub retirement +splits one hub into several. Multiple central hubs are permitted only under this +rule: the owning hub is determined by the record's repository and domain. Without +it, the same peer-database divergence recurs at larger scale. + +**8. Cache reads are advisory and must carry their age.** Cross-repo answers from +a cache are advisory and should be presented with staleness. For the repository +an agent is working in, the file is truth and the cache is never consulted for +correctness. + +## Orphan disposition + +The 28 records with no backing file are the only ones a cache rebuild would drop. +They fall into three classes, to be separated before any rebuild: + +1. **Broken links** — a file exists but `backing_filename` was never recorded. + `RMGR-WP-0004` is one: the workplan file exists and is committed. These are + metadata repairs, not data loss, and are likely the largest class. +2. **Live hub-first records** — `proposed`, `ready`, or `backlog` with no file, + in `activity-core`, `core-hub`, `hub-core`, `issue-core`, `ops-hub`, + `prj-forgejo-org-refactor`, `railiance-enablement`, `railiance-infra`, + `reef-railiance`. Each needs a repository file written or an explicit drop. + These are `ADR-001` violations and must not be preserved as hub-only records. +3. **Closed hub-first records** — `finished` or `archived` with no file. Retain + as historical provenance where cheap; do not reconstruct plans that are done. + +A cache rebuild enforces `ADR-001` retroactively: the only casualties are records +that broke it. + +## Consequences + +**Positive.** The divergence becomes discardable rather than mergeable. Offline +work is fully supported without a write buffer for file-backed state — the git +commit is the write. No hub-side conflict model is needed for either data kind. +Authority stops being a policy claim and becomes a structural property. + +**Negative.** The 28 orphans require case-by-case disposition before a rebuild. +Any consumer that treats a local hub as authoritative must be corrected. The +one-hub-per-record rule constrains the retirement's hub split. + +**Rescoping.** `STATE-WP-0068` (offline write buffer and edge relay) is scoped as +a single mechanism. Under decision 4, most of what it buffers does not need +buffering — only the append-only hub-native stream does. Its scope should be +re-examined before more is built on it; this may reduce work rather than add it. + +**Correction to `ADR-007`.** Decision 2 there calls the workstation instance a +"development read replica". It was neither a replica nor smaller — it held 306 +more workplans than the primary. Superseded by decisions 1–3 here. + +## Implementation + +Owned by `repo-manager` for file-derived state (decision `747011c6`; it already +owns repository representation, file-backed record indexing, and reconciliation) +and by `hub-core` for hub-native records. Tracked under `RMGR-WP-0005`. + +## References + +- ADR-001 — workplans originate as repo files; hub is a read model +- ADR-007 — identifier uniqueness, derived identifiers, worker topology +- Decision `747011c6` — repository standards belong to Repo Manager +- `RMGR-WP-0005` — registrar consolidation and deterministic identifiers +- `STATE-WP-0068` — offline write buffer and edge relay (rescope candidate) +- Divergence measurement, 2026-08-17: 955 local / 649 primary / 320 local-only + + +## Outcome (2026-08-24) + +> Added by `CUST-WP-0067`. This ADR read as though its remediation had landed. +> It had not: the two-instance condition it measured on 2026-08-17 was still +> live seven weeks later, and had continued to accumulate records the whole +> time. An ADR that describes a fix in the present tense is easily mistaken for +> a fix that happened — worth stating plainly for the ones that follow. + +The peer-database arrangement is now resolved, by deletion rather than by +reconciliation: + +- **The local hub instance is retired.** Rather than making two instances + coexist safely, the second one was removed. Exactly one process binds + `127.0.0.1:8000` and it is the tunnel to central. Nothing remains that could + impersonate the primary, and no call site needed editing — retiring the + impersonator made the existing `127.0.0.1:8000` defaults correct. +- **Decision 3 carried the argument.** Because local work requires no hub at + all, and Repo Manager already maintains a file-derived index, the local + instance was redundant rather than load-bearing. +- **Repo Manager gained the write path it was already assigned.** + `hub-record-authority.yaml` gives it `managed_repos` as `file-derived`, but it + exposed no command for it; the only working path lived in the State Hub repo + and defaulted to a local address. `rmgr repo-onboard` closes that, and refuses + to onboard a repository whose backing file is uncommitted, unpushed, or has no + upstream — a record whose source is only local cannot be re-derived, which is + the failure this ADR exists to prevent. +- **33 of the 44 were re-derived onto central**, taking it from 78 to 111 + repositories. The remaining 11 carry written dispositions rather than guessed + values, per the orphan-disposition principle above. + +One cause sat deeper than the topology: the hub resolved its classification +allowed-values file from three hardcoded developer-workstation checkouts, so in +a container every classification write failed. Repository classification could +*only* be written from a workstation. That is a second instance of this ADR's +own theme — authority that depends on where a process happens to run is not +authority — and is why "central derives" had never been achievable for this +record type. + +## Outcome (2026-08-28) + +> Added by `CUST-WP-0068`. The 2026-08-24 outcome closed the *repository* +> divergence. The work-record divergence this ADR originally measured — 955 +> local / 649 primary — remained, because the retired instance's database was +> still load-bearing. That is now closed. + +- **Central holds 1167 workplans.** Records that existed only in the cache + were re-derived from their files, renamed onto the canonical scheme, or + given a written disposition (`docs/recovery/cache-only-disposition-2026-08-28.md`). +- **No open work record exists only in the cache.** Remaining cache-only + slugs are aliases of recovered records, clay-borg product files (not + workplans), or prefix-migration residue. +- **The cache database is discarded.** Final dump + `~/backups/state-hub-cache-2026-08-28.dump`. Container `infra-postgres-1` + and volume `infra_pg_data` removed. Port 5432 is free. +- **The local instance is no longer load-bearing for any record type.** + Decision 3 is now true in operation, not only in argument. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-011.md b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-011.md new file mode 100644 index 0000000..635d394 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-011.md @@ -0,0 +1,290 @@ +--- +id: CUST-ADR-011 +type: architecture-decision-record +title: "Federated Namespaces: Four Planes, Declared Posture, and the Limits of Reconciliation" +status: proposed +owner: the-custodian +revision: "draft-2" +last_reviewed: "2026-08-17" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-08-17" +tags: ["architecture", "federation", "namespace", "identity", "reconciliation", "posture", "fos", "adr-007", "adr-008", "adr-010"] +--- + +# ADR-011: Federated Namespaces — Four Planes, Declared Posture, and the Limits of Reconciliation + +## Status + +Proposed, draft-2. Amends `ADR-007` decisions 1 and 2; extends `ADR-010` +decision 4; adopts the plane/ladder/posture form and the accuracy-not-altitude +conformance rule from `ADR-008` (Multi-Tenancy Framework). + +## Context + +The ecosystem must be **federation-capable** — able to work without a live link +to central services — and **chain-of-command efficient**. +`federated-organization-standard_v1.0` already resolves the organizational half: + +> global coherence is achieved through policy, escalation, and shared protocols +> rather than through monolithic control (`:51`) +> +> bounded autonomy plus disciplined coordination (`:86`) + +Authority is a **policy** layer; availability is a **mechanism**. Distributed +version control makes the same separation: an integration branch is privileged by +agreement, never by a technical property. + +**One accepted decision violates this.** `ADR-007` decision 1 requires running +numbers that are *globally* unique and allocated forward only. Global sequential +allocation requires a coordinator — precisely the central dependency federation +must survive. Two disconnected forks each allocating `RCLUSTER-WP-0015` is a +certainty, not an edge case. + +This is not hypothetical. Instance-per-client tenancy (`CUST-WP-0058`) already +makes every client deployment a fork of the stack; the business model committed +to forking before the identifier scheme could express it. + +### Why this draft changed form + +Draft-1 stated federation as a set of binary rules — notably "records fork, +effects do not". That is a single verdict where a graduated declaration belongs, +and it is the same conflation `ADR-008` §03 diagnoses for multi-tenancy: +*"'Is this multi-tenant?' is treated as one question. It is four, and they are +independent."* + +"Is this federation-capable?" is likewise not one question. A namespace can be +strong on identity and incapable of disconnected operation; it can operate +disconnected and have no way to reconcile afterwards. Draft-1 could not say so. + +This draft reuses `ADR-008`'s vocabulary rather than inventing a parallel one. +Two competing maturity vocabularies in one canon would be its own collision. + +## Decision + +**1. Records live in a namespace.** A namespace is a federated branch of the +ecosystem — a fleet instance, a client deployment, an autonomous domain that may +operate disconnected. It is the unit of sovereignty in FOS terms and the unit of +divergence here. + +**2. Federation is four orthogonal planes.** + +| Plane | Question | Vocabulary owner | +|---|---|---| +| Namespace **N** | How are records named and disambiguated across branches? | this ADR | +| Autonomy **U** | Can work proceed with no link to central services? | repo-manager | +| Reconciliation **R** | What happens when branches meet again? | repo-manager | +| Substrate **S** | Does this namespace hold independently diverging operational state? | railiance-platform | + +**3. Each plane carries a ladder.** Higher is stronger, not better. The right +level is the one a namespace can evidence and its risk warrants. + +```text +N0 no namespace concept; identifiers assume one global space +N1 namespace implied by deployment, never recorded +N2 namespace declared; identifiers namespace-scoped; qualification available +N3 N2 + lineage recorded, and derived identifiers keyed on (namespace, identifier) + +U0 routine work requires the central service +U1 degraded read-only; writes blocked while disconnected +U2 work proceeds locally: file-derived state written to files, hub-native buffered +U3 U2 + exercised — a dated disconnection drill with evidence + +R0 no defined path; divergence is unrecoverable +R1 manual, per-record comparison and hand resolution +R2 append-only events plus derived state: union and re-derive, deterministic +R3 R2 + lineage-aware; distinguishes divergence from collision automatically + +S0 shares operational substrate with another namespace — cannot fork safely +S1 shared substrate, namespace-keyed +S2 dedicated substrate, no independent state divergence yet +S3 independent operational substrate with diverged state — the fork is real +``` + +**4. A namespace declares a posture vector**, in the repository rather than the +hub, consistent with `ADR-010` decision 4 (file-derived state) and `ADR-008` +decision 5.1. + +```yaml +federation: + current: { N: 1, U: 1, R: 1, S: 1 } + target: { N: 3, U: 2, R: 2, S: 1 } + reviewed: "2026-08-18" + gap: + U: "Registration requires the registrar under ADR-007 decision 2 interim. + Lifts when derived identifiers land (RMGR-WP-0005-T03)." +``` + +**The ecosystem's own posture today is `N1 U1 R1 S1`** — one implied namespace, +disconnected registration blocked, reconciliation manual, substrate shared. +Declaring that is conformant. Claiming better is not. + +**5. Conformance is accuracy, not altitude.** Adopted verbatim in force from +`ADR-008` §06: a namespace is conformant when its declared posture is accurate, +its target recorded, and it claims no level it cannot evidence. It is +non-conformant when it overclaims, at any altitude. + +Declaring `S0` is conformant. Concealing `S0` is not. A repository may join at +any posture; it may not join silently. This matters more here than it does for +tenancy: the cost of discovering a true posture during reconciliation is the +whole difference between T2 and T3. + +**6. Identifiers are unique within a namespace, qualified across.** `ADR-007` +decision 1 is amended: uniqueness and forward-only allocation are +**namespace-scoped**. Global identity is the pair `(namespace, identifier)`. + +```text +RCLUSTER-WP-0015 this namespace — the common case, unchanged +RCLUSTER-WP-0015@railiance01 a foreign namespace, explicitly qualified +``` + +Unqualified always means "in my own namespace", exactly as `main` does against +`origin/main`. A suffix preserves existing sorting, grep, and tooling; a prefix +would break them. + +**7. Derived identifiers key on the pair.** `ADR-007` decision 2 (C2) derives +UUIDv5 from the identifier. Under federation the input must be +`(namespace, identifier)`. Deriving from the identifier alone would make two +forks holding unrelated work under the same number compute the *same* UUID — a +collision manufactured by the fix. C2 is not yet built, so this is free now and +expensive later. + +**8. The planes couple at their upper levels. State the coupling; do not use it +to argue the planes are one.** + +- `R3` requires `N3` — distinguishing divergence from collision requires lineage. +- A namespace may not sustain `U2` or above while below `R2`. Operating + disconnected without deterministic reconciliation accumulates divergence that + cannot later be merged. Autonomy without reconciliation is not federation; it + is an unplanned fork. +- `T3` is entailed by `S3` in any participant (decision 10). + +**9. Anti-overclaim.** This is the most misusable fact in the framework, and it +is the direct analogue of `ADR-008`'s `P0 → P1 → P2` warning. + +> Adopting namespace-qualified identifiers is movement on the **N plane only**. +> It buys disambiguation, citability, and coordinator-free allocation. It does +> **not** raise autonomy, reconciliation, or substrate independence by one step. +> A system at `N3 U0 R0 S0` cannot operate disconnected and cannot reconcile. + +No namespace may be described as "federation-capable", "independently operable", +or "able to run disconnected" on N-plane evidence alone. Such a claim requires +declared `U` and `R` levels with evidence behind them. + +**Commercially**, client-facing claims about instance independence must cite the +`S` and `U` levels, never the identifier scheme. Instance-per-client tenancy is +sold on substrate independence; the qualifier does not supply it. + +**10. The reconciliation tier is derived from participants' postures, not judged +after the fact.** + +```text +any participant at S3 -> T3 (regardless of other planes) +all participants S<=S2 and R>=R2 -> T2 feasible +any participant below R2 -> T1 at best; manual thereafter +``` + +This is the practical payoff of declaring postures: the cost of rejoining is +**computable before a fork is created**, rather than discovered when +reconciliation is attempted. + +**11. Coexistence is the normal case; merging is the exception.** The qualifier +is primarily a coexistence mechanism, not merge preparation. Most forks — client +instances above all — are never intended to return. Permanent parallel operation +is a first-class outcome, not a failure to reconcile. + +**12. Evidence.** A claimed level requires evidence, per `ADR-008` §13. + +| Level | Evidence | +|---|---| +| `N2` | namespace declared in the repo; qualified references resolve | +| `N3` | lineage fields present; a derived identifier reproduced by an independent implementation | +| `U2` | work completed with central unreachable, committed, later accepted without rework | +| `U3` | dated disconnection drill record | +| `R2` | replaying the union of events reproduces identical derived state | +| `R3` | a collision and a divergence, correctly distinguished, in test | +| `S2` / `S3` | substrate inventory naming the stateful systems and whether their state has diverged | + +**13. Guard.** Declared postures regress silently. Posture is checked by the same +consistency machinery that checks repository standards (`RMGR-WP-0004-T02`, +`rmgr conform`), so a namespace that drops below its declared level is a +reported defect rather than a discovery. A regression found by guarding is a +defect; a regression declared in advance is a decision. + +## Reconciliation tiers, and where automation stops + +| Tier | Situation | Mechanism | Automatable | +|---|---|---|---| +| **T0 Fork** | namespace created, lineage recorded | metadata | yes | +| **T1 Sync** | pull canon, plans, or knowledge from another namespace | append events, re-derive | largely | +| **T2 Reintegration** | records diverged, no participant above `S2` | union events, re-derive, human-resolve genuine contradictions | partly | +| **T3 Amalgamation** | any participant at `S3` | governed programme — see below | **no** | + +**T3 is not a merge.** It resembles the merger or acquisition of distinct +companies far more than a version-control merge, and must be planned as such: + +- due diligence — inventory both sides, item by item; +- survivor decisions per subsystem — keep, move, replace, retire; +- migration with evidence, and decommissioning of what does not survive; +- governance integration — reconciling two bodies of canon is a human act, + never automated; +- identity, credential, and data custody consolidation. + +**T3 may legitimately end without integration.** Permanent coexistence, or +divestment, are valid outcomes. Real mergers most often fail at systems +integration; an architecture that assumes eventual convergence is lying about its +own limits. + +### Known cost + +`prj-state-hub-retirement` is a T3 reconciliation inside a *single* +organization — two hub implementations, distinct infrastructure. It required a +dedicated project repository, a 425-item disposition inventory (`SHR-INV-0001`), +staged migration with strangler adapters, and explicit retirement gates. That is +the realistic cost of T3 under the most favourable conditions possible: one +owner, one canon, one credential authority. + +Nothing here reduces that cost. Declared postures make it **visible before** a +fork is created rather than discovered when reconciliation is attempted. + +## Consequences + +**Positive.** Disconnected operation stops requiring a coordinator for identity. +`ADR-007` decision 1's contradiction is resolved rather than tolerated. A +namespace mid-journey can describe itself honestly instead of being +non-conformant. Reconciliation cost becomes computable in advance. Client +instances become expressible. + +**Negative.** Every namespace gains a posture to declare and keep accurate, and +tooling must learn qualified references. Decision 8's coupling rule constrains +disconnected operation to namespaces that can reconcile — deliberately. + +**Deferred.** Merge machinery for T2 is **not** specified here and should not be +built until a real fork needs it. T3 needs no machinery by definition; it needs a +project repository and human judgement. + +**Amended.** `ADR-007` decision 1 — uniqueness is namespace-scoped. +`ADR-007` decision 2 — derivation input becomes `(namespace, identifier)`. + +## Open question + +**What is the namespace boundary in practice?** Candidates: a fleet instance +(`railiance01`), a client deployment, or an FOS autonomous domain. FOS's +recursion — "from projects to companies to foundation-like umbrella structures" — +suggests namespaces **nest**, which would make qualification relative rather than +absolute, closer to DNS than to a flat handle. The notation survives either +answer, so implementation is not blocked, but the data model is unfinished until +it is settled. + +## References + +- `canon/standards/federated-organization-standard_v1.0.md` — bounded autonomy, + escalation, sovereignty by default, rebuildability +- ADR-001 — workplans originate as repo files +- ADR-007 — identifier uniqueness and derived identifiers (amended here) +- ADR-008 — Multi-Tenancy Framework; source of the plane/ladder/posture form and + the accuracy-not-altitude conformance rule +- ADR-010 — hub authority, local cache, and the two kinds of hub data +- `CUST-WP-0058` — instance-per-client tenancy +- `SHR-INV-0001` — 425-item disposition inventory, T3 cost evidence +- `RMGR-WP-0004-T02` — `rmgr conform`, the guard machinery diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-012.md b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-012.md new file mode 100644 index 0000000..6f7dd58 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/CUST-ADR-012.md @@ -0,0 +1,273 @@ +--- +id: CUST-ADR-012 +type: architecture-decision-record +title: "What the Hub Projects: Forge as Projection Source, Working Copies as Preliminary Overlay" +status: accepted +owner: the-custodian +revision: "1.0" +last_reviewed: "2026-08-25" +review_interval: 6m +decided_by: Bernd Worsch +date: "2026-08-25" +tags: ["architecture", "state-hub", "forgejo", "projection", "provenance", "authority", "contributors", "adr-001", "adr-010"] +--- + +# ADR-012: What the Hub Projects — Forge as Projection Source, Working Copies as Preliminary Overlay + +## Status + +**Accepted 2026-08-25** by Bernd Worsch. Supersedes `ADR-010` decision 1's phrase "authoritative as a reading of +the repositories" by making the reading concrete, and implements decision 6's +unbuilt notion of "preliminary". + +## Context + +`ADR-001` says work originates as repository files and the hub is a read model. +`ADR-010` says central is authoritative *as a reading of the repositories*. Both +leave one question unanswered, and it turns out to be the load-bearing one: + +> **Which** repository files? The forge, or a working copy? + +Verified on 2026-08-25, the answer today is **neither**. + +Central runs one FastAPI pod on railiance01 with a CNPG database. Inspected from +inside, it holds `/app` — its own source — and nothing else. No clone tree, no +repository mount; the `sweep` hostPath that would give it one is disabled. **The +hub never reads a repository.** It cannot see the forge, and it cannot see a +workstation. + +What actually happens is the inverse of the ADR language: `fix-consistency` and +`registrar-reconcile` run *on a workstation*, read files in `/home//`, +and POST the result over a tunnel. The `host_paths` field records this plainly — +117 repositories carry a path on `bnt-lap001`, a laptop. + +So the projection derives from **whichever checkout most recently ran the sync**. +That is a third thing, distinct from both the forge and any particular working +copy, and it is written down nowhere. + +### Why this is not academic + +Every failure investigated under `CUST-WP-0067` and `CUST-WP-0068` is downstream +of it: + +- A local hub and central both served port 8000, and every default reached the + local one for seven weeks. Neither could be distinguished by what it projected. +- 50 repository records pointed at a retired forge, so the sync could not match + the checkout and silently registered nothing. +- Duplicate registrations accumulated: the same file registered twice under two + identifiers, because two environments each believed they were projecting it. +- `git_fingerprint` — the field that should say which commit a projection + reflects — still holds the *initial commit* for `the-custodian`, while + `last_state_synced_at` is minutes old. There is no reliable way to ask central + what state it is a projection *of*. + +### The forcing function + +With one contributor, "last writer wins" is invisible, because there is only one +writer. With several, each pushes a projection of a different repository state +into one shared read model, and each will look locally consistent while +disagreeing with the others. That is the peer-database problem `ADR-010` +diagnosed, relocated one layer up and made harder to see. + +The window to fix this cheaply is before the second contributor, not after. + +## Decision + +**1. The forge is the projection source.** Central derives its projection from +Forgejo — the pushed state of the default branch — not from any working copy. +"Authoritative as a reading of the repositories" now names a specific reading: +what the forge holds. + +Forgejo runs in the same cluster as central, so this is an in-cluster fetch. No +tunnel, no cross-network credentials, and central already carries a `git` binary. + +**2. Every projected record carries the commit it derived from.** A projection +without provenance cannot be audited, and the existing `git_fingerprint` proves +the point by being wrong and silent about it. Records must reference the commit +that produced them, and a projection whose commit is older than the forge's head +must be visibly stale rather than quietly current. + +**3. Unpushed local work is a preliminary overlay, never the baseline.** A +working copy may contribute records the forge does not yet hold. They are marked +preliminary, attributed to their environment, and never silently merged into the +derived baseline. When the commit that carries them reaches the forge, the +baseline absorbs them and the overlay entry retires. + +This implements `ADR-010` decision 6, which named "preliminary" but left it +unbuilt. + +**4. The overlay is a label, not a second store.** Preliminary records live in +the same projection, flagged. They are not a parallel database and not a local +hub. Every local projection this ecosystem has had was eventually mistaken for +authority; the defence is that there is nothing separate to mistake. + +**5. A working copy is a workspace, not a source of authority.** Deleting a +checkout after pushing must lose nothing and invalidate nothing. Validation of a +projection must not require a local clone of the repository being validated. + +**6. Push-based sync is retired as the primary path.** Workstation tooling stops +being how records reach central. It may continue to *propose* preliminary +records under decision 3, but the baseline is derived, not received. This closes +the gap where "central derives" was policy while "the laptop pushes" was +practice. + +**7. The projection must be resettable from the forge, as a routine operation.** +There must be a supported way to reconcile the projection — for one repository or +for the whole fleet — against what Forgejo holds: create what is missing, update +what differs, and **retire what no longer derives**. + +> **Amended 2026-08-25, before implementation.** This decision originally said +> *discard and rebuild*. That operation cannot exist, and the reason is a good +> one. `progress_events`, `tasks`, `decisions` and `review_contracts` all +> reference workplans with `ON DELETE RESTRICT`, and 1067 of 1075 workplans carry +> at least one such reference. Deleting a workplan would mean deleting the +> hub-native records attached to it — which `ADR-010` decision 4 forbids, and +> which the schema refuses to allow. +> +> The database is enforcing this ADR's own boundary one layer down. A progress +> event recording work on a workplan is a fact that happened; destroying it to +> tidy a derived projection would be losing hub-native truth to fix a +> derived-state problem, which is exactly backwards. +> +> Retirement achieves what reset is for — the projection converges on the forge, +> and records that no longer derive stop appearing as live work — without +> erasing anything that only the hub holds. Reset reconciles; it does not +> destroy. + +This is the decision that makes the others checkable rather than merely stated. +A read model that cannot be rebuilt from its source is not a projection; it is a +database with a projection's reputation, and the difference only becomes visible +on the day someone needs to rebuild it. `ADR-010` decision 2 already asserts that +a cache "may be discarded and reconstructed from the repositories at any time" — +that claim has never been executed, and an untested rebuild path is an assumption, +not a capability. + +Three properties make it real rather than ceremonial: + +- **Routine, not emergency.** It should be run deliberately and often enough that + it is known to work, not discovered under pressure. A reset that has never been + performed is indistinguishable from one that does not work. +- **Per repository is the unit.** Not a convenience over a fleet-wide operation — + the repository *is* the unit of reconstruction, and the fleet-wide form is + defined as iteration over it. + + This follows from the source. Each repository is a separate forge repository + with its own history and its own head commit, so its projection is derivable in + isolation and provable in isolation: fetch that repository, rebuild its + records, compare against that repository's head. Nothing about that requires + knowing the state of any other repository, and a design that made it require so + would be inventing a dependency the source does not have. + + It also bounds the blast radius, which is what makes decision 7's "routine" + achievable. A rebuild that can only run fleet-wide is an operation nobody runs + casually, and therefore an operation nobody runs — which is precisely how + `ADR-010`'s never-executed reconstruction claim came to be believed. Scoped to + one repository, a rebuild is small enough to be ordinary, and ordinary is the + only state in which it stays known-working. + + It is also the only form that composes with the refusals below. A repository + holding projection-only records can be held back and dispositioned while every + healthy repository around it is rebuilt; an all-or-nothing reset is blocked + entirely by a single bad repository, which in practice means it is blocked + permanently. + + With contributors, per-repository scope is what keeps one person's rebuild from + touching another person's records. + + The fleet-wide form must therefore be a loop over the per-repository form, + sharing one implementation. The rarely-used dangerous path and the frequently + used safe path must be the same code, so the rare one is exercised by the + common one rather than trusted on the strength of never having been run. +- **Idempotent and verifiable.** A reset followed by a reset produces the same + projection, and the result can be compared against the forge to show it matches. + Derived identifiers (`ADR-007`) are what make this possible: the same commit + yields the same record identities every time. + +**Retirement must be visible, not silent.** A retired record states that the +forge no longer derives it, and remains inspectable — including from whatever +hub-native history is still attached. A record that merely disappears from a +listing is indistinguishable from one that was never there. + +**Reset does not restore the preliminary overlay.** Overlay records exist +precisely because the forge does not hold them, so a rebuild from the forge +cannot reproduce them and must not pretend to. Reset therefore discards +preliminary state, and must say so plainly before it runs. + +**A reset must refuse when records exist only in the projection.** If the hub +holds records with no counterpart in the forge, rebuilding destroys them. That is +not hypothetical: as of 2026-08-25, 111 work records existed only in a retired +local database, and a rebuild at that moment would have erased them. The reset +path must detect that condition and stop, naming what would be lost, rather than +proceed and report success. + +The refusal is evaluated per repository, in keeping with the scope above. A +repository whose records all exist in the forge is rebuildable regardless of what +any other repository holds, and a fleet-wide run must skip and report the +repositories it refuses rather than abort the whole pass. Otherwise one +unresolved repository blocks reconstruction everywhere, and the capability decays +back into the untested assumption this decision exists to prevent. + +**8. Formal git review stays optional.** Deriving from the default branch gives a +shared baseline without requiring pull requests. Review can be adopted per +repository where it earns its keep; this ADR neither mandates nor forbids it. + +## Consequences + +**Positive.** The hub becomes provably a projection: reconcilable on demand +against its source, and therefore knowable to be one. Truth becomes checkable by anyone, from anywhere, +without a clone. +Multiple contributors share one baseline instead of overwriting each other's +views. Provenance becomes auditable — every record can name its commit. The +"push then delete the working copy" case simply works. The distinction between +committed and uncommitted work becomes visible in the model rather than a matter +of who ran which command last. + +**Negative.** Git becomes load-bearing for the hub: Forgejo availability now +affects projection freshness. A derive loop needs a cadence, and freshness +becomes a property to monitor rather than assume. Unpushed work becomes +explicitly second-class — which is its honest status, but it will feel like a +restriction to a solo developer used to local-first behaviour. + +**Migration.** The `sweep` hostPath — central reading a node-local clone tree — +is a half-measure toward this decision and was disabled pending "governed remote +reconciliation" while railiance01 checkouts still targeted Gitea. As of +2026-08-25 all 79 node checkouts track Forgejo, so that stated blocker has +cleared. Sweep should be evaluated as a stepping stone or retired in favour of a +direct forge fetch, not left dormant with an obsolete justification. + +**Unresolved.** This ADR does not settle the derive cadence, whether central +clones or uses the Forgejo API, how preliminary records are surfaced in the +dashboard and MCP, or what happens to a preliminary record whose commit never +arrives. Nor does it settle how hub-native records — progress events, decisions, +inbox messages, which `ADR-010` decision 4 classes as originating in the hub — +survive a reset. They are not forge-derived and must not be destroyed by a +rebuild of forge-derived state; the boundary needs drawing before reset is +built. Those belong to implementation. + +## Relationship to prior decisions + +- **`ADR-001`** — unchanged. Work still originates as repository files; this ADR + says which copy of them the hub reads. +- **`ADR-010`** — decisions 1 and 5 are sharpened, not reversed: central still + derives and still does not accept pushes of derived state. Decision 6's + "preliminary" gains a mechanism. The local-cache-versus-database framing stands. +- **`ADR-003`** — partially superseded. Decision 2 composes fingerprints from + filesystem `mtime`, which is a property of one workstation rather than of the + source; under decision 1 here the input is the commit. Decision 5 already + stated the rebuild principle correctly but had never been exercised; decision 7 + here makes it an operation with a source, a scope and a verification. +- **`ADR-007`** — derived identifiers become more valuable here: a forge-derived + projection and a preliminary overlay compute the same identifier for the same + record, so absorbing an overlay entry needs no reconciliation. + +## References + +- `ADR-001` — workplans originate as repo files; hub is a read model +- `ADR-010` — hub authority, local cache, and the two kinds of hub data +- `ADR-007` — identifier uniqueness and derived identifiers +- `CUST-WP-0067` — hub target resolution; retired the impersonating local instance +- `CUST-WP-0068` — cache-only work-record recovery; surfaced the stale + `git_fingerprint` and the duplicate registrations +- Verification, 2026-08-25: central pod holds no repository files; `sweep` + disabled; 117 repositories record a laptop path; `the-custodian` + `git_fingerprint` is the initial commit while `last_state_synced_at` is current diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0006.md b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0006.md new file mode 100644 index 0000000..f0a8e29 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0006.md @@ -0,0 +1,108 @@ +--- +id: NK-ADR-0006 +type: architecture-decision-record +title: "Recursive Multi-Tenant Identity and Authorization Architecture" +status: accepted +owner: net-kingdom +revision: "1" +decided: "2026-05-17" +last_reviewed: "2026-08-22" +review_interval: 12m +--- + +# ADR-0006 - Recursive Multi-Tenant Identity and Authorization Architecture + +**Status:** Accepted +**Date:** 2026-05-17 +**Deciders:** Bernd Worsch + +## Context + +The Coulomb platform is being built from the same repositories and +services that will later support other use cases. This creates a +recursive architecture problem: Coulomb needs to use the shared identity, +security, policy, and deployment capabilities, while those capabilities +are themselves part of the infrastructure being built. + +If this recursion is left implicit, the first internal use case can drift +into being treated as the platform root of trust. That would make future +multi-tenant use harder, blur operational authority, and make secure +bootstrap/recovery decisions harder to reason about. + +NetKingdom already owns identity and security architecture concerns. +key-cape provides a lightweight IAM implementation of the NetKingdom IAM +Profile. Keycloak remains the expanded production IAM option. privacyIDEA +is relevant for MFA/token lifecycle. flex-auth is emerging as the +canonical authorization control plane and practical reference +implementation of CARING authorization semantics. Topaz is the most +likely first delegated authorization runtime behind flex-auth. + +## Decision + +We will document and implement the platform security architecture as a +recursive multi-tenant architecture with three explicit planes: + +- **Bootstrap plane** - establishes the first trusted runtime and recovery + authority before normal platform services exist. +- **Platform control plane** - operates shared identity, MFA, secrets, + authorization, policy, audit, and explanation services. +- **Tenant plane** - runs Coulomb and future workloads under scoped tenant + authority. + +Coulomb will be treated as the first internal/reference tenant, not as the +platform root of trust. + +NetKingdom will own the canonical security architecture and standards. +Railiance will own deployment layering and orchestration boundaries. +flex-auth will own the canonical authorization interface and CARING-based +policy/decision model. Topaz will be the first delegated PDP runtime, +with other authorization engines treated as adapters where useful. + +## Consequences + +- Architecture documentation must separate platform-root authority from + tenant administration, even for Coulomb. +- Workplans for identity, authorization, and bootstrapping must include + explicit tenant and control-plane boundaries. +- Bootstrap design must include trust-state transitions and recovery + procedures rather than assuming the final IAM service already exists. +- flex-auth should model tenants, platform resources, CARING descriptors, + decision envelopes, and runtime adapters in a provider-neutral way. +- key-cape and Keycloak should be treated as implementations of the IAM + Profile, not as the canonical source of resource authorization + semantics. +- A future orchestration repo may be useful, but only to coordinate safe + sequencing across Railiance and NetKingdom capabilities. It must not + bypass Railiance stack ownership. + +## Alternatives Considered + +### Treat Coulomb As The Platform Root + +This is simpler during early development but creates long-term coupling +between one internal use case and the shared platform. It makes later +multi-tenant operation and secure bootstrap harder. + +### Put All Security Semantics Into Keycloak + +Keycloak is useful for expanded IAM and can provide authorization +features, but making it the canonical model would make lightweight mode +and future authorization backends harder to support. The preferred model +keeps identity provider concerns separate from canonical authorization +semantics. + +### Create An Orchestration Repo Immediately + +A dedicated orchestration repo may become appropriate. Creating it before +we define trust states and repo boundaries would risk encoding accidental +sequence logic too early. The immediate step is to document the state +machine and update workplans. + +## Follow-Up + +- Refine bootstrapping around explicit trust-state transitions. +- Add tenant/control-plane language to flex-auth authorization workplans. +- Define the first production Topaz integration boundary for flex-auth. +- Decide when key-cape is sufficient and when Keycloak expanded mode is + required. +- Decide what, if anything, should live in a future orchestration repo. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0007.md b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0007.md new file mode 100644 index 0000000..6571d3d --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0007.md @@ -0,0 +1,154 @@ +--- +id: NK-ADR-0007 +type: architecture-decision-record +title: "Security Orchestration Boundary" +status: accepted +owner: net-kingdom +revision: "1" +decided: "2026-05-18" +last_reviewed: "2026-08-22" +review_interval: 12m +--- + +# ADR-0007 - Security Orchestration Boundary + +**Status:** Accepted +**Date:** 2026-05-18 +**Refined:** 2026-05-21 (meta-orchestration layer — see below) +**Deciders:** Bernd Worsch, Codex + +## Context + +The recursive platform security architecture needs careful sequencing: +host trust, cluster trust, bootstrap secrets, runtime secret authority, +runtime identity, runtime authorization, tenant onboarding, and readiness +verification. + +That sequencing crosses NetKingdom and Railiance ownership boundaries. +NetKingdom owns the canonical security architecture, IAM Profile, +credential/bootstrap standards, and authorization semantics. Railiance +owns deployment layering for infrastructure, clusters, platform services, +and applications. OpenBao adds an important runtime-secret authority to +the platform control plane, but it does not change those ownership +boundaries. + +Creating a dedicated orchestration repo too early would risk encoding +temporary bootstrap order and accidental stack assumptions as a permanent +interface. Leaving every sequence implicit would also be risky: platform +root actions, OpenBao initialization, policy import, and tenant onboarding +must be auditable and repeatable. + +## Decision + +Security orchestration will stay in Railiance playbooks for now. + +NetKingdom will define the trust-state model, readiness checks, policy +semantics, OpenBao boundaries, and tenant/control-plane rules. Railiance +playbooks will own the concrete deployment sequencing across +`railiance-infra`, `railiance-cluster`, `railiance-platform`, and +`railiance-apps`. + +A dedicated orchestration repo is deferred until the sequencing surface is +stable enough to justify its own product boundary. If created later, it +must coordinate safe sequencing and readiness reporting; it must not own +security policy semantics or bypass Railiance stack ownership. + +## Consequences + +- NK-WP-0006 is implemented as architecture, standards, ADRs, and + workplan constraints rather than a new repo. +- OpenBao bootstrap, unseal/recovery, audit, backup, and workload-secret + delivery belong in Railiance platform playbooks, governed by + NetKingdom standards. +- Cross-repo readiness should be reported as checks against explicit + trust states, not as a hidden imperative script. +- A future orchestration repo needs a new ADR before creation. + +## Future Repo Trigger + +Revisit a dedicated orchestration repo only if at least two of these are +true: + +- multiple Railiance deployments need the same security sequencing + interface; +- readiness reporting becomes a reusable artifact consumed by operators, + agents, or CI; +- rollback and recovery workflows need a cross-repo state machine that no + single Railiance layer can own cleanly; +- tenant onboarding becomes a repeatable workflow spanning identity, + flex-auth, Topaz, OpenBao, object storage, and application repos. + +## Alternatives Considered + +### Create A Dedicated Orchestration Repo Now + +This would give sequencing a visible home, but it would probably encode +unstable details before OpenBao runtime operations, flex-auth/Topaz +policy import, and tenant onboarding have enough implementation feedback. + +### Put Orchestration In NetKingdom + +NetKingdom owns the security model, but it should not become the +deployment repo for every stack layer. This would blur architecture +ownership with platform deployment ownership. + +### Leave Sequencing Entirely Informal + +This avoids premature structure but leaves bootstrap and runtime trust +transitions too dependent on operator memory. The accepted approach keeps +the sequence explicit while leaving concrete deployment in the Railiance +stack. + +## Refinement (2026-05-21): Meta-Orchestration Layer + +The original decision left "what NetKingdom does about orchestration" +defined only negatively (it does not own deployment mechanics). This +refinement names the positive role. It sharpens, and does not overturn, +the accepted decision: execution stays in Railiance. + +There are two distinct layers: + +- **Railiance — execution orchestration (the "how").** A library of + scenario playbooks plus the tools that run them. Each playbook + provisions a slice of the landscape, ships **sensible defaults**, and + **exposes parameters** for tuning. Multiple playbooks exist for multiple + scenarios. +- **NetKingdom — meta-orchestration (the "what" and "who").** NetKingdom + does not re-implement deployment mechanics. It (1) **selects** the + services and playbooks a given scenario requires, (2) **decides + parametrization** where tuning is warranted and otherwise relies on + playbook defaults, and (3) **holds the responsibility map** — which + element (a Railiance layer, an external provider, a tenant-owned piece) + owns what across the whole IT landscape. + +The relationship is architect ↔ contractor (or conductor ↔ players): +NetKingdom composes the score and assigns parts; Railiance executes them. +This is the same discipline used elsewhere — the IAM Profile is the +contract while key-cape/Keycloak are implementations, and the State Hub is +a read model over execution. Meta-orchestration is a **decide/compose** +layer over Railiance's **execute** layer. + +This binds directly to the capability ladder +(`docs/platform-identity-security-architecture.md` → *Capability +Progression*): the ladder is the menu, a scenario selects a subset of +tiers, and meta-orchestration is the act of binding that subset to +playbooks + parameters + a responsibility map, producing a turn-key +landscape. + +### New Dependency — Playbook / Capability Contract + +For NetKingdom to select and parametrize reliably, **Railiance playbooks +must publish a declared interface**: the capability each playbook +provisions, its parameters (with defaults and constraints), and the +responsibility it claims. This catalog is the orchestration-layer analog +of the IAM Profile. Without it, meta-orchestration composes against +implicit behavior and breaks when a playbook changes. Establishing this +contract is the prerequisite for any concrete meta-orchestration work. + +### Effect on the Future Repo Trigger + +Meta-orchestration logic now has a clear home (NetKingdom) regardless of +whether a dedicated **execution**-orchestration repo is later created +under the Future Repo Trigger above. A future repo, if created, would host +reusable execution sequencing — not the scenario-composition and +responsibility-mapping role, which remains NetKingdom's. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0008.md b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0008.md new file mode 100644 index 0000000..3c5fc7f --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0008.md @@ -0,0 +1,94 @@ +--- +id: NK-ADR-0008 +type: architecture-decision-record +title: "Object Storage STS Credential Vending Boundary" +status: accepted +owner: net-kingdom +revision: "1" +decided: "2026-05-18" +last_reviewed: "2026-08-22" +review_interval: 12m +--- + +# ADR-0008 - Object Storage STS Credential Vending Boundary + +**Status:** Accepted +**Date:** 2026-05-18 +**Deciders:** Bernd Worsch, Codex + +## Context + +NetKingdom needs a canonical pattern for issuing short-lived +object-storage credentials to platform and tenant workloads. The first +known consumer is `artifact-store`, but the pattern must work for future +S3-compatible consumers without making each application repo own identity, +authorization, root object-store credentials, or backend-specific STS +differences. + +The backend landscape is not uniform. AWS S3, Ceph RGW, and MinIO/AIStor +can use web-identity STS-style flows. Cloudflare R2 exposes temporary +credentials through a provider API or local signing with parent access +material. OpenBao is now part of the Railiance platform stack as runtime +secret authority, but it is not an identity provider or authorization +policy engine. + +## Decision + +NetKingdom will define a provider-neutral credential-vending interface +backed by provider-native temporary credential mechanisms where possible. + +The trust path is: + +1. IAM Profile token proves the actor or workload. +2. flex-auth decides whether the actor may receive credentials for the + requested protected system, tenant, bucket, prefix, action set, TTL, + and assurance level. +3. The credential-vending service exchanges the approved request with + the backend-specific temporary credential mechanism. +4. OpenBao stores parent credentials, broker configuration, lease + metadata, and audit evidence where useful, but it does not replace + flex-auth authorization. +5. Consumers receive normalized temporary credentials containing access + key id, secret access key, session token, and expiration. + +## Consequences + +- `artifact-store` needs temporary credential support, especially + `AWS_SESSION_TOKEN` and refresh behavior, before it can fully consume + the production vending pattern. +- Backend-specific differences are isolated in the vending service, not + leaked into application policy. +- OpenBao remains runtime secret infrastructure and audit support; it + does not become the object-storage policy source. +- Provider-native STS is preferred when available because it gives the + storage backend direct lease/expiration semantics. +- Cloudflare R2 requires a broker path that protects parent access + material, most likely through OpenBao custody. + +## Alternatives Considered + +### Give Applications Long-Lived Access Keys + +This is simple but leaves applications holding durable credentials and +pushes policy into ad hoc bucket configuration. It is acceptable only as +a transitional bridge with scoped credentials and explicit rotation. + +### Put Object-Storage Policy In Keycloak Or key-cape + +Identity providers can assert who the actor is and coarse groups or +roles, but they should not become the canonical source of bucket, +prefix, action, TTL, and explanation semantics. + +### Use OpenBao As The Credential Vending Policy Engine + +OpenBao is valuable for secret custody, broker configuration, leases, +and audit records. Making it the policy decision point would duplicate +flex-auth, blur the platform/tenant boundary, and make authorization +semantics backend-specific. + +### Require One Backend Everywhere + +A single backend would simplify implementation but does not match the +platform direction. Railiance and NetKingdom need a stable security +interface across AWS, self-hosted S3-compatible stores, and Cloudflare +R2-like APIs. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0010.md b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0010.md new file mode 100644 index 0000000..4f7c0a2 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0010.md @@ -0,0 +1,108 @@ +--- +id: NK-ADR-0010 +type: architecture-decision-record +title: "Orchestration vs Dependency, and Self-Coherent Intent" +status: accepted +owner: net-kingdom +revision: "1" +decided: "2026-05-21" +last_reviewed: "2026-08-22" +review_interval: 12m +--- + +# ADR-0010 - Orchestration vs Dependency, and Self-Coherent Intent + +**Status:** Accepted (repo classification subject to ongoing refinement) +**Date:** 2026-05-21 +**Deciders:** Bernd Worsch, Codex + +## Context + +While aligning the ecosystem's `INTENT.md` files, two relationships that +had been blurred turned out to be fundamentally different, and a content +principle for intent emerged. Both are foundational enough that future +interface and boundary refinements should be measured against them. + +NetKingdom performs meta-orchestration (ADR-0007): it selects, parametrizes, +and assigns responsibility across an IT landscape. But "things NetKingdom +meta-orchestrates" is not the same as "things NetKingdom depends on," and +the two had been conflated. + +## Decision + +### Principle 1 — Orchestration is not dependency + +NetKingdom relates to other repositories in two distinct ways: + +- **Orchestrated** — the repo provides a service that **holds resources + NetKingdom must manage**: users, roles, scopes, policies, credentials, + infrastructure resources, and the like. NetKingdom composes, + parametrizes, and holds responsibility for those resources. +- **Dependency** — NetKingdom **uses the repo as a tool** to provide its + own interface, without managing resources the tool holds. + +**The defining question:** *does the repo provide a service holding +resources that NetKingdom needs to orchestrate?* + +- Yes → **orchestrated**. +- No, it is merely used → **dependency**. + +Worked examples: + +- `railiance-fabric` is a tool NetKingdom uses to provide an interface; it + holds no NetKingdom-managed resources → **dependency**. +- `railiance-infra`, `railiance-cluster`, `railiance-platform` define and + hold resources → **orchestrated**. +- An IAM directory (users, groups) or a policy store (roles, scopes, + policies) holds exactly the resource kinds in the criterion → + **orchestrated**. + +This classification is applied now (see the responsibility map) and will +be refined as interfaces and boundaries mature. Borderline cases are +expected. + +### Principle 2 — Intent is self-coherent + +Every repository's `INTENT.md` describes **that repository's own** purpose +and direction, abstractly and stably. Therefore: + +- It must **not** define itself in terms of NetKingdom. +- It must **not** reference the intent of sister projects. +- It must **not** even encode dependencies — dependencies are more + concrete and less stable than intent should be. + +Intent is the most abstract, most stable layer. Relationships — +orchestration, dependency, interfaces, boundaries — are recorded +**outside** intent: in NetKingdom's responsibility map, architecture docs, +ADRs, and interface contracts. This keeps every repo's intent free of +external reference points, so it stays stable while the interfaces and +boundaries between repos are refined over time. + +## Consequences + +- The earlier idea of adding a "place in the NetKingdom-orchestrated + landscape" block to downstream `INTENT.md` files is **rejected**. It + would violate Principle 2. +- Cross-repo `INTENT.md` work becomes: ensure each orchestrated repo has a + **self-coherent** intent — author one where missing, and remove external + references (to NetKingdom or sister projects) where present. +- The orchestration/dependency relationship and the per-repo + **responsibility map** live in net-kingdom, not in the downstream repos. +- A responsibility-map artifact in net-kingdom enumerates, per orchestrated + repo, which resources NetKingdom manages: `docs/responsibility-map.md`. +- ADR-0007's meta-orchestration layer is unchanged; this ADR clarifies + *what* NetKingdom orchestrates versus merely uses. + +## Alternatives Considered + +### Treat every related repo uniformly + +Simpler, but it conflates "manages the resources this service holds" with +"uses this tool," which produces an incoherent responsibility map and +tempts downstream repos to encode NetKingdom into their intent. + +### Record relationships inside each repo's intent + +Convenient for a reader of a single repo, but it couples intents to each +other and to NetKingdom, making the most-stable layer the least stable. +Relationships belong in interface contracts and the responsibility map. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0011.md b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0011.md new file mode 100644 index 0000000..9e02a24 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0011.md @@ -0,0 +1,139 @@ +--- +id: NK-ADR-0011 +type: architecture-decision-record +title: "NetKingdom IAM Profile Ownership And Version Governance" +status: accepted +owner: net-kingdom +revision: "1" +decided: "2026-05-22" +last_reviewed: "2026-08-22" +review_interval: 12m +--- + +# ADR-0011 - NetKingdom IAM Profile Ownership And Version Governance + +**Status:** Accepted +**Date:** 2026-05-22 +**Deciders:** Bernd Worsch, Codex + +## Context + +The IAM Profile is the identity contract that applications, flex-auth, +key-cape, Keycloak, and bootstrap identity tooling all target. It defines +the OIDC discovery, flow, token, claim, assurance, tenant, and conformance +requirements that make lightweight and expanded identity modes +interchangeable at the application boundary. + +A draft IAM Profile v0.1 existed in the-custodian canon with an +all-hubs scope. That draft captured useful material: OIDC discovery, +Authorization Code + PKCE, service-account tokens, required claims, +token lifecycle, emergency access, and local-development behavior. +However, NetKingdom now owns the platform identity domain. SCOPE.md names +the NetKingdom IAM Profile as an in-scope, versioned standard, and +ADR-0006 requires key-cape and Keycloak to be implementations of the +profile rather than the canonical source of authorization semantics. + +The v0.1 draft also used hub-specific scope and role vocabulary. That +made sense for the Custodian hub landscape, but the core NetKingdom +profile must be platform-neutral so it can serve tenant, service, +application, and agent use cases without encoding one downstream system's +scope names. + +## Decision + +NetKingdom is the canonical owner of the IAM Profile. + +The profile is versioned under `canon/standards/` in this repository. The +first canonical NetKingdom version is +`canon/standards/iam-profile_v0.2.md`. + +The relationship to the earlier the-custodian draft is: + +- the-custodian IAM Profile v0.1 is superseded as a core/platform + standard; +- NetKingdom owns the provider-neutral core profile; +- downstream systems may define hub-, tenant-, or application-specific + scopes and roles as extensions, but those extensions must map back to + the core identity and authorization input contract; +- key-cape lightweight mode and Keycloak expanded mode are + interchangeable implementations of the same profile; +- flex-auth consumes the profile as normative identity input and must not + re-derive identity facts from provider-specific state. + +## Versioning + +The IAM Profile uses explicit document versions: + +- Patch/editorial changes clarify wording, examples, or non-normative + guidance without changing the token contract. +- Minor versions add optional claims, optional flows, or additional + conformance checks that existing implementations can pass unchanged. +- Major or breaking versions change required claims, claim meanings, + validation rules, flow requirements, assurance semantics, tenant + semantics, or token acceptance rules. + +Every versioned profile file remains immutable enough for downstream +references to cite. New versions are added as new files rather than +rewriting historical versions in place, except for clearly editorial +fixes that do not affect semantics. + +## Breaking-Change Governance + +A breaking profile change requires: + +1. a new ADR or ADR refinement that explains the change and migration + path; +2. a new versioned profile document; +3. an update to the executable conformance suite; +4. a coexistence window that lets at least one previous supported profile + version and the new version be accepted where practical; +5. notification in workplans or interface docs for known consumers, + especially key-cape, Keycloak/expanded-mode work, flex-auth, and + application integration docs. + +Breaking changes include: + +- removing or renaming a required claim; +- changing the meaning, type, or allowed values of required claims such + as `tenant`, `principal_type`, `roles`, `groups`, `scope`/`scp`, or + `assurance`; +- changing accepted issuer, audience, or signing validation rules; +- weakening PKCE, MFA/assurance, local-development rejection, or + emergency-access requirements; +- moving authorization decisions into an identity provider instead of + flex-auth. + +## Consequences + +- `canon/standards/iam-profile_v0.2.md` is the canonical profile. +- the-custodian's v0.1 draft should carry a relocation/deprecation note + pointing to this repository. +- Hub-specific scopes such as `hub:*`, `ops:*`, and `fin:*` are + downstream extensions, not core profile vocabulary. +- key-cape and Keycloak must emit or normalize to the same claim contract + before applications and flex-auth consume tokens. +- The conformance suite in `tools/iam-profile-conformance/` is the + executable contract for implementations. + +## Alternatives Considered + +### Keep The Custodian Draft As Canonical + +The draft is useful, but keeping ownership there would conflict with +NetKingdom's repository scope and with ADR-0006's responsibility split. +It would also leave the profile coupled to Custodian hub vocabulary. + +### Make Keycloak The Reference Provider + +Keycloak is the expanded-mode implementation and remains important for +enterprise federation. Making it the reference provider would make +lightweight mode, local bootstrap, and future identity adapters secondary +to one implementation. The accepted model keeps providers +interchangeable behind the profile. + +### Put Scope And Role Vocabulary In The Core Profile + +A shared vocabulary is useful, but core identity must stay stable across +applications and tenants. Downstream systems can define extension scopes +and roles as long as they map to the core claim shapes and flex-auth +decision inputs. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0012.md b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0012.md new file mode 100644 index 0000000..9ec584f --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0012.md @@ -0,0 +1,124 @@ +--- +id: NK-ADR-0012 +type: architecture-decision-record +title: "Playbook Capability Contract Ownership" +status: accepted +owner: net-kingdom +revision: "1" +decided: "2026-05-22" +last_reviewed: "2026-08-22" +review_interval: 12m +--- + +# ADR-0012 - Playbook Capability Contract Ownership + +**Status:** Accepted +**Date:** 2026-05-22 +**Deciders:** Bernd Worsch, Codex + +## Context + +ADR-0007 refined NetKingdom's orchestration role into a +meta-orchestration layer. NetKingdom selects the services and playbooks a +scenario needs, decides which parameters may be tuned, and holds the +responsibility map. Railiance remains the execution-orchestration layer: +Railiance playbooks provision and converge the actual infrastructure, +cluster, platform services, and application layers. + +That split requires a stable interface. If a Railiance playbook only +describes behavior implicitly, NetKingdom cannot safely compose it into a +scenario, compare it with another playbook, or know which parameter +changes are safe. The IAM Profile provides the precedent: the consumer +that needs a stable contract defines the contract, and providers conform +to it. + +## Decision + +NetKingdom owns the Playbook Capability Contract schema and vocabulary. +Railiance owns playbook implementation and publishes one conformant +declaration per playbook. + +The first canonical contract is +`canon/standards/playbook-capability-contract_v0.1.md`, backed by the +machine-readable schema in +`canon/schemas/playbook-capability-declaration_v0.1.schema.json` and the +validator in `tools/playbook-capability-contract/`. + +The contract is NetKingdom-owned with Railiance co-design: + +- NetKingdom defines the schema, controlled vocabulary, trust-state + language, parameter-sensitivity rules, and conformance criteria. +- Railiance authors and maintains declarations beside the playbooks they + describe. +- Railiance execution stays unchanged. The declaration never becomes the + playbook runner. +- NetKingdom meta-orchestration consumes declarations to select, + parametrize, sequence, and build responsibility maps for scenarios. + +ADR-0007 remains unchanged: execution stays in Railiance. + +## Versioning + +The contract uses explicit document versions: + +- Patch/editorial changes clarify wording or examples without changing + declaration semantics. +- Minor versions add optional fields, vocabulary entries, or validator + warnings that existing declarations can ignore. +- Breaking versions change required fields, field meanings, allowed + vocabulary, parameter-sensitivity semantics, trust-state semantics, or + catalog consumption rules. + +Declarations carry `metadata.contract_version`. A catalog may accept more +than one contract version during a migration window, but must report the +version used for each selected playbook. + +## Breaking-Change Governance + +A breaking change requires: + +1. an ADR or ADR refinement explaining the change and migration path; +2. a new versioned standard and schema; +3. an updated validator; +4. a coexistence window for the previous supported version where + practical; +5. notice to known declaration publishers, especially Railiance repos. + +Breaking changes include: + +- removing or renaming required fields; +- changing capability ids or resource-kind vocabulary; +- changing trust-state meanings; +- changing which parameter sensitivities are tenant-tunable; +- changing catalog selection or override semantics; +- moving execution responsibility out of Railiance into NetKingdom. + +## Consequences + +- Playbook declaration files live beside Railiance playbooks, normally at + `capabilities/playbooks/*.yaml`. +- NetKingdom can validate declarations before consuming them. +- A playbook interface change becomes visible and versioned instead of an + implicit break. +- The responsibility map can be assembled from declarations, while + Railiance keeps execution ownership. + +## Alternatives Considered + +### Put The Contract In Railiance + +Railiance owns execution, so this is tempting. But NetKingdom is the +consumer that needs stable scenario composition and responsibility-map +inputs. Keeping the contract in NetKingdom mirrors the IAM Profile +pattern and keeps scenario semantics close to the responsibility map. + +### Make Declarations Free-Form Documentation + +Free-form docs are readable but not safely composable. NetKingdom needs a +validator and controlled vocabulary so a playbook change cannot silently +break a scenario. + +### Build A Dedicated Execution-Orchestration Repo Now + +ADR-0007 explicitly defers that. The contract is useful now and does not +require a new runner or repo boundary. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0013.md b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0013.md new file mode 100644 index 0000000..f60c51a --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0013.md @@ -0,0 +1,177 @@ +--- +id: NK-ADR-0013 +type: architecture-decision-record +title: "Tenant Onboarding Grouping Taxonomy" +status: accepted +owner: net-kingdom +revision: "2" +decided: "2026-07-23" +last_reviewed: "2026-08-22" +review_interval: 12m +--- + +# ADR-0013 - Tenant Onboarding Grouping Taxonomy + +**Status:** Accepted +**Date:** 2026-07-23 +**Amended:** 2026-08-22 (current classification versus historical identifier segment) +**Deciders:** Bernd Worsch, Codex + +## Context + +`canon/standards/iam-profile_v0.2.md`'s "Tenant Claim" section lists four +*suggested* (not exhaustive) tenant identifiers: `tenant:platform`, +`tenant:coulomb`, `tenant:sandbox:`, `tenant:customer:`. + +Separately, an unratified exploration +(`docs/princedom-isolation-exploration.md`) proposes a non-exclusive +**capability-role** model for tenants: `PLTF` (operates the platform), +`IAM` (organizes its own users/secrets), `VEN` (provides apps/services to +others), `CUS` (consumes apps/services from `PLTF` or `VEN` tenants) — one +tenant can hold multiple roles simultaneously. + +Binky Hedgehog GmbH is being onboarded as the platform's first tenant +outside `tenant:coulomb` (`key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md`), +forcing a concrete identifier decision. Binky already exercises both `CUS` +(consumes `qonto-assistant`) and, per `binky-control/INTENT.md`, will exercise +`VEN` (packages and sells offers built on the ecosystem to real external +customers) — it is not purely a "customer" in the role sense. + +Using a capability-role word (`customer`, `vendor`, ...) as the tenant +*grouping* segment collides with that separate role dimension: a tenant +labeled `tenant:customer:binky` would carry a stale, misleading label the +moment it also starts acting as a vendor, and tenant identifiers are +expensive to rename once tokens, OpenBao paths, and downstream config +reference them. + +## Decision + +Adopt an onboarding-risk / entity-shape **grouping** taxonomy for the tenant +identifier's second segment, replacing the `sandbox`/`customer` suggested +identifiers in `iam-profile_v0.2.md`'s "Tenant Claim" section: + +```text +tenant:: + +trial - test/trial/showcase tenants only +friendly - known, easily reached, tolerant of experimentation/instability +single - one-person business entities (freelance consultants) +small - up to 10 employees at time of onboarding (attoo) +medium - up to 100 employees (attoo) +large - up to 1000 employees (attoo) +enterprise - 1001+ employees (attoo) +consumer - private individuals +family - a legal family +community - a non-legal group of people +association - a legal association of people +agentic - financially enabled AI entities +``` + +The taxonomy has two deliberately different uses: + +- At creation, the identifier's grouping segment records the tenant's + onboarding-time classification. The complete identifier is immutable, so + this segment is historical after creation. +- The tenant record's `grouping` field records current classification. It may + change as the entity changes and is authoritative for present-day policy, + including guardrails and spend ceilings. + +No consumer may parse the identifier's middle segment and treat it as current +grouping. Consumers needing current grouping MUST read it from `tenant-engine`. +Identifier creation still validates the segment against this vocabulary; +historical does not mean free-form or optional. + +Grouping is deliberately **orthogonal to capability role** +(`PLTF`/`IAM`/`VEN`/`CUS`, subsequently ratified by ADR-0014): grouping +describes *what kind of entity a tenant is and its current onboarding-risk +classification*; role describes *what it does on the platform*. Both are +carried as tenant metadata, but never conflated into the immutable identifier +segment — that conflation is exactly what this ADR avoids. + +`tenant:platform` and `tenant:coulomb` remain **reserved, ungrouped +identifiers outside this taxonomy**: `tenant:platform` is the control-plane +root, not a business entity being onboarded; `tenant:coulomb` is the +reference tenant established by ADR-0006, predating this taxonomy, and none +of the twelve groupings meaningfully describe "the platform building +itself." The taxonomy applies to tenants onboarded from here forward. +*(This sub-point completes an open question raised during KEY-WP-0004 +drafting and is Codex's reasoned proposal — flagged for Bernd's explicit +confirmation rather than assumed settled.)* + +First application: Binky Hedgehog GmbH maps to `friendly` — known, +reachable, tolerant of early instability — giving **`tenant:friendly:binky`** +(`key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md`). + +## Scope and Governance Classification + +Per ADR-0011's breaking-change governance, this does **not** meet the +breaking-change bar: it doesn't add, remove, or rename a required claim; it +doesn't change `tenant`'s type or validation rule (still an opaque string); +it doesn't affect acceptance of any previously-issued token +(`tenant:platform`, `tenant:coulomb` tokens remain valid as-is, and no +`tenant:customer:*` or `tenant:sandbox:*` tokens have been issued yet). It +only replaces non-normative suggested-identifier guidance for tenants +onboarded going forward. + +Per ADR-0011's own versioning rule ("New versions are added as new files... +except for clearly editorial fixes that do not affect semantics"), this +qualifies as an **editorial update** to `iam-profile_v0.2.md`'s Tenant Claim +section — not a new versioned profile document. + +## Consequences + +- `canon/standards/iam-profile_v0.2.md`'s "Tenant Claim" section needs a + follow-up edit replacing the `sandbox`/`customer` suggested identifiers + with this taxonomy. Not made by this ADR itself — tracked as follow-up so + the change is reviewable on its own. +- `key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md` + already reflects this decision (`tenant:friendly:binky`). +- Future tenant onboarding work should classify a tenant against this list + before minting an identifier, rather than reaching for a role word. +- Tenant identifiers never change when current grouping changes. The middle + segment is creation-time history; `tenant-engine` is authoritative for the + current grouping value. +- Policy and commercial consumers, including spend-ceiling resolution, MUST + query `tenant-engine` and MUST NOT derive current grouping by splitting a + tenant identifier. +- The capability-role model (`PLTF`/`IAM`/`VEN`/`CUS`) remains a separate + dimension, now ratified by ADR-0014. Role metadata is carried alongside — + not instead of — current grouping and the historical identifier segment. +- `tenant:platform` and `tenant:coulomb` are reserved outside the taxonomy, + pending Bernd's explicit confirmation (see Decision). + +## Alternatives Considered + +### Keep using capability-role words as the tenant grouping + +Rejected: tenants are already known to hold multiple roles at once (Binky is +`CUS` now, `VEN` later); a role-word grouping would need renaming as a +tenant's roles evolve, which tenant identifiers are specifically expensive +to do once tokens, OpenBao paths, and config reference them. + +### Retrofit `tenant:coulomb` into the new taxonomy + +Considered `tenant:friendly:coulomb` or similar. Rejected for now: Coulomb +is structurally the reference tenant from ADR-0006, predating this taxonomy, +and none of the twelve entity-shape groupings describe "the platform's own +ecosystem-development tenant." Revisit if a future grouping is ever added +that genuinely fits it. + +### Do nothing / keep the four original suggested identifiers + +Rejected: `tenant:customer:binky` was the working default going into Binky's +onboarding despite the role-collision problem above; the fleet needs this +resolved before the first non-Coulomb tenant goes live, not after. + +## Follow-Up + +- Edit `canon/standards/iam-profile_v0.2.md`'s Tenant Claim section to + replace the old suggested identifiers with this taxonomy (separate, + reviewable change). +- Confirm the `tenant:platform`/`tenant:coulomb` reserved/ungrouped + treatment explicitly. +- ADR-0014 and the Tenant Engine Boundary Contract define how capability-role + metadata is carried alongside grouping. +- Keep `tenant-engine`'s identifier parser vocabulary-validating for creation + and lookup compatibility, but do not expose parsed grouping as current + classification. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0014.md b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0014.md new file mode 100644 index 0000000..a7f01fa --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0014.md @@ -0,0 +1,183 @@ +--- +id: NK-ADR-0014 +type: architecture-decision-record +title: "Tenant Capability Roles, Carrying Mechanism, and Tenant-Engine Ownership" +status: accepted +owner: net-kingdom +revision: "1" +decided: "2026-07-23" +last_reviewed: "2026-08-22" +review_interval: 12m +--- + +# ADR-0014 - Tenant Capability Roles, Carrying Mechanism, and Tenant-Engine Ownership + +**Status:** Accepted +**Date:** 2026-07-23 +**Deciders:** Bernd Worsch, Codex + +## Context + +ADR-0013 introduced the tenant onboarding grouping taxonomy +(`trial`/`friendly`/`single`/.../`agentic`), deliberately orthogonal to a +separate, unratified **capability-role** model sketched in +`docs/princedom-isolation-exploration.md`: `PLTF` (operates the platform), +`IAM` (organizes its own users/auth/secrets), `VEN` (provides apps/services +to others), `CUS` (consumes apps/services from `PLTF` or `VEN` tenants) — +non-exclusive, a tenant may hold several at once. + +That exploration left open where capability roles actually live (a per-token +claim vs. a registry), who owns them, how they're granted or revoked, and +how this interacts with the IAM Profile's existing `roles` claim — which is +a *per-subject* claim ("coarse identity roles" for the human/service/agent +holding the token), a different concept from a *per-tenant* capability fact. +Conflating the two would be a category error: `roles: ["VEN"]` on a token +would ambiguously mean "this subject has vendor-role" vs. "this subject's +tenant is a vendor." + +No existing service owns tenant-as-an-entity facts (existence, grouping, +capability roles, plan/subscription state) as a queryable resource. +`user-engine`'s own boundary contract +(`canon/standards/user-engine-boundary-contract_v0.1.md`) explicitly scopes +`user-engine` to *consuming* tenant identifiers and *storing tenant-scoped +records*, not owning tenant identity or capability facts. + +Bernd's direction (2026-07-23): + +- Implement the previously-discussed hybrid carrying approach: cache a + tenant's capability roles on the token for ordinary decisions, but + require a live check for critical/high-stakes actions. +- Role grants are usually tied to a payment plan — most concretely, `IAM` + means the tenant has its own dedicated key-cape/Keycloak instance for + isolation, scale, and performance, which is itself a paid capability. +- `trial`-grouped tenants may hold **any** capability role without + restriction, specifically so the platform can showcase, test, and explore + every role. Safety for trial tenants comes from resource guardrails + (spend limits defaulting to zero budget, entity/action count limits), not + from role gating — guardrail design itself is future work, not this ADR. +- A new service, `tenant-engine`, will be built (Bernd) as the owner of this + domain, organized beside `user-engine` rather than inside it — smaller, + single-purpose services are easier to reason about and drift less, matching + the fleet's existing convention (`activity-core`, `audit-core`, `user-engine`, + and others). + +## Decision + +1. **Capability-role vocabulary ratified as core NetKingdom vocabulary**: + `PLTF`, `IAM`, `VEN`, `CUS`, non-exclusive. `IAM` specifically means: the + tenant operates its own dedicated IAM implementation instance (lightweight + key-cape or expanded Keycloak) rather than sharing the platform's, for + isolation/scale/performance — not "any tenant that happens to have users." + +2. **`tenant-engine` is the canonical owner** of tenant-domain facts: tenant + existence, grouping (ADR-0013), capability roles (this ADR), plan/ + subscription assignment, and — reserved for future design, not built now — + guardrail/quota policy. It is a new, separate service, not a module inside + `user-engine`. Its ownership boundary is defined in the companion contract, + `canon/standards/tenant-engine-boundary-contract_v0.1.md`. + +3. **Carrying mechanism: hybrid cache + live re-validation.** `tenant-engine` + is the single source of truth. `key-cape` stamps a cached, optional + `tenant_roles` claim onto issued tokens at issuance time, sourced from + `tenant-engine` (added to the IAM Profile as a new optional claim — + `canon/standards/iam-profile_v0.3.md`). Consumers may trust the cached + claim for ordinary decisions. `flex-auth` **MUST** re-validate live + against `tenant-engine` — never trust the cached claim alone — before + authorizing privileged or high-stakes actions, using the same threshold + class the profile already defines for `assurance.level >= aal2` + (privileged, destructive, platform-root, secret, credential-vending + flows). This bounds staleness risk for ordinary actions to a token's + short lifetime (5–30 minutes for service/agent tokens, per the profile's + Token Lifecycle table) while guaranteeing freshness exactly where it + matters most. + +4. **Role governance is plan-linked.** Granting a role is normally a + consequence of a tenant's payment-plan state in `tenant-engine`, not a + separate manual workflow — starting with `IAM`. `tenant-engine` records + which plan grants which role(s); `adaptive-pricing` remains the source of + plan/pricing-model *definitions*, `tenant-engine` owns the tenant's + *current* plan/subscription assignment, referenced by id, never + duplicated locally. Whether `VEN` needs an approval gate beyond payment + (reselling access carries legal/compliance exposure a payment alone + doesn't cover) is **not resolved by this ADR** — left to `tenant-engine`'s + own workplan. + +5. **Trial tenants may hold any capability role, unrestricted.** The `trial` + grouping's purpose (showcase, test, explore) requires demonstrating every + role. Safety is enforced through resource guardrails instead: trial + tenants default to a **spend budget of zero**, with entity and action + count limits to follow. Guardrail policy design (exact limits, + enforcement point, override process) is real, near-term future work, + reserved as `tenant-engine`'s to own once designed — not specified by + this ADR. + +6. **Grouping and capability role are independent axes** recorded on the + same tenant record in `tenant-engine`. Neither constrains the other + except where a future guardrail policy explicitly says so. + +## Consequences + +- `canon/standards/iam-profile_v0.3.md` adds the optional `tenant_roles` + claim and folds in ADR-0013's tenant-identifier vocabulary update (both + non-breaking per ADR-0011's own minor-version rule — no existing + implementation is invalidated by either change). Supersedes + `iam-profile_v0.2.md`. +- `tenant-engine` becomes a new repository with its own workplans (Bernd). + `canon/standards/tenant-engine-boundary-contract_v0.1.md` defines its + ownership boundary now, before code exists — the same sequencing + `user-engine`'s contract followed. +- `flex-auth` policy packages gating high-stakes actions must add a + `tenant-engine` live-lookup step; they cannot trust `tenant_roles` alone + for those decisions. +- `key-cape` needs a `tenant-engine` integration at token-issuance time to + source the cached claim — tracked in `key-cape`'s own workplans, not here. +- `docs/platform-identity-security-architecture.md`'s Tenant Model section + is updated to reflect the grouping + role split and `tenant-engine`'s role + (companion change alongside this ADR). +- Guardrail/quota policy is named as required near-term work and given an + owner (`tenant-engine`), but is explicitly not designed by this ADR. + +## Alternatives Considered + +### Token-claim-only, no live re-validation + +Rejected: staleness would be unbounded within a token's lifetime for +genuinely high-stakes actions. A stale `VEN` grant surviving a plan +downgrade or cancellation is not an acceptable risk for money-movement or +credential-vending flows — exactly the class the profile already treats as +requiring the strongest assurance. + +### Registry-only, no cached claim + +Rejected: every ordinary request would pay a `tenant-engine` round-trip even +for non-critical checks, adding latency and a hard runtime dependency for +every consumer, not just the ones handling privileged actions. + +### Restrict role eligibility by tenant grouping (e.g., `trial` cannot hold `VEN`) + +Rejected per Bernd's direction: `trial` tenants exist specifically to +showcase every role. Resource guardrails are the intended safety mechanism +instead, keeping the two axes (grouping, role) independent. + +### Put tenant-role/plan storage inside `user-engine` + +Rejected. `user-engine`'s own boundary contract scopes it to consuming +tenant identifiers and storing tenant-*scoped* user records, not owning +tenant-as-an-entity facts. A dedicated service avoids coupling a +security-critical, high-frequency lookup (used by `flex-auth` on every +privileged decision, and by `key-cape` on every token issuance) to +`user-engine`'s much larger surface (registration flows, factor models, +family dataspace onboarding) that has nothing to do with tenant capability +state. + +## Follow-Up + +- `tenant-engine` repository creation and its own workplan (Bernd). +- `key-cape` integration: source `tenant_roles` from `tenant-engine` at + token issuance. +- `flex-auth` policy package updates: live `tenant-engine` re-validation + gate for privileged actions. +- Guardrail/quota policy design for `trial` (and eventually all) tenants: + spend limits, entity/action count limits, enforcement point, override + process. +- Resolve whether `VEN` needs an approval gate beyond payment-plan state. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0015.md b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0015.md new file mode 100644 index 0000000..bea3323 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/NK-ADR-0015.md @@ -0,0 +1,178 @@ +--- +id: NK-ADR-0015 +type: architecture-decision-record +title: "NetKingdom Railiance Workload Packaging and Relational Platform" +status: accepted +owner: net-kingdom +revision: "1" +decided: "2026-08-11" +last_reviewed: "2026-08-22" +review_interval: 12m +--- + +# ADR-0015 - NetKingdom Railiance Workload Packaging and Relational Platform + +**Status:** Accepted +**Date:** 2026-08-11 +**Deciders:** Bernd Worsch, Claude + +## Context + +NetKingdom's runtime services are deployed today outside the Railiance +reef/rail/`rapp` model. `tenant-engine` and `user-engine` run on the coulomb +substrate with digest-pinned images +(`forgejo.coulomb.social/coulomb/{tenant,user}-engine@sha256:…`), but their +Kubernetes manifests live in this repo at `sso-mfa/k8s//runtime.yaml` +— a canon repo holding runtime YAML — applied imperatively, with +`verify-t0*.sh` scripts as verification. Neither service declares +`railiance/app.toml`, appears in `reef-railiance/bindings/rapps.yaml`, or is +reconciled by a GitOps controller. + +The decision to bring NetKingdom under Railiance governance forces two +questions this ADR settles. + +**Packaging granularity.** `railiance-master/docs/repository-axes.md` maps +`rapp-*` to "one Railiance-managed workload" and states that a `rapp` "does +not replace the responsibility repo that owns the broader domain. It is +explicitly about managed wrapping, not ownership." Precedent exists in two +shapes: `rapp-qonto` (`ownership_repo: qonto-assistant`, one workload, thin +package repo pointing back at the domain repo) and `rapp-postgres` +(`ownership_repo: railiance-platform`, one workload with a `consumers:` list +and a `consumer_contract`). The open question was whether NetKingdom's engines +are one workload or several. + +**Relational storage.** The engines have diverged. `user-engine` already runs +CloudNative PG (`kind: Cluster` in its own namespace) with NetworkPolicies and +a `migrations/` surface. `tenant-engine` runs SQLite on a 1 Gi RWO PVC, chosen +in TEN-WP-0004 for expedience. `railiance-platform` (S3) declares CloudNative +PG the canonical database operator, and `rapp-postgres` packages it with a +`database-per-consumer` boundary unit and OpenBao dynamic credentials. +Separately, `rail-kubernetes`'s wave-1 contract explicitly states the rail must +**not** assume "a generic persistent-storage contract" — so a per-workload PVC +is unsupported by the rail regardless of which database is chosen. + +TEN-WP-0005 was drafted against PostgreSQL and implemented against SQLite, +because T05 must roll out against the runtime that exists. That workplan's +`TenantStore` Protocol was kept as an explicit seam for this decision. + +## Decision + +### 1. Separate `rapp-*` repos per engine + +Each deployed NetKingdom engine gets its own managed workload package repo: + +```text +rapp-tenant-engine ownership_repo: tenant-engine +rapp-user-engine ownership_repo: user-engine +``` + +Following `rapp-qonto`'s shape: the `rapp` owns Railiance packaging, rail +compatibility, workload-specific smoke/health checks, rollout and rollback +expectations, secret references, and dependency declarations. The engine repo +retains domain ownership; `net-kingdom` retains canon. + +This follows the one-workload rule rather than working around it. The engines +are already independent on every axis a `rapp` declaration must state: +separate namespaces, separate image digests, separate release cadence, and +(until decision 2 lands) different storage. A single `rapp-netkingdom` would +have to declare one `workload_identity`, one `rollout_contract`, and one +`rollback_contract` across services that genuinely differ. + +The decisive property is **independent rollback**: rolling back a bad +`tenant-engine` revision must not force a `user-engine` revision change. A +single rapp would make that either impossible or fictional. + +Rollout ordering between the engines is a declared constraint between rapps, +not a reason to merge them: `tenant-engine` before `user-engine`, following the +dependency direction. + +### 2. `secrets-engine` is not packaged as a `rapp` + +`secrets-engine` has no deployed workload — no Containerfile, no Makefile, no +Kubernetes manifests. It is a catalog/policy/workflow layer over OpenBao, whose +packaging already belongs to `rapp-openbao` with custody and lane policy in +`railiance-platform`. + +A `rapp` wraps a workload; `secrets-engine` has none to wrap. Revisit only if +it becomes a runtime service, at which point this decision reopens for that +repo alone and not for the packaging model. + +### 3. CloudNative PG is the default relational platform for production + +Production NetKingdom services requiring relational storage use CloudNative PG +via `rapp-postgres`, consuming the `database-per-consumer` boundary unit and +the `openbao-dynamic-database-credential` lane, with tenant-keying per +`business-app-service-contract_v0.1` section 1.3. + +Per-workload SQLite-on-a-PVC is not a production pattern. It remains acceptable +for local development and tests. + +`tenant-engine` migrates from SQLite to cnpg. Its `TenantStore` Protocol makes +this a backend swap behind an existing seam rather than a rewrite; the +lifecycle semantics proven in TEN-WP-0005 (atomic compare-and-swap, durable +idempotency receipts, forward-only migration) are the conformance bar the +PostgreSQL backend must meet, and its store-conformance suite is already +parametrised across backends to enforce exactly that. + +## Consequences + +- Two new `rapp-*` repos to create, each requiring a `declarations/rapp.yaml`, + a binding in `reef-railiance/bindings/rapps.yaml`, and its own readiness + evidence progression (`declared` → `installed` → `verified` → + `production-approved`). +- Per-binding evidence multiplies, but per-reef evidence does not — the + `reef-production-readiness-contract` puts substrate, ingress, storage, + network, and backup evidence on the reef, once. What multiplies is the + critical-workload gate (threat model, negative authorization tests, rollback + rules, residual-risk owner), which identity-plane services warrant + individually. A shared NetKingdom threat model may be referenced by both + bindings rather than duplicated. +- Runtime manifests move out of `net-kingdom/sso-mfa/k8s/` into the respective + `rapp` repos. This repo stops holding runtime YAML and returns to canon, + standards, and ADRs. +- `tenant-engine` requires a data migration from SQLite to cnpg, including + migration of existing tenants, grants, plan assignments, and idempotency + receipts. TEN-WP-0005-T05's rollout plan is affected: it currently targets + the SQLite runtime. +- NetKingdom services become subject to the `railiance/app.toml` staged + promotion contract (Stage 1 local → Stage 2 canary → Stage 3 production), + including declared rollback commands and health endpoints. Digest-pinned + images already satisfy `digest_policy = "required"`. +- The reef must accept or mitigate its single-server and shared-control-plane + risk for each NetKingdom binding, as it must for `rapp-qonto`. The reef name + and a `high` criticality label are not evidence. + +## Alternatives Considered + +**One `rapp-netkingdom` for all associated repos.** Rejected. It would reduce +binding-evidence count and match the current coupled deployment (both engines' +manifests share one tree; `flex-auth` runs per-consumer instances +`flex-auth-tenant-engine` and `flex-auth-user-engine`). But it contradicts the +one-workload rule in `repository-axes.md`, and would force a single rollback +contract across independently versioned services. A rollback contract that +depends on which service failed is not a contract. This alternative would be +correct only if the engines were always promoted and rolled back as one atomic +cutover — which their separate digests and release cadences contradict. + +**Keep SQLite for `tenant-engine`, standardise later.** Rejected. It leaves the +two engines operationally dissimilar, which is the opposite of the intent, and +`rail-kubernetes` does not support the per-workload persistent-storage contract +the PVC depends on. Deferring also grows the migration: every tenant, grant, +and receipt written between now and the cutover is data to move. + +**A `rapp` per NetKingdom concern with a `consumers:` list, mirroring +`rapp-postgres`.** Rejected as a category error. `rapp-postgres`'s consumers +are consumers *of one workload* (PostgreSQL); NetKingdom's engines are distinct +workloads, not consumers of a shared one. + +## Follow-Up + +- Create `rapp-tenant-engine` and `rapp-user-engine`; move runtime manifests + out of `net-kingdom/sso-mfa/k8s/`. +- Add both bindings to `reef-railiance/bindings/rapps.yaml` at `declared`. +- Add `railiance/app.toml` to `tenant-engine` and `user-engine`. +- Open a `tenant-engine` workplan for the cnpg backend and data migration; + reconcile with TEN-WP-0005-T05, whose rollout currently targets SQLite. +- Confirm whether `secrets-engine` is intended to remain a non-deployed control + layer. This ADR assumes it is. +- Record the NetKingdom-wide threat model that both bindings will reference. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RINFRA-ADR-0005.md b/docs/evidence/2026-10-11-served-publication-history/sources/RINFRA-ADR-0005.md new file mode 100644 index 0000000..1d29224 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RINFRA-ADR-0005.md @@ -0,0 +1,69 @@ +--- +id: RINFRA-ADR-0005 +type: adr +title: "k3s API is tunnel-only" +status: accepted +owner: railiance-infra +revision: "accepted-1" +last_reviewed: "2026-08-22" +review_interval: 6m +--- + +# ADR-005 — k3s API is tunnel-only + +**Status:** Accepted +**Date:** 2026-08-15 +**Deciders:** implementation of RAIL-HO-WP-0009-T04 +**Workplans:** `RAIL-HO-WP-0009` + +--- + +## Context + +Operator addresses on this network rotate with the ISP lease. A public UFW +allowlist for `6443/tcp` is therefore a treadmill: + +- miss a rotation and `kubectl` breaks mid-session +- leave the old grant standing and it becomes a grant to a stranger +- hand-add the new address and the declaration drifts again + +That cycle produced this workplan. The live allowlist drifted by hand *during +the session that was fixing allowlist drift*, and again before the next +session (`89.244.90.248` appeared undeclared). On 2026-08-15 the workstation +egress address was `85.132.220.102` — already on the revoked list as a +"historic" operator address. + +`docs/deploy-stack.md` already documents API access over the ops-bridge SSH +tunnel for CoulombCore (`k3s-api-coulombcore`, local port 16443). The same +tunnel already exists for Railiance01 (`k3s-api-railiance01`, local port +16444). SSH itself stays public, so the host remains recoverable. + +## Decision + +The public k3s API allowlist is **empty**. Operator and agent `kubectl` access +uses the ops-bridge tunnels: + +| Cluster | Tunnel | Local port | Remote | +|-------------|-------------------------|------------|--------| +| CoulombCore | `k3s-api-coulombcore` | 16443 | 6443 | +| Railiance01 | `k3s-api-railiance01` | 16444 | 6443 | + +```bash +bridge up k3s-api-railiance01 +# kubeconfig server: https://127.0.0.1:16444 +``` + +Trade: every operator `kubectl` action depends on ops-bridge. That is accepted. +A rotating public allowlist is the worse dependency. + +Emergency break-glass remains SSH: `ssh railiance01 -- sudo k3s kubectl …`. +Do not re-open `6443/tcp` to Anywhere. + +## Consequences + +- `k3s_api_allowed_sources` stays `[]`. +- Former public grants live in `k3s_api_revoked_sources` so a firewall-tagged + converge deletes them. +- Goss asserts the 6443 allowlist size is exactly the declared length (zero) + and that no revoked address remains. +- Amending this ADR is required before adding any new public 6443 source. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RISK-F-0001.md b/docs/evidence/2026-10-11-served-publication-history/sources/RISK-F-0001.md new file mode 100644 index 0000000..308b814 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RISK-F-0001.md @@ -0,0 +1,226 @@ +--- +id: RISK-F-0001 +type: finding +title: "flex-auth /v1/check authenticates no caller" +status: fixed +owner: risk-nexus +reported_by: flex-auth +reported_via: rapp-postgres +routed_by: rapp-postgres +date_reported: "2026-08-17" +system: flex-auth +environment: production +fix_owner: flex-auth +fix_tracking: FLEX-WP-0015 (finished 2026-08-19) +# Graded by risk-nexus 2026-08-19 — docs/rulings/2026-08-19-first-grading.md +severity: high +severity_at_production: high +severity_superseded: "critical (2026-08-19, graded on L3 before reading the inbox)" +impact: I4 +likelihood: L2 +fidelity_modifier: false +production_rescore: false +disclosure: public +publication: published +publication_id: risk-f-0001-flex-auth-unauthenticated-check +publication_path: "findings/flex-auth-unauthenticated-check/v1/index.html" +publication_url: "https://policy.coulomb.social/findings/flex-auth-unauthenticated-check/v1/" +published_on: "2026-09-01" +publication_subtitle: "The estate's authorization oracle authenticated no caller for as long as the endpoint existed. Found by reading, not by monitoring; fixed in two days." +revision: "published-1" +last_reviewed: "2026-08-20" +review_interval: 6m +embargo_lifted: "2026-08-19 — FLEX-WP-0015 finished, live probes return 401" +embargo_was_since: "2026-08-19" + +escalation: withdrawn +escalation_trigger: 1 +escalation_status: withdrawn-before-sending +date_fixed: "2026-08-19" +last_checked: "2026-08-20T20:44:46Z" +next_check: "2026-08-20T20:44:46Z" +cadence: instant +clean_streak: 0 +graded_by: risk-nexus +ruling: RISK-RULING-2026-08-19 +--- + +# RISK-F-0001 — flex-auth authenticates no caller on the decision surface + +## What is true + +`POST /v1/check` and `POST /v1/batch_check` authenticate no caller. Any +workload with network reach to the ClusterIP Service can assert any subject and +any tenant and receive an authoritative **allow**. + +`flex-auth` is the estate's authorization oracle. Every service that delegates +a decision to it is relying on an answer that anyone able to reach the pod can +obtain for any identity they care to name. + +Self-reported by `flex-auth` as `A0` on their own inbound surface, in their +Tenancy Posture review. Their words: "flex-auth is the estate's authorization +oracle and it trusts its callers completely." + +## How it was found + +Not by a probe, an incident, or an alert. By `flex-auth` assessing themselves +against the Tenancy Posture A ladder during a review they were asked to do — +and their own note says they did not know they were carrying it. + +That provenance matters for triage: nothing was watching for this, and nothing +would have found it. It has presumably been true for as long as the endpoint +has existed. + +## Exposure, as far as the reporter stated it + +- The Service is `ClusterIP`, so reach requires a workload inside the cluster. +- No claim was made that network policy restricts which workloads can reach it, + and this record does not assume one. **If a default-deny NetworkPolicy fronts + the service, that materially changes the exposure and should be verified + rather than inferred** — `flex-auth` did not state it either way, and I have + not checked, because doing so would be reporting on a system I do not own. + +## What makes it worse than a single service's defect + +A false allow from this endpoint is not confined to `flex-auth`. It is the +answer other services act on. `tenant-engine` separately reports that its own +mutations are authorized by `flex-auth` and that direct authority over its rows +would mean "privilege escalation across NetKingdom rather than data tampering +confined to one store". The same reasoning applies to a forged allow. + +## Owner and state + +`flex-auth` owns the fix and has tracked it as `FLEX-WP-0015-T02`, to ship +through the staged-promotion path rather than a direct apply. They classify it +as the only urgent item of their five follow-ups. Nothing is asked of them by +this record beyond what they have already committed to. + +## What this repo is asked to decide + +1. **Severity.** Not the reporter's to set. +2. **Disclosure.** Build mode is currently public-by-default, and this is + precisely the class of finding where that stops being obviously right — a + live authorization bypass in the service every other service trusts. The + controlled-disclosure scheme this repo anticipates does not exist yet, so + the choice today is publish or hold, with no mechanism between them. +3. **Escalation.** Whether this reaches the operator personally. The candidate + triggers in INTENT include "anything exposing real tenant data" — this + exposes the decision that governs access to it, which may or may not be the + same thing, and that judgement is this repo's. + +## Related, reported at the same time and not yet filed + +Three further defects surfaced from the same review round. They are recorded +here so they are visible, not filed as findings, because filing them was not +asked for: + +- `tenant-engine` — `events()` returns the entire event log unfiltered. A live + cross-tenant read at `E2`. +- `audit-core` — read path applies no tenant filter; a credential with + `may_read` can read any tenant's events. Bounded by deployment (`may_read: + false` on the production sender) and not by code. Tracked `AUDIT-WP-0008-T04`. +- `apps-pg` (`railiance-platform`) — no backup configured at all: no + `barmanObjectStore`, no retention policy, `BestEffort` QoS. `R0` there means + no recovery, not merely no erasure policy. + +All four were found the same way, by repos reading their own code against a +ladder, within a day of each other. That is a fact about the estate's +observability worth carrying into triage: **four live defects, none found by +monitoring.** + +## Register ruling — 2026-08-19 + +`critical` (`I4` × `L3`, no fidelity modifier), embargoed until +`FLEX-WP-0015-T02` ships, **escalated** to the operator on trigger 1. + +The question this finding put — whether governing access to tenant data counts +as exposing it — is answered **yes**. An authorization oracle that can be +forged is not one step removed from the data; it is the step. + +Impact is `I4` because a forged allow does not stay here: it is the answer +other services act on, and `tenant-engine` has stated what direct authority +over its rows would mean. Likelihood is `L3` — the normal working set, inside +the cluster, no additional step — and the register follows the reporter in +neither assuming a default-deny NetworkPolicy nor assuming its absence. + +No fidelity modifier: this endpoint answers honestly about a caller it never +checked. The false-record hazard lives in `RISK-F-0002`'s constraint, where a +consumer of this endpoint would begin signing records asserting an +authorization that was never made. **That constraint binds this finding's +remediation**: `ops-warden`'s `policy.enabled` must not be turned on until +`/v1/check` authenticates its callers, and the ordering is + +``` +flex-auth warn-only -> ops-warden gate presents its SA token -> logs clean + -> flex-auth fail-closed -> ops-warden policy.enabled: true +``` + +Nothing further is asked of `flex-auth` beyond what they have committed to, +except one fact only they can supply: **is there a default-deny NetworkPolicy +in front of the Service?** It is the single fact that would most change this +grade, and it is the first question at review. + +Reasoning: `docs/rulings/2026-08-19-first-grading.md`. + +## Reviews + +- **2026-08-19** — graded. Next review 2026-08-26 (`critical` → 7 days). + Open at review: the NetworkPolicy question; whether `FLEX-WP-0015-T02` has + moved; whether the embargo still holds. + +## Re-grade and close — 2026-08-19 (same day) + +**Corrected from `critical` to `high`, and closed as `fixed`.** Both changes +come from messages that were already in this repo's inbox when the first grade +was set. The register graded before it read them. + +**The exposure was narrower than graded.** `flex-auth` answered the +NetworkPolicy question on 2026-08-18: both production Deployments ship a +NetworkPolicy in the same manifest, and it is *narrower* than default-deny — +ingress restricted to one `namespaceSelector` plus one `podSelector` on port +8080, egress empty. In force since before the period the `A0` describes. So the +reachable set was never "any pod in the cluster"; it was the single paired +workload per Deployment. + +That is `L2`, not `L3`. Impact stays `I4` — what a forged allow reaches does +not change — so the grade is `high`. `flex-auth` also corrected their own +earlier phrasing to `ops-warden` in the same message, unprompted, and that +correction is why the fact reached this register at all. + +Three caveats `flex-auth` asked to be recorded rather than taken from them, and +they are why the grade did not fall further: label selectors are network +position, not identity; the policy could not bind the asserted `resource.system`, +which is what made cross-system impersonation possible; and enforcement depends +on a CNI they could not verify from a cluster where `kubectl` returned +`Unauthorized`. They said so rather than letting a manifest stand in for a probe. + +**It is fixed.** On 2026-08-19 `flex-auth` reported both production Deployments +enforcing ADR-0004 TokenReview, with live unbound-request probes returning 401 +rather than a decision, on both the `user-engine` and `tenant-engine` pins. +`FLEX-WP-0015` is finished and `tenancy.current.A` is 2. That is a probe +against the running system, which is the standard `docs/method/review.md` sets +for closing: something concrete read, not something been told. + +**Disclosure flips to `public`.** The embargo condition was "FLEX-WP-0015-T02 +ships to production" and it is met. Handover to `policy-nexus` is the next +step and is not done yet. + +**The escalation is withdrawn without being sent.** It was `pending-operator` +for roughly four hours, and the fix landed first. Withdrawing it is correct — +escalating a fixed defect makes the operator the queue for history — but the +register does not get to be pleased about it. The escalation would have been +sent on facts that were already stale, and only luck put the fix on the same +day. + +**What this cost, recorded because it is the register's own defect.** The +NetworkPolicy answer arrived 2026-08-18. The fix notice arrived 2026-08-19 +at 12:35. The first grading ran at 21:14 the same day, on neither. Reading the +inbox is now step 0 of grading and question 0 of every review — see +`docs/method/review.md` — and this finding is the case that bought it. + +## Reviews + +- **2026-08-19** — re-graded `high`, closed `fixed`, disclosure `public`, + escalation withdrawn. Remaining: handover to `policy-nexus`; the CNI + enforcement question is `flex-auth`'s and no longer this finding's. +- **2026-08-20** — not clean: Publication handover requested; publication front-matter applied and the wait on policy-nexus typed. Cadence instant → instant; checked again immediately. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RISK-F-0008.md b/docs/evidence/2026-10-11-served-publication-history/sources/RISK-F-0008.md new file mode 100644 index 0000000..eef8247 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RISK-F-0008.md @@ -0,0 +1,364 @@ +--- +id: RISK-F-0008 +type: finding +title: "The legal basis for retaining audit facts against an erasure request has been assumed, never established" +status: accepted +owner: risk-nexus +reported_by: audit-core +reported_via: audit-core +routed_by: audit-core +date_reported: "2026-08-18" +date_filed: "2026-08-19" +system: audit-core +environment: production +fix_owner: risk-nexus +fix_tracking: unset +related: [RISK-F-0005] +supersedes: RISK-N-0002 +# Graded by risk-nexus 2026-08-19 — docs/rulings/2026-08-19-third-grading.md +severity: medium +severity_at_production: high +impact: I3 +likelihood: L2 +fidelity_modifier: false +production_rescore: true +disclosure: public +publication: published +publication_id: risk-f-0008-audit-retention-legal-basis +publication_path: "findings/audit-retention-legal-basis/v1/index.html" +publication_url: "https://policy.coulomb.social/findings/audit-retention-legal-basis/v1/" +published_on: "2026-09-01" +publication_subtitle: "The estate retains personal data in audit records on grounds nobody had actually established. Published as a question, because it is one." +revision: "published-1" +last_reviewed: "2026-08-20" +review_interval: 6m +escalation: answered +escalation_trigger: 2 +escalation_status: answered +accepted_by: the-custodian +accepted_on: "2026-08-20" +accepted_until: "the estate holds a real person's data, or a counterparty requires a stated position" +escalation_answered: "2026-08-20" +escalation_answered_by: the-custodian +escalation_act: rule +decision: "identity in audit records: opaque subject ids preferred, agent identifiers where possible, operator credentials only where necessary, policy decisions tracked to the responsible party; zone-level privacy guarantees may raise the floor" +outstanding: "a defensible retention period per category (waits on audit-core's co-residency horizon), and the trigger list for buying an external answer" +determination: RISK-REG-0001 +last_checked: "2026-08-20T21:36:44Z" +next_check: "2026-08-20T21:36:44Z" +cadence: instant +clean_streak: 0 +waiting_on: + - who: audit-core + what: "does a keyed commitment restore erasability without breaking chain verification; what is the platform-pg co-residency horizon" + since: "2026-08-20" + would_change: "a working keyed commitment narrows RISK-REG-0001 to retained-by-obligation categories only" + default: "encrypt-then-hash recorded as the only known route, and the retention period recorded as unstateable" + default_at: "2026-11-17" +graded_by: risk-nexus +ruling: RISK-RULING-2026-08-19-C +--- + +# RISK-F-0008 — the exemption nobody has established + +## What is true + +`audit-core` holds audit evidence across tenants, targets `R2` on the Tenancy +Posture retention ladder, and has declared `R4` — verified erasure — +unreachable by design. The technical reasoning is sound and documented +(`audit-core/docs/erasure-and-audit.md`, framework Decision 4.5.3): +crypto-shredding would destroy the evidence the service exists to hold, and +their integrity chain commits to a SHA-256 of the cleartext record, which +survives key destruction as a confirmation oracle against low-entropy audit +rows. Destroying a key does not erase content a surviving commitment can still +be tested against. + +The consequence is that if an Article 17 request arrives naming a data subject +in the audit trail, `audit-core` has no mechanism. The answer would rest on +audit evidence being exempt — legal obligation, or legitimate interest in fraud +and security investigation. + +**Those grounds are ordinary. Nobody in this estate has actually reached them.** +`audit-core` routed the question here on 2026-08-18 rather than absorbing it, +saying plainly that they are not competent to answer it and that they have been +assuming it. §19.11 of the framework says the same in its own words: the legal +basis for retaining audit facts remains a risk/legal question outside the +framework. + +## Why this repo owns it + +This is the first finding where `fix_owner` is `risk-nexus`. + +`INTENT.md` moved regulatory intake here from `policy-nexus` on 2026-08-17, +precisely because deciding what a rule demands of us is a judgement about risk +rather than an act of publishing. `audit-core` routed it by both available +routes and asked for an owner rather than an opinion. Refusing it would be this +repo declining its own remit. + +What this repo owns is the **record**: what the source says, when, and what +therefore is or is not established. It does not own legal advice — `INTENT.md` +is explicit — and it does not own the redesign. If the basis does not hold, +`audit-core` owns encrypt-then-hash at accept time, and that is not +retrofittable onto events already accepted. + +## The three questions, as asked + +1. On what basis does the estate retain personal data inside audit records + against an erasure request, and does that basis hold for the categories + `audit-core` stores? +2. Does it hold across the full 30-day recoverable window and beyond, given + that at `P1` the real erasure horizon is the maximum across every + co-resident on `platform-pg`, not the value `audit-core` declares? +3. If it does not hold, `R4` is urgent rather than theoretical, and the answer + is a substantial redesign with a long lead time. + +## Register ruling — 2026-08-19 + +`medium` today (`I3` × `L2`), `high` at production, `public`, **escalated on +trigger 2**. + +`I3`: an unmet retention obligation in the audit store crosses from a technical +question to an obligation with an outside counterparty, and the remediation is +a non-retrofittable redesign rather than a patch. `L2`: no request has arrived +and the estate holds no real data subject's records yet, but the trigger is +somebody else's to pull and needs no foothold here. + +`production_rescore: true`. The likelihood of an Article 17 request is a +function of having real users; that is exactly what production means. + +**Escalation, trigger 2** — "creates or reveals an obligation with an outside +counterparty". It reveals one. The estate cannot decide unilaterally that this +obligation is small, and the operator is the only party who can commission an +answer that is more than an assumption. The ask is narrow: authorise someone to +establish the basis, or record that the estate knowingly runs on the assumption +and for how long. + +**Disclosure `public`.** Nothing here shortens a path to a defect: it is a +question about a legal basis, published as a question. `audit-core`'s technical +reasoning is already written down and worth reading. + +## How it got here + +Ruled a note on 2026-08-19 (`RISK-N-0002`) on the reasoning that no obligation +exists yet. That ruling was made without reading `audit-core`'s message, which +had been in this repo's inbox since 2026-08-18 and asks specifically for an +owner. The note was wrong on the second floor test: recording this *does* +change a decision, because the redesign it might force cannot be retrofitted +and therefore has to be decided early or not at all. + +`RISK-N-0002` is superseded by this record. + +## Reviews + +- **2026-08-19** — promoted from note, graded, escalated. Open at review: + has the basis been established or the assumption recorded; has anything + changed about what categories `audit-core` stores. + +## Suggested disposition — 2026-08-20, proposed by risk-nexus + +Offered because this repo owns the finding and the operator asked for a +direction. It is not legal advice, and this repo cannot make it into one: what +follows is a *route to an answer* and a hedge against the answer being no. + +### The reframe: the expensive thing is not the legal question + +`audit-core` asks whether the exemption holds. That question is cheap to +answer badly and expensive to answer properly, and the temptation is to +schedule the proper version and wait. + +But the cost of a "no" is not fixed — it grows daily. The remedy they name, +encrypt-then-hash at accept time, cannot be retrofitted onto events already +accepted. **Every day the estate accepts events under the current scheme, the +un-erasable set grows by one day.** So the decision that actually needs taking +now is not "is it exempt" but "do we keep manufacturing records we could never +erase while we find out". + +That splits the finding into two decisions with very different prices. + +### 1. Establish the basis internally, now, for the cost of an afternoon + +Not a legal opinion — a **written determination** that says which ground is +being relied on, for which category of data, and for how long. Today the +estate has no such document; that is the whole finding. + +The shape it should take, per category of personal data in the audit trail: + +| Category | Likely ground | The part that is actually arguable | +| --- | --- | --- | +| Operator and agent identifiers | Art 6(1)(f) legitimate interest in security, with Recital 49 squarely on point | little — this is the ordinary case | +| Counterparty or end-user identifiers in event payloads | Art 17(3)(e), defence of legal claims; Art 6(1)(f) | **duration**, not existence | +| Commercial records that happen to pass through audit | Art 17(3)(b) plus German §257 HGB / §147 AO retention | scope — retention duties cover books and invoices, not application logs generally | + +Where such determinations usually fail is **not** the ground. It is the +retention period: a blanket "we keep audit forever under legitimate interest" +is much weaker than "we keep these fields for N months because X". That lands +precisely on `audit-core`'s question 2 — the real horizon being the maximum +across every co-resident on `platform-pg` rather than the declared value. + +Recording the determination converts an assumption into a position that can be +argued with. That is what this register exists to produce, and it does not +require a lawyer to write down. + +### 2. Stop the un-erasable set from growing — a cheaper hedge than the redesign + +`audit-core`'s stated obstacle is precise and correct: their chain commits to +`SHA-256(cleartext)`, audit records are low-entropy, so the retained hash +survives key destruction as a confirmation oracle. Guess, hash, compare. + +The oracle exists because the commitment is over cleartext with no secret in +it. A **keyed commitment** removes it: replace the digest with an HMAC (or a +hash over record plus a high-entropy per-subject salt) where the key or salt +lives outside the audit store and is destroyable per subject. + +What that buys, and why it is cheaper than the redesign they costed: + +- Destroying the per-subject key makes the commitment untestable — no guess + can be confirmed. That is crypto-shredding restored, which their analysis + correctly found unavailable under a plain hash. +- The integrity chain still verifies. It chains over commitment values, and + those persist after key destruction; what is lost is the ability to + re-derive a commitment from cleartext, which is exactly what erasure means. +- It is a change at accept time only. No re-processing of stored events, no + new storage layer, no change to the read path. + +This is a suggestion to `audit-core`, not an instruction, and they own whether +it is sound — they know their chain and this repo does not. The claim worth +testing with them is narrow: **does a keyed commitment restore erasability +without breaking chain verification?** If yes, the expensive redesign becomes a +contingency rather than a plan, and the daily accrual stops. + +### 3. Buy the real answer only when something triggers it + +An external determination costs money and needs a real question. Propose three +triggers, any of which fires it: + +- the estate first holds a real person's data; +- a counterparty contract requires a stated erasure position; +- an actual Art 17 request arrives. + +Until one fires, the internal determination plus the hedge is a proportionate +posture, and `severity_at_production: high` plus `production_rescore: true` +already guarantee this is re-read before production completes. + +### What this repo would record if the operator agrees + +`status: accepted` with the determination attached, `escalation` answered as +`rule`, and the review kept at 90 days. The finding stays open and visible +until the determination exists — an accepted risk with no written basis is the +same assumption it started as, wearing a different word. + +### Also worth saying, because it is the cheapest fix of all + +Every field of personal data that never enters the audit trail is a field with +no erasure question. Where an opaque subject identifier would carry the same +evidentiary weight as a name or an address, the identifier is strictly better, +and that is a `audit-core` design choice available today at no legal cost. + +## Operator decision — 2026-08-20: minimise the identity, keep the accountability + +The custodian ruled on what goes into an audit record, which is the half of +this finding that shrinks the question rather than answering it: + +1. **Opaque subject identifiers are preferred.** Where an opaque id carries the + same evidentiary weight as a name or an address, it is the id that goes in. +2. **Agent identifiers where possible.** Agents act; attribute to the acting + agent identity rather than to a person behind it. +3. **Operator credentials only where necessary.** Not as a convenience, not as + a default — where the record genuinely requires the operator. +4. **Policy decisions are tracked to the responsible party.** Accountability is + preserved by linking a decision to who is answerable for it, not by + retaining personal data in the record itself. +5. **Zone guarantees may raise the floor.** If a zone establishes additional + privacy, pseudonymity or anonymity guarantees, those apply — the current + level is not a permanent ceiling. That work is `zone-engine`'s + (`ZONE-WP-0001`), and this finding should be re-read when a zone lands one. + +**Why this is more than a preference.** Personal data that never enters the +audit trail has no erasure question, no exemption to establish, and nothing to +argue about with a regulator. Points 1-3 shrink the population the legal basis +has to cover; point 4 is what stops that shrinking from costing accountability, +which is the usual objection to minimising an audit log. + +It also changes the shape of the accrual problem. The un-erasable set still +grows daily, but each day's records now carry less that would need erasing — +so the cost of a "no" answer falls with every event accepted under the new +rule rather than rising. + +**What is still outstanding**, and stays escalated: + +- The **written determination** of the retention basis — which ground, for + which category, for how long. `risk-nexus` owns writing it; it needs no + further authorisation and is scheduled into the next workplan. +- The **trigger list** for buying an external answer (first real person's data, + first counterparty contract requiring a stated position, first Art 17 + request). Proposed, not yet ruled on. + +The escalation is therefore `partially-answered`, not closed. `make check` will +keep listing it. + +**Routed to `audit-core` on 2026-08-20**, together with the keyed-commitment +question — which remains theirs to judge, because they know their chain. + +## The determination exists — 2026-08-20 + +`docs/regulatory/RISK-REG-0001` (`audit-retention-basis.md`). The estate now +has a written position rather than an assumption, which was this finding's +substance. + +What it says, in short: Art 6(1)(f) with Art 32 for operator and agent audit +records; Art 17(3)(e) for records evidencing a counterparty transaction; +Art 17(3)(b) only where a commercial or tax retention duty independently +applies, and not extended to application logs generally. + +**The weak part is duration, not existence**, and the record says so rather +than sounding confident. A position of the form "we keep audit forever because +it is audit" is the one that fails; a period per category is what holds. The +estate does not have one yet, and the reason is `audit-core`'s own question 2 — +at `P1` the real horizon is the maximum across every co-resident on +`platform-pg`, not the declared value. **That infrastructure fact is the most +likely point of failure in the whole position.** + +The operator's minimisation ruling improves this materially: it shrinks the +category whose retention is hardest to justify, leaving mostly the row where +the ground is strong. A weak argument avoided by holding less data beats a +strong one relied upon. + +The finding stays open. What remains is a retention period per category, which +waits on the co-residency horizon, and the trigger list for buying an external +determination. The record is reviewed every 90 days with this finding, or +immediately on any trigger. +- **2026-08-20** — not clean: The determination now exists: RISK-REG-0001 states the grounds per category and names duration as the weak point. Cadence instant → instant; checked again immediately. + +## Operator decision — 2026-08-20: no external determination, and a policy set instead + +Ruled: **the estate will not buy an external determination while it is +building.** The internal determination (`RISK-REG-0001`) stands as the recorded +position, and the finding moves to `accepted` — deliberately carried, with a +named accepter and a condition that ends it. + +That is not the same as the trigger list being rejected. The triggers survive +as what ends the acceptance: a real person's data, or a counterparty requiring +a stated position. What was declined is spending money in advance of either. + +**The compensating control is the thing that makes this defensible.** Rather +than defer the question, the operator directed that the estate **define and +keep a set of legal policies for reuse**, because future work contexts will +need specific positions in place and should retrieve them rather than research +them. + +`docs/regulatory/policies/` now catalogues thirteen, keyed by activation +condition. Two of them turned out to be **already active and unowned**: +commercial and tax retention (`RISK-POL-0009`), and the e-invoicing receiving +obligation (`RISK-POL-0012`), live since 2025 with no system in the estate +named as the receiving point. + +Finding an unnoticed live obligation in the first hour of building the +catalogue is the argument for having built it. The reason this repo exists is +that regulation was previously "consulted and discarded"; a set that answers +"what applies if we do X" before anyone does X is the opposite of that. + +**Still open under the acceptance**, and unchanged by this ruling: `audit-core` +on whether a keyed commitment restores erasability, and the `platform-pg` +co-residency horizon that decides whether the stated retention periods are +achievable. An accepted risk still gets checked. +- **2026-08-20** — not clean: Trigger list ruled: no external determination in build mode; accepted with the legal policy set as the compensating control. Cadence instant → instant; checked again immediately. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-DEPENDENCIES.md b/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-DEPENDENCIES.md new file mode 100644 index 0000000..b6779d4 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-DEPENDENCIES.md @@ -0,0 +1,139 @@ +--- +id: RISK-METHOD-DEPENDENCIES +type: method +title: "Waiting: how this register depends on other people without becoming a queue" +status: adopted +owner: risk-nexus +adopted: "2026-08-20" +workplan: RISK-WP-0001 +review_interval: 6m +disclosure: public +revision: "adopted-1" +last_reviewed: "2026-08-20" +--- + +# Waiting + +By 2026-08-20 the register had accumulated nine waits in four days, one of them +four hops deep: `RISK-F-0003`'s embargo waited on `RISK-F-0009`, which waited +on `railiance-platform` fixing a deny set, which waited on someone verifying it +against live OpenBao, which waited on a credential nobody has. + +Nothing in that chain is anyone's fault, and every link was individually +reasonable. That is exactly why it needs a rule: deep dependencies are not +built deliberately, they accrete one sensible step at a time. + +## The principle + +**The register never waits to decide. It decides, and revises when told.** + +A wait is a *refinement* pending, not a decision pending. If the register +cannot act until someone answers, the register has made that person's silence +into its own paralysis — and `INTENT.md` is explicit that a register nobody +acts on is worse than none. + +## The four rules + +### 1. Every wait is typed + +No record may say "waiting on X". A wait carries six things: + +```yaml +waiting_on: + - who: tenant-engine + what: "confirm or correct the unfiltered events() read; open fix tracking" + since: "2026-08-19" + would_change: "grade rises if the log carries payload rather than metadata" + default: "grade stands as recorded; absent fix tracking noted as a stall" + default_at: "2026-09-03" +``` + +`would_change` is the discipline. If nobody can say what the answer would +change, there is nothing being waited for, and the wait should be deleted +rather than carried. + +### 2. Depth one + +**A record may wait on at most one other record, and never on a record that is +itself waiting.** + +When the chain would go deeper, the far end is cut: the record takes its own +default and says which unresolved thing it declined to wait for. Two hops is +the point at which nobody can see the whole line any more, and a wait nobody +can see is indistinguishable from a thing that was dropped. + +Applied 2026-08-20: `RISK-F-0009`'s embargo condition was +"verified against live OpenBao", which no one in the estate can currently do. +It now lifts on **`railiance-platform` reporting the coverage**, with live +verification recorded as a refinement rather than a condition. That cut the +`RISK-F-0003` chain from four hops to two. + +### 3. Defaults are dates, and defaults are pessimistic + +Every wait resolves on a date whether or not anyone answers. The default is +**the reading the stated facts already support** — never a hold, never a +downgrade earned by silence. + +This is what removes the incentive to wait. Silence does not buy an owner a +softer grade or a quieter register; it costs them the grade the evidence +supports, which is usually the one they would want corrected. Answering is how +a grade improves, and that is the right shape for the incentive. + +The register says so in advance, to the owner, in writing. A default nobody was +warned about is an ambush, not a rule. + +### 4. A condition naming somebody else's action carries a date beside it + +"Embargo lifts when X ships" is a dependency with no end. "Lifts when X ships, +or is re-decided on 2026-09-20" terminates. + +Re-decided is not the same as lifted — the re-decision may extend the hold with +a fresh reason. What it may not do is extend by default, which is how holds +quietly become permanent. + +## What this does not solve + +Some dependencies are real and cannot be defaulted away. Nobody can verify an +OpenBao policy without a token, and no rule here conjures one. + +What the rules do is stop that from propagating: the register grades on what is +stated, records what it could not verify, and keeps its own position +independent of the blockage. `docs/method/verification.md` bounds what this repo +can establish itself, and every grade resting on a document rather than a probe +says so on its face. + +## Waits outlive statuses + +A finding that is `fixed` can still owe something. `RISK-F-0001` was closed on +2026-08-19 and is still waiting on `policy-nexus` for the publication entry +that turns `disclosure: public` into an actual address. + +So the waiting list is built from **every** finding, not from the watched ones. +A closed record with an open obligation is precisely the thing that goes quiet, +because nothing is prompting anyone to look at it any more. + +## A wait is not a findings-only idea + +Adopting a rule is nobody's finding. Registering a canon kind is nobody's +finding. Publishing a document is nobody's finding. All three are obligations +someone owes, and each one that lived outside the mechanism was invisible to it. + +So waits attach to **any** record this repo keeps — findings, regulatory +records, workplans — and `make check` reports them together. `RISK-WP-0001` +carries two: the escalation rule that is producing decisions while still +`status: proposed`, and the canon question about what kind of thing a finding +is. + +The first of those has the most uncomfortable default in the register, and it +points inward: **if the rule is not adopted by 2026-09-17, it is recorded as de +facto in force but unratified, and every escalation sent under it says so on +its face.** That is worse than either adopting or rejecting it, which is the +point — a draft that quietly governs is the thing this register was built to +notice. + +## Where the waits are visible + +`make check` reports every open wait with its age, its owner and its default +date, flags any default that has come due, and flags any wait that points at a +record which is itself waiting — a depth-two violation, caught by tooling +rather than by someone noticing. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-DISCLOSURE.md b/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-DISCLOSURE.md new file mode 100644 index 0000000..2ae7eb6 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-DISCLOSURE.md @@ -0,0 +1,144 @@ +--- +id: RISK-METHOD-DISCLOSURE +type: method +title: "Disclosure: publish now, hold, or restrict" +status: adopted +owner: risk-nexus +adopted: "2026-08-19" +workplan: RISK-WP-0001-T02 +review_interval: 6m +disclosure: public +revision: "adopted-1" +last_reviewed: "2026-08-20" +--- + +# Disclosure + +Whether and when a finding is published. `policy-nexus` is the surface; this +document decides what it is handed. + +## What disclosure is not + +**A finding file in this repo is not a publication.** This repo is a private +checkout on a private forge. Holding a finding means not routing it to +`policy-nexus`; it does not mean hiding it from the estate, from the owning +repo, or from the operator. Every state below is fully visible internally. + +## The states + +| State | Meaning | Entry condition | +| --- | --- | --- | +| `public` | Published through `policy-nexus` now. | The finding is fixed, or reading it gives no one an advantage they do not already have. | +| `embargoed` | Held, with a stated condition that lifts it. | Live, unfixed, and the text would help someone reach the defect faster than they could without it. | +| `restricted` | Held with no expected lift. | Publication would remain harmful after the fix — third-party material, a named person, or a credential-shaped detail that survives remediation. | + +There is no fourth state and no `unset` after grading. A finding whose +disclosure has not been decided is an ungraded finding. + +## Embargo is a record, not a silence + +`INTENT.md` requires the record that a delay was deliberate rather than a +document quietly going missing. An `embargoed` finding therefore carries: + +```yaml +disclosure: embargoed +embargo_condition: "FLEX-WP-0015-T02 ships to production" +embargo_since: "2026-08-19" +embargo_review: "2026-08-26" +``` + +`embargo_condition` must be an event someone can observe, not a mood. "Until +it is safer" is not a condition. `embargo_review` follows the finding's +severity interval from `docs/method/review.md`; when it passes, the embargo is +re-decided, not extended by default. + +An embargo that has outlived two consecutive reviews without its condition +moving is itself a finding — the remediation has stalled, and the hold is now +doing the work the fix was supposed to do. + +## The build-mode deferral, re-taken + +`INTENT.md` recorded controlled disclosure as deferred to production, reasoning +that build mode has no users to expose. `RISK-F-0001` arrived and tested it: a +live authorization bypass in the service every other service trusts, with +publish-or-forget as the only available choice. + +**The deferral is narrowed, not kept and not abandoned.** + +What was right about it: build mode does have no consumers to protect, and +building an embargo *mechanism* — timed release, staged notification, +coordinated disclosure with third parties — before there is anyone to +coordinate with would be machinery for its own sake. + +What was wrong about it: it conflated the mechanism with the decision. The +argument for publishing in build mode is that there are no users to expose. +That argument says nothing about attackers, and `RISK-F-0001` is exactly the +class where the two come apart — the finding names an unauthenticated +decision surface and the service that carries it. Publishing that while it is +live helps precisely one kind of reader. + +So the ruling is: + +1. **Build-mode default stays publish.** Architecture, method, fixed findings, + and findings whose exposure is already bounded go out. The estate publishing + what it knows is wrong remains the norm and does not need a case made for it + each time. +2. **Live-and-reachable is the exception, and it exists now.** A finding that + is unfixed *and* whose text shortens the path to the defect is `embargoed` + until the fix lands. That is the missing middle `INTENT.md` said did not + exist. It costs one front-matter field and a line in `REGISTER.md`. +3. **The mechanism stays deferred.** No timed release, no coordinated + disclosure protocol, no notification tiers. Those wait for real users, as + originally reasoned. What is not deferred is the *decision*, because + `RISK-F-0001` demonstrated the decision is needed before the machinery is. + +This is a decision of this repo, taken 2026-08-19 with `RISK-F-0001`, +`RISK-F-0002` and `RISK-F-0003` in hand rather than hypothetically. It is +revisable, and the production transition is the scheduled moment to revisit it. + +## Test for "shortens the path" + +Ask: does the finding tell a reader something that materially reduces the work +of reaching the defect, beyond what reading the repo would give them? + +- A file path and line number in a private repo — no, that is already there. +- "This surface authenticates nobody, here is its cluster address" — yes. +- "These five named lanes vend real secret values without the boundary firing" + — yes. +- "This system had no backups configured" — no, once backups exist; yes, while + they do not, because it names when destruction is unrecoverable. + +When the answer is genuinely unclear, embargo and re-decide at the review. The +cost of a wrong embargo is a delayed publication; the cost of a wrong publish +is not recoverable. + +## Publication happens elsewhere + +A `public` finding is handed to `policy-nexus` under its publication contract +and gets a permanent address there. This repo never serves it and never +edits it after handover; corrections go through the same route as the original. + +## The standing route, when an embargo lifts + +`RISK-WP-0002-T03`. Written down because publication will arrive in a trickle +as conditions clear, not as a batch, and a route improvised each time is a +route that eventually is not taken. + +1. **The check that lifts the embargo records it.** `make checked` on the + finding, with the lift as the reason. An embargo lifting is never a clean + check — something moved. +2. **The finding gets publication front-matter**, in the shape `policy-nexus` + already requires of everyone: `owner`, `revision`, `last_reviewed`, + `review_interval`. No body rewrite. +3. **This repo asks `policy-nexus` for an entry**, giving `source_repo`, + `source_path`, a proposed `canonical_path` under `findings///`, + and a one-line subtitle. Addressing and permanence are theirs + (`POLICY-NEXUS-WP-0001`); this repo does not invent a scheme. +4. **`publication: published` is recorded back on the finding**, with the URL. + A finding that says `public` but has no address is a claim, not a + publication — the same class of error as a backup nobody has restored from. + +The contract publishes **a file from the owning repo**, so what is handed over +is exactly what a reader gets. That makes the whole-versus-summary decision +(T01) a decision about what a finding *file* contains, not about how it is +rendered. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-REVIEW.md b/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-REVIEW.md new file mode 100644 index 0000000..7173344 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-REVIEW.md @@ -0,0 +1,199 @@ +--- +id: RISK-METHOD-REVIEW +type: method +title: "Review and expiry: what happens when nobody looks" +status: adopted +owner: risk-nexus +adopted: "2026-08-19" +workplan: RISK-WP-0001-T04 +review_interval: 6m +disclosure: public +revision: "adopted-1" +last_reviewed: "2026-08-20" +--- + +# Review and expiry + +`INTENT.md`: a finding that has sat untouched past its review date is itself a +finding. Silence is not resolution. This document makes that operable. + +## The cadence ladder + +Operator ruling, 2026-08-20. **Intervals are not set by severity. They are +earned by outcomes.** + +A finding is checked, and the check comes back clean or it does not. Clean +climbs one rung. Anything wrong drops straight back to the bottom. + +| Rung | Wait before the next check | +| --- | --- | +| `instant` | now, and again immediately until a check comes back clean | +| `1h` | one hour | +| `8h` | eight hours | +| `24h` | one day | +| `48h` | two days | +| `96h` | four days | +| `7d` | one week | +| `14d` | two weeks | +| `1mo` | one month | +| `1q` | one quarter — **the ceiling; nothing is ever checked less often than this** | + +Two rules and one escape: + +- **Climb on clean.** One rung per clean check, never two. +- **Reset on anything wrong.** Not a slide down one rung — straight to + `instant`. A matter that has just moved has no track record, whatever it had + before. +- **The operator may defer.** An `instant` finding can be deferred to a stated + future date by explicit operator decision, recorded as `deferred_to`. That is + the only way out of the bottom rung other than a clean check, and it is a + decision with a name on it rather than a check quietly not happening. + +### Who checked, and when anyone last did + +`RISK-WP-0005-T06`. Two defences against the rung telling a lie: + +- **`checked_by` on every check.** `record_check.py` writes it. A rung earned + by nobody in particular is visible as such. +- **A heartbeat.** If nothing anywhere in the register has been checked for two + days, `make check` says so **before anything else**. A `1q` rung means + "stable for a quarter" and "nobody looked for a quarter", and those read + identically from the outside — the heartbeat is what separates them. + +### The rung is the signal + +This is the point of the design, not a side effect. **The cadence a finding +sits on is a statement about how stable the estate has been on that matter.** + +`RISK-F-0002` at `1q (9)` says the signing gate has been examined ten times +over three months and nothing has moved. `RISK-F-0002` at `instant (0)` says +something changed within the day. The number carries information that no +severity does — severity says how bad it would be, the rung says how settled +it is — and the two are independent. A `low` finding that keeps resetting is +telling you something a `critical` one at the ceiling is not. + +It is also self-correcting in the direction that matters. Anything volatile +gets attention often, automatically, without anyone deciding it deserves it; +anything genuinely quiet stops consuming attention, without anyone deciding to +stop looking. Neither of those judgements has to be made by a person who might +be wrong or busy. + +### What "clean" means + +A check is **clean** when nothing about the assessment moved: the grade still +holds, every stated blocker is still true, the fix state is unchanged, the +disclosure state is still right, and no new fact has arrived. + +A check is **not clean** when any of those moved — including when they moved in +a good direction. `RISK-F-0001` being fixed is not a clean check; it is a large +change, and the next check comes immediately. Good news resets the ladder +exactly like bad news, because the ladder measures stillness, not health. + +### Starting position + +Every finding starts at `instant`. A register with no check history has no +grounds to wait, and the first clean check is what buys the first hour. + +The whole register sat at `instant (0)` on 2026-08-20, which is correct and +temporary: everything in it had been graded, re-graded or ruled on within the +preceding day. + +## What a review is + +Five questions, answered in writing on the finding. It takes minutes; it is not +an investigation. + +0. **Has the inbox said anything?** Read the messages addressed to this repo + before anything else. This is question zero because on 2026-08-19 the + register graded `RISK-F-0001` `critical` while two messages sat unread in + its own inbox — one narrowing the exposure, one reporting the fix. Both + changed the grade. A register that does not read its own inbox is guessing + with a straight face. +1. **Is the grade still right?** Re-read impact and likelihood against what has + changed. New facts move the grade in both directions. +2. **Is the blocker still true?** This is the one `RISK-F-0002` bought with + evidence: *a blocker is a claim about the world at a date*. Its own stated + blocker — "`flex-auth` is not deployed yet" — was invalidated within a day + by `RISK-F-0001`, and nothing would have re-checked it. Every review + re-checks every stated blocker. +3. **Has the fix moved?** Read the owner's tracking record, not our memory of + it. Confirm the record still exists and still refers to this defect. +4. **Is the disclosure state still right?** An embargo whose condition has + been met is published; an embargo whose condition has not moved in two + reviews is a stall. + +The finding gets a dated line under `## Reviews`, `last_reviewed` is updated, +and `review_by` is pushed by one interval. A review that changes nothing still +writes the line — "checked, nothing moved" is the evidence that the silence was +observed rather than accidental. + +## When a check is missed + +Overdue is not a status change on the finding. It is a fact about *this repo*, +and it surfaces in three places: + +- `make check` lists it under "Checks due", with how late it is and which rung + it is on. +- `REGISTER.md` shows the next check as **due**. +- A finding sitting at the bottom rung for more than fourteen days with no + movement fires escalation trigger 5. Bottom rung means it keeps failing or + keeps being skipped; fourteen days of that is a stall whichever it is. + +The register does not auto-escalate severity for lateness and does not +auto-close anything. Both would be the register lying about its own state to +make a number look better. + +## The production re-score + +Every finding carries `severity_at_production` alongside `severity` +(`docs/method/severity.md`). Where the two differ, the finding is flagged +`production_rescore: true`. + +On the day any part of the estate declares production readiness, every flagged +finding is re-scored before that declaration completes. This is not a review +date — it is an event, and it fires regardless of where the review dates +happen to sit. + +Until then, `make check` lists the flagged findings so the size of that +obligation is visible rather than discovered on the day. + +## Front-matter this adds + +```yaml +last_checked: "2026-08-20T05:40:00Z" +next_check: "2026-08-20T06:40:00Z" +cadence: 1h +clean_streak: 1 +production_rescore: true +deferred_to: "" # only by explicit operator decision +``` + +`next_check` is what the nag reads, and it is an absolute moment rather than a +duration, so nothing has to recompute an interval to know whether a check is +late. The rungs run in hours as well as days, so it carries a time. + +## Closing a finding + +A finding leaves `open` for exactly one of: + +- `fixed` — the owner's record shows the defect gone, and this repo has read + something concrete rather than been told. Publication follows if the + disclosure state was `embargoed`. +- `accepted` — the estate is deliberately carrying it. Requires who accepted + it, why, and what ends the acceptance. `accepted` is not closed: it stays on + the ladder forever, and it climbs like anything else. +- `mitigated` — the live gap is closed but the finding is not. `RISK-F-0003` is + the case: the boundary now fires, and the omission that let it not fire is + still there. Stays watched. +- `withdrawn` — the finding was wrong, or the defect never existed. Say which. + +**Any status the tooling does not recognise keeps the finding watched, and the +unrecognised word is reported.** `RISK-F-0003` arrived as `mitigated` on +2026-08-20, before that word existed here, and dropped silently out of +`make check` — a finding vanishing from the nag because someone used an +unfamiliar word is precisely the failure this register exists to prevent. The +tooling now fails loud instead of quiet. + +There is no `stale`, no `wontfix` and no silent expiry. A finding that nobody +will fix and nobody will accept stays `open` and keeps arriving in the nag, +because that is the true state. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-SEVERITY.md b/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-SEVERITY.md new file mode 100644 index 0000000..cd8ae77 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-SEVERITY.md @@ -0,0 +1,207 @@ +--- +id: RISK-METHOD-SEVERITY +type: method +title: "Severity: how bad, how likely, and what is not a risk at all" +status: adopted +owner: risk-nexus +adopted: "2026-08-19" +workplan: RISK-WP-0001-T01 +review_interval: 6m +disclosure: public +revision: "adopted-1" +last_reviewed: "2026-08-20" +--- + +# Severity + +This is `risk-nexus`'s judgement instrument. It is not canon, it is not a +standard, and it binds nobody else. It exists so that two findings graded a +month apart are graded the same way, and so that a grade can be argued with. + +It was written against three real findings (`RISK-F-0001`, `RISK-F-0002`, +`RISK-F-0003`) and it must keep grading those three sensibly or it is wrong. + +## The two axes + +### Impact — what happens if it goes wrong once + +| Band | Name | Test | +| --- | --- | --- | +| `I1` | negligible | Confined to one component. No data leaves it, no record is falsified, no recovery is lost. | +| `I2` | limited | One system's data or availability. Recoverable. Confined to a single tenant, actor or lane. | +| `I3` | serious | Crosses a boundary — tenant, system, or trust — or removes recoverability for one system. | +| `I4` | severe | Crosses the estate. What is compromised here propagates to everything that trusts it, or the data loss is unbounded. | + +Impact is scored at **one occurrence**, not at the worst imaginable campaign. +"An attacker who already owns the cluster could do this too" is not an impact +argument. + +### Likelihood — how far anyone has to reach + +| Band | Name | Test | +| --- | --- | --- | +| `L1` | remote | Requires access nobody currently holds and no ordinary process grants. | +| `L2` | possible | Requires a foothold the estate does grant somewhere — an in-cluster workload, an agent session, a scoped token. | +| `L3` | likely | Reachable from inside the normal working set with no additional step. | +| `L4` | present | No barrier at all, or it is already happening. | + +Likelihood is about **reach**, not about intent or about whether anyone has +bothered. `risk-nexus` does not model attackers; it models what the system +permits. + +Where the reporter has not established exposure, the finding says so and the +grade uses the band the *stated* facts support — not the worst case, and not +zero. `RISK-F-0001` explicitly declines to assume a default-deny NetworkPolicy +exists; the grade must decline with it, and the unverified fact becomes a +review item rather than a silent assumption in either direction. + +## The grid + +| | `L1` | `L2` | `L3` | `L4` | +| --- | --- | --- | --- | --- | +| `I4` | medium | high | **critical** | **critical** | +| `I3` | low | medium | high | **critical** | +| `I2` | low | low | medium | high | +| `I1` | note | low | low | medium | + +Four severities: `low`, `medium`, `high`, `critical`. `note` is not a +severity; see the floor. + +## The fidelity modifier + +**A control that lies is one impact band worse than the same control absent.** + +Apply `+1` impact band (capped at `I4`) when the failure mode produces a +*false record* rather than *no record*: an attestation that a check passed +when nothing checked, an audit line asserting an authorization that was never +made, a green signal derived from an unreachable test. + +The reasoning is `RISK-F-0002`'s and the register adopts it: an absent control +is a gap you can find by looking; a lying control is a gap that survives +looking, because the evidence you would look at is the thing that is wrong. +Only one of the two states misleads the person investigating afterwards. + +The modifier applies to the state being scored. A finding that describes both +states — control absent today, control lying if switched on in the wrong order — +gets **two scores and one of them is the register's headline**; see +"Which state is scored". + +## Which state is scored + +The headline `severity` is the state of the world **today**. A hazard that +would be created by a *future* action is not the headline, because a register +that scores hypotheticals stops describing the estate. + +The hazard is not lost. It is recorded on the finding as a named +**constraint** with its own grade, and it attaches to whatever action would +trigger it — usually another finding's remediation. `RISK-F-0002` is the +worked example: the gate being off is today (headline), the gate being +switched on while the oracle is forgeable is a constraint on `RISK-F-0001`'s +fix, graded separately and higher. + +If the constraint's grade is higher than the headline, the finding says so in +its ruling. A reader must not be able to come away with the low number and +miss the high one. + +## Build mode + +Every finding is graded twice: + +- `severity` — today, in build mode, with today's likelihood. +- `severity_at_production` — the same impact, with likelihood re-read for a + system carrying real users and real tenant data. + +Build mode legitimately lowers **both** axes, for different reasons: +likelihood, where the reach itself depends on a production deployment that has +not happened; and impact, where the data that would be exposed does not exist +yet. What it must never lower is `severity_at_production` — the defect does +not improve because the calendar has not reached it. + +> *Amended 2026-08-19 (`RISK-WP-0001-T07`).* This paragraph originally said +> build mode was a likelihood input and never an impact one. Grading the +> unverified tenant boundary broke that: what build mode changes there is the +> consequence of an occurrence, not the reach of it. The instrument was wrong +> on first hard use and is corrected rather than worked around. + +Where the two grades differ, the production transition is a mandatory +re-score. `docs/method/review.md` binds the review date to it, so the re-score +is a scheduled event and not somebody's memory. + +## Non-adversarial findings + +Likelihood is written as reach because most findings are about someone getting +somewhere. Where a finding is about loss, corruption or outage — no backup, no +recovery path, an eviction-prone deployment — there is no attacker to model. + +For those, likelihood reads as **the chance of the triggering event inside one +review interval**: `L1` would be surprising, `L2` is an ordinary failure the +estate has seen before, `L3` is expected in the normal course of running, +`L4` is already happening. Impact is unchanged: what is lost, and whether it +comes back. + +> *Added 2026-08-19 (`RISK-WP-0001-T07`).* Forced by `RISK-F-0006`, where the +> defect is an absent backup and the reach reading produced nonsense. + +## Live incidents + +Everything above assumes a latent defect — something reachable that nobody is +currently reaching. When someone is, three things change: + +- **Likelihood is `L4`.** The band means "already happening" and this is what + it is for. +- **Impact is scored on what has occurred plus what is still reachable**, not + on the worst case. An incident in progress has facts; use them. +- **The grade is provisional and expected to move.** File first, grade within + the hour, re-grade as facts arrive. `docs/method/intake.md` has the rest, + including the 72-hour clock that `first_observed` starts. + +## The floor + +`INTENT.md`: if a finding would not change anyone's decision, it is a note, +not a risk. Concretely, a register entry requires **both**: + +1. **An owner who could act.** Some repo, or the operator, can do something + about it. No actor, no entry. +2. **A decision that changes.** Recording it alters what someone does, when + they do it, or what they must not do first. + +Fails either test → it is a note in `notes/`, not a finding in `findings/`. +Notes are not graded, not reviewed, and not published. They exist so that +"we saw it" survives without inflating the register. + +An `I1`/`L1` cell is `note` in the grid for the same reason: something that +is both negligible and unreachable is a thing we know, not a risk we carry. + +Two things the floor does **not** exclude: + +- **Known and deliberate.** `RISK-F-0002` is a decision somebody made on + purpose. It still passes the floor, because it changes what may be switched + on and in what order. Deliberate is not the same as tracked. +- **Omission-shaped.** `RISK-F-0003` is a default that silently produces + ungoverned lanes. The individual lane is small; the default is not. + +## Provenance is a grading input + +All four defects known to this register were found by repos reading their own +code against a ladder, within days of each other. None was found by monitoring. + +Where a finding's provenance is "we happened to look", the register does not +get to assume that similar defects would have been caught. That raises +likelihood for the class, not for the instance, and it belongs in the ruling's +reasoning rather than in a modifier — the register grades what is filed, and +notes when the filing was luck. + +## Recording a grade + +The finding's front-matter carries: + +```yaml +severity: critical # headline, today +severity_at_production: critical +impact: I4 # band, before modifiers +likelihood: L3 +fidelity_modifier: false # true if +1 applied, with the reason in the ruling +``` + +and the ruling section states impact, likelihood, any modifier, and the one +sentence that would have to become false for the grade to change. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-VERIFICATION.md b/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-VERIFICATION.md new file mode 100644 index 0000000..e841cbe --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RISK-METHOD-VERIFICATION.md @@ -0,0 +1,112 @@ +--- +id: RISK-METHOD-VERIFICATION +type: method +title: "What this register may verify for itself, and what it must take from owners" +status: adopted +owner: risk-nexus +adopted: "2026-08-20" +workplan: RISK-WP-0004-T05 +review_interval: 6m +disclosure: public +revision: "adopted-1" +last_reviewed: "2026-08-20" +--- + +# Verification + +`RISK-WP-0004-T05` asked what this repo can legitimately establish itself, +because two gradings were resting on file comparison. The answer was found by +trying, on 2026-08-20, rather than by reasoning about it. + +## The rule + +**A register may look. It may not touch, and it may not conclude on a system's +behalf.** + +Verification here means: running a read-only command a reporter has already +named, or that answers a question the register itself wrote down as open, and +recording exactly what came back. + +Permitted: + +- read-only reads of live state (`kubectl get`, `bao policy read`, an HTTP + probe of a documented endpoint); +- comparing what is deployed against what a repo said is deployed; +- recording the discrepancy and routing it as a **question**. + +Not permitted: + +- any write, apply, patch, restart or rotation — including one that would + obviously improve things; +- concluding what a defect means for a system this repo does not own. The + system stays authoritative about itself (`INTENT.md`); +- verifying instead of asking. The owner is asked first; verification settles + what an owner cannot see or has invited someone to check. + +`rapp-postgres` declined to check the NetworkPolicy on `flex-auth`'s behalf, +saying that would be reporting on a system they do not own. That was right +**for a reporter**. This register is the downstream party whose grade depended +on the answer, and `flex-auth` explicitly named the command and asked for +someone with credentials to run it. Those two positions are compatible. + +## What this host can actually reach — established 2026-08-20 + +| Target | Result | Consequence | +| --- | --- | --- | +| Kubernetes (`railiance01`) | **reads work** — `kubectl get ns`, `get networkpolicy -o json` | Live cluster state is verifiable by this register | +| OpenBao (`bao.coulomb.social`) | **403 permission denied** on `token lookup` and `policy read` | Policy claims are not verifiable here; `RISK-F-0009` rests on file comparison | + +The asymmetry is worth stating plainly: **the register can check what the +cluster admits, and cannot check what the secret store permits.** Every grade +touching an OpenBao policy is therefore a grade on a document, and says so. + +That is not a gap to close by acquiring credentials. A risk register holding +production secret-store access has traded a verification problem for a much +worse one. If OpenBao claims need verifying, the right answer is for the owner +to run the read and report it, which is what `ops-warden` did — the obstacle +there was an expired token, not the arrangement. + +## A file is not a safe proxy for the server + +Established 2026-08-21, by evidence rather than by caution. `ops-warden` ran +the live OpenBao read that `RISK-F-0009` had been graded without, and **the +deployed policy differs from the committed file.** No lane maps to the drifted +path, so nothing was exposed — but the general claim is now proven rather than +suspected. + +Consequences this register accepts: + +- **Every grade made off a checkout is a grade on a document**, and says so on + its face. `RISK-F-0009` does. +- A verification that reads a file is a **weaker artifact** than one that reads + a server, and the record must not blur them. `RISK-V-0001` reads a server; + the OpenBao comparison in `RISK-F-0009` reads a file. +- Where only the owner can reach the server, the owner's probe is the evidence + and the register says whose it is. That is not a lesser standard — it is the + correct one, given `verification.md`'s own limits. + +The corollary is uncomfortable and worth stating: **drift between file and +server is invisible to anyone reading files**, which is most of this estate's +tooling, including `fix_tracker.py`. What that tool reads is what a repo +*recorded*, and a record can be as stale as any other claim — as four +self-reported stale blockers in twelve hours demonstrated on 2026-08-21. + +## Recording a verification + +One file per verification in `docs/verifications/`, `RISK-V-NNNN`, stating the +command, the raw result, what it confirms, what it contradicts, and what it +does not establish. It names the findings it bears on, and those findings link +back. + +A verification is evidence, not a ruling. It can move a grade; it does not +close a finding on its own, and it never speaks for the owning repo. + +## Verification and the cadence ladder + +A verification that contradicts something is a check that is **not clean**: the +affected findings reset to `instant`. A verification that confirms what was +already recorded is clean, and may be exactly the evidence a rung is built on. + +The first one (`RISK-V-0001`) did both — it confirmed the ingress claim +`RISK-F-0001` was graded on, contradicted the egress claim in the same message, +and surfaced a third policy nobody had mentioned that bears on `RISK-F-0002`. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0001.md b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0001.md new file mode 100644 index 0000000..b616db6 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0001.md @@ -0,0 +1,143 @@ +--- +id: RMASTER-ADR-0001 +title: "Repository Prefix Architecture" +status: accepted +revision: "accepted-1" +owner: railiance-master +last_reviewed: "2026-07-25" +review_interval: 6m +--- + +# ADR-0001: Repository Prefix Architecture + +Date: 2026-07-25 +Status: Accepted + +## Context + +Railiance already has a meaningful set of ownership repos such as +`railiance-infra`, `railiance-cluster`, `railiance-platform`, +`railiance-enablement`, `railiance-apps`, `railiance-forge`, and +`railiance-fabric`. + +That structure is useful, but it does not by itself capture all of the +dimensions Railiance now needs. + +In particular, the architecture needs clear source-controlled homes for: + +- workload execution contracts across different operations architectures, +- managed workload packages as first-class repos, +- and concrete substrate boundaries such as named servers, workstations, or + grouped environments. + +Without that separation, Kubernetes-specific workload semantics remain mixed +into `railiance-cluster`, workload wrappers remain mixed into ownership repos, +and concrete substrates remain under-described. + +Railiance also operates inside a wider ecosystem around Railiance, Net Kingdom, +and the Helix Forge software factory. That wider context needs architecture +that can support both exploratory operation and production-grade evolution +without leaving key responsibilities ambiguous. + +## Decision + +Railiance adopts four canonical repository families: + +1. `railiance-*` for ownership and responsibility areas +2. `rail-*` for workload execution contracts +3. `rapp-*` for Railiance-managed workload packages +4. `reef-*` for durable substrate boundaries + +The current implementation wave is explicitly centered on `rail-kubernetes` as +the default base rail. Additional rails are introduced only when a concrete +workload has a sound reason to run on a distinct execution architecture. + +`rail-knative` is the first expected follow-on rail, motivated by the need to +support `qonto-assistent`, but it should follow the `rail-kubernetes` boundary +cleanup rather than bypass it. + +## Meaning Of Each Family + +### `railiance-*` + +Owns major architectural responsibilities, shared policies, and durable layer +boundaries. + +### `rail-*` + +Owns how workloads run on a specific execution architecture such as Kubernetes, +Knative, KEDA, Fission, or Nuclio. + +In the current phase, `rail-kubernetes` is the default path for platform +services and managed applications. + +### `rapp-*` + +Owns the managed workload package for one service or application, whether it is +an internal workload or a wrapped upstream product. + +Its role is to provide the managed wrapper and scaffolding needed to run that +workload in the Railiance and Net Kingdom context. It does not replace +responsibility ownership. + +### `reef-*` + +Owns the concrete substrate reality where rails and `rapp`s are bound, such as +a named server, cluster, workstation, or grouped substrate class. + +A reef is about purpose-bound compute resources, not merely about individual +machines. + +## Consequences + +### Positive + +- Ownership, execution, workload identity, and substrate identity become + separate concerns. +- Railiance can support multiple execution architectures without forcing all + workload semantics into `railiance-cluster`. +- Platform services and applications can both become first-class managed + workload packages where appropriate. +- Concrete substrates gain a clear architectural home without overloading the + ownership repos. +- The framework gains a disciplined default path for introducing new rails + instead of proliferating them speculatively. + +### Required Follow-On Work + +- Define the initial `rail-*` contracts, starting with `rail-kubernetes`. +- Evolve the current `railiance/app.toml` and overlay pattern into a + rail-aware, eventually rail-neutral workload packaging contract. +- Identify which current workloads should become `rapp-*` repos. +- Extend `railiance-fabric` so rails, `rapp`s, and reefs become graph-native. +- Define when mixed-rail reefs are acceptable and when clearer substrate + separation should be preferred. + +### Constraints + +- `reef-*` must not become a default one-repo-per-machine pattern. +- Generic logic stays in the appropriate ownership repo. +- A named machine gets its own reef only when that machine is itself a durable + substrate boundary. +- `rapp-*` repos must not become shadow ownership repos. +- Transitional substrate labels should not be canonized before the pattern is + operationally stable. + +## Current Interpretation For Existing Repos + +- `railiance-*` repos remain the primary ownership axis. +- Current workload-execution logic in `railiance-cluster` is a candidate to + migrate into `rail-kubernetes`. +- Current workload wrappers inside `railiance-apps`, `railiance-platform`, or + `railiance-forge` may evolve into `rapp-*` repos over time. +- Current named substrates such as COULOMBCORE, RAILIANCE01, and WORKSTATION + may justify `reef-*` repos when they represent real operational boundaries. +- A reef may host more than one rail in early or mixed environments, but + production-critical substrates should prefer clearer purpose and primary-rail + boundaries unless a mixed design is justified. + +## Notes + +This ADR defines the repository taxonomy. It does not yet mandate a full +migration or rename of existing repos. Migration should happen when it produces +clearer ownership and lower ambiguity, not merely for naming purity. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0002.md b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0002.md new file mode 100644 index 0000000..872d4a9 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0002.md @@ -0,0 +1,74 @@ +--- +id: RMASTER-ADR-0002 +title: "Wave 1 rail-kubernetes Boundary" +status: accepted +revision: "accepted-1" +owner: railiance-master +last_reviewed: "2026-07-25" +review_interval: 6m +--- + +# ADR-0002: Wave 1 `rail-kubernetes` Boundary + +Date: 2026-07-25 +Status: Accepted + +## Context + +Railiance wants `rail-*` repos to represent workload execution contracts rather +than abstract naming ideas. + +Today, the concrete Kubernetes workload contract already exists, but it is +embedded in `railiance-cluster`. That repo currently owns both: + +- the S2 Kubernetes substrate +- and the generic workload lifecycle and tooling that run on top of it + +That mixed ownership is the main blocker to introducing the first real rail. + +## Decision + +Railiance will treat `rail-kubernetes` as the wave-1 base rail. + +The boundary is: + +- `railiance-cluster` owns the Kubernetes substrate and its prerequisites +- `rail-kubernetes` owns the generic workload-on-Kubernetes execution contract + +Workload-specific helper flows currently living in `railiance-cluster` are +temporary exceptions and must be rehomed rather than carried forward as part of +the generic rail. + +## Consequences + +### Positive + +- The first real rail gains a concrete ownership boundary. +- `railiance-cluster` can return to a clean S2 substrate identity. +- Future rails such as `rail-knative` get a stable base boundary to extend from. +- `rapp-*` repos gain a clearer execution-contract home for Kubernetes-managed + workloads. + +### Required Follow-On Work + +- Create the detailed `rail-kubernetes` boundary contract and migration map. +- Split generic lifecycle docs, schema, examples, and Stage 1-3 tooling out of + `railiance-cluster`. +- Leave a compatibility path so current users of the cluster repo are not + broken during the migration. +- Rehome workload-specific helpers that do not belong in either boundary. + +### Constraints + +- `rail-kubernetes` must not take ownership of cluster bootstrap, operators, or + kubeconfig management. +- `railiance-cluster` must not continue as the owner of generic workload + promotion semantics after the split. +- `rail-knative` should follow this cleanup, not bypass it. + +## Notes + +This ADR does not require all current files to move immediately. + +It requires the ownership line to be explicit now, so practical repo separation +can proceed without ambiguity. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0003.md b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0003.md new file mode 100644 index 0000000..cb8d608 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0003.md @@ -0,0 +1,67 @@ +--- +id: RMASTER-ADR-0003 +title: "First-Wave rapp Selection" +status: accepted +revision: "accepted-1" +owner: railiance-master +last_reviewed: "2026-07-25" +review_interval: 6m +--- + +# ADR-0003: First-Wave `rapp-*` Selection + +Date: 2026-07-25 +Status: Accepted + +## Context + +Railiance wants `rapp-*` repos to represent managed workload packages rather +than new ownership layers. + +The current workload surfaces already suggest several candidates: + +- OpenBao in `railiance-platform` +- the forge workload in `railiance-forge` +- multiple S5 workloads in `railiance-apps` + +But they are not equally ready for first extraction. + +## Decision + +The first-wave `rapp-*` order is: + +1. `rapp-openbao` +2. `rapp-vergabe-teilnahme` +3. later `rapp-forgejo` after the forge runtime target stabilizes + +The forge workload is explicitly deferred from the first wave because current +Gitea operation and future Forgejo direction are both active realities. + +## Consequences + +### Positive + +- The first `rapp-*` extraction proves the third-party platform-service wrapper + model. +- The second proves the user-facing self-built app wrapper model. +- The deferred forge wrapper avoids immediate rename churn and packaging + ambiguity. + +### Required Follow-On Work + +- Write repo-local extraction work for `rapp-openbao`. +- Write repo-local extraction work for `rapp-vergabe-teilnahme`. +- Reassess the forge wrapper when the production package target is no longer + split between current Gitea and future Forgejo. + +### Constraints + +- `rapp-*` repos must stay packaging-focused and must not become shadow + ownership repos. +- Shared S3 policy stays in `railiance-platform`. +- Shared S5 release patterns stay in `railiance-apps`. + +## Notes + +This ADR chooses sequence, not a mandatory destination for every workload in the +ecosystem. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0004.md b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0004.md new file mode 100644 index 0000000..8a36edb --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0004.md @@ -0,0 +1,71 @@ +--- +id: RMASTER-ADR-0004 +title: "First-Wave reef Rollout" +status: accepted +revision: "accepted-1" +owner: railiance-master +last_reviewed: "2026-07-26" +review_interval: 6m +--- + +# ADR-0004: First-Wave `reef-*` Rollout + +Date: 2026-07-25 +Status: Accepted + +## Context + +Railiance now has a reef model, but it needs a concrete first rollout. + +The current substrate reality is not uniform: + +- Railiance01 is the clearest new-workload home +- CoulombCore is still active but transitional +- the workstation role is operator edge compute rather than server substrate + +At the same time, S1 ownership is ambiguous because `railiance-hosts` and +`railiance-infra` duplicate each other. + +## Decision + +The first-wave reef rollout is: + +1. `reef-railiance` +2. `reef-coulombcore` +3. `reef-ops-workstations` + +`railiance-infra` is the canonical S1 ownership repo going forward. + +`railiance-hosts` is treated as predecessor or duplicate state to be retired, +frozen, or reduced later rather than as a second canonical S1 architecture home. + +## Consequences + +### Positive + +- Railiance gets a grouped home-reef seed whose name remains stable as more + home servers are added. +- Transitional CoulombCore reality is acknowledged without being treated as the + long-term preferred production pattern. +- Operator edge compute is modeled without defaulting to one repo per machine. +- Reef planning now rests on one canonical S1 ownership line. + +### Required Follow-On Work + +- Create repo-local rollout work for the first reef repos. +- Define how additional Railiance home servers join `reef-railiance` and what + future split criteria would justify narrower reefs. +- Plan the `railiance-hosts` cleanup direction relative to `railiance-infra`. + +### Constraints + +- Multi-rail reefs are acceptable early, especially on transitional substrates. +- Production-grade separation should still prefer clearer purpose and primary + rail boundaries as criticality rises. +- Transitional substrate nicknames should remain provisional until the pattern + is stable enough to canonize. + +## Notes + +This ADR chooses the first rollout set. It does not require that every future +substrate be modeled the same way. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0005.md b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0005.md new file mode 100644 index 0000000..b441fbe --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0005.md @@ -0,0 +1,41 @@ +--- +id: RMASTER-ADR-0005 +title: "Derived Rail Composition" +status: accepted +revision: "accepted-1" +owner: railiance-master +last_reviewed: "2026-07-26" +review_interval: 6m +--- + +# ADR-0005: Derived Rail Composition + +Date: 2026-07-26 +Status: Accepted + +## Context + +Knative provides distinct workload activation and revision semantics but runs +on Kubernetes. Treating it as an unrelated peer rail would duplicate generic +workload lifecycle and substrate assumptions. + +## Decision + +Railiance distinguishes base and derived rails. + +`rail-kubernetes` is the default base rail for platform workloads. +`rail-knative` is a derived rail that inherits a versioned +`rail-kubernetes` common contract and overrides only Knative-specific +activation, revision, autoscaling, traffic, cold-start, and rollback semantics. + +The detailed contract is +[`docs/rail-composition-contract.md`](../rail-composition-contract.md). + +## Consequences + +- New platform workloads continue to use `rail-kubernetes` unless a specialized + rail is justified. +- Derived rails declare base-rail compatibility rather than copying lifecycle + contracts. +- Fabric and conformance tooling must understand rail dependency and readiness. +- Knative installation stays with the S2 substrate owner. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0006.md b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0006.md new file mode 100644 index 0000000..fc98cf3 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0006.md @@ -0,0 +1,52 @@ +--- +id: RMASTER-ADR-0006 +title: "Reef Production Admission" +status: accepted +revision: "accepted-2" +owner: railiance-master +last_reviewed: "2026-08-29" +review_interval: 6m +--- + +# ADR-0006: Reef Production Admission + +Date: 2026-07-26 +Status: Accepted + +## Context + +Fabric topology can say that a reef hosts a rail or binds a workload, but that +does not demonstrate capacity, isolation, recoverability, or approval for a +critical internet-facing service. + +## Decision + +Rail and `rapp` bindings use explicit readiness states: `declared`, +`installed`, `verified`, `production-approved`, and `deprecated`. + +Critical workloads require machine-readable conformance evidence plus explicit +acceptance of residual risks that cannot be automated. Mixed-rail reefs use +defined split triggers. + +The detailed contract is +[`docs/reef-production-readiness-contract.md`](../reef-production-readiness-contract.md). + +Who may reach a listener is a different axis: +[`ADR-0008`](ADR-0008-private-by-default-exposure.md). Production +admission does not imply a public surface. A public surface requires +this admission **and** an exposure grant. + +Neither admission nor exposure is an authorization decision. Whether an +actor may perform an action on a resource is `access-engine` +([`ADR-0009`](ADR-0009-netkingdom-security-layer-interaction.md)). +`production-approved` MUST NOT be read as permission to act. + +## Consequences + +- `hosts_rail` and `binds_rapp` no longer imply deployability. +- `reef-railiance` may host Knative in wave 2, but Qonto cannot be called + production-approved solely from that declaration. +- Repeated evidence collection should become functional automation. +- `production-approved` is not permission to publish a listener. See + ADR-0008. +- `production-approved` is not an authorization decision. See ADR-0009. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0007.md b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0007.md new file mode 100644 index 0000000..47e41f4 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0007.md @@ -0,0 +1,113 @@ +--- +id: RMASTER-ADR-0007 +title: "Rapp Declaration Contract" +status: accepted +revision: "accepted-2" +owner: railiance-master +last_reviewed: "2026-08-23" +review_interval: 6m +--- + +# ADR-0007: Rapp Declaration Contract + +Date: 2026-08-13 +Status: Accepted + +## Context + +`RMASTER-WP-0017` through `RMASTER-WP-0019` established the four-axis +model and materialized the first family repos. The model held up. Its +enforcement did not. + +A 2026-08-11 survey by `railiance-platform` found that the three live +`rapp.yaml` files were mutually unreadable: rollout, smoke, and rollback +contracts used different shapes; metadata that both rails carry consistently +appeared in only one rapp; `reef-railiance` `bound_rapps` listed `rapp-qonto` +only, while `rapp-openbao` and `rapp-postgres` were already live on the same +reef. `docs/repo-family-bootstrap-contract.md` named fields in prose and +could not catch any of this. + +The same survey treated rapp grouping as something that might be derived from +Forgejo organizations or from State Hub domains. Neither works. A repo lives +in exactly one Forgejo org, so `org:repo` is one-to-many. A repo may +legitimately contribute to more than one rapp, so `rapp:repo` is +many-to-many. A many-to-many grouping cannot be derived from a one-to-many +one. Domains fail in both directions. The three dimensions also change at +different speeds. + +Canon OAS P1 requires independent perspectives to be separate orthogonal +dimensions. Decision `d07ee5f9` already recorded this from the +`railiance-platform` side; this ADR is the framework record. + +## Decision + +1. **Bounded rapp context is its own dimension.** It is derived neither from + Forgejo organizations nor from State Hub domains. Grouping is a first-class + declaration, not a projection of some other axis. + +2. **Two cardinalities stay distinct.** Repos to rapps is many-to-many: a + repo may appear in the `composition.member_repos` of more than one rapp. + Deployables to rapps is one-to-one: every running deployable has exactly + one rapp that owns its rollout. The coverage question — *does every live + deployable belong to exactly one rapp?* — is well-formed only if these + stay distinct. + +3. **Granularity is grouped-by-bounded-context.** One rapp per cohesive group + that deploys, versions, and rolls back together, not one rapp per + deployable. Grouping is legitimate only where members share rollout and + rollback fate. A single-repo rapp is the one-member case of the same + `composition` block, not a second shape. + +4. **The schema is normative.** `schemas/rapp.schema.json`, + `schemas/rail.schema.json`, and `schemas/reef.schema.json` define the + shapes. Framework prose cites those files. It does not restate their + fields. Reef `bound_rapps` is a derived projection of `rapp.bound_reefs`, + not a hand-maintained registry. + +5. **Coverage includes every managed running deployable.** Application, + operational, and tooling runtimes participate in the same exactly-one-rapp + invariant when they are installed, scheduled, or otherwise operated as a + managed deployable. This includes a managed one-shot Job; it does not turn + a human command or approval act into a workload. Human access, credential + patterns, broker actions, one-off operational acts, and infrastructure + resources that are not workloads retain their native actor, lane, activity, + or resource identity. + + A running deployable that predates rapp extraction is migration debt. Until + an authoritative declaration claims it, workload-based controls report it + as `unknown`; they do not infer a rapp from its repository, namespace, path, + labels, or apparent owner. A subject explicitly established as not being a + workload is `not-applicable`. `unknown` and `not-applicable` are different + outcomes and omission must not collapse them. + + Railiance Master remains the sole owner of the normative rapp vocabulary and + schemas. Consumer catalogs may store explicit references and integration + owners may resolve them, but neither creates a parallel declaration surface + or copies rapp metadata as another source of truth. + +The detailed shapes, including the single normative form of the rollout, +smoke, and rollback contracts, live in the schema files and +`schemas/README.md`. + +## Consequences + +- Drift across family declarations fails in + `tools/validate-family-declarations.py` instead of accumulating in prose. +- `railiance-platform` `RAILIANCE-WP-0015-T02` can converge `rapp-openbao` + and `rapp-postgres` onto one shape. Migration belongs to the owning repos; + this ADR does not move any declaration. +- `reef-railiance` must stop treating `bound_rapps: [rapp-qonto]` as source + of truth. The list is already stale. +- Three further `rapp-*` repos (`rapp-secrets-engine`, `rapp-tenant-engine`, + `rapp-user-engine`) carry the family prefix and no declaration. They are + visible to the validator as undeclared and must be declared, renamed, or + retired by their owners. +- Operational and tooling deployables are not exempt from family coverage. + Existing pre-rapp runtimes may continue during migration, but their workload + identity remains visibly `unknown` to controls until declared. +- Runtime inventory is still required to prove universal coverage. Repository + discovery alone cannot establish that every running unit has exactly one + authoritative rapp. +- Calling the validator from `fix-consistency` still waits on + `the-custodian` admitting the family prefixes into the classification + standard. That sequencing is not this repo's. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0008.md b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0008.md new file mode 100644 index 0000000..62ab3fb --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0008.md @@ -0,0 +1,104 @@ +--- +id: RMASTER-ADR-0008 +title: "Private-by-default Exposure" +status: accepted +revision: "accepted-2" +owner: railiance-master +last_reviewed: "2026-08-29" +review_interval: 6m +--- + +# ADR-0008: Private-by-default Exposure + +Date: 2026-08-15 +Status: Accepted + +## Context + +ADR-0006 says a topology binding is not permission to run a workload in +production. It does not say who may reach a listener. A working deploy, +a `hosts_rail` / `binds_rapp` line, or an Ingress object has been enough +to put something on the public internet. + +Family readiness vocabularies are deliberately not unified +(`schemas/README.md`). Reef `lifecycle_state` has no +`production-approved`. Rapp `readiness_state` has no +`production-approved` either. Exposure cannot be derived from those +enums. + +Live public surfaces already exist on `reef-railiance`. They must be +named as grants, not silently grandfathered and not taken down by this +decision. + +Ratified 2026-08-15 under `RMASTER-WP-0023-T01`. + +## Decision + +New reefs, rails, and rapps are unreachable from the public internet +until they are production-safe **and** explicitly granted. The field +name is `exposure`. Missing field means `private`. + +| Posture | Meaning | Who may reach it | +| --- | --- | --- | +| `private` | No extra listener we control | in-cluster DNS, same-reef services | +| `operator` | Same listener as `private`, plus a named ops-bridge / SSH tunnel | operator and approved agents | +| `public` | Internet listener we publish | anyone the Ingress / DNS / UFW surface allows | + +`operator` is an access annotation, not a different packet posture. It +does not open a host port or a public Ingress. + +Default for a new reef, rail, or rapp is `private`. Use `operator` only +for a named admin or API surface that must be reachable before admission +(k3s API, OpenBao UI). Do not prefer `operator` as the debug default. + +`public` requires both an ADR-0006 binding of `production-approved` and +an explicit grant. A deploy, a binding line, or an Ingress object is not +a grant. Do not key `public` off rapp `readiness_state` and do not add +`production-approved` to the rapp enum for this purpose. + +A reef public surface (host port or public DNS we publish) is a +**substrate grant**, not reef `lifecycle_state`. A rapp cannot be +`public` on a reef that has not granted a public surface. Conflicting +declarations fail closed. + +The shared vocabulary is one enum. It is not one schema shape and not +one admission check: + +| Family | What the field means | What makes `public` legal | +| --- | --- | --- | +| `rapp-*` | intended consumer-facing listener | binding `production-approved` + grant | +| `rail-*` | which listener classes the rail may emit | rail may emit public Ingress only when a bound grant exists | +| `reef-*` | host ports and public DNS we publish | substrate grant | + +A grant is accepted residual risk, not a self-serve wish. Required +fields: hostname or port, reason, `approved_on`, residual-risk owner. + +`6443` / the k3s API is not a grantable public surface. + +This contract covers listeners we control: host ports, Ingress, Service +types, and public DNS we publish. Provider-native internet APIs +(Scaleway S3 and other provider-delegated endpoints) are outside this +enum. + +Ops-bridge is the normal path to a shielded thing. + +The detailed per-family reading lives in +[`docs/exposure-posture-contract.md`](../exposure-posture-contract.md). + +## Consequences + +- ADR-0006 still answers "may this binding run in production?" This ADR + answers "who may reach the listener?" Do not merge the axes. +- Neither question is an authorization decision. Whether an actor may + perform an action is `access-engine` ([`ADR-0009`](ADR-0009-netkingdom-security-layer-interaction.md)). + `exposure: public` MUST NOT be read as permission to act. +- The three readiness enums stay distinct on purpose. +- Family schemas grow an additive `exposure` field. Rapp + `data_classification: public` is a different field and must not be + reused as the posture name. +- Implementation stays in the owning repos. This ADR does not install + NetworkPolicy, UFW, Ingress, or tunnels. +- Existing public surfaces on `reef-railiance` remain up until named as + grants. This ADR is not a shutdown plan. +- CoulombCore host inventory and Q7 / Goss reaction stay outside this + decision. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0009.md b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0009.md new file mode 100644 index 0000000..2d5daca --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RMASTER-ADR-0009.md @@ -0,0 +1,83 @@ +--- +id: RMASTER-ADR-0009 +title: "NetKingdom Security-Layer Interaction Boundary" +status: accepted +revision: "accepted-1" +owner: railiance-master +last_reviewed: "2026-08-29" +review_interval: 6m +--- + +# ADR-0009: NetKingdom Security-Layer Interaction Boundary + +Date: 2026-08-29 +Status: Accepted + +## Context + +NetKingdom Security Layer Model v0.7 is accepted. Section 20 restates +Railiance workload-operation definitions owned by this repository and +states consumption rules every Railiance consumer of NetKingdom security +owes. Companion v0.2 §9 is the operative form of the same boundary. + +This repository had declared the four axes and the workload coverage rule +in its own voice, but had no machine-readable layer declaration, no +recorded assent to §20, and no framework contract that bound rails, rapps, +and reefs to those consumption rules. Admission (ADR-0006) and exposure +(ADR-0008) were live and were not demarcated from authorization. + +Statute §20.4: an interaction boundary between two frameworks is owned by +neither alone. Changes to §20 require this repository's assent for the +axis definitions and `glas-harness` assent for the session and tool-policy +seam. + +Ratified 2026-08-29 under `RMASTER-WP-0026`. + +## Decision + +1. **This repository is Taxonomy of Railiance workload operations.** The + machine-readable declaration is [`layer.yaml`](../../layer.yaml). It is + not a NetKingdom §4 catalog row. It is not PEP-shaped. It holds no + Tooling-layer client. + +2. **Statute §20.1 restates our definitions and does not author them.** + Workload, the four axes, and the rule that `rein-*` is not a fifth axis + remain this repository's. NetKingdom may cite them; it may not redefine + them without our assent. + +3. **Statute §20.2 is the consumption constitution** for every Railiance + consumer of NetKingdom security. The detailed contract is + [`docs/netkingdom-security-consumption-contract.md`](../netkingdom-security-consumption-contract.md). + +4. **Statute §20.3 remains unset.** This repository will not imply a + mapping of rails, rapps, reefs, or ownership onto Taxonomy, Tooling, + Engine, or Staff. The five questions are tracked, unanswered, in + [`docs/netkingdom-axis-layer-open-questions.md`](../netkingdom-axis-layer-open-questions.md). + +5. **Admission, exposure, and authorization stay three questions.** + ADR-0006 answers whether a binding may run in production. ADR-0008 + answers who may reach a listener we control. `access-engine` answers + whether an actor may perform an action. `production-approved` and + `exposure: public` are not authorization decisions. + +6. **Changes to this boundary** require this repository's assent for the + axis definitions. Changes that touch the glas-harness seam require + `glas-harness` assent as well. + +## Consequences + +- Rails, rapps, and reefs consume `access-engine`, `approval-engine`, + `secrets-engine`, and `audit-core`. They do not grow local substitutes. +- This repository does not host a PDP, an approval store, a credential + plane, an evidence archive, or an actuation surface. +- PEP stance maps belong in the repositories that cause protected side + effects, inventoried in statute §13.1, not here. +- Observation-in-production and automatic containment remain estate-wide + zeros. Framework plans must not assume they exist. +- `gate-house` can cite this ADR as this repository's own-voice + declaration and §20 assent, rather than a review note about us. + +## Notes + +This ADR does not amend ADR-0001 through ADR-0008. It adds the security +consumption axis those records did not have to name. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RPLAT-ADR-0001.md b/docs/evidence/2026-10-11-served-publication-history/sources/RPLAT-ADR-0001.md new file mode 100644 index 0000000..64c9519 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RPLAT-ADR-0001.md @@ -0,0 +1,68 @@ +--- +id: RPLAT-ADR-0001 +title: "ADR-0001 — S3 owns platform services, not the substrate beneath them" +status: accepted +revision: "1.0" +owner: railiance-platform +date: "2026-08-17" +last_reviewed: "2026-08-17" +review_interval: 12m +--- + +# ADR-0001 — S3 owns platform services, not the substrate beneath them + +## Context + +`railiance-platform` is S3 on the OAS Stack: the shared services several +applications depend on — PostgreSQL, secrets, cache, object storage. The layers +around it are S1 `railiance-infra` (OS and host concerns), S2 +`railiance-cluster` (Kubernetes runtime, ingress), S4 `railiance-enablement` +(tooling and CI), S5 `railiance-apps` (workloads). + +This boundary has been stated in `SCOPE.md` and in ADR-003 of `railiance-infra` +since the five-repo split, and it has been tested twice. `RAIL-PL-WP-0001` +existed to extract platform services *out* of S2 subcharts. On 2026-08-17 +`POLICY-NEXUS-WP-0001` assigned this repo "the substrate — DNS, TLS, ingress, +hosting" for `policy.coulomb.social`, which would move the boundary back the +other way. + +The pressure is predictable and will recur: S3 is the layer that looks like it +owns infrastructure, because it owns things that feel infrastructural. Recording +the rule as an ADR rather than as a line in `SCOPE.md` gives future requests +something to be answered against. + +## Decision + +**S3 owns shared platform *services* and the interfaces consumers bind to. It +does not own DNS, TLS, Kubernetes ingress, host configuration, or CI.** A +request that would place any of those here is redirected to S1, S2 or S4 rather +than absorbed, even when this repo is the closest available owner and even when +absorbing it would be faster. + +Where S3 has a genuine part in such a request — an object-storage bucket, a +credential lane, a database — it supplies that part and co-signs the rest. It +does not become the owner of the whole by being the first repo asked. + +## Consequences + +- `POLICY-NEXUS-WP-0001` T04 needs splitting: ingress and TLS to S2, DNS to S1 + or the reef boundary, storage and credentials here. Routed as `RPF-WP-0018` + finding F1. +- A request this repo declines does not thereby become nobody's. Declining + carries an obligation to name the owner, which is why F1 names two. +- The rule cuts against this repo's convenience in the common case. That is + what makes it a rule rather than a preference. +- `rapp-openbao` and `rapp-postgres` are the same principle applied inward: + the deployable package left, custody and policy stayed. See + `docs/rapp-openbao-boundary.md` and `docs/rapp-postgres-boundary.md`. + +## Alternatives considered + +**Accept the substrate assignment as written.** Fastest, and the requester had +already resolved it with the operator. Rejected: it re-imports the coupling +`RAIL-PL-WP-0001` spent a workplan removing, and a boundary that yields to +whoever asks most recently is not a boundary. + +**Own ingress for S3-adjacent services only.** A narrower version, and it fails +on the first argument about what counts as adjacent. The line has to be drawn +where it can be checked. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RPLAT-ADR-0002.md b/docs/evidence/2026-10-11-served-publication-history/sources/RPLAT-ADR-0002.md new file mode 100644 index 0000000..2acff67 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RPLAT-ADR-0002.md @@ -0,0 +1,86 @@ +--- +id: RPLAT-ADR-0002 +title: "ADR-0002 — S3 owns the placement rule; the package repo owns the number" +status: proposed +revision: "1.0" +owner: railiance-platform +date: "2026-08-17" +last_reviewed: "2026-08-17" +review_interval: 6m +--- + +# ADR-0002 — S3 owns the placement rule; the package repo owns the number + +## Context + +An earlier draft of +`net-kingdom/canon/standards/tenancy-posture_v0.1.md` §8.2 proposed that +database placement policy — dedicated versus shared, and when that changes — +be owned by `railiance-platform`, co-signed by `adaptive-pricing`. +`tenant-engine` raised the same gap independently on 2026-08-16: both patterns +are live on railiance01, neither is written down, and each new service copies +whichever neighbour it looked at. + +The complication is that this repo no longer holds the specs. `RAILIANCE-WP-0012` +and `RAILIANCE-WP-0015` moved the deployable surface to the `rapp-*` repos. +`platform-pg`'s `instances`, `max_connections`, memory limit and retention are +`rapp-postgres`'s cluster CR. Tenancy Posture §19.8 nonetheless asks *this repo* +for `platform-pg`'s declared maximum size — a question one hop from where its +answer lives. + +Accepting ownership without stating this would produce either an answer we +cannot substantiate or a quiet non-answer. + +## Decision + +**Accept placement ownership, split along rule and number.** + +*This repo owns the rule:* the default placement level, what overrides it, who +is accountable for each workload's placement, that a shared cluster must publish +a ceiling and an overflow target before its third consumer, that retention has a +floor and a ceiling, and that a consumer's erasure horizon on shared substrate +is disclosed rather than inherited silently. + +*The package repo owns the number:* what the ceiling actually is, which resource +binds first, what the retention values are, and the validator that enforces them +where the CR lives. + +**Placement ownership per workload sits with the consuming repo; this repo +co-signs.** The consumer knows its latency requirement, erasure horizon and +compliance position; S3 knows the substrate. A placement change needs both. +Putting the decision solely here would recreate the problem §8.2 exists to +avoid — an operations-shaped repo holding a commercial decision alone. + +`adaptive-pricing` declined the standing co-signature on 2026-08-17. The +replacement is binding and machine-reconcilable: it publishes typed tier +minimums; this policy validates placement against them. Ownership stays here. + +## Consequences + +- `docs/placement-policy.md` is the operative document; this ADR is the + decision behind it. +- §19.8 is answered for `platform-pg` by `rapp-postgres` ADR-0004: four + consumers, with `platform-pg-2` named as the still-absent overflow target. + Rule P-4.1 still requires `apps-pg` to declare its own ceiling before its + third consumer. +- This repo owes `apps-pg`'s ceiling under its own rule. A policy whose author + is exempt is not a policy. +- §8.1 requires triggers to be *monitored*. This repo has no monitoring plane, + so the connection-and-memory-ceiling trigger is recorded `unmonitored` + pending `railiance-telemetry`. Declaring a ceiling is a partial substitute — + a written threshold is checkable by a human at provisioning, which beats a + limit discovered by an OOM kill. +- `adaptive-pricing`'s decline is recorded as single policy ownership plus a + mandatory typed constraint join, not as an absent signature. + +## Alternatives considered + +**Decline ownership; route it to `rapp-postgres`.** They hold the specs and the +operational knowledge. Rejected: placement is a cross-cluster question and +`rapp-postgres` owns one package. A policy owned by one substrate's operator +cannot govern movement between substrates. + +**Accept whole, including the numbers.** Rejected: it would either re-import +the deployable surface this repo deliberately gave up, or produce numbers +restated here that drift from the CR — a second source of truth for exactly the +values a consumer must be able to trust. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/RPLAT-ADR-0003.md b/docs/evidence/2026-10-11-served-publication-history/sources/RPLAT-ADR-0003.md new file mode 100644 index 0000000..a157fa7 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/RPLAT-ADR-0003.md @@ -0,0 +1,68 @@ +--- +id: RPLAT-ADR-0003 +title: "ADR-0003 — Decisions that bind others live in docs/adr, not only in the State Hub" +status: accepted +revision: "1.0" +owner: railiance-platform +date: "2026-08-17" +last_reviewed: "2026-08-17" +review_interval: 12m +--- + +# ADR-0003 — Decisions that bind others live in `docs/adr`, not only in the State Hub + +## Context + +This repo recorded decisions with the State Hub's `record_decision()` and wrote +governing content as prose in `docs/` — 24 files on 2026-08-17, none carrying a +status, owner, revision or review date. It held no ADRs at all. + +Two things made that a defect rather than a style. + +**The hub is a read model.** The estate's standing rule is that local files are +the source of truth and the hub reflects them. A decision that exists only as a +hub record inverts that for the one class of content where it matters most. + +**`policy-nexus` publishes canon and ADRs only**, globbing `docs/adr/*.md` from +each repo. A repo with no ADRs is invisible to the estate's publication surface +no matter how much governing content it holds. This repo owns S3 boundary +rules, the credential-lane model and the consumption-mode gate — all binding on +other repos, all unpublishable. + +The gap was structural, not an oversight: nothing in the session protocol ever +told anyone to write an ADR here. + +## Decision + +**A decision that binds anyone outside this repo is recorded as an ADR in +`docs/adr/`, with the frontmatter in `docs/adr/README.md`, before or alongside +any hub record.** The hub record points at the ADR; it does not replace it. + +Runbooks and procedures stay as prose in `docs/` and stay unpublished. The test +is not importance — several runbooks are critical — but whether the document +*decides* something for someone else. + +## Consequences + +- `docs/adr/` exists, with a convention, a template and this repo's first three + ADRs. +- Existing prose is not converted wholesale. Content is promoted when it is + next touched, or when a consumer needs to cite it. +- `last_reviewed` and `review_interval` are mandatory here, which is stricter + than the estate norm — of 69 ADRs across 19 repos on 2026-08-17, 21 had YAML + frontmatter and 2 had any notion of a review date. `POLICY-NEXUS-WP-0001` T05 + cannot compute staleness without those fields and must not invent them, since + inventing them would make the publication a second source of truth, which is + that workplan's own top risk. Routed as `RPF-WP-0018` finding F5. +- Session protocol guidance should follow, so the next agent writes an ADR + without being told. Not done in this ADR. + +## Alternatives considered + +**Keep decisions in the hub and have `policy-nexus` read it.** Rejected on +both sides: it would make a read model authoritative, and it would give the +publication surface a source that no repo can diff or review. + +**Add frontmatter to all 24 existing `docs/` files.** Rejected. Most are +runbooks that should not be published, and stamping them with a status would +assert a decision that was never made. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/canon-autonomy-lanes.md b/docs/evidence/2026-10-11-served-publication-history/sources/canon-autonomy-lanes.md new file mode 100644 index 0000000..312b941 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/canon-autonomy-lanes.md @@ -0,0 +1,102 @@ +--- +id: canon-autonomy-lanes +type: standard +title: "Autonomy Lanes (Fleet) v0.1" +domain: custodian +status: accepted +version: "0.1" +owner: the-custodian +revision: "accepted-1" +last_reviewed: "2026-08-31" +review_interval: 6m +created: "2026-07-20" +updated: "2026-07-20" +scope: fleet +related_workplans: + - CUST-WP-0060 +supersedes: none +origin: binky-control/AutonomyPolicy.md (ratified for Binky 2026-07-16, DEC-2026-001) +--- + +# Autonomy Lanes (Fleet) v0.1 + +> Ratified by founder 2026-07-20 (CUST-WP-0060-T01, hub decision f4640f9e). Promotes the +> binky-control AutonomyPolicy lane model to fleet canon, unchanged in +> substance. binky-control's `AutonomyPolicy.md` remains the company-level +> policy instance; this standard makes the lane vocabulary and enforcement +> rules fleet-wide. + +## The rule + +> If the responsible human is unavailable, the system must either continue +> safely, prepare the next decision, or explicitly defer with evidence. It +> must not silently idle. + +Valid states: `proceeding` | `prepared for review` | `deferred by policy`. +Invalid state: `waiting because unsure`. Uncertainty triggers research, +comparison, preparation, or risk classification — not paralysis. +"Ask the human" is never the default. + +## Lanes + +| Lane | Name | Agent may | Human involvement | +| --- | --- | --- | --- | +| **green** | Safe autonomous | research, summarize, classify, draft, organize, test, prepare | none (human-on-the-loop via logs) | +| **blue** | Reversible execution | internal docs, backlog updates, local scripts, reports, local commits | none, but logged | +| **yellow** | Deferred approval | prepare external emails, invoices, offers, releases, payments | async approval via decision records | +| **orange** | Time-window approval | customer/legal/accounting/banking actions needing business hours | engagement records, batched into command days | +| **red** | Human-only | signing, strategic commitments, bank/credential changes, sensitive legal/HR/security | always; agents prepare packages only | + +Optimization target: **maximize green+blue throughput, queue yellow, batch +orange, protect red from automation.** Risk-tiered gating (in-the-loop for +irreversible actions, on-the-loop for recoverable ones) is the only +oversight pattern that scales. + +## Lane as a spine field + +`lane` is a **mandatory field on every work record** (see +`work-record-types_v0.1.md`) and on every agent instance manifest. The +chain is closed end-to-end: + +``` +work record.lane → view routing (autopilot / decision / engagement) + → harness tool profile (enforced allow-list) + → ops-warden credential lane (catalog access) +``` + +Declaring a lane an item does not hold is a policy violation the harness +must catch (tool profile refuses the run). + +## Approval packages (yellow and above) + +An agent hitting a lane boundary prepares a complete decision package, +never a bare question: recommended action, reason, evidence links, risk +level, reversibility, deadline (`needed_by`), attention cost, fallback if +no response, options. + +## Human attention is a WIP-limited workstation + +The responsible human's capacity is modeled like any lane capacity: +attention budget (minutes/day, max decisions/day, max deep topics/day) and +availability as first-class state (`available | evening_window | +unavailable | office_hour_day`), with an explicit escalation allow-list +when unavailable (critical security incident, payment failure, legal +deadline). Yellow routes to the decision view, orange to the engagement +view, both subject to that capacity. + +**Time-boxing is hub-owned:** `needed_by` / `fallback_if_no_response` +windows are watched by the hub → NATS → activity-core; a lapsed window +raises the escalation or executes the declared fallback — never a human +re-reading a file. + +## Raising autonomy + +Autonomy per area is raised only after the system proves reliable there, +by explicit human decision recorded as a decision work record. Lane +assignments are auditable history; lowering a lane needs no approval. + +## References + +- `binky-control/AutonomyPolicy.md` (origin instance) +- `canon/standards/work-record-types_v0.1.md` +- `research/2026-07-19-work-orchestration-best-practices.md` §2 diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/canon-contrib-convention.md b/docs/evidence/2026-10-11-served-publication-history/sources/canon-contrib-convention.md new file mode 100644 index 0000000..7cfcdf8 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/canon-contrib-convention.md @@ -0,0 +1,161 @@ +--- +id: canon-contrib-convention +type: standard +title: "Contribution Convention v0.1" +domain: custodian +status: accepted +version: "0.1" +owner: the-custodian +revision: "accepted-1" +last_reviewed: "2026-08-31" +review_interval: 6m +created: "2026-02-28" +--- + +# Contribution Convention v0.1 + +## Purpose + +This document defines the canonical convention for tracking upstream contributions +across all custodian-ecosystem repositories. A *contribution* is any intentional +engagement with an external upstream project: a bug report, feature request, +extension-point proposal, or pull request. + +Contributions are tracked as Markdown artifacts with typed YAML frontmatter, +committed to the repository that authors them, and indexed in the State Hub DB. +This enables the Custodian to maintain a full audit trail of all upstream +engagement across all six project domains. + +## Artifact Types + +| Code | Name | Description | +|------|------|-------------| +| `br` | Bug Report | A defect reported to an upstream project | +| `fr` | Feature Request | A capability requested from an upstream project | +| `ep` | Extension-Point Proposal | A proposed extension point in an upstream project | +| `upr`| Upstream PR | A pull request submitted or planned for upstream | + +## Directory Layout + +``` +/ + contrib/ + bug-reports/ # BR artifacts + feature-requests/ # FR artifacts + extension-points/ # EP artifacts + upstream-prs/ # UPR artifacts +``` + +Each artifact file follows the naming pattern: + +``` +------.md +``` + +Examples: +- `contrib/bug-reports/2026-03-01--observablehq--framework--plot-tooltip-flicker.md` +- `contrib/upstream-prs/2026-02-26--observablehq--framework--toc-sidebar-inject.md` +- `contrib/extension-points/EP-CUST-002--anthropic--claude-code--hook-on-tool-result.md` + +## Frontmatter Schema + +All contribution artifacts share a common base frontmatter: + +```yaml +--- +id: ------- # unique string ID +type: br | fr | ep | upr # required +target_org: # required +target_repo: # required +title: "Human-readable title" # required +status: draft | submitted | acknowledged | accepted | rejected | merged | withdrawn +domain: infotech | agents | communication | ... # current sector domain; see Repo Classification Standard §6 +related_workstream: # optional +state_hub_contribution_id: # set once registered in State Hub +created: "YYYY-MM-DD" +updated: "YYYY-MM-DD" +--- +``` + +### BR — Bug Report (additional fields) + +```yaml +upstream_version: "1.2.3" # version where bug was observed +reproduction_steps: | + 1. … + 2. … +expected: "What should happen" +actual: "What actually happens" +workaround: "Any known workaround" # optional +upstream_issue_url: "https://…" # optional — link once filed +``` + +### FR — Feature Request (additional fields) + +```yaml +motivation: "Why this feature is needed" +proposed_api: | + # Sketch of the proposed interface +alternatives: "What we do instead today" +upstream_issue_url: "https://…" # optional +``` + +### EP — Extension-Point Proposal (additional fields) + +```yaml +ep_id: EP--NNN # e.g. EP-RAIL-007 +location: "src/file.ts:42" # file:line in upstream where hook would live +proposed_interface: | + # Interface or callback signature sketch +rationale: "Why this extension point is valuable" +upstream_issue_url: "https://…" # optional +``` + +### UPR — Upstream PR (additional fields) + +```yaml +local_component_path: "relative/path/in/this/repo" +target_upstream_file: "path/in/upstream/repo" +upstream_pr_url: "https://…" # set once submitted +draft_pr_body: | + ## Summary + … + + ## Motivation + … +``` + +## ID Schemes + +- **BR / FR / UPR**: `-------` — date-prefixed slug +- **EP**: `EP--NNN` where `` is uppercase (CUST, RAIL, MARK, etc.) + and `NNN` is a zero-padded three-digit sequence per domain (EP-CUST-001, + EP-CUST-002, …). This scheme is an extension of the Railiance EP convention + defined in the staged-promotion-lifecycle workstream; this canon document is + the authoritative master spec. + +## Status Lifecycle + +``` +draft → submitted → acknowledged → accepted → merged + ↘ ↘ + rejected withdrawn +``` + +- `draft`: artifact created locally, not yet sent upstream +- `submitted`: issue filed or PR opened upstream +- `acknowledged`: upstream team has responded +- `accepted`: upstream has agreed to the proposal +- `rejected`: upstream has declined +- `merged`: PR merged / fix released +- `withdrawn`: we decided not to pursue + +## Relationship to State Hub + +Once an artifact is registered via `register_contribution()` MCP tool or +`POST /contributions/` API, the State Hub assigns a UUID and returns it. +That UUID is written back into `state_hub_contribution_id` in the frontmatter. + +The State Hub is a **read/cache layer** — the Markdown file is always the +authoritative source of truth. If the DB is reset, contributions can be +re-ingested from the files. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/canon-project-repository-flavor.md b/docs/evidence/2026-10-11-served-publication-history/sources/canon-project-repository-flavor.md new file mode 100644 index 0000000..9648119 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/canon-project-repository-flavor.md @@ -0,0 +1,282 @@ +--- +id: canon-project-repository-flavor +type: standard +title: "Project Repository Flavor (prj-) v0.1" +domain: custodian +status: accepted +version: "0.1" +owner: the-custodian +revision: "accepted-1" +last_reviewed: "2026-08-31" +review_interval: 6m +created: "2026-08-09" +updated: "2026-08-09" +scope: fleet +related_workplans: + - SHR-WP-0001 + - CUST-WP-0050 +related_adrs: + - ADR-001 + - ADR-005 +supersedes: none +closes: + - "ADR-005 deferred naming convention" + - "CUST-WP-0050 open question D1a (naming + archival trigger details)" +--- + +# Project Repository Flavor (`prj-`) v0.1 + +## Purpose + +Define the durable **project repository** flavor used for complex +cross-repository efforts: naming, required files, authority boundary, +lifecycle, residual handoff, and archive procedure. + +This standard implements and completes **ADR-005** (*Cross-Repo Workplans Live +in Dedicated Project Repos*). It does **not** replace the Repo Classification +Standard: project repositories still use `category: project` in +`.repo-classification.yaml`. + +## When to use a project repository + +Create a project repository when **all** of the following hold: + +1. The work coordinates change across **two or more** functional repositories. +2. Binding the coordination workplan to any single product/tooling repo would + misrepresent ownership or pollute that repo's history. +3. The effort has a **bounded outcome** with explicit success and retirement + conditions (not an open-ended product roadmap). + +Do **not** create a project repository for: + +- single-repo workplans; +- small cross-cutting fixes that fit one lead repo's workplan plus dependency + links; +- durable products, libraries, or services (those use `INTENT.md` and ordinary + product/tooling/research categories); +- long-lived domain charters (those remain under `the-custodian/canon/projects/`). + +## Naming + +| Element | Convention | +| --- | --- | +| Git / local directory | `prj-` | +| Hub `repo` slug | same as directory name | +| Workplan prefix | short uppercase prefix derived from the project, ending in `-WP-` (example: `SHR-WP-` for State Hub Retirement) | +| Classification | `category: project` | + +**Canonical prefix is `prj-`.** ADR-005's illustrative `proj-` form is +superseded for new repositories. Do not invent parallel prefixes (`project-`, +`xrepo-`, …). + +Slug rules: + +- lowercase kebab-case; +- outcome-oriented (`prj-state-hub-retirement`), not a random ticket id; +- stable for the life of the project (rename only with an explicit migration + note in `history/`). + +## Authority boundary + +| Concern | Owner | +| --- | --- | +| Project goal, success gates, retirement conditions | project repo (`GOAL.md`) | +| Architecture decisions, sequencing, dependency map, migration ledger, risks, consolidated evidence | project repo | +| Cross-repo workplans that coordinate the effort | project repo (`workplans/`) | +| Production implementation, tests, deployables | **participating functional repositories** | +| Per-repo implementation workplans and local evidence | each functional repository | +| Durable post-project standards and ADRs | permanent homes (`the-custodian` canon, or the owning product/tooling repo) | + +**Hard rules:** + +1. Project repositories **MUST NOT** host production implementation code for the + systems being changed. +2. Project workplans **link** child workplans by stable ID; they **MUST NOT** + copy or replace child task lists. +3. The hub remains a read/index layer; work records originate as files + (ADR-001). +4. Results of the project live primarily in the modified functional repositories + and promoted canon. The project repo is coordination + provenance. + +## Required files + +At registration / first meaningful commit: + +| Path | Required | Role | +| --- | --- | --- | +| `GOAL.md` | **yes** | Outcome, invariants, success gates, project retirement conditions. **Replaces `INTENT.md` for this flavor.** | +| `SCOPE.md` | **yes** | Project authority, participating repos, in/out of scope, work-record rule. | +| `README.md` | **yes** | Short entry point linking `GOAL.md`, `SCOPE.md`, genesis/history, active workplans. | +| `AGENTS.md` | **yes** | Agent session protocol for this project (orient → work → close; hub integration). | +| `.repo-classification.yaml` | **yes** | `category: project` plus domain / capability tags for the *effort*. | +| `workplans/` | **yes** | At least one foundation workplan; prefix unique to the project. | +| `history/` | recommended | Genesis and significant milestones (why the project exists). | +| `WORK-RECORDS.md` | generated | Hub index; do not hand-edit. | +| `.custodian-brief.md` | generated | Offline orientation brief. | + +### `GOAL.md` (not `INTENT.md`) + +Durable product and tooling repositories explain **why they exist** with +`INTENT.md`. Project repositories are temporary and outcome-bounded; they use +**`GOAL.md`** instead. + +`GOAL.md` MUST include: + +1. **Outcome** — what success looks like in one short section. +2. **Invariants** — rules that must hold during the project. +3. **Success gates** — checkable conditions for “goal achieved”. +4. **Project retirement** — conditions under which *this repository* may be + archived (usually: gates met + residuals handed off + durable decisions + promoted). + +Recommended frontmatter: + +```yaml +--- +repo: prj- +repo_flavor: project +project_status: draft | active | completed | archived +started: "YYYY-MM-DD" +reviewed: "YYYY-MM-DD" +--- +``` + +**Anti-pattern:** shipping both `INTENT.md` and `GOAL.md` in a `prj-` repo. +If a durable intent emerges, promote it into a permanent repository's +`INTENT.md` (or canon) and keep the project goal as the temporary outcome +document. + +### Relation to durable `INTENT.md` + +| Repository kind | Purpose document | +| --- | --- | +| product / tooling / research / business / experimental | `INTENT.md` | +| **project (`prj-`)** | **`GOAL.md` only** | + +Tooling that scaffolds repositories (e.g. `statehub register`) MUST treat +`GOAL.md` + `repo_flavor: project` as sufficient purpose documentation for +`prj-` repos and MUST NOT require inventing an `INTENT.md` that pretends the +project is a permanent product. + +## Lifecycle + +``` +draft → active → completed → archived +``` + +| Status | Meaning | Repo / hub signals | +| --- | --- | --- | +| `draft` | Convention and foundation still forming; may not yet be registered | `project_status: draft`; workplans often `proposed` / `ready` | +| `active` | Coordination and child implementation in progress | `project_status: active`; at least one workplan `active` or `ready` | +| `completed` | All success gates have accepted evidence; residuals have live owners outside this repo | `project_status: completed`; project workplans `finished` or cancelled with rationale | +| `archived` | Repository is read-only provenance | forge archive flag; hub repo/workplan `archived`; no normal writes | + +Promotion rules: + +- `draft → active` when `GOAL.md` / `SCOPE.md` are reviewable and the foundation + workplan is `ready` or `active`. +- `active → completed` only when **every** `GOAL.md` success gate has accepted + evidence **and** residuals are live work records elsewhere (see below). +- `completed → archived` after the completion record is written, durable + decisions are promoted, and a final consistency/link check passes. + +## Residuals before completion + +Actionable leftovers MUST NOT live only in finished workplan prose or +`SCOPE.md`. Before any project workplan is set to `finished`, and again before +`project_status: completed`, each residual becomes a **live work record** +outside the closing narrative: + +- intake with `origin: residual` + `origin_ref: `, or +- a next workplan / decision / engagement / register-entry in a permanent repo. + +Canon: `work-record-types_v0.1.md` § Residuals. Fleet residual listing is a +State Hub concern, not a project-local queue file. + +## Completion record + +Before archival, the project repository MUST contain a completion record +(recommended path: `history/YYYY-MM-DD-completion.md`) that lists: + +1. deployed or merged revisions that realize the outcome; +2. every participating repository touched, with links to merged PRs/commits or + child workplan IDs; +3. promoted durable artefacts (canon paths, ADRs, standards); +4. migration and retirement evidence pointers (counts, cutover windows, backups); +5. residual work-record IDs and their owning repos; +6. rollback expiry or remaining operational obligations, if any. + +Without this record, do not archive. + +## Archive procedure + +1. Verify all success gates and retirement conditions in `GOAL.md`. +2. Confirm residuals are live and owned outside this repository. +3. Promote remaining durable decisions/standards to permanent homes; leave + pointers from the project repo. +4. Finish or cancel project workplans; move finished files to + `workplans/archived/YYMMDD--.md` per workplan convention. +5. Write the completion record. +6. Set `project_status: archived` in `GOAL.md` frontmatter. +7. Run `statehub fix-consistency` (or equivalent) so the hub reflects finished + workplans and the archived status. +8. Archive the repository in the forge (read-only); do **not** delete it. +9. Optionally mark the hub registration archived / read-only. + +**Archive means read-only provenance, never deletion.** Historical evidence and +coordination narrative remain queryable. + +## Workplan and agent conventions + +- Workplan files: `workplans/-WP-NNNN-.md` with fleet status + vocabulary (`proposed` … `archived`). +- Session start order for agents: `GOAL.md` → `SCOPE.md` → genesis/history → + active workplans (see project `AGENTS.md`). +- Progress and task status follow ordinary State Hub / fix-consistency rules. +- Ad-hoc low-risk fixes may use `workplans/ADHOC-YYYY-MM-DD.md` with canonical + id `{PREFIX}-WP-ADHOC-YYYY-MM-DD`; anything multi-step belongs in a normal + workplan. + +## Minimal layout example + +```text +prj-example-cutover/ + GOAL.md + SCOPE.md + README.md + AGENTS.md + .repo-classification.yaml + history/ + 2026-08-09-genesis.md + workplans/ + EX-WP-0001-foundation.md +``` + +## Reference instance + +The first repository under this standard is +`prj-state-hub-retirement` (State Hub retirement / hub-core consolidation). +Use it as the structural reference when scaffolding later `prj-` repos; do not +copy its domain-specific content. + +## Conformance checklist + +A repository conforms to the `prj-` flavor when: + +- [ ] Named `prj-` and classified `category: project`. +- [ ] Has `GOAL.md` with outcome, invariants, success gates, and retirement. +- [ ] Has no durable-product `INTENT.md` (or documents an explicit temporary + exception during migration of a mis-scaffolded repo). +- [ ] Has `SCOPE.md`, `README.md`, `AGENTS.md`, and `.repo-classification.yaml`. +- [ ] Hosts coordination workplans only; implementation lives in child repos. +- [ ] Links child workplans by ID rather than duplicating their tasks. +- [ ] Defines archive/completion discipline consistent with this standard. +- [ ] On completion, writes a completion record and archives read-only. + +## Related + +- ADR-001 — Workplans and Work Items Are Repository Artefacts +- ADR-005 — Cross-Repo Workplans Live in Dedicated Project Repos +- `repo-classification-standard_v1.0.md` — `category: project` +- `work-record-types_v0.1.md` — residuals and work-record kinds +- `workplan-terminology-fleet_v0.1.md` — workplan vocabulary diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/canon-work-record-types.md b/docs/evidence/2026-10-11-served-publication-history/sources/canon-work-record-types.md new file mode 100644 index 0000000..403cd5e --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/canon-work-record-types.md @@ -0,0 +1,243 @@ +--- +id: canon-work-record-types +type: standard +title: "Work Record Types & Identity (Fleet) v0.1" +domain: custodian +status: accepted +version: "0.1" +owner: the-custodian +revision: "accepted-1" +last_reviewed: "2026-08-31" +review_interval: 6m +created: "2026-07-20" +updated: "2026-08-23" +scope: fleet +related_workplans: + - CUST-WP-0060 +supersedes: none +machine_copy: canon/standards/work-record-types.yaml +--- + +# Work Record Types & Identity (Fleet) v0.1 + +> Ratified by founder 2026-07-20 (CUST-WP-0060-T01, hub decision f4640f9e). Source: +> `research/WorkOrchestrationArchitectureDraft.md` v0.2 (founder-reviewed +> 2026-07-20). Extends — does not replace — +> `workplan-terminology-fleet_v0.1.md` and ADR-001/ADR-005. + +## Purpose + +Define **work record** as the umbrella term for every identified, +lifecycle-bearing coordination artefact in the fleet; register the closed +list of work-record **kinds**, their id schemes and abstract lifecycles; +and fix the two-layer identity rule. This is the backbone convention for +all work — planning, development, testing, operations, security & +compliance, controlling, billing, marketing, sales — under one conceptual +framework (different demands are met by flow profiles and kind-specific +fields, never by parallel ontologies). + +## Core definition + +A **work record** is any identified, lifecycle-bearing coordination +artefact. A **task** is one *kind* of work record — the smallest executable +unit. Every work record, regardless of kind, carries the **conventional +spine**: + +| Field | Convention | +| --- | --- | +| `id` | canonical name per the type registry below | +| `uuid` | UUIDv7, hub-assigned at first index, written back to the source file | +| `kind` | one of the registered kinds | +| `lane` | autonomy lane (`autonomy-lanes_v0.1.md`) | +| `status` | abstract lifecycle of the kind (below) | +| `owner` | agent identity (`agt-…`) or human | +| `repo` | owning repo (ADR-001/ADR-005 anchor) | +| `tags` | policy + derived tags (§ Tags) | +| `created` / `updated` | dates (UUIDv7 supplies fine-grained internal time) | + +Kind-specific fields (budget, evidence, counterparty, deadline_pressure, +attention_cost, …) extend the spine; they never replace it. + +## Kind registry (closed list) + +| Kind | Id scheme | Meaning | Absorbs / legacy | +| --- | --- | --- | --- | +| `workplan` | `{PREFIX}-WP-NNNN` · `{PREFIX}-WP-ADHOC-YYYY-MM-DD` | structured, dependency-bearing body of work; repository-qualified daily container for small same-day tasks | unqualified `ADHOC-YYYY-MM-DD`; `MASON-0001` (grandfathered) | +| `task` | `{WP-ID}-TNN` | smallest executable unit | unqualified `ADHOC-YYYY-MM-DD-TNN`; `MASON-0001-TNN` (grandfathered); issue-core issues become external projections only | +| `intake` | `{PREFIX}-IN-NNNN` | spark: idea, finding, directive, request | hub suggestions (legacy, read-only close-out); `AWQ-NNN` (grandfathered) | +| `decision` | `{PREFIX}-DEC-YYYY-NNN` | approval package + resolution | `DEC-YYYY-NNN` (binky, grandfathered as-is) | +| `engagement` | `{PREFIX}-ENG-YYYY-NNN` | scheduled external interaction (counterparty, prepared material, time window) | `OH-YYYY-NNN` (grandfathered) | +| `register-entry` | existing schemes: `RISK-NNN`, `RISK-F-NNNN`, `RISK-REG-NNNN`, `TD-…`, `CAP-…`, `CCR-YYYY-NNNN`, `ADR-NNN` | risks/findings, regulatory records, technical debt, capability requests, credential change requests, architecture decisions | unchanged semantics, brought under the spine | + +Rules: + +1. **Prefix grammar:** `{PREFIX}` is one or more uppercase alphanumeric + segments, each beginning with a letter, joined by hyphens. Both compact + prefixes such as `CUST` and repository-derived prefixes such as + `RAPP-OPENBAO` or `RAIL-K8S` are canonical. The final `-WP-` token remains + the unambiguous kind separator. +2. **The registry is closed.** An id pattern not registered in + `work-record-types.yaml` fails CI and `fix-consistency` (the *sidetrack + detector*). New kinds/prefixes are added by canon PR — deliberately + cheap (one YAML entry + one schema) so the legitimate path is easier + than any workaround. +3. **Grandfathering:** existing ids are never renamed. Legacy prefixes map + to kinds in the registry; retirement of a *scheme* (never of records) + follows the legacy-meter discipline of + `workplan-terminology-fleet_v0.1.md`. + +Compatibility correction 2026-08-23: State Hub message +`24504ce7-4f73-47da-a150-629b07b37073` exposed that the machine pattern did +not accept the already owner-registered `RAPP-OPENBAO-WP` prefix. The grammar +now matches ADR-007's repository-derived prefix model; prefix ownership and +uniqueness remain separate registry checks and are not weakened by this syntax +correction. + +The same compatibility review initially registered unqualified +`ADHOC-YYYY-MM-DD` containers. A same-day collision between Activity Core and +Net Kingdom then demonstrated that this form violates ADR-007's fleet-namespace +uniqueness rule and cannot be used with deterministic UUID derivation. New +daily records therefore use `{PREFIX}-WP-ADHOC-YYYY-MM-DD`; the filename remains +`workplans/ADHOC-YYYY-MM-DD.md`. Existing unqualified ids are grandfathered and +are never silently re-derived or disambiguated. Ad-hoc containers remain only +for low-risk work completed directly. + +Risk Nexus findings (`RISK-F-*`) and regulatory records (`RISK-REG-*`) are +registered as variants of `register-entry`, not as parallel work kinds. Their +lifecycles remain owned by the register convention. `RISK-N-*` notes are +deliberately not registered: a note has no lifecycle, so it does not meet the +core definition of a work record. A note becomes part of the backbone only +when an event promotes its substance into a lifecycle-bearing kind. + +## Identity layering + +- **UUIDv7 is primary** for bookkeeping, relations, dependencies, and + history. It is stable across renames, migrations, and terminology + transitions. +- **Canonical names are primary for humans and agents** — UI, CLI, API + output, prose, commits. UUIDs stay inspectable on demand but are never + required reading. +- The hub writes the UUID back into the source file at first index + (pattern: today's `state_hub_*_id` fields). +- A repeated source block with the same canonical id **and the same UUID** is + one record with duplicate source occurrences. Index it once, retain every + source location as diagnostic provenance, and report the duplication for + governed cleanup. The repetition must not mint a second record. The same + canonical id with different UUIDs is an identity collision and fails closed. + +## Abstract lifecycles (canon-fixed, minimal) + +| Kind | Lifecycle | +| --- | --- | +| task | `wait → todo → progress → done \| cancel` (InfoTechCanon) | +| workplan | `proposed → ready → active → blocked → backlog → finished → archived` | +| intake | `open → vetted → routed → closed(promoted \| declined \| absorbed)` | +| decision | `prepared → resolved(approved \| rejected \| revised \| deferred)` | +| engagement | `queued → prepared → done` | +| register-entry | per existing register conventions (unchanged) | + +**Promotion is a first-class transition:** `intake.routed → +workplan | task | decision | engagement`, executed by one CLI/MCP call that +writes both artefacts, sets `promoted_to` / `origin` back-links, and +re-syncs. Manual transcription of an intake item into other kinds is a +process defect. + +**Kind-specific rigor lives in flow profiles**, not extra states: named +task-flow-engine workstation definitions whose entry/exit assertions encode +per-work-kind gates (e.g. compliance tasks cannot exit `progress` without +an evidence link). Profiles are YAML in repos, in the same reconciliation +loop. + +## Residuals (role, not kind) + +A **residual** is work intentionally left after a workplan (or large task +bundle) finishes. It is **not** a registered kind — inventing a parallel +“residuals queue” or parking leftovers only in `SCOPE.md` / finished-file +prose is a process defect. Residuals must be **live work records** so the +hub can list them across repos and domains. + +| Residual shape | Capture as | Required links | +| --- | --- | --- | +| Small Green/Blue follow-up, parkable | **intake** (e.g. AWQ / `*-IN-*`) | `origin: residual`, `origin_ref: ` | +| Multi-step / dependency-bearing | **workplan** (spawn next WP) | body or frontmatter names parent WP; optional `origin`/`origin_ref` when promoted from intake | +| Needs founder choice | **decision** | same origin fields when created from residual intake | +| Needs founder time window | **engagement** | same | +| Persistent gap / risk | **register-entry** (risk, tech debt, …) | cross-link parent WP in notes | + +**Close discipline for finished workplans:** + +1. Short residual **narrative** may remain under the closing task (human + context). +2. **Before** `status: finished`, each residual that is still actionable + becomes a live record (intake and/or child workplan). Prose alone is not + the backlog. +3. Progress milestone may name the handoff ids. +4. `statehub fix-consistency` so the index (`WORK-RECORDS.md`, hub) reflects + the handoff. + +**Fleet list (future CLI):** filter open/vetted/routed intakes (and ready/ +active workplans) with `origin ∈ {residual, handoff}` and optional +`topic_id` / `repo` / `origin_ref`. Implementation home for that query is +**state-hub** (`statehub residuals` / API), not domain repos. + +## Source files, index, and views + +1. **Any repo file is a potential source of work records.** Records are + authored as schema-valid YAML blocks in context — thematic live + documents, meeting notes, design docs, triage logs. The schema is the + contract, not the location. Workplans keep dedicated files. +2. **A generated per-repo work-record index** references every registered + record in its source location (transclusion-style; markitect-family + tooling). It is the "everything across all files" orientation view and a + concurrency cross-check. +3. **Every working list is a generated view** (`[auto]`-marked) over + indexed records — autopilot views, decision queues, engagement batches, + briefs. Hand-maintained completed-logs and queue tails are retired. +4. Ordering of views is **lane + priority + age** by default; dependency + capture optional; WSJF optional and situational (overload triage only). + +## Tags + +Two classes, both in `tags`, never in `status`: + +- **Derived tags** — computed, read-only in files: `stalled`, `stale`, + `overdue-decision`, `budget-breached`, `orphaned`. +- **Policy tags** — assigned under tagging policies: `automatable`, + `needs-human`, `unclear-implementation`, `red-adjacent`, + `compliance-relevant`, `residual` (optional badge when origin is not used). + +States migrate badly; tags migrate trivially. Anything situational is a tag. + +**Origin values (intake and promoted children):** use stable strings, not free +prose. For residuals prefer `origin: residual` (or `handoff`) and +`origin_ref: ` (e.g. `BINKY-WP-0006`). Other established +origins (e.g. `legacy-suggestion:`, mail-triage ids) remain valid. + +## Budgets + +Guardrail envelopes resolve programmatically at run start: +workplan-frontmatter override → per-repo `budgets.yaml` (lane/default +envelopes) → **global best-guess default**. Nothing runs unbounded. Budget +resolution and enforcement is automation infrastructure, never agentic +effort; metering via token events; breach ⇒ `budget-breached` + escalation. + +## Reconciliation loop (normative) + +``` +author (any repo file) → CI schema validation → commit + → fix-consistency: register/sync, UUID write-back + → hub read model → generated index + views → NATS events → activity-core +``` + +The hub remains a read model per ADR-001; runtime operations data (logs, +metrics, run histories, token events) is the sanctioned DB-only exception. + +## References + +- `research/WorkOrchestrationArchitectureDraft.md` (v0.2) +- `research/2026-07-19-work-orchestration-infrastructure-survey.md` +- `research/2026-07-19-work-orchestration-best-practices.md` +- `canon/architecture/adr-001-workplans-as-repo-artefacts.md`, `adr-005-…` +- `canon/standards/workplan-terminology-fleet_v0.1.md` +- `canon/standards/autonomy-lanes_v0.1.md` +- `state-hub/docs/task-flow-engine-spec.md` diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/canon-workplan-terminology-fleet.md b/docs/evidence/2026-10-11-served-publication-history/sources/canon-workplan-terminology-fleet.md new file mode 100644 index 0000000..495b834 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/canon-workplan-terminology-fleet.md @@ -0,0 +1,147 @@ +--- +id: canon-workplan-terminology-fleet +type: standard +title: "Workplan Terminology (Fleet) v0.1" +domain: custodian +status: accepted +version: "0.2" +owner: the-custodian +revision: "accepted-1" +last_reviewed: "2026-08-31" +review_interval: 6m +created: "2026-07-08" +updated: "2026-07-20" +scope: fleet +related_workplans: + - CUST-WP-0055 + - STATE-WP-0054 + - STATE-WP-0069 + - CUST-WP-0060 +supersedes: none +--- + +# Workplan Terminology (Fleet) v0.1 + +## Purpose + +Define **workplan** as the canonical fleet term for repo-backed deliverable work +indexed by State Hub. Preserve explicit **legacy bridges** where APIs, events, +generated fields, or historical documents still say `workstream`, until +metered retirement criteria are met. + +This standard complements: + +- **ADR-001** — workplans originate as repo files; the hub indexes them. +- **STATE-WP-0054** — State Hub compatibility layer and `legacy-meter`. +- **STATE-WP-0069** — State Hub legacy interface retirement (child plan). +- **CUST-WP-0055** — fleet-wide coordination and prose migration. + +Authoritative interface matrix for State Hub: +`state-hub/docs/workplan-terminology-transition.md`. + +## Canonical term + +| Concept | Fleet term | Meaning | +| --- | --- | --- | +| Repo-backed body of work | **workplan** | Markdown file under `workplans/` per ADR-001 | +| Workplan file id | `{PREFIX}-WP-NNNN` | e.g. `CUST-WP-0055`, `STATE-WP-0069` | +| Hub index row | **workplan** (product term) | DB entity keyed by UUID; may still use legacy column names internally | +| Task within a workplan | **task** | `id: {WP}-TNN` in workplan file blocks | + +## Work-record umbrella (v0.2 addendum, CUST-WP-0060) + +The workplan and task definitions above are unchanged. Since v0.2 they are +two *kinds* of the fleet umbrella term **work record** — any identified, +lifecycle-bearing coordination artefact. The full kind registry (workplan, +task, **intake item**, **decision**, **engagement**, **register entry**), +the conventional spine, identity layering (UUIDv7 internal, canonical names +external), and grandfathered legacy id schemes (`AWQ-`, `DEC-`, `OH-`) are +normative in `work-record-types_v0.1.md` (+ machine registry +`work-record-types.yaml`). Autonomy-lane vocabulary is normative in +`autonomy-lanes_v0.1.md`. "Suggestion" is a legacy bridge name for the +intake kind (state-hub suggestions table: read-only legacy pending +close-out). This file keeps its `_v0.1` name for reference stability; the +frontmatter `version` field is authoritative. + +Use **workplan** in human-facing prose: `SCOPE.md`, `AGENTS.md`, `INTENT.md`, +README, workplan bodies, operator runbooks, and agent session guidance. + +## Legacy bridges (keep until retired) + +Do **not** mass-rename these in the CUST-WP-0055 migration window: + +| Surface | Canonical | Legacy bridge | Retirement owner | +| --- | --- | --- | --- | +| Workplan frontmatter link | (implicit workplan UUID) | `state_hub_workstream_id` | Separate bridge-field migration | +| REST/MCP parameters | `workplan_id` | `workstream_id` | `STATE-WP-0069` + legacy-meter | +| REST routes | `/workplans/` | `/workstreams/` | `STATE-WP-0069` | +| MCP procedures | `create_workplan`, etc. | `create_workstream`, etc. | `STATE-WP-0069` | +| State Hub completion event | `org.statehub.workplan.completed` | `org.statehub.workstream.completed` | `STATE-WP-0069` T05 | +| activity-core catalog (custodian-era) | `org.statehub.workplan.completed` | `org.workstream.completed` | `CUST-WP-0055` T03 | +| Archived workplan prose | workplan (when editing) | historical "workstream" text | Grandfathered; header note only | +| Grandfathered filenames | — | paths containing `workstream` | ADR-001 non-rename policy | + +**Do not** introduce a bare `org.workplan.completed` event subject. State Hub +uses the `org.statehub.*` namespace. + +## Event subjects (normative) + +| Subject | Status | Notes | +| --- | --- | --- | +| `org.statehub.workplan.completed` | **preferred** | Emitted on workplan finish; subscribe here | +| `org.statehub.workstream.completed` | legacy (metered) | Dual-published today; retire per legacy-meter | +| `org.workstream.completed` | legacy (catalog) | activity-core custodian-era type; align to State Hub subject | + +## Agent and documentation rules + +1. **Write workplan** in instructions, orientation steps, and error messages + aimed at humans or coding agents. +2. **Mention workstream only** in an explicit compatibility footnote, e.g.: + > Legacy term: State Hub still exposes `/workstreams` and `workstream_id` + > aliases until legacy-meter retires them (`STATE-WP-0069`). +3. **Leave unchanged** in code or docs when the string is a wire key, route, + legacy-meter registry entry, or `state_hub_workstream_id` frontmatter field. +4. **Prefer** `GET /workplans/` and `workplan_id` in new examples and scripts. + +Regenerated agent files (`state-hub/scripts/update_agent_instruction_files.py`) +should follow rules 1–2. Templates live under +`state-hub/scripts/project_rules/*.template`. + +## Retirement rule (unchanged) + +A legacy interface may be removed only when **all** are true: + +- registered in State Hub `legacy-meter`; +- replacement reference verified; +- no manual hold; +- zero measured calls in the review window. + +State Hub owns usage evidence; activity-core may run weekly review activities. +See `state-hub/docs/workplan-terminology-transition.md`. + +## Verification + +Fleet terminology drift is measured with: + +```bash +python tools/scan_workstream_terminology.py +python tools/scan_workstream_terminology.py --repo --json +``` + +Baseline artefact (2026-07-08): `docs/evidence/workstream-terminology-baseline-20260708.json`. + +Exit criteria and scan exclusions: `CUST-WP-0055` T08. + +## Out of scope (this standard) + +- Renaming `state_hub_workstream_id` in workplan frontmatter. +- Database table or ORM model renames (completed in `STATE-WP-0065`). +- Rewriting archived workplan bodies in bulk (`CUST-WP-0055` T07). + +## References + +- `canon/architecture/adr-001-workplans-as-repo-artefacts.md` +- `state-hub/docs/workplan-terminology-transition.md` +- `state-hub/workplans/STATE-WP-0054-workplan-terminology-transition-legacy-meter.md` +- `state-hub/workplans/STATE-WP-0069-workplan-terminology-legacy-retirement.md` +- `workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md` diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/coulomb-estate-architecture.md b/docs/evidence/2026-10-11-served-publication-history/sources/coulomb-estate-architecture.md new file mode 100644 index 0000000..565a92e --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/coulomb-estate-architecture.md @@ -0,0 +1,230 @@ +--- +id: coulomb-estate-architecture +title: "Coulomb estate architecture" +status: proposed +owner: the-custodian +revision: "draft-3" +version: "0.1" +last_reviewed: "2026-08-31" +review_interval: 6m +--- + +# Coulomb estate — Software Architecture Documentation (arc42) + +## About this document + +This is the first-wave **estate map**. It describes how the Coulomb / +Custodian estate is put together: canons, hubs, rails, and publication. +System-level arc42 documents (Railiance, NetKingdom, State Hub, Policy +Nexus) live in their owning repos. Chapter 9 lists estate ADRs; it does +not paste them. + +--- + +## 1. Introduction and Goals + +the-custodian holds meaning, boundaries, and continuity for a local-first +agent estate. Implementation lives in product repos. Coordination state +is a read-model of repository files, not the origin of those files. + +### 1.1 Requirements Overview + +- Work, decisions, and canon originate as files in the owning repo. +- A publication surface keeps governing documents at permanent URLs. +- Runtime (Rails, rApps, identity, storage) is consumed from platform + packages, not reimplemented in the custodian. +- Cross-repo work is coordinated, not owned, by a dedicated project repo + when it does not belong to one product. + +### 1.2 Quality Goals + +1. Rebuildability — State Hub can be reconstructed from registered + repository files (ADR-001). +2. Concept ownership — canons import, they do not redefine (ADR-006). +3. Permanence of published policy addresses (policy-nexus ADR-0001). +4. Honest currency — stale documents are visibly stale. + +### 1.3 Stakeholders + +| Role | Concern | +| --- | --- | +| Operator | What must be discussed in person; ratification. | +| the-custodian | Canon, values, constitution, estate ADRs. | +| Product repos | Implementation and per-repo ADRs. | +| railiance-platform | Substrate: DNS, TLS, ingress, hosting. | +| policy-nexus | Publication only. | +| info-tech-canon | Semantic model, not this estate's building blocks. | + +--- + +## 2. Architecture Constraints + +N/A in this revision — local-first files, no second source of truth, +stdlib-preferring tooling, single-node rail availability. To be written +as first-wave complete. + +--- + +## 3. System Scope and Context + +**In:** estate canon (`constitution`, `standards`, `architecture` ADRs), +the federation of canons, the publication contract, the hub-as-read-model +rule, and the map of first-wave systems. + +**Out:** product implementation, InfoTechCanon's landscape model, +regulatory intake (`risk-nexus`), and per-system deployment detail +(those belong in the system arc42). + +### 3.1 Business Context + +The estate is a set of repositories that together run Coulomb products +and the agent work-factory. Readers need one map of what governs what. +Owners need a place that is not also the implementation. + +### 3.2 Technical Context + +| Neighbour | Interface | +| --- | --- | +| Owning git repos | Source of workplans, ADRs, canon. | +| State Hub | Derived index / cache of those files. | +| policy.coulomb.social | Generated publication of canon and ADRs. | +| Railiance reef | Runtime for hubs, rApps, Forgejo. | +| NetKingdom | Identity, tenancy, IAM profile. | +| info-tech-canon | Imported semantics, not estate structure. | + +--- + +## 4. Solution Strategy + +- **Files first.** Workplans and ADRs are markdown in git. The hub + rebuilds from them (ADR-001, ADR-003). +- **One registrar.** Workplan identifiers are globally unique; this + workstation is not the registrar (ADR-007). +- **Three canons, federated.** Custodian (governance), InfoTechCanon + (information-system semantics), CommerceCanon (counterparty + semantics). They import, they do not redefine (ADR-006). +- **Publish, do not author.** policy-nexus reads owning repos and emits + static addresses. It never writes back. +- **Project repos for cross-repo work** (`prj-*`), not an unbound hub + workplan (ADR-005). + +--- + +## 5. Building Block View + +### 5.1 Level 1 – System/Top-Level + +```text + ┌─────────────────────────┐ + │ the-custodian │ + │ constitution, values, │ + │ estate ADRs, memory │ + └────────────┬────────────┘ + ┌─────────────────────┼─────────────────────┐ + ▼ ▼ ▼ + ┌───────────────┐ ┌────────────────┐ ┌─────────────────┐ + │ info-tech- │ │ commerce-canon │ │ net-kingdom │ + │ canon │ │ │ │ (identity / │ + │ (semantics) │ │ │ │ tenancy) │ + └───────────────┘ └────────────────┘ └────────┬────────┘ + │ + ┌───────────────┐ ┌────────────────┐ │ + │ state-hub │◄───│ product repos │◄────────────┘ + │ (read model) │ │ + project repos│ + └───────────────┘ └────────┬───────┘ + │ + ┌────────────┴────────────┐ + ▼ ▼ + ┌────────────────┐ ┌─────────────────┐ + │ railiance │ │ policy-nexus │ + │ (reef, rApps) │ │ (publication) │ + └────────────────┘ └─────────────────┘ +``` + +### 5.2 Level 2 – Key Components + +N/A in this revision. + +### 5.3 Level 3 – Internal Structure (as needed) + +N/A in this revision. + +--- + +## 6. Runtime View + +N/A — estate coordination is file sync plus hub rebuild, not a single +runtime scenario. System runtimes belong in their own arc42. + +--- + +## 7. Deployment View + +N/A — Railiance owns where things run. This document names the +substrate; it does not map nodes. + +--- + +## 8. Cross-Cutting Concepts + +N/A in this revision. Concept ownership and the hub/cache split are +already in §4 and the ADRs in §9. + +--- + +## 9. Architecture Decisions + +Estate ADRs in `the-custodian/canon/architecture/`. Status is the source +front-matter. + +| Id | Status | Decision | +| --- | --- | --- | +| `CUST-ADR-001` | accepted | File-backed work originates in repositories; the Hub projects it and separately owns declared Hub-native records. | +| `CUST-ADR-002` | accepted | Custodian agent runtime design. | +| `CUST-ADR-003` | accepted | Hub state is derived and fingerprint-invalidated. | +| `CUST-ADR-004` | accepted | Connectivity-first network posture. | +| `CUST-ADR-005` | accepted | Cross-repo work lives in a `prj-*` repo. | +| `CUST-ADR-006` | accepted | Three canons; import, do not redefine. | +| `CUST-ADR-007` | accepted | Namespace-aware work-record identity, deterministic Hub ids, and repository worker topology. | +| `adr-008` | superseded | Relocated to NetKingdom Tenancy Posture. | +| `CUST-ADR-010` | proposed | Two kinds of hub data; local cache vs authority. | +| `CUST-ADR-011` | proposed | Federated namespaces and reconciliation limits. | +| `CUST-ADR-012` | accepted | Forge is the projection source; unpushed working-copy state is preliminary. | + +Related and published elsewhere: policy-nexus ADR-0001; Tenancy Posture +and IAM Profile v0.3 plus the current NetKingdom security standards; +NetKingdom ADR-0006–0008 and 0010–0015; railiance-master ADR-0001–0009; +activity-core ACT-ADR-001–007; ops-warden ADR-0001–0005 and 0007–0010; +railiance-platform RPLAT-ADR-0001–0003. + +Unresolved WP-0003 conflicts (hosts/infra duplicate ADR-003/004, +coulomb-social ADR-0002 partial supersession) are **not** listed as +current. + +--- + +## 10. Quality Requirements + +N/A in this revision — rebuildability and currency already have +mechanical checks (`fix-consistency`, `make currency`). + +--- + +## 11. Risks and Technical Debt + +N/A in this revision. Known residuals are the Forge-derived reset and +preliminary-overlay implementation under ADR-012. Markitect `arc42-v1` is not +registered yet. + +--- + +## 12. Glossary + +| Term | Meaning | +| --- | --- | +| Estate | The set of Coulomb / Custodian repos, canons, hubs, and rails. | +| Canon | Governing documents owned by one of the three federated canons. | +| Read model | A derived index. Never the origin of work or decisions. | +| Publication entry | One explicit object in policy-nexus `publication.json`. | +| First-wave complete | Chapters 1, 3, 4, 5.1, 9 and 12 are real; others real or N/A. | +| Project repo | A `prj-*` repo that coordinates cross-repo work (ADR-005). | diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/net-kingdom-architecture.md b/docs/evidence/2026-10-11-served-publication-history/sources/net-kingdom-architecture.md new file mode 100644 index 0000000..c18856e --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/net-kingdom-architecture.md @@ -0,0 +1,152 @@ +--- +id: net-kingdom-architecture +title: "NetKingdom architecture" +status: proposed +owner: net-kingdom +revision: "draft-3" +version: "0.1" +last_reviewed: "2026-08-31" +review_interval: 6m +--- + +# NetKingdom — Software Architecture Documentation (arc42) + +## About this document + +First-wave arc42 for NetKingdom: the estate's identity and tenancy +security core. Chapter 9 lists governing ADRs and standards; it does +not paste them. + +--- + +## 1. Introduction and Goals + +NetKingdom is the open security core for DevSecOps on Kubernetes. It +owns identity, tenancy posture, and the contracts that flex-auth, +key-cape, tenant-engine, and railiance workloads implement. + +### 1.1 Requirements Overview + +- One IAM profile, versioned, owned here. +- Tenancy described as graduated axes, not a single on/off switch. +- Workload packaging and credential vending have explicit boundaries. + +### 1.2 Quality Goals + +1. Provider-neutral identity contract. +2. Recursive multi-tenant authorization that implementers can declare. +3. Honest about what is not there yet (Tenancy Posture). + +### 1.3 Stakeholders + +| Role | Concern | +| --- | --- | +| net-kingdom | Canon owner for identity and tenancy. | +| flex-auth / key-cape / tenant-engine | Implementers of the contracts. | +| railiance-master | Workload packaging on the rail. | +| the-custodian | Federation; does not redefine these concepts. | + +--- + +## 2. Architecture Constraints + +N/A for this stub. + +--- + +## 3. System Scope and Context + +**In:** IAM profile, tenancy posture, tenant/user-engine boundaries, +credential management, playbook capability contract, NetKingdom ADRs. +**Out:** publication (policy-nexus), rail runtime (railiance), estate +work-factory (the-custodian). + +### 3.1 Business Context + +Security here is dynamic and adversarial. The system exists so +implementers share one contract instead of copying a neighbour. + +### 3.2 Technical Context + +Consumers: flex-auth, key-cape, tenant-engine, audit-core, rApps. +Published today: Tenancy Posture +`/standards/tenancy-posture/v0.1/`. + +--- + +## 4. Solution Strategy + +N/A for this stub — recursive multi-tenant identity (ADR-0006) and the +IAM profile ownership rule (ADR-0011) are the spine. + +--- + +## 5. Building Block View + +### 5.1 Level 1 – System/Top-Level + +N/A for this stub. + +--- + +## 6. Runtime View + +N/A for this stub. + +--- + +## 7. Deployment View + +N/A for this stub. + +--- + +## 8. Cross-Cutting Concepts + +N/A for this stub. + +--- + +## 9. Architecture Decisions + +| Publication id | Status | Decision or standard | +| --- | --- | --- | +| `netkingdom-tenancy-posture` | proposed | Graduated multi-tenancy posture. | +| `netkingdom-iam-profile-v0.3` | accepted | Current provider-neutral identity contract. | +| `netkingdom-posture-feedback-v0.1` | proposed | Governed posture feedback. | +| `netkingdom-security-layer-model-v0.7` | accepted | Current security-layer interaction model. | +| `netkingdom-security-scenario-composition-v0.1` | proposed | Security scenario composition. | +| `netkingdom-security-zones-v0.1` | proposed | Security-zone vocabulary and boundaries. | +| `NK-ADR-0006` | accepted | Recursive multi-tenant identity and authorization. | +| `NK-ADR-0007` | accepted | Security orchestration boundary. | +| `NK-ADR-0008` | accepted | Object-storage STS credential vending. | +| `NK-ADR-0010` | accepted | Orchestration, dependency, and self-coherent intent. | +| `NK-ADR-0011` | accepted | IAM Profile ownership and version governance. | +| `NK-ADR-0012` | accepted | Playbook capability-contract ownership. | +| `NK-ADR-0013` | accepted | Tenant onboarding grouping taxonomy. | +| `NK-ADR-0014` | accepted | Tenant capability roles and tenant-engine ownership. | +| `NK-ADR-0015` | accepted | Railiance workload packaging and relational platform. | + +Custodian ADR-008 is superseded by Tenancy Posture and is not current. + +--- + +## 10. Quality Requirements + +N/A for this stub. + +--- + +## 11. Risks and Technical Debt + +N/A for this stub. The IAM Profile publication id is now globally qualified. + +--- + +## 12. Glossary + +| Term | Meaning | +| --- | --- | +| IAM Profile | Provider-neutral OIDC contract owned here. | +| Tenancy Posture | Graduated axes for describing multi-tenancy. | +| Tenant-engine | Lifecycle and capability roles for tenants. | diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-iam-profile-v0.3.md b/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-iam-profile-v0.3.md new file mode 100644 index 0000000..0cf00be --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-iam-profile-v0.3.md @@ -0,0 +1,482 @@ +--- +id: netkingdom-iam-profile-v0.3 +type: standard +title: "NetKingdom IAM Profile v0.3" +domain: netkingdom +status: accepted +owner: net-kingdom +revision: "accepted-1" +last_reviewed: "2026-08-22" +review_interval: 6m +version: "0.3" +created: "2026-07-23" +updated: "2026-08-22" +scope: core-platform +supersedes: + - canon/standards/iam-profile_v0.2.md +adr: + - docs/adr/ADR-0011-iam-profile-ownership-and-version-governance.md + - docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md + - docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md +--- + +# NetKingdom IAM Profile v0.3 + +> Minor version. Per ADR-0011's versioning rule, this adds an optional claim +> and clarifies non-normative guidance — no required claim, validation rule, +> or previously-issued token is invalidated. Existing v0.2 implementations +> remain conformant; `tenant_roles` and the revised Tenant Claim guidance are +> additive. + +## Purpose + +The NetKingdom IAM Profile is the provider-neutral OIDC contract that +identity implementations issue and applications consume. + +It defines: + +- OIDC discovery and endpoint requirements; +- Authorization Code + PKCE for human login; +- service-account and workload identity token requirements; +- human, service, and agent principal representation; +- tenant, tenant-grouping, and platform-boundary claims; +- tenant capability roles and their carrying mechanism; +- explicit assurance evidence; +- the identity-to-authorization claim contract consumed by flex-auth; +- local-development and emergency-access behavior; +- executable conformance expectations. + +Applications target this profile, not a concrete identity provider. +key-cape is the lightweight implementation. Keycloak is the expanded-mode +implementation. Both are interchangeable at the application and +authorization boundary when they conform to this document. + +## Ownership + +NetKingdom owns the core/platform profile. See ADR-0011. + +Downstream systems may define extension scopes, roles, resource names, +and tenant policy vocabularies. Those extensions are not part of the core +profile unless a future version explicitly adopts them. Extension +vocabularies must map back to the core claims in this document before +flex-auth or applications consume them. + +## Design Principles + +- Consumers trust signed OIDC tokens, not provider-specific sessions. +- Identity providers assert identity and authentication evidence; they do + not make final resource authorization decisions. +- The same profile works in lightweight key-cape mode and expanded + Keycloak mode. +- Tenancy is explicit. `tenant:platform` is distinct from tenant planes + such as `tenant:coulomb` and from later tenants grouped per ADR-0013. +- A tenant's onboarding **grouping** (ADR-0013) and its **capability + roles** (ADR-0014) are independent axes. Neither is encoded in the other; + a tenant's roles may change without renaming its identifier. +- Human, service, and agent principals are distinguishable. +- Assurance evidence is explicit enough for flex-auth policy. +- Local-development issuers are useful but never accepted by production. +- Emergency access is auditable, time-bounded, and reviewable. + +## Discovery Contract + +Every IAM Profile implementation MUST expose OIDC discovery at: + +```text +GET /.well-known/openid-configuration +``` + +The discovery response MUST include: + +| Field | Requirement | +| --- | --- | +| `issuer` | Exact issuer identifier used in tokens | +| `authorization_endpoint` | Required for human Authorization Code + PKCE | +| `token_endpoint` | Required for token exchange and service accounts | +| `jwks_uri` | Required for signature validation | +| `userinfo_endpoint` | Required when userinfo is supported by the flow | +| `scopes_supported` | MUST include `openid`; SHOULD include `profile` and `email` | +| `response_types_supported` | MUST include `code` | +| `grant_types_supported` | MUST include `authorization_code`; MUST include `client_credentials` or a documented workload-token exchange for service identities | +| `id_token_signing_alg_values_supported` | MUST include the implementation signing algorithm; RS256 is required for v0.2+ conformance | +| `code_challenge_methods_supported` | MUST include `S256` | + +The response SHOULD include `end_session_endpoint` where logout is +supported and `claims_supported` listing the core claims below. + +Consumers MUST discover endpoints and key material from the issuer +metadata instead of hardcoding provider-specific paths. + +## Required Flows + +### Human Interactive Flow + +Human users authenticate with Authorization Code + PKCE. + +Required properties: + +- PKCE with `S256` is mandatory for browser and CLI clients. +- Implicit flow is not part of the profile. +- MFA or equivalent strong assurance is mandatory for privileged, + destructive, platform-root, and emergency access in production. +- Access tokens are short-lived. +- Refresh tokens are allowed only for trusted clients with explicit + rotation and revocation. + +### Service Account Flow + +Service-to-service traffic uses client credentials or a deployment's +documented workload identity token-exchange equivalent. + +Required properties: + +- Service subjects are stable and named for service plus environment. +- Secrets or workload credentials are delivered through the + credential-management standard, not plaintext configuration. +- Tokens include an audience that identifies the target service. +- Tokens carry `principal_type: service`. +- Service accounts receive only required scopes and roles. +- Credentials are rotated and never shared between environments. + +### Agent Principal Flow + +Agents are automation principals that may act autonomously or under +delegated authority. + +Required properties: + +- Tokens carry `principal_type: agent`. +- Tokens include an `agent` object with `id` and `mode`. +- `agent.mode` is `autonomous` or `delegated`. +- Delegated agents MUST identify the delegating actor using `actor_sub` + or an equivalent `act.sub` claim. +- Agent tokens MUST carry the tenant they operate within. +- Agent tokens MUST include assurance evidence for both the agent + credential and any delegated human authority when policy needs it. + +## Core Claims + +Access tokens accepted by production consumers MUST provide the following +claims after provider mapping or normalization: + +| Claim | Type | Meaning | +| --- | --- | --- | +| `iss` | string | OIDC issuer URL or issuer identifier | +| `sub` | string | Stable subject identifier unique within `iss` | +| `aud` | string or array | Intended audience; MUST include the receiving service | +| `exp` | number | Expiry timestamp | +| `iat` | number | Issued-at timestamp | +| `nbf` | number | Not-before timestamp, recommended for production tokens | +| `jti` | string | Token identifier, recommended for audit and replay controls | +| `tenant` | string | Tenant identifier such as `tenant:platform` or `tenant:friendly:binky` | +| `principal_type` | string | `human`, `service`, or `agent` | +| `groups` | array | Group memberships, possibly empty | +| `roles` | array | Coarse **identity** roles for the subject, possibly empty | +| `scope` or `scp` | string or array | Granted OAuth scopes | +| `assurance` | object | Authentication and credential assurance evidence | + +Recommended human claims: + +| Claim | Meaning | +| --- | --- | +| `preferred_username` | Human-readable username | +| `email` | Contact identity | +| `name` | Display name | + +Recommended service claims: + +| Claim | Meaning | +| --- | --- | +| `azp` or `client_id` | Authorized client/service identifier | +| `service` | Object naming the service and environment | + +Recommended agent claims: + +| Claim | Meaning | +| --- | --- | +| `agent.id` | Stable agent identifier | +| `agent.mode` | `autonomous` or `delegated` | +| `actor_sub` or `act.sub` | Delegating subject for delegated agents | + +Optional claims (new in v0.3): + +| Claim | Type | Meaning | +| --- | --- | --- | +| `tenant_roles` | array | Cached tenant **capability** roles (`PLTF`/`IAM`/`VEN`/`CUS`), possibly empty. See "Tenant Roles" below — this is a point-in-time cache, not the authoritative source. | + +### Role Claim + +The canonical **subject**-level role claim is `roles`, an array of strings. +This is distinct from `tenant_roles` (below) — a subject's own coarse +identity roles are not the same fact as which capability roles the +subject's tenant holds. + +Expanded-mode Keycloak deployments may also expose provider-native roles +such as `realm_access.roles`, but conforming tokens consumed by flex-auth +or applications MUST either emit `roles` directly or pass through a +normalizing adapter that produces `roles`. + +### Scope Vocabulary + +The core profile defines only OAuth/OIDC base scopes: + +| Scope | Meaning | +| --- | --- | +| `openid` | Required for OIDC login | +| `profile` | Basic profile claims | +| `email` | Email claim where appropriate | +| `offline_access` | Refresh-token capable access where explicitly allowed | + +Hub-, application-, and resource-specific scopes such as `hub:*`, +`ops:*`, `fin:*`, or storage actions are downstream extensions. They are +valid only when the consuming system defines them and maps them to +flex-auth resource/action semantics. + +## Tenant Claim + +`tenant` is required for every token accepted by profile consumers. + +Tenant identifiers follow `tenant::`, where `` is +one of the taxonomy ratified by ADR-0013 at identifier creation: + +```text +trial - test/trial/showcase tenants only +friendly - known, easily reached, tolerant of experimentation/instability +single - one-person business entities (freelance consultants) +small - up to 10 employees at time of onboarding (attoo) +medium - up to 100 employees (attoo) +large - up to 1000 employees (attoo) +enterprise - 1001+ employees (attoo) +consumer - private individuals +family - a legal family +community - a non-legal group of people +association - a legal association of people +agentic - financially enabled AI entities +``` + +`tenant:platform` and `tenant:coulomb` remain **reserved, ungrouped** +identifiers outside this taxonomy: `tenant:platform` is the platform +control-plane tenant, not a business entity being onboarded; `tenant:coulomb` +is the first internal/reference tenant established by ADR-0006, predating +this taxonomy. Tenant administration for `tenant:coulomb` or any grouped +tenant must never imply platform-root authority. + +Subjects may have access to multiple tenants, but a token used for a +request MUST identify the tenant context for that request. If a client +needs to switch tenant context, it obtains a new token or uses an +approved token-exchange flow that records the target tenant. + +The grouping segment is an immutable record of the tenant's +**onboarding-time** onboarding-risk / entity-shape classification. It remains +vocabulary-valid but becomes historical if the tenant's classification later +changes. The authoritative current grouping is the `grouping` field held by +`tenant-engine`; consumers MUST NOT split `tenant` and treat its middle segment +as current policy input. Changing current grouping never renames the tenant. +Neither the historical segment nor current grouping gates which capability +roles (below) a tenant may hold — see Tenant Roles. + +## Tenant Roles + +Tenant **capability roles** are a separate fact from the grouping above and +from the subject-level `roles` claim: `PLTF`, `IAM`, `VEN`, `CUS` +(ratified by ADR-0014), non-exclusive — a tenant may hold several +simultaneously, and holding one does not require or restrict any grouping. + +**Source of truth:** `tenant-engine` +(`canon/standards/tenant-engine-boundary-contract_v0.1.md`), not this +profile and not any token. `tenant-engine` records role grants/revocations, +their link (if any) to a plan/subscription, and emits domain events on +change. + +**Carrying mechanism — hybrid, not claim-only:** + +- `key-cape` (or Keycloak) MAY stamp a cached `tenant_roles` claim onto an + issued token at issuance time, sourced from `tenant-engine`. +- Consumers MAY trust the cached claim for ordinary, non-privileged + decisions. +- Consumers **MUST NOT** trust the cached claim for privileged, destructive, + platform-root, secret, credential-vending, or otherwise `assurance.level + >= aal2`-class decisions. Those decisions **MUST** query `tenant-engine` + live for current role state before authorizing the action. +- This bounds staleness for ordinary actions to the issuing token's + lifetime (5-30 minutes for service/agent tokens, see Token Lifecycle) + while guaranteeing freshness exactly where a stale grant (e.g. `VEN` + surviving a plan cancellation) would matter most. + +`trial`-grouped tenants may hold any capability role without restriction — +the grouping exists to showcase, test, and explore every role. Safety for +`trial` tenants is enforced through `tenant-engine`-owned resource +guardrails (spend limits, entity/action counts — reserved, not yet +specified), not through role gating. + +## Assurance Evidence + +The canonical assurance claim is `assurance`. + +It is an object with these fields: + +| Field | Type | Meaning | +| --- | --- | --- | +| `level` | string | `aal0`, `aal1`, `aal2`, `aal3`, or `break_glass` | +| `methods` | array | Authentication methods, e.g. `pwd`, `otp`, `webauthn`, `client_secret`, `workload_identity`, `upstream_mfa` | +| `mfa` | boolean | Whether the authentication included multiple factors or equivalent upstream evidence | +| `source` | string | Provider of the evidence, e.g. `key-cape`, `keycloak`, `privacyidea`, `entra`, `local-identity` | +| `at` | number | Authentication time, recommended | + +Level meanings: + +| Level | Meaning | +| --- | --- | +| `aal0` | Local/dev or unauthenticated bootstrap evidence; never production privileged | +| `aal1` | Single-factor or service credential evidence | +| `aal2` | MFA or equivalent strong upstream assurance | +| `aal3` | Phishing-resistant or hardware-backed assurance | +| `break_glass` | Time-bounded emergency access with post-event review | + +Privileged, destructive, platform-root, secret, credential-vending, and +emergency flows require `aal2` or stronger unless a policy explicitly +permits a narrower service or workload identity path. Emergency access +MUST use `break_glass` and short token lifetimes. This is the same +threshold class that gates when `tenant_roles` MUST be re-validated live +against `tenant-engine` rather than trusted from the token — see Tenant +Roles. + +Provider-native claims such as `acr` and `amr` may be present, but +consumers use `assurance` as the normalized profile claim. + +## Identity To Authorization Contract + +flex-auth consumes IAM Profile tokens as normative identity input. +flex-auth MUST NOT re-derive identity, tenant, group, role, or assurance +facts from provider-specific session state. + +The profile guarantees these inputs for authorization decision envelopes: + +| Decision input | Source claim | +| --- | --- | +| Subject | `sub` | +| Issuer | `iss` | +| Audience | `aud` | +| Tenant | `tenant` | +| Principal type | `principal_type` | +| Groups | `groups` | +| Subject roles | `roles` | +| Tenant capability roles | `tenant_roles` (cached); `tenant-engine` live lookup required for high-stakes decisions | +| Scopes | `scope` or `scp` | +| Assurance | `assurance` | +| Authorized client | `azp` or `client_id`, where present | +| Agent/delegation context | `agent`, `actor_sub`, or `act`, where present | +| Token lifetime/audit ids | `iat`, `nbf`, `exp`, `jti`, where present | + +Authorization decisions are made by flex-auth and its delegated PDP +adapters. Identity providers may assert roles or scopes, but those claims +are inputs to policy, not final permission to act on a resource. + +## Token Lifecycle + +Recommended production defaults: + +| Token | Lifetime | Notes | +| --- | --- | --- | +| Human access token | 5-15 minutes | Short-lived bearer token | +| Refresh token | 8-12 hours | Rotated and revoked on logout or suspicion | +| Service token | 5-30 minutes | Reissued by client credentials or workload identity | +| Agent token | 5-30 minutes | Shorter when delegated or platform-scoped | +| Emergency token | 5-15 minutes | Requires incident/review record | + +Consumers MUST reject expired tokens and tokens with invalid issuer, +audience, signature, `nbf`, or algorithm. Clock skew tolerance SHOULD be +small, normally no more than 60 seconds. + +JWKS material may be cached, but consumers MUST tolerate key rotation by +refreshing JWKS when a token uses an unknown `kid`. + +## Local Development Profile + +A local file-backed provider may be used for development, tests, and +bootstrap contexts where the full platform is unavailable. + +It MUST: + +- expose OIDC discovery; +- issue signed JWTs; +- support deterministic test users and service accounts; +- use local-only issuer URLs or a clearly local issuer identifier; +- mark tokens as local/development through issuer, audience, or + assurance evidence; +- be rejected by production consumers. + +Production consumers MUST reject: + +- issuer `local-identity`; +- `http://` issuers; +- loopback issuers such as `localhost` or `127.0.0.1`; +- tokens with `assurance.level: aal0`; +- tokens where the environment marks the issuer as local/dev. + +## Emergency And Break-Glass Access + +Emergency access is allowed only as a break-glass path. + +Requirements: + +- Emergency identities are disabled by default. +- Activation requires an incident, decision, or human-recorded review + reference. +- Tokens are short-lived and carry the `emergency` role. +- Tokens carry `assurance.level: break_glass`. +- Every emergency action emits an audit/progress/incident event. +- Emergency access is reviewed after use and then disabled again. + +Emergency access MUST NOT bypass audit logging or flex-auth policy. + +## Conformance + +An implementation conforms to IAM Profile v0.3 when it passes the +executable conformance suite in: + +```text +tools/iam-profile-conformance/ +``` + +The suite validates: + +- discovery document completeness; +- PKCE `S256` advertisement and rejection of authorization requests that + omit a code challenge; +- JWKS structure and key ids; +- token issuer, audience, expiry, `nbf`, `iat`, and RS256 signature; +- tenant, principal type, groups, roles, scopes, and assurance claim + shape; +- `tenant_roles` claim shape when present (array of ratified role strings); +- agent and delegated-agent claim shape; +- local-development issuer rejection in production mode. + +Conformance must be runnable against both key-cape lightweight issuers +and Keycloak expanded-mode issuers. Implementations may add provider +adapters, but the token consumed by applications and flex-auth must match +the core claim contract above. `tenant_roles` conformance does not require +an implementation to emit the claim (it is optional); when emitted, it +must match the ratified vocabulary. + +## Validation Checklist + +A service or implementation is profile-ready when: + +- it reads OIDC discovery rather than hardcoding endpoints; +- it validates issuer, audience, expiry, `nbf`, algorithm, and + signature; +- it refreshes JWKS on unknown `kid`; +- it supports Authorization Code + PKCE for human login; +- it supports service-account or workload identity tokens; +- it emits `tenant`, `principal_type`, `groups`, `roles`, + `scope`/`scp`, and `assurance`; +- it uses the ADR-0013 grouping vocabulary for new tenant identifiers; +- if it consumes `tenant_roles`, it treats the claim as a cache and + re-validates live against `tenant-engine` before any `aal2`-class + decision; +- it maps provider-native claims into the canonical core claims; +- it rejects local-development issuers in production; +- it logs emergency access with a durable audit trail; +- flex-auth receives identity facts from the profile, not from + provider-specific sessions. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-posture-feedback-v0.1.md b/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-posture-feedback-v0.1.md new file mode 100644 index 0000000..0b72654 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-posture-feedback-v0.1.md @@ -0,0 +1,109 @@ +--- +id: netkingdom-posture-feedback-v0.1 +type: standard +title: "NetKingdom Posture Feedback v0.1" +domain: netkingdom +status: proposed +version: "0.1" +owner: net-kingdom +created: "2026-08-23" +updated: "2026-08-23" +last_reviewed: "2026-08-23" +review_interval: 3m +scope: self-optimizing-security +schema: + - canon/schemas/posture-feedback-report_v0.1.schema.json +validator: + - tools/posture-feedback/posture_feedback.py +related: + - canon/standards/tenancy-posture_v0.1.md + - canon/standards/security-zones_v0.1.md +--- + +# NetKingdom Posture Feedback v0.1 + +## 1. Purpose + +This contract is the first bounded C6 feedback mechanism. It turns explicit +posture review dates, evidence freshness, implemented-but-unevidenced controls, +and declared gaps into deterministic remediation **proposals**. + +It does not modify a posture level, policy, declaration, workplan, State Hub, +or runtime. Human or separately governed automation decides whether a proposal +becomes work. + +## 2. Deterministic time + +Evaluation requires an explicit RFC 3339 `as_of` timestamp. Wall-clock time is +never read implicitly. A date-only `review_due` remains current through that +calendar date in UTC and becomes overdue on the following UTC date. + +A non-negative horizon in days identifies items due soon. Changing the horizon +changes the report digest and is therefore visible. + +## 3. Owner resolution + +The evaluator routes only from authoritative declaration fields: + +- posture review, gaps, and implemented controls: `responsible_repo`; +- evidence replacement: `evidence_freshness..responsible_repo`; +- security-zone review: `zones.responsible_party`. + +If the relevant field is absent, owner resolution is `unknown`. The evaluator +must not infer ownership from the service name, repository path, Git remote, +previous work, or another policy subject. + +## 4. Finding classes + +| Finding | Trigger | Severity | +| --- | --- | --- | +| `posture-review-overdue` | `as_of` is after `tenancy.review_due` | high | +| `posture-review-due-soon` | review is within the horizon | medium | +| `zone-review-overdue` | `as_of` is after `zones.review_due` | high | +| `zone-review-due-soon` | zone review is within the horizon | medium | +| `evidence-freshness-unknown` | a current adversarial level has no complete freshness entry | high | +| `evidence-expired` | `as_of` is after `valid_until` | high | +| `evidence-due-soon` | evidence expires within the horizon | medium | +| `implemented-not-evidenced` | an `implemented` level is above `current` | medium | +| `declared-gap` | a non-empty `tenancy.gap` entry exists | low | + +The review horizon does not generate a due-soon finding for an item already +overdue or expired. Exact equality with a timestamp is still valid; expiry is +strictly `as_of > valid_until`. + +Current adversarial levels are `E2`, `R4`, and `V2`–`V4`. This vocabulary comes +from Tenancy Posture §13. Mechanical evidence is evaluated for expiry only when +its declaration explicitly supplies `valid_until`. + +## 5. Proposal and safety boundary + +Every finding receives a stable id derived from its source declaration, +service, finding class, control, and due value. It contains the authoritative +owner or `unknown`, current evidence state, reason, and recommended action. +For declarations under the workspace containing this repository, the source is +normalized to `/` so absolute checkout locations do not perturb the +identity. This source normalization identifies an input only; it is never an +ownership inference. + +Every report declares: + +```yaml +automation: + mode: proposal-only + external_write_permitted: false + policy_mutation_permitted: false + declaration_mutation_permitted: false +``` + +Expired or unknown evidence does not silently inherit freshness and does not +silently downgrade a level. It makes the uncertainty visible for governed +review. Consumers that use the report as an admission gate may fail closed on +high findings, but that is a separate owner decision. + +## 6. Exit behavior + +The CLI emits a report conforming to +`posture-feedback-report_v0.1.schema.json`. `--fail-on high` exits non-zero when +at least one high-severity finding exists; `medium` includes medium and high; +`low` includes every finding; `none` reports without a finding-based failure. +Invalid declarations always exit non-zero. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-security-layer-model-v0.7.md b/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-security-layer-model-v0.7.md new file mode 100644 index 0000000..fdc231e --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-security-layer-model-v0.7.md @@ -0,0 +1,1396 @@ +--- +id: netkingdom-security-layer-model-v0.7 +type: standard +title: "NetKingdom Security Layer Model v0.7" +domain: netkingdom +status: accepted +version: "0.7" +supersedes: canon/standards/security-layer-model_v0.6.md +owner: gate-house +publication_owner: net-kingdom +created: "2026-08-28" +updated: "2026-08-28" +last_reviewed: "2026-08-28" +review_interval: 3m +source_revision: "gate-house@516ed4e" +standard_token: security-layer-model_v0.7 +# "assented_by" records assent to a BOUNDARY, given at the version named. +# It is not assent to the current text. Revision reviews are listed in §14. +assented_by: + - "flex-auth FLEX-DEC-2026-001" + - "kings-guard KG-DEC-2026-001" + - "ops-warden ADR-0010" + - "audit-core AUDIT-IN-0001, and v0.4 review with three findings" + - "flex-auth FLEX-DEC-2026-002 (v0.4, §9.3 contested)" + - "kings-guard v0.4 review, four findings" + - "ops-warden 2026-08-29 v0.4 review, three findings" +related: + - canon/standards/security-zones_v0.1.md + - canon/standards/tenancy-posture_v0.1.md + - canon/standards/credential-management_v0.2.md + - gate-house/decisions/decisions.md + - net-kingdom/history/2026-08-29-layering-standard-assessment.md + - gate-house/history/2026-08-28-security-layer-model-and-gate-house-recut.md +--- + +# NetKingdom Security Layer Model v0.7 + +## 1. Purpose + +This standard states how NetKingdom's IT-security estate is layered, and what +each layer may and may not do. It answers one question: + +> **Given a repository, which layer is it in, and what does that permit it to +> own?** + +The layers are distinguished by **determinism** and by **the kind of artifact +the layer produces**, not by technical tier, deployment topology, or team. + +It is not an org chart, not a network model, not a deployment topology, and not +a dependency graph. It does not assign work, and it does not replace any +repository's boundary contract; it constrains what such a contract may claim. + +**What changed in v0.7.** v0.6 announced a rule it never wrote: §1 and §15 said +the standard separates human and agent principals inside Staff, and §3.4 was +byte-identical to v0.5. `kings-guard` found it and put it correctly — *a rule +stated about a standard in its own change log is not a rule*, which is §11's own +principle turned on the standard. §3.4 is now written. + +The rest are collisions between rules written for the clean case: §6.4's first +obligation forbade what its third obligation blesses, and its second forbade the +session-bound allow §9.7.1 permits. §9.4's atomicity was described as closing a +threat it does not close. §19 graded the document it lived in. And §20 records +the interaction boundary with Railiance operations, on definitions from +`railiance-master` rather than inference. §15 records the change list. + +**What changed in v0.6.** An independent assessment against industry practice +(`net-kingdom/history/2026-08-29-layering-standard-assessment.md`) found the model +sound as a layering constitution and incomplete as a *self-healing* one: cognition, +authority, and execution are specified, but the two verbs that close a healing +loop — observe in production and actuate through a deterministic surface — are +pending, and one is unstaffed. It also found the Engine layer untyped, so that +"we need an engine for X" drifts toward "X now decides", and the enforcement +point unnamed. + +v0.6 types the engines (§3.3), names the enforcement point (§6.4), replaces the +containment assignment with an actuation surface held at zero (§9.2), separates +human and agent principals inside Staff (§3.4), puts time into the model (§9.7), +requires the Taxonomy artifacts that make §6.2 compileable rather than +reviewable (§17), and composes the sibling standards it had only cited (§18). +Section numbers below §14 are unchanged: the estate cites them. + +**What changed in v0.5.** All four reviewing repositories returned findings on +v0.4, and one contested a rule. §9.3 was wrong: it collapsed *engine reachable +but degraded* with *engine not reachable at all*, and the second case has no +evaluator in the path to express anything. §9.1 collapsed *no route exists* with +*route exists under a declared gap*, which would have forced a false "pending" +onto a production capability. §11 claimed mechanical checkability for a rule +that cannot be checked in prose. §13 filed two opposite conformance states in one +table and recorded proposed owners as owners. §9.6 needed the load-bearing +distinction it implied but never drew. §15 records the change list. + +**What changed in v0.4.** `audit-core` assented to the approval evidence half +and corrected the rationale twice. v0.3 rested §9.4 on that repository's INTENT +principle 6, which is an aspiration; the shipped bound in its `docs/integrity.md` +is weaker and conditional. More consequentially, no append-only archive can prove +**omission at source** — a suppressed revocation leaves the chain intact — which +is now stated as an estate-wide doctrine constraint (§9.6) rather than left +implicit. `audit-core` was also referenced as an owner in v0.3 without appearing +in the §4 catalog at all; it is catalogued here, as an Engine, on its own +declaration. §15 records the change list. + +**What changed in v0.3.** Two engines were seeded to own concepts v0.2 recorded +as unowned: `approval-engine` takes the approval object that §13 left homeless, +and `maturity-engine` takes graded progression — closing a §9.1 defect in +gate-house's own catalog claim, which asserted conformance review with no engine +to act through. §15 records the change list. v0.3 is **proposed**: the two new +engines are seeded by owner direction and have no other side to assent yet, and +the evidence half of the approval split needs `audit-core`'s assent. + +**What changed in v0.2.** v0.1 was assented to by all three repositories whose +boundaries moved, and each returned a finding. v0.1 had one lane for a Staff +repository that legitimately touches Tooling — read-only diagnostics — which is +narrower than the estate as it actually stands, and a rule with no lane for a +real sanctioned case is satisfied by relabelling rather than by closing the gap. +v0.1 also catalogued a capability (§4, containment) that §5 forbade discharging, +and applied its reconstructability test to engines but not to the doctrine +gate-house feeds them. §15 records the full change list. + +## 2. Authority and conformance + +| Fact or rule | Authority | +| --- | --- | +| The layers, their definitions, and the rules between them | This standard, owned by gate-house | +| Which layer a given repository is in | This standard, §4 catalog | +| What a repository owns within its layer | That repository's `INTENT.md` and boundary contract | +| Whether a specific request is permitted | `access-engine` — never this standard | +| Whether a Tooling contact is sanctioned | The declaring repository, under the shapes in §5, reviewable by gate-house | +| Security doctrine and invariants | gate-house | +| Publication | net-kingdom canon | +| Whether an invariant is *watched in practice* | the observing repository's own report — never this standard, and never §12's diagram | + +A repository conforms when its `INTENT.md` declares its layer, its claims fall +within that layer's permissions (§3), and its Tooling contacts take one of the +sanctioned shapes in §5 or are declared as gaps under §5.3. + +**No estate argument may cite observation that has not happened.** §12 lists +`kings-guard` against the loop's fourth step, and that repository has reported +that it has never observed a real event. Until it reports otherwise, no +assessment, review, or decision in this estate may treat an invariant as being +watched in practice on the strength of the diagram. Lifted here from §12 so it +cannot be lost in a summary. + +## 3. The layers + +| Layer | Character | Produces | Deterministic | +| --- | --- | --- | --- | +| **Taxonomy** | cross-cutting language | terms, semantic contracts, standards | n/a — describes | +| **Tooling** | infrastructure and state | data structures, persistence | yes | +| **Engines** | interfaces for a modeled concept | APIs, contracts | yes | +| **Staff** | management, operations, change, controlling | specifications, decisions, workplans, tasks | **no** | + +### 3.1 Taxonomy + +Cross-cutting language. Taxonomy repositories define terms and semantic +contracts so the other layers interoperate without integration by +interpretation. They own no runtime position and no state any layer depends on. + +`info-tech-canon` holds ecosystem-wide semantic contracts. NetKingdom-specific +security architecture — including this standard — is net-kingdom canon's. + +### 3.2 Tooling + +Deterministic infrastructure: data structures, persistence, and the consistent, +performant, scalable keeping of state. Much of it is third-party. + +### 3.3 Engines + +Deterministic APIs for a modeled concept — a user, a tenant, a zone, a secret, +an access rule. An engine's defining property is that **the same authoritative +input state yields the same result**. Engines are where the estate's +deterministic guarantees live, and therefore where every enforcement boundary +MUST sit. + +A repository whose core function is inference or judgment fails this test by +construction and is Staff, however much of its work happens at runtime. + +**Engines are typed.** "Engine" is one layer but four roles, and collapsing them +hides different failure modes. Every §4 Engine row carries a role: + +| Role | Meaning | Outage means | +| --- | --- | --- | +| **PDP** | renders the authorization decision — `access-engine`, and only it (§6) | consumer residue (§9.3) | +| **PIP** | supplies facts a decision consumes as claims — user, tenant, zone, approval, maturity | input degradation, engine's own fallback (§9.3) | +| **Evidence** | records what happened and proves integrity of what it holds — `audit-core` | MUST NOT block the operation being recorded — a default, not a property; see below | +| **Lifecycle** | a deterministic API over Tooling it owns — `secrets-engine` | the owning engine's failure semantics | + +The roles are why *"we need an engine for X"* does not mean *"X now decides"*. +A new engine is a PIP unless this standard is amended to say otherwise, and §6 +means it can never be a second PDP. + +The Evidence row's outage rule is an estate **trade**, not a property of +evidence engines. Choosing availability there means accepting that a compromised +source can suppress a record and that detection is the answer (§9.6). The +opposite shape — *do not proceed unless an independent custodian already holds +the record* — is the only one that puts evidence outside the actor's blast +radius **before** the act. The estate has not needed it, so it is not ruled out +by a table cell: an operation whose control genuinely requires independent +recording before effect is a declared exception, raised when needed. Raised by +`audit-core` against its own row. + +The industry vocabulary is deliberately mirrored here — PDP, PIP, PEP as in +NIST ZTA and XACML — because it is how the estate talks to the outside and how a +PEP is stopped from quietly becoming a PDP. The determinism cut in §3 stays +primary where the two disagree. + +### 3.4 Staff + +Interactive and non-deterministic. Staff is the management layer: operations, +change, innovation, and controlling. It works through agentic capability — +assistants and autonomous agents — and its artifacts are specifications, +decisions, workplans, and tasks. + +Staff repositories MUST NOT hold state that another layer depends on at +runtime, and MUST NOT render or cache any decision an Engine is responsible for. + +Acting at runtime does not make a repository an Engine. Being agentic makes it +Staff, and §5 governs how it acts. + +**Two principals, one layer.** Humans and agents are both non-deterministic and +both Staff, so they share the layer's permissions. They do not share blast +radius. Four rules bind the agent principal specifically: + +1. **No standing credential.** An agent holds no long-lived credential of its + own. Authority is issued per task, time-bounded under §9.7, and attributable + to the principal on whose behalf it acts. +2. **Tool use is a conduit or an Engine API.** An agent acts through §5.2 — the + owner's tool under the caller's identity, presenting no authority of its own + — or through an engine. There is no third route. Tool availability is not + permission: a callable tool means the operation exists, not that this actor + may invoke it. +3. **Agent memory is not a state plane.** Agent memory, tool-call traces, and + prompt caches are the agent's own. They MUST NOT become state another layer + depends on at runtime unless catalogued as Tooling in §4, which subjects them + to §5 like anything else, and to §5's sunset. +4. **Every agent action is reconstructable as the caller's action**, bounded by + §9.6 — the archive shows the actions it received, not that it received all of + them. + +Session semantics — session loops, tool policy, harness routing, model +selection — are **not** governed here. They belong to `glas-harness` and its +`rein-*` backends (§20). This standard governs what an agent may be authorized +to do; `glas-harness` governs how an agent session is conducted. Rule 2 is the +seam between them, and neither side may treat its own half as sufficient. + +v0.6 claimed these rules in its change log and did not write them. Found by +`kings-guard`, which is the repository they bind hardest and which offered to +assent to them sight-unseen. + +## 4. Layer catalog + +| Repository | Layer | Role | Owns | +| --- | --- | --- | --- | +| `info-tech-canon` | Taxonomy | — | ecosystem-wide semantic contracts and terminology | +| `net-kingdom` | Taxonomy | — | NetKingdom standards of record; publication | +| `key-cape` | Tooling | — | packaged identity tooling; IAM profile; authentication | +| `OpenBao` | Tooling | — | secret storage, leases, PKI, dynamic secret engines | +| `user-engine` | Engine | PIP | users, accounts, memberships | +| `tenant-engine` | Engine | PIP | tenant-as-an-entity facts | +| `zone-engine` | Engine | PIP | zone identity and membership — offline reference conformance per its 2026-08-23 disposition | +| `secrets-engine` | Engine | Lifecycle | credential abstraction, custody, lifecycle | +| `audit-core` | Engine | Evidence | audit event custody, retention, integrity verification, export — explicitly not a decision point (§9.6) | +| `access-engine` | Engine | **PDP** | **the policy decision** — the only decision point (§6) | +| `approval-engine` | Engine | PIP | the approval object — durable, authenticated, consumable, atomically supersedable (§9.4) | +| `maturity-engine` | Engine | PIP | graded progression against declared criteria and evidence; the gap register; capability readiness (§9.5) | +| `gate-house` | Staff | — | security doctrine, authority context, curriculum; **conformance review — through `maturity-engine` (§9.5)** | +| `ops-mason` | Staff | PEP-shaped | building and tearing down access routes and perimeters | +| `ops-warden` | Staff | PEP-shaped | operational access lanes, stewardship, runbooks; SSH certificate issuance — **declared-gap** (§9.1, §13) | +| `kings-guard` | Staff | — | adaptive defence and judgment; observation of Staff-reachable sources — identity and secret observation **pending**; **proposes** containment, which it does not own (§9.2) | +| `whitehat-security` | Staff | — | offensive validation | + +An **actuation surface** — reduce authority, require step-up, isolate a workload +— is catalogued nowhere because it does not exist. See §9.2: it is an Engine +concept held at zero, not a Staff capability. + +`access-engine` is the ruled name for the repository currently called +`flex-auth`; both denote the same authority until the governed rename completes. +Execution conditions for that rename are recorded in its migration decision, not +here. + +## 5. The binding rule + +> **Staff never touches Tooling directly. It acts only through Engine APIs.** + +A Staff repository MUST NOT hold a direct client for a Tooling-layer system — +no direct database connection, no direct OpenBao client, no direct cluster +mutation — outside the shapes below. This is the architectural form of *no +privilege from cognition*, and it is deliberately mechanically checkable. + +**Scope.** "Tooling-layer system" means a system catalogued as Tooling in §4. +Infrastructure the estate runs but has not catalogued — the State Hub, +`llm-connect`, and similar — is outside this rule, because a rule that silently +covered them would put every Staff repository in undeclared violation on +adoption day: they all write progress events. Such clients SHOULD be recorded +in the repository's declaration as non-Tooling for completeness of the check, +and the way to bring one under §5 is to catalogue it in §4, deliberately. + +Raised by `ops-warden`, which held clients for both and declined to resolve the +scope question on gate-house's behalf. + +**The carve-out sunsets.** It is a pressure valve, and a valve left open becomes +a second persistence plane under the Staff layer — which §3.4 forbids in spirit. +Three rules bound it: every non-Tooling client MUST be listed in the +repository's declaration; an uncatalogued store that another layer **reads** +MUST, within two review intervals, either be catalogued as Tooling in §4 or be +declared a gap under §5.3; and a Staff-owned event bus or memory store MUST NOT +become the estate's de facto state plane. Today's instances are the State Hub +and `llm-connect`; tomorrow's are agent memory, tool-call traces, and prompt +caches (§3.4). + +Three shapes are sanctioned. Everything else is a violation. + +### 5.1 Read-only diagnostic observation + +A Staff repository MAY read Tooling state for diagnostics where the owning +engine exposes no equivalent. It MUST be declared in the repository's +`INTENT.md`. It grants no write, and it is an engine gap to close, not a +standing arrangement. + +### 5.2 Conduit + +A Staff repository MAY run the **owner's** tool under the **caller's** identity, +supplying no authority of its own. The test is the supplied-authority property: +the conduit MUST NOT present its own credential, MUST NOT widen what the caller +could already do, and MUST be reconstructable as the caller's action in audit. + +A conduit that presents its own token is not a conduit; it is §5.3 or a +violation. This shape MUST be declared, and the no-authority property SHOULD be +covered by a test. + +The reconstructability requirement is an audit-dependent claim and is therefore +bounded by §9.6: the archive shows the conduit actions it received, not that it +received all of them. + +### 5.3 Declared engine gap + +Where a Staff repository must contact Tooling directly and no engine exposes the +capability, it MUST declare the contact rather than take an exemption. A +declared gap carries, machine-readably: + +| Field | Meaning | +| --- | --- | +| `capability` | what the contact does | +| `intended_owner` | the engine that should own it | +| `blocked_on` | why it cannot move today | +| `review` | a date, not "when convenient" | + +A declared gap is **tracked non-conformance**, not conformance. It does not +expire on its own and it is not a licence to add more. It exists because a rule +offering no lane for a real sanctioned case gets satisfied by relabelling rather +than by closing the gap — and a tracked gap is visible, whereas a relabelled one +is not. + +Prior art: `ops-warden` runs equivalent machinery for delegated lanes (27 +catalog entries carrying `delegation:`, queryable via `warden route gaps`), and +has offered it as reusable. + +**No fourth "operator of third-party Tooling" shape.** It has been proposed, on +the argument that someone must operate `OpenBao` and every operational necessity +otherwise looks like a gap. Declined: §5.3 already sanctions the operation while +keeping it visible, and a clean "operator" shape would convert a tracked gap +into a permanent allowance — the relabelling failure this standard exists to +prevent. A permanent operational necessity is a declared gap whose review +interval keeps returning, which is the correct amount of friction. If the review +becomes ceremonial, that is an argument for closing the gap, not for renaming +it. + +## 6. One decision point + +`access-engine` is the only policy decision point in NetKingdom. No other +repository, in any layer, may render or cache authorization decisions. + +First ruled in `zone-engine/INTENT.md` §5 — *"flex-auth is the policy decision +point. It stays the only one."* The failure mode, from the same source: *"It +becomes a second decision point… it would arrive as a small convenience."* + +### 6.1 Compiled data that determines an outcome is still deciding + +A registry, cache, or schema that resolves a result before the engine runs has +decided early. Provenance MUST remain reconstructable from the engine's decision +record. + +### 6.2 Doctrine reaches the decision as an input, or it is not applied + +This rule binds gate-house on the same terms. **An authority ceiling, mandate +constraint, or operating-mode restriction that determines an outcome MUST reach +the decision either as an input claim on the request or as a rule in the +versioned policy package**, so that its application is reconstructable from the +decision record. + +Doctrine that influences outcomes by any other route is a second decision point +wearing an author's hat. This is not a limit on gate-house's authorship; it is +what keeps that authorship auditable at decision time. + +### 6.3 No Staff repository may host a decision point + +A deterministic authority boundary inside a non-deterministic layer contradicts +the invariant the estate is built on. gate-house was re-cut on this ground. + +### 6.4 The enforcement point + +The standard has been precise about the decision and silent about the gate. A +decision that nothing refuses to proceed without is advice. + +A **PEP** is any runtime that causes a protected side effect. It is a *shape*, +not a repository: `ops-warden` issuing a certificate, `ops-mason` opening a +route, and any protected system acting on a verdict are all PEP-shaped. Being +PEP-shaped does not move a repository out of its layer. + +Four obligations, and they are normative: + +1. **No side effect without a decision record, or a recorded stance.** A PEP + MUST NOT perform the protected action unless it holds a decision from + `access-engine` identifying the request it was rendered for, **or** its + declared §9.3 stance for the applicable scope permits proceeding without one + **and the application of that stance is recorded in place of the decision**. + The second limb is stricter than silence, not looser: a fail-open result is + metadata, never an absent record. `ops-warden` `ca.py` writes the zone, the + failure mode, and a decision id present only where a decision was rendered. + + v0.6's unqualified form made the shipped stance §9.3 sanctions into a + violation — the same defect as v0.5's §9.1, a rule written for the clean case + producing a false result on the adjacent case already sanctioned elsewhere. + Raised by `ops-warden`, which is the reference shape for limb two. + +2. **No recaching of the verdict beyond its own binding.** A stored verdict + replayed **outside the decision's stated binding and lifetime** is a second + decision point deciding early (§6.1). Within them it is the decision being + used as issued — a session-bound allow under §9.7.1 is used across later + requests by construction, and v0.6 forbade what §9.7.1 permits. + + The test is mechanical, not a matter of implementer judgement: replay is + permitted **iff the canonical request digest matches and the decision's + lifetime holds**. `access-engine` computes that digest over normalised + subject, action, resource, and context, and it is already in every decision + binding. A retry after a transport failure is therefore the same request; a + different resource is not. + + **Negative caching is permitted, narrowly.** A cached DENY cannot manufacture + authority — §8's asymmetry holds — and protects against retry storms. It is + permitted where the refusal is itself recorded against the request that was + refused (obligation 4), and where the cache lifetime is declared alongside + the stance map. A stale deny is an availability failure and will be + misdiagnosed as a policy one, so it must be visible as what it is. Ruled + explicitly because it is the first thing an implementer under load reaches + for. Raised by `access-engine`. + +3. **A declared unreachable-engine stance** (§9.3): total, per zone or + equivalent scope, no implicit default, no per-call discretion, published + rather than held in code comments or in a dataclass default. `ops-warden` + `ADR-0009` and its `pep-stance.yaml` are the reference shape. + + The published map MUST equal the shipped behaviour, and that equality SHOULD + be asserted by a test. A published map free to drift from the code is worse + than none, because it invites reliance it cannot support. Raised by + `ops-warden`, which found its own map unpublished while being cited as the + reference for this obligation. + +4. **Reconstructability**, bounded by §9.6. + +Every PEP-shaped consumer MUST publish its stance map at a path named in its +layer declaration, and those maps MUST be inventoried in the §13.1 register +until `maturity-engine` can hold them. §9.3 is otherwise a ruling with no +register behind it, and *"`z0`–`z2` and unknown fail open"* becomes the estate's +real policy without anyone having compiled it into a versioned package. + +v0.6 named a register that did not exist — a requirement whose register is +missing is a capability catalogued without a surface, by §9.1's own logic. +Raised independently by `ops-warden` and `access-engine`; §13.1 now exists, and +its first inventory has one row, which is itself the finding. + +Raised by the 2026-08-29 independent assessment: NIST ZTA splits decide from +enforce, and this standard had only the first half. + +## 7. Relationship to the Active Secrets Management Canon + +```text +Staff interactive, non-deterministic ≈ Cognitive Plane +Engines deterministic APIs ≈ Authority Plane +Tooling deterministic state ≈ Execution Plane +Taxonomy cross-cutting language +``` + +*Cognition proposes. Authority disposes. Infrastructure executes.* is therefore +NetKingdom's layering rule, not only its security maxim. §5 and §6 are that +principle applied to repositories rather than to requests. + +## 8. Vocabulary demarcations + +| Term | Belongs to | Not | +| --- | --- | --- | +| **access lane** | ops-warden, ops-mason (Staff) — how a worker reaches a host | the decision whether they may | +| **access rule** | access-engine (Engine) — whether an actor may act | the route by which they arrive | +| **control plane** | Engine layer | a Staff repository's self-description | +| **doctrine** | gate-house | a lane owner's runbook | +| **runbook** | the Staff repository stewarding the lane | a substitute for doctrine | +| **posture** | kings-guard publishes; gate-house defines its authority meaning; access-engine renders it | a privilege source | + +Posture carries an asymmetry that MUST hold: adaptive systems may reduce +authority, require step-up, or request containment. They MUST NOT +probabilistically manufacture additional authority. + +The asymmetry is what bounds the damage when observation is incomplete (§9.6): +suppressed evidence can only prevent a tightening that should have happened, never +engineer a loosening. That is an argument for keeping it absolute rather than +situational. + +## 9. Capability assignment + +### 9.1 The catalog may not assign what the rules forbid discharging + +A Staff repository MUST NOT be catalogued in §4 as owning a capability it cannot +discharge under these rules. Two marks distinguish the two ways that happens, and +they are not interchangeable: + +| Mark | Meaning | +| --- | --- | +| **pending** | No route exists. No engine exposes the capability, the repository makes no Tooling contact, and the capability is **zero** — not degraded. | +| **declared-gap** | A route exists through a §5.3 declared gap. The capability **works** and is tracked, with an intended owner and a review date in §13. | + +v0.4 had only `pending`, which forced a false choice. `ops-warden` holds +production-verified SSH certificate issuance through a declared OpenBao contact; +marking it `pending` would have told readers the repository does not do the one +thing it demonstrably does daily, while leaving it unmarked left §4 disagreeing +with §13. Neither is acceptable, and the defect was in this section rather than +in the catalog. + +`pending` was written for `kings-guard`'s containment — no route, capability +zero — and remains correct there. `declared-gap` is the case §5.3 was added to +sanction. Raised by `ops-warden`. + +Both marks apply per capability, not per repository. A repository may hold one +capability outright, another under a declared gap, and a third pending. + +### 9.2 Actuation does not exist, and containment is not Staff's to own + +Self-healing needs four verbs: observe, evaluate, decide, actuate. Observation is +`kings-guard` and is unstaffed (§12). Evaluation is `maturity-engine` and is +seeded. Decision is `access-engine` and works. **Actuation has no surface at +all**, and a model with no actuation surface describes a diagnosis machine +rather than a healing one. + +v0.5 marked containment `pending` against `kings-guard`, which was the right +mark on the wrong repository. Containment is not a Staff capability that happens +to lack a route: **reduce authority, require step-up, isolate a workload** are +authority-changing operations, and under §6 an authority-changing operation is +rendered by an Engine and enforced by a PEP (§6.4). A Staff repository proposes +containment; it never performs it. + +The **actuation surface** is therefore an Engine concept — likely a small +surface on `access-engine` together with runtime PEPs — carrying the same +reconstructability rules as any other decision: a containment action is a +decision record, not a side channel. + +It is **unowned and held at zero**. `access-engine` is recorded in §13 as a +*proposed* owner and has explicitly not reviewed it (`FLEX-DEC-2026-002`). No +repository may be catalogued as owning containment until the surface exists — +§9.1 applied to the estate's most operationally tempting gap, and the standard's +own medicine. + +Until then `kings-guard` proposes and judges, its containment claim stays at +zero rather than degraded, and no argument may assume the estate can contain +anything automatically. + +### 9.3 Degraded mode: two failure cases, two owners + +v0.4 collapsed two failures into one rule. They have different owners because +one has an evaluator in the path and the other does not. + +**Input degradation — the engine's.** Where `access-engine` is reachable but +cannot reach its own inputs, the deterministic *fail to reduced authority* +default belongs to the engine. This keeps the decision at the decision point and +keeps the fallback deterministic, which a Staff-layer fallback could never be. + +**Engine unreachable — necessarily the consumer's.** Where `access-engine` is +not reachable at all, it applies nothing, because it is not running. Whatever +happens next is the consumer's behaviour by construction: fail-open is not +expressible by a policy decision point, since there is no evaluator in the path +to express it. A standard that assigns this to the engine assigns it to nobody. + +The consumer's residue is bounded rather than free. A protected system MUST +declare its unreachable-engine stance ahead of time, per zone or equivalent +scope, and that stance MUST be auditable and total — no implicit default, no +per-call discretion. `ops-warden` `ADR-0009` already satisfies this: a total +per-zone map, open for `z0`–`z2` and unknown, closed for `z3-critical`, +replacing the global `policy.enabled` / `policy.fail_closed` switches it +superseded. + +**Unchanged: engine-unavailable is not grounds for a Staff break-glass path.** +The distinction is whether an engine is there to ask. A bypass around a +*reachable* engine is a second decision point, and an incident is when an +attacker most wants that shortcut. A consumer choosing its declared behaviour +when there is no engine to ask is not a bypass; it is the only thing left. + +Contested by `flex-auth` (`FLEX-DEC-2026-002`), which has held since 2026-08-19 +that fail-open is not expressible by a PDP, and which noted v0.4 collided with +shipped behaviour in a repository that had assented to this standard. + +### 9.4 Approvals are an engine concept, not a Staff or audit concern + +The approval object — durable, authenticated entries, distinct-approver +counting, atomic supersession, single consumption, revocation without holder +cooperation — is owned by `approval-engine`. + +It is not Staff's: §3.4 forbids Staff holding state another layer depends on at +runtime. It is not the decision point's: an evaluator that owns the object it +evaluates is self-dealing. It is not the audit fabric's: an approval needs +mutable, in-path, current-state semantics, and an append-only archive is built +for the opposite property. + +`access-engine` consumes approvals as **input claims** under §6.2 and never +mutates them. Every issuance, use, supersession, and revocation is emitted to +`audit-core`: the operative state and the evidence record are different +artifacts with different owners. + +The evidence guarantee is bounded, and the bound is `audit-core`'s +`docs/integrity.md`, not its INTENT principle 6. An in-database hash chain +detects a rewritten payload only if the attacker does not also recompute the +suffix — which a database owner can. Detection against that class requires the +external chain-head attestation, and even with it the store is not WORM, object +lock, or archival custody. `tamper_evidence` is therefore conditional on live +preconditions, not a property of the store at rest, and approval events receive +exactly the guarantee every other source receives. + +**Emission atomicity is `approval-engine`'s obligation.** An approval MUST NOT be +issued, consumed, superseded, or revoked without the corresponding event being +durably queued in the same transaction. + +**The queue MUST be local.** The durable queue MUST live in `approval-engine`'s +own transactional store, and **no synchronous dependency on `audit-core` may sit +inside the state-change transaction**. With a genuine local outbox, fail-closed +triggers only when `approval-engine`'s own store is unavailable — where the +change could not have been recorded anyway — and an `audit-core` outage does not +block a revocation. Satisfying the requirement by emitting synchronously to +`audit-core` inside the transaction is also atomic, and turns an audit outage +into an inability to revoke: the operation least tolerable to block during an +incident, and the same coupling this section rejects for reads. Raised by +`audit-core`. +`audit-core` reports what it received and does not imply it is everything that +happened; without atomic emission the evidence half is silently incomplete and +nothing detects the gap. This is a condition of `audit-core`'s assent +(`AUDIT-IN-0001`) and belongs in `approval-engine`'s contract before the evidence +half is treated as load-bearing. + +`audit-core` MUST NOT expose an approval-validity query. Records, yes; a verdict +on whether an approval is still valid, never — a consumer branching on that +answer would route an authorization decision through the audit fabric, which is +what this section exists to prevent. Callers needing current state ask +`approval-engine`. + +### 9.5 Graded progression is an engine concept + +Maturity — how far a subject has progressed against declared criteria and +submitted evidence — is owned by `maturity-engine`. Given the same criteria and +the same evidence it MUST return the same level; that determinism is what makes +it an Engine rather than an opinion. + +The division with Staff: **gate-house judges and proposes; maturity-engine +computes and remembers.** Interpretation is inference and stays Staff. A +criterion that cannot be evaluated by rule is not yet a criterion. + +This closes a defect in v0.2's own catalog: `gate-house` was assigned +conformance review with no engine to act through, which is exactly the §9.1 +problem raised against the containment claim. Staff acts only through Engine +APIs, including gate-house. + +**A maturity level MUST NOT be compiled into registry content.** Until +`access-engine`'s decision provenance carries a registry-snapshot digest — a gap +it self-declared in §13 — a level reaching a decision through the registry is not +reconstructable from the decision record. Levels arrive as request claims or as +versioned policy rules. Same constraint, and same reason, as zone stance. + +**A maturity level MUST NOT gate a decision directly.** Under §6.1, compiled +data that determines an outcome is still deciding. If a level determines whether +an action is permitted, it MUST reach `access-engine` as an input claim or a +versioned policy rule under §6.2, never by a consumer branching on a fetched +level. + +Approvals and maturity are deliberate opposites — a closed binary state machine +against an open graded ladder — and neither engine may drift toward the other. + +### 9.6 Evidence proves alteration and truncation, not omission at source + +An append-only archive with a verified hash chain proves that records were not +**altered or truncated after arrival**. It cannot prove that a record was never +sent. Against a compromised or buggy source, a suppressed event leaves the chain +perfectly intact and verification reports intact. + +This bound is estate-wide. Statements of the form *"the audit record proves it +happened"* are unsound; the sound form is *"the archive proves the records it +holds were not altered or truncated after arrival"*. Its mirror is equally +unsound: **absence of a record is not evidence of non-occurrence**, and no +control may read it as such. + +**Load-bearing versus attributive evidence.** The atomicity obligation attaches +to the first, not to both: + +| Kind | Test | Obligation | +| --- | --- | --- | +| **Load-bearing** | a control's soundness depends on the event being present or absent — an approval revocation, a containment action, a denial | emission MUST be atomic with the state change (§9.4) | +| **Attributive** | the event supports forensic reconstruction and attribution, and no control branches on its presence | atomicity SHOULD be sought; where it is deliberately traded away, the trade MUST be declared and completeness MUST NOT be claimed | + +Where a repository deliberately makes emission non-atomic — `ops-warden`'s +`# audit must not block signing` is the estate's live example, chosen so that an +audit-store failure cannot remove production host access — the trade is +legitimate for attributive evidence, MUST be declared where the trail is +documented, and MUST NOT be described in terms that imply completeness. The +availability argument is real in both directions: making it atomic gives the +estate's operational access lane a new dependency on its own evidence store. + +**Consequence for adaptive systems.** Suppression does not degrade observation +neutrally, it biases it optimistic, and silently: an event never emitted is never +evaluated, so no finding is raised and the last posture stands. A confidence +score computed from the richness of the record in hand cannot express doubt about +the completeness of the stream — a well-formed observation from a 90%-suppressed +stream scores high. That is this section's failure reproduced one layer up, in +the consumer. + +Two things follow. + +**Which control covers which threat.** v0.6 read as though emission atomicity +closed this section's opening sentence. It does not, and the decomposition is +owed to the reader: + +| Threat | Covered by | When | +| --- | --- | --- | +| **Accidental omission** — process dies between mutation and emit | emission atomicity, local outbox (§9.4) | prevented | +| **Adversarial omission** — a compromised source declines to insert, deletes before drain, or drains to nowhere | cadence and reconciliation | **detected, after the fact** | +| Adversarial omission at a compromised source | — | **nothing in this model prevents it** | + +The outbox sits inside the blast radius of the component whose compromise this +section posits, so it makes emission atomic against crash and partial failure +and nothing more. That residual is real and is stated rather than implied. +Raised by `audit-core`, correcting a remedy it had itself proposed. + +1. **The §8 asymmetry bounds the damage, and this is its clearest payoff.** + Because an adaptive system may only reduce authority and never manufacture it, + suppression can only prevent a tightening that should have happened. It cannot + be used to engineer a loosening. The harm is a missed reduction, not an + invented privilege — which is an argument for keeping the asymmetry absolute. +2. **Silence is a signal, and for load-bearing evidence it is the only control + in its class.** A source of **attributive** evidence SHOULD declare an + expected emission cadence; a source of **load-bearing** evidence **MUST**. + A drop below the declared rate is a finding in its own right — the stream + observed, not only its contents — and needs no Tooling contact, because the + source publishes its own stream. + + **Rate monitoring is the wrong form for rare events**, and rare is exactly + where the stakes are highest: the most valuable event to suppress is the + negative one, and revocations, denials, and containment actions are + infrequent by nature. A source emitting a handful of revocations a month has + no rate to drop below, and suppression is indistinguishable from a quiet + month. For **low-volume load-bearing classes** the required form is therefore + **positive reconciliation or a heartbeat**: compare the source's own state + transitions against the evidence engine's event count per class and treat + divergence as a finding, or assert *nothing to report* as a signed positive + claim that can itself go missing. Rate monitoring never produces a claim that + can be missing; a heartbeat does. `GH-WP-0002-T04` is the reference instance. + Raised by `audit-core`. + +Raised by `audit-core` against its own principle; extended by `kings-guard` from +its own evaluator and confidence model. + +### 9.7 Decisions have a lifetime + +A single decision point deciding on stale claims is a single decision point +deciding wrongly. The model has had no temporal law, and the approval race in +§16 was its first symptom. + +1. **Every allow has an explicit lifetime** — a TTL, or a binding to a session + or obligation that ends. An allow with no stated end is a standing grant, and + standing grants are what this estate exists to remove. +2. **Revocation and supersession have a visibility deadline**, and its shape + differs by role. A **PEP** has one boundary and MUST state one deadline. A + **PDP** MUST state a deadline **per input class**, because a decision is a + join over sources with unrelated refresh behaviour — approval-claim freshness, + registry snapshot cadence, policy package activation, directory ETag. A single + number at a PDP is either a fiction or the worst case, and the worst case is + the slowest and least visible input. "Eventually" is not a stance; an unstated + deadline is an unbounded replay window. + + A consequence worth naming: a stated deadline for a fact carried by a + registry snapshot is unfalsifiable while decision provenance holds no snapshot + digest, since nobody can determine afterwards which snapshot a decision read. + The deadline and the digest are one gap seen from two sides, which promotes + `access-engine`'s self-declared provenance gap (§13) from housekeeping to a + conformance prerequisite. Raised by `access-engine` against its own backlog. +3. **Consumption is a state change, never an inference.** An approval is + consumed by a mutation in `approval-engine` (§9.4). It MUST NOT be inferred + from the existence of a decision record — the decision precedes the action + and the action precedes consumption, so a decision record proves an intent to + act, not an act. +4. **Three failure modes are named, and each needs an owner**: an allow rendered + then never consumed; a double consumption by racing callers; consumption + after the authorized action has already failed. Neither engine closes these + alone. Recorded in §16 and `GH-WP-0002-T06`. + +### 9.8 Partition is not one-dimensional + +§9.3 handles *engine unreachable*. A real estate spends most of its incident +time in the band between reachable and gone: partial PIP reachability, clock +skew across a decision and its enforcement, and two consumers with different +declared stances seeing different worlds at the same moment. + +Two rules hold today, and the rest is open (§16). A PEP MUST resolve its own +stance from its declared map without consulting another consumer — divergent +views are expected and are not a coordination problem to be solved at enforcement +time. And where clock skew could extend a lifetime under §9.7, the shorter +reading governs. + +## 10. Changing layer + +A repository's layer is not permanent. `zone-engine` changed layer in practice +when its runtime hypothesis was falsified. + +A layer change MUST be recorded as a decision, MUST update the repository's +`INTENT.md`, and MUST obtain assent from the repositories whose boundaries move. +A repository MUST NOT acquire a new layer's permissions by gradual practice. + +"No gradual practice" needs a check rather than a sentence. A layer change MUST +carry six artifacts, written from the `zone-engine` case that the procedure +should have been derived from in the first place: + +| Artifact | Why | +| --- | --- | +| before/after `INTENT.md` | the declaration is the conformance surface (§11) | +| client inventory | what the repository holds against Tooling, before and after | +| gap inventory | which §5.3 gaps close, open, or transfer | +| assent list | every repository whose boundary moves | +| state-migration decision | what happens to live state and to consumers reading it | +| permission freeze | no new permissions of the target layer are exercised until the cut completes | + +The freeze is the one that makes the rule checkable: a repository mid-change +holds its old permissions, not the union of both. + +## 11. Conformance + +Conformance has four states, and the distinction between the last two is the +point: + +| State | Meaning | +| --- | --- | +| **Conforming** | no Tooling contact, or only §5.1/§5.2 shapes, declared | +| **Blocked-clean** | the capability does not exist because no engine exposes it, and the repository makes **no** Tooling contact — §9.1 `pending`, and not a non-conformance | +| **Declared gap** | a §5.3 contact with owner, blocker, and review date — tracked non-conformance | +| **Undeclared violation** | anything else — a finding | + +**Blocked-clean is not a lesser state than conforming.** A repository that +declined a break-glass path and left a capability at zero has complied at cost; +a repository that quietly opened a direct client and declared nothing has not. +Any downstream scoring — `maturity-engine` included (§9.5) — MUST NOT rank the +first below the second. Raised by `kings-guard`, whose three gaps are all of +this kind and which would otherwise have been graded down three times for +having taken the standard seriously. + +**Who must declare.** A repository the estate authors declares its layer in its +own `INTENT.md`. For a component the estate catalogues but does not author — +third-party or vendored, such as `OpenBao` — the §4 catalog row **is** the +declaration, and no `INTENT.md` obligation attaches. A rule that assigns an +obligation the holder cannot discharge is the §9.1 defect applied to conformance +rather than capability. + +A layer stated *about* a repository by another repository is not a declaration. +Review notes, catalog rows, and correspondence record an intent to adopt; only +the repository's own file conforms. + +**Declaration form.** Because prose cannot distinguish a declaration from a +transcribed review, a declaration MUST carry a machine-readable form: a `layer:` +key in the `INTENT.md` frontmatter, or an equivalent declaration file. Without +it this section asserts a property it cannot deliver — the defect this standard +has now corrected three times elsewhere. `ops-warden` has implemented a reference +form (`layer.yaml`, a conformance script, and a test covering the §5.2 +no-authority property) and offered it to the repositories that have yet to +declare. Raised by `audit-core`, which noted that `flex-auth`'s conforming +declaration is legible as one only by following its decision trail. + +Mechanically checkable: + +- every estate-authored repository in §4 carries a machine-readable layer + declaration; +- every direct Tooling client in a Staff repository maps to a declared §5.1, + §5.2, or §5.3 entry, and non-Tooling clients are recorded so the check is + total; +- no repository other than `access-engine` exposes an authorization decision + surface; +- no §4 capability is catalogued without an engine surface, a `pending` mark, or + a `declared-gap` mark. + +Requires review: whether claims stay inside layer permissions; whether compiled +or cached data has become an early decision (§6.1); whether doctrine is reaching +decisions as declared inputs (§6.2); whether the §8 vocabulary is used correctly. + +## 12. The conformance loop + +Doctrine no engine implements is fiction. The loop is normative, not +aspirational: + +```text +gate-house asserts an invariant + → the engines implement it, or declare a gap + → whitehat-security tries to break it + → kings-guard observes it in operation + → findings return to gate-house as doctrine change +``` + +A finding that a rule is unsatisfiable is a **success** of this loop, not a +failure of the reporting repository. Four of this standard's five versions exist +because a reviewing repository used it. + +**Step four is currently aspiration.** `kings-guard` has disclosed that it has +never observed anything in operation: the pilot is specified and scaffolded, every +input is a hand-built fixture, and no test has met a real event. Until it reports +otherwise, no argument in this estate may assume an invariant is being watched in +practice because §12 lists a repository against that step. + +## 13. Open gaps + +Two different things are recorded here, and they are opposite conformance states +(§11). A **declared contact** means the repository touches Tooling because no +engine exposes the capability. An **unowned capability** means no route exists +and the repository makes no contact at all. Reading them as one list would grade +restraint as though it were non-conformance. + +An `intended owner` is a **proposal to** the named repository, not an assignment +**onto** it. §2 keeps ownership in the repository's own `INTENT.md`, so the +register distinguishes proposed from assented. + +| Gap | State | Declared by | Owner | Owner status | +| --- | --- | --- | --- | --- | +| SSH-CA signing write (`VaultCA`, `bao kv put`) | declared-contact | ops-warden | secrets-engine | proposed | +| Authentication / assurance evidence | unowned-capability | kings-guard | identity layer + audit-core | **access-engine declined** | +| Secret-use evidence | unowned-capability | kings-guard | secrets-engine | proposed | +| Actuation / containment surface | unowned-capability | **gate-house (estate-wide)** | access-engine + runtime engines | proposed | +| Identity and secret observation | unowned-capability | kings-guard | as above | proposed | +| Stance-map register had no implementation | declared-contact | ops-warden, access-engine | gate-house | resolved in §13.1 | +| Registry-snapshot digest in decision provenance | declared-contact | flex-auth | flex-auth | self-declared | +| Approval storage and lifecycle | — | flex-auth | approval-engine | assigned (§9.4) | +| Approval evidence | — | gate-house | audit-core | **assented** (`AUDIT-IN-0001`) | +| Approval evidence custody stronger than the shipped bound — WORM, object lock, transparency log | unowned-capability | audit-core | — | unassigned | +| Emission atomicity for approval state changes | — | audit-core | approval-engine | assigned (§9.4) | +| Non-atomic audit emission on the SSH signing lane | declared-contact | ops-warden | ops-warden | self-declared, attributive (§9.6) | + +The actuation row is no longer attributed to `kings-guard`. §9.2 ruled that +containment is not a Staff capability lacking a route, so `kings-guard` is not +its declarer: the gap is estate-wide and blocks every repository's ability to +act. Raised by `kings-guard`, which asked not to carry a row for a capability +the standard had just ruled was never theirs. + +`access-engine` declined authentication and assurance evidence +(`FLEX-DEC-2026-002`): it consumes assurance claims as input and never redefines +them, so evidence of authentication belongs to the identity layer and +`audit-core`. It owns evidence of the decision, which it already emits. The +containment surface is recorded as proposed and remains `pending` under §9.2. + +Whether approvals warrant custody stronger than every other source is doctrine +work not yet done; until it is, approval evidence carries the same guarantee as +any other source and §9.6 bounds what may be claimed from it. + +**What is normative here, and what is a snapshot.** Three rules are part of this +standard and survive wherever the register lives: + +1. the two marks — `pending` and `declared-gap` (§9.1); +2. the owner-status rule — a proposed owner is not an assigned one (§2); +3. the scoring rule — `blocked-clean` MUST NOT rank below conforming (§11). + +**The table above is a snapshot, not statute.** It moves into `maturity-engine` +as soon as that engine can store state, and the `state` and owner-status columns +MUST survive the migration. A standard that is also a backlog keeps attracting +findings that belong in the register, and its review interval is far slower than +the register's real rate of change. + +### 13.1 PEP stance-map register + +Every PEP-shaped consumer publishes an unreachable-engine stance map (§6.4, +obligation 3). This is the inventory until `maturity-engine` can hold it. + +| Consumer | Stance map | Shape | +| --- | --- | --- | +| `ops-warden` | `ops-warden/pep-stance.yaml` | total per-zone; open `z0`–`z2` and unknown, closed `z3-critical`; test asserts the published map equals the shipped default (`ADR-0009`) | +| `ops-mason` | — | **not published**; catalogued PEP-shaped in §4 | + +**One row is the finding.** The aggregate of consumer stances is the estate's +real authorization behaviour, and it is currently one published map and one +absence. `access-engine` has noted it is the repository positioned to notice +when that aggregate diverges from what the policy packages say — which it cannot +do while the register is nearly empty. + +## 14. Adoption + +Status is **accepted**, on the owner's decision of 2026-08-29. + +Two things that acceptance does and does not mean, kept apart because +`ops-warden` asked for the distinction: + +| | | +| --- | --- | +| **Boundary assent** | given by the four repositories below, at the version named in each record, and undisturbed since | +| **Revision review** | each of the four reviewed v0.6 and returned findings; **every change in v0.7 is the adopted remedy of a finding they raised** | +| **Not claimed** | no repository has reviewed v0.7 *as text*. The first revision review will confirm or correct it | + +Accepting a standard nobody has re-read is a deliberate call: the estate learns +more from using it than from another round of prose refinement, and the changes +in v0.7 were requested rather than invented. Findings against the accepted text +remain welcome and are §12's normal business, not an exception. + +| Repository | Record | Outcome | +| --- | --- | --- | +| flex-auth | `FLEX-DEC-2026-001` | assent to all three items; one self-declared non-conformance; two rename conditions | +| kings-guard | `KG-DEC-2026-001` | assent; declined the offered §5 relaxation; raised §9.1 | +| ops-warden | `ADR-0010` | assent to all three; veto not exercised; offered the §5.3 amendment | +| audit-core | `AUDIT-IN-0001` | assent to the evidence half with conditions; corrected the rationale twice; raised §9.6 | + +Adoption for a repository means its `INTENT.md` declares its layer, its +ownership claims fall inside that layer, its Tooling contacts are declared under +§5, and any shared boundary has been assented to by the other side. + +**Adoption status as of 2026-08-29: seven of sixteen** estate-authored §4 +repositories have declared in their own voice — `gate-house`, `flex-auth`, +`kings-guard`, `ops-warden`, `audit-core`, `approval-engine`, `maturity-engine`. +The remaining nine — `info-tech-canon`, `net-kingdom`, `key-cape`, +`user-engine`, `tenant-engine`, `zone-engine`, `secrets-engine`, `ops-mason`, +`whitehat-security` — carry a layering review note authored by `gate-house` and +have not answered it. Those notes state a layer but do not constitute a +declaration, and this standard does not claim estate-wide adoption on their +basis. Declaration requests are open as intakes in each. + +## 15. Change log + +v0.1 → v0.2: + +1. **§5 restructured** into three sanctioned shapes. Added §5.2 conduit + (ops-warden's question, ruled) and §5.3 declared engine gap (ops-warden's + amendment, accepted). +2. **§6.2 added** — doctrine must reach the decision as an input claim or a + versioned policy rule (flex-auth's boundary drawn back, accepted). +3. **§9 added** — the catalog may not assign a capability the rules forbid + discharging; containment marked pending; degraded-mode fallback ruled into + the engine (kings-guard's finding). +4. **§11 restructured** — conformance now has three states, distinguishing a + tracked gap from an undeclared violation. +5. **§12 made normative**, with the explicit statement that an + unsatisfiability finding is a success of the loop. +6. **§13 added** — open gaps register, including the unowned approval storage + and lifecycle capability. +7. §4 catalog gained the pending mark and ops-warden's SSH certificate lane. + +v0.2 → v0.3: + +1. **§9.4 added** — approvals assigned to `approval-engine`, with the operative + state and the evidence record separated between it and `audit-core`. +2. **§9.5 added** — graded progression assigned to `maturity-engine`, closing + the §9.1 defect in gate-house's own conformance-review claim, and carrying + the guardrail that a level may never gate a decision directly. +3. §4 catalog gained both engines; gate-house's conformance-review claim now + names the engine it acts through. +4. §13 register updated: the approval hole is assigned, two new entries added. + +v0.6 → v0.7, from four reviews: + +1. **§3.4 is written.** v0.6 announced the human/agent principal separation in + §1 and §15 and left §3.4 byte-identical to v0.5 — a silent edit failure. A + rule stated about a standard in its own change log is not a rule. Found by + `kings-guard`. The same failure had also dropped two §16 entries, restored + here. +2. **§6.4 obligation 1 rewritten** — it forbade what obligation 3 blesses. A PEP + may proceed under its declared §9.3 stance provided the application of that + stance is *recorded in place of* the decision. Stricter than v0.6 where it + counts: a fail-open result is metadata, never silence. Raised by `ops-warden`. +3. **§6.4 obligation 2 rewritten** — it forbade the session-bound allow §9.7.1 + permits. Scoped to replay outside the decision's own binding and lifetime, + with the canonical request digest as the mechanical test, and negative + caching ruled permitted where the refusal is recorded and the cache lifetime + declared. Raised by `access-engine`. +4. **§6.4 obligation 3** gained the requirement that the published stance map + equal shipped behaviour, asserted by test. **§13.1** now exists as the + register §6.4 mandated and v0.6 did not implement. +5. **§9.6 gained a threat decomposition** — atomicity prevents accidental + omission; cadence and reconciliation detect the adversarial case after the + fact; nothing prevents it at a compromised source. Raised by `audit-core` + against its own proposed remedy. +6. **§9.6 cadence is now MUST for load-bearing sources**, with positive + reconciliation or a heartbeat as the required form for low-volume classes, + because rate monitoring fails exactly where the stakes are highest. +7. **§9.7.2 splits by role** — a PDP states a deadline per input class, a PEP one + at its boundary. Promotes `access-engine`'s provenance gap to a conformance + prerequisite. +8. **§3.3's Evidence row** is stated as an estate trade rather than a property, + leaving independent-recording-before-effect raisable as a declared exception. +9. **§17** moves the decision-record schema to `access-engine`, which argued it + against its own interest; `kings-guard` drafts the emission-cadence schema. +10. **§13** no longer attributes the actuation gap to `kings-guard`; it is + estate-wide. **§19 removed** — a verdict inside a standard grades the + document it lives in. **§17/§18** demoted from H1 to H2. +11. **§20 added** — the Railiance interaction boundary, on `railiance-master`'s + definitions, including that `rein-*` is not a fifth axis. + +v0.5 → v0.6, from the independent assessment of 2026-08-29: + +1. **§3.3 types the engines** — PDP, PIP, Evidence, Lifecycle, with a role + column in §4. A new engine is a PIP unless this standard says otherwise, so + "we need an engine for X" cannot drift into "X now decides". +2. **§6.4 names the enforcement point** — a PEP shape with four obligations: no + side effect without a decision record, no local recaching of the verdict, a + declared unreachable-engine stance, reconstructability. The standard had the + decision and not the gate. +3. **§9.2 replaced** — containment was marked pending against the wrong + repository. Actuation is an Engine concept, unowned, held at zero; Staff + proposes containment and never performs it. +4. **§3.4 separates the two Staff principals** — human and agent share the layer + but not blast radius: no standing credential, conduit or engine API only, + agent memory is not a state plane, every action reconstructable as the + caller's. +5. **§9.7 puts time into the model** — explicit lifetimes, revocation visibility + deadlines, consumption as a state change never inferred, and the three race + modes named. **§9.8** states what holds under partition and leaves the rest + open. +6. **§17 requires the Taxonomy artifacts** — claim, decision-record, gap-record, + and emission-cadence schemas — without which §6.2 and §11 are reviewable but + not compileable. Ownership proposed, not assigned. +7. **§18 composes the sibling standards** — how zone stance, tenancy posture, + and a credential lifecycle event each enter a decision as a claim. They were + cited in frontmatter and nowhere in the rules. +8. **§5 gained a sunset** on the uncatalogued-infrastructure carve-out, and + §5.3 **declines** a proposed fourth "operator of third-party Tooling" shape: + it would convert a tracked gap into a permanent allowance. +9. **§10 gained the six artifacts** a layer change must carry, written from the + `zone-engine` case, including a permission freeze during the cut. +10. **§2 lifts the observation rule** — no estate argument may cite observation + that has not happened. **§13** separates its three normative rules from the + table, which is now a snapshot due to move into `maturity-engine`. +11. **§16** the approval custody question is **decided: no**, rather than left + open. **§19** records the fitness verdict, including that the estate can + propose and decide but cannot yet watch or act. + +v0.4 → v0.5, all from review findings: + +1. **§9.1 split into two marks** — `pending` (no route, capability zero) and + `declared-gap` (route exists under §5.3, capability works and is tracked). + v0.4's single mark would have forced a false `pending` onto ops-warden's + production SSH issuance. Raised by `ops-warden`. +2. **§9.3 rewritten** — input degradation is the engine's; engine-unreachability + is necessarily the consumer's, bounded by a declared, auditable, total stance. + Contested by `flex-auth`: fail-open is not expressible by a PDP, and v0.4 + collided with `ops-warden` `ADR-0009`. +3. **§5 gained a scope rule** — "Tooling-layer system" means a §4 Tooling row; + uncatalogued infrastructure is outside §5 and recorded rather than policed. + Without it every Staff repository was in undeclared violation for writing + progress events. Raised by `ops-warden`. +4. **§9.4 requires a local outbox** — no synchronous dependency on `audit-core` + inside the state-change transaction, so an audit outage cannot block a + revocation. Raised by `audit-core`. +5. **§9.5 forbids compiling maturity levels into registry content** until + decision provenance carries a registry-snapshot digest. Raised by `flex-auth`. +6. **§9.6 gained the load-bearing / attributive distinction**, the mirror rule + that absence is not evidence of non-occurrence, the optimistic-bias + consequence for adaptive systems, and silence-as-signal. Raised by + `kings-guard` on top of `audit-core`'s original. +7. **§11 gained a fourth state** — blocked-clean, which MUST NOT rank below + conforming — and a machine-readable declaration form. Raised by `kings-guard` + and `audit-core`. +8. **§13 gained state and owner-status columns** — declared-contact versus + unowned-capability, proposed versus assented owner. `access-engine`'s + decline of authentication evidence is recorded. Raised by `kings-guard` and + `flex-auth`. +9. **§8** records the asymmetry's payoff under incomplete observation; **§12** + records that its fourth step is unstaffed; **§14** corrects the adoption + arithmetic and the status contradiction. + +Amended in place while `proposed`, 2026-08-28: §11 gained the who-must-declare +rule after a conformance sweep found the standard required an `INTENT.md` +declaration from `OpenBao`, which the estate does not author; and §14 gained the +honest adoption count. + +v0.3 → v0.4: + +1. **§4 catalog gained `audit-core`** as an Engine, on its own declaration. v0.3 + named it as an owner in §9.4 and §13 without cataloguing it — a §11 defect in + the standard itself, raised by `audit-core`. +2. **§9.4 evidence rationale rewritten** to cite `audit-core`'s shipped + `docs/integrity.md` bound rather than its INTENT principle 6, and to state + that `tamper_evidence` is conditional on live preconditions. +3. **§9.4 gained emission atomicity** as `approval-engine`'s obligation, and the + prohibition on `audit-core` exposing an approval-validity query. +4. **§9.6 added** — evidence proves alteration and truncation, not omission at + source. Estate-wide; the sound and unsound forms of the claim are stated. +5. **§13** — evidence half recorded as assented with conditions; two new gaps: + stronger approval custody (unassigned) and emission atomicity + (`approval-engine`). + +## 16. Open questions + +- ~~Whether approvals warrant archival custody stronger than every other audit + source.~~ **Decided (§13): no.** Approval evidence carries the same bound as + every other source. The acute risk for approvals is *omission* — a suppressed + revocation — and archival custody does not address omission at all; emission + atomicity with a local outbox (§9.4) and a detection surface + (`GH-WP-0002-T04`) do. Leaving it open while calling the evidence half + load-bearing created a promise the archive cannot cash. If a future + requirement genuinely needs WORM or a transparency log, that is a different + store with a different owner, raised then. +- Whether SSH certificate issuance evidence is load-bearing or attributive + (§9.6). Ruled attributive here on the argument that no control branches on the + presence of a signing record; `ops-warden` asked for the ruling and the trade + is genuinely two-sided, so it is flagged rather than settled. +- Who marks an approval consumed, and at what point relative to the decision + (§9.4). `flex-auth` notes the decision precedes the action and the action + precedes consumption, so an allow rendered against an approval then never + consumed, or consumed twice by a racing caller, is a gap neither engine closes + alone. Needed before `FLEX-WP-0017` T05. +- Whether other §4 repositories are missing layer declarations; `audit-core` + flagged its own absence and asked whether the catalog needs the same + correction elsewhere. +- Whether the gap register migrates from this standard into `maturity-engine` + once that engine exists, leaving the standard to state the rules only. +- Whether Tooling warrants subdivision between third-party and homegrown. +- How a future `role-engine` divides responsibility with `access-engine`. +- Whether declared gaps need an estate-wide register rather than per-repository + declarations; ops-warden's `warden route gaps` is candidate machinery. +- Whether non-security repositories adopt the same model. The determinism cut is + not security-specific; if non-security Staff also may not hold + runtime-dependent state, the estate gets one constitution rather than a + security ghetto. +- The rest of §9.8: split brain, partial PIP reachability, and clock skew beyond + the two rules stated. +- Publication integrity of the Taxonomy layer itself. This standard demands + reconstructability of decisions while its own publication path has no digest, + freeze, or rollback discipline. +- The fitness verdict formerly at §19 now lives in + `net-kingdom/history/2026-08-29-layering-standard-assessment.md`. A grade + inside a standard of record becomes normative by adjacency and ages against the + text it grades. Raised by `access-engine`. Its two substantive points remain + live: observation in production is unstaffed (§12) and actuation has no surface + (§9.2). +- The **working companion** (`net-kingdom/SECURITY-COMPANION.md`, v0.2, root of + the repository for onboarding) is the operative form of this statute. The statute governs on disagreement, and a + disagreement is a finding. The v0.1 gap `access-engine` found — publish + your stance map, but nowhere saying where, and no inventory obligation — is + fixed in v0.2 §5.3. +- How the Railiance operational axes meet this model beyond §20's first + statement, which is deliberately minimal. + +--- + +## 17. Taxonomy artifacts + +§6.2 says doctrine reaches a decision as an input claim or a versioned policy +rule. As prose that is a rule a reviewer can apply. As an interface it does not +exist, because nothing defines what a claim *is*. §11 calls itself mechanically +checkable while resting on that gap. + +Four artifacts are therefore required, owned by Taxonomy and versioned like any +standard: + +| Artifact | Contents | +| --- | --- | +| **request-claim schema** | identity, tenant, zone stance, posture, approval, maturity, assurance — each with its issuer and freshness rule | +| ~~decision-record schema~~ | **moved to `access-engine`** — see below | +| **gap-record schema** | the §5.3 fields — `capability`, `intended_owner`, `blocked_on`, `review` — plus the §13 `state` and owner-status | +| **emission-cadence declaration** | the expected rate a source publishes, so silence is a finding (§9.6) | + +Until these exist, §6.2 and §11 are reviewable but not compileable, and every +engine invents its own claim shape at its own boundary. + +**The decision-record schema is not Taxonomy's.** A decision record is the PDP's +output artifact — the one thing in the estate only `access-engine` produces — and +§2 keeps ownership in the producing repository's own `INTENT.md`. Taxonomy +authoring the schema for an artifact only one engine emits would invert the +ownership rule this standard applies everywhere else. `access-engine` publishes +it as a contract; Taxonomy holds only the shared field vocabulary the claim +schema references. + +Raised by `access-engine` **against its own interest** — the same §2 argument it +used to decline authentication evidence, applied where it takes work on rather +than off. Symmetry of that kind is what makes the ownership rule credible. + +**The emission-cadence declaration has a drafter.** `kings-guard` is its only +consumer, cannot implement silence-as-signal without it, and has offered to draft +it against `qonto-assistant` and hand it to whichever Taxonomy repository takes +ownership — rather than inventing a local shape, which is the drift §17 exists to +prevent. Accepted as a draft; ownership still rests with Taxonomy. + +**Ownership is proposed, not assigned.** `info-tech-canon` holds ecosystem-wide +semantic contracts and `net-kingdom` holds NetKingdom standards of record; the +split between them for these four artifacts is theirs to draw, and §2 keeps +ownership in the owning repository's `INTENT.md`. Neither has assented. + +## 18. Composition with the sibling standards + +The related-standards list has been frontmatter and little else. If the +following sentences cannot be written, the list is decoration — so they are +written here rather than in the siblings. + +**Zone stance** (`security-zones_v0.1`). A zone answers which scrutiny a +workload has qualified for; membership is `zone-engine`'s. The *effect* of a +zone on a decision belongs in a versioned `access-engine` policy package, never +in registry content — that ruling is zone-engine's §5, and §6.1 is its +generalization. Zone stance therefore enters a decision as a **claim on the +request or a rule in the package**, and a decision that turned on a zone must +name the package version that read it. + +**Tenancy posture** (`tenancy-posture_v0.1`). Posture is a bounded security-state +input, published by its owner and never a privilege source (§8). It enters as a +**claim**, carries its own freshness, and the §8 asymmetry binds it: posture may +tighten a decision and may never loosen one. A posture too stale to trust is a +missing claim, and a missing claim is not permission. + +**Credential lifecycle** (`credential-management_v0.2`). Issuance, rotation, and +revocation are `secrets-engine`'s and `OpenBao`'s, downstream of a decision — a +credential is an artifact of authority, never its source. A lifecycle event +becomes an **input** to a later decision as a claim (this credential is current, +this lease is bound to this task), never a side channel that changes an outcome +without appearing in the decision record. Revocation visibility is bounded by +§9.7. + +Each of the three composes the same way, which is the point: **facts arrive as +claims, effects live in versioned policy, and anything that changes an outcome +appears in the decision record.** + +--- + +## 20. The Railiance interaction boundary + +Operations is not NetKingdom's. Workload operations are organized by +**Railiance**, whose framework repository is `railiance-master`, and NetKingdom +provides the security and approval framework those operations consume. This +section states the boundary as it stands today. It is expected to evolve, and it +is written here so that evolution is visible rather than inferred. + +Definitions are `railiance-master`'s and are restated, not authored, here. + +### 20.1 What Railiance organizes + +A **workload** is a managed running deployable. Human commands, credential +patterns, broker actions, approvals, and infrastructure resources that are not +themselves deployables **are not workloads** — which is why an approval object +(§9.4) is not a Railiance axis and never becomes one. + +Every workload is operated through four composable axes, each answering a +different question about the same workload: + +| Prefix | Axis | Question | +| --- | --- | --- | +| `railiance-*` | ownership | Who owns this capability? | +| `rail-*` | execution contract | How does this workload run? | +| `rapp-*` | managed package | What exactly is packaged and operated? | +| `reef-*` | substrate | Where is it bound, and as what operational reality? | + +`rein-*` is **not a fifth axis**. Reins are `glas-harness` agent-harness +backends; the name echoes `rail-*` analogically, not taxonomically. Agentic +session semantics — session loops, tool policy, harness routing, model selection +— belong to `glas-harness`. When a rein is installed and operated as a managed +service it is a workload like any other, packaged and bound through the four +axes above. + +### 20.2 What holds today + +For any Railiance consumer of NetKingdom security, without exception: + +1. Authorization decisions come from `access-engine` and from nowhere else (§6). +2. Approvals are objects in `approval-engine`, consumed as claims (§9.4). +3. Credentials are materialized by `secrets-engine` **after** a decision, never + as a substitute for one. +4. Evidence goes to `audit-core` under the bound in §9.6. +5. Anything causing a protected side effect is **PEP-shaped** and owes the four + obligations in §6.4 — including a published unreachable-engine stance in the + §13.1 register. + +### 20.3 What is not settled + +The mapping between the axes and this model is deliberately thin, because +guessing it would be worse than admitting it: + +- A **`rapp-*`** is the most likely *resource* a decision is rendered about, but + nothing states its identity form in a request claim. +- A **`rail-*`** describes how a workload runs and is therefore where PEP shape + is most likely to live — but §6.4 obligations attach to repositories, and a + rail is a contract, so whether a rail can *carry* an obligation is unwritten. +- A **`reef-*`** answers where a workload is bound, which is adjacent to a + security zone (`security-zones_v0.1`) without being one. `zone-engine` records + that a reef capping availability for everything bound to it is a canon + composition problem. That composition is unwritten. +- The **`railiance-*` ownership axis** names who owns a capability, which is + adjacent to the principal a decision is rendered for. Adjacent is not equal, + and no rule connects them. +- **`glas-harness` and reins** hold tool policy and session semantics for agents, + while §3.4 rule 2 holds that an agent acts only through a conduit or an engine + API. Those two must compose, and neither side may treat its own half as + sufficient. That seam is the most consequential of the five, because it is + where "tool availability is not permission" is actually enforced or lost. + +### 20.4 How this boundary changes + +An interaction boundary between two frameworks is owned by neither alone. +Changes to §20 require assent from `railiance-master` for the axis definitions +and from `glas-harness` for the session and tool-policy seam, on the same terms +as any other boundary in this standard (§10). NetKingdom states what a consumer +owes; it does not define what a rail, rapp, reef, or rein *is*. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-security-scenario-composition-v0.1.md b/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-security-scenario-composition-v0.1.md new file mode 100644 index 0000000..ffebffd --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-security-scenario-composition-v0.1.md @@ -0,0 +1,142 @@ +--- +id: netkingdom-security-scenario-composition-v0.1 +type: standard +title: "NetKingdom Security Scenario Composition v0.1" +domain: netkingdom +status: proposed +version: "0.1" +owner: net-kingdom +created: "2026-08-23" +updated: "2026-08-23" +last_reviewed: "2026-08-23" +review_interval: 3m +scope: meta-orchestration +schema: + - canon/schemas/security-scenario_v0.1.schema.json +validator: + - tools/security-scenario-composer/security_scenario_composer.py +adr: + - docs/adr/ADR-0007-security-orchestration-boundary.md + - docs/adr/ADR-0012-playbook-capability-contract-ownership.md +related: + - canon/standards/playbook-capability-contract_v0.1.md +--- + +# NetKingdom Security Scenario Composition v0.1 + +## 1. Purpose + +This contract defines the deterministic, plan-only boundary between a requested +NetKingdom capability set and the independently owned playbook entry points that +can realize it. It consumes conformant Playbook Capability Contract v0.1 +declarations and produces an owner-routed responsibility, trust, parameter, and +readiness handoff. + +Composition answers **what is selected, in which trust order, with which safe +parameters, and who must execute and evidence it**. It does not run a playbook, +mint a credential, infer authority, or declare a runtime ready. + +## 2. Authority boundary + +- NetKingdom owns scenario intent, provider selection, parameter-policy checks, + trust sequencing, and the composed responsibility map. +- The declaration owner owns the playbook or stable entry point, execution, + rollback, and readiness evidence. +- Railiance retains deployment execution for Railiance-owned declarations. +- A composed plan is not authorization to execute. Any approval, custody, + credential, or change-window gate named by an owner remains in force. + +## 3. Scenario input + +Scenario files conform to +`canon/schemas/security-scenario_v0.1.schema.json`: + +```yaml +id: scenario:c0-local-identity-reference +authority: netkingdom +initial_trust: + - bare_host_trust +requires: + capabilities: + - c0.bootstrap-identity +providers: + c0.bootstrap-identity: net-kingdom.local-identity +parameter_overrides: + net-kingdom.local-identity: + bootstrap_username: bootstrap-admin +``` + +`authority` uses the Playbook Capability Contract vocabulary: `platform`, +`netkingdom`, or `tenant`. Parameter sensitivity and tuning-authority rules are +applied before a plan is emitted. + +`initial_trust` lists trust states established outside this composition. The +composer never assumes an initial trust state. A required state must be present +there or be satisfied by an earlier selected declaration. + +`providers` pins a required capability to an exact declaration id. A pin is +mandatory when more than one valid declaration provides the capability. A pin +may not name an unrequested capability or a declaration that does not provide +the keyed capability. + +## 4. Fail-closed selection rules + +Composition fails when any of the following is true: + +- a declaration is invalid or declaration ids are duplicated; +- a capability is unknown, duplicated, or has no provider; +- multiple providers match and the scenario does not pin one; +- a provider pin does not match the requested capability; +- an override targets an unselected declaration or unknown parameter; +- an override violates type, constraint, sensitivity, or tuning authority; +- a required parameter has neither a default nor an override; +- a required trust state cannot be established without a cycle or inference. + +Selection order never resolves ambiguity. Filesystem order, catalog order, +lexical order, and prior deployment state are not provider authority. + +## 5. Trust sequencing + +The composer starts only with the scenario's explicit `initial_trust` set. It +then selects the lexically first **eligible** declaration, where eligible means +all of that declaration's required trust states have already been established. +After the step, and only for composition purposes, the declaration's satisfied +states become available to later steps. + +Lexical ordering makes independent eligible steps reproducible; it does not +grant one provider precedence during selection. If no remaining declaration is +eligible, composition fails and reports the unresolved trust states. + +Readiness checks attached to a satisfied state are obligations for the owning +executor. They are not marked satisfied merely because the plan contains them. + +## 6. Composition output + +A successful output has +`apiVersion: netkingdom.io/security-scenario-composition/v0.1` and +`kind: SecurityScenarioComposition`. It contains: + +- the requested capabilities and exact selected declaration ids; +- effective parameter values with their source, sensitivity, and tuning + authority; +- ordered execution handoffs containing owner, repository, entry point, + required trust, produced trust, and readiness obligations; +- a flattened responsibility map attributable to declaration ids; +- the final *planned* trust-state set; +- an explicit `execution.permitted: false` boundary. + +The output is non-secret planning material. Declarations and scenarios must use +secret references rather than secret values as required by the Playbook +Capability Contract. + +## 7. Conformance + +Use the canonical tool: + +```text +python3 tools/security-scenario-composer/security_scenario_composer.py \ + --scenario [ ...] +``` + +Exit zero means the declarations and scenario compose deterministically. It +does not mean the plan was executed or its readiness evidence was observed. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-security-zones-v0.1.md b/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-security-zones-v0.1.md new file mode 100644 index 0000000..1d3df36 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-security-zones-v0.1.md @@ -0,0 +1,352 @@ +--- +id: netkingdom-security-zones-v0.1 +type: standard +title: "NetKingdom Security Zones v0.1" +domain: netkingdom +status: proposed +version: "0.1" +owner: zone-engine +publication_owner: net-kingdom +created: "2026-08-22" +updated: "2026-08-22" +last_reviewed: "2026-08-22" +review_interval: 3m +source_revision: "zone-engine@a510393" +standard_token: security-zones_v0.1 +related: + - canon/standards/tenancy-posture_v0.1.md + - repo-manager/docs/adr-004-authoritative-workload-declarations.md +--- + +# NetKingdom Security Zones v0.1 + +## 1. Purpose + +A security zone is a named workload-admission standard. It answers which +scrutiny a workload has qualified for; control-owner policy then answers what a +particular control does in that zone. A zone is not a repository label, a +credential lane, a network segment, a reef, or a temporary exception. + +This standard is a sibling of `tenancy-posture_v0.1`. It owns zone identity, +membership, admission, resolution, and the time-boxed exception lifecycle. +`flex-auth` remains the only PDP for decisions it renders. Every other control +continues to be owned and evaluated at its existing enforcement point. + +## 2. Authority and conformance + +Authority is split deliberately: + +| Fact or rule | Authority | +| --- | --- | +| Workload identity and requested membership | Workload's responsible repo | +| Zone identities and admission standards | This standard, owned by zone-engine | +| Per-zone stance | Owner of the control that renders the effect | +| Failure behavior when a dependency is unavailable | Owner of the PEP | +| Publication | net-kingdom canon | + +Conformance is **accuracy, not altitude**. A lower zone declared with evidence +is conformant. A stricter zone that the workload cannot evidence is not. +Changing membership to evade a control is not an exception mechanism. + +The workload is the sole **zone** policy subject. It includes independently +governed application, automation, maintenance, and operational/control-plane +execution units. The workload executing a tunnel, credential broker, policy +service, or maintenance operation requires identity; the tunnel, grant, lane, +pattern, action, human, or agent does not become a workload merely because a +control observes it. Those retain native typed identity and caller/resource +context. + +## 3. Resolution is authoritative + +The stable `workload_identity.name` is the join key. Runtime principals, +resources, credential lanes, and controls reference it explicitly. A resolver +MUST NOT infer membership from repository ownership, paths, lane types, actor +classes, environment, criticality, reefs, organization posture, or naming +conventions. + +For a managed deployable, the authoritative cross-repository reference is the +Repo Manager v1 tuple `(rapp_id, workload_identity.name)`, optionally narrowed +by `deployable`. A catalog also declares whether its subject is workload +applicable. Resolution returns both the reference result and admission result: + +- `satisfied` — the workload has an authoritative identity and evidence meeting + the declared zone's floor; `effective_zone` is the declared zone; +- `unsatisfied` — identity and membership are declared, but evidence is below + the admission floor; `effective_zone` is `unknown`; +- `unknown` — identity, membership, or a required floor/evidence input cannot be + resolved for a workload-applicable subject; `effective_zone` is `unknown`; +- `not-applicable` — the owning catalog explicitly establishes that the subject + is an action, actor, lane, pattern, or resource rather than a workload; no + zone is resolved and the control uses that subject's native identity. + +`unknown` is a result, not a zone. A control MUST declare an explicit treatment +for it. That treatment may deny, escalate, or use a reviewed build-stage rule; +it never manufactures membership and never grants an exception. + +## 4. Zone catalog + +The initial catalog follows the estate's existing `M0`–`M3` maturity ladder and +adds one non-monotone continuity band required by ops-warden `ADR-0006`. + +| Zone id | Admission floor | Environment/data boundary | Purpose | +| --- | --- | --- | --- | +| `z0-experimental` | `M0` promotion evidence | Synthetic only; no real credential or user data | Experiments where advisory controls preserve iteration speed | +| `z1-operational` | `M1` promotion evidence | Production secret handling for at most `internal` data | Early operational workloads with an owned front door | +| `z2-protected` | `M2` promotion evidence | `prod`; at most `confidential` data | Production workloads requiring review, SLO/on-call, and incident readiness | +| `z3-critical` | `M3` promotion evidence | `prod`; at most `restricted` data | Critical or regulated workloads with the strongest normal failure behavior | +| `z2-continuity` | `M2` plus dependency/recovery evidence | `prod`; at most `confidential` data | Foundational access or recovery workloads where fail-closed dependency cycles would cause an outage | + +`z2-continuity` is a sibling of `z2-protected`, not a relaxation of its +admission floor. It exists +because enforcement stance is not monotone: a tunnel or credential-issuance +path can require production scrutiny while deliberately remaining fail-open for one +availability-sensitive control. Placement on a reef is never evidence for any +zone. + +### 4.1 Context floor + +The declared zone must be at least as strict as the workload context requires. +The context floor is the maximum of every resolved input: + +| `criticality` | Minimum maturity | +| --- | --- | +| `low` | `M0` | +| `medium` | `M1` | +| `high` | `M2` | +| `critical` | `M3` | + +Data-class floors are consumed from the canon-owned classification mapping; the +current `synthetic → M0`, `internal → M1`, `confidential → M2`, and +`restricted → M3` mapping is already used by ops-warden. An absent or unresolved +floor makes admission `unknown`. In particular, `public` is disclosure policy +and `synthetic` is data origin: they are not aliases, and this standard does not +invent a floor while info-tech-canon's ruling is pending. + +`organization_posture` may select a versioned control profile, but it never +changes identity, membership, or admission. + +## 5. Stance and failure-mode model + +Every owner-qualified control publishes a total mapping over all zone ids plus +`unknown`. There is no implicit default. + +- `enforced` — the control's deny or escalation effect changes the outcome; +- `advisory` — the control evaluates fully and records the outcome it would + have produced, but does not block; +- `exempt` — the control is deliberately not evaluated for this zone and emits + the versioned exemption reason. + +Failure mode is separate and PEP-side: + +- `fail_closed` — an unavailable or invalid evaluator produces the control's + safe blocking/escalation outcome; +- `fail_open` — the PEP proceeds and records that the control was unavailable. + +`exempt` has no failure mode. A local control with no remote dependency uses +`fail_closed` to describe evaluator errors. Changing stance is a policy-package +change owned by the control owner, not a membership change. + +### 5.1 Initial build-stage control profile + +This profile is the v0.1 proposal for the first consumer. It is not stored in a +workload's `zones:` declaration. flex-auth owns the pre-sign rows; ops-warden +owns the other rows and every PEP failure mode. + +| Zone | flex-auth pre-sign stance / ops-warden PEP | agent high-risk read boundary | `warden plan` zone rule | +| --- | --- | --- | --- | +| `z0-experimental` | `advisory` / `fail_open` | `enforced` / `fail_closed` | `advisory`; evaluator failure cannot produce `autonomous` | +| `z1-operational` | `advisory` / `fail_open` | `enforced` / `fail_closed` | `advisory`; evaluator failure cannot produce `autonomous` | +| `z2-protected` | `enforced` / `fail_open` | `enforced` / `fail_closed` | `enforced`; minimum `founder_required` when the zone rule matches | +| `z3-critical` | `enforced` / `fail_closed` | `enforced` / `fail_closed` | `enforced`; minimum `founder_required` when the zone rule matches | +| `z2-continuity` | `enforced` / `fail_open` | `enforced` / `fail_closed` | `enforced`; minimum `founder_required` when the zone rule matches | +| `unknown` | `advisory` / `fail_open` under the versioned `build` profile | `enforced` / `fail_closed` | `enforced`; never `autonomous` from zone evidence | + +The `unknown` pre-sign treatment is an explicit organization-build policy, not +a permissive membership default. It must change through a versioned control +profile when the organization posture graduates. + +The agent read boundary stays enforced in every zone: build-stage permissiveness +does not extend to disclosing high-risk credentials. For a missing lane risk: + +- `z0-experimental` may resolve to `standard` only when admission proves the + lane can expose synthetic material exclusively; +- `z1-operational` and `z2-protected` resolve to at least `high`; +- `z3-critical` resolves to `critical`, treated by the + boundary as at least `high`; and +- unresolved membership resolves to at least `high`. + +An explicit grade always remains preferable. `accepted` is an acceptance record, +not a risk grade. + +## 6. Declaration in `tenancy.yaml` + +For a single-service declaration, `zones:` is a sibling of `tenancy:` and +`workload_identity`. For a `services:` declaration, both `workload_identity` and +`zones` occur inside the same service entry. A multi-service file MUST NOT use a +top-level `zones:` block. + +Every managed running deployable has an authoritative +`rapp-*/declarations/rapp.yaml`. Its `workload_identity.declaration_ref` points +to that declaration, and consuming catalogs reference it using the Repo Manager +v1 tuple. A pre-rapp deployable is migration debt and resolves `unknown`. An +independently governed operational execution unit that is not a managed +deployable may declare directly in its responsible repo's `tenancy.yaml`; this +does not turn a human action or infrastructure resource into a fictional rapp or +workload. + +```yaml +schema_version: "0.1" +framework: netkingdom-tenancy-posture +service: ops-bridge-tunnel +role: operational-access-path +workload_identity: + name: ops-bridge-tunnel + kind: operational-control-plane + responsible_repo: ops-bridge + identity_bindings: + - scheme: ssh-certificate + authority: ops-warden + subject: agt-ops-bridge + principal_type: agent + environment: prod +tenancy: + # tenancy-posture_v0.1 content omitted +zones: + standard: security-zones_v0.1 + membership: z2-continuity + responsible_party: ops-bridge + justification: foundational tunnel path must retain availability under PDP loss + context: + maturity: M2 + criticality: high + data_classification: confidential + evidence: + - ref: docs/evidence/ops-bridge-tunnel-zone.md + supports: [M2, continuity-dependency, recovery] + reviewed: "2026-08-22" + review_due: "2026-11-22" +``` + +The `zones:` block contains only membership evidence. It MUST NOT contain +control stance, failure mode, organization posture, or exceptions. + +Required fields are: + +- `standard` — exactly `security-zones_v0.1`; +- `membership` — one zone id from §4; +- `responsible_party` — the party answering for this membership; +- `justification` — why the zone fits the workload's actual context; +- `context` — the evidenced `maturity`, `criticality`, and + `data_classification` used for admission. A managed workload's latter two + values must agree with its resolved rapp projection; `n/a` requires an + evidence-backed reason; +- `evidence` — one or more references and the admission facts each supports; +- `reviewed` and `review_due` — ISO dates, with review due after review. + +Permanent membership changes are reviewed source changes. A change to a lower +floor also records its reason and approver in the change review. Temporary +relaxation uses an exception and never changes `membership`. + +## 7. Compilation and resolved view + +Compilation produces a workload-addressable resolved record. At minimum it +contains: + +```yaml +workload_id: ops-bridge-tunnel +workload_ref: + applicability: applicable + rapp_id: null # required for a managed deployable + name: ops-bridge-tunnel + deployable: null # optional for a managed deployable +identity_binding: ssh-certificate/ops-warden/agt-ops-bridge +declared_zone: z2-continuity +admission: satisfied +effective_zone: z2-continuity +membership_revision: sha256: +guarantees: + - authoritative-workload-identity + - explicit-zone-membership + - non-inferred-resolution + - enforcement-time-exception-expiry +controls: + - id: flex-auth/pre-sign + policy_owner: flex-auth + stance: enforced + pep_owner: ops-warden + failure_mode: fail_open + policy_ref: +``` + +The `membership_revision` covers the authoritative workload binding, `zones:` +block, and source revision. Control results include their policy/profile version +and any active exception id and expiry. This is the machine-readable answer to +“which zone is this workload in, and what applies there?” It may be compiled +into existing consumer artifacts; it is not a synchronous zone-engine lookup. + +For managed deployables, compilation consumes the exact Repo Manager reference +projection: + +```yaml +workload_ref: + applicability: applicable + rapp_id: rapp-issue-core + name: issue-core + deployable: issue-core # optional +``` + +The owning catalog uses `applicability: not-applicable` for a native non-workload +subject. Absence of either applicability or an expected reference is `unknown`, +not `not-applicable`. Zone-engine consumes these outcomes; it does not parse a +path or repository name to repair them. + +For flex-auth's pre-sign control, the governed workload is the target of the +certificate or grant, so the compiler writes `workload_id`, `security_zone`, +`security_zone_admission`, and `security_zone_revision` on the resource +attributes. Caller identity remains in the subject. A control that governs the +requesting workload must declare that role explicitly and use separately named +caller-workload attributes. + +The dormant `trust_zone: platform` constant is not security-zone membership and +MUST be retired before adoption. The new concept is named `security_zone`; the +two fields must not coexist as competing zone sources. + +## 8. Membership-change observability + +A membership change becomes effective only through a reviewed declaration and +a newly compiled artifact. The compiler emits the source and membership +revision, rejects ambiguous identities, and reports additions, removals, and +changes against the preceding snapshot. Controls expose the membership revision +in their decision or verdict evidence. + +A zone that can be widened by editing an unversioned runtime label is not +conformant. + +## 9. Time-boxed exceptions + +The normative lifecycle is the ZONE-WP-0001-T04 decision in +`zone-engine/docs/exception-lifecycle-2026-08-22.md`: only the control owner's designated +authority grants a named-workload, named-zone, named-control relaxation within a +declared maximum duration. Enforcement applies it only for +`not_before <= now < not_after`; invalid or unevaluable records are inactive, +expiry restores the base rule automatically, and no minted credential, lease, +or session may outlive the exception. + +Exceptions live with the versioned control policy or PEP configuration and are +evaluated where their effects occur. This requires no zone-engine runtime. + +## 10. Adoption + +Net-kingdom published this standard at revision `337484a`. Adoption requires: + +1. flex-auth and ops-warden accept the initial control profile or publish a + versioned replacement with total zone and `unknown` coverage; +2. at least two workload owners declare authoritative identities and zones; +3. a third consumer compiles or reads the resolved view; and +4. ops-warden retires `policy.enabled` and the dormant `trust_zone` constant in + the same migration. + +All four gates were met on 2026-08-22. The owning zone-engine repository records +the exact consumer revisions, tests, resolved membership digests, and live +caller decision in +`docs/evidence/security-zone-adoption-2026-08-22.md`. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-tenancy-posture.md b/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-tenancy-posture.md new file mode 100644 index 0000000..c217e21 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/netkingdom-tenancy-posture.md @@ -0,0 +1,1447 @@ +--- +id: netkingdom-tenancy-posture +type: standard +title: "NetKingdom Tenancy Posture v0.1" +domain: netkingdom +status: proposed +version: "0.1" +created: "2026-08-17" +updated: "2026-08-23" +scope: multi-tenancy-security-framework +revision: "draft-14" +owner: net-kingdom +last_reviewed: "2026-08-23" +review_interval: 6m +declaration_schema: canon/schemas/tenancy-posture_v0.1.schema.json +adr: + - docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md + - docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md + - docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md +related: + - canon/standards/iam-profile_v0.3.md + - canon/standards/tenant-engine-boundary-contract_v0.1.md + - canon/standards/credential-management_v0.2.md + - docs/platform-identity-security-architecture.md +--- + +# NetKingdom Tenancy Posture v0.1 — Six Axes, Graduated Levels, Declared Conformance + +## Status + +**Proposed, draft-13; ratification-ready.** Relocated from +`the-custodian/canon/architecture` on +2026-08-17: multi-tenancy is part of the IT-security framework NetKingdom +provides, so this framework belongs in NetKingdom canon beside the IAM Profile +and the tenant-engine boundary contract, not in the work-factory canon. + +- **draft-1** proposed a single model with fixed characteristics. Rejected: it + could not describe a repo that is not there yet. +- **draft-2** reframed to graduated levels per axis. Externally corroborated + (§16), but four of its statements were wrong and one thing it needed was + missing. +- **draft-3** applied those corrections, added the retention axis, and + recorded an adoption stance. +- **draft-4** closed the two gaps draft-3 left open: `R4` had no mechanism + beyond waiting, and the noisy-neighbour evidence artifact asserted something + shared infrastructure cannot provide. +- **draft-5** relocated to NetKingdom and renamed the dimensions from *planes* + to *axes*, because the word was already taken (§0). +- **draft-6** applied `tenant-engine`'s review: five changes, including an + axis that did not fit its data shape. +- **draft-7** applies `audit-core`, `railiance-platform` and `flex-auth`. + Eleven further changes, two of them corrections to statements this document + made as fact about other repos. **Every posture I guessed was too generous, + on every repo that has now self-reported.** +- **draft-8** applies `adaptive-pricing`'s review, the last of the six, and the + consistency review across all declarations. It adds the missing availability + axis, a canonical declaration schema, explicit authority for tier assurance, + retention/placement coupling, downgrade propagation, and honest sanctioned + customer language. It also corrects the distinction between an implemented + control and an evidenced current level. + +- **draft-9** answers `zone-engine`'s `ZONE-WP-0001-T01`. It rules that + enforcement stance is **not** a seventh axis (Decision 5.6) while reserving + `zones:` in `tenancy.yaml` so the estate keeps one declaration surface, and it + records the reef/`P`/`V` reconciliation as an open defect of this document + rather than of the repo that noticed it (Decision 8.4). +- **draft-10** answers `zone-engine`'s `ZONE-WP-0001-T03`. It keeps the + workload as the sole security-zone policy subject, makes operational and + control-plane execution units part of that term, and requires unresolved + workload identity or membership to remain `unknown` without zone inference + (Decision 5.6.1). It also closes the textual reef/`P` boundary while leaving + the substrate-provider declaration and mechanical `V` join as implementation + work (Decision 8.4.2). +- **draft-11** makes that ruling declarable. A `zones:` block now requires an + authoritative `workload_identity` beside it, including for non-`rapp` + operational workloads; multi-service files carry both per service. Missing + identity remains absence rather than a guessed join (Decision 5.6.2). +- **draft-12** reconciles that declaration with RMGR-ADR-004 and the published + `security-zones_v0.1` proposal. Managed deployables use their authoritative + rapp declaration and the exact Repo Manager reference tuple; independently + governed operational execution units that are not managed deployables may + declare locally. Native actions, actors, lanes, patterns, and resources are + explicitly `not-applicable`, while omitted or unresolved workload references + remain `unknown` (Decision 5.6.2). +- **draft-13** advances the `audit-core` worked example from E1 to E2 after + bounded adversarial run `WH-ENG-20260822-AUDIT-E2-03` supplied the artifact + required by §13.2. The claim remains explicitly bounded and freshness-dated: + the attempted cross-tenant attacks did not work; this is not a universal + isolation proof. +- **draft-14** makes evidence freshness and remediation ownership declarable. + Adversarial evidence may now carry its observation and expiry timestamps, + bounded scope, responsible repository, and replacement action. A separate + proposal-only evaluator treats absent authoritative owner or freshness as + `unknown`; it does not infer either or mutate the declared posture. + +**Reviewed by all six. The score:** six repos found three live defects in their +own code by reading the ladders — `tenant-engine`'s unfiltered +event accessor, `audit-core`'s unfiltered read path, `flex-auth`'s +unauthenticated `/v1/check` — and `railiance-platform` found `apps-pg` running +with no backup configured at all while writing its §10.2 disclosure. The +framework changed to fit the repos; no repo was told to fabricate a posture. + +Informed by five external research digests plus their index in +`the-custodian/research/2026-08-17-adr008-*`, which carry full citations for +the external claims made here. + +## 0. Terminology: axes, not planes + +`docs/platform-identity-security-architecture.md` — accepted, 2026-07-23 — +already uses **plane** for a trust and deployment layer: the *bootstrap plane*, +the *platform control plane*, and *tenant planes*. That meaning is established, +ratified, and owned by this repo. + +Drafts 1–4 of this document, written elsewhere, used **plane** for something +different: an independent dimension of concern. Two incompatible senses of one +word inside one canon is exactly the concept-ownership collision the estate has +been careful about elsewhere, and the newcomer yields. + +This framework therefore describes six **axes**. They are orthogonal to +NetKingdom's planes, not a subdivision of them: + +- A **plane** is *where* something runs and what trust it carries — bootstrap, + platform control, tenant. +- An **axis** is *which property* of tenancy is being described — identity, + authorization, enforcement, placement, retention, availability. + +A workload in the tenant plane has a position on all six axes. A platform +control plane service does too. The two vocabularies compose and neither +replaces the other. + +The rename is also an improvement. A posture vector is literally a point in +six-dimensional space, and "axis" says that where "plane" did not. + +## 1. Context + +Drafts 1–4 opened by claiming the estate "has never written down what it is +building". Relocation proved that wrong, and the correction is worth keeping +visible: `docs/platform-identity-security-architecture.md` has described the +trust model, the tenant model and a capability progression since 2026-07-23. +The accurate claim is narrower — **what was missing is a way to say how far a +given service has got, and to hold several answers at once.** Seven documents +cover slices of the subject and none of them does that: + +| Document | Covers | Status | +|---|---|---| +| `iam-profile_v0.3` (NetKingdom) | Tenant identifier shape, `tenant_roles` claim, staleness rules | Ratified | +| `tenant-engine-boundary-contract_v0.1` (NetKingdom) | Who owns tenant records, roles, plan assignment | Ratified | +| `business-app-service-contract_v0.1` §1 (Custodian) | Business apps: instance-per-client, tenant-keyed data | Ratified | +| `rapp-postgres` ADR-0001 | Consumer + tenant isolation in PostgreSQL | Proposed, governs one repo | +| `rapp-postgres` ADR-0002 | Per-consumer retention and the erasure horizon | Proposed, governs one repo | +| `shared-platform-relational-storage_v0.1` | The stacked-boundary gap | Routed 2026-08-10, **still unratified** | +| `platform-identity-security-architecture` (NetKingdom) | Trust model, planes, tenant model, capability progression | Accepted 2026-07-23 | + +This document is downstream of that architecture and must not restate it. It +answers one question the architecture leaves open: given the model, **where is +this particular service today, and how would anyone know?** + +Four failures existed when drafting began. + +**The gap was diagnosed once and the fix stalled.** The v0.1 draft was written +to fill this hole and has sat unratified in neither canon directory. §20 +attaches a ratification path so this one does not join it. + +**Placement was owned by nobody.** `user-engine-pg` and `target-revenue-pg` are +dedicated; `apps-pg`, `net-kingdom-pg`, `platform-pg`, `state-hub-db` and +`forgejo-db` are shared. Both live, neither written down. `tenant-engine` +raised this with `railiance-platform` on 2026-08-16. Draft-8 resolves the +authority split in §8.2. + +**Two contradictory defaults were already ratified.** Business apps get +instance-per-client; platform services pool. Nothing says which shape a new +service takes, and no definition separates the categories. Decision 4.4.1 now +supplies the default; §19.4 retains the missing classification rule. + +**There is no honest way to describe a repo that is not there yet.** The estate +absorbs repos with weak or absent tenant separation. Today such a repo is +simply non-conformant, leaving it two bad options: misrepresent its posture, or +stay outside the framework. + +## 2. What this document is + +**A framework, not a model.** It specifies no single correct implementation. It +supplies terminology (§3, §4), a declaration (§5), a conformance rule (§6), +methodology (§12), and evidence definitions (§13). + +A service is conformant when its declared posture is accurate and its +trajectory recorded. A service is non-conformant when it claims a level it +cannot evidence — regardless of how high or low that level is. + +## 3. Six orthogonal axes + +"Is this multi-tenant?" is treated as one question. It is six, and they are +independent: + +| Axis | Question | Vocabulary owner | +|---|---|---| +| **Identity (I)** | How is a tenant named and validated? | `tenant-engine` / IAM Profile | +| **Authorization (A)** | How is a request bound to the tenants it may act for? | `flex-auth` | +| **Enforcement (E)** | Where, mechanically, is the tenant boundary enforced? | This framework | +| **Placement (P)** | Which substrate holds a tenant's data? | `railiance-platform` | +| **Retention (R)** | How long does data persist, and how is it erased? | The storage platform; policy by the consumer | +| **Availability (V)** | What failure can the complete service path survive, and within what recovery objective? | The delivering service; substrate facts by its providers | + +Conflation produces errors today. `rapp-postgres`'s `PostgresConsumer` carries +`tenantIsolation: consumer-service-boundary` — an **E**-axis fact in a +**P**-axis artifact, reading as though storage enforces something it does not. +The "dedicated versus shared" argument mixes P (capacity, blast radius) with E +(correctness). + +The axes are separated *precisely so each may sit at a different level*. + +**Decision 3.1:** every document, declaration and plan tier that says +"isolation" MUST name which axis it means. + +**Decision 3.2:** the axes couple at their tops and the couplings MUST be +stated where they apply, not used to argue the axes are one: + +- `E4` is reachable only at `P3` or above. +- `R`'s erasure horizon is bounded below by `P` — on shared substrate, a + consumer's horizon is the instance maximum (§4.5). +- `R4` by key destruction is bounded by the **key boundary**, which is an + E-axis property. Shredding a single tenant's data requires the application + to encrypt under a per-tenant key before writing; the storage platform cannot + supply it. **Reaching the top of the retention ladder is not a retention + project.** +- `V` composes as the **minimum across the critical request path**, not the + maximum of its components. A replicated application on a single-instance + database is not V2. A tested degraded mode may remove a dependency from that + path, but the bypass itself is part of the V evidence. + +**Decision 3.3 — scope.** The P and R ladders describe a service's **primary +datastore**. The V ladder describes the service's complete critical request +path, including providers it synchronously depends on. Caches, search indices, +message queues and background jobs are named leak surfaces in the external +baselines and are assessed separately, not silently covered by a datastore +level. A declaration names material secondary stores and asynchronous paths as +exceptions rather than implying that one vector proves them safe. + +## 4. Graduated levels + +Each axis carries an ordered ladder. Higher is stronger, not better: the right +level is the one a service can evidence and its risk warrants. + +### 4.1 Identity (I) + +| Level | State | +|---|---| +| **I0** | No tenant concept. Data not attributable to a tenant. | +| **I1** | A local tenant notion exists but is not canonical, **or** the tenant is taken from the request rather than from a verified token. | +| **I2** | Canonical identifiers, bound at the identity provider and carried as a verified claim, **and verified by this service on its own inbound calls**. | +| **I3** | I2 plus capability roles honoured, with live `tenant-engine` re-query for privileged, destructive, credential-vending or `aal2`-class decisions. | + +I1 now explicitly absorbs request-supplied tenant identifiers. "Never trust +client-supplied tenant IDs without validation" is a named anti-pattern; a +service reading the tenant from a header is at I1 however canonical the string. + +**An axis is assessed on a service's own inbound surface, never on its +authority over the concept.** `tenant-engine` is the source of existence for +tenant records and is nonetheless at I1, because it takes the acting identity +from the request body rather than from a verified token. Draft-5 conflated +these by naming the authority inside the I2 definition, which made the level +describing canonical identity unclaimable by the service that provides it. +Corrected on tenant-engine's review — a reader would otherwise assume the +authority must be at I2 by definition. + +`business-app-service-contract` §2.1 sets app-local accounts as the v1 baseline +for business apps — a sanctioned low level with recorded triggers for moving +up. That is the pattern this framework generalises. + +### 4.2 Authorization (A) + +| Level | State | +|---|---| +| **A0** | No authorization, or tenant context not carried. | +| **A1** | Ad-hoc checks scattered through handlers. | +| **A2** | A single local authorization boundary; tenant context bound once, centrally. | +| **A3** | Decisions delegated to `flex-auth` as PDP, with live re-query where the IAM Profile requires it. | +| **A4** | A3 over a **standard** PDP interface (OpenID AuthZEN Authorization API 1.0), so the decision point is swappable and the enforcement point is not coupled to one engine's request shape. | + +**This ladder describes enforcement points.** A decision point cannot occupy +A3 — "delegated to `flex-auth`" is not something `flex-auth` can do. A service +that *is* a PDP declares **two numbers**: its own inbound level, and the +maximum it enables for consumers. `flex-auth` reads `A0, enables A3` — accurate, +and considerably more alarming than `A3`, which is the point. Raised by +`flex-auth`, whose absence from the §5 worked examples was this surfacing +implicitly. + +A4 is new. The specification reached Final in January 2026 and Keycloak shipped +experimental support in May; the argument for it is **interoperability** — a +swappable decision point and an enforcement point not coupled to one engine's +request shape. + +*Correction from flex-auth's review:* earlier drafts also justified A4 as +ending the copying of action strings between repos. **It does not.** AuthZEN +standardises the envelope — subject, action, resource, context, endpoint — and +deliberately does not standardise the action vocabulary or the policy language. +At A4, `tenant.guardrail.set` still has to be agreed and still gets copied. +Those are two problems with different fixes, and the cheaper one is not A4: +`flex-auth`'s registry already carries action definitions per system and could +serve them read-only. The vocabulary argument is withdrawn. + +**Internal service-to-service calls are in scope for this axis.** "Skipping +tenant validation for internal services" is a named anti-pattern and our estate +is mostly internal calls. + +*Correction from flex-auth's review:* earlier drafts asserted that `flex-auth` +calls `tenant-engine` synchronously on the authorization path. **That is not +true.** The adapter is built and complete and has no non-test caller, so the +IAM Profile's live re-query exists and is unwired — which is also why +`flex-auth` cannot reach I3. Built-and-unwired is the worst of the three states +because it reads as capability. + +The requirement, narrowed on their proposal because the original was too strong +to be met and would have made `tenant-engine` a hard availability dependency of +every decision in the estate: + +> Tenant context MUST be carried on every internal hop and MUST NOT be +> re-derived from a service identity. It MUST be revalidated against +> `tenant-engine` at least once per request chain — at the service that holds +> or mutates the tenant's data, or before a privileged, destructive, +> credential-vending or `aal2`-class decision, whichever comes first. A hop +> that neither holds tenant data nor makes such a decision may carry the +> context without revalidating it. + +**And carrying tenant context is worthless without an authenticated hop to +carry it over.** `flex-auth` found this in itself: it carries tenant context +faithfully and cannot distinguish "user-engine asking on behalf of tenant X" +from "any pod asking on behalf of tenant X". + +### 4.3 Enforcement (E) + +| Level | Mechanism | +|---|---| +| **E0** | None. Data not tenant-keyed; separation incidental or absent. | +| **E1** | Data tenant-keyed, filtering applied per query at call sites. | +| **E2** | Filtering centralised at a single service-side choke point binding authenticated identity to permitted tenants. | +| **E3** | E2 **plus** platform-assisted filtering: row-level security keyed on a tenant GUC set transaction-locally, or an equivalent enforced data-access layer. | +| **E4** | Structural: the credential a workload holds cannot address another tenant's data at all. Requires per-tenant credentials and per-tenant substrate. | + +**Correction from draft-2.** Draft-2 described E3 as something "the application +cannot trivially route around". That is false and it was this document +overclaiming in exactly the way §6 prohibits. Any session can re-issue `SET` on +a custom GUC, so an attacker with SQL execution can reset the tenant and read +across the boundary. What E3 buys is precise, and the ladder must say so: + +| Threat | E1 | E2 | E3 | E4 | +|---|:--:|:--:|:--:|:--:| +| A developer forgets a tenant predicate | ✗ | ✓ | ✓ | ✓ | +| A new code path bypasses the choke point | ✗ | ✗ | ✓ | ✓ | +| SQL injection reaching the connection | ✗ | ✗ | ✗ | ✓ | +| The application process is compromised | ✗ | ✗ | ✗ | ✓ | + +E3 is a strong control against **accident** — the common case, and the one that +causes real breaches — and no control at all against **compromise**. Only E4 +holds against both, because the credential itself cannot address another +tenant's data. + +**Correction: E3 layers on E2, it does not replace it.** External practice +treats application-layer and database-layer filtering as complementary. A +service that dropped its choke point on reaching E3 would be *worse* off, since +E3 fails open under injection. Claiming E3 therefore requires the E2 evidence +artifact as well. + +**Correction: the GUC is set transaction-locally.** Draft-2 said "at pool +checkout", which is session scope and the wrong instrument. Under a pooler in +statement mode, `SET` leaks between clients and returns other tenants' rows — +a failure that appears only under production concurrency and produces no error. +Use `SET LOCAL` inside an explicit transaction. + +**Platform enforcement is a platform obligation.** Reaching E3 requires the +storage platform to *offer* the mechanism: provisioned policies, a documented +GUC contract, and a probe. Where a consumer wants E3 and the platform has not +supplied it, the gap is the platform's. §19.6 asks `rapp-postgres` to define +that contract, which must carry `FORCE ROW LEVEL SECURITY` on every tenant +table (without it the table owner bypasses policies silently, and ADR-0001 +already established that our migration role owns the tables it creates), no +`BYPASSRLS` on leased roles, `SECURITY INVOKER` for ordinary logic, and an +`EXPLAIN` comparison because RLS disables functional indexes built on +non-leakproof functions. + +**Not all data is tenant-keyed, and the ladder must not pretend otherwise.** A +registry whose rows *are* the tenants has no per-tenant predicate to scope a +policy by; enforcing one would break the service's function rather than secure +it. `tenant-engine`'s `tenants` table is the worked example — `key-cape` +enumerates it at token issuance and `flex-auth` queries it live, both of which +are cross-tenant reads by design. + +A service with mixed data shapes declares **E-level plus a registry +exception**: the level its tenant-keyed tables hold, and a named list of tables +excluded because they are registries rather than tenant data. The exception is +part of the claim and is reviewable; an unnamed exception is an overclaim. +Without this, mixed-shape services either overclaim or stay at E2 permanently, +and `tenant-engine` declined to claim E3 on precisely that reasoning. + +**Default expectation** for a new platform service: E2 at first serve, E3 +recorded as target. Services whose cross-tenant exposure would be a reportable +breach SHOULD target E3 or above. + +### 4.4 Placement (P) + +| Level | Shape | Live occupants | +|---|---|---| +| **P0** | Shares a database with another consumer. | None sanctioned; the state absorbed repos arrive in. | +| **P1** | Database per consumer, shared cluster. | `audit-core` on `platform-pg`; `tenant-engine` (target, TEN-WP-0009 — still on SQLite) | +| **P2** | Dedicated cluster per consumer. | `user-engine-pg`, `target-revenue-pg` | +| **P3** | Dedicated cluster per tenant. | Business apps per `business-app-service-contract` §1.2 | +| **P4** | P3 plus separate region or jurisdiction. | None | + +Enforcement and placement are independent axes. Plotted together, with where +each service actually sits — parenthesised entries are targets or defaults +rather than current positions, and `—` marks a cell the coupling in §3.2 makes +unreachable: + +| E \ P | P0 | P1 | P2 | P3 | P4 | +|---|---|---|---|---|---| +| **E4** | — | — | — | (business app) | | +| **E3** | | (target) | | | | +| **E2** | (tenant-engine) | audit-core | | | | +| **E1** | (absorbed repo) | | | | | +| **E0** | | | | | | + +**P0 → P1 → P2 is movement along the horizontal axis only.** Those steps buy +consumer isolation, capacity predictability, independent retention and a +smaller operational blast radius. They do not raise the tenant boundary by one +step. Only P3 makes E4 reachable. This is the most misusable fact in the +framework and §11 governs how it may be described. + +**Decision 4.4.1:** P1 is the default for platform services; P3 for +client-facing business apps, as already ratified. A service unsure which it is +must resolve that first (§19.4). + +**Decision 4.4.2 — placement scopes to data substrate.** Identity-provider +placement (realm-per-tenant versus Organizations) is the same silo/pool +decision on a different substrate, is live in our estate, and is undecided. +Realm-per-tenant carries a stated ceiling around 5–20 tenants, far below our +target. Recorded here as a parallel question (§19.7), not folded into P. + +### 4.5 Retention and erasure (R) + +New in draft-3. Implemented abstractly by the storage platform for any dataset; +policy is built on top of that interface by the consumer or its governance +layer. Reference implementation: `rapp-postgres` ADR-0002. + +| Level | State | +|---|---| +| **R0** | No retention or deletion position. Data kept indefinitely by default; no deletion path exists. | +| **R1** | Platform default retention applies (N=30 days). The consumer has declared no requirement. | +| **R2** | Retention declared as N days per dataset; the **erasure horizon** is published, and the consumer makes no promise shorter than it. | +| **R3** | Policy-driven deletion: the consumer or its governance layer declares what is due, the platform sweeps whole datasets on that instruction and evidences each run. | +| **R4** | Verified erasure: data proven unrecoverable across live storage, backups and derived copies, by one of the two routes below. | + +**R4 has two routes and a service MUST name which one it uses.** + +| Route | Mechanism | Cost | +|---|---|---| +| **Horizon-elapsed** | Wait out the published erasure horizon; the data ages out of every retained copy. | Available to everyone, proves little, and the wait is set by a co-resident's retention requirement rather than your own. | +| **Key-destroyed** | Encrypt per entity, then destroy the key. Retained copies survive but are unreadable. | Requires per-entity keys, strong encryption, and an auditable destruction record. Immediate. | + +**Decision 4.5.3 — key destruction is not sufficient on its own.** The +key-destroyed route requires that **no retained commitment reveals the erased +content**. Found by `audit-core`, and it is a general defect rather than a fact +about them: + +- A SHA-256 over a canonical record whose fields are low-entropy — event type, + actor, tenant, subject, timestamp — is a **confirmation oracle**. Anyone + holding the hash can guess the payload, hash the guess, and confirm a match. + Destroying the key does not make the content unrecoverable while that hash + survives. +- Shreddability is **not retrofittable** onto an integrity chain that commits + to cleartext. It has to be built as encrypt-then-hash at accept time, with the + chain committing to ciphertext. Retrofitting means rewriting the chain — the + exact thing a tamper-evident log exists to make detectable. + +So a service claiming R4 by key destruction must show that its retained +commitments — hashes, chains, indexes, search keys — do not reveal what was +erased. The remedies are an HMAC under a per-subject key that dies with the +key, or a per-record salt destroyed alongside it. **`audit-core` cannot reach +R4 under its current design and targets R2; a fleet R4 target must exempt it +explicitly.** + +**Regulatory standing of the key-destroyed route, stated carefully because +overclaiming here is worse than anywhere else in this framework.** Data +protection authorities have accepted key destruction as erasure where physical +deletion would be manifestly disproportionate, and the practice is recognised +under conditions — strong encryption, irreversible destruction, and an auditable +record of it. **The EDPB has not formally endorsed it as Article 17 erasure.** A +service reaching R4 by key destruction is making a defensible claim, not a +settled one, and must say so rather than reporting a clean "deleted". + +Three further properties. + +**The erasure horizon is the interval between deleting data and it ceasing to +be recoverable from anything the platform holds.** Deleting a row does not +remove it from yesterday's backup. With an N-day window, deleted data remains +recoverable for N days. That is the difference between "deleted" and "erased" +and the estate had never written it down. + +**On shared substrate, retention is not per-consumer.** Physical backup is +instance-wide — one WAL stream, one window — so the instance retention is +*derived* as the maximum across co-resident consumers, and every consumer's +horizon is that maximum. A consumer declaring 7 days beside one declaring 90 +gets 90. This is the retention analogue of ADR-0001's blast-radius disclosure: +state the coupling rather than imply an isolation that is not there. + +**Retention is therefore a placement trigger.** A consumer needing a horizon +shorter than the instance floor cannot have one at P1. It moves to P2 for a +reason with nothing to do with performance — which is exactly why it needs +recording, since nobody looks for a retention argument when reviewing +placement. + +**Decision 4.5.4 — a retention promise binds both R and P.** A tier making a +retention claim records an R minimum and a maximum erasure horizon in days. It +also requires P2 or above **unless** its provider contract guarantees that the +shared-substrate horizon stays within that maximum and rejects or notifies +before a co-resident change would extend it. A bare `R2` minimum is +insufficient: at P1 another consumer can change the promise without changing +the tier or its holder. + +Deletion splits mechanism from policy. The platform deletes whole **datasets** +on instruction and records an opaque policy reference it never interprets, so +every deletion traces to what authorised it. Rows are not a dataset: row expiry +is the consumer's own DML under its migration lease. Dropping a consumer's +whole database is an operator-gated offboarding step, never a scheduled one. + +### 4.6 Availability (V) + +New in draft-8. `adaptive-pricing` found that §11 required availability claims +to map to a minimum level while the framework supplied no availability +vocabulary. Placement is not a substitute: a dedicated cluster can still be a +single instance on a single node. + +| Level | State | +|---|---| +| **V0** | No availability or recovery position. Recovery is untested or depends on improvisation. | +| **V1** | Restart or recreate recovery in one failure domain is documented and exercised. Interruption is expected; this is recovery, not failover. | +| **V2** | Redundant instances provide automated service failover, with measured RTO/RPO; a shared failure domain or critical dependency may remain. | +| **V3** | The complete critical path survives loss of one declared failure domain, with measured RTO/RPO from an exercise. | +| **V4** | The complete critical path survives regional loss through tested multi-region failover, with measured RTO/RPO. | + +**Decision 4.6.1 — V is end-to-end.** A service declares the minimum across +the components and synchronous providers required to serve the operation. An +application with three replicas over a V1 database is V1. A status page or +replica count is not evidence of a higher level. + +**Decision 4.6.2 — availability claims name the operation.** A read-only +degraded mode and a mutation path may have different V levels. Decision 5.2 +applies: declare the paths and quote the minimum unless the customer-facing +claim explicitly and unambiguously names the narrower operation. + +## 5. The posture vector + +A service states one level per axis, plus a target, review dates, evidence and +any exceptions. `current` is the highest **evidenced** level; a control present +in code but still awaiting the evidence required by §13 goes in `implemented`, +not in `current`: + +```yaml +schema_version: "0.1" +framework: netkingdom-tenancy-posture +service: example-service +role: tenant-data-service +tenancy: + current: { I: 2, A: 3, E: 2, P: 1, R: 1, V: 1 } + implemented: { E: 3 } + target: { I: 2, A: 3, E: 3, P: 1, R: 2, V: 2 } + reviewed: "2026-08-17" + review_due: "2027-02-17" + service_class: interactive + gap: + E: "RLS is implemented; the §13 E3 probe is still absent." + R: "Retention declared; erasure horizon not yet published to consumers." + V: "Automated failover is not implemented or exercised." +evidence: + A3: "docs/evidence/authorization-denial.md" + E2: "docs/evidence/cross-tenant-review.md" + P1: "rapp-postgres/docs/evidence/isolation-2026-08-10.md" +``` + +**Placement exceptions.** Draft-2 assigned one P level per service, which +cannot express the vertically partitioned model — most tenants pooled, some +dedicated — that §11's isolation tiers require. A tier requiring `P2` bought by +three tenants would put the service at two levels at once, forcing an over- or +under-claim. Placement is therefore declared as a default plus exceptions: + +```yaml + placement_exceptions: + - tenants: ["tenant:enterprise:*"] + P: 3 + reason: "isolation tier; see adaptive-pricing tier definition" +``` + +A service with exceptions must be able to say which tenants are on which +substrate. That mapping is a first-class artifact, not archaeology. + +**Decision 5.5 — a provider declares what it makes reachable, not where it +sits.** The six ladders describe a *consumer* of infrastructure. They describe +a *provider* of it badly, and `railiance-platform`'s review demonstrated how +badly: `apps-pg` is `I0 A0 E0` because a database has no tenant concept, +carries no tenant claim and applies no tenant predicate. Those zeros are +**structural, not weak** — the cluster is exactly as strong as its consumers +make it. + +The sharp case is OpenBao at `E0`. Literally correct, and actively misleading: +the mechanism in place is credential-scoped structural separation — `E4` +machinery — pointed at a *consumer* boundary rather than a tenant one. A reader +scanning a column of E values would rank it below a service doing per-query +filtering in application code, inverting the real security position. + +So a platform service additionally declares, per axis, **the level available +now, the maximum it can make reachable, and what a consumer must do to reach +it**. For `apps-pg`: E4 unreachable (shared credential per consumer, no +per-tenant credential), E3 conditional on the GUC contract, R2 blocked on a +backup target, V1 at most on the single-node rail. That is the sentence a +consumer actually needs, and no arrangement of the consumer ladders produces +it. + +A provider's own `P` is `n/a`, not a number. `apps-pg` *provides* `P1`; it is +not *at* `P1`, and writing `P: 1` there would later read as an isolation claim. + +**Decision 5.6 — enforcement stance is not a seventh axis, and `zones:` is +reserved in this file.** `zone-engine` asked whether *enforcement stance* — +whether a given control is enforced, advisory or exempt in a given band of the +estate — should fold in here rather than become a second standard. It should +not, for a reason that is structural rather than territorial. + +**Every one of the six ladders is monotone: higher is stronger, and higher is +what a service wants.** That assumption is load-bearing throughout. §12's +*improve* step moves a service up. §12's *guard* checks that none is below what +it declared. §6 has to make a special allowance for a level that is +*permanently* low by design, and §12's guard is told not to nag it — the +allowance exists because low-is-normal is the exception here. + +Enforcement stance is not monotone. The correct stance for a bootstrap lane is +deliberately and permanently *below* the top rung, and the top rung is +sometimes the wrong answer outright: `ops-warden`'s `ADR-0006` is exactly the +finding that a fail-closed authorization gate on the SSH lane the tunnels +depend on is not a stronger position, it is an outage. A ladder whose top is +sometimes wrong is an enumeration, not a ladder, and putting one inside this +vector would break `current`/`target`/`gap`, the guard, and §6 for the six that +are. + +§8.3 already refused an axis for a weaker reason than this one — that a QoS +level would be an unenforced claim. Enforcement stance *is* enforced. It fails +the other half of the same test. + +There is a second, sharper reason. §6's conformance rule is *accuracy, not +altitude*, and it works because this framework is **descriptive**: it never +blocks anything by itself. Prescription enters only through §11 and Decision +8.2, where a **requirer** — never the declaring repo — sets a minimum level and +the two are machine-reconciled. Enforcement stance is prescriptive by nature. +Fold it in as an axis and an accurately declared `exempt` becomes conformant +*and* exempt: a conformance rule that hands out the exemption it exists to +audit. The declarer must not be the party that sets the stance. + +So the split is the one `flex-auth` already argued to `zone-engine`: +**membership is data and is declared; stance is a rule and belongs to the +control's owner.** Membership is posture-shaped and behaves like a level. +Stance behaves like a tier minimum under Decision 8.2 — asserted elsewhere, +joined by machine. + +**What canon rules, and it is binding on the sibling standard:** + +- A security-zone standard is a **separate document** in this family, drafted + by `zone-engine` and published in NetKingdom canon beside this one and the + `*-engine` boundary contracts. It carries over §6 verbatim and §13's evidence + discipline. +- **Zone membership is declared in `tenancy.yaml`**, under a reserved top-level + `zones:` key, sibling to `tenancy:` and `provider:` — *not* inside + `tenancy.current`. Decision 5.4 makes this file the repo's single posture + declaration surface, and a second root file would recreate the divergence + §5.4 was written to end. One file, one review cadence, one validator; two + standards, because the two have different owners and different conformance + semantics. +- `organization_posture` (`ops-warden` WP-0029) does **not** belong in this + file at all, under either key. It is a fleet-wide, time-varying scalar + describing the estate, not a property of the declaring service, and a + per-repo copy of a global would go stale in as many places as there are + repos. It is an *input* to stance selection and should be read by the zone + model, not absorbed into a declaration. + +**Decision 5.6.1 — the workload remains the sole zone policy subject, and +absence resolves to `unknown`.** Here **workload** means an independently +governed execution unit that performs application, automation, or operational +control-plane work and can be attributed at enforcement time to both a +responsible party and an authoritative workload identity. The independently +governed executing unit behind operator-driven or automated SSH access, tunnel +operation, credential brokering, policy compilation or enforcement, or +maintenance is a workload when such a unit exists. The observed access, tunnel, +grant, lane, pattern, or action is not itself a workload. A human or agent +identity remains caller context; it does not replace the workload whose +execution is being governed. + +A repository, credential lane, grant template, pattern, or software package is +not an alternative **zone** policy subject. A broker runtime is a workload; the +grants and lanes it handles retain native resource identity and may explicitly +be `not-applicable` to workload resolution. Every managed running deployable, +including operational and tooling runtimes, has one authoritative +`rapp-*/declarations/rapp.yaml` under RMGR-ADR-004. A pre-rapp managed runtime +is migration debt and resolves `unknown`. An independently governed operational +execution unit that is not a managed deployable may declare directly in its +responsible repo's `tenancy.yaml`. The declaration surface broadens to cover +real workloads; the subject model does not broaden merely to totalize an +incomplete registry. + +When a workload-applicable subject lacks an authoritative workload identity, +when its reference is absent or ambiguous, or when that identity has no +authoritative zone membership, the resolved membership MUST be `unknown`. +It MUST NOT be inferred from a repository owner, credential path, lane type, +actor class, environment, criticality, reef, organization posture, or a default +zone. A control owner MAY define an explicit, reviewable fail-safe treatment for +`unknown` — including denial, escalation, or a build-stage rule — but that +treatment is stance, not membership. `unknown` never silently inherits a +permissive zone or exception. + +**Decision 5.6.2 — zone membership requires an authoritative workload binding +in the same declaration entry.** A bare service name is a label, not identity +evidence. Any single-service declaration carrying `zones:` MUST also carry +`workload_identity`; in a layer repo using `services:`, both fields live on the +same service entry. Top-level `zones:` is not valid for a multi-service file, +because it would make membership ambiguous. + +The binding states: + +- `name` — the stable workload id, exactly equal to the declaration's + `service`; +- `kind` — application, platform service, automation, operational control + plane, or maintenance job; +- `responsible_repo` — the repository accountable for the identity and zone + declaration; +- one or more `identity_bindings`, each naming the identity scheme, + authoritative issuer or registry, exact subject, IAM Profile principal type, + and optional environment and evidence; and +- for a managed deployable, a `declaration_ref` to its authoritative rapp + declaration. Consuming catalogs reference it with the exact Repo Manager v1 + tuple `(rapp_id, workload_identity.name)` and optional `deployable`. + +An identity binding uses `principal_type: service` or `agent`. A human identity +may still be required as actor or delegation context, but cannot be the sole +workload identity. A managed application, operational runtime, or tooling +runtime references its rapp declaration. Only an independently governed +operational execution unit that is not a managed deployable declares directly +in its responsible repo's `tenancy.yaml`; native actions and resources do not +acquire a fictional workload or rapp merely to enter policy. + +The stable workload id is the join key. Credential lanes, grants, controls, and +compiled policy resources reference that id explicitly; compilers MUST NOT +recover it by parsing a credential path or repository name. Multiple runtime +principals may bind to one workload when environments or mechanisms differ, but +the bindings must be unique and remain owner-reviewed. If no authoritative +binding matches the runtime principal, Decision 5.6.1 returns `unknown`. + +```yaml +service: ops-bridge-tunnel +role: operational-access-path +workload_identity: + name: ops-bridge-tunnel + kind: operational-control-plane + responsible_repo: ops-bridge + identity_bindings: + - scheme: ssh-certificate + authority: ops-warden + subject: agt-ops-bridge + principal_type: agent + environment: prod +zones: + standard: security-zones_v0.1 + membership: z2-continuity + responsible_party: ops-bridge + justification: foundational tunnel path must retain availability under PDP loss + context: + maturity: M2 + criticality: high + data_classification: confidential + evidence: + - ref: docs/evidence/ops-bridge-tunnel-zone.md + supports: [M2, continuity-dependency, recovery] + reviewed: "2026-08-22" + review_due: "2026-11-22" +``` + +Worked examples after applying the evidence rule and minimum-across-paths rule +consistently: + +| Service | Current | Notes | +|---|---|---| +| `tenant-engine` | `I1 A0 E1 P n/a R0 V0` | Acting identity is caller-supplied; unauthorised read paths set the A minimum; E2-shaped child-table controls are not evidenced; SQLite is outside P; no erasure or availability evidence. This corrects draft-7, which quoted A2/E2 despite its own minimum/evidence rules. | +| `audit-core` | `I1 A2 E2 P1 R2 V0` | Bounded adversarial run `WH-ENG-20260822-AUDIT-E2-03` passed all three calibrated cross-tenant probes over ten operations, so E2 is evidenced as of 2026-08-22. This establishes only that the attempted attacks did not work. The 24-hour facility baseline requires review or replacement by `2026-08-23T22:10:25Z`. Its 30-day retention and erasure horizon remain declared and published. | +| `flex-auth` | `I1 A0 E1 P n/a R n/a V0` | Enables A3 for consumers. `/v1/check` authenticates no caller; E2 is implemented but not evidenced. | +| `platform-pg` (provider) | `I0 A0 E0 P n/a R2 V1` | Provides P1; backup/restore and single-node recovery are evidenced. Provides no tenant boundary by itself. | +| `apps-pg` (provider) | `I0 A0 E0 P n/a R0 V0` | Zeros are structural, except R0/V0 are live gaps: no backup and no recovery evidence. | +| `adaptive-pricing` observatory | `I0 A0 E0 P n/a R n/a V0` | Local, unauthenticated, single-user analysis surface; not a production service. | +| A newly absorbed repo | `I1 A1 E1 P0 R0 V0` | Conformant **if declared**, with a recorded path. | + +**Decision 5.1:** the posture vector is declared in the repo, not in the hub, +consistent with local-files-are-source-of-truth. + +**Decision 5.2 — declare per path, quote the minimum.** A service whose +mutations are authorized and whose reads are not is at the reads' level. The +quoted number is the minimum across paths; the per-path detail is declared +beside it. + +Draft-6 required only the minimum, on `tenant-engine`'s review. `audit-core` +then showed why that is insufficient on its own: a bare minimum destroys +signal, because `E3`-write/`E1`-read declares identically to `E1`/`E1`. Bare +per-path invites "our write path is E3", which is the sentence §6 exists to +stop. Both, related explicitly, is the rule. + +Two services found this shape in themselves within a day of each other — +`tenant-engine` (writes authorized, three read routes not) and `audit-core` +(write path tenant-filtered, read path not filtered at all). Most services +enforce harder on write than read, so this is the common case, not the corner. + +**Decision 5.3 — `n/a` is a level, and it is conformant.** `P0` presupposes a +shared database and `R0` presupposes retained data. A service holding nothing +at rest — `flex-auth` runs with its registry and policy baked read-only into the +image and no decision log persisted — is neither. A datastore outside a +ladder's substrate vocabulary, such as `tenant-engine`'s current SQLite PVC, +also uses `n/a` rather than inventing a level. Without an admissible `n/a`, a +missing rung **forces** the fabrication §6 prohibits, which is precisely what +draft-1 was rejected for. `n/a` is declared with a stated reason. + +**Decision 5.4 — the vector lives at `tenancy.yaml` in the repo root.** +Draft-6 said "in the repo" and not where or in what shape, which left §12's +guard needing per-repo archaeology. `flex-auth` adopted `tenancy.yaml` +speculatively; adopted here as the convention. A repo representing one service +uses the single-service form above. A layer repo uses the schema's `services` +list in the same root file — one vector per service, never an average. The +normative schema is +`canon/schemas/tenancy-posture_v0.1.schema.json`; prose documents may explain a +declaration but do not replace it. The schema carries `current`, `implemented`, +`target`, `reviewed`, `review_due`, `gap`, `placement_exceptions`, +`service_class` (§8.3), per-path detail (§5.2), and provider reachability +(§5.5), plus the workload identity prerequisite for zone membership (§5.6.2). +It also permits `responsible_repo` for authoritative posture routing and +`evidence_freshness` for machine-readable evidence observation, expiry, scope, +owner, and remediation metadata. Their absence remains valid declaration +syntax; feedback resolution must report `unknown`, never infer them from a +directory, service name, or previous owner. +From the `net-kingdom` repo, owners validate one or more declarations +with `uv run tools/tenancy-posture/validate.py ...`; the validator applies +the JSON Schema and the evidence, date, implemented/current and provider-range +rules that JSON Schema alone cannot express. + +## 6. Conformance is accuracy, not altitude + +> **A service is conformant when its declared posture is accurate, its target +> is recorded, and it does not claim a level it cannot evidence. It is +> non-conformant when it overclaims — at any altitude.** + +- Declaring `E0` is conformant. Concealing `E0` is not. +- A repo may be absorbed at any posture. It may not be absorbed silently. +- No service is blocked from the estate for being low on a ladder. Services MAY + be blocked from *specific work* — serving a tenant grouping, holding a data + class, carrying a plan tier — by requirements expressed as minimum levels. +- Downgrading is permitted and must be declared. A regression found by guarding + is a defect; a regression declared in advance is a decision. +- **A low level may be permanent by design, and the declaration must be able to + say so.** `flex-auth` is `I1` and always will be: a decision point evaluates + the claims it is handed, and verifying its own inputs would make it the + identity provider its scope refuses to be. A `target` equal to `current` with + a reason is a settled position, not a stalled trajectory, and §12's guard + must not nag it as though it were one. + +**Decision 6.1 — downgrades propagate.** Before a planned downgrade of a +current level or a provider's available level, the declaring repo MUST resolve +the tier definitions and consumers that reference it. A downgrade below a +recorded minimum blocks the change until the claim is changed, the workload is +moved, or the affected owner explicitly accepts the gap. An unplanned +regression is an incident and triggers the same notifications. Updating +`tenancy.yaml` without notifying dependants is declaration drift, not a +completed downgrade. + +Without the axis separation, "not rigorous about tenant separation" is one +verdict a repo passes or fails. With it, the same repo is +`I1 A1 E1 P0 R0 V0` with a path — a plan, not an indictment. + +## 7. Portability across placement levels + +Movement between P levels must be operational, not a rebuild: + +- Connect by injected credential only — no cluster, host, namespace or database + name in source. +- Own a whole database, never tables inside someone else's. +- Idempotent schema creation. +- No cross-database joins or co-location assumptions. + +**Decision 7.1:** mandatory at P1 and above. At P3, SHOULD rather than MUST — a +per-client instance that never moves is not misconformant for naming its own +database. + +## 8. Placement triggers + +Recorded at provisioning time: noisy neighbour on a latency-critical path; a +compliance or residency requirement; a plan tier requiring a higher minimum; an +erasure horizon that no longer fits (§4.5); connection or memory ceiling +reached. + +**Decision 8.1:** triggers MUST be *monitored*, not merely recorded. A trigger +in a YAML comment nobody re-reads is documentation, not control. + +**Decision 8.2 — split authority, machine-reconciled.** `railiance-platform` +owns the placement rule; the package repo owns the substrate numbers and +enforcement; the consuming repo owns its workload requirements; +`adaptive-pricing` owns any tier minimum. `adaptive-pricing` declined a standing +co-signature and the framework accepts the replacement: typed tier minima are +joined to consumer and provider declarations at tier definition and whenever +one changes. A machine-checkable constraint must not depend on somebody +remembering to collect a signature. + +**Decision 8.2.1 — trigger monitoring has an owner.** The provider monitors +capacity ceilings and co-residency; the consumer monitors latency, compliance +and erasure requirements; `adaptive-pricing` monitors tier-definition changes. +The placement owner reconciles those signals. A trigger marked `unmonitored` is +an explicit gap and cannot support a customer assurance claim. + +### 8.3 Service class — a placement input, never a priority + +A latency-critical consumer and a batch consumer can share an instance today +with nothing distinguishing them. `tenant-engine` sits on `flex-auth`'s +synchronous authorization path and chose a 5s statement timeout for that +reason; `audit-core`, co-resident, is not latency-critical. Nothing prioritises +between them. + +**The framework does not add a QoS axis, because the platform cannot enforce +one.** Community PostgreSQL has no resource governor: no per-role CPU or I/O +priority, no resource queues, no workload classes. Those exist in EDB's +enterprise variant, in Greenplum, and in SQL Server — not in what we run. A +declared priority level would therefore be an unenforced claim sitting in a +declaration, which is precisely what retiring `tenantIsolation` was about. An +axis implies graduation and enforcement; this has neither. + +**Decision 8.3.1 — co-residents are equal.** On shared substrate no consumer's +query yields to another's. A consumer whose latency requirement cannot survive +an unprioritised neighbour must escalate to `P2`. That is the honest mechanism +and it is the only one we have. + +**Decision 8.3.2 — service class is declared anyway**, as a category rather +than a level: `latency-critical`, `interactive`, or `batch`. It buys three +things, none of which is priority: + +- **A placement input.** Mixing `latency-critical` with `batch` on one instance + is a recognised mismatch. It may still be the right call — it is right today + — but it should be a decision, not an accident of who was provisioned when. +- **A trigger.** A `latency-critical` consumer acquiring a `batch` co-resident + is a recorded placement trigger under §8, on the same footing as noisy + neighbour. +- **An acceptance criterion for evidence.** The noisy-neighbour artifact in §13 + asks whether measured degradation is *acceptable*; without a declared class + that word has no referent. Degradation tolerable for `batch` may be an + outage for `latency-critical`. + +**Decision 8.3.3 — class mixture must be visible.** The platform reports which +classes are co-resident. An unenforceable risk that nobody can see is strictly +worse than one that is stated. + +### 8.4 Substrate location is not evidence — and reefs are not reconciled with `P` or `V` + +**Decision 8.4.1 — location is not evidence of any property of the workload on +it.** Three repos have now written this rule locally in three vocabularies: +`railiance-master`'s *topology is not readiness*, `zone-engine`'s *placement is +not posture*, and §3.1 here, which requires every use of "isolation" to name +its axis. They are one rule. Stated once: **the substrate a workload sits on is +never, by itself, evidence for a level on any ladder in this framework.** +Binding to a reef, being on a dedicated instance, or naming a rail proves +placement and nothing else. Other repos should cite this rather than restate +it. + +**Decision 8.4.2 — the reef taxonomy and this framework are not reconciled, and +that is this document's defect.** `zone-engine` asked whether canon should +reconcile reefs with the posture axes, on the assumption that this was a scope +question for the zone model. It is not: the unreconciled pair is not +zone ↔ reef, it is **reef ↔ `P` and `V`**, and it belongs to canon. + +`repo-manager` owns substrate placement (`reef-railiance`, `reef-storage`) with +an explicit residual-risk acceptance attached to a binding. §7 and §8 of this +document presuppose that placement is fully described by the `P` ladder. It is +not. `P` grades **tenant data isolation within a datastore**; a reef is a named +**compute substrate carrying an accepted residual risk**. The `P` ladder has no +rung meaning "single node, shared control plane, risk accepted", and inventing +one would be the fabrication §6 prohibits. + +A reef binding therefore satisfies none of the `P` couplings in Decision 3.2 +by itself. It records the compute substrate and its accepted residual risk; it +does not establish a database-per-consumer or database-per-tenant boundary, a +per-tenant credential or encryption boundary, or an erasure horizon. Those +remain properties of the workload and data provider declarations. The binding +does participate in `V`, because availability composes across the complete +critical path and the substrate can impose a ceiling. + +The live consequence is on `V`, not `P`. §4.6 already warns that "a dedicated +cluster can still be a single instance on a single node", and Decision 4.6.1 +makes `V` the minimum across the synchronous path. `reef-railiance` is +single-node with a shared control plane, which **caps `V` for every workload +bound to it** regardless of that workload's own replica count — which Decision +4.6.1 already says is not evidence. Nothing today joins the reef's facts to a +consumer's `V` declaration, so a rapp can declare `V2` accurately by its own +reading and be wrong by this document's own composition rule. + +This is `railiance-platform`'s provider-declaration finding (Decision 5.5) +generalised one layer down. A reef is a **provider** and has nowhere to state +what it makes reachable. Tracked as `NK-WP-0027`; the fix is canon's, and the +zone model is not blocked on it. + +The known escalation short of P2 is gateway-level prioritisation — ordering +submissions in a connection proxy by the requesting tenant's current +consumption. It is real, it is where the industry puts this when it must, and +it is new infrastructure we do not run. Recorded as the option, not adopted. + +## 9. Credentials as a tenancy control + +Short-lived leased credentials re-read at connection checkout, with +overlap-first rotation, bound the residual risk at every E level below E4: a +leaked credential expires rather than persisting. Stronger than the industry +norm of a long-lived per-service secret. + +**Decision 9.1:** static long-lived database credentials are not a sanctioned +path for any service above E0. + +**Decision 9.2 — the rule extends to consumer-facing credentials.** Draft-6 +named database access only. `audit-core` pointed out that its *ingest* +credentials are static long-lived bearer tokens, rotated by publishing a second +alongside the first — and that the argument applies with **more** force to the +credential that actually carries the tenant claim than to the one that reaches +the database behind it. Read as an accidental omission; it was. Consumer-facing +credentials are named in. Where a service cannot yet meet this, it is a stated +gap rather than a silent exclusion. + +## 10. Blast radius must be published + +**Decision 10.1:** every platform holding consumer data MUST publish, in +concrete terms, what a leaked runtime credential can and cannot reach at the +levels it operates. `rapp-postgres` ADR-0001 §5 is the reference. Where the +model cannot provide a guarantee, the platform says so and names the +escalation. + +**Decision 10.2 — quotas are disclosed, not discovered.** The same obligation +extends from what a leaked credential can reach to what the platform will +refuse to do for you. Every consumer MUST be told, at provisioning, the +throttles and quotas enforced against it — connection limits, statement +timeouts, idle-transaction timeouts — and told again when they change. A +consumer learning its statement timeout by hitting it in production is a +disclosure failure, not a consumer bug. This is how `tenant-engine` was +provisioned, by good practice rather than by rule; the rule now exists. + +## 11. Commercial expression + +- **11.1** Plan tiers are expressed *internally* as typed assurance + requirements. A tier may require `E3 P2 R2 V2` and a maximum erasure horizon; + it need not print those labels anywhere customer-facing. +- **11.2** Marketing and product language is free. No requirement to expose + level labels or this document. "Dedicated infrastructure", "isolated + tenancy", "private instance" all remain available. +- **11.3** The constraint is on **evidence, not vocabulary**. A customer-facing + isolation, availability or retention claim must map to a minimum level the + delivering service actually holds, recorded once when the tier is defined. + The review is internal and happens at tier definition — not per campaign. +- **11.4** Two hard lines, because these reach contracts and compliance + questionnaires: + - A claim that another tenant **cannot** reach the customer's data requires + **E4**. + - A claim that deleted data **is gone** requires **R4**, or an erasure + horizon disclosed alongside it. Where R4 is reached by key destruction, the + claim is defensible but not settled law (§4.5) — it may be made, and it may + not be made in language that implies a regulator has blessed it. + - A claim that service survives loss of a zone requires **V3**; regional-loss + language requires **V4**. "High availability" without a named failure and + measured recovery objective is not an assurance claim this framework can + evidence. +- **11.5 — sanctioned honest language.** The strong prohibitions above must + not leave a commercial writer with only silence: + - E3 may be described as database-backed defence against an omitted tenant + filter; it must not be paraphrased as "another tenant cannot reach". + - P2 may be described as a dedicated service database cluster with an + independent capacity and restore boundary; it is not tenant-dedicated. + - R2 may state the declared retention and published erasure horizon. + - V1 may state exercised restart recovery in one failure domain and must say + that interruption and single-domain loss remain. +- **11.6 — authority and reconciliation.** The tier definition is authoritative + for the minimum and customer wording. `tenancy.yaml` is authoritative for + the delivering service's current level; provider declarations are + authoritative for what infrastructure makes available. None is derived by + copying another. Approval joins them and fails closed on a missing, stale or + insufficient declaration. A performance-differentiated tier requires P2 or + an enforceable resource governor; service class alone grants no priority. + +## 12. Methodology — analyze, establish, improve, guard + +**Analyze.** Assess a repo against the ladders; produce `tenancy.current` with +reasoning recorded. Applies to new and absorbed services alike. + +**Establish.** Declare the target and gap. The target is set by data class, +tenant groupings served and plan tiers carried — not by ambition. + +**Improve.** Move one axis at a time. Raising P while leaving E untouched is +the characteristic misstep. + +**Guard.** Verify continuously that the declared posture holds — **against the +service's own declaration**, not a universal maximum. Nobody must prove every +service is at E4; the check is that none is below what it declared. + +Regression found by guarding is a defect; regression declared in advance is a +decision. The estate has been bitten twice by silent pin rollbacks producing +ordinary-looking 403s and 404s rather than errors. Posture regression looks the +same — an RLS context leak returns correct-looking rows for the wrong tenant. +Guarding must be designed for invisible failure, not for crashes. + +## 13. Evidence per level + +**Decision 13.1:** a current level is claimed only with its evidence artifact +present. This turns §6's accuracy rule from an honour system into a check. +`implemented` records a control observed in code or configuration whose required +artifact is still absent; it never satisfies a tier minimum. + +**Decision 13.1a — the floor needs no artifact, only a reason.** Found +independently by `audit-core` and `flex-auth`: the table below defines +artifacts from `I2`, `A2`, `E1`, `P1`, `R2` upward and none below, so a literal +13.1 made the lowest rungs unclaimable — including §5's own worked example of a +conformant absorbed repo, `I1 A1 E1 P0 R0`, which could not satisfy it on any +axis. A rule that forbids the declaration §6 exists to permit is a defect in +the rule. + +At `I0/I1`, `A0/A1`, `E0`, `P0`, `R0/R1`, `V0`, or `n/a`, a declaration +requires a **stated reason**, not an artifact. Evidence is what stops you +overclaiming, and there is nothing to overclaim at those floors. + +**Decision 13.4 — an artifact must assert something achievable.** Draft-3's +noisy-neighbour evidence required proof that a saturating consumer "does not +breach" another's allowance. Shared infrastructure cannot provide that; the +risk is inherent and cannot be wholly removed. An artifact that can only fail, +or that passes by being run gently enough, is an overclaim wearing the costume +of evidence. Where a property cannot be guaranteed, the artifact measures and +records it instead. + +**Decision 13.2 — evidence is of two kinds, and conflating them is an +overclaim.** *Mechanical* evidence is a structural assertion a machine can make +and belongs in CI. *Adversarial* evidence is semantic, requires setting up +separate tenant contexts and comparing responses, and carries a review date +rather than a green build. Cross-tenant findings are the category external +testing practice identifies as needing human review. **A passing CI run is not +E2 evidence.** + +**Decision 13.5 — freshness and ownership are explicit inputs.** A declaration +may attach `evidence_freshness.` to an evidence key. An adversarial entry +requires `observed_at`, `valid_until`, `responsible_repo`, `scope`, and +`remediation`; a mechanical entry may omit `valid_until` when the artifact is +continuously re-established by the referenced revision or CI control. The +timestamps use RFC 3339 and the responsible repository is the authority for +replacement evidence. + +The feedback evaluator does not parse prose for dates, infer ownership from a +file path, or silently extend a validity window. A current adversarial claim +without freshness metadata resolves to **freshness `unknown`**. An expired +artifact resolves to **freshness `expired`**. Neither automatically rewrites the +declared level: the evaluator emits a deterministic owner-routed remediation +proposal so review remains observable and controlled. The proposal contract is +`posture-feedback_v0.1`; it performs no State Hub write or policy mutation. + +| Level | Evidence | Kind | +|---|---|---| +| **I2** | Identifiers validated against the vocabulary; rejection test for a malformed id; binding shown to come from a verified token | Mechanical | +| **I3** | Live re-query demonstrated on an `aal2`-class path; cached-claim path shown unused there | Mechanical | +| **A2** | Choke point identified; test that an unbound request is refused | Mechanical | +| **A3** | Live decision with a denial observed at the endpoint, not only at the decision surface | Mechanical | +| **A4** | Decision served over the standard interface; a second PDP substituted without PEP change, **with the decision differences between the two recorded** — substitution proves interface portability, not decision equivalence | Mechanical | +| **E1** | Every tenant-owned table carries the tenant key | Mechanical | +| **E2** | Choke point identified; identity bound to tenant A demonstrably cannot read tenant B | **Adversarial**, with a review date | +| **E3** | `FORCE ROW LEVEL SECURITY` on every tenant table; no `BYPASSRLS` on leased roles; probe that a session without the GUC reads nothing; probe that a wrong GUC reads nothing; `EXPLAIN` comparison | Mechanical | +| **E4** | Per-tenant credential demonstrated unable to connect to another tenant's substrate | Mechanical | +| **P1–P4** | Provisioning declaration plus the platform's isolation probes | Mechanical | +| **Shared P1–P2 capacity assurance** | A recorded baseline of per-consumer resource usage; a run in which one consumer saturates its declared allowance; evidence that the governance controls **bind** (the greedy consumer is held at its limits) and that the degradation co-residents experience is **measured, recorded and judged acceptable against each one's declared service class** (§8.3); the aggregate headroom at time of measurement | **Adversarial**, load-generated, with a review date | +| **R2** | Declared retention rendered; erasure horizon published and reported in the operator surface | Mechanical | +| **R3** | Sweep evidence records: timestamp, dataset, identifiers removed, authorising policy reference | Mechanical | +| **R4** | Erasure demonstrated across live data, backups and derived copies within the horizon | **Adversarial** | +| **V1** | Critical dependencies enumerated; restart/recreate recovery exercised; interruption and measured recovery time recorded | Mechanical exercise | +| **V2** | One instance terminated while traffic continues or recovers automatically; measured RTO/RPO and remaining shared failure domains recorded | **Adversarial**, failure-injected | +| **V3** | Declared failure domain removed in an exercise; complete critical path and degraded modes observed against RTO/RPO | **Adversarial**, failure-injected | +| **V4** | Region made unavailable in an exercise; traffic and state recover in the alternate region against RTO/RPO | **Adversarial**, failure-injected | + +The P1–P4 artifact proves the declared placement topology. The shared-capacity +artifact is additional: it is required before a P1/P2 service can claim that a +noisy-neighbour control binds, that the trigger is actively guarded, or that a +customer performance assurance survives co-residency. It is not required merely +to report the true topology as P1 or P2. No such capacity artifact exists in the +estate today, so §11 requires P2 or an enforceable governor for a +performance-differentiated tier. + +**Decision 13.3:** the tenant-boundary E2/E3 and noisy-neighbour artifacts do +not exist anywhere in the estate today. `rapp-postgres` runs 19 adversarial probes, all +against the *consumer* boundary, none against the tenant boundary inside a +consumer. Externally, what this framework calls a tenant boundary failure is +**Broken Object Level Authorization** — OWASP API1, top of the API Security Top +10 since that list launched, and the most commonly exploited API vulnerability +in published assessments. We have no coverage for the highest-ranked risk in +our class of system. §19.3 records the owner. + +## 14. Adoption stance — structure, not tooling + +**Decision 14.1:** external research is design input. This estate adopts +published standards and structural patterns; it does not adopt tooling unless +that tooling is an established industry standard with broad application. +Everything else is built ground-up, so it can be optimised and refactored as +the estate sees fit. + +| Class | Stance | +|---|---| +| Security baselines (OWASP Multi-Tenant Security Cheat Sheet, API Security Top 10) | Adopt as the external reference our ladders answer to | +| Standards bodies (OpenID AuthZEN 1.0) | Adopt — this is what A4 is | +| Reference taxonomies (Azure tenancy models, AWS SaaS Lens, cell architecture) | Adopt as structure | +| Engine behaviour (PostgreSQL RLS mechanics) | Facts, not tooling | +| Third-party analyzers and test frameworks | **Do not adopt.** Take their rule taxonomies as checklists for probes we write ourselves | + +The practical effect is small and good: `rapp-postgres` already owns a +ground-up probe harness — bash and psql, no dependency tree — that found four +real defects in its own provisioning SQL. The evidence artifacts in §13 become +new probes in a tool we control. One idea worth reimplementing from the +external survey is **policy-diff classification**: labelling a change to an +enforcement policy as safe or breaking *before* it lands. + +## 15. Alternatives considered + +**One fixed model with a single set of characteristics** (draft-1). *Rejected:* +cannot describe a repo that is not there yet, forcing absorbed repos to +misrepresent their posture or stay outside. A framework that can only describe +its own end state is not a framework. + +**A maturity model with a single overall level.** *Rejected:* collapses the +axis separation. A service strong on identity and weak on enforcement has a +specific, actionable gap; one composite score hides it and invites averaging. + +**Prohibiting row-level security** (draft-2's inherited position). *Rejected in +draft-2, refined in draft-3:* RLS is a real rung against the common threat. The +error was never RLS — it was describing E3 in E4's language. + +**Schema-per-consumer in one database.** *Rejected:* `pg_catalog` is readable +per-database, so every co-resident enumerates every other's table and column +names regardless of grants. Retained as a describable state, never a target. + +**Mandating E4 for everyone.** *Rejected:* the tenant taxonomy includes +`consumer` (private individuals) and `family`. A cluster per private individual +is economically impossible; the taxonomy is itself evidence pooling is +required. + +**Per-consumer physical backup retention.** *Rejected:* CNPG retention is a +property of the instance's WAL archive. There is no mechanism, and claiming it +would be a fabricated guarantee. Hence the derived maximum in §4.5. + +**Platform-scheduled row expiry.** *Rejected:* requires the platform to hold +DML authority over consumer schemas and interpret consumer data semantics, both +forbidden by ADR-0001. The consumer's migration lease is the correct +instrument. + +**Leaving each repo to its own model.** *Rejected:* the status quo, which +produced two contradictory ratified defaults and an unowned placement question. + +## 16. Held against outside practice + +**The graduated reframe is corroborated, not invented here.** Microsoft's +tenancy-model guidance states it almost verbatim: *"Instead of viewing +isolation as a discrete property, consider it a spectrum. You can deploy +components of your architecture that are more isolated or less isolated than +other components in the same architecture."* The same guidance derives our E↔P +coupling independently — shared deployment means enforcement lives in +application code; dedicated deployment means it is structural. + +**Stronger than typical.** Most multi-tenancy literature models one boundary, +tenant-to-tenant. This estate has **two stacked boundaries**: platform-service +to platform-service, and tenant to tenant inside a consumer. Naming them +separately and refusing to enforce both with one mechanism is uncommon and +correct. Graduated per-axis levels also beat the silo/pool/bridge trichotomy, +which is approximately our P axis with the other four missing — which is why +it cannot express "pooled infrastructure, structurally enforced boundary". + +**Weaker than typical.** The pool model's standard mitigation is a *verified* +enforcement layer every service is demonstrably routed through. We have the +concept and none of the verification (§13.3). + +**Adopted without naming it.** Short-lived leased credentials re-read at +checkout beat the long-lived-secret norm. §9 promotes it to a tenancy control. + +**Still unexplored.** Neither P nor R describes a **cell** — a slice of +infrastructure with a *fixed maximum size*, sized so one cell's failure is +survivable and cell count scales linearly. `platform-pg` is, in these terms, an +uncapped cell: §17 computes a ceiling and nothing enforces it (§19.8). + +Sources: the five research digests in +`the-custodian/research/2026-08-17-adr008-*`, which carry full citations for +the claims in this section. + +## 17. Scaling demands + +Derived from the live `platform-pg` specification. Connection arithmetic is +exact; the per-backend memory estimate remains unmeasured and is explicitly a +gap in `rapp-postgres` ADR-0004. + +``` +instances: 1 (no HA; single-node rail) +max_connections: 100 +memory limit: 1Gi +per consumer: 14 connections (12 runtime + 2 migration) +``` + +**The hard connection bound is roughly six declarations; the enforceable +operational ceiling is four.** Seven declarations request 98 of 100 connections +before CNPG's instance manager, metrics exporter and reserved slots. Every one +of them is politely inside its declared 14-connection allowance; the instance +still fails. ADR-0004 sets four because memory is expected to bind first and +fails by OOM-killing every co-resident rather than refusing one connection. + +That distinction matters because our governance addresses the wrong shape. +Per-consumer `connection_limit`, `statement_timeout` and +`idle_in_transaction_session_timeout` guard well against **one greedy +consumer**. They do nothing about **the aggregate of many modest ones**, which +is the second and less intuitive noisy-neighbour failure and the one this +number describes. Two workload consumers plus the isolation probe occupy three +of the four declared slots. The next workload request must trigger measurement +and the overflow decision before admission. + +**Memory likely binds first.** 100 backends against 1Gi is ~10MB per backend. +Connection exhaustion errors clearly; memory pressure OOM-kills and degrades +every co-resident at once. + +**E3 and pooling.** *Corrected from draft-2, which had this backwards.* +Transaction-scoped context (`SET LOCAL` inside an explicit transaction) is what +makes E3 **safe** under a pooler. Statement-level pooling is what breaks it, +serving other tenants' rows under concurrency with no error. E3 constrains +which pooling mode is available, not whether pooling is available. + +**Retention consumes the volume.** WAL accumulates with the window, and §4.5 +makes the window the maximum across consumers. A consumer declaring a long +retention extends everyone's horizon *and* everyone's storage draw against a +20Gi volume. + +**Restore time couples all consumers.** Physical backup is instance-wide, so a +consumer's RTO is a function of *total* instance size, not its own. + +**`platform-pg` is V1.** `instances: 1` on a single-node rail provides exercised +restart recovery and no failover. P1 describes its consumer placement and says +nothing about this availability fact; the new V axis carries it. + +## 18. Consequences + +- The estate gains one vocabulary and a way to be honest about partial + adoption. +- Absorbed repos get a described state and a path instead of a failing grade. +- `tenantIsolation` in `PostgresConsumer` is revealed as a mislabelled field. +- The verification problem becomes tractable: guard against declaration. +- Draft-2's RLS prohibition is reversed and its E3 description corrected; + `rapp-postgres` acquires an obligation to define and offer the mechanism. +- Adding a consumer with long retention **silently extends everyone's erasure + horizon**. This must reach the consumer review checklist, not only this + document. +- A service selling an isolation tier must maintain a tenant→substrate mapping + it does not have today. +- Availability becomes an end-to-end, evidenced property rather than an + inference from replica count or placement. +- Nothing here changes a running system. + +## 19. Review resolutions and residual questions + +1. **`tenantIsolation` field — resolved.** `rapp-postgres` retired it. A + consumer declaration asks for mechanisms; posture lives in the consumer's + `tenancy.yaml`. +2. **Placement ownership — resolved.** §8.2 records the split. The policy has + one owner; typed tier requirements replace the declined commercial + co-signature. +3. **E2, E3 and noisy-neighbour evidence** — **owned as of 2026-08-17** by + `whitehat-security` (WHITEHAT-WP-0001), an independent adversarial evidence + facility seeded for this purpose. `audit-core` and `tenant-engine` were + right to decline it as fleet-scope work; the answer was a home of its own + rather than a volunteer. + + **Owned by NetKingdom** — corrected 2026-08-17; an earlier revision of this + section proposed otherwise on independence grounds and was overruled. + Offensive security is security work and belongs with the repo that owns + security. The facility is framed offensively rather than as a conformance + checker: it is pointed at infrastructure we choose, our own estate among + them, and conformance testing is one use of a general capability. + + The residual tension is recorded rather than resolved: NetKingdom owns this + framework *and* the facility that tests conformance to it, so those findings + are NetKingdom assessing NetKingdom. The mitigation is that findings leave + for `risk-nexus`, under `the-custodian`, rather than being closed in place. + Proportionate, not perfect. Revisit if conformance findings start getting + quietly closed. + + Two consequences land back here. **Cadence is now a security parameter, not + a schedule** — for any control whose guarantee is detection rather than + prevention, the interval between probe runs *is* the exposure window, and + `rapp-postgres` ADR-0003 leaves that number to the facility. And **a passing + suite is not proof of isolation**; it is proof that the attacks attempted + did not work. §13's evidence artifacts should be read with that distinction, + because a green run recorded as "E2 verified" would be exactly the overclaim + §6 prohibits. +4. **Business app vs platform service — open.** Custodian canon: a classification + rule. Candidate: reuse `repo-classification-standard_v1.0`. +5. **Tier → minimum level mapping — policy resolved, implementation open.** + `adaptive-pricing` owns typed minima and wording; `tenant-engine` owns plan + assignment by id. Current tiers make no assurance claims. +6. **The E3 mechanism — resolved.** `rapp-postgres` ADR-0003 publishes the GUC + contract with the `FORCE`/`BYPASSRLS`/`SECURITY INVOKER`/`EXPLAIN` + requirements. +7. **Identity-provider placement — open.** Owner of `key-cape`: realm-per-tenant or + Organizations? Realm-per-tenant's ~5–20 tenant ceiling is below our target. +8. **Cell sizing — resolved for `platform-pg`.** `rapp-postgres` ADR-0004 sets + four consumers and names absent overflow target `platform-pg-2`; measurement + and provisioning remain live gaps. +9. **Retention floor and ceiling — resolved as policy.** Both exist; requests + outside them fail validation and the package repo owns the numbers. +10. **Engine neutrality — open.** The P ladder rests on a PostgreSQL property. + State it engine-specifically and say so, or abstract it and risk a + non-Postgres implementation that silently differs? +11. **Erasure versus audit — framework resolved.** `audit-core`: crypto-shredding a tenant's audit + records destroys the evidence the service exists to hold, and ADR-0001 §2 + deliberately built the role model so history could not be rewritten. The + usual resolution separates the *fact* of an event, retained, from its + *personal payload*, encrypted per subject and shreddable. Raised because a + naive "R4 everywhere" target would instruct the audit service to destroy + its own evidence. `audit-core` targets R2 and is explicitly not a fleet R4 + target. The legal basis for retaining audit facts remains a risk/legal + question outside this framework. +12. **Quality of service** — **resolved 2026-08-17.** Co-residents are equal; + a declared *service class* informs placement but never grants priority. + See §8.3. The question asked whether to add a QoS dimension; the answer is + no, and the reason is that we could not enforce one. + +**Tenant grouping ambiguity — resolved outside this framework.** ADR-0013 +revision 2 makes the identifier segment immutable onboarding-time history and +the `tenant-engine` record authoritative for current grouping. Consumers do not +parse current policy or spend-ceiling inputs from `tenant::`. + +## 20. Ratification path + +1. Reviewed by `tenant-engine`, `flex-auth`, `audit-core`, `rapp-postgres`, + `railiance-platform` and `adaptive-pricing` against §19. **Complete in + draft-8.** +2. Each publishes its own posture vector (§5) as part of review. **The + framework is validated by whether it can describe them accurately** — if a + repo cannot express itself in these six ladders, the ladders are wrong and + this document changes, not the repo. **Complete in draft-8; all six root + declarations validate against the canonical schema.** +3. On acceptance, **supersedes** the routing of + `rapp-postgres/docs/canon-drafts/shared-platform-relational-storage_v0.1-draft.md`, + whose §§3–8 are absorbed here. That draft is withdrawn rather than left + pending. +4. On acceptance, `rapp-postgres` ADR-0001 through ADR-0004 move to `accepted` + and are annotated as the PostgreSQL implementation of the E, P, R and + shared-capacity rules. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0001.md b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0001.md new file mode 100644 index 0000000..9d59349 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0001.md @@ -0,0 +1,90 @@ +--- +id: ops-warden-adr-0001 +type: adr +title: "ADR-0001 — The routing catalog is a pointer layer, never a second copy" +domain: infotech +repo: ops-warden +status: accepted +version: "1.0" +revision: "1" +owner: ops-warden +binds: "ops-warden; every repo contributing a catalog entry" +created: "2026-06-20" +updated: "2026-08-18" +last_reviewed: "2026-08-18" +review_interval: 6m +enforced_by: tests/test_routing.py +supersedes: "" +successor: "" +--- + +# ADR-0001 — The routing catalog is a pointer layer, never a second copy + +## Status + +Accepted. Decided during WARDEN-WP-0010 (access routing charter), enforced in code +since WARDEN-WP-0011. Restated here because it binds repos other than ops-warden +and had, until now, no address they could cite. + +## Context + +`registry/routing/catalog.yaml` tells a worker which subsystem owns a credential +need and where the authoritative procedure lives. The obvious temptation, every +time someone uses it, is to add the procedure itself: the reader is already here, +the steps are short, and one more copy seems cheaper than a second lookup. + +That temptation is the failure mode. A copied procedure is correct on the day it +is written and silently wrong afterwards, because the owner changes theirs without +knowing ours exists. The estate has already paid for this once, between an ADR and +its published page — fixed by generating the page from the markdown rather than +maintaining both. + +The catalog is consulted precisely when someone is about to touch a credential. +Being confidently wrong there is worse than being absent. + +## Decision + +**For any subsystem ops-warden does not own, a catalog entry carries identifiers +and pointers only** — `owner_repo`, `subsystem`, `wiki_ref`, `canon_ref`, +`need_keywords`, and the secret-free handoff metadata `warden access` needs. + +**Authored procedure is permitted only where `warden_executes: true`.** A `steps:` +block and a `cert_command:` may exist on the SSH certificate lane and nowhere else, +because that is the one lane ops-warden actually owns. Rotation `steps:` are the +narrow exception and describe what the *owner* does, recorded because rotation +guidance had no other home; they are still pointers in spirit and must not grow +into a runnable substitute for the owner's tooling. + +**No secret material in this file, ever.** + +This is enforced, not merely documented. `tests/test_routing.py` fails any non-SSH +entry carrying a `steps` block, and checks that every `wiki_ref` anchor resolves to +a real section. A rule that is only written down is a rule that erodes. + +## Consequences + +**Accepted cost.** Two lookups instead of one. A worker who wants the procedure +follows the pointer. We consider a correct second hop cheaper than a stale first +one. + +**Anchors must resolve.** Because the entry is only a pointer, a broken pointer is +a total failure rather than a cosmetic one. Hence the anchor test — which has +already caught a real break (`ADHOC-2026-08-11-T01`, a stale +`rapp-qonto-keycape-client` anchor). + +**Other repos are bound by this.** When another repo asks us to add or rename a +lane, we add the pointer and decline to absorb the procedure. That has been +exercised: on 2026-08-11 railiance-platform asked ops-warden to rename an active +lane, and the answer was to cross-reference the id from their CCR rather than have +this repo carry a second identity for the same thing. + +**It constrains what this repo may usefully become.** ops-warden cannot grow into +a documentation site for other people's credential procedures, however often that +is asked for. The value of the catalog is that a reader knows it points at truth +rather than at a copy of truth. + +## Related + +- `registry/routing/catalog.yaml` — the file this governs, header comment +- `wiki/AccessRouting.md` — the issue-vs-route role and boundary +- `ADR-0005` — the narrower charter this follows from diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0002.md b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0002.md new file mode 100644 index 0000000..042c619 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0002.md @@ -0,0 +1,90 @@ +--- +id: ops-warden-adr-0002 +type: adr +title: "ADR-0002 — ops-warden is a transparent conduit, never a secret broker" +domain: infotech +repo: ops-warden +status: accepted +version: "1.0" +revision: "1" +owner: ops-warden +binds: "ops-warden" +created: "2026-06-26" +updated: "2026-08-18" +last_reviewed: "2026-08-18" +review_interval: 6m +enforced_by: "src/warden/access.py; wiki/OperatorAccessAssist.md" +supersedes: "" +successor: "" +--- + +# ADR-0002 — ops-warden is a transparent conduit, never a secret broker + +## Status + +Accepted. Decided during WARDEN-WP-0014 (operator access assist), tightened by +WARDEN-WP-0026 (disclosure hygiene). + +## Context + +`warden access` is the operator front door for every credential need in the estate. +For lanes marked `exec_capable` it does more than advise: it runs the owner's tool +and returns the value. Anything that fetches secrets on request looks like a broker, +and the gravity toward becoming one is strong — a broker is more convenient at every +individual call site. + +The distinction is not stylistic. A broker holds authority; a conduit borrows the +caller's. Only one of those creates a new thing worth attacking. + +## Decision + +**ops-warden runs the owner's tool with the caller's own identity, and takes no +custody of the value.** The caller's credentials do the work. ops-warden holds +nothing after the command returns, stores nothing, and caches nothing. + +**Forbidden: a standing broker.** ops-warden must not hold its own long-lived +secret-read credential in order to serve values to callers who could not have +fetched them themselves. If the caller lacks authority, the correct outcome is a +denial from the owner's system — not a fetch performed on their behalf by a more +privileged intermediary. + +The test is a question: *could the caller have run this themselves?* If yes, we are +a conduit and may proxy. If no, proxying is privilege laundering and is refused. + +**Owner-native front doors outrank the proxy.** Where an owner has shipped their own +exec surface — `secrets-engine exec`, the railiance-platform credential broker — we +route there and do not proxy. The proxy is a fallback for lanes nobody fronts yet, +not a preferred path. This is why `whynot-design-npm-publish` and +`ops-warden-warden-sign-token` are `native` rather than `interim`. + +**The value must not land somewhere it will be logged.** Sanctioned transports are +`--out` (mode-0600 file), `--exec` (child process env), and `--wrap` (a single-use +OpenBao wrapping token). Streaming to a non-terminal stdout is refused without an +explicit `--unsafe-stdout`, which exists for interactive humans only. + +## Consequences + +**ops-warden never becomes a credential store, and gains no value by being +compromised beyond the SSH CA it already holds.** This is the whole point. An +attacker who owns ops-warden gets the SSH signing lane — serious, bounded, and +already the thing this repo is hardened around — not a key to every secret in the +estate. + +**Some requests cannot be served, and that is the correct answer.** When a caller +lacks authority, ops-warden routes and explains rather than fetching. This reads as +unhelpfulness at the moment it happens; it is the property that makes the front door +safe to point every agent at. + +**Every proxied fetch is auditable and attributable to the caller**, because it ran +as them. `audit.jsonl` records metadata only — never values, guarded in code. + +**The `--unsafe-stdout` escape hatch is a known liability.** It exists because +humans in terminals legitimately need to see values. It is also exactly the shape of +the 2026-07-16 disclosure, where a value reached a captured stdout. `ADR-0004` +constrains it further for agent sessions. + +## Related + +- `wiki/OperatorAccessAssist.md#the-conduit-vs-broker-boundary-the-security-model` +- `ADR-0004` — the agent-session read boundary built on top of this +- `ADR-0003` — why proxied lanes are tracked as interim rather than owned diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0003.md b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0003.md new file mode 100644 index 0000000..5fc474c --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0003.md @@ -0,0 +1,95 @@ +--- +id: ops-warden-adr-0003 +type: adr +title: "ADR-0003 — Cover gaps, but never silently own them" +domain: infotech +repo: ops-warden +status: accepted +version: "1.0" +revision: "1" +owner: ops-warden +binds: "ops-warden" +created: "2026-07-01" +updated: "2026-08-18" +last_reviewed: "2026-08-18" +review_interval: 6m +enforced_by: "registry/routing/catalog.yaml delegation:; warden route gaps" +supersedes: "" +successor: "" +--- + +# ADR-0003 — Cover gaps, but never silently own them + +## Status + +Accepted. Stated as INTENT §9, made structural by WARDEN-WP-0030 (delegation +register). + +## Context + +ops-warden owns exactly one lane: SSH certificate issuance. It nonetheless fronts +around eleven credential lanes as a caller-identity proxy, because no other +component fronts them yet and a worker blocked on a credential is a worker blocked. + +Covering a gap is legitimate and this repo intends to keep doing it. The failure is +subtler: **a cover that is never recorded as a cover becomes ownership by default.** +Nobody decides to permanently own another component's lane. It happens because the +interim arrangement worked, nobody wrote down that it was interim, and the intended +owner never learned they were expected to build a front door. + +By August 2026 the primitive to hand a lane back existed and was proven — `exec_owner` +/ `exec_command`, used by secrets-engine for npm publish and by the railiance-platform +credential broker for warden-sign — and was used by 2 of 24 lanes. The other +twenty-two had no record of who *should* own them. + +## Decision + +**Every catalog entry carries a `delegation:` block**, with a `mode:` of: + +| `mode` | Meaning | +| --- | --- | +| `permanent` | Ours forever. SSH certificate issuance, and nothing else | +| `native` | The owner has a front door; we route to it and execute nothing | +| `interim` | We are covering a gap. Requires `intended_owner:` and `blocked_on:` | + +**`interim` without an `intended_owner` is not permitted.** If we cannot name who +should own it, we have not understood the lane well enough to be fronting it. + +**`blocked_on:` must name a specific condition, not a mood.** "No front door yet" is +not a blocker; "secrets-engine has not confirmed whether `exec --catalog` generalizes +over arbitrary OpenBao lanes (asked 2026-08-11, msg 7d55d332)" is. A blocker with a +question and a date can be chased. A blocker without one is an excuse with a +timestamp. + +**The interim set is queryable**: `warden route gaps` lists it with review dates and +staleness. A cover that nobody can enumerate is a cover nobody will retire. + +**A blocker is a claim about the world at a date, and expires.** `reviewed:` is +bumped only on a real re-check, never inherited. This was learned the hard way: +`RISK-F-0001` invalidated one of our blockers within a day and nothing would have +re-checked it. + +## Consequences + +**Retiring a cover is a normal, expected event rather than a renegotiation.** When +an owner ships their front door the lane flips `interim` → `native`. This has +happened twice and both were routine. + +**Other repos can see what we are holding for them.** The register is why key-cape +and user-engine were able to confirm or decline lanes in August 2026 — the question +was answerable because it had been written down. One of those answers was "not ours", +which is a legitimate and useful outcome. + +**We accept looking worse than we are.** `warden route gaps` publishes a list of +things this repo is doing that it would rather not be doing. That is the intent: the +alternative is a repo that looks clean because nobody counted. + +**This register is not a risk register.** Interim lanes are tracked work with an +owner and a date, not defects. They do not get bulk-filed into `risk-nexus`, which +needs to stay small enough to read. Defects go there; gaps stay here. + +## Related + +- `INTENT.md` §9 — the principle this formalizes +- `history/2026-08-11-delegation-surface-assessment.md` — the assessment that forced it +- `.claude/rules/finding-routing.md` — the register-versus-findings boundary diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0004.md b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0004.md new file mode 100644 index 0000000..08e5516 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0004.md @@ -0,0 +1,85 @@ +--- +id: ops-warden-adr-0004 +type: adr +title: "ADR-0004 — High-risk lanes refuse raw value streaming to agent sessions" +domain: infotech +repo: ops-warden +status: accepted +version: "1.0" +revision: "1" +owner: ops-warden +binds: "ops-warden; any agent runtime calling warden access" +created: "2026-07-20" +updated: "2026-08-18" +last_reviewed: "2026-08-18" +review_interval: 6m +enforced_by: "src/warden/access.py (exit 7); OpenBao policy agent-high-risk-boundary" +supersedes: "" +successor: "" +--- + +# ADR-0004 — High-risk lanes refuse raw value streaming to agent sessions + +## Status + +Accepted. Decided during WARDEN-WP-0026 (credential disclosure hygiene), in +response to a real disclosure on 2026-07-16. + +## Context + +On 2026-07-16 a secret value reached a captured stdout. The mechanism was ordinary: +`bao kv get -field=X` in an agent session. Nothing was misconfigured and nobody +misused a tool. The value was read correctly, by an authorized caller, using the +documented command — and an agent session records its stdout, so the value landed in +a transcript that outlives the shell. + +This is a structural mismatch, not a mistake to train away. Agent sessions are +logged by design; that is what makes them reviewable. A human at a terminal sees a +value and it scrolls away. An agent "seeing" a value writes it into a durable +context that may be stored, replayed, or sent to an inference provider. + +Guidance alone will not fix it. The command is correct, it is in every runbook, and +the next agent that needs the value will reach for it. + +## Decision + +**When `WARDEN_AGENT_ID` is set and the catalog lane is `risk: high`, ops-warden +refuses to stream the raw value and exits 7.** The agent is not blocked from doing +its work — `--out`, `--exec`, `--wrap` and `--fingerprint` all remain available. +It is blocked from doing its work *in a way that writes the secret into a transcript*. + +**The boundary is enforced at the credential store as well as at the CLI.** The +OpenBao policy `agent-high-risk-boundary` denies data-read on those paths for agent +tokens, allowing metadata and capabilities only. A control that lives solely in our +own CLI is a control that ends the moment someone calls `bao` directly. + +**Verification must not require a read.** To check a lane, use +`bao token capabilities` — allow/deny — never a read of the value. This is the +specific habit the disclosure taught us to break. + +**Exposure is reportable without reading.** `warden taint ` reports KV v2 +`custom_metadata` (`exposed_at`, `exposed_version`) and touches no secret data. + +## Consequences + +**Agents can still do everything they could before, by a different route.** `--exec` +covers nearly every real case: the child process gets the value in its environment, +the agent never sees it. The friction is deliberate and small. + +**Exit 7 is a contract other runtimes depend on.** It is a distinguishable code, not +a generic failure, so a caller can tell "refused by boundary" from "lane broken" and +retry correctly. Changing it is a breaking change to every agent runtime. + +**`risk: high` becomes a load-bearing catalog field** rather than documentation. +Classifying a lane now changes runtime behaviour, so it must be set deliberately. + +**We accept that `--unsafe-stdout` still exists for humans.** The boundary keys on +`WARDEN_AGENT_ID`, so an agent that does not set it is not caught. That is a known +limit: this ADR raises the floor for cooperating runtimes and hardens the store +behind them; it does not claim to stop a determined caller. + +## Related + +- `wiki/playbooks/agent-read-boundary.md` +- `wiki/playbooks/exposed-taint.md` +- `ADR-0002` — the conduit rule this narrows for agent callers diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0005.md b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0005.md new file mode 100644 index 0000000..b637aa8 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0005.md @@ -0,0 +1,79 @@ +--- +id: ops-warden-adr-0005 +type: adr +title: "ADR-0005 — Implement one lane narrowly, route everything else" +domain: infotech +repo: ops-warden +status: accepted +version: "1.0" +revision: "1" +owner: ops-warden +binds: "ops-warden" +created: "2026-06-18" +updated: "2026-08-18" +last_reviewed: "2026-08-18" +review_interval: 6m +enforced_by: "SCOPE.md; registry/routing/catalog.yaml warden_executes" +supersedes: "" +successor: "" +--- + +# ADR-0005 — Implement one lane narrowly, route everything else + +## Status + +Accepted. The founding charter decision, taken 2026-06-18 +(`history/2026-06-18-access-routing-intent-shift-assessment.md`). + +## Context + +ops-warden began as an SSH certificate manager. It then became the place workers +asked when they did not know where a credential came from — which is a real need, +and the obvious way to serve it is to start fetching credentials. + +Down that path is a component that issues SSH certificates, vends API keys, brokers +tokens, and holds authority over all of them: a single point whose compromise is +total. NetKingdom's architecture deliberately separates identity (key-cape), +authorization (flex-auth), and secrets (OpenBao). A helpful front door that absorbed +all three would quietly undo that separation, one convenience at a time. + +## Decision + +**ops-warden executes exactly one lane with its own authority: SSH certificate +issuance for `adm`/`agt`/`atm` actors.** `warden_executes: true` appears on one +catalog entry and is expected to stay that way. + +**For every other need it routes, and where the lane is `exec_capable` it may assist +by proxying as the caller** under `ADR-0002`. Routing is not a lesser service — it is +the service. Knowing which subsystem owns a need, and being right about it, is what +this repo sells. + +**Scope growth is tested by ownership, not by usefulness.** "Would this be handy in +ops-warden?" is the wrong question and almost always answers yes. The right question +is "does ops-warden have the authority to own this, permanently?" If the answer is no, +the correct outcome is a pointer, or an `interim` cover recorded under `ADR-0003`. + +## Consequences + +**The blast radius stays bounded and known.** Compromising ops-warden yields the SSH +signing lane. That is worth defending well precisely because it is the only thing here. + +**We say no to requests that would be easy to say yes to.** `warden secret`, +`warden login`, `warden bao`, `warden tunnel` do not exist and must not be invented; +the agent instructions name them as anti-patterns because agents keep reaching for +them. Each would be a day's work and a permanent widening. + +**Being useful therefore depends on the pointers being right**, which is the whole +weight behind `ADR-0001`'s anchor enforcement and the catalog's review dates. A router +that routes wrongly is worse than no router. + +**It leaves real gaps visible rather than filled.** Six workload lanes and three +tenant lanes are covered interim because secrets-engine and tenant-engine have not +shipped front doors. Under this ADR that is the correct state, tracked under +`ADR-0003`, and not a signal that ops-warden should absorb them. + +## Related + +- `SCOPE.md` — the issue-vs-route table +- `wiki/AccessRouting.md` — role and boundary +- `ADR-0001`, `ADR-0002`, `ADR-0003` — the three rules that follow from this one diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0007.md b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0007.md new file mode 100644 index 0000000..b80d9ca --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0007.md @@ -0,0 +1,100 @@ +--- +id: ops-warden-adr-0007 +type: adr +title: "ADR-0007 — Build-stage permissiveness stops at credential disclosure" +domain: infotech +repo: ops-warden +status: accepted +version: "1.0" +revision: "1" +owner: ops-warden +binds: "ops-warden" +created: "2026-08-19" +updated: "2026-08-19" +last_reviewed: "2026-08-19" +review_interval: 6m +enforced_by: "registry/routing/catalog.yaml risk grades; src/warden/cli.py agent read-boundary; WARDEN-WP-0032-T06" +supersedes: "" +successor: "" +--- + +# ADR-0007 — Build-stage permissiveness stops at credential disclosure + +## Status + +Accepted 2026-08-19, alongside grading the last 14 ungraded catalog lanes. + +## Context + +`ADR-0006` deferred a global fail-closed authorization gate because uniform +enforcement across an estate under deep refactor hardens the access needed to +perform the refactor. The organization's declared posture is `build` +(WP-0029), and the operator has confirmed the estate need not be tight yet. + +That is correct, and it is also the kind of principle that quietly generalises +past its warrant. Read loosely, "we are in build stage" argues for relaxing +every control, including the ones that stop a credential landing in a logged +agent transcript. Those are not the same class of control, and the difference +is not severity — it is **cost**. + +`RISK-F-0003` made the distinction concrete. `ADR-0004` reads as a categorical +rule: high-risk lanes refuse raw value streaming to agent sessions. The +implementation was `risk == "high"` against an **optional** field, so 14 of 27 +lanes never reached the control at all — five of them `exec_capable`. The +control had not been relaxed by anyone's decision. It had simply never been +reached, which is worse, because nothing announced it. + +## Decision + +**Build-stage permissiveness applies to controls that gate work. It does not +apply to controls that prevent credential disclosure.** + +The test is friction, not severity: + +- A control that can **block a legitimate operation** — a fail-closed + authorization gate, an enforcement stance — is a candidate for relaxation + while the organization is in `build`, and `ADR-0006` scopes that relaxation + to zones. +- A control that **redirects how a value moves without preventing the work** — + the agent read-boundary, which refuses raw stdout but leaves `--out`, + `--exec`, `--wrap` and `--fingerprint` fully available — is not relaxed by + build posture, because relaxing it buys nothing. Nobody is unblocked by it. + +The asymmetry that settles it: a blocked operation is recovered by retrying. +A credential written into a logged transcript is not recovered by rotation — +rotation limits the damage, it does not unwrite the log. The 2026-07-16 +disclosure is the case in point. + +**Therefore, regardless of `organization_posture`:** + +1. Every catalog lane carries an explicit `risk` grade. **Absence is not a + grade**, and a lane that omits it is a defect, not a default. +2. Grading is done on merit, per lane. This decision is not licence to grade + everything `high` — an over-broad grade is its own inaccuracy, and + `tenancy-posture` §6's *accuracy, not altitude* applies to this field too. +3. Minimum credential-handling standards — the read-boundary, the safe fetch + transports, the no-secret audit guard — hold in every posture. + +## Consequences + +**We accept** the grading cost, now and on every new lane. That is the point: +`WARDEN-WP-0032-T06` makes an ungraded lane impossible rather than merely +discouraged, because a rule enforced by remembering is not enforced. + +**We reject** "build stage" as a general argument in credential-handling +discussions. It is a real and useful argument about *gating*, and citing it +against a disclosure control is a category error this record exists to name. + +**We note what this decision is not.** It does not set severity for +`RISK-F-0003` — that is `risk-nexus`'s. It does not make ops-warden the judge of +other repos' controls. And it does not survive contact with a zone model that +says otherwise: when `zone-engine` defines admission standards, a zone may +legitimately require *more* than this floor. It may not require less. + +## Related + +- `ADR-0004` — high-risk lanes refuse raw value streaming to agent sessions +- `ADR-0006` — enforcement is zone-scoped, never a global flag +- `RISK-F-0003` — the read-boundary blind spot that prompted this +- `WARDEN-WP-0032-T05` / `T06` — the grading, and making absence impossible +- `zone-engine` `ZONE-WP-0001` — where admission standards will be defined diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0008.md b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0008.md new file mode 100644 index 0000000..6c2f317 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0008.md @@ -0,0 +1,94 @@ +--- +id: ops-warden-adr-0008 +type: adr +title: "ADR-0008 — A lane's risk grade covers every field its path discloses" +domain: infotech +repo: ops-warden +status: accepted +version: "1.0" +revision: "1" +owner: ops-warden +binds: "ops-warden" +created: "2026-08-21" +updated: "2026-08-21" +last_reviewed: "2026-08-21" +review_interval: 6m +enforced_by: "registry/routing/catalog.yaml fields + risk; tests/test_routing.py::test_high_risk_lanes_classified" +supersedes: "" +successor: "" +--- + +# ADR-0008 — A lane's risk grade covers every field its path discloses + +## Status + +Accepted 2026-08-21, after `secrets-engine` found two under-graded lanes while +reviewing ops-warden's own catalog metadata. + +## Context + +`ADR-0007` requires every catalog lane to carry an explicit `risk` grade. It does +not say what the grade is *of*, and the omission turned out to matter. + +The catalog describes a lane by a single `fetch_command` naming a single field — +`bao kv get -field=ISSUE_CORE_API_KEY `. Grading followed that description. +But the unit of disclosure is not the field, it is the **path**: `bao kv get` +without `-field` returns every key stored there, and an agent session that +discloses one field has disclosed all of them. + +On 2026-08-19, grading all 27 lanes, ops-warden graded +`issue-core-ingestion-api-key` and `reuse-surface-hub-write-token` as `standard` +— "ordinary internal workload secrets". Both grades read only the headline field. +`CCR-2026-0002` records a deliberate decision to keep `GITEA_BACKEND_TOKEN` at the +first path; `CCR-2026-0005` declares a dual-consumer webhook HMAC at the second. +Neither is recovered by rotating the credential the lane is named after. + +Three details make this worth a record rather than a fix: + +- **The evidence was already ours.** The field sets were in the CCRs the catalog + already cites as authoritative. This was not missing data; it was unread data. +- **A test held the error still.** `test_high_risk_lanes_classified` asserted + `issue-core-ingestion-api-key` was *not* high. A first grading pass had marked + it high, the test contradicted it, and the test was believed. A test that + encodes a judgement defends that judgement from correction. +- **Another repo found it.** `secrets-engine` graded both `high` independently + while drafting catalog entries whose schema records `fields`. A schema that + names the field set makes the right grade obvious; ours did not have one. + +## Decision + +**A lane's `risk` grade is a property of its path, and must cover the union of +everything a read of that path would disclose.** + +1. Where the field set is known, the catalog records it as `fields`, with the + authority it came from. +2. The grade is argued against the most damaging field, not the named one. +3. Where the field set is unknown, that is stated — never assumed to be one + field. An unverified field set is a reason to grade conservatively, matching + the `inter-hub-bootstrap-ssh` precedent under `ADR-0007`. +4. Establishing a field set must not be done by reading the secret. Use the + owning CCR, the owner's catalog, or `bao kv metadata`. `bao kv get` on a + high-risk path is the 2026-07-16 vector and is forbidden by + `ADR-0004` for agent sessions regardless of intent. + +## Consequences + +`ADR-0007` is unchanged and still governs: every lane carries an explicit grade, +and absence fails safe. This record says what that grade must account for. + +Grading gets more expensive: it now requires knowing what is at a path, not just +what the lane is called. That cost is the point — the cheap version produced two +wrong answers in one pass and is the reason this exists. + +A test that asserts a grade is asserting a judgement. When a grade is disputed, +re-argue it from evidence before trusting the test that encodes it. + +## Related + +- `ADR-0007` — every lane carries an explicit grade; build-stage permissiveness + stops at credential disclosure +- `ADR-0004` — high-risk lanes refuse raw value streaming to agent sessions +- `ADR-0001` — the catalog is a pointer layer; `fields` records the owner's + declared field set with its source, and does not restate their procedure +- `WARDEN-WP-0033-T02`; `secrets-engine` `SECRETS-WP-0006` +- `history/2026-07-16-credential-disclosure-lessons.md` diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0009.md b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0009.md new file mode 100644 index 0000000..461d756 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0009.md @@ -0,0 +1,101 @@ +--- +id: ops-warden-adr-0009 +type: adr +title: "ADR-0009 — Adopt security-zones v0.1 as a consumer" +domain: infotech +repo: ops-warden +status: accepted +version: "1.0" +revision: "1" +owner: ops-warden +binds: "ops-warden" +created: "2026-08-22" +updated: "2026-08-22" +last_reviewed: "2026-08-22" +review_interval: 3m +enforced_by: "tenancy.yaml; registry/routing/catalog.yaml workload_ref; scripts/build_flex_auth_registry.py; src/warden/policy.py; src/warden/config.py" +supersedes: "ops-warden-adr-0006" +successor: "" +--- + +# ADR-0009 — Adopt security-zones v0.1 as a consumer + +## Status + +Accepted 2026-08-22 after zone-engine completed `ZONE-WP-0001-T03/T05` and +published the declaration, compilation, stance, and failure-mode contract in +canon revision `337484a`; zone-engine's reference compiler is revision +`9b6ada7`. + +## Context + +ADR-0006 rejected a repo-wide `policy.enabled` switch because one boolean plus +one `fail_closed` value made flex-auth a uniform dependency of every signing +path, including continuity paths needed to repair that dependency. It deferred +the replacement to zone-engine rather than designing an estate model here. + +The owning model now exists. A zone is an evidenced workload-admission fact; +control stance remains with the control owner, and dependency failure behavior +remains with the PEP. Membership resolves only through an authoritative +workload identity. Missing identity, membership, admission evidence, or a +required floor is `unknown`, never an inferred permissive zone. + +## Decision + +Ops-warden adopts `security-zones_v0.1` and accepts its initial build-stage rows +for the controls ops-warden owns: + +- the pre-sign PEP fails open for `z0-experimental`, `z1-operational`, + `z2-protected`, `z2-continuity`, and build-profile `unknown`; it fails closed + for `z3-critical`; +- the agent high-risk read boundary remains enforced and fail-closed in every + zone and for `unknown`; +- `warden plan` never derives `autonomous` authority from unknown zone evidence. + +The implementation follows four rules: + +1. `policy.enabled` and the global `policy.fail_closed` setting are retired and + rejected by configuration loading. The PEP chooses failure behavior from a + total per-zone map. +2. The existing compiled flex-auth registry is the resource-membership carrier. + Actor resources receive `workload_id`, `security_zone`, + `security_zone_admission`, and `security_zone_revision`. The dormant + `trust_zone: platform` constant is removed; it is not repurposed. +3. Workload joins are explicit. Managed deployables use Repo Manager's exact + `(rapp_id, workload_identity.name, deployable?)` tuple. Independent + operational workloads use their owner-reviewed `tenancy.yaml`. Catalog + owners distinguish `not-applicable` from applicable-but-`unknown`; no path or + repository-name inference is allowed. +4. A fail-open signing result is metadata, not silence. Signature and unified + audit records carry the selected zone, failure mode, outcome, and decision id + when one exists. + +Ops-warden itself declares `z1-operational`. That is an accuracy decision: the +workload has M1 evidence and does not yet have the SLO history, on-call rotation, +or exercised recovery evidence needed for z2 admission. + +## Consequences + +The global flip and its failure cycle no longer exist. An unknown target remains +observable and follows the versioned build profile without manufacturing +membership. A future organization-posture graduation changes the versioned +control profile, not each workload declaration. + +The flex-auth policy package still owns pre-sign stance. Ops-warden can compile +and send the membership attributes, handle `allow`/`audit_only`/deny, and apply +the correct PEP failure mode; it does not write flex-auth's Rego rows. + +Catalog coverage is intentionally honest at adoption: exact references resolve +where authoritative declarations exist, applicable lanes without one report +`unknown` with a reason, and generic actions/patterns are explicitly +`not-applicable`. Resolution coverage improves by adding owner declarations, +never by adding heuristics here. + +## Related + +- `security-zones_v0.1` (net-kingdom canon revision `337484a`; zone-engine + compiler revision `9b6ada7`) +- Repo Manager `helixforge.workloads.ops-warden-reference.v1` revision `890f3b0` +- NetKingdom tenancy-posture Decisions 5.6.1/5.6.2 +- `WARDEN-WP-0032` +- `ADR-0004`, `ADR-0007`, and `ADR-0008` diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0010.md b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0010.md new file mode 100644 index 0000000..65e4ed2 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/ops-warden-adr-0010.md @@ -0,0 +1,109 @@ +--- +id: ops-warden-adr-0010 +type: adr +title: "ADR-0010 — ops-warden is Staff: lanes, not rules, and one declared engine gap" +domain: infotech +repo: ops-warden +status: accepted +version: "1.0" +revision: "1" +owner: ops-warden +binds: "ops-warden" +created: "2026-08-28" +updated: "2026-08-28" +last_reviewed: "2026-08-28" +review_interval: 3m +enforced_by: "INTENT.md layer declaration; docs/adr/ADR-0002; docs/adr/ADR-0003; docs/adr/ADR-0005; registry/routing/catalog.yaml delegation fields" +supersedes: "" +successor: "" +--- + +# ADR-0010 — ops-warden is Staff: lanes, not rules, and one declared engine gap + +## Status + +Accepted 2026-08-28, answering intake `WARDEN-IN-0001` from gate-house, which +carries decision `GH-DEC-2026-001`. The standard being adopted — +`net-kingdom/canon/standards/security-layer-model_v0.1.md` — is `proposed`, and was +proposed pending assent from flex-auth, kings-guard, and ops-warden. This ADR is +ops-warden's half of that assent. + +## Context + +The estate acquired overlapping claims to the same responsibility, most visibly two +repositories describing themselves as the authorization control plane. The layer +model resolves the overlap by layering repositories on determinism — Taxonomy, +Tooling, Engines, Staff — and by two rules: Staff never touches Tooling directly +(§5), and `access-engine` is the only policy decision point (§6). + +ops-warden is assigned Staff. Two demarcations follow that touch this repository: +the security curriculum it had been carrying belongs to gate-house, and the words +*access lane* and *access rule* are bound to different owners. + +Full reasoning: `history/2026-08-28-security-layer-model-assent.md`. + +## Decision + +**1. ops-warden is Staff and declares it.** `INTENT.md` carries the layer label and +the §5 invariant. ops-warden holds no state another layer depends on at runtime and +renders no authorization decision — it consumes them. + +**2. Lanes, not rules.** ops-warden owns *how* a worker reaches a host: SSH +certificate issuance, the routing catalog, `warden access`, `warden plan`, +`cert_command`. It never owns *whether* a worker may — that is `access-engine` +(today `flex-auth`), and ops-warden neither renders nor caches that decision. This +restates what `ADR-0002` and `ADR-0005` already bind; it is recorded here because +the demarcation is now normative estate-wide and other repositories rely on +ops-warden holding to it. The ruled rename `flex-auth` → `access-engine` is assented +to; ops-warden asks only for a window in which both names resolve. + +**3. Doctrine goes to gate-house; runbooks stay here.** ops-warden does not restate +security doctrine, the authority model, or the curriculum. It references +gate-house's. It keeps everything operational about the lanes it stewards: which +subsystem owns which need, how to obtain a credential lane by lane, and conformance +evidence for its own lanes. `.claude/rules/credential-routing.md` is runbook, not +curriculum, and stays inlined in this and every other repository. + +**4. One declared engine gap, not an exemption.** `src/warden/vault.py` (`VaultCA`) +is a direct OpenBao client performing a write from a Staff repository. It is a §5 +non-conformance. ops-warden declares it rather than arguing it away: + +- **intended owner:** `secrets-engine` (credential abstraction, custody, lifecycle) +- **blocked on:** no engine exposes an SSH certificate signing surface +- **review:** with this ADR, every 3 months + +Until that surface exists, ops-warden continues to sign — refusing to would remove +production host access to close a documentation gap — and reports the position as +open. `warden desk`'s `bao kv put` is declared on the same terms. `taint.py` is +metadata-only observation, declared under §5's read-only allowance. `proxy.py` +supplies no authority of its own: it runs the owner's tool under the caller's +identity and is governed by `ADR-0002`. + +This is `ADR-0003` turned inward. ops-warden has required an intended owner and a +blocker on 27 catalog lanes it holds for other repositories; it holds itself to the +same record. + +## Consequences + +ops-warden's conformance under §10 is *declared non-conformant with a tracked +closure path*, not clean. That is the accurate state and it is the state that gets +fixed, because it names an owner who can fix it. + +An amendment to §5 has been offered to gate-house — a second sanctioned shape +alongside read-only diagnostics: a declared engine gap carrying intended owner, +blocker, and review date, machine-readable so §10 can tell a tracked gap from an +undeclared violation. It is offered, not assumed; §5 stays gate-house's to write. If +gate-house declines it, ops-warden's position is a plain non-conformance and is +reported as one. + +The `NetKingdom Security Literacy` section stops being a prose second source for +`registry/routing/catalog.yaml`, which `ADR-0001` had already ruled against for +catalog procedure. + +## Related + +- `net-kingdom/canon/standards/security-layer-model_v0.1.md` (proposed, gate-house) +- `gate-house/decisions/decisions.md` — `GH-DEC-2026-001` +- `history/2026-08-28-security-layer-model-assent.md` +- `ADR-0001`, `ADR-0002`, `ADR-0003`, `ADR-0005`, `ADR-0009` +- `WARDEN-IN-0001` diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/policy-nexus-adr-0001.md b/docs/evidence/2026-10-11-served-publication-history/sources/policy-nexus-adr-0001.md new file mode 100644 index 0000000..db5bf41 --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/policy-nexus-adr-0001.md @@ -0,0 +1,74 @@ +--- +id: policy-nexus-adr-0001 +title: "Policy addressing and permanence" +status: accepted +owner: the-custodian +revision: "accepted-1" +last_reviewed: "2026-08-18" +review_interval: 6m +--- + +# ADR-0001 — policy addressing and permanence + +- Status: accepted +- Date: 2026-08-18 +- Owner: the-custodian + +## Decision + +A document has one stable current address and immutable revision addresses: + +```text +//// +////revisions// +``` + +`` is one of `standards`, `architecture`, `constitution`, or `adr`. +`` is a kebab-case slug that is unique on the site. The owner-facing +rules live in `docs/publication-contract.md`. + +The publication manifest records both. Existing public paths become permanent +redirect aliases; the first is `/tenancy-posture.html`. No URL is derived from +a checkout path, branch name, build number or hosting implementation. + +The current address advances only when the owning source publishes a new +revision. A revision address is write-once: the builder records the exact git +commit (or commit plus working-tree marker) and the full source-content digest. +It refuses to replace that revision with different content. Committing the +same bytes may update the current page's provenance but does not rewrite the +already-published revision page. The generated publication manifest makes the +source repository, path, revision and digest machine-readable. + +Superseded and withdrawn documents are never deleted. The current page gains a +status banner and link to its successor; every historical revision continues +to resolve. “Withdrawn” means retained and visibly non-current. Deletion is not +a lifecycle state. + +Source Git history remains the authority for versions that predate this site. +Once a revision is published, the built revision page is also retained by the +hosting artifact/release. Rollback republishes a previous complete build; it +does not rebuild old pages from a changed working tree. + +## Permanence promise + +A published URL is expected to resolve indefinitely. Breaking that promise +requires an explicit the-custodian decision plus a redirect/export plan. A DNS, +hosting or repository migration does not qualify: those must preserve paths. + +This repo owns addressing, rendering and currency. It does not decide whether +a draft is accepted; ratification belongs to the canon owner/the-custodian. + +## Publication scope + +Canon `standards`, `architecture`, and `constitution` are in scope. `values`, +`tpsc`, and `projects` are out until their owner marks individual documents as +governing. Per-repo ADRs are in scope through explicit manifest entries. +Workplans, evidence, runbooks and general docs are not. + +## Consequences + +- Builds fail if a source disappears, an id differs, a path collides, or an + immutable revision would change; stale output is not silently called fresh. +- Pages show status, revision, owner, last review and exact source revision. +- Availability remains restart recovery on the single-node rail. This contract + promises stable addressing, not a high-availability SLA. diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/policy-nexus-architecture.md b/docs/evidence/2026-10-11-served-publication-history/sources/policy-nexus-architecture.md new file mode 100644 index 0000000..beb8b8c --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/policy-nexus-architecture.md @@ -0,0 +1,161 @@ +--- +id: policy-nexus-architecture +title: "Policy Nexus architecture" +status: proposed +owner: the-custodian +revision: "draft-1" +version: "0.1" +last_reviewed: "2026-08-18" +review_interval: 6m +--- + +# Policy Nexus — Software Architecture Documentation (arc42) + +## About this document + +This document follows the **arc42** template for the publication surface +at `policy.coulomb.social`. It is the first-wave architecture document +this repository is allowed to author. Other first-wave systems are +written in their owning repos. + +--- + +## 1. Introduction and Goals + +Policy Nexus exists so estate canon and architecture decisions have a +permanent, current, addressable public home. Before this system, +governing documents were served from disposable artifact URLs. + +### 1.1 Requirements Overview + +- Publish canon (`standards`, `architecture`, `constitution`) and + per-repo ADRs from the repositories that own them. +- Keep one stable current URL and one immutable revision URL per + document (ADR-0001). +- Fail closed when a source disappears or an immutable revision would + change. +- Show status, owner, revision, and review currency on every page. + +### 1.2 Quality Goals + +1. Permanence of published addresses. +2. Honesty about staleness. +3. Source-of-truth stays upstream — this system never writes back. +4. Builds are deterministic and stdlib-only. + +### 1.3 Stakeholders + +| Role | Concern | +| --- | --- | +| the-custodian | What counts as canon; ratification. | +| Source-repo owners | Their document is published accurately. | +| railiance-platform / rapp-policy-nexus | Substrate, Helm, promotion. | +| Readers inside and outside the estate | A URL that still resolves. | + +--- + +## 2. Architecture Constraints + +N/A for this stub — first-wave complete will record stdlib-only +rendering, single-node rail availability, and the no-CMS rule. + +--- + +## 3. System Scope and Context + +**In:** addressing, rendering, currency, the public static site. +**Out:** ratification, legal advice, regulatory intake (`risk-nexus`), +editing surfaces, search. + +### 3.1 Business Context + +Source owners publish governing documents. This system makes those +documents findable and durable. It does not decide what is current +canon. + +### 3.2 Technical Context + +- Inputs: `publication.json`, sibling or fetched git checkouts, + `source-inventory.json`. +- Outputs: static HTML, `publication-manifest.json`, an OCI image. +- Neighbours: owning source repos, Forgejo Actions, `rapp-policy-nexus`, + `railiance-apps`, the reef Traefik/cert-manager substrate. + +--- + +## 4. Solution Strategy + +N/A for this stub — pull-on-schedule ingestion, explicit manifest +entries, stdlib renderer. To be written as first-wave complete. + +--- + +## 5. Building Block View + +### 5.1 Level 1 – System/Top-Level + +N/A for this stub — builder, inventory auditor, currency checker, +OCI artifact, Helm package in `rapp-policy-nexus`. + +### 5.2 Level 2 – Key Components + +N/A for this stub. + +### 5.3 Level 3 – Internal Structure (as needed) + +N/A for this stub. + +--- + +## 6. Runtime View + +N/A — the published site is static files. Daily source-pull and +promotion are operational scenarios for a later revision. + +--- + +## 7. Deployment View + +N/A for this stub — see WP-0001-T04. Single-node restart recovery, not +a high-availability SLA. + +--- + +## 8. Cross-Cutting Concepts + +N/A for this stub. + +--- + +## 9. Architecture Decisions + +| Id | Status | Decision | Source | +| --- | --- | --- | --- | +| `policy-nexus-adr-0001` | accepted | Stable current address + immutable revision address; no deletion | `docs/adr/ADR-0001-addressing-and-permanence.md` | + +Later `publish` ADRs from WP-0003 land here. `local` and unresolved +`conflict` rows do not. + +--- + +## 10. Quality Requirements + +N/A for this stub — currency failure is already a build/CI failure. + +--- + +## 11. Risks and Technical Debt + +N/A for this stub. Known residual: Markitect `arc42-v1` schema is not +registered yet (WP-0002-T02). + +--- + +## 12. Glossary + +| Term | Meaning | +| --- | --- | +| Current address | The stable URL for the document as it now stands. | +| Revision address | Write-once URL for one source digest. | +| Publication entry | One object in `publication.json`. Discovery is not publication. | +| First-wave complete | Chapters 1, 3, 4, 5.1, 9 and 12 are real; others real or N/A. | diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/railiance-architecture.md b/docs/evidence/2026-10-11-served-publication-history/sources/railiance-architecture.md new file mode 100644 index 0000000..1a5367e --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/railiance-architecture.md @@ -0,0 +1,151 @@ +--- +id: railiance-architecture +title: "Railiance architecture" +status: proposed +owner: railiance-master +revision: "draft-3" +version: "0.1" +last_reviewed: "2026-08-31" +review_interval: 6m +--- + +# Railiance — Software Architecture Documentation (arc42) + +## About this document + +First-wave arc42 for the Railiance runtime substrate. Deeper chapters +belong to follow-on work in this repo. Chapter 9 lists the ADRs this +repo already publishes; it does not paste them. + +--- + +## 1. Introduction and Goals + +Railiance-master is the authoritative source for Railiance framework +architecture: repo families, workload models, and substrate boundaries +that implementation repos must not invent locally. + +### 1.1 Requirements Overview + +- Name the rails, reefs, and rApps and who owns each boundary. +- Keep those decisions in `docs/adr/` with publication-grade metadata. +- Consume platform packages; do not fork identity or tenancy. + +### 1.2 Quality Goals + +1. Reviewable boundary decisions. +2. Private-by-default exposure until admission. +3. Derived rails compose; they do not fork policy. + +### 1.3 Stakeholders + +| Role | Concern | +| --- | --- | +| railiance-master | Framework language and first-wave rApp set. | +| railiance-platform | S3, placement, substrate services. | +| railiance-apps | Production digest bindings. | +| NetKingdom | Identity and tenancy posture of workloads. | + +--- + +## 2. Architecture Constraints + +N/A for this stub. + +--- + +## 3. System Scope and Context + +**In:** rails, reefs, rApp packaging, admission, exposure defaults. +**Out:** tenant identity semantics (NetKingdom), publication of policy +(policy-nexus), OS baseline (railiance-hosts). + +### 3.1 Business Context + +Implementation repos solve immediate cluster problems. This system +holds the shared meaning so those repos do not drift. + +### 3.2 Technical Context + +Neighbours: `railiance-platform`, `railiance-apps`, `rapp-*` packages, +the reef (Traefik, cert-manager), Forgejo, NetKingdom, policy-nexus. + +--- + +## 4. Solution Strategy + +N/A for this stub — repository-prefix architecture and rapp-first wave +are already in the ADRs in §9. + +--- + +## 5. Building Block View + +### 5.1 Level 1 – System/Top-Level + +N/A for this stub. + +--- + +## 6. Runtime View + +N/A for this stub. + +--- + +## 7. Deployment View + +N/A for this stub. + +--- + +## 8. Cross-Cutting Concepts + +N/A for this stub. + +--- + +## 9. Architecture Decisions + +Published on policy.coulomb.social from this repo: + +| Id | Status | Decision | +| --- | --- | --- | +| `RMASTER-ADR-0001` | accepted | Repository prefix architecture | +| `RMASTER-ADR-0002` | accepted | Wave 1 rail-kubernetes boundary | +| `RMASTER-ADR-0003` | accepted | First-wave rapp selection | +| `RMASTER-ADR-0004` | accepted | First-wave reef rollout | +| `RMASTER-ADR-0005` | accepted | Derived rail composition | +| `RMASTER-ADR-0006` | accepted | Reef production admission | +| `RMASTER-ADR-0007` | accepted | Rapp declaration contract | +| `RMASTER-ADR-0008` | accepted | Private-by-default exposure | +| `RMASTER-ADR-0009` | accepted | NetKingdom security-layer interaction | + +Also published from `railiance-platform`: `RPLAT-ADR-0001` (S3 platform +services), `RPLAT-ADR-0002` (placement rule), `RPLAT-ADR-0003` +(decisions live in the repo). + +Unresolved: identical accepted ADR-003/004 copies in railiance-hosts +and railiance-infra. Not listed as current here until those owners rule. + +--- + +## 10. Quality Requirements + +N/A for this stub. + +--- + +## 11. Risks and Technical Debt + +N/A for this stub. + +--- + +## 12. Glossary + +| Term | Meaning | +| --- | --- | +| Rail | A composed runtime path. | +| Reef | The production admission environment. | +| rApp | A packaged workload bound by a declaration contract. | diff --git a/docs/evidence/2026-10-11-served-publication-history/sources/state-hub-architecture.md b/docs/evidence/2026-10-11-served-publication-history/sources/state-hub-architecture.md new file mode 100644 index 0000000..dc4370b --- /dev/null +++ b/docs/evidence/2026-10-11-served-publication-history/sources/state-hub-architecture.md @@ -0,0 +1,161 @@ +--- +id: state-hub-architecture +title: "State Hub architecture" +status: proposed +owner: state-hub +revision: "draft-3" +version: "0.1" +last_reviewed: "2026-08-31" +review_interval: 6m +--- + +# State Hub — Software Architecture Documentation (arc42) + +## About this document + +First-wave arc42 for State Hub, the estate's live coordination +read-model. This service is in active retirement planning; new +permanent ownership should not land here. Chapter 9 points at the +estate ADRs that still bind it. + +--- + +## 1. Introduction and Goals + +State Hub is a queryable, auditable memory of work: domains, repos, +workplans, tasks, decisions, progress. Files remain the origin. The +hub is derived state (custodian ADR-001, ADR-003). + +It remains operational until retirement gates in +`prj-state-hub-retirement` are met. Replacement ownership is moving +toward `repo-manager` and `hub-core`. + +### 1.1 Requirements Overview + +- Rebuild coordination state from registered repository files. +- Deterministic UUIDv5 work-record identifiers (ADR-007). Any instance may + reconcile the same repository; the canonical record id produces the same + database key and writeback bytes on every instance. +- Preserve compatibility; do not take new permanent architectural + ownership. + +### 1.2 Quality Goals + +1. Rebuildability from git. +2. Hub never becomes the origin of work. +3. Extraction paths stay open. + +### 1.3 Stakeholders + +| Role | Concern | +| --- | --- | +| state-hub | Live read-model during retirement. | +| the-custodian | Estate rules the hub must not invert. | +| repo-manager | Incoming consistency / repo representation. | +| product repos | Workplan files the hub indexes. | + +--- + +## 2. Architecture Constraints + +N/A for this stub — retirement program is the binding constraint. + +--- + +## 3. System Scope and Context + +**In:** indexing workplans/tasks/decisions, consistency rebuild, +query API and dashboard used today. +**Out:** being the source of work items; new cross-domain capabilities; +publication of policy (policy-nexus). + +### 3.1 Business Context + +Files are excellent for canon and provenance. The estate still needs a +live query surface while retirement proceeds. + +### 3.2 Technical Context + +Inputs: workplan markdown via `fix-consistency`. Outputs: HTTP/MCP +APIs. Neighbours: every registered repo, activity-core (ops runs), +policy-nexus (does not index the hub). + +--- + +## 4. Solution Strategy + +N/A for this stub. The strategy is already in the estate ADRs: files +first, materialized derived state, single registrar, local cache vs +authority (ADR-010, proposed). + +--- + +## 5. Building Block View + +### 5.1 Level 1 – System/Top-Level + +N/A for this stub. + +--- + +## 6. Runtime View + +N/A for this stub. + +--- + +## 7. Deployment View + +N/A for this stub. + +--- + +## 8. Cross-Cutting Concepts + +N/A for this stub. + +--- + +## 9. Architecture Decisions + +Estate-wide binding decisions live in the-custodian and are listed on the +estate map. Service-local implementation decisions live in `docs/adr/` and do +not supersede the estate decisions: + +| Estate ADR | Status | Why it binds this system | +| --- | --- | --- | +| `CUST-ADR-001` | accepted | File-backed work originates in repositories; the Hub projects it. Published `/adr/custodian-workplans-as-repo-artefacts/v1/`. | +| `CUST-ADR-003` | accepted | How derived state invalidates; ADR-012 supplies commit provenance. | +| `CUST-ADR-007` | accepted | Namespace-aware identity and deterministic work-record UUIDs. | +| `CUST-ADR-010` | proposed | Two kinds of hub data. | +| `CUST-ADR-011` | proposed | Namespace and reconciliation limits. | +| `CUST-ADR-012` | accepted | Forge projection baseline and preliminary overlays. | + +| Local ADR | Status | Scope | +| --- | --- | --- | +| `STATE-ADR-001` | accepted | Repository identity and recovery contract for canonical-name changes. | + +Do not treat a State Hub `/decisions` row as the published ADR. + +--- + +## 10. Quality Requirements + +N/A for this stub. + +--- + +## 11. Risks and Technical Debt + +N/A for this stub. Residual: this workstation cannot mint registrar +UUIDs. + +--- + +## 12. Glossary + +| Term | Meaning | +| --- | --- | +| Read model | Derived index; never the origin. | +| Registrar | The single instance allowed to mint workplan UUIDs. | +| Retirement | Coordinated move of capabilities out of this repo. | diff --git a/docs/releases/2026-10-11-security-layer-v08-publication-proposal.md b/docs/releases/2026-10-11-security-layer-v08-publication-proposal.md new file mode 100644 index 0000000..8cded39 --- /dev/null +++ b/docs/releases/2026-10-11-security-layer-v08-publication-proposal.md @@ -0,0 +1,58 @@ +# PolicyNexus security guardrail publication — source preparation + +Stable proposal ID: POLICY-PUB-20261011-SECURITY-LAYER-V08. Status: preparation; no publication or runtime approval requested by this document. Baseline observed 2026-10-11T00:16:33Z. Publishing source, building/pushing an OCI candidate, updating the production binding and changing the live Helm release are separate actions. No such action was executed by this review. + +## Intended result and ownership + +Publish accepted NetKingdom security-layer-model v0.8 (Markdown SHA256 `3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b`), change v0.7's canonical lifecycle notice to superseded while preserving its served immutable HTML, and publish the two annotated CUST ADR002/004 current revisions while retaining accepted-1 pages. PolicyNexus renders owner decisions; this release does not revise guardrail decisions or activate a policy enforcement service. + +PolicyNexus owns source audit and static OCI publication. rapp-policy-nexus owns chart 0.2.0, hardening, verification and rollback. railiance-apps owns the production four-pin binding; railiance-platform owns the cluster/Helm execution lane. Argo adoption remains RPF-WP-0043, a separate blocked change. Current hosting is Helm release `policy-nexus`, namespace `policy-nexus`, hostname `policy.coulomb.social` on railiance01. No PolicyNexus Argo Application exists. The RPF0043 transition permits attended founder-approved Helm releases through 2026-12-21 under the existing production-tier gate. + +## Verified baseline and rollback anchor + +The Deployment has one ready replica, generation/observed generation 8, Helm ownership and no Argo tracking annotation. Helm revision 8 is deployed. All four baseline pins match live Deployment annotations, public manifest/inventory and `railiance-apps/bindings/policy-nexus-production.json`: + +- Image: `forgejo.coulomb.social/coulomb/policy-nexus@sha256:23df0ab3dc80d2a92b8058eeb572c222ceb05d17434f173a24142b2450ab3cea`. +- Publication manifest: `09f9f7edcc46e31d4b3f73b62d16fdf17808ae1f893f312242b7b9e2037319e0`. +- Source inventory: `9658803bc79d19cef1a9c842d1a8c161f1998d59ce6e4bc4df3c88aba2f4c9c4`. +- Source set: `03f04902a3559099433795453829d75470aa1490dd243b42e7703b6eab87772f`. + +After approval, rollback is the existing `make policy-nexus-rollback POLICY_NEXUS_REVISION=8` through RailianceApps/rApp, then restore the above binding and verify public and workload pins. Re-observe immediately before execution: a changed live revision, Argo adoption or baseline identities invalidates revision 8 as an unreviewed rollback anchor. Rollback retains all old immutable pages in the old image but temporarily removes the newly introduced v0.8 URL; record this availability effect if rollback occurs. There are no PVCs, database migrations or credentials in this package. + +## Historical failed preparation and verified source repair + +The first prepared clean site was structurally valid but failed publication transition safety. The complete-history source repair below resolves that failure; no OCI candidate or native approval is implied. Public baseline contains 72 documents; the clean candidate contains 73. Exact source-digest/revision/lifecycle comparison found seven other source changes besides the intended v0.8 addition, v0.7 lifecycle and two ADR annotations: tenancy-posture, work-record-types, workplan-terminology-fleet, NetKingdom architecture, RMASTER-ADR0006, Warden ADR0004 and RISK-F0008. The source review now explicitly includes these seven changes in the publication scope: four already carry newer accepted owner revisions; three need the minimal publication revision corrections described below. This mirrors owner source decisions and records no new acceptance. + +A read-only capture of every one of the 72 prior current immutable revision URLs succeeded. Only v0.7 and CUST ADR002/004 retain original served bytes in the prepared clean site. Sixty-five other immutable pages regenerate with different bytes and four old revision paths disappear. Source HEAD changes alone change embedded HTML provenance even when Markdown is unchanged. Historical URLs must survive byte-exact. `immutable-audit.json`, captured HTML and `transition-failure.txt` preserve this failure; clean-build verification alone does not establish publication safety. + +The new comparison helper checks every old immutable path and hash and refuses semantic changes outside an explicit reviewed ID allowlist. Five meaningful regressions passed, including unchanged Markdown/new repository HEAD, missing previous revisions, failed public capture and allowlisted content that still rewrites history. The original prepared clean site failed the gate; its output is retained. The corrected 73-document clean source mirror now passes with every one of the 72 old immutable pages byte-exact. This helper is preparation tooling, not a live deployment authorization or CI integration. Owner archives must retain actual prior HTML plus original source hash/revision; changed source under the same revision must be explicitly versioned rather than silently overriding an immutable URL. + +## Reviewed complete source repair and additional publication scope + +`docs/evidence/2026-10-11-served-publication-history/receipt.json` pins the exact delivered manifest, 72 observed HTML pages, and separately 72 original owner Markdown sources. Original source verification used actual Git object reads at the commit embedded in each served page; all72 hashes match HTML and delivered manifest. The generic archive restore checks every document's coverage, path, hash, id/revision and provenance and refuses conflicting existing output before publication. It restores history independently of whether a current document selects a new revision. Existing three owner snapshots are untouched. All45 PolicyNexus tests pass, including seven generic-history and five transition regressions. No whole-release historical-completeness claim beyond captured current URLs is made; earlier URLs absent from the delivered manifest remain discoverable history, and explicit owner snapshots continue to be retained. + +The seven additional reviewed source deltas are explicit publication scope: + +- Custodian work-record-types accepted-1 -> accepted-2 and workplan-terminology accepted-1 -> accepted-2: owner accepted review2026-09-14; text records founder ratification of STATE-WP0092 flavor/default-exclusion and dependency semantics. No new decision is invented. +- RMASTER ADR0006 accepted-2 -> accepted-3: owner accepted review2026-09-21; adds the founder Kubernetes change gate and transition consequences while retaining original decision. Hosting still uses the separately recorded RPF0043 attended transition, not automatic adoption. +- Warden ADR0004 revision1 ->2: owner accepted source recognizes issued coding-agent subject, states both caller-visible identity markers remain advisory, and preserves enforced OpenBao boundary rather than claiming a new authorization mechanism. +- Tenancy-posture draft-14 -> draft-15: preserve proposed status and review dates; only publication revision changes now. The pre-existing source delta records audit-core restart evidence V0 ->V1 with its bounded measurement/date. This does not accept the proposed standard. +- NetKingdom architecture draft-3 -> draft-4: preserve proposed status and review dates; only publication revision changes now. The pre-existing delta adds the proposed emission-cadence profile to the source index, with migration pending. +- RISK-F0008 published-1 -> published-2: preserve existing accepted finding, accepter, acceptance ending conditions, dates and overdue regulatory-review terms; only publication revision changes now. Owner-source additions record Sep1/2/5/22 review/history and cadence, explicitly withholding renewed acceptance or regulatory assurance. Publishing those records does not resolve the substantive retention gap. + +The three new revision URLs were read-only checked and all returned404 before preparation (no current public collision). Their prior URLs remain archived byte-exact. `content-review.json` retains exact before/current semantic diffs. Only the three frontmatter revision strings are staged owner changes; current source content is not frozen or newly ratified. + +## Exact candidate and CI gates before asking for approval + +1. Commit the staged complete publisher archive/helper, source review and three owner publication revision fixes. All72 original owner sources have been verified against exact Git commits, and the clean source mirror preserves all72 prior current immutable URLs byte-exact. Preserve original owner historical snapshots and failure evidence. Run all PolicyNexus tests, inventory check, currency and release checks against a clean source mirror. Run the transition helper with the reviewed exact ID allowlist. Retain both the failed baseline and corrected result. +2. Capture the PolicyNexus exact 40-hex revision and each upstream exact revision in `_sources/source-lock.json`, derived by the existing pinned-source-fetch workflow. Candidate identity must come from the actual CI image, not the local 73-document build. `publish-image.yaml` runs on main/schedule/dispatch, uses tier2 container-build, has registry publication authority and no cluster deployment authority. Dedicated source/registry secrets remain inside CI; this review does not retrieve them. Public remote main was `bfef142d4cf8084b76084e4b0c7775999d72c2e7`, with no reported commit statuses; the queried Actions endpoint returned 404. Neither observation proves a successful pending candidate build. +3. The workflow uses pinned Python source-fetch/build tooling and pinned nginx runtime base, builds `release-artifact`, audits exact lock/inventory and publishes source-/git-/main tags. Resolve the actual registry digest, retain build/check output and verify registry bytes by extracting publication-manifest/source-inventory. Record their SHA256s and source-set digest together. Tags alone cannot be approved as runtime identity. No candidate image or new paired four-pin set exists in this preparation receipt. +4. Prepare a reviewed RailianceApps production binding containing those four exact candidate pins in one change, along with exact source lock, inventory report, content delta and immutable transition receipt. Inspect the image/security findings under the existing owner release policy. Render chart0.2.0 with that binding; compare against the live baseline and require changes limited to image plus three digest annotations. Run existing server dry-run through the approved operator lane after exact action scope is agreed. Do not adopt into Argo as part of this publication. +5. Recheck cluster headroom and existing ownership. Deployment resources stay 10m CPU/32Mi requested, 100m/64Mi limit, nonroot101, readonly root, no service-account token and deny-egress. RollingUpdate maxSurge1/maxUnavailable0 requires an extra 10m/32Mi requested for a brief second pod. Node allocatable CPU is4; current available requested CPU was not measured by this review, so headroom remains a preflight prerequisite. +6. Founder proposal must name exact source revisions, image and all three evidence digests, chart/render digest, binding digest, target, operation and rollback. Founder authorization applies to that exact runtime revision; newer CI schedules do not authorize a moving candidate. Only then execute the existing guarded Helm upgrade --atomic --wait --timeout5m via rApp. + +## Verification and estimates + +Require one ready serving Deployment on the approved digest with all three evidence annotations, HTTPS certificateReady, exact public manifest/inventory hashes, correct inventory source-set digest and source lock/provenance matching the approved source. Explicitly fetch the v0.8 canonical/immutable pages, v0.7 superseded canonical notice and old v0.7 immutable SHA `7d7a38a8c3b9fff65b46280fcd5723eff77dc9d0784f871bb35f718bc4e8c0e7`, plus both current ADR revisions and preserved accepted-1 pages. Fetch and hash all captured baseline immutable paths against the reviewed receipt. Existing rApp verify_live.sh samples only the first document; its green status cannot replace these changed-content/history checks. Keep its TLS, runtime pin and legacy redirect checks as additional evidence. + +Incremental infrastructure spending estimate EUR0 (existing runner/registry/cluster; no purchased resources). Preparation/build estimated minutes rather than customer downtime; avoid parallel heavy builds on this workstation. Rollout budget5min with atomic rollback; expected brief static-serving rollout with one existing replica retained until readiness. Main risks: wrong source provenance, lost immutable history, unreviewed additional source changes, registry candidate not available to cluster, and insufficient surge CPU causing a stalled rollout. All are concrete gates above. No new paid API calls, credential changes, policy enforcement writes or customer data changes are included. diff --git a/publication.json b/publication.json index b074049..46c2faf 100644 --- a/publication.json +++ b/publication.json @@ -667,5 +667,11 @@ "revision_path": "methods/risk-dependencies/v1/revisions/{revision}/index.html", "review_interval": "6m" } + ], + "published_history": [ + { + "receipt_path": "docs/evidence/2026-10-11-served-publication-history/receipt.json", + "receipt_digest": "8946a4d79e11d9ea90d9e1d4efb43b2fbae5a8b3e869fc7b84e4be58d3e43128" + } ] } diff --git a/tests/test_publication_transition.py b/tests/test_publication_transition.py new file mode 100644 index 0000000..90595e5 --- /dev/null +++ b/tests/test_publication_transition.py @@ -0,0 +1,49 @@ +import hashlib +import importlib.util +import json +from pathlib import Path +import tempfile +import unittest +spec = importlib.util.spec_from_file_location('transition', Path(__file__).parents[1] / 'tools' / 'verify_publication_transition.py') +m = importlib.util.module_from_spec(spec) +spec.loader.exec_module(m) + +class TransitionTests(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.addCleanup(self.tmp.cleanup) + self.root = Path(self.tmp.name) + self.doc = {'id':'a','revision_path':'a/revisions/1/index.html','revision':'1','source_digest':'old','source_revision':'old-head'} + self.old = self.root / 'baseline.json' + self.old.write_text(json.dumps({'documents':[self.doc]})) + self.audit = self.root / 'audit.json' + self.audit.write_text(json.dumps({'immutable_results':[{'id':'a','revision_path':self.doc['revision_path'],'http_status':200,'served_html_sha256':hashlib.sha256(b'original').hexdigest()}]})) + self.page = self.root / self.doc['revision_path'] + self.page.parent.mkdir(parents=True) + self.page.write_bytes(b'original') + self.write_candidate(self.doc | {'source_revision':'new-head'}) + def write_candidate(self, doc): + (self.root / 'publication-manifest.json').write_text(json.dumps({'documents':[doc]})) + def verify(self, allow=()): + return m.verify(self.root, self.old, self.audit, allow) + def test_new_head_same_markdown_preserves_original_html(self): + self.assertEqual(self.verify()['history_preserved'], 1) + self.page.write_bytes(b'regenerated new-head') + with self.assertRaisesRegex(ValueError,'immutable bytes changed'): self.verify() + def test_reviewed_new_revision_still_requires_previous_history(self): + self.write_candidate(self.doc | {'revision':'2','source_digest':'new'}) + with self.assertRaisesRegex(ValueError,'unreviewed'): self.verify() + self.verify(['a']) + self.page.unlink() + with self.assertRaisesRegex(ValueError,'absent'): self.verify(['a']) + def test_allowlist_cannot_approve_history_rewrite(self): + self.page.write_bytes(b'rewrite') + with self.assertRaisesRegex(ValueError,'immutable bytes changed'): self.verify(['a']) + def test_failed_capture_refuses(self): + self.audit.write_text(json.dumps({'immutable_results':[{'id':'a','revision_path':self.doc['revision_path'],'http_status':404}]})) + with self.assertRaisesRegex(ValueError,'not successfully captured'): self.verify() + def test_path_traversal_refuses(self): + self.doc['revision_path']='../outside' + self.old.write_text(json.dumps({'documents':[self.doc]})) + self.audit.write_text(json.dumps({'immutable_results':[{'id':'a','revision_path':'../outside','http_status':200,'served_html_sha256':'a'*64}]})) + with self.assertRaisesRegex(ValueError,'unsafe'): self.verify() diff --git a/tests/test_published_history.py b/tests/test_published_history.py new file mode 100644 index 0000000..e5196f4 --- /dev/null +++ b/tests/test_published_history.py @@ -0,0 +1,66 @@ +from pathlib import Path +import hashlib +import json +import tempfile +import unittest +from unittest import mock +from test_publication import _fixture, build_site + +class PublishedHistoryTests(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.addCleanup(self.tmp.cleanup) + self.root = Path(self.tmp.name) + self.manifest = _fixture(self.root) + self.source = self.root/'canon-repo/canon/standards/example.md' + self.output = self.root/'build' + self.env = mock.patch.dict('os.environ', {'POLICY_NEXUS_SOURCE_ROOT':''}) + self.env.start();self.addCleanup(self.env.stop) + with mock.patch.object(build_site, '_source_revision', return_value='a'*40): + build_site.build(self.manifest,self.output) + self.delivered = json.loads((self.output/'publication-manifest.json').read_text()) + self.doc = self.delivered['documents'][0] + self.original = (self.output/self.doc['revision_path']).read_bytes() + self.archive = self.root/'archive';self.archive.mkdir() + (self.archive/'source.md').write_bytes(self.source.read_bytes()) + (self.archive/'page.html').write_bytes(self.original) + delivered_bytes = (self.output/'publication-manifest.json').read_bytes() + (self.archive/'manifest.json').write_bytes(delivered_bytes) + record = {k:self.doc[k] for k in ['id','revision','revision_path','source_repo','source_path','source_revision','source_digest']} + record.update(archived_source_path='source.md',rendered_page_path='page.html',rendered_page_digest=hashlib.sha256(self.original).hexdigest()) + self.receipt = {'schema_version':'policy-nexus-published-history/v1','publication_manifest_path':'manifest.json','publication_manifest_digest':hashlib.sha256(delivered_bytes).hexdigest(),'documents':[record]} + self.configure() + def configure(self): + data = json.dumps(self.receipt).encode();(self.archive/'receipt.json').write_bytes(data) + manifest = json.loads(self.manifest.read_text());manifest['published_history']=[{'receipt_path':'archive/receipt.json','receipt_digest':hashlib.sha256(data).hexdigest()}] + self.manifest.write_text(json.dumps(manifest)) + def clean_build(self): + with mock.patch.object(build_site, '_source_revision', return_value='b'*40): + return build_site.build(self.manifest,self.root/'clean') + def test_clean_new_head_same_source_retains_actual_old_bytes(self): + self.clean_build() + self.assertEqual((self.root/'clean'/self.doc['revision_path']).read_bytes(),self.original) + self.assertIn(b'b'*40,(self.root/'clean'/self.doc['canonical_path']).read_bytes()) + def test_new_revision_retains_previous_path_in_clean_build(self): + self.source.write_text(self.source.read_text().replace('draft-1','draft-2').replace('No position.','Updated position.')) + records=self.clean_build() + self.assertEqual((self.root/'clean'/self.doc['revision_path']).read_bytes(),self.original) + self.assertTrue((self.root/'clean'/records[0]['revision_path']).is_file()) + def test_conflicting_existing_history_refuses_without_replacing_output(self): + path=self.output/self.doc['revision_path'];path.write_bytes(b'conflict') + with self.assertRaisesRegex(RuntimeError,'conflicts'): + build_site.build(self.manifest,self.output) + self.assertEqual(path.read_bytes(),b'conflict') + def test_missing_archived_old_page_refuses_clean_build(self): + (self.archive/'page.html').unlink() + with self.assertRaisesRegex(ValueError,'artifact'):self.clean_build() + self.assertFalse((self.root/'clean').exists()) + def test_tampered_original_owner_source_refuses(self): + (self.archive/'source.md').write_bytes(b'changed') + with self.assertRaisesRegex(RuntimeError,'owner source digest'):self.clean_build() + def test_delivered_manifest_coverage_cannot_drop_document(self): + self.receipt['documents']=[];self.configure() + with self.assertRaisesRegex(ValueError,'every delivered'):self.clean_build() + def test_changed_source_under_same_revision_refuses(self): + self.source.write_text(self.source.read_text().replace('No position.','Changed position.')) + with self.assertRaisesRegex(RuntimeError,'Publish a new revision'):self.clean_build() diff --git a/tools/build_site.py b/tools/build_site.py index 61a0472..4795a78 100644 --- a/tools/build_site.py +++ b/tools/build_site.py @@ -162,6 +162,71 @@ def _restore_historical_revisions( target.write_bytes(page_bytes) +def _restore_published_history(manifest_path: Path, manifest: dict[str, Any], output: Path) -> None: + """Restore publisher-held observed bytes, never re-render old owner decisions.""" + seen: set[PurePosixPath] = set() + root = manifest_path.parent.resolve() + + def artifact(base: Path, relative: str) -> Path: + path = (base / _safe_path(relative)).resolve() + if root not in path.parents or not path.is_file(): + raise ValueError("published history artifact must stay inside publisher repository") + return path + + for archive in manifest.get("published_history", []): + receipt_path = artifact(root, archive["receipt_path"]) + receipt_bytes = receipt_path.read_bytes() + if hashlib.sha256(receipt_bytes).hexdigest() != archive["receipt_digest"]: + raise RuntimeError("published history receipt digest mismatch") + receipt = json.loads(receipt_bytes) + if receipt.get("schema_version") != "policy-nexus-published-history/v1": + raise ValueError("published history receipt schema mismatch") + base = receipt_path.parent + delivered_bytes = artifact(base, receipt["publication_manifest_path"]).read_bytes() + if hashlib.sha256(delivered_bytes).hexdigest() != receipt["publication_manifest_digest"]: + raise RuntimeError("published history delivered manifest digest mismatch") + delivered = json.loads(delivered_bytes) + delivered_documents = delivered["documents"] + originals = {item["id"]: item for item in delivered_documents} + records = receipt["documents"] + if (len(originals) != len(delivered_documents) + or len(records) != len(originals) + or {item["id"] for item in records} != set(originals)): + raise ValueError("published history receipt must cover every delivered document once") + for record in records: + original = originals[record["id"]] + for field in ("revision", "revision_path", "source_repo", "source_path", "source_digest"): + if record[field] != original[field]: + raise RuntimeError("published history differs from delivered manifest") + revision_path = _safe_path(record["revision_path"]) + if "/revisions/" not in revision_path.as_posix() or revision_path in seen: + raise ValueError("published history requires unique immutable revision paths") + seen.add(revision_path) + source_bytes = artifact(base, record["archived_source_path"]).read_bytes() + page_bytes = artifact(base, record["rendered_page_path"]).read_bytes() + if hashlib.sha256(source_bytes).hexdigest() != record["source_digest"]: + raise RuntimeError("published history original owner source digest mismatch") + if hashlib.sha256(page_bytes).hexdigest() != record["rendered_page_digest"]: + raise RuntimeError("published history observed HTML digest mismatch") + meta, _ = split_frontmatter(source_bytes.decode("utf-8")) + if (meta.get("id") != record["id"] + or (meta.get("revision") or meta.get("version")) != record["revision"]): + raise ValueError("published history original owner id/revision mismatch") + text = page_bytes.decode("utf-8") + commit = SOURCE_REVISION.search(text) + digest = SOURCE_DIGEST.search(text) + if (not CLEAN_GIT_REVISION.fullmatch(record["source_revision"]) + or not commit or not digest + or html.unescape(commit.group(1)) != record["source_revision"] + or html.unescape(digest.group(1)) != record["source_digest"]): + raise RuntimeError("published history HTML/original owner provenance mismatch") + target = output / revision_path + if target.exists() and (target.is_symlink() or target.read_bytes() != page_bytes): + raise RuntimeError("published history immutable path conflicts with existing 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 ( @@ -234,6 +299,7 @@ def build( records: list[dict[str, str]] = [] try: + _restore_published_history(manifest_path, manifest, temporary) for document in manifest["documents"]: repo = resolved_repositories[document["source_repo"]] source = (repo / document["source_path"]).resolve() diff --git a/tools/verify_publication_transition.py b/tools/verify_publication_transition.py new file mode 100644 index 0000000..cd06b93 --- /dev/null +++ b/tools/verify_publication_transition.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""Compare a candidate against captured public history, without network or authority.""" +import argparse +import hashlib +import json +from pathlib import Path, PurePosixPath + +SEMANTIC_FIELDS = ('source_digest', 'revision', 'status', 'lifecycle', 'currency') + +def safe_path(value): + p = PurePosixPath(value) + if p.is_absolute() or '..' in p.parts or not p.parts: + raise ValueError('unsafe publication path') + return p + +def verify(candidate, baseline_manifest, immutable_audit, allowed_changed_ids): + candidate = Path(candidate) + old = json.loads(Path(baseline_manifest).read_text())['documents'] + new = json.loads((candidate / 'publication-manifest.json').read_text())['documents'] + old_by = {d['id']: d for d in old} + new_by = {d['id']: d for d in new} + if len(old_by) != len(old) or len(new_by) != len(new): + raise ValueError('duplicate document identity') + errors = [] + changed = set(new_by) - set(old_by) + for identity, before in old_by.items(): + after = new_by.get(identity) + if after is None: + errors.append(f'{identity}: published document removed') + elif any(before.get(k) != after.get(k) for k in SEMANTIC_FIELDS): + changed.add(identity) + for identity in sorted(changed - set(allowed_changed_ids)): + errors.append(f'{identity}: unreviewed semantic change') + history = json.loads(Path(immutable_audit).read_text())['immutable_results'] + if len(history) != len(old) or {h['id'] for h in history} != set(old_by): + errors.append('baseline capture does not cover every previously published document') + for record in history: + identity = record['id'] + if record.get('http_status') != 200 or not record.get('served_html_sha256'): + errors.append(f'{identity}: prior public history was not successfully captured') + continue + if record.get('revision_path') != old_by[identity]['revision_path']: + errors.append(f'{identity}: captured path differs from baseline manifest') + continue + path = candidate / safe_path(record['revision_path']) + if not path.is_file() or path.is_symlink(): + errors.append(f'{identity}: old immutable revision absent or unsafe') + elif hashlib.sha256(path.read_bytes()).hexdigest() != record['served_html_sha256']: + errors.append(f'{identity}: old immutable bytes changed') + if errors: + raise ValueError('\n'.join(errors)) + return {'previous_documents': len(old), 'candidate_documents': len(new), + 'reviewed_semantic_changes': sorted(changed), 'history_preserved': len(history)} + +if __name__ == '__main__': + p = argparse.ArgumentParser(description=__doc__) + p.add_argument('--candidate', type=Path, required=True) + p.add_argument('--baseline-manifest', type=Path, required=True) + p.add_argument('--immutable-audit', type=Path, required=True) + p.add_argument('--allowed-changed-id', action='append', default=[]) + a = p.parse_args() + try: + result = verify(a.candidate, a.baseline_manifest, a.immutable_audit, a.allowed_changed_id) + except (ValueError, OSError, KeyError, TypeError) as e: + p.exit(1, str(e) + '\n') + print(json.dumps(result, sort_keys=True))