Automate policy source freshness and inventory
Some checks failed
Build and publish policy-nexus image / build-and-push (push) Failing after 2s
Some checks failed
Build and publish policy-nexus image / build-and-push (push) Failing after 2s
This commit is contained in:
parent
78d096bdd5
commit
03a4fab9e0
17 changed files with 1647 additions and 42 deletions
|
|
@ -11,10 +11,20 @@ on:
|
||||||
- "Containerfile"
|
- "Containerfile"
|
||||||
- "deploy/**"
|
- "deploy/**"
|
||||||
- "publication.json"
|
- "publication.json"
|
||||||
|
- "source-inventory.config.json"
|
||||||
|
- "source-inventory.json"
|
||||||
- "tests/**"
|
- "tests/**"
|
||||||
- "tools/**"
|
- "tools/**"
|
||||||
|
schedule:
|
||||||
|
# Pull-based freshness audit. A failed source checkout, inventory drift, or
|
||||||
|
# overdue document is visible as a failed scheduled Forgejo Actions run.
|
||||||
|
- cron: "17 04 * * *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: policy-nexus-publication
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: forgejo.coulomb.social
|
REGISTRY: forgejo.coulomb.social
|
||||||
IMAGE_NAME: coulomb/policy-nexus
|
IMAGE_NAME: coulomb/policy-nexus
|
||||||
|
|
@ -30,23 +40,27 @@ jobs:
|
||||||
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
set -eu
|
set -eu
|
||||||
REF="${GITHUB_SHA:-main}"
|
REF="${GITHUB_SHA:-}"
|
||||||
|
test "${#REF}" -eq 40
|
||||||
SHORT="${REF:0:7}"
|
SHORT="${REF:0:7}"
|
||||||
mkdir -p buildctx/_sources/net-kingdom "${HOME}/bin"
|
BUILD_ROOT="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "${BUILD_ROOT}"' EXIT
|
||||||
|
BUILD_CONTEXT="${BUILD_ROOT}/buildctx"
|
||||||
|
mkdir -p "${BUILD_CONTEXT}" "${HOME}/bin"
|
||||||
|
|
||||||
wget -qO /tmp/policy-nexus.tar.gz \
|
wget -qO "${BUILD_ROOT}/policy-nexus.tar.gz" \
|
||||||
"https://forgejo.coulomb.social/${GITHUB_REPOSITORY}/archive/${SHORT}.tar.gz"
|
"https://forgejo.coulomb.social/${GITHUB_REPOSITORY}/archive/${SHORT}.tar.gz"
|
||||||
tar xzf /tmp/policy-nexus.tar.gz -C buildctx --strip-components=1
|
tar xzf "${BUILD_ROOT}/policy-nexus.tar.gz" \
|
||||||
|
-C "${BUILD_CONTEXT}" --strip-components=1
|
||||||
|
|
||||||
NETKINGDOM_REVISION=$(git ls-remote \
|
python3 "${BUILD_CONTEXT}/tools/fetch_sources.py" \
|
||||||
https://forgejo.coulomb.social/coulomb/net-kingdom.git \
|
--config "${BUILD_CONTEXT}/source-inventory.config.json" \
|
||||||
refs/heads/main | awk '{print $1}')
|
--destination "${BUILD_CONTEXT}/_sources" \
|
||||||
test -n "$NETKINGDOM_REVISION"
|
--policy-revision "${REF}"
|
||||||
NETKINGDOM_SHORT="$(printf '%s' "$NETKINGDOM_REVISION" | cut -c1-7)"
|
NETKINGDOM_REVISION="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["repositories"]["net-kingdom"]["revision"])' "${BUILD_CONTEXT}/_sources/source-lock.json")"
|
||||||
wget -qO /tmp/net-kingdom.tar.gz \
|
SOURCE_SET_DIGEST="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["source_set_digest"])' "${BUILD_CONTEXT}/_sources/source-lock.json")"
|
||||||
"https://forgejo.coulomb.social/coulomb/net-kingdom/archive/${NETKINGDOM_SHORT}.tar.gz"
|
SOURCE_TAG="source-${SOURCE_SET_DIGEST}"
|
||||||
tar xzf /tmp/net-kingdom.tar.gz \
|
REVISION_TAG="git-${REF}-sources-${SOURCE_SET_DIGEST:0:16}"
|
||||||
-C buildctx/_sources/net-kingdom --strip-components=1
|
|
||||||
|
|
||||||
wget -qO- https://download.docker.com/linux/static/stable/x86_64/docker-27.3.1.tgz \
|
wget -qO- https://download.docker.com/linux/static/stable/x86_64/docker-27.3.1.tgz \
|
||||||
| tar xz --strip-components=1 -C "${HOME}/bin" docker/docker
|
| tar xz --strip-components=1 -C "${HOME}/bin" docker/docker
|
||||||
|
|
@ -57,17 +71,43 @@ jobs:
|
||||||
|
|
||||||
IMAGE="${REGISTRY}/${IMAGE_NAME}"
|
IMAGE="${REGISTRY}/${IMAGE_NAME}"
|
||||||
docker build \
|
docker build \
|
||||||
--file buildctx/Containerfile \
|
--file "${BUILD_CONTEXT}/Containerfile" \
|
||||||
--build-arg "VCS_REVISION=${REF}" \
|
--build-arg "VCS_REVISION=${REF}" \
|
||||||
--build-arg "NETKINGDOM_REVISION=${NETKINGDOM_REVISION}" \
|
--build-arg "NETKINGDOM_REVISION=${NETKINGDOM_REVISION}" \
|
||||||
--tag "${IMAGE}:git-${REF}" \
|
--build-arg "SOURCE_SET_DIGEST=${SOURCE_SET_DIGEST}" \
|
||||||
|
--tag "${IMAGE}:${SOURCE_TAG}" \
|
||||||
|
--tag "${IMAGE}:${REVISION_TAG}" \
|
||||||
--tag "${IMAGE}:main" \
|
--tag "${IMAGE}:main" \
|
||||||
buildctx
|
"${BUILD_CONTEXT}"
|
||||||
docker push "${IMAGE}:git-${REF}"
|
PUSH_OUTPUT="$(docker push "${IMAGE}:${SOURCE_TAG}")"
|
||||||
|
printf '%s\n' "${PUSH_OUTPUT}"
|
||||||
|
IMAGE_DIGEST="$(printf '%s\n' "${PUSH_OUTPUT}" | awk '/digest: sha256:/{print $2}' | tail -1)"
|
||||||
|
test -n "${IMAGE_DIGEST}"
|
||||||
|
docker push "${IMAGE}:${REVISION_TAG}"
|
||||||
docker push "${IMAGE}:main"
|
docker push "${IMAGE}:main"
|
||||||
|
|
||||||
PUBLICATION_DIGEST=$(docker run --rm --entrypoint sha256sum \
|
PUBLICATION_DIGEST=$(docker run --rm --entrypoint sha256sum \
|
||||||
"${IMAGE}:git-${REF}" /usr/share/nginx/html/publication-manifest.json \
|
"${IMAGE}:${SOURCE_TAG}" /usr/share/nginx/html/publication-manifest.json \
|
||||||
| awk '{print $1}')
|
| awk '{print $1}')
|
||||||
echo "published=${IMAGE}:git-${REF}"
|
SOURCE_INVENTORY_DIGEST=$(docker run --rm --entrypoint sha256sum \
|
||||||
|
"${IMAGE}:${SOURCE_TAG}" /usr/share/nginx/html/source-inventory.json \
|
||||||
|
| awk '{print $1}')
|
||||||
|
echo "published=${IMAGE}:${SOURCE_TAG}"
|
||||||
|
echo "image_digest=${IMAGE_DIGEST}"
|
||||||
echo "publication_manifest_digest=${PUBLICATION_DIGEST}"
|
echo "publication_manifest_digest=${PUBLICATION_DIGEST}"
|
||||||
|
echo "source_inventory_digest=${SOURCE_INVENTORY_DIGEST}"
|
||||||
|
echo "source_set_digest=${SOURCE_SET_DIGEST}"
|
||||||
|
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
|
||||||
|
{
|
||||||
|
echo "## policy-nexus release candidate"
|
||||||
|
echo
|
||||||
|
echo "The scheduled pull only publishes an immutable candidate; it does not deploy."
|
||||||
|
echo
|
||||||
|
echo "- image: \`${IMAGE}@${IMAGE_DIGEST}\`"
|
||||||
|
echo "- publication manifest: \`${PUBLICATION_DIGEST}\`"
|
||||||
|
echo "- source inventory: \`${SOURCE_INVENTORY_DIGEST}\`"
|
||||||
|
echo "- source set: \`${SOURCE_SET_DIGEST}\`"
|
||||||
|
echo
|
||||||
|
echo "Promotion requires an explicit paired digest update in rapp-policy-nexus and railiance-apps."
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
fi
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
build/source-inventory.json
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
|
ARG SOURCE_SET_DIGEST=unknown
|
||||||
|
|
||||||
FROM docker.io/nginxinc/nginx-unprivileged@sha256:65e3e85dbaed8ba248841d9d58a899b6197106c23cb0ff1a132b7bfe0547e4c0 AS runtime-base
|
FROM docker.io/nginxinc/nginx-unprivileged@sha256:65e3e85dbaed8ba248841d9d58a899b6197106c23cb0ff1a132b7bfe0547e4c0 AS runtime-base
|
||||||
|
|
||||||
ARG VCS_REVISION=unknown
|
ARG VCS_REVISION=unknown
|
||||||
|
ARG SOURCE_SET_DIGEST
|
||||||
LABEL org.opencontainers.image.title="policy-nexus" \
|
LABEL org.opencontainers.image.title="policy-nexus" \
|
||||||
org.opencontainers.image.description="Canonical Coulomb policy publication surface" \
|
org.opencontainers.image.description="Canonical Coulomb policy publication surface" \
|
||||||
org.opencontainers.image.source="https://forgejo.coulomb.social/coulomb/policy-nexus" \
|
org.opencontainers.image.source="https://forgejo.coulomb.social/coulomb/policy-nexus" \
|
||||||
org.opencontainers.image.revision="$VCS_REVISION"
|
org.opencontainers.image.revision="$VCS_REVISION" \
|
||||||
|
org.coulomb.policy.source-set-digest="$SOURCE_SET_DIGEST"
|
||||||
|
|
||||||
COPY --chown=101:101 deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
COPY --chown=101:101 deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
|
@ -16,13 +20,19 @@ COPY --chown=101:101 build/ /usr/share/nginx/html/
|
||||||
|
|
||||||
FROM docker.io/library/python@sha256:d09d15e60962ca365d1cd544a48773bac9d33f2fb1b00f2aa0deec78ade7dc31 AS release-builder
|
FROM docker.io/library/python@sha256:d09d15e60962ca365d1cd544a48773bac9d33f2fb1b00f2aa0deec78ade7dc31 AS release-builder
|
||||||
ARG NETKINGDOM_REVISION
|
ARG NETKINGDOM_REVISION
|
||||||
|
ARG SOURCE_SET_DIGEST
|
||||||
ENV POLICY_NEXUS_SOURCE_REVISION_NET_KINGDOM=$NETKINGDOM_REVISION
|
ENV POLICY_NEXUS_SOURCE_REVISION_NET_KINGDOM=$NETKINGDOM_REVISION
|
||||||
|
ENV POLICY_NEXUS_SOURCE_ROOT=/workspace/_sources
|
||||||
WORKDIR /workspace/policy-nexus
|
WORKDIR /workspace/policy-nexus
|
||||||
COPY . /workspace/policy-nexus
|
COPY . /workspace/policy-nexus
|
||||||
COPY _sources/net-kingdom /workspace/net-kingdom
|
COPY _sources /workspace/_sources
|
||||||
RUN rm -rf build \
|
RUN python3 -m unittest discover -s tests -p 'test_*.py' \
|
||||||
&& python3 -m unittest discover -s tests -p 'test_*.py' \
|
|
||||||
&& python3 tools/build_site.py publication.json --output build \
|
&& python3 tools/build_site.py publication.json --output build \
|
||||||
|
&& python3 tools/source_inventory.py check \
|
||||||
|
--source-root /workspace/_sources \
|
||||||
|
--lock /workspace/_sources/source-lock.json \
|
||||||
|
--report build/source-inventory.json \
|
||||||
|
&& test "$(python3 -c 'import json; print(json.load(open("/workspace/_sources/source-lock.json"))["source_set_digest"])')" = "$SOURCE_SET_DIGEST" \
|
||||||
&& python3 tools/verify_release.py build \
|
&& python3 tools/verify_release.py build \
|
||||||
&& python3 tools/check_currency.py publication.json
|
&& python3 tools/check_currency.py publication.json
|
||||||
|
|
||||||
|
|
|
||||||
11
Makefile
11
Makefile
|
|
@ -1,4 +1,4 @@
|
||||||
.PHONY: build check currency clean release-build release-check image-build
|
.PHONY: build check currency source-audit clean release-build release-check image-build
|
||||||
|
|
||||||
# Publication targets. Source of truth is always the upstream repo; pages here
|
# Publication targets. Source of truth is always the upstream repo; pages here
|
||||||
# are generated and must never be hand-edited.
|
# are generated and must never be hand-edited.
|
||||||
|
|
@ -6,12 +6,17 @@ SRC_NETKINGDOM ?= ../net-kingdom
|
||||||
|
|
||||||
build:
|
build:
|
||||||
python3 tools/build_site.py publication.json --output build
|
python3 tools/build_site.py publication.json --output build
|
||||||
|
python3 tools/source_inventory.py check --report build/source-inventory.json
|
||||||
|
|
||||||
check:
|
check:
|
||||||
python3 -m unittest discover -s tests -p 'test_*.py'
|
python3 -m unittest discover -s tests -p 'test_*.py'
|
||||||
python3 -m py_compile tools/render.py tools/build_site.py tools/check_currency.py tools/verify_release.py
|
python3 -m py_compile tools/render.py tools/build_site.py tools/check_currency.py tools/verify_release.py tools/source_inventory.py tools/fetch_sources.py
|
||||||
|
$(MAKE) source-audit
|
||||||
git diff --check
|
git diff --check
|
||||||
|
|
||||||
|
source-audit:
|
||||||
|
python3 tools/source_inventory.py check
|
||||||
|
|
||||||
currency:
|
currency:
|
||||||
python3 tools/check_currency.py publication.json
|
python3 tools/check_currency.py publication.json
|
||||||
|
|
||||||
|
|
@ -22,7 +27,7 @@ release-check:
|
||||||
python3 tools/verify_release.py build
|
python3 tools/verify_release.py build
|
||||||
python3 tools/check_currency.py publication.json
|
python3 tools/check_currency.py publication.json
|
||||||
|
|
||||||
release-build: clean build release-check
|
release-build: build release-check
|
||||||
|
|
||||||
image-build: release-check
|
image-build: release-check
|
||||||
@test -n "$(IMAGE_REF)" || (echo "IMAGE_REF is required" >&2; exit 2)
|
@test -n "$(IMAGE_REF)" || (echo "IMAGE_REF is required" >&2; exit 2)
|
||||||
|
|
|
||||||
14
README.md
14
README.md
|
|
@ -28,6 +28,20 @@ make currency
|
||||||
|
|
||||||
`publication.json` is the explicit source and address registry. A build fails
|
`publication.json` is the explicit source and address registry. A build fails
|
||||||
closed when a source is unavailable or an immutable revision would change.
|
closed when a source is unavailable or an immutable revision would change.
|
||||||
|
`source-inventory.config.json` defines the bounded canon/ADR discovery scope,
|
||||||
|
while `source-inventory.json` records an explicit reviewed disposition for every
|
||||||
|
matching source. `make source-audit` fails when a source appears or disappears
|
||||||
|
without that review. Working-tree-only files in sibling repos do not affect the
|
||||||
|
audit; local checks inspect committed Git trees.
|
||||||
|
|
||||||
|
The Forgejo workflow pulls exact `main` revisions for all inventoried source
|
||||||
|
repos every day at 04:17 UTC and on manual dispatch. It fails visibly on an
|
||||||
|
unavailable source, unreviewed inventory drift, invalid release, or overdue
|
||||||
|
published document. Successful runs publish immutable `source-<source-set-digest>`
|
||||||
|
candidates and a moving discovery tag, but never deploy them. Production
|
||||||
|
promotion remains an explicit review of the registry-resolved OCI digest and
|
||||||
|
publication-manifest digest together in `rapp-policy-nexus` and
|
||||||
|
`railiance-apps`.
|
||||||
|
|
||||||
Production publication is split from runtime ownership. This repository builds
|
Production publication is split from runtime ownership. This repository builds
|
||||||
and publishes the immutable OCI site image; `rapp-policy-nexus` owns the Helm
|
and publishes the immutable OCI site image; `rapp-policy-nexus` owns the Helm
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@
|
||||||
|
|
||||||
| Kind | ID | Status | Lane | Source |
|
| Kind | ID | Status | Lane | Source |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| workplan | POLICY-NEXUS-WP-0001 | active | — | workplans/POLICY-NEXUS-WP-0001-permanent-publication-surface.md |
|
| workplan | POLICY-NEXUS-WP-0001 | finished | — | workplans/POLICY-NEXUS-WP-0001-permanent-publication-surface.md |
|
||||||
| task | POLICY-NEXUS-WP-0001-T01 | done | — | workplans/POLICY-NEXUS-WP-0001-permanent-publication-surface.md |
|
| task | POLICY-NEXUS-WP-0001-T01 | done | — | workplans/POLICY-NEXUS-WP-0001-permanent-publication-surface.md |
|
||||||
| task | POLICY-NEXUS-WP-0001-T02 | done | — | workplans/POLICY-NEXUS-WP-0001-permanent-publication-surface.md |
|
| task | POLICY-NEXUS-WP-0001-T02 | done | — | workplans/POLICY-NEXUS-WP-0001-permanent-publication-surface.md |
|
||||||
| task | POLICY-NEXUS-WP-0001-T03 | progress | — | workplans/POLICY-NEXUS-WP-0001-permanent-publication-surface.md |
|
| task | POLICY-NEXUS-WP-0001-T03 | done | — | workplans/POLICY-NEXUS-WP-0001-permanent-publication-surface.md |
|
||||||
| task | POLICY-NEXUS-WP-0001-T04 | done | — | workplans/POLICY-NEXUS-WP-0001-permanent-publication-surface.md |
|
| task | POLICY-NEXUS-WP-0001-T04 | done | — | workplans/POLICY-NEXUS-WP-0001-permanent-publication-surface.md |
|
||||||
| task | POLICY-NEXUS-WP-0001-T05 | done | — | workplans/POLICY-NEXUS-WP-0001-permanent-publication-surface.md |
|
| task | POLICY-NEXUS-WP-0001-T05 | done | — | workplans/POLICY-NEXUS-WP-0001-permanent-publication-surface.md |
|
||||||
| task | POLICY-NEXUS-WP-0001-T06 | done | — | workplans/POLICY-NEXUS-WP-0001-permanent-publication-surface.md |
|
| task | POLICY-NEXUS-WP-0001-T06 | done | — | workplans/POLICY-NEXUS-WP-0001-permanent-publication-surface.md |
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,9 @@
|
||||||
"revision": "draft-8",
|
"revision": "draft-8",
|
||||||
"revision_path": "standards/tenancy-posture/v0.1/revisions/draft-8/index.html",
|
"revision_path": "standards/tenancy-posture/v0.1/revisions/draft-8/index.html",
|
||||||
"source_digest": "99f802d91a0b3a65f0dac58230d8904f7c61cf3f81eff072fbbc59b634612a8a",
|
"source_digest": "99f802d91a0b3a65f0dac58230d8904f7c61cf3f81eff072fbbc59b634612a8a",
|
||||||
"source_revision": "cced59d3aa1dc0aa08fc128fc8c76699f59dcd90",
|
"source_path": "canon/standards/tenancy-posture_v0.1.md",
|
||||||
|
"source_repo": "net-kingdom",
|
||||||
|
"source_revision": "f4f885289e196b6770fea5d509959e3031fd9295",
|
||||||
"status": "proposed",
|
"status": "proposed",
|
||||||
"title": "NetKingdom Tenancy Posture v0.1"
|
"title": "NetKingdom Tenancy Posture v0.1"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en"><meta charset="utf-8">
|
<html lang="en"><meta charset="utf-8">
|
||||||
<meta name="policy-source-revision" content="cced59d3aa1dc0aa08fc128fc8c76699f59dcd90">
|
<meta name="policy-source-revision" content="f4f885289e196b6770fea5d509959e3031fd9295">
|
||||||
<meta name="policy-source-digest" content="99f802d91a0b3a65f0dac58230d8904f7c61cf3f81eff072fbbc59b634612a8a">
|
<meta name="policy-source-digest" content="99f802d91a0b3a65f0dac58230d8904f7c61cf3f81eff072fbbc59b634612a8a">
|
||||||
<title>NetKingdom Tenancy Posture v0.1</title>
|
<title>NetKingdom Tenancy Posture v0.1</title>
|
||||||
<style>
|
<style>
|
||||||
|
|
@ -191,7 +191,7 @@ a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-off
|
||||||
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-tenancy-posture</span> <span class="stat">proposed · draft-8</span> <span>net-kingdom</span> <span>reviewed 2026-08-17</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom Tenancy Posture v0.1</h1><p class="sub">A framework for describing, holding and improving multi-tenancy — including where we are not there yet.</p><p class="sub">Source: <code>net-kingdom · canon/standards/tenancy-posture_v0.1.md · cced59d3aa1dc0aa08fc128fc8c76699f59dcd90</code></p><p class="sub">Review due: 2027-02-17</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#status"><span class="n">·</span>Status</a></li><li><a href="#s0"><span class="n">0</span>Terminology: axes, not planes</a></li><li><a href="#s1"><span class="n">1</span>Context</a></li><li><a href="#s2"><span class="n">2</span>What this document is</a></li><li><a href="#s3"><span class="n">3</span>Six orthogonal axes</a></li><li><a href="#s4"><span class="n">4</span>Graduated levels</a></li><li><a href="#s5"><span class="n">5</span>The posture vector</a></li><li><a href="#s6"><span class="n">6</span>Conformance is accuracy, not altitude</a></li><li><a href="#s7"><span class="n">7</span>Portability across placement levels</a></li><li><a href="#s8"><span class="n">8</span>Placement triggers</a></li><li><a href="#s9"><span class="n">9</span>Credentials as a tenancy control</a></li><li><a href="#s10"><span class="n">10</span>Blast radius must be published</a></li><li><a href="#s11"><span class="n">11</span>Commercial expression</a></li><li><a href="#s12"><span class="n">12</span>Methodology — analyze, establish, improve, guard</a></li><li><a href="#s13"><span class="n">13</span>Evidence per level</a></li><li><a href="#s14"><span class="n">14</span>Adoption stance — structure, not tooling</a></li><li><a href="#s15"><span class="n">15</span>Alternatives considered</a></li><li><a href="#s16"><span class="n">16</span>Held against outside practice</a></li><li><a href="#s17"><span class="n">17</span>Scaling demands</a></li><li><a href="#s18"><span class="n">18</span>Consequences</a></li><li><a href="#s19"><span class="n">19</span>Review resolutions and residual questions</a></li><li><a href="#s20"><span class="n">20</span>Ratification path</a></li></ol></nav><main><section id="status"><h2>Status</h2>
|
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-tenancy-posture</span> <span class="stat">proposed · draft-8</span> <span>net-kingdom</span> <span>reviewed 2026-08-17</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom Tenancy Posture v0.1</h1><p class="sub">A framework for describing, holding and improving multi-tenancy — including where we are not there yet.</p><p class="sub">Source: <code>net-kingdom · canon/standards/tenancy-posture_v0.1.md · f4f885289e196b6770fea5d509959e3031fd9295</code></p><p class="sub">Review due: 2027-02-17</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#status"><span class="n">·</span>Status</a></li><li><a href="#s0"><span class="n">0</span>Terminology: axes, not planes</a></li><li><a href="#s1"><span class="n">1</span>Context</a></li><li><a href="#s2"><span class="n">2</span>What this document is</a></li><li><a href="#s3"><span class="n">3</span>Six orthogonal axes</a></li><li><a href="#s4"><span class="n">4</span>Graduated levels</a></li><li><a href="#s5"><span class="n">5</span>The posture vector</a></li><li><a href="#s6"><span class="n">6</span>Conformance is accuracy, not altitude</a></li><li><a href="#s7"><span class="n">7</span>Portability across placement levels</a></li><li><a href="#s8"><span class="n">8</span>Placement triggers</a></li><li><a href="#s9"><span class="n">9</span>Credentials as a tenancy control</a></li><li><a href="#s10"><span class="n">10</span>Blast radius must be published</a></li><li><a href="#s11"><span class="n">11</span>Commercial expression</a></li><li><a href="#s12"><span class="n">12</span>Methodology — analyze, establish, improve, guard</a></li><li><a href="#s13"><span class="n">13</span>Evidence per level</a></li><li><a href="#s14"><span class="n">14</span>Adoption stance — structure, not tooling</a></li><li><a href="#s15"><span class="n">15</span>Alternatives considered</a></li><li><a href="#s16"><span class="n">16</span>Held against outside practice</a></li><li><a href="#s17"><span class="n">17</span>Scaling demands</a></li><li><a href="#s18"><span class="n">18</span>Consequences</a></li><li><a href="#s19"><span class="n">19</span>Review resolutions and residual questions</a></li><li><a href="#s20"><span class="n">20</span>Ratification path</a></li></ol></nav><main><section id="status"><h2>Status</h2>
|
||||||
<p><strong>Proposed, draft-8; ratification-ready.</strong> Relocated from <code>the-custodian/canon/architecture</code> 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.</p>
|
<p><strong>Proposed, draft-8; ratification-ready.</strong> Relocated from <code>the-custodian/canon/architecture</code> 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.</p>
|
||||||
<ul><li><strong>draft-1</strong> proposed a single model with fixed characteristics. Rejected: it could not describe a repo that is not there yet.</li><li><strong>draft-2</strong> reframed to graduated levels per axis. Externally corroborated (§16), but four of its statements were wrong and one thing it needed was missing.</li><li><strong>draft-3</strong> applied those corrections, added the retention axis, and recorded an adoption stance.</li><li><strong>draft-4</strong> closed the two gaps draft-3 left open: <code>R4</code> had no mechanism beyond waiting, and the noisy-neighbour evidence artifact asserted something shared infrastructure cannot provide.</li><li><strong>draft-5</strong> relocated to NetKingdom and renamed the dimensions from <em>planes</em> to <em>axes</em>, because the word was already taken (§0).</li><li><strong>draft-6</strong> applied <code>tenant-engine</code>'s review: five changes, including an axis that did not fit its data shape.</li><li><strong>draft-7</strong> applies <code>audit-core</code>, <code>railiance-platform</code> and <code>flex-auth</code>. Eleven further changes, two of them corrections to statements this document made as fact about other repos. <strong>Every posture I guessed was too generous, on every repo that has now self-reported.</strong></li><li><strong>draft-8</strong> applies <code>adaptive-pricing</code>'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.</li></ul>
|
<ul><li><strong>draft-1</strong> proposed a single model with fixed characteristics. Rejected: it could not describe a repo that is not there yet.</li><li><strong>draft-2</strong> reframed to graduated levels per axis. Externally corroborated (§16), but four of its statements were wrong and one thing it needed was missing.</li><li><strong>draft-3</strong> applied those corrections, added the retention axis, and recorded an adoption stance.</li><li><strong>draft-4</strong> closed the two gaps draft-3 left open: <code>R4</code> had no mechanism beyond waiting, and the noisy-neighbour evidence artifact asserted something shared infrastructure cannot provide.</li><li><strong>draft-5</strong> relocated to NetKingdom and renamed the dimensions from <em>planes</em> to <em>axes</em>, because the word was already taken (§0).</li><li><strong>draft-6</strong> applied <code>tenant-engine</code>'s review: five changes, including an axis that did not fit its data shape.</li><li><strong>draft-7</strong> applies <code>audit-core</code>, <code>railiance-platform</code> and <code>flex-auth</code>. Eleven further changes, two of them corrections to statements this document made as fact about other repos. <strong>Every posture I guessed was too generous, on every repo that has now self-reported.</strong></li><li><strong>draft-8</strong> applies <code>adaptive-pricing</code>'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.</li></ul>
|
||||||
<p><strong>Reviewed by all six. The score:</strong> six repos found three live defects in their own code by reading the ladders — <code>tenant-engine</code>'s unfiltered event accessor, <code>audit-core</code>'s unfiltered read path, <code>flex-auth</code>'s unauthenticated <code>/v1/check</code> — and <code>railiance-platform</code> found <code>apps-pg</code> 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.</p>
|
<p><strong>Reviewed by all six. The score:</strong> six repos found three live defects in their own code by reading the ladders — <code>tenant-engine</code>'s unfiltered event accessor, <code>audit-core</code>'s unfiltered read path, <code>flex-auth</code>'s unauthenticated <code>/v1/check</code> — and <code>railiance-platform</code> found <code>apps-pg</code> 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.</p>
|
||||||
|
|
@ -441,4 +441,4 @@ per consumer: 14 connections (12 runtime + 2 migration)</pre>
|
||||||
</section>
|
</section>
|
||||||
<section id="s20"><h2><span class="sn">20</span>Ratification path</h2>
|
<section id="s20"><h2><span class="sn">20</span>Ratification path</h2>
|
||||||
<ol><li>Reviewed by <code>tenant-engine</code>, <code>flex-auth</code>, <code>audit-core</code>, <code>rapp-postgres</code>, <code>railiance-platform</code> and <code>adaptive-pricing</code> against §19. <strong>Complete in draft-8.</strong></li><li>Each publishes its own posture vector (§5) as part of review. <strong>The framework is validated by whether it can describe them accurately</strong> — if a repo cannot express itself in these six ladders, the ladders are wrong and this document changes, not the repo. <strong>Complete in draft-8; all six root declarations validate against the canonical schema.</strong></li><li>On acceptance, <strong>supersedes</strong> the routing of <code>rapp-postgres/docs/canon-drafts/shared-platform-relational-storage_v0.1-draft.md</code>, whose §§3–8 are absorbed here. That draft is withdrawn rather than left pending.</li><li>On acceptance, <code>rapp-postgres</code> ADR-0001 through ADR-0004 move to <code>accepted</code> and are annotated as the PostgreSQL implementation of the E, P, R and shared-capacity rules.</li></ol>
|
<ol><li>Reviewed by <code>tenant-engine</code>, <code>flex-auth</code>, <code>audit-core</code>, <code>rapp-postgres</code>, <code>railiance-platform</code> and <code>adaptive-pricing</code> against §19. <strong>Complete in draft-8.</strong></li><li>Each publishes its own posture vector (§5) as part of review. <strong>The framework is validated by whether it can describe them accurately</strong> — if a repo cannot express itself in these six ladders, the ladders are wrong and this document changes, not the repo. <strong>Complete in draft-8; all six root declarations validate against the canonical schema.</strong></li><li>On acceptance, <strong>supersedes</strong> the routing of <code>rapp-postgres/docs/canon-drafts/shared-platform-relational-storage_v0.1-draft.md</code>, whose §§3–8 are absorbed here. That draft is withdrawn rather than left pending.</li><li>On acceptance, <code>rapp-postgres</code> ADR-0001 through ADR-0004 move to <code>accepted</code> and are annotated as the PostgreSQL implementation of the E, P, R and shared-capacity rules.</li></ol>
|
||||||
</section><footer><span>netkingdom-tenancy-posture · draft-8 · proposed</span><span>net-kingdom · canon/standards/tenancy-posture_v0.1.md · cced59d3aa1dc0aa08fc128fc8c76699f59dcd90</span></footer></main></div></div></html>
|
</section><footer><span>netkingdom-tenancy-posture · draft-8 · proposed</span><span>net-kingdom · canon/standards/tenancy-posture_v0.1.md · f4f885289e196b6770fea5d509959e3031fd9295</span></footer></main></div></div></html>
|
||||||
|
|
|
||||||
130
source-inventory.config.json
Normal file
130
source-inventory.config.json
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"repositories": {
|
||||||
|
"activity-core": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/activity-core.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"artifact-store": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/artifact-store.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"binect-js": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/binect-js.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"coulomb-loop": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/coulomb-loop.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"coulomb-social": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/coulomb-social.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"evidence-binder": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/evidence-binder.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"flex-auth": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/flex-auth.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"glas-harness": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/glas-harness.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"kaizen-agentic": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/kaizen-agentic.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"key-cape": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/key-cape.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"markitect-main": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/markitect-main.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"net-kingdom": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/net-kingdom.git",
|
||||||
|
"selectors": ["canon/standards/**", "docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"policy-nexus": {
|
||||||
|
"local": true,
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"railiance-hosts": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/railiance-hosts.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"railiance-infra": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/railiance-infra.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"railiance-master": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/railiance-master.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"railiance-platform": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/railiance-platform.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"rapp-postgres": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/rapp-postgres.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"rein-aharness": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/rein-aharness.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"target-revenue": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/target-revenue.git",
|
||||||
|
"selectors": ["docs/adr/*.md"]
|
||||||
|
},
|
||||||
|
"the-custodian": {
|
||||||
|
"branch": "main",
|
||||||
|
"remote": "https://forgejo.coulomb.social/coulomb/the-custodian.git",
|
||||||
|
"selectors": [
|
||||||
|
"canon/architecture/**",
|
||||||
|
"canon/constitution/**",
|
||||||
|
"canon/standards/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"excluded_scopes": [
|
||||||
|
{
|
||||||
|
"repository": "the-custodian",
|
||||||
|
"path": "canon/values",
|
||||||
|
"reason": "Out of publication scope until the canon owner marks an individual document as governing."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"repository": "the-custodian",
|
||||||
|
"path": "canon/tpsc",
|
||||||
|
"reason": "Out of publication scope until the canon owner marks an individual document as governing."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"repository": "the-custodian",
|
||||||
|
"path": "canon/projects",
|
||||||
|
"reason": "Project material is not governing canon for this publication surface."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
749
source-inventory.json
Normal file
749
source-inventory.json
Normal file
|
|
@ -0,0 +1,749 @@
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/adr-001-event-bridge-architecture.md",
|
||||||
|
"source_repo": "activity-core"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/adr-002-definition-format.md",
|
||||||
|
"source_repo": "activity-core"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/adr-003-rule-instruction-model.md",
|
||||||
|
"source_repo": "activity-core"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/adr-004-producer-trust-boundary.md",
|
||||||
|
"source_repo": "activity-core"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/adr-005-ops-runs-vs-dev-work-records.md",
|
||||||
|
"source_repo": "activity-core"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/0001-content-addressed-storage.md",
|
||||||
|
"source_repo": "artifact-store"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/0002-event-log-source-of-truth.md",
|
||||||
|
"source_repo": "artifact-store"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/0003-manifest-canonical-cbor.md",
|
||||||
|
"source_repo": "artifact-store"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/0004-control-plane-data-plane-contract.md",
|
||||||
|
"source_repo": "artifact-store"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/0005-v1-tech-stack.md",
|
||||||
|
"source_repo": "artifact-store"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/0006-oci-compatibility-reachable.md",
|
||||||
|
"source_repo": "artifact-store"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "excluded",
|
||||||
|
"reason": "Directory index, not an architecture decision record.",
|
||||||
|
"source_path": "docs/adr/README.md",
|
||||||
|
"source_repo": "artifact-store"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/001-no-listall-method.md",
|
||||||
|
"source_repo": "binect-js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-001-workplan-prefix.md",
|
||||||
|
"source_repo": "coulomb-loop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-002-customer-supplier-boundary.md",
|
||||||
|
"source_repo": "coulomb-loop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-003-cadence-ramp-policy.md",
|
||||||
|
"source_repo": "coulomb-loop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-004-repo-rotation-on-diminishing-returns.md",
|
||||||
|
"source_repo": "coulomb-loop"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0001-netkingdom-identity.md",
|
||||||
|
"source_repo": "coulomb-social"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0002-space-content-forgejo-markdown.md",
|
||||||
|
"source_repo": "coulomb-social"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0003-page-centric-markdown-sor.md",
|
||||||
|
"source_repo": "coulomb-social"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0004-content-plane-thin-git-upgrades.md",
|
||||||
|
"source_repo": "coulomb-social"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0001-reference-ui-surface.md",
|
||||||
|
"source_repo": "evidence-binder"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/0001-implementation-language-and-skeleton.md",
|
||||||
|
"source_repo": "flex-auth"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/0002-rego-in-markdown-policy-format.md",
|
||||||
|
"source_repo": "flex-auth"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/0003-topaz-aligned-mvp.md",
|
||||||
|
"source_repo": "flex-auth"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-001-rein-harness-family.md",
|
||||||
|
"source_repo": "glas-harness"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-002-credential-brokering-and-composable-reins.md",
|
||||||
|
"source_repo": "glas-harness"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-003-scheduling-and-blueprint-sourcing-stay-rein-local.md",
|
||||||
|
"source_repo": "glas-harness"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-004-composable-reins-stay-deferred.md",
|
||||||
|
"source_repo": "glas-harness"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-001-workplan-convention.md",
|
||||||
|
"source_repo": "kaizen-agentic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-002-project-memory-convention.md",
|
||||||
|
"source_repo": "kaizen-agentic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-003-protocols-artifact-convention.md",
|
||||||
|
"source_repo": "kaizen-agentic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-004-project-metrics-convention.md",
|
||||||
|
"source_repo": "kaizen-agentic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-005-scheduled-agent-execution.md",
|
||||||
|
"source_repo": "kaizen-agentic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-006-customer-engagement-convention.md",
|
||||||
|
"source_repo": "kaizen-agentic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-007-forward-deployed-engagement-convention.md",
|
||||||
|
"source_repo": "kaizen-agentic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0001-choose-go-for-keycape.md",
|
||||||
|
"source_repo": "key-cape"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-001-client-side-debug-storage.md",
|
||||||
|
"source_repo": "markitect-main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-002-robustness-principle-for-production-use.md",
|
||||||
|
"source_repo": "markitect-main"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/credential-management_v0.2.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/iam-profile_v0.2.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/iam-profile_v0.3.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/playbook-capability-contract_v0.1.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "published",
|
||||||
|
"reason": "Published through an explicit publication.json document entry.",
|
||||||
|
"source_path": "canon/standards/tenancy-posture_v0.1.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/tenant-engine-boundary-contract_v0.1.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/user-engine-boundary-contract_v0.1.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0007-security-orchestration-boundary.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0008-object-storage-sts-credential-vending.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0010-orchestration-vs-dependency-self-coherent-intent.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0011-iam-profile-ownership-and-version-governance.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0012-playbook-capability-contract-ownership.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0015-netkingdom-railiance-workload-packaging-and-relational-platform.md",
|
||||||
|
"source_repo": "net-kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0001-addressing-and-permanence.md",
|
||||||
|
"source_repo": "policy-nexus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-002-repo-boundary-hosts-vs-bootstrap.md",
|
||||||
|
"source_repo": "railiance-hosts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-003-railiance-5repo-stack-architecture.md",
|
||||||
|
"source_repo": "railiance-hosts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-004-forgejo-in-cluster-actions-runner.md",
|
||||||
|
"source_repo": "railiance-hosts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-002-repo-boundary-hosts-vs-bootstrap.md",
|
||||||
|
"source_repo": "railiance-infra"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-003-railiance-5repo-stack-architecture.md",
|
||||||
|
"source_repo": "railiance-infra"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-004-forgejo-in-cluster-actions-runner.md",
|
||||||
|
"source_repo": "railiance-infra"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-005-k3s-api-tunnel-only.md",
|
||||||
|
"source_repo": "railiance-infra"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0001-repository-prefix-architecture.md",
|
||||||
|
"source_repo": "railiance-master"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0002-rail-kubernetes-wave-1-boundary.md",
|
||||||
|
"source_repo": "railiance-master"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0003-rapp-first-wave-selection.md",
|
||||||
|
"source_repo": "railiance-master"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0004-first-wave-reef-rollout.md",
|
||||||
|
"source_repo": "railiance-master"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0005-derived-rail-composition.md",
|
||||||
|
"source_repo": "railiance-master"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0006-reef-production-admission.md",
|
||||||
|
"source_repo": "railiance-master"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0007-rapp-declaration-contract.md",
|
||||||
|
"source_repo": "railiance-master"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0008-private-by-default-exposure.md",
|
||||||
|
"source_repo": "railiance-master"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0001-s3-platform-service-boundary.md",
|
||||||
|
"source_repo": "railiance-platform"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0002-placement-policy-ownership.md",
|
||||||
|
"source_repo": "railiance-platform"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0003-decisions-live-in-the-repo.md",
|
||||||
|
"source_repo": "railiance-platform"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "excluded",
|
||||||
|
"reason": "Directory index, not an architecture decision record.",
|
||||||
|
"source_path": "docs/adr/README.md",
|
||||||
|
"source_repo": "railiance-platform"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0001-consumer-boundary-and-tenant-isolation.md",
|
||||||
|
"source_repo": "rapp-postgres"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0002-data-retention-and-erasure.md",
|
||||||
|
"source_repo": "rapp-postgres"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0003-e3-row-level-security-contract.md",
|
||||||
|
"source_repo": "rapp-postgres"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0004-platform-pg-cell-ceiling.md",
|
||||||
|
"source_repo": "rapp-postgres"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-001-agent-harness-architecture.md",
|
||||||
|
"source_repo": "rein-aharness"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0001-stage0-library-stack.md",
|
||||||
|
"source_repo": "target-revenue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "docs/adr/ADR-0002-hosted-trust-service-stack.md",
|
||||||
|
"source_repo": "target-revenue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/architecture/adr-001-workplans-as-repo-artefacts.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/architecture/adr-002-custodian-agent-runtime-design.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/architecture/adr-003-materialized-derived-state.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/architecture/adr-004-connectivity-first-network-posture.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/architecture/adr-005-cross-repo-workplans-project-repos.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/architecture/adr-006-canon-federation-concept-ownership.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/architecture/adr-007-workplan-identity-and-repo-worker-topology.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/architecture/adr-008-multi-tenancy-model.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/architecture/adr-010-hub-authority-and-local-cache-model.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/architecture/adr-011-federated-namespaces-and-reconciliation-limits.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/constitution/bootstrap-protocol_v0.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/constitution/custodian_constitution_v0.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/autonomy-lanes_v0.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/business-app-service-contract_v0.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/contrib-templates/br-template.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/contrib-templates/ep-template.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/contrib-templates/fr-template.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/contrib-templates/upr-template.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/contribution-convention_v0.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/coulombcore-production-freeze_v0.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/credential-management_v0.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/federated-organization-standard_v1.0.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/iam-profile_v0.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/orthogonal-architecture-schema_v1.0.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/orthogonal-architecture_v1.0.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/privileged-execution-control-schema-cicd_v0.2.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/privileged-execution-control-schema-kubernetes-rbac_v0.2.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/privileged-execution-control-schema-os-sudo_v0.2.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/privileged-execution-control-schema_v0.2.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "unsupported-format",
|
||||||
|
"reason": "Inventoried governing source is not Markdown and has no renderer yet.",
|
||||||
|
"source_path": "canon/standards/privileged-execution-control_v0.2",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/project-repository-flavor_v0.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/repo-classification-standard_v1.0.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "unsupported-format",
|
||||||
|
"reason": "Inventoried governing source is not Markdown and has no renderer yet.",
|
||||||
|
"source_path": "canon/standards/repo-classification.allowed.yaml",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "unsupported-format",
|
||||||
|
"reason": "Inventoried governing source is not Markdown and has no renderer yet.",
|
||||||
|
"source_path": "canon/standards/repo-classification.exclusions.yaml",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/sbom-convention_v0.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "unsupported-format",
|
||||||
|
"reason": "Inventoried governing source is not Markdown and has no renderer yet.",
|
||||||
|
"source_path": "canon/standards/schemas/work-records/decision.schema.json",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "unsupported-format",
|
||||||
|
"reason": "Inventoried governing source is not Markdown and has no renderer yet.",
|
||||||
|
"source_path": "canon/standards/schemas/work-records/engagement.schema.json",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "unsupported-format",
|
||||||
|
"reason": "Inventoried governing source is not Markdown and has no renderer yet.",
|
||||||
|
"source_path": "canon/standards/schemas/work-records/intake.schema.json",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "unsupported-format",
|
||||||
|
"reason": "Inventoried governing source is not Markdown and has no renderer yet.",
|
||||||
|
"source_path": "canon/standards/schemas/work-records/spine.schema.json",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "unsupported-format",
|
||||||
|
"reason": "Inventoried governing source is not Markdown and has no renderer yet.",
|
||||||
|
"source_path": "canon/standards/work-record-types.yaml",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/work-record-types_v0.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disposition": "metadata-pending",
|
||||||
|
"reason": "In scope; awaits explicit publication addressing and owner/revision/review metadata.",
|
||||||
|
"source_path": "canon/standards/workplan-terminology-fleet_v0.1.md",
|
||||||
|
"source_repo": "the-custodian"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -46,6 +46,8 @@ def _release(root: Path) -> Path:
|
||||||
"review_due": "2027-02-18",
|
"review_due": "2027-02-18",
|
||||||
"canonical_path": "standards/example/v1/index.html",
|
"canonical_path": "standards/example/v1/index.html",
|
||||||
"revision_path": "standards/example/v1/revisions/draft-1/index.html",
|
"revision_path": "standards/example/v1/revisions/draft-1/index.html",
|
||||||
|
"source_repo": "canon",
|
||||||
|
"source_path": "canon/standards/example.md",
|
||||||
"source_revision": COMMIT,
|
"source_revision": COMMIT,
|
||||||
"source_digest": SOURCE_DIGEST,
|
"source_digest": SOURCE_DIGEST,
|
||||||
}
|
}
|
||||||
|
|
@ -54,10 +56,38 @@ def _release(root: Path) -> Path:
|
||||||
(build / "publication-manifest.json").write_text(
|
(build / "publication-manifest.json").write_text(
|
||||||
json.dumps(manifest, sort_keys=True) + "\n", encoding="utf-8"
|
json.dumps(manifest, sort_keys=True) + "\n", encoding="utf-8"
|
||||||
)
|
)
|
||||||
|
(build / "source-inventory.json").write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"schema_version": "policy-nexus-source-inventory/v1",
|
||||||
|
"source_set_digest": "3" * 64,
|
||||||
|
"repositories": [
|
||||||
|
{"name": "canon", "revision": COMMIT, "source_count": 1}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"source_repo": "canon",
|
||||||
|
"source_path": "canon/standards/example.md",
|
||||||
|
"disposition": "published",
|
||||||
|
"reason": "test",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
sort_keys=True,
|
||||||
|
)
|
||||||
|
+ "\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
return build
|
return build
|
||||||
|
|
||||||
|
|
||||||
class ReleaseVerificationTest(unittest.TestCase):
|
class ReleaseVerificationTest(unittest.TestCase):
|
||||||
|
def test_release_pipeline_does_not_delete_immutable_revision_tree(self) -> None:
|
||||||
|
makefile = (ROOT / "Makefile").read_text(encoding="utf-8")
|
||||||
|
containerfile = (ROOT / "Containerfile").read_text(encoding="utf-8")
|
||||||
|
self.assertIn("release-build: build release-check", makefile)
|
||||||
|
self.assertNotIn("RUN rm -rf build", containerfile)
|
||||||
|
|
||||||
def test_accepts_clean_provenance_and_returns_manifest_digest(self) -> None:
|
def test_accepts_clean_provenance_and_returns_manifest_digest(self) -> None:
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
build = _release(Path(directory))
|
build = _release(Path(directory))
|
||||||
|
|
@ -67,6 +97,7 @@ class ReleaseVerificationTest(unittest.TestCase):
|
||||||
).hexdigest()
|
).hexdigest()
|
||||||
self.assertEqual(expected, evidence["publication_manifest_digest"])
|
self.assertEqual(expected, evidence["publication_manifest_digest"])
|
||||||
self.assertEqual(["example"], evidence["documents"])
|
self.assertEqual(["example"], evidence["documents"])
|
||||||
|
self.assertEqual("3" * 64, evidence["source_set_digest"])
|
||||||
|
|
||||||
def test_rejects_working_tree_source_revision(self) -> None:
|
def test_rejects_working_tree_source_revision(self) -> None:
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
|
|
||||||
142
tests/test_source_inventory.py
Normal file
142
tests/test_source_inventory.py
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).parents[1]
|
||||||
|
sys.path.insert(0, str(ROOT / "tools"))
|
||||||
|
from source_inventory import check, refresh
|
||||||
|
|
||||||
|
|
||||||
|
REVISION = "a" * 40
|
||||||
|
|
||||||
|
|
||||||
|
def _fixture(root: Path) -> tuple[Path, Path, Path, Path]:
|
||||||
|
source = root / "docs/adr/ADR-0001.md"
|
||||||
|
source.parent.mkdir(parents=True)
|
||||||
|
source.write_text("# ADR-0001\n", encoding="utf-8")
|
||||||
|
config = root / "source-inventory.config.json"
|
||||||
|
config.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"repositories": {
|
||||||
|
"policy-nexus": {
|
||||||
|
"local": True,
|
||||||
|
"selectors": ["docs/adr/*.md"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
publication = root / "publication.json"
|
||||||
|
publication.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"documents": [
|
||||||
|
{
|
||||||
|
"source_repo": "policy-nexus",
|
||||||
|
"source_path": "docs/adr/ADR-0001.md",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
revisions = {"policy-nexus": REVISION}
|
||||||
|
digest = hashlib.sha256(
|
||||||
|
json.dumps(revisions, sort_keys=True, separators=(",", ":")).encode()
|
||||||
|
).hexdigest()
|
||||||
|
lock = root / "source-lock.json"
|
||||||
|
lock.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"source_set_digest": digest,
|
||||||
|
"repositories": {"policy-nexus": {"revision": REVISION}},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
return config, root / "source-inventory.json", publication, lock
|
||||||
|
|
||||||
|
|
||||||
|
class SourceInventoryTest(unittest.TestCase):
|
||||||
|
def test_refresh_and_check_bind_publication_to_inventory(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
config, inventory, publication, lock = _fixture(root)
|
||||||
|
refresh(
|
||||||
|
config,
|
||||||
|
inventory,
|
||||||
|
publication,
|
||||||
|
policy_root=root,
|
||||||
|
source_root=root.parent,
|
||||||
|
)
|
||||||
|
report = check(
|
||||||
|
config,
|
||||||
|
inventory,
|
||||||
|
publication,
|
||||||
|
policy_root=root,
|
||||||
|
source_root=root.parent,
|
||||||
|
lock_path=lock,
|
||||||
|
)
|
||||||
|
self.assertEqual(1, report["dispositions"]["published"])
|
||||||
|
self.assertEqual(REVISION, report["repositories"][0]["revision"])
|
||||||
|
|
||||||
|
def test_check_rejects_unreviewed_new_source(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
config, inventory, publication, lock = _fixture(root)
|
||||||
|
refresh(
|
||||||
|
config,
|
||||||
|
inventory,
|
||||||
|
publication,
|
||||||
|
policy_root=root,
|
||||||
|
source_root=root.parent,
|
||||||
|
)
|
||||||
|
(root / "docs/adr/ADR-0002.md").write_text("# New\n", encoding="utf-8")
|
||||||
|
with self.assertRaisesRegex(ValueError, "unreviewed sources"):
|
||||||
|
check(
|
||||||
|
config,
|
||||||
|
inventory,
|
||||||
|
publication,
|
||||||
|
policy_root=root,
|
||||||
|
source_root=root.parent,
|
||||||
|
lock_path=lock,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_check_rejects_published_disposition_drift(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
config, inventory, publication, lock = _fixture(root)
|
||||||
|
refresh(
|
||||||
|
config,
|
||||||
|
inventory,
|
||||||
|
publication,
|
||||||
|
policy_root=root,
|
||||||
|
source_root=root.parent,
|
||||||
|
)
|
||||||
|
value = json.loads(inventory.read_text(encoding="utf-8"))
|
||||||
|
value["sources"][0]["disposition"] = "metadata-pending"
|
||||||
|
inventory.write_text(json.dumps(value), encoding="utf-8")
|
||||||
|
with self.assertRaisesRegex(ValueError, "must exactly match"):
|
||||||
|
check(
|
||||||
|
config,
|
||||||
|
inventory,
|
||||||
|
publication,
|
||||||
|
policy_root=root,
|
||||||
|
source_root=root.parent,
|
||||||
|
lock_path=lock,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
@ -149,8 +149,13 @@ def build(
|
||||||
manifest_path = manifest_path.resolve()
|
manifest_path = manifest_path.resolve()
|
||||||
manifest = load_manifest(manifest_path)
|
manifest = load_manifest(manifest_path)
|
||||||
as_of = as_of or dt.date.today()
|
as_of = as_of or dt.date.today()
|
||||||
|
source_root = os.environ.get("POLICY_NEXUS_SOURCE_ROOT")
|
||||||
repository_paths = {
|
repository_paths = {
|
||||||
name: (manifest_path.parent / config["path"]).resolve()
|
name: (
|
||||||
|
(Path(source_root) / name).resolve()
|
||||||
|
if source_root
|
||||||
|
else (manifest_path.parent / config["path"]).resolve()
|
||||||
|
)
|
||||||
for name, config in manifest["repositories"].items()
|
for name, config in manifest["repositories"].items()
|
||||||
}
|
}
|
||||||
output_parent = output.resolve().parent
|
output_parent = output.resolve().parent
|
||||||
|
|
@ -255,6 +260,8 @@ def build(
|
||||||
"lifecycle": lifecycle,
|
"lifecycle": lifecycle,
|
||||||
"canonical_path": canonical.as_posix(),
|
"canonical_path": canonical.as_posix(),
|
||||||
"revision_path": revision_path.as_posix(),
|
"revision_path": revision_path.as_posix(),
|
||||||
|
"source_repo": document["source_repo"],
|
||||||
|
"source_path": document["source_path"],
|
||||||
"source_revision": source_revision,
|
"source_revision": source_revision,
|
||||||
"source_digest": source_digest,
|
"source_digest": source_digest,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
143
tools/fetch_sources.py
Normal file
143
tools/fetch_sources.py
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Fetch exact Forgejo source archives declared by the policy source inventory."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from pathlib import Path, PurePosixPath
|
||||||
|
import subprocess
|
||||||
|
import tarfile
|
||||||
|
import tempfile
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
|
def _revision(remote: str, branch: str) -> str:
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "ls-remote", remote, f"refs/heads/{branch}"],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
revision = result.stdout.split(maxsplit=1)[0] if result.stdout.strip() else ""
|
||||||
|
if len(revision) != 40 or any(char not in "0123456789abcdef" for char in revision):
|
||||||
|
raise ValueError(f"{remote}: could not resolve a clean 40-hex {branch} revision")
|
||||||
|
return revision
|
||||||
|
|
||||||
|
|
||||||
|
def _archive_url(remote: str, revision: str) -> str:
|
||||||
|
if not remote.startswith("https://") or not remote.endswith(".git"):
|
||||||
|
raise ValueError(f"archive source must be an HTTPS .git URL, got {remote!r}")
|
||||||
|
return f"{remote[:-4]}/archive/{revision[:7]}.tar.gz"
|
||||||
|
|
||||||
|
|
||||||
|
def _extract(archive: Path, target: Path) -> None:
|
||||||
|
target.mkdir(parents=True, exist_ok=False)
|
||||||
|
with tarfile.open(archive, "r:gz") as bundle:
|
||||||
|
members = bundle.getmembers()
|
||||||
|
roots = {
|
||||||
|
PurePosixPath(member.name).parts[0]
|
||||||
|
for member in members
|
||||||
|
if PurePosixPath(member.name).parts
|
||||||
|
}
|
||||||
|
if len(roots) != 1:
|
||||||
|
raise ValueError(f"{archive}: expected exactly one archive root")
|
||||||
|
root = next(iter(roots))
|
||||||
|
for member in members:
|
||||||
|
path = PurePosixPath(member.name)
|
||||||
|
if not path.parts or path.parts[0] != root:
|
||||||
|
raise ValueError(f"{archive}: inconsistent archive root")
|
||||||
|
relative = PurePosixPath(*path.parts[1:])
|
||||||
|
if not relative.parts:
|
||||||
|
continue
|
||||||
|
if relative.is_absolute() or ".." in relative.parts:
|
||||||
|
raise ValueError(f"{archive}: unsafe member {member.name!r}")
|
||||||
|
destination = target.joinpath(*relative.parts)
|
||||||
|
if member.isdir():
|
||||||
|
destination.mkdir(parents=True, exist_ok=True)
|
||||||
|
continue
|
||||||
|
if not member.isfile():
|
||||||
|
# Links and special files are not part of the publishable source
|
||||||
|
# corpus. Skipping them avoids materializing archive links; if a
|
||||||
|
# selector ever names one, the inventory audit fails on absence.
|
||||||
|
continue
|
||||||
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
source = bundle.extractfile(member)
|
||||||
|
if source is None:
|
||||||
|
raise ValueError(f"{archive}: could not read {member.name!r}")
|
||||||
|
with destination.open("wb") as output:
|
||||||
|
while chunk := source.read(1024 * 1024):
|
||||||
|
output.write(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
def fetch(config_path: Path, destination: Path, policy_revision: str) -> dict[str, object]:
|
||||||
|
if len(policy_revision) != 40 or any(
|
||||||
|
char not in "0123456789abcdef" for char in policy_revision
|
||||||
|
):
|
||||||
|
raise ValueError("--policy-revision must be a clean 40-hex Git commit")
|
||||||
|
config = json.loads(config_path.read_text(encoding="utf-8"))
|
||||||
|
if config.get("schema_version") != 1:
|
||||||
|
raise ValueError("source inventory config schema_version must be 1")
|
||||||
|
destination.mkdir(parents=True, exist_ok=True)
|
||||||
|
if any(destination.iterdir()):
|
||||||
|
raise ValueError(f"destination must be empty: {destination}")
|
||||||
|
|
||||||
|
revisions: dict[str, str] = {}
|
||||||
|
repositories: dict[str, dict[str, str]] = {}
|
||||||
|
for name, repository in sorted(config["repositories"].items()):
|
||||||
|
if repository.get("local"):
|
||||||
|
revision = policy_revision
|
||||||
|
repositories[name] = {"revision": revision, "source": "workflow-checkout"}
|
||||||
|
revisions[name] = revision
|
||||||
|
continue
|
||||||
|
remote = repository["remote"]
|
||||||
|
branch = repository.get("branch", "main")
|
||||||
|
revision = _revision(remote, branch)
|
||||||
|
url = _archive_url(remote, revision)
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".tar.gz") as archive:
|
||||||
|
with urllib.request.urlopen(url, timeout=60) as response:
|
||||||
|
while chunk := response.read(1024 * 1024):
|
||||||
|
archive.write(chunk)
|
||||||
|
archive.flush()
|
||||||
|
_extract(Path(archive.name), destination / name)
|
||||||
|
revisions[name] = revision
|
||||||
|
repositories[name] = {
|
||||||
|
"revision": revision,
|
||||||
|
"remote": remote,
|
||||||
|
"branch": branch,
|
||||||
|
}
|
||||||
|
|
||||||
|
canonical = json.dumps(revisions, sort_keys=True, separators=(",", ":")).encode()
|
||||||
|
lock: dict[str, object] = {
|
||||||
|
"schema_version": 1,
|
||||||
|
"source_set_digest": hashlib.sha256(canonical).hexdigest(),
|
||||||
|
"repositories": repositories,
|
||||||
|
}
|
||||||
|
(destination / "source-lock.json").write_text(
|
||||||
|
json.dumps(lock, indent=2, sort_keys=True) + "\n", encoding="utf-8"
|
||||||
|
)
|
||||||
|
return lock
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--config", type=Path, default=Path("source-inventory.config.json"))
|
||||||
|
parser.add_argument("--destination", type=Path, required=True)
|
||||||
|
parser.add_argument("--policy-revision", required=True)
|
||||||
|
args = parser.parse_args()
|
||||||
|
lock = fetch(args.config.resolve(), args.destination.resolve(), args.policy_revision)
|
||||||
|
print(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"source_set_digest": lock["source_set_digest"],
|
||||||
|
"repositories": len(lock["repositories"]),
|
||||||
|
},
|
||||||
|
sort_keys=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
295
tools/source_inventory.py
Normal file
295
tools/source_inventory.py
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Audit the explicit canon/ADR corpus and emit deterministic source evidence."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import fnmatch
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA_VERSION = "policy-nexus-source-inventory/v1"
|
||||||
|
DISPOSITIONS = {"published", "metadata-pending", "excluded", "unsupported-format"}
|
||||||
|
|
||||||
|
|
||||||
|
def _read_json(path: Path) -> dict[str, Any]:
|
||||||
|
value = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
if not isinstance(value, dict):
|
||||||
|
raise ValueError(f"{path}: expected a JSON object")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _tracked_files(repo: Path) -> list[str]:
|
||||||
|
if not repo.is_dir():
|
||||||
|
raise FileNotFoundError(f"source repository is unavailable: {repo}")
|
||||||
|
if (repo / ".git").exists():
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "-C", str(repo), "ls-tree", "-r", "--name-only", "HEAD"],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
return [line for line in result.stdout.splitlines() if line]
|
||||||
|
return sorted(
|
||||||
|
path.relative_to(repo).as_posix()
|
||||||
|
for path in repo.rglob("*")
|
||||||
|
if path.is_file() and not path.is_symlink()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _revision(repo: Path) -> str:
|
||||||
|
if not (repo / ".git").exists():
|
||||||
|
raise ValueError(f"{repo}: source lock is required for an archive checkout")
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "-C", str(repo), "rev-parse", "HEAD"],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
revision = result.stdout.strip()
|
||||||
|
if len(revision) != 40 or any(char not in "0123456789abcdef" for char in revision):
|
||||||
|
raise ValueError(f"{repo}: invalid Git revision {revision!r}")
|
||||||
|
return revision
|
||||||
|
|
||||||
|
|
||||||
|
def _repo_paths(
|
||||||
|
config: dict[str, Any], *, policy_root: Path, source_root: Path
|
||||||
|
) -> dict[str, Path]:
|
||||||
|
paths: dict[str, Path] = {}
|
||||||
|
for name, repository in config["repositories"].items():
|
||||||
|
paths[name] = policy_root if repository.get("local") else source_root / name
|
||||||
|
return paths
|
||||||
|
|
||||||
|
|
||||||
|
def discover(
|
||||||
|
config: dict[str, Any], *, policy_root: Path, source_root: Path
|
||||||
|
) -> tuple[list[dict[str, str]], dict[str, Path]]:
|
||||||
|
paths = _repo_paths(config, policy_root=policy_root, source_root=source_root)
|
||||||
|
sources: list[dict[str, str]] = []
|
||||||
|
for name, repository in sorted(config["repositories"].items()):
|
||||||
|
selectors = repository.get("selectors", [])
|
||||||
|
if not selectors:
|
||||||
|
raise ValueError(f"{name}: at least one source selector is required")
|
||||||
|
for path in _tracked_files(paths[name]):
|
||||||
|
if any(fnmatch.fnmatchcase(path, selector) for selector in selectors):
|
||||||
|
sources.append({"source_repo": name, "source_path": path})
|
||||||
|
return sources, paths
|
||||||
|
|
||||||
|
|
||||||
|
def _published_sources(publication_path: Path) -> set[tuple[str, str]]:
|
||||||
|
publication = _read_json(publication_path)
|
||||||
|
return {
|
||||||
|
(document["source_repo"], document["source_path"])
|
||||||
|
for document in publication.get("documents", [])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _new_entry(source: dict[str, str], published: set[tuple[str, str]]) -> dict[str, str]:
|
||||||
|
key = (source["source_repo"], source["source_path"])
|
||||||
|
path = Path(source["source_path"])
|
||||||
|
if key in published:
|
||||||
|
disposition = "published"
|
||||||
|
reason = "Published through an explicit publication.json document entry."
|
||||||
|
elif path.name.lower() == "readme.md":
|
||||||
|
disposition = "excluded"
|
||||||
|
reason = "Directory index, not an architecture decision record."
|
||||||
|
elif path.suffix.lower() != ".md":
|
||||||
|
disposition = "unsupported-format"
|
||||||
|
reason = "Inventoried governing source is not Markdown and has no renderer yet."
|
||||||
|
else:
|
||||||
|
disposition = "metadata-pending"
|
||||||
|
reason = (
|
||||||
|
"In scope; awaits explicit publication addressing and owner/revision/review metadata."
|
||||||
|
)
|
||||||
|
return source | {"disposition": disposition, "reason": reason}
|
||||||
|
|
||||||
|
|
||||||
|
def refresh(
|
||||||
|
config_path: Path,
|
||||||
|
inventory_path: Path,
|
||||||
|
publication_path: Path,
|
||||||
|
*,
|
||||||
|
policy_root: Path,
|
||||||
|
source_root: Path,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
config = _read_json(config_path)
|
||||||
|
if config.get("schema_version") != 1:
|
||||||
|
raise ValueError("source inventory config schema_version must be 1")
|
||||||
|
discovered, _paths = discover(config, policy_root=policy_root, source_root=source_root)
|
||||||
|
published = _published_sources(publication_path)
|
||||||
|
existing: dict[tuple[str, str], dict[str, str]] = {}
|
||||||
|
if inventory_path.exists():
|
||||||
|
for source in _read_json(inventory_path).get("sources", []):
|
||||||
|
existing[(source["source_repo"], source["source_path"])] = source
|
||||||
|
sources = []
|
||||||
|
for source in discovered:
|
||||||
|
key = (source["source_repo"], source["source_path"])
|
||||||
|
sources.append(existing.get(key, _new_entry(source, published)))
|
||||||
|
inventory = {
|
||||||
|
"schema_version": 1,
|
||||||
|
"sources": sources,
|
||||||
|
}
|
||||||
|
inventory_path.write_text(
|
||||||
|
json.dumps(inventory, indent=2, sort_keys=True) + "\n", encoding="utf-8"
|
||||||
|
)
|
||||||
|
return inventory
|
||||||
|
|
||||||
|
|
||||||
|
def _load_revisions(
|
||||||
|
config: dict[str, Any], paths: dict[str, Path], lock_path: Path | None
|
||||||
|
) -> tuple[dict[str, str], str]:
|
||||||
|
if lock_path:
|
||||||
|
lock = _read_json(lock_path)
|
||||||
|
if lock.get("schema_version") != 1:
|
||||||
|
raise ValueError("source lock schema_version must be 1")
|
||||||
|
repositories = lock.get("repositories", {})
|
||||||
|
revisions = {
|
||||||
|
name: repositories[name]["revision"] for name in config["repositories"]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
revisions = {name: _revision(paths[name]) for name in config["repositories"]}
|
||||||
|
for name, revision in revisions.items():
|
||||||
|
if len(revision) != 40 or any(char not in "0123456789abcdef" for char in revision):
|
||||||
|
raise ValueError(f"{name}: invalid locked revision {revision!r}")
|
||||||
|
canonical = json.dumps(revisions, sort_keys=True, separators=(",", ":")).encode()
|
||||||
|
digest = hashlib.sha256(canonical).hexdigest()
|
||||||
|
if lock_path:
|
||||||
|
locked_digest = lock.get("source_set_digest")
|
||||||
|
if locked_digest != digest:
|
||||||
|
raise ValueError(
|
||||||
|
f"source lock digest mismatch: recorded {locked_digest!r}, computed {digest}"
|
||||||
|
)
|
||||||
|
return revisions, digest
|
||||||
|
|
||||||
|
|
||||||
|
def check(
|
||||||
|
config_path: Path,
|
||||||
|
inventory_path: Path,
|
||||||
|
publication_path: Path,
|
||||||
|
*,
|
||||||
|
policy_root: Path,
|
||||||
|
source_root: Path,
|
||||||
|
lock_path: Path | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
config = _read_json(config_path)
|
||||||
|
inventory = _read_json(inventory_path)
|
||||||
|
if config.get("schema_version") != 1 or inventory.get("schema_version") != 1:
|
||||||
|
raise ValueError("source inventory config and inventory schema_version must be 1")
|
||||||
|
discovered, paths = discover(config, policy_root=policy_root, source_root=source_root)
|
||||||
|
discovered_keys = {
|
||||||
|
(source["source_repo"], source["source_path"]) for source in discovered
|
||||||
|
}
|
||||||
|
entries = inventory.get("sources", [])
|
||||||
|
inventory_keys: set[tuple[str, str]] = set()
|
||||||
|
for source in entries:
|
||||||
|
key = (source.get("source_repo", ""), source.get("source_path", ""))
|
||||||
|
if key in inventory_keys:
|
||||||
|
raise ValueError(f"duplicate source inventory entry: {key[0]}/{key[1]}")
|
||||||
|
inventory_keys.add(key)
|
||||||
|
if source.get("disposition") not in DISPOSITIONS:
|
||||||
|
raise ValueError(f"{key[0]}/{key[1]}: invalid disposition")
|
||||||
|
if not source.get("reason"):
|
||||||
|
raise ValueError(f"{key[0]}/{key[1]}: disposition reason is required")
|
||||||
|
missing = sorted(discovered_keys - inventory_keys)
|
||||||
|
stale = sorted(inventory_keys - discovered_keys)
|
||||||
|
if missing or stale:
|
||||||
|
details = []
|
||||||
|
if missing:
|
||||||
|
details.append("unreviewed sources: " + ", ".join(f"{r}/{p}" for r, p in missing))
|
||||||
|
if stale:
|
||||||
|
details.append("inventory entries no longer present: " + ", ".join(f"{r}/{p}" for r, p in stale))
|
||||||
|
raise ValueError("; ".join(details) + "; run source_inventory.py refresh and review the diff")
|
||||||
|
|
||||||
|
published = _published_sources(publication_path)
|
||||||
|
inventory_published = {
|
||||||
|
(source["source_repo"], source["source_path"])
|
||||||
|
for source in entries
|
||||||
|
if source["disposition"] == "published"
|
||||||
|
}
|
||||||
|
if published != inventory_published:
|
||||||
|
raise ValueError(
|
||||||
|
"published source inventory must exactly match publication.json: "
|
||||||
|
f"manifest_only={sorted(published - inventory_published)}, "
|
||||||
|
f"inventory_only={sorted(inventory_published - published)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
revisions, source_set_digest = _load_revisions(config, paths, lock_path)
|
||||||
|
counts = {disposition: 0 for disposition in sorted(DISPOSITIONS)}
|
||||||
|
repository_counts = {name: 0 for name in config["repositories"]}
|
||||||
|
for source in entries:
|
||||||
|
counts[source["disposition"]] += 1
|
||||||
|
repository_counts[source["source_repo"]] += 1
|
||||||
|
return {
|
||||||
|
"schema_version": SCHEMA_VERSION,
|
||||||
|
"source_set_digest": source_set_digest,
|
||||||
|
"dispositions": counts,
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"name": name,
|
||||||
|
"revision": revisions[name],
|
||||||
|
"source_count": repository_counts[name],
|
||||||
|
}
|
||||||
|
for name in sorted(config["repositories"])
|
||||||
|
],
|
||||||
|
"excluded_scopes": config.get("excluded_scopes", []),
|
||||||
|
"sources": entries,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("command", choices=("check", "refresh"))
|
||||||
|
parser.add_argument("--config", type=Path, default=Path("source-inventory.config.json"))
|
||||||
|
parser.add_argument("--inventory", type=Path, default=Path("source-inventory.json"))
|
||||||
|
parser.add_argument("--publication", type=Path, default=Path("publication.json"))
|
||||||
|
parser.add_argument("--policy-root", type=Path)
|
||||||
|
parser.add_argument("--source-root", type=Path)
|
||||||
|
parser.add_argument("--lock", type=Path)
|
||||||
|
parser.add_argument("--report", type=Path)
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
config_path = args.config.resolve()
|
||||||
|
policy_root = (args.policy_root or config_path.parent).resolve()
|
||||||
|
source_root = (args.source_root or policy_root.parent).resolve()
|
||||||
|
try:
|
||||||
|
if args.command == "refresh":
|
||||||
|
inventory = refresh(
|
||||||
|
config_path,
|
||||||
|
args.inventory.resolve(),
|
||||||
|
args.publication.resolve(),
|
||||||
|
policy_root=policy_root,
|
||||||
|
source_root=source_root,
|
||||||
|
)
|
||||||
|
print(f"{args.inventory}: recorded {len(inventory['sources'])} source(s)")
|
||||||
|
return 0
|
||||||
|
report = check(
|
||||||
|
config_path,
|
||||||
|
args.inventory.resolve(),
|
||||||
|
args.publication.resolve(),
|
||||||
|
policy_root=policy_root,
|
||||||
|
source_root=source_root,
|
||||||
|
lock_path=args.lock.resolve() if args.lock else None,
|
||||||
|
)
|
||||||
|
if args.report:
|
||||||
|
args.report.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
args.report.write_text(
|
||||||
|
json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8"
|
||||||
|
)
|
||||||
|
counts = report["dispositions"]
|
||||||
|
print(
|
||||||
|
f"source inventory ok: {len(report['sources'])} source(s), "
|
||||||
|
f"{counts['published']} published, {counts['metadata-pending']} metadata-pending, "
|
||||||
|
f"source-set {report['source_set_digest']}"
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
except (KeyError, OSError, ValueError, subprocess.CalledProcessError) as exc:
|
||||||
|
print(f"source inventory failed: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|
@ -46,8 +46,11 @@ def verify(build: Path) -> dict[str, Any]:
|
||||||
|
|
||||||
index = build / "index.html"
|
index = build / "index.html"
|
||||||
manifest_path = build / "publication-manifest.json"
|
manifest_path = build / "publication-manifest.json"
|
||||||
if not index.is_file() or not manifest_path.is_file():
|
inventory_path = build / "source-inventory.json"
|
||||||
raise ValueError("release requires index.html and publication-manifest.json")
|
if not index.is_file() or not manifest_path.is_file() or not inventory_path.is_file():
|
||||||
|
raise ValueError(
|
||||||
|
"release requires index.html, publication-manifest.json and source-inventory.json"
|
||||||
|
)
|
||||||
|
|
||||||
manifest_bytes = manifest_path.read_bytes()
|
manifest_bytes = manifest_path.read_bytes()
|
||||||
manifest = json.loads(manifest_bytes)
|
manifest = json.loads(manifest_bytes)
|
||||||
|
|
@ -59,6 +62,19 @@ def verify(build: Path) -> dict[str, Any]:
|
||||||
if not isinstance(documents, list) or not documents:
|
if not isinstance(documents, list) or not documents:
|
||||||
raise ValueError("publication manifest must contain at least one document")
|
raise ValueError("publication manifest must contain at least one document")
|
||||||
|
|
||||||
|
inventory_bytes = inventory_path.read_bytes()
|
||||||
|
inventory = json.loads(inventory_bytes)
|
||||||
|
if inventory.get("schema_version") != "policy-nexus-source-inventory/v1":
|
||||||
|
raise ValueError("source inventory schema_version is invalid")
|
||||||
|
source_set_digest = inventory.get("source_set_digest", "")
|
||||||
|
if not HEX_DIGEST.fullmatch(source_set_digest):
|
||||||
|
raise ValueError("source inventory requires a valid source_set_digest")
|
||||||
|
inventoried_published = {
|
||||||
|
(source.get("source_repo"), source.get("source_path"))
|
||||||
|
for source in inventory.get("sources", [])
|
||||||
|
if source.get("disposition") == "published"
|
||||||
|
}
|
||||||
|
|
||||||
verified: list[str] = []
|
verified: list[str] = []
|
||||||
for document in documents:
|
for document in documents:
|
||||||
document_id = document.get("id", "<unknown>")
|
document_id = document.get("id", "<unknown>")
|
||||||
|
|
@ -72,6 +88,8 @@ def verify(build: Path) -> dict[str, Any]:
|
||||||
"review_due",
|
"review_due",
|
||||||
"canonical_path",
|
"canonical_path",
|
||||||
"revision_path",
|
"revision_path",
|
||||||
|
"source_repo",
|
||||||
|
"source_path",
|
||||||
):
|
):
|
||||||
if not document.get(field) or document.get(field) == "unknown":
|
if not document.get(field) or document.get(field) == "unknown":
|
||||||
raise ValueError(f"{document_id}: release metadata field {field} is required")
|
raise ValueError(f"{document_id}: release metadata field {field} is required")
|
||||||
|
|
@ -84,6 +102,9 @@ def verify(build: Path) -> dict[str, Any]:
|
||||||
)
|
)
|
||||||
if not HEX_DIGEST.fullmatch(source_digest):
|
if not HEX_DIGEST.fullmatch(source_digest):
|
||||||
raise ValueError(f"{document_id}: invalid source_digest {source_digest!r}")
|
raise ValueError(f"{document_id}: invalid source_digest {source_digest!r}")
|
||||||
|
source_key = (document["source_repo"], document["source_path"])
|
||||||
|
if source_key not in inventoried_published:
|
||||||
|
raise ValueError(f"{document_id}: source is not published in source inventory")
|
||||||
|
|
||||||
canonical = build / _safe_relative(document["canonical_path"])
|
canonical = build / _safe_relative(document["canonical_path"])
|
||||||
revision = build / _safe_relative(document["revision_path"])
|
revision = build / _safe_relative(document["revision_path"])
|
||||||
|
|
@ -105,6 +126,8 @@ def verify(build: Path) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"schema_version": "policy-nexus-release/v1",
|
"schema_version": "policy-nexus-release/v1",
|
||||||
"publication_manifest_digest": hashlib.sha256(manifest_bytes).hexdigest(),
|
"publication_manifest_digest": hashlib.sha256(manifest_bytes).hexdigest(),
|
||||||
|
"source_inventory_digest": hashlib.sha256(inventory_bytes).hexdigest(),
|
||||||
|
"source_set_digest": source_set_digest,
|
||||||
"generated_as_of": manifest["generated_as_of"],
|
"generated_as_of": manifest["generated_as_of"],
|
||||||
"documents": verified,
|
"documents": verified,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ type: workplan
|
||||||
title: "Stand up policy.coulomb.social as the permanent publication surface"
|
title: "Stand up policy.coulomb.social as the permanent publication surface"
|
||||||
domain: infotech
|
domain: infotech
|
||||||
repo: policy-nexus
|
repo: policy-nexus
|
||||||
status: active
|
status: finished
|
||||||
owner: the-custodian
|
owner: the-custodian
|
||||||
topic_slug: policy-nexus
|
topic_slug: policy-nexus
|
||||||
created: "2026-08-17"
|
created: "2026-08-17"
|
||||||
|
|
@ -134,7 +134,7 @@ immutability, lifecycle notices and currency.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: POLICY-NEXUS-WP-0001-T03
|
id: POLICY-NEXUS-WP-0001-T03
|
||||||
status: progress
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "0b76e184-dcd2-4c12-92e1-49d1f2e0a431"
|
state_hub_task_id: "0b76e184-dcd2-4c12-92e1-49d1f2e0a431"
|
||||||
```
|
```
|
||||||
|
|
@ -161,10 +161,22 @@ owning repo triggers on merge). Pull is simpler and keeps the direction of
|
||||||
dependency clean; push is fresher. Recommend pull with a manual trigger, and
|
dependency clean; push is fresher. Recommend pull with a manual trigger, and
|
||||||
record the choice.
|
record the choice.
|
||||||
|
|
||||||
2026-08-18: the explicit pull manifest and exact source-revision recording are
|
Completed 2026-08-18. The pull model is now explicit and automated. A reviewed
|
||||||
implemented, and missing or inconsistent sources fail the build. Tenancy
|
inventory enumerates 124 committed sources across 21 repositories: one is
|
||||||
Posture is the first entry. Enumerating the remaining in-scope canon and ADR
|
published, 113 await source-owner publication metadata/addressing, eight are
|
||||||
corpus and connecting scheduled/manual checkout refresh remain open.
|
non-Markdown formats without a renderer, and two are ADR directory indexes.
|
||||||
|
The inventory also records the explicit exclusion of `values`, `tpsc`, and
|
||||||
|
`projects`. New or removed matching sources fail the audit until their
|
||||||
|
disposition is reviewed.
|
||||||
|
|
||||||
|
The Forgejo workflow checks out every external source at an exact commit on a
|
||||||
|
daily schedule and manual dispatch, records a deterministic source-set digest,
|
||||||
|
and fails visibly on fetch, inventory, currency, or release verification
|
||||||
|
errors. It publishes an immutable source-set candidate only. Promotion remains
|
||||||
|
the separate paired OCI/publication-digest approval owned by
|
||||||
|
`rapp-policy-nexus` and `railiance-apps`. Release builds also retain the prior
|
||||||
|
generated tree so an unchanged semantic revision cannot acquire rewritten
|
||||||
|
immutable provenance when an upstream repository advances for unrelated work.
|
||||||
|
|
||||||
### T04 — Deploy to policy.coulomb.social
|
### T04 — Deploy to policy.coulomb.social
|
||||||
|
|
||||||
|
|
@ -223,7 +235,8 @@ is quietly out of date is worse than no document.
|
||||||
|
|
||||||
Completed 2026-08-18 for the published corpus. Pages and the index expose
|
Completed 2026-08-18 for the published corpus. Pages and the index expose
|
||||||
review due dates and overdue state; `make currency` exits non-zero for stale or
|
review due dates and overdue state; `make currency` exits non-zero for stale or
|
||||||
undeclared review metadata. Expansion follows T03 automatically.
|
undeclared review metadata. The daily source-pull workflow is the delivery
|
||||||
|
channel for that failure, and expansion follows the reviewed T03 inventory.
|
||||||
|
|
||||||
### T06 — withdrawn
|
### T06 — withdrawn
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue