diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index fcc714f..dbd4a71 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -34,7 +34,7 @@ | workplan | ACTIVITY-WP-0028 | finished | — | workplans/ACTIVITY-WP-0028-activity-review-cli.md | | workplan | ACTIVITY-WP-0029 | finished | — | workplans/ACTIVITY-WP-0029-hub-port-alignment.md | | workplan | ACTIVITY-WP-0030 | finished | — | workplans/ACTIVITY-WP-0030-daily-sbom-catchup.md | -| workplan | ACTIVITY-WP-0031 | active | — | workplans/ACTIVITY-WP-0031-production-execution-reliability-cleanup.md | +| workplan | ACTIVITY-WP-0031 | finished | — | workplans/ACTIVITY-WP-0031-production-execution-reliability-cleanup.md | | workplan | ACTIVITY-WP-0032 | active | — | workplans/ACTIVITY-WP-0032-glas-profile-execution-contract.md | | workplan | ACTIVITY-WP-0033 | finished | — | workplans/ACTIVITY-WP-0033-sbom-catchup-retry-boundary.md | | workplan | ACTIVITY-WP-0034 | finished | — | workplans/ACTIVITY-WP-0034-sbom-controlled-source-reference.md | @@ -44,6 +44,7 @@ | workplan | ACTIVITY-WP-ADHOC-2026-06-01 | finished | — | workplans/ADHOC-2026-06-01.md | | workplan | ACTIVITY-WP-ADHOC-2026-08-20 | finished | — | workplans/ADHOC-2026-08-20.md | | workplan | ACTIVITY-WP-ADHOC-2026-08-23 | finished | — | workplans/ADHOC-2026-08-23.md | +| workplan | ACTIVITY-WP-ADHOC-2026-09-14 | finished | — | workplans/ADHOC-2026-09-14.md | | workplan | custodian-WP-0001 | done | — | workplans/custodian-WP-0001-temporal-backbone.md | | workplan | custodian-WP-0002 | done | — | workplans/custodian-WP-0002-triggers-ops.md | | workplan | custodian-WP-0003a | done | — | workplans/custodian-WP-0003a-event-bridge-model-rules-registry.md | @@ -199,7 +200,7 @@ | task | ACTIVITY-WP-0031-T02 | done | — | workplans/ACTIVITY-WP-0031-production-execution-reliability-cleanup.md | | task | ACTIVITY-WP-0031-T03 | done | — | workplans/ACTIVITY-WP-0031-production-execution-reliability-cleanup.md | | task | ACTIVITY-WP-0031-T04 | done | — | workplans/ACTIVITY-WP-0031-production-execution-reliability-cleanup.md | -| task | ACTIVITY-WP-0031-T05 | wait | — | workplans/ACTIVITY-WP-0031-production-execution-reliability-cleanup.md | +| task | ACTIVITY-WP-0031-T05 | done | — | workplans/ACTIVITY-WP-0031-production-execution-reliability-cleanup.md | | task | ACTIVITY-WP-0032-T01 | done | — | workplans/ACTIVITY-WP-0032-glas-profile-execution-contract.md | | task | ACTIVITY-WP-0032-T02 | done | — | workplans/ACTIVITY-WP-0032-glas-profile-execution-contract.md | | task | ACTIVITY-WP-0032-T03 | done | — | workplans/ACTIVITY-WP-0032-glas-profile-execution-contract.md | @@ -231,6 +232,8 @@ | task | ACTIVITY-WP-ADHOC-2026-06-01-T03 | done | — | workplans/ADHOC-2026-06-01.md | | task | ACTIVITY-WP-ADHOC-2026-08-20-T01 | done | — | workplans/ADHOC-2026-08-20.md | | task | ACTIVITY-WP-ADHOC-2026-08-23-T01 | done | — | workplans/ADHOC-2026-08-23.md | +| task | ACTIVITY-WP-ADHOC-2026-09-14-T01 | done | — | workplans/ADHOC-2026-09-14.md | +| task | ACTIVITY-WP-ADHOC-2026-09-14-T02 | done | — | workplans/ADHOC-2026-09-14.md | | task | T01 | done | — | workplans/custodian-WP-0001-temporal-backbone.md | | task | T02 | done | — | workplans/custodian-WP-0001-temporal-backbone.md | | task | T03 | done | — | workplans/custodian-WP-0001-temporal-backbone.md | diff --git a/k8s/railiance/20-runtime.yaml b/k8s/railiance/20-runtime.yaml index f678da6..ed7a48e 100644 --- a/k8s/railiance/20-runtime.yaml +++ b/k8s/railiance/20-runtime.yaml @@ -1202,7 +1202,7 @@ spec: restartPolicy: OnFailure containers: - name: migrate - image: activity-core:railiance01-prod + image: activity-core:fi-publication-20260914 imagePullPolicy: Never command: ["python", "-m", "alembic", "upgrade", "head"] envFrom: @@ -1230,7 +1230,7 @@ spec: restartPolicy: OnFailure containers: - name: sync - image: activity-core:railiance01-prod + image: activity-core:fi-publication-20260914 imagePullPolicy: Never command: - sh @@ -1287,7 +1287,7 @@ spec: spec: containers: - name: api - image: activity-core:railiance01-prod + image: activity-core:fi-publication-20260914 imagePullPolicy: Never command: ["uvicorn", "activity_core.api:app", "--host", "0.0.0.0", "--port", "8010"] ports: @@ -1363,7 +1363,7 @@ spec: fsGroup: 1000 containers: - name: worker - image: activity-core:railiance01-prod + image: activity-core:fi-publication-20260914 imagePullPolicy: Never command: ["python", "-m", "activity_core.worker"] ports: @@ -1458,7 +1458,7 @@ spec: spec: containers: - name: event-router - image: activity-core:railiance01-prod + image: activity-core:fi-publication-20260914 imagePullPolicy: Never command: ["python", "-m", "activity_core.event_router"] envFrom: diff --git a/src/activity_core/context_resolvers/state_hub.py b/src/activity_core/context_resolvers/state_hub.py index a129285..bbdd5a3 100644 --- a/src/activity_core/context_resolvers/state_hub.py +++ b/src/activity_core/context_resolvers/state_hub.py @@ -43,8 +43,9 @@ from __future__ import annotations import json import logging import os +import re import time -from datetime import datetime, timezone +from datetime import date, datetime, timezone from typing import Any import httpx @@ -655,10 +656,10 @@ def _fi_brief_status(params: dict[str, Any]) -> dict[str, Any]: Params: repo (default "freedom-intelligence"), timezone (IANA name, default Europe/Berlin — "today" is evaluated in this zone). - due is true when no progress event with event_type=fi_daily_brief and - detail.repo matching the target repo exists for today. Executing sessions - must post that event on completion (see freedom-intelligence - docs/daily-brief-playbook.md). + Only a pushed completion with an immutable origin SHA and the canonical + brief path can clear due. Compare detail.date, not event arrival date, so + late delivery of yesterday's brief cannot suppress today's work. Git tree + verification is performed by freedom-intelligence's durability audit. """ repo = str(params.get("repo") or _FI_BRIEF_DEFAULT_REPO) kind = str(params.get("kind") or "daily_brief") @@ -670,15 +671,23 @@ def _fi_brief_status(params: dict[str, Any]) -> dict[str, Any]: today = now_local.date() events = _binky_progress_events(_FI_BRIEF_EVENT_TYPE, repo) - timestamps = [ - ts - for ts in ( - _parse_progress_timestamp(item.get("created_at")) for item in events - ) - if ts > datetime.min.replace(tzinfo=timezone.utc) - ] - last_run = max(timestamps, default=None) - due = last_run is None or last_run.astimezone(tz).date() != today + valid = [] + for event in events: + detail = _progress_detail(event) + day = detail.get("date") + try: + parsed_day = date.fromisoformat(day) + except (ValueError, TypeError): + continue + if (parsed_day.isoformat() != day or detail.get("repo") != repo + or detail.get("pushed") is not True + or not re.fullmatch(r"[0-9a-f]{40}", str(detail.get("origin_sha", ""))) + or detail.get("path") != f"briefs/{day[:4]}/{day[5:7]}/{day}.md"): + continue + valid.append(event) + timestamps = [_parse_progress_timestamp(event.get("created_at")) for event in valid] + last_run = max((ts for ts in timestamps if ts > datetime.min.replace(tzinfo=timezone.utc)), default=None) + due = not any(_progress_detail(event)["date"] == today.isoformat() for event in valid) return { "items": [ diff --git a/tests/test_state_hub_context_resolver.py b/tests/test_state_hub_context_resolver.py index d3bce6f..a8279c6 100644 --- a/tests/test_state_hub_context_resolver.py +++ b/tests/test_state_hub_context_resolver.py @@ -1150,7 +1150,9 @@ def test_fi_brief_status_not_due_after_todays_run(monkeypatch) -> None: { "event_type": "fi_daily_brief", "created_at": "2026-07-17T05:30:00+00:00", - "detail": {"repo": "freedom-intelligence"}, + "detail": {"repo": "freedom-intelligence", "date": "2026-07-17", + "path": "briefs/2026/07/2026-07-17.md", + "pushed": True, "origin_sha": "a" * 40}, } ] }, @@ -1195,3 +1197,27 @@ def test_fi_brief_status_unknown_kind(monkeypatch) -> None: assert result["items"] == [] assert "unknown kind" in result["error"] + + +@pytest.mark.parametrize("override", [ + {"pushed": False}, {"pushed": "true"}, {"origin_sha": ""}, + {"origin_sha": "main"}, {"path": "briefs/wrong.md"}, {"date": "bad-date"}, + {"repo": None}, +]) +def test_fi_brief_rejects_unpublished_or_malformed_completion(monkeypatch, override): + detail = dict(repo="freedom-intelligence", date="2026-07-17", + path="briefs/2026/07/2026-07-17.md", pushed=True, origin_sha="a" * 40) + detail.update(override) + _binky_resolver_env(monkeypatch, {"fi_daily_brief": [dict( + event_type="fi_daily_brief", created_at="2026-07-17T05:30:00+00:00", detail=detail)]}) + result = StateHubContextResolver().resolve("fi_brief_status", None, {"repo": "freedom-intelligence"}) + assert result["items"][0]["due"] is True + + +def test_fi_brief_late_yesterday_event_does_not_clear_today(monkeypatch): + detail = dict(repo="freedom-intelligence", date="2026-07-16", + path="briefs/2026/07/2026-07-16.md", pushed=True, origin_sha="a" * 40) + _binky_resolver_env(monkeypatch, {"fi_daily_brief": [dict( + event_type="fi_daily_brief", created_at="2026-07-17T05:30:00+00:00", detail=detail)]}) + result = StateHubContextResolver().resolve("fi_brief_status", None, {"repo": "freedom-intelligence"}) + assert result["items"][0]["due"] is True diff --git a/workplans/ADHOC-2026-06-01.md b/workplans/ADHOC-2026-06-01.md index 60b209a..ed958af 100644 --- a/workplans/ADHOC-2026-06-01.md +++ b/workplans/ADHOC-2026-06-01.md @@ -9,6 +9,7 @@ owner: custodian topic_slug: custodian created: "2026-06-01" updated: "2026-06-03" +state_hub_workstream_id: "4b5abcd1-e1a2-5840-a852-2d14c867c5f8" --- # ACTIVITY-WP-ADHOC-2026-06-01 — activity-core opportunistic fixes diff --git a/workplans/ADHOC-2026-09-14.md b/workplans/ADHOC-2026-09-14.md new file mode 100644 index 0000000..48086be --- /dev/null +++ b/workplans/ADHOC-2026-09-14.md @@ -0,0 +1,49 @@ +--- +id: ACTIVITY-WP-ADHOC-2026-09-14 +type: workplan +title: Require publication evidence for the FI daily due gate +domain: infotech +repo: activity-core +status: finished +owner: codex +created: "2026-09-14" +updated: "2026-09-14" +state_hub_workstream_id: "5434dcd8-f1a4-5869-a306-dfa7eaff5885" +--- + +## Reject unpublished and misdated FI completion events + +```task +id: ACTIVITY-WP-ADHOC-2026-09-14-T01 +status: done +priority: high +state_hub_task_id: "78b343f0-4059-5df6-b4e3-2187a97dccef" +``` + +Only pushed=true, full origin SHA and canonical date/path can clear due. +Use detail.date rather than created_at, so late delivery cannot suppress the +next day. The FI verifier checks actual origin objects. Resolver suite: 47 +passed, including invalid publication and delayed-delivery regression cases. + +## Deploy and verify the production resolver + +```task +id: ACTIVITY-WP-ADHOC-2026-09-14-T02 +status: done +priority: high +state_hub_task_id: "0d1d729c-ce48-5784-99cc-dea4a5e5b587" +``` + +Use image activity-core:fi-publication-20260914, layered over the exact live +baseline (container image ID sha256:e6e9f0171e36ce0f50a970435ea55f10d428f1743050d1629a94ff4fd19cec37). +The baseline resolver module was byte-identical to pre-change repo HEAD; +only the tested module is replaced. No schema or other application changes. +Keep the remote checkout's unrelated dirty files untouched. + +Roll API, worker and event-router; verify rollout, live due=true for the +September 14 brief, and Temporal next action 05:30 UTC / 07:30 Berlin. +Future run completion must be proven separately by FI's --require-date audit. + +Live verification: API, worker and event-router run the new image and are ready. +API imports the patched resolver; September 14 remains due until publication. +The three image smoke cases (unpublished, published, late delivery) passed.