Skip to content

[A of #7929] a spec-declared provenance mark set at both read-scope merge boundaries, so the driver can restore the author-facing cross-field diagnostic without re-disclosing policy #8220

Description

@os-zhuang

This is "A" of the maintainer ruling of 2026-08-12 on #7929 (comment 5266067250, verbatim 「接受你的全部建议。」 — adopting "B now, A next"). B landed in #8198 (merged a5dcb74, closing #7929 and #7988). This card is the sanctioned follow-up, not a new proposal.

⛔ Filed unassigned — claim it before writing code, per the repo's claim discipline.

Why A exists

B stopped driver-sql's cross-field INVALID_FILTER refusal from echoing its operands, for every caller, because the driver genuinely cannot tell an administrator's predicate from an author's. Measured on both merge boundaries: the read scope arrives as a bare FilterCondition in where, DriverQuery is Omit<QueryAST, 'object'> with no provenance slot, and the only thing that crosses (context) says who is asking, never which subtree they did not write.

The accepted cost, stated plainly in B's changeset: an author debugging their own cross-field filter now gets the redacted message too. Their diagnostic is relocated to the server log, not destroyed. A is what gives it back — behind a real mark rather than a guess.

What A has to build

  1. A provenance mark declared in packages/spec. Declared, not conventional — the whole point is that the driver may trust it because the contract defines it.
  2. Set at BOTH merge boundaries, or the fix is half-live:
    • plugin-security's CRUD injection — the ast.where = ast.where ? { $and: [ast.where, scoped] } : scoped merge around security-plugin.ts:2198;
    • service-analytics' read-scope merge (ObjectQLStrategy.withReadScope).
  3. driver-sql consumes it: a marked (policy-authored) subtree keeps B's redaction; an unmarked, author-written filter gets its full diagnostic back — both columns, the operator, the list index, the boundary reason.

⚠️ The invariant that must not regress — fail closed

An unmarked or ambiguous predicate must default to WITHHELD. The mark is permission to reveal, never a requirement to prove secrecy. A driver-side guess at provenance is precisely the shape triage rejected on #7929, and an inverted default would silently restore the original disclosure — including the sentence naming which column is the object's tenant-isolation column. Any design where "the mark is missing" lands on the disclosing branch is wrong, however convenient.

⚠️ One pin A is expected to REWRITE — it is not a regression

packages/runtime/src/cross-field-refusal-operand-withhold.test.ts contains a byte-equality assertion: an author-written $field filter and a policy-injected one must produce identical messages. That pin is the strongest available statement of "the driver cannot tell them apart" — which is exactly the fact A is chartered to change.

Whoever implements A must rewrite that assertion deliberately, and say so in the changeset. ⛔ Do not treat it as a failing test to make green by weakening it, and ⛔ do not delete it — replace it with its successor: marked and unmarked now differ, and the unmarked-by-default case still withholds. Left unexplained, this pin reads like a bug to the next agent who runs the suite.

Scope notes

Done means

An author's own cross-field filter names its columns again; a policy-injected one does not; an unmarked one does not; and all three are pinned at the two real merge boundaries with a real driver, not a mock.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions