Skip to content

service-automation: a runAs:'user' flow's data context carries no org_user_ids, so sys_user's org-members row scope cannot resolve and the run sees only the acting user's own sys_user row #24448

Closed

Description

@objectstack-fleet

Filing gate ①, class (a): a context producer breaks the contract its consumer reads. Found by the #24341 dev (report 6259053044, out-of-scope finding 1, and the escalation it stopped on); filed by domain:engine seat 1 (seat post #6367). ⛔ Not a claim.

Dedupe words: runAs user org_user_ids flow context; resolveRunDataContext org peers; sys_user_org_members flow; hook runAs user context peers.

Filed by domain:engine seat 1 · os-project-manager · session_01JtSQQ3EWsbNPzyYMug1tXy


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p1 + target:v18 · bug · domain:services · area:access · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-19T04:04Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-19T06:49Z
    Session: session_01LR1cD9JqgkeqY7k9rrZH8s
    Account: os-warren (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24448-run-context-org-user-ids
    Worktree: objectstack-issue-24448
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    File surface: packages/services/service-automation/src/runtime-identity.ts (resolveRunDataContext's runAs:'user' branch) and the place in service-automation that builds the run's identity from its trigger (where positions and permissions are carried today), with tests beside them; the cross-lane rider packages/objectql/src/hook-run-as.ts (deriveHookApi's 'user' pin) and its test, declared to domain:engine seat 1 on #6367, taken only if the failing probe confirms the hook path and its fix needs no edit in packages/core; ⛔ not packages/plugins/plugin-approvals/** (held by #24375 this round) and ⛔ not packages/core/**; a changeset (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (the default tier; dispatch-gates --tier printed no path-derived mandate for this surface)
    Clause-②: yes
    Responsibility: service-automation's resolveRunDataContext (this lane), and on the hook path objectql's deriveHookApi | none: the core resolver resolveAuthzContext seeds org_user_ids on the request path, and neither producer calls it | any runAs:'user' flow or hook that reads an object whose policy reads current_user.org_user_ids; live today on the shipped sys_user_org_members policy
    Thread-read: 6259397801
    Serial constraints cleared: no open PR (a file scan of all 14 open PRs at this claim) and no pm:dispatched claim (21 read) names service-automation, hook-run-as or resolve-authz-context. #24321 (p1, pm:queue, unclaimed) reaches runtime-identity.ts too (seat 1's note on #6021 §2), so it is serialised behind this card. Seat 1's in-flight #24340 (plugin-security) is disjoint in files; under the director seat's A′ reading on #24449 (not yet ruled) this card is its precondition.

    Why this card first. It is the producer the director seat's recommended A′ on #24449 names as #24340's precondition, and it lands on its own merits under either letter (#24449, "After the ruling"). #24321 shares its file and waits behind it.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling-pointer · #24449 ruled A′ at 6261536443 (batch #370 item 1, the maintainer's 「24449 24289 同意」) · director seat (summon #43 continuation 5, session_01QJC7i981TSs9L1CGJK59Ax) · 2026-10-19T06:58Z

    For the claiming seat (claim 6261453614): this card is now the precondition of #24341, because the reader #24340 is already on main (7702f3ca2b). What the ruling binds: resolveRunDataContext's runAs:'user' branch seeds org_user_ids the way core/src/security/resolve-authz-context.ts does ([] or the organisation's peer list); the safety plugin does not backfill; a pin that a runAs:'user' flow reading sys_user sees the colleagues the request path sees.

  4. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24448,
      "status": "done",
      "branch": "claude/issue-24448-run-context-org-user-ids",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24495",
      "session": "session_01LR1cD9JqgkeqY7k9rrZH8s",
      "premise_still_valid": true,
      "summary": "Probe confirmed on the real stack (ObjectQL + SecurityPlugin + driver-sql): a runAs:'user' flow reading sys_user answered [usr_me] where the same user's request answered [usr_me, usr_peer]. Run setup (AutomationEngine.resolveRunContext) now seeds org_user_ids from the plugin's grants bridge, which already called resolveUserAuthzGrants (the request path's resolver) and dropped the field; it is seeded also when the door already forwarded permissions (only the peer list is taken then), a carried list is kept, and resolveRunDataContext copies it to every data node. The hook rider (A4) was probed and holds (request-fired runAs:'user' hook saw [usr_me, usr_peer], approvals-mirror-fired saw [usr_me]) but its repair lives in objectql engine.ts / hook-wrappers.ts or plugin-approvals, outside the declared rider surface, so it is cut and reported below for domain:engine.",
      "tests": "Probe before the fix (6ca644728, test only): vitest run src/runas-user-org-peers.integration.test.ts -> 'Tests 2 failed | 2 passed': record-change shape and doors' shape both 'expected [ 'usr_me' ] to deeply equal [ 'usr_me', 'usr_peer' ]'; control and no-organization green. After the fix (tree = 373edbc79): 4 targeted files 69/69 passed (VERDICT command-exit 0). Full package at head 304dea50d (merges origin/main ae6bd1a69; closure rebuilt 'Tasks: 30 successful, 30 total'): 'pnpm --filter @objectstack/service-automation typecheck' exit 0 ('check:test-typecheck: OK'); 'vitest run --maxWorkers=2' -> 'Test Files 190 passed (190)', 'Tests 2541 passed (2541)', VERDICT command-exit 0. Ablations (subjects imported relatively from src by the tests, so no dist leg; scripts/ablation-replace.mjs, anchor 1 -> 0, blob changed, restored blob == HEAD, git diff HEAD empty): (1) delete 'if (orgUserIds) out.org_user_ids = orgUserIds;' -> 2 red, '[ 'usr_me' ]' vs '[ 'usr_me', 'usr_peer' ]'; (2) negative pin, bridge returns the cross-organization union of the user's peers -> 3 red, '[ 'usr_me', 'usr_other', 'usr_peer' ]'; (3) old condition (seed only when permissions absent) -> 3 red (integration doors' shape + 2 unit pins). Hook probe (temporary file, deleted, never committed): 'PROBE request-fired hook saw: [\"usr_me\",\"usr_peer\"]', 'PROBE mirror-fired  hook saw: [\"usr_me\"]'.",
      "gates": "Local, head 304dea50d: 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack' derived 68 commands; each run with its exit captured before any pipe: 67 exit 0; 'pnpm check:dual-build-cjs-loads' exit 3 (PREREQUISITE NOT MET, it reads every package's dist/) = NOT MEASURED, left to CI. '--ran' exit 0: '68 derived famil(ies) accounted for - 67 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)'. CI check-runs on 304dea50d at report time (37): success - Auto Label, Check Documentation Links, Check PR Size, Dogfood Regression Gate, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, The card this PR closes must claim this branch, filter; skipped - Build Docs, Console Pin Gate, Dogfood Regression Gate (matrix shard), Dogfood Verify CLI, Packed-tarball smoke (opt-in), Temporal Conformance (live PG + MySQL); in_progress - Build Core, Check Changeset, Flag docs affected by code changes, Governed Surface Queue Guard, Lint (8 jobs), Test Core (1/6..6/6), Type Check (4 jobs). Not awaited.",
      "line_budget": "390 (371 additions + 19 deletions, 7 files, as GitHub counts PR #24495)",
      "files_changed": [
        ".changeset/24448-run-context-org-user-ids.md",
        "packages/services/service-automation/src/runtime-identity.ts",
        "packages/services/service-automation/src/engine.ts",
        "packages/services/service-automation/src/plugin.ts",
        "packages/services/service-automation/src/runas-user-org-peers.integration.test.ts",
        "packages/services/service-automation/src/builtin/crud-runas.test.ts",
        "packages/services/service-automation/src/runas-grant-resolution.integration.test.ts"
      ],
      "deviations": [
        "Surface reading: the identity edits land in engine.ts (resolveRunContext, FlowUserGrants) and plugin.ts (the grants bridge) as the claim's 'place in service-automation that builds the run's identity from its trigger'; nothing in packages/spec, packages/core, packages/objectql or plugin-approvals was edited.",
        "The carrier for the list on the run context is a service-automation-local SeededRunContext type over spec AutomationContext, because packages/spec is outside the surface; declaring the key on AutomationContext is the named alternative (PR Acceptance notes).",
        "An existing pin changed meaning: crud-runas.test.ts 'does NOT re-resolve when the trigger already carried permissions' asserted called === 0; the resolver is now asked for the peer list only, so it is renamed and re-pinned (envelope verbatim, peers seeded), with a new case that a context carrying both resolves nothing (called === 0).",
        "NOT MEASURED locally: pnpm check:dual-build-cjs-loads (exit 3, needs a whole-repo build) and test:repo (no repo-project test edited); both run in CI.",
        "Not re-merged after 304dea50d: origin/main was 5 commits ahead at PR open (45bec43cd), none touching service-automation or the run identity path.",
        "Attribution: commits carry the AGENTS.md model-free trailer pair and the PR body the session-URL footer, instead of the harness reminder's model-named trailer and footer.",
        "Hook rider not taken (see out_of_scope_findings): the fix site is outside hook-run-as.ts + its test."
      ],
      "mcp_calls": "0 - no MCP GitHub tool was called",
      "api_writes": "2 fleet-write relay strokes (each one POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/24495/assignees ['os-warren']; (2) comment -> POST /repos/objectstack-ai/objectstack/issues/24448/comments (this report). Plus git push of the branch (not REST).",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a · reach: named producer - plugin-approvals' hand-built { isSystem: true, userId } writes (approval-service.ts, approval-node.ts) fire runAs:'user' hooks through ScopedContext.withRunAs('user') (packages/objectql/src/engine.ts), which pins { ...executionContext, isSystem: false } with no positions, no permission sets and no org_user_ids · evidence: temporary probe on the real stack (ObjectQLPlugin + identity objects + SecurityPlugin + driver-sql), a runAs:'user' hook reading sys_user through ctx.api: fired from the request-resolved context saw [usr_me, usr_peer], fired from { isSystem: true, userId, tenantId } saw [usr_me] · lane: domain:engine (the triage's rider, cut here: repair needs an async resolution through resolveUserAuthzGrants in engine.ts / hook-wrappers.ts, or the producers seeding the envelope) · dedupe words: runAs user hook withRunAs org_user_ids; hook pinned context positions permissions hand-built system context; approvals mirror runAs user hook sys_user peers",
        "carrier: none · a runAs:'user' run's data context also lacks accessible_org_ids, systemPermissions and posture that the request path carries (the group posture's wall reads accessible_org_ids); read-only, not probed · noted in PR Acceptance notes, not filed",
        "carrier: none · declaring org_user_ids on spec AutomationContext would let the REST/action doors forward the request's list and skip the run-setup re-resolution; not needed for correctness · noted in PR Acceptance notes, not filed"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Rework record (patch round 1) · domain:services seat 2 · os-warren · session_01LR1cD9JqgkeqY7k9rrZH8s · 2026-10-19T08:20Z. Same claim (6261453614), same branch, the same dev resumed.

    Review of report 6262726497 and PR #24495 at head 304dea50d. The code is accepted as it stands; one item is owed.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24448,
      "status": "done",
      "patch_round": 1,
      "branch": "claude/issue-24448-run-context-org-user-ids",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24495",
      "session": "session_01LR1cD9JqgkeqY7k9rrZH8s",
      "premise_still_valid": true,
      "summary": "Patch round 1: the changeset now declares '@objectstack/service-automation': minor and ends with the seat's Clause-② yes (widening) line. The rest of the file is byte for byte unchanged (git diff: exactly 2 lines). Ruling A' item 2: the bridge meets it. plugin.ts:1292-1300 returns resolveUserAuthzGrants(engineQl, userId, { tenantId, bypassGrantsCache: true }).org_user_ids unchanged. That value starts as [userId] at resolve-authz-context.ts:1306 and becomes the organisation's members plus the user at :1446-1455 when a tenant is given, which is exactly what resolveAuthzContext copies onto an authenticated request at :737. The [] seed at :475 is the anonymous one, and a runAs:'user' run never reaches it because a user-less run is refused first. engine.ts:6456-6457 stores the value and runtime-identity.ts:273 passes it to the data context; plugin-security is untouched, so nothing is backfilled. No code change was made. One edge for the seat to judge: a run whose resolver throws (or has no ObjectQL), or that was suspended before this release, carries no list at all rather than []. Under the merged one-policy predicate that drops the self arm too (fail closed). The core resolver gives no [] on a failed read either: it raises AuthzStoreUnavailableError (resolve-authz-context.ts:27-30).",
      "tests": "No code moved in this round (changeset only), so the package suite was not re-run. Its last reading stands at 304dea50d: typecheck exit 0, 'Test Files 190 passed (190)', 'Tests 2541 passed (2541)'. CI on 7e6f99f7a re-ran it: Test Core success, all six shards success.",
      "gates": "Local, head 7e6f99f7a (git rev-parse --short HEAD). Closure restored from the turbo cache ('Tasks: 30 successful, 30 total', 'Cached: 30 cached, 30 total', VERDICT command-exit 0). 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack' (no paths) derived the same 68 commands as round 0. Each ran in the foreground in six chunks, with its exit captured before any pipe: 67 exit 0; 'pnpm check:dual-build-cjs-loads' exit 3, PREREQUISITE NOT MET (reads every package's dist/), NOT MEASURED. '--ran' exit 0: 'Run reconciliation — 68 derived, 67 run, 1 NOT-MEASURED, 0 UNRUN'. Changeset families: check-adr-0087-registration --base origin/main '✓ this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)'; check-changeset-no-major --base origin/main '✓ This diff introduces no `major` bump'. Its LEVEL AXIS reads the PR body's declaration and reports NOT APPLICABLE locally, so the PR body's Clause-② line (the seat's, now 'yes (widening: ...)') is what CI's Check Changeset judges. check-empty-changeset '✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added)'. CI check-runs on 7e6f99f7a (47, all completed): 36 success, including the five required contexts (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard) and Check Changeset (two runs, both success); 8 skipped (Auto Label and Check PR Size duplicates, Build Docs, Console Pin Gate, Dogfood Regression Gate shard matrix, Dogfood Verify CLI, Packed-tarball smoke, Temporal Conformance); 3 cancelled (duplicate runs of No other open PR may claim the same single-writer path, Part-of PR must not also close its card, and The card this PR closes must claim this branch; each also has a success run on this sha).",
      "line_budget": "390 (371 additions + 19 deletions, 7 files, as GitHub counts PR #24495 at 7e6f99f7a; the changeset edit replaced 2 lines inside a file the PR adds)",
      "files_changed": [
        ".changeset/24448-run-context-org-user-ids.md"
      ],
      "deviations": [
        "The round-0 worktree had been removed after the PR opened. It was re-created on the same local branch, whose head equalled the remote's (304dea50d), before the edit, and removed again after this report.",
        "PR body not touched by this round; read once at report time, its line 2 already carries the seat's corrected 'Clause-②: yes (widening: ...)' line, which CI's Check Changeset (success on 7e6f99f7a) judged."
      ],
      "mcp_calls": "0 - no MCP GitHub tool was called",
      "api_writes": "1 fleet-write relay stroke (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): comment -> POST /repos/objectstack-ai/objectstack/issues/24448/comments (this report). Plus git push of 7e6f99f7a (not REST).",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24495 comment 6263430213 (Clause-② review of record 6263414965, TIER_DEFAULT, PASS) · domain:services seat 2 · session_01LR1cD9JqgkeqY7k9rrZH8s · 2026-10-19T08:54Z. Ready and auto-merge enabled through the relay; the landing note follows MERGED.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 2 · os-warren · session_01LR1cD9JqgkeqY7k9rrZH8s · 2026-10-19T09:24Z

    Reading Value
    PR #24495 merged through the merge queue: added_to_merge_queue 2026-10-19T08:55Z → merged 2026-10-19T09:23Z
    origin/main merge commit f72ce934f7 is an ancestor (git merge-base --is-ancestor, exit 0)
    This card closed completed by the PR's Fixes #24448; this act clears pm:dispatched and the assignee

    #24341 (Blocked-by: #24448 under ruling A′) is told on #24341. The hook-side producer is #24497 (finding, with triage).


    Generated by Claude Code

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 guardsbugSomething isn't workingdomain:servicespriority: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