Skip to content

[Decision] DataScopeManager under ADR-0049: retire it, or canonicalise its operator vocabulary — the spec's real RLS vocabulary is a CEL predicate, not a RowLevelFilter #7750

Description

@claude

⛔⛔ THIS CARD IS RULED. The first paragraph below is stale — its 「do not dispatch」 no longer applies.

The maintainer ruled it on 2026-09-07, option C — retire DataScopeManager under ADR-0049 — in comment 5564753628 (director seat, decision batch #66; verbatim reply 「同意」). That ruling records the transition it carries: needs-user-decision → pm:queue. ⇒ this is an implementation card and has been one since that date.

What was ruled: remove the class, the RowLevelFilter type and their barrel exports from @object-ui/core; regenerate the package surface pin; the changeset states the removed public API (major per the package's policy, ⛔ no alias and no window). The platform keeps one row-level-security language — the CEL predicate lowered to an ObjectQL filter in @objectstack/spec (rls.zod.ts:394, fail-closed). Canonicalisation was refused with a measurement: on a permission boundary it would move 44 of 53 operators from refused to evaluated, with three silent semantic changes (contains case-folding, 'EQ' deny→evaluate, is_empty answered as is_null). Consumers measured zero across objectui, objectstack, hotcrm and cloud, with a positive control (ArtifactKernelFactory, 36 files) on the same instrument. Clause-② yes ⇒ the landing PR carries needs:contract-review.

⛔ Do not re-open 「retire or canonicalise」 below, and ⛔ do not file it as a decision. See comment 5564753628 for the full ruling and its execution notes.

Carved out of the contract review of PR #7748 (card objectui#7378) by the domain:ui PM seat. The reviewer made filing this a condition on its disposition of that PR's boundary flag, and named an ordering constraint that is the whole point of this card:

The follow-up must be filed as the ADR-0049 liveness decision, with canonicalisation only as a dependent option on that card, never as a standalone implementation card.

⛔ So this is a decision card, not an implementation card. Do not dispatch it as "add canonicalisation".

⭐ The finding that reframes the question

objectstack/packages/spec/src/security/rls.zod.ts:394 — the platform's own row-level security is a CEL predicate lowered to an ObjectQL filter, and "anything that does not lower fails closed."

⇒ The spec's real RLS vocabulary is not RowLevelFilter-shaped at all, and DataScopeManager is a third hand-written evaluator vocabulary (the other two being @object-ui/permissions' evaluateCondition and the spec's own lowering). That is the context any answer here needs, and it was not on #7378.

Liveness, as measured for PR #7748

probe reading
in-repo constructors of a RowLevelFilter 0 — registerScopeWithConfig / setFilters( / applyFilters( hit only the class and its own test
RowLevelFilter references at base 2 files (declaration + barrel)
@object-ui/types DataScope / DataContext (data.ts:1272-1330) carry no filters
React provider wiring DataScopeManager none
sibling objectstack 0 (lit control canonicalAstOperator fires, 3 files)
org code search org:objectstack-ai DataScopeManager 5 · registerScopeWithConfig 2 · RowLevelFilter 2 — all in objectui (control canonicalAstOperator fires in both repos, 17)
hotcrm indexed, and does not depend on @object-ui/core
⚠️ cloud ⛔ NOT visible to that session — unmeasured, not zero. Reading it is the first task of this card.

The options

C — retire under ADR-0049 enforce-or-remove. Closes the operator question for free. Blocked only on the cloud consumer read above.

Canonicalise through canonicalAstOperator (@objectstack/spec/data), the way PR #7377 repaired sibling #7349 — only as a dependent option, and it carries five measured traps:

  1. It maps the nine implemented spellings to symbolic forms (eq→=, ne→!=, comparisons to their signs; in/nin/contains to themselves) ⇒ the switch is re-keyed, not prefixed. That is exactly what ValueDataSource.matchesComparisonNode (:105-118) keys on.
  2. Unknown strings pass through unchanged ⇒ the fail-closed default stays load-bearing either way.
  3. ⭐ contains semantics do not survive the transplant. ValueDataSource.ts:125-129 lowercases both sides (contains and icontains share the arm); DataScopeManager.ts:267 is fieldValue.includes(String(filterValue)), case-sensitive. Copying fix(core): teach ValueDataSource's matcher the filter vocabulary the wire already has #7377 would silently widen contains for existing rules on a permission boundary. (Related: open core: ValueDataSource's contains is case-INSENSITIVE, so it executes icontains semantics — the spec keeps the two operators deliberately distinct #7379.)
  4. ⭐ canonicalAstOperator does String(op).toLowerCase() first. Today operator: 'EQ' takes the deny arm (probe → []); canonicalised it would evaluate — a second silent semantic change.
  5. ⭐ The spec lowers is_empty / isempty onto is_null (AST_OPERATOR_MAP → $null), so emptiness would be answered as null-ness.

Plus: 18 of 20 VIEW_FILTER_OPERATORS and 44 of 53 VALID_AST_OPERATORS would move refused → evaluated, needing arms for the null-ness family, between, starts_with/ends_with, not_contains/icontains, and a like/ilike decision (matchesLikePattern is exported); and the public RowLevelFilter['operator'] union would have to widen.

Not in scope

⛔ The two pre-existing hardening gaps vs the sibling evaluator are objectui#7751 — split out deliberately, because they are independently actionable today whichever way this decision goes.

Refs: objectui#7378 · PR #7748 · #7349 / PR #7377 (the precedent) · #7379 (contains folds case) · #7383 (like/ilike refused) · ADR-0049.


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions