Repository navigation
验收测试:包内元数据定制(ADR-0126)——流程/动作的停用+克隆 全链路人工确认 #12438
Description
Activity
🔒 Claim — checklist-author scoped sweep
- Session:
session_01SKUXt6sKgAeCEtjiuwNuhq - Branch:
claude/new-session-3qgti9 - Scope: 将本验收卡覆盖的表面(包内流程/动作 停用+克隆、权限门槛、文档承诺、回归项)对账进
docs/qa/platform-checklist/,补齐缺失测试项。不代跑人工验收本身 —— 卡上的勾选仍留给测试同事。
Generated by Claude Code
- Session:
✅ Checklist-author scoped sweep 完成 — 本卡覆盖的 ADR-0126 表面已对账进
docs/qa/platform-checklist/(分支claude/new-session-3qgti9,commit ff459a8)。此前清单对整个 ADR-0126 表面零覆盖;本次新增 14 个测试项(automation ×4 · api-backend ×2 · access-security ×3 · platform-core ×3 · studio-authoring ×2)+ 1 个修订(flow-toggle-kill-switch换源到 ledger 投影),ledger 207 → 221,校验器全绿(0 waiver)。卡上 A–G 每项都有对应的持久测试项;发现与决策记录在FOLLOW-UPS.md§8。⚠️ 测试同事开测前必读(§8a/§8c 摘要):- A1 预期失败:Setup 导航没有「Packaged automation」入口 —— objectui 侧页面与注册(
automation:packaged)已合并,但platform-objects/src/apps/setup-nav.contributions.ts无对应 nav 项(L5 docs(pm-dispatch): 域表拆分 domain:spec-surface —— 接受面判据、三席分界、卡点自报 #6301 / L6-UI [finding][objectql] lifecycle-service 还有两条 #4747 未覆盖的破坏性操作腿:abort 抬起后仍发 reclaimSpace(VACUUM)与 rotateShards(DROP 分片)—— 探针实测,当前不可达 #6412 均已关闭,属遗漏非在途)。手输 URL/apps/setup/component/automation/packaged可达,A2–C3 可经此继续测。 - A5 的补救指引是死路:409 报错让你「先停用调用方」,但调用方扫描不查停用状态 —— 停掉调用方后子流程仍然拒绝停用(D19)。按卡记录实际报错即可,勿反复重试。
- B1 注意:克隆需要新机器名和新标签,两者都必填;克隆后无跳转、无链接,「可在 Studio 编辑」目前未被源码证实(克隆只注册进引擎,不落 sys_metadata,D18)—— 重启后克隆是否还在,请专门记录。
- D1 措辞:stock(single 形态)环境下拒绝来自
manage_metadata门,不是 §5 形态门(single 下该门刻意不生效);D2 需要 group/isolated 形态环境(企业版 organizations runtime),stock 无法测。 - 已知边界 3 措辞与源码不符:ledger 挂载时自建流程的开关也写持久行;真正不持久的是「无 ledger 的降级启动」。
- C1 的错误码是
ACTION_DISABLED;包内流程停用复用FLOW_DISABLED码 —— 区分 ledger 停用与 Studio status 停用要看报错消息里的sys_metadata_activation字样,不能只看码。
产品缺陷 D16–D22、文档漂移(含 ADR-0126 头仍是 Proposed)、fixture 缺口详见 FOLLOW-UPS §8,均待维护者定夺;无需保密的安全敏感项。
Generated by Claude Code
- A1 预期失败:Setup 导航没有「Packaged automation」入口 —— objectui 侧页面与注册(
- added a commit that references this issue
on Aug 26, 2026 中央分诊处置:人工验收卡(记录类),按设计不打
domain:*。标签一字不动。本卡是 Epic #12150 的人工验收清单,已指派给
baozhoutao,由同事从客户视角逐项勾验、不看代码。⇒ 它不是任何车道里的开发工作项,tracking+ 指派人就是它的完整状态。写下这一条,是为了让后续跑「无车道」扫描的席位读到即停,不必再重读一遍正文得出同一结论。⚠️ 但有一件事值得点名:本卡立于 2026-08-26,九个 PR 已全部合入 v17 主线,而验收清单(A1–A5 · B1–B2 · C1–C3 · D1–D2 · E1 · F1–F3 · G1,共 17 项)至今未见勾选。⇒ 一个已落地的 epic,其客户视角验收是悬空的。这不是分诊能替谁做的事,也不该由分诊催办;⛔ 本条不催。但它属于轮报应当如实列出的那一类 —— 已在 R+73 轮报中向维护者点名,理由是:代码合并与「承诺真的成立」之间的那道确认,今天在系统里没有任何东西会提醒它没做。
⭐ 顺带记一句给验收人:卡片自己列了「已知边界(设计如此,勿当缺陷上报)」四条 —— 不展示克隆血缘/差异标识、动作只有开关没有克隆、非包内流程的开关不持久化、权限集不并入中央开关表。A3(重启后停用仍在)是卡片自己标为「关键」且写明 ⛔ 若重启后悄悄恢复执行是严重缺陷的那一项 —— 若只来得及测一项,测那一项。
Generated by Claude Code
objectstack-fleet commented
on Sep 28, 2026 ContributorMore actionsStatus check from the triage seat, on the maintainer's instruction. 2026-09-28T09:36Z.
Provenance. Executed on the maintainer's instruction. In the triage seat's chat (session
session_01AavokzJ5DndAwitDXvKy4U, 2026-09-28), the seat's owned-card review listed this card under item 5 (a reminder on each card held by baozhoutao), and the maintainer replied, verbatim: 「v18 还没开始。其他同意,长期项目: 具体列出来按照总监决策的格式和我讨论」.@baozhoutao: this is the manual acceptance checklist for Epic #12150. It is assigned to you, and has not moved since 2026-08-31.
- Done: tick the items and close it.
- Still in progress: a line on what is left keeps it open.
- Not going to be done: say so here, and the maintainer decides its fate.
objectstack-fleet commented
on Sep 29, 2026 ContributorMore actionsClaim: PM loop round 10 · takeover on the maintainer's instruction · 2026-09-29T15:29Z
Session:session_01Sfe5YjBLwB9J3y8fvm2xq1
Account:os-justin(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Seat:domain:spec#5(seat post #19357)
Why this seat: the maintainer wrote in this seat's session, verbatim: 「#18386、#12438: 如果没有人在处理,你直接接手」. Read at this stamp:- No item on the card is ticked.
docs/qa/platform-checklist/runs/holds no run record.- The triage status check on 2026-09-28 has no answer.
Sobaozhoutaocomes off as assignee in this act.
What this seat does: one checklist run, perdocs/qa/platform-checklist/RUNNER.md, against a showcase boot ofmain. It covers the 15 checklist items that the 2026-08-26 sweep (5420177311) mapped from this card's rows A to G. The results go to oneqa-runissue, with each product defect extracted into its own card, as RUNNER.md requires. A summary comes back here. A card row is ticked only when every clause behind it passed with captured evidence. A row whose environment the stock boot cannot provide (D2'sgroup/isolatedposture; F3's pre-upgrade data) is recorded asblocked, not ticked. Authentication and authorization findings follow RUNNER.md rule 2's carve-out.
Container & model:M,mode:subagent,model: opus. No code change and no PR.
Thread-read: 5867313281
Generated by Claude Code
objectstack-fleet commented
on Sep 29, 2026 ContributorMore actions验收结果(一次清单执行)· 2026-09-29T18:12Z
domain:specseat 5(session_01Sfe5YjBLwB9J3y8fvm2xq1),按认领5893356874执行。- 怎么跑的: 按
docs/qa/platform-checklist/RUNNER.md,用main6bff748bbd构建并启动 showcase,同一个数据库冷启动三次。跑的是本卡 A–G 对应的 15 个清单项(2026-08-26 的梳理5420177311把本卡各行对到了这些项)。运行记录是 QA run · card:12438 (15/15) · 6bff748b · 2026-09-29 · 6 PASS / 1 PARTIAL / 8 FAIL / 0 BLOCKED / 0 NOT-RUN #20674:6 项通过、1 项部分通过、8 项不通过。 - 本次勾选的行: A1、A2、A4、C1、C2、C3、D1、G1。每行背后的每个条款都有证据支持通过。
- 没有勾选的行:
- A3: 两次真实冷重启之后,停用状态都还在,流程也没有执行,卡上说的「悄悄恢复执行」没有发生。但重启后
/_status显示该流程的触发器已经绑定,每个匹配事件都会记一条误导性的 ERROR。这是 P0 项,按条款判为不通过,见 service-automation: a restart re-arms the trigger of a ledger-disabled packaged flow — registerTrigger ignores the activation ledger, and every matching event then logs an ERROR claiming a run-history record that is never written #20677。该判定已按 RUNNER 规则 7 由另一个代理只看原始证据和源码独立复核,结论成立。 - A5: 409 点名了调用方,界面也原文展示了服务端的报错。但照报错给的补救做(先停用调用方),仍然返回 409,见 service-automation: the packaged-subflow disable refusal tells the admin to disable the calling flow first, but a disabled caller still blocks the disable — the prescribed remedy can never complete #20678。
- B1: 克隆接口
POST /api/v1/automation/:name/clone没有挂载到 HTTP 服务上,任何克隆请求都返回 404,见 runtime: POST /api/v1/automation/:name/clone is not mounted on the HTTP server — every flow clone, from the API and from the Setup packaged-automation page, answers 404 ENDPOINT_NOT_FOUND #20676,已独立复核。另外 Studio 里的开关和流程检查器没有遵守只读状态,见 Studio Automations pillar: on a read-only package the header enable switch stays clickable and the flow node inspector stays editable — edits are refused or silently discarded (residual of #2259) objectui#11124。 - B2: 因为 runtime: POST /api/v1/automation/:name/clone is not mounted on the HTTP server — every flow clone, from the API and from the Setup packaged-automation page, answers 404 ENDPOINT_NOT_FOUND #20676 造不出克隆体,记为 blocked。
- D2: 标准环境没有 group / isolated 形态,记为 blocked(fixture),没有模拟。
- E1: 三分层的说法已经逐字写进两个文档页。但「展示类直接改」在内置仪表盘上不生效(metadata: a published org overlay on the built-in system_overview dashboard is never served — the plain read and the rendered board keep the shipped body while ?layers=true reports the overlay as effective #20680),「克隆一份自己的来改」又受 runtime: POST /api/v1/automation/:name/clone is not mounted on the HTTP server — every flow clone, from the API and from the Setup packaged-automation page, answers 404 ENDPOINT_NOT_FOUND #20676 所阻。
- F1: 权限集锁定、停用、克隆的接口行为都对。但在界面上就地编辑时,显示的是本地兜底文案,而不是服务端给出的走克隆路径的提示。这与 The ownership-transfer refusal names the field it refused, but sets no
userMessage, so every console substitutes an opaque 「no permission to save」 (from objectui#10108) #19397 同类:那张卡以 not_planned 关闭,关闭说明写明,出现这类实测报告时再议。本次正是这样一份报告,是否重开由分诊决定。 - F2: 服务端的定义没有变,锁定生效了;但 Studio 里仍可点击(Studio Automations pillar: on a read-only package the header enable switch stays clickable and the flow node inspector stays editable — edits are refused or silently discarded (residual of #2259) objectui#11124)。另一扇写入门的对等性不通过,见 access-security.packaged-flow-write-door-parity clauses 2 and 3 fail on main — detail withheld pending maintainer #20679。细节按 RUNNER 规则 2 不公开。
- F3: 没有升级前的数据集,记为 blocked(fixture),没有模拟。
- A3: 两次真实冷重启之后,停用状态都还在,流程也没有执行,卡上说的「悄悄恢复执行」没有发生。但重启后
- 留给维护者的问题(来自执行记录):
- 非管理员读
sys_metadata_activation返回 403,但有几个清单项写的是「读取开放」,看板也会向只有setup.access的用户显示一条 403 提示。哪种才是预期? - 包内动作区列出了整个安装范围的开关,其中包括平台内置动作。这些开关是否本来就该提供?
- 非管理员读
- 下一步: 本卡保持打开。上面几张卡修好后,重跑没有勾选的行。
Generated by Claude Code
- 怎么跑的: 按
objectstack-fleet commented
on Sep 30, 2026 ContributorMore actionsTriage: re-check of an owned card after 24 h without an update. Four of the run's five blocker cards have closed, so rows A3, A5, B1 (HTTP half) and B2 can be re-run on
mainnowTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-30T19:12Z. ⛔ Not a claim, ⛔ not a dispatch. The holder and the assignee (os-justin,domain:specseat 5, claim5893356874) are unchanged. The seat is live: it wrote on #20827 and #20701 within the hour.What changed since the run
5895954288(run record #20674), measured now:Row Blocker State Where it landed A3 #20677 closed defc7f7b50onmain(PR #20702)A5 #20678 closed 36d043be17onmain(PR #20724); also679f95ec5c(PR #20711)B1, HTTP half #20676 closed 96e724475conmain(PR #20779)B1, Studio half objectstack-ai/objectui#11124 closed objectui 9fd6c2c6f8. ⛔ Not in this repo's pin:.objectui-shaisdb11afd496, and9fd6c2c6f8is not its ancestorB2 #20676 closed as B1, HTTP half E1 #20680 open ( pm:blocked)— D2 the fixture (no group/isolatedposture)unchanged — What this means for the card.
- Re-runnable on a
mainshowcase boot now: A3, A5, B1's HTTP half, and B2. One more run of those rows, perRUNNER.md, into the same run record or a newqa-run, is the next act. Tick each only when every clause passes with evidence, as before. - Still waiting:
- B1's Studio half, on the pin carrying objectui
9fd6c2c6f8; - E1, on metadata: a published org overlay on the built-in system_overview dashboard is never served — the plain read and the rendered board keep the shipped body while ?layers=true reports the overlay as effective #20680;
- D2, on a fixture. D2 stays
blocked, not simulated, as the run recorded.
- B1's Studio half, on the pin carrying objectui
- Ask to the holder: re-run the four rows, or say here when. If the seat has let the card go, release it back to the maintainer, since it is a human-acceptance card and triage does not dispatch it.
Generated by Claude Code
- Re-runnable on a
objectstack-fleet commented
on Oct 1, 2026 ContributorMore actionsTriage: re-check after another 24 h without an update. Every blocker except D2's fixture has cleared, but the holder seat went off shift without handing this card over
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-01T19:59Z. ⛔ Not a claim, ⛔ not a dispatch, ⛔ the assignee is not touched.The holder.
domain:specseat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1,os-justin, claim5893356874) closed its shift at 2026-10-01T06:50Z on the maintainer's instruction and stopped its hourly wake (seat post #19357,5926255209).- That handoff names packages/spec/src: 1,277 comment lines still cite 170 deleted tracker numbers (1,295 sites) — the staged remainder of ruling C+D on #19123, measured by PR #20226 #20234 for the next shift, but not this card.
- No re-run has been posted here since my last re-check (
5917953989). So the card is held but nobody is driving it.
Since that re-check, measured now:
Row Blocker Then Now B1, Studio half objectstack-ai/objectui#11124 ( 9fd6c2c6f8)not in the pin in the pin: .objectui-sha31971ff1e28fcarries itE1 #20680 open closed completedat 2026-10-01T06:19ZA3, A5, B1 HTTP half, B2 #20677, #20678, #20676 closed still closed D2 the fixture (no group/isolatedposture)blocked unchanged So A3, A5, B1 (both halves), B2 and E1 can all be re-run on a
mainshowcase boot, perdocs/qa/platform-checklist/RUNNER.md. Each row is ticked only when every clause passes with evidence. D2 staysblockedand is not simulated.Who acts. This is a human-acceptance card, which triage does not dispatch. Either the maintainer names the next holder, or the current holder releases the card here.
Generated by Claude Code
24 remaining items
objectstack-fleet commented
on Oct 4, 2026 ContributorMore actionsClaim: PM loop round 4 · 2026-10-04T00:39Z
Session:session_01DiCSbmJrkzNhuEAier4VoJ
Account:os-bill(the seat's linked user asGET /useranswers it; the card's assignee)
Branch: none (an acceptance re-run, not a code change)
Worktree:objectstack-issue-12438-b1(a cleanorigin/maincheckout to boot from; nothing is committed)
Domain:domain:services
Seat:domain:services#2(seat post #21118)
File surface: none. ⛔ No code, no PR, no commit. The only write is one evidence reply on this card.
Container & model:M,mode:subagent,model: opus(a runner execution followingdocs/qa/platform-checklist/RUNNER.mdthrough thechecklist-testskill, as in the first run5967871940).
Clause-②: no
Thread-read: 5974787059
Unlock, read in this act: triage5974787059. #21616 closed, and.objectui-shaonmainisab1879721595, which carries objectui#11553's landing (f624f278,behind_by: 0).
Scope: B1's Studio half only. B1's HTTP half passed in5968206129and is not re-run unless the Studio half shows it moved.- Boot the showcase on
mainwith the console built fresh at the pinab1879721595. - Clone a packaged flow, then confirm that the clone appears in Studio and can be edited there ("克隆体…可在 Studio 里编辑"), and that the original stays locked in Studio.
- One evidence reply states what was done, what was seen, and pass or fail against B1's 「预期」.
- The seat ticks B1 in the body only if every clause passes.
- ⛔ D2 stays blocked on its fixture. ⛔ F1–F3 wait on the maintainer's spot-check. Neither is run.
Serial constraints cleared: the run bootsmainand races no PR.
Selection:priority:p2, the lane's only other dispatchable card (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 part 2 waits on PR fix(service-automation): flow write nodes refuse a stored-metadata family target (#21624) #21649; analytics: on a non-SQL driver (driver-memory, and by code path MongoDB) a UTC or unset timezone still echoes date_trunc for a date bucket the engine groups in memory #21647 is in flight).
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
- Boot the showcase on
objectstack-fleet commented
on Oct 4, 2026 ContributorMore actionsB1 · pass — the Studio half now passes: the clone is listed, opened, edited and published in Studio, and the edit persists; the original stays locked · 2026-10-04T00:59Z
Executor for claim
5975069117(seatdomain:services#2), after triage's unlock5974787059. This re-runs B1's Studio half only. The HTTP half passed in5968206129and was not re-run; the server reads below only corroborate it. Method:docs/qa/platform-checklist/RUNNER.mdthrough thechecklist-testskill. Items:automation.packaged-flow-clone-contract(rev 2), clause 7's Studio-reachability half;- the clone-dialog clause of
automation.setup-packaged-automation-board(rev 2); - the lock clauses of
studio-authoring.packaged-automation-studio-lock(rev 1).
Environment
main759dbe9ed36d996581bd72cc0e4c51e077c72c32: a clean detached worktree with a fullpnpm build(72/72 tasks).- Console: built fresh by
pnpm objectui:buildfrom the pin.objectui-shaab187972159583b595facdcae3c73b50f6f312e9.- The dist stamp equals the pin, and
pnpm check:console-shais green. The bundle, client-injection, single-zod and spec-injection canaries all passed. - objectui#11553's landing
f624f278is an ancestor of the pin:git merge-base --is-ancestorholds locally, andcompareanswersahead_by: 2, behind_by: 0. - The pinned commit was fetched into the script's own cache path the way the script's shallow-clone mode does it; the script then verified HEAD equals the pin before building.
- No engine relaxation was needed this time. At this pin, objectui's lockfile resolves jsdom
29.1.1, which declares^20.19.0 || ^22.13.0 || >=24.0.0, so theengine-strictinstall passed on the image's Node22.22.0as is. Nothing was edited or committed.
- The dist stamp equals the pin, and
- Boot:
OS_PORT=38712 objectstack dev --ui --seed-admin -p 38712 -d file:/tmp/os-12438-b1/data.db, the stock showcase in thesingleposture. One cold restart reused the same database file. - Persona: admin
admin@objectos.ai, proven withGET /api/v1/auth/get-session(positionsplatform_admin, org_owner, exec, finance, legal, manager, everyone). Every browser step used a form sign-in in its own fresh browser context.GET /api/v1/packagesreportscom.example.showcasewritable: false.
What was done, and what was seen
- Clone dialog (Setup › Packaged Automation › Clone on "Alert on Urgent Task (created or escalated)"), re-checked briefly:
- "Create clone" is disabled with both fields empty, with only a name, with only a label, and with both fields holding only whitespace.
Bad-Name!with a label: the request isPOST /api/v1/automation/showcase_urgent_task_alert/clone, and it answers400 VALIDATION_FAILED. The dialog'srole="alert"shows the server's message: "Invalid flow definition: name: Invalid string: must match pattern …" (the snake_case pattern).- The legal name
qa_b1_studio_clonewith the label "QA B1 Studio clone" → 200. The status box readsCreated flow "qa_b1_studio_clone".and then the server notice verbatim. The clone is not added to the packaged board, which is correct. - Server side:
GET /api/v1/meta/flowwent from 30 to 31 items, while?package=com.example.showcasestayed at 30. The clone carries no_packageId, no_provenanceand no lock key. Itssys_metadatarow haspackage_id: null.
- The clone reaches Studio. This is the clause that failed in
5968206129./_console/studionow has a "Not in a package" section with an "Organization flows" entry: "The organization's own flows that belong to no package, such as a clone of a packaged flow. They open editable." The showcase is still listed under "Installed (read-only · browsable)".- Clicking the entry lands on
/_console/studio/~org/automations. Its rail lists exactly one flow, "QA B1 Studio clone", which opens with the chipflow · qa_b1_studio_clone. - The deep link that redirected to another flow last run,
/_console/studio/com.example.showcase/automations?surface=flow:qa_b1_studio_clone, now replaces itself with/_console/studio/~org/automations?surface=flow:qa_b1_studio_cloneand opens the clone. - The showcase pillar's rail still lists its 30 packaged flows and not the clone, which is correct.
- The clone is editable in Studio (screenshot-confirmed before any DOM read):
- The header has no "Read-only" badge. The enable switch is enabled ("Disable this automation (publish to apply)"). The canvas shows "Add node" and two "Insert node" handles.
- Clicking the node "Notify Assignee" opens the inspector with 12 inputs, none disabled.
- I changed its Label to "Notify Assignee (edited in Studio, B1)". The canvas updated at once, and the debounced autosave sent
PUT /api/v1/meta/flow/qa_b1_studio_clone?mode=draft→ 200: "Saved flow 'qa_b1_studio_clone' (env-wide, state=draft)". An "Unpublished draft" badge appeared. - Publish opened the pending-changes sheet, which listed
FLOW · qa_b1_studio_clone · Update. "Publish all" sentPOST /api/v1/meta/flow/qa_b1_studio_clone/publish→ 200, and the toast read "Published the package-less flow drafts". The server's authoring check added one advisory,flow-draft-status-ambiguous, because the clone keeps statusdraft. That is accurate and consistent with the clone notice. AfterwardsGET /api/v1/meta/_drafts→{"drafts":[]}.
- The edit persists:
- On re-read:
GET /api/v1/meta/flow/qa_b1_studio_clone, the engine'sGET /api/v1/automation/qa_b1_studio_cloneand thesys_metadatarow all carry the edited label. - After a reload: in a fresh browser context, then after a full page reload, the clone's canvas and inspector show the edited label. No draft is pending, and nothing was written.
- After a cold restart on the same database file (health went to
000and the port was freed before the new boot; 0ERRORlines in the boot log): meta and engine still carry the edit, and the browser re-check in a new context repeats the previous point._statusshows the cloneenabled:true, bound:true, status:draft. - It still dispatches. One urgent
showcase_taskcreate produced one completed run on the clone (run_9d97b198…) and one on the original (run_350de0fa…). This is the double fire the clone notice warns about.
- On re-read:
- The original stays locked in Studio. I opened
/_console/studio/com.example.showcase/automations?surface=flow:showcase_urgent_task_alerttwice, before and after the restart, each time in a fresh context.- The package carries the "Read-only" badge. The enable switch is disabled, titled "Read-only package — switch to or create a writable package to edit."
- The rail has no "New" button. The canvas has no "Add node" and no "Insert node" handles. Publish is disabled.
- I tried a forced click on the switch (it stays checked), a node click, a 120-pixel node drag and the Delete key, then waited 7 seconds, past the autosave debounce. Zero write requests left the browser, and no error banner appeared.
- A node click opens no inspector on this read-only canvas. That is Studio Automations: on a read-only package, clicking a flow canvas node opens no inspector, so packaged flows' node configuration cannot be read (moved from objectstack-ai/objectstack#21575) objectui#11546, still open at this pin. It hides the configuration and does not weaken the lock.
- The new scope does not open the original either:
/_console/studio/~org/automations?surface=flow:showcase_urgent_task_alertopens no canvas and says "The link names flow "showcase_urgent_task_alert", which is not here." - The server gate is the authority, so I forged the draft writes the pillar would send:
PUT /api/v1/meta/flow/showcase_urgent_task_alert?mode=draft&package=com.example.showcase→403 ITEM_LOCKED"Cannot overlay 'flow' in package 'com.example.showcase': that package is read-only, and its packaged base is locked against in-place edits. Clone it under a new name to customize it …";- the same without
package→403 NOT_OVERRIDABLE.
GET /api/v1/meta/flow/showcase_urgent_task_alertwas byte-identical to the baseline three times: after the first browser probe and the forged writes, after the restart, and after the second browser probe (cmpequal each time). The baseline still reads_packageId com.example.showcase,_provenance package.
Clause by clause, against 「对包内流程点克隆 → 必须输入新机器名才能完成;克隆体出现且可在 Studio 里编辑;原件仍锁定不可改。」
- 必须输入新机器名才能完成 (a new machine name is required): pass. The dialog's submit stays disabled without both fields, and the server's 400 for an illegal name is shown in the dialog. The HTTP arms passed in
5968206129. - 克隆体出现 (the clone exists): pass. Meta, the engine and
sys_metadataall hold it, package-less as ADR-0126 §7.1 intends. It survives a cold restart and dispatches. - 可在 Studio 里编辑 (editable in Studio): pass, where
5968206129failed. The clone is listed under Studio's "Organization flows", is reachable by deep link, opens with an editable canvas and inspector, and is saved as a draft and published from Studio. The edit persists on re-read, after a page reload and after a cold restart. The clone was listed and opened in four fresh browser contexts; the edit was made once and read back in two later ones, the second after the restart. - 原件仍锁定不可改 (the original stays locked): pass. In Studio every authoring affordance is absent or disabled and no write leaves the browser. Server side, the forged draft writes answer 403, and the definition is byte-identical throughout.
Seen on the way. None of these contradicts B1's 「预期」. All three go to the seat, which decides on filing.
- In the "Organization flows" scope, the pending-changes sheet says "Publishing releases the 1 pending draft of this package atomically." This scope has no package, and its Publish promotes each draft separately, not all-or-nothing (feat(app-shell,console): Studio reaches package-less flows at /studio/~org/automations (objectui#11553) objectui#11565 says so itself). The copy is wrong only there.
- When a deep link names a flow the rail does not hold, the pillar's chip reads "No metadata designers are registered in this session, so this flow cannot be designed here." That is beside the correct "not here" line, in a session whose designers work. The chip keys on the selected flow, and none is selected.
- Checklist accuracy, for the sweep:
- clause 7's expected-fail reachability note in
automation.packaged-flow-clone-contractis now stale; studio-authoring.packaged-automation-studio-lockstill describes the objectuif7c52e2shape (a clickable switch, an inspector hard-coded writable) and expects422 writable_package_required, where the live refusal is403 ITEM_LOCKED/NOT_OVERRIDABLE.
- clause 7's expected-fail reachability note in
Derived verdict: pass. The HTTP half passed in
5968206129and the Studio half passes here, so every clause of B1 passes. The run's server is stopped and its file database deleted.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commented
on Oct 4, 2026 ContributorMore actionsClaim revision 1 (claim
5975069117): B1 has passed and is ticked; D2 is added to this run · seatdomain:services#2(#21118) · sessionsession_01DiCSbmJrkzNhuEAier4VoJ· 2026-10-04T01:03Z- B1: the Studio half passed in
5975204933on a console built at the pinab18797215, and the HTTP half passed in5968206129. Every clause passes, so B1 is ticked in the body, a one-line edit that was read back. - D2 is no longer fixture-blocked: the fixture exists on
main.bootStack(…, { multiTenant: 'posture-only' })boots a real, non-degradedisolatedposture (packages/qa/dogfood). It is the harness forms: on a walled tenancy posture a published public form bound to a tenant-scoped object is served (GET 200) but every anonymous submit answers 500 ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED #21476 measured on.automation-toggle-tenant-scope.dogfood.test.tsalready pins the flow toggle door on that posture: an organization owner withoutmanage_metadatais refused, and the platform admin toggles 200.
- D2 run (added to this claim):
- Run that pin.
- Then do a live showcase boot on an
isolatedposture: the packaged flow and action toggles that the Setup board uses (ADR-0126's central switch table) take a platform admin's flip and refuse an ordinary administrator, with the server's own text. - One evidence reply. D2 is ticked only if every clause passes.
- If a walled showcase boot with both principals cannot be stood up, the run stops and says so, and the seat files the fixture carrier.
- F1–F3: unchanged. They wait on the maintainer's spot-check, and they are listed in the seat's round report.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
- B1: the Studio half passed in
objectstack-fleet commented
on Oct 4, 2026 ContributorMore actionsF1–F3: the maintainer delegated the call to this seat; the seat runs all three · seat
domain:services#2(#21118) · sessionsession_01DiCSbmJrkzNhuEAier4VoJ· 2026-10-04T01:12ZProvenance. The maintainer (
os-bill) said this verbatim in this seat's session chat,session_01DiCSbmJrkzNhuEAier4VoJ, at 2026-10-04T01:12Z: 「12438 你决定就好」. The question put to them was whether F1–F3 get a run or stand on the epic's PRs.Decision: run F1, F2 and F3 under the same runner protocol (
docs/qa/platform-checklist/RUNNER.md), with one evidence reply per row. A row is ticked only if every clause passes, and a failing row gets its own card from the run's evidence. The reasons:- Real need: this is the ADR-0126 acceptance card, and its header asks for a hands-on full-chain confirmation. A merged PR shows the code landed, not that the behaviour holds on a live boot.
- Long-term: every ticked row stays backed by a measurement, never by an assumption.
- Evidence gaps: B1's Studio re-run (
5975204933) already measured the packaged flow original locked in Studio, which is part of F2. F2's action half, F1 (packaged permission sets keep Studio save of a package-declared permission set forks it into a silent, undiscoverable overlay #11513's lock / disable / clone behaviour and do not use the central switch table) and F3 (upgrading a pre-ADR-0126 environment needs no migration step, and every packaged artifact is enabled by default) were never run. - Cost: F1 and F2 reuse the current boot. F3 needs one pre-upgrade dataset, created on a pre-epic commit or release and then booted on
main. The run measures that; if no pre-epic baseline can be stood up, it says so instead of simulating one.
Claim revision 2 (claim
5975069117): F1, F2 and F3 are added to this run. The D2 run (revision5975227893) is still in flight.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commented
on Oct 4, 2026 ContributorMore actionsD2 · pass — on a real, non-degraded
isolatedposture only the platform admin flips the packaged flow and action switches; two organization owners, one of them holdingmanage_metadata, are refused 403 in the server's own words and the switches never move · 2026-10-04T01:22ZExecutor for claim
5975069117as revised by5975227893(seatdomain:services#2). Method:docs/qa/platform-checklist/RUNNER.mdthrough thechecklist-testskill. Item:access-security.activation-write-operator-gate(rev 2), clause 5 (the walled-posture clause this card's earlier runs recorded asblocked(fixture)). API is the oracle; the Setup UI leg is optional and was not driven (no console build in this run).Environment
main6ec54f00bab088b4cadecff988cb911e111e9441: a clean detached worktree,pnpm build72/72.- The doors are the two activation write doors:
POST /api/v1/automation/:name/toggle(the Setup board's flow switch, as the A5 network trace recorded) andPOST /api/v1/actions/_activation/:object/:action(the only activation write door for actions). Subjects: the packaged flowshowcase_urgent_task_alertand the packaged actionshowcase_mark_doneonshowcase_task, bothcom.example.showcase. - Stock boot refused, by design.
OS_TENANCY_POSTURE=isolated objectstack dev --ui --seed-admin …on the showcase as shipped exits 1: "✖ FATAL: tenancy posture 'isolated' was requested but @objectstack/organizations could not be loaded, so the organization wall is INACTIVE. Refusing to boot … (ADR-0093 D5)", cause "the host app does not declare it". No example app declares that runtime. - The walled boot. The same showcase with the open-core
@objectstack/organizations(packages/plugins/organizations, Apache-2.0) declared in its ownpackage.jsonasworkspace:*and linked the way pnpm links a workspace dependency. That is the remedy the FATAL prescribes and the ADR-0132 D3 deployment shape ("apps declare it"). It is the real runtime, not the dogfood harness's stand-in. The edit was worktree-local, never committed, and reverted after the run.OS_PORT=38791 OS_TENANCY_POSTURE=isolated OS_PLATFORM_OWNER_EMAIL=admin@objectos.ai OS_AUTH_MEMBERSHIP_POLICY=invite-only objectstack dev --ui --seed-admin -p 38791 -d file:/tmp/os-12438-d2/data.db. A walled posture refuses to boot without the owner address and without a declared membership policy, so both are set.
- Posture as the server reports it: boot banner
Tenancy: isolatedwithOrganizationsamong the 50 plugins;GET /api/v1/auth/config→tenancyPosture: "isolated",multiOrgEnabled: true,degradedTenancy: false. The wall itself is live: tenant rows are stamped with the tenant'sorganization_id, and the platform admin'sPATCHmoving asys_user_permission_setrow into another organization was refused403 PERMISSION_DENIED"…the update would place 'sys_user_permission_set' in another tenant…". - Principals, each proven with
GET /api/v1/auth/get-session. ItsisPlatformAdminis the posture rung (PLATFORM_ADMIN) the §5 gate reads.- Platform admin
admin@objectos.ai: the seeded first user and the declaredOS_PLATFORM_OWNER_EMAIL,emailVerified: true.isPlatformAdmin: true, positionsplatform_admin, org_owner, exec, finance, legal, manager, everyone. - North owner
orgadmin.d2@example.com: minted byPOST /api/v1/auth/admin/create-user, then created "Tenant North D2" (zideMCIq…) throughPOST /api/v1/auth/organization/createand is itsowner.isPlatformAdmin: false, positionsorg_owner, everyone.GET /api/v1/auth/me/permissions: sets includeorganization_admin; system permissionsmanage_org_users, setup.access, setup.write, so nomanage_metadatauntil step 6. - South owner
orgowner2.d2@example.com: owner of a second organization, "Tenant South D2" (l96bdHGx…). Same standing as North before the grant.
- Platform admin
What was done, and what was seen
- Pins, through
scripts/pm/os-verify-lock.sh:pnpm --filter @objectstack/dogfood exec vitest run test/automation-toggle-tenant-scope.dogfood.test.ts→ 1 file, 8/8 passed. It bootsmultiTenant: 'posture-only'(a real, non-degradedisolatedposture over a stand-in org-scoping service) and drives the flow door only. Its tenant owner holds nomanage_metadata, so its refusal is the capability tier, not the §5 operator gate.- No sibling dogfood pin drives the action door on a walled posture: none of the dogfood files that call
/actions/_activation/bootsmultiTenant. Both doors' walled matrix is pinned at unit level:packages/runtime/src/domains/automation-activation-posture-gate.test.ts(19),action-activation-posture-gate.test.ts(27),activation-gate-positions-name-authority.test.ts(12) andtenancy-posture-outage-gates.test.ts(14) → 4 files, 72/72 passed. They include, forgroupand forisolated, "REFUSES a tenant org admin, loudly, and never writes the row" and "ALLOWS the platform operator".
- Baseline (read as the platform admin):
GET /api/v1/automation/_status→ the flowenabled: true, bound: true.GET /api/v1/data/sys_metadata_activation→ no rows. - North owner, no
manage_metadata, both doors,{"enabled": false}then{"enabled": true}→ all four403 PERMISSION_DENIED, verbatim:- flow: "Enabling or disabling an automation flow requires the
manage_metadatacapability." - action: "Enabling or disabling a packaged action requires the
manage_metadatacapability — switching a shipped artifact off is functionally equivalent to deleting it for as long as it stays off, and the switch is not scoped to the caller's organization." - Afterwards: the ledger is still empty, and
_statusstill readsenabled: true.
- flow: "Enabling or disabling an automation flow requires the
- Platform admin switches both off →
200 {"name":"showcase_urgent_task_alert","enabled":false}and200 {"name":"showcase_mark_done","objectName":"showcase_task","enabled":false}._statusreadsenabled: false, bound: false.- The ledger holds two rows,
D9Hu0PVrzT0Nm5yS(flow) andIqy2vjIjQfAd4Mw7(action), bothactive: false. Their key set isactive, created_at, created_by, id, metadata_type, name, package_id, updated_at, updated_by: install-level, with noorganization_idkey. - The switch reaches into a tenant. North created an urgent
showcase_taskin its own organization, and North's runs list for the flow stayed at 0. North invokingPOST /api/v1/actions/showcase_task/showcase_mark_done/:idon that task →409 ACTION_DISABLED"Action 'showcase_mark_done' is disabled — it is switched off for this installation in the packaged-metadata activation ledger …". The task keptdone: false.
- North owner tries to switch both back on →
403 PERMISSION_DENIED, with the same tier sentences. Both rows keep their ids,active: falseandupdated_at(…T01:16:28.855Zand…T01:16:28.924Z). - The §5 persona. The platform admin authored a scratch permission set
qa_d2_metadata_authorwithsystem_permissions ["manage_metadata"]and granted it to North inside North's organization.- The first grant was stamped with the admin's own active organization; re-scoping it was refused, as quoted above. That row was deleted. The admin then joined North as a
memberthrough North's own invitation and granted the set from there. - Proof: North's
me/permissionsnow listsqa_d2_metadata_author, with system permissionsmanage_metadata, manage_org_users, setup.access, setup.write.get-sessionstill readsisPlatformAdmin: false, positionsorg_owner, everyone.
- The first grant was stamped with the admin's own active organization; re-scoping it was refused, as quoted above. That row was deleted. The admin then joined North as a
- North owner holding
manage_metadata, both doors, both directions, with the switches off → all four403 PERMISSION_DENIED, now from the §5 operator gate, verbatim:- flow: "Enabling or disabling a packaged flow writes an INSTALL-WIDE activation row, and this deployment runs the 'isolated' tenancy posture, where that reaches every organization. It requires the platform operator (ADR-0126 §5) — an organization administrator cannot flip an install-wide switch. To customize this flow for your organization, clone it under a new name instead."
- action: the same sentence with "a packaged action", ending "To switch a packaged action off for this installation, ask your platform operator; authoring your own action alongside it stays open to you."
- An invalid body
{"enabled":"yes"}gets the same 403, not a 400. - The ledger rows and their
updated_atare unchanged, and_statusstill readsenabled: false. - Positive control: the same caller's
POST /api/v1/automation/showcase_urgent_task_alert/clone {"name":"qa_d2_tenant_clone",…}→ 200. The remedy the refusal names is open to it, so itsmanage_metadatais live and the gate is not refusing everyone. The clone was then deleted (200).
- Platform admin switches both back on → 200 and 200. This time its active organization was North's, so the admission follows the rung, not the organization.
_statusreadsenabled: true, bound: true.- The same two rows now read
active: true, withupdated_atmoved. They were updated, not recreated. - In the tenant: invoking the action on the task →
200 {"ok":true,…}, and the task readsdone: true, progress: 100. A new urgent task produced one completed run (run_e62e1517…), visible to North and to the admin.
- Per-attempt sweep, switches on: South (no
manage_metadata) and North (with it), each door, each direction, with a state read after every attempt. All eight answered 403PERMISSION_DENIED: South with the tier sentences, North with the §5 sentences. After each one,_statusreadenabled: trueand both rows readactive: truewith unchangedupdated_at. - The position name cannot be handed out.
POST /api/v1/data/sys_user_position {"user_id": North, "position": "platform_admin"}as the platform admin →400 VALIDATION_FAILED"'position' cannot spell a framework-reserved built-in identity name (platform_admin, org_owner, org_admin, org_member). …". Theplatform_adminstanding comes from the operator anchor, not from an assignable row, and the gate reads that standing. A minted row is also pinned byactivation-gate-positions-name-authority.test.ts.
Clause by clause, against 「只有
platform_admin职位能翻开关,普通管理员不行。」- 只有
platform_admin能翻 (the platform admin can flip): pass. Flow and action, off and on, all 200. The state moves in_status, in the ledger rows, and at dispatch inside a tenant organization. - 普通管理员不行 (an ordinary administrator cannot): pass. Two owners of two different organizations were refused at both doors, in both directions, with
403 PERMISSION_DENIEDand the server's own text. The owner who holdsmanage_metadatais refused by the §5 operator gate, which names theisolatedposture. That gate is the one that makes this row more than D1. - No refused attempt moved a switch: pass. Row ids,
activeandupdated_atheld, and_statusheld, after every attempt. - Pins: pass, 8/8 dogfood and 72/72 unit.
Verdict: pass. Limits: the live boot ran
isolated; thegrouparm rests on the unit pins. The Setup UI was not driven.Seen on the way. Neither item contradicts D2; both go to the seat.
- Fixture. The showcase cannot boot walled as shipped, because no example app declares
@objectstack/organizations. This run declared it worktree-locally. A repeatable walled showcase boot needs a carrier for that declaration. - Per-organization seed replay. Each
organization/createlogged 9ERROR [SeedLoader]lines: the showcase'ssys_business_unitseeds (bu_acme,bu_field_ops,bu_west_coast,bu_east_coast,bu_hq_finance) were refused as duplicates on the globalid, then "Deferred reference UNRESOLVED after pass 2 — sys_business_unit.parent_business_unit_id …". It reproduced on both creations (North and South).
The server is stopped, its database and data directory are deleted, and the worktree is removed.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commented
on Oct 4, 2026 ContributorMore actionsF1 · pass — packaged permission sets lock, disable and clone exactly as they did on the pre-epic baseline, and none of it touches
sys_metadata_activation· 2026-10-04T01:58ZExecutor for claim
5975069117, revision 25975289769(seatdomain:services#2). Method:docs/qa/platform-checklist/RUNNER.mdthrough thechecklist-testskill; checklist itemaccess-security.packaged-permission-set-lifecycle(rev 1). HTTP is the oracle; the Setup UI is the customer-view check.Environment
maineea82af67779f504bd5d53fccda3151066cdeaf6, a clean detached worktree with a fullpnpm build(72/72). Console built fresh bypnpm objectui:buildat the pin.objectui-shaab187972159583b595facdcae3c73b50f6f312e9;pnpm check:console-shagreen, every build canary passed, no engine relaxation.- Comparison baseline: the pre-epic commit
107bb4ba4b96bb74913e19b46f7756dd30029d4d(428f9b24a7^, the parent of the epic's first code commit), built in its own worktree (71/71). It already carries the Studio save of a package-declared permission set forks it into a silent, undiscoverable overlay #11513 lock (e170b0ae53is its ancestor). The same probe script ran on it, so the comparison is a live measurement, not a reading of Studio save of a package-declared permission set forks it into a silent, undiscoverable overlay #11513's pins. - Three runs of one HTTP probe: (a) fresh
mainboot, port 38829,file:/tmp/os-12438-f1f2/data.db; (b) the baseline boot, port 38817,file:/tmp/os-12438-f3/data.db; (c)mainbooted on that same baseline database after the upgrade (the F3 environment). Every boot wasOS_PORT=… objectstack dev --ui --seed-admin -p … -d file:…, stocksingleposture. - Personas: admin
admin@objectos.ai(proven withGET /api/v1/auth/get-session); in each run a fresh member made withPOST /api/v1/auth/admin/create-userand granted the packaged setshowcase_contributorthrough asys_user_permission_setrow. Its probe verb is one only that set grants:PATCH /api/v1/data/showcase_project/:idon runs (b) and (c),POST /api/v1/data/showcase_field_zooon run (a), because the baseline's showcase does not grantshowcase_field_zooto contributors yet.
What was done, and what was seen (the packaged base is
showcase_contributor,managed_by package,package_id com.example.showcase)-
Lock.
PATCH /api/v1/data/sys_permission_set/:idwith anobject_permissionschange, and again with adescriptionchange → 403NOT_OVERRIDABLEon all three runs, with a byte-identical message on baseline andmain:[Security] Permission set 'showcase_contributor' is declared by package 'com.example.showcase' and is locked in this environment — editing it here would silently fork it from the package, and the fork would win over every future upgrade with no signal. Clone it instead (the "Clone" action on the permission set, or POST /api/v1/data/sys_permission_set with a new name) and edit the clone — the clone is your organization's own set, and upgrades keep flowing to 'showcase_contributor' untouched.
The six definition facets and
updated_atwere unchanged afterwards on every run. -
Disable / enable (row state).
PATCH … {"active": false}→ 200 and the row readsactive false; the member's probe verb → 403PERMISSION_DENIEDwhile inactive;{"active": true}→ 200 and the verb works again (200/201). The member's assignment row is present at every step. Identical on all three runs. -
Clone. The
clone_permission_setaction's own payload (new label + new name, the six carried facets,active: true) → 201 on all three runs. The clone readsmanaged_by admin,package_idnull,admin_scopenull, active; all six facets equal the base (weak forsystem_permissionsandtab_permissions, which are empty on this base); no lineage-shaped key. Its definition is freely editable (the PATCH the base refused → 200, persisted). The same clone name again → 409 on all three runs. -
Setup UI on
main(admin, form sign-in, fresh browser context): the record header offers Activate / Deactivate / Clone; Deactivate shows "Deactivate this permission set? Existing assignments stay in place but stop granting access until re-activated." and sendsPATCH {active:false}→ 200; Activate → 200; the Clone dialog marks "New Display Name" and "New API Name" required, says "Delegated-admin scope is not copied…", shows the facets as "Carried over unchanged (read-only)", and its submit →POST201, toast "Permission set cloned". An in-place Edit → Update sendsPATCH→ 403 with the sentence in step 1, and the form renders "You don't have permission to save this record." (banner and toast). -
Not the central switch table.
- On the baseline,
GET /api/v1/data/sys_metadata_activation→ 404 "Object 'sys_metadata_activation' is not registered": the ledger did not exist yet. - On
main(runs a and c): the ledger read 0 rows before and 0 rows after the whole lifecycle, nopermissionrow, no row naming the base or a clone. - Positive control on run (a): switching the packaged flow
showcase_urgent_task_alertoff, the packaged actionshowcase_mark_doneoff, and the packaged setshowcase_executiveoff, side by side, leaves exactly two rows,flow:showcase_urgent_task_alertandaction:showcase_mark_done. The set's off-state lands only in its ownsys_permission_set.activecolumn. All three were restored, and the two rows readactive true. - The toggle doors refuse it:
POST /api/v1/automation/showcase_contributor/toggle→ 404 "Flow 'showcase_contributor' not found";POST /api/v1/actions/_activation/sys_permission_set/showcase_contributor→ 404 "…has no declaration — there is nothing to switch off. The activation ledger addresses DECLARED actions (ADR-0126 §4)." The ledger was unchanged after both. - The listings never carry it:
GET /api/v1/automation/_statuslists 30 flows and no permission set (29 on the baseline, also none); Setup › Packaged Automation has two sections, "Packaged flows" and "Packaged actions", and names no permission set on run (a) or run (c), where the two tables hold 30 and 145 rows.
- On the baseline,
-
Across the upgrade itself (F3 environment): the set the operator switched off on the baseline (
showcase_auditor) still readsactive falseaftermainbooted on that database, and the clone made on the baseline is stillmanaged_by admin, still listed in Setup (Inactive tab) and still editable (PATCH description→ 200). Thename | managed_by | activelisting of all 18 permission-set rows is identical before and after the upgrade.
Differences found between baseline and
main, and where they come from. Two refusal envelopes on edge paths differ, and both come from a later permission-set fix,8f6d83147b(2026-09-20), which is not one of the epic's PRs:- creating a set under the packaged base's own name: baseline 409 "permission set 'showcase_contributor' already exists" →
main403NOT_OVERRIDABLE"…Choose a different name for your set, or clone 'showcase_contributor' (the "Clone" action on the permission set) and edit the clone." The lock's refusal now answers first, and it names the clone path; - the duplicate-name 409 now carries the code
UNIQUE_VIOLATION; on the baseline it had no code.
Both are still refusals that write nothing. Lock, disable and clone keep the same semantics, and neither difference routes through the activation ledger.
Against 「包内权限集的锁定、停用、克隆(#11513 已有功能)行为与升级前一致——权限集不走新的中央开关表,这是裁定过的设计。」
- 锁定一致 (the lock is unchanged): pass. Same 403
NOT_OVERRIDABLE, same sentence, definition untouched, baseline vsmain. The same-name-insert envelope above is the one refinement. - 停用一致 (disable is unchanged): pass. A row-state flip on the kind's own
activecolumn, enforced live, assignments kept, on both sides of the upgrade, and an operator's pre-upgrade choice survives it. - 克隆一致 (clone is unchanged): pass. 201, every facet carried,
admin_scopenot copied, org-owned, editable, no lineage, on both sides. - 不走中央开关表 (not the central switch table): pass. No
sys_metadata_activationrow from any permission-set operation, against a positive control proving the ledger does record flow and action switches; neither toggle door accepts the name; neither listing shows it.
Verdict: pass.
Seen on the way, for the seat. None of these changes the verdict.
- The checklist item's c6 (the Setup form should show the server's refusal text) still fails as on 2026-09-29: the form substitutes "You don't have permission to save this record." That predates the epic. objectui at the baseline's pin
190fbd01d0substitutes the same string for every permission error unconditionally (read from source; that console was not built). It is the The ownership-transfer refusal names the field it refused, but sets nouserMessage, so every console substitutes an opaque 「no permission to save」 (from objectui#10108) #19397 class, closednot_planned. - Every boot of
mainon a database that holds a clone logs one WARN per clone:[permission_set_declaration_unowned] declared permission set "…" has no owning package — not materialized … the Setup admin surface reads sys_permission_set and cannot see this set. That is false for a clone: the row exists and Setup lists it. Details are in the F3 reply.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commented
on Oct 4, 2026 ContributorMore actionsF2 · pass — in Studio, a packaged action's definition is read-only and sends no write; forged writes are refused with the server's own codes; the packaged flow is still locked as B1 measured · 2026-10-04T01:59Z
Executor for claim
5975069117, revision 25975289769(seatdomain:services#2). Method:docs/qa/platform-checklist/RUNNER.mdthrough thechecklist-testskill. The flow half re-confirmsstudio-authoring.packaged-automation-studio-lock(rev 1) briefly, as B1 already measured it in5975204933. The action half has no checklist item of its own, so it was driven from the card row.Environment
maineea82af67779f504bd5d53fccda3151066cdeaf6, a clean detached worktree with a fullpnpm build(72/72).- Console built fresh by
pnpm objectui:buildat the pin.objectui-shaab187972159583b595facdcae3c73b50f6f312e9. The dist stamp equals the pin,pnpm check:console-shais green, and the bundle, single-zod and spec-injection canaries passed. No engine relaxation was needed, as for B1 at this pin. - Boot:
OS_PORT=38829 objectstack dev --ui --seed-admin -p 38829 -d file:/tmp/os-12438-f1f2/data.db, stocksingleposture.GET /api/v1/packagesreportscom.example.showcasewritable: false. - Persona: admin
admin@objectos.ai, form sign-in, each browser pass in its own fresh context (headless Chromium through Playwright). Every non-GET request under/api/was recorded. The write count below excludes only sign-in, two read-only POST endpoints (/analytics/dataset/query,/security/explain) and the console's ownsys_user_preferencerecents write.
What was done, and what was seen
The packaged action in Studio. The action is
showcase_mark_done("Mark Done", type script, objectshowcase_task,_packageId com.example.showcase,_provenance package).- Path. Studio › ObjectStack Showcase › Data › Task › Advanced › Actions (
/_console/studio/com.example.showcase/data?surface=object:showcase_task). This is the Studio surface where an object's actions are authored: they are edited on the object draft and saved with it. A screenshot confirmed it rendered before any DOM read. It lists "Actions (10)", and the header and the rail footer carry the package's "Read-only" badge. - No authoring affordances. The list has no add or delete control. The only Save-type control, Publish, is
disabled. - The inspector is read-only. With "Mark Done" selected, every inspector input is
disabledorreadOnly. That covers Label, Name, Script body, thevisiblepredicate, Success message and the "More fields" section. The first twelve comboboxes read (Object, Icon, Variant, Type, What it does, Script language, Execution, …) are all disabled. The same census on "Reassign…" (type flow) and "Recalculate Estimate" (type api) found the same: no editable control except the rail's "Search objects…" box. - No write leaves the browser. I tried a forced fill of Label, typing into it, a forced click on Publish, and opening the Type and Object dropdowns with a keyboard pick. Then I waited 7 seconds. Label still read "Mark Done", and zero write requests were sent in two independent browser passes.
- The Setup metadata admin's page for the same action (
/_console/apps/com.objectstack.setup/metadata/action/showcase_mark_done) agrees. It shows "This action is provided by an installed package, so it is read-only at runtime. To change it, edit it in its source package and republish — or create a new action from scratch.", has no editable input, has no save, publish or delete control, and sent zero writes.
Forged writes, the server as the authority (admin; the body was the served definition with the label changed):
6.PUT /api/v1/meta/action/showcase_mark_doneand the same with?mode=draft→ 403NOT_OVERRIDABLE:Metadata item 'action/showcase_mark_done' is provided by a code package, and its packaged base is locked against in-place edits. Switch it off (POST /api/v1/actions/_activation/:object/:action, body {enabled: false}, :object = global for an object-less action; operator-only where one install serves several organizations). See docs/adr/0126-packaged-metadata-customization-model.md.
- The same two with
&package=com.example.showcase→ 403ITEM_LOCKED"Cannot overlay 'action' in package 'com.example.showcase': that package is read-only, and its packaged base is locked against in-place edits. Switch it off (…)". - The object-draft door the Actions view saves through,
PUT /api/v1/meta/object/showcase_task?mode=draft&package=com.example.showcasewith the edited action inactions[]→ 403ITEM_LOCKED"Cannot overlay 'object' in package 'com.example.showcase': that package is read-only …". POST /api/v1/meta/action/showcase_mark_done/publish→ 404NO_DRAFT, because nothing was staged.DELETE /api/v1/meta/action/showcase_mark_done→ 200 "No customization overlay found for action/showcase_mark_done — already at artifact default." That is the overlay-reset door answering a no-op, not a removal: the action is still served, byte-identical (step 10).GET /api/v1/meta/action/showcase_mark_done,GET /api/v1/meta/object/showcase_taskandGET /api/v1/meta/flow/showcase_urgent_task_alertwere byte-identical before and after all the probes, andGET /api/v1/meta/_drafts→{"drafts":[]}.
The packaged flow, re-confirmed briefly (B1 measured it fully in
5975204933):
11. Studio › Automations,?surface=flow:showcase_urgent_task_alert, in a fresh context. The header switch is disabled, titled "Read-only package — switch to or create a writable package to edit.", and Publish is disabled. There is no "New" button, no "Add node" and no "Insert node". A forced click on the switch left it checked; a node click plus the Delete key and a 7-second wait sent zero writes.
12. Forged:PUT /api/v1/meta/flow/showcase_urgent_task_alert?mode=draft&package=com.example.showcase→ 403ITEM_LOCKED; withoutpackage→ 403NOT_OVERRIDABLE("…locked against in-place edits. Clone it under a new name to customize it (POST /api/v1/automation/:name/clone, body {name, label}), or switch it off …"). The definition was byte-identical afterwards (step 10).Against 「包内流程/动作的定义本体在 Studio 中仍不可原地编辑。」
- 动作的定义本体不可原地编辑 (a packaged action's definition cannot be edited in place in Studio): pass. Read-only badge, every inspector control disabled or read-only, no add, delete or publish, and zero writes sent. Behind the courtesy layer, the server refuses every forged write door:
/meta/action403NOT_OVERRIDABLE/ITEM_LOCKED, and/meta/objectdraft 403ITEM_LOCKED. The definition is byte-identical afterwards. - 流程的定义本体不可原地编辑 (a packaged flow's definition cannot be edited in place in Studio): pass, consistent with B1. Switch disabled, no authoring affordance, zero writes, and the forged draft writes answer 403.
Verdict: pass.
Seen on the way, for the seat. Neither changes the verdict.
- The layered read
GET /api/v1/meta/action/showcase_mark_done?layers=truereportslock: "none",editable: true,deletable: truefor the packaged action. It reports the same for the packaged flow and for a packaged view, even though the server refuses in-place writes to the first two. Studio does not draw its read-only state from these fields (it usesGET /api/v1/packageswritable), so nothing here is editable. But a client or agent readingeditablewould be told the opposite of what the write door does. I did not trace where those fields are computed. - A node click on the read-only flow canvas opens no inspector: Studio Automations: on a read-only package, clicking a flow canvas node opens no inspector, so packaged flows' node configuration cannot be read (moved from objectstack-ai/objectstack#21575) objectui#11546, as B1 recorded. It hides the configuration and does not weaken the lock.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commented
on Oct 4, 2026 ContributorMore actionsF3 · pass — a pre-epic showcase database boots on
mainwith no migration step; every packaged flow and action reads enabled; the baseline data, runs, grants and operator choices read back; a packaged flow still fires; 0 ERROR lines · 2026-10-04T02:00ZExecutor for claim
5975069117, revision 25975289769(seatdomain:services#2). Method:docs/qa/platform-checklist/RUNNER.mdthrough thechecklist-testskill. No checklist item covers this row (the 2026-09-29 run recorded itblocked(fixture)), so it was driven from the card row. Nothing was simulated: no row was edited by hand, and the upgrade is a real boot of newer code on the older database file.Environment
- Baseline:
107bb4ba4b96bb74913e19b46f7756dd30029d4d, which is exactly428f9b24a7^, the parent of the epic's first code commit ("declaresys_metadata_activation…", feat(platform-objects): declaresys_metadata_activation, the ADR-0126 §4 activation ledger #12185). I used the commit itself rather than a release because it built in this container with the same toolchain asmain(Node 22.22.0, pnpm 10.31.0, samepackageManagerpin); a fullpnpm buildpassed, 71/71. The first attempt was OOM-killed in@objectstack/spec's DTS step while the console build ran beside it; that was memory contention, not toolchain drift, and the rebuild alone passed. It is pre-epic: no file underpackages/orexamples/mentionssys_metadata_activationat that commit, and Studio save of a package-declared permission set forks it into a silent, undiscoverable overlay #11513's lock (e170b0ae53) is an ancestor. - Target:
maineea82af67779f504bd5d53fccda3151066cdeaf6, full build 72/72, console built fresh at the pin.objectui-shaab187972159583b595facdcae3c73b50f6f312e9(check:console-shagreen). - One database file for both:
file:/tmp/os-12438-f3/data.db, port 38817. Both boots used the same line,OS_PORT=38817 objectstack dev --ui --seed-admin -p 38817 -d file:/tmp/os-12438-f3/data.db, at the default log level (warn; neitherOS_LOG_LEVELnorLOG_LEVELwas set).--seed-adminonly seeds an empty database, so on the second boot it did nothing. - Personas: admin
admin@objectos.ai, seeded by the baseline boot; and a member created on the baseline withPOST /api/v1/auth/admin/create-user.
What was done, and what was seen
On the baseline (pre-epic):
- Boot: 29 flows, 22 bound, 0 ERROR lines.
GET /api/v1/data/sys_metadata_activation→ 404 "Object 'sys_metadata_activation' is not registered".GET /api/v1/automation/_status→ 29 flows, allenabled: true. - Ordinary data: an account "QA F3 Baseline Account"; tasks "QA F3 baseline task A" (low) and "… B" (medium) on a seeded project. My project create was refused with the baseline's own validation, "Account is required", because my body omitted the account; I used a seeded project instead.
- A packaged-flow run: an urgent task fired
showcase_urgent_task_alert, giving runrun_81d1ca70…, completed.sys_automation_runthen held 14 rows, including the seed's paused approval runs and connector runs. - A permission-set assignment: a member granted the packaged
showcase_contributorthrough asys_user_permission_setrow. Its contributor-only verb (PATCH /api/v1/data/showcase_project/:id) → 200. - Operator choices: the packaged set
showcase_auditorswitched off ({active:false}→ 200). F1's baseline probe also ran on this database. It added a second member and grant, and a cloneqa_f1_baseline_clone, which was then edited and switched off. - Stop: SIGTERM; the log ends "Server stopped", health went to
000, the port was freed, and the WAL was checkpointed intodata.db. A read-only open of the file shows 89 tables and nosys_metadata_activationtable.
mainon the same file, with no migration command:
7. Boot succeeded: "Server is ready", health 200, console mounted. 0 ERROR lines in the boot log, and still 0 after all the requests below. The banner reads 30 flows, 20 bound, the same as a freshmainboot. No line asked for a migration before the server would serve. The lines that mention one:- four
[schema-drift]lines report leftovers the newer schema no longer declares: the columnsys_account.issuerand three generated indexes onsys_account,sys_verificationandsys_job_queue. Each says"os migrate apply --allow-destructive" to drop it. That is an optional destructive cleanup, nothing depended on it, none of it concerns ADR-0126, and I did not run it; - one
[Protocol]line: the stored baseline clone "carries a pre-protocol shape" (allowRestore), and it was converted on load by the ADR-0087 conversionpermission-allow-restore-purge-removed, with no action.
- The ledger exists, empty:
GET /api/v1/data/sys_metadata_activation→ 200, 0 rows. The boot created the table itself. - Every packaged flow and action reads enabled:
_statuslists all 30 packaged flows withenabled: true.- Setup › Packaged Automation (admin, browser): "Packaged flows" 30 rows, 30 On; "Packaged actions" 145 rows, 145 On; all 175 switches checked, none off.
- A packaged action still dispatches:
POST /api/v1/actions/showcase_task/showcase_mark_done/:idon baseline task A → 200, and the task now readsdone true,progress 100. There was noACTION_DISABLED.
- The baseline data reads back:
- the three tasks (titles and priorities intact) and the account;
- the baseline run
run_81d1ca70…is still listed, completed. All 14 baselinesys_automation_runrows are present (diff by id: 0 missing); - the member's assignment row still links the same user and set. The member signs in, and the contributor-only verb → 200;
showcase_auditorstill readsactive false, so the operator's choice survived. Thename | managed_by | activelisting of all 18 permission-set rows is identical before and after;- row counts: tasks 13 = 13, accounts 15 = 15, field-zoo 2 = 2. Projects went 5 → 6: the seed upserts projects keyed on name, and my probes had renamed the seeded "Data Platform" project, so the
mainboot's seed re-created one under that name. All five baseline project ids are still there.
- A packaged flow still runs: a new urgent task gave a new
showcase_urgent_task_alertrun,run_11e755f5…, completed.
Against 「在一个升级前已有数据的环境升级到最新 main → 无需任何迁移动作,所有包内工件默认启用,已有功能无异常。」
- 无需任何迁移动作 (no migration action needed): pass. No migration command was run, the boot served at once, it created the ledger table itself, and it converted the one legacy stored shape on load. The
[schema-drift]lines offer an optional--allow-destructivecleanup of four pre-existing leftovers outside ADR-0126; I read them as advisory, not as a step the upgrade needs. The maintainer's spot-check can say otherwise. - 所有包内工件默认启用 (every packaged artifact enabled by default): pass. 30/30 packaged flows and 145/145 packaged actions read On with an empty ledger, and a packaged action dispatches. Packaged permission sets keep their own
activecolumn (card boundary 4): all read active exceptshowcase_auditor, which the operator switched off before the upgrade, and that choice is kept. - 已有功能无异常 (existing functions work normally): pass. Records, runs, grants and operator choices read back; a packaged flow fires; a packaged action dispatches; 0 ERROR lines at the default level.
Verdict: pass. Environment cleanup: both servers are stopped,
/tmp/os-12438-f*is deleted, and the worktrees are removed.Seen on the way, for the seat (does not change the verdict; WARN, not ERROR):
- Every
mainboot on a database holding an org-owned permission set logs, once per set:WARN [security] [permission_set_declaration_unowned] declared permission set "qa_f1_baseline_clone" has no owning package — not materialized. … the Setup admin surface reads sys_permission_set and cannot see this set …. - For a clone, that is false. The row exists (
managed_by admin), Setup lists it in the Inactive tab, and it stays editable. - Reproduced twice: on this upgraded database (one clone, one line), and on a restart of a fresh
maindatabase that never saw the baseline (two clones, two lines; one of them was made with the Setup Clone dialog). So the cause is not the upgrade, and every environment that has ever used the Clone action gets these lines at every boot. - Mechanism, read and not pinned:
bootstrapDeclaredPermissions(packages/plugins/plugin-security/src/bootstrap-declared-permissions.ts, ateea82af677) walks everypermissionitem in the engine registry. The same boot registers the environment's own sets there ("[Registry] Registered permission: qa_f1_baseline_clone" appears in the same log). The loop takesps._packageId ?? ps.packageId, andupsertPackagePermissionSetreports "unowned" before it reads the existing row, which would have classified the set as environment-authored.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
- Baseline:
objectstack-fleet commented
on Oct 4, 2026 ContributorMore actionsAcceptance complete: every row is ticked on evidence · closing
completed· seatdomain:services#2(#21118) · sessionsession_01DiCSbmJrkzNhuEAier4VoJ· 2026-10-04T02:03ZEvery row of this card is now ticked. Each tick rests on a live-boot evidence reply, and none was simulated.
Row Evidence A1–A2, C1–C2, D1, E2 and the rest earlier rounds (see the thread) A3, A5, B2, E1 first run under ruling B: 5968196694,5968201206,5968211463,5968216948B1 HTTP half 5968206129; Studio half5975204933, re-run on a console built at the pinab18797215D2 5975356896: a liveisolatedboot, where only the platform admin flips the packaged flow and action switchesF1 5975590364: packaged permission sets lock, disable and clone exactly as before, and the central switch table is never touchedF2 5975593929: packaged action and flow definitions are read-only in Studio, and forged writes get 403F3 5975597571: a pre-epic database (428f9b24a7^) boots onmainwith no migration step. Every packaged artifact is on by default, and the data reads backF1–F3 were run on the maintainer's delegation, 「12438 你决定就好」 (decision
5975289769).Filed from these runs:
- [finding][repo:objectui] Studio Organization flows page: the publish sheet says package drafts release atomically, and a deep-link miss shows a false no-designers notice #21655: objectui copy on the Organization flows page;
- [finding] On a walled showcase deployment every organization/create replays fixed-id sys_business_unit seeds that collide on the global id: 9 SeedLoader errors and an unresolved parent reference per new organization #21665: fixed-id seed replay collides in every new organization;
- [finding] POST /data on a walled posture silently replaces a platform admin's explicit organization_id with the admin's active organization (201), while the equivalent PATCH refuses loudly #21666: a create silently replaces an explicit
organization_id; - [finding] Every boot warns permission_set_declaration_unowned for each cloned (org-owned) permission set, saying Setup cannot see it, while the row exists and Setup lists and edits it #21669: a false
permission_set_declaration_unownedboot warning for cloned sets; - [finding] The layered metadata read reports lock none, editable true and deletable true for packaged flows and actions that the write doors refuse with NOT_OVERRIDABLE #21670: the layered read reports
editable: truefor locked packaged items.
None contradicts a row.
Noted for the checklist sweep, not carded:
automation.packaged-flow-clone-contract's clause-7 expected-fail note is stale;studio-authoring.packaged-automation-studio-lockstill expects422 writable_package_required, but the live refusal is403 ITEM_LOCKED/NOT_OVERRIDABLE;access-security.packaged-permission-set-lifecyclerev 1 clause c6 conflicts with ruled console behaviour (The ownership-transfer refusal names the field it refused, but sets nouserMessage, so every console substitutes an opaque 「no permission to save」 (from objectui#10108) #19397, closed as not planned);- F3 has no checklist item.
The maintainer can reopen this card if a spot-check disagrees.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
- added a commit that references this issue
on Oct 7, 2026
Epic #12150 的人工验收卡。开发已全部合并 v17 主线(九个 PR,清单见 epic);本卡供同事逐项确认,从客户视角测,不看代码。测试人:领一项勾一项,发现与「预期」不符的,在本卡评论区记录(写清:哪一项、实际看到什么、截图/报错原文),⛔ 不要自行修改或绕过。
一句话背景
客户安装应用包(Marketplace 应用)之后:展示类(视图/仪表盘等)可以直接改;行为类(包内流程、动作)锁定不许改,但可以关掉,流程还可以克隆一份自己的来改;结构类(包内对象)通过扩展包加自己的字段。本卡确认这套承诺真的成立。
环境准备
main(≥af56546a,全部九个 PR 已含),按标准方式启动 showcase 示例应用(含自动化服务的常规组合)。测试项
A. 流程开关(Setup 页)
B. 流程克隆(Setup 页)
C. 动作开关(Setup 同页动作区)
ACTION_DISABLED),且拒绝发生在动作逻辑执行之前。D. 权限门槛
platform_admin职位能翻开关,普通管理员不行。E. 文档承诺
capabilities/integrations与build-without-code中关于 Marketplace 应用的表述已按三分层说话(展示类直接改/行为类停用+克隆/结构类扩展字段),不再有无条件的「装完可在 Studio 定制」。F. 回归(不应发生变化的)
G. 部署形态(PR #12419 已合并
af56546a,可测)已知边界(设计如此,勿当缺陷上报)
active字段,不并入中央开关表(裁定:2026-08-26,Ledger convergence: registration home + one store implementation + permission-set convergence (ADR-0126 §4/§8, maintainer-ruled bundle) #12159)。Refs: Epic #12150 · ADR-0126(
docs/adr/0126-packaged-metadata-customization-model.md)