From 281aa0e033074e686e99d21d388eeb1216a678a7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:08:48 +0000 Subject: [PATCH 01/14] =?UTF-8?q?docs(qa):=20re-point=20automation.setup-p?= =?UTF-8?q?ackaged-automation-board=20to=20the=20Studio=20door=20and=20Set?= =?UTF-8?q?up=20=E2=80=BA=20Packaged=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd --- .../platform-checklist/areas/automation.json | 101 ++++++++++-------- 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/docs/qa/platform-checklist/areas/automation.json b/docs/qa/platform-checklist/areas/automation.json index 0344791c0eb..5b831a1c04e 100644 --- a/docs/qa/platform-checklist/areas/automation.json +++ b/docs/qa/platform-checklist/areas/automation.json @@ -2243,10 +2243,10 @@ }, { "id": "automation.setup-packaged-automation-board", - "title": "The Setup packaged-automation page: reachable from Setup nav, lists packaged flows + packaged actions, toggle persists, clone on flow rows only, no lineage surface, server refusals verbatim in role=alert", + "title": "The packaged-automation doors after ADR-0146 D4/D11: Setup nav serves Packaged actions (the retired flows page's bookmark lands in Studio), Studio's Automations pillar lists the packaged flows, its ledger switch persists, New similar is offered on flows only, no lineage surface, server refusals verbatim in role=alert", "since": "v17", "status": "active", - "revision": 10, + "revision": 11, "priority": "P1", "surface": "browser", "personas": [ @@ -2256,81 +2256,84 @@ "fixtures": { "app": "showcase", "requires": [ - "a console bundle built from this checkout's ui/ tree with `pnpm console:build` (the page is ui/ code — a packages/console/dist older than the tree tests last week's page: RUNNER.md's stale-console-bundle trap)", - "the packaged showcase flows (com.example.showcase) and at least one packaged action for the actions section", - "the stock subflow pair showcase_task_done_notify_owner → showcase_notify_owner (drives the 409 refusal-rendering clause)" + "a console bundle built from this checkout's ui/ tree with `pnpm console:build` (both doors are ui/ code — a packages/console/dist older than the tree tests last week's pages: RUNNER.md's stale-console-bundle trap)", + "the packaged showcase flows (com.example.showcase, read-only in Studio) and at least one packaged action for the Packaged actions page", + "the stock subflow pair showcase_task_done_notify_owner → showcase_notify_owner (drives the 409 refusal-rendering clause)", + "the read-only base's customization pair com.example.showcase.custom, which New similar targets when no package is sent: the clone door ensures it through ADR-0146 D3's door (D4)" ], "knownGaps": [ - "REPO OWNERSHIP (ADR-0054): the PAGE — markup, testids, dialogs, refusal rendering — is objectui's (packages/app-shell/src/views/setup/PackagedAutomationPage.tsx + PackagedActionsSection.tsx, registered as ref 'automation:packaged' in services/builtinComponents.tsx); the Setup NAV METADATA that reaches it is objectstack's (packages/platform-objects/src/apps/setup-nav.contributions.ts — nav_packaged_automation, landed by #12457). A locator or copy fix goes to objectui; a nav-entry regression goes HERE. Conflating the two repos is how the original nav gap survived both repos' tests — each side now pins its own half (framework: setup-packaged-automation-nav.test.ts; objectui: PackagedAutomationPage.navContribution.test.tsx), and only the live sidebar click observes the seam between them", - "THE PINS ARE COMPONENT TESTS UNDER ui/ — `automated.ref` names ui/packages/app-shell component tests, runnable from this checkout with that package's vitest; and those tests are the MOCKED half only (stubbed fetch): they prove the page against faked responses and structurally cannot see the missing framework nav entry, a live server's refusal bodies, or persistence", - "MEMBER POSTURE MEASURED, NOT ADJUDICATED (QA run #23163, objectstack 36b2889c, objectui 1fb1ced36243): a plain member could NOT open the Setup app — so neither the page nor its switches — and its read of sys_metadata_activation answered 403. That is the opposite of the posture acceptance[7] was authored against (the member reaches the page; the ledger and meta reads are open), and of the 'Reads stay open' note in packages/platform-objects/src/system/sys-metadata-activation.object.ts. The maintainer question the clause flags is still open; this note only records what the run met. QA run #26468 met the same posture at 756220c4 (member 403 at the Setup app's open, and 403 on the activation read): the second run to see it. While the member cannot reach the page, acceptance[5]'s member-toggle cell cannot be driven through it either: score that cell on the reachability a run actually meets, and say which" + "TRANSITION (ADR-0146 D4/D11, #28785 then #28818): the Setup page this item was authored against (PackagedAutomationPage.tsx, ref 'automation:packaged', nav_packaged_automation) is retired. Its flows half is Studio's Automations pillar on the read-only package (the FlowLedgerSwitch header switch and FlowItemMoreMenu's New similar); its actions half is Setup › Packaged actions (PackagedActionsPage.tsx, ref 'automation:packaged-actions', nav_packaged_actions). The retired page's bookmark /apps/APP/component/automation/packaged is a static route (PackagedAutomationRedirect, AppContent.tsx) that opens /studio/PKG/automations when ?package= names one and /studio/~all/metadata/flow otherwise (packagedAutomationStudioPath, setupMetadataRedirect.ts). Re-pointed from QA run #29124 (W4-L1, 7fa9873f), which drove every clause on these replacements", + "REPO OWNERSHIP (ADR-0054) still splits the nav clause: the PAGES — markup, testids, dialogs, refusal rendering — are the ui/ tree's (Studio: ui/packages/app-shell/src/views/studio-design/; Setup › Packaged actions: views/setup/PackagedActionsPage.tsx + PackagedActionsSection.tsx); the Setup NAV METADATA that reaches the actions page is packages/platform-objects/src/apps/setup-nav.contributions.ts (nav_packaged_actions). Each side pins its own half (setup-packaged-actions-nav.test.ts; PackagedActionsPage.navContribution.test.tsx), and only the live sidebar click observes the seam between them", + "MEMBER POSTURE MEASURED, NOT ADJUDICATED (QA runs #23163 at 36b2889c, #26468 at 756220c4, #29124 at 7fa9873f): a plain member cannot open the Setup app, and #29124 measured that Studio refuses the member too; the member's read of sys_metadata_activation answers 403 while GET /api/v1/automation/_status and GET /api/v1/meta/action stay open. That is the opposite of the 'Reads stay open' note in packages/platform-objects/src/system/sys-metadata-activation.object.ts. The maintainer question acceptance[7] flags is still open. While the member reaches neither door, acceptance[5]'s member cell is scored over the API (the toggle door's 403 body): record that it was" ] }, "steps": [ "boot the showcase on its own port and DB (dogfood §0, OS_PORT exported); build/serve the pinned console; sign in as the dev admin", - "nav probe: GET /api/v1/meta/app?id=setup and search the served navigation for a type:'component' item with componentRef 'automation:packaged'; then look for the entry in the rendered Setup sidebar and click it", - "regardless of the nav verdict, reach the page and screenshot it settled (only if the sidebar entry is MISSING — a regression of #12457 — fall back to the typed URL /_console/apps/com.objectstack.setup/component/automation/packaged — where the sidebar entry lands, the app segment being the package id (QA run #24545) — recording that the typed URL was needed, so the rest of the item is still scored)", - "inventory: compare the flows table rows against GET /api/v1/meta/flow (which items carry a real _packageId and _provenance != 'org') and GET /api/v1/automation/_status (the runtime list the page joins as its spine), and the actions section against its own packaged-actions read. Read each row's Activation cell as two facts: the switch shows the activation flag, and the badge beside it (data-testid packaged-flow-activation, its data-reading attribute) says whether the flow RUNS (objectui packagedFlows.ts activationReading). The badge reads 'On' for an enabled flow whose _status row carries no unboundCause (bound, or with no trigger to bind); 'Not running here', with the switch still checked, for an enabled flow whose row carries one; and 'Off', switch unchecked, for a disabled flow — showcase_approver_bindings, authored status: 'obsolete' (examples/app-showcase/src/automation/flows/approver-bindings.flow.ts). On a default boot (OS_AUTOMATION_SCHEDULED_WORK_ENABLED unset) the two type: 'schedule' flows, showcase_scheduled_digest (interval) and showcase_task_due_reminder (time_relative), carry unboundCause 'deployment_policy' (RUNNER.md, the scheduled-work entry), so they read 'Not running here', each with a Details chip holding the platform's reason. Derived from the 32 flows allFlows declares at 93e6c1a5f8 (examples/app-showcase/src/automation/flows/index.ts): 1 obsolete → Off; those 2 → Not running here; 29 → On (18 record-triggered flows and 1 api-triggered flow, which bind, and 10 with no trigger); a boot that exports the switch reads 31 On / 1 Off. Reconcile the rows against _status, not against these digits: a row whose unboundCause is anything but deployment_policy (disabled_subflow, missing_trigger, binding_failure) is a finding to read, not a count to match", - "toggle: flip showcase_urgent_task_alert OFF; reload the page fully; record the switch state and the sys_metadata_activation row; flip it back ON", - "clone dialog: open Clone on a flow row — it opens PREFILLED with SOURCE_copy and 'LABEL (copy)', both editable and submit enabled (objectui#13344, ui/packages/app-shell/src/views/setup/PackagedAutomationPage.tsx), so clear each field first — then try submitting with name empty, label empty, then name 'Bad-Name!' + a label; record what renders; then a legal clone and the post-clone notice; DELETE the clone via the API afterwards", - "refusal rendering (admin): flip showcase_notify_owner OFF — screenshot the row after the 409 lands", - "refusal rendering + posture (plain member): sign in as the member IN A FRESH BROWSER CONTEXT (own session — both localStorage token and cookie, or the cell is still the admin's), open the page, record what renders BEFORE any click, then flip any switch and screenshot the row", - "lineage sweep: with the clone from step 6 present, search the whole page (both sections) for any diff/badge/'customized'/lineage affordance" + "nav probe: GET /api/v1/meta/app?id=setup and search the served navigation for the type:'component' item with componentRef 'automation:packaged-actions' (and confirm no item names 'automation:packaged'); then click the entry in the rendered Setup sidebar. Then type the retired page's bookmark /_console/apps/com.objectstack.setup/component/automation/packaged and record where it lands (no ?package= → /_console/studio/~all/metadata/flow), and once more with ?package=com.example.showcase (→ /_console/studio/com.example.showcase/automations, the query kept)", + "open Studio on the read-only package: /_console/studio/com.example.showcase/automations; screenshot the rail settled", + "inventory: compare the rail's flows against GET /api/v1/automation/_status (the runtime list whose rows carry the ledger flag) and GET /api/v1/meta/flow (the items whose _packageId is com.example.showcase). Open a flow (the URL gains ?surface=flow:NAME, studioPillarItemPath in metadataArea.ts) and read the header switch data-testid auto-ledger-switch as two facts: aria-checked is the ledger flag of that flow's _status row, and data-reading says whether the flow RUNS ('on' — 'On for this deployment'; 'not-running' — 'On for this deployment, not running here', with the platform's reason in auto-ledger-not-running-reason; 'off' — 'Off for this deployment'). The readings come from objectui packagedFlows.ts activationReading, the same rule the retired page used. Expect showcase_approver_bindings (authored status: 'obsolete') off, and on a default boot (OS_AUTOMATION_SCHEDULED_WORK_ENABLED unset) showcase_scheduled_digest and showcase_task_due_reminder not-running with unboundCause 'deployment_policy'. Reconcile against _status, not against counts: QA run #29124 read 32 flows on both the rail and _status", + "toggle: on showcase_urgent_task_alert flip auto-ledger-switch OFF (POST /api/v1/automation/showcase_urgent_task_alert/toggle); reload the page fully and re-open the flow; record the switch state and the sys_metadata_activation row (metadata_type 'flow', name showcase_urgent_task_alert, active false); flip it back ON", + "New similar: on an open packaged flow open the ⋯ menu (auto-item-more) → New similar (auto-item-new-similar). The dialog (auto-new-similar-dialog) opens PREFILLED with NAME_copy and 'LABEL (copy)', both editable and submit enabled (FlowItemMoreMenu.tsx openNewSimilar), so clear each field first — then try submitting with name empty, label empty, then name 'Bad-Name!' + a label; record what renders; then make a legal copy and record the landing strip (auto-new-similar-strip, with the door's notice in auto-new-similar-notice) and where the copy opened; DELETE the copy via the API afterwards", + "Packaged actions: open Setup › Packaged actions (the sidebar entry of steps[1]) and screenshot the section (data-testid packaged-actions-section) and its rows", + "refusal rendering (admin): open showcase_notify_owner in Studio and flip its auto-ledger-switch OFF — screenshot the studio-refusal strip after the 409 lands and read the role of studio-refusal-message", + "refusal rendering + posture (plain member): sign in as the member IN A FRESH BROWSER CONTEXT (own session — both localStorage token and cookie, or the cell is still the admin's); open Setup and Studio and record what renders BEFORE any click; then flip a switch where a door is reachable, or else POST /api/v1/automation/showcase_urgent_task_alert/toggle {\"enabled\": false} as the member and capture the 403 body", + "lineage sweep: with the copy from step 6 present, search the Studio flow page, the rail and the Packaged actions page for any diff/'customized'/clonedFrom/lineage affordance" ], "acceptance": [ { - "clause": "the page is REACHABLE FROM SETUP NAVIGATION: the served setup app metadata contains a type:'component' nav item with componentRef 'automation:packaged', and clicking the sidebar entry lands on the page", + "clause": "the Setup navigation serves the Packaged actions entry and no longer the retired flows page: the served setup app carries the type:'component' item componentRef 'automation:packaged-actions' and none naming 'automation:packaged', clicking the sidebar entry lands on Setup › Packaged actions, and the retired page's bookmark lands in Studio (the cross-package flow list, or the named package's Automations pillar), never on a dead Setup page", "oracle": "network", - "verify": "the GET /api/v1/meta/app?id=setup body carries the item — id 'nav_packaged_automation', type 'component', componentRef 'automation:packaged', label 'Packaged Automation', in group_apps beside Packages — AND the sidebar click navigates to the page. The entry is contributed at packages/platform-objects/src/apps/setup-nav.contributions.ts (#12457, fixing the gap the #12438 sweep found: the page had merged in objectui with no framework nav entry naming its ref, reachable only by typed URL). Two deliberate ABSENCES on the entry are part of the contract — no requiresService ('automation') and no requiredPermissions (the sys_metadata_activation ledger works without the automation service, #12419; the write doors gate server-side) — do not 'fix' either. ⛔ A typed-URL landing must still NOT tick this clause: each repo pins only its own half (framework nav metadata: setup-packaged-automation-nav.test.ts; objectui ref→URL resolution: PackagedAutomationPage.navContribution.test.tsx), and the live served-app body + real sidebar click is the only observation that crosses the seam the original defect lived in", - "evidence": "the meta/app body (item present or absent) + sidebar screenshot" + "verify": "the GET /api/v1/meta/app?id=setup body carries id 'nav_packaged_actions', type 'component', componentRef 'automation:packaged-actions', label 'Packaged Actions' (setup-nav.contributions.ts), and nav_packaged_automation occurs 0 times (tombstoned in setup-nav-dead-key-tombstone.test.ts) — AND the sidebar click navigates to the page. The entry carries no requiresService and no requiredPermissions, as its predecessor did (the write doors gate server-side) — do not 'fix' either. The bookmark legs are the navigation trace of steps[1]: /component/automation/packaged → /studio/~all/metadata/flow with no ?package=, → /studio/com.example.showcase/automations with one (packagedAutomationStudioPath; pinned by AppContent.setupMetadataStudio-28578.test.tsx). ⛔ A typed-URL landing does not tick the sidebar half: each repo half pins only itself (setup-packaged-actions-nav.test.ts; PackagedActionsPage.navContribution.test.tsx), and the live served body + real sidebar click is the only observation that crosses the seam", + "evidence": "the meta/app body + sidebar screenshot + the two bookmark navigation traces" }, { - "clause": "inventory: the flows table lists exactly the packaged flows (each row's switch, the activation flag, is checked by default, an obsolete-status flow excepted: the engine holds it disabled, so its switch is unchecked) and the packaged-actions section renders beside it; tenant/org-authored flows are ABSENT. This clause judges the switch, not the badge beside it: on a default boot the two policy-unbound schedule flows keep a checked switch while their badge reads 'Not running here' (steps[3]), and that reading does not fail this clause", + "clause": "inventory: Studio's Automations rail on the read-only showcase package lists exactly that package's packaged flows, each with a row in GET /api/v1/automation/_status, and an open flow's ledger switch is checked by default (an obsolete-status flow excepted: the engine holds it disabled). A flow the customization pair adds appears only marked 'In com.example.showcase.custom' (engine.studio.customize.addedIn), never as one of the base's flows. This clause judges the flag, not the reading: on a default boot the two policy-unbound schedule flows keep a checked switch whose data-reading is 'not-running' (steps[3]), and that does not fail this clause", "oracle": "dom", - "verify": "rows (data-testid packaged-flow-) match the isPackagedFlowItem filter — a real _packageId, not 'sys_metadata', and _provenance != 'org', joined against the engine runtime list as spine (objectui packagedFlows.ts). The clone created later must NOT appear (it is a tenant artifact). Erring either way is a real defect: a tenant flow shown gets an install-wide switch Studio owns; a packaged flow filtered out leaves no off-switch at all. Score each row's switch against the enabled field of its row in the same GET /api/v1/automation/_status read steps[3] takes: the page checks the switch exactly where that field is not false (joinPackagedFlows in packagedFlows.ts; the Switch's checked prop in PackagedAutomationPage.tsx), and the engine sets it false only for an obsolete or invalid status or a ledger toggle (isFlowEnabled, service-automation engine.ts). OS_AUTOMATION_SCHEDULED_WORK_ENABLED moves the badge, never the flag: on a fresh boot the only unchecked switch is the obsolete-status flow's (showcase_approver_bindings), whether that variable is exported or not. The badge (data-testid packaged-flow-activation) is steps[3]'s reading, reconciled there against the row's unboundCause: ⛔ do not score a 'Not running here' badge as a switch reading Off, nor read a checked switch as a flow that runs", - "evidence": "row list vs the two API reads, side by side" + "verify": "rail rows vs the _status rows filtered to the package: same names, none missing, none extra (a copy from steps[5] shows only with its 'In PAIR' mark). For each opened flow, auto-ledger-switch aria-checked equals the enabled field of its _status row (StudioDesignSurface.tsx: ledgerRow from flowStatus, FlowLedgerSwitch checked), which the engine sets false only for an obsolete or invalid status or a ledger toggle (isFlowEnabled, service-automation engine.ts). OS_AUTOMATION_SCHEDULED_WORK_ENABLED moves the reading, never the flag. ⛔ Do not score a 'not-running' reading as a switch reading Off, nor read a checked switch as a flow that runs. QA run #29124 read 32 flows on the rail and in _status", + "evidence": "rail list vs the two API reads, side by side, plus the switch's aria-checked and data-reading per flow opened" }, { - "clause": "the toggle flips state AND persists: after flipping OFF and a full reload the switch still reads Off and the sys_metadata_activation row reads active=false", + "clause": "the toggle flips state AND persists: after flipping OFF in Studio and a full reload the switch still reads Off and the sys_metadata_activation row reads active=false", "oracle": "api", - "verify": "the reload re-derives the switch from the server (runtime states), not from component state; corroborate with the data-API row read — the DOM alone cannot distinguish a durable flip from an optimistic one", + "verify": "the reload re-derives the switch from the server's _status row, not from component state (the switch binds the ledger, never the definition's status: AutomationsPillar.ledgerSwitch-28148.test.tsx 'binds the switch to the ledger, not the definition, and a flip posts the toggle and saves no draft'); corroborate with the data-API row read — the DOM alone cannot distinguish a durable flip from an optimistic one. QA run #29124 measured the state across a reload and in the activation row", "evidence": "post-reload screenshot + the row read" }, { - "clause": "clone is offered on FLOW rows ONLY — no clone affordance anywhere in the packaged-actions section", + "clause": "copying is offered on FLOWS only: an open packaged flow's ⋯ menu offers New similar, and Setup › Packaged actions offers no clone or copy affordance anywhere", "oracle": "dom", - "verify": "every flow row carries a Clone button; the actions section has none (its own header states why — ADR-0126 §8 keeps the action clone pre-chartered; PackagedActionsSection.tsx header ⛔ 'No clone'). A clone control appearing on actions is a FAIL against the charter, not a bonus", - "evidence": "screenshots of both sections" + "verify": "auto-item-new-similar is present in the flow's menu; packaged-actions-section carries no clone/copy control (PackagedActionsSection.tsx header ⛔ 'No clone' — ADR-0126 §8 item 2 keeps the action clone pre-chartered). A clone control appearing on actions is a FAIL against the charter, not a bonus", + "evidence": "screenshots of the flow menu and the actions section" }, { - "clause": "NO lineage surface: no diff-vs-base, no 'customized' badge, no clonedFrom link, no drift indicator anywhere on the page — including after a clone exists", + "clause": "NO lineage surface: no diff-vs-base, no clonedFrom link, no drift indicator on the Studio flow page, its rail or the Packaged actions page — including after a copy exists. The pair's 'In com.example.showcase.custom' mark and a customization's own controls (ADR-0146 D2) say where an item is saved, not what it was copied from, and do not fail this clause", "oracle": "dom", - "verify": "the step-9 sweep finds nothing lineage-shaped (ADR-0126 §9 / the ⛔ no-drift-surface rule quoted in the page header). Displayed ancestry would mean a data source the platform ruled it does not track — its appearance is a FAIL even if it looks helpful", - "evidence": "full-page screenshot with the clone present" + "verify": "the step-10 sweep finds nothing that names the copy's source (ADR-0126 §9; FlowItemMoreMenu.tsx: 'No lineage is drawn … nothing here says what the copy was made from'; PackagedActionsSection.tsx ⛔ 'No drift or ancestry surface'). Displayed ancestry would mean a data source the platform ruled it does not track — its appearance is a FAIL even if it looks helpful", + "evidence": "full-page screenshots with the copy present" }, { - "clause": "server refusals render VERBATIM in a role=alert beside the control that caused them: the plain member's toggle shows the exact manage_metadata sentence; the admin's subflow toggle shows the 409 naming showcase_task_done_notify_owner; the local fallback strings appear nowhere", + "clause": "server refusals render VERBATIM and are announced: the admin's subflow toggle shows the 409 naming showcase_task_done_notify_owner in Studio's refusal strip, whose sentence element carries role=alert; the plain member's toggle answers the exact manage_metadata sentence; the local fallback strings appear nowhere", "oracle": "dom", - "verify": "the member's alert text equals 'Enabling or disabling an automation flow requires the `manage_metadata` capability.' (domains/automation.ts) and the admin's names the caller — actionErrorDetail reads error → error.message → message and falls back only when the body is unreadable (objectui core/src/actions/actionErrorDetail.ts; the alert at PackagedAutomationPage.tsx). The i18n fallbacks (packagedAutomation.toggleFailed / toggleFailedHttp) rendering instead of the server sentence means the body was dropped — the server's remedy ('disable the callers first' / the capability name) is the actionable half", - "evidence": "both alert screenshots with the text transcribed" + "verify": "the admin cell: [data-testid=studio-refusal-message] has role=\"alert\" (StudioRefusalStrip, StudioDesignSurface.tsx — #29139 closed by #29224, which put the role on the sentence alone, so 'Show me' and Details stay outside the alert) and its text is the server's 409 sentence naming the caller; toggleLedger hands it over verbatim (requestFlowToggle, packagedFlowLedger.ts, reads it with actionErrorDetail). The member cell: the 403 text equals 'Enabling or disabling an automation flow requires the `manage_metadata` capability.' (FLOW_ENABLEMENT_DENY_MESSAGE, domains/automation.ts) — read from the strip if the member reaches a switch, otherwise from the toggle door's body (fixtures.knownGaps[2]; QA run #29124 scored it over the API), and the record says which. The i18n fallbacks (engine.studio.auto.ledger.toggleFailed / toggleFailedHttp) rendering instead of the server sentence means the body was dropped", + "evidence": "the admin strip screenshot with its role attribute and text transcribed + the member's 403 body (or strip screenshot)" }, { - "clause": "the clone dialog requires BOTH machine name and label before submit is possible, and a server-refused name surfaces legibly: the illegal-name 400 renders in the dialog's alert — record exactly what text arrives, because the per-field details[] are dropped client-side", + "clause": "the New similar dialog requires BOTH a new machine name and a new label before submit is possible, a server-refused name surfaces verbatim in role=alert inside the dialog, and a legal copy lands switched off in the pair with the door's notice shown as sent", "oracle": "dom", - "verify": "the submit control is disabled until both fields are non-empty (cloneNameValid, which disables the clone dialog's submit button in objectui PackagedAutomationPage.tsx — so the 'required' 400s of the API item are unreachable from this dialog); submitting 'Bad-Name!' round-trips the server 400 and the dialog alert (role=alert) shows the message actionErrorDetail extracted — details[] never reach the DOM, so if the rendered sentence does not state the ^[a-z_][a-z0-9_]*$ constraint legibly, record what DID render as the finding rather than inventing a pass. A legal clone then shows the server's post-clone notice verbatim", - "evidence": "dialog screenshots: disabled submit, the 400 alert text, the post-clone notice" + "verify": "auto-new-similar-submit is disabled while either field is blank (copyValid, FlowItemMoreMenu.tsx — so the API's 'required' 400s are unreachable from this dialog); submitting 'Bad-Name!' round-trips the clone door's 400 and auto-new-similar-refusal (role=alert) shows the sentence actionErrorDetail extracted, with the dialog still open on the names typed — details[] never reach the DOM, so if the sentence does not state the ^[a-z_][a-z0-9_]*$ constraint legibly, record what DID render as the finding rather than inventing a pass. A legal copy on the read-only base sends no package (the door picks the pair), opens in com.example.showcase.custom, and auto-new-similar-strip reads 'Saved the copy NAME in package com.example.showcase.custom.' with the door's notice verbatim in auto-new-similar-notice. QA run #29124 measured the validation and the 400 in role=alert", + "evidence": "dialog screenshots: disabled submit, the 400 alert text, the landing strip with its notice" }, { - "clause": "posture record (not a pass/fail gate): record whether a plain member REACHES the page and sees the switches, which lists load, what the activation read answers, and where the first refusal arrives. Authored (#12438) against a posture where the member reached the page with open reads and met the refusal only after a click; at 36b2889c the member could not open Setup and the activation read answered 403 (knownGaps[2]). Record the posture as observed", + "clause": "posture record (not a pass/fail gate): record whether a plain member REACHES Setup › Packaged actions or Studio's Automations pillar, which lists load, what the activation read answers, and where the first refusal arrives. Authored (#12438) against a posture where the member reached the page with open reads and met the refusal only after a click; QA run #29124 met a member refused at both Setup and Studio, with the activation read 403 and _status and /meta/action open (knownGaps[2]). Record the posture as observed", "oracle": "dom", - "verify": "ComponentNavView resolves the ref with no authz check (objectui ComponentNavView.tsx) and sys_metadata_activation declares its reads open (apiMethods ['get', 'list'], 'Reads stay open so operability surfaces can answer \"what is disabled here?\"', sys-metadata-activation.object.ts) — but a member's read of it answered 403 in QA runs #23163 and #26468, so the declaration opens the verbs and the member's permission set grants no read. Record which the run meets: open reads (the member sees real install-wide state with dead controls) or the 403 (knownGaps[2]). Whether that read-exposure is intended is a maintainer question tracked in the #12438 sweep's follow-ups — this clause RECORDS what a member sees (which lists load, what the first refusal moment is); it does not adjudicate", - "evidence": "the member's pre-click page screenshot + the list of requests the page made and their statuses" + "verify": "sys_metadata_activation declares its reads open (apiMethods ['get', 'list'], 'Reads stay open so operability surfaces can answer \"what is disabled here?\"', sys-metadata-activation.object.ts) — but a member's read of it answered 403 in QA runs #23163, #26468 and #29124, so the declaration opens the verbs and the member's permission set grants no read. Record which the run meets. Whether that read-exposure is intended is a maintainer question tracked in the #12438 sweep's follow-ups — this clause RECORDS what a member sees; it does not adjudicate", + "evidence": "the member's pre-click screenshots of both doors + the list of requests and their statuses" } ], "negative": [ - "ticking the nav clause off a typed-URL landing (or off either repo's unit pin alone) is the exact false positive this item exists to block — the page working and the page being findable are different facts, and the second was red for a full epic before #12457 precisely because each repo's tests proved only its own half", - "scoring the member's refusal cell while the admin's token is still live in the browser context is an auth-state-leak — the alert would show a 409 (subflow) instead of the 403 (capability), silently proving the wrong clause", - "a toggle whose switch flips back On after reload while the API row says active=false (or vice versa) is a FAIL wherever the mismatch lies — the page must derive from the server, and the server must have written the row" + "ticking the nav clause off a typed-URL landing (or off either repo's unit pin alone) is the exact false positive this item exists to block — the page working and the page being findable are different facts", + "scoring the member's refusal cell while the admin's token is still live in the browser context is an auth-state-leak — the strip would show a 409 (subflow) instead of the 403 (capability), silently proving the wrong clause", + "a toggle whose switch flips back On after reload while the API row says active=false (or vice versa) is a FAIL wherever the mismatch lies — the switch must derive from the server, and the server must have written the row", + "a Setup page still listing packaged FLOWS, or a sidebar item naming 'automation:packaged', is a FAIL: two switches for one ledger row is the split ADR-0146 D11 retired" ], "traps": [ "wrong-persona", @@ -2340,12 +2343,12 @@ ], "automated": { "kind": "test", - "ref": "ui/packages/app-shell/src/views/setup/PackagedAutomationPage.test.tsx + PackagedActionsSection.test.tsx + PackagedAutomationPage.navContribution.test.tsx — the MOCKED component half only (stubbed fetch): rendering, filter, dialog validation, alert wiring; they cannot evidence live refusal bodies or persistence. PackagedAutomationPage.navContribution.test.tsx is acceptance[0]'s ui half (the ref→URL resolution its verify names), component-layer like the others: it scores no clause on its own. The nav clause's framework half is pinned HERE by packages/platform-objects/src/apps/setup-packaged-automation-nav.test.ts (#12457: entry present, ref/label/group, the two deliberate absences, parses as a NavigationContribution) — runnable from this checkout, but it proves the contribution only; the served body + sidebar click still need the live run" + "ref": "ui/packages/app-shell/src/views/studio-design/AutomationsPillar.ledgerSwitch-28148.test.tsx (the ledger switch on a read-only package: binds the ledger flag, the off and not-running readings, the walled switch, the refusal verbatim, and 'announces the subflow guard's 409: one alert, the sentence verbatim, and again on a second refusal') + AutomationsPillar.newSimilar-28567.test.tsx (New similar: no target on a read-only base, the copy opened where the answer named, a 409 verbatim with the dialog kept open, no menu in the package-less scope) + StudioDesignSurface.refusalAnnounced-29139.test.tsx (the strip's sentence is the role=alert, 'Show me' and Details outside it) + ui/packages/app-shell/src/views/setup/PackagedActionsSection.test.tsx + PackagedActionsPage.navContribution.test.tsx + ui/packages/app-shell/src/console/__tests__/AppContent.setupMetadataStudio-28578.test.tsx (the retired page's bookmark → Studio) — all the MOCKED component half (stubbed fetch): they cannot evidence live refusal bodies or persistence, and score no clause on their own. The nav clause's framework half is pinned by packages/platform-objects/src/apps/setup-packaged-actions-nav.test.ts (entry present, ref/label/group, the deliberate absences); the served body + sidebar click still need the live run" }, "source": [ - "docs/adr/0126-packaged-metadata-customization-model.md §7.4 (the Setup surface: on/off + clone, authoring stays in Studio; ⛔ no drift/ancestry surface)", - "objectui packages/app-shell/src/views/setup/PackagedAutomationPage.tsx (the clone dialog's validation — cloneNameValid, which disables its submit button — and its role=alert rendering of clone.refusal) + PackagedActionsSection.tsx (header: ⛔ no clone on actions) + packagedFlows.ts (isPackagedFlowItem + runtime-spine join)", - "objectui packages/app-shell/src/services/builtinComponents.tsx (registerAppComponent ref 'automation:packaged') + views/ComponentNavView.tsx (ref resolution, no gate) + views/setup/PackagedAutomationPage.navContribution.test.tsx (ref→URL resolution — the mocked half of the nav clause)", + "docs/adr/0126-packaged-metadata-customization-model.md §7.4 (on/off + clone; ⛔ no drift/ancestry surface) as amended by docs/adr/0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md D4 (the packaged flow's switch and New similar move to the item in Studio; Setup › Packaged automation retires) and D11 (the retire list, N-28)", + "ui/packages/app-shell/src/views/studio-design/FlowLedgerSwitch.tsx (auto-ledger-switch: aria-checked = the ledger flag, data-reading = activationReading) + FlowItemMoreMenu.tsx (New similar: copyValid disables submit; auto-new-similar-refusal role=alert) + StudioDesignSurface.tsx (toggleLedger, StudioRefusalStrip with studio-refusal-message role=alert, auto-new-similar-strip) + ui/packages/app-shell/src/views/setup/packagedFlows.ts (activationReading) + packagedFlowLedger.ts (requestFlowToggle)", + "ui/packages/app-shell/src/views/setup/PackagedActionsPage.tsx + PackagedActionsSection.tsx (header: ⛔ no clone, ⛔ no drift surface) + ui/packages/app-shell/src/services/builtinComponents.tsx (registerAppComponent ref 'automation:packaged-actions') + ui/packages/app-shell/src/views/studio-design/setupMetadataRedirect.ts#packagedAutomationStudioPath (the retired page's bookmark)", "objectui packages/core/src/actions/actionErrorDetail.ts (error → error.message → message; details[] dropped)", "packages/platform-objects/src/apps/setup-nav.contributions.ts#nav_packaged_actions (nav_packaged_actions → componentRef 'automation:packaged-actions', pinned by setup-packaged-actions-nav.test.ts). It replaced nav_packaged_automation → 'automation:packaged' (#12457): the page's flows half retired into Studio's Automations pillar (ADR-0146 D11, #28578), and its actions half is this entry; nav_packaged_automation is tombstoned in setup-nav-dead-key-tombstone.test.ts", "packages/runtime/src/domains/automation.ts (the exact enablement 403 sentence)", @@ -2414,6 +2417,12 @@ "date": "2026-10-27", "change": "personas[1], steps[5], fixtures.knownGaps[2] and acceptance[7].verify follow the wave-2 head. Checklist-accuracy findings of the 18.2.0 wave 2 (QA run #26468, release-18.2.0-w2-tier1-a, at 756220c4; wave anchor #25807, slice A): a fresh sign-up is refused on stock, so the member is minted invite-first; the clone dialog opens prefilled (objectui#13344), so 'submit with name empty' needs the fields cleared; and acceptance[7].verify asserted reads 'open by ruling' while two runs measured the member's read 403. Re-read at base e4ba846f (PackagedAutomationPage.tsx: SOURCE_copy and the (copy) label default; sys-metadata-activation.object.ts enable.apiMethods). The posture question is still not adjudicated here; no clause's assertion changed", "ref": "#25807" + }, + { + "revision": 11, + "date": "2026-11-02", + "change": "Re-pointed to the doors that replaced the retired Setup › Packaged automation page (ADR-0146 D4/D11; #28785 retired its flows half, #28818 sent its bookmark to Studio). Measured inputs: QA run #29124 (W4-L1, 7fa9873f; card note 6572222369), which failed A0, A1, A2, A3, A5 and A6 as stale spec and passed A4 and A7. Clause by clause, re-read at base dfbe34d887: acceptance[0] asserts nav_packaged_actions ('automation:packaged-actions'), the absence of 'automation:packaged', and the retired bookmark's landing in Studio (packagedAutomationStudioPath); acceptance[1] reads Studio's Automations rail against _status (32 flows in the run) and the header auto-ledger-switch's aria-checked/data-reading; acceptance[2] the same flip persisted across a reload; acceptance[3] New similar on flows, no clone on Setup › Packaged actions; acceptance[4] no lineage, now naming that the pair's 'In PAIR' mark and ADR-0146 D2's customization controls are not lineage; acceptance[5] re-points to Studio's refusal strip and, per the seat's ruling, to #29139's outcome instead of marking it transitional: #29224 put role=alert on studio-refusal-message, so the clause keeps 'announced' — the member's 403 cell is scored over the API while the member reaches neither door (#29124 measured exactly that); acceptance[6] the New similar dialog (copyValid, auto-new-similar-refusal role=alert, the landing strip in the pair); acceptance[7] records the posture #29124 met. steps, fixtures, negative, automated.ref and source[0]–[2] follow. No clause dropped; acceptance[4] no longer forbids a 'customized' badge, because ADR-0146 D2 charters that mark on served facts", + "ref": "#28579" } ] }, From a6314d0bcf1dbfea5b5c8acd2676b21a1aa7eddb Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:10:45 +0000 Subject: [PATCH 02/14] docs(qa): re-point automation.packaged-flow-clone-contract to new similar into the .custom pair Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd --- .../platform-checklist/areas/automation.json | 71 +++++++++++-------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/docs/qa/platform-checklist/areas/automation.json b/docs/qa/platform-checklist/areas/automation.json index 5b831a1c04e..a917cf3bcd9 100644 --- a/docs/qa/platform-checklist/areas/automation.json +++ b/docs/qa/platform-checklist/areas/automation.json @@ -2121,10 +2121,10 @@ }, { "id": "automation.packaged-flow-clone-contract", - "title": "POST /automation/:name/clone: whole-definition copy mutating exactly name/label/status, protection envelope dropped, no ancestry, draft-but-armed (double-fire notice verbatim), 409/404/400 refusal arms — and the honest clause: the clone must survive a restart and reach Studio", + "title": "POST /automation/:name/clone (Studio's New similar): whole-definition copy mutating exactly name/label/status, written switched off (status obsolete) into a package — the base's .custom pair when `package` is omitted —, protection envelope dropped, no ancestry, notice verbatim, 400/404/409/422 refusal arms — and the honest clause: the copy survives a restart and reaches Studio", "since": "v17", "status": "active", - "revision": 10, + "revision": 11, "priority": "P1", "surface": "mixed", "personas": [ @@ -2134,19 +2134,21 @@ "app": "showcase", "requires": [ "a FILE-backed database (the restart clause is unreachable without one — same discipline as automation.durable-suspend-restart)", - "showcase_urgent_task_alert (packaged record_change flow — the double-fire specimen: its trigger is an urgent showcase_task create, cheap to fire twice) and showcase_task_done_notify_owner (packaged flow carrying a subflow node → showcase_notify_owner — the references-not-re-pointed specimen)" + "showcase_urgent_task_alert (packaged record_change flow — the no-double-fire specimen: its trigger is an urgent showcase_task create, cheap to fire) and showcase_task_done_notify_owner (packaged flow carrying a subflow node → showcase_notify_owner — the references-not-re-pointed specimen)", + "the read-only base com.example.showcase and its pair com.example.showcase.custom: with `package` omitted the door ensures the pair (ADR-0146 D3) and lands the copy there (resolveCloneTarget, domains/automation.ts)", + "for acceptance[7]'s outside-closure arm, a writable package whose manifest declares no dependency on com.example.showcase (e.g. a scratch package made from the Studio landing); delete it at teardown" ] }, "steps": [ "boot the showcase on its own port against its own file DB (dogfood §0, OS_PORT exported); sign in as the dev admin", - "refusal arms first (no state left behind): POST /api/v1/automation/showcase_urgent_task_alert/clone with {} · with {\"name\":\"x\"} (label missing) · with {\"label\":\"x\"} (name missing) · with {\"name\":\"\",\"label\":\"Empty\"} (empty name, code invalid_type) · with {\"name\":\"showcase_urgent_task_alert\",\"label\":\"Same\"} (same-name) · with {\"name\":\"Bad-Name!\",\"label\":\"Illegal\"} (violates ^[a-z_][a-z0-9_]*$) · and POST /api/v1/automation/does_not_exist/clone {\"name\":\"qa_x\",\"label\":\"X\"} (unknown source) — record every status + code + message", - "the copy: POST /api/v1/automation/showcase_urgent_task_alert/clone {\"name\":\"qa_urgent_alert_clone\",\"label\":\"QA urgent alert clone\"} — record the FULL response body (flow + notice)", + "refusal arms first (no state left behind): POST /api/v1/automation/showcase_urgent_task_alert/clone with {} · with {\"name\":\"x\"} (label missing) · with {\"label\":\"x\"} (name missing) · with {\"name\":\"\",\"label\":\"Empty\"} (empty name, code invalid_type) · with {\"name\":\"showcase_urgent_task_alert\",\"label\":\"Same\"} (same-name) · with {\"name\":\"Bad-Name!\",\"label\":\"Illegal\"} (violates ^[a-z_][a-z0-9_]*$) · and POST /api/v1/automation/does_not_exist/clone {\"name\":\"qa_x\",\"label\":\"X\"} (unknown source) — record every status + code + message. Then the `package` arms, each on POST /api/v1/automation/showcase_urgent_task_alert/clone {\"name\":\"qa_x\",\"label\":\"X\",\"package\":…}: \"package\": \"\" (blank) · \"package\": \"qa_no_such_pkg\" (not installed) · \"package\": \"com.example.showcase\" (read-only) · \"package\": the scratch package with no dependency on the base (outside the closure); after each, confirm GET /api/v1/automation/qa_x answers 404", + "the copy: POST /api/v1/automation/showcase_urgent_task_alert/clone {\"name\":\"qa_urgent_alert_clone\",\"label\":\"QA urgent alert clone\"} (no `package`) — record the FULL response body (flow + packageId + notice)", "read both definitions back (GET /api/v1/automation/qa_urgent_alert_clone and …/showcase_urgent_task_alert) and diff them key by key", - "double-fire: POST /api/v1/data/showcase_task with priority='urgent'; GET both flows' runs lists", + "no double-fire: POST /api/v1/data/showcase_task with priority='urgent' (with its required project), three times; GET both flows' runs lists", "references: POST /api/v1/automation/showcase_task_done_notify_owner/clone {\"name\":\"qa_task_done_clone\",\"label\":\"QA task-done clone\"}; read the clone's subflow node config", - "ordinariness: PUT /api/v1/automation/qa_urgent_alert_clone with the clone's own definition (label tweaked) — it is a tenant flow, the edit lands", - "restart probe: stop the server; cold-boot over the SAME database file; GET /api/v1/automation/qa_urgent_alert_clone and GET /api/v1/meta/flow (does any package/tenant metadata hold the clone?); then, in the browser as the admin (this leg needs a served console: on a tree built with `pnpm build` alone /_console/ answers 503 until `pnpm console:build` has run), open /_console/studio/~org/automations (the 'Organization flows' scope) and confirm the clone is listed on the Automations rail and opens in the designer; create one more urgent task (with its required project) and confirm the clone's runs list grows on the new process", - "teardown: DELETE /api/v1/automation/qa_urgent_alert_clone and …/qa_task_done_clone (or discard the run's own DB)" + "ordinariness: PUT /api/v1/automation/qa_urgent_alert_clone with the clone's own definition (label tweaked) — it is the pair's own flow, the edit lands", + "restart probe: stop the server; cold-boot over the SAME database file; GET /api/v1/automation/qa_urgent_alert_clone and GET /api/v1/meta/flow (which package holds the copy?); create one more urgent task and read both runs lists; then, in the browser as the admin (this leg needs a served console: on a tree built with `pnpm build` alone /_console/ answers 503 until `pnpm console:build` has run), open /_console/studio/com.example.showcase/automations and confirm the copy is listed on the rail marked 'In com.example.showcase.custom' and opens in the designer; open /_console/studio/~org/automations and record what it lists", + "teardown: DELETE /api/v1/automation/qa_urgent_alert_clone and …/qa_task_done_clone (or discard the run's own DB), and the scratch package" ], "acceptance": [ { @@ -2156,16 +2158,16 @@ "evidence": "the four 400 bodies with their details arrays" }, { - "clause": "the copy is whole-definition with exactly name/label/status mutated, the protection envelope and read decorations dropped, and NO ancestry: every other key of the clone deep-equals the source; none of the 10 FLOW_CLONE_DROPPED_KEYS (_diagnostics, _draft, _lock, _lockReason, _lockSource, _provenance, _packageId, _packageVersion, _lockDocsUrl, protection) is present on the clone; no clonedFrom-shaped key exists in the response or the stored definition", + "clause": "the copy is whole-definition with exactly name/label/status mutated, the protection envelope and read decorations dropped, and NO ancestry: every other key of the copy deep-equals the source; none of the 10 FLOW_CLONE_DROPPED_KEYS (_diagnostics, _draft, _lock, _lockReason, _lockSource, _provenance, _packageId, _packageVersion, _lockDocsUrl, protection) is present on the copy; no clonedFrom-shaped key exists in the response or the stored definition. The response names where the copy LIVES (packageId com.example.showcase.custom when `package` was omitted), which is not ancestry", "oracle": "api", - "verify": "the step-4 diff: differences are exactly {name, label, status:'draft'} (FLOW_CLONE_MUTATED_FIELDS, flow-clone.ts) plus the ABSENCE of the dropped keys (flow-clone.ts — METADATA_READ_DECORATIONS + MetadataProtectionFields, derived, plus the package author's `protection` block, named (#23120): 2+7+1=10 at base 9a7838edf5; count them from the source at run time rather than trusting this line). A missing facet (nodes, edges, variables, runAs, retry…) is the #11703 param-list failure shape; a surviving _packageId or _lock defeats the feature (the clone would be package-owned or locked); any ancestry key violates amendment ruling 2 / §9", - "evidence": "the key-by-key diff + the dropped-keys absence check" + "verify": "the step-4 diff: differences are exactly {name, label, status:'obsolete'} (FLOW_CLONE_MUTATED_FIELDS and FLOW_CLONE_STATUS, flow-clone.ts — 'obsolete' since #28149, ADR-0146 D4) plus the ABSENCE of the dropped keys (flow-clone.ts — METADATA_READ_DECORATIONS + MetadataProtectionFields, derived, plus the package author's `protection` block, named (#23120): 2+7+1=10 at base 9a7838edf5; count them from the source at run time rather than trusting this line). The step-3 response carries data.packageId 'com.example.showcase.custom' (domains/automation.ts, the clone arm's deps.success). A missing facet (nodes, edges, variables, runAs, retry…) is the #11703 param-list failure shape; a surviving _packageId or _lock defeats the feature (the copy would claim the base's package or be locked); any ancestry key violates amendment ruling 2 / §9", + "evidence": "the key-by-key diff + the dropped-keys absence check + the response's packageId" }, { - "clause": "status 'draft' is NOT an off-switch and the response says so verbatim: one urgent create fires BOTH base and clone (double-fire), and the clone response's notice equals FLOW_CLONE_NOTICE", + "clause": "the copy is written switched OFF and never double-runs beside its original, and the response says so verbatim: over the step-5 urgent creates the base runs each time and the copy never runs, and the response's notice equals FLOW_CLONE_NOTICE", "oracle": "api", - "verify": "step 5: one new run in EACH flow's runs list for the same create — the engine disables on status obsolete/invalid only, draft binds its trigger (flow-clone.ts). The response notice matches flow-clone.ts verbatim, including the toggle instruction — the sentence is the only thing standing between the admin and the two-flows-one-trigger surprise, so a paraphrase is a FAIL", - "evidence": "both runs lists bracketing the create + the notice text from the clone response" + "verify": "step 5: one new run per create in the base's runs list, none in the copy's — the copy carries status 'obsolete', one of the two values the engine disables a flow on (FLOW_CLONE_STATUS, flow-clone.ts; registerFlow, service-automation engine.ts). The notice matches flow-clone.ts FLOW_CLONE_NOTICE verbatim ('The copy is switched off and does not run until you switch it on, and it still uses the same subflows, actions and objects as the original.'), so a paraphrase is a FAIL. A copy run on a base trigger is the double-fire ADR-0146 D4 ruled out — FAIL. QA run #29002 measured 3 creates: the base ran each time, the copy never", + "evidence": "both runs lists bracketing the creates + the notice text from the clone response" }, { "clause": "the refusal arms answer their own status+code: same/taken name → 409 RESOURCE_CONFLICT with the shadow-rationale message suggesting a free name; unknown source → 404 RESOURCE_NOT_FOUND; an illegal machine name → 400 (the definition goes through registerFlow's own validation, one policy not two)", @@ -2180,38 +2182,45 @@ "evidence": "the clone's subflow node config read" }, { - "clause": "the clone is an ordinary tenant flow at the engine: PUT /api/v1/automation/qa_urgent_alert_clone lands (2xx, edit visible on re-read)", + "clause": "the copy is an ordinary flow of the pair at the engine: PUT /api/v1/automation/qa_urgent_alert_clone lands (2xx, edit visible on re-read)", "oracle": "api", - "verify": "step 7. ⚠️ Scope note: this proves the clone is editable, NOT that the packaged base is protected at this door — PUT /automation/:name currently performs no lock/provenance check on ANY flow (domains/automation.ts), and that door-parity question is access-security.packaged-flow-write-door-parity's expected-fail probe, not this clause", + "verify": "step 7. ⚠️ Scope note: this proves the copy is editable, NOT that the packaged base is protected at this door — that door-parity question is access-security.packaged-flow-write-door-parity's item, not this clause", "evidence": "the PUT response + the re-read" }, { - "clause": "the honest clause — the clone OUTLIVES the process and REACHES Studio: after a cold restart over the same database file the clone still exists and dispatches, and it is listed on a Studio Automations rail (it is 'an ordinary org/install-owned flow', ADR-0126 §7.1, under the shipped 'customize in Studio' promise §1.3 — the Setup page's own copy says 'Editing happens in Studio')", + "clause": "the honest clause — the copy OUTLIVES the process and REACHES Studio: after a cold restart over the same database file the copy still exists in com.example.showcase.custom, still switched off (it does not run on a new urgent create), and it is listed on the base's Studio Automations rail marked 'In com.example.showcase.custom' and opens in the designer (ADR-0146 D4: 'new similar' lands in the pair; ADR-0126 §1.3's 'customize in Studio' promise)", + "oracle": "api", + "verify": "step 8: GET /api/v1/automation/qa_urgent_alert_clone on the new process answers the copy; GET /api/v1/meta/flow shows it held by com.example.showcase.custom; the post-restart urgent create adds a run to the base only. Reachability: /_console/studio/com.example.showcase/automations lists the copy with the pair's mark (engine.studio.customize.addedIn 'In {pair}'; a read-only base's rail lists the flows its pair adds, StudioDesignSurface.tsx railPairId) and its deep link opens it. The package-less scope /_console/studio/~org/automations no longer holds the copy: QA run #29002 read 'No flows outside a package…' there, which is correct now that no clone is written without a package (ADR-0070 D5/D6). A copy absent from the base's rail, or a rail that opens another flow for the copy's deep link, fails the clause. Record which half failed (durability, reachability, or both) — do NOT soften the clause to a same-process read", + "evidence": "the post-restart GET (present or 404) + the meta/flow listing showing which package holds the copy + the rail screenshot" + }, + { + "clause": "the `package` target arms answer their own status + code and write nothing: a blank `package` → 400 on the field (code invalid_type); a package not installed → 422 WRITABLE_PACKAGE_REQUIRED; a read-only package → 422 WRITABLE_PACKAGE_REQUIRED; a writable package whose closure does not hold the source's package → 409 RESOURCE_CONFLICT", "oracle": "api", - "verify": "step 8: GET /api/v1/automation/qa_urgent_alert_clone on the new process, and the flow present in metadata a Studio rail can list. DURABILITY holds since cb4c31d (#20907, with the clone save path #20761): the clone survives a cold restart and still dispatches. REACHABILITY holds since objectui#11553 (the vendored console at f8267df93aba carries it; #21332 closed as a duplicate on 2026-10-03): Studio's org scope, /_console/studio/~org/automations ('Organization flows'), lists package-less flows on its Automations rail, so the clone stored with package_id null (_packageId deliberately stripped, flow-clone.ts) is listed there and opens in the designer. A clone absent from that rail, or a rail that opens another flow for the clone's deep link, fails the clause. Record which half failed (durability, reachability, or both) — do NOT soften the clause to a same-process read", - "evidence": "the post-restart GET (present or 404) + the meta/flow listing showing where, if anywhere, the clone is held" + "verify": "step 2's package arms: each message carries its bracketed reason (new_similar_target_not_installed, new_similar_target_read_only, new_similar_target_outside_closure — newSimilarTargetRefusal, domains/new-similar-target.ts) and ends 'Nothing was written.', and the follow-up GET answers 404 each time. The blank arm is the clone arm's own 400 (domains/automation.ts: 'must be a non-empty package id when sent'), never read as omitted. Pinned at the route layer by packages/runtime/src/domains/automation-flow-clone-custom-package.test.ts ('an explicit target: `package`'), a mocked-service pin that does not drive the live dispatcher", + "evidence": "the four refusal bodies + the four 404 re-reads" } ], "negative": [ - "a clone missing any source facet the diff can name (a node, an edge, a variable, the retry block) is the #11703 enumerated-param-list regression — fail the copy clause even if the clone runs", + "a copy missing any source facet the diff can name (a node, an edge, a variable, the retry block) is the #11703 enumerated-param-list regression — fail the copy clause even if the copy runs", "a clonedFrom / ancestry field appearing ANYWHERE — response, stored definition, or a later listing — is a FAIL of amendment ruling 2: a response field is the cheapest place for lineage to reappear and a UI that reads one starts displaying what the platform ruled it does not track", "a same-name clone answering 2xx is the silent order-dependent shadow #11665 §2.2 measured — the worst outcome this door has, because storage legitimately holds both rows and only the engine's bare-name map breaks", - "an anonymous or non-manage_metadata caller reaching the clone door (anything but 401/403 PERMISSION_DENIED) is a FAIL — the door is in the #10145 authoring write set precisely because a clone REGISTERS flow metadata at environment scope" + "an anonymous or non-manage_metadata caller reaching the clone door (anything but 401/403 PERMISSION_DENIED) is a FAIL — the door is in the #10145 authoring write set precisely because a clone REGISTERS flow metadata at environment scope", + "a copy written without a package (package_id null, listed under /_console/studio/~org/automations) is the package-less clone ADR-0146 D4 retired — FAIL" ], "traps": [ "dispatcher-vs-hono-route", "destructive-in-place" ], "source": [ - "docs/adr/0126-packaged-metadata-customization-model.md §7.1 (mandatory new name, whole-definition copy, no linkage), §9 (no re-pointing, no ancestry), §1.3 (the shipped 'customize in Studio' promise the honest clause holds the clone to)", + "docs/adr/0126-packaged-metadata-customization-model.md §7.1 (mandatory new name, whole-definition copy, no linkage), §9 (no re-pointing, no ancestry), §1.3 (the shipped 'customize in Studio' promise the honest clause holds the copy to), as amended by docs/adr/0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md D4 (clone kept only as 'new similar': a target package, the pair by default, the copy written inactive)", "packages/runtime/src/flow-clone.ts#FLOW_CLONE_STATUS (FLOW_CLONE_STATUS draft — and why it is deliberately not an off-switch), (FLOW_CLONE_MUTATED_FIELDS), (FLOW_CLONE_DROPPED_KEYS derived from spec), (FLOW_CLONE_NOTICE), (409 + name-taken message), (deep copy via structuredClone; _packageId et al. stripped)", - "packages/runtime/src/domains/automation.ts#clone (the clone arm: body-first validation, mandatory name+label, 404/409 probes, registerFlow-only registration, no ancestry on the response), (clone in the manage_metadata write set)", - "packages/runtime/src/domains/automation-flow-clone.test.ts#dispatcher (route-level pins for the validation and refusal arms — a mocked-service pin; it cannot see the live dispatcher, the double-fire, or the restart — which is exactly the remainder this item drives)", + "packages/runtime/src/domains/automation.ts#clone (the clone arm: body-first validation, mandatory name+label, optional `package`, 404/409 probes, registerAndSaveFlow, packageId + notice on the response, no ancestry), (clone in the manage_metadata write set) + resolveCloneTarget (the pair by default, ensured through the protocol's ensureCustomizationPackage) + packages/runtime/src/domains/new-similar-target.ts#newSimilarTargetRefusal (the 422/409 target arms)", + "packages/runtime/src/domains/automation-flow-clone.test.ts#dispatcher (route-level pins for the validation and refusal arms) + automation-flow-clone-custom-package.test.ts (the pair by default, switched off; the `package` arms) — mocked-service pins; they cannot see the live dispatcher, the runs, or the restart — which is exactly the remainder this item drives", "packages/spec/src/automation/flow.zod.ts (machine-name regex)", - "objectui packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx + studioScope.ts (the org scope ~org that lists package-less flows; objectui#11553, pinned by StudioDesignSurface.packageLessFlows-11553.test.tsx) — the reachability half of the honest clause", + "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (a read-only base's Automations rail lists the flows its pair adds, marked 'In PAIR' — engine.studio.customize.addedIn) + FlowItemMoreMenu.tsx (New similar, the Studio door onto this endpoint) — the reachability half of the honest clause", "examples/app-showcase/src/automation/flows/index.ts#UrgentTaskAlertFlow (UrgentTaskAlertFlow; TaskDoneNotifyOwnerFlow → showcase_notify_owner)", "#12438 (sweep) · Epic #12150", - "cross-ref: access-security.packaged-flow-write-door-parity (owns the packaged-base write-door question this item's ordinariness clause must not be read as answering); automation.setup-packaged-automation-board (the clone dialog UI half)" + "cross-ref: access-security.packaged-flow-write-door-parity (owns the packaged-base write-door question this item's ordinariness clause must not be read as answering); automation.setup-packaged-automation-board (the New similar dialog UI half)" ], "history": [ { @@ -2238,7 +2247,13 @@ "change": "surface api → mixed, and steps[7] says its browser leg needs a built console. Checklist-accuracy finding of the 18.2.0 wave 2 (QA run #26476, release-18.2.0-w2-manual-api, at 756220c4; wave anchor #25807, slice A): acceptance[6] and steps[7] open Studio in a browser, so the item is not api-only, and on a `pnpm build` tree the run met /_console/ answering 503 until `pnpm console:build`. Every other step stays an api step. No clause changed", "ref": "#25807" }, - { "revision": 10, "date": "2026-10-31", "change": "acceptance[0] and steps[1] separate an absent key from an empty one. Checklist-accuracy finding of QA run #27441 (18.3.0 sweep, wave 3, manual set, at c4c515ad; sweep anchor #27377): an empty-string name or label answers 400 with code invalid_type, and 'required' is only for an absent key; the clause now says both and adds the empty-string arm. Re-read at base 488b3ed0: packages/runtime/src/domains/automation.ts (the clone door's name and label details: absent ⇒ 'required', otherwise 'invalid_type'). The three absent-key arms are unchanged", "ref": "#27441" } + { "revision": 10, "date": "2026-10-31", "change": "acceptance[0] and steps[1] separate an absent key from an empty one. Checklist-accuracy finding of QA run #27441 (18.3.0 sweep, wave 3, manual set, at c4c515ad; sweep anchor #27377): an empty-string name or label answers 400 with code invalid_type, and 'required' is only for an absent key; the clause now says both and adds the empty-string arm. Re-read at base 488b3ed0: packages/runtime/src/domains/automation.ts (the clone door's name and label details: absent ⇒ 'required', otherwise 'invalid_type'). The three absent-key arms are unchanged", "ref": "#27441" }, + { + "revision": 11, + "date": "2026-11-02", + "change": "Re-pointed to ADR-0146 D4's 'new similar' (#28149: the copy lands in a package, the base's .custom pair when `package` is omitted, written status 'obsolete'). Measured input: QA run #29002 (W4-C3b1, 7fa9873f; card note 6570476620), which failed acceptance[1], [2] and [6] as stale spec. Re-read at base dfbe34d887 (flow-clone.ts FLOW_CLONE_STATUS 'obsolete' and FLOW_CLONE_NOTICE; domains/automation.ts resolveCloneTarget; new-similar-target.ts): acceptance[1]'s mutated status is 'obsolete' and the response's packageId is named as where the copy lives; acceptance[2] is inverted from 'double-fire, draft is not an off-switch' to 'switched off, never double-runs' — the property the run measured over 3 creates; acceptance[6] reads the copy on the base's Studio rail marked 'In com.example.showcase.custom', and ~org's empty list as correct; acceptance[5]'s 'tenant flow' becomes the pair's flow. New acceptance[7] asks the `package` arms the note named as unasked (blank 400, not installed 422, read-only 422, outside the closure 409). steps, fixtures, negative and source follow; no clause weakened", + "ref": "#28579" + } ] }, { From 7b8e9a907effd63147c7a619b1677e995a632977 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:12:06 +0000 Subject: [PATCH 03/14] =?UTF-8?q?docs(qa):=20mark=20the=20switched-off=20s?= =?UTF-8?q?entence's=20Setup=20=E2=80=BA=20Packaged=20automation=20remedy?= =?UTF-8?q?=20transitional=20on=20the=20two=20dispatch=20items?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd --- docs/qa/platform-checklist/areas/api-backend.json | 11 +++++++++-- docs/qa/platform-checklist/areas/automation.json | 11 ++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/qa/platform-checklist/areas/api-backend.json b/docs/qa/platform-checklist/areas/api-backend.json index 226cf6ef021..b1c14e233e2 100644 --- a/docs/qa/platform-checklist/areas/api-backend.json +++ b/docs/qa/platform-checklist/areas/api-backend.json @@ -1678,7 +1678,7 @@ "title": "A disabled packaged action is refused 409 ACTION_DISABLED at BOTH dispatch doors — after the capability gate (a 403 is never an oracle for the switch), before the param contract and the record load, durable across restart", "since": "v17", "status": "active", - "revision": 11, + "revision": 12, "priority": "P0", "surface": "api", "personas": [ @@ -1695,7 +1695,8 @@ ], "knownGaps": [ "The MCP door cannot be driven live with showcase_mark_done: the bridge's AI-exposure gate (actionAiExposureError, fail-closed on ai.exposed !== true) sits AHEAD of the activation consult, so a non-exposed action answers the exposure refusal regardless of its switch. showcase_portfolio_snapshot is the only stock ai.exposed action. The MCP transport IS drivable live on a stock boot: the boot banner prints `MCP: http://localhost:/api/v1/mcp`, and QA run #21056 drove POST /api/v1/mcp tools/call run_action against showcase_portfolio_snapshot (disabled → isError carrying code ACTION_DISABLED, status 409; re-enabled → ok). Score the MCP clause live. Where the endpoint is not mounted, record acceptance[4] blocked(environment), with the unit pin cited beside it: action-activation-dispatch.test.ts 'door 2' calls invokeBusinessAction in-process over a stubbed engine and metadata service, with no MCP transport and no HTTP, so it is evidence of that layer only, never the clause's pass (RUNNER rule 6, a pin scores only the layer it drives).", - "The record-load half of the ordering proof is scored from the DISABLED traces: code order plus the absence of any read/side effect. An enabled control on the same nonexistent recordId answers 404 RECORD_NOT_FOUND, because every door refuses a subject the caller-scope load did not deliver (refuseDeniedSubjectLoad, packages/runtime/src/action-execution.ts — the script/body door, the flow door and the declarative branch Mark Done now takes all call it; measured on a dogfood boot of #25468: enabled 404, disabled 409, same id). That 404 corroborates the order but is not the leg's evidence. The load itself is still caught (loadActionSubjectRecord), so a new-record or object-less action, which attempts no load, never 404s." + "The record-load half of the ordering proof is scored from the DISABLED traces: code order plus the absence of any read/side effect. An enabled control on the same nonexistent recordId answers 404 RECORD_NOT_FOUND, because every door refuses a subject the caller-scope load did not deliver (refuseDeniedSubjectLoad, packages/runtime/src/action-execution.ts — the script/body door, the flow door and the declarative branch Mark Done now takes all call it; measured on a dogfood boot of #25468: enabled 404, disabled 409, same id). That 404 corroborates the order but is not the leg's evidence. The load itself is still caught (loadActionSubjectRecord), so a new-record or object-less action, which attempts no load, never 404s.", + "TRANSITIONAL — the remedy half of acceptance[0] and acceptance[6] (ADR-0146 D4/D11): the ACTION_DISABLED sentence still says an administrator can turn the action on again 'under Setup › Packaged automation' (describeTurnedOffPackagedItem('action'), packages/types/src/packaged-item-turned-off.ts). That page is retired: an action's switch is now Setup › Packaged actions (nav_packaged_actions, componentRef 'automation:packaged-actions'), and the old page's bookmark opens Studio (#28785, #28818). Both doors, the 409, the code and every ordering leg are unchanged, and the clauses keep the sentence as served at dfbe34d887, so a run still scores them on that text. The remedy naming a page that no longer exists is a product defect of the sentence's producer, carried in the #28579 slice-1 report's out_of_scope_findings; when the producer names Setup › Packaged actions, the 'Setup › Packaged automation remedy' spelling in acceptance[0] and [6] is re-pointed in the same change" ] }, "steps": [ @@ -1849,6 +1850,12 @@ "date": "2026-11-01", "change": "QA run #28545 (W3-C1R) finding: acceptance[6] (clause and verify) expected ACTION_DISABLED 'naming showcase_bulk_reassign', but since #27842 the sentence names no action (action-activation.ts describeDisabled returns describeTurnedOffPackagedItem('action'), and packaged-action-disabled-dispatch-live.dogfood.test.ts expectDisabledSentence ignores its action argument). The clause now asks 409 ACTION_DISABLED with the turned-off sentence on the /actions/showcase_task/showcase_bulk_reassign route, and the target flow untouched. The status, the code and the own-switch ordering are kept; only the expectation that could never hold is replaced. Read from source at base 6b9c1ca991. Wave anchor #27377, wave-3 accuracy slice S9 (#28641); finding parked in anchor comment 6554042054", "ref": "#28545" + }, + { + "revision": 12, + "date": "2026-11-02", + "change": "Re-measured against main after N-28/N-30 (#28818, #29264), at base dfbe34d887: the REST and MCP doors, the 409 ACTION_DISABLED, the D4 → activation → params → record order and the restart leg are all unchanged, so steps and clauses stand. fixtures.knownGaps[2] (new) marks the remedy half of acceptance[0] and [6] TRANSITIONAL: the served sentence (describeTurnedOffPackagedItem('action'), @objectstack/types) still names 'Setup › Packaged automation', which ADR-0146 D4/D11 retired; the action switch is Setup › Packaged actions. No clause is weakened: the sentence is still pinned as served. The wrong place is reported as a product defect for its own card, not fixed here", + "ref": "#28579" } ] }, diff --git a/docs/qa/platform-checklist/areas/automation.json b/docs/qa/platform-checklist/areas/automation.json index a917cf3bcd9..a660f7ccb5e 100644 --- a/docs/qa/platform-checklist/areas/automation.json +++ b/docs/qa/platform-checklist/areas/automation.json @@ -1871,7 +1871,7 @@ "title": "Disabling a packaged flow writes a durable sys_metadata_activation row that survives a cold restart with the trigger left unbound; re-enable UPDATES the row and restores firing", "since": "v17", "status": "active", - "revision": 8, + "revision": 9, "priority": "P0", "surface": "api", "personas": [ @@ -1883,6 +1883,9 @@ "a FILE-backed database — restart survival is structurally unreachable on the in-memory store; record the db path in the run env (the same requirement automation.durable-suspend-restart carries, and its cold-restart recipe — stop the process entirely, boot a second server over the SAME database file — is the one to reuse here)", "showcase_urgent_task_alert (examples/app-showcase/src/automation/flows/index.ts UrgentTaskAlertFlow) — a PACKAGED record_change flow (package com.example.showcase, examples/app-showcase/objectstack.config.ts) with NO packaged subflow callers, so its disable is never refused by the §7.3 guard this item does not test", "⚠️ export OS_PORT= as well as passing -p (RUNNER.md's connector self-URL standing fact) — the showcase's self-pinging connectors otherwise fail every unrelated flow run in the boot" + ], + "knownGaps": [ + "TRANSITIONAL — acceptance[1] and negative[3] (ADR-0146 D4/D11): the FLOW_DISABLED sentence this item pins verbatim still tells the reader to turn the flow on 'under Setup › Packaged automation, or copy it there under a new name' (describeTurnedOffPackagedItem('flow'), packages/types/src/packaged-item-turned-off.ts), and that page is retired: the flow's ledger switch and its New similar are on the flow in Studio's Automations pillar, and the old page's bookmark redirects there (#28785, #28818). The item drives only the API doors (toggle, trigger, _status, the data API), so nothing else in it moved. The clause keeps the sentence exactly as served at dfbe34d887, so a run still scores it PASS on that text; the remedy's wrong place is a product defect of the sentence's producer, carried in the #28579 slice-1 report's out_of_scope_findings and not re-pointed here. When the producer names the live door, acceptance[1].verify's quoted sentence and negative[3]'s remedy phrase are re-pointed to it in the same change" ] }, "steps": [ @@ -2000,6 +2003,12 @@ "date": "2026-10-30", "change": "acceptance[1] and negative[3] respelled for #27966: the ledger-disabled FLOW_DISABLED message is now written for the caller (the flow is turned off for this deployment, an administrator can turn it on again under Setup › Packaged automation, or copy it there under a new name) and names no system table and no ADR; the engine writes the code, the flow, sys_metadata_activation and ADR-0126 §7.2 to an info log line. It is the same builder the switched-off action sentence reads from (#27842). The verdict is unchanged: the message still distinguishes the ledger dimension from the status one, and the code is still FLOW_DISABLED.", "ref": "#27966" + }, + { + "revision": 9, + "date": "2026-11-02", + "change": "Re-measured against main after N-28/N-30 (#28818, #29264), at base dfbe34d887: the item drives the API doors only (POST /automation/:name/toggle, /trigger, _status, the data API), none of which moved, so steps and clauses stand. fixtures.knownGaps[0] (new) marks acceptance[1]'s and negative[3]'s remedy phrase TRANSITIONAL: the served FLOW_DISABLED sentence (describeTurnedOffPackagedItem('flow'), @objectstack/types) still names 'Setup › Packaged automation' and a copy made 'there', a page ADR-0146 D4/D11 retired into Studio's Automations pillar. The clause keeps the sentence exactly as served, so no clause is weakened; the remedy's wrong place is reported as a product defect for its own card, not fixed here", + "ref": "#28579" } ] }, From 94c3f6fd55cefba05834a8c738a68b3cf0c6d166 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:12:40 +0000 Subject: [PATCH 04/14] docs(qa): flow-node-type-matrix makes its writable copy through Studio's New similar Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd --- docs/qa/platform-checklist/areas/automation.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/qa/platform-checklist/areas/automation.json b/docs/qa/platform-checklist/areas/automation.json index a660f7ccb5e..042a4d8ad4b 100644 --- a/docs/qa/platform-checklist/areas/automation.json +++ b/docs/qa/platform-checklist/areas/automation.json @@ -579,7 +579,7 @@ "title": "Every demonstrable flow node type authors in the designer, executes in a run, and surfaces its step in run logs", "since": "v15", "status": "active", - "revision": 12, + "revision": 13, "priority": "P1", "surface": "mixed", "personas": [ @@ -622,7 +622,7 @@ ], "steps": [ "boot the showcase on its own port and DB (dogfood §0); sign in as the dev admin", - "GET /api/v1/automation/actions (the designer palette feed) and record the descriptor list; open the Studio flow-designer palette on a WRITABLE scope and screenshot it — the stock showcase package is read-only, so its canvas offers no Add node: clone a flow first (POST /api/v1/automation/NAME/clone with both `name` and `label` in the body — a missing label is refused, field label required — or Setup › Packaged Automation, as QA run #24545 did) and open the clone under Studio's ~org scope", + "GET /api/v1/automation/actions (the designer palette feed) and record the descriptor list; open the Studio flow-designer palette on a WRITABLE package and screenshot it — the stock showcase package is read-only, so its canvas offers no Add node: make a copy into its writable customization pair first, from Studio (/_console/studio/com.example.showcase/automations, open a flow, ⋯ → New similar, a new machine name and label) or over the API (POST /api/v1/automation/NAME/clone with both `name` and `label` and no `package` — a missing label is refused, field label required). The copy lands in com.example.showcase.custom switched off (ADR-0146 D4) and opens there; it is also listed on the base's rail marked 'In com.example.showcase.custom'. Delete it at teardown", "record_change chain: POST a showcase_task WITH `assignee` set to a real user's email, under a project that has an `owner` (the project lookup is required), with status 'in_review' — task_status_flow allows done only from in_review and does not check a create — then PATCH status→done — fires showcase_task_completed (script+save_notice+notify; the PATCH answer carries its `notice`), showcase_fan_out_notify (parallel+http), showcase_resilient_sync (try_catch), showcase_task_done_notify_owner (subflow); the create also fires showcase_task_follow_up (wait) and showcase_declarative_connector_ping / showcase_mcp_connector_echo (connector_action). ⚠ The notify steps address the task's assignee and its project's owner: a task created without an assignee, or under a project without an owner, makes them fail LOUDLY for want of a recipient — a correct refusal, not a notify FAIL (QA run #23166)", "on-demand chain: POST /api/v1/automation//trigger for showcase_batch_reminders {params:{tasks:[…]}} (loop), showcase_inquiry_purge (get_record+decision+delete_record — seed at least one closed showcase_inquiry first), showcase_release_signoff {params:{items:[…]}} (map — its itemObject is showcase_task, so each item is record-shaped, {id, title})", "browser chain: run the Tasks row action showcase_bulk_reassign → showcase_reassign_wizard (screen + update_record)", @@ -752,7 +752,13 @@ "ref": "#25807" }, { "revision": 11, "date": "2026-10-31", "change": "fixtures.requires[0] names the second assignment carrier and steps[5] the seeded approval run. Fixture findings of QA run #27441 (18.3.0 sweep, wave 3, manual set, at c4c515ad; sweep anchor #27377): assignment is also exercised by showcase_one_task_signoff, and the boot already leaves a paused showcase_expense_signoff run, while PATCHing an already-submitted report starts nothing. Re-read at base 488b3ed0: examples/app-showcase/src/automation/flows/index.ts (showcase_one_task_signoff mark_ok / mark_no; showcase_expense_signoff's record-after-update start condition), examples/app-showcase/src/security/seed-approval-demo.ts (launches showcase_expense_signoff on EXP-2001). No clause changed", "ref": "#27441" }, - { "revision": 12, "date": "2026-11-01", "change": "variants gain save_notice, the FlowNodeAction member #28251 added: a record-triggered flow tells the person who made the write what the save set in motion, and the create/update answer carries it as `notice`. The showcase demonstrates it on showcase_task_completed (node tell_saver, between summarize and notify), so fixtures.requires[0] and steps[2] name it; acceptance[0] counts 20 of 22 offered variants and acceptance[1] 21 step-logging variants; enumSource.expect is 21", "ref": "#28251" } + { "revision": 12, "date": "2026-11-01", "change": "variants gain save_notice, the FlowNodeAction member #28251 added: a record-triggered flow tells the person who made the write what the save set in motion, and the create/update answer carries it as `notice`. The showcase demonstrates it on showcase_task_completed (node tell_saver, between summarize and notify), so fixtures.requires[0] and steps[2] name it; acceptance[0] counts 20 of 22 offered variants and acceptance[1] 21 step-logging variants; enumSource.expect is 21", "ref": "#28251" }, + { + "revision": 13, + "date": "2026-11-02", + "change": "steps[1] re-pointed to the Studio door (ADR-0146 D4/D11): the copy that gives the palette a writable canvas is made with Studio's New similar (or the clone door with no `package`) and lands in com.example.showcase.custom, not under Studio's ~org scope, which no longer receives a package-less clone (#28149), and Setup › Packaged Automation, the old route to it, is retired (#28785). Read at base dfbe34d887 (FlowItemMoreMenu.tsx New similar; domains/automation.ts resolveCloneTarget), with QA run #29002's measurement of where a copy lands (card note 6570476620). No clause changed", + "ref": "#28579" + } ], "enumSource": { "file": "packages/spec/src/automation/flow.zod.ts", From 4fd3557b3b70d0e1009719b8aed257b45ccaa1c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:14:02 +0000 Subject: [PATCH 05/14] docs(qa): re-point studio-authoring.packaged-automation-studio-lock to New-in-pair and the ledger switch Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd --- .../areas/studio-authoring.json | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/docs/qa/platform-checklist/areas/studio-authoring.json b/docs/qa/platform-checklist/areas/studio-authoring.json index c9a09f6db11..49f70eccaad 100644 --- a/docs/qa/platform-checklist/areas/studio-authoring.json +++ b/docs/qa/platform-checklist/areas/studio-authoring.json @@ -1278,10 +1278,10 @@ }, { "id": "studio-authoring.packaged-automation-studio-lock", - "title": "Studio Automations pillar on a read-only package: a packaged flow is NOT editable in place — New-flow and autosave honor the courtesy gate, the enable/disable switch produces no metadata write, and inspector edits are inert or announced, never silently discarded", + "title": "Studio Automations pillar on a read-only package: a packaged flow is NOT editable in place — New lands in the base's .custom pair, autosave never writes the base, the header switch is the activation-ledger switch (a toggle, no metadata write), and inspector edits are inert or announced, never silently discarded", "since": "v17", "status": "active", - "revision": 6, + "revision": 7, "priority": "P1", "surface": "browser", "personas": ["admin (opens Studio on the read-only package)"], @@ -1292,26 +1292,28 @@ "a console built from this tree with `pnpm console:build` — the pillar markup under test is the objectui code under ui/, so a stale bundle tests yesterday's gate" ], "knownGaps": [ - "FIXED at objectui 1fb1ced36243 (objectui#11124), recorded so runs don't re-derive it: on a read-only package the Automations pillar's enable/disable switch is disabled (disabled={!isEditable || !!saving || readOnly}, its read-only reason carried by aria-describedby and a tooltip since objectui#12303, not a `title`) and toggleEnabled is guarded as well, so a forced click saves nothing; the flow node inspector receives the pillar's REAL readOnly flag, threaded as the Data pillar threads it, so its inputs are disabled; New-flow and autosave still honour the gate (StudioDesignSurface.tsx). QA run #23163 measured exactly that: the switch disabled with a reason title, a forced click sending nothing, the inspector inputs disabled, the flow byte-identical. The pre-fix shape — a clickable switch only the server's 422 refused, and inspector edits that took locally and vanished on reload — is gone; meeting it again is a regression FAIL, not a standing finding", - "objectui code now pins the read-only posture: ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.automationsReadOnly-11124.test.tsx ('disables the header \"Enabled\" switch, and a click on it saves nothing'; 'opens the flow inspector read-only: the node inputs are disabled'; no canvas edit handles). It is a jsdom unit test, so the item stays browser-driven end to end; run it from this checkout to cite it beside the live read — FROM ui/, against the ui/ workspace config: `cd ui && pnpm exec vitest run --root . --maxWorkers=2 packages/app-shell/src/views/studio-design/StudioDesignSurface.automationsReadOnly-11124.test.tsx`. app-shell has no vitest config of its own (its `test` script is `vitest run --root ../.. packages/app-shell/`), so `pnpm --filter @object-ui/app-shell exec vitest run FILE` from the package misses the ui/ setup; QA run #26715 read 10/10 only the ui/ way" + "FIXED at objectui 1fb1ced36243 (objectui#11124), recorded so runs don't re-derive it: the flow node inspector on a read-only package receives the pillar's REAL readOnly flag, threaded as the Data pillar threads it, so its inputs are disabled, and autosave honours the gate (StudioDesignSurface.tsx). The pre-fix shape — inspector edits that took locally and vanished on reload — is gone; meeting it again is a regression FAIL, not a standing finding", + "CHANGED SHAPE (ADR-0146 D3/D4, #28572 and #28148; QA run #29002 at 7fa9873f, card note 6570476620): on a read-only package (1) the rail's New is OFFERED and creates the flow in the base's customization pair com.example.showcase.custom (openCreateFlow, gated on !packageReadOnly || newInPair), never in the base; (2) the header switch over a packaged flow is the activation-ledger switch (FlowLedgerSwitch, data-testid auto-ledger-switch): a click is POST /api/v1/automation/NAME/toggle, answered 200 with the definition unchanged. The disabled definition switch the 11124 pin asserts is what the pillar draws only when no _status row is served for the flow — that pin answers the _status probe 404 — so ui/packages/app-shell/src/views/studio-design/AutomationsPillar.ledgerSwitch-28148.test.tsx is the pin for the live shape", + "OUT OF SCOPE HERE — Customize (ADR-0146 D9, #28570): a packaged flow's editors stay read-only until the author presses Customize (data-testid studio-customize), which opens them onto a declared override saved in the pair. This item does not press it; every clause below is about the base staying unedited without it. The override journey is its own item", + "pins: ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.automationsReadOnly-11124.test.tsx ('opens the flow inspector read-only: the node inputs are disabled'; no canvas edit handles; and, with no _status row, 'disables the header \"Enabled\" switch, and a click on it saves nothing') + AutomationsPillar.ledgerSwitch-28148.test.tsx ('binds the switch to the ledger, not the definition, and a flip posts the toggle and saves no draft'). They are jsdom unit tests, so the item stays browser-driven end to end; run them from this checkout to cite them beside the live read — FROM ui/, against the ui/ workspace config: `cd ui && pnpm exec vitest run --root . --maxWorkers=2 packages/app-shell/src/views/studio-design/StudioDesignSurface.automationsReadOnly-11124.test.tsx packages/app-shell/src/views/studio-design/AutomationsPillar.ledgerSwitch-28148.test.tsx`. app-shell has no vitest config of its own (its `test` script is `vitest run --root ../.. packages/app-shell/`), so `pnpm --filter @object-ui/app-shell exec vitest run FILE` from the package misses the ui/ setup; QA run #26715 read 10/10 only the ui/ way" ] }, "steps": [ "boot the showcase (objectstack dev --ui --seed-admin -p -d file:/tmp//data.db), sign in admin@objectos.ai/admin123; open Studio, switch to the read-only com.example.showcase package, open the Automations pillar", "pin the precondition: GET /api/v1/packages must report com.example.showcase writable:false — the pillar's readOnly gate is pkgWritable===false (objectui StudioDesignSurface.tsx) and an UNKNOWN writability (fetch failed / still loading) deliberately leaves the pillar ungated, so a race here fakes a gate failure (hydration-race)", "baseline the server truth: GET /api/v1/meta/flow/showcase_urgent_task_alert (full body) — every later clause diffs against this read", - "affordance sweep with the network panel recording: confirm the New-flow button is ABSENT from the rail heading; select the packaged flow and wait past the autosave debounce after touching the canvas — no flow save may leave the browser", - "click the enable/disable switch in the pillar header and capture everything: whether it is disabled/absent (it is disabled, and its read-only reason rides aria-describedby and a tooltip — objectui#12303 — not a `title`), whether a forced click fires any request (it must not — were it to, the request would be a flow DRAFT save flipping status active↔obsolete, StudioDesignSurface.tsx toggleEnabled), and what the UI surfaces", - "select a flow node and edit a property in the inspector; record whether the edit visibly takes on the canvas/draft, and whether any affordance announces a read-only or will-not-persist state", + "affordance sweep with the network panel recording: record the rail's New (+) — offered on the read-only base since ADR-0146 D3 — and open its dialog, reading where it says the flow lands (the pair, com.example.showcase.custom), then cancel; select the packaged flow (do NOT press Customize) and wait past the autosave debounce after touching the canvas — no flow save naming the base may leave the browser", + "click the header switch (data-testid auto-ledger-switch, reading 'On for this deployment') and capture everything: the request it fires (POST /api/v1/automation/showcase_urgent_task_alert/toggle {\"enabled\": false}, expected 200), that no metadata write (PUT /meta/flow…, a draft save) accompanies it, and the switch's new reading; then flip it back on", + "select a flow node and edit a property in the inspector (Customize NOT pressed); record whether the edit visibly takes on the canvas/draft, and whether any affordance announces a read-only or will-not-persist state", "reload the pillar (re-open the package) and re-inspect: record whether the inspector edit survived or vanished, and whether its discard was ever announced", "forge the write the client gate withholds, both spellings: PUT /api/v1/meta/flow/showcase_urgent_task_alert?mode=draft&package=com.example.showcase and the same PUT without ?package=, each with a modified body — capture each status and code (the top-level `code` of the flat { error, code } refusal body). Then close out: GET /api/v1/meta/flow/showcase_urgent_task_alert again and diff against the baseline — byte-identical" ], "acceptance": [ { - "clause": "the courtesy gate holds on the affordances that consult it: on a read-only package the New-flow button is absent from the Automations rail and the flow autosave never fires — no draft write leaves the browser from ordinary canvas edits", + "clause": "the courtesy gate holds on the base: on a read-only package the rail's New creates into the base's customization pair, never into the base, and the flow autosave never fires against the base — no draft write naming com.example.showcase leaves the browser from ordinary canvas edits", "oracle": "network", - "verify": "the rail DOM carries no New button (gated on !readOnly, objectui StudioDesignSurface.tsx); the network trace across the edit session and past the autosave debounce carries zero flow save/PUT from the autosave path (blocked on readOnly)", - "evidence": "the rail DOM read + the flow-write-free network trace over the debounce window" + "verify": "the rail's New is present and its dialog targets com.example.showcase.custom (openCreateFlow is offered when !packageReadOnly || newInPair, StudioDesignSurface.tsx; QA run #29002 measured it present and targeting the pair); the network trace across the edit session and past the autosave debounce carries zero flow save/PUT naming the base (the autosave is blocked while editLocked = readOnly && !flowUnlocked). A New that writes into com.example.showcase, or a draft save naming it, is the FAIL", + "evidence": "the New dialog's target + the base-write-free network trace over the debounce window" }, { "clause": "a packaged flow definition is not editable in place in Studio (ADR-0126 §2 Regime C, card F2): after driving every affordance the pillar offers, the flow's server-side metadata is byte-identical — and any write that slips past client courtesy is refused server-side with a 403 — code ITEM_LOCKED when the write names the read-only package (?package=com.example.showcase: the named read-only base), NOT_OVERRIDABLE when it does not (flow declares allowEnvironmentOverlay:false) — each naming the clone or toggle remedy (ADR-0124 D1: the server is the authority, the client gate is courtesy)", @@ -1320,15 +1322,15 @@ "evidence": "the before/after meta reads, diffed + each refusal body" }, { - "clause": "the enable/disable switch honors the read-only package lock exactly as the New-flow button does: clicking it produces NO metadata write", + "clause": "the header switch over a packaged flow is the activation-ledger switch and produces NO metadata write: a click toggles the deployment's ledger row through POST /api/v1/automation/NAME/toggle and leaves the flow definition byte-identical", "oracle": "network", - "verify": "click the switch: the PASS shape is disabled/absent-or-inert (no request fires). The switch is disabled — its read-only reason on aria-describedby and a tooltip (objectui#12303), not a `title` — and toggleEnabled is guarded (objectui#11124), so even a forced click fires nothing; the expected-fail this clause once carried (a clickable switch only the server refused) is fixed. A switch that fires a request is now a FAIL of this clause; a 2xx on that write escalates to clause 2's hard FAIL", - "evidence": "the click's network capture (no request) + the disabled switch's DOM state and its accessible description" + "verify": "click auto-ledger-switch: the trace carries POST /api/v1/automation/showcase_urgent_task_alert/toggle (200) and no PUT /meta/flow or draft save; the step-8 meta read diffs byte-identical to the baseline (FlowLedgerSwitch.tsx 'never the definition's status'; StudioDesignSurface.tsx toggleLedger; QA run #29002: toggle 200, definition unchanged). Where the deployment's posture walls the caller the switch draws disabled with the console's sentence (auto-ledger-walled-reason) and nothing is sent — record which. A click that sends a draft save flipping status active↔obsolete is the pre-ADR-0146 D4 shape and FAILS this clause; a 2xx on that write escalates to clause 2's hard FAIL", + "evidence": "the click's network capture + the switch's data-reading before and after" }, { - "clause": "inspector edits are inert OR their discard is announced: the flow node inspector on a read-only package either disables its inputs or tells the author the edit will not persist — an edit that visibly takes and silently vanishes on reload is the failure", + "clause": "inspector edits are inert OR their discard is announced: the flow node inspector on a read-only package (Customize not pressed) either disables its inputs or tells the author the edit will not persist — an edit that visibly takes and silently vanishes on reload is the failure", "oracle": "dom", - "verify": "edit attempt on a node property, then reload the pillar and re-read the inspector + canvas: the edit must not silently vanish. At objectui 1fb1ced36243 the inspector receives the pillar's real readOnly flag (objectui#11124), so its inputs are DISABLED — the inert half of the clause, which is the PASS shape; the expected-fail this clause carried (inputs editable, the edit taking locally and vanishing on reload) is fixed. An input that accepts the edit is now a FAIL", + "verify": "edit attempt on a node property, then reload the pillar and re-read the inspector + canvas: the edit must not silently vanish. The inspector receives the pillar's lock (editLocked, objectui#11124; StudioDesignSurface.tsx), so its inputs are DISABLED — the inert half of the clause, which is the PASS shape. An input that accepts the edit with Customize not pressed is a FAIL", "evidence": "before-edit / after-edit / after-reload screenshots + the inspector input DOM state (disabled or not)" } ], @@ -1338,13 +1340,13 @@ ], "traps": ["stale-console-bundle", "hydration-race", "automation-input"], "source": [ - "objectui packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (readOnly = pkgWritable===false; unknown writability deliberately leaves the pillar ungated), (the prop's own contract: 'hide/disable flow-authoring affordances'), (autosave blocked on readOnly), (the enable/disable switch — disabled={!isEditable || !!saving || readOnly} and a guarded toggleEnabled since objectui#11124), (toggleEnabled = client.save flow draft flipping status active↔obsolete), (New-flow gated on !readOnly), (the inspector receives the real readOnly flag since objectui#11124, as the Data pillar's editors do), (the rail lists package-scoped flows) + StudioDesignSurface.automationsReadOnly-11124.test.tsx (the pin) — objectui owns this pillar's markup, so locator and gate fixes land there (ADR-0054)", + "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (readOnly = pkgWritable===false; unknown writability deliberately leaves the pillar ungated), (openCreateFlow: New offered on a read-only base when newInPair, landing in the pair — ADR-0146 D3, #28572), (autosave and the inspector held by editLocked = readOnly && !flowUnlocked), (toggleLedger: the ledger switch's POST /automation/NAME/toggle — ADR-0146 D4, #28148), (the definition switch, disabled on a read-only package, drawn only when no _status row is served) + FlowLedgerSwitch.tsx + StudioDesignSurface.automationsReadOnly-11124.test.tsx and AutomationsPillar.ledgerSwitch-28148.test.tsx (the pins) — the ui/ tree owns this pillar's markup, so locator and gate fixes land there (ADR-0054)", "packages/metadata-protocol/src/sys-metadata-repository.ts + packages/metadata-protocol/src/sys-metadata-repository.package-writability.test.ts (a read-only code package's write refusals — the server half of the lock; com.example.showcase is source-loaded read-only. The test pins 'a read-only base answers ITEM_LOCKED / 403 where a writable base answers NOT_OVERRIDABLE / 403')", "docs/adr/0126-packaged-metadata-customization-model.md §2/§3 (flow = Regime C: locked base, in-place edit refused loudly at the write door — card F2)", "packages/metadata-protocol/src/sys-metadata-repository.ts#readOnlyBaseOverrideError (the 403 ITEM_LOCKED a write naming a read-only base answers) + packages/metadata-protocol/src/protocol.ts (the 403 NOT_OVERRIDABLE an overlay write on a non-overlayable type answers when no package is named)", "#12438 (the ADR-0126 packaged flow/action sweep this item lands from)", - "access-security.readonly-package-locks-studio (object designer + permission matrix — cross-referenced, not widened), access-security.packaged-flow-write-door-parity (the API-side door parity for the same packaged flow), automation.setup-packaged-automation-board (the ledgered enable/disable surface — a different mechanism from this pillar's status flip)", - "cross-ref: do not re-prove or widen access-security.readonly-package-locks-studio — its steps own the object designer + permission matrix surfaces; this item owns the Automations pillar. And do not conflate this switch with the packaged-flow activation ledger: the Studio pillar switch flips the flow's deployment `status` field via a draft save (a metadata write, refused on a read-only package), while the LEDGERED enable/disable (sys_metadata_activation) lives on the Setup packaged-automation board — automation.setup-packaged-automation-board / automation.packaged-flow-disable-durable own that surface" + "access-security.readonly-package-locks-studio (object designer + permission matrix — cross-referenced, not widened), access-security.packaged-flow-write-door-parity (the API-side door parity for the same packaged flow), automation.setup-packaged-automation-board and automation.packaged-flow-disable-durable (the activation ledger this pillar's switch now writes — its UI and API contracts)", + "cross-ref: do not re-prove or widen access-security.readonly-package-locks-studio — its steps own the object designer + permission matrix surfaces; this item owns the Automations pillar. The header switch over a packaged flow writes the activation ledger (sys_metadata_activation) since ADR-0146 D4; that ledger's persistence and dispatch effect are automation.setup-packaged-automation-board's and automation.packaged-flow-disable-durable's — this item asks only that the click writes no metadata" ], "history": [ { "revision": 1, "date": "2026-08-26", "change": "new item (#12438 card F2, UI half): the Studio Automations pillar's read-only package lock — the promise (no in-place edit, switch produces no metadata write, inspector edits inert or announced) asserted with the current source shape recorded as the expected finding: the switch is clickable with only the server-side 422 behind it (disabled expression omits readOnly, StudioDesignSurface.tsx) and the inspector hardcodes readOnly:false so edits take locally and silently vanish on reload; New-flow and autosave honor the gate. Grounded in objectui f7c52e2 + the framework's writable_package_required door; owns the Automations pillar only, cross-referencing readonly-package-locks-studio for the surfaces it already owns", "ref": "#12438" }, @@ -1352,7 +1354,13 @@ { "revision": 3, "date": "2026-10-16", "change": "acceptance[1] (clause, verify), acceptance[2] (verify, evidence), steps[4] and [7], knownGaps[0] and source re-pointed (checklist-accuracy finding 5 of QA run #23942, wave #23878). Clause 1 named the server refusal as 422 [writable_package_required]; the forged flow write is refused 403 — ITEM_LOCKED when it names com.example.showcase, NOT_OVERRIDABLE when it names no package (sys-metadata-repository.package-writability.test.ts pins the pair) — so step 7 now forges both spellings and the clause scores status and code. The switch's reason is an aria-describedby plus tooltip since objectui#12303, not a `title`. Not loosened: a 2xx on any flow write is still the hard FAIL, and a 403 with any other code fails the clause", "ref": "#24036" }, { "revision": 4, "date": "2026-10-26", "change": "fixtures.requires[1] and fixtures.knownGaps[1] drop the retired objectui-pin wording (a vendored console matching .objectui-sha; a pin runnable only from an objectui checkout), which #26035 left to this file's card: the console builds from this tree with pnpm console:build and the pin resolves under ui/. Wording only; the clauses are unchanged", "ref": "#25848" }, { "revision": 5, "date": "2026-10-27", "change": "fixtures.knownGaps[1], steps[7] and acceptance[1] (clause, verify) corrected from the Automations card of QA run #26715 (at 3fcafcd3, all four clauses pass; re-read at 43731031). (1) The pin runs only from ui/ against the ui/ workspace config (`pnpm exec vitest run --root . FILE`): app-shell has no vitest config of its own, so the filtered spelling from the package misses the setup. (2) The forged write's 403 is the flat { error, code } body, so the code is read at the top level, not as error.code. Not loosened: the status must be 403 and the code ITEM_LOCKED or NOT_OVERRIDABLE", "ref": "#26715" }, - { "revision": 6, "date": "2026-11-01", "change": "negative[2] moved to source[6]: it was a scope note naming the items that own the neighbouring surfaces, which no run can decide, so it could only score skipped. Its text is unchanged except the SCOPE: label, which now reads cross-ref:, the spelling the sibling source rows use. Ruling 6545938831 item ① on #28345 (scope notes leave negative[], which keeps only decidable entries; the #27651 precedent). No clause changed. Wave anchor #27377", "ref": "#28345" } + { "revision": 6, "date": "2026-11-01", "change": "negative[2] moved to source[6]: it was a scope note naming the items that own the neighbouring surfaces, which no run can decide, so it could only score skipped. Its text is unchanged except the SCOPE: label, which now reads cross-ref:, the spelling the sibling source rows use. Ruling 6545938831 item ① on #28345 (scope notes leave negative[], which keeps only decidable entries; the #27651 precedent). No clause changed. Wave anchor #27377", "ref": "#28345" }, + { + "revision": 7, + "date": "2026-11-02", + "change": "Re-pointed to ADR-0146 D3/D4 on the read-only base. Measured input: QA run #29002 (W4-C3b1, 7fa9873f; card note 6570476620), which failed acceptance[0]'s New half and acceptance[2] as stale spec and passed acceptance[1], [3], negative[0] and [1]. Re-read at base dfbe34d887: acceptance[0] now asks that New (offered since #28572) lands in com.example.showcase.custom and that nothing writes the base; acceptance[2] asks that the header switch, now the activation-ledger switch (FlowLedgerSwitch, auto-ledger-switch, #28148), fires the toggle and no metadata write, the definition byte-identical — the property the clause always guarded; acceptance[3] says Customize is not pressed. fixtures.knownGaps record the changed shape, that the 11124 pin answers _status 404 (so AutomationsPillar.ledgerSwitch-28148.test.tsx pins the live switch), and that Customize (ADR-0146 D9) is out of this item's scope; steps[3]–[5] and source follow. No clause weakened: the lock on the base is asserted as before", + "ref": "#28579" + } ] }, { From 5639aa09de6e71ec2cc5498d8e5b4eba74ba5a5f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:15:13 +0000 Subject: [PATCH 06/14] =?UTF-8?q?docs(qa):=20access-permission-set-lifecyc?= =?UTF-8?q?le=20=E2=80=94=20New=20on=20a=20read-only=20package=20lands=20i?= =?UTF-8?q?n=20its=20.custom=20pair?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd --- .../areas/studio-authoring.json | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/qa/platform-checklist/areas/studio-authoring.json b/docs/qa/platform-checklist/areas/studio-authoring.json index 49f70eccaad..a17e3a133fe 100644 --- a/docs/qa/platform-checklist/areas/studio-authoring.json +++ b/docs/qa/platform-checklist/areas/studio-authoring.json @@ -3633,10 +3633,10 @@ }, { "id": "studio-authoring.access-permission-set-lifecycle", - "title": "Permission sets are created, found and locked honestly in Studio's Access pillar: the New dialog derives the identifier and refuses one already taken — published or draft — the rail searches and shows its empty states, and a read-only package offers no edit while the server refuses the forged write", + "title": "Permission sets are created, found and locked honestly in Studio's Access pillar: the New dialog derives the identifier and refuses one already taken — published or draft — the rail searches and shows its empty states, and on a read-only package New lands in its .custom pair while the packaged set offers no edit and the server refuses the forged write", "since": "v18", "status": "active", - "revision": 4, + "revision": 5, "priority": "P2", "surface": "mixed", "personas": [ @@ -3656,7 +3656,7 @@ "New permission set again with the identifier project_editor (a draft holds it): capture the refusal and the network", "publish; then New permission set with the identifier project_editor once more (now published): capture what happens, and GET /api/v1/meta/permission/project_editor (live) and ?state=draft", "rail search: type part of the label, part of the identifier, then a string nothing matches", - "read-only: switch to com.example.showcase → Access; read the banner and the affordances on a packaged set; then forge PUT /api/v1/meta/permission/showcase_contributor?mode=draft&package=com.example.showcase with a modified body, and a brand-new name into the same package; GET showcase_contributor before and after" + "read-only: switch to com.example.showcase → Access; read the banner and the affordances on a packaged set (do not press New similar or Customize — those journeys are their own items); then New permission set (+) on the read-only package: give it a fresh name, save, and capture the network — POST /api/v1/packages/com.example.showcase/ensure-customization (expected 201 on the first use, 200 once the pair exists) followed by the create into com.example.showcase.custom; GET /api/v1/meta/permission/NAME?state=draft&package=com.example.showcase.custom; delete that set at teardown. Then forge PUT /api/v1/meta/permission/showcase_contributor?mode=draft&package=com.example.showcase with a modified body, and a brand-new name into the same package; GET showcase_contributor before and after" ], "acceptance": [ { @@ -3678,10 +3678,10 @@ "evidence": "the three DOM reads" }, { - "clause": "on a read-only package the pillar shows the read-only banner, offers no New set and no editing control", + "clause": "on a read-only package the pillar shows the read-only banner and the packaged set offers no editing control, and New permission set creates into the base's customization pair, never into the base (ADR-0146 D3)", "oracle": "screenshot", - "verify": "the banner names the package as read-only; no + button; the set's controls are disabled or absent", - "evidence": "the read-only screenshot described" + "verify": "the banner names the package as read-only; the packaged set's controls are disabled or absent; the + is offered (openCreateSet is defined when newInPair, StudioDesignSurface.tsx) and its save calls POST /api/v1/packages/com.example.showcase/ensure-customization (packages-io.ts ensureCustomizationPackage) before the create lands in com.example.showcase.custom — corroborated by the network trace and the draft read of step 6. QA run #29002 measured ensure 201 then the set created in com.example.showcase.custom. A set created into com.example.showcase itself is the FAIL", + "evidence": "the read-only screenshot described + the New save's network trace and draft read" }, { "clause": "the server refuses the forged write — 403 for the packaged set (ITEM_LOCKED), 422 WRITABLE_PACKAGE_REQUIRED for a new name — and the packaged set is byte-identical afterwards", @@ -3698,7 +3698,7 @@ "stale-console-bundle" ], "source": [ - "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (the Access pillar: the New set dialog, the taken-identifier check findTakenIdentifier and CreateTakenNotice before the create's If-None-Match: *, discardDraft, the rail search and empty states)", + "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (the Access pillar: the New set dialog, the taken-identifier check findTakenIdentifier and CreateTakenNotice before the create's If-None-Match: *, discardDraft, the rail search and empty states, openCreateSet offered on a read-only base when newInPair — ADR-0146 D3, #28572) + ui/packages/app-shell/src/views/studio-design/packages-io.ts#ensureCustomizationPackage (the ensure door the New save calls first on a read-only base)", "ui/packages/i18n/src/namespaces/studio/en.ts (engine.studio.access.created / createTaken / createTakenElsewhere / emptyMain / search / bannerTitleReadOnly)", "access-security.readonly-package-locks-studio (the sibling read-only surfaces — cross-referenced)", "cross-ref: the object designer and matrix locks on a read-only package are access-security.readonly-package-locks-studio; the matrix editor's own UX is studio-authoring.permission-matrix-editor-ux" @@ -3727,6 +3727,12 @@ "date": "2026-11-01", "change": "negative[1] moved to source[3]: it was a scope note naming the items that own the neighbouring surfaces, which no run can decide, so it could only score skipped. Its text is unchanged except the SCOPE: label, which now reads cross-ref:, the spelling the sibling source rows use. Ruling 6545938831 item ① on #28345 (scope notes leave negative[], which keeps only decidable entries; the #27651 precedent). No clause changed. Wave anchor #27377", "ref": "#28345" + }, + { + "revision": 5, + "date": "2026-11-02", + "change": "acceptance[3] and steps[5] re-pointed to ADR-0146 D3 (#28572): the read-only showcase package now offers New permission set, which ensures the pair and creates the set in com.example.showcase.custom. Measured input: QA run #29002 (W4-C3b1, 7fa9873f; card note 6570476620) — acceptance[3] failed only on its 'no New set' half; ensure answered 201, the set landed in the pair, the packaged set's controls were all disabled, and the forged writes got 403 ITEM_LOCKED / 422 WRITABLE_PACKAGE_REQUIRED (acceptance[4] passes as written). Re-read at base dfbe34d887 (StudioDesignSurface.tsx openCreateSet, packages-io.ts ensureCustomizationPackage). The clause now asserts New lands in the pair and never in the base; the read-only banner and the disabled controls are kept. No clause weakened", + "ref": "#28579" } ] }, From 9b1c044a18e6432d9bffc4a307d2e95f36da78b6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:16:24 +0000 Subject: [PATCH 07/14] =?UTF-8?q?docs(qa):=20home-admin-cluster-links=20?= =?UTF-8?q?=E2=80=94=208=20Administration=20targets,=20legacy=20catalog=20?= =?UTF-8?q?URLs=20land=20in=20Studio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd --- .../areas/platform-core.json | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/qa/platform-checklist/areas/platform-core.json b/docs/qa/platform-checklist/areas/platform-core.json index 975ae25fb99..6f1305644fc 100644 --- a/docs/qa/platform-checklist/areas/platform-core.json +++ b/docs/qa/platform-checklist/areas/platform-core.json @@ -2770,10 +2770,10 @@ }, { "id": "platform-core.home-admin-cluster-links", - "title": "Shell-hard-coded Administration targets + the retired System hub: every Administration target URL resolves to a real page (no RouteNotFound, no RecordDetailView for an object named 'system'), /apps/:app/system lands on the settings hub in one hop and every hub entry resolves, the five legacy system/* URLs redirect (users and organizations to their object routes, roles, positions and permissions to the Setup metadata catalog), and a member's shell offers no Administration entry", + "title": "Shell-hard-coded Administration targets + the retired System hub: every Administration target URL resolves to a real page (no RouteNotFound, no RecordDetailView for an object named 'system'), /apps/:app/system lands on the settings hub in one hop and every hub entry resolves, the five legacy system/* URLs redirect (users and organizations to their object routes, roles, positions and permissions to Studio's All metadata catalog in its environment scope), and a member's shell offers no Administration entry", "since": "v16", "status": "active", - "revision": 5, + "revision": 6, "priority": "P1", "surface": "browser", "personas": [ @@ -2787,22 +2787,22 @@ ], "knownGaps": [ "the pinning suites (objectui apps/console/src/__tests__/AppContent.systemHubRoutes.test.tsx and AppContent.legacyRedirects.test.tsx) are in this checkout at ui/apps/console/src/__tests__/AppContent.systemHubRoutes.test.tsx and ui/apps/console/src/__tests__/AppContent.legacyRedirects.test.tsx: run them at the run's own framework SHA and cite their output. They mount the route table in a MemoryRouter with the page bodies stubbed (vi.mock), so they score the routing layer only — which URL resolves or redirects where (RUNNER rule 6, a pin scores only the layer it drives); that each target lands on a live, rendering page with the server's data stays the browser walk below", - "the Administration cluster has NO rendering surface at the pin: UnifiedSidebar's home navigation (base Home + Documentation, and the isWorkspaceAdmin-gated sys-administration group) is mounted only inside ConsoleLayout, which sets the app context, and /home renders HomeLayout, which deliberately omits the sidebar. So the cluster's 9 entries are walked as target URLs (the same URLs other producers emit — the zero-app empty state's System Settings button, the home Quick Action, legacy bookmarks), never as clicks; a run that hunts for the cluster in a sidebar reads its absence as a defect (#21782 VF4)" + "the Administration cluster has NO rendering surface at the pin: UnifiedSidebar's home navigation (base Home + Documentation, and the isWorkspaceAdmin-gated sys-administration group) is mounted only inside ConsoleLayout, which sets the app context, and /home renders HomeLayout, which deliberately omits the sidebar. So the cluster's 8 entries are walked as target URLs (the same URLs other producers emit — the zero-app empty state's System Settings button, the home Quick Action, legacy bookmarks), never as clicks; a run that hunts for the cluster in a sidebar reads its absence as a defect (#21782 VF4)" ] }, "steps": [ - "as admin, enumerate the Administration target list from source — UnifiedSidebar's adminItems: sys-settings → /apps/setup/system, sys-apps → …/system/apps, sys-marketplace → …/system/marketplace, sys-objects → /apps/setup/metadata/object, sys-datasources → /apps/setup/metadata/datasource, sys-users → …/system/users, sys-orgs → …/system/organizations, sys-roles → …/system/roles, sys-config → …/system/settings (9 entries; the two metadata-admin ones deliberately point at the canonical /metadata/:type routes, #3660/#3739). No sidebar renders this list at the pin (see knownGaps), so it is walked as URLs", + "as admin, enumerate the Administration target list from source — UnifiedSidebar's adminItems: sys-settings → /apps/setup/system, sys-apps → …/system/apps, sys-marketplace → …/system/marketplace, sys-datasources → /apps/setup/metadata/datasource, sys-users → …/system/users, sys-orgs → …/system/organizations, sys-roles → …/system/roles, sys-config → …/system/settings (8 entries: sys-objects left with #28818, ADR-0146 D1 — Setup carries no metadata editor or catalog; sys-datasources keeps the canonical /metadata/datasource route, #3660, and stays in Setup, the one metadata type D1 keeps there). No sidebar renders this list at the pin (see knownGaps), so it is walked as URLs", "navigate to EVERY target URL directly: each lands on a real page — screenshot first, then assert no RouteNotFound, no 'No Apps Configured' empty state, and above all no RecordDetailView rendering for an object literally named 'system' (the historical failure: short words fell through to the record-id tail — objectui#3655's two measured landings)", "open /apps/setup/system: it redirects in ONE hop to /apps/setup/system/settings (SystemLandingRedirect — the SystemHubPage card wall, its cards and count badges, was retired by objectui#3743, carried out in objectui#10507), where SettingsHub lists the settings manifests the server returns for this user; click every hub entry the same way. The ungated bare system/ai-approvals route is ai.console-ai-surface-gating's subject, not this walk's", - "legacy redirects: navigate directly to /apps/setup/system/users, …/organizations, …/roles, …/positions, …/permissions. users and organizations redirect to their object routes per the #3655 map (users→sys_user, organizations→sys_organization list). roles, positions and permissions redirect to the Setup metadata catalog in its environment scope (SystemCatalogRedirect, objectui#7611; positions and permission sets are registry items, ADR-0131 D3): roles→/apps/setup/metadata/position?scope=environment [ADR-0090 D3 rename — 'Roles' and 'Positions' are one surface], positions→the same, permissions→/apps/setup/metadata/permission?scope=environment [#3655 decision A: the permission-set surface]. Each landing page renders", + "legacy redirects: navigate directly to /apps/setup/system/users, …/organizations, …/roles, …/positions, …/permissions. users and organizations redirect to their object routes per the #3655 map (users→sys_user, organizations→sys_organization list). roles, positions and permissions redirect in ONE hop to Studio's All metadata catalog in its environment scope (SystemCatalogRedirect → setupMetadataStudioPath, ADR-0146 D1, #28818; positions and permission sets are registry items, ADR-0131 D3): roles→/studio/~all/metadata/position?scope=environment [ADR-0090 D3 rename — 'Roles' and 'Positions' are one surface], positions→the same, permissions→/studio/~all/metadata/permission?scope=environment [#3655 decision A: the permission-set surface]. Each landing page renders", "as the plain member, open /home and one app: the shell offers no Administration entry anywhere — /home has no sidebar (HomeLayout omits it) and the top-bar Help menu offers documentation only; the server-side half (Setup app refusal for the member) is platform-core.builtin-apps-nav-render clause 2/3 — cross-reference, do not re-score here" ], "acceptance": [ { - "clause": "all 9 Administration target URLs (UnifiedSidebar adminItems — a source list with no rendering surface at the pin, walked as URLs) resolve to real pages: no RouteNotFound, no dead fall-through to a RecordDetailView for 'system', no 'No Apps Configured' dead end (the #3590 fresh-env case: sys-settings targets /apps/setup/system, which resolves in BOTH the no-app and app-bearing branches)", + "clause": "all 8 Administration target URLs (UnifiedSidebar adminItems — a source list with no rendering surface at the pin, walked as URLs) resolve to real pages: no RouteNotFound, no dead fall-through to a RecordDetailView for 'system', no 'No Apps Configured' dead end (the #3590 fresh-env case: sys-settings targets /apps/setup/system, which resolves in BOTH the no-app and app-bearing branches)", "oracle": "screenshot", "verify": "per-URL screenshot-then-DOM walk; the URL list diffed against UnifiedSidebar's adminItems source so a grown/renamed entry is caught, not silently outside the walk", - "evidence": "the 9 per-URL screenshots + the entry-list diff" + "evidence": "the 8 per-URL screenshots + the entry-list diff" }, { "clause": "every Settings-hub entry reached from /apps/:app/system (which redirects in one hop to /system/settings) resolves to a live page.", @@ -2811,9 +2811,9 @@ "evidence": "the redirect trace + the per-entry verdict table" }, { - "clause": "the five legacy system/* URLs redirect and land on rendering pages — bookmarks and the URLs the retired hub once emitted keep working. users and organizations land on their object routes (sys_user, the sys_organization list — the #3655 map). roles and positions land on the Setup metadata catalog at /apps/setup/metadata/position?scope=environment (the ADR-0090 rename, one surface under two vocabularies), and permissions at /apps/setup/metadata/permission?scope=environment (decision A: rules-and-assignments is layer 2) — the catalog, not the sys_position / sys_permission_set object routes, since objectui#7611 (ADR-0131 D3: positions and permission sets are registry items)", + "clause": "the five legacy system/* URLs redirect and land on rendering pages — bookmarks and the URLs the retired hub once emitted keep working. users and organizations land on their object routes (sys_user, the sys_organization list — the #3655 map). roles and positions land in ONE hop on Studio's All metadata catalog at /studio/~all/metadata/position?scope=environment (the ADR-0090 rename, one surface under two vocabularies), and permissions at /studio/~all/metadata/permission?scope=environment (decision A: rules-and-assignments is layer 2) — Studio's catalog since #28818 (ADR-0146 D1: Setup carries no metadata catalog), never the sys_position / sys_permission_set object routes (ADR-0131 D3: positions and permission sets are registry items)", "oracle": "network", - "verify": "the navigation trace per legacy URL shows the redirect target: the object route for users and organizations, the …/metadata/TYPE?scope=environment catalog route for roles, positions and permissions; the landing renders. A roles, positions or permissions URL landing on the sys_position / sys_permission_set object route is the pre-objectui#7611 shape, so suspect the stale-console-bundle trap before scoring it", + "verify": "the navigation trace per legacy URL shows the redirect target: the object route for users and organizations, /studio/~all/metadata/TYPE?scope=environment for roles, positions and permissions, reached without a stop on any /apps/setup/metadata/… URL; the landing renders. QA run #28981 measured exactly these landings at 7fa9873f. A roles, positions or permissions URL landing on the Setup catalog (/apps/setup/metadata/TYPE) or on the sys_position / sys_permission_set object route is a pre-#28818 shape, so suspect the stale-console-bundle trap before scoring it", "evidence": "the five redirect traces + landings" }, { @@ -2830,9 +2830,9 @@ ], "traps": ["wrong-persona", "hydration-race", "stale-console-bundle"], "source": [ - "objectui packages/app-shell/src/layout/UnifiedSidebar.tsx (homeNavigation: base Home + Documentation, and the isWorkspaceAdmin-gated adminItems cluster with the #3590 /system targeting note and the #3660/#3739 canonical-metadata-route notes — the target list this item walks; mounted only by ConsoleLayout, which sets the app context) + packages/app-shell/src/console/home/HomeLayout.tsx (/home deliberately omits the sidebar)", - "objectui apps/console/src/AppContent.tsx (SystemLandingRedirect: the bare …/system landing forwards to …/system/settings; SystemHubPage — its cards and count badges — retired by objectui#3743, carried out in objectui#10507) + apps/console/src/pages/settings/SettingsHub.tsx (the registry-driven hub: the settings manifests the server returns for this user)", - "objectui apps/console/src/AppContent.tsx (SystemObjectRedirect — users and organizations, the #3655 object map and the measured pre-fix landings; SystemCatalogRedirect — roles, positions and permissions onto the Setup catalog in its environment scope since objectui#7611, with the ADR-0090 roles→position note and decision A's permissions→permission note) + packages/app-shell/src/views/metadata-admin/catalog-scope.ts (ENVIRONMENT_SCOPE_QUERY, scope=environment) + the exported systemRoutes fragment", + "ui/packages/app-shell/src/layout/UnifiedSidebar.tsx (homeNavigation: base Home + Documentation, and the isWorkspaceAdmin-gated adminItems cluster — 8 entries since sys-objects left with #28818 — with the #3590 /system targeting note and the #3660 canonical datasource-route note; the target list this item walks; mounted only by ConsoleLayout, which sets the app context) + ui/packages/app-shell/src/console/home/HomeLayout.tsx (/home deliberately omits the sidebar)", + "ui/apps/console/src/AppContent.tsx (SystemLandingRedirect: the bare …/system landing forwards to …/system/settings; SystemHubPage — its cards and count badges — retired by objectui#3743, carried out in objectui#10507) + ui/apps/console/src/pages/settings/SettingsHub.tsx (the registry-driven hub: the settings manifests the server returns for this user)", + "ui/apps/console/src/AppContent.tsx (SystemObjectRedirect — users and organizations, the #3655 object map and the measured pre-fix landings; SystemCatalogRedirect — roles, positions and permissions onto Studio's All metadata catalog in its environment scope through setupMetadataStudioPath since #28818, after objectui#7611 had moved them onto the Setup catalog; the ADR-0090 roles→position note and decision A's permissions→permission note) + ui/packages/app-shell/src/views/studio-design/setupMetadataRedirect.ts#setupMetadataStudioPath + ui/packages/app-shell/src/views/metadata-admin/catalog-scope.ts (ENVIRONMENT_SCOPE_QUERY, scope=environment) + the exported systemRoutes fragment", "objectui apps/console/src/__tests__/AppContent.systemHubRoutes.test.tsx + AppContent.legacyRedirects.test.tsx (the objectui-side pins — see knownGaps)", "platform-core.builtin-apps-nav-render (owns the server-side Setup refusal for the member — this item owns the shell-hard-coded home cluster, which that item does not touch)" ], @@ -2866,6 +2866,12 @@ "date": "2026-10-26", "change": "fixtures.knownGaps[0] re-pointed off the retired objectui-only pin fact — the seat's REVISE on #26119 (the same class as boot-health and url-overlay-contract): it said the two pinning suites live entirely in the objectui repo, are not runnable from this checkout, and cited the standing 23-item fact, which #26109 retires. Both are on origin/main under ui/apps/console/src/__tests__/; they run here and score the routing layer they drive, page bodies stubbed. No clause changed; the 'objectui …' provenance labels in source stay. Re-read at 1bbb7bf4", "ref": "#25845" + }, + { + "revision": 6, + "date": "2026-11-02", + "change": "Re-pointed to main after N-28 (#28818, ADR-0146 D1). Measured input: QA run #28981 (18.3.0 wave 4, 7fa9873f; card note 6571282495), which failed acceptance[2] as stale spec: /apps/setup/system/roles, /positions and /permissions land in one hop on /_console/studio/~all/metadata/position?scope=environment and …/permission?scope=environment, and the routing pins (AppContent.systemHubRoutes, legacyRedirects) assert the Studio landing. Re-read at base dfbe34d887: acceptance[2] and steps[3] name the Studio landing; the Administration count is read from source, per the seat's ruling — UnifiedSidebar adminItems has 8 entries (sys-objects removed by #28818), so steps[0], acceptance[0] and fixtures.knownGaps[1] say 8, and the title names Studio's catalog; source[0]–[2] re-point to ui/ paths and SystemCatalogRedirect's Studio target. No clause weakened", + "ref": "#28579" } ] }, From 9301386a28c196313bc880d90f89dae84d95065e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:17:04 +0000 Subject: [PATCH 08/14] docs(qa): write-feedback drives Studio discard on the All metadata item page Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd --- docs/qa/platform-checklist/areas/ux-conventions.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/qa/platform-checklist/areas/ux-conventions.json b/docs/qa/platform-checklist/areas/ux-conventions.json index ad5e39955ec..22d07ab3219 100644 --- a/docs/qa/platform-checklist/areas/ux-conventions.json +++ b/docs/qa/platform-checklist/areas/ux-conventions.json @@ -9,7 +9,7 @@ "title": "Every user-initiated write answers visibly — success names what changed, a refusal says why, and a no-op claims nothing", "since": "v17.7", "status": "active", - "revision": 9, + "revision": 10, "priority": "P1", "surface": "browser", "personas": [ @@ -41,7 +41,7 @@ "import — Tasks → Import a two-row CSV → the result screen", "Studio autosave — rename a field label in the scratch package", "Studio publish — Publish the scratch package", - "Studio discard — on the metadata designer (/_console/apps/setup/metadata/object/OBJ for a scratch-package object with a pending draft), click 'Discard draft' and accept the in-app confirm dialog (it is no longer the browser's native confirm); the Studio pillars offer no discard. A scratch object's name carries its package's namespace prefix, so read it off the package before building the URL", + "Studio discard — on the All metadata area's item page, /_console/studio/~all/metadata/object/OBJ (where Setup's /_console/apps/setup/metadata/object/OBJ now redirects, ADR-0146 D1, #28818), for a scratch-package object with a pending draft, click 'Discard draft' and accept the in-app confirm dialog (it is no longer the browser's native confirm). Open it on the ~all leg: the per-package /_console/studio/PKG/metadata/object/OBJ opens the object's Data pillar instead (areaItemPillarPath, metadataArea.ts), which offers no discard. A scratch object's name carries its package's namespace prefix, so read it off the package before building the URL", "settings save — Settings → Localization → save a value" ], "steps": [ @@ -159,6 +159,12 @@ "date": "2026-11-01", "change": "acceptance[0].verify and acceptance[1].verify say the Studio top bar's save status ('Saved' / 「已保存」) is not write feedback: it names no item, so a Studio row passes only on a toast or inline text naming the item or package, and a row with only that status scores FAIL. This is ruling 6545938831 item ② on #28345, which keeps the convention's wording (success names its subject) and settles how a runner scores that indicator, in both clauses; neither clause's wording changed. Held from slice S5 (revision 8). Wave anchor #27377", "ref": "#28345" + }, + { + "revision": 10, + "date": "2026-11-02", + "change": "variants[10] re-pointed to the Studio door: the Setup designer URL it named, /_console/apps/setup/metadata/object/OBJ, now redirects to /_console/studio/~all/metadata/object/OBJ (ADR-0146 D1, #28818), the All metadata area's item page, where 'Discard draft' lives (ResourceEditPage.tsx), so the row is driven there directly; the per-package /_console/studio/PKG/metadata/object/OBJ opens the Data pillar, which has no discard (areaItemPillarPath, metadataArea.ts). Measured input: QA run #28981 (18.3.0 wave 4, 7fa9873f; card note 6571282495). Re-read at base dfbe34d887. The 'Studio pillars offer no discard' half is narrowed to the Data pillar, since the Access pillar offers one (access-discard-draft). No clause changed", + "ref": "#28579" } ] }, From 8ecf948316dd8d9c7d9f016700a2905af5e1a467 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:17:49 +0000 Subject: [PATCH 09/14] =?UTF-8?q?docs(qa):=20object-designer-roundtrip=20?= =?UTF-8?q?=E2=80=94=20the=20live=20spec's=20F2=E2=80=93F4=20now=20open=20?= =?UTF-8?q?Studio's=20All=20metadata=20designer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd --- .../areas/studio-authoring.json | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/qa/platform-checklist/areas/studio-authoring.json b/docs/qa/platform-checklist/areas/studio-authoring.json index a17e3a133fe..ed8c319235e 100644 --- a/docs/qa/platform-checklist/areas/studio-authoring.json +++ b/docs/qa/platform-checklist/areas/studio-authoring.json @@ -102,7 +102,7 @@ "title": "Object designer round-trip: field add/edit/reorder persists to metadata, identifiers derive and survive typing", "since": "v16", "status": "active", - "revision": 11, + "revision": 12, "priority": "P1", "surface": "mixed", "personas": ["admin"], @@ -135,13 +135,13 @@ { "clause": "a new field's API name derives from its label on blur, so the data column is the derived name — never a frozen field_N (the contract behind audit finding 2, where the saved record stored 'field_2': 'in_progress' forever)", "oracle": "api", - "verify": "the field's API-name input shows asset_code after the label blur (corroborated at the Setup designer door by objectui F3, which drives that door, not Studio), and the stored record's column key is asset_code", + "verify": "the field's API-name input shows asset_code after the label blur (corroborated by F3 at the All metadata area's designer door, /studio/PKG/metadata/object/new — a sibling door inside Studio, not the Data pillar), and the stored record's column key is asset_code", "evidence": "designer screenshot + the record read" }, { "clause": "typed identifiers survive per-keystroke sanitisation — repair_asset keeps its underscore when typed char-by-char, and the stored name is NS_repair_asset", "oracle": "dom", - "verify": "after a screenshot confirms the dialog rendered, the identifier input's value equals the typed repair_asset, character for character, and the dialog's stored-name preview reads NS_repair_asset; the meta read's name is NS_repair_asset (pre-fix repair_asset yielded 'repairasset'; objectui F2 corroborates the typing half at the Setup designer door)", + "verify": "after a screenshot confirms the dialog rendered, the identifier input's value equals the typed repair_asset, character for character, and the dialog's stored-name preview reads NS_repair_asset; the meta read's name is NS_repair_asset (pre-fix repair_asset yielded 'repairasset'; F2 corroborates the typing half at the All metadata area's designer door)", "evidence": "screenshot + input value read" }, { @@ -155,9 +155,12 @@ "an API name frozen at field_N after its label was set pre-save is audit finding 2 regressed — FAIL, but confirm against a fresh objectui build first: the vendored /_console bundle may predate the derive-on-blur fix the e2e now pins" ], "traps": ["stale-console-bundle", "automation-input"], - "automated": { "kind": "e2e", "ref": "ui/e2e/live/studio-object-designer.spec.ts — ⚠ F2, F3 and F4 drive the Setup designer route (/apps/showcase_app/metadata/object/new), not the Studio Data pillar this item's steps drive since revision 9, so each is evidence at a sibling door onto the same save. F3 pins acceptance[1]'s designer half (the API-name input derives asset_code from the label on blur) and F2 pins acceptance[2] (a name typed character by character keeps its underscore). F4 pins acceptance[3]'s banner half ONLY: it gives the picklist one real option, not three, adds an empty row and finds no metadata-validation banner after the debounce, but it never publishes or reads the stored options, so 'the PUBLISHED picklist carries exactly the 3 authored options' is driven by hand. F1 (a package created from the Studio surface's switcher appears in it without a reload) asserts no clause of this item. No pin covers acceptance[0] or acceptance[1]'s stored-column half" }, + "automated": { + "kind": "e2e", + "ref": "ui/e2e/live/studio-object-designer.spec.ts — F2, F3 and F4 drive Studio's All metadata area designer on a package the test authors (/studio/PKG/metadata/object/new?package=PKG, the metadata-admin object editor; the Setup designer route they drove before redirects there since #28818, ADR-0146 D1), not the Data pillar's New object dialog this item's steps drive since revision 9, so each is evidence at a sibling door onto the same save. F3 pins acceptance[1]'s designer half (the API-name input derives asset_code from the label on blur) and F2 pins acceptance[2]'s typing half (a name typed character by character keeps its underscore). F4 corroborates acceptance[3] under a different fixture: it gives the picklist ONE real option, adds an empty row, finds no metadata-validation banner after the debounce, then saves a DRAFT and reads back exactly the one option — so the banner half and 'the empty row was dropped' are pinned at that door, while 'the PUBLISHED picklist carries exactly the 3 authored options' is still driven by hand. F1 (a package created from the Studio surface's switcher appears in it without a reload) asserts no clause of this item. No pin covers acceptance[0] or acceptance[1]'s stored-column half" + }, "source": [ - "objectui: e2e/live/studio-object-designer.spec.ts (F1–F4, the objectui#1926 regression classes: per-keystroke sanitisation, label→api-name derivation, empty picklist row, switcher refresh)", + "ui/e2e/live/studio-object-designer.spec.ts (F1–F4, the objectui#1926 regression classes: per-keystroke sanitisation, label→api-name derivation, empty picklist row, switcher refresh; F2–F4 open the All metadata area's designer since #28818)", "docs/audits/2026-07-studio-package-create-ux-dogfood.md (finding 2 — field API name did not follow the label at audit time)", "docs/audits/2026-07-studio-package-create-ux-dogfood.md (finding 1 — read-only package rejects designer saves server-side, which is why a writable package is a fixture requirement)" ], @@ -212,7 +215,13 @@ "ref": "#26715" }, { "revision": 10, "date": "2026-10-31", "change": "steps[1] names the New object dialog's confirm button: 'Save as draft', not Create, in both places the step named it. Every Studio create dialog takes its submit label from engine.studio.createDraft (ui/packages/i18n/src/namespaces/studio/en.ts; ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx, the engine.studio.data.newObject dialog). Accuracy finding of QA run #27438. No clause changed. Wave anchor #27377, wave-3 accuracy slice S2; read at base a0f865491d", "ref": "#27438" }, - { "revision": 11, "date": "2026-11-02", "change": "fixtures.knownGaps[0] names the invocation the ui vitest guard accepts, the form revision 8 of studio-authoring.expression-editors landed in its knownGaps[1] (#28701): a jsdom unit pin runs from ui/ as cd ui && pnpm exec vitest run --maxWorkers=1 packages/PKG/src/FILE. The pnpm --filter @object-ui/PKG exec form it quoted runs in the package directory, which the guard refuses (ui/scripts/vitest-invocation-guard.mjs, verdict package-cwd), so a runner following it is refused at config load, before any test runs. Wording only; no clause changed. Checklist-accuracy finding of QA run #28656 (W3-RT), carried by #28750", "ref": "#28656" } + { "revision": 11, "date": "2026-11-02", "change": "fixtures.knownGaps[0] names the invocation the ui vitest guard accepts, the form revision 8 of studio-authoring.expression-editors landed in its knownGaps[1] (#28701): a jsdom unit pin runs from ui/ as cd ui && pnpm exec vitest run --maxWorkers=1 packages/PKG/src/FILE. The pnpm --filter @object-ui/PKG exec form it quoted runs in the package directory, which the guard refuses (ui/scripts/vitest-invocation-guard.mjs, verdict package-cwd), so a runner following it is refused at config load, before any test runs. Wording only; no clause changed. Checklist-accuracy finding of QA run #28656 (W3-RT), carried by #28750", "ref": "#28656" }, + { + "revision": 12, + "date": "2026-11-02", + "change": "automated.ref, source[0] and acceptance[1]/[2] verify re-pointed: the live spec's F2–F4 no longer drive the Setup designer route; since #28818 (ADR-0146 D1) they open Studio's All metadata area designer on a package the test authors, /studio/PKG/metadata/object/new?package=PKG, and F4 now saves a draft and reads back the one stored option. Re-read at base dfbe34d887 (ui/e2e/live/studio-object-designer.spec.ts newObjectIn, F4's draft read). The pins stay corroboration at a sibling door (the area's metadata-admin editor, not the Data pillar's New object dialog the steps drive), and F4 still seeds one option where acceptance[3] judges three and reads a draft where the clause reads the published picklist, so no clause's scoring basis changed", + "ref": "#28579" + } ] }, { From 0e3e0da54d1d423781a2376662c06efdc13fe27e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:19:03 +0000 Subject: [PATCH 10/14] docs(qa): authoring-validation-not-persisted drives the Source tab on Studio's All metadata page Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd --- .../platform-checklist/areas/studio-authoring.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/qa/platform-checklist/areas/studio-authoring.json b/docs/qa/platform-checklist/areas/studio-authoring.json index ed8c319235e..337bcaf56f6 100644 --- a/docs/qa/platform-checklist/areas/studio-authoring.json +++ b/docs/qa/platform-checklist/areas/studio-authoring.json @@ -557,7 +557,7 @@ "title": "An invalid authored shape is rejected at save with a LOCATED error and is not persisted", "since": "v16", "status": "active", - "revision": 5, + "revision": 6, "priority": "P1", "surface": "mixed", "personas": ["admin"], @@ -575,7 +575,7 @@ "attempt PUT /api/v1/meta/object/qa_invalid_probe?mode=draft with a field MISSING its type; capture the status and full error body", "attempt PUT /api/v1/meta/view/qa_invalid_views?mode=draft with `type`/`columns` at the CONTAINER level (the flat-list-view mistake) BESIDE a container key (e.g. `list: {…}`), so the body is container-shaped and the container's guidance answers; capture the guidance error. Bind the body's `object` to an object that holds NO stored view container (a fresh scratch object): against one that already has a container, the container name-collision check answers 400 VALIDATION_ERROR naming the other owner first and masks the guidance (protocol.ts viewContainerNameCollisionRefusal; QA run #26715 met it). A body of `type`/`columns` alone is judged as an inline view config instead and refused as naming no `object`/`viewKind` — the same 'Wrap it: defineView({ list: … })' remedy, different first sentence (packages/spec/src/ui/view.zod.ts INLINE_VIEW_OBJECT_REQUIRED / INLINE_VIEW_KIND_REQUIRED)", "after each rejection, GET the name back and record what the store holds", - "drive one invalid shape through the browser door that reaches the server gate — the Setup metadata editor's Source tab (/_console/apps/setup/metadata/object/NAME), whose autosave PUTs it — and capture the surfaced error. The Studio designer holds an incomplete shape client-side and never sends it ('Not saved yet: …'), so it does not exercise the 422 — it must locate the offending field, not say a generic 'save failed'", + "drive one invalid shape through the browser door that reaches the server gate — the metadata editor's Source tab on Studio's All metadata item page (/_console/studio/~all/metadata/object/NAME; the Setup URL it was reached by, /_console/apps/setup/metadata/object/NAME, redirects there since #28818, ADR-0146 D1), whose autosave PUTs it — and capture the surfaced error. The Data pillar's object designer holds an incomplete shape client-side and never sends it ('Not saved yet: …'), so it does not exercise the 422 — it must locate the offending field, not say a generic 'save failed'", "author the CORRECTED shapes (field with a type; views wrapped under list:) and confirm both save 200 — the gate rejects the shape, not the name", "dashboard-kind stray keys are deep-covered by dashboards.strict-widget-rejects-stray-keys — cite a pass there rather than re-enumerating the 11 legacy keys here" ], @@ -628,7 +628,13 @@ { "revision": 2, "date": "2026-08-11", "change": "recorded run #7695's sighting of the ALREADY-KNOWN spurious '_diagnostics' banner on a freshly saved VALID draft (the designer re-validates the server's own annotation on read-back) as a knownGap + a NOT-this-item's-FAIL negative, so the sighting is not filed as a new defect and the false positive is told apart from a real located-error miss. Note only; no clause changed", "ref": "#7753" }, { "revision": 3, "date": "2026-09-23", "change": "re-spelled the located-error shape, which had stopped being true: the clause quoted '[invalid_metadata] … fields..type: Required', but saveMetaItem's spec-validation refusal no longer opens with a bracketed restatement of its code (retired in 7a25a3ee9c; the token rides code INVALID_METADATA), and on the REST and dispatcher doors its message is a headline of 'path [zod code]' locators with the per-issue prose on issues[]. Measured on c11852406 for a field missing its type: 'object/qa_invalid_probe failed spec validation: 1 issue — fields.amount.type [invalid_value]', 422, INVALID_METADATA. The dispatcher source pointer presented that test's hand-built mock message as the producer's text; it now says what the test pins, and the producer's per-face renderer is cited beside it. The capability itself (4xx, located path, nothing persisted) is unchanged", "ref": "#19709" }, { "revision": 4, "date": "2026-10-19", "change": "steps[1], steps[3], acceptance[1] (clause, verify) and acceptance[3].verify corrected from QA run #24571 (18.0 sweep, 5b14b7f3; carried by #24604), re-read at objectstack f89d29e382 and the console pin 4016370af3c9. (1) The quoted container guidance answers only a container-shaped body; a flat `type`/`columns` body is judged as an inline view config and refused as naming no `object`/`viewKind`, with the same Wrap remedy (view.zod.ts). (2) The Studio designer holds an incomplete shape client-side and never PUTs it, so the browser door that reaches the server gate is the Setup metadata editor's Source tab, which the run used. Wave anchor #24579 §3", "ref": "#24671" }, - { "revision": 5, "date": "2026-10-27", "change": "steps[1] and fixtures.requires[1] name the fixture the wrong-layer view probe needs (checklist-accuracy finding of the Data card of QA run #26715, at 3fcafcd3; re-read at 43731031): bound to an object that already has a stored view container, the container-shaped body is refused 400 VALIDATION_ERROR by the name-collision check before the ViewSchema guidance can answer, so the probe needs an object with no container. No clause changed", "ref": "#26715" } + { "revision": 5, "date": "2026-10-27", "change": "steps[1] and fixtures.requires[1] name the fixture the wrong-layer view probe needs (checklist-accuracy finding of the Data card of QA run #26715, at 3fcafcd3; re-read at 43731031): bound to an object that already has a stored view container, the container-shaped body is refused 400 VALIDATION_ERROR by the name-collision check before the ViewSchema guidance can answer, so the probe needs an object with no container. No clause changed", "ref": "#26715" }, + { + "revision": 6, + "date": "2026-11-02", + "change": "steps[3] re-pointed to the Studio door: the metadata editor whose Source tab autosave reaches the server gate is now served by Studio's All metadata area (/_console/studio/~all/metadata/object/NAME, the same metadata-admin ResourceEditPage), and the Setup URL the step named redirects there (setupMetadataStudioPath, #28818, ADR-0146 D1). On the ~all leg the item page opens the generic editor, not the Data pillar (areaItemPillarPath answers null without a package), so the Source tab is still there. 'The Studio designer' that holds an incomplete shape client-side is named as the Data pillar's, since both doors are now Studio's. Read at base dfbe34d887; not yet driven live at this URL — the next run records it. No clause changed", + "ref": "#28579" + } ] }, { From e37bf7fbceb95ef1f3ca1ea50288b7a7c55d65bb Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:21:46 +0000 Subject: [PATCH 11/14] docs(qa): mark the package-less overlay items transitional after the ADR-0146 D11 closure Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd --- .../areas/studio-authoring.json | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/qa/platform-checklist/areas/studio-authoring.json b/docs/qa/platform-checklist/areas/studio-authoring.json index 337bcaf56f6..4b40d3f3b03 100644 --- a/docs/qa/platform-checklist/areas/studio-authoring.json +++ b/docs/qa/platform-checklist/areas/studio-authoring.json @@ -642,7 +642,7 @@ "title": "The metadata type registry gates runtime writes: allowEnvironmentOverlay=false kinds refuse overlay (403 NOT_OVERRIDABLE), allowRuntimeCreate=false kinds refuse creation (403 NOT_CREATABLE) — both sides", "since": "v16", "status": "active", - "revision": 7, + "revision": 8, "priority": "P1", "surface": "api", "personas": ["admin"], @@ -652,6 +652,7 @@ "the stock showcase artifact (artifact-backed objects like showcase_task and packaged views are the locked targets; a scratch name serves the allowed-create side)" ], "knownGaps": [ + "TRANSITIONAL — acceptance[1], acceptance[4], steps[1] and [5], variants[3] and [4] (ADR-0146 D2/D11; read at base dfbe34d887): the 'overlay open' side of this gate is no longer a package-less PUT. A package-less write onto a view or dashboard a read-only code package ships now answers 403 NOT_OVERRIDABLE naming the base's .custom pair (#28850, packages/metadata-protocol/src/protocol-packaged-base-refusal.ts#packagelessOverlayWriteRefusal), so steps[1]'s PUT of a packaged showcase view is refused and steps[5] has no row to reset. The open side is now a declared override saved in com.example.showcase.custom (overrides: { package, basedOn }, packages/spec/src/kernel/override-declaration.zod.ts#OverrideDeclarationSchema), removed by deleting the pair's row — the ensure door and the closed package-less write are new items of #28579 slice 2 (the card's Wanted 2, N-13/N-16b/N-17). Until slice 2 re-points them, record acceptance[1] and [4] as blocked(dependency) citing #28579 and a 403 NOT_OVERRIDABLE on steps[1] as the expected answer, never as a FAIL. acceptance[0], [2] and [3] are unchanged and run as written", "CORRECTION TO RUN #7637 (recorded by run #7695): the Studio read-only badge is HONEST on this build/pin — it is not asserting a lock the server declines to apply. With OS_METADATA_WRITABLE=permission set, GET /meta reports permission { allowEnvironmentOverlay: true, overrideSource: 'env' } (the enum is registry|env — packages/spec/src/api/protocol.zod.ts) and the editor becomes fully writable; the badge tracks the real writable computation, proven BOTH ways in that run. #7637's contrary observation does not reproduce here — do not act on it as written, and do not re-file it", "ROUTE NOTE (run #7695 / #7690): a flow authored over the metadata API is NOT editable in Studio — it opens behind 'This flow is provided by an installed package, so it is read-only at runtime', with or without `?package=`. objectui's ResourceEditPage treats an item as artifact-backed when `layered.code != null && _packageId !== 'sys_metadata'` (packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx / 1311-1323) and `flow` declares allowEnvironmentOverlay:false, so for THIS kind the banner is arguably telling the truth: the server would refuse the overlay write anyway. The editable path is the console's own create page. Recorded as a route note, NOT a defect — the polarity-reversed case (the same heuristic locking an object published into a WRITABLE package, which Studio and the server both treat as editable) IS the defect and is filed at objectui#4308; whoever works that card should confirm this case stays correct" ] @@ -724,7 +725,13 @@ { "revision": 4, "date": "2026-10-20", "change": "line citations re-pointed — the comma-range spelling that round 7 of wave anchor #24579 named (18.0 sweep; round 7's report 6281433885, out_of_scope_findings[1]). source[5] cited lines 1311-1323 of objectui ResourceEditPage.tsx in the comma-range spelling, written with a space (authored in 3bb9340974 as the list 951-955 and 1311-1323). At the objectui pin that commit shipped, 6314e87f2d49, 1311-1323 was the isArtifactItem test; at the shipped pin 12dc7b47c340 it has moved and also excludes provenance 'org'. source[5] now names isArtifactItem. objectui is read at the shipped pin 12dc7b47c340 and cited in prose, because no objectui anchor resolves in this gate. The spelling is what #13786's strip left when it removed only the first range of a ':A,B' list, and the line-citation limb reads only the colon spellings, so no gate caught it. Where the surrounding words still describe the range the strip removed, the new citation names that code too. No clause's assertion changed", "ref": "#24752" }, { "revision": 5, "date": "2026-10-26", "change": "automated.ref says what its only pin proves (checklist-accuracy finding of QA run #25815, release-18.2.0-tier1 at 6679644a, objectui pin b41ed7f68956; wave anchor #25807): overlay-precedence.test.ts is a unit test of the overlay whitelist that stops above the HTTP door, so it corroborates acceptance[0], [2], [3] and negative[0] and scores none, and no pin touches acceptance[4]'s DELETE reset. The clauses are unchanged", "ref": "#25848" }, { "revision": 6, "date": "2026-10-27", "change": "title, acceptance[0] (clause, verify), acceptance[2] (clause, verify) and variants[2] spell the served codes NOT_OVERRIDABLE and NOT_CREATABLE (checklist-accuracy note of the coordinator leg of QA run #26715, at 3fcafcd3, where all five clauses passed): the item spelled them lowercase, after the registry's doc comment, while the door serves SCREAMING_SNAKE codes per ADR-0112. The source rows keep the lowercase where they quote source text. No clause is loosened", "ref": "#26715" }, - { "revision": 7, "date": "2026-10-31", "change": "steps[4]'s brand-new object declares sharingModel. QA run #27438's first PUT of qa_org_probe without one answered 422 security-owd-unset, the error-tier authoring rule in packages/lint/src/validate-security-posture.ts, before the allowRuntimeCreate gate could be read; with sharingModel private it saved 200, and acceptance[3] passed. Accuracy finding of QA run #27438. No clause changed. Wave anchor #27377, wave-3 accuracy slice S2; read at base a0f865491d", "ref": "#27438" } + { "revision": 7, "date": "2026-10-31", "change": "steps[4]'s brand-new object declares sharingModel. QA run #27438's first PUT of qa_org_probe without one answered 422 security-owd-unset, the error-tier authoring rule in packages/lint/src/validate-security-posture.ts, before the allowRuntimeCreate gate could be read; with sharingModel private it saved 200, and acceptance[3] passed. Accuracy finding of QA run #27438. No clause changed. Wave anchor #27377, wave-3 accuracy slice S2; read at base a0f865491d", "ref": "#27438" }, + { + "revision": 8, + "date": "2026-11-02", + "change": "fixtures.knownGaps[0] (new) marks the positive overlay half TRANSITIONAL — acceptance[1] and [4], steps[1] and [5], variants[3] and [4]. Re-measured from source at base dfbe34d887: since #28850 (ADR-0146 D11) a package-less write onto a view or dashboard a read-only code package ships answers 403 NOT_OVERRIDABLE naming the .custom pair (packagelessOverlayWriteRefusal), so the packaged showcase view this item overlays is refused and there is no overlay row to reset; the open side is a declared override in com.example.showcase.custom (ADR-0146 D2), which slice 2 of #28579 adds as new items. Those two clauses are scored blocked(dependency) until then — a measured narrowing, recorded here, because as written they would FAIL by design. acceptance[0], [2] and [3] (NOT_OVERRIDABLE on the artifact-backed object, NOT_CREATABLE, the independent create gate) are unchanged", + "ref": "#28579" + } ] }, { @@ -1383,7 +1390,7 @@ "title": "Packaged views and dashboards are 'yours to edit directly' (ADR-0126 Regime O): the designer live-edits the PACKAGED artifact, publish reaches the end user, reset-overlay returns the shipped base — while a Regime-C flow in the same surface stays locked", "since": "v17", "status": "active", - "revision": 5, + "revision": 6, "priority": "P2", "surface": "browser", "personas": ["admin (authors the overlay in the designer)", "end user (renders the published change)"], @@ -1396,6 +1403,7 @@ "a console built from this tree with `pnpm console:build`" ], "knownGaps": [ + "TRANSITIONAL (ADR-0146 D2/D11; read at base dfbe34d887): the contract this item proves — a packaged view or dashboard edited in place as a package-less org overlay, reset by deleting the overlay row — is closed on main. A package-less save, publish, active delete or rollback onto a view, dashboard, report, translation or email template that a read-only code package ships answers 403 NOT_OVERRIDABLE naming the base's .custom pair (#28850, packages/metadata-protocol/src/protocol-packaged-base-refusal.ts#packagelessOverlayWriteRefusal), and a write naming the read-only base itself is refused naming the pair too (#29270). What replaced it is Studio's Customize: the edit is a declared override (overrides: { package, basedOn }) saved in com.example.showcase.custom, and 'Restore package original' removes it (ADR-0146 D2, D9). That journey is a new item of #28579 slice 2 (the card's Wanted 2, 'view, dashboard and report customize, export and restore', N-17), not a re-point of this one, so the item carries `blocked` until slice 2 lands it: acceptance[0]–[3] would FAIL by design on main, and acceptance[4]'s flow lock is asserted live by studio-authoring.packaged-automation-studio-lock. Do not run it; do not file its refusals as defects", "DASHBOARD OVERLAY DOOR VERIFIED PRESENT at authoring time (this worktree): DEFAULT_METADATA_TYPE_REGISTRY declares dashboard allowEnvironmentOverlay:true (packages/spec/src/kernel/metadata-plugin.zod.ts), same as view — so the dashboard leg is ASSERTED, not waived. If a run finds the flag flipped, revise this item (ADR-0005 admission-pair change) before scoring", "dashboards.system-overview-live-counts drives the SAME system_overview board expecting its stock shape — the dashboard leg here MUST end with the reset-overlay step; an overlay left behind corrupts that item's next run", "the neighbouring failure class is objectui#4308 mis-tiering (an item in a WRITABLE package shown behind the installed-package lock): if the packaged view/dashboard opens LOCKED, read the ResourceEditPage tiering (isArtifactItem; artifact-backed canWrite = allowEnvironmentOverlay) and the layered envelope's provenance, and forge the raw PUT, before filing a registry defect" @@ -1449,6 +1457,7 @@ "leaving the system_overview overlay behind after the run corrupts dashboards.system-overview-live-counts' stock fixture — the reset step is part of the item, not cleanup courtesy" ], "traps": ["stale-console-bundle", "hydration-race", "automation-input", "cache-staleness"], + "blocked": { "by": "dependency", "ref": "#28579 slice 2 — ADR-0146 D2/D11 closed the package-less overlay this item proves (#28850); its replacement is slice 2's customize/restore item" }, "source": [ "packages/spec/src/kernel/metadata-plugin.zod.ts#allowEnvironmentOverlay (view allowEnvironmentOverlay:true), (dashboard allowEnvironmentOverlay:true — the overlay door the dashboard leg rides, verified present), (flow allowEnvironmentOverlay:false — the same-surface Regime-C negative)", "docs/adr/0126-packaged-metadata-customization-model.md §3 (Regime O = view, dashboard, report, translation, email_template; flow = Regime C)", @@ -1464,7 +1473,13 @@ { "revision": 2, "date": "2026-10-16", "change": "fixtures.requires[0] and [1], steps[0], [1] and [7] and acceptance[2] (clause) and [4] (clause, verify) corrected (checklist-accuracy finding 6 of QA run #23942, wave #23878). The showcase ships individual views, not a container, so the 'discover the container name' instruction pointed at nothing — the item now picks a packaged view from the list and reads its /layers. The flow's banner is the lock banner ('This item is locked and cannot be edited or deleted.' plus the owning package), not an 'installed-package' banner; its wording is recognised, not scored. system_overview is defined in platform-objects but registered by plugin-auth, which is why its /layers reports com.objectstack.plugin-auth. Not loosened: the flow must still open locked with no operating save path", "ref": "#24036" }, { "revision": 3, "date": "2026-10-26", "change": "fixtures.requires[3] names a console built from this tree with pnpm console:build instead of a vendored console matching the retired .objectui-sha pin (#26035 left this file to its card). Wording only; the clauses are unchanged", "ref": "#25848" }, { "revision": 4, "date": "2026-10-27", "change": "steps[2] and steps[5] say to blur the edited input before Save (checklist-accuracy finding of the Interfaces card of QA run #26715, at 3fcafcd3, where every clause passed): the designer form takes a value on blur, so Save does not see an edit whose input still holds focus. negative[3] spells the cross-referenced code NOT_OVERRIDABLE, as org-override-registry-gate now does. No clause changed", "ref": "#26715" }, - { "revision": 5, "date": "2026-11-01", "change": "negative[3] moved to source[7]: it was a scope note naming the items that own the neighbouring surfaces, which no run can decide, so it could only score skipped. Its text is unchanged except the SCOPE: label, which now reads cross-ref:, the spelling the sibling source rows use. Ruling 6545938831 item ① on #28345 (scope notes leave negative[], which keeps only decidable entries; the #27651 precedent). No clause changed. Wave anchor #27377", "ref": "#28345" } + { "revision": 5, "date": "2026-11-01", "change": "negative[3] moved to source[7]: it was a scope note naming the items that own the neighbouring surfaces, which no run can decide, so it could only score skipped. Its text is unchanged except the SCOPE: label, which now reads cross-ref:, the spelling the sibling source rows use. Ruling 6545938831 item ① on #28345 (scope notes leave negative[], which keeps only decidable entries; the #27651 precedent). No clause changed. Wave anchor #27377", "ref": "#28345" }, + { + "revision": 6, + "date": "2026-11-02", + "change": "Marked TRANSITIONAL with `blocked` (by: dependency, ref #28579 slice 2). Re-measured from source at base dfbe34d887 after N-28/N-30 and the D11 closure: the item proves a packaged view/dashboard edited directly as a package-less org overlay and reset by deleting that row, and main now refuses that write — 403 NOT_OVERRIDABLE naming the .custom pair for a package-less write (#28850, packagelessOverlayWriteRefusal) and a refusal naming the pair for a write that names the read-only base (#29270). The Studio door that replaced it is Customize → a declared override in com.example.showcase.custom, undone by 'Restore package original' (ADR-0146 D2/D9) — the card's Wanted 2 (N-17), a new item of slice 2. acceptance[0]–[3] would FAIL by design, so the item leaves the runnable set rather than scoring false FAILs; acceptance[4] (the flow stays locked) stays asserted live by studio-authoring.packaged-automation-studio-lock. No clause text changed. The seat decides whether slice 2 rewrites this id in place or retires it with supersededBy", + "ref": "#28579" + } ] }, { From 70361791c16af4e9d02a8a7bccc19b1f3c7aed8c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:22:42 +0000 Subject: [PATCH 12/14] docs(qa): object-designer-roundtrip names the All metadata designer as its second door Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd --- docs/qa/platform-checklist/areas/studio-authoring.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/qa/platform-checklist/areas/studio-authoring.json b/docs/qa/platform-checklist/areas/studio-authoring.json index 4b40d3f3b03..8da7ec2dbef 100644 --- a/docs/qa/platform-checklist/areas/studio-authoring.json +++ b/docs/qa/platform-checklist/areas/studio-authoring.json @@ -116,7 +116,7 @@ ] }, "steps": [ - "open Studio on the writable package — /_console/studio/PKG/data (PKG = the writable package's id; note its namespace NS, from GET /api/v1/packages) — and click New object. Studio is the authoring door this item drives (QA run #26715 drove every clause through it); the Setup metadata editor (/_console/apps/setup/metadata/object/new?package=PKG) is a second door onto the same save, and it is the one the objectui pins in automated.ref drive. ⛔ Not /_console/apps/com.objectstack.studio/…: the studio app is not loaded on a stock boot (RUNNER.md environment facts), so that route renders inside another app's chrome", + "open Studio on the writable package — /_console/studio/PKG/data (PKG = the writable package's id; note its namespace NS, from GET /api/v1/packages) — and click New object. Studio is the authoring door this item drives (QA run #26715 drove every clause through it); Studio's All metadata area designer (/_console/studio/PKG/metadata/object/new?package=PKG — where the retired Setup metadata editor URL /_console/apps/setup/metadata/object/new now redirects, #28818) is a second door onto the same save, and it is the one the pins in automated.ref drive. ⛔ Not /_console/apps/com.objectstack.studio/…: the studio app is not loaded on a stock boot (RUNNER.md environment facts), so that route renders inside another app's chrome", "in the New object dialog type the Label 'Repair Asset', THEN type the identifier repair_asset character by character (never .fill — the F2 per-keystroke-sanitisation bug only reproduces when typed) and confirm its underscore survives. The dialog applies the package namespace itself: it previews and stores NS_repair_asset (StudioDesignSurface prefixObjectName, objectui CreateItemDialog.storedName-11792 — no double prefix when NS_ is typed as well), which the publish gate requires in a namespaced package (NAMESPACE_PREFIX). Type the identifier after the Label, and re-check it before 'Save as draft' (objectui#12680: a Label typed after a hand-typed identifier can re-derive it). Keep the pre-selected OWD choice and click 'Save as draft' (the dialog's confirm button, engine.studio.createDraft; no Studio create dialog has a button named Create)", "add a Text field, set its label to 'Asset Code', blur — the API name input must derive asset_code (F3)", "add a Picklist field and author its 3 real options, THEN add one EMPTY option row; wait out the debounced live validation — no validation banner may appear for the empty row (F4). ⚠ Order matters: with NO real option yet the select-needs-options banner fires, legitimately — the empty rows ARE dropped, so the field has no options — which collides with a literal 'no banner' (objectui F4, as repaired at 0b36ffa, gives its picklist one real option first for this reason). Assert the empty-row half with real options present", @@ -219,7 +219,7 @@ { "revision": 12, "date": "2026-11-02", - "change": "automated.ref, source[0] and acceptance[1]/[2] verify re-pointed: the live spec's F2–F4 no longer drive the Setup designer route; since #28818 (ADR-0146 D1) they open Studio's All metadata area designer on a package the test authors, /studio/PKG/metadata/object/new?package=PKG, and F4 now saves a draft and reads back the one stored option. Re-read at base dfbe34d887 (ui/e2e/live/studio-object-designer.spec.ts newObjectIn, F4's draft read). The pins stay corroboration at a sibling door (the area's metadata-admin editor, not the Data pillar's New object dialog the steps drive), and F4 still seeds one option where acceptance[3] judges three and reads a draft where the clause reads the published picklist, so no clause's scoring basis changed", + "change": "automated.ref, source[0], steps[0]'s second-door note and acceptance[1]/[2] verify re-pointed: the live spec's F2–F4 no longer drive the Setup designer route; since #28818 (ADR-0146 D1) they open Studio's All metadata area designer on a package the test authors, /studio/PKG/metadata/object/new?package=PKG, and F4 now saves a draft and reads back the one stored option. Re-read at base dfbe34d887 (ui/e2e/live/studio-object-designer.spec.ts newObjectIn, F4's draft read). The pins stay corroboration at a sibling door (the area's metadata-admin editor, not the Data pillar's New object dialog the steps drive), and F4 still seeds one option where acceptance[3] judges three and reads a draft where the clause reads the published picklist, so no clause's scoring basis changed", "ref": "#28579" } ] From 1fb71f6057ca74d892e81e07ee0d67944aa2d9dd Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:23:36 +0000 Subject: [PATCH 13/14] =?UTF-8?q?docs(qa):=20FOLLOW-UPS=20=C2=A713=20?= =?UTF-8?q?=E2=80=94=20the=20slice-1=20re-point,=20what=20is=20transitiona?= =?UTF-8?q?l,=20and=20the=20switched-off=20sentence=20finding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd --- docs/qa/platform-checklist/FOLLOW-UPS.md | 40 ++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/qa/platform-checklist/FOLLOW-UPS.md b/docs/qa/platform-checklist/FOLLOW-UPS.md index e66a1967f26..2876e8d7651 100644 --- a/docs/qa/platform-checklist/FOLLOW-UPS.md +++ b/docs/qa/platform-checklist/FOLLOW-UPS.md @@ -779,3 +779,43 @@ non-numeric fields) and `expression-editors` knownGaps[4] (the RLS editor reads CEL' on a formula-field predicate). These three were read from source, not measured. `app-and-nav-properties` scores acceptance[5]'s `requiresObject` cell as not judged: neither Studio nor Setup edits that key, and no ruling says Studio must. + +## 13. Scoped re-point 2026-11-02 — the Setup-designer items to the Studio door (#28579 slice 1, ADR-0146 N-29) + +**Not a sweep, and no new items.** Sixteen named items were re-measured against `main` +(`dfbe34d887`) after N-28 (#28818, with #28785) and N-30 (#29264), using the measured notes +on #28579 (QA runs #28981, #29002, #29124) as inputs. Each changed item's `history` row says +what moved and why. Left untouched, with the reason in the slice's report: +`integration-system.external-datasource-federated-read` and `external-schema-browser-ui` +(the datasource is the one metadata type ADR-0146 D1 keeps in Setup, and its routes still +render there) and `platform-core.packaged-object-extend-only` (API-only; both write doors +answer as before). + +### 13a. Transitional, and why + +- **`studio-authoring.packaged-display-class-direct-edit`** carries `blocked` + (`dependency`, #28579 slice 2), and **`studio-authoring.org-override-registry-gate`** + scores acceptance[1] and [4] `blocked(dependency)`: both prove a package-less overlay on a + packaged view or dashboard, which #28850 closed (403 `NOT_OVERRIDABLE` naming the + `.custom` pair). The replacement — Customize as a declared override in the pair, and + Restore package original — is slice 2's new items. +- **The switched-off sentence's remedy** in `api-backend.packaged-action-disabled-dispatch` + and `automation.packaged-flow-disable-durable`: see 13b. The clauses keep the served text. + +### 13b. Product finding — reported, not filed here + +- `describeTurnedOffPackagedItem` (`packages/types/src/packaged-item-turned-off.ts`), the one + sentence the `ACTION_DISABLED` and `FLOW_DISABLED` 409s read, still tells the end user an + administrator can turn the item on again "under Setup › Packaged automation" (and, for a + flow, "copy it there under a new name"). That page is retired (ADR-0146 D4/D11): an + action's switch is Setup › Packaged actions, and a flow's switch and New similar are on the + flow in Studio. The finding is in the slice's report for the seat to card. + +### 13c. Other items still naming a retired Setup door (outside this slice's list) + +`platform-core.metadata-authoring-roundtrip` steps[7] and `studio-authoring.record-page-roundtrip` +steps[0]/[3] (`/apps/showcase_app/metadata/page/…`), `studio-authoring.permission-matrix-editor-ux` +steps[0] (`/apps/com.objectstack.setup/metadata/permission/…`) and +`automation.packaged-flow-subflow-disable-refusal` steps[7] (the retired Setup +packaged-automation page as the UI half). Those Setup URLs redirect to Studio since #28818, +so the items still reach a page; their text was not re-measured here. From 9e8f8521316ef09654a0200ac594b25c68c744c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 14:24:30 +0000 Subject: [PATCH 14/14] docs(qa): the board's strip citation follows StudioRefusalStrip.tsx after the main merge Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_017ihL2gLghEqeDKTbDTC7Zd --- docs/qa/platform-checklist/areas/automation.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/qa/platform-checklist/areas/automation.json b/docs/qa/platform-checklist/areas/automation.json index 042a4d8ad4b..1df7cd358dc 100644 --- a/docs/qa/platform-checklist/areas/automation.json +++ b/docs/qa/platform-checklist/areas/automation.json @@ -2343,7 +2343,7 @@ { "clause": "server refusals render VERBATIM and are announced: the admin's subflow toggle shows the 409 naming showcase_task_done_notify_owner in Studio's refusal strip, whose sentence element carries role=alert; the plain member's toggle answers the exact manage_metadata sentence; the local fallback strings appear nowhere", "oracle": "dom", - "verify": "the admin cell: [data-testid=studio-refusal-message] has role=\"alert\" (StudioRefusalStrip, StudioDesignSurface.tsx — #29139 closed by #29224, which put the role on the sentence alone, so 'Show me' and Details stay outside the alert) and its text is the server's 409 sentence naming the caller; toggleLedger hands it over verbatim (requestFlowToggle, packagedFlowLedger.ts, reads it with actionErrorDetail). The member cell: the 403 text equals 'Enabling or disabling an automation flow requires the `manage_metadata` capability.' (FLOW_ENABLEMENT_DENY_MESSAGE, domains/automation.ts) — read from the strip if the member reaches a switch, otherwise from the toggle door's body (fixtures.knownGaps[2]; QA run #29124 scored it over the API), and the record says which. The i18n fallbacks (engine.studio.auto.ledger.toggleFailed / toggleFailedHttp) rendering instead of the server sentence means the body was dropped", + "verify": "the admin cell: [data-testid=studio-refusal-message] has role=\"alert\" (ui/packages/app-shell/src/views/studio-design/StudioRefusalStrip.tsx#StudioRefusalStrip — #29139 closed by #29224, which put the role on the sentence alone, so 'Show me' and Details stay outside the alert) and its text is the server's 409 sentence naming the caller; toggleLedger hands it over verbatim (requestFlowToggle, packagedFlowLedger.ts, reads it with actionErrorDetail). The member cell: the 403 text equals 'Enabling or disabling an automation flow requires the `manage_metadata` capability.' (FLOW_ENABLEMENT_DENY_MESSAGE, domains/automation.ts) — read from the strip if the member reaches a switch, otherwise from the toggle door's body (fixtures.knownGaps[2]; QA run #29124 scored it over the API), and the record says which. The i18n fallbacks (engine.studio.auto.ledger.toggleFailed / toggleFailedHttp) rendering instead of the server sentence means the body was dropped", "evidence": "the admin strip screenshot with its role attribute and text transcribed + the member's 403 body (or strip screenshot)" }, { @@ -2377,7 +2377,7 @@ }, "source": [ "docs/adr/0126-packaged-metadata-customization-model.md §7.4 (on/off + clone; ⛔ no drift/ancestry surface) as amended by docs/adr/0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md D4 (the packaged flow's switch and New similar move to the item in Studio; Setup › Packaged automation retires) and D11 (the retire list, N-28)", - "ui/packages/app-shell/src/views/studio-design/FlowLedgerSwitch.tsx (auto-ledger-switch: aria-checked = the ledger flag, data-reading = activationReading) + FlowItemMoreMenu.tsx (New similar: copyValid disables submit; auto-new-similar-refusal role=alert) + StudioDesignSurface.tsx (toggleLedger, StudioRefusalStrip with studio-refusal-message role=alert, auto-new-similar-strip) + ui/packages/app-shell/src/views/setup/packagedFlows.ts (activationReading) + packagedFlowLedger.ts (requestFlowToggle)", + "ui/packages/app-shell/src/views/studio-design/FlowLedgerSwitch.tsx (auto-ledger-switch: aria-checked = the ledger flag, data-reading = activationReading) + FlowItemMoreMenu.tsx (New similar: copyValid disables submit; auto-new-similar-refusal role=alert) + StudioDesignSurface.tsx (toggleLedger, auto-new-similar-strip) + StudioRefusalStrip.tsx (studio-refusal-message role=alert, the one strip every pillar draws) + ui/packages/app-shell/src/views/setup/packagedFlows.ts (activationReading) + packagedFlowLedger.ts (requestFlowToggle)", "ui/packages/app-shell/src/views/setup/PackagedActionsPage.tsx + PackagedActionsSection.tsx (header: ⛔ no clone, ⛔ no drift surface) + ui/packages/app-shell/src/services/builtinComponents.tsx (registerAppComponent ref 'automation:packaged-actions') + ui/packages/app-shell/src/views/studio-design/setupMetadataRedirect.ts#packagedAutomationStudioPath (the retired page's bookmark)", "objectui packages/core/src/actions/actionErrorDetail.ts (error → error.message → message; details[] dropped)", "packages/platform-objects/src/apps/setup-nav.contributions.ts#nav_packaged_actions (nav_packaged_actions → componentRef 'automation:packaged-actions', pinned by setup-packaged-actions-nav.test.ts). It replaced nav_packaged_automation → 'automation:packaged' (#12457): the page's flows half retired into Studio's Automations pillar (ADR-0146 D11, #28578), and its actions half is this entry; nav_packaged_automation is tombstoned in setup-nav-dead-key-tombstone.test.ts", @@ -2451,7 +2451,7 @@ { "revision": 11, "date": "2026-11-02", - "change": "Re-pointed to the doors that replaced the retired Setup › Packaged automation page (ADR-0146 D4/D11; #28785 retired its flows half, #28818 sent its bookmark to Studio). Measured inputs: QA run #29124 (W4-L1, 7fa9873f; card note 6572222369), which failed A0, A1, A2, A3, A5 and A6 as stale spec and passed A4 and A7. Clause by clause, re-read at base dfbe34d887: acceptance[0] asserts nav_packaged_actions ('automation:packaged-actions'), the absence of 'automation:packaged', and the retired bookmark's landing in Studio (packagedAutomationStudioPath); acceptance[1] reads Studio's Automations rail against _status (32 flows in the run) and the header auto-ledger-switch's aria-checked/data-reading; acceptance[2] the same flip persisted across a reload; acceptance[3] New similar on flows, no clone on Setup › Packaged actions; acceptance[4] no lineage, now naming that the pair's 'In PAIR' mark and ADR-0146 D2's customization controls are not lineage; acceptance[5] re-points to Studio's refusal strip and, per the seat's ruling, to #29139's outcome instead of marking it transitional: #29224 put role=alert on studio-refusal-message, so the clause keeps 'announced' — the member's 403 cell is scored over the API while the member reaches neither door (#29124 measured exactly that); acceptance[6] the New similar dialog (copyValid, auto-new-similar-refusal role=alert, the landing strip in the pair); acceptance[7] records the posture #29124 met. steps, fixtures, negative, automated.ref and source[0]–[2] follow. No clause dropped; acceptance[4] no longer forbids a 'customized' badge, because ADR-0146 D2 charters that mark on served facts", + "change": "Re-pointed to the doors that replaced the retired Setup › Packaged automation page (ADR-0146 D4/D11; #28785 retired its flows half, #28818 sent its bookmark to Studio). Measured inputs: QA run #29124 (W4-L1, 7fa9873f; card note 6572222369), which failed A0, A1, A2, A3, A5 and A6 as stale spec and passed A4 and A7. Clause by clause, re-read at base dfbe34d887: acceptance[0] asserts nav_packaged_actions ('automation:packaged-actions'), the absence of 'automation:packaged', and the retired bookmark's landing in Studio (packagedAutomationStudioPath); acceptance[1] reads Studio's Automations rail against _status (32 flows in the run) and the header auto-ledger-switch's aria-checked/data-reading; acceptance[2] the same flip persisted across a reload; acceptance[3] New similar on flows, no clone on Setup › Packaged actions; acceptance[4] no lineage, now naming that the pair's 'In PAIR' mark and ADR-0146 D2's customization controls are not lineage; acceptance[5] re-points to Studio's refusal strip and, per the seat's ruling, to #29139's outcome instead of marking it transitional: #29224 put role=alert on studio-refusal-message, so the clause keeps 'announced' — the member's 403 cell is scored over the API while the member reaches neither door (#29124 measured exactly that); acceptance[6] the New similar dialog (copyValid, auto-new-similar-refusal role=alert, the landing strip in the pair); acceptance[7] records the posture #29124 met. steps, fixtures, negative, automated.ref and source[0]–[2] follow. No clause dropped; acceptance[4] no longer forbids a 'customized' badge, because ADR-0146 D2 charters that mark on served facts. After merging main at a20dcae3f9 the strip lives in its own StudioRefusalStrip.tsx (#29380) and the switch stays focused while a toggle is in flight (#29375, aria-disabled while busy); the citations follow, and no clause moves", "ref": "#28579" } ]