Moves the list of private organisation / pilot / codename
strings the security gate blocks OUT of the repo entirely.
Before: tools/security/check-staged.sh + the Today AI prompt
+ docs/today-pipeline.md held the strings in plaintext. The
whole point of the gate is to keep certain strings out of
committed artefacts, so holding them in a committed
artefact was self-defeating — anyone with read access to
the repo trivially recovered the very list we tried to
protect.
After:
- The gate reads a runtime file
`${FAI_BANNED_TERMS_FILE:-~/.fai-security/banned-terms.txt}`
at scan time. One regex per line, `#`-prefixed comments,
matched case-insensitively against staged diffs and
commit messages. Repos contain no copy.
- Pre-commit / commit-msg modes log a warning + skip the
confidential-terms scan if the file is missing (fresh
checkouts shouldn't trip until the operator bootstraps
the list).
- CI mode (`check-staged.sh ci`) FAILS when the file is
missing — runners are expected to be bootstrapped by
their deploy step.
- The unit-test harness uses a synthetic placeholder term
(`SYNTHETIC_BANNED_TERM_XYZZY`) injected via a temp
banned-terms file, so the test never references real
customer names.
- docs/today-pipeline.md + tools/today/prompt.template.md
point at the runtime file instead of enumerating terms.
Operator bootstrap (one-time, per machine):
mkdir -p ~/.fai-security && chmod 700 ~/.fai-security
printf '\\b%s\\b\\n' TERM_A TERM_B > ~/.fai-security/banned-terms.txt
chmod 600 ~/.fai-security/banned-terms.txt
Gate self-tests: 18 passed, 0 failed.
Signed-off-by: flemming-it <sf@flemming.it>
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
5.7 KiB
Today-Hero Editorial Pipeline
Why this exists
The Studio store ships an editorial Today-Hero card (_StoreTodayHero in
lib/pages/store.dart) that plays the role Apple's Today tab plays in the App
Store: a single curated story per release that sets context for what the
operator should care about right now.
Editorial curation beats algorithmic recommendations on the discovery surfaces operators love — but it only works if the curation actually happens. Manual copywriting per release does not survive a solo-dev cadence. This pipeline produces draft stories every day, has the operator accept or reject them, and feeds the accepted story straight into Studio without a code release.
Loop overview
┌────────────────────────────────────────────────────────────────────┐
│ 1. SIGNAL COLLECTOR tools/today/collect.sh │
│ git log + store-index diff + audit-log highlights → │
│ plain-text "what happened in the last N hours" summary │
│ │
│ 2. PROPOSER tools/today/propose.sh │
│ local System-AI (Ollama by default) drafts 3 story candidates │
│ using tools/today/prompt.template.md + the signal summary → │
│ ~/.fai/today/proposals/<ISO-DATE>-<n>.yaml │
│ │
│ 3. REVIEW + ACCEPT tools/today/accept.sh <proposal-id> │
│ operator skims the proposal files, picks one (or none) → │
│ ~/.fai/today/active.yaml │
│ │
│ 4. STUDIO lib/pages/store.dart │
│ Studio reads active.yaml at startup; falls back to the │
│ compiled-in `_kCurrentTodayStory` when the file is absent │
│ or invalid (KRITIS / fresh install path) │
└────────────────────────────────────────────────────────────────────┘
The accept step is the only human gate. Everything else can run from a launchd
agent (macOS), systemd timer (Linux), or cron — see
tools/today/README.md for the on-disk recipes.
Schema
~/.fai/today/active.yaml (and every proposal file) follows this shape:
schema: today/v1
badge_en: CAPABILITY FEDERATION
badge_de: CAPABILITY-FEDERATION
title_en: Two MCP servers away from a richer hub
title_de: Zwei MCP-Server bis zu einem reicheren Hub
body_en: |
Click a recommended public source below — DeepWiki for repository docs,
Semgrep for security scans — and synthetic `mcp.<server>.<tool>`
capabilities appear here within seconds. No Node, no API key, no
subprocess.
body_de: |
Klicke unten auf eine öffentliche Quelle — DeepWiki für Repo-Doku,
Semgrep für Security-Scans — und synthetische
`mcp.<server>.<tool>`-Capabilities erscheinen binnen Sekunden hier.
Kein Node, kein API-Key, kein Subprozess.
icon: hub_outlined # Material icon name; loader maps to IconData
cta: openSettings | none # enum from _TodayCta
cta_label_en: Open MCP settings
cta_label_de: MCP-Einstellungen öffnen
schema: today/v1 is mandatory and gates loader compatibility. Future schema
revisions bump the version and Studio renders the const fallback rather than
attempt a dirty migration.
Design rules for the prompt
The prompt template (tools/today/prompt.template.md) hard-encodes:
- One story, not three feature bullets. Apple-Today-style storytelling — narrative arc, single takeaway, calls the operator forward.
- Bilingual peers, not machine translation. EN and DE are written in parallel by the same prompt pass; tone matches between languages.
- No marketing speak. The "no buzzwords" rule from
feedback_no_marketing_speak.mdis included in the prompt: ban "viral", "killer", "powerful", "just works", "revolutionary", "game-changing". - No private references. Generic terms only — no pilot
customer names, internal codenames, or specific
institutions. The operator's banned-terms list lives at
~/.fai-security/banned-terms.txt(outside the repo on purpose; gated by the pre-commit hook intools/security/check-staged.sh). - Concrete CTA. Every story names exactly one action the operator can take in Studio (which page, which button).
- Body fits in ~3 sentences. Hero card height is fixed; longer stories truncate.
Audit + air-gap
- All proposed and accepted stories live as plain YAML on disk;
reviewable by
git logif checked in. - The pipeline calls only the operator's already-configured System-AI (Ollama / OpenAI / vLLM). It never reaches out to a CMS, never phones home, and works air-gapped if the System-AI does.
- KRITIS deployments can disable the pipeline entirely by removing the
cron entry; Studio falls back to
_kCurrentTodayStoryand continues to work.
Out of scope (Phase 0.5)
- Multi-story rotation (one active story at a time).
- A/B-testing or click-through tracking — F∆I has no surveillance budget and no telemetry pipeline to feed it into.
- Auto-publication to git (operator-curated boundary stays manual).
- Hub-served stories. Studio reads
~/.fai/today/active.yamldirectly for now; promoting this to a HubAdmin RPC is a Phase 1 question once multi-tenant Studio appears.