FI-WP-0005 T01-T03: owner declaration, publication decision, egress hosts.

- Owner declaration for the profiled fi-daily-research-brief (proposed):
  inputs, single-commit briefs/** grant, fast-forward-only publication,
  quality rules, model requirements without env selection, completion
  evidence and rollback pins.
- Decision: origin publication is a typed grant on the run, not an
  executor default.
- docs/sources-egress.yaml: 17 exact host:443 entries for the sandbox,
  tested for sand-boxer format and drift against the prose allowlist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 51320@bnt-lap001
Assistant-Session: 9d40b4c7-8e3c-42ee-b755-d658d4640d6c
This commit is contained in:
tegwick 2026-09-22 12:00:59 +02:00
parent 7009521fdc
commit 5c5c298643
8 changed files with 396 additions and 4 deletions

View file

@ -0,0 +1,113 @@
# Owner declaration — fi-daily-research-brief (FI-WP-0005-T01)
#
# Answers rein-aharness d0b45acb / REINAH-WP-0003-T04: the owner-approved
# replacement for the profile-absent `fi-research-brief` route. This file is
# FI's authority over WHAT runs and under WHICH grant. It does not schedule
# (activity-core), pick a rein/sandbox/model (glas-harness) or execute
# (rein-aharness). Where another layer has not shipped the needed piece yet,
# the field names the FI-WP-0005 task that tracks it.
declaration_version: "1"
definition_id: fi-daily-research-brief
owner: freedom-intelligence
approved_by: Bernd Worsch
approved_at: "2026-09-22"
status: proposed # -> active at FI-WP-0005-T08 cutover
execution:
shape: agent # operator decision 2026-09-22 (option B)
harness_profile_ref: harness.fi-research-brief@1.0.0 # requested, FI-WP-0005-T05; not yet in the Glas catalog
selection: harness_profile_ref only. No label or approach_hint routing, no
fallback to the compatibility executor once cut over.
inputs:
# Read from the checked-out workspace at the run's base commit.
brief_date:
type: date
tz: Europe/Berlin
source: activity-core due item (context.item.date)
repository_files:
- docs/sources-allowlist.md
- docs/daily-brief-playbook.md
- briefs/_template.md
- inventory/RESERVE-STATUS.md
- research/2026-07-24-baseline-field-survey.md
- research/2026-07-24-nas-strategic-collection-plan.md
repository_globs:
- inventory/catalog/*.yaml # authority for sizes, licenses, collection status
prior_briefs:
names: last 5 by date, excluding brief_date
full_text: most recent 1
git_log: last 12 commits, oneline
network:
egress_hosts: docs/sources-egress.yaml # FI-WP-0005-T03; model route is profile-owned
secrets: none. The run reads no FI credential. Model credentials stay with
the profile's owner route (Glas / sand-boxer).
outputs:
artifact: briefs/{yyyy}/{mm}/{date}.md
format: briefs/_template.md, axes AD, collection-candidate section
exactly_one_new_file: true
must_not_modify_existing_files: true
repository_grant:
# Grant v1 fields (activity-core RepositoryGrant). v1 rejects publish=true,
# so publication rides on grant v2 (FI-WP-0005-T04).
version: "2" # requested; v1 shape below validated against activity-core b63131e+
allowed_paths:
- briefs/**
commit_count: {min: 1, max: 1}
publication:
remote: origin
ref: main
mode: fast-forward-only
decision: docs/decisions/2026-09-22-brief-origin-publication.md
commit_message: "FI daily research brief {date}"
quality_rules:
# Acceptance for canary comparison (FI-WP-0005-T07) and prompt contract.
- inventory/catalog is the authority. Do not re-announce cataloged models
as new, and never state sizes or licenses that contradict the catalog.
- Every parameter count, size, price or benchmark claim cites a primary
source that was fetched in this run. If it is not verifiable, say so
instead of estimating.
- Deltas only, measured against prior briefs and the baseline, not a resurvey.
- Cover axes AD, or state explicitly that an axis had no delta.
- Rumor (social media, unsourced aggregators) is not reported as confirmed.
- No credentials, customer data or model weights in the brief.
model:
# FI states requirements; the Glas profile pins provider/model/route.
model_class: frontier
required_capabilities: [tool_use, web_fetch_via_https_proxy, long_context]
limits_hint: {timeout_seconds: 1800, max_turns: 60}
constraints:
- No Binky-named variables (BRIEF_DAILY_MODEL, MAIL_TRIAGE_MODEL) and no
process-environment model selection. The profile version is the only selector.
- The playbook rule "no Claude Code / Codex on railiance01" means no
host-installed agent. A pinned agent executable inside the Glas sandbox is
acceptable only if the profile owner confirms it is not a host
installation. Operator to confirm (FI-WP-0005-T05).
completion:
event: fi_daily_brief
required_detail:
date: brief_date
path: outputs.artifact
pushed: true
origin_sha: 40-hex commit on origin/main that contains the artifact
harness_profile_ref: execution.harness_profile_ref
due_resolution: published-artifact resolver (FI-WP-0005-T04). The day
clears only if the artifact is on origin/main AND the event says
pushed=true. In the interim, fi_brief_status (activity-core 289aff3), same semantics.
on_failure: executor_run ok=false, no fi_daily_brief, day stays due.
independent_audit: scripts/verify_brief_durability.py --require-date {date}
rollback:
pins:
- glas profile version (previous harness.fi-research-brief@x.y.z)
- this declaration and the definition, by git revision in this repo
procedure: pin the previous profile version or revert the definition
revision, then sync activity-core definitions. Until the legacy expiry
(2026-12-31) the compatibility executor rein-aharness@11020e8 remains a
manual one-shot recovery. There is no host timer (FI-WP-0005-T06).
evidence_required: one exercised rollback at cutover (FI-WP-0005-T08)

View file

@ -65,5 +65,9 @@ action:
5. [x] Executor: `rein-aharness fi-research-brief` + `scripts/railiance-rhythm/`
(install on railiance01; not workstation cron)
Migration (FI-WP-0005): the owner declaration for the profiled replacement is
`fi-daily-research-brief.declaration.yaml` (status `proposed`). This definition
stays on the compatibility route until the T08 cutover.
Layers: activity-core (when/what/where) → rein-aharness (execute) → this repo.
See `docs/recurrence-ops.md` and `activity-core/docs/recurring-automations-playbook.md`.

View file

@ -0,0 +1,60 @@
# Decision: Origin publication of daily briefs is an explicit, typed grant
**Date:** 2026-09-22
**Status:** accepted
**Decided by:** Bernd Worsch (operator), FI owner
**Affects:** `activity-definitions/fi-daily-research-brief.md`,
`activity-definitions/fi-daily-research-brief.declaration.yaml`, FI-WP-0005
**Supersedes:** the grant wording in FI-WP-0004-T04, which granted "origin
publication as a named capability of `fi-research-brief`". That capability
lived inside one rein command. This decision moves it to the queued run's
repository grant.
---
## Context
A brief day counts only when the brief is on `origin/main` (FI-WP-0004). The
executor therefore has to push. rein-aharness (`d0b45acb`, REINAH-WP-0003-T04)
requires that publication be a separate, owner-approved decision, not a
default of the executor. activity-core repository-grant v1 is local-only and
rejects `publish: true`.
## Decision
1. **FI grants publication, scoped exactly:**
- remote `origin`, ref `main`
- mode **fast-forward only**: no force push, no other refs, no tags
- only commits whose changes stay within `briefs/**`
- at most one commit per run
2. **The grant rides on the run, not the executor.** It is carried as
activity-core repository-grant v2 `publication` (requested in FI-WP-0005-T04).
No rein, profile or tool may publish FI content without that grant on the
claimed `ops_run`.
3. **Failure semantics:**
- Push rejected or failed: the run fails, no `fi_daily_brief` is posted,
and the day stays due.
- Non-fast-forward (origin moved): the executor may rebase its single
brief commit onto the new origin head once, then retry. A second failure
is a failed run. It never merges and never forces.
- Success requires `origin_sha` evidence, and activity-core close
reconciliation checks it against the grant.
4. **Until grant v2 is live**, the compatibility path (`rein-aharness
fi-research-brief`, commit `11020e8`) continues under FI-WP-0004-T04 semantics.
It ends at cutover (FI-WP-0005-T08) or at the legacy expiry, whichever
comes first.
## Not granted
- Writes outside `briefs/**`. The catalog, inventory, workplans and docs stay
human- or workplan-driven.
- Publication to any other remote or mirror.
- Opening PRs or issues, or posting to other external systems.
## Consequences
- activity-core needs grant v2 and origin-SHA close evidence (FI-WP-0005-T04).
- The Glas tool profile must be able to push, but only under a grant
(FI-WP-0005-T05).
- `scripts/verify_brief_durability.py` remains the independent audit of the
result.

View file

@ -3,6 +3,11 @@
Standing channels for Freedom Intelligence briefs (axes AD). Prefer **primary**
sources over aggregators. This is a watchlist, not a scrape mandate.
Automated runs reach the network only through the hosts in
[`sources-egress.yaml`](sources-egress.yaml) (FI-WP-0005-T03). Adding a
channel here that needs a new host means adding that host there too;
`scripts/test_sources_egress.py` checks the two stay in step.
---
## Axis A — Frontier & commercial

99
docs/sources-egress.yaml Normal file
View file

@ -0,0 +1,99 @@
# Network egress for the daily research brief sandbox (FI-WP-0005-T03).
#
# Machine-readable companion to docs/sources-allowlist.md. The Glas profile
# for fi-daily-research-brief declares exactly these hosts in
# network.egress (default: deny). The model provider route is NOT listed
# here; it belongs to the profile (glas-harness), not to FI.
#
# sand-boxer rules (docs/bwrap-egress.md): exact lowercase DNS names, port 443
# only, no wildcards, no IP literals. Enforcement is by destination, not by
# path: every host below is a whole-host trust decision. Keep the list minimal.
#
# `channel` must appear verbatim in docs/sources-allowlist.md
# (scripts/test_sources_egress.py enforces this so the two cannot drift).
version: 1
hosts:
# Axis A — frontier & commercial
- host: openai.com:443
axes: [A]
channel: OpenAI / Anthropic / Google / xAI / DeepSeek blogs & release notes
why: Release notes, model pages, API pricing. Returned 403 to a plain
client on 2026-09-22 (bot protection); may be unreadable unattended.
- host: www.anthropic.com:443
axes: [A]
channel: OpenAI / Anthropic / Google / xAI / DeepSeek blogs & release notes
why: News and model announcements (apex redirects here).
- host: platform.claude.com:443
axes: [A]
channel: Model cards for API models
why: Model overview, context limits, pricing tables (docs.anthropic.com
now 301-redirects here, 2026-09-22).
- host: blog.google:443
axes: [A]
channel: OpenAI / Anthropic / Google / xAI / DeepSeek blogs & release notes
why: Gemini release announcements.
- host: ai.google.dev:443
axes: [A]
channel: Official pricing pages
why: Gemini API models and pricing.
- host: x.ai:443
axes: [A]
channel: OpenAI / Anthropic / Google / xAI / DeepSeek blogs & release notes
why: Grok release notes. Returned 403 to a plain client on 2026-09-22.
- host: api-docs.deepseek.com:443
axes: [A, B]
channel: OpenAI / Anthropic / Google / xAI / DeepSeek blogs & release notes
why: DeepSeek news, API pricing and model list.
- host: arena.ai:443
axes: [A]
channel: LMSYS / Arena / artificialanalysis-class charts
why: Directional leaderboard only; note gaming risk (lmarena.ai
301-redirects here, 2026-09-22).
- host: artificialanalysis.ai:443
axes: [A]
channel: LMSYS / Arena / artificialanalysis-class charts
why: Price/performance charts, directional.
- host: www.swebench.com:443
axes: [A, D]
channel: SWE-bench Verified / Live leaderboards
why: Harness+model pairs.
# Axis B — edge / local / open
- host: huggingface.co:443
axes: [B, C]
channel: Hugging Face org feeds
why: Model cards, licenses, sizes via /api/models. No weight download
(weight CDN hosts are deliberately absent).
- host: ollama.com:443
axes: [B]
channel: llama.cpp, vLLM, MLX, Ollama release notes
why: Ollama library and release notes.
# Axes B/C/D — GitHub-hosted releases (llama.cpp, vLLM, MLX, Unsloth, TRL,
# OpenHands, Aider, SWE-agent, OpenCode, Cline, ...)
- host: github.com:443
axes: [B, C, D]
channel: OpenHands, Aider, SWE-agent, OpenCode, Cline repos/releases
why: Release pages for runtimes, training tools and harnesses.
- host: api.github.com:443
axes: [B, C, D]
channel: llama.cpp, vLLM, MLX, Ollama release notes
why: Structured release listing instead of scraping HTML.
# Axis C — training & specialization
- host: arxiv.org:443
axes: [C]
channel: arXiv cs.LG, cs.CL, cs.AI (recent)
why: Abstracts and listings.
- host: export.arxiv.org:443
axes: [C]
channel: arXiv cs.LG, cs.CL, cs.AI (recent)
why: arXiv query API (the sanctioned programmatic endpoint).
# Axis D — harness & fleet
- host: modelcontextprotocol.io:443
axes: [D]
channel: MCP / tool-protocol standards
why: MCP specification and changelog.
# Deliberately absent:
# - Model provider API hosts (profile-owned route).
# - Hugging Face / GitHub content CDNs (no weight or asset download in briefs).
# - Social media / X (allowlist: rumor is not confirmation).
# - Internal Coulomb repos (sand-boxer, activity-core): read from the
# checked-out workspace or hub, not over the network.

View file

@ -0,0 +1,42 @@
"""The fi-daily-research-brief owner declaration parses and its references resolve."""
from pathlib import Path
import unittest
import yaml
ROOT = Path(__file__).resolve().parent.parent
DECLARATION = ROOT / 'activity-definitions' / 'fi-daily-research-brief.declaration.yaml'
class DeclarationTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.doc = yaml.safe_load(DECLARATION.read_text(encoding='utf-8'))
def test_referenced_files_exist(self):
inputs = self.doc['inputs']
paths = list(inputs['repository_files'])
paths.append(inputs['network']['egress_hosts'])
paths.append(self.doc['repository_grant']['publication']['decision'])
for rel in paths:
self.assertTrue((ROOT / rel).is_file(), rel)
def test_grant_is_single_commit_within_briefs(self):
grant = self.doc['repository_grant']
self.assertEqual(grant['allowed_paths'], ['briefs/**'])
self.assertEqual(grant['commit_count'], {'min': 1, 'max': 1})
self.assertEqual(grant['publication'],
{'remote': 'origin', 'ref': 'main', 'mode': 'fast-forward-only',
'decision': grant['publication']['decision']})
def test_completion_requires_published_evidence(self):
detail = self.doc['completion']['required_detail']
self.assertIs(detail['pushed'], True)
self.assertIn('origin_sha', detail)
def test_no_environment_model_selection(self):
self.assertNotIn('env', self.doc['model'])
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,51 @@
"""docs/sources-egress.yaml stays valid for sand-boxer and in step with the prose allowlist."""
from pathlib import Path
import re
import unittest
import yaml
ROOT = Path(__file__).resolve().parent.parent
EGRESS = ROOT / 'docs' / 'sources-egress.yaml'
ALLOWLIST = ROOT / 'docs' / 'sources-allowlist.md'
# sand-boxer bwrap-egress: exact lowercase DNS name, port 443, no wildcard or IP literal.
HOST = re.compile(r'^(?=.{1,253}:443$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}:443$')
MAX_HOSTS = 24 # every host is a whole-host trust decision; raise only deliberately
class SourcesEgressTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.doc = yaml.safe_load(EGRESS.read_text(encoding='utf-8'))
cls.hosts = cls.doc['hosts']
cls.prose = ALLOWLIST.read_text(encoding='utf-8')
def test_version(self):
self.assertEqual(self.doc['version'], 1)
def test_host_format(self):
for entry in self.hosts:
self.assertRegex(entry['host'], HOST)
def test_no_duplicates_and_bounded(self):
names = [e['host'] for e in self.hosts]
self.assertEqual(len(names), len(set(names)))
self.assertLessEqual(len(names), MAX_HOSTS)
def test_entries_complete(self):
for entry in self.hosts:
self.assertEqual(set(entry), {'host', 'axes', 'channel', 'why'}, entry['host'])
self.assertTrue(entry['why'].strip(), entry['host'])
self.assertTrue(set(entry['axes']) <= set('ABCD'), entry['host'])
def test_every_axis_covered(self):
covered = {axis for e in self.hosts for axis in e['axes']}
self.assertEqual(covered, set('ABCD'))
def test_channel_named_in_prose_allowlist(self):
for entry in self.hosts:
self.assertIn(entry['channel'], self.prose, entry['host'])
if __name__ == '__main__':
unittest.main()

View file

@ -70,7 +70,7 @@ network egress beyond a model provider.
```task
id: FI-WP-0005-T01
status: todo
status: done
priority: high
state_hub_task_id: "bf8c1870-bdca-51d6-beaf-c5f000037205"
```
@ -86,27 +86,39 @@ publication policy (reference T02), and rollback. No Binky-named env vars.
**Done when:** the declaration is committed, and every field rein listed in
`d0b45acb` maps to a concrete value or to a named follow-up task here.
**2026-09-22:** Done. `activity-definitions/fi-daily-research-brief.declaration.yaml`
(status `proposed`). It covers profile ref (pending T05), inputs, outputs, grant
(its v1 fields validate against activity-core `RepositoryGrant`; publication pending
T04), quality rules, model requirements without env selection, completion
evidence and rollback pins. `scripts/test_declaration.py` checks it. Open
operator question: whether a pinned agent executable inside the Glas sandbox
satisfies the playbook's "no Claude Code / Codex on railiance01" rule.
### T02 — Publication decision record
```task
id: FI-WP-0005-T02
status: todo
status: done
priority: high
state_hub_task_id: "25475ede-ebd9-5c31-8c22-718e30acf8ac"
```
`docs/decisions/2026-09-XX-brief-origin-publication.md`: FI grants
`docs/decisions/2026-09-22-brief-origin-publication.md`: FI grants
fast-forward-only publication of `briefs/**` to `origin/main`; no force, no
other refs; publication failure fails the run and leaves the day due. Supersedes
the implicit grant wording in FI-WP-0004-T04.
**Done when:** the decision is committed and cited by T01 and T04.
**2026-09-22:** Done. Adds a single rebase-and-retry on non-fast-forward;
it never merges or forces. Cited by the declaration and the activity-core
proposal `8ee9a6ab`.
### T03 — Machine-readable source host list
```task
id: FI-WP-0005-T03
status: todo
status: done
priority: high
state_hub_task_id: "9b92d7f4-f4c8-5ce5-9367-462653515e90"
```
@ -120,6 +132,12 @@ path, so every host is a trust decision.
**Done when:** the list is committed with a test, and the host count and the
rationale for each host are recorded.
**2026-09-22:** Done. `docs/sources-egress.yaml` has 17 hosts covering axes AD.
`scripts/test_sources_egress.py` checks sand-boxer format, the 24-host cap and
that each channel is named in the prose allowlist. A live check replaced
redirecting hosts (docs.anthropic.com → platform.claude.com, lmarena.ai →
arena.ai). openai.com and x.ai return 403 to plain clients, which is noted.
### T04 — activity-core handoff: grant v2 publication + generic due resolver
```task