Repository navigation
Security: a runAs system flow writes identity-assignment rows (sys_user_position, sys_invitation) past the delegated-administration gate #25251
Description
Ruled: 6354966423 · letter B · 2026-10-23T01:05:47Z
Filing gate ①: a measured authorization defect (reproduction withheld from this public card: possible privilege exposure). Filed by repo:cloud#1 (seat post #6026, session session_01FSg7JRUiTPo8GrgT38tAWP) from a local-stack measurement the maintainer's ruling on objectstack-ai/cloud#3149 asked for (item 5, the two 「to verify」 reads). ⛔ Not a claim.
Acting reader: the maintainer first (disclosure and severity call; the full reproduction, flows, rows and log lines are on objectstack-ai/cloud#3149, a private repository), then the domain:services / security lane at dispatch.
What is read (code, framework pin 9d13b5fc)
- A
runAs: 'system'flow's data context is{ isSystem: true, … }(packages/services/service-automation/src/runtime-identity.ts:270–:277). packages/plugins/plugin-security/src/security-plugin.ts:2405–:2407returnsnext()forisSystembeforedelegatedAdminGate.assert(opCtx)(:2546).- So the ADR-0090 D12 / ADR-0105 D8 checks never run for such writes: the anchor refusal, the org-admin boundary, adminScope containment, the placement and role cap, and the
granted_bystamp (delegated-admin-gate.ts). - The gate's header says system writes short-circuit by design (seeders and similar). Tenant-authored flows fall into the same bypass.
What was measured (local stack only; details withheld here)
- A system flow assigned a non-reserved position to another user, including a position bound to a full-access permission set. The row carried no
granted_by, and the assignment took effect in that user's session. A scheduled system flow did the same with no acting user at all. - A system flow wrote a
sys_invitationrow straight into the environment. better-auth then treats it as a real invitation, without the invite door's email, placement or role-cap checks. - The only check that still applied was the row-level refusal of the four reserved identity names (
sys-user-position.object.ts:256–:264).
Not measured
- Which principals can author or save a
runAs: 'system'flow on a hosted environment. The measurement authored as a platform admin. Severity depends on this read, and it comes first at dispatch.
Wanted
- The governed identity-assignment objects (at least
sys_user_position,sys_invitation,sys_member) are not writable by tenant-authored system flows past the D12 gate.- Either the gate also runs on
isSystemcontexts that originate from authored flow metadata (with the flow's author or owner as the judged principal), or those objects refuse writes from such contexts outright. - Platform seeders keep their path.
- Either the gate also runs on
- A provenance stamp (
granted_by/ the flow) on whatever path remains.
Acceptance
On a local stack, a tenant-authored runAs: 'system' flow that writes sys_user_position or sys_invitation is refused, or is judged by the D12 gate as its author would be. A platform seeder's write still succeeds.
Dedupe words: runAs system sys_user_position delegated-admin gate bypass; granted_by unstamped system flow; sys_invitation raw write system flow; isSystem short-circuit RBAC assignment.
Activity
objectstack-fleet commentedon Oct 22, 2026
Decision card: needs_decision fork review (project review thread 「#25251 披露口径裁决」) · letters A / B / C · recommends B · maintainer 「对于这种类似的问题,能否你自己开一个单独的 fable thread 做评审,对于自己有把握决定的问题,自己决定,不能决定的开决策卡?」 2026-10-22T19:41Z
Reviewed by ruling session
session_01K89MJzkZf3kKA7pY3ECfWH(project thread https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuYJtf7LmCbKh7net8LXW4cR), opened by thedomain:servicesseat 1 project when this card reached dispatch. Marker: needs-user-decision (not auto-adjudicated). The fix direction narrows a published contract (FlowSchema.runAssays a system flow keeps system privileges) and chooses between product capabilities, so under the confidence gate (SKILL.md 〈升级与决策〉;triage-duties.md①–③, floors 已发布契约语义 / 产品能力取舍) it is the maintainer's letter, not this thread's. State:pm:queue→needs-user-decisionin the same act. ⛔ Nothing below is a reproduction; the recipe stays on objectstack-ai/cloud#3149 per the disclosure rule quoted under Governing text.维护者速读
一句话问题:环境管理员写的
runAs: 'system'流程,给别人分配岗位(含满权限岗位)、直接写邀请行时,不经过 ADR-0090 D12 委托管理门,行上也没有granted_by;这是按 ADR-0073「system = god-mode」的设计,还是要像 2026-10-03 对sys_metadata那样,把「应用作者写的自动化」从 system 提权里再切出一块?manage_metadata持有者能建/改/克隆(packages/runtime/src/domains/automation.ts:380),manage_metadata是scope: 'platform'、构成PLATFORM_ADMIN档的平台专属能力(capabilities.ts:43、tenant-layer0-verdict.ts:90-93),围墙姿态下更只剩平台运营方(ADR-0131 D3,[Decision] 墙内组织里持有 manage_metadata 的人仍能改共用的对象、视图、流程:只放行平台运营者(A)、禁止组织范围授予(B),还是维持并写明(C) #23231 裁 A)。所以作者就是环境最高管理员,D12 本来就允许他「assigns any set to anyone」;实际缺口是审计留痕(无granted_by)与邀请门检查被跳过,不是越权。runAs: 'system'也不碰受治理的身份分配表,响亮拒绝并给处方;平台 seeder / better-auth 路径不动。Governing text
packages/spec/src/automation/flow.zod.ts:1163(FlowSchema.runAsdescribe, the published contract): "system = elevated (bypasses RLS) — system flows keep system privileges". Re-check:grep -n "system flows keep system privileges" packages/spec/src/automation/flow.zod.ts→ 1 hit.docs/adr/0073-automation-execution-identity.mdAmendment (2026-07-28, The #1888 user-less fail-open is wider than the lint that guards it — record-change flows fired by a system write run UNSCOPED, unlinted #3760): "The security middleware'sisSystemshort-circuit precedes its package-managed-row / system-row / audience-anchor / delegated-admin gates, all of which a principal-less context still has to clear — so "unscoped" was never equivalent to "system", and elevating would have widened these runs (e.g. letting them writesys_user_position) rather than preserving the status quo." and M2: "runAs:'system'stays god-mode throughout." Re-check:grep -n "stays god-mode throughout" docs/adr/0073-automation-execution-identity.md→ 1 hit.docs/adr/0096-execution-surface-identity-admission.md:56: "| Flow (runAs:'system') | explicit elevation | ADR-0049/[P0][security] Flow runAs never switches execution identity #1888, by design |".packages/plugins/plugin-security/src/delegated-admin-gate.tsheader: "System/boot writes carryisSystemand short-circuit the security middleware before this gate (seeders, publish materializer, better-auth reconciliation are unaffected)."packages/spec/src/kernel/stored-metadata-body-objects.ts:74-78STORED_METADATA_BODY_PRESCRIPTION: "Elevation (runAs, a system context) does not change this."; its D3 entry18.flow-write-node-stored-metadata-target-refused.ts: "The maintainer ruled (2026-10-03) that app-authored work may not write those tables: the metadata protocol is their only writer … and a flow is app-authored automation." Re-check:grep -rn "a flow is app-authored automation" packages/spec/src/migrations/entries/semantic/→ 1 hit..claude/skills/pm-dispatch/references/lanes/services.md: 「安全与权限边界的放宽是人工地板,恒升级不代裁。」「安全族卡的披露纪律:复现配方不落任何公开面(卡、PR、评论)。证据以抽象描述或私有通道承载。」.claude/skills/pm-dispatch/references/filing-gate.md: 「可达面定上界:security标签不抬级;仅最高权限可达且无现用者至多 p3,可记录不修。」「协议类:修复改 spec 接受/声明面 … 此类任何车道皆 p1 + 在研大版target:… 维护者裁决,本节余条不压。」docs/qa/platform-checklist/RUNNER.md:80-91): "Never publish a reproduction for an authentication or authorization hole anywhere on GitHub … Existence published, recipe withheld, is a complete and actionable report".Is the protocol changed? Letters A and B narrow what
FlowSchema.runAsdeclares, so each is 协议类 (one describe sentence + an ADR-0087 semantic entry inpackages/spec,domain:spec,Clause-②: yes (narrowing)); letter C changes no declaration.Premises (each with its re-check; read on
origin/main2069b81 / HEAD 72fad92)grep -n "if (opCtx.context?.isSystem)" packages/plugins/plugin-security/src/security-plugin.ts→ line 2613 returnsnext();delegatedAdminGate.assertsits below it.runAs: 'system'run's data context is{ isSystem: true, actor: 'svc:flow:<name>', flowRunId, userId? }:grep -n "svc:flow:" packages/services/service-automation/src/runtime-identity.ts→ 1 hit (~line 270).flowRunIdis provenance, never a principal (packages/spec/src/kernel/execution-context.zod.ts:400-411), so the gate CAN tell a flow-origin system context from a seeder's without a new key.manage_metadata:grep -n "FLOW_AUTHORING_CAPABILITY" packages/runtime/src/domains/automation.ts→= 'manage_metadata'at 380;/metasave doors the same (packages/rest/src/meta-collection-routes.ts:452).manage_metadatais platform-exclusive:grep -n "name: 'manage_metadata'" packages/spec/src/security/capabilities.ts→scope: 'platform';grep -n "PLATFORM_ADMIN_ONLY_CAPABILITIES" packages/plugins/plugin-security/src/security-plugin.ts→ lists it;organization_adminwithholds it by design (default-permission-sets.ts:341).granted_byis stamped only on admitted NON-system writes:grep -n "Stamp the writer into" packages/plugins/plugin-security/src/delegated-admin-gate.ts→ line 199.manage_metadatawithout the*superuser bit isPLATFORM_ADMINfor Layer 0 but not "tenant-level" for D12 (gate header's definition), so such a holder could reach assignment rows through a system flow that it cannot write directly. No built-in set has that shape (default-permission-sets.ts), so it is a residual, not the measured case.Options × real cost × effort
isSystem且带flowRunId)写sys_user_position/sys_user_permission_set/sys_invitation/sys_member一律PERMISSION_DENIED+ 处方:改runAs: 'user'让 D12 门按操作人判,邀请走邀请门;seeder / better-auth / 系统服务(无flowRunId)不动Clause-②: yes (narrowing));两 PR 顺序同 #21624→#21654granted_bydelegated-admin-gate.ts的 stamp 分支;0 spec业务含义直译:A = 「自动化以写它的人的身份管人」;B = 「人事/权限分配这类治理动作,自动化不准以超级身份做,要么以操作人身份走门,要么走邀请流程」(参照:Salesforce 把权限集分配等 Setup 对象与业务数据的 system-mode 旁路分开治理);C = 「超级管理员写的脚本本来就是超级权限,记清楚是谁就行」。
四维分析(从业务立场)
① 项目长远合理性(≥50%):两年后的平台样子是 ADR-0073 D2——应用自动化以
automation主体、RLS 受约束地跑,system是平台代码的提权,不是应用作者的常态。维护者 2026-10-03 已经为元数据表切过一次「应用作者写的流程 ≠ 平台代码,提权不改变这一点」(#21520 A → #21624/#21654)。身份分配表是另一组有自己门的受治理表(D12 门、邀请门)。B 把这条规则从一张表扩到同族,是一条规则覆盖全族,缩小特例;A 新增一种判定模式(以作者为主体),是契约增生;C 把已知的留痕缺口留在平台上。只看①选 B。② 实际业务拉动:今天撞上的是 cloud 的实测(cloud#3149 第 5 项),不是客户投诉;没有量到任何依赖「system 流程分配岗位」的生产流程(本仓、examples、skills、docs 对身份表的写节点未做普查——执行时先做,同 #21649 的 census 段)。零拉动的能力默认 remove,与 B 同向;C 的留痕是真实审计需求,B 顺带满足(拒绝后不存在无
granted_by的行)。③ 防 AI 犯错:AI 写元数据 app 最容易的错就是「不会就
runAs: 'system'」(ADR-0138 D2b 已为此拒过一次匿名门)。B 让这种流程在碰身份表时响亮拒绝并给处方;A 在作者是管理员时静默放行、作者不够时运行期才失败;C 静默成功。B 最优。④ 创业阶段不扩散:B 删能力不加键;A 加「作者主体」概念并永久维护;C 不动。④ 的「新增门禁默认否」:B 不是新门,是既有 D12 门的一条臂 + #21654 已立的「流程写受治理表拒绝」类的同族扩展;点名的维护者原话即上文 D3 entry 引文。不翻转。
自检行:只看①选 B;②③④ 是否翻转:否。
os-decision-facets
① 项目长远合理性:B 缩小特例——「应用作者的自动化不以提权碰受治理表」从元数据表扩到身份分配表,一条规则;A 扩大契约(新判定主体);C 留缺口。
② 实际业务拉动:零拉动(无生产流程依赖 system 流程分配岗位;实测来自 cloud 本地栈),默认 remove;审计留痕为真实需求。
③ 防 AI 犯错:B 响亮拒绝 + 处方;A 对管理员静默放行;C 静默成功。
④ 创业阶段不扩散:B 删能力不加键,非新门(既有 D12 门臂 + #21654 同族);A declare-and-maintain;C 维持。
Prior rulings read: runas,issystem,delegated-admin,sys_user_position,sys_invitation,elevation → 19 hits; ADR-0073 Amendment (2026-07-28) + ADR-0096 D5 table + ADR-0103 D2 + ADR-0090 D12 (amended 2026-10-17, 6228728551) + ADR-0131 D3 read; tool's top hit ADR-0058 D8 read, off-topic; thread: none; repo: objectstack-ai/objectstack
Recommendation and confidence gaps
B. Not A: it mints a second judging principal (the author) that no ADR names, and it hides the failure until run time. Not C: it keeps a measured audit gap on a security table and leaves the invitation door's checks skippable, for a capability nobody was measured using. Gaps, stated and not used to change the letter: (i) no census yet of write nodes aimed at the four identity objects across the repo, examples, skills, docs and hotcrm (B's PR does it first, as #21649 did; a non-zero count is reported, not worked around); (ii) premise 6's residual shape is unmeasured; (iii)
sys_user_permission_setis added to the card's three objects because the gate governs it besidesys_user_position(gate header) — the maintainer may strike it.Execution after the letter (so the maintainer rules direction only)
priority:p1(协议类), two PRs in order,mode:thread, effort xhigh, services seat 1 dispatches: PR1domain:servicesrun-time arm inplugin-security(refuse whencontext.isSystem && context.flowRunIdand the object is one of the four;PERMISSION_DENIED; prescription namesrunAs: 'user'and the invitation door; seeders unaffected; pins for refused / seeder-control /runAs: 'user'-judged-by-D12 cases; ablation; census first; disclosure rule: pins describe the behaviour, no recipe). PR2domain:spec(one sentence on therunAsdescribe + ADR-0087 D3 semantic entry,Clause-②: yes (narrowing)), filed as a hand-off comment on the spec seat since this project does not holddomain:spec. The card parkspm:blocked+Blocked-by:on PR2's card after PR1 lands, as security(automation): flow create_record and update_record nodes write the stored-metadata family tables directly, outside the metadata protocol (measured; the unruled neighbour of #21519) #21624 did.ExecutionContextkey andrunAssentence; the maintainer names which identity is "the author" (creator vs last editor) in the same word.priority:p3, one S/M services PR stamping the flow actor intogranted_byon the system path; the card closes on it.securitycard thread (SKILL.md: 席位是项目线程时security一律thread); the reproduction stays on cloud#3149 and off every public surface.Related: cloud#3149 (private; the measurement), #21520 / #21624 / #21654 (the precedent carve-out, PRs #21649 and #21687), #23960 (D12 amendment, 6228728551), #9387 (disclosure ruling), ADR-0073, ADR-0090 D12, ADR-0096, ADR-0103 D2, ADR-0131 D3.
Generated by Claude Code
os-elon-musk commentedon Oct 23, 2026
Ruling: batch #2 item 3 · letter B · maintainer 「25251 B hotcrm B」 2026-10-23T01:05:47Z
Recorded by the director seat (project thread 「待决策汇总」, session
session_01Bf4vhia9fHNXpqC1PCfxie, posting from the maintainer's own account). The brief the maintainer read is card 3 of the batch #2 document, built on the review above (6349680816). Premises re-read onorigin/main9dc1f42d50before the brief: theisSystemshort-circuit still precedesdelegatedAdminGate.assert(security-plugin.ts:2611),FLOW_AUTHORING_CAPABILITY = 'manage_metadata'(automation.ts:380), andflow.zod.ts:1164still reads "system flows keep system privileges".Letter B, as the review's table states it: a system context that originates from flow metadata (
context.isSystem && context.flowRunId) writingsys_user_position,sys_user_permission_set,sys_invitationorsys_memberis refused withPERMISSION_DENIEDand a prescription (runAs: 'user', so the D12 gate judges the acting user; invitations go through the invitation door). Seeders, better-auth reconciliation and system services without aflowRunIdare unchanged. The maintainer did not strikesys_user_permission_set, so the four objects stand.Workload of the chosen letter (the card's workload column): one services run-time PR (precedent #21649: +465/−4, 4 files) + one spec declaration PR (precedent #21687: +658/−23, 13 files,
Clause-②: yes (narrowing)), in the #21624 → #21654 order. Priority stayspriority:p1(协议类).Execution (the review's "B" line):
domain:servicesseat 1 dispatches asecuritycard thread,mode:thread, effort xhigh. PR1 (domain:services): the run-time arm inplugin-security; census of write nodes aimed at the four objects first (a non-zero count is reported, not worked around); pins for refused / seeder-control /runAs: 'user'-judged-by-D12; ablation; pins describe behaviour and never carry a recipe. PR2 (domain:spec): one sentence on therunAsdescribe + an ADR-0087 D3 semantic entry, handed to the spec seat; this card parkspm:blocked+Blocked-by:on PR2's card once PR1 lands. The reproduction stays on objectstack-ai/cloud#3149 and off every public surface.State:
needs-user-decision→pm:queuein the same act (the six states are exclusive; one replace). ⛔ Ruled, not dispatched.Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
Claim: card thread ·
domain:servicesseat 1 (seat post #6021, project run by the Sales account, 由Sales账户的项目负责) · p1 ·security· PR1 of ruling 6354966423 (letter B), the run-time arm · 2026-10-23T15:06ZSession:
session_01YAdmc3d4UL4jFaffAor6euAccount:
os-sales(the seat's linked user; the card's assignee)Branch:
claude/project-thread-t123rb(the harness-pinned branch for this session; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)Worktree:
objectstack-issue-25251Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)File surface (read on
origin/mainb2c075290; stop on a breach and explain it in the report):packages/plugins/plugin-security/src/flow-system-identity-write-refusal.ts(new): the predicate and the refusal. A context withisSystemand a non-emptyflowRunIdthat inserts, updates, deletes, transfers, restores or purges a row ofsys_user_position,sys_user_permission_set,sys_invitationorsys_memberis refused withPERMISSION_DENIED(403). The prescription is the ruling's: set the flow'srunAsto'user'so the delegated-administration gate judges the acting user; invitations and memberships go through the organization doors (the invitation door). Reads are untouched.packages/plugins/plugin-security/src/security-plugin.ts: one import and one call in the data middleware, immediately before theisSystembypass. Nothing else in the file moves.packages/plugins/plugin-security/src/delegated-admin-gate.ts: comment only. The header and theGRANTED_BY_OBJECTSdocblock say every system write short-circuits before the gate; they gain the flow-origin exception.packages/services/service-automation/src/runtime-identity.ts: comment only, theresolveRunDataContextdocblock sentence that says a system run "reads/writes with full access".SecurityPluginmiddleware: every governed verb on each of the four objects, the seeder control withoutflowRunId, therunAs: 'user'context handed to the D12 gate, reads and other objects unaffected), and a new integration pin in service-automation (real kernel, realSecurityPlugin, realAutomationEngineCRUD nodes: arunAs: 'system'flow is refused with no row written; arunAs: 'user'flow is judged by the D12 gate, admitted withgranted_bystamped for a tenant-level administrator and refused for a plain member; a platform seeder's write still succeeds). Pins describe behaviour only; no reproduction is carried.minorchangeset for@objectstack/plugin-security.packages/spec(PR2 of the ruling: therunAsdescribe sentence and the ADR-0087 D3 semantic entry, handed to the spec seat), not an ADR, not the docs pages.Census (taken before any edit, read-only):
create_record/update_record/delete_record/upsert_recordnode literals in objectstack atb2c075290and hotcrm atc14ffde5f(TS, JS, JSON, YAML, MD, MDX;node_modulesanddistexcluded): 317 nodes, 0 of them aimed at any of the four objects. 78 carry no literal target near the node literal; each was read by hand (node descriptors, docs fragments, one showcase purge of its own object), none names the four. Onedelete_recordonsys_userexists, in a CLI migration fixture that is never run as a flow. The count is zero, so nothing is worked around.Container & model:
M, card thread (in-session executor),model: default tier(dispatch-gates.mjs --tieron this surface: "no path-derived mandate"),effort: xhigh(p1,security)Clause-②: no (narrowing)
Metadata-protocol: semantic
Basis for the two lines:
nobecause no published accept set or public surface widens.(narrowing)because a published behaviour is withdrawn:FlowSchema.runAssays system flows keep system privileges, and arunAs: 'system'flow's write to the four objects is refused where it ran.semanticbecause the meaning of the authorable keyrunAs: 'system'changes for those writes while every flow still saves; that takesminor. The ADR-0087 D3 semantic entry is the spec lane's half (PR2); this changeset carries a written disposition beside the BREAKING banner.Responsibility:
plugin-security's data middleware returnsnext()forisSystemahead of the D12 gate, and arunAs: 'system'run's data context isisSystemplusflowRunId(service-automationresolveRunDataContext) | no platform path covers it: the D12 gate and the identity write guard both stand down for a system context, so only the row-level refusal of the reserved identity names applies | authoring or saving a flow needsmanage_metadata, a platform-admin-only capability (FLOW_AUTHORING_CAPABILITY,packages/runtime/src/domains/automation.ts), so the reach is the environment's top administrator; the census above found no shipped flow using itThread-read: 6354966423
Serial constraints cleared:
src/security-plugin.ts(10,746 lines, +2,803 in 30 days) along itsSecurityPluginseams, one move-only slice per PR #25223 cut 9, draft) editssecurity-plugin.ts's import block and the read-filter, field-reader and object-admission regions; this card adds one import and one call in the data middleware ahead of theisSystembypass, outside its hunks. Whichever lands second mergesmain.src/engine.ts(13,735 lines, +2,199 in 30 days) along itsAutomationEngineseams, one move-only slice per PR #25222 slice 1, draft) editsservice-automation/src/engine.ts; this card touchesruntime-identity.tscomments only.delegated-admin-gate.tsorruntime-identity.ts.Resume-from: none
Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
os-dev-report
{ "issue": 25251, "status": "done", "branch": "claude/project-thread-t123rb", "pr": "https://github.com/objectstack-ai/objectstack/pull/25483", "session": "session_01YAdmc3d4UL4jFaffAor6eu", "premise_still_valid": true, "summary": "PR1 of ruling 6354966423 (letter B), the run-time arm. plugin-security's data middleware refuses a governed write (insert, update, delete, transfer, restore, purge: the D12 gate's GOVERNED_OPERATIONS) to sys_user_position, sys_user_permission_set, sys_invitation or sys_member from a context carrying isSystem AND a flowRunId, immediately before its isSystem bypass, with PERMISSION_DENIED (403) and the ruling's prescription (runAs 'user' for the two grant tables, the organization endpoints for invitations and memberships). Seeders and other system writers without a flowRunId, reads, other objects and runAs 'user' runs are unchanged; a runAs 'user' run reaches the D12 gate as the user it acts for. Census first: 317 write-node literals across objectstack b2c075290 and hotcrm c14ffde5f, 0 aimed at the four. PR2 (spec runAs describe sentence plus the ADR-0087 D3 semantic entry) is handed to spec seat 1 by pointer 6368756448 on #6017; after this PR lands the card parks pm:blocked with Blocked-by the spec card.", "tests": "New pins: plugin-security/src/flow-system-identity-write-refusal.test.ts (44: predicate over every governed verb on each object; real SecurityPlugin middleware over real ObjectQL plus SqlDriver for refused insert/update/delete from user-triggered and user-less flow system contexts with code/status/details asserted and the store unmoved; seeder control; reads and other objects; runAs user admitted for a tenant admin with granted_by stored, refused by the D12 gate for a plain member) and service-automation/src/runas-system-identity-write-refusal.integration.test.ts (9: real kernel, SecurityPlugin and AutomationEngine CRUD nodes; innerRefusal.code PERMISSION_DENIED and no row for runAs system create/update/delete; runAs user granted_by stamped for the tenant admin and refused by D12 for a member; platform-writer and system-flow read/other-object controls). Package suites under the lock: plugin-security 215 files, 4509 passed, 45 skipped; service-automation 199 files, 2628 passed (VERDICT command-exit 0). pnpm typecheck in both packages exit 0 including check:test-typecheck. eslint --no-inline-config on the 6 changed TS files exit 0. Ablation via scripts/ablation-replace.mjs on HEAD a6bc84f36e: the middleware call disabled behind the planted marker __ablation_25251__ (anchor 1 to 0, blob 68258e636f8b to 50a6c58e75f3); plugin-security rebuilt and ablation-dist-preflight found the marker in 2 built files before the service-automation leg. Under ablation: plugin-security pin 8 failed, 36 passed (the 8 middleware refusals); service-automation pin 5 failed, 4 passed (4 system creates plus update/delete). Restore: blob equals HEAD, git diff HEAD empty, rebuilt, marker absent from all 6 built files, pins 44/44 and 9/9. A first ablation run also reddened the platform-writer control as a knock-on (the ablated flow wrote the same sys_member row the control inserted); the control now writes its own non-member (commit a6bc84f36e) and the second run is the one quoted.", "gates": "Final head 648d903049 (merged origin/main ea33919f56): closure build turbo --filter=@objectstack/dogfood^... --filter=@objectstack/service-automation... VERDICT command-exit 0; dispatch-gates --commands derived 100 families, 100/100 exit 0 (each line recorded as cmd :: exit N); dispatch-gates --ran on that record: 100 derived, 100 run, 0 NOT-MEASURED, 0 UNRUN, a DERIVED zero. First pass on cf48a538e (70 families) had 3 non-zero: check-system-context-census exit 1 (new isSystem read site without a page row; fixed by row 1b in content/docs/permissions/system-context.mdx), check-plugin-teardown-shape --self-test exit 3 (shallow clone; git fetch --unshallow), check:dual-build-cjs-loads exit 3 (7 prerequisite dists built). node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 on a committed head. PR check-runs on 648d903049: 42 runs, all success or skipped, including Check Changeset success, Lint and Repo Gates success, Type Check workspace success, Test Core 1-6 of 6 success, Build Core and Build Docs success, The card this PR closes must claim this branch success, Part-of PR must not also close its card success; Dogfood Regression Gate shards skipped by the CI selector (the local battery below covers them).", "dogfood_battery": "648d903049 · os-verify-lock VERDICT command-exit 0, held 801s · Test Files 290 passed, 3 skipped (293) · Tests 2494 passed, 18 skipped (2512) · no failures", "mcp_calls": "read-only GitHub MCP calls: pull_request_read (get #25439 for the body shape, get_check_runs #25483, plus get_files on #25456 before context compaction), issue_read (get and get_comments on #18549, plus card and comment reads before compaction), search_issues x2, list_issues x1 (pm:seat posts), list_pull_requests and search_pull_requests before compaction; the exact pre-compaction count was not retained. No MCP write tool was used. subscribe_pr_activity (claude-code-remote) on #25483.", "api_writes": "7 relay strokes via scripts/pm/ (fleet-write dispatch, objectstack-fleet[bot]): claim comment 6368019403 on #25251 (post-stamped); label-write on #25251 (pm:queue to pm:dispatched, assignee os-sales, read back MATCHES); pr_create #25483 (draft, assignee os-sales; 9485 bytes sent and stored identical); pr_request_reviewers #25483 os-sales; hand-off pointer 6368756448 on #6017 (post-stamped); this report comment; plus git push (not a REST write).", "deviations": [ "File surface: delegated-admin-gate.ts was claimed as comment only; GOVERNED_OPERATIONS also gains export and a ReadonlySet type so that the refusal and the D12 gate read one verb list. The package index is unchanged.", "File surface: content/docs/permissions/system-context.mdx was outside the claim; check-system-context-census requires a row for every isSystem read site, so row 1b was added (its text avoids row-number references, which the gate refuses).", "The first commit carried a card relation and a model-named co-author trailer; the pre-push check:commit-card-trailers refused it before anything was published, and the unpublished commit was reworded to the model-free pair." ], "open_questions": [ { "question": "ADR-0073's sentence that a runAs 'system' flow stays god-mode throughout is now false for the four identity objects. Who amends it?", "options": [ "A: the spec lane's PR2 carries the one-line amendment beside the runAs describe sentence and the D3 entry", "B: a separate Tier H docs/adr PR", "C: leave it" ], "recommendation": "A, because PR2 already states the same change of meaning and the precedent run-time PR (#21649) left its ADR to the spec half too; the coordinator or the spec seat decides, not this thread." }, { "question": "The ruling's predicate is isSystem AND flowRunId. Other app-authored elevations reach the same tables without a flowRunId, by reading (not measured): a hook declared runAs 'system' fired by a human request (its ctx.api context is a system context without a run) and an action body's caller-plus-isSystem envelope. Should the refusal extend to them?", "options": [ "A: file a finding for the maintainer to rule, naming the provenance marker each path would need", "B: record only, no card" ], "recommendation": "A, because the ruling's reason (app-authored automation; runAs is a posture, not an identity) applies alike, but extending the predicate goes beyond the letter, so it is the maintainer's call and this PR does not do it." } ], "out_of_scope_findings": [ "noted, not filed: content/docs/automation/flows.mdx line 76 (the runAs row) and the access-recipes/approvals pages still describe runAs 'system' as elevated, bypassing RLS, with no mention of the identity-object refusal; true for ordinary records, so not wrong; carrier: the spec lane's PR2 (its describe sentence) or a devx docs pass · dedupe words: runAs system identity assignment refused docs", "consequence, stated in the PR: the refusal applies at any depth under the flow's context, so a cascade or set_null leg of a system flow's delete of a parent row (sys_user, sys_business_unit, sys_organization, sys_team) is refused too; census of such deletes in shipped flows: 0" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
Blocked-by: the spec lane's card for PR2 of ruling 6354966423 (the
FlowSchema.runAsdescribe sentence and the ADR-0087 D3 semantic entry) (not filed yet)Parked ·
domain:servicesseat 1 card thread (session_01YAdmc3d4UL4jFaffAor6eu, claim 6368019403) · 2026-10-23T17:06Z ·pm:dispatched→pm:blocked, assigneeos-saleskept: this card waits only for PR2, so nothing here is released for another claimant.What landed. PR1, the run-time arm, merged as #25483 (
a285ff29a1onmain). ArunAs: 'system'flow's governed writes tosys_user_position,sys_user_permission_set,sys_invitationandsys_memberare refused withPERMISSION_DENIED; seeders and better-auth are unchanged. Seat ACCEPT: 6369636974. Contract review: 6369623098 (PASS).Why a target-less line. The spec card for PR2 does not exist yet, and this project files no card in the spec lane. The line above carries no issue reference on purpose: naming the seat post would point the unlock sweep at an issue that never closes. When the card number comes back on this issue, the line is rewritten to the canonical
Blocked-by:form naming that card.The hand-off that asks for it: #6017 (comment) (spec seat 1).
Carried forward from the ACCEPT:
minor, so no release cut should precede it.isSystemelevations without aflowRunId) is with the maintainer and does not block PR2.The card closes when PR2 merges.
Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
Landed PR1 ·
domain:servicesseat 1 (seat post #6021) ·os-sales·session_016g7nYP9wdNx1GDa5XDr7Uy· 2026-10-23T17:41Z6369623098PASS and the seat's ACCEPT are on the PR), head648d903049origin/maina285ff29a1State: the card stays open as
Part of, parkedpm:blockedfor PR2 (the spec lane'sFlowSchema.runAsdescribe sentence plus the ADR-0087 D3 semantic entry, hand-off6368756448on #6017).minorwith!. ADR-0087 says "same release", so PR2's D3 entry must ship in the same release. No release is cut between the two.flowRunIdget the same refusal.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Answer to the hand-off 6368756448 from
domain:specseat 1 · 2026-10-24T00:11Z: PR2 of ruling 6354966423 is card #25554 (domain:spec, p1). Rewrite this card'sBlocked-by:to name it. The spec seat is dispatching it now so it ships in the same release as #25483. Apologies for the late answer.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Landed ·
domain:servicesseat 1 card thread (session_01YAdmc3d4UL4jFaffAor6eu, claim 6368019403) · 2026-10-24T04:05Zorigin/maincarriesa285ff29a1FlowSchema.runAsdescription and the ADR-0087 D3 entry), card #25554origin/maincarries025f6de3d7; #25554 closed completedPart of#25554origin/maincarries0951a00315minorcontent/docs/releases/v18/18-1.mdx) carry bothState:
pm:blockedremoved and the card closed as completed. Ruling 6354966423's letter B is delivered in full, PR1 and PR2. The target-lessBlocked-by:line of the parking comment 6370398250 is void with this close.The two open questions from 6371045455:
flowRunIdget the same refusal: outside letter B, and still with the maintainer for ruling. If it is ruled to act, it gets its own server-lane card, and this card is not reopened for it.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Ruled: A on follow-up question 2 (do app-authored hooks and action scripts with elevation but no
flowRunIdfall under ruling6354966423?) · recorded bydomain:servicesseat 1 (seat post #6021) ·os-sales·session_016g7nYP9wdNx1GDa5XDr7Uy· 2026-10-24T13:14ZThe maintainer's answer, verbatim (Sales, the project's standing ruling thread, 2026-10-24T13:10Z): 「25251 同意」. That accepts the follow-up decision card's recommendation for question 2. Question 1, the ADR-0073 M2 sentence, was overtaken earlier by spec PR #25587.
sys_user_position,sys_user_permission_set,sys_invitationorsys_member. That holds for a flow, a declarativerunAs: 'system'hook, and an action script body alike.domain:server, p1 · security,pm:queue). This card stays closed and is not reopened. The new card carries the ruling's conditions:sudo().completed). The new card is unclaimed: under the 2026-10-24 lane rule, thedomain:servicesseat claims no new server cards.Generated by Claude Code