diff --git a/docs/assessments/netkingdom-2026-10-14/hub-public-registration-proposal-r1.json b/docs/assessments/netkingdom-2026-10-14/hub-public-registration-proposal-r1.json new file mode 100644 index 00000000..98286a9e --- /dev/null +++ b/docs/assessments/netkingdom-2026-10-14/hub-public-registration-proposal-r1.json @@ -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." +} diff --git a/docs/assessments/netkingdom-2026-10-14/hub-public-resume-plan.json b/docs/assessments/netkingdom-2026-10-14/hub-public-resume-plan.json new file mode 100644 index 00000000..d4cb569d --- /dev/null +++ b/docs/assessments/netkingdom-2026-10-14/hub-public-resume-plan.json @@ -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"}} +] diff --git a/docs/assessments/netkingdom-2026-10-14/register_public_hub.py b/docs/assessments/netkingdom-2026-10-14/register_public_hub.py new file mode 100644 index 00000000..de76f70e --- /dev/null +++ b/docs/assessments/netkingdom-2026-10-14/register_public_hub.py @@ -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) diff --git a/docs/assessments/netkingdom-2026-10-14/scoped-access-handoff.json b/docs/assessments/netkingdom-2026-10-14/scoped-access-handoff.json index f9525b84..44097c5e 100644 --- a/docs/assessments/netkingdom-2026-10-14/scoped-access-handoff.json +++ b/docs/assessments/netkingdom-2026-10-14/scoped-access-handoff.json @@ -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" + } } diff --git a/docs/assessments/netkingdom-2026-10-14/test_register_public_hub.py b/docs/assessments/netkingdom-2026-10-14/test_register_public_hub.py new file mode 100644 index 00000000..0b522314 --- /dev/null +++ b/docs/assessments/netkingdom-2026-10-14/test_register_public_hub.py @@ -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()