Skip to content

Commit 53b3657

Browse files
dmealingclaude
andcommitted
docs(#195): CHANGELOG + roadmap follow-ups + agent-docs vocab
- CHANGELOG.md [Unreleased]: the four projection read-model origin capabilities (any/all/collect/computed/first) with their semantics + lowerings + the cross-port parity + the follow-ups. - spec/roadmap.md: the #195 fast-follows — the "not-migrate-managed" escape-valve FR (to design with Fable; @sqlView-style DDL attr + managed-elsewhere marker, generalizes to any DB object), a collect native-array persistence roundtrip gate, and #204 (the codegen-ts projection array-typing drop). - agent-docs body.ts: the scaffolded agent-context now teaches any/all/collect + origin.computed + origin.first (adopter AI agents discover the new read-model origins). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NGQ7oSuNcjhsMHWwZzhBwr
1 parent 7ae9dd4 commit 53b3657

3 files changed

Lines changed: 15 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
77

88
## [Unreleased]
99

10+
### #195 — four projection read-model origin capabilities (semantic, backend-agnostic)
11+
12+
Projections can now express four common admin/monitoring read-model shapes as metadata-managed, drift-checked origins instead of hand-written unmanaged SQL — each defined as a semantic `rows → value` derivation (the RDB view lowering is one realization). Registered + validated + natively-typed in **all five ports** (TS / C# / Java / Python / Kotlin); the TS `meta migrate` view synthesizer lowers them for Postgres and SQLite (ADR-0015 — schema is TS-owned).
13+
14+
- **`origin.aggregate @agg: any | all`** — a predicate quantifier over the related row-set ("did **any** related turn fail?" / "did **every** one succeed?"). `@filter` is the quantified predicate (required), `@of` is forbidden, the field is `field.boolean`. Empty related set → `any=false` / `all=true` (vacuous truth). Lowered as `bool_or`/`bool_and` (Postgres) / `MAX`/`MIN(CASE …)` (SQLite), phantom-row-guarded + `COALESCE`d to the pinned empty-set value.
15+
- **`origin.aggregate @agg: collect`** — an array rollup of `@of` across the related set (the field must be `isArray:true`, element type = the `@of` column). `@distinct` = set semantics; `@orderBy` sets element order (mutually exclusive with `@distinct`, which orders by value). Empty set → `[]`. Lowered as `array_agg` (Postgres) / `json_group_array` (SQLite).
16+
- **`origin.computed @expr`** — a row-level value computed from the base entity's own fields via a new **`attr.expression`** structured expression tree (a closed node grammar: field/value refs, comparisons sharing the filter op vocabulary, `isNull`/`isNotNull`, `and`/`or`/`not`, `coalesce`). The tree's inferred type must equal the field's declared subType (`ERR_COMPUTED_TYPE_MISMATCH`); an unknown node is a fail-closed load error (`ERR_UNKNOWN_EXPR_NODE`). The flagship case `payload IS NOT NULL` avoids shipping a heavy column. (Retires the `origin.expression` reservation — #159's future arithmetic becomes additive node kinds in this grammar.)
17+
- **`origin.first`** — argmax-then-project: the single related row selected by `@orderBy` along `@via`, projecting its `@of` column ("the latest child's status"). The carrying field must not be `@required` (empty related set → null). Lowered as a **correlated scalar subquery** (`ORDER BY … , child.pk ASC LIMIT 1`) that composes with the view's `GROUP BY`; the related PK ascending is always the deterministic tie-breaker; `@orderBy` nulls sort last.
18+
19+
Shared new attrs `@distinct` (bool) + `@orderBy` (string array); the per-capability validation (conditional presence, field-shape, type-preservation, the closed expression grammar + type inference) and native projection-field typing (`any/all/collect` non-null; `first` nullable) hold identically across all five ports (each with a 20-case validation suite mirroring the reference). A load-time WARN fires when an inflation-sensitive aggregate (`sum`/`avg`/non-distinct `collect`) coexists with ≥2 to-many join branches. Cross-port conformance fixtures cover all five capabilities incl. the zero-related-rows determinism pins. Follow-ups: a `collect` native-array persistence roundtrip gate, the projection array-typing fix (`collect → T[]` in `codegen-ts`#204), and the "not-migrate-managed" escape-valve FR (see `spec/roadmap.md`).
20+
1021
### Program D — value-object jsonb columns are PATCH-able cross-port
1122

1223
Value-object jsonb columns (`field.object @objectRef @storage:jsonb`, single AND `@isArray`) are now bound → nested-validated → written on `POST` and `PATCH` in **all five ports** (TS / Python / Java / Kotlin / C#), closing the deliberate FR-036 Day-1 simplification (Java/Kotlin/C# previously excluded VO columns from the patch set, so a single-port fix would have broken byte-identical api-contract parity). Nested VO constraints validate in full (a VO string member over `@maxLength`, an empty `@required` member, a present-`null` on a required member → `400 {"error":"validation"}`), identically across ports. The FR-035 tristate holds for VO columns: absent → untouched; present-`null` clears a nullable column but 400s a `@required` one; present-`[]` writes an empty array (distinct from present-`null` → SQL NULL). Gated by `fixtures/api-contract-conformance/jsonb/scenarios/jsonb-value-object-patch.yaml` in both lanes (hand-rolled reference server + generated artifact over Testcontainers Postgres), all five ports. A latent TS runtime bug was fixed along the way: the `ObjectManager` validator did not recurse into `field.object` member constraints (nested violations wrote a 201). `field.map` (dict-of-VO), the Kotlin `field.string @dbColumnType=jsonb` open-bag PATCH, and TPH entities with VO columns remain tracked follow-ups.

server/typescript/packages/sdk/src/agent-docs/body.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,9 @@ When a list needs computed columns (counts, sums, joined fields), create a **pro
330330
331331
\`meta gen\` produces a read-only \`useProgramSummaries(filter)\` hook, a SQL view DDL in the migration, and a read-only GET-only route.
332332
333-
**Aggregate vocabulary**: \`count\`, \`sum\`, \`avg\`, \`min\`, \`max\`.
333+
**Aggregate vocabulary** (\`origin.aggregate @agg\`): \`count\`, \`sum\`, \`avg\`, \`min\`, \`max\`; plus \`any\`/\`all\` (a boolean predicate quantifier over a required \`@filter\` — no \`@of\`; "did any/every related row match?"), and \`collect\` (an array rollup of \`@of\` — the field must be \`isArray: true\`; \`@distinct\` dedupes, \`@orderBy\` sets element order).
334+
335+
**Other read-model origins**: \`origin.computed\` — a row-level value from the base row's own fields via a structured \`@expr\` tree (e.g. \`{ "op": "isNotNull", "arg": { "field": "payloadJson" } }\` → a boolean, to avoid shipping a heavy column); \`origin.first\` — the single related row picked by \`@orderBy\` along \`@via\`, projecting \`@of\` (e.g. "the latest child's status"; the field must not be \`@required\` — an empty related set yields null).
334336
335337
**Multi-level via paths** are supported: \`@via: "Program.weeks.workouts"\` builds a 2-level JOIN tree.
336338

spec/roadmap.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ _(FR-032 was developed under the working number "FR-026" — see commit history;
124124

125125
## Planned
126126

127+
- **#195 follow-ups — projection read-model origins.** The four capabilities (`origin.aggregate @agg:any|all|collect`, `origin.computed @expr`, `origin.first`) shipped (validate + type in all 5 ports; TS `meta migrate` view synthesis; see `CHANGELOG.md` [Unreleased] + `docs/superpowers/specs/2026-07-15-issue-195-projection-readmodel-origins-design.md`). Remaining as fast-follows: (a) the **"not-migrate-managed" escape valve** — a **separate FR to design with Fable**: an `@sqlView`-style attribute carrying hand-written view DDL PLUS a distinct "managed elsewhere" marker, generalizing to **any** DB object (entities included), so a genuinely-irreducible view no longer aborts the whole `meta migrate` run (the four capabilities cover the known monitoring shapes but do not remove the all-or-nothing abort); (b) a **`collect` native-array persistence roundtrip** gate (Postgres `text[]` vs SQLite `json_group_array` → same wire) in `fixtures/persistence-conformance/`; (c) **#204** — the `codegen-ts` projection array-typing fix (a projection array field's `isArray`/`storage:jsonb` is dropped in `projection-decl`, so `collect → T[]` currently types as scalar). `origin.computed`'s expression grammar is designed so **#159**'s arithmetic / `case` / `@via`-joined refs slot in as additive node kinds.
127128
- **FR-019 — Shared + externally-provided enums.** Stop redeclaring a `field.enum` inline in every consuming entity: a package-level abstract `field.enum` materializes ONE standalone enum type per port (the existing D6 `extends` reuse vocabulary), and **`@provided: true`** — a provenance flag on the named-type *declaration* (not the field), shared cross-type with value objects — references an existing hand-written type instead of emitting one (per-port namespace via codegen config, never a metadata FQN — retires the C#-only `@csEnumType`). Decision in [ADR-0026](decisions/ADR-0026-shared-and-provided-named-types.md); implementation spec `docs/superpowers/specs/2026-06-06-fr-019-shared-and-provided-enums-design.md`. (Generators are now subclass-extensible across all 5 ports, so this lands on open seams.)
128129
- **FR-020 — `@inheritance` persistence strategy (single-table vs joined).** TPH (one physical table, nullable subtype columns) is the single implicit strategy today (FR-014/FR-017). FR-020 adds an `@inheritance` attr to choose **joined** (a base table + per-subtype tables, joined on read) as an alternative — **additive, default stays TPH** (no breaking change). Status: design (proposed, sequenced after FR-017, which shipped). Design: `docs/superpowers/specs/2026-06-07-fr-020-inheritance-strategy-design.md`. *(Was not previously surfaced in this roadmap.)*
129130
- **FR-025 — Cross-port package-binding codegen config.** Config for how a metadata FQN/package maps to each port's native namespace/package at codegen time (the ADR-0001 build-time binding), so cross-package references (and metadata shared across projects, FR-023) emit correct imports per language. Status: design (ready for implementation). Design: `docs/superpowers/specs/2026-06-08-fr-025-cross-port-package-binding-config-design.md`. *(Was not previously surfaced in this roadmap.)*

0 commit comments

Comments
 (0)