Plan: graphify-findings-cleanup
- PRD: n/a (findings from
/graphifyknowledge-graph audit, not a PRD-tracked feature) · Issue: n/a - Risk class: Low
- Status: Approved <2026-07-15> — independent review (fresh context) found A3 was
wrong-direction and A1's seed.sql line was a mechanical overclaim; both corrected below, plus 5 same-class 🟡 findings the original audit missed, fixed in the same PR.
Approach
/graphify update surfaced 13 AMBIGUOUS edges + doc-drift patterns. Findings were re-verified against origin/main directly (not the stale local checkout the graph was built from — see Open Questions) before inclusion here; one candidate finding (OpenAPI MandateId enum still listing inverse-cramer/dax-mdax-smi-directors) turned out to be a false positive already fixed in PR #46 and is excluded.
Two tiers:
- Tier A (this plan, executed directly): unambiguous doc/schema drift — the
correct answer is derivable from current code, no editorial judgment needed.
- Tier B (listed, not executed): genuine judgment calls — compliance content,
business/content decisions, file-deletion calls — held for explicit direction.
Affected files & modules
A1 — Mandate count stale throughout README.md README.md has a whole section header ### The five mandates plus 8 more "five"/"5 mandate(s)" references. Current catalog (packages/shared/src/mandates.ts) has seven, unchanged in count since the mid-session swap (which replaced 2 of 7, not reduced the count). Rewrite the section + all count references to seven, listing all seven mandate names.
A2 — `docs/compliance/strategy-playbooks/README.md` — full rewrite, not word-swap
- Mandate table still lists
Inverse CramerandInsider DACH(removed in #46);
missing Liquid Private Equity and Deutsche Credit (added in #46).
- Ingest-script line references
pnpm ingest:inverse-cramer/pnpm ingest:dax-mdax-smi
— both removed from package.json in #46 (dead commands).
- Cron line says "(Vercel)" and lists the same two dead routes — crons moved to
Hetzner /etc/cron.d/stratify per docs/runbooks/hetzner-cutover.md / hetzner-power-schedule.md, and the routes themselves no longer exist.
A3 — REVERTED, moved to Tier B. Original plan: tighten Signal.mandateNumber maximum: 5 → 4 to match the 4-value MandateId enum. Wrong — independent review caught that mandates.ts numbers all 7 mandates 1-7, and the live partner route (apps/web/app/api/v1/mandates/[mandateId]/signals/route.ts) validates via isMandateId() against all 7, not just the OpenAPI's 4-value enum. Tightening to 4 would have made a real, partner-reachable value (5, Europe Defense) newly non-compliant with the spec — backwards from the actual drift, which is that the OpenAPI MandateId enum (and Signal.mandateNumber's bound) may need _widening_ to 7, not narrowing to 4. Whether the partner API is meant to expose all 7 mandates or stay deliberately scoped to 4 is a product/API-contract decision, not mine to guess — reverted both openapi/stratify-api.yaml and the parallel hand-written zod bound in packages/shared/src/webhook-events.ts back to their original maximum: 5 (itself also wrong either way, but not a regression I'm introducing). Moved to Tier B below.
A4 — `docs/jenkins.md` + `docs/runbooks/pre-launch-quality.md` — false "GH Actions disabled" claim Both state GitHub Actions is "intentionally disabled." .github/workflows/dos-ci.yml (build/lint/tests/verify/secret-scan on every PR) and .github/workflows/deploy.yml (auto-deploy on merge, added PR #51 — never once succeeded, see the deploy runbook) gate main right now — this claim was already false before #51 and is more false after it. Correct both docs to state GitHub Actions runs CI + deploy; note Jenkins' exact current relationship to that (parallel gate vs. vestigial) is unverified — don't assert Jenkins is _disabled_ without evidence, just fix the demonstrably false part.
Tier A2 — additional same-class fixes made after independent review
The A1/A2 audit wasn't as thorough as claimed; independent review caught 5 more instances of the exact same mandate-swap-not-propagated bug, all mechanically fixed in the same PR (no judgment calls):
README.md:193— "Seven mandates seeded from mandates.ts" was itself a
mechanical overclaim (I swapped "Five"→"Seven" without opening the file): supabase/seed.sql only seeds 3 of 7 mandates directly. Corrected to state that precisely, crediting migrations 0016/0019/0022/0023 for the rest.
README.md:165— "Four Vercel cron drainers (every 2 min)" was also stale
(same Vercel→Hetzner drift as A2, just a location the original sweep missed) — the real Hetzner crontab runs these once daily at 06:00 UTC. Fixed, plus a collateral indentation regression the first pass's reformatting introduced.
docs/README.md— the public team-handbook mandate table had the identical
stale Inverse Cramer/Insider DACH rows as A2's file. Fixed with the same "_not yet written_" convention for the two undocumented playbooks.
docs/product-design.md— rows 2 and 4 of the flagship mandate-narrative
table still described Inverse Cramer/Insider DACH even though the doc's own header prose already said "sieben Strategie-Mandate." Replaced with the exact mandates.ts thesis/universe text for Liquid Private Equity/Deutsche Credit.
apps/web/e2e/landing.spec.ts— a test literally named `'renders the seven
mandates' asserted getByText('Inverse Cramer'), which no longer renders anywhere. Swapped to 'Liquid Private Equity'`.
docs/api/quickstart.md— every example in the file used a mandate id
(koalitionsmix-eu) from an even older, pre-Inverse-Cramer product generation that would 404 against the real API today. Swapped every occurrence to pelosi-tracker-plus with matching name/thesis/universe fields; also fixed 3 "five mandates" count references.
docs/compliance/strategy-playbooks/README.md:10— Deutsche Credit's
benchmark was missing the trailing "Index" present in backtest.ts's MANDATE_BENCHMARK. One-word fix.
Tier B — not executed, needs your call
- `Signal.mandateNumber` bound (
openapi/stratify-api.yaml,
packages/shared/src/webhook-events.ts) — currently maximum: 5, matching neither the OpenAPI's own 4-value MandateId enum nor the real 7-mandate catalog isMandateId() validates against. Needs a decision: is the partner API meant to expose all 7 mandates (widen enum + bound to 7), or deliberately scoped to 4 (bound to 4, and the scoping should be documented as intentional rather than left implicit)? Not currently causing a live bug — the zod schema isn't enforced anywhere in the actual webhook-delivery code path — but the spec is wrong either way.
scripts/ingest/koalitionsmix-eu.ts,scripts/ingest/transatlantischer-wahlkreis.ts
— dead ingest scripts for mandates removed even before the Inverse Cramer/Insider DACH generation, unreferenced by any package.json script. Same "delete or keep as historical record" call as the other orphaned artifacts below.
docs/compliance/signalgeber-classification.md: "FMA" used for what look like two
different regulators (Liechtenstein vs. probably Austria) across different compliance docs — legal-content question, not mine to resolve.
docs/closed-beta/partner-runbook.mdvsdocs/api/phase-c-endpoints.md: runbook
treats a Phase-C webhook as testable in sandbox; the endpoints doc says it 404s pre-license. docs/compliance/closed-beta-sign-off-checklist.md also cites two different mechanisms for the "propagated trade <60s" criterion. Needs whoever owns closed-beta ops to say which is current.
docs/Stratify Design System/export/src/*.jsx(6 files) + the bundled
export/stratify-clickdummy.html are byte-identical duplicates of prototype/*. Delete as redundant, or keep as an intentional "handoff export" artifact?
- Pitch-deck sprawl:
docs/stratify_pitch_deck_v1.md(2 copies incl. one under
Design System/uploads/), docs/pitch-deck/stratify-preseed-v2.html, -v3.html, -v3.pdf, and an unversioned .pdf matching v3 content. No canonical pointer. Consolidate/archive, or intentional historical record?
apps/web/content/insights/{digitaler-souveraenitaets-stack-thesis,koalitionsmix-eu-q2-2026}.md
reference "mandates" not in the 7-entry catalog — likely just editorial blog pieces mislabeled as mandates by the extraction, not a real catalog gap, but worth a 10-second confirm.
Test strategy
- A1, A2, A4, and the Tier A2 additional fixes: doc-only, no test surface except
apps/web/e2e/landing.spec.ts (assertion text updated to match what actually renders — not run as part of the standard gate, Playwright is a separate test:e2e command).
- A3: reverted, not shipped — see Tier B.
- Full gate:
pnpm run lint && pnpm run typecheck && pnpm test— green (shared
44, web 69, mobile 19).
Migration / flag implications
- Flag: n/a
- Schema: n/a — the one schema-adjacent change (A3, the
mandateNumberbound)
was reverted after review rather than shipped in either direction.
Open questions
- The graph this plan is based on was built against a stale local checkout
(/Users/tobiastemmen/thoughtful-toby/stratify on feature/test-t4-ci, 11 commits behind origin/main) — every Tier A finding above was individually re-verified against origin/main directly, so this plan is sound, but the underlying graphify-out/graph.json in that directory should not be trusted for further "is X still true on main" questions without the same re-check.