Fix access inventory evidence and reconcile closure coordination
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a11beb-4eef-7fc1-94a7-597130a01ae9
This commit is contained in:
parent
490a89dca6
commit
57ad4abefc
9 changed files with 342 additions and 111 deletions
|
|
@ -6,8 +6,11 @@ Dev Hub (State Hub API): http://127.0.0.1:8000
|
|||
|
||||
This project is **deliberately deferred**. Read `GOAL.md` "Why this is
|
||||
deferred" and the `ORGREF-WP-0001` entry gate before proposing execution work.
|
||||
T01 and T02 are read-only and safe to advance at any time; **T03 onward must
|
||||
not start until all four entry-gate conditions hold.**
|
||||
Within ORGREF-WP-0001, T01 and T02 are read-only and safe to advance at any
|
||||
time; **ORGREF-WP-0001-T03 onward must not start until all four entry-gate
|
||||
conditions hold.** ORGREF-WP-0002 coordinates access control independently,
|
||||
under its own accepted policy, owner boundaries and visibility-wave gates.
|
||||
It does not authorize repository transfers or waive the org-migration gate.
|
||||
|
||||
If you believe the gate is met, say which of the four conditions changed and
|
||||
what evidence shows it — do not infer readiness from elapsed time.
|
||||
|
|
|
|||
12
GOAL.md
12
GOAL.md
|
|
@ -3,7 +3,7 @@ repo: prj-forgejo-org-refactor
|
|||
repo_flavor: project
|
||||
project_status: draft
|
||||
started: "2026-08-11"
|
||||
reviewed: "2026-09-28"
|
||||
reviewed: "2026-10-09"
|
||||
---
|
||||
|
||||
# Project goal
|
||||
|
|
@ -42,6 +42,16 @@ dependency between the rollback prerequisite and T04. Preparation artifacts:
|
|||
[inventory](inventory/README.md), [taxonomy](taxonomy/README.md), and
|
||||
[preparation record](history/2026-09-28-preparation.md).
|
||||
|
||||
## Access-control coordination — 2026-10-09
|
||||
|
||||
ORGREF-WP-0002 independently coordinates the accepted October 8 policy: private
|
||||
forge repositories and packages, external deliberate publication, authenticated
|
||||
consumers and staged visibility changes. T01–T03 have accepted preparation;
|
||||
identity implementation and closure proofs remain with their owner records.
|
||||
See [policy](decisions/forge-visibility-policy.md) and the
|
||||
[October 9 review](history/2026-10-09-review-and-maintenance.md). This work does
|
||||
not satisfy or waive the four-condition org-transfer entry gate.
|
||||
|
||||
## Invariants
|
||||
|
||||
1. **No silent breakage.** Every reference that moves is inventoried before it
|
||||
|
|
|
|||
13
SCOPE.md
13
SCOPE.md
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
Temporary coordination repo for splitting the single `coulomb/` Forgejo
|
||||
organization into stewardship organizations without breaking GitOps, image
|
||||
pulls, or clones.
|
||||
pulls, or clones, and coordinating private forge access under ORGREF-WP-0002.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -32,6 +32,8 @@ by ID; it does not duplicate their tasks.
|
|||
package registry
|
||||
- Migration sequencing, rollback plan, and cutover evidence
|
||||
- Promotion of the durable org-assignment convention to a permanent home
|
||||
- Forge access-consumer inventory, identity-owner coordination and visibility
|
||||
sequencing under the accepted ORGREF-WP-0002 policy
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -53,9 +55,10 @@ by ID; it does not duplicate their tasks.
|
|||
|
||||
| Repo | Role |
|
||||
| --- | --- |
|
||||
| `railiance-forge` | Forgejo runtime owner — executes org/transfer operations |
|
||||
| `railiance-forge` | Historical forge-owner records; re-confirm ownership before org/transfer operations |
|
||||
| `key-cape` / `net-kingdom` | OIDC contract and KnownSecurityCase client registration |
|
||||
| `railiance-platform` | ArgoCD Application sources, OpenBao lanes referencing repo URLs |
|
||||
| `railiance-apps` | Chart image `repository:` paths and chart metadata |
|
||||
| `railiance-apps` | Current Forgejo Helm/runtime and runner owner; chart image paths and metadata |
|
||||
| `railiance-master` | Durable home candidate for the org-assignment convention |
|
||||
| `the-custodian` | Canon home candidate; repo-classification alignment |
|
||||
| Current server-backed `coulomb/` repo inventory | Subjects of the migration; 112 was the seed count |
|
||||
|
|
@ -97,3 +100,7 @@ project's workplans, never copied.
|
|||
both tasks wait on explicit evidence and acceptance gaps. Execution requires the unchanged
|
||||
`ORGREF-WP-0001` entry gate and resolution of its circular rollback dependency
|
||||
- Evidence and next sequence: [readiness review](history/2026-09-28-readiness-review.md)
|
||||
|
||||
- October 8–9: WP-0002 policy and inventories accepted; identity integration
|
||||
waits on KEY-WP-0038 / NK-WP-0048 and RAPPS-WP-0016. Private forge access
|
||||
is independent of the unchanged WP-0001 org-transfer gate.
|
||||
|
|
|
|||
57
history/2026-10-09-review-and-maintenance.md
Normal file
57
history/2026-10-09-review-and-maintenance.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Review and maintenance — 2026-10-09
|
||||
|
||||
User instruction: check changes and open work, review and implement, then commit
|
||||
and sync. The checkout began clean and matched origin/main after fetch.
|
||||
|
||||
## Open work and authority
|
||||
|
||||
ORGREF-WP-0001 remains blocked: T01/T02 wait on authenticated completeness and
|
||||
stewardship acceptance; T03–T05 retain all four entry conditions. No new evidence
|
||||
establishes readiness and the rollback-gate amendment remains proposed.
|
||||
|
||||
ORGREF-WP-0002 has T01–T03 done and T04–T09 waiting. The October 8 policy settles
|
||||
private packages, no anonymous forge access, Telegram uploads, the interim
|
||||
visibility-only guardrails and the agent driver. KEY-WP-0038 has an accepted
|
||||
claim contract; registration T02 waits on NK-WP-0048-T06, and claims proof T03
|
||||
is pending. RAPPS-WP-0016 has break-glass preparation T01 todo; authentication
|
||||
and account/team proof remain waiting. Their stable IDs are linked in this
|
||||
project's workplan. Owner implementation stays in those repositories.
|
||||
|
||||
## Implemented
|
||||
|
||||
- Corrected access collector package-index classification: API namespaces do
|
||||
not establish repository identity. Hostnames remain distinct for legacy-host
|
||||
disposition, rather than combining old and current forge references.
|
||||
- Schema 0.2 replaces the unsupported anonymous-pull inference with a count of
|
||||
pulls without named credentials. Node and service-account authentication
|
||||
require separate evidence, and named secrets are not proof of working access.
|
||||
- Made traversal deterministic, excluded the coordinator's generated artifacts,
|
||||
reused the reference collector's sensitive/binary exclusions, and reported
|
||||
exclusions and read errors. Symlinks and non-regular files (including FIFOs)
|
||||
are not followed or read; diagnostics never contain raw exception text.
|
||||
- Reconciled GOAL/SCOPE and WP-0002 with the accepted policy and actual owner
|
||||
records. Moved collector authentication proof to T06, before closure; T09
|
||||
verifies completeness and handback. Retained interim and normal wave gates.
|
||||
- Qualified the AGENTS.md gate explicitly as WP-0001's gate, consistent with
|
||||
WP-0002's existing independence. No condition or rollback exception changed.
|
||||
- Put task headings before their task blocks so projected titles refer to the
|
||||
correct stable task IDs. Removed obsolete undecided-policy claims and withdrew
|
||||
unsafe inline Secret/registry inspection commands while preserving the dated
|
||||
founder results.
|
||||
|
||||
## Validation and limits
|
||||
|
||||
Ten unit tests pass, covering CLI determinism, separate hosts, package targets,
|
||||
credential uncertainty, sensitive/binary/symlink/FIFO exclusions, sanitized read
|
||||
errors and the existing migration inventory checks. Whitespace validation passes.
|
||||
A scoped CLI replay scanned key-cape and reused the retained October 8 runtime
|
||||
capture: 10 GitOps consumers, 43 image pulls without named credentials, zero
|
||||
scan errors. The coordinator was correctly excluded. Temporary output remains
|
||||
in /tmp; retained accepted evidence was not overwritten.
|
||||
|
||||
A fleet-wide replay was interrupted after it did not finish promptly; it is not
|
||||
complete evidence. The scoped replay is a collector integration check, not a
|
||||
fresh fleet census, deployable-coverage receipt or unchanged-fleet G1 pair.
|
||||
No credential requests, Secret reads, owner assignments, forge visibility
|
||||
changes, transfers or other runtime changes were made. Remaining work stays
|
||||
with the documented decisions and owner records.
|
||||
|
|
@ -23,7 +23,7 @@ Secrets and node registry configuration were not inspected.
|
|||
| package index | kaizen-agentic, vergabe-teilnahme | PyPI `simple/` index without credentials | package-read token |
|
||||
| federation | reuse-surface | about 60 capability indexes by raw URL | read identity, or a published index location |
|
||||
| code readers | about 8 files, below | clones, API and raw reads without token handling | owner review; re-point to an authenticated path |
|
||||
| campaign images | pr-hall-of-helix, fluid-telegram | Telegram fetching post images by raw URL | a deliberate public asset location (ORGREF-WP-0002-T01 point 4) |
|
||||
| campaign images | pr-hall-of-helix, fluid-telegram | Telegram fetching post images by raw URL | adapter fetches privately and uploads to Telegram (accepted policy; FT-WP-0001-T14) |
|
||||
|
||||
Lower risk: `repo-gitops` and `repo-image-reference` are the cluster consumers
|
||||
seen from their source repos. `doc-link` means links break for the public, not
|
||||
|
|
@ -71,29 +71,15 @@ URLs and whynot-design's SSH `repository` field.
|
|||
All 160 local clones use SSH with the founder's key (`forgejo-remote`). They keep
|
||||
working as long as that account can read the repositories.
|
||||
|
||||
## Founder checks (presence only, no secret values)
|
||||
## Historical founder checks
|
||||
|
||||
Agents do not read Secrets, so run these in your own terminal and paste the output.
|
||||
|
||||
1. ArgoCD repository credentials, by name, type and URL only:
|
||||
|
||||
```bash
|
||||
kubectl -n argocd get secrets \
|
||||
-l 'argocd.argoproj.io/secret-type in (repository,repo-creds)' \
|
||||
-o go-template='{{range .items}}{{.metadata.name}} {{index .metadata.labels "argocd.argoproj.io/secret-type"}} {{if .data.url}}{{.data.url | base64decode}}{{end}}{{"\n"}}{{end}}'
|
||||
```
|
||||
|
||||
2. k3s registry configuration on railiance01, as key names with every value
|
||||
stripped:
|
||||
|
||||
```bash
|
||||
ssh railiance01 'sudo test -f /etc/rancher/k3s/registries.yaml \
|
||||
&& sudo sed -nE "s/^([[:space:]]*[A-Za-z0-9_.\"-]+:).*/\1/p" /etc/rancher/k3s/registries.yaml \
|
||||
|| echo absent'
|
||||
```
|
||||
|
||||
`absent`, or no `auth:` under the forge host, means all 43 pulls depend on
|
||||
anonymous registry access today.
|
||||
The October 8 results below are preserved as dated owner evidence. The original
|
||||
inline Secret template and registry-value-stripping commands are withdrawn:
|
||||
the estate forbids standalone go-template/jsonpath against real Secrets, and
|
||||
text stripping is not a reliable boundary for registry credentials. Do not
|
||||
repeat them. Obtain a current sanitized receipt from the platform/node owner;
|
||||
Secret annotation checks use the output-suppressing platform maintenance helper
|
||||
specified in the-custodian's agent environment orientation, section 6.
|
||||
|
||||
### Results, 2026-10-08 (run by the founder)
|
||||
|
||||
|
|
@ -114,3 +100,20 @@ Agents do not read Secrets, so run these in your own terminal and paste the outp
|
|||
- Consumers outside these working trees (external sites and links, other
|
||||
people's clones) cannot be found by scanning; T08's anonymous probe after each
|
||||
wave is the backstop.
|
||||
|
||||
## Collector corrections — 2026-10-09
|
||||
|
||||
The retained October 8 JSON uses schema 0.1. Its `anonymous_image_pulls` field
|
||||
was an inference from absent pod pull-secret names; only the separate founder
|
||||
node-auth check supports the anonymous-access conclusion for that snapshot.
|
||||
Do not reuse this inference for another cluster or capture.
|
||||
|
||||
Schema 0.2 reports `image_pulls_without_named_credentials`, with access marked
|
||||
unknown until node/service-account credential evidence exists. It preserves
|
||||
forge hostnames, so legacy references stay separate, and classifies package
|
||||
API indexes as package consumers with null repository targets (package namespace
|
||||
is not repository identity). `scan_exclusions` and `scan_error_count` expose
|
||||
coverage limits; the coordinator is omitted to avoid self-referential output.
|
||||
Sensitive/encrypted, binary and symlinked paths are not read. The source scan is
|
||||
deterministic for unchanged inputs, but no claim of full consumer completeness
|
||||
or deployment state follows from matching outputs.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
import sys
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools"))
|
||||
from access_consumers import classify, runtime_consumers, target_repo # noqa: E402
|
||||
from access_consumers import classify, runtime_consumers, scan_repo, target_repo # noqa: E402
|
||||
|
||||
|
||||
class ClassifyTest(unittest.TestCase):
|
||||
|
|
@ -18,6 +23,8 @@ class ClassifyTest(unittest.TestCase):
|
|||
("README.md", "/coulomb/x", "see", "doc-link"),
|
||||
("docs/evidence/run.json", "/coulomb/x", "x", "record"),
|
||||
("Makefile", "/coulomb/x", "push", "build-tooling"),
|
||||
("pyproject.toml", "/api/packages/coulomb/pypi/simple", "url: x", "package-consumer"),
|
||||
("client.py", "/api/v1/packages/coulomb", "u = ...", "package-consumer"),
|
||||
]
|
||||
for f, p, line, want in cases:
|
||||
self.assertEqual(classify(f, p, line), want, f)
|
||||
|
|
@ -28,16 +35,73 @@ class ClassifyTest(unittest.TestCase):
|
|||
self.assertEqual(target_repo("/v2/coulomb/flex-auth/manifests/x"), "coulomb/flex-auth")
|
||||
self.assertEqual(target_repo("/coulomb/activity-core@sha256:ab"), "coulomb/activity-core")
|
||||
self.assertIsNone(target_repo("/user/login"))
|
||||
self.assertIsNone(target_repo("/api/packages/coulomb/pypi/simple"))
|
||||
self.assertIsNone(target_repo("/api/v1/packages/coulomb"))
|
||||
self.assertIsNone(target_repo("/api/v1/orgs/coulomb/repos"))
|
||||
|
||||
def test_runtime_anonymous_pull(self):
|
||||
def test_missing_pull_secret_does_not_prove_anonymous_access(self):
|
||||
rt = {"applications": [], "workloads": [{
|
||||
"namespace": "ns", "kind": "Deployment", "name": "api", "image_pull_secret_names": [],
|
||||
"images": ["forgejo.coulomb.social/coulomb/x@sha256:00"]}]}
|
||||
(c,) = runtime_consumers(rt)
|
||||
self.assertEqual(c["class"], "cluster-image-pull")
|
||||
self.assertTrue(c["credential"].startswith("anonymous"))
|
||||
self.assertTrue(c["credential"].startswith("unknown"))
|
||||
self.assertEqual(c["target"], "coulomb/x")
|
||||
|
||||
def test_scan_excludes_sensitive_and_binary_files_without_values(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
repo = Path(d)
|
||||
(repo / "client.py").write_text('url = "https://user:hidden@forgejo.coulomb.social/coulomb/valid?token=hidden"\n')
|
||||
(repo / ".env").write_text("forgejo.coulomb.social/coulomb/private-value\n")
|
||||
(repo / "secret.sops.yaml").write_text("forgejo.coulomb.social/coulomb/encrypted-value\n")
|
||||
(repo / "binary").write_bytes(b"\0forgejo.coulomb.social/coulomb/binary-value")
|
||||
(repo / "link.py").symlink_to(repo / "client.py")
|
||||
os.mkfifo(repo / "pipe")
|
||||
diagnostics = []
|
||||
hits = scan_repo(repo, diagnostics)
|
||||
self.assertEqual([h["target"] for h in hits], ["coulomb/valid"])
|
||||
serialized = json.dumps(hits)
|
||||
for value in ["hidden", "private-value", "encrypted-value", "binary-value"]:
|
||||
self.assertNotIn(value, serialized)
|
||||
self.assertEqual({x["reason"] for x in diagnostics},
|
||||
{"sensitive-or-encrypted", "binary-content", "symlink-not-followed", "non-regular-file"})
|
||||
|
||||
def test_cli_is_repeatable_and_keeps_legacy_host_separate(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
root = Path(d)
|
||||
repo = root / "reader"
|
||||
repo.mkdir()
|
||||
subprocess.run(["git", "init", "-q", str(repo)], check=True)
|
||||
(repo / "client.py").write_text(
|
||||
'a = "https://forgejo.coulomb.social/coulomb/x"\n'
|
||||
'b = "https://gitea.coulomb.social/coulomb/x"\n')
|
||||
runtime = root / "runtime.json"
|
||||
runtime.write_text(json.dumps({"applications": [], "workloads": []}))
|
||||
assignments = root / "assignments.csv"
|
||||
assignments.write_text("repository,proposed_org\nx,netkingdom\n")
|
||||
output = root / "output.json"
|
||||
command = [sys.executable, str(Path(__file__).resolve().parents[1] / "tools/access_consumers.py"),
|
||||
"--root", str(root), "--runtime", str(runtime),
|
||||
"--assignments", str(assignments), "--output", str(output)]
|
||||
subprocess.run(command, check=True, capture_output=True)
|
||||
first = output.read_bytes()
|
||||
subprocess.run(command, check=True, capture_output=True)
|
||||
self.assertEqual(first, output.read_bytes())
|
||||
report = json.loads(first)
|
||||
self.assertEqual({c["host"] for c in report["consumers"]},
|
||||
{"forgejo.coulomb.social", "gitea.coulomb.social"})
|
||||
self.assertEqual(report["scan_error_count"], 0)
|
||||
|
||||
def test_unreadable_file_is_reported_without_raw_error(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
repo = Path(d)
|
||||
(repo / "client.py").write_text("placeholder")
|
||||
diagnostics = []
|
||||
with patch.object(Path, "read_text", side_effect=OSError("private error detail")):
|
||||
self.assertEqual(scan_repo(repo, diagnostics), [])
|
||||
self.assertEqual(diagnostics, [{"repository": repo.name,
|
||||
"path": "client.py", "reason": "unreadable-file"}])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -17,9 +17,12 @@ import csv
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from inventory import BINARY, SENSITIVE
|
||||
|
||||
FORGE_HOSTS = ("forgejo.coulomb.social", "gitea.coulomb.social")
|
||||
URL = re.compile(
|
||||
r"(?:https?://)?(?P<host>forgejo\.coulomb\.social|gitea\.coulomb\.social)"
|
||||
|
|
@ -34,6 +37,7 @@ KINDS = [
|
|||
("record", lambda f, p, line: f.startswith(("docs/evidence/", "evidence/", "tests/", "test/"))
|
||||
or "/evidence/" in f or "/tests/" in f or f.endswith(("PKG-INFO", ".sum"))),
|
||||
("raw-fetch", lambda f, p, line: "/raw/" in p or "/media/" in p),
|
||||
("package-consumer", lambda f, p, line: p.startswith(("/api/packages/", "/api/v1/packages/"))),
|
||||
("api-client", lambda f, p, line: p.startswith("/api/")),
|
||||
("ci-action", lambda f, p, line: ".forgejo/workflows" in f or ".gitea/workflows" in f
|
||||
or ".github/workflows" in f),
|
||||
|
|
@ -65,29 +69,68 @@ def target_repo(path):
|
|||
parts = [p for p in (path or "").split("/") if p]
|
||||
if parts[:2] == ["api", "v1"] and len(parts) >= 5 and parts[2] == "repos":
|
||||
parts = parts[3:]
|
||||
elif parts and parts[0] in {"api", "v2"}:
|
||||
elif parts and parts[0] == "api":
|
||||
# Package paths name an owner/index, not a repository. Other API
|
||||
# endpoints also cannot establish a repository identity.
|
||||
return None
|
||||
elif parts and parts[0] == "v2":
|
||||
parts = parts[1:]
|
||||
if len(parts) >= 2 and parts[0] not in {"user", "explore", "assets"}:
|
||||
return f"{parts[0]}/{parts[1].removesuffix('.git').split('@')[0].split(':')[0]}"
|
||||
return None
|
||||
|
||||
|
||||
def scan_repo(repo):
|
||||
def scan_repo(repo, diagnostics=None):
|
||||
hits = []
|
||||
for dirpath, dirnames, filenames in os.walk(repo):
|
||||
dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS and not d.startswith(".")
|
||||
or d in {".forgejo", ".gitea", ".github"}]
|
||||
for name in filenames:
|
||||
if name in SKIP_FILES:
|
||||
continue
|
||||
diagnostics = diagnostics if diagnostics is not None else []
|
||||
def excluded(path, reason):
|
||||
diagnostics.append({"repository": repo.name, "path": path, "reason": reason})
|
||||
def walk_error(error):
|
||||
excluded(str(Path(error.filename).relative_to(repo)), "unreadable-directory")
|
||||
for dirpath, dirnames, filenames in os.walk(repo, onerror=walk_error):
|
||||
kept = []
|
||||
for d in sorted(dirnames):
|
||||
rel = str((Path(dirpath) / d).relative_to(repo))
|
||||
if (Path(dirpath) / d).is_symlink():
|
||||
excluded(rel, "symlink-not-followed")
|
||||
elif d in SKIP_DIRS or (d.startswith(".") and d not in {".forgejo", ".gitea", ".github"}):
|
||||
excluded(rel, "excluded-directory")
|
||||
else:
|
||||
kept.append(d)
|
||||
dirnames[:] = kept
|
||||
for name in sorted(filenames):
|
||||
full = Path(dirpath) / name
|
||||
rel = str(full.relative_to(repo))
|
||||
if name in SKIP_FILES:
|
||||
excluded(rel, "generated-work-record")
|
||||
continue
|
||||
if SENSITIVE.search(rel):
|
||||
excluded(rel, "sensitive-or-encrypted")
|
||||
continue
|
||||
if full.suffix.lower() in BINARY:
|
||||
excluded(rel, "binary-extension")
|
||||
continue
|
||||
try:
|
||||
if full.is_symlink() or full.stat().st_size > MAX_BYTES:
|
||||
if full.is_symlink():
|
||||
excluded(rel, "symlink-not-followed")
|
||||
continue
|
||||
info = full.stat()
|
||||
if not stat.S_ISREG(info.st_mode):
|
||||
excluded(rel, "non-regular-file")
|
||||
continue
|
||||
if info.st_size > MAX_BYTES:
|
||||
excluded(rel, "over-1MB")
|
||||
continue
|
||||
text = full.read_text(encoding="utf-8")
|
||||
except (OSError, UnicodeDecodeError):
|
||||
except UnicodeDecodeError:
|
||||
excluded(rel, "non-utf8-content")
|
||||
continue
|
||||
except OSError:
|
||||
excluded(rel, "unreadable-file")
|
||||
continue
|
||||
if "\0" in text:
|
||||
excluded(rel, "binary-content")
|
||||
continue
|
||||
rel = str(full.relative_to(repo))
|
||||
for number, line in enumerate(text.splitlines(), 1):
|
||||
for m in URL.finditer(line):
|
||||
hits.append({
|
||||
|
|
@ -119,7 +162,7 @@ def runtime_consumers(runtime):
|
|||
m = URL.search(u)
|
||||
if m:
|
||||
out.append({"consumer": f"argocd:{app['name']}", "class": "cluster-gitops",
|
||||
"kind": "gitops-source", "target": target_repo(m.group("path")),
|
||||
"host": m.group("host"), "kind": "gitops-source", "target": target_repo(m.group("path")),
|
||||
"credential": "unknown (ArgoCD repo credentials not inspected)"})
|
||||
for w in runtime.get("workloads", []):
|
||||
for image in w.get("images", []):
|
||||
|
|
@ -128,9 +171,10 @@ def runtime_consumers(runtime):
|
|||
secrets = w.get("image_pull_secret_names") or []
|
||||
out.append({"consumer": f"k3s:{w['namespace']}/{w['kind']}/{w['name']}",
|
||||
"class": "cluster-image-pull", "kind": "image-pull",
|
||||
"host": m.group("host"),
|
||||
"target": target_repo(m.group("path")),
|
||||
"credential": "pull secret named: " + ",".join(secrets)
|
||||
if secrets else "anonymous (no imagePullSecrets)"})
|
||||
if secrets else "unknown (no imagePullSecrets; node registry auth not inspected)"})
|
||||
return out
|
||||
|
||||
|
||||
|
|
@ -160,17 +204,19 @@ def main():
|
|||
consumers = runtime_consumers(runtime)
|
||||
remotes = collections.Counter()
|
||||
scanned = []
|
||||
diagnostics = []
|
||||
coordinator = Path(__file__).resolve().parents[1]
|
||||
for repo in sorted(Path(args.root).iterdir()):
|
||||
if not (repo / ".git").exists() or repo.name.startswith("rescued-"):
|
||||
if not (repo / ".git").exists() or repo.name.startswith("rescued-") or repo.resolve() == coordinator:
|
||||
continue
|
||||
scanned.append(repo.name)
|
||||
remotes[remote_form(repo) or "none"] += 1
|
||||
grouped = collections.defaultdict(list)
|
||||
for h in scan_repo(repo):
|
||||
grouped[(h["kind"], h["target"])].append(f"{h['file']}:{h['line']}")
|
||||
for (kind, target), where in sorted(grouped.items(), key=lambda kv: (kv[0][0], str(kv[0][1]))):
|
||||
for h in scan_repo(repo, diagnostics):
|
||||
grouped[(h["host"], h["kind"], h["target"])].append(f"{h['file']}:{h['line']}")
|
||||
for (host, kind, target), where in sorted(grouped.items(), key=lambda kv: (kv[0][0], kv[0][1], str(kv[0][2]))):
|
||||
consumers.append({"consumer": f"repo:{repo.name}", "class": CLASS_OF_KIND[kind],
|
||||
"kind": kind, "target": target, "locations": where[:10],
|
||||
"host": host, "kind": kind, "target": target, "locations": where[:10],
|
||||
"location_count": len(where)})
|
||||
|
||||
for c in consumers:
|
||||
|
|
@ -179,19 +225,28 @@ def main():
|
|||
|
||||
summary = collections.Counter(c["class"] for c in consumers)
|
||||
report = {
|
||||
"schema_version": "0.1",
|
||||
"schema_version": "0.2",
|
||||
"workplan": "ORGREF-WP-0002-T02",
|
||||
"runtime_capture": os.path.basename(args.runtime),
|
||||
"scanned_repositories": len(scanned),
|
||||
"local_remote_forms": dict(remotes),
|
||||
"summary_by_class": dict(sorted(summary.items())),
|
||||
"anonymous_image_pulls": sum(1 for c in consumers if c["class"] == "cluster-image-pull"
|
||||
and c["credential"].startswith("anonymous")),
|
||||
"image_pulls_without_named_credentials": sum(1 for c in consumers if c["class"] == "cluster-image-pull"
|
||||
and c["credential"].startswith("unknown")),
|
||||
"consumers": consumers,
|
||||
"scan_exclusions": diagnostics,
|
||||
"scan_error_count": sum(d["reason"].startswith("unreadable") for d in diagnostics),
|
||||
"limits": [
|
||||
"Runtime classes come from the runtime capture named above, not a live read.",
|
||||
"ArgoCD repository credentials and pull-secret contents were not inspected; "
|
||||
"a named pull secret is not proof that it authenticates to the forge.",
|
||||
"Missing imagePullSecrets do not prove anonymous pulls: node-level registry "
|
||||
"auth and service-account defaults require owner evidence.",
|
||||
"Sensitive/encrypted, binary, large, symlinked and excluded paths are recorded "
|
||||
"in scan_exclusions; unreadable paths prevent a completeness claim.",
|
||||
"The coordinator is excluded to prevent generated inventory self-reference.",
|
||||
"Hostnames are preserved so legacy-host references can be reviewed separately.",
|
||||
"Package indexes name a namespace, not a verified repository; their target is null.",
|
||||
"Only literal forge hostnames are found; computed URLs and consumers outside "
|
||||
"these working trees (external sites, Telegram, other people's clones) are not.",
|
||||
"history/ and archived/ directories are skipped; they record, they do not read.",
|
||||
|
|
@ -199,7 +254,8 @@ def main():
|
|||
}
|
||||
Path(args.output).write_text(json.dumps(report, indent=1, sort_keys=False) + "\n")
|
||||
print(json.dumps({k: report[k] for k in ("scanned_repositories", "local_remote_forms",
|
||||
"summary_by_class", "anonymous_image_pulls")},
|
||||
"summary_by_class", "image_pulls_without_named_credentials",
|
||||
"scan_error_count")},
|
||||
indent=1))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,8 @@ It must not:
|
|||
|
||||
## Tasks
|
||||
|
||||
### T01 — Build the repeatable reference inventory
|
||||
|
||||
```task
|
||||
id: ORGREF-WP-0001-T01
|
||||
status: wait
|
||||
|
|
@ -159,6 +161,8 @@ in `railiance-platform/argocd/applications/`; `repository:
|
|||
forgejo.coulomb.social/coulomb/core-hub` in `railiance-apps/charts/core-hub/values.yaml`;
|
||||
`home:`/`sources:` URLs across at least five charts.
|
||||
|
||||
### T02 — Agree stewardship criteria and taxonomy
|
||||
|
||||
```task
|
||||
id: ORGREF-WP-0001-T02
|
||||
status: wait
|
||||
|
|
@ -200,6 +204,8 @@ home, both pending acceptance. Decision
|
|||
whynot as an additional steward, and records evidence for ops-hub/disaster-control.
|
||||
Naming and maintainer acceptance remain pending.
|
||||
|
||||
### T03 — Verify Git and package transfer behavior
|
||||
|
||||
```task
|
||||
id: ORGREF-WP-0001-T03
|
||||
status: wait
|
||||
|
|
@ -222,6 +228,8 @@ documentation — the container-registry path is the dangerous one because its
|
|||
failure is deferred to the next pod reschedule rather than surfacing at rename
|
||||
time.
|
||||
|
||||
### T04 — Prove transfer rollback
|
||||
|
||||
```task
|
||||
id: ORGREF-WP-0001-T04
|
||||
status: wait
|
||||
|
|
@ -240,6 +248,8 @@ Record exact operation, product/version, old/new paths, uncached pulls, GitOps
|
|||
revision/health, retention protections, credentials scope, cleanup and recovery
|
||||
time. Unrelated application rollout rollback is not acceptable transfer proof.
|
||||
|
||||
### T05 — Write the staged cutover plan
|
||||
|
||||
```task
|
||||
id: ORGREF-WP-0001-T05
|
||||
status: wait
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ flavor: implementation
|
|||
owner: claude-code
|
||||
topic_slug: railiance
|
||||
created: "2026-10-08"
|
||||
updated: "2026-10-08"
|
||||
updated: "2026-10-09"
|
||||
depends_on: []
|
||||
related_repos:
|
||||
- key-cape
|
||||
|
|
@ -48,9 +48,10 @@ does not apply. It changes **who may read**, not where things live.
|
|||
It reuses ORGREF-WP-0001's preparation: the `inventory/` collectors, the
|
||||
stewardship assignments in `taxonomy/` (as owners for each consumer) and the
|
||||
reference scan. It also gives back to ORGREF-WP-0001: once the forge is closed,
|
||||
the anonymous collector can no longer see private repositories, so T09 moves it
|
||||
to an authenticated read identity. That resolves the access half of decision
|
||||
`5f3680fc` (authenticated metadata).
|
||||
the anonymous collector can no longer see private repositories. T06 must prove
|
||||
its authenticated read identity before closure; T09 verifies the handback.
|
||||
Accepted complete metadata evidence can resolve the access half of decision
|
||||
`5f3680fc`; having a token alone does not establish completeness.
|
||||
|
||||
## Target state
|
||||
|
||||
|
|
@ -58,14 +59,15 @@ to an authenticated read identity. That resolves the access half of decision
|
|||
use NetKingdom-issued identities or scoped Forgejo tokens with an owner and a
|
||||
rotation path. A local administrator account remains only as sealed
|
||||
break-glass, with its credentials in OpenBao.
|
||||
- Organisations and repositories are **private by default**. Public visibility
|
||||
is an explicit, recorded allowlist entry with a reason.
|
||||
- Organisations, repositories and packages are private, with no anonymous
|
||||
forge access. Deliberate publication goes to a separate location through the
|
||||
reviewed per-repository `public/` convention.
|
||||
- Every consumer that read the forge anonymously reads it with its own
|
||||
credential, with least privilege, before its repositories close.
|
||||
- Campaign images that must stay world-readable (Telegram posts fetch them by
|
||||
URL) come from a deliberate public asset location, not from a forge repo that
|
||||
happens to be public.
|
||||
- Anonymous probes show only the allowlist; the evidence is recorded.
|
||||
- The fluid-telegram adapter reads private images with its own identity and
|
||||
uploads them to Telegram (FT-WP-0001-T14). A public asset store is later work.
|
||||
- Anonymous probes show no forge repositories or packages after the final wave;
|
||||
the evidence is recorded.
|
||||
|
||||
## Boundaries
|
||||
|
||||
|
|
@ -80,7 +82,9 @@ It must not:
|
|||
- configure key-cape, Forgejo, OpenBao or any consumer itself. Each change lands
|
||||
through the owner repo's own workplan.
|
||||
- flip a production repository's visibility before T07's rehearsal passes and
|
||||
the founder approves that wave
|
||||
the founder approves that wave, except the separately approved visibility-only
|
||||
interim route in `decisions/forge-visibility-policy.md` section 5, whose clone,
|
||||
sign-in, recovery and one-repository rollback checks remain mandatory
|
||||
- read or print secret values (credential routing: `warden route`)
|
||||
- move, rename or restructure repositories (that is ORGREF-WP-0001)
|
||||
|
||||
|
|
@ -90,6 +94,8 @@ separate review, not something the flip repairs.
|
|||
|
||||
## Tasks
|
||||
|
||||
### T01 — Decide the visibility policy
|
||||
|
||||
```task
|
||||
id: ORGREF-WP-0002-T01
|
||||
status: done
|
||||
|
|
@ -97,8 +103,6 @@ priority: high
|
|||
state_hub_task_id: "6387cb32-dd73-5ee0-b51e-d4aea475bcca"
|
||||
```
|
||||
|
||||
### T01 — Decide the visibility policy
|
||||
|
||||
Record a founder decision covering:
|
||||
|
||||
1. **Default:** private for organisations and repositories, plus whether
|
||||
|
|
@ -117,7 +121,7 @@ Record a founder decision covering:
|
|||
now with existing local accounts. If yes: which ones, accepting that their
|
||||
consumers move to local tokens temporarily and are re-homed in T06.
|
||||
|
||||
2026-10-08: points 1 and 2 decided, recorded in
|
||||
2026-10-08: all six policy points and the driver role decided, recorded in
|
||||
`decisions/forge-visibility-policy.md`. The forge is private with no anonymous
|
||||
access, and public codebases go to a separate location. Publication is a
|
||||
per-repo `public/` folder of symlinks only, projected outward by a shared
|
||||
|
|
@ -129,9 +133,14 @@ trust management comes later, not now. Points 5 and 6 decided: interim
|
|||
lockdown of 13 repositories under the founder's guardrail (tighten gradually,
|
||||
no lockout, no asset loss, nothing hard to repair: clones first, one repository,
|
||||
verify and roll back once, then small batches, visibility only). T03 runs in
|
||||
parallel. Pre-check: executor-sandbox (diverged, unpushed local commits) and
|
||||
ops-mason (uncommitted changes) wait for their owners. Open: point 3 (packages)
|
||||
and the owner.
|
||||
parallel. Packages are private; node registry auth, an ArgoCD credential template
|
||||
and scoped CI/package identities are the accepted approach. An agent drives the
|
||||
plan; the founder holds policy and production wave approval. Pre-check:
|
||||
executor-sandbox (diverged, unpushed local commits) and ops-mason (uncommitted
|
||||
changes) wait for their owners. All policy points are settled; implementation
|
||||
proof remains outstanding.
|
||||
|
||||
### T02 — Inventory every anonymous reader
|
||||
|
||||
```task
|
||||
id: ORGREF-WP-0002-T02
|
||||
|
|
@ -140,8 +149,6 @@ priority: high
|
|||
state_hub_task_id: "ace26a22-50e2-58c6-b4ad-85d95c284fce"
|
||||
```
|
||||
|
||||
### T02 — Inventory every anonymous reader
|
||||
|
||||
Read-only. Extend `inventory/` with an access-consumer inventory: every party
|
||||
that reads the forge today without credentials, the repositories or packages it
|
||||
reads, and the owner (from `taxonomy/assignments.csv`) who will give it an
|
||||
|
|
@ -178,6 +185,8 @@ pulls anonymous). One node-level registry auth entry would cover every pull
|
|||
(T06). The stale Gitea credential on the node is a cleanup item. Code-reader
|
||||
confirmation moves to the owners under T06.
|
||||
|
||||
### T03 — Review what has already been public
|
||||
|
||||
```task
|
||||
id: ORGREF-WP-0002-T03
|
||||
status: done
|
||||
|
|
@ -185,8 +194,6 @@ priority: high
|
|||
state_hub_task_id: "9c2ca7be-23f8-58c5-9bfd-e44a26ce4584"
|
||||
```
|
||||
|
||||
### T03 — Review what has already been public
|
||||
|
||||
Before closing, and independent of it: run a secret and sensitive-content scan
|
||||
over the public repositories' history (credentials, tokens, private keys,
|
||||
internal hostnames or IPs, personal data). Findings go to their owners for
|
||||
|
|
@ -207,19 +214,23 @@ rest is benign (identifiers, digests, test fixtures, placeholders). Record and
|
|||
limits: `inventory/history-exposure-2026-10-08.md`. The rotation is tracked with
|
||||
the owners.
|
||||
|
||||
### T04 — NetKingdom: Forgejo as an OIDC client (child workplan in key-cape)
|
||||
|
||||
```task
|
||||
id: ORGREF-WP-0002-T04
|
||||
status: wait
|
||||
depends_on: [ORGREF-WP-0002-T01]
|
||||
blocking_reason: "The NetKingdom side follows the visibility and identity decisions; open the key-cape child workplan once T01 is resolved."
|
||||
depends_on: [ORGREF-WP-0002-T01, KEY-WP-0038-T03]
|
||||
blocking_reason: "KEY-WP-0038-T03 must deliver claims evidence; registration T02 waits on NK-WP-0048-T06 (KnownSecurityCase), not an ad-hoc helper."
|
||||
priority: high
|
||||
state_hub_task_id: "112617e1-a7c3-59c1-bf25-235d58ec118b"
|
||||
```
|
||||
|
||||
### T04 — NetKingdom: Forgejo as an OIDC client (child workplan in key-cape)
|
||||
|
||||
Drafted 2026-10-08 as key-cape `KEY-WP-0038` (ready, founder-approved).
|
||||
Open a key-cape workplan, linked here by ID, to:
|
||||
Owner record: key-cape `KEY-WP-0038`
|
||||
(`098e3e2c-a138-560e-b2d3-3a15eb49d8a9`). Reviewed October 9: T01 claim
|
||||
contract accepted; T02 registration waits on net-kingdom `NK-WP-0048-T06`
|
||||
(`d9b26bcf-a7c6-59a2-92a0-f913d76a203c`); T03 claims proof is pending.
|
||||
The owner uses the accepted KnownSecurityCase route, with ops-mason and
|
||||
ops-warden, rather than a case-specific script. The owner record covers:
|
||||
|
||||
- register Forgejo as a confidential OIDC client (redirect URI, scopes, PKCE)
|
||||
with its client secret in OpenBao, through the railiance-platform custody path
|
||||
|
|
@ -233,20 +244,22 @@ Open a key-cape workplan, linked here by ID, to:
|
|||
Acceptance: a test login against a staging client returns the expected claims
|
||||
for one human, one agent and one service identity.
|
||||
|
||||
### T05 — Forgejo: NetKingdom login and team mapping (child workplan in railiance-apps)
|
||||
|
||||
```task
|
||||
id: ORGREF-WP-0002-T05
|
||||
status: wait
|
||||
depends_on: [ORGREF-WP-0002-T04]
|
||||
blocking_reason: "Forgejo can only add the authentication source once the NetKingdom client and claims exist."
|
||||
depends_on: [ORGREF-WP-0002-T04, RAPPS-WP-0016-T03]
|
||||
blocking_reason: "RAPPS-WP-0016-T03 must prove account linking and team grants after KEY-WP-0038 delivers the client and claims."
|
||||
priority: high
|
||||
state_hub_task_id: "6c635b25-a4ae-5259-9317-de3fe38f6234"
|
||||
```
|
||||
|
||||
### T05 — Forgejo: NetKingdom login and team mapping (child workplan in railiance-apps)
|
||||
|
||||
railiance-apps owns the Forgejo Helm release (`docs/forgejo-on-railiance01.md`).
|
||||
Drafted 2026-10-08 as railiance-apps `RAPPS-WP-0016` (ready, founder-approved).
|
||||
Open a railiance-apps workplan, linked here by ID, to:
|
||||
Owner record: railiance-apps `RAPPS-WP-0016`
|
||||
(`f6b1d6f6-0abb-5774-87b9-bf3cfb640a3b`). Reviewed October 9: break-glass
|
||||
preparation T01 is todo; authentication, linking and grants remain waiting.
|
||||
The owner record covers:
|
||||
|
||||
- add the NetKingdom OIDC authentication source and claim-to-team mapping;
|
||||
disable open registration; link existing local accounts (founder, agent
|
||||
|
|
@ -260,6 +273,8 @@ Open a railiance-apps workplan, linked here by ID, to:
|
|||
Acceptance: the founder and one agent sign in through NetKingdom and see what
|
||||
their team grants. Break-glass login is verified once and recorded.
|
||||
|
||||
### T06 — Give every consumer its own read identity
|
||||
|
||||
```task
|
||||
id: ORGREF-WP-0002-T06
|
||||
status: wait
|
||||
|
|
@ -269,23 +284,25 @@ priority: high
|
|||
state_hub_task_id: "1285f94d-3b57-502b-8aeb-96fab9578287"
|
||||
```
|
||||
|
||||
### T06 — Give every consumer its own read identity
|
||||
|
||||
For each consumer class in T02, its owner provisions a scoped credential and
|
||||
proves it works **against a private canary repository and package** created by
|
||||
this project, while anonymous access to that canary fails:
|
||||
|
||||
- cluster: deploy tokens for ArgoCD/Flux repo credentials, `imagePullSecrets`
|
||||
for registry pulls (railiance-apps, railiance-platform)
|
||||
- cluster: the accepted ArgoCD repository credential template and node-level
|
||||
read-only registry login (railiance-apps, railiance-platform and node owner);
|
||||
prove fresh pulls, including service-account and node credential behavior
|
||||
- runners: tokens or OIDC for checkout and reusable actions (railiance-apps, which runs `forgejo-runner`)
|
||||
- agents and workstations: NetKingdom identities or scoped tokens routed through
|
||||
ops-warden; nothing pasted into repositories
|
||||
- State Hub, repo-manager and the ORGREF collector: authenticated read identity
|
||||
- fluid-telegram and pr-hall-of-helix: images moved to the T01 public asset
|
||||
location, with the `compose visual` raw-URL bases updated
|
||||
- State Hub, repo-manager and the ORGREF collector: authenticated read identity,
|
||||
with private repository/package enumeration proven before T08
|
||||
- fluid-telegram and pr-hall-of-helix: private image fetch and Telegram upload
|
||||
through FT-WP-0001-T14; prove posts without anonymous raw-image reads
|
||||
|
||||
Each owner records its proof in its own workplan. This plan tracks the list.
|
||||
|
||||
### T07 — Rehearse the flip and the rollback
|
||||
|
||||
```task
|
||||
id: ORGREF-WP-0002-T07
|
||||
status: wait
|
||||
|
|
@ -295,14 +312,14 @@ priority: high
|
|||
state_hub_task_id: "0dcf25e2-cda1-5f87-b7fe-5b7e27635565"
|
||||
```
|
||||
|
||||
### T07 — Rehearse the flip and the rollback
|
||||
|
||||
On throwaway repositories and packages: switch public → private → public, and
|
||||
verify consumer reads, anonymous denial, web links, Actions runs and image pulls
|
||||
at each step. Record the exact commands or API calls, the time each step takes,
|
||||
and what a rollback restores (visibility) and what it does not (anything already
|
||||
fetched). This rehearsal is the gate for T08.
|
||||
|
||||
### T08 — Close the forge in waves
|
||||
|
||||
```task
|
||||
id: ORGREF-WP-0002-T08
|
||||
status: wait
|
||||
|
|
@ -313,8 +330,6 @@ priority: high
|
|||
state_hub_task_id: "42724077-9837-5aef-ad3b-6551f2103aa2"
|
||||
```
|
||||
|
||||
### T08 — Close the forge in waves
|
||||
|
||||
Change production visibility in founder-approved waves. Each wave lists its
|
||||
repositories and packages and the consumers that read them. Proposed order:
|
||||
|
||||
|
|
@ -327,6 +342,8 @@ After each wave: consumer health checks, anonymous probe, and a recorded
|
|||
rollback point. A failing consumer rolls its repositories back to public and
|
||||
stops the wave. The fix lands with the consumer's owner.
|
||||
|
||||
### T09 — Verify, hand back and record
|
||||
|
||||
```task
|
||||
id: ORGREF-WP-0002-T09
|
||||
status: wait
|
||||
|
|
@ -336,23 +353,27 @@ priority: medium
|
|||
state_hub_task_id: "82f53328-02f5-5953-830a-00019d7dee92"
|
||||
```
|
||||
|
||||
### T09 — Verify, hand back and record
|
||||
|
||||
- Anonymous probe of the server: only the T01 allowlist is visible. Record the
|
||||
evidence under `inventory/`.
|
||||
- Move ORGREF-WP-0001's collector to the authenticated identity from T06 and
|
||||
note the effect on decision `5f3680fc`.
|
||||
- Anonymous probe of the server: no repositories or packages are visible.
|
||||
Record the evidence under `inventory/`.
|
||||
- Verify ORGREF-WP-0001's collector still enumerates private metadata with the
|
||||
identity proven in T06; reconcile completeness and note the effect on decision
|
||||
`5f3680fc`. Do not postpone collector migration until after closure.
|
||||
- Add a row to the-custodian `docs/security-close-down-checklist.md`
|
||||
recording that the forge is closed by default, plus anything relaxed along the
|
||||
way that must be re-armed.
|
||||
- Record any consumer still on a temporary credential as live owner work, not as
|
||||
prose in this file.
|
||||
|
||||
## Open questions for review
|
||||
## Review — 2026-10-09
|
||||
|
||||
- Owner: is the founder the right owner, or should an agent own the driver with
|
||||
the founder holding T01/T08?
|
||||
- Should T03 (history review) block T08, or run in parallel and only gate
|
||||
rotations?
|
||||
- Is an interim lockdown (T01 point 5) worth the temporary-token churn, given how
|
||||
long T04–T07 will take?
|
||||
T01–T03 are done; T04–T09 remain waiting on owner implementation and proof.
|
||||
The October 8 policy settles driver ownership, package treatment, interim
|
||||
lockdown and parallel exposure review. No new production wave is authorized by
|
||||
this review. The interim route must satisfy its own recorded guardrails; the
|
||||
normal closure route retains T06/T07 and founder wave approval.
|
||||
|
||||
Collector schema 0.2 fixes package-index classification, separates legacy hosts,
|
||||
records scan exclusions/errors and treats missing pull secrets as unknown access.
|
||||
October 8 founder checks remain historical proof for that runtime snapshot;
|
||||
new runtime records require current owner confirmation. This review does not
|
||||
reopen T02 or replace its accepted evidence.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue