This is the current implemented pattern in the repo. It is not the earlier 180-line rewrite sketch.
The live canonical example today is:
margin_monitor_citi_rates_history_vw- API route:
/v1/api/views/margin_monitor_citi_rates_history_vw
The current shape is:
Open Data Contract YAML
-> generated SQL view artifact
-> generated JTD / JS validator / JSDoc / Python serde artifacts
-> handwritten thin FastAPI route
-> handwritten screen fetch assembly
-> generated validator used at screen/runtime boundary
So the system is contract-generated at the artifact layer but still uses a handwritten thin adapter at the backend and usually a handwritten screen fetcher at the screen edge.
The best current example is the margin monitor Citi history path.
contracts/tripwire/margin_monitor_citi_rates_history_vw.yamltripwire/data/99_margin_monitor_citi_rates_history_vw.sql.j2
tripwire/bin/generate_view_contracts.pytripwire/Makefile
tripwire/target/views/99_margin_monitor_citi_rates_history_vw.sqltripwire/target/_generated/view_contracts/margin_monitor_citi_rates_history_vw.jtd.jsontripwire/target/_generated/view_contracts/margin_monitor_citi_rates_history_vw.validator.mjstripwire/target/_generated/view_contracts/margin_monitor_citi_rates_history_vw.fetcher.mjstripwire/target/_generated/view_contracts/margin_monitor_citi_rates_history_vw.jsdoc.jstripwire/tripwire/_generated_view_contracts/margin_monitor_citi_rates_history_vw.pg_serde.py
tripwire/tripwire/api/sql/citi_rates_history.sqltripwire/tripwire/api/server.py
tripwire/src/screens/margin_monitor.runtime.jstripwire/src/screens/margin_monitor.html.js
margin_monitor.runtime.js sets:
/v1/api/views/margin_monitor_citi_rates_history_vw
and passes that into an assembled fetcher.
The actual screen does not directly use the generated .fetcher.mjs.
Instead it uses a handwritten functional fetcher assembly that:
- builds the request path
- fetches JSON or NDJSON
- validates rows with the generated validator
- maps the flat row shape into screen-specific nested state
tripwire/tripwire/api/server.py exposes a thin route that:
- selects a fixed SQL file
- substitutes a fixed view name
- streams rows back
The route is handwritten and intentionally boring.
The query SQL is a small projection/order file under:
tripwire/tripwire/api/sql/
The actual DB view definition comes from the generated view SQL under:
tripwire/data/99_*.sql.j2
The details are not ad hoc; the normal Tripwire Makefile path builds them.
tripwire/Makefile runs:
uv run python bin/generate_view_contracts.py
That produces:
- SQL view artifacts in
tripwire/target/views/ - JTD / validator / fetcher / JSDoc artifacts in
tripwire/target/_generated/view_contracts/ - Python pg-serde artifacts in
tripwire/tripwire/_generated_view_contracts/
The same Makefile copies generated web artifacts into the web-visible tree:
target/_generated/view_contracts/->target/www/_generated/view_contracts/
That is why screen code can load generated validators from paths like:
/v1/_generated/view_contracts/margin_monitor_citi_rates_history_vw.validator.mjs
At repo-root make, Tripwire generation runs inside the normal build cycle, so
these artifacts are part of the regular packaged output rather than a special lane.
The ODC YAML is the schema source for generated view artifacts.
generate_view_contracts.py emits:
*.jtd.json
These are part of the normal generation path, not a side experiment.
generate_view_contracts.py emits:
*.validator.mjs
The live margin monitor screen imports these validators lazily and validates fetched rows before mapping them into runtime state.
generate_view_contracts.py emits:
*.jsdoc.jscontract_row_source.jsdoc.js
These are the JSDoc boundary types used for strict checkJs / TSC-adjacent build-time discipline.
generate_view_contracts.py emits:
*.pg_serde.py
The backend can use the generated row-to-JSONable mapping via VIEW_PG_SERDE when a route passes view_name=... into the shared streaming response path.
The current repo has explicit proof for generation.
tripwire/tests/test_view_contract_generation.py
That test proves the generator emits, for the canonical view and peers:
- SQL migration/view artifact
- JTD schema
- JS validator module
- JSDoc module
- Python pg-serde module
- generated index exports
tripwire/Makefile also has a dedicated boundary lane:
make jsdoc-boundary-check
and the normal make test path includes that lane.
The live screen code references the generated validator path directly, so if the generation/copy-up path breaks, the screen boundary breaks honestly.
- Open Data Contracts exist and drive generated artifacts.
- JTD generation is part of the normal Makefile flow.
- JSDoc generation is part of the same flow.
- Python pg-serde generation exists for view-backed rows.
- Thin backend routes are still handwritten.
- Screen fetch assembly is still usually handwritten.
- Generated validators are available for runtime/build-time use.
- The live Citi path is the best “real current pattern” example.
- The backend is not a 180-line rewrite.
- The server is not fully generated from contracts.
- The screen is not wired directly to generated fetchers in the main margin monitor path.
- The handwritten adapter layer has not been deleted.
Postgres relation
-> generated view SQL
-> migrate installs relation
-> FastAPI thin adapter exposes route
-> screen fetch assembly calls route
-> generated JS validator checks row shape
-> screen maps row into local runtime model
If you are adding a new simple view-backed endpoint today, follow this pattern:
- add a contract YAML under
contracts/tripwire/ - add a
tripwire/data/99_*.sql.j2view template - let the normal generation flow emit SQL/JTD/validator/JSDoc/pg-serde artifacts
- add a thin SQL file in
tripwire/tripwire/api/sql/ - add a thin handwritten route in
tripwire/tripwire/api/server.py - only add screen wiring if the screen actually needs it
The source of truth is the code and generated artifacts in the repo. This note is a map, not a substitute for reading the files.