Verify brief publication against live origin and record historical gaps

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a09cbd-43c1-79f3-809e-1ee97b40b64d
This commit is contained in:
tegwick 2026-09-14 00:37:51 +02:00
parent 8a65297d56
commit 8bd2e5ff92
10 changed files with 565 additions and 90 deletions

View file

@ -88,11 +88,13 @@ Never stage model weights in WSL. See [streaming operations](docs/streaming-rese
**Durability check** (hub events vs git files): **Durability check** (hub events vs git files):
```bash ```bash
python3 scripts/verify_brief_durability.py --require-hub python3 scripts/verify_brief_durability.py --require-hub --allow-reconciled
``` ```
--- ---
Historical gaps remain visible; [audit and reconciliation contract](docs/brief-durability-audit.md).
## License ## License
MIT — see [`LICENSE`](LICENSE). Model weights retain **upstream** licenses. MIT — see [`LICENSE`](LICENSE). Model weights retain **upstream** licenses.

View file

@ -0,0 +1,66 @@
# Daily brief durability audit
The verifier resolves the live `origin/main` commit with `git ls-remote` and
reads that immutable Git tree. If necessary, it fetches missing objects. Local
files, local commits and stale remote-tracking refs cannot establish completion.
An unreachable remote or Hub is an error (exit 2), never a successful check.
Hub progress reads are paginated; repeating/malformed pages fail closed.
```bash
python3 scripts/verify_brief_durability.py --require-hub
python3 scripts/verify_brief_durability.py --allow-reconciled --json
python3 scripts/verify_brief_durability.py --allow-reconciled --require-date 2026-09-14
```
The last command is the acceptance check for the September 14 scheduled run.
Before that run publishes, it must fail. A known historical gap cannot satisfy
`--require-date`.
For events dated September 13, 2026 onward (or any event containing push
attestation), the verifier requires `pushed=true`, the canonical date/path,
and a full `origin_sha` that is an ancestor of the live remote head and contains
the nonempty brief. The current remote tree must also contain the file.
Earlier events with files on origin are reported separately as legacy evidence;
the audit does not invent historical push attestations.
## September 711 reconciliation
The five original `fi_daily_brief` events remain intact in the append-only Hub
log. The evidence file
[`2026-09-14-brief-completion-reconciliation.json`](evidence/2026-09-14-brief-completion-reconciliation.json)
records exact event IDs, original details and the audited remote commit. No
missing brief is reconstructed or counted as successful.
After publishing this evidence to origin, append one correction per event:
```bash
python3 scripts/reconcile_brief_completions.py # review, read-only
python3 scripts/reconcile_brief_completions.py --apply # append corrections
```
The helper validates every source event and missing path before writing. It
uses stable idempotency keys and skips already recorded corrections. Correction
events use `fi_daily_brief_reconciliation`, never the completion event type.
The verifier accepts a correction only when its event ID/date/path/disposition/
reason matches the evidence JSON in a published ancestor commit. Default strict
mode still fails on these missing dates. `--allow-reconciled` explicitly allows
the acknowledged historical debt, prints every gap, and fails on any new gap or
invalid attestation.
## Next-run readiness (2026-09-14 Berlin)
Temporal schedule `activity-schedule-3169ab1f-882b-59c7-9763-d014dc96f4fc` is
unpaused; its next action is September 14 at 07:30 Europe/Berlin (05:30 UTC).
The claim-loop service is active and was restarted after executor commit
`11020e8` (push-before-completion). Keep its FI checkout synchronized before the
run: the executor pushes its current branch and does not pull automatically.
The activity-core resolver also needs the publication contract: only an event
with `pushed=true`, a full SHA, canonical path and matching `detail.date` may
clear today's due bit. An event for yesterday delivered today must leave today
due. The Git audit remains the authoritative check of actual origin objects;
the resolver checks the executor's attestation.
This preflight is not evidence that the future scheduled run succeeded. Run
the exact-date audit after publication and retain its result.

View file

@ -141,3 +141,11 @@ The baseline survey (2026-07-24) already covers the field. The **first daily**
should only list changes **after** that documents research window. If nothing should only list changes **after** that documents research window. If nothing
moved, a short no-delta brief still completes the rhythm and posts moved, a short no-delta brief still completes the rhythm and posts
`fi_daily_brief`. `fi_daily_brief`.
## Durability audit and historical corrections
See [the Git-backed audit contract](brief-durability-audit.md) for live-origin verification,
paginated Hub reads, and append-only correction records. `--allow-reconciled`
explicitly acknowledges the five missing September 711 briefs; it never
counts them as successful. Use `--require-date YYYY-MM-DD` to prove a scheduled
day has a valid published completion. An unavailable Hub/remote fails the audit.

View file

@ -0,0 +1,98 @@
{
"schema_version": 1,
"repo": "freedom-intelligence",
"checked_origin_sha": "8a65297d56da216eea1e4cea362da3bfbd68fff6",
"corrections": [
{
"original_event_id": "5d9461ef-bf99-44b2-90fe-d8a3bf5524f9",
"date": "2026-09-07",
"path": "briefs/2026/09/2026-09-07.md",
"disposition": "false_completion",
"reason": "Executor reported a local commit without origin publication; no brief exists in the audited origin/main tree.",
"original_created_at": "2026-09-07T05:30:15.016103Z",
"original_detail": {
"date": "2026-09-07",
"path": "briefs/2026/09/2026-09-07.md",
"repo": "freedom-intelligence",
"wrote": true,
"executor": "rein-aharness",
"committed": true,
"skipped_existing": false,
"collection_candidates": 0
}
},
{
"original_event_id": "90b7719f-4bcf-475b-8042-715ee8bc8127",
"date": "2026-09-08",
"path": "briefs/2026/09/2026-09-08.md",
"disposition": "false_completion",
"reason": "Executor reported a local commit without origin publication; no brief exists in the audited origin/main tree.",
"original_created_at": "2026-09-08T05:30:26.872826Z",
"original_detail": {
"date": "2026-09-08",
"path": "briefs/2026/09/2026-09-08.md",
"repo": "freedom-intelligence",
"wrote": true,
"executor": "rein-aharness",
"committed": true,
"skipped_existing": false,
"collection_candidates": 2
}
},
{
"original_event_id": "54754f04-5175-4755-8473-ee42ebe95232",
"date": "2026-09-09",
"path": "briefs/2026/09/2026-09-09.md",
"disposition": "false_completion",
"reason": "Executor reported a local commit without origin publication; no brief exists in the audited origin/main tree.",
"original_created_at": "2026-09-09T05:30:22.905105Z",
"original_detail": {
"date": "2026-09-09",
"path": "briefs/2026/09/2026-09-09.md",
"repo": "freedom-intelligence",
"wrote": true,
"executor": "rein-aharness",
"committed": true,
"skipped_existing": false,
"collection_candidates": 0
}
},
{
"original_event_id": "1dc604ca-43d2-49de-a39b-dd90f05b10a8",
"date": "2026-09-10",
"path": "briefs/2026/09/2026-09-10.md",
"disposition": "false_completion",
"reason": "Executor reported a local commit without origin publication; no brief exists in the audited origin/main tree.",
"original_created_at": "2026-09-10T05:30:31.005526Z",
"original_detail": {
"date": "2026-09-10",
"path": "briefs/2026/09/2026-09-10.md",
"repo": "freedom-intelligence",
"wrote": true,
"executor": "rein-aharness",
"committed": true,
"skipped_existing": false,
"collection_candidates": 1
}
},
{
"original_event_id": "84144b03-dce4-4623-9191-5e36fe3c2692",
"date": "2026-09-11",
"path": "briefs/2026/09/2026-09-11.md",
"disposition": "false_completion",
"reason": "Executor reported a local commit without origin publication; no brief exists in the audited origin/main tree.",
"original_created_at": "2026-09-11T05:30:13.749386Z",
"original_detail": {
"date": "2026-09-11",
"path": "briefs/2026/09/2026-09-11.md",
"repo": "freedom-intelligence",
"wrote": true,
"executor": "rein-aharness",
"committed": true,
"skipped_existing": false,
"collection_candidates": 1
}
}
],
"interpretation": "These five dates remain missing. Append-only corrections acknowledge false completions; they do not create briefs, claim recovery, or satisfy a required-date check."
}

View file

@ -143,3 +143,11 @@ python3 scripts/verify_brief_durability.py --require-hub
- Skipping the completion event (leaves `due=true` forever) - Skipping the completion event (leaves `due=true` forever)
- Posting `fi_daily_brief` for a local-only commit (clears `due`, loses the day) - Posting `fi_daily_brief` for a local-only commit (clears `due`, loses the day)
- Reusing `railiance-platform-pg-backup` for weights (30-day expiry) - Reusing `railiance-platform-pg-backup` for weights (30-day expiry)
## Durability audit and historical corrections
See [the Git-backed audit contract](brief-durability-audit.md) for live-origin verification,
paginated Hub reads, and append-only correction records. `--allow-reconciled`
explicitly acknowledges the five missing September 711 briefs; it never
counts them as successful. Use `--require-date YYYY-MM-DD` to prove a scheduled
day has a valid published completion. An unavailable Hub/remote fails the audit.

View file

@ -77,3 +77,11 @@ tunnel ports). Check health before declaring hub down.
| ---- | ------ | | ---- | ------ |
| 2026-07-23 | Stub created | | 2026-07-23 | Stub created |
| 2026-07-24 | `fi_daily_brief` contract + activity-core linkage documented | | 2026-07-24 | `fi_daily_brief` contract + activity-core linkage documented |
## Durability audit and historical corrections
See [the Git-backed audit contract](brief-durability-audit.md) for live-origin verification,
paginated Hub reads, and append-only correction records. `--allow-reconciled`
explicitly acknowledges the five missing September 711 briefs; it never
counts them as successful. Use `--require-date YYYY-MM-DD` to prove a scheduled
day has a valid published completion. An unavailable Hub/remote fails the audit.

View file

@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""Append corrections for published evidence records. Dry run unless --apply.
Original progress events are immutable. Corrections never clear the daily due
bit and never claim that the missing brief was recovered.
"""
import argparse
import json
import os
from pathlib import Path
import urllib.request
from verify_brief_durability import CORRECTION_TYPE, Origin, REPO, ROOT, brief_path, git, load_events
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument('--evidence', default='docs/evidence/2026-09-14-brief-completion-reconciliation.json')
ap.add_argument('--hub', default=os.environ.get('STATE_HUB_URL', 'http://127.0.0.1:8000'))
ap.add_argument('--apply', action='store_true')
args = ap.parse_args()
origin = Origin(ROOT)
path = Path(args.evidence)
if path.is_absolute() or '..' in path.parts:
raise ValueError('evidence must be a repository-relative path')
if not origin.blob(path.as_posix()):
raise RuntimeError('publish the evidence to origin/main before reconciliation')
evidence = json.loads(git(ROOT, 'show', f'{origin.sha}:{path.as_posix()}').stdout)
originals = {e['id']: e for e in load_events(args.hub, 10)}
existing = load_events(args.hub, 10, CORRECTION_TYPE)
pending = []
for record in evidence['corrections']:
original = originals[record['original_event_id']]
d = original['detail']
expected = brief_path(record['date'])
if (d.get('repo') != REPO or d.get('date') != record['date']
or d.get('path') != expected or record['path'] != expected
or record['disposition'] != 'false_completion' or origin.blob(expected)):
raise RuntimeError('original event or missing-file evidence changed; audit again')
if any(e.get('detail', {}).get('original_event_id') == original['id']
and origin.correction_recorded(e.get('detail', {})) for e in existing):
print(f"already reconciled {record['date']} {original['id']}")
continue
detail = {k: record[k] for k in ('original_event_id', 'date', 'path', 'disposition', 'reason')}
detail.update(repo=REPO, evidence_sha=origin.sha, evidence_path=path.as_posix())
pending.append(dict(event_type=CORRECTION_TYPE, author='codex',
summary=f"FI {record['date']}: acknowledge false completion; brief remains missing",
detail=detail))
# Validate all records before making the first write.
for event in pending:
if not args.apply:
print(json.dumps(event))
continue
original_id = event['detail']['original_event_id']
req = urllib.request.Request(f"{args.hub.rstrip('/')}/progress/",
data=json.dumps(event).encode(), method='POST',
headers={'Content-Type': 'application/json', 'Idempotency-Key': f'fi-false-completion-{original_id}'})
with urllib.request.urlopen(req, timeout=15) as response:
created = json.load(response)
print(f"reconciled {event['detail']['date']} correction={created['id']}")
return 0
if __name__ == '__main__':
raise SystemExit(main())

View file

@ -0,0 +1,136 @@
import io
import json
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
from verify_brief_durability import Origin, assess, brief_path, git, load_events
class DurabilityTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.root = Path(self.tmp.name)
self.remote = self.root / 'remote.git'
self.repo = self.root / 'repo'
self.repo.mkdir()
git(self.root, 'init', '--bare', str(self.remote))
git(self.repo, 'init', '-b', 'main')
git(self.repo, 'config', 'user.name', 'Test')
git(self.repo, 'config', 'user.email', 'test@example.invalid')
git(self.repo, 'remote', 'add', 'origin', str(self.remote))
(self.repo / 'README.md').write_text('fixture\n')
self.commit()
git(self.repo, 'push', 'origin', 'main')
def commit(self):
git(self.repo, 'add', '.')
git(self.repo, 'commit', '-m', 'fixture')
return git(self.repo, 'rev-parse', 'HEAD').stdout.strip()
def write_brief(self, day='2026-09-14', text='A brief\n'):
p = self.repo / brief_path(day)
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(text)
return p
def event(self, day='2026-09-14', **kwargs):
detail = dict(repo='freedom-intelligence', date=day, path=brief_path(day), **kwargs)
return dict(id='event-1', detail=detail)
def test_untracked_file_does_not_count(self):
self.write_brief()
self.assertFalse(Origin(self.repo).blob(brief_path('2026-09-14')))
def test_unpushed_commit_does_not_count(self):
self.write_brief()
self.commit()
self.assertFalse(Origin(self.repo).blob(brief_path('2026-09-14')))
def test_fresh_remote_beats_stale_tracking_ref(self):
old = git(self.repo, 'rev-parse', 'HEAD').stdout.strip()
self.write_brief()
sha = self.commit()
git(self.repo, 'push', 'origin', 'main')
git(self.repo, 'update-ref', 'refs/remotes/origin/main', old)
origin = Origin(self.repo)
self.assertEqual(origin.sha, sha)
self.assertTrue(origin.blob(brief_path('2026-09-14')))
def test_fetches_missing_remote_objects(self):
clone = self.root / 'other'
git(self.root, 'clone', '-b', 'main', str(self.remote), str(clone))
self.write_brief()
self.commit()
git(self.repo, 'push', 'origin', 'main')
self.assertTrue(Origin(clone).blob(brief_path('2026-09-14')))
def test_valid_published_completion(self):
self.write_brief()
sha = self.commit()
git(self.repo, 'push', 'origin', 'main')
result = assess([self.event(pushed=True, origin_sha=sha)], [], Origin(self.repo), '2026-09-14')
self.assertEqual(len(result['valid']), 1)
self.assertEqual(result['failures'], [])
def test_unpublished_attestation_rejected_even_when_file_is_remote(self):
self.write_brief()
self.commit()
git(self.repo, 'push', 'origin', 'main')
(self.repo / 'extra').write_text('unpublished')
unpublished = self.commit()
result = assess([self.event(pushed=True, origin_sha=unpublished)], [], Origin(self.repo))
self.assertEqual(len(result['failures']), 1)
def test_missing_push_evidence_rejected_for_new_events(self):
self.write_brief()
self.commit()
git(self.repo, 'push', 'origin', 'main')
self.assertEqual(len(assess([self.event()], [], Origin(self.repo))['failures']), 1)
def test_empty_file_rejected(self):
self.write_brief(text='')
self.commit()
git(self.repo, 'push', 'origin', 'main')
self.assertFalse(Origin(self.repo).blob(brief_path('2026-09-14')))
def test_acknowledged_gap_is_not_a_success(self):
event = self.event('2026-09-07')
record = dict(original_event_id=event['id'], date='2026-09-07',
path=brief_path('2026-09-07'), disposition='false_completion', reason='unpublished')
(self.repo / 'evidence.json').write_text(json.dumps({'corrections': [record]}))
sha = self.commit()
git(self.repo, 'push', 'origin', 'main')
correction = dict(detail=dict(record, repo='freedom-intelligence',
evidence_path='evidence.json', evidence_sha=sha))
result = assess([event], [correction], Origin(self.repo), '2026-09-07')
self.assertEqual(len(result['reconciled']), 1)
self.assertEqual(result['valid'], [])
self.assertEqual(len(result['failures']), 1) # required day still fails
correction['detail']['original_event_id'] = 'different-event'
result = assess([event], [correction], Origin(self.repo))
self.assertEqual(result['reconciled'], [])
self.assertEqual(len(result['failures']), 1)
def test_malformed_dates_fail_without_path_traversal(self):
result = assess([dict(id='bad', detail={'date': '../../etc/passwd'})], [], Origin(self.repo))
self.assertEqual(len(result['failures']), 1)
class PaginationTests(unittest.TestCase):
def test_reads_all_pages(self):
pages = [[{'id': '1'}, {'id': '2'}], [{'id': '3'}]]
with patch('urllib.request.urlopen', side_effect=[io.BytesIO(json.dumps(p).encode()) for p in pages]) as call:
self.assertEqual(len(load_events('http://hub', 1, page_size=2)), 3)
self.assertIn('offset=2', call.call_args.args[0].full_url)
def test_repeating_page_fails(self):
with patch('urllib.request.urlopen', side_effect=[io.BytesIO(b'[{"id":"1"}]'), io.BytesIO(b'[{"id":"1"}]')]):
with self.assertRaises(RuntimeError):
load_events('http://hub', 1, page_size=1)
if __name__ == '__main__':
unittest.main()

View file

@ -1,114 +1,192 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Fail if State Hub fi_daily_brief events are not on origin/main. """Audit Hub completions against a freshly resolved origin/main Git tree.
A brief day is complete only when briefs/YYYY/MM/YYYY-MM-DD.md exists in Exit 0: checks pass (or explicitly allowed acknowledged historical gaps).
this git repository (the clone should be origin/main). Hub events that Exit 1: missing/invalid evidence. Exit 2: verification could not run reliably.
claim wrote/committed without a matching file are the 2026-08/09 failure --require-date YYYY-MM-DD also requires a valid completion for that day.
mode: local commit on railiance, due-bit cleared, durable memory empty.
Usage:
python3 scripts/verify_brief_durability.py
STATE_HUB_URL=http://127.0.0.1:8000 python3 scripts/verify_brief_durability.py
Exit 0 = every hub success event has a brief file.
Exit 1 = mismatch (or hub unreachable when --require-hub is set).
""" """
from __future__ import annotations from __future__ import annotations
import argparse import argparse
from datetime import date
import json import json
import os import os
import sys
import urllib.error
import urllib.request
from pathlib import Path from pathlib import Path
import re
import subprocess
import sys
import urllib.parse
import urllib.request
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
REPO = 'freedom-intelligence'
CONTRACT_START = '2026-09-13'
CORRECTION_TYPE = 'fi_daily_brief_reconciliation'
SHA = re.compile(r'[0-9a-f]{40}')
def brief_path(date: str) -> Path: def brief_path(day: str) -> str:
year, month, _ = date.split("-") if date.fromisoformat(day).isoformat() != day:
return ROOT / "briefs" / year / month / f"{date}.md" raise ValueError('date must be YYYY-MM-DD')
return f'briefs/{day[:4]}/{day[5:7]}/{day}.md'
def load_events(url: str, timeout: float) -> list[dict]: def git(root: Path, *args: str, check=True):
req = urllib.request.Request( result = subprocess.run(['git', '-C', str(root), *args], capture_output=True,
f"{url.rstrip('/')}/progress/?event_type=fi_daily_brief", text=True, timeout=120)
headers={"Accept": "application/json"}, if check and result.returncode:
) raise RuntimeError(f'git {args[0]} failed: {result.stderr.strip()}')
with urllib.request.urlopen(req, timeout=timeout) as resp: return result
data = json.loads(resp.read().decode())
if isinstance(data, list):
return data class Origin:
if isinstance(data, dict): def __init__(self, root: Path):
for key in ("items", "events", "results"): self.root = root
if isinstance(data.get(key), list): # Never trust a stale tracking ref or the checked-out branch.
return data[key] remote = git(root, 'ls-remote', '--exit-code', 'origin', 'refs/heads/main').stdout.split()
raise RuntimeError(f"unexpected hub payload type {type(data)}") if len(remote) != 2 or not SHA.fullmatch(remote[0]):
raise RuntimeError('origin/main did not resolve to one commit')
self.sha = remote[0]
if git(root, 'cat-file', '-e', f'{self.sha}^{{commit}}', check=False).returncode:
git(root, 'fetch', '--no-tags', '--no-write-fetch-head', 'origin', 'refs/heads/main')
git(root, 'cat-file', '-e', f'{self.sha}^{{commit}}')
def blob(self, path: str, revision: str | None = None) -> bool:
revision = revision or self.sha
if not SHA.fullmatch(revision):
return False
entry = git(self.root, 'ls-tree', revision, '--', path, check=False)
fields = entry.stdout.split()
# Reject symlinks, trees and empty files as completion artifacts.
if entry.returncode or len(fields) != 4 or fields[0] not in {'100644', '100755'}:
return False
return fields[1] == 'blob' and int(git(self.root, 'cat-file', '-s', fields[2]).stdout) > 0
def published(self, revision: str, path: str) -> bool:
if not SHA.fullmatch(revision):
return False
return (git(self.root, 'merge-base', '--is-ancestor', revision, self.sha,
check=False).returncode == 0 and self.blob(path, revision))
def correction_recorded(self, detail: dict) -> bool:
revision = str(detail.get('evidence_sha', ''))
path = str(detail.get('evidence_path', ''))
if not self.published(revision, path):
return False
try:
evidence = json.loads(git(self.root, 'show', f'{revision}:{path}').stdout)
records = evidence['corrections']
except (ValueError, KeyError, TypeError):
return False
fields = ('original_event_id', 'date', 'path', 'disposition', 'reason')
return any(isinstance(row, dict) and all(row.get(k) == detail.get(k) for k in fields)
for row in records)
def load_events(url: str, timeout: float, event_type='fi_daily_brief', page_size=1000) -> list[dict]:
events = []
seen = set()
for offset in range(0, 1_000_000, page_size):
query = urllib.parse.urlencode(dict(event_type=event_type, limit=page_size, offset=offset))
req = urllib.request.Request(f"{url.rstrip('/')}/progress/?{query}",
headers={'Accept': 'application/json'})
with urllib.request.urlopen(req, timeout=timeout) as response:
page = json.load(response)
if not isinstance(page, list):
raise RuntimeError('unexpected Hub progress payload: expected a list')
for event in page:
if not isinstance(event, dict) or not event.get('id') or event['id'] in seen:
raise RuntimeError('malformed or unstable Hub pagination; retry verification')
seen.add(event['id'])
events.extend(page)
if len(page) < page_size:
return events
raise RuntimeError('Hub pagination exceeded safety bound')
def assess(events, corrections, origin, required_date=None):
result = {'origin_sha': origin.sha, 'valid': [], 'legacy': [], 'reconciled': [], 'failures': []}
correction_index = {}
for correction in corrections:
d = correction.get('detail') or {}
if not isinstance(d, dict) or d.get('repo') != REPO:
continue
if (d.get('disposition') == 'false_completion' and d.get('reason')
and origin.correction_recorded(d)):
correction_index.setdefault(d.get('original_event_id'), []).append(d)
for event in events:
d = event.get('detail') or {}
if not isinstance(d, dict):
result['failures'].append({'id': event.get('id'), 'reason': 'invalid event detail'})
continue
if d.get('repo') not in {None, REPO}:
continue
row = {'id': event.get('id'), 'date': d.get('date')}
try:
path = brief_path(d.get('date', ''))
except (ValueError, TypeError):
result['failures'].append(dict(row, reason='invalid or missing date'))
continue
if not origin.blob(path):
acknowledged = any(c.get('date') == d['date'] and c.get('path') == path
for c in correction_index.get(event.get('id'), []))
target = 'reconciled' if acknowledged else 'failures'
result[target].append(dict(row, reason='brief absent from origin/main', path=path))
continue
if d.get('path', path) != path:
result['failures'].append(dict(row, reason='event path does not match date'))
continue
modern = d['date'] >= CONTRACT_START or 'pushed' in d or 'origin_sha' in d
if modern:
if (d.get('pushed') is not True or d.get('path') != path
or not origin.published(str(d.get('origin_sha', '')), path)):
result['failures'].append(dict(row, reason='invalid pushed/origin_sha publication evidence'))
continue
result['valid'].append(row)
else:
# Historic files exist on origin but predate the push-attestation contract.
result['legacy'].append(row)
if required_date and required_date not in {r['date'] for r in result['valid']}:
result['failures'].append({'date': required_date, 'reason': 'required day has no valid published completion'})
return result
def main() -> int: def main() -> int:
ap = argparse.ArgumentParser(description=__doc__) ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument( ap.add_argument('--hub', default=os.environ.get('STATE_HUB_URL', 'http://127.0.0.1:8000'))
"--hub", ap.add_argument('--timeout', type=float, default=8)
default=os.environ.get("STATE_HUB_URL", "http://127.0.0.1:8000"), ap.add_argument('--require-hub', action='store_true', help='compatibility flag; Hub is always required')
) ap.add_argument('--require-date', help='require a valid pushed completion for this exact day')
ap.add_argument("--timeout", type=float, default=8.0) ap.add_argument('--allow-reconciled', action='store_true', help='acknowledged historical gaps do not fail the audit')
ap.add_argument( ap.add_argument('--json', action='store_true', help='machine-readable audit result')
"--require-hub",
action="store_true",
help="exit 1 if the hub cannot be reached (default: warn and check git only)",
)
args = ap.parse_args() args = ap.parse_args()
git_dates = sorted(
p.stem
for p in (ROOT / "briefs").rglob("20*.md")
if p.name[0].isdigit()
)
print(f"git briefs: {len(git_dates)} last={git_dates[-1] if git_dates else '-'}")
try: try:
if args.require_date:
brief_path(args.require_date)
origin = Origin(ROOT)
events = load_events(args.hub, args.timeout) events = load_events(args.hub, args.timeout)
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError, RuntimeError) as exc: corrections = load_events(args.hub, args.timeout, CORRECTION_TYPE)
msg = f"hub unreachable or unreadable at {args.hub}: {exc}" result = assess(events, corrections, origin, args.require_date)
if args.require_hub: except (OSError, ValueError, RuntimeError, subprocess.SubprocessError) as exc:
print(f"FAIL {msg}", file=sys.stderr) if args.json:
return 1 print(json.dumps({'ok': False, 'error': str(exc)}))
print(f"WARN {msg}")
return 0
missing: list[str] = []
present: list[str] = []
for ev in events:
detail = ev.get("detail") or {}
date = str(detail.get("date") or "")
if not date:
continue
repo = detail.get("repo")
if repo and repo != "freedom-intelligence":
continue
path = brief_path(date)
if path.is_file():
present.append(date)
else: else:
missing.append(date) print(f'ERROR verification unavailable: {exc}', file=sys.stderr)
return 2
missing = sorted(set(missing)) result['ok'] = not result['failures'] and (args.allow_reconciled or not result['reconciled'])
present = sorted(set(present)) if args.json:
print(f"hub fi_daily_brief dates on git: {len(present)}") print(json.dumps(result, indent=2))
if missing: else:
print("FAIL hub events with no brief file:") print(f'origin/main: {origin.sha} (resolved from live remote)')
for date in missing: print(f"valid published events: {len(result['valid'])}; legacy files on origin: {len(result['legacy'])}")
print(f" {date} expected {brief_path(date).relative_to(ROOT)}") for row in result['reconciled']:
print( print(f"GAP acknowledged false completion: {row['date']} event={row['id']}")
"A local-only commit must not clear due. " for row in result['failures']:
"Push to origin, or treat the hub event as a false completion." print(f"FAIL {row.get('date', '-')}: {row['reason']}")
) print('PASS (acknowledged gaps remain missing)' if result['ok'] and result['reconciled']
return 1 else 'PASS' if result['ok'] else 'FAIL durability audit')
print("ok: every hub fi_daily_brief date has a brief file") return 0 if result['ok'] else 1
return 0
if __name__ == "__main__": if __name__ == '__main__':
sys.exit(main()) sys.exit(main())

View file

@ -105,6 +105,12 @@ state_hub_task_id: "e4fbc634-462c-5f07-8a27-ed6ad7315bfe"
- Tighten `activity-definitions/fi-daily-research-brief.md` and the - Tighten `activity-definitions/fi-daily-research-brief.md` and the
playbook so a local-only commit does not clear `due`. playbook so a local-only commit does not clear `due`.
**2026-09-14 follow-up:** verifier now resolves live origin/main, reads Git
objects, paginates Hub events, and validates pushed/SHA evidence. Exact-event
corrections preserve the five missing dates as explicit historical gaps.
`--require-date` proves a specific scheduled day; see
`docs/brief-durability-audit.md`.
**Done when:** `python3 scripts/verify_brief_durability.py` fails on the **Done when:** `python3 scripts/verify_brief_durability.py` fails on the
known Sep 2026 hub/git mismatch (or reports it as debt until those known Sep 2026 hub/git mismatch (or reports it as debt until those
events are reconciled). events are reconciled).
@ -217,7 +223,7 @@ Operator / railiance-platform:
```task ```task
id: FI-WP-0004-T09 id: FI-WP-0004-T09
status: in_progress status: progress
priority: medium priority: medium
state_hub_task_id: "304816de-5065-5928-b9bd-da1b5019c3d4" state_hub_task_id: "304816de-5065-5928-b9bd-da1b5019c3d4"
``` ```