Filing gate ①: a seam defect with a named location and a reproduction. finding, class b. reach: measured in-process on the registered security service's explain(), which the explain route dispatches to. The HTTP route itself is NOT MEASURED.
Reader who acts: triage, for the first grade. plugin-security (explain-engine.ts) is domain:services. ⚠️ Classes and positions only.
Dedupe: mcp__github__search_issues for "security explain visible JSON column operator find INVALID_FILTER record attribution multi-valued scalar comparison refused" returned 55 hits. None of them is this defect. The nearest is #20431 (closed): the same shape, explain answers visible while find refuses 400, for a cross-field comparison of two classes. Its [#20431] note in explain-engine.ts states the contract this card measures broken for another refusal class. Also #21299 (open), the aggregate positions' close-out for { $field } comparisons, a different family.
Seam
@objectstack/core's JSON_COLUMN_INCOMPATIBLE_OPERATORS, the read's refusal on a declared JSON-stored column, versus plugin-security's explain-engine.ts record attribution (applyRecordAttribution / matchUnderDeclaredColumns). Read and write now both refuse these operators: the read through core's two faces, and the write since PR #21317 (#21254). Explain still evaluates them.
Source
The #21254 dev's report (PR #21317, out_of_scope_findings[0]), measured on driver-sql (better-sqlite3) at 5a56607ab, as a member resolving a permission set whose using is the predicate.
reach:
using |
stored row |
explain (read) |
find under the same policy |
record.tags != 'x' (multi-valued) |
['y'] |
visible: true, decided by RLS |
400 INVALID_FILTER |
record.meta == 'x' (json) |
'x' |
visible: true, decided by RLS |
400 INVALID_FILTER |
!(record.tags in ['x']) |
['y'] |
visible: true, decided by RLS |
400 INVALID_FILTER |
Contract it breaks: the [#20431] note in explain-engine.ts ("the explanation fails with the envelope the find fails with"), and skills/objectstack-data/rules/security.md (explain "answers from the enforcing code path").
Direction (for triage; not a ruling)
Explain refuses with the find's envelope, through the same rule. PR #21317 exports findJsonColumnCheckRefusal, which the attribution could call, so there is ⛔ no copy of the set. Pins: the table's three rows answer INVALID_FILTER / 400 from explain on both drivers. contains / notContains and a scalar column are the controls.
Serial
After PR #21317 (#21254), whose exported helper this would reuse.
Generated by Claude Code · domain:services seat 2 (#21118) · session_01DiCSbmJrkzNhuEAier4VoJ · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
Filing gate ①: a seam defect with a named location and a reproduction.⚠️ Classes and positions only.
finding, class b.reach:measured in-process on the registeredsecurityservice'sexplain(), which the explain route dispatches to. The HTTP route itself is NOT MEASURED.Reader who acts: triage, for the first grade.
plugin-security(explain-engine.ts) isdomain:services.Dedupe:
mcp__github__search_issuesfor "security explain visible JSON column operator find INVALID_FILTER record attribution multi-valued scalar comparison refused" returned 55 hits. None of them is this defect. The nearest is #20431 (closed): the same shape, explain answers visible while find refuses 400, for a cross-field comparison of two classes. Its [#20431] note inexplain-engine.tsstates the contract this card measures broken for another refusal class. Also #21299 (open), the aggregate positions' close-out for{ $field }comparisons, a different family.Seam
@objectstack/core'sJSON_COLUMN_INCOMPATIBLE_OPERATORS, the read's refusal on a declared JSON-stored column, versusplugin-security'sexplain-engine.tsrecord attribution (applyRecordAttribution/matchUnderDeclaredColumns). Read and write now both refuse these operators: the read through core's two faces, and the write since PR #21317 (#21254). Explain still evaluates them.Source
The #21254 dev's report (PR #21317,
out_of_scope_findings[0]), measured on driver-sql (better-sqlite3) at5a56607ab, as a member resolving a permission set whoseusingis the predicate.reach:usingexplain(read)findunder the same policyrecord.tags != 'x'(multi-valued)['y']visible: true, decided by RLS400 INVALID_FILTERrecord.meta == 'x'(json)'x'visible: true, decided by RLS400 INVALID_FILTER!(record.tags in ['x'])['y']visible: true, decided by RLS400 INVALID_FILTERContract it breaks: the [#20431] note in
explain-engine.ts("the explanation fails with the envelope the find fails with"), andskills/objectstack-data/rules/security.md(explain "answers from the enforcing code path").Direction (for triage; not a ruling)
Explain refuses with the find's envelope, through the same rule. PR #21317 exports
findJsonColumnCheckRefusal, which the attribution could call, so there is ⛔ no copy of the set. Pins: the table's three rows answerINVALID_FILTER/ 400 from explain on both drivers.contains/notContainsand a scalar column are the controls.Serial
After PR #21317 (#21254), whose exported helper this would reuse.
Generated by Claude Code ·
domain:servicesseat 2 (#21118) ·session_01DiCSbmJrkzNhuEAier4VoJ· https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ