Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 27 additions & 20 deletions docs/qa/platform-checklist/areas/access-security.json

Large diffs are not rendered by default.

21 changes: 11 additions & 10 deletions docs/qa/platform-checklist/areas/ai.json
Original file line number Diff line number Diff line change
Expand Up @@ -593,10 +593,10 @@
},
{
"id": "ai.console-ai-surface-gating",
"title": "Console AI affordances gate on the access-filtered agent catalog: an agent-less boot hides FAB / chat dock / ⌘⇧I / top-bar link and redirects a stale /ai bookmark without flash — with the UNgated SystemHub 'AI Approvals' card pinned as an expected-fail probe",
"title": "Console AI affordances gate on the access-filtered agent catalog: an agent-less boot hides FAB / chat dock / ⌘⇧I / top-bar link and redirects a stale /ai bookmark without flash — with the ungated bare system/ai-approvals route pinned as an expected-fail probe",
"since": "v16",
"status": "active",
"revision": 1,
"revision": 2,
"priority": "P2",
"surface": "browser",
"personas": ["admin (form sign-in — the pending-actions poll sends the COOKIE half only, credentials:'include' with no bearer header, so an injected-token session reads as anonymous there)"],
Expand All @@ -619,8 +619,8 @@
"sweep the gated affordances on the settled shell: floating chatbot FAB, the right-docked chat rail, the AppHeader AI/assistant entry, the Home layout's AI CTAs — screenshot each region, THEN read the DOM to confirm absence",
"press ⌘⇧I (the chat-dock toggle) and confirm nothing mounts — ConsoleLayout only arms the listener when dockEnabled",
"navigate directly to /ai (the stale-bookmark path): capture that a loading fallback holds while the catalog resolves, then the redirect to home lands with the splash preserved — record whether any frame of chat UI flashed",
"as admin open the System hub (SystemHubPage): screenshot the admin card cluster and record whether the 'AI Approvals' card renders on this agent-less boot (expected at head: it DOES — the card is built unconditionally, SystemHubPage.tsx)",
"click the card through to system/ai-approvals; capture the network for ~15s: GET /api/v1/ai/pending-actions?status=pending firing every ~5s and answering 501 each time (poll never stops on error — usePendingActions clears nothing and re-arms)",
"as admin, sweep the surfaces that could advertise AI Approvals — the app sidebars, the settings hub (/apps/setup/system, which forwards to …/system/settings) and Home — and record whether any offers an 'AI Approvals' entry on this agent-less boot (expected: none; the SystemHubPage card wall that carried one was retired by objectui#3743, carried out in objectui#10507)",
"navigate directly to the bare /apps/setup/system/ai-approvals route (still registered with no AI-surface gate in the console route table — AppContent systemRoutes); capture the network for ~15s: GET /api/v1/ai/pending-actions?status=pending firing every ~5s and answering 501 each time (poll never stops on error — usePendingActions clears nothing and re-arms)",
"capture what the page renders: the destructive alert's text (must carry the Cloud/EE remedy sentence from the 501 body), AND whether the 'No actions waiting / When the AI proposes a sensitive action it will appear here for review' empty state renders beneath it as if a live queue exists",
"capture the browser console for the whole session"
],
Expand All @@ -644,10 +644,10 @@
"evidence": "the navigation capture / frame notes"
},
{
"clause": "EXPECTED FAIL at head (defect K2, sweep 2026-08-30): the SystemHub 'AI Approvals' card follows the same gate as every other AI affordance — i.e. it is absent on an agent-less boot. At head it is NOT: SystemHubPage.tsx builds the card unconditionally (no useAiSurfaceEnabled read, unlike FAB/dock/header/Home), so it renders and advertises a dead surface. A run that sees the card must score this clause FAIL with the screenshot — do not tick it green, and do not re-file the defect (the sweep's FOLLOW-UPS row owns it)",
"clause": "on an agent-less boot no console surface (nav, settings hub, Home) advertises an 'AI Approvals' entry; the bare /system/ai-approvals route is scored by A6.",
"oracle": "dom",
"verify": "screenshot the hub first, then read the card grid: the designed contract is no 'AI Approvals' card on an empty catalog; observed-at-head is the ungated card",
"evidence": "the hub screenshot + card-grid DOM"
"verify": "screenshot each surface first, then read its entries: no 'AI Approvals' entry on the app sidebars, the settings hub or Home. The route itself stays reachable by URL and is clause 6's subject — reaching it by typing the URL is not an advertisement",
"evidence": "the surface screenshots + their entry DOM reads"
},
{
"clause": "the landing page degrades with the REMEDY, not a fault: the poll's 501 body message (the single-sourced Cloud/EE sentence — see ai.open-edition-honest-degradation clause 2) surfaces verbatim in the page's destructive alert (usePendingActions call() throws body.error.message; AiPendingActionsInbox renders error.message)",
Expand All @@ -665,7 +665,7 @@
"negative": [
"ticking any AI capability as PRESENT from the 200 empty-agents courtesy is a recording error — the empty catalog is the hide signal (same negative as ai.open-edition-honest-degradation)",
"scoring an affordance ABSENT from a DOM read taken before a settled screenshot is the hydration-race trap — the gated controls are hidden during load BY DESIGN, so a too-early read proves nothing",
"the two expected-fail clauses must not flip to PASS silently: if a run observes the card gated / the empty-queue suppressed, the defect was fixed — revise this item (drop the expected-fail wording, bump revision) rather than quietly ticking",
"the expected-fail clause (clause 6) must not flip to PASS silently: if a run observes the empty-queue panel suppressed under the error and the poll bounded, the defect was fixed — revise this item (drop the expected-fail wording, bump revision) rather than quietly ticking",
"an anonymous probe of /ai/pending-actions answering 501 instead of 401 would be a REGRESSION of the #7653 anonymous-deny ordering (auth gate precedes every capability answer) — file it, it is not this item's expected 501"
],
"traps": ["hydration-race", "stale-console-bundle"],
Expand All @@ -674,13 +674,14 @@
"objectui packages/app-shell/src/layout/ConsoleLayout.tsx (FAB + chat dock + ⌘⇧I all gated on showChatbot/dockEnabled)",
"objectui packages/app-shell/src/layout/AppHeader.tsx (top-bar AI entry gated on the same hook) + console/home/HomeLayout.tsx (Home CTAs)",
"objectui packages/app-shell/src/console/ConsoleShell.tsx (RequireAiSurface — waits for resolve, splash-preserving redirect, objectui#6507)",
"objectui apps/console/src/pages/system/SystemHubPage.tsx (the 'AI Approvals' card built UNconditionally — the K2 gap) + AppContent.tsx (the system/ai-approvals route) + pages/system/AiPendingActionsPage.tsx (thin wrapper, 'Polled every 5 seconds')",
"objectui apps/console/src/AppContent.tsx (systemRoutes: system/ai-approvals still registered with no AI-surface gate — the K2 route half; the bare …/system landing forwards to the settings hub since SystemHubPage and its 'AI Approvals' card were retired, objectui#3743 / objectui#10507) + pages/system/AiPendingActionsPage.tsx (thin wrapper, 'Polled every 5 seconds')",
"objectui packages/plugin-chatbot/src/usePendingActions.ts,217-300 (cookie-only call(), error → error.message, pollInterval 5000 re-arming regardless of errors) + AiPendingActionsInbox.tsx (destructive alert + the error-blind 'No actions waiting' empty state)",
"packages/runtime/src/domains/ai.ts#capabilityUnavailable (#7653 anonymous-deny first; the /ai/agents empty-catalog courtesy #4058/#4053; every other /ai/* → capabilityUnavailable 501) + domains/unavailable.ts (single-sourced remedy sentence)",
"ai.open-edition-honest-degradation (the API half this item mirrors in the browser — 501 body/discovery parity is proven THERE, not re-proven here)"
],
"history": [
{ "revision": 1, "date": "2026-08-30", "change": "new item (sweep 2026-08-30, cross-angle hit 1+4): the console-side AI gating had no coverage — the catalog-signal design (useAiSurfaceEnabled) hides FAB/dock/⌘⇧I/header/Home affordances and no-flash-redirects /ai on an agent-less boot, while SystemHubPage's 'AI Approvals' card is built ungated and its page polls the dead /ai/pending-actions endpoint every 5s (501) rendering a fake empty queue beside the error alert. Authored as a NEW browser sibling of ai.open-edition-honest-degradation (which keeps the API half) rather than extending it, so neither surface double-covers the other; the two K2 defect clauses are expected-fail probes, the defect row itself is the sweep's FOLLOW-UPS entry", "ref": "#sweep-2026-08-30" }
{ "revision": 1, "date": "2026-08-30", "change": "new item (sweep 2026-08-30, cross-angle hit 1+4): the console-side AI gating had no coverage — the catalog-signal design (useAiSurfaceEnabled) hides FAB/dock/⌘⇧I/header/Home affordances and no-flash-redirects /ai on an agent-less boot, while SystemHubPage's 'AI Approvals' card is built ungated and its page polls the dead /ai/pending-actions endpoint every 5s (501) rendering a fake empty queue beside the error alert. Authored as a NEW browser sibling of ai.open-edition-honest-degradation (which keeps the API half) rather than extending it, so neither surface double-covers the other; the two K2 defect clauses are expected-fail probes, the defect row itself is the sweep's FOLLOW-UPS entry", "ref": "#sweep-2026-08-30" },
{ "revision": 2, "date": "2026-10-05", "change": "clause 4, steps 6-7, negative 3, the title and the source re-pointed (stale, #21782 VF4). objectui 9eea12a2b (objectui#10507, fixing objectui#3743) removed SystemHubPage and its 'AI Approvals' card before 17.6.0, so K2's card half is moot. The clause now asserts that no console surface advertises the entry (verifier's wording). The route half of K2 remains: system/ai-approvals is still registered ungated (AppContent systemRoutes), so steps 6-7 reach it by URL and clause 6 keeps its expected-fail scoring. Negative 3 names clause 6 only. Re-read at objectui 9dfaca6543: unchanged", "ref": "#21797" }
]
},
{
Expand Down
26 changes: 14 additions & 12 deletions docs/qa/platform-checklist/areas/approvals.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,22 +174,23 @@
"title": "Inbox actions are metadata-driven and gated by the viewer's relationship",
"since": "v16",
"status": "active",
"revision": 3,
"revision": 4,
"priority": "P1",
"surface": "browser",
"personas": ["approver who is NOT the submitter", "same user viewing a request they submitted"],
"personas": ["an approver who is NOT the submitter and NOT an override actor (no platform-admin or tenant-admin standing), so record.viewer.can_override is false on the request", "the dev admin: the submitter of the invoice request (the own-request case) and an OVERRIDE actor (a platform admin — record.viewer.can_override is true on any PENDING request), for the override half of clause 2"],
"fixtures": {
"app": "showcase",
"requires": [
"requests with a real (non-null) submitter — seeded since #3411 (invoice submitted by admin, others by a no-position persona)",
"seed-approval-demo.ts: the invoice dual sign-off request is submitted BY the admin (their own request, so 我发起的 is non-empty), while EXP-2001 / EXP-DEMO are submitted by Mei Phone (other-submitter requests the admin approves)"
"seed-approval-demo.ts: the invoice dual sign-off request is submitted BY the admin (their own request, so 我发起的 is non-empty), while EXP-2001 / EXP-DEMO are submitted by Mei Phone (other-submitter requests the admin approves)",
"a non-override approver on an other-submitter PENDING request: the seeded approver of EXP-2001 / EXP-DEMO is the dev admin, an override actor, so invite a plain member and make them an approver of one such request (for example through the request's reassign action as the admin) before step 2"
]
},
"steps": [
"boot showcase isolated (dogfood §0); sign in as the dev admin",
"other-submitter case: reach the inbox the way a user does — the Approvals entry in the account app's Inbox group (/_console/apps/com.objectstack.account/component/approvals/inbox), NOT the bare /system/approvals deep link (#7234) — open the EXP-DEMO request from the 待我审批 tab; screenshot, then read the rendered action set from the drawer DOM",
"boot showcase isolated (dogfood §0); sign in as the dev admin for the own-request case and the override half, and as the non-override approver (personas) for the other-submitter case",
"other-submitter case, as the NON-override approver: reach the inbox the way a user does — the Approvals entry in the account app's Inbox group (/_console/apps/com.objectstack.account/component/approvals/inbox), NOT the bare /system/approvals deep link (#7234) — open the other-submitter request from the 待我审批 tab; screenshot, then read the rendered action set from the drawer DOM. Then open the SAME pending request as the dev admin and read its action set too (the override half)",
"own-request case: open the invoice request from the 我发起的 tab; screenshot, then read its action set",
"GET /api/v1/approvals/requests/:id for both and record the server-computed viewer flags (can_act, is_submitter) and status",
"GET /api/v1/approvals/requests/:id for both requests, as each viewer, and record the server-computed viewer flags (can_act, is_submitter, can_override) and status",
"cross-check each rendered action against the declared metadata actions on sys_approval_request (visibility expressions gate on record.viewer.can_act / record.viewer.is_submitter / record.status)",
"spot-check an approver action: approve EXP-DEMO from the drawer; capture the POST and re-read the request",
"spot-check a submitter action: send a reminder on the invoice request; capture POST /api/v1/approvals/requests/:id/remind",
Expand All @@ -203,15 +204,15 @@
"evidence": "screenshot + DOM action list"
},
{
"clause": "submitter-side actions (send-reminder, recall) appear ONLY on the viewer's own request",
"clause": "send-reminder appears only on the viewer's own pending request; Recall appears on the viewer's own pending/returned request and, for an override actor (platform/tenant admin), on any pending request; a non-admin approver sees neither on another submitter's request.",
"oracle": "dom",
"verify": "own-request drawer shows the two extra actions; other-submitter drawer does not",
"evidence": "side-by-side action lists for the two requests"
"verify": "the own-request drawer shows both; the non-override approver's drawer on the other-submitter request shows neither; the dev admin's drawer on that same pending request shows Recall and NOT send-reminder — approval_recall's second visibility arm is record.viewer.can_override (the #3424 override lever, button added by #12716), and approval_remind stays submitter-only. Read each viewer's flags beside its drawer",
"evidence": "action lists for the three (viewer, request) pairs + the viewer flags each was rendered against"
},
{
"clause": "the rendered gating mirrors the server's viewer flags, which are computed on the request read — not a client heuristic",
"oracle": "api",
"verify": "for both requests, the API's viewer flags (can_act / is_submitter) predict exactly which action groups rendered; declared visibility expressions on sys_approval_request gate on those flags plus status",
"verify": "for both requests and every viewer, the API's viewer flags (can_act / is_submitter / can_override) predict exactly which action groups rendered; declared visibility expressions on sys_approval_request gate on those flags plus status",
"evidence": "request reads + the rendered sets"
},
{
Expand All @@ -233,14 +234,15 @@
"traps": ["automation-input", "hydration-race"],
"source": [
"#3358 §1", "#3411",
"packages/plugins/plugin-approvals/src/sys-approval-request.object.ts (declared actions + viewer-flag visibility expressions)",
"packages/plugins/plugin-approvals/src/sys-approval-request.object.ts (declared actions + viewer-flag visibility expressions — approval_recall's submitter arm OR record.viewer.can_override, #12716; approval_remind submitter-only)",
"packages/rest/src/rest-route-ledger.ts (the approvals action route family)",
"objectui apps/console/src/pages/system/ApprovalsInboxPage.tsx (server-declared actions rendered; 待我审批 / 我发起的 tabs)"
],
"history": [
{ "revision": 1, "date": "2026-08-07", "change": "initial import from the #3358 evidence run (7-action table proven after #3411 stamped real submitters)", "ref": "#3358" },
{ "revision": 2, "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" },
{ "revision": 3, "date": "2026-08-10", "change": "entry path refreshed: the inbox is reached through the account app's Approvals nav entry (component route) instead of the bare /system/approvals deep link, so the item exercises the post-#7213 user path", "ref": "#7331" }
{ "revision": 3, "date": "2026-08-10", "change": "entry path refreshed: the inbox is reached through the account app's Approvals nav entry (component route) instead of the bare /system/approvals deep link, so the item exercises the post-#7213 user path", "ref": "#7331" },
{ "revision": 4, "date": "2026-10-05", "change": "clause 2 re-pointed, with the personas, steps 1, 2 and 4, the clause-3 verify and a fixtures line (stale, #21782 VF4). In sys-approval-request.object.ts, approval_recall's visible is the submitter arm OR record.viewer.can_override == true (the #3424 override lever, button added by #12716), which platform/tenant admins hold on any pending request (isOverrideActor); approval_remind stays submitter-only. Revision 3 (2026-08-10) predates #12716. The verifier's wording is used for the clause. The run's approver persona was the dev admin, an override actor, so against that persona the old clause also read as an invalid pairing. The personas now name a non-override approver, and the admin's override view is asserted as its own leg", "ref": "#21797" }
]
},
{
Expand Down
Loading
Loading