Restore frontend reporting on grant-aware worker with monthly review packet
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e738-9e15-7600-87e6-90dec3a7beef
This commit is contained in:
parent
428f2d71b0
commit
4bd07a4f44
7 changed files with 164 additions and 9 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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"])
|
||||||
|
|
|
||||||
87
tests/test_frontend_monthly_review.py
Normal file
87
tests/test_frontend_monthly_review.py
Normal 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'
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue