Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 9d38f625e5cf7b01a99cdba857b74aa52927477e
parent 87a01399559b16038556d483f71e7c44c0b79a84
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon,  5 Oct 2026 02:09:14 -0400

plans: report sites — a site's reports, cited-only publishing and moment pages

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Aplans/report-sites.md | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 156 insertions(+), 0 deletions(-)

diff --git a/plans/report-sites.md b/plans/report-sites.md @@ -0,0 +1,156 @@ +# Report sites — a site built around cited reports + +Status: PLAN (2026-10-04), awaiting operator review. Not started. + +## What it is + +Two independent axes on a site: + +| Axis | Values | Today | +|---|---|---| +| `reports` | 0..n published reports, each a cited document rendered as pages | none | +| `publish` | `"full"` — the searchable corpus (today); `"cited"` — ONLY the moments the site's reports cite | always full | + +- A **report site** is `publish: "cited"` + ≥1 report: no search, no browse, no full transcripts, no archives. +- A **full site with reports** keeps everything it has and gains a Reports section; its moment pages also link + into the corpus (`/?v=…&t=`). +- Every citation in a report opens a **moment page** on the site: the quote, a self-hosted evidence clip of the + cited span (720p, ± a little context), the surrounding transcript lines, record metadata (title, channel, + date), and a link to the original at its time. A post citation shows the captured screenshot and media + (capture-posts) and the post text instead of a clip. +- A report claim can carry the reviewed source's own sentence as an image (shoot-page) and the source's + archive links. + +First user: one private, local-only report site. The machinery is generic so later sites (and later reports +on existing full sites) need configuration, not code. + +## The report document — `report.json` (format `archilyzer-report`, version 1) + +```jsonc +{ "format": "archilyzer-report", "version": 1, + "id", "kind": "factcheck" | "sweep", "title", "subtitle", "summary" /* md */, "published", "updated", + "subject": { "source": "s0" }, // the document under review (optional) + "verdicts": { "PARTLY": { "label": "…" } }, // optional label/colour overrides of the shared vocab + "sources": { "s0": { "kind": "article", "title", "url", "publisher", "author", "date", + "archives": [{ "label", "url", "context" }], // links in context, as the source had them + "saved": "sources/s0/page.html" } }, // input for stills only — NEVER published + "citations": { + "c01": { "kind": "video", "channel", "id", "start", "end", "quote", "pad": { "before": 5, "after": 5 } }, + "p01": { "kind": "post", "channel", "id", "quote" }, + "a01": { "kind": "source", "source": "s0", "quote", "image": "stills/a01.png" } }, + "sections": [ { "id", "title", "body" /* md */, + "claims": [ { "id", "text", "verdict" /* optional */, + "sourceQuote": { "citation": "a01" }, + "findings": "md with [label](cite:c01) refs", + "citations": ["c01", "p01"] } ] } ] } +``` + +- A fact-check is sections (chapters) → claims with a verdict. A sweep report is sections with no verdicts, or + bodies with inline `cite:` links. +- The verdict vocabulary (CORROBORATED, PARTLY, CONTRADICTED, NOT_FOUND, UNTESTABLE + labels/colours) moves to + common; `umtool/report-to-video/factcheck.mjs` re-exports it — one copy. +- Files per site: `transcripts/sites/<siteId>/reports/<reportId>/{report.json, stills/, sources/<sid>/…}`. + `site.json` `reports: [ids]` is the published, ordered list; an unlisted directory is a draft. +- Validation (zod, `common/lib/report/`): ids resolve, every `cite:` ref exists, spans are sane (cap ~120 s), + stills exist; at compose time every video quote is checked against its cue window (fail loudly on drift; a + platform with two ids must say which). + +## New `site.json` keys + +| Key | Default | Notes | +|---|---|---| +| `publish` | `"full"` | `"cited"` publishes only reports + their moments. Only the non-default is written. | +| `reports` | `[]` | Ordered report ids under `reports/`. | + +`channels` keeps its meaning (the pool a site's citations may resolve against). Docs in `SITE_FIELD_DOCS`, +`SITE.md` regenerated; the editor's site form gets the publish control; a new **Reports** tab under +`/sites/<id>/` lists reports, their validation problems, and "Prepare evidence media". + +## Routes (export app) + +| Route | What | +|---|---| +| `/reports/` | Report index; the HOME page of a `cited` site | +| `/reports/<reportId>/` | The report: summary, tally (fact-check), sections → claims (verdict chip, source sentence still, findings with citation links, archive links in context) | +| `/m/<channel>/<id>/<start>-<end>/` | Video moment page | +| `/m/<channel>/<id>/` | Post moment page | + +- Server-rendered at build from JSON that compose writes under `public/reports/**` and `public/m/**`, read like + `export/app/lib/archives.ts`. +- `generateStaticParams` must never be empty (Next 16 static export error E87 on an empty dynamic route): a site + with zero reports emits one placeholder param that renders "no reports". +- A `cited` site's shell has no workspace/search, no `/ask`, no downloads/duplicates; the home page must not call + `countTranscripts()` (it throws without a summaries manifest). +- A full site shows a **Reports** header link when it has reports. + +## Evidence media + +- Clips are cut ON THE HOST, before the per-site build, by a prepare step (`archilyzer reports prepare <siteId>` + and an editor job kind with `needsMedia`): the container comes from the corpus clip window + (`data/<id>/clips/<from>-<to>.*`) or the saved-video store pointer; cut accurately, 1280×720 H.264 crf 23, AAC. + Cached at `.export-index/sites/<siteId>/report-media/<hash>.mp4` (hash = source + span + profile), so the docker + build (read-only mounts, no ffmpeg) only copies. +- The cutter's tier lookup and cut args move from `umtool/report-to-video/sources.mjs` / `umtool/lib/report/` into + `common/lib/evidenceClip-server.ts`; umtool re-exports (common may not import umtool). +- A citation whose media is missing fails prepare with a list (the editor's fetch-window / persist fill it). +- Published layout: `out/media/clips/<channel>/<id>/<start>-<end>.mp4`, `out/media/posts/<channel>/<id>/…` + (only cited captures; the post-visibility rule still applies), `out/reports/<r>/stills/*.png`. +- Limits: refuse a file over 24 MiB (Pages: 25 MiB), keep the site under the 20k-file cap. +- The existing guard test that forbids publish code from naming `posts-media` is amended to allow exactly the + one module that copies CITED captures, with a test that it copies nothing else. + +## Compose and the contract + +- `compose-site` gains a reports stage: resolve citations against the shared transcript trees, verify quotes, + write report + moment JSON, copy media and stills. +- `publish: "cited"` prunes everything corpus-shaped from `public/` (summaries, stats, transcripts, subs, posts, + digests, archives, duplicates, tags, aliases, chart templates, service worker) — `public/` is shared by every + site's build in turn, so stale data from the previous site must not survive — and an **out/ allowlist audit** + refuses a cited build that contains anything outside reports/moments/media/stills/shell assets. +- Always emit `site.json` (`channels: []` for a cited site) and `corpus.json` with `site.scope: "cited"`, + `audience`, and `reports: { index: "/reports/index.json" }` — the private-deploy guard and compose's + cross-site check read them. `CONTRACT.corpusSpec` → 5; readers tolerate a cited scope (an old reader sees an + empty corpus, which is safe). `llms.txt` and the sitemap get a cited variant listing report routes. +- Hub and homepage exclude `cited` sites for now; MCP may later gain `list_reports` / `get_report`. +- umtool's cue walk (`cues.mjs`) cannot resolve cues off a cited site — documented. + +## Converters (to make reports from what exists) + +- umtool video manifest / ledger → report.json. +- Sweep markdown (`/sweep` output: `[title @ mm:ss](<origin>/?v=…&t=…)`, `[post by …](url)`) → report.json, ends + widened with `resolve-windows widen`. +- `shoot-page` batch items from each claim's `sourceQuote`. +- A bespoke report shape (any other generator) converts outside the repo and validates against the schema. + +## Slices + +| Slice | What | Size | Needs | +|---|---|---|---| +| R0 | `common/lib/report/`: zod schema, types, validation, moment keys, shared verdict vocab; `REPORT.md` generated | M | — | +| R1 | site keys `publish` / `reports` + site form control | S | — | +| R2 | evidence media: cutter port to common, `reports prepare` CLI + job kind, cited-capture copier + guard amendment | L | R0 | +| R3 | compose: reports stage, cited prune, allowlist audit, always-emit site/corpus, llms/sitemap variants | L | R0, R2 interface | +| R4 | export app: routes, Reports header link, cited shell, placeholder params | L | R0 (fixture JSON) | +| R5 | consumers: corpusSpec 5, reader tolerance, hub/homepage exclusion, builtExport audit hook | M | R0 | +| R6 | converters: manifest → report, sweep md → report, shoot-page items | M | R0 | +| R7 | editor Reports tab: list, problems, Prepare media, private build | M | R1, R2 | +| R8 | e2e: a cited fixture site (own playwright config), compose + audit unit tests | M | R3, R4 | + +``` +wave 1: R0, R1 +wave 2: R2, R4, R5, R6 (after R0) +wave 3: R3 (after R2), R7 (after R1, R2) +wave 4: R8 (after R3, R4) +``` + +## Risks + +1. Stale `public/` leaking corpus data into a cited build → explicit prune + out/ allowlist audit before anything + is served or deployed. +2. A cited build without `site.json` / `corpus.json` silently disables the private-deploy guard → always emit. +3. Next 16 empty-params build failure → placeholder param. +4. Post privacy: only cited captures, the visibility rule applies, saved source pages are never published. +5. Moment JSON carries only cited fields and a bounded cue context. +6. Size: span cap, 24 MiB per-file refusal, 20k files. +7. Cue drift between a corpus and an earlier publish; platforms with two ids → validated at compose, loud failure. +8. Contract change for third-party readers → spec bump, safe degradation.