feat(retirement): route SBOM scans through repo-manager
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
This commit is contained in:
parent
b9d9ffed5f
commit
81861d816b
3 changed files with 70 additions and 22 deletions
|
|
@ -144,12 +144,12 @@ def record_mutation(
|
||||||
detail: dict[str, Any] | None = None,
|
detail: dict[str, Any] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
from datetime import datetime, timezone
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
path = meter_path()
|
path = meter_path()
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
row = {
|
row = {
|
||||||
"ts": datetime.now(timezone.utc).isoformat(),
|
"ts": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
|
||||||
"source": source,
|
"source": source,
|
||||||
"kind": kind,
|
"kind": kind,
|
||||||
"repo_slug": repo_slug,
|
"repo_slug": repo_slug,
|
||||||
|
|
@ -409,6 +409,31 @@ def rm_update_register_entry(
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def rm_scan_sbom(
|
||||||
|
*,
|
||||||
|
repo_path: str | Path,
|
||||||
|
repo_slug: str | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Derive a versioned SBOM snapshot from repository-owned sources."""
|
||||||
|
args = ["sbom", "scan", "--path", str(repo_path)]
|
||||||
|
if repo_slug:
|
||||||
|
args.extend(["--slug", repo_slug])
|
||||||
|
|
||||||
|
code, out, err = run_rmgr(args)
|
||||||
|
try:
|
||||||
|
result = json.loads(out.strip() or "{}")
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
result = {
|
||||||
|
"ok": False,
|
||||||
|
"error": f"rmgr non-json exit={code} stderr={err!r} stdout={out[:500]!r}",
|
||||||
|
}
|
||||||
|
if code != 0:
|
||||||
|
result.setdefault("ok", False)
|
||||||
|
result.setdefault("error", f"rmgr exit={code} stderr={err!r}")
|
||||||
|
result["exit_code"] = code
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def rm_scaffold(
|
def rm_scaffold(
|
||||||
*,
|
*,
|
||||||
repo_path: str | Path,
|
repo_path: str | Path,
|
||||||
|
|
|
||||||
|
|
@ -208,16 +208,13 @@ flow and needs a State Hub API restart to take effect. Piloting on
|
||||||
| | |
|
| | |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Flag | `RM_SLICE_REGISTERS` |
|
| Flag | `RM_SLICE_REGISTERS` |
|
||||||
| Owner API | **does not exist** — see the readiness correction above |
|
| Owner API | `rmgr register ...`; `rmgr sbom scan|licence-report` |
|
||||||
| Rollback | n/a until there is something to roll back from |
|
| Rollback | flag → `off`; retain current State Hub projection |
|
||||||
|
|
||||||
**Blocked.** repo-manager has no register surface: no SBOM ingest, no repo
|
**Owner ready (2026-08-21).** Finished RMGR-WP-0008 supplies the shared six-kind
|
||||||
goals, no contributions, no technical-debt or extension-point registry. This
|
register spine, governed compatibility adapter, lockfile-derived SBOM and
|
||||||
slice cannot start until a repo-manager workplan builds one.
|
licence/copyleft report. Historical SBOM rows are cache provenance; rebuild
|
||||||
|
from current repository sources rather than copying them into authority.
|
||||||
Low coupling and low write volume still make this the right slice to prove the
|
|
||||||
`RM_SLICE_*` pattern **once the owner side exists** — but that is a
|
|
||||||
repo-manager-side prerequisite, not a State Hub task.
|
|
||||||
|
|
||||||
### A4 · Work + repo UI — 22 items
|
### A4 · Work + repo UI — 22 items
|
||||||
`work-coordination-ui` (14, `replace`) · `repo-ui` (8)
|
`work-coordination-ui` (14, `replace`) · `repo-ui` (8)
|
||||||
|
|
@ -225,11 +222,14 @@ repo-manager-side prerequisite, not a State Hub task.
|
||||||
| | |
|
| | |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Flag | `RM_SLICE_UI` |
|
| Flag | `RM_SLICE_UI` |
|
||||||
| Owner API | owner-side dashboard; hub pages redirect |
|
| Owner API | hub-core projection/query plus hub/ops UI clients |
|
||||||
| Rollback | remove redirect; hub pages are unchanged behind it |
|
| Rollback | remove redirect; hub pages are unchanged behind it |
|
||||||
|
|
||||||
`replace`, not `move`: the contract changes. Must follow A1–A3, since the pages
|
`replace`, not `move`: the contract changes. Must follow A1–A3, since the pages
|
||||||
render what those slices serve.
|
render what those slices serve. RMGR-ADR-003 corrects the earlier inventory
|
||||||
|
assignment: Repo Manager owns the repository contracts, not a dashboard. A4's
|
||||||
|
UI implementation therefore rides with B5 even though these 22 inventory items
|
||||||
|
remain grouped here for traceability.
|
||||||
|
|
||||||
### A5 · Topic spine — 8 items
|
### A5 · Topic spine — 8 items
|
||||||
`topic-spine` (8, `replace`)
|
`topic-spine` (8, `replace`)
|
||||||
|
|
@ -240,14 +240,17 @@ render what those slices serve.
|
||||||
| Owner API | `rmgr` topic/classification contract |
|
| Owner API | `rmgr` topic/classification contract |
|
||||||
| Rollback | flag → `off` |
|
| Rollback | flag → `off` |
|
||||||
|
|
||||||
Last in Wave A: it is a `replace` under a changed contract, and A1–A4 read it.
|
The versioned Repo Manager classification contract is published and accepted by
|
||||||
|
finished HUB-WP-0004. Consumer route switching remains a State Hub cutover
|
||||||
|
action.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Wave B — hub-core (128 items) · gated on HUB-WP-0004
|
## Wave B — hub-core (128 items) · HUB-WP-0004 owner gate satisfied
|
||||||
|
|
||||||
HUB-WP-0004 is `proposed`. **No hub-side adapter work until it is at least
|
HUB-WP-0004 finished 2026-08-21 and accepted the project architecture, runtime,
|
||||||
`ready`.** Slices are listed to fix scope and order, not to start.
|
ports, conformance scaffold, and absorption plan. Individual production
|
||||||
|
cutovers still require their slice evidence and operator gates.
|
||||||
|
|
||||||
| Slice | Capabilities | Items |
|
| Slice | Capabilities | Items |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
|
@ -435,14 +438,14 @@ discovered live at cutover.
|
||||||
|
|
||||||
1. ~~Resolve the legacy-meter window discrepancy~~ — done 2026-08-20; E1 and the
|
1. ~~Resolve the legacy-meter window discrepancy~~ — done 2026-08-20; E1 and the
|
||||||
evidenced part of E2 are executed.
|
evidenced part of E2 are executed.
|
||||||
2. **Raise a `repo-manager` workplan for the register surface** (A3), the UI
|
2. ~~Build the Repo Manager receiving surface for A3/A5 and settle A4
|
||||||
surface (A4), and the topic-spine contract (A5). Until that exists, 79 of
|
ownership~~ — finished in RMGR-WP-0008. A4 belongs to hub-core projection UI,
|
||||||
Wave A's 165 items have nowhere to go. This is now the critical path for the
|
not Repo Manager.
|
||||||
whole retirement, ahead of anything State Hub can do to itself.
|
|
||||||
3. Cut **A2a** (task status + reconcile, ~6 items) — the only slice in the plan
|
3. Cut **A2a** (task status + reconcile, ~6 items) — the only slice in the plan
|
||||||
executable today. First step is operational: create `~/.repo-manager/dual-run.yaml`
|
executable today. First step is operational: create `~/.repo-manager/dual-run.yaml`
|
||||||
and enable writeback/reconcile for one pilot repo, then watch the mutation
|
and enable writeback/reconcile for one pilot repo, then watch the mutation
|
||||||
meter. A2b–A2e need repo-manager parsers and adapters that do not exist.
|
meter. A2b–A2e need repo-manager parsers and adapters that do not exist.
|
||||||
4. Do not open Wave B until HUB-WP-0004 reaches `ready`; 125 items sit behind it.
|
4. HUB-WP-0004's owner-readiness gate is satisfied; advance Wave B only through
|
||||||
|
its per-slice dual-run and operator gates.
|
||||||
5. Do not build register, UI, or topic capability in State Hub to unblock
|
5. Do not build register, UI, or topic capability in State Hub to unblock
|
||||||
items 2–3 — `policies/retirement-freeze.md` makes that inadmissible.
|
items 2–3 — `policies/retirement-freeze.md` makes that inadmissible.
|
||||||
|
|
|
||||||
|
|
@ -93,3 +93,23 @@ def test_rm_update_register_entry_builds_shared_spine_command(monkeypatch):
|
||||||
assert seen["args"][0:2] == ["register", "put"]
|
assert seen["args"][0:2] == ["register", "put"]
|
||||||
assert "technical-debt" in seen["args"]
|
assert "technical-debt" in seen["args"]
|
||||||
assert '{"severity":"high"}' in seen["args"]
|
assert '{"severity":"high"}' in seen["args"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_rm_scan_sbom_builds_derived_snapshot_command(monkeypatch):
|
||||||
|
seen = {}
|
||||||
|
|
||||||
|
def fake_run(args, *, timeout=120):
|
||||||
|
seen["args"] = args
|
||||||
|
return (
|
||||||
|
0,
|
||||||
|
'{"schema":"repo-manager.sbom-snapshot.v1","ok":true,"entry_count":3}',
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.setattr(adapter, "run_rmgr", fake_run)
|
||||||
|
result = adapter.rm_scan_sbom(repo_path="/repos/demo", repo_slug="demo")
|
||||||
|
|
||||||
|
assert result["ok"] is True
|
||||||
|
assert result["schema"] == "repo-manager.sbom-snapshot.v1"
|
||||||
|
assert result["exit_code"] == 0
|
||||||
|
assert seen["args"] == ["sbom", "scan", "--path", "/repos/demo", "--slug", "demo"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue