Skip to content

QA run · card:12438 (15/15) · 6bff748b · 2026-09-29 · 6 PASS / 1 PARTIAL / 8 FAIL / 0 BLOCKED / 0 NOT-RUN #20674

Closed

Description

@objectstack-fleet

Checklist run for the manual acceptance card #12438 (Epic #12150, ADR-0126 packaged-metadata customization), executed per docs/qa/platform-checklist/RUNNER.md by the domain:spec#5 seat's dispatched runner (seat post #19357; claim on #12438 comment 5893356874). Text-only record; no artifact of this run lands in the tree.

Environment fingerprint

key value
framework 6bff748bbd484f00906c8d9a58f5a3621a20c2e6 (origin/main fetched at run start, 2026-09-29T15:3xZ), detached worktree, pnpm install --frozen-lockfile + pnpm build (72/72 tasks green)
objectui pin dd3f7e1be3561d63267d7162f3fc0ac52e72834d (.objectui-sha); vendored console built fresh at the pin (scripts/build-console.sh, stamp matches pin) from a private shallow clone, not a shared checkout
boot OS_PORT=38517 objectstack dev --ui --seed-admin -p 38517 -d file:(scratch)/main.db (showcase), stock composition, boot log Tenancy: single
boots boot1 15:41:48Z (no console yet) → cold stop, direct-sqlite probes → boot2 15:49:39Z (same DB file, console mounted) → cold stop → boot3 15:51:36Z (same DB file, OS_LOG_LEVEL=info to capture the info-level ledger lines). Run ended ~16:19Z
personas seeded admin admin@objectos.ai (server-side: isPlatformAdmin: true); stock demo member Mei Phone phone.demo@example.com (org_member, everyone); two admin-created members (self-registration is closed on this environment, SELF_REGISTRATION_CLOSED, invite-only posture): member A (granted a scratch set with system_permissions: ['manage_metadata'], positions org_member, everyone, no platform_admin), member M (granted showcase_contributor, later a scratch set with only setup.access). Every persona's identity was read back via GET /api/v1/auth/get-session before its cells were scored
browser Playwright core 1.62.1 driving /opt/pw-browsers/chromium, headless, one fresh browser context per persona, form sign-in (token + cookie)
execution sequential, one runner; Tier 1 pins batched first

Scope

Selector: every checklist item whose definition cites #12438 or ADR-0126 — resolved one id at a time with node scripts/checklist-select.mjs ID --json --include-blocked. The runner's own enumeration over docs/qa/platform-checklist/areas/*.json found exactly the same 15 items (no 16th). None is planned; none resolved as blocked.

item priority surface revision
platform-core.activation-ledger-registration-home P1 api 2
platform-core.activation-ledger-row-contract P2 api 2
platform-core.packaged-object-extend-only P2 api 1
api-backend.packaged-action-disabled-dispatch P0 api 2
api-backend.action-activation-door-contract P2 api 2
access-security.activation-write-operator-gate P1 api 2
access-security.packaged-flow-write-door-parity P1 api 1
access-security.packaged-permission-set-lifecycle P2 mixed 1
automation.flow-toggle-kill-switch P2 api 2
automation.packaged-flow-disable-durable P0 api 2
automation.packaged-flow-subflow-disable-refusal P1 api 1
automation.packaged-flow-clone-contract P1 api 1
automation.setup-packaged-automation-board P1 browser 2
studio-authoring.packaged-automation-studio-lock P1 browser 1
studio-authoring.packaged-display-class-direct-edit P2 browser 1

Tier 1 — pinned automation (run first, cited as evidence)

  • packages/qa/dogfood: test/packaged-activation-ledger-reach.dogfood.test.ts + test/action-params-contract.dogfood.test.ts — 2 files, 16/16 passed. The first describe ("Packaged-action disable is unavailable without the automation service — sys_metadata_activation has only one registrant #12359 — actions and NO automation service") passed all five of its tests, including its anti-vacuity control "the automation service really is absent from this composition".
  • packages/runtime: action-activation-posture-gate.test.ts, automation-activation-posture-gate.test.ts, action-activation-dispatch.test.ts, automation-flow-clone.test.ts — 4 files, 75/75.
  • packages/services/service-automation: activation-ledger-registration.test.ts, flow-activation-ledger.test.ts — 2 files, 31/31.
  • packages/objectql: action-activation.test.ts — 18/18.
  • packages/platform-objects: plugin.test.ts, apps/setup-packaged-automation-nav.test.ts — 26/26.
  • packages/plugins/plugin-security: packaged-permission-set-lock.test.ts, packaged-permission-set-restore-leg.test.ts — 22/22.

⚠️ automation-flow-clone.test.ts is green while the live clone door answers 404 (see F-1): it calls the domain handler directly, so it cannot see the missing HTTP mount — the dispatcher-vs-hono-route trap exactly.

Derived item verdicts

item verdict failing / blocked clauses
platform-core.activation-ledger-registration-home pass —
platform-core.activation-ledger-row-contract pass —
platform-core.packaged-object-extend-only pass —
api-backend.packaged-action-disabled-dispatch pass —
api-backend.action-activation-door-contract pass —
automation.flow-toggle-kill-switch pass —
access-security.activation-write-operator-gate partial c5 blocked(fixture)
automation.packaged-flow-disable-durable fail c3 (F-2)
automation.packaged-flow-subflow-disable-refusal fail c5 (F-3)
automation.packaged-flow-clone-contract fail c1, c4 (F-1); c2, c3, c5, c6, c7 blocked(product-bug F-1)
automation.setup-packaged-automation-board fail c7 (F-1); c5 blocked(product-bug F-1)
access-security.packaged-flow-write-door-parity fail c2, c3 (F-4, detail withheld)
studio-authoring.packaged-automation-studio-lock fail c3, c4 (F-5)
studio-authoring.packaged-display-class-direct-edit fail c3 (F-6)
access-security.packaged-permission-set-lifecycle fail c6 (F-7)

Per-clause verdicts

Clause numbers are 1-based positions in each item's acceptance array. Oracle evidence is text (server truth first; a screenshot is described in one line, never attached).

platform-core.activation-ledger-registration-home (rev 2) — pass

c verdict evidence
1 pass test: dogfood first describe 5/5 green incl. the anti-vacuity control; the flip that answered 503 now succeeds, one row, no tenant column, dispatch refuses, re-enable updates
2 pass api (boot1): POST /api/v1/actions/_activation/showcase_task/showcase_mark_done {"enabled":false} → 200 and POST /api/v1/automation/showcase_task_completed/toggle {"enabled":false} → 200; GET /api/v1/data/sys_metadata_activation lists a flow row (showcase_task_completed) and an action row (showcase_mark_done) side by side; flow-name list and action-name list never cross; re-enable flipped the same ids to active; mirrored by the dogfood "both consumers share ONE table" test
3 pass test: dogfood "exactly ONE owner" green + activation-ledger-registration.test.ts + platform-objects/src/plugin.test.ts green; the stock boot composing PlatformObjectsPlugin and AutomationServicePlugin came up healthy
4 pass test: dogfood "the table keeps the datasource binding it had before the registrar moved" green; manifest triple pinned in plugin.test.ts (green)
5 pass build: sys_metadata_activation sits in the platform-objects array of packages/spec/src/system/constants/platform-object-names.ts and in no other array. Checklist-accuracy note: the array holds 46 names at this sha, not the 41 the item states

platform-core.activation-ledger-row-contract (rev 2) — pass

c verdict evidence
1 pass api: admin GET /api/v1/data/sys_metadata_activation → 200; POST / PATCH /:id / DELETE /:id on it → 405 OBJECT_API_METHOD_NOT_ALLOWED each naming the refused operation and allowed: [get, list, aggregate, search, export]; ledger diff before/after the three probes empty (5 rows = 5 rows)
2 pass log (server stopped, direct sqlite): index uniq_sys_metadata_activation_metadata_type_name unique over exactly [metadata_type, name]; a second row for ('flow','showcase_urgent_task_alert') → SQLITE_CONSTRAINT_UNIQUE
3 pass log: PRAGMA table_info = id, created_at, updated_at, created_by, updated_by, metadata_type, name, package_id, active, __search — nothing tenant-shaped; an INSERT naming organization_id → table sys_metadata_activation has no column named organization_id; the control INSERT without it landed (1 change) and was deleted; rows byte-identical before/after
4 pass api: after re-enabling both switches the same ids (SSDabib1hvfNmGtF, fHgY_UDrXaUFRjSj) read active true, row count unchanged
5 pass api: distinct metadata_type over the full table after every shipped switch (flow toggles, action flips, the whole permission-set lifecycle) = {action, flow}; writer grep finds exactly two literals (flow-activation-store.ts, action-activation.ts)
6 pass api: ledger read before the cold stop vs after boot2 identical (9 rows = 9 rows, byte-equal); flows without a row (e.g. showcase_task_completed before its first toggle) ran as active

platform-core.packaged-object-extend-only (rev 1) — pass

c verdict evidence
1 pass api (re-driven, metadata-registry-serving not in this run): merged GET /api/v1/meta/object/showcase_account carries loyalty_tier (select bronze/silver/gold/platinum), linkedin_url (url), csat_score (number 0-100); PATCH /api/v1/data/showcase_account/(id) {"loyalty_tier":"gold","csat_score":87} → 200, re-read returns both values
2 pass api: PUT /api/v1/meta/object/showcase_account (served body + one added field) → 403 NOT_OVERRIDABLE ("'object' is not allowOrgOverride…"); same PUT with ?package=com.example.showcase → 403 ITEM_LOCKED ("…that package is read-only…")
3 pass api: post-refusal read byte-identical to the before image; probe field absent; three extension fields present

api-backend.packaged-action-disabled-dispatch (rev 2) — pass

c verdict evidence
1 pass api: after disabling showcase_mark_done (row fHgY_UDrXaUFRjSj active false, key set without organization_id), POST /api/v1/actions/showcase_task/showcase_mark_done with a bogus and with a REAL recordId → 409 ACTION_DISABLED, message "Action 'showcase_mark_done' is disabled — it is switched off for this installation in the packaged-metadata activation ledger (sys_metadata_activation, ADR-0126 §8)…"; the real task's done/progress/updated_at unchanged
2 pass api: disabled showcase_action_param_gallery + params {"bogus_key":1} → 409 ACTION_DISABLED; identical request after re-enable → 400 "Invalid action params: Action param "p_text" is required; Unknown action param "bogus_key"…"
3 pass api: both disabled traces carried a nonexistent recordId and answered 409 (enabled-control caveat noted: the record load is best-effort, so no contrasting 404 is expected)
4 pass api: showcase_zoo_perm_missing enabled vs disabled → two 403 PERMISSION_DENIED bodies, byte-identical (cmp); plain member also 403 while disabled
5 pass api (live MCP, /api/v1/mcp, tools/call run_action): disabled showcase_portfolio_snapshot → tool error {"code":"ACTION_DISABLED", …, "status":409} with the same producer sentence; re-enabled → runs (ok: true)
6 pass api: on boot2 and again on boot3 the invocation → 409; boot3 re-enable → invocation 200 {ok:true} and the task reads done: true, progress: 100; row fHgY_UDrXaUFRjSj still present, active true
7 pass api: disabled showcase_bulk_reassign (type flow) → 409 ACTION_DISABLED naming it; showcase_reassign_wizard runs 0 → 0; no flow-ledger row for the target

api-backend.action-activation-door-contract (rev 2) — pass

c verdict evidence
1 pass api: Mei Phone POST …/_activation/showcase_task/no_such_action {"enable":false} → 403 PERMISSION_DENIED "Enabling or disabling a packaged action requires the manage_metadata capability — switching a shipped artifact off is functionally equivalent to deleting it…" (no clone recommendation); anonymous twin → 401 UNAUTHENTICATED
2 pass api: 2-segment → 400 "Path must be /actions/_activation/:object/:action (use global for an object-less action)"; 4-segment → 404 ENDPOINT_NOT_FOUND, same shape as an unmounted control path
3 pass api: {"enable":false} → 400 VALIDATION_FAILED, details.fields[0] = {field:'enable', code:'unknown_field', message:'…did you mean \enabled`?'}; {"enabled":"false"}→ 400invalid_type; []→ 400invalid_typeon(body)`; none answered 200
4 pass api: {} → 200 enabled: true
5 pass api: undeclared → 404 RESOURCE_NOT_FOUND "…has no declaration — there is nothing to switch off…"; no refused probe wrote a row. Coverage note: the "metadata plane cannot answer → 503" sub-state has no stock trigger and no dedicated pin (the pinned 503 is the store's)
6 pass api: flip off → 200 {name, objectName, enabled:false}; one row, key set without organization_id; flip on → same id, active true; 4 rows total throughout
7 pass test: action-activation-posture-gate.test.ts "REFUSES an ambiguous name instead of switching off artifacts the caller did not name" green (no stock colliding fixture)
8 pass test: same file, "says so plainly when the engine has no activation ledger at all" (501) and "reports a write that could not be made durable, instead of a 200" (503) green

automation.flow-toggle-kill-switch (rev 2) — pass

c verdict evidence
1 pass api: urgent create → exactly one run whose trigger recordId is that task
2 pass api: after {enabled:false} an urgent create left the runs list at 1 → 1
3 pass api: /_status enabled/bound true/true → false/false → true/true
4 pass api: after {enabled:true} the next urgent create added exactly one run; unknown flow toggle → 404 RESOURCE_NOT_FOUND

access-security.activation-write-operator-gate (rev 2) — partial

c verdict evidence
1 pass api: member A (before any grant) flow toggle → 403 PERMISSION_DENIED "Enabling or disabling an automation flow requires the manage_metadata capability." — tier gate, no posture sentence (boot log Tenancy: single)
2 pass api: member A action flip → 403 with the shared activation-gate sentence ("…a packaged action…functionally equivalent to deleting it…")
3 pass api: invalid bodies ({"enabled":"false"} flow, {"enable":false} action) as member A → 403, never 400; ledger unchanged (same ids and updated_at), no action row created
4 pass api: member A with the scratch manage_metadata set, positions proven [org_member, everyone], no platform_admin → both doors 200; rows flow/action active false with no organization_id key; re-enabled
5 blocked(fixture) no group/isolated posture boot on stock (enterprise organizations runtime). Standing oracle green: both posture-gate unit files, incl. the group/isolated matrices
6 pass test: "ALLOWS engine self-invocation" arms green; live corroboration: boot3 info log "[Automation] Activation ledger: 1 packaged flow(s) are switched off…" hydrated with the gate armed, no error

Side note (posture, not a verdict): a member's GET /api/v1/data/sys_metadata_activation answers 403 PERMISSION_DENIED. Several items describe the ledger reads as open; they are open to the admin only on this build.

automation.packaged-flow-disable-durable (rev 2) — fail

c verdict evidence
1 pass api: after {enabled:false} row hqbO_6tCDIJLBKq3 flow / showcase_urgent_task_alert / com.example.showcase / active false; key set active, created_at, created_by, id, metadata_type, name, package_id, updated_at, updated_by
2 pass api: POST /api/v1/automation/showcase_urgent_task_alert/trigger → 409 FLOW_DISABLED "…switched off for this installation in the packaged-metadata activation ledger (sys_metadata_activation, ADR-0126 §7.2). Re-enable the packaged flow to arm it again, or run a clone of it under a new name."
3 fail api: after cold restart (boot2) /_status → enabled:false, bound:true; still bound:true after a settle window; reproduced on boot3, whose info log first says "Activation ledger: 1 packaged flow(s) are switched off … and were left unbound — 'showcase_urgent_task_alert'" and then "Trigger registered: record_change", after which /_status again reads bound:true. See F-2
4 pass api: post-restart urgent create on boot2 and on boot3 → runs 3 → 3 (no execution)
5 pass api: boot3 re-enable → the same row id hqbO_6tCDIJLBKq3 active true; next urgent create → runs 3 → 4, trigger recordId = that task

automation.packaged-flow-subflow-disable-refusal (rev 1) — fail

c verdict evidence
1 pass api: showcase_notify_owner disable → 409 DELETE_RESTRICTED "Flow 'showcase_notify_owner' cannot be disabled while 1 packaged flow still calls it as a subflow: 'showcase_task_done_notify_owner'. … Disable the calling flow first, or leave this one armed."
2 pass api: showcase_one_task_signoff disable → 409 DELETE_RESTRICTED naming showcase_release_signoff (map caller)
3 pass api: no ledger row for either refused name; driving the caller (task → done) produced a completed run whose call_notify subflow step is success
4 pass api: with the caller re-armed, enabling showcase_notify_owner → 200
5 fail api: caller showcase_task_done_notify_owner disabled (200; row active false; /_status enabled false), then the child's disable retried twice → 409 DELETE_RESTRICTED naming the already-disabled caller. Reproduced on the map pair on boot3. See F-3

automation.packaged-flow-clone-contract (rev 1) — fail

c verdict evidence
1 fail api: POST /api/v1/automation/showcase_urgent_task_alert/clone with {}, {"name":"x"}, {"label":"x"} → 404 ENDPOINT_NOT_FOUND "Not found" each (expected 400 with per-field required). See F-1
2 blocked(product-bug F-1) no clone can be created over HTTP
3 blocked(product-bug F-1) same
4 fail api: same-name → 404 ENDPOINT_NOT_FOUND (expected 409 RESOURCE_CONFLICT); Bad-Name! → 404 (expected 400); unknown source → 404 ENDPOINT_NOT_FOUND (expected 404 RESOURCE_NOT_FOUND from the door)
5 blocked(product-bug F-1) same
6 blocked(product-bug F-1) same
7 blocked(product-bug F-1) the honest clause (restart survival + Studio reachability, FOLLOW-UPS §8a D18) could not be staged; D18 remains unverified

automation.setup-packaged-automation-board (rev 2) — fail

c verdict evidence
1 pass network: GET /api/v1/meta/app?id=setup carries nav_packaged_automation (type component, componentRef automation:packaged, label "Packaged Automation", group Apps beside Packages, requiresService and requiredPermissions both absent); the sidebar link click landed on /_console/apps/com.objectstack.setup/component/automation/packaged (screenshot: the rendered page)
2 pass dom (after screenshot): 30 flow rows = exactly the 30 packaged items of GET /api/v1/meta/flow (every one _packageId com.example.showcase, _provenance package), all On; a tenant flow registered via POST /api/v1/automation (engine held it) was absent from the table; actions section rendered (144 rows)
3 pass api: switch click → POST …/showcase_urgent_task_alert/toggle 200; after a full reload the switch reads Off and row hqbO_6tCDIJLBKq3 active false; flipped back
4 pass dom: every flow row has a Clone button; the actions section contains no button at all (the only "clone" text in it is a platform action's label)
5 blocked(product-bug F-1) the step needs a clone to exist; the pre-clone sweep of the page found nothing lineage-shaped
6 pass dom: admin's showcase_notify_owner switch → 409; row alert text equals the server sentence naming showcase_task_done_notify_owner, switch stays On. Member M (scratch set with only setup.access, no manage_metadata, own browser context) → 403; row alert equals "Enabling or disabling an automation flow requires the manage_metadata capability."
7 fail dom: submit disabled with name or label empty (holds); Bad-Name! + label → the dialog alert reads "Not found" (server answered 404, not the 400); a legal name + label → "Not found" again and no post-clone notice. See F-1
8 pass (recorded) posture: Mei Phone (no setup.access) cannot open Setup at all — "This app can't be opened. The server did not return this app for your account."; member M reaches the page, which requests sys_metadata_activation?top=1000 → 403 and shows a pre-click alert "activation ledger HTTP 403", while _status, meta/flow, meta/action → 200 and all switches render live

access-security.packaged-flow-write-door-parity (rev 1) — fail

c verdict evidence
1 pass api: the /meta door refuses the packaged flow (403 NOT_OVERRIDABLE without ?package=, 403 ITEM_LOCKED with it); /meta read unchanged
2 fail detail withheld pending maintainer (RUNNER rule 2 carve-out). Reproduced twice
3 fail detail withheld pending maintainer (RUNNER rule 2 carve-out). Reproduced twice
4 pass api: the probes left no residue — the live definition is byte-identical to the pre-probe capture and an urgent create produced a run

studio-authoring.packaged-automation-studio-lock (rev 1) — fail

c verdict evidence
1 pass network: GET /api/v1/packages → com.example.showcase writable:false; pillar shows the Read-only badge; no New button in the rail heading; zero flow writes across a node selection, an inspector edit and an 8 s wait past the autosave debounce
2 pass api: GET /api/v1/meta/flow/showcase_urgent_task_alert byte-identical before/after the whole probe; no draft (NO_DRAFT); the one flow write that left the browser was refused. Checklist-accuracy note: it answered 403 ITEM_LOCKED, not the 422 writable_package_required the clause names
3 fail network: the header "Enabled" switch is clickable (not disabled); the click sent PUT /api/v1/meta/flow/showcase_urgent_task_alert?mode=draft&package=com.example.showcase → 403 ITEM_LOCKED; the error banner rendered the server text; the switch and the canvas status then showed "Disabled" / "obsolete" until reload. See F-5
4 fail dom: the flow node inspector inputs are enabled (disabled:false, readOnly:false); a Label edit took on the canvas, nothing announced read-only, no write left the browser, and after reopening the pillar the edit was gone. See F-5

studio-authoring.packaged-display-class-direct-edit (rev 1) — fail

c verdict evidence
1 pass api: designer on showcase_task.default opened with a "writable" badge and no installed-package banner; label edit + removing column progress auto-saved as draft overlays and POST …/publish → 200; GET /api/v1/meta/view/showcase_task.default serves "All Tasks (QA-12438)" without progress; layered read: code.label "All Tasks", overlayScope org
2 pass screenshot: Mei Phone's task list (fresh context, same server process) shows the tab "All Tasks (QA-12438)" and no Progress column
3 fail api: system_overview designer opened editable; widget title edit → PUT …?mode=draft 200 and POST …/publish 200; layered read shows overlay and effective title "Total Users (QA-12438)"; but GET /api/v1/meta/dashboard/system_overview (item and list door, admin and member, cache-busting parameter, after a settle window) keeps serving "Total Users", and the rendered board shows "Total Users" on two fresh loads. See F-6
4 pass api: "Reset overlay" (native confirm) → DELETE /api/v1/meta/view/showcase_task.default and DELETE /api/v1/meta/dashboard/system_overview → 200 reset:true; both served bodies byte-identical to the pre-edit baselines; end-user list shows "All Tasks" with Progress again
5 pass dom (after screenshot): designer on the packaged flow shows "This flow is provided by an installed package, so it is read-only at runtime…", inputs read-only, no save/publish control

access-security.packaged-permission-set-lifecycle (rev 1) — fail

c verdict evidence
1 pass api: definition PATCH /api/v1/data/sys_permission_set/(showcase_contributor id) → 403 NOT_OVERRIDABLE "[Security] Permission set 'showcase_contributor' is declared by package 'com.example.showcase' and is locked… Clone it instead…"; definition facets and updated_at unchanged
2 pass api: PATCH {"active":false} → 200; member M's showcase_field_zoo create → 403 while inactive, 201 after {"active":true}; M's assignment row CaP0a-Z8DdLUQP4r present throughout
3 pass api: replaying the Clone action's payload (params list, bodyExtra {active:true}) → 201; description / object / field / system / row-level / tab permissions equal the base; admin_scope null; active; no lineage key; same name again → 409 UNIQUE_VIOLATION. Weak spot: the base's system and tab permissions are empty, so those two carry-overs prove little
4 pass api: the base-refused PATCH shape on the clone → 200 and persisted; member A lacked the verb (403), then held it through the clone (201)
5 pass api: ledger 10 rows before = 10 rows after the whole lifecycle, byte-identical; no permission row, no row naming the set or the clone
6 fail dom: Setup list rows offer Activate / Deactivate / Clone; the Clone dialog marks both name fields required ("… is required") and states "Delegated-admin scope is not copied…" (holds). But an in-place Edit → Update renders "You don't have permission to save this record." (banner + toast) while the server's 403 carries the clone-path sentence; reproduced twice. See F-7

Reproduction rules for every fail

F-1 — the flow clone door is not mounted on the HTTP server. Clauses: automation.packaged-flow-clone-contract c1, c4 (c2, c3, c5, c6, c7 blocked); automation.setup-packaged-automation-board c7 (c5 blocked).

  1. Boot showcase as above; sign in as the seeded admin.
  2. POST /api/v1/automation/showcase_urgent_task_alert/clone body {"name":"qa_urgent_alert_clone","label":"QA urgent alert clone"}.
  3. Expected: 200 with the cloned flow and the clone notice (or the door's own 400/404/409 arms). Actual: 404 {"success":false,"error":{"code":"ENDPOINT_NOT_FOUND","message":"Not found"}} for every body, method and caller (anonymous too); GET /api/v1/automation/qa_urgent_alert_clone → 404 RESOURCE_NOT_FOUND. Positive control on the same prefix: POST /api/v1/automation/showcase_notify_owner/toggle {"enabled":true} → 200.
  4. UI: Setup → Packaged Automation → Clone on any flow row → fill a legal name and label → Create clone → dialog alert "Not found"; network POST /api/v1/automation/(flow)/clone → 404.
    Mechanism (read, for the fix): the domain arm exists (packages/runtime/src/domains/automation.ts, POST /:name/clone), but registerAutomationRoutes in packages/runtime/src/dispatcher-plugin.ts mounts each /automation route explicitly and has no /:name/clone mount (none in its history), so Hono's not-found answers before the dispatcher runs. The route is also absent from packages/runtime/src/route-ledger.ts.

F-2 — after a restart, a ledger-disabled packaged flow's trigger is re-armed. Clause: automation.packaged-flow-disable-durable c3.

  1. Boot with a file DB; admin: POST /api/v1/automation/showcase_urgent_task_alert/toggle {"enabled":false} → 200; GET /api/v1/automation/_status → enabled:false, bound:false.
  2. Stop the server process entirely; boot again over the same DB file (OS_LOG_LEVEL=info to see the lines below).
  3. GET /api/v1/automation/_status. Expected: enabled:false, bound:false. Actual: enabled:false, bound:true (stable after waiting).
  4. Boot log order: "[Automation] Activation ledger: 1 packaged flow(s) are switched off for this installation and were left unbound — 'showcase_urgent_task_alert'." then "[Automation] Bound 30 flow(s) from the protocol at kernel:ready" then "Trigger registered: record_change".
  5. POST /api/v1/data/showcase_task with priority: 'urgent': no run is recorded (execution is still refused), but the server logs at ERROR "Trigger-fired run of flow 'showcase_urgent_task_alert' failed (trigger 'record_change') — … the terminal failure is recorded in the flow's run history …" with the FLOW_DISABLED sentence, and GET /api/v1/automation/showcase_urgent_task_alert/runs (also ?status=failed) shows no such run.
    Mechanism (read, for the fix): AutomationEngine.registerTrigger (packages/services/service-automation/src/engine.ts) calls activateFlowTrigger for every unbound flow of the new trigger type without consulting isFlowEnabled, and trigger plugins register at kernel:ready, after hydrateFlowActivations ran in start(). registerFlow does consult the ledger; this path does not.

F-3 — the subflow refusal's own remedy cannot be followed (FOLLOW-UPS §8a D19). Clause: automation.packaged-flow-subflow-disable-refusal c5.

  1. Admin: POST /api/v1/automation/showcase_notify_owner/toggle {"enabled":false} → 409 DELETE_RESTRICTED "… Disable the calling flow first, or leave this one armed."
  2. POST /api/v1/automation/showcase_task_done_notify_owner/toggle {"enabled":false} → 200 (ledger row active false; /_status enabled false).
  3. Retry step 1. Expected: 200 and a ledger row active false. Actual: 409 DELETE_RESTRICTED naming the now-disabled caller. Same on the map pair (showcase_release_signoff disabled, then showcase_one_task_signoff still refused).
    Mechanism: packagedSubflowCallers (engine.ts) scans registered flows with no activation check.

F-4 — write-door parity on a packaged flow. Clauses: access-security.packaged-flow-write-door-parity c2, c3. Detail withheld pending maintainer (RUNNER rule 2 carve-out); the reproduction stays with the runner session and was reported to the dispatching seat.

F-5 — Studio Automations pillar ignores the read-only package flag on two affordances (objectui). Clauses: studio-authoring.packaged-automation-studio-lock c3, c4.

  1. Admin → /_console/studio/com.example.showcase/automations?surface=flow%3Ashowcase_urgent_task_alert (the package reads Read-only; GET /api/v1/packages → writable false).
  2. Click the header "Enabled" switch. Expected: disabled or inert, no request. Actual: clickable; PUT /api/v1/meta/flow/showcase_urgent_task_alert?mode=draft&package=com.example.showcase → 403 ITEM_LOCKED; banner shows the server text; the switch and canvas status keep showing "Disabled" / "obsolete" until reload.
  3. Click the start node; in the inspector change Label. Expected: inputs disabled or an announced will-not-persist state. Actual: inputs editable, the edit shows on the canvas, no write leaves the browser, nothing is announced, and it is gone after reopening the pillar.
    Mechanism (pinned objectui dd3f7e1b, packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx): the pillar switch's disabled omits readOnly; the flow inspector is mounted with readOnly: false hardcoded. Residual of objectui#2259 (fixed for the Data pillar).

F-6 — the built-in system_overview dashboard: a published org overlay is never served. Clause: studio-authoring.packaged-display-class-direct-edit c3.

  1. Admin → /_console/apps/com.objectstack.setup/metadata/dashboard/system_overview; select the "Total Users" widget; change its title to "Total Users (QA-12438)"; Publish.
  2. Network: PUT /api/v1/meta/dashboard/system_overview?mode=draft → 200, POST /api/v1/meta/dashboard/system_overview/publish → 200 "Published draft".
  3. GET /api/v1/meta/dashboard/system_overview?layers=true → overlay and effective carry the new title, overlayScope org.
  4. GET /api/v1/meta/dashboard/system_overview and GET /api/v1/meta/dashboard → the first widget title is still "Total Users" (admin and member, with a cache-busting query parameter, after waiting); the Setup → System Overview board renders "Total Users".
  5. Control on the same boot: the same PUT + publish on the showcase-packaged showcase_ops_dashboard → the plain read serves the new title.
    Notes: the built-in's served _packageId is com.objectstack.plugin-auth, while the dashboard is declared in packages/platform-objects/src/apps/dashboards/system_overview.dashboard.ts. Not re-tested across a restart. Overlays were reset afterwards (DELETE → reset:true).

F-7 — the packaged permission-set lock refusal is replaced in the Setup form. Clause: access-security.packaged-permission-set-lifecycle c6.

  1. Admin → Setup → Permission Sets → row "Showcase Contributor" → menu → Edit; change Description; Update.
  2. Network: PATCH /api/v1/data/sys_permission_set/(id) → 403 NOT_OVERRIDABLE with the clone-path sentence.
  3. Rendered: banner and toast "You don't have permission to save this record." Expected by the clause: the server's clone-path sentence.
    Mechanism: objectui's form substitutes form.noPermissionToSave for permission errors unless the producer sets userMessage (ruled behaviour, objectstack#3821); the lock error sets none. Same class as 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 (closed not_planned; its triage names a dogfood report of the opaque message as the reopen trigger).

Fixture gaps and environment blocks

  • access-security.activation-write-operator-gate c5: no group/isolated posture boot on stock (enterprise organizations runtime) — blocked(fixture); card row D2.
  • Card row F3 (upgrade from a pre-upgrade dataset): no pre-upgrade dataset on a stock boot — blocked(fixture); not simulated.
  • The plain-member cells of the board needed a scratch set carrying only setup.access: a member without it cannot open the Setup app at all.
  • Self-registration is closed (invite-only), so fresh members were created through POST /api/v1/auth/admin/create-user and had to change their password before any gate would answer (PASSWORD_EXPIRED otherwise).

Checklist-accuracy notes (for the wave anchor, not extracted)

  • platform-core.activation-ledger-registration-home c5: the platform-objects array holds 46 names, not 41.
  • studio-authoring.packaged-automation-studio-lock c2 names 422 writable_package_required; the live refusal is 403 ITEM_LOCKED (the /meta protocol layer answers first).
  • automation.setup-packaged-automation-board c6/c8 assume a plain member reaches the page; the Setup app requires setup.access. The member-read posture of sys_metadata_activation (403 for non-admins) contradicts the "reads are open" wording in several items.
  • studio-authoring.packaged-display-class-direct-edit: system_overview is served under _packageId com.objectstack.plugin-auth, not platform-objects; views are single items (showcase_task.default), not a container.
  • platform-core.activation-ledger-row-contract step 7: the physical column list also carries __search.
  • Observation, not scored: the layered read of the packaged flow showcase_urgent_task_alert reports editable: true, deletable: true, lock: "none" while the /meta door refuses writes to it.

Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Close-out — extraction per RUNNER.md (extraction obligation) and the checklist-test skill §5. One line per extracted card; each card carries a QA-source: line pointing back here.

    Extracted: #20676 · automation.packaged-flow-clone-contract · c1, c4
    Extracted: #20676 · automation.setup-packaged-automation-board · c7
    Extracted: #20677 · automation.packaged-flow-disable-durable · c3
    Extracted: #20678 · automation.packaged-flow-subflow-disable-refusal · c5
    Extracted: #20679 · access-security.packaged-flow-write-door-parity · c2, c3
    Extracted: objectstack-ai/objectui#11124 · studio-authoring.packaged-automation-studio-lock · c3, c4
    Extracted: #20680 · studio-authoring.packaged-display-class-direct-edit · c3
    

    Not extracted, on purpose:


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    Close-out completed: closed completed

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-06T16:05Z. The maintainer approved clearing the open qa-run records' close-out debt: 「同意,动手」. This was audited row by row (read-only) against checklist-test §5 before this write.

    Extracted: #21794 · access-security.packaged-permission-set-lifecycle · c6
    Superseded-by: #21720 · access-security.activation-write-operator-gate


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions