diff --git a/docs/qa/platform-checklist/areas/access-security.json b/docs/qa/platform-checklist/areas/access-security.json index 6e76c661614..740de89de1c 100644 --- a/docs/qa/platform-checklist/areas/access-security.json +++ b/docs/qa/platform-checklist/areas/access-security.json @@ -3489,7 +3489,7 @@ "title": "The /s/:token share-link landing page renders the resolved record minus redacted fields, gates on password, and refuses dead links with designed states — never a blank page or a raw error", "since": "v16", "status": "active", - "revision": 5, + "revision": 6, "priority": "P2", "surface": "browser", "personas": [ @@ -3504,9 +3504,9 @@ "tokens minted over the API as the liaison member per the API item's step shapes (plain, password, audience signed_in, short-expiry) — this item drives only the RENDERING; the mint/resolve/revoke API clauses stay on the API item" ], "knownGaps": [ - "automated.ref lives ENTIRELY in the objectui repo — from this checkout it is neither runnable nor pin-evidenced (the RUNNER standing fact): drive the item by hand in the browser, or run the pin in an objectui checkout and name the revision", + "automated.ref names objectui code imported into this repository: the pin is ui/apps/console/src/pages/shared-record-shape.test.ts in this checkout (RUNNER's ui/ standing fact), so run it from this tree at the run's framework sha and cite that sha — there is no separate objectui revision to name. It pins the 401 envelope fold only: everything rendered is still driven by hand in the browser", "the ai_conversations transcript branch (objectui SharedRecordPage.tsx: the SharedRecordPage component's ai_conversations branch, with its messages-fetch effect and the ChatbotEnhanced readOnly transcript) is ai_conversations-only (Cloud/EE service-ai) — the same knownGap split the API item records for /:token/messages; not runnable on open-framework showcase", - "FIXED, regression row, expected PASS (objectui#11649): the page no longer folds every 401 into the password prompt. Measured at objectui 1fb1ced36243 (the pin this row was written at), again at daeb77fc84dd (#23878) and at f8267df93aba (the objectui pin when revision 3 was written). The pin is retired (#25745): the console a run boots is built from this repo's ui/ by `pnpm console:build` into packages/console/dist (gitignored; `pnpm build` does not refresh it), so re-read ui/apps/console/src/pages/SharedRecordPage.tsx at the run's own framework sha: SharedRecordPage.tsx reads a 401 by the body's error.code through shared-record-shape.ts resolveGateOf — NEEDS_PASSWORD and WRONG_PASSWORD open the password prompt, SIGN_IN_REQUIRED opens the sign-in gate, and any other code shows the server's message, not a prompt — so a signed_in-audience link opened anonymously renders the dedicated 'Sign in required' state, never 'Password required'. acceptance[3] now asserts that state (QA run #23163 saw it live at objectstack 36b2889c). The objectui pins are apps/console/src/pages/SharedRecordPage-11649.test.tsx ('shows the sign-in path back to this link, and no password prompt') and shared-record-shape.test.ts ('reads each 401 code as its gate'), objectui-repo-only like knownGaps[0]" + "FIXED, regression row, expected PASS (objectui#11649): the page no longer folds every 401 into the password prompt. Measured at objectui 1fb1ced36243 (the pin this row was written at), again at daeb77fc84dd (#23878) and at f8267df93aba (the objectui revision when revision 3 was written). The pin is retired (#25745): the console a run boots is built from this repo's ui/ by `pnpm console:build` into packages/console/dist (gitignored; `pnpm build` does not refresh it), so re-read ui/apps/console/src/pages/SharedRecordPage.tsx at the run's own framework sha: SharedRecordPage.tsx reads a 401 by the body's error.code through shared-record-shape.ts resolveGateOf — NEEDS_PASSWORD and WRONG_PASSWORD open the password prompt, SIGN_IN_REQUIRED opens the sign-in gate, and any other code shows the server's message, not a prompt — so a signed_in-audience link opened anonymously renders the dedicated 'Sign in required' state, never 'Password required'. acceptance[3] now asserts that state (QA run #23163 saw it live at objectstack 36b2889c). The pins are ui/apps/console/src/pages/SharedRecordPage-11649.test.tsx ('shows the sign-in path back to this link, and no password prompt') and ui/apps/console/src/pages/shared-record-shape.test.ts ('reads each 401 code as its gate'), run from this checkout like knownGaps[0]" ] }, "steps": [ @@ -3571,7 +3571,7 @@ ], "automated": { "kind": "unit", - "ref": "objectui apps/console/src/pages/shared-record-shape.test.ts (the envelope fold + the redactFields→redactedFields rename — the #3983 pin); objectui-repo-only, see knownGaps. Everything rendered stays manual" + "ref": "ui/apps/console/src/pages/shared-record-shape.test.ts (the envelope fold + the redactFields→redactedFields rename — the #3983 pin); objectui code imported under ui/, run from this checkout, see knownGaps[0]. Everything rendered stays manual" }, "source": [ "objectui apps/console/src/App.tsx (the /s/:token route, mounted outside the authenticated shell)", @@ -3610,6 +3610,12 @@ "date": "2026-10-26", "change": "Retired-pin text (#26035, which left area files to these ledger cards): knownGaps[2] told the run to re-read SharedRecordPage.tsx 'at whatever pin the run boots', naming this repo's .objectui-sha. The pin is retired (#25745); the console builds from ui/ with `pnpm console:build` into packages/console/dist, which `pnpm build` does not refresh. The row now says so and points at ui/apps/console/src/pages/SharedRecordPage.tsx, read on main b4199c96 (resolveGateOf and both named pins present under ui/apps/console/src/pages/). knownGaps[0] and automated.ref still call the ref objectui-repo-only, in step with RUNNER.md's standing fact, which #25850 owns. No clause or status changed", "ref": "#25835" + }, + { + "revision": 6, + "date": "2026-10-27", + "change": "fixtures.knownGaps[0], knownGaps[2] and automated.ref re-pointed off the retired objectui-repo wording: the three pins they name live in this checkout under ui/apps/console/src/pages/ and run from this tree at the run's framework sha, so knownGaps[0] no longer tells a runner they are unrunnable here (#26626, maintainer-directed). Measured revisions and historical objectui issue numbers kept. No clause changed, no status changed", + "ref": "#26626" } ] }, diff --git a/docs/qa/platform-checklist/areas/ai.json b/docs/qa/platform-checklist/areas/ai.json index d4e9e49ea7e..202d3f718bc 100644 --- a/docs/qa/platform-checklist/areas/ai.json +++ b/docs/qa/platform-checklist/areas/ai.json @@ -644,7 +644,7 @@ "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 — and the ungated bare system/ai-approvals route degrades honestly: one 501 read stops the poll, and the error replaces the empty queue (objectui#11736)", "since": "v16", "status": "active", - "revision": 7, + "revision": 8, "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)"], @@ -706,7 +706,7 @@ { "clause": "honest degradation on the bare route: no fake empty queue and no unbounded dead poll. The inbox renders the destructive alert INSTEAD of the 'No actions waiting' empty state (the empty state is drawn only when the read answered, AiPendingActionsInbox.tsx), and the poll stops after the one 501 (a refused read ends it, usePendingActions.ts). This was expected-fail K2 until objectui#11736 fixed both halves", "oracle": "network", - "verify": "the ~15s network capture shows a single 501 read of /api/v1/ai/pending-actions and no further request; the page screenshot shows the error alert with no empty-queue panel beneath it. objectui pins both halves — packages/plugin-chatbot/src/__tests__/usePendingActions.pollFaultPolicy-11736.test.tsx ('a refused read stops the poll') and AiPendingActionsInbox.errorReplacesEmpty-11736.test.tsx ('an errored read shows the error, not an empty queue'); both now live in this tree under ui/packages/plugin-chatbot/src/__tests__/ (@object-ui/plugin-chatbot), so run them at the run subject sha to cite them beside the live read", + "verify": "the ~15s network capture shows a single 501 read of /api/v1/ai/pending-actions and no further request; the page screenshot shows the error alert with no empty-queue panel beneath it. Unit pins in this tree cover both halves — ui/packages/plugin-chatbot/src/__tests__/usePendingActions.pollFaultPolicy-11736.test.tsx ('a refused read stops the poll') and ui/packages/plugin-chatbot/src/__tests__/AiPendingActionsInbox.errorReplacesEmpty-11736.test.tsx ('an errored read shows the error, not an empty queue') (@object-ui/plugin-chatbot), so run them at the run subject sha to cite them beside the live read", "evidence": "the poll trace + the page screenshot" } ], @@ -734,7 +734,8 @@ { "revision": 4, "date": "2026-10-13", "change": "acceptance[5] (clause and verify) flipped from expected-fail K2 to the positive contract, with steps[6] and [7], negative[2], the title and source[4] and [5] (stale spec, #23163 C5; parked on #23121, carried by #23169). objectui#11736 made the poll follow its last answer (a refused read stops it, a transient one backs off within a ceiling) and made the inbox draw its empty state only when the read answered. QA run #23163 read one 501, no re-arm, and no empty state under the alert, at objectstack 36b2889c with objectui 1fb1ced36243. Re-read from objectui source at that pin, still this repo's .objectui-sha, including the two pins now named in the clause", "ref": "#23169" }, { "revision": 5, "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[0] cited lines 71-88 of objectui useAiSurface.ts in the comma-range spelling, the second range of the list 45397edf53 authored (9-40 and 71-88). At the objectui pin that commit shipped, 9602dc820450, and still at the shipped pin 12dc7b47c340, those lines are the useAiSurfaceEnabled hook, so source[0] now names it. 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": 6, "date": "2026-10-26", "change": "acceptance[5].verify re-pointed off the retired objectui pin (#26035): the .objectui-sha file is gone (#25745) and the two objectui pins it named now live in this tree under ui/packages/plugin-chatbot. Found while applying the 18.2.0 sweep's ai findings (#25836, wave anchor #25807). No clause changed, no status changed", "ref": "#25836" }, - { "revision": 7, "date": "2026-10-26", "change": "the landing-page REMEDY clause's verify: the 501 message the alert string-equals now reads 'ObjectOS Cloud/Enterprise' (was the retired product name), following the ObjectOS product-name ruling and moving with REMEDY_DETAIL['ai']", "ref": "#26228" } + { "revision": 7, "date": "2026-10-26", "change": "the landing-page REMEDY clause's verify: the 501 message the alert string-equals now reads 'ObjectOS Cloud/Enterprise' (was the retired product name), following the ObjectOS product-name ruling and moving with REMEDY_DETAIL['ai']", "ref": "#26228" }, + { "revision": 8, "date": "2026-10-27", "change": "acceptance[5].verify: the two unit pins are named by their ui/ paths instead of as objectui pins (#26626, maintainer-directed). No clause changed, no status changed", "ref": "#26626" } ] }, { diff --git a/docs/qa/platform-checklist/areas/approvals.json b/docs/qa/platform-checklist/areas/approvals.json index 6fbeeb3a1f5..423b180eec1 100644 --- a/docs/qa/platform-checklist/areas/approvals.json +++ b/docs/qa/platform-checklist/areas/approvals.json @@ -1676,7 +1676,7 @@ "title": "Approval requests on v18: the request record page serves record:approval_decision and a Reply action that lands in its timeline, `enable.approvalsVisibleToReaders` lets a plain record reader see the record's requests read-only (and only then), the generic /data door serves request rows by approvals visibility, and titles shown to a reader use only what that reader may read", "since": "v18", "status": "active", - "revision": 3, + "revision": 4, "priority": "P1", "surface": "mixed", "personas": [ @@ -1691,7 +1691,7 @@ "for the reader leg: a scratch object (or showcase object via the scratch app) with enable.approvalsVisibleToReaders:true" ], "knownGaps": [ - "the console clause (acceptance[4]) needs a bundled console: build it with `pnpm console:build`, which builds ui/apps/console from this checkout, so the console and the spec it renders are one tree by construction (packages/console/build-dist.sh). The blocked(environment) this gap used to record — the retired .objectui-sha pin not building against current spec — no longer arises" + "the console clause (acceptance[4]) needs a bundled console: build it with `pnpm console:build`, which builds ui/apps/console from this checkout, so the console and the spec it renders are one tree by construction (packages/console/build-dist.sh). The blocked(environment) this gap used to record — a console built from a pinned objectui revision instead of this tree, not building against current spec — no longer arises" ] }, "steps": [ @@ -1764,6 +1764,12 @@ "date": "2026-10-26", "change": "two checklist-accuracy findings of the 18.2.0 sweep's QA run #25815, Tier 1 set, at framework 6679644a (findings card #25838, wave anchor #25807): acceptance[1].verify names the reply's HTTP legs — the comment landing in the timeline, and the pending-approver-or-submitter-only refusal — because no pin in automated.ref asserts either, and cites the two service-layer pins that do as evidence of their layer only; automated.ref's acceptance[2] annotation says request-read-gate.integration.test.ts drives ObjectQL and the data protocol in-process, never /data/sys_approval_request over HTTP. Also respelled while there, the retired pin flow (#26052, #26035): knownGaps[0] builds the console with `pnpm console:build` from this tree, and the pin-skew block it recorded no longer arises. Pins re-read at base 9753d163. No clause changed", "ref": "#26116" + }, + { + "revision": 4, + "date": "2026-10-27", + "change": "fixtures.knownGaps[0]: the retired pin-file name dropped from the description of the gap that no longer arises (#26626, maintainer-directed). No clause changed, no status changed", + "ref": "#26626" } ] }, diff --git a/docs/qa/platform-checklist/areas/attachments-storage.json b/docs/qa/platform-checklist/areas/attachments-storage.json index 0e0359d054b..eaef865d72d 100644 --- a/docs/qa/platform-checklist/areas/attachments-storage.json +++ b/docs/qa/platform-checklist/areas/attachments-storage.json @@ -248,7 +248,7 @@ "title": "Gated downloads: 401 anonymous, 403 parent-invisible, signed URL for the entitled — per gating class (attachments-scope / field-owned / public_read opt-out / ungated)", "since": "v15.1", "status": "active", - "revision": 7, + "revision": 8, "priority": "P1", "surface": "mixed", "personas": ["admin (uploader/owner)", "signed-up member who cannot read the parent record (qa-scratch-authz persona B: the API deny clauses)", @@ -314,7 +314,7 @@ "a deny that surfaces in the panel as 'Download failed (403)' instead of the mapped copy means the error-envelope dialect broke (#3689 note in the panel) — file it against objectui, not storage" ], "traps": ["wrong-persona", "dispatcher-vs-hono-route", "stale-console-bundle"], - "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts (the CLOSED side — clauses 0, 1 and 2: anonymous 401 AUTH_REQUIRED on both download routes, 403 ATTACHMENT_DOWNLOAD_DENIED for an authenticated member who cannot read the parent, and, for clause 2, the 200 {success, data:{url}} envelope only — answered to the admin owner, who also owns and reads the parent, and to a non-uploader member who reads it; the url is never followed. Clause 2's uploader-bypass leg (the file's owner_id user against a parent it cannot otherwise read) and its 'the url serves the bytes' leg are not asserted here and are hand-driven) + packages/qa/dogfood/test/attachments-public-read-acl.dogfood.test.ts (clause 3, the OPEN side, asserted on ONE attachments-scope file attached to a private parent: the anonymous capability URL is 401 BEFORE the flip, 302 AFTER acl='public_read' — and the redirect is FOLLOWED anonymously and the served bytes compared, because a 302 into a wall would satisfy a status-only assertion — the /url sibling likewise reopens in its declared envelope, and flipping the acl back to 'private' re-closes both routes, which is what shows the acl VALUE is the cause rather than state accrued by the earlier requests. A sibling file on the SAME parent with the same uploader and scope keeps its 401/403, so the opt-out is per FILE and not a global fail-open. The TTL is read back out of the minted capability URL to prove WHICH branch ran: public_read returns presignedTtl (measured >3000s) while an authorized gated grant returns downloadTtl (measured <=300s) — a distinction no status code exposes, so a widened gate admitting the anonymous caller through the AUTHORIZED path would still be caught. The field-owned gating class (ref_object/ref_id, ADR-0104 D3 wave 2) is covered on both sides too, including its distinct FILE_DOWNLOAD_DENIED deny code, which is the evidence that the field-owned arm — not the attachments arm — is the one that ran). STILL MANUAL: clause 4, the browser's friendly denial copy in RecordAttachmentsPanel (ui/packages/app-shell/src/views/RecordAttachmentsPanel.tsx since the objectui import), is a screenshot oracle over the console bundle, which this repo builds from its ui/ tree with `pnpm console:build` into the gitignored packages/console/dist (`pnpm build` does not refresh it; the objectui pin is retired). It has NO pin, must be hand-driven, and the clause-3 pin must NOT be read as covering it." }, + "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts (the CLOSED side — clauses 0, 1 and 2: anonymous 401 AUTH_REQUIRED on both download routes, 403 ATTACHMENT_DOWNLOAD_DENIED for an authenticated member who cannot read the parent, and, for clause 2, the 200 {success, data:{url}} envelope only — answered to the admin owner, who also owns and reads the parent, and to a non-uploader member who reads it; the url is never followed. Clause 2's uploader-bypass leg (the file's owner_id user against a parent it cannot otherwise read) and its 'the url serves the bytes' leg are not asserted here and are hand-driven) + packages/qa/dogfood/test/attachments-public-read-acl.dogfood.test.ts (clause 3, the OPEN side, asserted on ONE attachments-scope file attached to a private parent: the anonymous capability URL is 401 BEFORE the flip, 302 AFTER acl='public_read' — and the redirect is FOLLOWED anonymously and the served bytes compared, because a 302 into a wall would satisfy a status-only assertion — the /url sibling likewise reopens in its declared envelope, and flipping the acl back to 'private' re-closes both routes, which is what shows the acl VALUE is the cause rather than state accrued by the earlier requests. A sibling file on the SAME parent with the same uploader and scope keeps its 401/403, so the opt-out is per FILE and not a global fail-open. The TTL is read back out of the minted capability URL to prove WHICH branch ran: public_read returns presignedTtl (measured >3000s) while an authorized gated grant returns downloadTtl (measured <=300s) — a distinction no status code exposes, so a widened gate admitting the anonymous caller through the AUTHORIZED path would still be caught. The field-owned gating class (ref_object/ref_id, ADR-0104 D3 wave 2) is covered on both sides too, including its distinct FILE_DOWNLOAD_DENIED deny code, which is the evidence that the field-owned arm — not the attachments arm — is the one that ran). STILL MANUAL: clause 4, the browser's friendly denial copy in RecordAttachmentsPanel (ui/packages/app-shell/src/views/RecordAttachmentsPanel.tsx since the objectui import), is a screenshot oracle over the console bundle, which this repo builds from its ui/ tree with `pnpm console:build` into the gitignored packages/console/dist (`pnpm build` does not refresh it). It has NO pin, must be hand-driven, and the clause-3 pin must NOT be read as covering it." }, "source": [ "docs/plans/release-15.1-test-plan.md §A12 / §C1 (#2755/#2970)", "packages/services/service-storage/src/storage-routes.ts#authorizeDownload (authorizeDownload: gating classes, verdict→status mapping, downloadTtl vs presignedTtl)", @@ -353,6 +353,12 @@ "date": "2026-10-27", "change": "qa-scratch-authz step 1 now declares manifest.namespace 'qa' (#26791). Replayed on a fresh stock showcase boot at main 8eef9a8d: as written, the install derived namespace 'attachments' from the package id and steps 2-4 each answered 422 NAMESPACE_PREFIX, so a literal replay provisioned no scratch parent and this item's recipe-backed clauses would have scored blocked(fixture). With the declared namespace, steps 1-4 answered 201/200/200/200 and step 6's three set saves 200. The object names (qa_vault / qa_shared / qa_nofiles) are unchanged, so no step or clause of this item changed", "ref": "#26791" + }, + { + "revision": 8, + "date": "2026-10-27", + "change": "automated.ref: the 'objectui pin is retired' aside dropped; the sentence already names the in-repo console build line (`pnpm console:build` from ui/) (#26626, maintainer-directed). No clause changed, no status changed", + "ref": "#26626" } ] }, diff --git a/docs/qa/platform-checklist/areas/i18n.json b/docs/qa/platform-checklist/areas/i18n.json index 298837ff025..cfae0baf61a 100644 --- a/docs/qa/platform-checklist/areas/i18n.json +++ b/docs/qa/platform-checklist/areas/i18n.json @@ -179,7 +179,7 @@ "title": "Every translatable surface localizes on a zh-CN session — one pass over the full translation-group vocabulary, plus the runtime i18n routes it resolves through", "since": "v15", "status": "active", - "revision": 10, + "revision": 11, "priority": "P1", "surface": "browser", "personas": ["admin on a zh-CN session"], @@ -193,7 +193,7 @@ "the showcase's declared surface carries FROZEN untranslated debt, ratcheted by scripts/check-i18n-coverage.mjs + i18n-coverage-baseline.json — expect some declared strings to legitimately fall back to en; the per-surface check is 'translated keys render translated, untranslated keys render the SOURCE label (never a raw dotted key)', not 100% coverage", "objects._views..emptyState WAS unafforded and no longer is: the #7714 specimen has landed. examples/app-showcase/src/ui/views/task.view.ts authors an emptyState on the `urgent` view (its comment block cites #7714) and src/system/translations/index.ts carries the zh-CN half under objects.showcase_task._views.urgent.emptyState.{title,message}. The variant is runnable; the only setup it needs is an empty `urgent` view (demote the two urgent seed rows)", "objects._sections is addressable ONLY on sections that declare a `name` (packages/spec/src/ui/component.zod.ts: a nameless section renders its authored label in every locale; packages/lint's translation-section-name-missing rule nudges authors toward the name). On the stock showcase every surface this item reads declares one: showcase_task's DEFAULT form (the New/Edit form, section `task`), the task record page — examples/app-showcase/src/ui/pages/task-detail.page.ts's record:details block names its sections overview / schedule / details since #22036, reusing objects.showcase_task._sections — and showcase_contact's default form. QA run #23163 read all three in zh under zh-CN, so the old reading (the record page renders EN in every locale) is stale. ⚠ The named formViews tabbed / wizard / split are reachable from NO console door: `…/showcase_task/view/tabbed` is a LIST-view route and renders the grid (records-forms.form-view-gallery says why), so do not read _sections there", - "acceptance[5]'s CONFIRM half has no stock specimen at all, so on stock it is PIN-ONLY (QA run #21782 recorded it blocked(fixture) as 'no zh confirmText'; parked on #22017 row 8). No showcase action declares a `confirmText` (src/ui/actions, src/data/objects; only a view's bulkActionDefs carry one, and those are not `_actions` entries), so a zh-CN bundle entry has nothing to translate. Score it from the objectui pins in `automated`. The toast half stays live (e.g. showcase_recalc_estimate's zh successMessage). A live confirm specimen would need a scratch-package action declaring confirmText plus a translation overlay (translation is runtime-writable); that is not run-proven" + "acceptance[5]'s CONFIRM half has no stock specimen at all, so on stock it is PIN-ONLY (QA run #21782 recorded it blocked(fixture) as 'no zh confirmText'; parked on #22017 row 8). No showcase action declares a `confirmText` (src/ui/actions, src/data/objects; only a view's bulkActionDefs carry one, and those are not `_actions` entries), so a zh-CN bundle entry has nothing to translate. Score it from the unit pins under ui/ that `automated` names. The toast half stays live (e.g. showcase_recalc_estimate's zh successMessage). A live confirm specimen would need a scratch-package action declaring confirmText plus a translation overlay (translation is runtime-writable); that is not run-proven" ] }, "steps": [ @@ -335,7 +335,8 @@ { "revision": 7, "date": "2026-10-13", "change": "knownGaps[2], steps[7], variants[6] and the showcase source note re-pointed (knownGap stale, #23163; parked on #23121, carried by #23169). task-detail.page.ts's record:details block has named its sections overview / schedule / details since #22036, so the task record page renders them translated; QA run #23163 read zh headings on the New form, the task record page and the contact form under zh-CN. The step used to name the tabbed / wizard / split formViews, but those are reachable from no console door, and `…/view/tabbed` renders the grid. The negative that keeps a nameless section out of resolver FAILs stands", "ref": "#23169" }, { "revision": 8, "date": "2026-10-16", "change": "variants[5], acceptance[7].clause, steps[8] and [9], source[0] and automated.ref corrected (checklist-accuracy note 4 and the fixture gaps of QA run #23951, wave #23878). variants[5] still marked the view-empty-state variant blocked(fixture), contradicting knownGaps[1] and steps[6] — it ran and passed. acceptance[7] counted the served tree as an eleven-group platform face over a ten-group per-app one; the spec declares eleven per-app groups (appTranslationDataShape, picklists included — source[0]'s list had dropped it) and twelve platform groups (plus settings). The dashboard and page steps now name a translated specimen each, because no showcase page carries a translated title (the run scored pages on platform page connect_agent). automated.ref's revision note moves to the current pin. Wording and specimen names only: no request or oracle changed", "ref": "#24036" }, { "revision": 9, "date": "2026-10-19", "change": "acceptance[2].verify says how to find an untranslated key — checklist-accuracy finding of the 18.0 QA sweep (QA run #24553, i18n.surface-matrix; wave anchor #24579 §3): the coverage baseline debt is a count, not a key list. Re-derived at base 9a7838edf5 (scripts/i18n-coverage-baseline.json maps each config path to a number). No clause changed", "ref": "#24649" }, - { "revision": 10, "date": "2026-10-26", "change": "automated.ref corrected (checklist-accuracy findings a5 and a6/a7/a9 of QA run #25815, release-18.2.0-tier1 at 6679644a; wave anchor #25807), with the retired objectui-pin wording replaced (#26035 left this file to its card). a5: the two objectui pins cover the confirm copy and the successMessage handed to the toast, under stubbed action, confirm and toast handlers with no server round-trip, so the network-oracle clause stays the run's. a6/a7/a9: the i18n routes have HTTP coverage only as the anonymous-deny suite's member 200 control, which asserts none of the locale set, the group vocabulary or the zz-ZZ shape these clauses name. The pins now resolve under ui/ in this checkout, not in an objectui checkout at a .objectui-sha revision. No clause is loosened", "ref": "#25842" } + { "revision": 10, "date": "2026-10-26", "change": "automated.ref corrected (checklist-accuracy findings a5 and a6/a7/a9 of QA run #25815, release-18.2.0-tier1 at 6679644a; wave anchor #25807), with the retired objectui-pin wording replaced (#26035 left this file to its card). a5: the two objectui pins cover the confirm copy and the successMessage handed to the toast, under stubbed action, confirm and toast handlers with no server round-trip, so the network-oracle clause stays the run's. a6/a7/a9: the i18n routes have HTTP coverage only as the anonymous-deny suite's member 200 control, which asserts none of the locale set, the group vocabulary or the zz-ZZ shape these clauses name. The pins now resolve under ui/ in this checkout, not in an objectui checkout at a .objectui-sha revision. No clause is loosened", "ref": "#25842" }, + { "revision": 11, "date": "2026-10-27", "change": "fixtures.knownGaps[3]: the CONFIRM half is scored from the unit pins under ui/ that automated names, not 'the objectui pins' (#26626, maintainer-directed). No clause changed, no status changed", "ref": "#26626" } ] }, { diff --git a/docs/qa/platform-checklist/areas/platform-core.json b/docs/qa/platform-checklist/areas/platform-core.json index 061ebe12a17..9cab5e6dfde 100644 --- a/docs/qa/platform-checklist/areas/platform-core.json +++ b/docs/qa/platform-checklist/areas/platform-core.json @@ -2516,7 +2516,7 @@ "title": "Keyboard shortcut surface: `?` and ?shortcuts=1 open the dialog and Esc closes it, typing in an input never triggers it, the dialog lists exactly the shortcuts whose handler is mounted and every listed one fires (⌘K, ⌘/Ctrl+B shown as ⌘B, Esc, ?), the five formerly advertised dead accelerators (⌘/, N, R, ⌘E, ⌘D) are neither listed nor bound, and the AI-chat pair ⌘⇧O / ⌘⇧S is listed only on the page that handles it (objectui#11674)", "since": "v16", "status": "active", - "revision": 5, + "revision": 6, "priority": "P2", "surface": "browser", "personas": ["seeded admin (admin@objectos.ai / admin123)"], @@ -2527,7 +2527,7 @@ ], "knownGaps": [ "⌘⇧I (chat dock) is wired but the dock only exists when the AI surface is present — on a stock open boot the agent catalog is empty, the listener is not armed and ⌘⇧I is deliberately inert; that WHOLE cell (inert-when-gated AND armed-when-entitled) is scored by ai.console-ai-surface-gating — here record only that the accelerator's handler exists and is gated, never a second verdict", - "⌘⇧O / ⌘⇧S are advertised by the AI chat page itself, beside its handler (AiChatPage's useAdvertiseShortcut(NEW_CHAT_SHORTCUT / TOGGLE_CHATS_LIST_SHORTCUT) next to matchAiChatShortcut, objectui#11674), so the dialog lists them ONLY while that page is mounted. The off-page half is scorable on every stock boot: the dialog shows no AI-chat group and neither chord does anything. The on-page half needs the AI chat page, which is reachable only where the access-filtered agent catalog serves at least one agent, i.e. a deployment running the Cloud/Enterprise @objectstack/service-ai, or a console rebuilt with VITE_AI_BASE_URL pointing at such a server (ai.console-ai-surface-gating knownGaps). The open framework ships no agent server by design and CI has no such deployment, so that half scores blocked(environment) (QA run #21782, parked on #22017 row 9). objectui pins that the page's advertisements carry chords its matcher answers (KeyboardShortcutsDialog.wiredOnly-11674.test.tsx, the AI chat page describe) and the key-to-intent mapping (packages/app-shell/src/console/ai/__tests__/matchAiChatShortcut.test.ts); neither is a live on-page run" + "⌘⇧O / ⌘⇧S are advertised by the AI chat page itself, beside its handler (AiChatPage's useAdvertiseShortcut(NEW_CHAT_SHORTCUT / TOGGLE_CHATS_LIST_SHORTCUT) next to matchAiChatShortcut, objectui#11674), so the dialog lists them ONLY while that page is mounted. The off-page half is scorable on every stock boot: the dialog shows no AI-chat group and neither chord does anything. The on-page half needs the AI chat page, which is reachable only where the access-filtered agent catalog serves at least one agent, i.e. a deployment running the Cloud/Enterprise @objectstack/service-ai, or a console rebuilt with VITE_AI_BASE_URL pointing at such a server (ai.console-ai-surface-gating knownGaps). The open framework ships no agent server by design and CI has no such deployment, so that half scores blocked(environment) (QA run #21782, parked on #22017 row 9). Unit pins under ui/ cover the page's advertisements carrying chords its matcher answers (ui/packages/app-shell/src/chrome/KeyboardShortcutsDialog.wiredOnly-11674.test.tsx, the AI chat page describe) and the key-to-intent mapping (ui/packages/app-shell/src/console/ai/__tests__/matchAiChatShortcut.test.ts); neither is a live on-page run" ] }, "steps": [ @@ -2556,13 +2556,13 @@ { "clause": "the sidebar's displayed chord equals its binding: the dialog lists Toggle sidebar as `⌘ B` and lists no bare B; bare B does nothing and ⌘B / Ctrl+B toggle the sidebar. This was expected-fail K1a until objectui#11674 derived the dialog's rows from the mounted handlers", "oracle": "dom", - "verify": "the dialog's Navigation group reads `⌘ B` for Toggle sidebar and carries no bare-B row; a bare-B keypress leaves the sidebar state unchanged (read the sidebar's data-state), and ⌘B and Ctrl+B each flip it. objectui pins the same pair in packages/app-shell/src/chrome/KeyboardShortcutsDialog.wiredOnly-11674.test.tsx ('a listed row whose chord does nothing is caught: a bare B leaves the sidebar alone'), which this checkout carries as ui/packages/app-shell/src/chrome/KeyboardShortcutsDialog.wiredOnly-11674.test.tsx; run it at the run's framework SHA — the Console builds from this tree, so there is no separate objectui revision to name — to cite it beside the live read", + "verify": "the dialog's Navigation group reads `⌘ B` for Toggle sidebar and carries no bare-B row; a bare-B keypress leaves the sidebar state unchanged (read the sidebar's data-state), and ⌘B and Ctrl+B each flip it. ui/packages/app-shell/src/chrome/KeyboardShortcutsDialog.wiredOnly-11674.test.tsx pins the same pair ('a listed row whose chord does nothing is caught: a bare B leaves the sidebar alone'); run it at the run's framework SHA — the Console builds from this tree, so there is no separate objectui revision to name — to cite it beside the live read", "evidence": "the sidebar state trace + the dialog screenshot" }, { "clause": "the advertised set equals the wired set: every row the dialog lists fires its own handler, and none of the five formerly advertised accelerators ⌘/ (focus search), N (create), R (refresh), ⌘E (edit), ⌘D (dark mode) is listed; each of the five still produces NO effect. Each probe is its own record: effect-absence proven against the specific once-advertised effect (focus, overlay, network refetch, edit surface, root theme class). This was expected-fail K1b until objectui#11674 made the dialog list only shortcuts whose handler is mounted", "oracle": "network", - "verify": "the step-3 inventory lists none of the five; per key, the once-advertised effect's own oracle stays silent (no focus change via document.activeElement, no ?form=new push, no list refetch on the wire, no edit route, documentElement class list unchanged); reproduce twice on fresh loads per RUNNER rule 2. objectui pins the listed half in KeyboardShortcutsDialog.wiredOnly-11674.test.tsx ('every row the console dialog lists fires its real handler, with ⌘ and with Ctrl')", + "verify": "the step-3 inventory lists none of the five; per key, the once-advertised effect's own oracle stays silent (no focus change via document.activeElement, no ?form=new push, no list refetch on the wire, no edit route, documentElement class list unchanged); reproduce twice on fresh loads per RUNNER rule 2. the listed half is pinned in ui/packages/app-shell/src/chrome/KeyboardShortcutsDialog.wiredOnly-11674.test.tsx ('every row the console dialog lists fires its real handler, with ⌘ and with Ctrl')", "evidence": "the five per-key traces/DOM reads" }, { @@ -2617,6 +2617,12 @@ "date": "2026-10-26", "change": "acceptance[2].verify re-pointed off the retired pin: it told the runner to run the objectui pin from an objectui checkout at the .objectui-sha revision. There is no .objectui-sha any more: objectui lives in this repository under ui/ and the Console builds from the framework SHA (#26052). The pin is ui/packages/app-shell/src/chrome/KeyboardShortcutsDialog.wiredOnly-11674.test.tsx, run at the run's own SHA. Wording only: no clause changed. #26035 left this file's retired pin wording to the 18.2.0 ledger card #25845. Re-read at 9753d163", "ref": "#26035" + }, + { + "revision": 6, + "date": "2026-10-27", + "change": "fixtures.knownGaps[1], acceptance[2].verify and acceptance[3].verify: the unit pins are named by their ui/ paths in this checkout instead of as objectui pins (#26626, maintainer-directed). No clause changed, no status changed", + "ref": "#26626" } ] }, diff --git a/docs/qa/platform-checklist/areas/records-forms.json b/docs/qa/platform-checklist/areas/records-forms.json index 7250f44e6e9..56880405ff7 100644 --- a/docs/qa/platform-checklist/areas/records-forms.json +++ b/docs/qa/platform-checklist/areas/records-forms.json @@ -1114,7 +1114,7 @@ "title": "Grid-level conditional rules: row-scoped requiredWhen and parent-scoped readonlyWhen in the inline line-item grid", "since": "v16", "status": "active", - "revision": 9, + "revision": 10, "priority": "P1", "surface": "browser", "personas": [ @@ -1143,7 +1143,7 @@ { "clause": "row-scoped requiredWhen flags the cell per row: only the row crossing quantity >= 100 is marked, and the batch save is blocked with a named cell error while its description is empty", "oracle": "dom", - "verify": "after screenshot, assert the required flag on row 1 and its absence on row 2; the blocked save shows the named cell error and NOTHING is written (pinned for the inline flag by objectui e2e/live/grid-conditional-rules.spec.ts). Note for the reader: the client still sends the one batch, and the server refuses it 400 (description / required) — that send is by design (recheck 6218243967 item 13), so score the refusal and the unchanged rows, never the absence of a request; the objectui pin asserts only the inline required flag", + "verify": "after screenshot, assert the required flag on row 1 and its absence on row 2; the blocked save shows the named cell error and NOTHING is written (pinned for the inline flag by ui/e2e/live/grid-conditional-rules.spec.ts). Note for the reader: the client still sends the one batch, and the server refuses it 400 (description / required) — that send is by design (recheck 6218243967 item 13), so score the refusal and the unchanged rows, never the absence of a request; that spec asserts only the inline required flag", "evidence": "screenshots + blocked save" }, { @@ -1242,6 +1242,12 @@ "date": "2026-10-26", "change": "automated.ref, from Tier 1 run #25815 (6679644a, pin b41ed7f68956): (1) it no longer names the grid-file-upload spec dropped at revision 8, which a ref parser read as a pin; the drop and its reason stay in history revision 8. (2) It says both remaining pins are Playwright live specs that score only in a browser run against a booted stack, and only the halves named. fixtures.knownGaps[0], the objectui-pinned-automation note, said the objectui specs live in a separate repo and none exist here; they are under ui/ in this checkout since the import, so it says where they resolve and how they run (devx seat 2's direction, carried for #26035). Re-read on main cc378e93. No clause changed", "ref": "#25846" + }, + { + "revision": 10, + "date": "2026-10-27", + "change": "acceptance[0].verify: the live spec is named by its ui/ path and no longer called the objectui pin (#26626, maintainer-directed). No clause changed, no status changed", + "ref": "#26626" } ] }, @@ -1863,7 +1869,7 @@ "title": "CSV import: GBK decode, required-field hint, legacy-fallback notice", "since": "v16", "status": "active", - "revision": 8, + "revision": 9, "priority": "P2", "surface": "browser", "personas": [ @@ -1880,7 +1886,7 @@ ] }, "steps": [ - "run the pinned unit suites (they feed real GBK bytes and assert the exact decode, the disabled-Next hint, and the visible degrade notice): pnpm exec vitest run packages/plugin-grid/src/importParsers.test.ts packages/plugin-grid/src/__tests__/importMissingRequiredHint.test.tsx packages/plugin-grid/src/__tests__/importLegacyReferenceGuard.test.tsx — with cwd ui/, the objectui tree's root in this checkout (the .objectui-sha pin is retired: the console builds from ui/ at the framework commit, so the run cites that commit). ⛔ Not `pnpm --filter @object-ui/plugin-grid exec vitest run src/…`: the vitest invocation guard (ui/scripts/vitest-invocation-guard.mjs) refuses a package-cwd run, which would otherwise silently execute the wrong suite (objectui#3378)", + "run the pinned unit suites (they feed real GBK bytes and assert the exact decode, the disabled-Next hint, and the visible degrade notice): pnpm exec vitest run packages/plugin-grid/src/importParsers.test.ts packages/plugin-grid/src/__tests__/importMissingRequiredHint.test.tsx packages/plugin-grid/src/__tests__/importLegacyReferenceGuard.test.tsx — with cwd ui/, the objectui tree's root in this checkout (the console builds from ui/ at the framework commit, so the run cites that commit). ⛔ Not `pnpm --filter @object-ui/plugin-grid exec vitest run src/…`: the vitest invocation guard (ui/scripts/vitest-invocation-guard.mjs) refuses a package-cwd run, which would otherwise silently execute the wrong suite (objectui#3378)", "capture the suite output as the primary evidence (the native file-picker hand-off is deliberately not browser-automated)", "optionally spot-check visually: open the import wizard on showcase_contact, load a GBK CSV past the native picker, and screenshot the decoded preview", "in the spot-check, map columns while leaving a required field (name) unmapped; observe the Next control and its hint", @@ -1970,6 +1976,12 @@ "date": "2026-10-27", "change": "automated.ref names the dogfood pin for acceptance[3]'s server half and negative[1] at the import door (#26573 slice 3). No clause changed", "ref": "#26573" + }, + { + "revision": 9, + "date": "2026-10-27", + "change": "steps[0]: the retired pin-file aside dropped; the step already runs the suites from ui/ at the framework commit (#26626, maintainer-directed). No clause changed, no status changed", + "ref": "#26626" } ] }, @@ -2315,7 +2327,7 @@ "title": "Gantt is interactive, not a picture: drag persists, locked tasks survive auto-schedule, host veto restores", "since": "v16", "status": "active", - "revision": 6, + "revision": 7, "priority": "P2", "surface": "browser", "personas": [ @@ -2327,7 +2339,7 @@ "the task gantt view (task.view.ts 'Schedule (Gantt)': startDateField/endDateField/titleField/progressField)" ], "knownGaps": [ - "the stock task gantt view (examples/app-showcase/src/ui/views/task.view.ts, listViews.gantt) declares NO dependenciesField / lockField / parentField, so the auto-schedule, locked-task and subtree-drag surfaces are unreachable LIVE on stock fixtures. ⚠ All three ARE GanttConfig keys (packages/spec/src/ui/view.zod.ts#GanttConfigSchema). QA run #21782 read `lockField` as no spec key; that is wrong, so the variants need no re-point. The objectui pins `automated` names per clause are component-layer evidence for A2 and A3, never their pass (RUNNER rule 6): those api clauses are scored from live row reads. The one sub-leg no pin reaches is a DIRECT drag on a locked LEAF bar: GanttView.tsx returns from the drag start for a locked task, but only the summary-drag and link-drop locked cases are unit-pinned. It needs the view extended live, with lockField pointing at a boolean on showcase_task such as `done` (a view overlay or a scratch copy). That recipe is not run-proven, so score the sub-leg blocked(fixture) until a run proves it. Re-confirmed by QA run #23163 (objectstack 36b2889c, objectui 1fb1ced36243): the stock view still declares no lockField, and no pin reaches the direct leaf drag. The nearest pin, GanttView.tree.test.tsx ('omits drag/resize/progress handles and the dependency dot on a locked bar'), proves a locked leaf loses its resize grips and link dot, NOT that dragging its body writes nothing, so it does not score the sub-leg either", + "the stock task gantt view (examples/app-showcase/src/ui/views/task.view.ts, listViews.gantt) declares NO dependenciesField / lockField / parentField, so the auto-schedule, locked-task and subtree-drag surfaces are unreachable LIVE on stock fixtures. ⚠ All three ARE GanttConfig keys (packages/spec/src/ui/view.zod.ts#GanttConfigSchema). QA run #21782 read `lockField` as no spec key; that is wrong, so the variants need no re-point. The unit pins `automated` names per clause (under ui/packages/plugin-gantt/src) are component-layer evidence for A2 and A3, never their pass (RUNNER rule 6): those api clauses are scored from live row reads. The one sub-leg no pin reaches is a DIRECT drag on a locked LEAF bar: GanttView.tsx returns from the drag start for a locked task, but only the summary-drag and link-drop locked cases are unit-pinned. It needs the view extended live, with lockField pointing at a boolean on showcase_task such as `done` (a view overlay or a scratch copy). That recipe is not run-proven, so score the sub-leg blocked(fixture) until a run proves it. Re-confirmed by QA run #23163 (objectstack 36b2889c, objectui 1fb1ced36243): the stock view still declares no lockField, and no pin reaches the direct leaf drag. The nearest pin, GanttView.tree.test.tsx ('omits drag/resize/progress handles and the dependency dot on a locked bar'), proves a locked leaf loses its resize grips and link dot, NOT that dragging its body writes nothing, so it does not score the sub-leg either", "AUTOMATION LIVES IN THE objectui TREE, WHICH IS ui/ IN THIS CHECKOUT — `automated.ref` names objectui specs exclusively, by objectui-relative paths that resolve under ui/ (a unit suite at ui/packages/…, a Playwright spec at ui/e2e/…). They run from that tree: a unit suite with cwd ui/ (pnpm exec vitest run FILE), a live spec only against a booted stack serving a console built by pnpm console:build. Drive the item by hand in the browser and score it as a manual run, OR run the pin from ui/ and cite its output naming the framework commit it ran at. ⛔ Never score it as covered on the strength of the `automated` field alone — an unrun pin is a claim, not evidence. Full protocol: RUNNER.md, the objectui-pinned-automation standing fact, whose 'none of those specs exist here' predates the ui/ import." ] }, @@ -2423,6 +2435,12 @@ "date": "2026-10-26", "change": "From Tier 1 run #25815 (6679644a, pin b41ed7f68956), re-read in ui/packages/plugin-gantt on main cc378e93. (1) automated.ref and knownGaps[0] scored acceptance[1] and [2] from component and unit cases that stop at the component's update callback; those clauses' oracle is api, so the cases are now named as evidence of their layer only. (2) The ref named objectui 0abd4f9f8769 for the mapping while the run pinned b41ed7f68956; it now says the cases are present at both. fixtures.knownGaps[1], the objectui-pinned-automation note, said the objectui specs live in a separate repo and none exist here; they are under ui/ in this checkout since the import, so it says where they resolve and how they run (devx seat 2's direction, carried for #26035). Re-read on main cc378e93. No clause changed", "ref": "#25846" + }, + { + "revision": 7, + "date": "2026-10-27", + "change": "fixtures.knownGaps[0]: the per-clause unit pins are located under ui/packages/plugin-gantt/src instead of called objectui pins (#26626, maintainer-directed). Measured objectui revisions kept. No clause changed, no status changed", + "ref": "#26626" } ] }, @@ -2494,7 +2512,7 @@ "title": "Dirty forms guard ACCIDENTAL closes: the X / Escape / backdrop prompt on a dirty form, the footer Cancel discards without a prompt by design, save proceeds, nothing is lost silently", "since": "v15", "status": "active", - "revision": 5, + "revision": 6, "priority": "P2", "surface": "browser", "personas": [ @@ -2530,7 +2548,7 @@ { "clause": "the guard's behavior is pinned by the existing unit suite", "oracle": "test", - "verify": "with cwd ui/, the objectui tree's root in this checkout (the console builds from ui/ at the framework commit; the .objectui-sha pin is retired): pnpm exec vitest run packages/plugin-form/src/discardGuard.test.tsx. The file holds 11 it() cases on main at cc378e93: the X close, the footer Cancel and the pristine paths for ModalForm and DrawerForm, plus the beforeunload guard. QA run #23163's 14/14 was counted on another tree, so cite the count the run's own tree reports. No case drives Escape or a backdrop click, so acceptance[0]'s and negative[0]'s Escape and backdrop legs are driven by hand. ⛔ Not `pnpm --filter @object-ui/plugin-form exec vitest run src/discardGuard.test.tsx`: the vitest invocation guard (ui/scripts/vitest-invocation-guard.mjs) refuses a run whose cwd is inside a package, because the root config's project globs resolve against the cwd and such a run would silently execute the wrong suite (objectui#3378)", + "verify": "with cwd ui/, the objectui tree's root in this checkout (the console builds from ui/ at the framework commit): pnpm exec vitest run packages/plugin-form/src/discardGuard.test.tsx. The file holds 11 it() cases on main at cc378e93: the X close, the footer Cancel and the pristine paths for ModalForm and DrawerForm, plus the beforeunload guard. QA run #23163's 14/14 was counted on another tree, so cite the count the run's own tree reports. No case drives Escape or a backdrop click, so acceptance[0]'s and negative[0]'s Escape and backdrop legs are driven by hand. ⛔ Not `pnpm --filter @object-ui/plugin-form exec vitest run src/discardGuard.test.tsx`: the vitest invocation guard (ui/scripts/vitest-invocation-guard.mjs) refuses a run whose cwd is inside a package, because the root config's project globs resolve against the cwd and such a run would silently execute the wrong suite (objectui#3378)", "evidence": "test output" } ], @@ -2578,6 +2596,12 @@ "date": "2026-10-27", "change": "automated.ref says what the unit suite holds and why the rest stays a live leg: every clause needs a browser to drive the guard's exit, acceptance[1]'s re-read included (#26573 Done-when). No clause changed", "ref": "#26573" + }, + { + "revision": 6, + "date": "2026-10-27", + "change": "acceptance[2].verify: the retired pin-file aside dropped; the verify already runs the suite from ui/ at the framework commit (#26626, maintainer-directed). No clause changed, no status changed", + "ref": "#26626" } ] }, @@ -3011,7 +3035,7 @@ "title": "Record discussion: an @mention comment reconciles optimistically, persists to sys_comment, interleaves with activity, pings the mentioned user's bell — and the feed filter shows exactly each mode's subset without dropping author-extended activity", "since": "v16", "status": "active", - "revision": 11, + "revision": 12, "priority": "P1", "surface": "browser", "personas": [ @@ -3048,7 +3072,7 @@ "RE-THREAD (UPDATE-SIDE) FEEDS_DISABLED probe — take the sys_comment row posted earlier against the feeds-ENABLED record and PATCH /api/v1/data/sys_comment/ with { thread_id: 'qafeeds_nofeeds:' } as admin, capturing the refusal. This is the UPDATE half of the #10170 gate (enforceFeedsCapability registered on beforeUpdate as well as beforeInsert, packages/plugins/plugin-audit/src/audit-writers.ts): re-read the comment afterward and confirm its thread_id is UNCHANGED", "control for both probes — repeat the same two calls against a feeds-ENABLED parent (the seeded showcase record used above) and confirm they SUCCEED. A 403 that also fires on the enabled parent is measuring access, not the capability gate, and neither clause may be scored from it", "FEED FILTER — on a record whose feed now holds at least one comment (posted above) and one field-change activity (the attributable edit above), cycle the filter control through all four FeedFilterMode values, screenshotting each state before reading the rendered rows", - "OPEN-VOCABULARY probe — plant an author-EXTENDED activity type: sys_activity narrows apiMethods to ['get','list'] (sys-activity.object.ts), so a direct POST is refused BY DESIGN — the sanctioned channel is a scratch object declaring activityMilestones[].type with a custom value (ADR-0052 §5b.2, forwarded verbatim) whose milestone the run then triggers; if that authoring is unavailable on the boot under test, score the clause from the objectui pin (recordActivityFeed.test.ts) and record the substitution", + "OPEN-VOCABULARY probe — plant an author-EXTENDED activity type: sys_activity narrows apiMethods to ['get','list'] (sys-activity.object.ts), so a direct POST is refused BY DESIGN — the sanctioned channel is a scratch object declaring activityMilestones[].type with a custom value (ADR-0052 §5b.2, forwarded verbatim) whose milestone the run then triggers; if that authoring is unavailable on the boot under test, score the clause from the unit pin ui/packages/plugin-detail/src/renderers/__tests__/recordActivityFeed.test.ts and record the substitution", "RECIPIENT READ GATE (acceptance[9]) — on a stock `single` boot with fixtures.requires[2]. The stock boot's audience posture is invite_only, so POST /api/v1/auth/sign-up/email answers 403 SELF_REGISTRATION_CLOSED: the admin mints the member with POST /api/v1/auth/admin/create-user (it lands in the default organization with mustChangePassword true), and the member signs in, calls POST /api/v1/auth/change-password and signs in again. First prove the member's access as the member (GET /api/v1/data/showcase_private_note/ID answers 404, GET /api/v1/data/showcase_account/ID answers 200) and read the member's and the admin's GET /api/v1/notifications. As admin, POST /api/v1/data/sys_comment on the note's thread (thread_id 'showcase_private_note:ID') with mentions naming the member by user id, then a second comment there naming the member by email, then one on the account's thread naming the member by user id and the admin itself. Re-read both inboxes and count the rows of type collab.mention per comment (each row's body is the comment's preview). WALLED LEG, on the group-posture boot of fixtures.requires[2] (#26625), as the seeded admin, the platform owner: (1) POST /api/v1/auth/sign-in/email and keep the session's activeOrganizationId, organization A; (2) POST /api/v1/auth/organization/create {name: 'QA Mention Org B', slug: a fresh one per run} → organization B, then POST /api/v1/auth/organization/set-active {organizationId: B}; (3) POST /api/v1/auth/admin/create-user {email, password, name} → the user lands in B as member with mustChangePassword true; (4) grant the user read on sys_comment: POST /api/v1/packages {manifest: {id: 'com.objectstack.qamention.walled', name: 'QA walled mention fixture', version: '1.0.0', type: 'app', namespace: 'qamention'}, enableOnInstall: true} (201), PUT /api/v1/meta/permission/qamention_comment_reader?package=com.objectstack.qamention.walled {name: 'qamention_comment_reader', label: 'QA mention comment reader', objects: {sys_comment: {allowRead: true}}} (200, saved env-wide), POST /api/v1/data/sys_user_permission_set {user_id, permission_set: 'qamention_comment_reader'} (201); (5) the user signs in, calls POST /api/v1/auth/change-password, signs in again, and proves the grant: GET /api/v1/data/sys_comment answers 200; (6) CONTROL in B: as admin, POST /api/v1/data/showcase_account {name} (organization_id B), confirm the user reads it (200), then POST /api/v1/data/sys_comment on its thread with mentions naming the user by id: the user's inbox gains exactly one collab.mention whose actionUrl names that account. A control that gains nothing means the user cannot read comments and the leg below measures nothing; (7) set-active A, POST /api/v1/data/showcase_account {name} (organization_id A), confirm the user's read of it answers 404 RECORD_NOT_FOUND, then post one comment on its thread naming the user by id and one naming them by email; (8) re-read the user's and the admin's inboxes: no new collab.mention for either comment, and the boot log carries '[audit] mention notification withheld' with reason cannot_read_source, the user id and each comment's id. Teardown: stop the boot (--fresh discards its DB; no repo file is touched)" ], "acceptance": [ @@ -3220,6 +3244,12 @@ "date": "2026-10-27", "change": "acceptance[9]'s walled leg is written out (#26625, maintainer-directed 「补测试数据配方。立卡」). steps[13] now carries the literal walled sequence, fixtures.requires[2] names the group-posture boot and its three variables, and knownGaps[2] says the leg is driven live. The sequence lives on the item because fixtures.provisioning takes one recipe, and this item already uses qa-feeds-disabled. The mention target holds sys_comment read through a scratch permission set (namespace qamention). No showcase set grants it, and a minted member was refused 403 PERMISSION_DENIED and withheld cannot_read_thread. Re-driven once on a group-posture boot at main 1708115e2f: acceptance[9] walled leg PASS. The control mention on organization B's account notified the user (one collab.mention, actionUrl /showcase_account/ID). The user's read of organization A's account answered 404. Both mentions on that account, by id and by email, were withheld cannot_read_source, and neither inbox gained a row. automated.ref's last sentence no longer says the leg waits on #26625 (folded at the seat's request). No clause changed", "ref": "#26625" + }, + { + "revision": 12, + "date": "2026-10-27", + "change": "steps[12]: the substitute unit pin is named by its ui/ path instead of as the objectui pin (#26626, maintainer-directed). No clause changed, no status changed", + "ref": "#26626" } ] }, @@ -4545,7 +4575,7 @@ "title": "fieldGroups[].visibleWhen section predicate: both spellings accepted, the parse normalizes to the envelope, retired visibleOn refused with the located prescription, derivation carries it verbatim — the console gates the section on the form AND the detail page (objectui#11630 fixed)", "since": "v17", "status": "active", - "revision": 8, + "revision": 9, "priority": "P1", "surface": "mixed", "personas": [ @@ -4593,7 +4623,7 @@ { "clause": "the console renders the derived section GATED — the whole 'Pro details' section, header included, absent while kind=basic and appearing LIVE when kind flips to pro, on form and detail alike. At objectui 1fb1ced36243 both halves hold: plugin-form fieldGroups.ts copies visibleWhen onto the derived form section, and the detail page references the group (`{ group }`) so record:details evaluates its visibleWhen per record (objectui#11630, fixed). A section rendered ungated on EITHER page is now a FAIL — a regression, not the known defect. Read the adapters at the running build's revision, or observe the gating itself, before scoring either half", "oracle": "dom", - "verify": "screenshots of both kind states on form and detail, after settle; plus the console revision consulted (stale-console-bundle discipline) — on both pages the section gates live. objectui pins the detail half in packages/plugin-detail/src/renderers/__tests__/record-details.groupVisibleWhen-11630.test.tsx ('record:details evaluates a group's visibleWhen exactly as the form does'); run it with cwd ui/ in this checkout (pnpm exec vitest run packages/plugin-detail/src/renderers/__tests__/record-details.groupVisibleWhen-11630.test.tsx) to cite it beside the live read", + "verify": "screenshots of both kind states on form and detail, after settle; plus the console revision consulted (stale-console-bundle discipline) — on both pages the section gates live. ui/packages/plugin-detail/src/renderers/__tests__/record-details.groupVisibleWhen-11630.test.tsx pins the detail half ('record:details evaluates a group's visibleWhen exactly as the form does'); run it with cwd ui/ in this checkout (pnpm exec vitest run packages/plugin-detail/src/renderers/__tests__/record-details.groupVisibleWhen-11630.test.tsx) to cite it beside the live read", "evidence": "the four screenshots + the console revision/adapter reading the verdict rests on" }, { @@ -4669,6 +4699,12 @@ "date": "2026-10-27", "change": "steps[1] names the scratch package and DECLARES its namespace (manifest id com.objectstack.qa.group, namespace 'qagroup'), and the scratch object is renamed qa_group_gate → qagroup_group_gate in fixtures.knownGaps[0], steps[1], steps[5] and acceptance[0].verify (#26876, the triage sweep of the qa-feeds-disabled pattern this step reuses). Replayed on a fresh stock showcase boot at main a51ba639 (REST only): with no namespace the install derived 'group' from the id and the object save answered 422 NAMESPACE_PREFIX, so the item's carrier could not be authored as written. Not 'qa': qa-scratch-authz's package owns it, and a second 'qa' install answers 422 NAMESPACE_CONFLICT. With 'qagroup', beside the qa, qamedia and qafeeds recipe packages on one boot (no collision), the install answered 201, the active PUT 200, the GET read fieldGroups[0].visibleWhen back as the bare string authored and highlightFields ['kind'], the visibleOn draft answered 422 INVALID_METADATA (fieldGroups.0 unrecognized_keys), a record POST 201, and the teardown DELETE 200. No clause's assertion or oracle changed", "ref": "#26876" + }, + { + "revision": 9, + "date": "2026-10-27", + "change": "acceptance[3].verify: the detail-half unit pin is named by its ui/ path instead of as an objectui pin (#26626, maintainer-directed). No clause changed, no status changed", + "ref": "#26626" } ] }, @@ -5895,7 +5931,7 @@ "title": "Defaults that changed meaning on v18 (ADR-0087 D3 18.* entries): an omitted list-view page size is 50 (was 25), an omitted editInline means editable in place, an autonumber with no `unique` is unique per organization, and a field-heavy form opens as a bounded modal on desktop", "since": "v18", "status": "active", - "revision": 4, + "revision": 5, "priority": "P2", "surface": "mixed", "personas": [ @@ -5911,7 +5947,7 @@ "the console bundle for the browser legs — see knownGaps" ], "knownGaps": [ - "STALE (QA runs #24558, #24616): the console those runs pinned (.objectui-sha 4016370af3c9, a pin since retired) built against current spec, so the browser clauses ran — they are no longer blocked(environment). The console now builds from this checkout's ui/ tree (pnpm console:build), with no pin to drift from" + "STALE (QA runs #24558, #24616): the console those runs used (built from objectui revision 4016370af3c9, before the console moved into this tree) built against current spec, so the browser clauses ran — they are no longer blocked(environment). The console now builds from this checkout's ui/ tree (pnpm console:build), with no pin to drift from" ] }, "steps": [ @@ -5984,6 +6020,12 @@ "date": "2026-10-27", "change": "automated.ref says the spec pins hold acceptance[0] and that acceptance[1]–[3] stay live browser legs, and why (#26573 Done-when). No clause changed", "ref": "#26573" + }, + { + "revision": 5, + "date": "2026-10-27", + "change": "fixtures.knownGaps[0]: the retired pin-file name dropped; the measured objectui revision 4016370af3c9 kept (#26626, maintainer-directed). No clause changed, no status changed", + "ref": "#26626" } ] } diff --git a/docs/qa/platform-checklist/areas/studio-authoring.json b/docs/qa/platform-checklist/areas/studio-authoring.json index 42fd0064732..22b4ac42ec5 100644 --- a/docs/qa/platform-checklist/areas/studio-authoring.json +++ b/docs/qa/platform-checklist/areas/studio-authoring.json @@ -1504,7 +1504,7 @@ "title": "A doc on the app menu follows the reader's audience: an admin authors a doc and puts it (or its book) on an app menu; a member who may read it sees it and opens it in its book; a member outside its audience does not see the entry and gets a refusal — never \"not found\", never a blank page — on the direct route", "since": "v17.5", "status": "active", - "revision": 6, + "revision": 7, "priority": "P1", "surface": "mixed", "personas": [ @@ -1522,7 +1522,7 @@ ], "knownGaps": [ "THE STOCK SHOWCASE CORPUS CARRIES NO { permissionSet } BOOK AND NO doc NAV ENTRY — verify first (GET /api/v1/meta/book, read each audience; GET /api/v1/meta/app?id=showcase_app, look for type:'doc'). The run authors the books, docs, permission set and nav entries at runtime and tears them down, as platform-core.docs-audience-gate allows; the audience is read off the stored row, so a runtime-authored book exercises the same resolver", - "FORK WINDOW CLOSED: every objectui pin from daeb77fc84dd on carried objectui PR 11241 and PR 11258, and so does the objectui code imported under ui/ — the 'You do not have access to this documentation' refusal page and the menu pruning — so a console built from this tree with `pnpm console:build` is the right one to drive (QA run #23942 ran outside the window). Only a console older than those PRs (db11afd496 held neither, source read) fails for a reason that is not this item's; record the commit the console was built from in the run record as usual", + "FORK WINDOW CLOSED: every objectui revision from daeb77fc84dd on carried objectui PR 11241 and PR 11258, and so does the objectui code imported under ui/ — the 'You do not have access to this documentation' refusal page and the menu pruning — so a console built from this tree with `pnpm console:build` is the right one to drive (QA run #23942 ran outside the window). Only a console older than those PRs (db11afd496 held neither, source read) fails for a reason that is not this item's; record the commit the console was built from in the run record as usual", "FIXTURE RECIPE, measured on the first run: the showcase is invite_only, so members come from POST /api/v1/auth/admin/create-user (mustChangePassword false), not sign-up; the Studio doc editor no longer refuses the read-only code package with a 422 — at objectui 1fb1ced36243 it saves an ENV-WIDE draft and offers no package picker (QA run #23166), so a doc authored through the editor lands outside the package that holds the books. Install a writable base first (POST /api/v1/packages with id, name, version, type 'app') and drive every book, doc and the app into it over the metadata API with ?package=, so books and docs share one package; the seeded admin holds no app-declared set, so once the gated book claims the doc the admin's own editor and /meta/app answer the audience refusal too — author the doc before the gated book, or read it back as the reader", "REPO OWNERSHIP (ADR-0054): the server half — /meta/app pruning, /meta/doc and /meta/book refusals — is objectstack's; the menu markup, the direct-route refusal page and the claiming-book permalink are objectui's. A locator or copy fix goes to objectui" ] @@ -1587,7 +1587,8 @@ { "revision": 3, "date": "2026-10-16", "change": "knownGaps[1] closed (checklist-accuracy finding 9 of QA run #23942, wave #23878): the .objectui-sha pin has been past the fork window since daeb77fc84dd — the console carries the refusal page and the menu pruning — so the knownGap no longer asks for a console built elsewhere. Wording only: no step, request or oracle changed", "ref": "#24036" }, { "revision": 4, "date": "2026-10-19", "change": "steps[0] aligned with fixtures.knownGaps[2] from QA run #24571 (18.0 sweep, 5b14b7f3): the step said to create the doc in the Studio doc editor while knownGaps[2] prescribes authoring every book, doc and the app over the metadata API with ?package= so they share one package; the run drove the API recipe. No clause changed. Wave anchor #24579 §3", "ref": "#24671" }, { "revision": 5, "date": "2026-10-20", "change": "steps[0] respelled: 'boot the showcase isolated' → 'boot the showcase on its own port and DB'. Round 12's out-of-scope finding (landing record 6287983829 on wave anchor #24579): the phrase 'showcase isolated' named the dogfood §0 own-port boot, and a runner read it as the `isolated` tenancy posture (OS_TENANCY_POSTURE=isolated). The new spelling names that boot by what it is, in RUNNER.md's own words for it (the trap table's counter: own port/DB, skill §0), and names no posture. Read at base e4d604efd3. No clause changed", "ref": "#25003" }, - { "revision": 6, "date": "2026-10-26", "change": "fixtures.knownGaps[1] drops the retired .objectui-sha wording (#26035 left this file to its card): the console builds from this tree with pnpm console:build, and the objectui code under ui/ carries PR 11241 and PR 11258 (the documentation refusal page is in ui/apps/console). Wording only; the clauses are unchanged", "ref": "#25848" } + { "revision": 6, "date": "2026-10-26", "change": "fixtures.knownGaps[1] drops the retired .objectui-sha wording (#26035 left this file to its card): the console builds from this tree with pnpm console:build, and the objectui code under ui/ carries PR 11241 and PR 11258 (the documentation refusal page is in ui/apps/console). Wording only; the clauses are unchanged", "ref": "#25848" }, + { "revision": 7, "date": "2026-10-27", "change": "fixtures.knownGaps[1]: 'objectui pin' reads 'objectui revision'; the historical objectui PR numbers and revisions are kept (#26626, maintainer-directed). No clause changed, no status changed", "ref": "#26626" } ] }, {