ACTIVITY-WP-0044-T02: read freshness-run.v1 results[] from rmgr
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 1m6s

The repo-manager runner reports per-repository rows under results[], not
receipts[]; the projection counted nothing against the real output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 692292@bnt-lap001
Assistant-Session: 89c7d8e8-e1b3-47eb-9411-a0ab227fca2f
This commit is contained in:
tegwick 2026-10-04 13:18:33 +02:00
parent d6eb788578
commit b2c6c56132
3 changed files with 29 additions and 10 deletions

View file

@ -156,11 +156,13 @@ def normalize_operation_result(operation_id: str, raw: Any) -> dict[str, Any]:
"script_exit_code": exit_code,
}
if operation_id == "repo_manager_freshness":
receipts = result.get("receipts")
# repo-manager.freshness-run.v1 reports one row per attempted repository
# under "results"; dry-run rows carry only a planned action, no outcome.
results = result.get("results")
outcomes = {name: 0 for name in REPO_MANAGER_FRESHNESS_OUTCOMES}
if isinstance(receipts, list):
for receipt in receipts:
outcome = receipt.get("outcome") if isinstance(receipt, dict) else None
if isinstance(results, list):
for row in results:
outcome = row.get("outcome") if isinstance(row, dict) else None
if outcome in outcomes:
outcomes[outcome] += 1
exit_code = result.get("exit_code", 0)
@ -171,7 +173,7 @@ def normalize_operation_result(operation_id: str, raw: Any) -> dict[str, Any]:
"apply": bool(result.get("apply")),
"limit": _safe_count(result.get("limit")),
"max_seconds": _safe_count(result.get("max_seconds")),
"receipt_count": len(receipts) if isinstance(receipts, list) else 0,
"result_count": len(results) if isinstance(results, list) else 0,
"outcomes": outcomes,
"exit_code": exit_code,
}

View file

@ -94,7 +94,7 @@ def test_invocation_projects_counts_only(monkeypatch):
def fake_run(argv, **kwargs):
calls.append((argv, kwargs["timeout"]))
out = {"receipts": [
out = {"schema": "repo-manager.freshness-run.v1", "results": [
{"repo": "a", "outcome": "applied", "detail": "must not persist"},
{"repo": "b", "outcome": "noop"},
{"repo": "c", "outcome": "refused"},
@ -106,7 +106,7 @@ def test_invocation_projects_counts_only(monkeypatch):
raw = repo_manager.repo_manager_freshness({"limit": 6, "max_seconds": 240, "apply": True})
projected = normalize_operation_result("repo_manager_freshness", raw)
assert calls[0][1] == 240 + repo_manager._TIMEOUT_GRACE_SECONDS
assert projected["receipt_count"] == 4
assert projected["result_count"] == 4
assert projected["outcomes"]["applied"] == 1
assert projected["outcomes"]["noop"] == 1
assert projected["outcomes"]["refused"] == 1
@ -114,6 +114,16 @@ def test_invocation_projects_counts_only(monkeypatch):
assert "must not persist" not in json.dumps(projected)
def test_dry_run_rows_without_outcome_count_nothing():
raw = {"schema": "repo-manager.freshness-run.v1", "dry_run": True,
"results": [{"slug": "a", "action": "reconcile"},
{"slug": "b", "action": "skip_noop"}],
"apply": False, "limit": 2, "max_seconds": 60, "exit_code": 0}
projected = normalize_operation_result("repo_manager_freshness", raw)
assert projected["result_count"] == 2
assert sum(projected["outcomes"].values()) == 0
def test_invocation_fails_on_non_json(monkeypatch):
monkeypatch.setattr(
repo_manager.subprocess, "run",
@ -127,7 +137,7 @@ def test_invocation_fails_on_non_json(monkeypatch):
async def test_execute_bounded_operation_dispatches_to_repo_manager(monkeypatch):
monkeypatch.setattr(
repo_manager, "repo_manager_freshness",
lambda params: {"receipts": [], "apply": False, "limit": 1, "max_seconds": 10},
lambda params: {"results": [], "apply": False, "limit": 1, "max_seconds": 10},
)
patch = await activities.execute_bounded_operation({"source": _source(apply=False)})
assert patch["freshness"]["kind"] == "repo_manager_freshness"

View file

@ -47,10 +47,9 @@ state_hub_task_id: "0cbbadc8-d217-55a3-bc2a-da81e19ec2a8"
```task
id: ACTIVITY-WP-0044-T02
status: wait
status: progress
priority: medium
needs_human: false
blocking_reason: "rmgr freshness run is being built in RMGR-WP-0020-T02; JSON output (receipts[].outcome) assumed from the T01 receipt contract"
depends_on: [RMGR-WP-0020-T01, RMGR-WP-0020-T02]
state_hub_task_id: "e4517812-aec2-57e9-9677-de4a89f3f48f"
```
@ -60,6 +59,14 @@ JSON object with `receipts[]` carrying `repo-manager.freshness-receipt.v1`
outcomes; adjust `normalize_operation_result` if the shape differs. Decide the
worker image/packaging of `rmgr` (no deployment in this workplan).
2026-10-04: RMGR-WP-0020-T01/T02 landed (repo-manager ad1ee3e). Flags match
(`--limit`, `--max-seconds` required, `--dry-run`). The run summary is
`repo-manager.freshness-run.v1` with per-repository rows under `results[]`
(not `receipts[]`); dry-run rows carry only a planned `action`. The projection
now reads `results[].outcome` and reports `result_count`; 35 focused tests pass.
Exit 1 with JSON (a failed attempt) still projects; exit 3/4 without JSON raise.
Remaining: decide how `rmgr` is packaged onto the worker.
## Founder-reviewed activation
```task