Restore frontend reporting on grant-aware worker with monthly review packet
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s
Build and Publish Container Image / build-and-push (push) Successful in 2m35s

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:56 +02:00
parent 428f2d71b0
commit 4bd07a4f44
7 changed files with 164 additions and 9 deletions

View file

@ -16,7 +16,7 @@ COPY .forgejo/workflows/image.yaml ./.forgejo/workflows/image.yaml
COPY schemas/ ./schemas/ COPY schemas/ ./schemas/
COPY k8s/ ./k8s/ COPY k8s/ ./k8s/
COPY scripts/render_gitops.py scripts/check_gitops_promotion.py ./scripts/ COPY scripts/render_gitops.py scripts/check_gitops_promotion.py ./scripts/
RUN uv sync --frozen --extra dev && uv run --no-sync python scripts/render_gitops.py --check --verify-frontend --verify-platform && uv run --no-sync pytest -q -p no:cacheprovider tests/test_frontend_patterns.py tests/test_instruction_evaluation.py tests/test_admin_sync_api.py tests/test_gitops_release.py tests/test_release_broker.py tests/test_release_transport.py tests/test_release_dispatch.py RUN uv sync --frozen --extra dev && uv run --no-sync python scripts/render_gitops.py --check --verify-frontend --verify-platform && uv run --no-sync pytest -q -p no:cacheprovider tests/test_frontend_patterns.py tests/test_frontend_monthly_review.py tests/test_repository_grant.py tests/test_instruction_evaluation.py tests/test_admin_sync_api.py tests/test_gitops_release.py tests/test_release_broker.py tests/test_release_transport.py tests/test_release_dispatch.py
# Stage 2 — runtime image # Stage 2 — runtime image
FROM python:3.12-slim@sha256:78387bc3881b8273120a12ebe6c1ab22b018ccc2c9adf565ae1ac9b536e184ea AS runtime FROM python:3.12-slim@sha256:78387bc3881b8273120a12ebe6c1ab22b018ccc2c9adf565ae1ac9b536e184ea AS runtime

View file

@ -1,6 +1,6 @@
# Bounded overlay of the deployed worker; no unrelated API/worker upgrade. # Preserve the deployed grant-aware runtime; restore only the domain resolver.
# Required local base image ID: FROM forgejo.coulomb.social/coulomb/activity-core@sha256:713bddad10a41950f446100a8b370fca8ccdd0b8969cccae751e3870c9c63ccd
# sha256:cd4e924c2809f0d1d319e53a9ddd20db2e88a1b543d62e6442702c184b7842f4
FROM activity-core:fi-publication-20260914
COPY src/activity_core/context_resolvers/frontend_patterns.py /app/src/activity_core/context_resolvers/frontend_patterns.py COPY src/activity_core/context_resolvers/frontend_patterns.py /app/src/activity_core/context_resolvers/frontend_patterns.py
COPY src/activity_core/context_resolvers/__init__.py /app/src/activity_core/context_resolvers/__init__.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

@ -1653,7 +1653,7 @@ spec:
name: actcore-runtime-config name: actcore-runtime-config
- secretRef: - secretRef:
name: actcore-runtime-secret name: actcore-runtime-secret
image: forgejo.coulomb.social/coulomb/activity-core@sha256:713bddad10a41950f446100a8b370fca8ccdd0b8969cccae751e3870c9c63ccd image: forgejo.coulomb.social/coulomb/activity-core@sha256:afd8a7af73132becba432e7b315d70f88d66676020169d5d8811641304136437
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
name: worker name: worker
ports: ports:

View file

@ -2200,7 +2200,7 @@ spec:
name: actcore-runtime-config name: actcore-runtime-config
- secretRef: - secretRef:
name: actcore-runtime-secret name: actcore-runtime-secret
image: forgejo.coulomb.social/coulomb/activity-core@sha256:713bddad10a41950f446100a8b370fca8ccdd0b8969cccae751e3870c9c63ccd image: forgejo.coulomb.social/coulomb/activity-core@sha256:afd8a7af73132becba432e7b315d70f88d66676020169d5d8811641304136437
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
name: worker name: worker
ports: ports:

View file

@ -10,6 +10,7 @@ from contextvars import ContextVar
from datetime import datetime, timezone from datetime import datetime, timezone
import hashlib import hashlib
import json import json
import math
from pathlib import PurePosixPath from pathlib import PurePosixPath
import re import re
import time import time
@ -149,6 +150,44 @@ def reduce_events(rows: list[tuple[dict, str]], errors: list[str]) -> list[dict]
return latest 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: def collect(mode: str = "daily", now: datetime | None = None) -> dict:
require(mode in {"daily", "weekly", "monthly"}, "invalid_mode") require(mode in {"daily", "weekly", "monthly"}, "invalid_mode")
now = now or datetime.now(timezone.utc) now = now or datetime.now(timezone.utc)
@ -165,6 +204,7 @@ def collect(mode: str = "daily", now: datetime | None = None) -> dict:
"workplan": "FEP-WP-0008", "executor_dependency": "GLAS-WP-0012 / HFACT-WP-0001", "workplan": "FEP-WP-0008", "executor_dependency": "GLAS-WP-0012 / HFACT-WP-0001",
} }
config = {"sources": []} config = {"sources": []}
root_sha = None
rows = [] rows = []
events_seen = set() events_seen = set()
total = 0 total = 0
@ -213,6 +253,13 @@ def collect(mode: str = "daily", now: datetime | None = None) -> dict:
before = first - timedelta(days=1) before = first - timedelta(days=1)
start = before.replace(day=1) start = before.replace(day=1)
report["window"] = {"start": start.isoformat(), "end_exclusive": first.isoformat()} 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. # 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] 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"]) latest = reduce_events(eligible_rows, report["errors"])

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

@ -8,7 +8,7 @@ status: blocked
owner: codex owner: codex
topic_slug: activity-core topic_slug: activity-core
created: "2026-09-27" created: "2026-09-27"
updated: "2026-09-27" updated: "2026-09-28"
state_hub_workstream_id: "12798750-3eee-5d81-a411-c3c4c5ce2d2c" state_hub_workstream_id: "12798750-3eee-5d81-a411-c3c4c5ce2d2c"
--- ---
@ -217,3 +217,24 @@ dispatcher and audit sink. Local code cannot supply those trust inputs. The new
worker factory neither enables a schedule nor grants an identity; deployment and worker factory neither enables a schedule nor grants an identity; deployment and
continuous health observation must be established through the existing owner lane. continuous health observation must be established through the existing owner lane.
No new workplan/task, production mutation or authority expansion was introduced. No new workplan/task, production mutation or authority expansion was introduced.
## Attended frontend reporting repair — 2026-09-28
The grant-aware image promotion at `428f2d7` removed the frontend resolver. The
September 28 natural daily run fired but failed required-context resolution.
The user approved exact worker digest `afd8a7af73132becba432e7b315d70f88d66676020169d5d8811641304136437`
through GitOps (State Hub decision `abdea390-1039-4da1-b129-33aad00b4396`).
The overlay retains base `713bddad…` and all other Python source hashes/grant identity;
only the domain resolver and registration change. It also includes bounded monthly
owner-snapshot health/decisions and measured effort with unknown counts. No automatic
pattern execution or standing release authority is granted.
58 focused tests, 17 GitOps tests, built-image October fixture, published digest
readback and server dry-run pass. Source and generated projection change only the
worker image; API/router and other resource fields stay unchanged. Rollback target:
`428f2d71b0f5ade11457e16d7b6341f571f6735f` (preserves grants but restores reporting
failure). The healthy interval is invalidated; restart it only after repair proof.
FEP-WP-0008 T03 retains integration, real use and October production packet; this
plan's T02/T03 remain wait for their original standing-authority/observation gates.
Repair provenance: frontend-patterns `a18d83f`, integrations/activity-core/ and
its docs/evidence/2026-09-28-reporting-* receipts. No new task/workplan.