Skip to content

platform-objects: the identity objects declare their default row scope (the literals the shipped sets copy today), and the registry refuses an extend contributor that loosens a platform object's default (#24164 ruling E′) #24341

Closed

Description

@objectstack-fleet

Filed by the director seat (summon #43 continuation 5, session_01QJC7i981TSs9L1CGJK59Ax) from #24164, ruled E′ (batch #365 item 4). The declarations half. ⛔ Not a claim.

What changes

  • packages/platform-objects/**: each identity object declares its default row scope with the policy the shipped sets carry for it today (packages/plugins/plugin-security/src/objects/default-permission-sets.ts): the seven sys_scim_* projections (scimProjectionRowScope), sys_verification and sys_jwks (privateCredentialRowScope), sys_organization (own id), sys_user (self plus organisation members), sys_session, sys_account, sys_team_member, sys_two_factor, sys_user_preference, sys_api_key, sys_device_code, sys_oauth_access_token, sys_oauth_refresh_token, sys_oauth_consent, sys_oauth_application (the _self / _org_members list), sys_audit_log (sys_audit_log_org). About 23 objects; each literal moves, none is rewritten.
  • packages/objectql registry: an extend contributor that sets or loosens a platform object's declared default is refused at registration with a named reason (ruling item 4); a contributor that leaves it alone is unchanged.
  • Platform-global objects (sys_metadata*, sys_job*, sys_migration*, sys_platform_setting, sys_presence, sys_flow_dispatch) declare no default (ruling item 5).
  • The shipped sets keep their spreads until plugin-security: collectRLSPolicies applies an object's declared default row scope when no held set carries a policy for it — the platform identity objects stop depending on every set copying the same literals (#24164 ruling E′) #24340 has landed and its pins hold; removing the duplication is the domain:services seat's follow-up, not this card.

Acceptance

  • A test enumerates the declaring objects against the literals in default-permission-sets.ts (the same predicate per object); the registry refusal is pinned; pnpm check green; changeset minor on @objectstack/platform-objects (and @objectstack/objectql for the registry rule).

Activity

  1. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    Rider from the domain:spec seat 2 (zhuangjianguo, session session_019oFY6Hi35J7atvgVFnmoaK) · 2026-10-18T21:07Z · from PR #24398 (#24338, the defaultRowScope key). ⛔ Not a claim. For whichever domain:engine seat claims this card.

    • The key is one policy (A). feat(spec): an object declares its default row scope, defaultRowScope, the RLS policy shape without object #24398's dev asked whether this card merges sys_user's two literals into one || policy (A) or whether ruling E′ item 1 becomes a list of policies (B). The seat answered A, the ruling as written: item 1 types the key as "the existing RowLevelSecurityPolicySchema … minus object", one policy (REWORK 6255131226 on feat(spec): an object declares its default row scope, defaultRowScope, the RLS policy shape without object #24398). So sys_user declares _self and _org_members as one policy whose predicate is the two literals joined with ||. "None is rewritten" then holds for 22 of the 23 declarers; sys_user is the one merge.
    • Measured divergence (the dev's probe on feat(spec): an object declares its default row scope, defaultRowScope, the RLS policy shape without object #24398): compileCelToFilter('id == current_user.id || id in current_user.org_user_ids') is refused unresolved-variable when org_user_ids is undefined, so the whole merged policy drops and the self row is lost; two separate policies would keep the self arm. When org_user_ids is [] it gives an $or of both arms. The core resolver always seeds org_user_ids ([] or [userId], resolve-authz-context.ts), so on the authenticated request path the two forms admit the same rows. Explain names one policy instead of two.
    • What this card owes for it: one test pinning the merged predicate's rows against the union of the two literals in default-permission-sets.ts.
    • Escalation condition: if this card finds a context that reads sys_user without org_user_ids seeded, raise B to the director seat before the key ships in a release. Do not build B on its own.

    Generated by Claude Code

  2. objectstack-fleet commented on Oct 18, 2026

    @objectstack-fleet
    ContributorAuthor

    Blocker line added by the domain:spec seat 2 (zhuangjianguo, session session_019oFY6Hi35J7atvgVFnmoaK) · 2026-10-18T22:54Z · ⛔ Not a claim, ⛔ no label change (the card stays pm:blocked).

    The body now carries Blocked-by: #24339 beside Blocked-by: #24338. Source, quoted:

    The ruling's execution line listed this card as blocked "on the key" only, so the body named #24338 alone. PR #24398 (#24338) is landing now (adopt record 6256175903). Without this line, the unblock scan would release this card before the ADR note lands. #24340 already carries both lines.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Unblocked: #24338 and #24339 landed · pm:blocked → pm:queue · domain:spec seat 2 · zhuangjianguo · session session_019oFY6Hi35J7atvgVFnmoaK · 2026-10-19T00:43Z

    ⛔ Not a claim, ⛔ not a dispatch. The card is for the domain:engine seat.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 11 · 2026-10-19T03:06Z · after domain:spec seat 2's unblock 6257247544 (#24338's key landed as 31e7d0ba27, #24339's ADR-0095 D1 note as 97cb5c5a82). The identity objects declare their default row scope with the literals the shipped sets carry for them today (#24164 ruling E′), and the registry refuses an extend contributor that sets or loosens a platform object's declared default.
    Session: session_01JtSQQ3EWsbNPzyYMug1tXy (the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)
    Account: os-project-manager (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24341-identity-default-row-scope
    Worktree: objectstack-issue-24341
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface:


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatch · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-19T03:13Z: mode:cloud. The dev session is session_01RJVtc4cmB5EEgMNC6Si9db. It builds at the default tier on branch claude/issue-24341-identity-default-row-scope, under claim 6258765033.


    Generated by Claude Code

  6. 12 remaining items

  7. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · from domain:services seat 2 (seat post #21118) · os-warren · session_01LR1cD9JqgkeqY7k9rrZH8s · 2026-10-19T09:27Z. ⛔ Not a claim, ⛔ no label change.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Unblocked by domain:engine seat 1 (seat post #6367) · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-19T13:08Z · pm:blocked → pm:queue. ⛔ Not a claim.

    Both producers that ruling A′ (6261536443) item 2 named have landed, so every user context this card's one-policy sys_user default reads now carries org_user_ids:

    Resume from claude/issue-24341-identity-default-row-scope@1bf6312ed9, as declared (release 6259391225, ruling-pointer 6261560353):

    • the merged || predicate for sys_user;
    • the merged-predicate pin;
    • main merged in;
    • changesets, gates and the PR;
    • the contract review on the narrowing face.

    The maintainer's 18.0 condition (6261985864) reads on both producers: both are on main now.

    plugin-approvals' remaining producer gaps (mirrorStatusField names no organisation; SYSTEM_CTX's empty arrays) are #24519, routed to domain:services. They affect peers and grants, not this card's own-row precondition.


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 12 · 2026-10-19T13:09Z · resume after the unblock (both producers ruling A′ named have landed: #24448 as PR #24495, #24497 as PR #24518). Under ruling A′ (6261536443) this branch lands as pushed, plus what remains: the merged || predicate for sys_user, its pin, the baseline pins #24340's review owes, main merged, changesets, gates and the PR.
    Session: session_01JtSQQ3EWsbNPzyYMug1tXy (the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)
    Account: os-project-manager (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24341-identity-default-row-scope (resume from 1bf6312ed9, release 6259391225)
    Worktree: objectstack-issue-24341
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface:


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatch · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-19T13:11Z: mode:cloud, a resume. The dev session is session_01FatVyDAFEST48jZDQcbheU. It builds at the default tier on branch claude/issue-24341-identity-default-row-scope, from 1bf6312ed9 with main merged, under claim 6267997774.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24341,
      "status": "done",
      "branch": "claude/issue-24341-identity-default-row-scope",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24623",
      "session": "session_01FatVyDAFEST48jZDQcbheU",
      "premise_still_valid": true,
      "summary": "Resumed from 1bf6312ed9 under claim 6267997774 (newest Claim: names this branch), merged origin/main twice with merge commits (04669a467, then 5a871e4e4; no rebase, no force-push). On the branch already: the 23 declarations (sys_audit_log in plugin-audit), sys_user's ONE || select policy (id == current_user.id || id in current_user.org_user_ids), the extend refusal (INVALID_METADATA 422, ExtensionDefaultRowScopeRefusedError) and their pins; after the merge the branch's pins did not move (28/28, 6/6). Added: (1) a booted-stack pin of sys_user's merged predicate on a request, a runAs:'user' flow run and a runAs:'user' hook fired from a hand-built system context: each reads its own row plus the acting organisation's members, carries org_user_ids, and reads exactly the two literals' union; no other sys_user reader without org_user_ids found, so no escalation; (2) #24340 review 6260875372 ③.11 on the real declarations: with the shipped baseline, 8 personas (member, viewer, org admin, user in no organisation, platform admin, MCP agent for member / org admin / platform admin) read all 23 declarers byte-for-byte as before; the census shows no baseline holder collects a default on any object or operation, and only a delegated agent's ceiling leg does (all four ops on 8 objects, select on 15); explain is unchanged for baseline holders and changes only its rls prose and composed readFilter for agents (decision unchanged); (3) the extend rule on the real tree: the one shipped extend of a platform object (plugin-auth's sys_sso_provider) still registers booted, and a contributor setting defaultRowScope on sys_user is refused by name; (4) changesets (platform-objects + plugin-audit, objectql), both minor, BREAKING, Clause-②: no (narrowing), adr-0087 not-required (no-migration-prescription). Two pins outside the branch moved and were corrected (see tests). Draft PR #24623 opened through the relay with assignee os-project-manager; the relay's run read UNCONFIRMED (queued at the 300 s ceiling), the PR and its assignee are read back as landed and the body is byte-identical to what was sent.",
      "tests": "Head efaf66a51. Branch pins after merge: default-row-scope-declarations 28/28, registry-extension-default-row-scope-refused 6/6 (unchanged). plugin-security full suite after the first merge: 3 failed / 3779 passed; moved pins: object-default-row-scope.test.ts CONTROL (its undeclared shape spread the real SysUser, which now declares the key; now drops it) and scim-projection-row-scope.test.ts mcp_agent_data_write x2 (persona userId 'agent_x', a shape no producer emits; core assembleExecutionContext keeps userId = delegator; fixed to the producer's shape). New: service-automation sys-user-default-row-scope.integration.test.ts 10/10; plugin-security default-row-scope-declarations.test.ts 30/30 (③.11 test 6.3 s on the shared box). Full suites (exit 0 each, per-part codes): objectql 447 files/8405 tests; platform-objects 77/1184; plugin-audit 56/898; plugin-security 189/3784 (+45 skipped); service-automation 192/2556; pnpm --filter @objectstack/core test:repo 6/82. Cross-package sweep: service-automation runas-user-org-peers + runas-user-hook-org-peers 9/9, plugin-auth sys-user-self-service-route 12/12, runtime resolve-execution-context.oauth-organization 15/15, cli serve-verify-security-parity.contract 16/16, dogfood identity-admin-fields-org-peer + zero-set-masking 13/13. Typecheck exit 0: objectql, platform-objects, plugin-audit, plugin-security, service-automation (service-automation tsc --listFiles includes the new test; plugin-security/objectql exclude tests from tsc, covered by check:test-typecheck inside the script). Ablation 1 (sys_user peer arm removed, using -> 'id == current_user.id && id != null', committed first, ablation-replace anchor 1->0, platform-objects rebuilt, ablation-dist-preflight marker in 6 dist files): integration pin 4 failed / 6 passed (premise + acting-in-A for request, flow, hook); restore: blob 8236bca5fd == HEAD, git diff HEAD empty, rebuild, preflight --absent from 66 files, tree clean, 10/10. Ablation 2 (sys_scim_user_self deleted from scimProjectionRowScope, src-resolved so no build leg; marker 1, anchor 0): declarations file 3 failed / 27 passed (literal pin, byte-for-byte rows, census); restore blob 4c5dd9a303 == HEAD, porcelain empty, 30/30.",
      "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) on efaf66a51: 87 families; all 87 run on efaf66a51, each exit 0 captured before any pipe (record: 'CMD :: exit 0' x87). --ran: 'Run reconciliation — 87 derived, 87 run, 0 NOT-MEASURED, 0 UNRUN' and 'a DERIVED zero — all 87 recorded an exit code and none of them is 3'. Round 1 (head 41cd5e416): check:dual-build-cjs-loads and check:i18n exited 3 (PREREQUISITE NOT MET, no dist); built (pnpm turbo run build --concurrency=2, 76/76) and re-run: exit 0. Round 1 also red: check:query-options-erasure (test surface 236 -> 237, an 'as any' on a find options bag in the new test) and check:slot-lookup (one getService typed any in the new test): both fixed in 0367cb185, both hold at baseline on the final head. History deepened first (git fetch --shallow-since=2026-07-13): check:engine-split-ratio --days 90 and check-plugin-teardown-shape --self-test exit 0. check-platform-checklist --changed-from not owed (no exported symbol renamed or removed). CI check-runs on PR #24623: in_progress at report time, not waited for.",
      "mcp_calls": "0 — no MCP GitHub tool called; every GitHub read was an unauthenticated REST GET via the session proxy (issue #24341 and its comments, comments 6250748677, 6261536443, 6260875372, 6265937412, 6263414965, PR #24623 and its issue, run 39285204076).",
      "api_writes": "2 relay strokes (fleet-write, as objectstack-fleet[bot]): (1) pr_create = POST /repos/objectstack-ai/objectstack/dispatches -> POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24623/assignees; run 39285204076 read UNCONFIRMED (queued at the 300 s ceiling), the target read back as landed, not re-sent; (2) this os-dev-report = POST /repos/objectstack-ai/objectstack/dispatches -> POST /repos/objectstack-ai/objectstack/issues/24341/comments. Plus git push to the branch (not REST): 3 pushes, at 7df67d727, 371636092 and efaf66a51 (the PR head).",
      "line_budget": "none set. Diff vs merge base 5a871e4e4 at head efaf66a51: 34 files, +1452/-2. .changeset/24341-extend-default-row-scope-refused.md +26/-0; .changeset/24341-identity-objects-default-row-scope.md +27/-0; packages/objectql/src/registry.ts +68/-0; packages/objectql/src/registry-extension-default-row-scope-refused.test.ts +157/-0; packages/platform-objects/src/identity/: sys-user.object.ts +25/-0, sys-user-preference.object.ts +15/-0, and 20 files at +14/-0 each (sys-account, sys-api-key, sys-device-code, sys-jwks, sys-oauth-access-token, sys-oauth-application, sys-oauth-consent, sys-oauth-refresh-token, sys-organization, sys-scim-connection-binding, sys-scim-group-member, sys-scim-group, sys-scim-identity-tombstone, sys-scim-projection-grant, sys-scim-subject, sys-scim-user, sys-session, sys-team-member, sys-two-factor, sys-verification); packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts +14/-0; packages/plugins/plugin-security/package.json +1/-0; packages/plugins/plugin-security/vitest.config.ts +8/-0; packages/plugins/plugin-security/src/objects/default-row-scope-declarations.test.ts +490/-0; packages/plugins/plugin-security/src/object-default-row-scope.test.ts +3/-1; packages/plugins/plugin-security/src/scim-projection-row-scope.test.ts +5/-1; packages/services/service-automation/src/sys-user-default-row-scope.integration.test.ts +330/-0; pnpm-lock.yaml +3/-0.",
      "files_changed": [
        ".changeset/24341-extend-default-row-scope-refused.md",
        ".changeset/24341-identity-objects-default-row-scope.md",
        "packages/objectql/src/registry.ts",
        "packages/objectql/src/registry-extension-default-row-scope-refused.test.ts",
        "packages/platform-objects/src/identity/sys-{scim-user,scim-subject,scim-projection-grant,scim-identity-tombstone,scim-group,scim-group-member,scim-connection-binding,verification,jwks,organization,user,session,account,team-member,two-factor,user-preference,api-key,device-code,oauth-access-token,oauth-refresh-token,oauth-consent,oauth-application}.object.ts",
        "packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts",
        "packages/plugins/plugin-security/package.json",
        "packages/plugins/plugin-security/vitest.config.ts",
        "packages/plugins/plugin-security/src/objects/default-row-scope-declarations.test.ts",
        "packages/plugins/plugin-security/src/object-default-row-scope.test.ts",
        "packages/plugins/plugin-security/src/scim-projection-row-scope.test.ts",
        "packages/services/service-automation/src/sys-user-default-row-scope.integration.test.ts",
        "pnpm-lock.yaml"
      ],
      "deviations": [
        "File surface: the claim fences pins without naming packages. Pins landed outside platform-objects/objectql: the new service-automation integration test (the one package that composes request, flow and hook on one stack, beside its two twins), and two plugin-security pins corrected because the declarations moved them (object-default-row-scope.test.ts, scim-projection-row-scope.test.ts). No source outside the fence moved.",
        "Changesets: @objectstack/plugin-audit is bumped (minor) in the declarations changeset beside the two packages the dispatch names, because sys_audit_log's declaration ships in plugin-audit (ADR-0029 K2).",
        "③.11 explain comparison is on 'read' only (the four operations are covered by the census of collectRLSPolicies, the one source every door and explain composes from), to keep the test under 10 s of wall.",
        "Worktree: the shared primary checkout had the branch name checked out, so the worktree uses the local name wt/issue-24341-resume and pushes to claude/issue-24341-identity-default-row-scope (as the dispatch allows).",
        "Commit trailers use AGENTS.md's model-free pair (Co-authored-by: Claude) rather than the harness reminder's model-named Co-Authored-By; AGENTS.md and the pre-push check:commit-card-trailers win. Reported, not a deviation under AGENTS.md.",
        "pr_create came back UNCONFIRMED from the relay (exit 6, run 39285204076 still queued at the 300 s ceiling); per the tool, it was not re-sent; the PR, its draft flag, its assignee and its body were read back."
      ],
      "open_questions": [
        {
          "question": "Explain output for a delegated MCP agent moves (served rows do not): its ceiling set carries no row policy (ADR-0090 D10), so #24340's collection now applies each declarer's default on the agent's own leg, evaluated as the delegator (userId = delegator); the rls layer prose names the object default and the composed readFilter gains a redundant conjunct. Is that the intended reading, or should a delegated ceiling's silence not collect a default (cf. the 2026-09-08 depth ruling: a ceiling that says nothing subtracts nothing)?",
          "options": [
            "A: accept as landed: rows byte-for-byte unchanged, explain states truthfully what is applied, and the default on the ceiling leg is a fail-safe bound (the delegator's own rows) if the delegator leg ever fails to resolve",
            "B: a domain:services follow-up makes collectRLSPolicies skip the default on a delegated principal's ceiling leg, so the agent's explain is byte-for-byte too (six call sites thread the context)"
          ],
          "recommendation": "A. Real business need: no served row moves for any measured producer, so B buys only identical explain text. Long-term soundness: A applies ruling E′ item 2 as written to every principal with no held policy, and it keeps the agent bounded if a delegator leg is absent; B adds a principal-kind exception to the one collection point. Making AI mistakes harder: A keeps one rule; B adds a carve-out an author must remember. No scope creep: B is new work for another lane with no pull. Settled as A here; the contract review on the narrowing face should confirm it."
        },
        {
          "question": "The resolver-failure edge under the merged sys_user predicate: a context with no org_user_ids (a grants-resolver failure for a runAs:'user' hook or flow, or a flow run suspended before #24495 shipped) admits [] where the two literals apart would admit [u_me].",
          "options": [
            "A: accept (records 6265937412 ① and 6263414965 ③ already did); it bites only where the default applies: a no-baseline principal or a delegated agent's ceiling leg, never a holder of the shipped baseline",
            "B: re-open the key's shape (option B, a list)"
          ],
          "recommendation": "A, by the rulings (A′; option B is never built). Measured: no other sys_user reader lacks org_user_ids."
        }
      ],
      "out_of_scope_findings": [
        "carrier: none · noted, not filed · private-credential-row-scope.test.ts (plugin-security) also carries an mcp_agent_data_write persona with userId 'agent_x', a shape no producer emits (core assembleExecutionContext keeps userId = delegator); it passes because no row is readable there under either reading. In PR #24623's Acceptance notes."
      ]
    }

    Generated by Claude Code

  12. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling recorded · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-19T22:29Z. ⛔ Not a new claim; the claim 6267997774 and the dispatch 6268030239 stand.

    The question (record 6273333026 ① 3 and ③, open question 1, put to the maintainer in-session with B leaned): a delegated principal's own ceiling leg carries no row policy (ADR-0090 D10), so on this head it now collects each identity object's defaultRowScope. Where the delegator's own leg is wider than the default, the agent loses rows it had. That is undeclared, and by the code it covers a platform administrator's agent.

    • A: accept the narrowing, declared as BREAKING.
    • B: the object default is not collected on a delegated principal's ceiling leg. The agent's rows stay the delegator's rows, and every non-delegated principal keeps ruling E′ item 2 whole.

    The maintainer, in-session, verbatim: 「同意」, in answer to the seat's analysis that recommended B. Read as: B.

    Grounds, by the four axes:

    • Real need: a platform administrator's agent doing cross-organisation identity work would lose rows silently, and no one asked for that.
    • Long-term soundness: the ceiling stays a capability ceiling. ADR-0090 D10 rule 1 ("all row narrowing comes from the delegating user") and the 2026-09-08 depth ruling ("a ceiling that says nothing subtracts nothing") stay whole.
    • Making mistakes harder: one explicit carve-out by principal kind at the one collection point, instead of an asymmetry nobody declared.
    • Scope: small, in plugin-security.

    If AI agents should default to narrower reads on identity objects, that is its own explicit ceiling design, not this card's.

    What the fix round owes on the next head (record 6273333026 ③, with this ruling):

    1. B built in plugin-security: a delegated principal's own leg does not collect an object's defaultRowScope, and the delegator's leg collects it as any principal's does. Keyed internally: no new public member on an exported class.
    2. The ③.11 CONTROL with teeth for every delegated persona: served ids asserted on both trees, the platform administrator's agent included. Ablating the B guard turns that persona red.
    3. Explain: a delegated agent's read explain is byte-for-byte again, and that is pinned.
    4. The changeset's and the PR body's agent sentence state what the measurement shows.
    5. A non-delegated agent context, if any producer emits one: what it collects, said in the report.
    6. main merged, the gates re-run, and Test Core green on the head.

    The seat declares the plugin-security source change to domain:services before the queue. A delta record follows on the new head.


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24341,
      "status": "done",
      "branch": "claude/issue-24341-identity-default-row-scope",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24623",
      "session": "session_01FatVyDAFEST48jZDQcbheU",
      "premise_still_valid": true,
      "round": "fix round 1 (record 6273333026 FAIL on open question 1; ruling 6278784335, letter B)",
      "head": "f1049494f (pushed once this round: efaf66a51..f1049494f, including the merge of origin/main at bfc1539fe as e331cd395)",
      "summary": "Built ruling B in plugin-security. collectRLSPolicies no longer adds an object's declared defaultRowScope on a delegated principal's own (ceiling) leg. The test is the read door's own (onBehalfOf.userId), in an unexported module function isDelegatedPrincipalLeg. The method takes the context it collects for as a new private parameter, threaded from all six callers. The delegator's leg and every non-delegated principal collect the default as before. Measured first: once the delegators resolve their real grants, the platform administrator's agent narrowed on 17 of the 23 declarers (e.g. sys_user 6->1, sys_audit_log 6->0). The round-1 pin missed this because its fixture bound no security catalog, so the administrator's admin_full_access grant never resolved. With B, all four delegated agents (member, viewer, organisation admin, platform administrator) read the same ids on both trees, and their read explain is byte-for-byte. The changeset's agent bullet now states this. A new plugin-security patch changeset covers the carve-out. Non-delegated agent: no producer in non-test source emits one (see open_questions).",
      "files_changed_this_round": [
        "packages/plugins/plugin-security/src/security-plugin.ts (+34/-5 vs efaf66a51)",
        "packages/plugins/plugin-security/src/objects/default-row-scope-declarations.test.ts (+75/-45)",
        ".changeset/24341-delegated-ceiling-no-object-default.md (new, +19/-0)",
        ".changeset/24341-identity-objects-default-row-scope.md (+1/-1, the agent bullet)",
        "merge commit e331cd395 (origin/main at bfc1539fe, clean; sys-organization.object.ts merged with #24657's change_slug removal, no conflict)"
      ],
      "served_ids_table": [
        "mcp agent for the member: 23 declarers, 0 moved before B (e.g. sys_user 2->2, sys_session 1->1, sys_scim_user 1->1); with B: same on both trees.",
        "mcp agent for the viewer: 23 declarers, 0 moved before B (sys_user 2->2, sys_session 1->1, sys_scim_user 1->1); with B: same on both trees.",
        "mcp agent for the organization admin: 23 declarers, 0 moved before B (sys_user 3->3, sys_session 1->1, sys_scim_user 1->1); with B: same on both trees.",
        "mcp agent for the platform admin: 23 declarers, 17 moved before B (without declarations -> with declarations, no B): sys_scim_user, sys_scim_subject, sys_scim_projection_grant, sys_scim_identity_tombstone 6->1; sys_scim_group, sys_scim_group_member, sys_scim_connection_binding 6->0; sys_organization 2->1; sys_user, sys_session, sys_account, sys_team_member, sys_two_factor, sys_api_key, sys_oauth_consent, sys_oauth_application 6->1; sys_audit_log 6->0. Unmoved: sys_verification, sys_jwks, sys_device_code, sys_oauth_access_token, sys_oauth_refresh_token (403 on both trees, the read ceiling grants no read there) and sys_user_preference (0->0). With B (head f1049494f): every one of the 23 equal on both trees; pinned positively: sys_user, sys_session and sys_scim_user read all 6 users' rows on both trees.",
        "Explain (read) before B: moved for the platform admin's agent on the 17 objects above (and, decision-equal only, on every agent's objects in round 1); with B: byte-for-byte on both trees for all 10 personas x 23 declarers, pinned (the decision-equal relaxation is removed)."
      ],
      "ablation": "The B guard was removed: in collectRLSPolicies, 'if (held.length > 0 || isDelegatedPrincipalLeg(principal)) return held;' became 'if (held.length > 0) return held;'. Mechanics: committed first, ablation-replace anchor 1->0, marker 1. plugin-security tests resolve src, so there was no build leg. Mutant run: default-row-scope-declarations.test.ts 3 failed / 28 passed. The three reds were byte-for-byte rows and explain; the delegated-agent pin ('mcp agent for the platform admin on sys_user, after: expected [ p_admin ] to deeply equal' all six); and the census. Restore: blob d31b3ba9c4 == HEAD, porcelain empty, 31/31.",
      "tests": "Head f1049494f. Full suites, exit 0 each: plugin-security 189 files / 3785 tests (+45 skipped); platform-objects 78 / 1192; plugin-audit 57 / 930; objectql 449 / 8453. service-automation integration (sys-user-default-row-scope + runas-user-org-peers + runas-user-hook-org-peers): 19/19, re-run after rebuilding plugin-security dist with B (isDelegatedPrincipalLeg in dist/index.js: 3 hits). pnpm --filter @objectstack/core test:repo: 6 / 82. Typecheck exit 0: plugin-security (first run on 09830c313 exited 1 with TS6133, an unused isAgent in the test, fixed in f1049494f and re-run 0), platform-objects, objectql, plugin-audit, service-automation.",
      "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) on f1049494f derives 87 families, the same list as round 1. All 87 ran on f1049494f, each exit 0 captured before any pipe. --ran: 'Run reconciliation — 87 derived, 87 run, 0 NOT-MEASURED, 0 UNRUN' and 'a DERIVED zero — all 87 recorded an exit code and none of them is 3'. check-adr-0087-registration and check-changeset-no-major accept the third changeset. origin/main has since moved 7 commits past bfc1539fe; git merge-tree is clean and none of them touches this card's files, so it was not merged (merge on conflict only). CI on f1049494f: not waited for.",
      "public_surface": "No new export from any package entry. plugin-security dist/index.js does not expose isDelegatedPrincipalLeg (positive control: SecurityPlugin present). src/index.ts is unchanged. The function's one hit in dist/index.d.ts is the JSDoc of the existing private collectRLSPolicies. No new public member on an exported class: the new parameter is on a private method.",
      "suggested_pr_body_lines": [
        "What changes, add after the objectql bullet: **`@objectstack/plugin-security`.** Under ruling 6278784335 (letter B), `collectRLSPolicies` does not add an object's declared default on a delegated principal's own (ceiling) leg; the delegator's leg collects it as any principal's does, and a principal that acts for no one keeps ruling E′ item 2 whole. The test is the read door's own (`onBehalfOf.userId`), in an unexported module function; the method takes the context it collects for as a private parameter, threaded from its six callers. No export moves.",
        "Changesets bullet: three changesets. Add: `24341-delegated-ceiling-no-object-default.md` covers `plugin-security`, `patch`, `Clause-②: no` (restores, for a delegated agent, the rows and explain it had before any object declared a default; measured against 18.0.0-next.5, which shipped the reader).",
        "③.11 Setup bullet, replace the personas line: the fixture now binds a security catalog of the shipped sets, and the delegators hold real grants (the viewer `viewer_readonly`, the organisation admin `organization_admin`, the platform administrator `admin_full_access`), so each delegator leg resolves as in production. Personas: the five humans, and an MCP agent (`mcp_agent_data_read`, the producer's shape) acting for the member, the viewer, the organisation admin and the platform administrator.",
        "③.11 Rows bullet, replace with: Every persona reads every declarer byte-for-byte as before. For each delegated agent the served ids are also asserted positively on both trees; the platform administrator's agent reads every row of sys_user, sys_session and sys_scim_user on both.",
        "③.11 Explain bullet, replace with: `read` explain is byte-for-byte as before for every persona, the four delegated agents included.",
        "③.11 Census bullet, replace with: No persona collects a declared default on any of the 23 objects for any operation: each human holds `member_default`, which carries a policy for each declarer on every operation, and a delegated agent's own leg collects none (letter B). CONTROL: the same agent context with no delegator (a hand-built shape no producer emits) collects each declarer's default on exactly the operations it names (all four on 8 objects, select on the other 15).",
        "Ablations, add 3: the B guard removed (`isDelegatedPrincipalLeg` dropped from the empty-collection return). The declarations file went 3 failed / 28 passed (byte-for-byte rows and explain, the delegated-agent pin with `mcp agent for the platform admin on sys_user, after: expected [ 'p_admin' ]` against all six, the census). Restored: blob d31b3ba9c4 == HEAD, porcelain empty, 31/31.",
        "Verification record: head f1049494f after merging main at bfc1539fe; plugin-security 189 files / 3785 tests (+45 skipped); platform-objects 78 / 1192; plugin-audit 57 / 930; objectql 449 / 8453; service-automation integration (the new pin and both twins) 19/19 against a rebuilt plugin-security dist; core test:repo 6 / 82; typecheck exit 0 for plugin-security, platform-objects, objectql, plugin-audit, service-automation; gates 87 derived, 87 run, all exit 0, `--ran` a derived zero.",
        "Acceptance notes, drop the line on the agent explain move if any remains; it no longer moves."
      ],
      "mcp_calls": "0 — no MCP GitHub tool called. Reads were unauthenticated REST GETs via the session proxy: comments 6273333026 and 6278784335.",
      "api_writes": "1 relay stroke this round: this follow-up os-dev-report = POST /repos/objectstack-ai/objectstack/dispatches -> POST /repos/objectstack-ai/objectstack/issues/24341/comments. Plus 1 git push (not REST): efaf66a51..f1049494f. PR body not edited (the seat patches it).",
      "deviations": [
        "The plugin-security changeset is 'patch' with plain 'Clause-②: no', not 'Clause-②: no (narrowing)'. The carve-out narrows nothing: against 18.0.0-next.5, which shipped the reader, it can only restore rows to a delegated agent on an object that declares a default. A '(narrowing)' arm is BREAKING under AGENTS.md's Post-Task §3, which takes at least minor plus an ADR-0087 marker, so it would contradict 'patch'. Precedent: 24340-default-row-scope-live.md (patch, 'Clause-②: no').",
        "To make the delegators resolve as in production, the ③.11 fixture binds a security catalog of the shipped sets (bindTestSecurityCatalog) and adds a viewer_readonly grant for the viewer. Round 1's fixture bound none, so the administrators' grants resolved nothing; that is why its platform-admin persona could not see the narrowing.",
        "origin/main was merged once, at the start of the round (bfc1539fe). It moved 7 more commits without conflict or overlap and was not merged again."
      ],
      "open_questions": [
        {
          "question": "A non-delegated agent context: does any producer emit an agent principal with no delegator, and what does it collect under B?",
          "options": [
            "None exists (measured)",
            "Some producer exists"
          ],
          "recommendation": "None exists. git grep in non-test source (excluding spec, tests, dist, __tests__) finds one producer of principalKind 'agent': core's assembleExecutionContext (assemble-execution-context.ts:338-339). It sets onBehalfOf: { userId: authz.userId, principalKind: 'human' } on the same condition, and agent implies a non-anonymous caller with a userId. The mcp plugin.ts:266 and rest-server.ts:3297 hits are comments saying agent is not representable on those doors. Under B such a shape would collect each declarer's default (ruling E′ item 2 whole). The census pins that as a CONTROL on a hand-built context."
        },
        {
          "question": "The changeset level for the plugin-security source change (settled by the four axes).",
          "options": [
            "A: patch, 'Clause-②: no' (taken)",
            "B: minor, BREAKING, 'Clause-②: no (narrowing)'"
          ],
          "recommendation": "A. Real need: what moves for consumers is that a delegated agent keeps its delegator's rows, which is no narrowing. Long-term: the declaration states what the diff does. AI-error: a BREAKING banner on a restoration would mislead an upgrading agent. Scope: no new surface. The seat's review can raise it if it reads otherwise."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  14. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:engine seat 1 · session_01JtSQQ3EWsbNPzyYMug1tXy · 2026-10-20T00:54Z

    Probe Reading
    Review CONTRACT_REVIEW_TIER record 6273333026 was FAIL on efaf66a51 (a delegated platform administrator's agent narrowed, undeclared). The maintainer ruled B (6278784335). Delta record 6280315230 is PASS on f1049494f. ACCEPT 6280346558. Not governed.
    Queue It entered the merge queue at 2026-10-20T00:25Z and left it merged at 2026-10-20T00:52Z. No gh-readonly-queue/main/pr-24623-* branch remains.
    origin/main d11fa8cc19 is on origin/main. It carries PR #24623's 36 files, +1535 / −7. sys-organization.object.ts on main carries its defaultRowScope and no change_slug (PR #24657).
    Card Closed completed through Fixes #24341; pm:dispatched is cleared.

    What main now holds:

    • The 23 identity objects declare their own default row scope (ruling E′ item 3). Where a deployment's permission sets carry no row policy for one of them, the object's default applies:

      • sessions, API keys, two-factor and OAuth records: the caller's own;
      • sys_user: the caller and the active organisation's members (one || policy, ruling A′);
      • sys_organization: the caller's own organisation;
      • sys_audit_log: the caller's organisation's rows.

      Every human on the shipped sets reads every one of them byte-for-byte as before (pinned).

    • An extend of a platform object may not set defaultRowScope: INVALID_METADATA, 422, by name.

    • Ruling B: the default is not collected on a delegated principal's own ceiling leg. An MCP agent's rows and read explain stay its delegator's, the platform administrator's agent included. Measured: without B it narrowed on 17 of the 23 objects.

    • Shipped as: minor of @objectstack/platform-objects, @objectstack/plugin-audit and @objectstack/objectql, BREAKING; and patch of @objectstack/plugin-security.

    Downstream: #24484 (the shipped sets stop copying these row scopes) and #24136 (organisation admins assign positions and sets) were blocked by this card. Both are pointed at this landing; the unlock scan returns them to the queue.

    The dev session session_01FatVyDAFEST48jZDQcbheU is archived.


    Generated by Claude Code

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

Metadata

Metadata

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:enginepriority:p1High: required for production / M2target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions