Skip to content

[finding] the published explain schema names only one zero-rows payload sentinel, and explain can now publish the other #13961

Description

@claude

Recording only, no severity asserted — routing and ranking are triage's. Found while
implementing slice 2 of #13639 (PR #13960); deliberately NOT repaired there, because the
repair lands in packages/spec and that card's scope is explain's computation, not its
schema.

Measured

packages/spec/src/security/explain.zod.ts documents the zero-rows payload vocabulary as
a two-item enumeration, in two places:

  • ExplainDecisionSchema.readFilter"the composed row filter the caller would be
    served with … (null = unrestricted, { id: '__deny_all__' } = zero rows)"
  • ExplainRecordAttributionSchema.rowFilter — same shape, both in the JSDoc and in the
    .describe() string, and the .describe() is mirrored verbatim into published docs at
    content/docs/references/security/explain.mdx (two rows).

That enumeration is now incomplete. As of PR #13960, explain reports verdict denies
and decision.allowed: false for a fail-closed RLS denial, whose readFilter /
rowFilter is plugin-security's RLS_DENY_FILTER — spelled __rls_deny__ followed by a
colon and a UUID-shaped string. That payload is published as composed (deliberately —
not rewriting it is the boundary between options A and B on #13639), so a consumer reading
the schema prose sees a zero-rows shape the prose does not name.

Why it is small

⛔ No consumer is misled about the decision: the sibling verdict field says denies
and decision.allowed is false, so a reader who consults them gets the right answer.
The gap is that the payload prose under-describes the vocabulary — incomplete, not wrong.
A consumer pattern-matching on the payload alone to detect "zero rows" would miss this
shape, and that is the only measured exposure.

Why it is worth recording anyway

The schema prose is the published contract for a diagnostic surface whose entire purpose
is telling an operator the truth about a request. "The one enumeration a reader would
trust is missing a member" is exactly the shape that costs someone an hour later, and it
is cheap to close.

Scope NOT established

Related

#13639 (the card this fell out of) · PR #13960 (slice 2, which made the shape reachable
with verdict denies) · PR #13942 (slice 1) · ADR-0123 D2

Generated by Claude Code


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions