You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit efc8544
Browse filesBrowse the repository at this point in the historyBrowse files
feat(#207): mirror the projection @filter fail-closed validation cross-port
Complete the #207 cross-port contract: a projection row-scope @filter whose
field-ref is dangling (names no declared field) or aggregate-derived
(origin.aggregate / origin.first / origin.collection — a WHERE runs before
aggregation, so it cannot see an aggregate) is now a fail-closed load error
(ERR_BAD_ATTR_FILTER) identically in every port (C#, Java, Python — mirroring
the TS reference validateProjectionFilter; Kotlin rides Java's loader), not
just TS. Own accessors throughout (the @filter is declared locally; origin.*
never inherits, ADR-0029/0039).
Gated by two shared negative conformance fixtures (error-projection-filter-
dangling / -aggregate) that every port's loader runs, so the contract can't
drift. The TS-only hardening (operator-band + malformed-compose-shape checks)
and the SQL WHERE lowering stay TS-side (ADR-0015) — the cross-port contract
is the two core reference checks.
Verified per port: TS conformance green, Python conformance green, C#
conformance 768 green, Java metadata 1173 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGQ7oSuNcjhsMHWwZzhBwr
Copy file name to clipboardExpand all lines: CHANGELOG.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,7 +12,8 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
12
12
A projection (`object.projection`) can now declare a row-scope `@filter` — a portable `attr.filter` object (`eq`/`ne`/`gt`/`gte`/`lt`/`lte`/`like`/`in`/`isNull` with `and`/`or`, desugared to canonical `{ field: { op: value } }` at parse time) selecting which rows the derived view returns. It lowers to an outer SQL `WHERE`, the metadata-managed way to express soft-delete / status / type views instead of a hand-written unmanaged view (which is drift, invisible to `verify --db`). Placement mirrors `origin.aggregate @filter` exactly: a `filter`-subtype attr on `object.projection` (the predicate is a LOGICAL derivation, so it lives on the object, not on `source.rdb` — it survives non-RDB lowerings).
13
13
14
14
-**Registered in all five ports** (TS / C# / Java / Python / Kotlin), `registry-conformance`-gated (`object.projection` now carries the `@filter` attr in the byte-matched manifest), with a `fixtures/conformance/projection-filter` corpus fixture proving it loads + canonical-serializes identically everywhere.
15
-
-**Resolution (TS lowering, ADR-0015 — schema is TS-owned).** Each filter field-ref names one of the projection's own declared fields and resolves against its `SelectColumn`: a **passthrough** (base OR joined) → `sourceAlias.sourceColumn` (so `WHERE joined.status IS NULL OR joined.status = 1` works across a join); a **computed** (`origin.computed`) field → its inlined expression. An **aggregate-derived** ref (`origin.aggregate`/`origin.first`) or a **dangling** ref (naming no declared field) is a fail-closed load error (`ERR_BAD_ATTR_FILTER`) — a `WHERE` runs before aggregation, so it cannot see an aggregate (post-aggregate filtering is a separate future `HAVING`). The `WHERE` renders after the joins and BEFORE any `GROUP BY`, composing with `origin.first` correlated subqueries.
15
+
-**Fail-closed validation (cross-port).** A `@filter` field-ref must name one of the projection's own declared fields, and that field must be addressable in a `WHERE`. An **aggregate-derived** ref (`origin.aggregate`/`origin.first`/`origin.collection`) or a **dangling** ref (naming no declared field) is a fail-closed load error (`ERR_BAD_ATTR_FILTER`) — a `WHERE` runs before aggregation, so it cannot see an aggregate (post-aggregate filtering is a separate future `HAVING`). This dangling/aggregate-derived check runs identically in **all five ports** (gated by shared negative conformance fixtures). TS additionally hardens the load (rejecting a non-array `and`/`or`, an empty op-object, and an op illegal for the field's subtype), so a malformed filter fails at load rather than silently dropping the predicate or crashing the view synthesizer.
16
+
-**Resolution + lowering (TS-owned, ADR-0015).** The SQL `WHERE` lowering lives in TS (schema is TS-owned). Each field-ref resolves against its `SelectColumn`: a **passthrough** (base OR joined) → `sourceAlias.sourceColumn` (so `WHERE joined.status IS NULL OR joined.status = 1` works across a join); a **computed** (`origin.computed`) field → its inlined expression. A field clause may carry multiple ops (a range `{ gte, lte }`), each AND-composed. The `WHERE` renders after the joins and BEFORE any `GROUP BY`, composing with `origin.first` correlated subqueries.
16
17
-**v1 scope: projections only.** A write-through entity read-view (`isWriteThrough`) is excluded by construction — the attr is registered on `object.projection`, not `object.entity` — because a filtered *replica* view breaks read-your-writes totality (write a row, read it back through the filter, it's gone).
17
18
- Gated by golden extract+emit tests, the loader validation suite, and a **real-Postgres round-trip** (emit → apply → introspect → re-diff EMPTY, plus a filtered view that returns only the matching subset). Reuses the shipped `attr.filter` desugar + the `ViewFilterClause` renderer (extended with an inlined-expression comparison node for computed refs).
0 commit comments