feat(spec): read-scope filter-subtree provenance mark — restore the author-facing cross-field diagnostic without re-disclosing policy (#8220) - #8429
Conversation
…t it at both merge boundaries; consume it in the SQL drivers (#8220) The mark (data/filter-subtree-provenance.ts) is set by plugin-security's CRUD RLS injection and service-analytics' ObjectQLStrategy.withReadScope, travels on the where tree by reference across the DriverQuery boundary, and is consumed by driver-sql / driver-turso's RemoteTransport: a subtree positively marked 'author' gets its full cross-field diagnostic back; 'policy', unmarked and ambiguous all keep the #7929/#8198 redaction (fail closed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AkiE189Csm7aBT2qCVSx3X
…te both B-era redaction pins deliberately - spec: filter-subtree-provenance unit tests (fail-closed on every degraded shape) - driver-sql / driver-turso: author discloses (eager + lazy seams), policy/ unmarked/ambiguous withhold byte-identically - plugin-security / service-analytics: both boundaries' stamping pinned, including the sibling-rewrite fail-closed vouch - runtime: the byte-equality pin rewritten into its successor (author differs and names columns; unmarked still withholds, byte-identical to policy) - service-analytics engine-fallback: B's blanket-redaction pin on the caller's own where rewritten into the author-restored assertions, with the policy- scope non-disclosure pinned as its fail-closed pair - spec api-surface/export-origins regenerated; changeset added Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AkiE189Csm7aBT2qCVSx3X
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 5 package(s): 114 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
…ce test's strategy import — clears the +1 type-check-debt drift (#8220) The new `src/__tests__/read-scope-provenance-mark.test.ts` imported `../strategies/objectql-strategy` without the ESM file extension. The repo resolves as NodeNext, so tsc raised TS2835 there: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean '../strategies/objectql-strategy.js'? service-analytics is DEBT-ledgered and has no `typecheck` script, so the workspace typecheck never read the file; only CI's full-closure `check:type-check-debt --re-measure` saw the count move 10 -> 11. Vitest resolved the extensionless specifier either way, which is why the suite was green while the gate was red. Every other `../strategies/*` import in this package's tests already carries `.js`. Raw `tsc --noEmit -p packages/services/service-analytics/tsconfig.json` is back to 10, and the surviving 10 match the ledger note exactly (TS6133 x1 + TS2339 x7 + TS7053 x2). The DEBT entry is untouched and the pre-existing 10 are left alone.
|
Audit (spec-seat PM, session Attribution correction for the record: the +1 was not in the provenance source edit — Generated by Claude Code |
Fixes #8220
Implements A of the #7929 maintainer ruling ("B now, A next" — B landed as #8198): a spec-declared provenance mark on filter subtrees, set at both read-scope merge boundaries, so the driver can give a vouched author back the full cross-field diagnostic while policy-injected predicates keep #8198's redaction.
(Opened by the dispatching PM on the dev's behalf — the dev's cloud session has no GitHub API egress; the branch and this description are the dev's work, relayed. Full report on #8220.)
What landed
packages/spec/src/data/filter-subtree-provenance.ts):FilterSubtreeProvenance('author' | 'policy'),markFilterSubtreeProvenance(non-enumerableSymbol.forkey; first-mark-wins; silent no-op on frozen input),filterSubtreeProvenanceOf, andresolveFilterSubtreeProvenance(root, node)— positional, innermost-mark-wins, identity-located; unreachable/conflicting ⇒null. The mark crosses the driver boundary ON thewheretree itself;DriverQuerygrows no slot, and its TSDoc declares the crossing and the fail direction.plugin-security's RLS injection marks every injected scope (RLS / CBP / delegator / deny-sentinel)'policy', and the caller's predicate'author'only under the identity vouchopCtx.ast.where === opCtx.options.where— a sibling-rewritten tree is vouched for nobody;service-analytics'ObjectQLStrategy.withReadScopemarks scope'policy'and the strategy-built user filter'author'(resolveFkAttr's scope arm'policy', its internal idFilter deliberately unmarked).driver-sqlresolves withheld refusals against the query's ownwhereroot at its two existing seams (theapplyFilterscatch for eager refusals;withWithheldFilterLog, now threaded with the root, for knex's lazy group callbacks) —'author'gets a freshINVALID_FILTER/400 carrying the full diagnostic (both columns, the operator, the list index, the boundary reason);'policy'/ unmarked / unreachable / ambiguous keep fix(driver-sql,driver-turso): a cross-field$fieldrefusal stops naming the two columns it compared (#7929, #7988) #8198's redaction + server-log relocation.driver-turso'sRemoteTransportapplies the same resolution atbuildWhereSQLentry under the sameSymbol.forkeys;driver-sqlite-wasminherits (pinned via the runtime suite).Fail-closed invariant
Unmarked or ambiguous ⇒ WITHHELD. The mark is permission to reveal, never a requirement to prove secrecy. Pinned at every layer, including a byte-equality unmarked==policy pin and a JSON-round-trip-drops-the-author-mark pin.
Two B-era pins rewritten deliberately (not regressions — accounted in the changeset)
packages/runtime/src/cross-field-refusal-operand-withhold.test.ts— the byte-equality pin ("author and policy messages identical") was the strongest statement of the fact this card was chartered to change; its successor is a three-way split: policy withholds; the vouched author names columns on BOTH real faces (CRUD andPOST /analytics/query); unmarked withholds byte-identical to policy.service-analyticscross-field-engine-fallback.test.ts— pinned B's blanket redaction on the caller's OWNwhere; those callers are now the vouched author, so the routed cases assert the corpusdiagnosticIncludesfragments back on the wire, with the policy-scope case gaining explicit non-disclosure assertions as the fail-closed pair.Why this is not #8116's mechanism (prior art read first, per the card's scope note)
injected-system-column-provenance(#8116/PR #8342) is COLUMN provenance — a derivation over metadata documents at rest ("is storage behind an injected column"). This is FILTER-SUBTREE provenance — an attestation about one query value in flight, knowable only at the merge moment. Folding them would give the column verdict a mutable runtime carrier it must not have, and the filter mark a document derivation it cannot have. Justified in both module headers.Scope fences
Verification
Full suites green: spec 10346 · plugin-security 1036 · driver-sql 1431 · objectql 3496 · driver-sqlite-wasm 393 · driver-turso 973 · service-analytics 1675 · runtime 2309. Typecheck 127/127. spec
check:generated13/13 (api-surface + export-origins regenerated for the new exports). Every family fromdispatch-gates.mjsgreen; ESLint clean on changed files. New pin files: spec (15), driver-sql (11, incl. the lazy group-callback disclosure seam), driver-turso (7), plugin-security (4), service-analytics (5); rewritten: runtime (11), service-analytics engine-fallback (93).Changeset:
minorfor@objectstack/spec+patch×4 consumers; non-breaking; adr-0087 gate green with no disposition needed.🤖 Generated with Claude Code
https://claude.ai/code/session_01Euoy6wyfzgiWtgCg4s6JK2
Generated by Claude Code