Skip to content

Report: lock in the stored report schema - #1271

Merged
PavelMakarchuk merged 5 commits into
mainfrom
feat/v3-report-schema
Oct 14, 2026
Merged

PavelMakarchuk merged 5 commits into
mainfrom
feat/v3-report-schema

Conversation

@PavelMakarchuk

Copy link
Copy Markdown
Contributor

Summary

v3 report records and their provenance now have one schema with a version. Every read goes through a parser that upgrades older shapes, and every write stamps the version. A report whose stored details can't be read shows an error on the report page, not a crash or an endless spinner.

What changed

  • libs/flagship/reportSchema.ts (new): REPORT_SCHEMA_VERSION = 2 (unversioned data is 1). It has field rules for FlagshipReportMeta and FlagshipReportRecord; the rule maps do not compile until every field has a rule. parseReportMeta, parseReportRecord and parseReportRow upgrade any older shape and fill safe defaults, or return a typed ReportSchemaError (not-an-object, invalid-field with the field path, or newer-version). stampReportMeta and stampReportRecord add the version on write. A damaged validation pin is dropped, and the report pins again. The record does not fail.
  • Custom baseline (Compare a v3 report with a saved reform #1265): the schema reads the optional meta.baseline {reformId, policyId, label} through its own ReportMetaBaseline type. This branch merges with feat/v3-custom-baseline with no conflicts in either order. The merged tree typechecks, and the flagship tests pass, including Compare a v3 report with a saved reform #1265's baseline tests.
  • Reads and writes: readReportMeta and saveReportMeta (runReport.ts) and the localStorage report store use the parser and stamp on write. Lists leave out unreadable entries. Writes keep entries that this version can't read, so a record saved by a newer version survives. The central store parses each row it gets back.
  • Report page: if the stored provenance can't be read, the page shows "This report's saved details can't be read". It offers Reload (when a newer version saved the details), Open without saved details (the provenance is then rebuilt from the policy) and Back to Build, plus the technical reason.
  • calculator-app routes: POST /api/reports checks each provision, and PATCH /api/reports/:id checks the validation pin, with the same rules that the client uses to read them back. The 400 response names the bad field. guardSchema and the body and text limits do not change.
  • Tests: fixtures of every older stored shape, taken from git history (3673cf9, ebb805b, aaf850a, 6c23805, b2a0963, f8294f9, ec39955, and e0cee74 from Compare a v3 report with a saved reform #1265). The tests cover upgrades, round trips, malformed input, store behaviour, the hook, the error component, the page, and the route integration tests.

Merging with #1252 (sign-in)

#1252 replaces the central report store with /v2/user-reports and deletes the calculator-app report routes. A trial merge gives these conflicts:

v2 impact

None. All changes are in flagship-only files: components/flagship, pages/flagship, libs/flagship, api/flagshipReportStore.ts, a new flagship hook, and the v3 /api/reports routes. No shared file is touched.

Tests run

  • bun run typecheck: pass
  • calculator-app: tsc --noEmit passes
  • bunx eslint on each changed file: pass. The calculator-app routes are outside the app's ESLint base path.
  • bunx prettier --check on each changed file: pass
  • bunx vitest run on the related suites: pass
  • bun run vitest (full suite): 392 files passed, 1 skipped; 4444 tests passed, 10 skipped

🤖 Generated with Claude Code

PavelMakarchuk and others added 4 commits October 14, 2026 14:59
reportSchema.ts holds the stored shape of a v3 report's provenance and
record, a schemaVersion (2; unversioned is 1), and parsers that upgrade
any older shape to the current one with safe defaults, or answer a typed
ReportSchemaError. It also reads meta.baseline from the custom-baseline
work, so either can merge first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every read of a report record or its provenance goes through the
parser, and every write stamps the current schemaVersion. A list leaves
out a record it can't read; entries this version can't read stay as
stored, so a newer version's records survive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A report whose stored provenance the schema can't read shows an error
in place of the report: reload when a newer version saved it, open the
report without it, or go back to Build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
POST checks each provision, and PATCH the validation pin, with the
rules the client reads them back with, and names the bad field in the
400. Body limits and the schema guard are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
policyengine-calculator-next Ready Ready Preview Oct 14, 2026 7:29pm UTC
policyengine-website Ready Ready Preview Oct 14, 2026 7:29pm UTC

Request Review

- A provision's growth, growthStart, growthBase, growthRoundTo and
  growthRound read back, so "Edit in Build" keeps how a value grows.
- Opening a report without damaged saved details removes them, so the
  next visit opens without asking; details a newer version saved stay.
- The page reads the saved details once per report for the error.
- ReportRecordError is now ReportDetailsError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

2 active deployments
Preview – policyengine-website — f1d48b41 Deployed Oct 14, 2026 by vercel[bot]
Preview – policyengine-calculator-next — f1d48b41 Deployed Oct 14, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant