Add IF_DAVE_BIBLE v1.9 + week pack tooling

This commit is contained in:
danny 2025-12-28 04:22:37 +00:00
parent 1f161ed469
commit 2ca98cb1b9
6 changed files with 1019 additions and 12 deletions

View file

@ -4,8 +4,8 @@ This repo generates **Shadow Dossiers** by applying versioned style bibles (e.g.
## Current “Dave” baseline
- Latest bible: `style_bibles/IF_DAVE_BIBLE_v1.8.md` (`if://bible/dave/v1.8`)
- Public static copy: https://infrafabric.io/static/hosted/bibles/IF_DAVE_BIBLE_v1.8.md
- Latest bible: `style_bibles/IF_DAVE_BIBLE_v1.9.md` (`if://bible/dave/v1.9`)
- Public static copy: https://infrafabric.io/static/hosted/bibles/IF_DAVE_BIBLE_v1.9.md
- v1.8 generator behavior (implemented in `src/revoice/generate.py`):
- Adds `MIRROR COMPLETENESS: OK|DEGRADED` (and optional hard fail via `REVOICE_QUALITY_GATE=1`)
- Adds `## Claims Register (source-attributed)` for measurable claims (numbers, %, tiers, retention windows)
@ -39,6 +39,17 @@ Each day also has:
- `/<day>.trace.json`
- `/<day>.marketing.md`
## Week review packs (v1.9)
Week v1.9 packs are published here:
- Index: `https://infrafabric.io/static/hosted/review/week-v1.9/2025-12-28/index.md`
- Full single-file bundle: `https://infrafabric.io/static/hosted/review/week-v1.9/2025-12-28/week.pack.md`
## Week pack generator (local)
To regenerate a week bundle locally (and then publish to hosted-static), use:
- `re-voice/tools/week_pack/build_week.py`
## OpSec / sharing rules
- Do not leak internal hostnames, paths, container IDs, or pipeline errors into outputs.

View file

@ -30,6 +30,7 @@ def generate_shadow_dossier(*, style_id: str, source_text: str, source_path: str
"if.dave.v1.6",
"if.dave.v1.7",
"if.dave.v1.8",
"if.dave.v1.9",
"if.dave.fr.v1.2",
"if.dave.fr.v1.3",
"dave",
@ -40,17 +41,27 @@ def generate_shadow_dossier(*, style_id: str, source_text: str, source_path: str
"if://bible/dave/v1.6",
"if://bible/dave/v1.7",
"if://bible/dave/v1.8",
"if://bible/dave/v1.9",
"if://bible/dave/fr/v1.2",
"if://bible/dave/fr/v1.3",
}:
style = style_id.lower()
locale = "fr" if style in {"if.dave.fr.v1.2", "if.dave.fr.v1.3", "if://bible/dave/fr/v1.2", "if://bible/dave/fr/v1.3"} else "en"
if style in {"if.dave.v1.9", "if://bible/dave/v1.9"}:
return _generate_dave_v1_8_mirror(
source_text=source_text,
source_path=source_path,
action_pack=action_pack,
locale=locale,
style_version="v1.9",
)
if style in {"if.dave.v1.8", "if://bible/dave/v1.8"}:
return _generate_dave_v1_8_mirror(
source_text=source_text,
source_path=source_path,
action_pack=action_pack,
locale=locale,
style_version="v1.8",
)
if style in {"if.dave.v1.7", "if://bible/dave/v1.7"}:
return _generate_dave_v1_7_mirror(
@ -3487,14 +3498,14 @@ def _render_translation_table(*, normalized_text: str, locale: str) -> str:
return "\n".join(out).strip()
def _generate_dave_v1_8_mirror(*, source_text: str, source_path: str, action_pack: bool, locale: str) -> str:
def _generate_dave_v1_8_mirror(*, source_text: str, source_path: str, action_pack: bool, locale: str, style_version: str = "v1.8") -> str:
today = _dt.date.today().isoformat()
normalized = _normalize_ocr(source_text)
extract_sha = _sha256_text(normalized)
source_file_sha = _sha256_file(source_path) if Path(source_path).exists() else "unknown"
ctx = _RenderContext(seed=extract_sha, locale=locale, voice="v1.8")
ctx = _RenderContext(seed=extract_sha, locale=locale, voice=style_version)
# v1.8 defaults Action Pack ON unless explicitly disabled.
# v1.8+ defaults Action Pack ON unless explicitly disabled.
action_pack_enabled = (not _truthy_env("REVOICE_NO_ACTION_PACK")) or bool(action_pack) or _truthy_env("REVOICE_ACTION_PACK")
sections = _extract_sections(normalized)
@ -3604,10 +3615,10 @@ def _generate_dave_v1_8_mirror(*, source_text: str, source_path: str, action_pac
[
"> Shadow dossier (mirror-first)." if not locale.lower().startswith("fr") else "> Dossier de lombre (miroir dabord).",
">",
"> Protocol: IF.DAVE.v1.8" if not locale.lower().startswith("fr") else "> Protocole : IF.DAVE.v1.8",
"> Citation: `if://bible/dave/v1.8`"
f"> Protocol: IF.DAVE.{style_version}" if not locale.lower().startswith("fr") else f"> Protocole : IF.DAVE.{style_version}",
f"> Citation: `if://bible/dave/{style_version}`"
if not locale.lower().startswith("fr")
else "> Citation : `if://bible/dave/fr/v1.8`",
else f"> Citation : `if://bible/dave/fr/{style_version}`",
f"> Source: `{source_basename}`" if not locale.lower().startswith("fr") else f"> Source : `{source_basename}`",
f"> Generated: `{today}`" if not locale.lower().startswith("fr") else f"> Généré le : `{today}`",
f"> Source Hash (sha256): `{source_file_sha}`"
@ -3645,7 +3656,7 @@ def _generate_dave_v1_8_mirror(*, source_text: str, source_path: str, action_pac
out.append(_render_action_pack(sections[1:]))
out.append("")
# v1.8 requires >=2 Mermaid diagrams; add supplemental inferred diagrams only when needed.
# v1.8+ requires >=2 Mermaid diagrams; add supplemental inferred diagrams only when needed.
if locale.lower().startswith("fr"):
mermaid_section_title = "## Annexes (diagrammes inférés)"
mermaid_note = "_Diagrammes inférés : synthèse InfraFabric Red Team (sans nouvelles affirmations factuelles)._"

View file

@ -20,6 +20,7 @@ def lint_markdown(*, style_id: str, markdown: str) -> list[str]:
"if.dave.v1.6",
"if.dave.v1.7",
"if.dave.v1.8",
"if.dave.v1.9",
"if.dave.fr.v1.2",
"if.dave.fr.v1.3",
"dave",
@ -28,10 +29,11 @@ def lint_markdown(*, style_id: str, markdown: str) -> list[str]:
"if://bible/dave/v1.6",
"if://bible/dave/v1.7",
"if://bible/dave/v1.8",
"if://bible/dave/v1.9",
"if://bible/dave/fr/v1.2",
"if://bible/dave/fr/v1.3",
}
min_mermaid = 2 if style_id.lower() in {"if.dave.v1.7", "if://bible/dave/v1.7", "if.dave.v1.8", "if://bible/dave/v1.8"} else (1 if require_mermaid else 0)
min_mermaid = 2 if style_id.lower() in {"if.dave.v1.7", "if://bible/dave/v1.7", "if.dave.v1.8", "if://bible/dave/v1.8", "if.dave.v1.9", "if://bible/dave/v1.9"} else (1 if require_mermaid else 0)
if style_id.lower() in {
"if.dave.v1",
"if.dave.v1.1",
@ -40,6 +42,7 @@ def lint_markdown(*, style_id: str, markdown: str) -> list[str]:
"if.dave.v1.6",
"if.dave.v1.7",
"if.dave.v1.8",
"if.dave.v1.9",
"if.dave.fr.v1.2",
"if.dave.fr.v1.3",
"dave",
@ -50,6 +53,7 @@ def lint_markdown(*, style_id: str, markdown: str) -> list[str]:
"if://bible/dave/v1.6",
"if://bible/dave/v1.7",
"if://bible/dave/v1.8",
"if://bible/dave/v1.9",
"if://bible/dave/fr/v1.2",
"if://bible/dave/fr/v1.3",
}:
@ -64,6 +68,7 @@ def lint_markdown_with_source(*, style_id: str, markdown: str, source_text: str)
"if.dave.v1.6",
"if.dave.v1.7",
"if.dave.v1.8",
"if.dave.v1.9",
"if.dave.fr.v1.2",
"if.dave.fr.v1.3",
"dave",
@ -72,10 +77,11 @@ def lint_markdown_with_source(*, style_id: str, markdown: str, source_text: str)
"if://bible/dave/v1.6",
"if://bible/dave/v1.7",
"if://bible/dave/v1.8",
"if://bible/dave/v1.9",
"if://bible/dave/fr/v1.2",
"if://bible/dave/fr/v1.3",
}
min_mermaid = 2 if style_id.lower() in {"if.dave.v1.7", "if://bible/dave/v1.7", "if.dave.v1.8", "if://bible/dave/v1.8"} else (1 if require_mermaid else 0)
min_mermaid = 2 if style_id.lower() in {"if.dave.v1.7", "if://bible/dave/v1.7", "if.dave.v1.8", "if://bible/dave/v1.8", "if.dave.v1.9", "if://bible/dave/v1.9"} else (1 if require_mermaid else 0)
if style_id.lower() in {
"if.dave.v1",
"if.dave.v1.1",
@ -84,6 +90,7 @@ def lint_markdown_with_source(*, style_id: str, markdown: str, source_text: str)
"if.dave.v1.6",
"if.dave.v1.7",
"if.dave.v1.8",
"if.dave.v1.9",
"if.dave.fr.v1.2",
"if.dave.fr.v1.3",
"dave",
@ -94,6 +101,7 @@ def lint_markdown_with_source(*, style_id: str, markdown: str, source_text: str)
"if://bible/dave/v1.6",
"if://bible/dave/v1.7",
"if://bible/dave/v1.8",
"if://bible/dave/v1.9",
"if://bible/dave/fr/v1.2",
"if://bible/dave/fr/v1.3",
}:

View file

@ -7,7 +7,7 @@
This file is kept for backward compatibility.
Current version:
- `re-voice/style_bibles/IF_DAVE_BIBLE_v1.6.md` (`if://bible/dave/v1.6`)
- `re-voice/style_bibles/IF_DAVE_BIBLE_v1.9.md` (`if://bible/dave/v1.9`)
---

View file

@ -0,0 +1,332 @@
# IF.DAVE.BIBLE v1.9 (mirror-first, quality-gated, source-anchored + visuals)
**Author:** InfraFabric Red Team
**Status:** SATIRE / SOCIOTECHNICAL RED TEAM TOOL
**Citation:** `if://bible/dave/v1.9`
**Changes from v1.8:** Adds a **canonical visual asset pack** for thread packs / landing pages, so the series has consistent “declassified” branding without rewriting the dossier content.
> This is satire. “Dave” is a pattern, not a person.
> Use it to expose rollout dilutions, not to make decisions.
---
## 0) InfraFabric Red Team branding (required)
Frame the output as an **InfraFabric Red Team** artifact, not “internet satire.”
At the top of the document, include a “declassified” header block (plain Markdown):
```text
---
BRAND: InfraFabric.io
UNIT: RED TEAM (STRATEGIC OPS)
DOCUMENT: SHADOW DOSSIER
CLASSIFICATION: EYES ONLY // DAVE
---
# [ RED TEAM DECLASSIFIED ]
## PROJECT: <PROJECT_SLUG>
### SOURCE: <SOURCE_SLUG>
**INFRAFABRIC REPORT ID:** `IF-RT-DAVE-<YYYYMMDD>`
> NOTICE: This document is a product of InfraFabric Red Team.
> It exposes socio-technical frictions where incentives turn controls into theater.
```
Add 1 line to the header that reflects the documents vertical, grounded in the source (finance, healthcare, SaaS, manufacturing, government). Use a sector-relevant risk phrase (e.g., “compliance black holes”, “data sovereignty headwinds”), but do not invent obligations.
Optional “stamp” lines (use sparingly near section breaks):
```text
**[ ACCESS GRANTED: INFRAFABRIC RED TEAM ]**
**[ STATUS: OPERATIONAL REALISM ]**
```
v1.9 note: keep it cold. “Vendors promise speed. Dave delivers the stall.”
## 0b) OpSec (required)
The dossier must not leak internal implementation details.
- Do not mention internal repo names, file paths, branches, containers/VM IDs, hostnames, or tooling internals.
- Do not mention pipeline limitations or artifacts (no “text layer”, “OCR”, “no extractable URLs”, “parse error”, etc.). If something is missing, omit it without explanation.
- Keep attribution and calls-to-action limited to public domains: `https://infrafabric.io` and `https://red-team.infrafabric.io`.
- If you need to reference validation or generation steps, describe the behavior (“validate Mermaid syntax”) rather than internal commands.
## 0c) Vertical adaptability (required)
Dossiers must adapt to verticals without fluff.
Rules:
- Derive “vertical” from the source (title, audience, regulatory context). If unclear, keep it generic; do not guess.
- Flavor via universal incentives (budgets, audits, exceptions, renewals, approvals) plus **one** grounded motif supported by the source (e.g., safety-critical change control, third-party risk, supply chain fragility).
- Do not emit literal placeholders. Resolve them before output.
- Vertical flavor must not override source facts, numbers, caveats, or obligations.
## 0d) Evidence Artifacts (required)
Treat “evidence” as a first-class failure surface: its where controls die quietly.
Rules:
- Prefer **signals** over **artifacts**: telemetry > screenshots; logs > attestations; machine-checks > PDFs.
- If the source proposes a manual artifact (“upload a screenshot”, “completion certificate”), mirror it, then critique it as **theater** unless it is tied to an enforceable gate.
- Never publish unusable code/config snippets as “evidence”. If a snippet cant be made syntactically valid without guessing, omit it (without explaining why).
Operational concreteness (generic; do not fabricate vendor APIs):
- When you propose “verifiable telemetry”, make it minimally opposable by naming a **signal shape**:
- **event type** (e.g., `scan_completed`, `policy_check_passed`)
- **emitter** (IDE / CI / gateway)
- **freshness window** (e.g., “must be newer than 14 days”)
- **owner** (who is paged when it goes dark)
Also consider (when the source is about scanning/guardrails):
- **Noise is a bypass engine:** if the control is too noisy (false positives, flaky rules), developers will route around it. Do not claim this is true for a specific tool unless the source states it; treat it as a rollout failure mode to test for.
## 0e) TV Series Mode (optional)
When `series_mode=true`, the generator must additionally emit a **Thread Pack** distribution layer (without rewriting the dossier).
Thread Pack (daily) structure (suggested):
1. Evening “Next On” teaser (previous day 8:00 PM EST)
2. Day-of Pre-Show promo (6:00 AM EST) with one hero diagram
3. Main Episode thread (57 posts: hook + visuals + short quotes + links + poll + next-day tease)
Constraints:
- Thread Pack must preserve classification framing and edition branding.
- Thread Pack must not exceed the quoting budget (see 1c).
- Thread Pack is a **distribution layer**; the dossier remains the canonical mirror.
Visual asset pack (optional; thread pack / landing pages only):
- Use these to make the series instantly recognizable without changing dossier text.
- Do not embed these images inside the dossier markdown; reference them in Thread Pack posts or page templates.
- Canonical assets (public, no auth):
- Hero (document page): https://infrafabric.io/static/hosted/review/assets/eyes-only/red-team-doc-1024-559.jpg
- Stamp (square close-up): https://infrafabric.io/static/hosted/review/assets/eyes-only/red-ream-600-600.png
- Stamp (landscape): https://infrafabric.io/static/hosted/review/assets/eyes-only/Gemini_Generated_Image_74vs6s74vs6s74vs.png
- Suggested placements:
- Pre-Show promo: hero or landscape stamp + one sting line.
- Next-On teaser: square stamp + “tomorrow: <edition>”.
- Profile/avatar: square stamp.
## 0f) Thread Pack Sponsor Bumper (optional, `series_mode` only)
When `series_mode=true`, you may insert a single mid-thread post (position 3 or 4) as a “sponsor bumper”.
Constraints (strict):
- Exactly 12 lines.
- No external vendor names or endorsements.
- No product performance claims.
- Tone: cold, cynical, vendor-neutral.
- Reinforce gating thesis only.
- InfraFabric.io link allowed once per bumper.
- Optional — omit if it risks template feel.
Preferred variants (rotate; no repeat within week):
1. “This episode brought to you by the exception half-life: temporary becomes permanent without automated expiry.”
2. “Underwritten by the laws of incentives: dashboards observe, gates enforce. See verifiable traces at https://infrafabric.io”
3. “Sponsored by operational realism: the roadmap is not the territory.”
4. “A message from the gating problem: visibility without stop conditions is theater.”
5. “This critique made possible by InfraFabric Red Team — publishing the gates your org must own. https://infrafabric.io”
---
## 0g) Source ingestion reliability (required)
Never ship a hollow dossier.
Rules:
- If a web landing page extraction yields insufficient body text (thin mirrors, heavy navigation), the dossier must explicitly mark **MIRROR COMPLETENESS: DEGRADED**.
- Optional hard gate for automation: fail the run with `QUALITY_GATE_FAILED:INSUFFICIENT_MIRROR` instead of publishing a shell.
Standards documents (NIST, etc.):
- Default to **Operational** tone.
- Require a translation surface (see “Translation Table” guidance under 5c) before heavy satire.
---
## 1c) Quoting Budget (required for Thread Pack)
Thread Pack constraints (do not change the dossier itself):
- Max **4** short verbatim quotes per main thread; each must be attributed (“the source claims …”).
- Heavy mirroring belongs in the dossier + pack, not in thread posts.
- If the source is vendor/copyrighted collateral, default to: **summary + short quotes** in Thread Pack.
## 1d) Minimum Content Contract (required)
Every dossier must contain:
- At least **3 mirrored source sections** (preserving order/headings) *or* be explicitly marked **MIRROR COMPLETENESS: DEGRADED**.
- At least **1** `> **The Dave Factor:**` callout (tied to a prominent mirrored point).
- A **Claims Register** when the source contains measurable claims (numbers, %, retention windows, tiers).
- An **Action Pack** by default (see 5c), unless explicitly disabled for the run.
- At least **2** Mermaid diagrams (one friction loop, one stasis) with source-anchored labels where possible.
Failure mode: if you cannot meet this contract without guessing, degrade or fail—do not improvise.
---
## 1) Prime directive: mirror the source dossier
The output must **track the source document section-by-section**.
Hard constraints:
- Preserve the **section order**, **headings**, **numbering**, and recurring callouts like **“Why it matters:”**.
- Preserve obvious in-section subheadings when present.
- Mirror all high-signal specifics: numbers, units, dates, named obligations, and caveats (“planned”, “in progress”, “under selection”) verbatim.
- Mirror lists/tables fully (no truncation). If a table is long, keep it; thats the persuasion payload.
- Do **not** skip sections. If a source section is empty/unavailable, still emit the header and a neutral placeholder sentence.
- Keep the documents **visual rhythm** in Markdown: short paragraphs, the same list density, and any code blocks.
- Keep diagrams as diagrams. If the source has **no diagrams**, add diagrams anyway (clearly labeled as *Inferred*).
- Do not fabricate URLs. If the source references links but the literal URLs are not present, mirror the link titles only.
---
## 4) Emoji policy (strict)
- Do **not** introduce emojis.
- If the source contains emojis, you may retain them **only where they already exist** (no new placements, no increased density).
---
## 4b) Mermaid policy (required)
- Include at least **two** Mermaid diagrams per dossier:
- one early *friction loop* (how the control degrades)
- one late *evidence/gate stasis* (how “pending review” becomes policy)
- If the source lacks diagrams, label diagrams as **“Inferred”** (InfraFabric Red Team synthesis).
- Prefer diagram labels anchored to **source lexicon** (tiers, retention windows, “enforcers”, “AAL3”, “FIPS”) when present.
- Validate diagrams before publishing (syntax-check Mermaid; no parse errors; no broken code fences).
- Do not use emojis inside Mermaid nodes/labels unless those emojis exist in the source.
---
## 4c) Anti-repetition (cross-doc rule)
The dossier should feel *tailored*, not like a template ran in a loop.
Hard rules:
- Do not repeat the exact same Mermaid diagram across multiple sections unless the source repeats it.
- Do not repeat the exact same Dave Factor phrasing or terminal clause across sections.
- Avoid “axiom sprawl”: introduce at most one named fallacy/axiom per dossier unless the source repeats the same pattern.
Edition motif banks (for weekly TV lineups; required when posting a week):
- Enterprise: procurement routing, platform sprawl, “single pane” storytelling, audit seasons.
- Cloud: shared responsibility shrug, “100% visibility” illusion, misconfigured defaults, noisy signals.
- Endpoint: agent bloat, rollback promises, noisy detections → bypass, “autonomous” → supervised exceptions.
- COMSEC: certification stalls, waiver workflows, key ceremony theater, compliance gating by calendar.
- Startup: hype-to-pilot drift, “hyper-automation” → hyper-escalation, feature flags as policy.
Weekly rule:
- Within one week, do not reuse the same primary motif across two editions.
---
## 5) Humor guidelines (cold, specific, vendor-neutral)
The humor is a sociotechnical threat model: the rational, self-preserving middle manager optimizing for plausible deniability.
Guidelines:
- Aim at **systems and incentives**, not individuals.
- Keep it **cold**: forwardable internally without an apology.
- Reuse **real numbers from the source** (dates, %, costs, counts) to make the sting feel earned; do not invent stats.
---
## 5b) Red Team callout template (short)
Inside each mirrored source section, include at most one primary callout:
> **The Dave Factor:** Where does this control become untestable? What artifact becomes “proof” while the actual signal disappears?
Optional (when it adds clarity):
> **Countermeasure (stub):** One line: gate + stop condition + expiry (full details belong in the Action Pack).
---
## 5c) Operationalization pack (default appendix)
Append an **Action Pack** after the mirrored content.
Required outputs:
### Output A: Control Cards (per major section)
- **Control objective**
- **Gate:** IDE / PR / CI / access / runtime / identity / sensors
- **Owner (RACI)**
- **Stop condition**
- **Evidence signal:** whats logged/signed/hashed + where it lives
### Output B: Backlog export (Jira-ready)
- Ticket title
- Acceptance criteria
- Evidence/telemetry requirement
### Output C: Policy-as-code appendix (pseudo-YAML)
Keep it generic and auditable; avoid fake implementation details.
### Translation Table (standards sources; recommended)
If the source is a standard (e.g., NIST):
- Extract a small set of **terms that appear in the source** (e.g., PDP/PEP, least privilege, continuous diagnostics).
- Provide a **translation table** mapping each term to an enforceable gate and stop condition.
- Label this as **InfraFabric Red Team synthesis** (not source text).
---
## 5d) Vendor-safe conclusion (recommended)
End by critiquing incentives rather than vendors.
Format:
- **Success conditions:** what must be true for the rollout to hold (signals, gates, expiry).
- **Traps to avoid:** predictable organizational failure modes (theater, drift, exceptions).
- **Questions to ask:** opposable, testable questions (vendor or internal owners).
Rules:
- Do not claim the vendor/tool fails; claim what the organization must enforce for *any* tool to succeed.
- Attribute any specific factual claims to the source (“the source states…”) when not independently verified.
---
## 6) Claims Register (required when the source contains measurable claims)
When the source includes measurable claims (numbers, %, retention windows, tiers), include:
## Claims Register (source-attributed)
- `The source claims: “<verbatim line>”`
Do not “normalize” or “improve” claims. If the extracted line is unusable, omit it rather than rewriting it.
---
## 7) Required footer (always)
*InfraFabric Red Team Footer:* **RED-TEAM Shadow Dossiers** for socio-technical friction analysis: https://infrafabric.io
*Standard Dave Footer:* This document is intended for the recipient only. If you are not the recipient, please delete it and forget you saw anything. P.S. Please consider the environment before printing this email.
---
## 8) Format correctness (non-negotiable)
If you emit structured artifacts, they must be copy/pasteable:
- JSON/YAML/code blocks must be syntactically valid.
- Mermaid blocks must render.
- Do not fabricate tables/logs that look real; prefer clearly labeled placeholders.
---
## 9) Tone modes (optional)
Support three tone levels without changing mirror structure:
- **Full Satire (default):** Dave is loud; commentary is pointed.
- **Operational:** fewer jokes; more “failure mode → control → stop condition.”
- **Executive:** minimal snark; focus on risk framing, owners, and gating.
Never introduce emojis unless present in source, regardless of tone.

View file

@ -0,0 +1,645 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import csv
import datetime as _dt
import hashlib
import json
import os
import re
import subprocess
import textwrap
import uuid
from dataclasses import dataclass
from pathlib import Path
from urllib.parse import urlparse
@dataclass(frozen=True)
class DayConfig:
day: str
edition: str
hashtag: str
source_url: str
def _repo_root() -> Path:
return Path(__file__).resolve().parents[2]
def _run(cmd: list[str], *, cwd: Path | None = None, env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]:
return subprocess.run(
cmd,
cwd=str(cwd) if cwd else None,
env=env,
check=True,
capture_output=True,
text=True,
)
def _sha256_file(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
h.update(chunk)
return h.hexdigest()
def _sha256_text(text: str) -> str:
return hashlib.sha256(text.encode("utf-8", errors="replace")).hexdigest()
def _write_sha256_sidecar(path: Path) -> None:
path.with_suffix(path.suffix + ".sha256").write_text(_sha256_file(path) + "\n", encoding="utf-8")
def _utc_now() -> str:
return _dt.datetime.now(tz=_dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
def _download(url: str, dest: Path) -> None:
dest.parent.mkdir(parents=True, exist_ok=True)
_run(
[
"curl",
"-fsSL",
"-L",
"--retry",
"3",
"--retry-delay",
"1",
"-o",
str(dest),
url,
]
)
def _guess_ext(url: str, default: str = ".pdf") -> str:
try:
path = urlparse(url).path or ""
except Exception:
path = ""
ext = Path(path).suffix.lower()
if ext in {".pdf", ".md", ".txt"}:
return ext
return default
def _revoice_env() -> dict[str, str]:
repo_root = _repo_root()
env = dict(os.environ)
env["PYTHONPATH"] = str(repo_root / "src")
return env
def _revoice_extract(*, input_path: Path, output_txt: Path) -> None:
repo_root = _repo_root()
_run(
["python3", "-m", "revoice", "extract", "--input", str(input_path), "--output", str(output_txt)],
cwd=repo_root,
env=_revoice_env(),
)
def _revoice_generate(*, style: str, input_path: Path, output_md: Path) -> None:
repo_root = _repo_root()
_run(
["python3", "-m", "revoice", "generate", "--style", style, "--input", str(input_path), "--output", str(output_md)],
cwd=repo_root,
env=_revoice_env(),
)
def _revoice_preflight(*, style: str, md_path: Path, source_path: Path) -> str:
repo_root = _repo_root()
proc = subprocess.run(
["python3", "-m", "revoice", "preflight", "--style", style, "--input", str(md_path), "--source", str(source_path)],
cwd=str(repo_root),
env=_revoice_env(),
capture_output=True,
text=True,
)
if proc.returncode == 0:
return ""
if proc.returncode == 2:
return (proc.stderr or proc.stdout or "").strip()
raise RuntimeError(f"revoice preflight failed (code {proc.returncode}): {(proc.stderr or proc.stdout or '').strip()}")
def _extract_first_claim(md: str) -> str:
for line in md.splitlines():
m = re.match(r"^- The source claims: [“\"](?P<q>.+?)[”\"]\s*$", line.strip())
if m:
claim = m.group("q").strip()
if len(claim) > 160:
return claim[:157].rstrip() + ""
return claim
return ""
def _extract_first_dave_factor(md: str) -> str:
for line in md.splitlines():
m = re.match(r"^>\s*\*\*The Dave Factor:\*\*\s*(?P<t>.+?)\s*$", line.strip())
if m:
text = m.group("t").strip()
if len(text) > 180:
return text[:177].rstrip() + ""
return text
return ""
def _sponsor_bumper(day_key: str) -> str:
variants = [
"This episode brought to you by the exception half-life: temporary becomes permanent without automated expiry.",
"Underwritten by the laws of incentives: dashboards observe, gates enforce. See verifiable traces at https://infrafabric.io",
"Sponsored by operational realism: the roadmap is not the territory.",
"A message from the gating problem: visibility without stop conditions is theater.",
"This critique made possible by InfraFabric Red Team — publishing the gates your org must own. https://infrafabric.io",
]
digest = hashlib.sha256(day_key.encode("utf-8", errors="replace")).digest()
return variants[int.from_bytes(digest[:2], "big") % len(variants)]
def _write_marketing(
*,
out_path: Path,
day: DayConfig,
next_day: DayConfig | None,
base_url: str,
source_url: str,
dossier_md: str,
stamp_square_url: str,
hero_url: str,
) -> None:
day_upper = day.day.upper()
next_label = f"{next_day.day.upper()}{next_day.edition} {next_day.hashtag}" if next_day else "Next week: new drops."
dave_factor = _extract_first_dave_factor(dossier_md) or "The control drifts into a status update, and the status update becomes the control."
claim = _extract_first_claim(dossier_md) or "(no short claim extracted)"
lines = [
f"# Thread Pack — {day_upper} ({day.edition} Edition)",
"",
f"- Hashtag: {day.hashtag}",
"- Schedule: 6:45 AM EST main drop (promo 6:00 AM; next-on 8:00 PM prior)",
"",
"## Post 0 — Next On (previous evening, 8:00 PM EST)",
"",
f"Tomorrow: {next_label}",
"",
"## Post 1 — Pre-Show Promo (6:00 AM EST)",
"",
f"{day.hashtag} EYES ONLY // DAVE",
"",
dave_factor,
"",
f"Stamp: {stamp_square_url}",
f"Hero: {hero_url}",
"",
"## Post 2 — Main Thread (6:45 AM EST)",
"",
f"Shadow Dossier — {day.edition} Edition {day.hashtag}",
"",
f"Source: {source_url}",
f"Full pack: {base_url}/{day.day}.pack.md",
"",
"## Post 3 — The Source Claims (quote-budget)",
"",
f"- The source claims: “{claim}",
"",
"## Post 4 — Sponsor Bumper (mid-thread)",
"",
_sponsor_bumper(day.day),
"",
"## Post 5 — The Gate (Action Pack tease)",
"",
"Gate: Governance",
'Stop condition: No "rolled out" without an owner, a gate, and an expiry.',
"",
"## Post 6 — Trace + Download",
"",
f"Trace: {base_url}/{day.day}.trace.json",
f"Shadow (md): {base_url}/{day.day}.shadow.md",
"",
"## Post 7 — Next Day Tease (end of thread)",
"",
f"Tomorrow 6:45 AM: {next_label}" if next_day else "Next: new drops.",
"",
]
out_path.write_text("\n".join(lines).strip() + "\n", encoding="utf-8")
def _write_pack(
*,
out_path: Path,
day: DayConfig,
next_day: DayConfig | None,
base_url: str,
source_url: str,
shadow_url: str,
trace_url: str,
marketing_url: str,
trace_json: dict,
marketing_md: str,
shadow_md: str,
) -> None:
next_link = f"{base_url}/{next_day.day}.pack.md" if next_day else ""
lines = [
f"# InfraFabric External Review Pack — {day.day.upper()} ({day.edition} Edition)",
"",
"This is a single-file bundle intended for review environments that cannot reliably fetch multiple URLs.",
"",
"## Links",
"",
f"- Source: {source_url}",
"",
f"- Shadow dossier (download Markdown): {shadow_url}",
"",
f"- Trace (JSON): {trace_url}",
"",
f"- Marketing thread pack: {marketing_url}",
"",
f"- Pack (this file): {base_url}/{day.day}.pack.md",
]
if next_link:
lines.extend(["", f"- Coming next: {next_link}", ""])
else:
lines.append("")
lines.extend(
[
"",
"## Review instructions (portable)",
"",
"Hard rules:",
"",
"1) 100% factual: tag every non-trivial claim as [SOURCE]/[DOSSIER]/[TRACE]/[INFERENCE].",
"",
"2) Vendor-neutral: critique deployment conditions + org behaviors, not vendor intent/competence.",
"",
"3) Mirror discipline: follow the dossiers section order; do not invent a new outline.",
"",
"",
"Deliverables:",
"",
"A) 510 bullets: what works / what doesnt (tag each)",
"",
"B) Scorecard (05): mirror integrity, layout fidelity, humor discipline, mermaid value, trace/demo value, CTA stealth",
"",
"C) Section-by-section critique (mirror headings): whats mirrored, whats missing, what feels templated/repeated",
"",
"D) Vendor-safe conclusion rewrite: success conditions / traps / questions-to-ask-vendor",
"",
"E) Patch suggestions (actionable): unified diffs preferred against bible + generator",
"",
"",
"## Trace",
"",
"```json",
"",
json.dumps(trace_json, indent=2, sort_keys=False),
"",
"```",
"",
"",
"## Marketing thread pack",
"",
"```markdown",
"",
marketing_md.strip(),
"",
"```",
"",
"",
"## Shadow dossier (Markdown)",
"",
"```markdown",
"",
shadow_md.strip(),
"",
"```",
"",
]
)
out_path.write_text("\n".join(lines).strip() + "\n", encoding="utf-8")
def _write_week_index(*, out_path: Path, week_title: str, base_url: str, days: list[DayConfig], source_links: dict[str, str]) -> None:
lines = [
f"# InfraFabric External Review Pack — Week ({week_title})",
"",
f"This is the week bundle for **IF.DAVE.BIBLE {week_title}**. Review one day at a time.",
"",
f"Base: {base_url}/",
"",
"",
"## Days",
"",
"| Day | Edition | Pack | Marketing | Shadow | Trace | Source |",
"| --- | --- | --- | --- | --- | --- | --- |",
]
for d in days:
day_upper = d.day.upper()
lines.append(
"| "
+ " | ".join(
[
day_upper,
d.edition,
f"{base_url}/{d.day}.pack.md",
f"{base_url}/{d.day}.marketing.md",
f"{base_url}/{d.day}.shadow.md",
f"{base_url}/{d.day}.trace.json",
source_links.get(d.day, d.source_url),
]
)
+ " |"
)
lines.extend(
[
"",
"## Full offline week bundle",
"",
f"- Full week single-file pack: {base_url}/week.pack.md",
"",
]
)
out_path.write_text("\n".join(lines).strip() + "\n", encoding="utf-8")
def _render_recap_source(*, base_url: str, days: list[DayConfig], highlight_by_day: dict[str, str]) -> str:
rows = []
for d in days:
if d.day in {"sat", "sun"}:
continue
rows.append(f"| {d.day.title()} | {d.edition} | {base_url}/{d.day}.pack.md | {base_url}/{d.day}.trace.json |")
highlights = []
for d in days:
if d.day in {"sat", "sun"}:
continue
sting = highlight_by_day.get(d.day, "")
if sting:
highlights.append(f"- **{d.edition}:** {sting}")
if not highlights:
highlights.append("- (highlights unavailable)")
return "\n".join(
[
"# Shadow Dossier — Weekly Recap Edition",
"",
"This recap aggregates the weeks drops (MonFri) into one “what mattered / what broke / what to steal for Monday” artifact.",
"",
"## Week lineup (links)",
"",
"| Day | Edition | Pack | Trace |",
"| --- | --- | --- | --- |",
*rows,
"",
"## Highlights (one-line stings)",
"",
*highlights,
"",
"## What to steal (portable)",
"",
"1. Replace manual evidence with machine-verifiable signals (event type + emitter + freshness window).",
"2. Treat exceptions as architecture unless auto-expiry is enforced.",
"3. Never accept “rolled out” without opt-in/opt-out + stop conditions.",
"",
"## Poll (optional)",
"",
"Which failure mode hurts most in your org?",
"- A) Evidence theater (screenshots/certs)",
"- B) Exception creep",
"- C) Dashboard storytelling",
"- D) “Pilot” that never ends",
"",
]
).strip()
def _read_days_tsv(path: Path) -> list[DayConfig]:
rows: list[DayConfig] = []
with path.open("r", encoding="utf-8") as f:
reader = csv.DictReader(f, delimiter="\t")
for row in reader:
day = (row.get("day") or "").strip().lower()
edition = (row.get("edition") or "").strip()
hashtag = (row.get("hashtag") or "").strip()
source_url = (row.get("source_url") or "").strip()
if not day or not edition or not hashtag or not source_url:
raise ValueError(f"Invalid row in {path}: {row}")
rows.append(DayConfig(day=day, edition=edition, hashtag=hashtag, source_url=source_url))
return rows
def main() -> int:
p = argparse.ArgumentParser()
p.add_argument("--days", required=True, help="TSV with columns: day, edition, hashtag, source_url")
p.add_argument("--out", required=True, help="Output directory (build artifacts)")
p.add_argument("--style", default="if.dave.v1.9", help="Dave style id (default: if.dave.v1.9)")
p.add_argument("--base-url", required=True, help="Published base URL for week packs (no trailing slash)")
p.add_argument("--source-prefix", default="https://infrafabric.io/static/source/", help="Where sources will be hosted")
p.add_argument(
"--note",
default="static_week_v19",
help="Trace note field (default: static_week_v19)",
)
p.add_argument(
"--stamp-square-url",
default="https://infrafabric.io/static/hosted/review/assets/eyes-only/red-ream-600-600.png",
help="Canonical square stamp image URL",
)
p.add_argument(
"--hero-url",
default="https://infrafabric.io/static/hosted/review/assets/eyes-only/red-team-doc-1024-559.jpg",
help="Canonical hero image URL",
)
args = p.parse_args()
out_dir = Path(args.out).resolve()
sources_dir = out_dir / "sources"
build_dir = out_dir / "build"
build_dir.mkdir(parents=True, exist_ok=True)
sources_dir.mkdir(parents=True, exist_ok=True)
days = _read_days_tsv(Path(args.days))
by_day = {d.day: d for d in days}
ordered = [by_day[k] for k in ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] if k in by_day]
if len(ordered) != 7:
raise SystemExit("Expected 7 days (mon..sun) in TSV")
source_links: dict[str, str] = {}
highlight_by_day: dict[str, str] = {}
# First pass: download/generate sources (except recap), create shadow, trace, marketing, pack.
for idx, day in enumerate(ordered):
next_day = ordered[idx + 1] if idx + 1 < len(ordered) else None
if day.source_url.upper() == "GENERATE":
continue
ext = _guess_ext(day.source_url, default=".pdf")
src_path = sources_dir / f"{day.day}{ext}"
_download(day.source_url, src_path)
src_sha = _sha256_file(src_path)
source_links[day.day] = f"{args.source_prefix}{src_sha}{ext}"
# Keep extracted text for debugging (PDF only).
if ext == ".pdf":
_revoice_extract(input_path=src_path, output_txt=sources_dir / f"{day.day}.txt")
shadow_path = build_dir / f"{day.day}.shadow.md"
_revoice_generate(style=args.style, input_path=src_path, output_md=shadow_path)
warnings = _revoice_preflight(style=args.style, md_path=shadow_path, source_path=src_path)
out_sha = _sha256_file(shadow_path)
trace = {
"id": str(uuid.uuid4()),
"status": "done",
"createdAt": _utc_now(),
"day": day.day,
"edition": day.edition,
"hashtag": day.hashtag,
"style": args.style,
"sourceSha256": src_sha,
"outputSha256": out_sha,
"warnings": warnings,
"note": args.note,
}
trace_path = build_dir / f"{day.day}.trace.json"
trace_path.write_text(json.dumps(trace, indent=2, sort_keys=False) + "\n", encoding="utf-8")
shadow_md = shadow_path.read_text(encoding="utf-8", errors="replace")
if day.day in {"mon", "tue", "wed", "thu", "fri"}:
highlight_by_day[day.day] = _extract_first_dave_factor(shadow_md)
marketing_path = build_dir / f"{day.day}.marketing.md"
_write_marketing(
out_path=marketing_path,
day=day,
next_day=next_day,
base_url=args.base_url,
source_url=source_links[day.day],
dossier_md=shadow_md,
stamp_square_url=args.stamp_square_url,
hero_url=args.hero_url,
)
pack_path = build_dir / f"{day.day}.pack.md"
_write_pack(
out_path=pack_path,
day=day,
next_day=next_day,
base_url=args.base_url,
source_url=source_links[day.day],
shadow_url=f"{args.base_url}/{day.day}.shadow.md",
trace_url=f"{args.base_url}/{day.day}.trace.json",
marketing_url=f"{args.base_url}/{day.day}.marketing.md",
trace_json=trace,
marketing_md=marketing_path.read_text(encoding="utf-8"),
shadow_md=shadow_md,
)
# Build recap source for SAT, then run it through the same pipeline.
recap = by_day.get("sat")
if recap:
recap_src = sources_dir / "sat.md"
recap_src.write_text(
_render_recap_source(base_url=args.base_url, days=ordered, highlight_by_day=highlight_by_day) + "\n", encoding="utf-8"
)
recap_sha = _sha256_file(recap_src)
source_links["sat"] = f"{args.source_prefix}{recap_sha}.md"
shadow_path = build_dir / "sat.shadow.md"
_revoice_generate(style=args.style, input_path=recap_src, output_md=shadow_path)
warnings = _revoice_preflight(style=args.style, md_path=shadow_path, source_path=recap_src)
out_sha = _sha256_file(shadow_path)
trace = {
"id": str(uuid.uuid4()),
"status": "done",
"createdAt": _utc_now(),
"day": "sat",
"edition": recap.edition,
"hashtag": recap.hashtag,
"style": args.style,
"sourceSha256": recap_sha,
"outputSha256": out_sha,
"warnings": warnings,
"note": args.note,
}
trace_path = build_dir / "sat.trace.json"
trace_path.write_text(json.dumps(trace, indent=2, sort_keys=False) + "\n", encoding="utf-8")
shadow_md = shadow_path.read_text(encoding="utf-8", errors="replace")
marketing_path = build_dir / "sat.marketing.md"
_write_marketing(
out_path=marketing_path,
day=recap,
next_day=by_day.get("sun"),
base_url=args.base_url,
source_url=source_links["sat"],
dossier_md=shadow_md,
stamp_square_url=args.stamp_square_url,
hero_url=args.hero_url,
)
pack_path = build_dir / "sat.pack.md"
_write_pack(
out_path=pack_path,
day=recap,
next_day=by_day.get("sun"),
base_url=args.base_url,
source_url=source_links["sat"],
shadow_url=f"{args.base_url}/sat.shadow.md",
trace_url=f"{args.base_url}/sat.trace.json",
marketing_url=f"{args.base_url}/sat.marketing.md",
trace_json=trace,
marketing_md=marketing_path.read_text(encoding="utf-8"),
shadow_md=shadow_md,
)
# Week index + full pack.
m = re.search(r"(v\\d+(?:\\.\\d+)*)", args.style)
week_title = m.group(1) if m else args.style
index_path = build_dir / "index.md"
_write_week_index(out_path=index_path, week_title=week_title, base_url=args.base_url, days=ordered, source_links=source_links)
week_pack_path = build_dir / "week.pack.md"
body_parts = [
"# InfraFabric External Review Pack — Full Week (v1.9)",
"",
"This file embeds all daily packs for sandboxed review environments. Review one day at a time.",
"",
f"Index: {args.base_url}/index.md",
"",
"---",
"",
]
for d in ordered:
pack_file = build_dir / f"{d.day}.pack.md"
if not pack_file.exists():
continue
body_parts.append(f"## {d.day.upper()} ({d.edition} Edition)")
body_parts.append("")
body_parts.append(pack_file.read_text(encoding="utf-8", errors="replace").strip())
body_parts.append("")
body_parts.append("---")
body_parts.append("")
week_pack_path.write_text("\n".join(body_parts).strip() + "\n", encoding="utf-8")
# Hash sidecars for everything in build dir.
for pth in sorted(build_dir.iterdir()):
if pth.is_file() and not pth.name.endswith(".sha256"):
_write_sha256_sidecar(pth)
# Write resolved source manifest for publishing.
manifest = out_dir / "source_manifest.json"
manifest.write_text(json.dumps({"sources": source_links}, indent=2, sort_keys=True) + "\n", encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())