Application Documentation Portal
Status: v1, v2 & v3 shipped (2026-06-15). The design brief below records the agreed architecture. The v1 (spec layer) slice is implemented and merged (PR #60–#67). The v2 (report overlay) slice is implemented and merged across five sub-slices (report model +
report/history.jsonsidecar, DocService overlay + badges, coverage dashboard + search filters, per-line SQL coverage highlighting, and run-trend sparklines); its plan is the Implementation plan — v2 (report overlay) section at the end of this document. The v3 (schema introspection) slice is implemented and merged across two sub-slices (catalog introspection +schema.jsonsidecar, PR #75; schema overlay + table reference pages + search, PR #76); its plan is the Implementation plan — v3 (schema introspection) section. The Table definitions content area it implements is described above; the route↔table dependency graph and a DB-free DDL parser remain deferred to v3.1.
Motivation
Section titled “Motivation”A TesseraQL application is defined entirely by declarative source: Simple YAML routes/jobs, 2-way SQL, Thymeleaf pages, declarative test suites, and Flyway migrations. Because that source is the specification, documentation derived from it is authoritative and cannot drift from the implementation — unlike hand-written reference docs.
The goal is a single, browsable per-application documentation portal, served in-product by Studio, that lets an operator or reviewer see, with cross-references between them:
- Page / route specs — every HTTP route and HTML page: method, path, recipe, declared inputs, security (auth + policy + CSRF), validation, notifications, response shape, and the SQL it binds.
- Test specs (YAML/SQL) — the declarative test suites that exercise each route/page, linked to the route they cover.
- Test results — pass/fail and coverage for the last run, overlaid onto the same routes.
- Table definitions — the schema the application reads and writes.
Because deny-by-default security and ABAC policies are declared per endpoint, the portal doubles as a security/audit artifact: for each endpoint it surfaces auth type, required policy, the input whitelist, and which SQL/statements run.
Key architectural decision: the seam is Markdown + JSON
Section titled “Key architectural decision: the seam is Markdown + JSON”The generator does not depend on any presentation framework. It emits two tool-agnostic
streams from the AppManifest:
- Deterministic spec Markdown + structured JSON — byte-stable, diffable, produced in the
Maven build like the existing
openapi.json/htmx-contract.jsonartifacts (reproducibility, design ch. 48). - These are then consumed by Studio for rendering and search.
Keeping the contract at Markdown + JSON means the presentation layer is swappable and the reproducible-artifact pipeline never takes on a foreign toolchain.
Two layers — spec (deterministic) vs report (run-dependent)
Section titled “Two layers — spec (deterministic) vs report (run-dependent)”The four content areas split by nature, and the codebase already embodies this split:
| Layer | Nature | Produced by (today) |
|---|---|---|
| spec | deterministic, from YAML/SQL/manifest; byte-stable, lint-checkable | GenerateMojo → openapi.json, htmx-contract.json |
| report | run-dependent (test execution, live DB state); not byte-stable | AppTestRunner → tesseraql-result.json, coverage/sql-coverage.json |
Page specs and test specs are spec layer. Test results, coverage, and introspected schema are report layer. The portal renders both but keeps them separate so the report overlay never compromises the spec layer’s reproducibility guarantee.
Presentation decision: Studio-only (single surface)
Section titled “Presentation decision: Studio-only (single surface)”The portal is rendered only in Studio — server-side, JVM-only, shipped with the runtime, using Thymeleaf + Hypermedia Components. No Astro/Starlight/MkDocs or other static-site generator is adopted, and no Node toolchain enters the build.
Crucially, Studio is itself a bundled TesseraQL application running on the runtime — not a
bolted-on servlet. Its source lives at tesseraql/apps/studio/ (route YAML + Thymeleaf pages),
is mounted via the AppSourceProvider SPI (StudioAppProvider), and its pages are ordinary
query-html routes whose data comes from a service: binding (a named Java provider invoked
instead of SQL) rather than from a database. For example
get.yml
binds sql.service: studio.explorer and renders
explorer.html,
a Thymeleaf template composing tql/shell with hc markup. The documentation portal is therefore
just another surface of this bundled app, authored the exact same way (see Studio rendering
and search below) — which is what makes its format Thymeleaf-customizable.
Rationale:
- Keeps the build Maven-complete and the spec artifact byte-reproducible (no npm dependency tree, no Pagefind/WASM index step).
- Stays SQL-first and hypermedia-native (AGENTS rule 11): one codebase, server-rendered, served by the runtime that already serves the app.
- The report layer (live test results, coverage, introspected schema) is inherently dynamic and authenticated — a static SSG cannot reach it; Studio can.
Accepted trade-offs: the in-product view targets authenticated operators, so there is no anonymous/SEO public docs reach. If a public, multi-version, SEO documentation site becomes an adoption need (Horizon 6), it is a separable later decision — the Markdown + JSON seam keeps a static export or an external SSG a cheap add-on. The portal deliberately does not try to become a multi-version / i18n / theming / offline documentation platform; that boundary is where an SSG would earn its keep, and where Studio must not reinvent one.
Content mapping — what exists vs what is new
Section titled “Content mapping — what exists vs what is new”Most raw material already exists as deterministic artifacts; the portal is largely an aggregation + presentation layer plus one genuinely new capability (schema modelling).
| Content | Layer | Status today | Work needed |
|---|---|---|---|
| Page / route specs | spec | RouteFile.definition + HtmxContractGenerator |
render per-page/route from the manifest |
| Test specs (YAML/SQL) | spec | TestSuite model + TestSuiteLoader |
present declared cases under the route they exercise |
| Test results + coverage | report | TestReport → tesseraql-result.json; SqlCoverage → coverage/sql-coverage.json |
ingest existing artifacts; do not re-run |
| Table definitions | spec + report | migration listing (v1) + catalog introspection → schema.json (v3) |
shipped; SQL→table dependency graph deferred to v3.1 |
References:
- Manifest model — AppManifest.java, RouteFile.java, ManifestLoader.java
- Existing generators — OpenApiGenerator.java, HtmxContractGenerator.java, wired by GenerateMojo.java
- 2-way SQL parse — Sql2WayParser.java
- Tests — TestSuite.java,
AppTestRunnerin tesseraql-report, coverage in tesseraql-coverage-core - Studio rendering — StudioViews.java
Cross-reference index
Section titled “Cross-reference index”The portal’s value is the page → SQL → test → result → table linkage. That test→route→SQL
linkage is already computed by ManifestCoverage
(ManifestCoverage.java)
— by SQL file path, identity contract, validate.route, and notify.route/notify.job. Today it
is computed on demand inside coverage. The portal should lift this linkage into a reusable
index shared by both coverage and docs, rather than recompute it. (Module placement is an open
question — see below.)
Table definitions
Section titled “Table definitions”Migrations are raw Flyway SQL (db/migration/V*__*.sql, with db/<datasource>/migration and
db/migration-<vendor> overlays), applied by AppMigrator / AppMigrations, and are not
modelled in AppManifest. There is no DDL parser. The only schema model is the scaffolding-only
TableSchema / TableIntrospector
(TableIntrospector.java),
which reads a live DB via JDBC DatabaseMetaData.
Decision:
- spec layer (cheap, deterministic, DB-free): add the list of migration files (per datasource,
with vendor overlays) to
AppManifestso the portal can always list migrations. - report layer (rich, run-dependent): extend
TableIntrospectorfrom single-table to catalog-wide and produce a schema model from the migrated test/CI database. This reuses existing code and is reproducible-from-DB-state; the test pipeline already stands a DB up (Testcontainers).
Deferred: a deterministic DDL parser (option B) for a DB-free rich schema view, and the SQL→table dependency graph (which route touches which table) — the latter needs table extraction from 2-way SQL and is the hardest semantic piece.
Studio rendering and search
Section titled “Studio rendering and search”- Markdown rendering: server-side via a standard library (
commonmark-javaorflexmark, Apache-2.0) → Hypermedia Components markup. This honours “prefer standard libraries” (no hand-rolled parser) and AGENTS rule 11 (hc components/behaviors for presentation; any gap is an upstream brief, not custom CSS). Studio renders both the generated spec Markdown and the hand-writtendocs/*.md. The Markdown dependency lives intesseraql-studio(the renderer), not intesseraql-coreor the generator — the generator emits Markdown text and needs no Markdown dependency. - Search (v1 = option C): a small in-memory inverted index built at mount time over the
generated spec content and Markdown docs. For an app-scoped corpus (tens–hundreds of
routes/pages) this is sufficient and adds no dependency. UX is htmx live-search (input →
server fragment of hc result list). Heavier engines (Lucene in a leaf module) or SQL-first RDB
full-text search (
tsvector, via a 2-way SQL route) are possible later if the corpus or cross-app search outgrows the in-memory index; not needed for v1.
How the portal is authored (and what is customizable)
Section titled “How the portal is authored (and what is customizable)”Because Studio is a bundled TesseraQL app (see Presentation decision above), the portal is built from the same primitives, not from bespoke Java views:
- Routes:
recipe: query-htmlpages withsql.service: docs.spec(and siblings for the report and schema views). A newdocs.*service provider — registered like the existingstudio.*providers — supplies the aggregated model: the cross-reference index, the spec fields, the ingested results/coverage, the schema, and the pre-rendered Markdown bodies. - Templates: Thymeleaf composing
tql/shell+ hc, exactly like explorer.html.StudioViews-style model maps (or records) feed${...}expressions.
The customization boundary follows from this split:
- Customizable (Thymeleaf + hc): layout, structure, which items appear and how they are ordered, navigation, branding, theming.
- Generated (not customizable): the derived facts themselves (routes, inputs, security, SQL, results, tables). Templates place them; they don’t author them.
- Markdown bodies: rendered server-side (
commonmark-java/flexmark) to sanitized HTML and placed withth:utext. They must satisfy Studio’s strict CSP (see explorer.html:default-src 'self', no inline script), so code highlighting is server-side classes + hc styles, never inline JS (AGENTS rule 11).
Who customizes — decision (option A): the portal ships as part of the framework-bundled app,
and app authors customize through blessed seams, not by editing bundled files (AGENTS rule 1):
the tql/shell layout, the app’s own templates/nav.html fragment, hc design tokens, and a small
set of documented overridable Thymeleaf fragments. A scaffold-into-app mode (option B) — emitting
editable doc routes/templates the author owns outright — is deferred; it grants maximum freedom
but fragments presentation and adds drift/maintenance, so it is added only if an app genuinely needs
a bespoke doc layout.
Proposed slices
Section titled “Proposed slices”- v1 — spec layer, DB-free, pure. Per-route/page reference from the manifest (inputs,
security, validation, notifications, response, bound SQL statement with declared binds and
/*%if*/structure), plus the declared test cases under each route via the lifted cross-reference index. Add migration listing toAppManifest. Emit deterministic spec Markdown + JSON viaGenerateMojo(byte-stable, ch. 48). Surface it asquery-htmlroutes in the bundled studio app, fed by a newdocs.*service provider and rendered bytql/shell+ Thymeleaf + hc, with the in-memory search index. - v2 — report overlay. Ingest
tesseraql-result.json+ coverage JSON; show per-route pass/fail and coverage badges. Kept separate from the spec layer. - v3 — schema. Catalog-wide introspection (extend
TableIntrospector) → table reference pages. Later: SQL→table linkage; optionally a deterministic DDL parser.
Non-goals
Section titled “Non-goals”- No static-site generator and no Node toolchain in the build.
- No multi-version / i18n / theme-switching / offline documentation platform in Studio
(per-app branding via the
tql/shelllayout, the app’snav.html, and hc tokens is in scope; building a documentation-site engine around it is not). - No scaffold-into-app doc mode (option B) in the first cut — bundled portal customized via blessed seams (option A) only.
- No DDL parser in v1 (introspection instead).
- No SQL→table dependency graph in v1.
Open questions
Section titled “Open questions”- Resolved in the v1 plan: the shared cross-reference index sits in
tesseraql-test-coreasCrossReferenceIndex(besideManifestCoverage, which delegates to it); the generator splits intoRouteSpecGenerator(tesseraql-yaml) andAppDocGenerator(tesseraql-report). - Open: exact packaging path by which
AppPackagerexposesspec.jsonto the runtime, and whether to keep the artifact-read boundary or accept atesseraql-studio→tesseraql-reportdependency for a single always-live path (see the v1 plan’s boundary decision). - Roadmap fit: this is adjacent to the Horizon 6 docs-site / adoption work, but the v1 spec layer is cheap and independent of it.
Implementation plan — v1 (spec layer, DB-free)
Section titled “Implementation plan — v1 (spec layer, DB-free)”Grounded in the current wiring: service providers are registered on
io.tesseraql.core.service.ServiceProviders via .register(name, params -> result)
(TesseraqlRuntime.java:506,667);
the bundled studio app is contributed through the AppSourceProvider SPI
(StudioAppProvider.java);
derived build artifacts are emitted by
GenerateMojo.java
mirroring OpenApiGenerator. Module facts that shape the design: tesseraql-report depends on
tesseraql-yaml + tesseraql-test-core + tesseraql-coverage-core; the maven plugin already
depends on tesseraql-report; tesseraql-studio depends only on tesseraql-core +
tesseraql-yaml and must stay light.
Generation happens at build; Studio reads the artifact (the key boundary decision)
Section titled “Generation happens at build; Studio reads the artifact (the key boundary decision)”The portal’s value needs test→route linkage, which lives above tesseraql-yaml in
tesseraql-test-core/tesseraql-report. Pulling those into tesseraql-studio would expand its
dependency surface (test runner, coverage, identity) for a view that mostly reads. So v1 splits
generation from rendering:
- Build time (heavy, in
tesseraql-report):AppDocGeneratoraggregates manifest + test specs + cross-reference index into a deterministic, byte-stablespec.jsonartifact. - Runtime (light, in
tesseraql-studio):DocServicereadsspec.json, renders Thymeleaf, and builds the search index — no new heavy dependency. - Live fallback: an unpackaged source/dev run has no
spec.json;DocServicethen renders a reduced live view (routes/pages/SQL only) from a yaml-sideRouteSpecGenerator, which needs only the manifest Studio already holds.
This keeps tesseraql-studio light and treats the rich spec as one more deterministic build
artifact (ch. 48). Alternative considered: make DocService depend on tesseraql-report and
regenerate live (one code path, always live including edit mode, but a heavier studio/runtime
dependency). Recommended path is artifact-read; the alternative is the documented fallback if the
packaging/path step proves more trouble than the extra dependency.
Module & class layout
Section titled “Module & class layout”| Module | New / changed | Responsibility |
|---|---|---|
tesseraql-test-core |
new CrossReferenceIndex; refactor ManifestCoverage to delegate |
reusable test→route→SQL linkage (today computed on demand inside ManifestCoverage) |
tesseraql-yaml |
new RouteSpecGenerator; add List<MigrationFile> migrations to AppManifest + loader scan + index |
manifest → route/page/SQL spec model (+ markdown); migration listing (spec-layer table part) |
tesseraql-report |
new AppDocGenerator (wraps RouteSpecGenerator, adds test cross-ref) → DocModel + toJson |
the full deterministic spec.json (+ markdown bundle) |
tesseraql-maven-plugin |
extend GenerateMojo |
write tesseraql-generated/docs/spec.json (+ markdown); ensure AppPackager includes it |
tesseraql-studio |
new DocService + DocViews; new bundled pages under tesseraql/apps/studio/web/_tesseraql/studio/ui/docs/...; add commonmark-java dep |
read spec.json (live fallback via RouteSpecGenerator), render, in-memory search |
tesseraql-camel-runtime |
register docs.* providers in TesseraqlRuntime (beside studio.*) |
wire the bundled doc pages to DocService |
Steps (each a small, reviewable commit — AGENTS rule 7)
Section titled “Steps (each a small, reviewable commit — AGENTS rule 7)”CrossReferenceIndexin test-core. Extract the linkageManifestCoveragecomputes (SQL file path, identity contract,validate.route,notify.route/notify.job) into a reusable class; makeManifestCoveragedelegate. No behaviour change — covered by the existingManifestCoverageTest. Add a focused unit test for the index itself.- Migration listing in the manifest. Add
List<MigrationFile> migrationstoAppManifest(only 4new AppManifest(...)call sites today), scandb/migration,db/<datasource>/migration, anddb/migration-<vendor>inManifestLoader, and include them inManifestIndexfor reproducibility.MigrationFilecarries datasource, vendor (if overlay), version, description, path — no DDL parsing. RouteSpecGeneratorin yaml. Manifest → orderedRouteSpecModel(per route: method, path, recipe, inputs with constraints, security auth/policy/CSRF, validation, notifications, response shape, bound SQL with the statement text, declared binds, and/*%if*///*%for*/structure viaSql2WayParser), plus the migration listing. Deterministic likeOpenApiGenerator(ordered maps,REPORTerror domain). Unit test asserts a stable model for thescaffold-demo-app.AppDocGeneratorin report. WrapRouteSpecGenerator; attach to each route its declared test cases (loaded statically viaTestSuiteLoader, linked viaCrossReferenceIndex); emitDocModel+toJson(manifest). Deterministic; unit test againstuser-admin-app.GenerateMojowiring. Writetesseraql-generated/docs/spec.json(and the markdown bundle); confirmAppPackagerpackages it so the runtime can resolve it from the app home. Byte-stable assertion test (re-run → identical bytes).DocService+ rendering in studio. Loadspec.jsonfrom the packaged app home (fallback toRouteSpecGeneratorlive when absent); addcommonmark-javato render hand-writtendocs/*.mdand description prose to sanitized HTML (CSP-safe — no inline JS); produceDocViewsmodel maps likeStudioViews.- Bundled pages + providers. Add
query-htmlroutes andtql/shell+hc Thymeleaf templates under.../ui/docs/(index, per-route spec, per-table listing, search-results fragment); registerdocs.index,docs.route,docs.searchonServiceProvidersinTesseraqlRuntimebeside thestudio.*block. - Search (option C). Build a small in-memory inverted index in
DocServiceover route specs, test specs, and rendered doc text at load time; thedocs.searchprovider tokenises the query and returns ranked hits; the page wires htmx live-search (input → hc result fragment). No new dependency. - Verify.
mvn spotless:applythenmvn -B -ntp verify(and-pl tesseraql-studio -am test); confirmBUILD SUCCESS(not just the piped tail).
Out of scope for v1 (recap)
Section titled “Out of scope for v1 (recap)”Test results and coverage overlay (v2), catalog-wide schema introspection and rich table pages (v3), SQL→table dependency graph, DDL parser, scaffold-into-app customization (option B), and any SSG/Node tooling.
Verification & conventions
Section titled “Verification & conventions”- Tests accompany each behavioural commit (rule 7); DB-backed paths are not needed in v1.
- Run
spotlessbefore the finalverify(codebase convention). - Land through a PR with CI green (rule 9); never push to
main.
Implementation plan — v2 (report overlay)
Section titled “Implementation plan — v2 (report overlay)”v1 ships the spec layer: a byte-stable spec.json (DB-free) that links each route to the
declarative test cases that cover it (TestRef{name, kind, target}), but carries no run
results — no pass/fail, no coverage numbers, no gate verdict. v2 adds the report layer: it
overlays the last test run’s results and coverage onto those same routes, without compromising the
spec layer’s reproducibility guarantee (design ch. 48, Two layers above).
The raw material already exists. AppTestRunner
runs the declarative suites against a database (wired by the test/coverage Maven goals in the
integration-test phase) and produces tesseraql-result.json (pass/fail per case) and
coverage/sql-coverage.json (SQL line/branch + 21 item-coverage kinds). v2 ingests this — it
never re-runs tests.
Design principles (the load-bearing decisions)
Section titled “Design principles (the load-bearing decisions)”- The overlay is a separate, optional, sidecar artifact — never inside
.tqlapp.spec.jsonis byte-stable and packaged into the reproducible.tqlappbyPackageAppMojo. Test results are run-dependent and non-deterministic; folding them into the archive would break artifact reproducibility (a flagged-sensitive concern). v2 writes a newreport.jsonsidecar into the app home’s.tesseraql/docs/directory, alongside (not inside) the packaged spec. Thegenerate/package-appgoals andAppPackagerare unchanged. - Phase ordering forces the sidecar. The default Maven lifecycle runs
package(which seals the.tqlapp) beforeintegration-test(whereAppTestRunnerruns). The report therefore cannot be packaged into the already-sealed archive even if we wanted to — sidecar is the only coherent placement, and it is also the correct one. - Graceful degradation. When
report.jsonis absent (an app that has never run its tests, or a pure source/dev run), the portal behaves exactly as v1 and renders “no test run recorded yet” empty states — mirroring the v1 live-fallback for a missingspec.json. - Join once, at generation time, by id/name. The spec already lists each route’s covering test
names; the run produces results by name; route coverage is the
kinds.route.coveredset of route ids. The generator (which holds the manifest, theRunResult, and theCrossReferenceIndex) performs the route→test→result and route→SQL joins once, emitting a route-keyedreport.json. The runtime then overlaysreport.routes[id]ontospec.routes[id]with no key-normalization guesswork. - The existing
sql-coverage.jsonis lossy; v2 needs a richer artifact.AppTestRunner’s hand-builtwriteCoverageserializes only counts (lineCount(),coverableLineCount()), not theSet<Integer>line sets inSqlCoverageReport. Line-level highlighting (the rich target) needs those line numbers, so v2 introduces a typed, Jackson-serializedReportGeneratorthat preserves them — superseding the ad-hoc string builder.
The report artifact — report.json
Section titled “The report artifact — report.json”A new typed model in tesseraql-report, Jackson-serialized (no hand-built JSON), route-keyed:
ReportDoc( schemaVersion: int, runId: String, // overridable via tesseraql.runId (e.g. CI build id); default = generatedAt generatedAt: String, // ISO-8601, stamped by the mojo summary: Summary(total, passed, failed, sqlLineRatio, sqlBranchRatio, gatePassed), thresholds: Thresholds(sqlLine, sqlBranch, kinds: Map<String,Double>), // from CoverageThresholds gate: Gate(passed, failures: List<String>), // from CoverageGate kinds: List<KindCoverage(kind, ratio, covered, declared, uncovered: List<String>)>, // 21 kinds routes: Map<String, RouteReport> // keyed by RouteSpec.id)RouteReport( covered: boolean, // route id ∈ kinds.route.covered tests: List<CaseResult(name, passed, message)>, // spec cross-ref names ∩ run results, by name sql: List<SqlCoverage(file, lineRatio, branchRatio, branchCount, branchOutcomes, coveredLines: List<Integer>, coverableLines: List<Integer>)>, itemCoverage: Map<String, Double> // per-kind ratio relevant to the route)Sources: summary/tests from TestReport/TestResult;
kinds/itemCoverage from ItemCoverage;
sql from SqlCoverageReport (now keeping its line sets); thresholds/gate from
CoverageThresholds
CoverageGate.
History. Each run also appends a compact HistoryEntry(runId, generatedAt, total, passed, failed, sqlLineRatio, sqlBranchRatio, gatePassed) to .tesseraql/docs/history.json, kept as a
ring of the last 20 runs so the app home never grows unbounded. report.json always holds only
the latest run; history.json feeds the trend sparklines.
Module & class layout
Section titled “Module & class layout”| Module | New / changed | Responsibility |
|---|---|---|
tesseraql-report |
new ReportGenerator → ReportDoc + toJson; reuse CrossReferenceIndex for the route→test join |
turn a RunResult (+ manifest + thresholds/gate + runId) into the typed report.json; append the history.json ring |
tesseraql-maven-plugin |
new ReportMojo (goal report, integration-test), or extend TestMojo/CoverageMojo |
run the suites, then write appHome/.tesseraql/docs/report.json + history.json; not added to the .tqlapp |
tesseraql-studio |
extend DocService (REPORT_PATH, optional read + overlay) + DocViews; new bundled coverage page; CSS-only additions in tesseraql.css |
read report.json if present, overlay onto RouteEntry, render badges / dashboard / SQL line highlighting / trend |
tesseraql-camel-runtime |
register docs.coverage provider beside docs.index/docs.route/docs.search in TesseraqlRuntime |
wire the dashboard page |
Boundary note: report.json is read by DocService exactly as spec.json is today — an
artifact-read path. No new heavy dependency enters tesseraql-studio; the typed ReportDoc
shape is mirrored as a small studio-side record (as DocSpec mirrors the build model).
Slices (each a standalone PR, CI-green, merged in order)
Section titled “Slices (each a standalone PR, CI-green, merged in order)”- Report model + generator + mojo (no UI).
ReportDocrecords +ReportGenerator(typed, deterministic JSON, name-join, preserves SQL line sets) intesseraql-report;ReportMojowritingreport.json+history.jsonintoappHome/.tesseraql/docs/, explicitly excluded from the.tqlapp. Unit tests for the generator; a Testcontainers IT (extendingAppTestRunnerIntegrationTest, which already stands up Postgres) asserts the artifacts and schema. No portal change yet. - DocService overlay + badges.
DocServicereadsreport.jsonwhen present and overlays it ontoRouteEntry;DocViewsexposes the merged model. Add pass/fail and coverage badges to the index and per-route pages, plus an index summary strip. Graceful empty states when the overlay is absent. Studio unit tests for the merge + degradation. - Coverage dashboard + search filters. New
query-htmlroute.../ui/docs/coveragewith adocs.coverageprovider and atql/shell+ hc template: per-kind ratio bars, gate verdict, aggregate SQL line/branch, uncovered and failing-case lists, and a nav entry. Extend the in-memory search to filterstatus:failing/coverage:untested. - SQL line-level highlighting. Render each route’s SQL source (from the spec’s statement text)
line-by-line, coloured by membership in
coveredLines/coverableLines. CSP-safe (server-side classes + hc styles, no inline JS — AGENTS rule 11). - History / trend. Read
history.jsonand render pass-rate and SQL line/branch sparklines on the dashboard; document the 20-run retention.
Resolved open items
Section titled “Resolved open items”runId: the mojo stampsgeneratedAt(ISO-8601) and defaultsrunIdto it; CI may override via thetesseraql.runIdparameter (e.g. the build number) for stable trend identity.- History retention:
history.jsonis a ring of the last 20 runs. - Provider namespace: the dashboard stays in the
docs.*family (docs.coverage), consistent with v1’sdocs.index/docs.route/docs.search.
Verification & conventions
Section titled “Verification & conventions”- DB-backed report generation is covered by Testcontainers ITs (the existing
AppTestRunnerIntegrationTestpattern), not H2 in a leaf module. - Studio overlay/merge and degradation are covered by plain unit tests (no DB).
report.jsonis a generated artifact — never hand-edited (rule 1).- Build-file / mojo / reproducibility changes are made deliberately (not auto-accepted).
- Run
spotless:applybefore the finalmvn -B -ntp verify; confirmBUILD SUCCESS. - Land each slice through a PR with CI green (rule 9); never push to
main.
Implementation plan — v3 (schema introspection)
Section titled “Implementation plan — v3 (schema introspection)”v1 ships the spec layer and v2 the report layer for routes, tests, results, and coverage.
The remaining content area is Table definitions — “the only real gap” (Content mapping above).
v1 already added migration listing to AppManifest (the cheap, deterministic spec-layer part is
shipped — the index page renders a migrations table). v3 adds the rich, run-dependent schema
view: introspect the migrated database catalog and surface browsable table reference pages, as one
more authoritative, drift-free surface of the same portal. Because deny-by-default endpoints already
expose which SQL runs, a per-table view of the underlying schema rounds out the security/audit story.
This realises the decision recorded under Table definitions (report layer: extend
TableIntrospector to catalog scope from the migrated test/CI DB) and the v3 — schema slice.
Scope for this round (decisions)
Section titled “Scope for this round (decisions)”- Core only: catalog-wide introspection + table reference pages. The SQL→table dependency graph (which route reads/writes which table) is the hardest semantic piece (table extraction from 2-way SQL, dialect variance, dynamic SQL) and is deferred to v3.1, as is a DB-free DDL parser.
- Sidecar only: schema comes from a build-time
schema.json; when absent the portal shows a “no schema introspected” empty state (mirroring v2’s graceful degradation). No live dev/edit-mode introspection fallback in this cut.
Design principles (consistent with v2)
Section titled “Design principles (consistent with v2)”- Optional, run-dependent sidecar — never inside
.tqlapp. Introspection needs a live, migrated database, so the result is non-deterministic across environments. v3 writes a newschema.jsonsidecar into the app home’s.tesseraql/docs/directory, alongsidereport.json/history.json.AppPackageralready excludes source.tesseraql/from the archive, so the schema never leaks into the reproducible.tqlapp— thegenerate/package-appgoals are unchanged. - Generate from the migrated DB the build already stands up. The
integration-testphase already applies migrations (migrate) and runs against a database (Testcontainers in CI). v3 introspects that same catalog. A dedicatedschemagoal keeps schema generation independent of the test run (it still produces a catalog when tests are skipped). - Reuse the single-table introspector. Generalise the scaffolding-only
TableIntrospector/TableSchema(already JDBCDatabaseMetaData-based) to catalog scope rather than writing new metadata code. - Graceful degradation + studio stays light.
DocServicereadsschema.jsonexactly as it readsreport.json; the typed build model is mirrored as a small null-tolerant studio record (asReportOverlaymirrorsReportDoc).tesseraql-studioopens no database (sidecar-only).
The schema artifact — schema.json
Section titled “The schema artifact — schema.json”A new typed model in tesseraql-report, Jackson-serialized, keyed by datasource:
SchemaDoc( schemaVersion: int, generatedAt: String, // ISO-8601, stamped by the mojo datasources: Map<String, CatalogSchema> // keyed by datasource name)CatalogSchema(tables: List<Table>) // tables sorted by name (deterministic)Table( name, type, // TABLE | VIEW schema, // db schema/namespace, nullable columns: List<Column>, // ordered by ordinal position primaryKey: List<String>, // ordered key columns foreignKeys: List<ForeignKey>, uniqueIndexes: List<Index>)Column(name, jdbcType, sqlTypeName, nullable, autoincrement, defaultValue, size)ForeignKey(name, columns: List<String>, refTable, refColumns: List<String>)Index(name, columns: List<String>, unique)The introspection itself (CatalogSchema + a CatalogIntrospector) lives in tesseraql-yaml’s
scaffold package beside TableIntrospector (JDK java.sql only, no new dependency, keeps the
core/yaml boundary). The serializable SchemaDoc wrapper + generator live in tesseraql-report.
Module & class layout
Section titled “Module & class layout”| Module | New / changed | Responsibility |
|---|---|---|
tesseraql-yaml (scaffold) |
new CatalogIntrospector + CatalogSchema |
catalog-wide JDBC introspection (getTables(null, schema, "%", {TABLE,VIEW}) → per table getColumns/getPrimaryKeys/getImportedKeys/getIndexInfo) |
tesseraql-report (docs) |
new SchemaDoc + SchemaGenerator (toJson) |
introspect the build DB → typed, deterministic schema.json |
tesseraql-maven-plugin |
new SchemaMojo (goal schema, integration-test) |
after migrate, write appHome/.tesseraql/docs/schema.json; reuse the tesseraql.jdbcUrl/username/password param block; never fail the build (overlay-only, like ReportMojo) |
tesseraql-studio |
new SchemaOverlay mirror; extend DocService (SCHEMA_PATH, optional read + degrade) + DocViews (schema(), table()); new bundled pages |
read schema.json if present; project catalog index + per-table detail; include table/column names in the in-memory search index |
tesseraql-camel-runtime |
register docs.schema + docs.table providers in TesseraqlRuntime (beside docs.coverage) |
wire the new pages |
Boundary note: tesseraql-report already depends on tesseraql-yaml and may use JDBC at build time
(rule 2 restricts only tesseraql-core). SchemaMojo is kept separate from ReportMojo because the
schema needs only the migrated DB, not the test run (alternative — fold into ReportMojo to share
one connection — rejected for that reason).
Slices (each a standalone PR, CI-green, merged in order — rule 7)
Section titled “Slices (each a standalone PR, CI-green, merged in order — rule 7)”- Schema model + introspector + sidecar (no UI).
CatalogIntrospector+CatalogSchema(yaml);SchemaDoc+SchemaGenerator+toJson(report);SchemaMojowriting.tesseraql/docs/schema.json. Unit test asserts deterministic JSON (re-run → identical bytes, sorted output). A Testcontainers IT (Postgres, mirroringReportGeneratorIntegrationTest) asserts introspected tables/columns/PK/FK against known migrations.AppPackagertest confirmsschema.jsonis not packed. No portal change yet. - Schema overlay + table reference pages.
SchemaOverlaymirror;DocServicereadsschema.json(optional, degrades to empty);DocViews.schema()(datasources → tables with column counts) andDocViews.table()(columns, PK badges, FKs linked to their target table page, unique indexes). New bundledquery-htmlpages.../ui/docs/schemaand.../ui/docs/schema/table(paramsds,name) ontql/shell+ hc; registerdocs.schema/docs.tableinTesseraqlRuntime; add a “Schema” nav entry; extend the in-memory search index with table + column names. Empty state when the sidecar is absent. Studio unit tests (merge + degradation, view projection from fixture JSON) and aStudioIntegrationTestrendering the catalog index + a table page over HTTP. (Split into 2a pages / 2b search if the PR grows large.)
Deferred (v3.1+)
Section titled “Deferred (v3.1+)”- The SQL→table dependency graph (route↔table cross-links): parse the directive-stripped 2-way
SQL skeleton (candidate: JSQLParser, Apache-2.0; reuse the
SqlCoverableLinesAST-visitor pattern overSql2WayParser). - A DB-free DDL parser (spec-layer schema without a live DB).
- Multi-datasource introspection beyond the single build-connected datasource.
- Live dev/edit-mode introspection fallback.
Verification & conventions
Section titled “Verification & conventions”- Catalog introspection is covered by a Testcontainers IT (the
ReportGeneratorIntegrationTestpattern), not H2 in a leaf module. - Studio overlay/merge, degradation, and view projection are covered by plain unit tests (no DB).
schema.jsonis a generated artifact — never hand-edited (rule 1).- Build-file / mojo / reproducibility changes are made deliberately (not auto-accepted).
- Run
spotless:applybefore the finalmvn -B -ntp verify; confirmBUILD SUCCESS. - Land each slice through a PR with CI green (rule 9); never push to
main.
Release diff (roadmap Phase 46)
Section titled “Release diff (roadmap Phase 46)”The portal’s Release diff page consolidates what a promotion changes from the captured
baselines: the API changelog against .tesseraql/docs/openapi.baseline.json, the schema DDL
delta against schema.baseline.json, and the migration set the app carries. The full
two-tree diff — routes, API, migrations, policies, schema tables — is the
tesseraql release-diff --app <candidate> --baseline <deployed-tree> CLI report (Markdown or
--json) and the tesseraql:release-diff Maven goal, which writes release-diff.md/.json
beside the other release evidence for the CI governance gate.