Prepare approved reporting repair and monthly review inputs

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e738-9e15-7600-87e6-90dec3a7beef
This commit is contained in:
tegwick 2026-09-28 11:22:19 +02:00
parent 9baf6f1829
commit a18d83f74a
16 changed files with 663 additions and 1 deletions

View file

@ -0,0 +1,6 @@
# Preserve the deployed grant-aware runtime; restore only the domain resolver.
FROM forgejo.coulomb.social/coulomb/activity-core@sha256:713bddad10a41950f446100a8b370fca8ccdd0b8969cccae751e3870c9c63ccd
COPY src/activity_core/context_resolvers/frontend_patterns.py /app/src/activity_core/context_resolvers/frontend_patterns.py
RUN printf '\nfrom activity_core.context_resolvers import frontend_patterns # noqa: F401\n' >> /app/src/activity_core/context_resolvers/__init__.py
# Image construction must prove both features coexist, without network or secrets.
RUN python -c "import activity_core.context_resolvers; from activity_core.context_resolvers.base import CONTEXT_RESOLVER_REGISTRY; from activity_core.repository_grant import RepositoryGrant; assert 'frontend-patterns' in CONTEXT_RESOLVER_REGISTRY; assert RepositoryGrant.model_validate({'version':'1','allowed_paths':['docs/'],'commit_count':{'min':1,'max':1},'publish':False}).payload()['publish'] is False"

View file

@ -0,0 +1,54 @@
# Bounded reporting repair for Activity Core
Owner task: FEP-WP-0008 T03, alongside existing ACTIVITY-WP-0041. No new workplan.
The September 27 grant-aware image promotion removed the frontend resolver. Keep
that grant-aware base. This overlay changes only the resolver module and its package
registration; it does not roll back grants, change schedules, or admit a mutation
executor. [Diagnosis](../../docs/evidence/2026-09-28-reporting-diagnosis.json).
`source.json` pins the upstream source revision and resolver hashes. Reproduce with
an isolated checkout of that exact activity-core revision:
```sh
git apply /home/worsch/frontend-patterns/integrations/activity-core/monthly-review.patch
cp /home/worsch/frontend-patterns/integrations/activity-core/Dockerfile Dockerfile.reporting-repair
cp /home/worsch/frontend-patterns/integrations/activity-core/test_frontend_monthly_review.py tests/
PYTHONDONTWRITEBYTECODE=1 .venv/bin/python -m pytest -q -p no:cacheprovider \
tests/test_frontend_patterns.py tests/test_frontend_monthly_review.py \
tests/test_repository_grant.py tests/test_resolve_context_binding.py
docker build -f Dockerfile.reporting-repair -t activity-core:fep-reporting-repair-20260928 .
```
The isolated checkout needs the owner's existing locked development environment.
The Docker build itself needs no new dependency installation: it extends the pinned
runtime and proves resolver registration plus typed grant support during construction.
The review compares all Python source hashes against the base, not only image labels.
The monthly extension reads this repository's `feedback/monthly-review-input.json`,
its cited evidence and learning ledger at the same resolved Git revision. It reports
six health dimensions as an **owner snapshot**, dated and stale after seven days;
these fields cannot grant execution authority. Measured research/execution/token/cost
sums include unknown-cycle counts and only the reporting month. Costs are not inferred
from tokens. At most three bounded decision proposals carry no-response defaults.
Missing input/evidence or invalid effort yields a degraded packet while retaining
receipt totals. The existing 3,900-character digest bound still applies. Daily and
weekly collection remain unchanged. It is not an independent live health observer.
The [October fixture](../../docs/evidence/2026-09-28-monthly-packet-fixture.json)
uses the built image with network disabled and a future clock. It is not an October
production run or completed monthly review. The first actual packet is due October 1.
Deploy only this worker's exact reviewed digest through the existing GitOps source
and ArgoCD path; preserve the API and event-router images and all non-image fields.
The current deployment is `428f2d7`, grant-aware base `713bddad…`; use that exact
revision as the rollback target, acknowledging that rollback restores broken domain
reporting. A rollback must preserve repository grants. Verify resolver and grant
hashes, all three definitions, a report-only scheduled smoke and monthly sink after
rollout. Do not retry the failed historical run as though it had succeeded naturally.
The owner's [release runbook](../../../activity-core/docs/gitops-release.md) requires
24 healthy hours and authenticated release authority. The domain failure invalidates
continuous-health claims; no admitted routine identity exists. An exact attended repair
exception is required before production promotion. Staging this artifact is not that
exception. No access/budget/policy expansion is part of the repair.

View file

@ -0,0 +1,77 @@
--- a/src/activity_core/context_resolvers/frontend_patterns.py
+++ b/src/activity_core/context_resolvers/frontend_patterns.py
@@ -10,6 +10,7 @@
from datetime import datetime, timezone
import hashlib
import json
+import math
from pathlib import PurePosixPath
import re
import time
@@ -149,6 +150,44 @@
return latest
+def monthly_review(root_sha: str, now: datetime, start: datetime, end: datetime) -> dict:
+ """Bounded, source-pinned owner snapshot and measured research totals.
+
+ This is review input, never authority or a live health attestation.
+ Missing/invalid input degrades the monthly packet while preserving receipts.
+ """
+ data = json.loads(raw("frontend-patterns", root_sha, "feedback/monthly-review-input.json"))
+ require(data["schema_version"] == 1, "invalid_monthly_input")
+ stamp = datetime.fromisoformat(data["as_of"].replace("Z", "+00:00"))
+ require(stamp.tzinfo is not None and stamp <= now, "invalid_monthly_date")
+ health = data["health"]
+ keys = {"schedule", "collection", "verification", "publication", "rollback", "executor"}
+ require(isinstance(health, dict) and set(health) == keys, "invalid_monthly_health")
+ require(all(isinstance(v, str) and 0 < len(v) <= 80 for v in health.values()), "invalid_monthly_health")
+ require(path_ok(data["evidence_ref"]), "invalid_monthly_evidence")
+ # The cited file must exist at this same source revision.
+ raw("frontend-patterns", root_sha, data["evidence_ref"])
+ decisions = data["decisions"]
+ require(isinstance(decisions, list) and len(decisions) <= 3, "invalid_monthly_decisions")
+ for decision in decisions:
+ require(isinstance(decision, dict) and set(decision) == {"question", "recommendation", "default"}, "invalid_monthly_decision")
+ require(all(isinstance(v, str) and 0 < len(v) <= 100 for v in decision.values()), "invalid_monthly_decision")
+ ledger = json.loads(raw("frontend-patterns", root_sha, "research/evolution/ledger.json"))
+ events = ledger["events"]
+ require(isinstance(events, list) and len(events) <= 1000, "invalid_learning_ledger")
+ selected = [e for e in events if start.date().isoformat() <= e["date"] < end.date().isoformat()]
+ totals = {}
+ for field in ("research_minutes", "execution_minutes", "reported_tokens", "cost_usd"):
+ values = [e["effort"][field] for e in selected]
+ require(all(v is None or (type(v) in (int, float) and math.isfinite(v) and v >= 0) for v in values), "invalid_effort")
+ totals[field] = {"known": round(sum(v for v in values if v is not None), 3), "unknown_cycles": sum(v is None for v in values)}
+ return {"basis": "owner snapshot; not live attestation", "as_of": data["as_of"],
+ "stale": (now - stamp).total_seconds() > 7 * 86400,
+ "health": health, "evidence_ref": data["evidence_ref"], "decisions": decisions,
+ "default": "Retain current policy/budgets; no new authority or maturity claim.",
+ "research_cycles": len(selected), "measured_effort": totals}
+
+
def collect(mode: str = "daily", now: datetime | None = None) -> dict:
require(mode in {"daily", "weekly", "monthly"}, "invalid_mode")
now = now or datetime.now(timezone.utc)
@@ -165,6 +204,7 @@
"workplan": "FEP-WP-0008", "executor_dependency": "GLAS-WP-0012 / HFACT-WP-0001",
}
config = {"sources": []}
+ root_sha = None
rows = []
events_seen = set()
total = 0
@@ -213,6 +253,13 @@
before = first - timedelta(days=1)
start = before.replace(day=1)
report["window"] = {"start": start.isoformat(), "end_exclusive": first.isoformat()}
+ try:
+ require(root_sha is not None, "missing_root_revision")
+ report["review"] = monthly_review(root_sha, now, start, first)
+ except (ValueError, KeyError, TypeError, AttributeError, OverflowError, httpx.HTTPError):
+ report["review"] = {"status": "unavailable", "health": "unknown", "decisions": [],
+ "default": "Retain current policy/budgets; no new authority or maturity claim."}
+ report["errors"].append("monthly_review_input_invalid_or_unavailable")
# Select latest event as of the end of the month, not latest today.
eligible_rows = [(e, d) for e, d in rows if datetime.fromisoformat(e["recorded_at"].replace("Z", "+00:00")) < first]
latest = reduce_events(eligible_rows, report["errors"])

View file

@ -0,0 +1,7 @@
{
"repository": "coulomb/activity-core",
"revision": "428f2d71b0f5ade11457e16d7b6341f571f6735f",
"resolver_sha256": "598ada41f2835ba08ba60ae7ca06c38acebaadc7c33b46d245a547107b2975e3",
"patched_resolver_sha256": "33aae2f3f0a17dfa90580c611de474064ca52b0aebf73939a9476a8560f46757",
"base_image": "forgejo.coulomb.social/coulomb/activity-core@sha256:713bddad10a41950f446100a8b370fca8ccdd0b8969cccae751e3870c9c63ccd"
}

View file

@ -0,0 +1,87 @@
import copy
import json
from datetime import datetime, timezone
from pathlib import Path
import pytest
from activity_core.context_resolvers import frontend_patterns as f
NOW = datetime(2026, 10, 1, 7, tzinfo=timezone.utc)
INPUT = {'schema_version': 1, 'as_of': '2026-09-28T09:00:00Z',
'health': {k: 'unverified' for k in ('schedule','collection','verification','publication','rollback','executor')},
'evidence_ref': 'docs/evidence/report.json', 'decisions': []}
EVENT = {'date': '2026-09-28', 'effort': {'research_minutes': 4, 'execution_minutes': 1, 'reported_tokens': None, 'cost_usd': None}}
def setup(monkeypatch, data=None, events=None, unavailable=False):
data = copy.deepcopy(INPUT if data is None else data)
events = [EVENT] if events is None else events
def raw(repo, sha, path):
assert repo == 'frontend-patterns' and sha == 'a'*40
if path == 'feedback/sources.json': return b'{"schema_version":1,"sources":[]}'
if path == 'feedback/monthly-review-input.json':
if unavailable: raise ValueError('unavailable')
return json.dumps(data).encode()
if path == 'research/evolution/ledger.json': return json.dumps({'events': events}).encode()
if path == 'docs/evidence/report.json': return b'{}'
raise ValueError('unexpected or missing file')
monkeypatch.setattr(f, 'raw', raw)
monkeypatch.setattr(f, 'revision', lambda *args: 'a'*40)
def test_october_packet_with_no_consumers_retains_health_and_unknown_cost(monkeypatch):
setup(monkeypatch)
r=f.collect('monthly', NOW)
assert r['status']=='no_consumers' and r['model_calls']==0
assert r['window']=={'start':'2026-09-01T00:00:00+02:00','end_exclusive':'2026-10-01T00:00:00+02:00'}
assert r['review']['health']['executor']=='unverified'
assert r['review']['measured_effort']['cost_usd']=={'known':0,'unknown_cycles':1}
assert r['review']['measured_effort']['research_minutes']=={'known':4,'unknown_cycles':0}
assert r['execution_ready'] is False
assert len(json.dumps(r,sort_keys=True,separators=(',',':'))) <= 3900
def test_incomplete_input_degrades_without_losing_receipt_summary(monkeypatch):
setup(monkeypatch, unavailable=True)
r=f.collect('monthly', NOW)
assert r['status']=='degraded' and r['real_uses']==0
assert r['review']['health']=='unknown' and r['review']['decisions']==[]
@pytest.mark.parametrize('change', [
{'as_of':'2026-10-02T00:00:00Z'}, {'as_of':'2026-09-28T00:00:00'},
{'evidence_ref':'../secret'}, {'decisions':[{}]*4},
{'health':{'executor':'ready'}}, {'decisions':[{'question':'x','recommendation':'y'}]},
{'evidence_ref':'docs/evidence/missing.json'},
])
def test_invalid_review_input_never_claims_health(monkeypatch, change):
data=copy.deepcopy(INPUT); data.update(change); setup(monkeypatch,data)
r=f.collect('monthly', NOW)
assert r['status']=='degraded' and r['review']['health']=='unknown'
def test_month_filter_and_missing_effort(monkeypatch):
old=copy.deepcopy(EVENT); old['date']='2026-08-31'
future=copy.deepcopy(EVENT); future['date']='2026-10-01'
unknown=copy.deepcopy(EVENT); unknown['effort']['research_minutes']=None
setup(monkeypatch, events=[old,future,EVENT,unknown])
r=f.collect('monthly', NOW)['review']
assert r['research_cycles']==2
assert r['measured_effort']['research_minutes']=={'known':4,'unknown_cycles':1}
def test_stale_owner_snapshot_is_not_live_health(monkeypatch):
data=copy.deepcopy(INPUT); data['as_of']='2026-09-01T00:00:00Z'; setup(monkeypatch,data)
r=f.collect('monthly',NOW)['review']
assert r['stale'] is True and 'not live attestation' in r['basis']
@pytest.mark.parametrize('value', [-1, float('nan'), True])
def test_invalid_effort_does_not_invent_totals(monkeypatch, value):
event=copy.deepcopy(EVENT); event['effort']['cost_usd']=value
setup(monkeypatch, events=[event])
assert f.collect('monthly', NOW)['status']=='degraded'
def test_daily_does_not_require_monthly_packet(monkeypatch):
setup(monkeypatch, unavailable=True)
assert f.collect('daily', NOW)['status']=='no_consumers'

View file

@ -0,0 +1,22 @@
--- a/k8s/railiance/20-runtime.yaml
+++ b/k8s/railiance/20-runtime.yaml
@@ -2200,7 +2200,7 @@
name: actcore-runtime-config
- secretRef:
name: actcore-runtime-secret
- image: forgejo.coulomb.social/coulomb/activity-core@sha256:713bddad10a41950f446100a8b370fca8ccdd0b8969cccae751e3870c9c63ccd
+ image: forgejo.coulomb.social/coulomb/activity-core@sha256:afd8a7af73132becba432e7b315d70f88d66676020169d5d8811641304136437
imagePullPolicy: IfNotPresent
name: worker
ports:
--- a/k8s/gitops/runtime.yaml
+++ b/k8s/gitops/runtime.yaml
@@ -1653,7 +1653,7 @@
name: actcore-runtime-config
- secretRef:
name: actcore-runtime-secret
- image: forgejo.coulomb.social/coulomb/activity-core@sha256:713bddad10a41950f446100a8b370fca8ccdd0b8969cccae751e3870c9c63ccd
+ image: forgejo.coulomb.social/coulomb/activity-core@sha256:afd8a7af73132becba432e7b315d70f88d66676020169d5d8811641304136437
imagePullPolicy: IfNotPresent
name: worker
ports: