docs: prepare safe public registry resume and access handoff
This commit is contained in:
parent
02ec62ca61
commit
0e7bcd128b
5 changed files with 586 additions and 5 deletions
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"id": "CUST81-REUSE-PUBLIC-REGISTRATION-R1",
|
||||
"supersedes": "CUST81-REUSE-REGISTRATION-001 for the public-only batch",
|
||||
"revision": 1,
|
||||
"status": "prepared; not executed",
|
||||
"authority": "User requested establishment of the reviewed capabilities with reuse-surface. This revision is limited to the 31 anonymously readable public repositories. It does not authorize or include private-source registration.",
|
||||
"target": "https://reuse.coulomb.social/v1/repos",
|
||||
"repositories": 31,
|
||||
"excluded_private_repositories": [
|
||||
"rapp-openbao",
|
||||
"rapp-policy-nexus",
|
||||
"rapp-postgres",
|
||||
"rapp-sbom-nexus",
|
||||
"rapp-telemetry",
|
||||
"rapp-tenant-engine",
|
||||
"rapp-user-engine"
|
||||
],
|
||||
"plan_file": "hub-public-resume-plan.json",
|
||||
"plan_sha256": "768449eaec05f38c6820f2747a2ef388b10b57a341dc01768218646499aaf39b",
|
||||
"helper_file": "register_public_hub.py",
|
||||
"helper_sha256": "c6a5f08e01af3a5c4b1d6557859f4400eaba0d7eb0bb1976f45bba6bc5a05fda",
|
||||
"verification_file": "test_register_public_hub.py",
|
||||
"verification_sha256": "519cd624eef4ad3c0ed97ab686368b4ffb26985066c8baa2d804e1c6c1af8e02",
|
||||
"credential_lane": "Existing reuse-surface-hub-write-token route via warden access --exec, only if owner admission has been provisioned and verified. No credential export or persistence.",
|
||||
"preconditions": [
|
||||
"The helper confirms its 31 actions exactly match hub-public-registration-plan.json and rejects all seven private repositories.",
|
||||
"Before any registration write, the helper anonymously re-fetches the immutable index and capability entries and the stable main index for each planned repository; all bytes must match local published artifacts.",
|
||||
"The helper reads GET /v1/repos and compares every submitted registration body field. A conflicting existing row or any POST 409 halts the batch.",
|
||||
"The write token is available only in the execution child after scoped owner access is confirmed."
|
||||
],
|
||||
"expected": "Every planned public registration exists with the exact submitted fields, and the 31 public-source capabilities are discoverable from a fresh hosted federation response.",
|
||||
"receipt": "Append-only sanitized JSONL at hub-public-resume-receipts.jsonl; includes attempt ID, timestamp, plan hash, repo, outcome and HTTP status only. Each POST has a durable post_intent event before transmission; a timeout records transport_unknown and stops. It never stores tokens, response bodies or headers.",
|
||||
"verification": "The helper performs a fresh GET /v1/repos after writes and appends readback_verified only if all 31 submitted fields match. Then fetch a fresh hosted federation response. Confirm all reviewed public capability IDs appear exactly once, no unexpected duplicates appear, and source bytes match preflight. Retain status and hashes, not credential material.",
|
||||
"rollback": "For a uniquely attributable row with outcome=applied in this proposal attempt, issue PATCH /v1/repos/{repo} with {\"enabled\":false}; do not delete registrations. Then GET each affected row and confirm enabled=false, and fetch a fresh federation response to confirm its capabilities are absent. Never change already_matches rows. If a POST has post_intent without a confirmed result, first read back that repo and resolve its state; do not roll it back based only on an ambiguous transport outcome.",
|
||||
"cost": "31 bounded metadata registrations, anonymous reads limited to these 31 repositories, and public readback on existing services; no deployment or recurring resource commitment.",
|
||||
"risk": "A stale readback can race with another registrar, or a write may partially succeed before transport failure. Each attempt appends evidence; 409/conflict halts. Resume by reading current state again, never by blindly replaying POSTs."
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
[
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"approval-engine","url":"https://forgejo.coulomb.social/coulomb/approval-engine/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"emergent-security","url":"https://forgejo.coulomb.social/coulomb/emergent-security/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"fin-hub","url":"https://forgejo.coulomb.social/coulomb/fin-hub/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"gate-house","url":"https://forgejo.coulomb.social/coulomb/gate-house/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"glas-harness","url":"https://forgejo.coulomb.social/coulomb/glas-harness/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"informed-decision","url":"https://forgejo.coulomb.social/coulomb/informed-decision/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"kings-guard","url":"https://forgejo.coulomb.social/coulomb/kings-guard/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"maturity-engine","url":"https://forgejo.coulomb.social/coulomb/maturity-engine/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"ops-mason","url":"https://forgejo.coulomb.social/coulomb/ops-mason/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"policy-nexus","url":"https://forgejo.coulomb.social/coulomb/policy-nexus/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"rail-kubernetes","url":"https://forgejo.coulomb.social/coulomb/rail-kubernetes/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"railiance-bootstrap","url":"https://forgejo.coulomb.social/coulomb/railiance-bootstrap/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"railiance-master","url":"https://forgejo.coulomb.social/coulomb/railiance-master/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"railiance-telemetry","url":"https://forgejo.coulomb.social/coulomb/railiance-telemetry/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"rapp-playwright","url":"https://forgejo.coulomb.social/coulomb/rapp-playwright/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"reef-railiance","url":"https://forgejo.coulomb.social/coulomb/reef-railiance/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"rein-aharness","url":"https://forgejo.coulomb.social/coulomb/rein-aharness/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"rein-openweights","url":"https://forgejo.coulomb.social/coulomb/rein-openweights/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"repo-manager","url":"https://forgejo.coulomb.social/coulomb/repo-manager/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"resource-control","url":"https://forgejo.coulomb.social/coulomb/resource-control/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"risk-nexus","url":"https://forgejo.coulomb.social/coulomb/risk-nexus/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"role-engine","url":"https://forgejo.coulomb.social/coulomb/role-engine/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"runbook-tutorials","url":"https://forgejo.coulomb.social/coulomb/runbook-tutorials/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"sand-boxer","url":"https://forgejo.coulomb.social/coulomb/sand-boxer/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"sbom-nexus","url":"https://forgejo.coulomb.social/coulomb/sbom-nexus/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"secrets-engine","url":"https://forgejo.coulomb.social/coulomb/secrets-engine/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"security-canon","url":"https://forgejo.coulomb.social/coulomb/security-canon/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"tenant-engine","url":"https://forgejo.coulomb.social/coulomb/tenant-engine/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"whitehat-security","url":"https://forgejo.coulomb.social/coulomb/whitehat-security/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"zone-engine","url":"https://forgejo.coulomb.social/coulomb/zone-engine/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}},
|
||||
{"method":"POST","path":"/v1/repos","body":{"repo":"rail-knative","url":"https://forgejo.coulomb.social/coulomb/rail-knative/raw/branch/main/registry/indexes/capabilities.yaml","domain":"infotech","enabled":true,"required":false,"registered_by":"codex-CUST-WP-0081"}}
|
||||
]
|
||||
304
docs/assessments/netkingdom-2026-10-14/register_public_hub.py
Normal file
304
docs/assessments/netkingdom-2026-10-14/register_public_hub.py
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
"""Bounded resume helper for the 31 public-only NetKingdom registrations.
|
||||
|
||||
Nothing runs at import time. Applying requires the explicit --apply flag and
|
||||
REUSE_SURFACE_TOKEN. Private repositories are rejected from this plan.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
|
||||
OUT = Path(__file__).resolve().parent
|
||||
BASE = "https://reuse.coulomb.social"
|
||||
PLAN_NAME = "hub-public-resume-plan.json"
|
||||
SOURCE_PLAN_NAME = "hub-public-registration-plan.json"
|
||||
PRIVATE_REPOS = {
|
||||
"rapp-openbao", "rapp-policy-nexus", "rapp-postgres", "rapp-sbom-nexus",
|
||||
"rapp-telemetry", "rapp-tenant-engine", "rapp-user-engine",
|
||||
}
|
||||
MAX_SOURCE_BYTES = 2 * 1024 * 1024
|
||||
|
||||
|
||||
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||
def redirect_request(self, *args: Any, **kwargs: Any) -> None:
|
||||
return None
|
||||
|
||||
|
||||
def read_json(path: Path) -> Any:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def validate_plan(plan: list[dict[str, Any]], original_public: list[dict[str, Any]]) -> set[str]:
|
||||
"""Require an exact copy of the 31-item public-only source plan."""
|
||||
if plan != original_public or len(plan) != 31:
|
||||
raise ValueError("public resume plan differs from the approved 31-item public plan")
|
||||
repos: list[str] = []
|
||||
for action in plan:
|
||||
if action.get("method") != "POST" or action.get("path") != "/v1/repos":
|
||||
raise ValueError("only POST /v1/repos actions are allowed")
|
||||
body = action.get("body")
|
||||
if not isinstance(body, dict) or not isinstance(body.get("repo"), str):
|
||||
raise ValueError("registration body has no repo slug")
|
||||
repo = body["repo"]
|
||||
if repo in PRIVATE_REPOS:
|
||||
raise ValueError("private repository is forbidden in public resume plan")
|
||||
expected_url = f"https://forgejo.coulomb.social/coulomb/{repo}/raw/branch/main/registry/indexes/capabilities.yaml"
|
||||
if body.get("url") != expected_url:
|
||||
raise ValueError(f"unexpected source URL for {repo}")
|
||||
if body.get("domain") != "infotech" or body.get("enabled") is not True or body.get("required") is not False:
|
||||
raise ValueError(f"unexpected registration fields for {repo}")
|
||||
repos.append(repo)
|
||||
if len(repos) != len(set(repos)):
|
||||
raise ValueError("duplicate repository in public resume plan")
|
||||
return set(repos)
|
||||
|
||||
|
||||
def body_matches(body: dict[str, Any], current: dict[str, Any]) -> bool:
|
||||
"""Compare every submitted field; extra hub-managed fields are permitted."""
|
||||
return all(key in current and current[key] == value for key, value in body.items())
|
||||
|
||||
|
||||
def validate_readback(plan: list[dict[str, Any]], rows: list[dict[str, Any]]) -> None:
|
||||
"""Require a fresh hub readback to contain every planned body exactly."""
|
||||
by_repo = {row.get("repo"): row for row in rows}
|
||||
if len(by_repo) != len(rows):
|
||||
raise ValueError("hub readback contains duplicate repository rows")
|
||||
for action in plan:
|
||||
body = action["body"]
|
||||
current = by_repo.get(body["repo"])
|
||||
if current is None or not body_matches(body, current):
|
||||
raise ValueError("hub readback does not match the public registration plan")
|
||||
|
||||
|
||||
def verify_public_sources(
|
||||
root: Path,
|
||||
plan: list[dict[str, Any]],
|
||||
publications: list[dict[str, Any]],
|
||||
inventory: dict[str, Any],
|
||||
fetch: Callable[[str], bytes],
|
||||
) -> None:
|
||||
"""Freshly verify only the planned public repos before registration writes."""
|
||||
commits = {
|
||||
row["repo"]: row["commit"]
|
||||
for row in publications
|
||||
if row.get("verified_remote") and row.get("repo") and row.get("commit")
|
||||
}
|
||||
capabilities: dict[str, list[str]] = {}
|
||||
for item in inventory["capabilities"]:
|
||||
capabilities.setdefault(item["repo"], []).append(item["entry_path"])
|
||||
|
||||
repos = [action["body"]["repo"] for action in plan]
|
||||
for repo in repos:
|
||||
if repo not in commits:
|
||||
raise ValueError(f"no verified immutable publication for {repo}")
|
||||
paths = ["registry/indexes/capabilities.yaml", *capabilities.get(repo, [])]
|
||||
for path in paths:
|
||||
local_path = root / repo / path
|
||||
expected = hashlib.sha256(local_path.read_bytes()).hexdigest()
|
||||
commit_url = f"https://forgejo.coulomb.social/coulomb/{repo}/raw/commit/{commits[repo]}/{path}"
|
||||
remote = fetch(commit_url)
|
||||
if len(remote) > MAX_SOURCE_BYTES:
|
||||
raise ValueError(f"published source exceeds byte limit for {repo}:{path}")
|
||||
actual = hashlib.sha256(remote).hexdigest()
|
||||
if actual != expected:
|
||||
raise ValueError(f"immutable published bytes differ for {repo}:{path}")
|
||||
stable_url = f"https://forgejo.coulomb.social/coulomb/{repo}/raw/branch/main/registry/indexes/capabilities.yaml"
|
||||
stable = fetch(stable_url)
|
||||
if len(stable) > MAX_SOURCE_BYTES:
|
||||
raise ValueError(f"stable public index exceeds byte limit for {repo}")
|
||||
stable_actual = hashlib.sha256(stable).hexdigest()
|
||||
index_expected = hashlib.sha256((root / repo / paths[0]).read_bytes()).hexdigest()
|
||||
if stable_actual != index_expected:
|
||||
raise ValueError(f"stable public index differs for {repo}")
|
||||
|
||||
|
||||
def preflight_public_batch(
|
||||
root: Path,
|
||||
plan: list[dict[str, Any]],
|
||||
original_public: list[dict[str, Any]],
|
||||
baseline: dict[str, Any],
|
||||
publications: list[dict[str, Any]],
|
||||
inventory: dict[str, Any],
|
||||
fetch: Callable[[str], bytes],
|
||||
) -> set[str]:
|
||||
"""Validate the bounded plan and freshly verify its public sources."""
|
||||
repos = validate_plan(plan, original_public)
|
||||
verified_public = {
|
||||
row["repo"] for row in baseline.get("results", [])
|
||||
if not row.get("errors") and row.get("stable_index_matches")
|
||||
}
|
||||
if not repos <= verified_public:
|
||||
raise ValueError("one or more planned repositories lack anonymous-source verification")
|
||||
verify_public_sources(root, plan, publications, inventory, fetch)
|
||||
return repos
|
||||
|
||||
|
||||
def append_receipt(path: Path, event: dict[str, Any]) -> None:
|
||||
"""Append one sanitized JSON line; never replace earlier attempts."""
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
line = (json.dumps(event, sort_keys=True, separators=(",", ":")) + "\n").encode()
|
||||
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)
|
||||
try:
|
||||
os.write(fd, line)
|
||||
os.fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
|
||||
|
||||
def reconcile(
|
||||
plan: list[dict[str, Any]],
|
||||
existing: dict[str, dict[str, Any]],
|
||||
post: Callable[[dict[str, Any]], int],
|
||||
receipt: Callable[[dict[str, Any]], None],
|
||||
*,
|
||||
apply: bool,
|
||||
plan_sha256: str,
|
||||
attempt_id: str | None = None,
|
||||
) -> None:
|
||||
"""Read-before-write; any mismatch or 409 halts the remaining batch."""
|
||||
attempt = attempt_id or str(uuid.uuid4())
|
||||
# Inspect the full readback before the first POST. A conflict late in the
|
||||
# plan must not leave earlier registrations newly created by this attempt.
|
||||
for action in plan:
|
||||
body = action["body"]
|
||||
repo = body["repo"]
|
||||
current = existing.get(repo)
|
||||
if current is not None and not body_matches(body, current):
|
||||
receipt({
|
||||
"attempt_id": attempt,
|
||||
"at": datetime.now(timezone.utc).isoformat(),
|
||||
"plan_sha256": plan_sha256,
|
||||
"repo": repo,
|
||||
"outcome": "conflict_existing",
|
||||
})
|
||||
raise RuntimeError(f"existing registration conflicts for {repo}")
|
||||
|
||||
for action in plan:
|
||||
body = action["body"]
|
||||
repo = body["repo"]
|
||||
current = existing.get(repo)
|
||||
base = {
|
||||
"attempt_id": attempt,
|
||||
"at": datetime.now(timezone.utc).isoformat(),
|
||||
"plan_sha256": plan_sha256,
|
||||
"repo": repo,
|
||||
}
|
||||
if current is not None:
|
||||
receipt({**base, "outcome": "already_matches"})
|
||||
continue
|
||||
if not apply:
|
||||
receipt({**base, "outcome": "would_create"})
|
||||
continue
|
||||
receipt({**base, "outcome": "post_intent"})
|
||||
try:
|
||||
status = post(body)
|
||||
except urllib.error.HTTPError as exc:
|
||||
status = exc.code
|
||||
except Exception:
|
||||
receipt({**base, "outcome": "transport_unknown"})
|
||||
raise RuntimeError(f"registration transport outcome is unknown for {repo}") from None
|
||||
if status == 409:
|
||||
receipt({**base, "http_status": 409, "outcome": "conflict_409"})
|
||||
raise RuntimeError(f"registration conflict returned 409 for {repo}")
|
||||
if status < 200 or status >= 300:
|
||||
receipt({**base, "http_status": status, "outcome": "request_failed"})
|
||||
raise RuntimeError(f"registration request failed for {repo}")
|
||||
existing[repo] = dict(body)
|
||||
receipt({**base, "http_status": status, "outcome": "applied"})
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--apply", action="store_true", help="perform authenticated POST requests")
|
||||
args = parser.parse_args()
|
||||
plan = read_json(OUT / PLAN_NAME)
|
||||
original_public = read_json(OUT / SOURCE_PLAN_NAME)
|
||||
baseline = read_json(OUT / "published-source-verification.json")
|
||||
pub = read_json(OUT / "registry-publication.json") + read_json(OUT / "supplement/registry-publication.json")
|
||||
inventory = read_json(OUT / "full-inventory.json")
|
||||
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect())
|
||||
|
||||
def fetch(url: str) -> bytes:
|
||||
with opener.open(url, timeout=30) as response:
|
||||
if response.status != 200:
|
||||
raise RuntimeError("public source verification returned non-200")
|
||||
body = response.read(MAX_SOURCE_BYTES + 1)
|
||||
if len(body) > MAX_SOURCE_BYTES:
|
||||
raise ValueError("public source exceeds the 2 MiB verification limit")
|
||||
return body
|
||||
|
||||
preflight_public_batch(OUT.parents[3], plan, original_public, baseline, pub, inventory, fetch)
|
||||
|
||||
def request(method: str, path: str, body: dict[str, Any] | None = None, *, auth: bool = False) -> tuple[int, Any]:
|
||||
headers = {"Accept": "application/json"}
|
||||
if body is not None:
|
||||
headers["Content-Type"] = "application/json"; data = json.dumps(body).encode()
|
||||
else:
|
||||
data = None
|
||||
if auth:
|
||||
token = os.environ.get("REUSE_SURFACE_TOKEN")
|
||||
if not token:
|
||||
raise RuntimeError("required owner-routed write credential is unavailable")
|
||||
headers["Authorization"] = "Bearer " + token
|
||||
req = urllib.request.Request(BASE + path, data=data, headers=headers, method=method)
|
||||
with opener.open(req, timeout=55) as response:
|
||||
raw = response.read()
|
||||
return response.status, json.loads(raw) if raw else None
|
||||
|
||||
status, listing = request("GET", "/v1/repos")
|
||||
if status != 200 or not isinstance(listing, dict) or not isinstance(listing.get("repos"), list):
|
||||
raise RuntimeError("hub registration readback is invalid")
|
||||
existing = {row["repo"]: row for row in listing["repos"]}
|
||||
if len(existing) != len(listing["repos"]):
|
||||
raise RuntimeError("hub returned duplicate repository registrations")
|
||||
|
||||
def post(body: dict[str, Any]) -> int:
|
||||
status, _ = request("POST", "/v1/repos", body, auth=True)
|
||||
return status
|
||||
|
||||
receipt_path = OUT / "hub-public-resume-receipts.jsonl"
|
||||
plan_hash = hashlib.sha256((OUT / PLAN_NAME).read_bytes()).hexdigest()
|
||||
attempt_id = str(uuid.uuid4())
|
||||
reconcile(plan, existing, post, lambda event: append_receipt(receipt_path, event),
|
||||
apply=args.apply, plan_sha256=plan_hash, attempt_id=attempt_id)
|
||||
if args.apply:
|
||||
try:
|
||||
read_status, readback = request("GET", "/v1/repos")
|
||||
if read_status != 200 or not isinstance(readback, dict) or not isinstance(readback.get("repos"), list):
|
||||
raise ValueError("invalid hub readback")
|
||||
validate_readback(plan, readback["repos"])
|
||||
except Exception as exc:
|
||||
append_receipt(receipt_path, {
|
||||
"attempt_id": attempt_id,
|
||||
"at": datetime.now(timezone.utc).isoformat(),
|
||||
"plan_sha256": plan_hash,
|
||||
"outcome": "readback_failed",
|
||||
"failure_type": type(exc).__name__,
|
||||
})
|
||||
raise RuntimeError("fresh hub registration readback failed") from None
|
||||
append_receipt(receipt_path, {
|
||||
"attempt_id": attempt_id,
|
||||
"at": datetime.now(timezone.utc).isoformat(),
|
||||
"plan_sha256": plan_hash,
|
||||
"repo_count": len(plan),
|
||||
"outcome": "readback_verified",
|
||||
})
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except Exception as exc:
|
||||
# Avoid serializing transport bodies, headers, exception details, or credentials.
|
||||
print(f"public registration stopped: {type(exc).__name__}", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
|
@ -9,15 +9,51 @@
|
|||
"field": "REUSE_SURFACE_TOKEN",
|
||||
"existing_read_policy": "workload-kv-read-reuse-surface-runtime",
|
||||
"request": "Owner to admit the caller through the existing scoped route and verify its metadata lookup and selected-field read. Retain existing denial evidence; do not grant administrative or unrelated workload access.",
|
||||
"delivery": "warden access --exec, without exporting or recording the value"
|
||||
"delivery": "warden access --exec, without exporting or recording the value",
|
||||
"approval_record": "CCR-2026-0005 is active and was previously approved by platform-operator and reuse-surface-owner; it does not name the caller principal.",
|
||||
"principal_binding": "Identify the caller OpenBao OIDC entity/accessor without recording any token. If a binding change is needed, record approval for that exact principal with both required approvers before applying the existing policy.",
|
||||
"existing_policy": "workload-kv-read-reuse-surface-runtime",
|
||||
"exact_allowed_paths": [
|
||||
"platform/data/workloads/reuse/reuse-surface/runtime-secrets",
|
||||
"platform/metadata/workloads/reuse/reuse-surface/runtime-secrets"
|
||||
],
|
||||
"negative_check": "An unrelated sibling path must remain denied. Do not broaden policy contents or attach administrative/ESO-only permissions."
|
||||
},
|
||||
"private_source_access": {
|
||||
"repositories": ["rapp-openbao", "rapp-policy-nexus", "rapp-postgres", "rapp-sbom-nexus", "rapp-telemetry", "rapp-tenant-engine", "rapp-user-engine"],
|
||||
"repositories": [
|
||||
"rapp-openbao",
|
||||
"rapp-policy-nexus",
|
||||
"rapp-postgres",
|
||||
"rapp-sbom-nexus",
|
||||
"rapp-telemetry",
|
||||
"rapp-tenant-engine",
|
||||
"rapp-user-engine"
|
||||
],
|
||||
"request": "Owner to provision a repository-read credential at the federation hub, confirm source-owner consent to public capability metadata, and verify hub-side index and entry fetches.",
|
||||
"return_only": ["configured auth_env name", "auth_header name if nondefault", "scope and verification receipts"],
|
||||
"visibility_change_authorized": false
|
||||
"return_only": [
|
||||
"configured auth_env name",
|
||||
"auth_header name if nondefault",
|
||||
"scope and verification receipts"
|
||||
],
|
||||
"visibility_change_authorized": false,
|
||||
"credential_design": "A new dedicated Forgejo identity/PAT restricted to repository-read for exactly these seven sources; verify a denied-write/admin negative. Keep it distinct from the hub writer token and from other workload credentials.",
|
||||
"delivery": "Owner-reviewed OpenBao custody and ESO delivery to the reuse-surface hub runtime, then configure the registration auth_env with the environment variable name only.",
|
||||
"approval": "Platform operator and each relevant source owner must approve the limited private capability-metadata disclosure and reader scope before provisioning."
|
||||
},
|
||||
"resume": "Update and retain the exact registration proposal revision with admitted auth_env references, verify source parity, execute bounded registrations, then require all 70 reviewed IDs exactly once in a fresh hosted federation.",
|
||||
"secret_values_requested_in_chat": false,
|
||||
"owner_message_sent": false
|
||||
"owner_message_sent": false,
|
||||
"attended_sequence": [
|
||||
"Review CCR-2026-0005 and identify the actual intended caller principal. The existing OIDC identity is not named in the CCR.",
|
||||
"If needed, obtain and record platform-operator plus reuse-surface-owner approval for binding that exact identity to the existing read-only policy. Verify read on the two exact paths and denial on a sibling path.",
|
||||
"Use warden access reuse-surface-hub-write-token --exec --field REUSE_SURFACE_TOKEN with a silent helper. Do not use --fetch or print/export the field.",
|
||||
"After the public-only helper receipt confirms 31 registrations, request the distinct seven-repository read-only source credential and owner disclosure consent. Return only the auth_env name, scope and sanitized verification receipts."
|
||||
],
|
||||
"public_batch_prepared": {
|
||||
"proposal": "hub-public-registration-proposal-r1.json",
|
||||
"plan": "hub-public-resume-plan.json",
|
||||
"helper": "register_public_hub.py",
|
||||
"repositories": 31,
|
||||
"status": "prepared and tested; not executed"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,171 @@
|
|||
"""Offline safety tests for the bounded public registration helper."""
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import register_public_hub as helper
|
||||
|
||||
|
||||
class PublicResumeTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.plan = helper.read_json(helper.OUT / helper.PLAN_NAME)
|
||||
cls.source_plan = helper.read_json(helper.OUT / helper.SOURCE_PLAN_NAME)
|
||||
|
||||
def test_plan_is_exactly_public_scope(self):
|
||||
repos = helper.validate_plan(self.plan, self.source_plan)
|
||||
baseline = helper.read_json(helper.OUT / "published-source-verification.json")
|
||||
verified = {r["repo"] for r in baseline["results"] if not r["errors"] and r["stable_index_matches"]}
|
||||
self.assertEqual(len(repos), 31)
|
||||
self.assertTrue(repos <= verified)
|
||||
self.assertTrue(repos.isdisjoint(helper.PRIVATE_REPOS))
|
||||
|
||||
def test_scope_rejects_mutation_and_private_repo(self):
|
||||
altered = json.loads(json.dumps(self.plan))
|
||||
altered[0]["body"]["domain"] = "other"
|
||||
with self.assertRaises(ValueError):
|
||||
helper.validate_plan(altered, self.source_plan)
|
||||
altered = json.loads(json.dumps(self.plan))
|
||||
altered[0]["body"]["repo"] = "rapp-postgres"
|
||||
with self.assertRaises(ValueError):
|
||||
helper.validate_plan(altered, self.source_plan)
|
||||
|
||||
def test_compares_every_submitted_registration_field(self):
|
||||
body = self.plan[0]["body"]
|
||||
self.assertTrue(helper.body_matches(body, dict(body, registered_at="hub-field")))
|
||||
self.assertFalse(helper.body_matches(body, {k: v for k, v in body.items() if k != "registered_by"}))
|
||||
changed = dict(body, required=True)
|
||||
self.assertFalse(helper.body_matches(body, changed))
|
||||
|
||||
def test_fresh_readback_requires_all_exact_plan_fields(self):
|
||||
rows = [dict(action["body"], registered_at="server") for action in self.plan]
|
||||
helper.validate_readback(self.plan, rows)
|
||||
rows[0]["enabled"] = False
|
||||
with self.assertRaisesRegex(ValueError, "does not match"):
|
||||
helper.validate_readback(self.plan, rows)
|
||||
|
||||
def test_dry_run_records_all_actions_without_posting(self):
|
||||
events = []
|
||||
posts = []
|
||||
helper.reconcile(
|
||||
self.plan, {}, lambda body: posts.append(body) or 201,
|
||||
events.append, apply=False, plan_sha256="a" * 64,
|
||||
)
|
||||
self.assertEqual(posts, [])
|
||||
self.assertEqual([event["outcome"] for event in events], ["would_create"] * 31)
|
||||
self.assertTrue(all(set(event) <= {"attempt_id", "at", "plan_sha256", "repo", "outcome"} for event in events))
|
||||
|
||||
def test_existing_conflict_stops_before_any_post(self):
|
||||
events = []
|
||||
posts = []
|
||||
repo = self.plan[1]["body"]["repo"]
|
||||
existing = {repo: {"repo": repo, "url": "https://wrong.invalid/index.yaml"}}
|
||||
with self.assertRaisesRegex(RuntimeError, "conflicts"):
|
||||
helper.reconcile(self.plan, existing, lambda body: posts.append(body) or 201,
|
||||
events.append, apply=True, plan_sha256="b" * 64)
|
||||
self.assertEqual(posts, [])
|
||||
self.assertEqual(events[-1]["outcome"], "conflict_existing")
|
||||
|
||||
def test_409_is_recorded_and_stops_batch(self):
|
||||
events = []
|
||||
calls = []
|
||||
with self.assertRaisesRegex(RuntimeError, "409"):
|
||||
helper.reconcile(self.plan[:3], {}, lambda body: calls.append(body) or 409,
|
||||
events.append, apply=True, plan_sha256="c" * 64)
|
||||
self.assertEqual(len(calls), 1)
|
||||
self.assertEqual(len(events), 2)
|
||||
self.assertEqual(events[0]["outcome"], "post_intent")
|
||||
self.assertEqual(events[1]["outcome"], "conflict_409")
|
||||
|
||||
def test_success_has_durable_intent_and_applied_receipts(self):
|
||||
events = []
|
||||
helper.reconcile(self.plan[:1], {}, lambda _body: 201, events.append,
|
||||
apply=True, plan_sha256="e" * 64)
|
||||
self.assertEqual([event["outcome"] for event in events], ["post_intent", "applied"])
|
||||
|
||||
def test_ambiguous_transport_is_recorded_without_retry(self):
|
||||
events = []
|
||||
def lost_response(_body):
|
||||
raise TimeoutError("do not store this message")
|
||||
with self.assertRaisesRegex(RuntimeError, "unknown"):
|
||||
helper.reconcile(self.plan[:2], {}, lost_response, events.append,
|
||||
apply=True, plan_sha256="f" * 64)
|
||||
self.assertEqual([event["outcome"] for event in events], ["post_intent", "transport_unknown"])
|
||||
self.assertNotIn("do not store this message", json.dumps(events))
|
||||
|
||||
def test_source_verification_fetches_only_the_planned_public_repo(self):
|
||||
action = self.plan[0]
|
||||
repo = action["body"]["repo"]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp) / "workspace"
|
||||
(root / repo / "registry/indexes").mkdir(parents=True)
|
||||
(root / repo / "registry/capabilities").mkdir(parents=True)
|
||||
index = b"index"
|
||||
entry = b"entry"
|
||||
(root / repo / "registry/indexes/capabilities.yaml").write_bytes(index)
|
||||
(root / repo / "registry/capabilities/example.md").write_bytes(entry)
|
||||
commits = [{"repo": repo, "commit": "abc123", "verified_remote": True}]
|
||||
inventory = {"capabilities": [{"repo": repo, "entry_path": "registry/capabilities/example.md"}]}
|
||||
urls = {}
|
||||
def fake_fetch(url):
|
||||
urls[url] = True
|
||||
return entry if url.endswith("example.md") else index
|
||||
helper.verify_public_sources(root, [action], commits, inventory, fake_fetch)
|
||||
self.assertEqual(len(urls), 3) # immutable index + entry, then stable index
|
||||
self.assertTrue(all(f"/{repo}/" in url for url in urls))
|
||||
|
||||
def test_source_verification_rejects_mismatch_before_registration(self):
|
||||
action = self.plan[0]
|
||||
repo = action["body"]["repo"]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp) / "workspace"
|
||||
(root / repo / "registry/indexes").mkdir(parents=True)
|
||||
(root / repo / "registry/indexes/capabilities.yaml").write_bytes(b"expected")
|
||||
inventory = {"capabilities": []}
|
||||
with self.assertRaisesRegex(ValueError, "differ"):
|
||||
helper.verify_public_sources(root, [action], [{"repo": repo, "commit": "abc", "verified_remote": True}], inventory, lambda _url: b"wrong")
|
||||
|
||||
def test_orchestrated_preflight_calls_source_verifier_for_only_the_31(self):
|
||||
baseline = helper.read_json(helper.OUT / "published-source-verification.json")
|
||||
publications = (helper.read_json(helper.OUT / "registry-publication.json")
|
||||
+ helper.read_json(helper.OUT / "supplement/registry-publication.json"))
|
||||
inventory = helper.read_json(helper.OUT / "full-inventory.json")
|
||||
root = helper.OUT.parents[3]
|
||||
fetched_repos = set()
|
||||
def local_public_fetch(url):
|
||||
repo = url.split("/coulomb/", 1)[1].split("/", 1)[0]
|
||||
rest = url.split("/raw/", 1)[1].split("/")
|
||||
path = "/".join(rest[2:])
|
||||
fetched_repos.add(repo)
|
||||
return (root / repo / path).read_bytes()
|
||||
repos = helper.preflight_public_batch(root, self.plan, self.source_plan, baseline,
|
||||
publications, inventory, local_public_fetch)
|
||||
self.assertEqual(len(repos), 31)
|
||||
self.assertEqual(fetched_repos, repos)
|
||||
|
||||
def test_source_verification_rejects_over_limit_response(self):
|
||||
action = self.plan[0]
|
||||
repo = action["body"]["repo"]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
(root / repo / "registry/indexes").mkdir(parents=True)
|
||||
(root / repo / "registry/indexes/capabilities.yaml").write_bytes(b"small")
|
||||
with self.assertRaisesRegex(ValueError, "byte limit"):
|
||||
helper.verify_public_sources(
|
||||
root, [action], [{"repo": repo, "commit": "abc", "verified_remote": True}],
|
||||
{"capabilities": []}, lambda _url: b"x" * (helper.MAX_SOURCE_BYTES + 1),
|
||||
)
|
||||
|
||||
def test_receipts_append_without_serializing_unapproved_fields(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = Path(tmp) / "receipts.jsonl"
|
||||
event = {"repo": "approval-engine", "outcome": "would_create", "plan_sha256": "d" * 64}
|
||||
helper.append_receipt(path, event)
|
||||
helper.append_receipt(path, event)
|
||||
rows = [json.loads(line) for line in path.read_text().splitlines()]
|
||||
self.assertEqual(rows, [event, event])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue