* docs: full accuracy overhaul, grounded claim-by-claim in real code Every doc in this repo (CLAUDE.md, README.md, SECURITY.md, docs/*.md) was audited and rewritten by a multi-pass process: independent rewrite per cluster, adversarial re-verification of that rewrite against the actual code, then a final cross-doc consistency pass — each stage re-checking claims against the repo directly rather than trusting the previous stage. Two corrections matter most: 1. docs/ICEBERG_WRITE_BOUNDARY.md, docs/11-lakehouse-write-plan.md, and docs/P0_QUERY_SOURCE_FLOW.md asserted src/services/quality-check-service.ts, quality-report-service.ts, src/data-platform/services/catalog-service.ts, and sql/002_p0_dataset_quality.sql/sql/003_*.sql as real, tested code. git log --all across every branch shows zero commits for any of these paths, ever. 0pt's data-quality/QA enforcement does not exist in code. Corrected everywhere, with a new docs/P0_DATASET_QUALITY_FLOW.md added as an explicit target-spec (not-yet-built) replacement. 2. github.com/sax3l/0pt (this repo) and git.siax.io/sax3l/0pt (Gitea) are two diverged remotes sharing part of the same history (services/api/ workers/adaptive_router.py is byte-identical between them). Gitea's main independently accumulated a WAF-bypass/proxy-rotation scraper stack in three 2026-07-13 commits, since removed via a Gitea PR; GitHub's main never had it. Which remote is canonical/deployed is unresolved — flagged consistently across the corpus, not decided here. Also newly and consistently documented: the tenant_id/project_id-read-from- client-body gap on the P0 lakehouse/rate-policy/source-query routes (no auth/session system exists anywhere in this repo yet); that every CL0UD/ N0D/AUD0/ST0RE platform client is real but mock-only (zero process.env reads for any of their *_URL vars); that two P0 mutation routes (lakehouse/mappings, sources/rate-policies) always fail over HTTP today because their prerequisite entity can never be created via any route; a schema bug in sql/004 (FK references to opt_datasets/opt_sources, which don't exist); and that 0pt.app's live deployment was independently confirmed (curled this pass) to be running this exact codebase with DATABASE_URL/REDIS_URL unset in production. New file: docs/12-edge-capability-api.md — the edge-native P0 TypeScript capability contract (app/api/0pt/*, /api/screen, /api/ai/parse), previously undocumented and distinct from docs/04-api.md's FastAPI-engine-only scope. Verified before commit: tsc --noEmit clean; vitest run 88 passed, 1 todo, 0 failures (13 files) — docs-only change, no source touched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs: reflect PR #5's lakehouse-mapping-registry + status-code fixes Follow-up to keep docs/12-edge-capability-api.md accurate once both PRs land: fix/0pt-full-bug-hunt (#5) fixed the shared-instance bug in the lakehouse mapping routes and added proper status-code mapping to the document extraction route. This doc was written against the pre-fix state in the same pass that added it, so it needs the same correction. Does not claim the lakehouse write path fully works now -- the deeper dataset-registration gap (no route ever calls DatasetService.registerDataset) is unchanged and still blocks it end-to-end; only the shared-instance half of the bug is fixed. --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
20 KiB
09 · Build plan
Phased, in dependency order. Finish a phase to its acceptance criteria before the next. Each phase = small, reviewable commits. Reconcile with existing code rather than rewriting wholesale.
Status callouts added 2026-07-19. Each phase below now carries a
Status:line, grep/read-verified againstmainthis pass. These are conservative on purpose: a phase is only called Done when this pass found real, working code satisfying its Accept criteria; anything else is Partial (with the specific evidence and gap named) or Not started. No phase is upgraded to Done without a code citation. Where a referenced doc (e.g.docs/design/...) doesn't exist, that's flagged rather than silently worked around. Seedocs/08-infrastructure.mdfor the full env/security grounding these statuses draw on.
Phase 0 — Foundation & repo
- Monorepo structure (
docs/08). Migrate existingsrc/orchestrator+src/workersintoservices/api. - Postgres schema + migrations (
docs/03);pgvectorenabled..env.example. docker-composeup (postgres, redis, api, worker, nocodb, metabase, caddy)./healthgreen.- CI skeleton (lint/typecheck/build).
- Accept:
docker compose upruns the whole stack locally; migrations apply;/healthreturns ok for db + queue. - Status: Partial.
- Repo structure exists but diverged from the
apps/web/packages/sharedlayoutdocs/08originally sketched — it settled onapp/(Next.js, repo root),lib/,src/(platform clients/services), andservices/api/(FastAPI).docs/08-infrastructure.mdnow documents the structure as-built. - Migration to
services/api/orchestrator+services/api/workersis done (verified: those directories exist and containmain.py/adaptive_router.py/pipeline.py/etc.; nosrc/orchestratororsrc/workersremain at the old paths). - Postgres schema is real:
sql/001_init.sql(CREATE EXTENSION vector,CREATE EXTENSION pgcrypto, 17 tables incl.orgs,users,orders,jobs,audit_log,bridge_provenance,db_grants,payments) +sql/004_iceberg_write_boundary.sql. Nosql/002_*orsql/003_*exists anywhere in this repo's history — some other docs (docs/ICEBERG_WRITE_BOUNDARY.mdetc.) cite asql/002_p0_dataset_quality.sqlthat was never built; seedocs/08-infrastructure.md's "Schema state" section. .env.example— now exists, on branchfeature/0pt-env-example(github.com/sax3l/0pt/pull/3), not yet merged tomain. It was blocked for the life of this project by.gitignore's blanket.env*rule silently matching it too; the same PR fixes that with!.env.example. Until that PR merges,mainstill has no.env.example.infra/docker-compose.ymlandinfra/Caddyfileare real, committed files (not sketches) covering exactly the services listed. This pass found no evidence they've ever actually been run — no CI, no deploy log, no reachable host checked. "File exists and looks correct" ≠ "verified to run"; treat the Accept criterion ("docker compose up runs... /health returns ok") as unverified.- CI skeleton — not built. There is no
.github/workflows/directory in this repo at all.pnpm lint/typecheck/testexist as local scripts only.
- Repo structure exists but diverged from the
Phase 1 — Design system in code
- Tailwind + shadcn;
globals.css(grain + reveal keyframes) anduseRevealverbatim (docs/05). - Build DataArtifact primitive (table/map/schema/chart/grid) and ProvenanceBadge (
docs/designprompts 5.1, 2.11). - Accept: a sample page renders mono tokens, a DataArtifact animates on scroll, reduced-motion respected, focus rings visible.
- Status: Partial — real components exist; scroll-motion/reduced-motion/focus-ring behavior not
independently exercised this pass.
- Tailwind + shadcn/radix stack: confirmed real in
package.json(tailwindcss,@radix-ui/*,class-variance-authority,tailwind-merge,tailwindcss-animate). hooks/use-reveal.ts,components/data-artifact.tsx,components/provenance-badge.tsxall exist and are used acrossapp/katalog,app/kallor,app/bestall,app/order/[id], and mostcomponents/sections/*files (grep-confirmed).docs/design/0pt-v0-build-brief.mdanddocs/design/0pt-design-coverage-and-visuals.md(the "prompts 5.1, 2.11" this bullet references) do not exist anywhere in this repo — confirmed via full-repo search; there is nodocs/design/directory at all.CLAUDE.md's own doc index has already been corrected to flag this as a gap rather than list them as real. Treat any reference to specific numbered "build-brief prompts" in this build plan as pointing at a doc that was never written — not this file's scope to author that content.- Reduced-motion handling, focus-ring visibility, and actual scroll-triggered animation behavior were not executed/verified in this pass (no browser run performed) — code presence is confirmed, runtime behavior is not.
- Tailwind + shadcn/radix stack: confirmed real in
Phase 2 — Marketing + catalog (placeholder data)
- Landing components top-to-bottom (build-brief 2.1–2.10): Header, Hero, Philosophy, Catalog teaser, Engine, Visualisation gallery, Coverage, Editorial, Proof, Footer.
- Full Catalog page + Dataset detail + Sourcing-map page (coverage doc 5.4). Postgres full-text search.
- All with loading/empty/error states + mobile.
- Accept: marketing site + catalog browse work end-to-end on placeholder catalog rows; one continuous scroll reads well; DoD met per screen.
- Status: Partial.
- Landing section components are real:
components/sections/{hero,philosophy,gallery,editorial, pricing,technology,testimonials,footer,featured-products,collection}-section.tsxall exist. app/katalog/page.tsx,app/katalog/[id]/page.tsx,app/kallor/page.tsxall exist.- Postgres full-text search was not found — grepped for
to_tsvector/websearch_to_tsqueryacross the whole repo, zero matches. Catalog browsing today reads fromlib/data.tsplaceholder data, not a full-text query against Postgres. This Accept criterion's "catalog browse... on placeholder catalog rows" half is plausible; the Postgres full-text search half is not built. - Loading/empty/error states + mobile + DoD-per-screen were not independently audited screen-by-screen
this pass —
components/states.tsx(LoadingSkeleton,ErrorState) exists and is imported widely, which is evidence toward this but not a full verification.
- Landing section components are real:
Phase 3 — Auth & portal shell
- Auth.js + Postgres adapter; sign in/up/verify/forgot.
orgs/users. - Portal shell + Dashboard ("Mina beställningar") with empty state.
- Accept: a user can sign up, land in an empty dashboard, sign out; routes protected.
- Status: Not done — this is the most consequential gap in the whole repo, not a minor loose end.
- There is no Auth.js integration.
lib/auth.tsis a from-scratch, ~40-line HMAC-SHA256 session scaffold (Web Crypto, no dependency) whose own comment states: "Production auth = Auth.js + Postgres adapter + OAuth (docs/02) — wired once the database is provisioned."app/api/auth/login/route.tsis a dev-only login gated by a sharedDEV_LOGIN_KEY, not sign-up/verify/forgot. orgsanduserstables exist insql/001_init.sql(schema is ready), but nothing in the app reads/writes them for real session-bound identity today.app/portal/page.tsxand sibling portal pages exist (shell is real), but routes are not protected by any real session check tied to that schema.- Concretely, this means the tenant/org isolation several
0pt.*API routes assume does not exist yet.app/api/0pt/lakehouse/mappings/route.ts,.../writes/route.ts,app/api/0pt/sources/rate-policies/route.ts,app/api/0pt/sources/[sourceId]/query/route.ts, andsrc/services/document-capture-service.tsall readtenant_id/project_idstraight from the request body — anyone can claim any tenant.app/api/orders/route.ts's ownGEThandler carries the literal comment// org scoping added with auth. This is an open security gap, not a two-tier design choice — seedocs/08-infrastructure.md's "Security & compliance" section for the full list. Do not build any doc or feature that assumes tenant-scoped auth already exists.
- There is no Auth.js integration.
Phase 4 — Order lifecycle end-to-end (test mode)
- Configurator (2.12) wired to
POST /estimates(real estimate math in FastAPI). - Deposit checkout (2.13) → Stripe test Checkout → webhook → order
IN_PRODUCTION. - Production tracker (2.14) reading
GET /orders/{id}/events(SSE) — pipeline may be simulated at this phase. - Validation & delivery (2.15): tabs render from a sample delivery; accept →
POST /payments/release(test) →DELIVERED; revise leaves escrow untouched. - AI-assist (
POST /ai/parse) prefilling the configurator. - Accept: a full custom order can be configured, deposit paid (test), watched, validated, accepted, and "delivered" — state machine in
docs/01traversed; escrow split correct. - Status: Partial — the payment/escrow spine is genuinely real; the SSE tracker is not.
app/api/estimates/route.tsexists.- Deposit checkout is real, working code:
app/api/payments/[order_id]/deposit/route.tscalls Stripe's Checkout Sessions API directly (not a stub) andapp/api/webhooks/stripe/route.tsverifies the webhook signature and advances order status viasetStatus. app/api/payments/[order_id]/release/route.tsis also real: it enforces the escrow rule in code — balance release is rejected with409 not_acceptedunlessorder.status === "accepted"— matching the "escrow split correct" accept criterion's intent.GET /api/orders/{id}/events(SSE) does not exist. OnlyGET/PATCH /api/orders/{id}were found (app/api/orders/[id]/route.ts); no/eventssub-route, and noEventSource/text/event-stream/ReadableStreamusage anywhere inapp/(grep-verified). If a production tracker is live today, it's pollingGET /api/orders/{id}, not the SSE stream this bullet describes — or it hasn't been built. Either way, the SSE contract as written is not implemented.app/api/ai/parse/route.tsis real: a genuine (non-fabricated) keyword/regex heuristic that prefills a config object, with a documented fallback toengineFetch("/ai/parse", ...)whenENGINE_URLis set. This one holds up as described.
Phase 5 — Real data engine (Nordic slice)
- Compliant connectors first: GLEIF + Brreg + Companies House (verify access terms; record in
sources.yaml). Remove any FlareSolverr/bypass path (docs/06). - Pipeline stages produce real
dim_company+fact_financialswithbridge_provenance; QA threshold drops low-confidence rows. - Production tracker now reflects real job metrics.
- Accept: a Nordic company dataset is produced for a real order, every field has provenance, quality report generated, level-0 fields shown honestly (not fabricated).
- Status: Partial.
services/api/sources/sources.yamlandconnectors.pyare real: GLEIF, Brreg, Companies House, PRH, and CVR are all registered, each with an explicitlicence: "... — VERIFY"tag rather than an assumed-clean claim — matches the "verify access terms" instruction.services/api/workers/adaptive_router.py's own comment confirms: "There are NO bypass/stealth/ proxy-rotation tiers here, by design (CLAUDE.md §1)" — the FlareSolverr/bypass removal is done, at least on this repo'smain(seedocs/08-infrastructure.md's "Known open question" about the diverged Gitea remote, which independently added such a stack on its ownmain— unresolved which remote is canonical).services/api/workers/pipeline.pyhas a real, working confidence-threshold QA drop:delivered = [r for r in deduped if r["confidence"] >= threshold], plus anavg_confidencefigure in its output. This is a genuine, narrow QA mechanism inside the Python engine.- This is not the same thing as the "quality report" / quality-check layer once described in
docs/ICEBERG_WRITE_BOUNDARY.md,docs/11-lakehouse-write-plan.md, anddocs/P0_QUERY_SOURCE_FLOW.md. Those three docs used to citesrc/services/quality-check-service.ts,src/services/quality-report-service.ts,src/types/quality-{check,report}.ts, andsrc/data-platform/services/catalog-service.tsas already-built and tested. None of those files has ever existed in this repo's git history, on any branch — and all three docs have since been corrected to say so explicitly (verified this pass), rather than repeating the original claim. So: the narrow Python-side confidence filter is real; the broader TS/lakehouse "quality report" this Accept criterion likely also intends is not built (seedocs/P0_DATASET_QUALITY_FLOW.mdfor the honest target spec). Whetherpipeline.py'savg_confidenceoutput satisfies "quality report generated" is a judgment call for whoever runs this phase — flagging rather than deciding it here. - Whether an actual Nordic dataset has been produced end-to-end for a real order was not verified this pass (would require running the stack).
Phase 6 — Delivery surfaces & integrations
- Exports: CSV / Parquet / JSON via R2 presigned URLs.
- Embedded NocoDB ("Min data") and Metabase dashboards via signed URLs in mono chrome.
- Integrations: FiveCRM push, webhooks, Google Sheets.
- File uploads (Uppy → R2) for matching/enrichment.
- Accept: a delivered dataset is downloadable, viewable in embedded NocoDB + Metabase (no separate login), and pushable to FiveCRM; uploads work.
- Status: Not done — this phase is UI-shell-only today.
- CSV/Parquet/JSON exports via R2 presigned URLs: not found.
app/api/uploads/route.ts(the closest R2-touching code) is a501stub with a hardcodedput_url: null— no presign logic exists yet, for uploads or exports. components/embed.tsx(EmbeddedView) is real — a sandboxed iframe shell — but the server-side signed-URL generator for Metabase/NocoDB (METABASE_SECRET_KEY/NOCODB_API_TOKEN) that would feed it a realsrcwas not found anywhere in this pass.- "FiveCRM" appears only as a destination label string in
lib/data.ts,lib/services.ts,app/portal/integrationer/page.tsx(explicitly described in that file's own comment as "presentationella toggles" — i.e. presentational, not wired), and as one of several regex-matched destination names inapp/api/ai/parse/route.ts's heuristic. No actual FiveCRM API push exists. uppyis not a dependency inpackage.json(checked directly) — the Uppy → R2 upload flow this bullet describes has no corresponding package installed, let alone implemented.
- CSV/Parquet/JSON exports via R2 presigned URLs: not found.
Phase 7 — Subscriptions / feeds
subscriptions; recurring jobs (weekly/daily) delivering deltas; webhooks on change.- Freshness monitoring; Resend emails (confirmation, "din data är klar", feedback).
- Accept: an order converts to a feed; a scheduled run produces a delta + fires a webhook + email.
- Status: Partial — data layer real, scheduling and email are not.
lib/subscriptions-store.tsis real CRUD against thesubscriptionstable (with an in-memory fallback when no DB is configured) —createSubscription/listSubscriptionsgenuinely insert/read.- No recurring-job scheduler (cron, queue consumer producing deltas, etc.) was found on the TS side of
this repo.
services/api/workers/queue.pyexists on the Python side but this pass did not trace whether it implements actual delta-producing scheduled runs — flagged as unverified rather than assumed either way. - Resend is not wired at all — grepped for
resend/Resendacross the whole repo, zero matches outsideRESEND_API_KEYbeing listed as an env var. No confirmation/"din data är klar"/feedback email has been implemented.
Phase 8 — Admin, hardening, compliance
- Admin: order queue + scope/quote + escrow release; source registry & freshness monitoring; worker/job monitoring; customer + catalog management.
- Customer DB-access grant flow (encrypted, read-only, time-boxed) + revoke.
- Security pass: RLS, presigned-only files, signed embeds, secret rotation,
audit_logeverywhere; DPA published; legal pages. - Finalize all loading/empty/error states + mobile across the app.
- Accept: admin can run the business; DB-grant flow works and self-revokes; security checklist passes; DoD met app-wide.
- Status: Partial — UI shell real, the security substance is not.
- Admin pages exist:
app/admin/{page,jobs/page,sources/page,customers/page,verktyg/page}.tsx. This pass did not verify whether they read real backend data or placeholders. sql/001_init.sqldefinesaudit_log,db_grants, andpaymentstables — schema is ready.- The DB-access grant flow is a stub, not a working feature.
app/api/connections/db-grant/route.tsreturns501withoutGRANT_ENC_KEY, and with it set returns a hardcoded201 { grant_id, status: "pending" }— no AES-GCM encryption, no read-only validation, and no self-revoke logic exist in code today, despite the route's own comment describing that target behavior. Seedocs/08-infrastructure.md's "Security & compliance" section. - RLS enforcement, presigned-only files (blocked on the Phase 6 R2 gap above), signed embeds (blocked on the Phase 6 NocoDB/Metabase gap above), and a published DPA were not found as implemented in this pass. Treat the "security checklist passes" accept criterion as not met.
- Admin pages exist:
Phase 9 — Launch
- Domain
0pt.app(Vercel) + subdomains (api/data/dash via Caddy). Sentry + uptime live. - Soft launch: Protosell internally + a handful of existing dealer relationships.
- Accept: a real external customer completes an order start→delivery in production; feedback loop running.
- Status: Domain is live; soft-launch/customer criterion not verified.
- Verified this pass (network check performed):
https://0pt.appreturns HTTP 200 and serves this repo's own Next.js app (title "0pt — your data", SIAX branding);GET /api/healthreturns{"ok":true,"service":"0pt-web","db":"not_configured","queue":"not_configured",...}, an exact shape match forapp/api/health/route.ts. README's "Live: https://0pt.app" is confirmed, and the response confirms the live deployment has noDATABASE_URL/REDIS_URLset — seedocs/08-infrastructure.md's "Hosting topology" section. This does not confirm which git remote/commit is deployed (see the diverged-remotes note elsewhere in this doc), or that api/data/dash subdomains or Caddy are live — only the root Vercel deployment was checked. - Sentry is not wired — no
sentrypackage inpackage.json, no Sentry config file anywhere in the repo (grep-confirmed).@vercel/analyticsis genuinely wired (app/layout.tsxrenders<Analytics />), which is a real but much smaller piece of the "Sentry + uptime live" bullet. - No evidence either way on the soft-launch/external-customer criterion — out of this doc's ability to verify from code alone.
- Verified this pass (network check performed):
Working conventions (for Claude Code)
- Tech docs + identifiers English; UI copy Swedish.
- Conventional Commits; one concern per PR; keep diffs reviewable.
- No secrets in code; update
.env.examplewhen adding env. (.env.exampleitself currently lives onfeature/0pt-env-example/ PR #3 — merge that first, or this instruction has nothing to update yet.) - Add/extend tests for engine logic (estimate math, fusion/confidence, dedup) and API contract.
- When a source's access/licence is unclear, verify and record in
sources.yamlbefore relying; if disallowed for bulk, route to licensed/partner or mark level 0 — never bypass, never fabricate. - Touch the design tokens/motion only via
docs/05; keep red ≤ once per screen; no photography. - If anything here conflicts with existing code, flag it in the PR description and propose the reconciliation rather than guessing.
- Two git remotes claim to be this repo (
github.com/sax3l/0ptandgit.siax.io/sax3l/0pt), and they have independently diverged (seeCLAUDE.md,README.md). Don't assume the checkout you're in is the only copy, and don't silently pick one as canonical — that's Simon's decision, flag it if it becomes relevant to the phase you're working on.