Skip to content

ADR-0139 E2: the engine composes the ReadableIds leaf once per parent object, fail closed, refuses it on write verbs and for D5's three classes, and marks the scope 'policy' (D2, D5) #22861

Description

@objectstack-fleet

Filing gate: ④ a coordination node's execution card, E2 of ADR-0139's plan (part of #22788). It is derived and in scope, so it inherits #22788's lane, grade and area. Filed by domain:engine seat 1 (#6367) · session_01JfJfBUC3cQ6hhgm9MQK76T, as the record's plan orders: "cut after acceptance", each card when its predecessor lands. ⛔ Classes, positions and functions only.

Predecessor landed: E1 (#22787), PR #22809 → 9814035c01. It adds the branded leaf ReadableIds in packages/spec/src/data/readable-ids.ts, admitted as a $in comparand only through objectql's assertListComparandShapes, and refuseUncomposedReadableIds as the interim 500 this card replaces.

Reader who acts: domain:engine seat 1, on its serial line after #22519, which edits the same file. ⛔ Not dispatched until then.

The work (ADR-0139's plan row E2, verbatim intent)

  • D2: the engine seam replaces every ReadableIds leaf by its composed form, before any driver sees it.

    • canReadObject(P, caller) false, a throw, or no such service ⇒ $in: [].
    • Otherwise scope = getReadFilter(P, caller): undefined ⇒ no restriction; the deny sentinel (RLS_DENY_FILTER) ⇒ $in: []; any other filter is lowered as P's own direct read lowers it and marked 'policy' (markFilterSubtreeProvenance).
    • The composed leaf carries { object: P, scope }. The tenant wall is the driver's (D3), never re-derived here.
    • Both halves are asked of the registered ISecurityService on every request, with nothing memoised across requests.
  • Refused: on write verbs; and for D5's three classes, by name, loudly (D3's envelope: 500 INTERNAL_ERROR, naming the leaf and the remedy):

    1. a delegated (on-behalf-of) caller;
    2. a parent narrowed by an object-scoped read middleware (sys_attachment, sys_comment, sys_comment_reaction, sys_approval_request, and the two gated objects);
    3. a federated parent (resolveDatasourceBinding).

    ⛔ No silent fallback.

  • The conformance case-set for the leaf, so E3 and E4 can each be measured as compiling or as refusing.

  • Acceptance (the record's row): canReadObject false ⇒ no row; the deny sentinel ⇒ no row; a delegated, middleware-gated or federated parent ⇒ the loud refusal.

Open for this card: the seam's position (must be measured; it may need a decision)

  • D2's text places the seam "where ObjectQL.lowerRelationConditions runs today, after ingress parsing and the comparand doors", which is before the middleware chain.
  • E1's pointer (6107554336 on ADR-0139 execution: the engine-internal semi-join leaf across spec, engine, drivers and consumers (E0–E7 coordination) #22788, verified at E1's head by its contract review) reads the code otherwise. The D4 emitters write ctx.ast.where inside the chain (plugin-audit's installActivityReadVisibility through registerMiddleware), and beforeFind hooks rewrite the AST inside the executor. So a seam before the chain would never see their leaves. It proposes composing once on the final AST, at the executor boundary between the before-hooks and the driver call, and moving the uncomposed refusal there.
  • The dev measures both positions first. If the composition must sit after the chain, that changes the position an accepted record names in its text. The card stops for a decision card, and the seat takes it to the maintainer. ⛔ The ADR text is Tier H, and E0 (its status line) is still owed.
  • From the same pointer, to carry:
    • lowerWhereFilterArray lowers through parseFilterAST, which does not opt in. The array branch should meet the same seam.
    • having's refusal of the leaf (engine.ts, near :19049) exists but is pinned by nothing, and D1 names having.

Duplicate check


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

No one assigned

    Labels

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:engineenhancementNew feature or requestpm:queuepriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions