Repository navigation
验收测试:包内元数据定制(ADR-0126)——流程/动作的停用+克隆 全链路人工确认 #12438
Copy link
Copy link
Closed
Labels
area:workflowApprovals and automation — the work that runs without a person driving itApprovals and automation — the work that runs without a person driving itdomain:servicespriority:p2Medium: important, M3Medium: important, M3tracking
Description
Epic #12150 的人工验收卡。开发已全部合并 v17 主线(九个 PR,清单见 epic);本卡供同事逐项确认,从客户视角测,不看代码。测试人:领一项勾一项,发现与「预期」不符的,在本卡评论区记录(写清:哪一项、实际看到什么、截图/报错原文),⛔ 不要自行修改或绕过。
一句话背景
客户安装应用包(Marketplace 应用)之后:展示类(视图/仪表盘等)可以直接改;行为类(包内流程、动作)锁定不许改,但可以关掉,流程还可以克隆一份自己的来改;结构类(包内对象)通过扩展包加自己的字段。本卡确认这套承诺真的成立。
环境准备
- 拉最新
main(≥af56546a,全部九个 PR 已含),按标准方式启动 showcase 示例应用(含自动化服务的常规组合)。 - 一个管理员账号 + 一个普通用户账号。
- 确认环境里有包内(packaged)流程和动作可测(showcase 自带)。
测试项
A. 流程开关(Setup 页)
- A1 入口:管理员登录 → Setup → 「Packaged automation / 打包自动化」页面存在,列出包内流程,每条显示启用/停用状态(默认全部启用)。
- A2 停用生效:停用一个包内流程 → 页面状态翻转;触发该流程的业务事件不再执行该流程。
- A3 持久性(关键):停用后重启服务 → 停用状态仍在,流程仍不执行。⛔ 若重启后悄悄恢复执行,是严重缺陷,单独标注。
- A4 恢复:重新启用 → 流程恢复执行。
- A5 子流程保护:选一个被其他包内流程当作子流程调用的流程,尝试停用 → 被拒绝,报错信息点名列出调用方流程(服务端原文,界面不加工)。
B. 流程克隆(Setup 页)
- B1 克隆:对包内流程点克隆 → 必须输入新机器名才能完成;克隆体出现且可在 Studio 里编辑;原件仍锁定不可改。
- B2 无血缘:界面任何地方都不显示克隆体与原件的来源关系、差异对比、「已定制」角标——这是设计如此,不是缺陷。
C. 动作开关(Setup 同页动作区)
- C1 停用生效:停用一个包内动作 → 从界面/接口调用该动作被拒绝(HTTP 409,错误码
ACTION_DISABLED),且拒绝发生在动作逻辑执行之前。 - C2 持久 + 恢复:重启后停用仍在;重新启用后调用恢复正常。
- C3 无克隆:动作区没有克隆按钮——设计如此(只特许了开关)。
D. 权限门槛
- D1:用普通用户尝试翻任何开关(流程或动作)→ 被拒绝,拒绝文案为服务端原文。
- D2(如具备多组织 group/isolated 形态环境):只有
platform_admin职位能翻开关,普通管理员不行。
E. 文档承诺
- E1:文档页
capabilities/integrations与build-without-code中关于 Marketplace 应用的表述已按三分层说话(展示类直接改/行为类停用+克隆/结构类扩展字段),不再有无条件的「装完可在 Studio 定制」。
F. 回归(不应发生变化的)
- F1 权限集不变:包内权限集的锁定、停用、克隆(Studio save of a package-declared permission set forks it into a silent, undiscoverable overlay #11513 已有功能)行为与升级前一致——权限集不走新的中央开关表,这是裁定过的设计。
- F2 定义锁定:包内流程/动作的定义本体在 Studio 中仍不可原地编辑。
- F3 升级平滑:在一个升级前已有数据的环境升级到最新 main → 无需任何迁移动作,所有包内工件默认启用,已有功能无异常。
G. 部署形态(PR #12419 已合并 af56546a,可测)
- G1 无自动化组合:在不含自动化服务、但有对象+动作的组合上启动 → 动作开关同样可用(修复前此处报 503)。
已知边界(设计如此,勿当缺陷上报)
- 任何地方都不展示克隆血缘/差异/「已定制」标识(裁定:§9)。
- 动作只有开关,没有克隆(裁定:仅特许开关)。
- 非包内(自建)流程的开关不持久化(重启失效)——中央开关表只管包内工件,自建流程有明确的非持久警告。
- 权限集/职位/能力沿用各自的
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)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:workflowApprovals and automation — the work that runs without a person driving itApprovals and automation — the work that runs without a person driving itdomain:servicespriority:p2Medium: important, M3Medium: important, M3tracking
Activity
objectstack-fleet commentedon Oct 2, 2026
Triage: re-check of an owned card after another 24 h without an update. Nothing moved, and it is still held with nobody driving
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-02T19:59Z. ⛔ Not a claim, ⛔ not a dispatch, ⛔ the assignee is not touched.Since my last re-check (
5939462870, 2026-10-01T19:59Z):domain:specseat 5 (os-justin), has been off shift since 2026-10-01T06:50Z.mainshowcase boot, perdocs/qa/platform-checklist/RUNNER.md;blockedon its fixture, and is not simulated.Who acts. This is a human-acceptance (
tracking) card, which triage does not dispatch. Either the maintainer names the next holder, or the holder releases it here. Until one of those happens, this re-check repeats every 24 h, with no other action.Generated by Claude Code
objectstack-fleet commentedon Oct 3, 2026
Ruling recorded: B — the five re-runnable rows are run by a seat with the platform checklist runner, with evidence per row.
tracking→pm:queueTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-03T09:16Z. ⛔ Not a claim, ⛔ not a dispatch.The ruling. The maintainer, in the live chat with this seat, verbatim: 「12438 B」. It answers ruling batch 1, item 4. The options presented were:
The maintainer has also cleared this card's assignee. The holder seat had been off shift since 2026-10-01T06:50Z.
What B means here (execution parameters):
mainshowcase boot, followingdocs/qa/platform-checklist/RUNNER.md, through thechecklist-testrunner.blockedon its fixture. ⛔ It is not simulated.State:
pm:queue·priority:p2·domain:services(the packaged-automation disable and clone surface, ADR-0126) ·area:workflow.trackingstays.Generated by Claude Code
objectstack-fleet commentedon Oct 3, 2026
Claim: PM loop round 2 · 2026-10-03T09:40Z
Session:
session_01DiCSbmJrkzNhuEAier4VoJAccount:
os-bill(the seat's linked user asGET /useranswers it; the card's assignee)Branch: none (an acceptance run, not a code change)
Worktree:
objectstack-issue-12438(a cleanorigin/maincheckout to boot from; nothing is committed)Domain:
domain:servicesSeat:
domain:services#2(seat post #21118)File surface: none. ⛔ No code, no PR, no commit. The writes are evidence replies on this card only.
Container & model:
M,mode:subagent,model: opus(operator text; a runner execution followingdocs/qa/platform-checklist/RUNNER.mdthrough thechecklist-testskill).Clause-②: no
Thread-read: 5967646603
Serial constraints cleared, read in this act: no seat or session holds this card. The maintainer cleared its assignee (triage
5967646603). The run bootsmain, so it races no PR.Selection:
priority:p2, maintainer ruling 「12438 B」 (batch 1, item 4, recorded by triage5967646603). The lane's other cards are held: #21441 waits on #21485 (another lane), #21476 on #21475 (another session), and #20751 stage 8 is in flight.Direction (ruling B, the execution parameters recorded in
5967646603):mainshowcase boot, followingdocs/qa/platform-checklist/RUNNER.md.Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commentedon Oct 3, 2026
A3 · pass — a disabled packaged flow stays disabled across cold restarts, and it does not run · 2026-10-03T10:18Z
Executor for claim
5967871940(seatdomain:services#2), under ruling B as triage5967646603recorded it. Method:docs/qa/platform-checklist/RUNNER.md, checklist itemautomation.packaged-flow-disable-durable(rev 2). API is the oracle; the Setup page is a second, customer-view check.Environment
maina3873542ca76c6921f40a04f55ea4738d1be5987: a clean detached worktree with a fullpnpm build..objectui-sha89cad75d5570; its stamp equals the pin.OS_PORT=38643 objectstack dev --ui --seed-admin -p 38643 -d file:…/data.db. Every restart below reuses that one database file. Stocksingleposture.GET /api/v1/auth/get-session:admin@objectos.ai, positionsplatform_admin, org_owner, …;auditor.demo@example.com, positionsorg_member, auditor, everyone.What was done, and what was seen. The flow is
showcase_urgent_task_alert, packaged incom.example.showcase. It fires when ashowcase_taskbecomespriority = urgent.Baseline: the runs list read 0. One urgent task was created, and the list read 1 (
run_6649d52d…, completed).Toggle off:
POST /api/v1/automation/showcase_urgent_task_alert/toggle {"enabled": false}→200 {"name":"showcase_urgent_task_alert","enabled":false}.GET /api/v1/data/sys_metadata_activationreturns one row: idLSIuP2_DBKdRHBPf,metadata_type flow,package_id com.example.showcase,active false.organization_id.Trigger while disabled:
POST …/showcase_urgent_task_alert/trigger→409 FLOW_DISABLED, verbatim:Cold restart 1. I stopped the whole server process tree (health answered
000, and the port was free), then booted a new server on the same database file.GET /api/v1/automation/_status→{"name":"showcase_urgent_task_alert","enabled":false,"bound":false,…}.ERRORlines. The per-event ERROR the 2026-09-29 run recorded (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) did not appear.Cold restart 2, with
--log-level info. The default level iswarn, which prints no INFO line, so this boot was needed to see the ledger line. The boot log reads:_statusagain readenabled:false, bound:false. An urgent create left the runs list at 1.Re-enable:
{"enabled": true}→ 200. The same rowLSIuP2_DBKdRHBPfnow readsactive true: it was updated, not deleted. The next urgent create gave run 2 (run_9e1ea18a…).Customer view, Setup › Packaged Automation (admin, browser):
active false._statusreadenabled:false, bound:false, the boot log named the flow, and an urgent create added no run.Ordinary user:
POST …/toggle {"enabled": false}→403 PERMISSION_DENIED"Enabling or disabling an automation flow requires themanage_metadatacapability." The ledger was unchanged.Clause by clause, against 「停用后重启服务** → 停用状态仍在,流程仍不执行。⛔ 若重启后悄悄恢复执行,是严重缺陷,单独标注。」**
_status(enabled false, bound false), the boot-log line and the Setup switch all agree.Verdict: pass. Every clause was consulted with evidence. No defect was found.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commentedon Oct 3, 2026
A5 · pass — disabling a called subflow is refused, the refusal names the callers in the server's own words, and the UI shows that text unchanged · 2026-10-03T10:19Z
Executor for claim
5967871940, under ruling B (triage5967646603). Environment as in the A3 reply:maina3873542ca76c6921f40a04f55ea4738d1be5987;89cad75d5570;admin@objectos.aiand ordinary userauditor.demo@example.com.Items:
automation.packaged-flow-subflow-disable-refusal(rev 2), plus the refusal-rendering clause ofautomation.setup-packaged-automation-board.What was done, and what was seen (admin unless noted)
POST /api/v1/automation/showcase_notify_owner/toggle {"enabled": false}→409 DELETE_RESTRICTED, verbatim:showcase_one_task_signoff→409 DELETE_RESTRICTED, naming'showcase_release_signoff'. That caller reaches it through a map node.showcase_closure_signoff→409 DELETE_RESTRICTED, naming'showcase_project_closure'.sys_metadata_activationholds no row for any of the three refused flows.donefired the caller: runrun_7fb372b8…, whose stepcall_notify(typesubflow) succeeded. The child ran too (run_141ec9ad…, completed).showcase_task_done_notify_owner→ 200. Retrying the child → 200, and its row readsactive false. On 2026-09-29 this retry answered 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); it now lands.active true.409.role="alert"paragraph whose text is byte-identical toerror.messagein step 1 (the string comparison is true).manage_metadatacapability."Clause by clause, against 「选一个被其他包内流程当作子流程调用的流程,尝试停用 → 被拒绝**,报错信息点名列出调用方流程(服务端原文,界面不加工)。」**
Verdict: pass. One arm was not driven: "names every caller when there are several". Each stock child has exactly one caller, so that arm rests on the engine unit pin, as the checklist item records.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commentedon Oct 3, 2026
B1 · fail — HTTP half: pass. Studio half: fails on 「克隆体…可在 Studio 里编辑」 because the clone reaches no Studio surface (#21332, still open). The original stays locked in both halves · 2026-10-03T10:19Z
Executor for claim
5967871940, under ruling B (triage5967646603). Environment as in the A3 reply:maina3873542ca76c6921f40a04f55ea4738d1be5987;.objectui-sha89cad75d5570, which contains objectui#11124's fix9fd6c2c6f8(196 commits ahead of it, 0 behind);admin@objectos.aiand ordinary userauditor.demo@example.com.Items:
automation.packaged-flow-clone-contract(rev 2);automation.setup-packaged-automation-board;studio-authoring.packaged-automation-studio-lock.HTTP half (admin,
POST /api/v1/automation/showcase_urgent_task_alert/clone){}→400 VALIDATION_FAILED, with detailsname/required: "A clone requires a new machine name —nameis mandatory (ADR-0126 §7.1). The clone is a sibling flow, not a revision of the one it was copied from."{"label":"x"}→ the same 400.{"name":"x"}→ 400 "A clone requires a new display name —labelis mandatory. …"{"name":"showcase_urgent_task_alert","label":"Same"}→409 RESOURCE_CONFLICT"Flow 'showcase_urgent_task_alert' already exists — a clone must take a NEW machine name. … Retry with a machine name no flow uses (for example 'showcase_urgent_task_alert_copy')."Bad-Name!→ 400 "Invalid flow definition: name: Invalid string: must match pattern/^[a-z_][a-z0-9_]*$/".404 RESOURCE_NOT_FOUND.meta/flowcount stayed at 30.{"name":"qa_urgent_alert_clone","label":"QA urgent alert clone"}→ 200{flow, notice}.name,labelandstatus(active→draft) differ._provenanceand_packageIdare dropped, none of the 9 protection keys is present, and no ancestry-shaped key appears in the response or the stored definition.draft, which is a lifecycle label and NOT an off-switch — a cloned record-change or schedule flow is bound to its trigger and will run alongside the flow it was copied from."showcase_task_done_notify_ownerkeepscall_notify.config.flowName = showcase_notify_owner; the reference is not re-pointed.PUT /api/v1/automation/qa_urgent_alert_clonewith the label "QA urgent alert clone (edited)" → 200, and the re-read shows the edit. After a cold restart on the same database the clone is still served with the edited label, and it dispatched on a new urgent create.manage_metadata, so these refusals come from the lock, not the capability tier:PUT /api/v1/automation/showcase_urgent_task_alert→403 NOT_OVERRIDABLE"Metadata item 'flow/showcase_urgent_task_alert' is provided by a code package, and its packaged base is locked against in-place edits. Clone it under a new name to customize it (…), or switch it off (…). See docs/adr/0126-packaged-metadata-customization-model.md."PUT /api/v1/meta/flow/showcase_urgent_task_alert→403 NOT_OVERRIDABLE, with the same sentence.?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. …"DELETE /api/v1/automation/showcase_urgent_task_alert→403 NOT_OVERRIDABLE"… locked against removal. …"403 PERMISSION_DENIED"Authoring automation flows requires themanage_metadatacapability." Nothing was created.Studio half (admin, browser)
Bad-Name!round-trips the server's 400, and the dialog alert shows its message.qa_ui_cloneproduced a status box readingCreated flow "qa_ui_clone"., followed by the server notice verbatim./_console/studioshows "MY PACKAGES (WRITABLE): No writable packages yet". Its only entry is "ObjectStack Showcase · Read-only"./_console/studio/com.example.showcase/automationslists exactly the 30 packaged flows.qa_urgent_alert_clone,qa_ui_cloneandqa_task_done_cloneare not among them.…/automations?surface=flow%3Aqa_urgent_alert_cloneredirects to?surface=flow%3Ashowcase_task_completed; so does the one forqa_ui_clone.GET /api/v1/meta/flowreturns 32 flows, including both clones made over HTTP.GET /api/v1/meta/flow?package=com.example.showcasereturns 30: the clones are stored with no package, and the Studio rail asks for one package's flows./_console/apps/setup/metadata/flow/qa_urgent_alert_clone). It is a designer with editable name and label fields, but it is not Studio. That admin's own Flow list, which scopes itself to?package=com.example.showcase, does not list the clone. Nothing was saved there.GET /api/v1/meta/flow/showcase_urgent_task_alertis unchanged.Clause by clause, against 「对包内流程点克隆 → 必须输入新机器名才能完成;克隆体出现且可在 Studio 里编辑;原件仍锁定不可改。」
repo:objectui), so no new card is needed.loadPackageSurfaces(client, 'flow', packageId).Derived verdict: fail. The HTTP half alone passes. The Studio half has one failed clause and one passed clause. Re-run this row once #21332 lands.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commentedon Oct 3, 2026
B2 · pass — with clones present, no screen shows lineage, a diff, or a 「已定制」 badge · 2026-10-03T10:20Z
Executor for claim
5967871940, under ruling B (triage5967646603). Environment as in the A3 reply:maina3873542ca76c6921f40a04f55ea4738d1be5987;89cad75d5570;admin@objectos.aiand ordinary userauditor.demo@example.com.Items: the no-lineage clause of
automation.setup-packaged-automation-board, and the no-ancestry clause ofautomation.packaged-flow-clone-contract.What was done. Three clones existed:
qa_urgent_alert_cloneandqa_task_done_clone(made over HTTP) andqa_ui_clone(made in the Setup dialog). I checked every UI screen that shows the original or a clone. On each, a settled screenshot came first. Then I swept the page text and badges for: customized, cloned from, clone of, copied from, based on, lineage, ancestor, origin, diff, drift, derived from, upstream, out of date, modified.What was seen
sys_metadata_activationholds no row for any clone.Clause by clause, against 「界面任何地方都不显示克隆体与原件的来源关系、差异对比、「已定制」角标——这是设计如此,不是缺陷。」
Verdict: pass. One limit: Studio cannot show a clone at all today (see the B1 reply, #21332), so no Studio screen of a clone could be checked. When #21332 adds such a screen, it needs this same check.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commentedon Oct 3, 2026
E1 · pass — both pages describe Marketplace apps in the three tiers, and neither still makes an unconditional "customize in Studio" promise · 2026-10-03T10:20Z
Executor for claim
5967871940, under ruling B (triage5967646603).maina3873542ca76c6921f40a04f55ea4738d1be5987.origin/mainhas since moved to6dd99b82c3. Both files are byte-identical between the two (git diffis empty).e3430a2cb5(docs: promise per-regime customization truth on integrations + build-without-code (ADR-0126 §8.4) #12368, the per-tier rewrite) and5983d11230(the page-title rule).What was seen
content/docs/capabilities/integrations.mdx, the "Marketplace templates" bullet, verbatim:content/docs/build-without-code.mdx, the Setup section, verbatim:customize in studio,ready to customizeandthen customizeover hand-writtencontent/docsfinds nothing. The old wording, "then customize in Studio" and "ready to customize in Studio", is gone.Clause by clause, against 「文档页
capabilities/integrations与build-without-code中关于 Marketplace 应用的表述已按三分层说话(展示类直接改/行为类停用+克隆/结构类扩展字段),不再有无条件的「装完可在 Studio 定制」。」Verdict: pass, on the row as written.
A caveat for the maintainer's spot-check:
build-without-codealso says the Studio Automation pillar shows "All flows in one list".Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commentedon Oct 3, 2026
Seat summary · ruling B run · 2026-10-03T10:24Z
Seat
domain:services#2(seat post #21118) · sessionsession_01DiCSbmJrkzNhuEAier4VoJ. This closes the round of claim5967871940. The run was onmaina3873542ca, the showcase, with the console built at pin89cad75d5570. There is one evidence reply per row.5968196694DELETE_RESTRICTEDnames the caller, and the UI shows the server text byte-identical59682012065968206129repo:objectui)59682114635968216948Still open on this card:
group/isolatedposture). Under the ruling, it is either checked or carded before this card closes.Findings from the run:
automation.packaged-flow-clone-contract's scope note saysPUT /automation/:namehas no lock check; it measured 403NOT_OVERRIDABLE.automation.packaged-flow-disable-durabledoes not say the boot-log line needs--log-level info.pnpm objectui:buildfails in this container image on an engine check (jsdom wants Node^22.22.2; the image has22.22.0). The run worked around it with an install-only engine relaxation; all build canaries passed. Not a repository defect.The card stays
pm:dispatchedon this seat until B1 and D2 resolve, so nothing is half-done. The next round re-runs B1's Studio half once #21332 lands.Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commentedon Oct 3, 2026
State:
pm:dispatched→pm:blocked· seatdomain:services#2(#21118) · sessionsession_01DiCSbmJrkzNhuEAier4VoJ· 2026-10-03T10:26Z. This corrects my summary5968254808, which said the card would staypm:dispatched.No run is in flight. What is left waits on upstream work:
group/isolatedposture fixture.The assignee stays
os-bill: this seat re-runs B1 when #21332 closes, and the unlock scan returns the card at that point.Blocked-by: #21332
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commentedon Oct 3, 2026
Triage: blocker re-derived. B1's Studio half is now carried by objectstack-ai/objectui#11553, and #21332 is closed as moved
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-03T14:04Z. ⛔ Not a claim, ⛔ not a dispatch.Blocked-by: objectstack-ai/objectui#11553
duplicatein this round, and its ruling and measurements now live on Studio: a cloned packaged flow reaches no Studio surface — Studio is routed per package, and the clone is correctly package-less (moved from objectstack-ai/objectstack#21332) objectui#11553 (bug· p2 ·domain:ui·area:studio·pm:queue)..objectui-sha. So this card unlocks when the pin carries objectui#11553's landing, not at the objectui merge.pm:blocked. ⛔ Triage does not touch the assignee.objectstack-fleet commentedon Oct 3, 2026
Triage: blocker re-derived to the pin bump #21616. objectui#11553 has landed, but the pin does not carry it yet
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-03T17:55Z. ⛔ Not a claim, ⛔ not a dispatch.Blocked-by: #21616
completedthrough PR objectui#11565 (f624f278).5969915716): B1 re-runs on a console built at.objectui-sha. The pin is still89cad75d5570, andcompare f624f278...89cad75d5570answersbehind_by: 63.columnshold wait on it #21616 (tooling· p2 ·domain:devx·pm:queue).pm:blocked. ⛔ Triage does not touch the assignee.Generated by Claude Code
objectstack-fleet commentedon Oct 3, 2026
Triage: unlocked on the install face. The pin now carries objectui#11553's landing, so
pm:blocked→pm:queuefor B1's re-runTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-03T23:57Z. ⛔ Not a claim, ⛔ not a dispatch.columnshold wait on it #21616 closed..objectui-shaonmainisab1879721595, set by1cbe165bfc("carries objectui f624f278 …").compare f624f278...ab18797215answersahead,behind_by: 0. So a console built at the pin serves package-less flows in Studio (objectui#11553).5968266945planned.group/isolatedfixture, and F1 to F3 still wait on the maintainer's spot-check. If B1 passes and D2 is the only item left, the seat re-blocks it on its fixture with a named carrier.Generated by Claude Code
objectstack-fleet commentedon Oct 4, 2026
Claim: PM loop round 4 · 2026-10-04T00:39Z
Session:
session_01DiCSbmJrkzNhuEAier4VoJAccount:
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:servicesSeat:
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: triage
5974787059. #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 in
5968206129and is not re-run unless the Studio half shows it moved.mainwith the console built fresh at the pinab1879721595.Serial constraints cleared: the run boots
mainand 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
objectstack-fleet commentedon Oct 4, 2026
B1 · 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;automation.setup-packaged-automation-board(rev 2);studio-authoring.packaged-automation-studio-lock(rev 1).Environment
main759dbe9ed36d996581bd72cc0e4c51e077c72c32: a clean detached worktree with a fullpnpm build(72/72 tasks).pnpm objectui:buildfrom the pin.objectui-shaab187972159583b595facdcae3c73b50f6f312e9.pnpm check:console-shais green. The bundle, client-injection, single-zod and spec-injection canaries all passed.f624f278is an ancestor of the pin:git merge-base --is-ancestorholds locally, andcompareanswersahead_by: 2, behind_by: 0.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.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.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
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).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.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.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)"./_console/studio/~org/automations. Its rail lists exactly one flow, "QA B1 Studio clone", which opens with the chipflow · qa_b1_studio_clone./_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.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.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":[]}.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.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.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./_console/studio/com.example.showcase/automations?surface=flow:showcase_urgent_task_alerttwice, before and after the restart, each time in a fresh context./_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."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 …";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 里编辑;原件仍锁定不可改。」
5968206129.sys_metadataall hold it, package-less as ADR-0126 §7.1 intends. It survives a cold restart and dispatches.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.Seen on the way. None of these contradicts B1's 「预期」. All three go to the seat, which decides on filing.
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.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 commentedon Oct 4, 2026
Claim 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:03Z5975204933on 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.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.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.Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commentedon Oct 4, 2026
F1–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: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.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 commentedon Oct 4, 2026
D2 · 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.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.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.@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.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…".GET /api/v1/auth/get-session. ItsisPlatformAdminis the posture rung (PLATFORM_ADMIN) the §5 gate reads.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.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.orgowner2.d2@example.com: owner of a second organization, "Tenant South D2" (l96bdHGx…). Same standing as North before the grant.What was done, and what was seen
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./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".GET /api/v1/automation/_status→ the flowenabled: true, bound: true.GET /api/v1/data/sys_metadata_activation→ no rows.manage_metadata, both doors,{"enabled": false}then{"enabled": true}→ all four403 PERMISSION_DENIED, verbatim:manage_metadatacapability."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."_statusstill readsenabled: true.200 {"name":"showcase_urgent_task_alert","enabled":false}and200 {"name":"showcase_mark_done","objectName":"showcase_task","enabled":false}._statusreadsenabled: false, bound: false.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.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.403 PERMISSION_DENIED, with the same tier sentences. Both rows keep their ids,active: falseandupdated_at(…T01:16:28.855Zand…T01:16:28.924Z).qa_d2_metadata_authorwithsystem_permissions ["manage_metadata"]and granted it to North inside North's organization.memberthrough North's own invitation and granted the set from there.me/permissionsnow listsqa_d2_metadata_author, with system permissionsmanage_metadata, manage_org_users, setup.access, setup.write.get-sessionstill readsisPlatformAdmin: false, positionsorg_owner, everyone.manage_metadata, both doors, both directions, with the switches off → all four403 PERMISSION_DENIED, now from the §5 operator gate, verbatim:{"enabled":"yes"}gets the same 403, not a 400.updated_atare unchanged, and_statusstill readsenabled: false.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)._statusreadsenabled: true, bound: true.active: true, withupdated_atmoved. They were updated, not recreated.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.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.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.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.activeandupdated_atheld, and_statusheld, after every attempt.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.
@objectstack/organizations. This run declared it worktree-locally. A repeatable walled showcase boot needs a carrier for that declaration.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 commentedon Oct 4, 2026
F1 · 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.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.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.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: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.
GET /api/v1/data/sys_metadata_activation→ 404 "Object 'sys_metadata_activation' is not registered": the ledger did not exist yet.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.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.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.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.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: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;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 已有功能)行为与升级前一致——权限集不走新的中央开关表,这是裁定过的设计。」
NOT_OVERRIDABLE, same sentence, definition untouched, baseline vsmain. The same-name-insert envelope above is the one refinement.activecolumn, enforced live, assignments kept, on both sides of the upgrade, and an operator's pre-upgrade choice survives it.admin_scopenot copied, org-owned, editable, no lineage, on both sides.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.
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.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 commentedon Oct 4, 2026
F2 · 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).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.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.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)./_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.disabled.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./_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:&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 (…)".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 中仍不可原地编辑。」
/meta/action403NOT_OVERRIDABLE/ITEM_LOCKED, and/meta/objectdraft 403ITEM_LOCKED. The definition is byte-identical afterwards.Verdict: pass.
Seen on the way, for the seat. Neither changes the verdict.
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.Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
objectstack-fleet commentedon Oct 4, 2026
F3 · 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
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.maineea82af67779f504bd5d53fccda3151066cdeaf6, full build 72/72, console built fresh at the pin.objectui-shaab187972159583b595facdcae3c73b50f6f312e9(check:console-shagreen).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.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):
GET /api/v1/data/sys_metadata_activation→ 404 "Object 'sys_metadata_activation' is not registered".GET /api/v1/automation/_status→ 29 flows, allenabled: true.showcase_urgent_task_alert, giving runrun_81d1ca70…, completed.sys_automation_runthen held 14 rows, including the seed's paused approval runs and connector runs.showcase_contributorthrough asys_user_permission_setrow. Its contributor-only verb (PATCH /api/v1/data/showcase_project/:id) → 200.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.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 fresh
mainboot. No line asked for a migration before the server would serve. The lines that mention one:[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;[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.GET /api/v1/data/sys_metadata_activation→ 200, 0 rows. The boot created the table itself._statuslists all 30 packaged flows withenabled: true.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.run_81d1ca70…is still listed, completed. All 14 baselinesys_automation_runrows are present (diff by id: 0 missing);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;mainboot's seed re-created one under that name. All five baseline project ids are still there.showcase_urgent_task_alertrun,run_11e755f5…, completed.Against 「在一个升级前已有数据的环境升级到最新 main → 无需任何迁移动作,所有包内工件默认启用,已有功能无异常。」
[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.activecolumn (card boundary 4): all read active exceptshowcase_auditor, which the operator switched off before the upgrade, and that choice is kept.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):
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 ….managed_by admin), Setup lists it in the Inactive tab, and it stays editable.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.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
objectstack-fleet commentedon Oct 4, 2026
Acceptance 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.
5968196694,5968201206,5968211463,59682169485968206129; Studio half5975204933, re-run on a console built at the pinab187972155975356896: a liveisolatedboot, where only the platform admin flips the packaged flow and action switches5975590364: packaged permission sets lock, disable and clone exactly as before, and the central switch table is never touched5975593929: packaged action and flow definitions are read-only in Studio, and forged writes get 4035975597571: 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:
organization_id;permission_set_declaration_unownedboot warning for cloned sets;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);The maintainer can reopen this card if a spot-check disagrees.
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
chore(objectui): bump the console pin to ab1879721595 (carries object…