Skip to content

Commit 4e0f5f5

Browse files
docs(qa): 18.0 sweep round 9 — three pin over-claims narrowed, comma-range line citations re-pointed (#24752)
Refs #24579 Clause-②: no Round 9 of the 18.0 release sweep's wave anchor (#24579), claim `6282463055`. One PR in two parts. These are checklist defects only: no product code, test, pin or objectui spec changes, in either repository. Each of the 23 edited items takes one `revision` bump and one `history` row (3 in part A, 20 in part B). The rows' `ref` is a placeholder in this head; a fill commit replaces exactly those strings with this PR's number. Base: `origin/main` `39e93b9ef9` (fast-forwarded; the branch carries no merge). objectui was read only, with `git show` at the shipped pin `12dc7b47c340` and at the sweep pin `4016370af3c9`, and older objectui files were fetched as raw text at the pin each item was authored against. ## Part A — `records-forms.json`, the seat ruling and round 8's three half-claims | item | fields | revision | spec / pin | what it asserts | choice | |:--|:--|:--|:--|:--|:--| | `records-forms.conditional-rules-grid` | `automated.ref`, `automated.stale` (removed), `acceptance[2].verify`, `acceptance[3].verify` | 7 → 8 | objectui `e2e/live/grid-file-upload.spec.ts` (blob `feb5ff39` at both pins) | uploads a file into the New Invoice dialog's first-line Receipt cell and asserts only that the submitted `/api/v1/batch` carries a receipt with a name and an http(s) url; it never re-reads the line or fetches the file | **drop** (seat ruling `6281811723`): named in `automated.ref` as dropped at revision 8. `stale.partial` named only this spec, so the whole `stale` block goes. `acceptance[3].verify` no longer says it pins the clause | | same item | (same) | (same) | objectui `e2e/live/grid-parent-rules.spec.ts` (blob `b94e2d63` at both pins) | on the New Invoice dialog's first line, with Qty filled: Qty and Unit Price enabled at draft, disabled at paid with the typed value kept, enabled again at draft. It never asserts the Product cell, which declares the same `readonlyWhen: parent.status == 'paid'` (`invoice.object.ts`) | **narrow**: `acceptance[2].verify` and the ref's note now say Qty and Unit Price only, and name the Product cell as unpinned. `acceptance[0]`'s inline-flag narrowing is left as round 8 wrote it | | `records-forms.master-detail-atomic-save` | `acceptance[0].verify`, `automated.ref` | 5 → 6 | objectui `e2e/live/master-detail.spec.ts` (blob `fdfaf571` at both pins) | on `showcase_project_workspace` ("New Project + Tasks"): the first `POST /api/v1/batch` the save sends opens with the populated `showcase_project` op, and carries one `showcase_task` op whose `project` is `{$ref: 0}`; one child, staged through the per-row form | **narrow, not drop**, by round 6's theme-B rule: the clause is carrier-neutral (master and children in one atomic batch, the child ops referencing the parent), and both tests assert exactly that on their own carrier, so the pin asserts a full clause. The verify now says what it asserts and that this run's two-line invoice payload is read from the network trace. Tier 1 run #24598 scored `acceptance[0]` pass on it (2/2) | | `records-forms.field-type-constraints` | `acceptance[7].verify`, `fixtures.knownGaps[0]` | 1 → 2 | objectui `e2e/live/summary-rollup.spec.ts` (blob `49338c81` at both pins) | two tests on `showcase_project`'s `total_estimate` (SUM) and `task_count` (COUNT) over `showcase_task`, through a direct `/api/v1/batch`; it never reads `showcase_invoice.total` | **correct the verify**: it now says no pin reads `invoice.total`. The spec was never in this item's `automated.ref` (API pins only), so there is nothing to drop there. Bounded in-place fix in the same item: `knownGaps[0]`'s `createColumn ~7354-7368`, a tilde line range no gate reads, became the `sql-driver.ts#createColumn` anchor its own text already named | Run records named in the history rows: #24598 (Tier 1), #24572 (round 1, records-forms) and #24616 (round 2, records-forms). ## Part B — the comma-range line-citation sweep Round 7's report (`6281433885`, `out_of_scope_findings[1]`) named the spelling `file.ts,NNN` / `file.ts,NNN-MMM`. It is the residue of #13786's strip (`cd354fd5e8`): that commit removed only the first range of each colon list (`:A,B` became `,B`), and the line-citation limb reads only the colon spellings, so nothing caught what was left. **Census, re-taken over every string of every `areas/*.json` file** (items and area-level recipes), with a rule wider than round 7's: a comma and 2–5 digits right after a word character, or an extension, a comma, a space and digits. | reading | at BASE `39e93b9ef9` | at head `347186527d` | |:--|--:|--:| | matches | 46 | 6 | | line citations among them | 42 | 2 | | — in round 7's 20 named items | 36 (35 fields) | 0 | | — in area-level recipes (`attachments-storage` ×3, `automation` ×1) | 4 | 0 | | — in `access-security.platform-owner-email-anchor`, held by open PR #24667 | 2 | 2 | | not citations (a count after a file name: `cli.dev-automigrate-policy` history[1], `integration-system.datasource-admin-lifecycle` acceptance[8], `platform-core.settings-hub-roundtrip` history[2], `records-forms.adhoc-filter-sort-builder` stale.partial) | 4 | 4 | | in the excluded items | 0 | 0 | Round 7 counted 21 items; the list it gives has 20, and all 20 hit. The wider rule also found two forms its rule missed: the share-link item's `branch,190-234` and the packaged-automation board's `(529 clone-dialog validation,516 …)`. **How each was re-pointed.** For every citation I found the commit that authored the original colon list (`git log -S`, before `cd354fd5e8`) and read the cited lines there, then found that code at this base. An objectstack citation became a `file#symbol` anchor the shared resolver accepts; a test file is cited by its case or block title; an objectui citation names the component, function or branch in prose, read at the shipped pin `12dc7b47c340`. Where the surrounding words still described the range the strip had removed, the new citation names that code too (for example both auth-manager gates, both code literals of the clone test, both invoice arms). The history rows do not contain any path-qualified anchors or comma ranges, so a later rename never forces an edit to a dated record. **Gate readings** (`node scripts/check-platform-checklist.mjs`): symbol anchors **918/928 at BASE → 949/959 at head**, +31 new anchor occurrences (28 resolve as `declaration`, 3 as `literal`: `#RESOURCE_CONFLICT`, `#RESOURCE_NOT_FOUND`, `#regenerate_backup_codes`), none refused, 18 file floors held; line citations 0 survive. PM hypotheses, measured: 1. **Holds.** `LINE_ANCHOR` needs a colon and `PATHLESS_COLON_CITATION` a colon too, so a comma-range passes today; the count rose by exactly the anchors added and none was refused. Two runs on the way caught my own history text writing a colon-digit range and a path-qualified anchor, and both were reworded before any commit. 2. **Holds, but the measured discriminator is the authoring commit, not the last revision.** Of the 40 re-pointed citations, 34 no longer pointed at their code at BASE or at the shipped pin; 5 still did (useAiSurface.ts, the clone test, the index-drift test, the unique-constraint test's line 141, the showcase apps index); and the memory-driver range never held the comment it quotes, even at the commit that authored it. Diffing each cited file between the item's last revision and BASE answers "unchanged" for 9 of 19 file pairs, including files whose cited lines had already moved before that revision, so that diff alone would have kept stale lines. 3. **Holds.** `grid-file-upload.spec.ts` stays in `attachments-storage.inline-grid-receipt-cells`' `automated.ref`, and `summary-rollup.spec.ts` stays in `records-forms.field-type-matrix`'s `source[4]` and in `master-detail-atomic-save`'s dropped-at-revision-5 note; only the three named items changed. ### Part B mapping (39 fields, 40 citations; the 20 items each carry one revision bump and one history row; the 3 recipes carry neither field) | item | field | old citation | new anchor or prose | read at | |:--|:--|:--|:--|:--| | `access-security.record-view-read-audit` | `acceptance[2].clause` | `read-audit.ts,516-521` | — packages/plugins/plugin-audit/src/read-audit.ts#buildRow writes both as null, for the reason the file header's section 'The row records NO field values, and that is load-bearing' gives) | BASE `39e93b9ef9` | | `access-security.share-link-landing-page` | `fixtures.knownGaps[1]` | `SharedRecordPage.tsx,190-234` | (objectui SharedRecordPage.tsx: the SharedRecordPage component's ai_conversations branch, with its messages-fetch effect and the ChatbotEnhanced readOnly transcript) | objectui `12dc7b47c340` | | `access-security.share-link-landing-page` | `acceptance[1].verify` | `SharedRecordPage.tsx,143-176` | the three round-trips (objectui SharedRecordPage.tsx: the component's password-gate branch, gate === 'password'): | objectui `12dc7b47c340` | | `access-security.share-link-landing-page` | `acceptance[2].verify` | `SharedRecordPage.tsx,178-186` | (objectui SharedRecordPage.tsx: the component's error branch, which renders the message fetchResolve sets for a 404 or a 410) | objectui `12dc7b47c340` | | `access-security.share-link-landing-page` | `source[1]` | `branch,190-234` | ai_conversations branch) | objectui `12dc7b47c340` | | `ai.console-ai-surface-gating` | `source[0]` | `packages/app-shell/src/hooks/useAiSurface.ts,71-88` | objectui packages/app-shell/src/hooks/useAiSurface.ts, its useAiSurfaceEnabled hook (the ONE signal: | objectui `12dc7b47c340` | | `attachments-storage recipe qa-scratch-authz` | `sequence[1].source` | `packages/runtime/src/domains/meta.ts,319` | (packages/runtime/src/domains/meta.ts#handleMetadataRequest, its PUT /metadata/:type/:name save branch, through packages/rest/src/rest-server.ts#metaItemPackageBinding) | BASE `39e93b9ef9` | | `attachments-storage recipe qa-media-constraints` | `why` | `examples/app-showcase/src/ui/actions/index.ts,337` | (examples/app-showcase/src/ui/actions/index.ts#ActionParamGalleryAction, its p_cover and p_attachments params — the dialog-widget/ADR-0059 upload-guard lane) | BASE `39e93b9ef9` | | `attachments-storage recipe qa-media-constraints` | `sequence[1].source` | `meta.ts,319` | (packages/runtime/src/domains/meta.ts#handleMetadataRequest for ?package=) | BASE `39e93b9ef9` | | `attachments-storage.field-accept-maxsize-server-enforced` | `fixtures.requires[1]` | `storage-routes.ts,255` | (packages/services/service-storage/src/storage-routes.ts#registerStorageFamily: its POST /storage/upload/presigned handler persists the pending sys_file row with the declared mimeType and size) | BASE `39e93b9ef9` | | `attachments-storage.field-accept-maxsize-server-enforced` | `fixtures.knownGaps[0]` | `examples/app-showcase/src/ui/actions/index.ts,337` | (examples/app-showcase/src/ui/actions/index.ts#ActionParamGalleryAction, its p_cover and p_attachments params) | BASE `39e93b9ef9` | | `attachments-storage.field-accept-maxsize-server-enforced` | `source[5]` | `examples/app-showcase/src/ui/actions/index.ts,337` | examples/app-showcase/src/ui/actions/index.ts#ActionParamGalleryAction) | BASE `39e93b9ef9` | | `automation recipe qa-flow-status-doors` | `sequence[1].source` | `packages/runtime/src/domains/meta.ts,319` | (packages/runtime/src/domains/meta.ts#handleMetadataRequest, its PUT /metadata/:type/:name save branch, through packages/rest/src/rest-server.ts#metaItemPackageBinding) | BASE `39e93b9ef9` | | `automation.packaged-flow-subflow-disable-refusal` | `source[4]` | `examples/app-showcase/src/automation/flows/index.ts,707,795,816,1322,1339` | examples/app-showcase/src/automation/flows/index.ts#TaskDoneNotifyOwnerFlow + examples/app-showcase/src/automation/flows/index.ts#ProjectClosureFlow + examples/app-showcase/src/automation/flows/index.ts#ReleaseSignoffFlow (the three stock caller→child pairs: each caller's subflow or map node names its child by config.flowName — showcase_notify_owner, showcase_closure_signoff and showcase_one_task_signoff) | BASE `39e93b9ef9` | | `automation.packaged-flow-clone-contract` | `acceptance[3].verify` | `automation-flow-clone.test.ts,439` | Codes pinned in packages/runtime/src/domains/automation-flow-clone.test.ts#RESOURCE_CONFLICT and packages/runtime/src/domains/automation-flow-clone.test.ts#RESOURCE_NOT_FOUND (its same-name 409 cases and its unknown-source 404 case) | BASE `39e93b9ef9` | | `automation.setup-packaged-automation-board` | `acceptance[6].verify` | `PackagedAutomationPage.tsx,529` | (cloneNameValid, which disables the clone dialog's submit button in objectui PackagedAutomationPage.tsx — so | objectui `12dc7b47c340` | | `automation.setup-packaged-automation-board` | `source[1]` | `(529 clone-dialog validation,516` | PackagedAutomationPage.tsx (the clone dialog's validation — cloneNameValid, which disables its submit button — and its role=alert rendering of clone.refusal) | objectui `12dc7b47c340` | | `cli.dev-automigrate-policy` | `source[4]` | `sql-driver-index-drift.test.ts,322-342` | + sql-driver-index-drift.test.ts, its describe block "dev auto-reconcile (autoMigrate: 'safe')" (the pins: | BASE `39e93b9ef9` | | `identity-auth.identity-import-wizard` | `fixtures.knownGaps[1]` | `service-sms/src/sms-plugin.ts,169` | (a log transport is the no-provider fallback: packages/services/service-sms/src/sms-plugin.ts#resolveInitialTransport answers LogSmsTransport, and init logs 'no provider configured') | BASE `39e93b9ef9` | | `identity-auth.identity-import-wizard` | `source[2]` | `auth-manager.ts,4074` | + packages/plugins/plugin-auth/src/auth-manager.ts#isEmailServiceAvailable + packages/plugins/plugin-auth/src/auth-manager.ts#isPhoneOtpDeliverable (the two gates | BASE `39e93b9ef9` | | `identity-auth.two-factor-backup-codes` | `acceptance[3].clause` | `sys-two-factor.object.ts,120` | ('Previous backup codes are now invalid': the resultDialog of the regenerate_backup_codes action, packages/platform-objects/src/identity/sys-two-factor.object.ts#regenerate_backup_codes) | BASE `39e93b9ef9` | | `identity-auth.first-run-owner-bootstrap` | `acceptance[0].verify` | `auth-plugin.ts,1936-1949` | (packages/plugins/plugin-auth/src/auth-plugin.ts#registerAuthRoutes: its ADR-0069 D5 network-gating middleware lets /config and /bootstrap-status through) | BASE `39e93b9ef9` | | `identity-auth.first-run-owner-bootstrap` | `source[6]` | `packages/cli/src/commands/dev.ts,339` | packages/cli/src/commands/dev.ts#seedAdmin (seed-admin defaults ON | BASE `39e93b9ef9` | | `identity-auth.self-signup-gate` | `variants[0]` | `audience-posture.ts,398-402` | invitation carve-out admits — packages/plugins/plugin-auth/src/audience-posture.ts#decideAudienceAdmission, whose pending-invitation branch admits before the posture switch) | BASE `39e93b9ef9` | | `identity-auth.self-signup-gate` | `variants[1]` | `audience-posture.ts,411-421` | +tag irrelevant: packages/plugins/plugin-auth/src/audience-posture.ts#emailDomainAllowed, which the email_domain case of packages/plugins/plugin-auth/src/audience-posture.ts#decideAudienceAdmission asks) | BASE `39e93b9ef9` | | `identity-auth.self-signup-gate` | `source[3]` | `apps/console/src/pages/auth/RegisterPage.tsx,69-88` | objectui apps/console/src/pages/auth/RegisterPage.tsx (the RegisterPage component's getAuthConfig probe, and its effect that bounces to /login when decideSignUpOffer answers 'closed', which is disableSignUp; | objectui `12dc7b47c340` | | `identity-auth.self-signup-gate` | `source[4]` | `apps/console/src/pages/auth/LoginPage.tsx,136,298` | objectui apps/console/src/pages/auth/LoginPage.tsx (the LoginPage component passes registerUrl to LoginFormCard only once the /auth/config read is known and decideSignUpOffer answers 'form', so never under disableSignUp; the signUpDisabled state it used to keep is gone) | objectui `12dc7b47c340` | | `identity-auth.email-verification-loop` | `steps[1]` | `LoginPage.tsx,346,360` | (objectui LoginPage.tsx: the onError of its LoginFormCard) | objectui `12dc7b47c340` | | `identity-auth.email-verification-loop` | `source[6]` | `apps/console/src/pages/auth/LoginPage.tsx,346,360` | objectui apps/console/src/pages/auth/LoginPage.tsx (the onError of its LoginFormCard: EMAIL_NOT_VERIFIED → /verify-email-prompt redirect) + objectui packages/app-shell/src/console/auth/signInRefusalMessages.ts (signInRefusalMessages: the named error copy, which moved there from LoginPage.tsx) | objectui `12dc7b47c340` | | `platform-core.docs-portal-render` | `source[0]` | `AppContent.tsx,176-185` | + AppContent.tsx, its systemRoutes (app-scoped /apps/:packageId/docs | objectui `12dc7b47c340` | | `records-forms.field-unique-enforcement` | `acceptance[2].verify` | `memory-unique-constraint.test.ts,141` | (pins: packages/drivers/driver-memory/src/memory-unique-constraint.test.ts, the cases 'update onto a taken value is refused and the row keeps its old value' and 'a row does not collide with ITSELF — an update that leaves the unique field alone passes') | BASE `39e93b9ef9` | | `records-forms.field-unique-enforcement` | `acceptance[3].verify` | `memory-driver.ts,627-631` | (packages/drivers/driver-memory/src/memory-driver.ts#updateMany 'Prepare and CHECK every row before mutating any of them'; pin) | BASE `39e93b9ef9` | | `records-forms.delete-behavior-matrix` | `source[2]` | `examples/app-showcase/src/data/objects/invoice.object.ts,184` | examples/app-showcase/src/data/objects/invoice.object.ts#InvoiceLine (its invoice master_detail, deleteBehavior 'cascade') + examples/app-showcase/src/data/objects/invoice.object.ts#Invoice (its required account lookup, the required-FK escalation); | BASE `39e93b9ef9` | | `records-forms.field-history-tracking` | `fixtures.requires[1]` | `task.object.ts,60` | (examples/app-showcase/src/data/objects/task.object.ts#Task: its status and priority fields declare trackHistory) | BASE `39e93b9ef9` | | `records-forms.field-history-tracking` | `source[3]` | `task.object.ts,60` | , examples/app-showcase/src/data/objects/task.object.ts#Task (its status and priority fields declare trackHistory) | BASE `39e93b9ef9` | | `search.command-palette-navigation` | `acceptance[0].verify` | `CommandPalette.tsx,391-402` | (objectui CommandPalette.tsx: its flattenNavigation function, and the CommandPalette component's per-type CommandGroup blocks) | objectui `12dc7b47c340` | | `search.command-palette-navigation` | `source[2]` | `examples/app-showcase/src/ui/apps/index.ts,50-65,92-95,102-105` | examples/app-showcase/src/ui/apps/index.ts#ShowcaseApp (the fixture: its navigation's page/object/dashboard/report declarations — the grp_data object leaves, and grp_analytics's command-center page, dashboards and reports — inside nested groups) | BASE `39e93b9ef9` | | `studio-authoring.org-override-registry-gate` | `source[5]` | `ResourceEditPage.tsx, 1311-1323` | ResourceEditPage.tsx (its isArtifactItem test, the artifact-backed heuristic behind the read-only banner; | objectui `12dc7b47c340` | | `studio-authoring.metadata-diagnostics-sweep` | `source[5]` | `DirectoryPage.tsx,242-254; console/AppContent.tsx,982` | + DirectoryPage.tsx (MetadataDirectoryPage: the package-scoped useGlobalDiagnostics sweep drives tile badges; the diagnostics link renders only when diagSummary.total > 0) + console/AppContent.tsx (the AppContent component's metadata/_diagnostics route) | objectui `12dc7b47c340` | Area-level provisioning recipes (`qa-scratch-authz`, `qa-media-constraints`, `qa-flow-status-doors`) carry no `revision` or `history` (README, area-level fixtures), so their four edits are recorded here only. ## Left alone, with reasons - `access-security.platform-owner-email-anchor` `source[5]` / `source[7]`: `env.ts#PLATFORM_OWNER_EMAIL_ENV,200` and `admin-routes.ts#manage_platform_settings,261`, the same residue behind an anchor that #15159 added later. Open PR #24667's head `1e8338d3b669` edits this item (`steps[0]`, `revision`, `history`), so it is out of this claim, although the dispatch's excluded list does not name it. Read at the authoring commit `45397edf53`, `,200` was `resolvePlatformOwnerEmail` and `,261` the `DATASOURCE_ADMIN_CAPABILITY` declaration, which is what a later round should anchor. - The dispatch's excluded items (`records-forms.object-hook-lifecycle`, `cli.migrate-plan-apply-json`, `cli.organization-ownership-ceremony`, `access-security.org-admin-assignment-boundary`, the seven walled-theme items) and `RUNNER.md`: none carries a comma-range citation at BASE, and none was edited. PRs #24714, #24722 and #24732 have merged since the dispatch; their items stay out of this claim. - The four count-after-file-name matches in the census table: they are not line citations, and two sit in dated `history` rows. - `docs/qa/platform-checklist/FOLLOW-UPS.md`: 11 comma-range citations. It is the sweeps' decision register, whose own header says "append, never rewrite", and it is outside the area ledger and this claim's file surface. ## Choice I settled myself: objectui citations in prose, not as `objectui:` anchors The grammar declares a cross-repo `objectui:` form, but this corpus registers no objectui checkout (`crossRepos` is empty), so the gate would report such an anchor as skipped and never judge it. By the four axes: **business need** — a runner needs the code located, and the component and branch names do that at the pin; **long-term fit** — an anchor the gate cannot check looks verified and is not, the "declared ≠ enforced" shape; **AI-error prevention** — a later agent would read a `#symbol` as gate-checked; **no scope creep** — the cross-repo form would invite registering a checkout, a new gate surface nobody asked for. Round 7 re-pointed its objectui comma-range (`ExternalDatasourcePanel.tsx`) in prose too. Each objectui symbol named here was read at `12dc7b47c340`. ## Acceptance notes (noted, not filed) - Residues of the same #13786 strip that carry no number, and so do not rot: parentheticals left as `(…), (…)` once their colon prefixes went, for example `automation.packaged-flow-subflow-disable-refusal` `source[1]` and `search.command-palette-navigation` `source[0]`. They point at no line; a reader loses only the position. carrier: #24579. - `records-forms` carries prose line numbers ("line 124", "lines …") in the permission-zoo text of one item; no gate reads them. carrier: #24579. - `git merge-tree` against PR #24681's head exits 1 with three conflicts (`content/docs/deployment/cli.mdx`, `organization-ownership-boot-guard.ts` and its test). The same three come from `origin/main` against that head alone, with none of this branch's commits, and `cli.json` auto-merges; the conflict is between #24681 and `main`. ## Merge-tree against the open PRs that hold excluded items Measured at head `347186527d` (`origin/main` was still `39e93b9ef9`): | PR | head | `git merge-tree --write-tree` exit | conflicts | |:--|:--|--:|:--| | #24731 | `aaec01614444` | 0 | none | | #24681 | `edb2db1e82b8` | 1 | 3, all between #24681 and `main` (control leg: `origin/main` against that head gives the same 3) | | #24667 | `1e8338d3b669` | 0 | none | ## Gates Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths; 10 paths vs merge base `39e93b9ef`): 13 families. All 13 ran on head `347186527d`, each exit captured before any pipe, all exit 0: `node scripts/check-ci-filter-parity.mjs`; `node scripts/check-closing-keyword-parity.mjs`; `node scripts/check-closing-keyword-parity.mjs --self-test`; `node scripts/check-comment-mask-corpus.mjs`; `pnpm --filter @objectstack/lint run check:doc-formula-expressions`; `pnpm check:cross-package-test-inputs`; `pnpm check:doc-authoring`; `pnpm check:driver-memory-census`; `pnpm check:gitlink-declared`; `pnpm check:nul-bytes`; `pnpm check:platform-checklist`; `pnpm check:refd-timer-probe`; `pnpm check:watch-hint-literal`. `--ran`: "13 derived famil(ies) accounted for — 13 run, 0 NOT-MEASURED". `check:doc-formula-expressions` first answered exit 3 (its prerequisite: `@objectstack/lint` unbuilt) and exit 0 after `bash scripts/pm/os-verify-lock.sh -c 'pnpm exec turbo run build --concurrency=2 --filter=@objectstack/formula --filter=@objectstack/lint'` ("VERDICT command-exit 0"). `check:platform-checklist` runs in no per-PR CI, so the local reading is its evidence: "OK — 16 areas, 328 items (323 active, 1 planned)", symbol anchors 949/959. Editing discipline: every area-file edit went through round 6's span-recording textual editor (`span-edit.mjs`, md5 `25b593a1721e1baaadd973794394f622`), extended with a `replace` op that pins its prior text (exactly one occurrence) and with recipe paths outside `items`. It never re-serialises a file, and it asserts that the new document equals the old with the declared ops applied, that every other item and top-level key is value-equal, that the changed leaf paths are the declared fields plus `revision` and one new `history` row, and that the text outside the edited spans is byte-identical. No changeset: `docs/qa/**` ships in no package (the root package is private and no `files[]` names it), so the PR carries `skip-changeset`. --- _Generated by [Claude Code](https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7a7a435 commit 4e0f5f5

10 files changed

Lines changed: 163 additions & 89 deletions

File tree

‎docs/qa/platform-checklist/areas/access-security.json‎

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3070,7 +3070,7 @@
30703070
"title": "Opt-in record-view auditing writes `read` rows for exactly the opted-in objects' detail reads, batched off the request path — stock boot audits nothing, excluded names refuse loudly, values never copied",
30713071
"since": "v17",
30723072
"status": "active",
3073-
"revision": 2,
3073+
"revision": 3,
30743074
"priority": "P2",
30753075
"surface": "api",
30763076
"personas": [
@@ -3109,7 +3109,7 @@
31093109
"evidence": "log line + the ledger rows before/after each read"
31103110
},
31113111
{
3112-
"clause": "the row records WHO/WHICH/WHEN and nothing else: action 'read', user_id = M, object_name/record_id correct, old_value AND new_value both null (the afterFind hook sees PRE-MASK plaintext, so copying values would mint a plaintext copy of exactly what FLS withholds — read-audit.ts,516-521), and created_at is the VIEW instant, not the flush instant",
3112+
"clause": "the row records WHO/WHICH/WHEN and nothing else: action 'read', user_id = M, object_name/record_id correct, old_value AND new_value both null (the afterFind hook sees PRE-MASK plaintext, so copying values would mint a plaintext copy of exactly what FLS withholds — packages/plugins/plugin-audit/src/read-audit.ts#buildRow writes both as null, for the reason the file header's section 'The row records NO field values, and that is load-bearing' gives), and created_at is the VIEW instant, not the flush instant",
31133113
"oracle": "api",
31143114
"verify": "field-by-field read of the landed row; created_at within the read's wall-clock window, not the flush's",
31153115
"evidence": "the row + the recorded clocks"
@@ -3173,6 +3173,12 @@
31733173
"date": "2026-10-17",
31743174
"change": "re-measured on main c91af8ed6 (checklist-accuracy finding from #23878): the stock leg's install-line grep was vacuous on a default boot, because the line logs at INFO and os dev defaults to warn, so steps[0], the fixture and clause 0's verify now boot at OS_LOG_LEVEL=info with an INFO-line positive control. The stock specimen GET /data/showcase_contact/ID is refused to a baseline member at the object gate (403), so the zero proved nothing either; the step now reads a showcase_account detail (200). knownGaps[0] gains the scripted scratch-copy recipe for the configured half, and steps 1 and 3 name X = showcase_account and the un-opted showcase_project. Re-run: stock boot at info (649 INFO lines), plain member detail reads of showcase_project, showcase_task and showcase_account all 200, action 'read' rows 0, no install line. Configured scratch copy: 'Plugin superseded: com.objectstack.audit' WARN, the sys_job exclusion WARN and 'AuditPlugin: record-view auditing installed on 1 object(s) — showcase_account'; member M's account detail read → 1 row (action read, user_id M, object_name showcase_account, record_id correct, old_value and new_value null, created_at 00:15:23.489Z inside the 23.385–23.497 read window, about 4 s before the flush read); the list read and the showcase_project detail read left the count at 1; the admin's detail read made it 2",
31753175
"ref": "#24036"
3176+
},
3177+
{
3178+
"revision": 3,
3179+
"date": "2026-10-20",
3180+
"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]). acceptance[2].clause cited lines 516-521 of read-audit.ts in the comma-range spelling, the second range of the two-range list 45397edf53 authored (55-75 and 516-521). At that commit lines 516-521 were the audit row's old_value / new_value nulls; at this base they hold a docblock, and the nulls sit in the buildRow closure of installReadAuditWriter. The clause now anchors read-audit.ts#buildRow and names the header section that gives the reason in prose. 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",
3181+
"ref": "#24752"
31763182
}
31773183
]
31783184
},
@@ -3181,7 +3187,7 @@
31813187
"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",
31823188
"since": "v16",
31833189
"status": "active",
3184-
"revision": 3,
3190+
"revision": 4,
31853191
"priority": "P2",
31863192
"surface": "browser",
31873193
"personas": [
@@ -3197,7 +3203,7 @@
31973203
],
31983204
"knownGaps": [
31993205
"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",
3200-
"the ai_conversations transcript branch (SharedRecordPage.tsx,190-234 — messages fetch + ChatbotEnhanced readOnly) 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",
3206+
"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",
32013207
"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 (this repo's .objectui-sha when revision 3 was written; re-read it at whatever pin the run boots): 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]"
32023208
]
32033209
},
@@ -3219,13 +3225,13 @@
32193225
{
32203226
"clause": "the password gate renders as designed and never leaks early: 401 → the 'Password required' prompt; wrong password → re-prompt with 'Wrong password.'; correct password → the record renders — and NO 401 response body carries the record",
32213227
"oracle": "network",
3222-
"verify": "the three round-trips (SharedRecordPage.tsx,143-176): two 401s with record-free bodies, then the 200 render; drive the input with native setter + events, not coordinate typing",
3228+
"verify": "the three round-trips (objectui SharedRecordPage.tsx: the component's password-gate branch, gate === 'password'): two 401s with record-free bodies, then the 200 render; drive the input with native setter + events, not coordinate typing",
32233229
"evidence": "the three network traces + screenshots of prompt and render"
32243230
},
32253231
{
32263232
"clause": "dead links refuse with designed, non-leaking states: revoked and expired render 'This link has expired or was revoked.' (410), a garbage token renders 'This link is invalid or no longer available.' (404) — never a blank page, a spinner that never settles, or a raw stack/JSON error, and no record data in any refusal body",
32273233
"oracle": "screenshot",
3228-
"verify": "each terminal state's screenshot shows the designed message (SharedRecordPage.tsx,178-186); the paired network bodies are record-free",
3234+
"verify": "each terminal state's screenshot shows the designed message (objectui SharedRecordPage.tsx: the component's error branch, which renders the message fetchResolve sets for a 404 or a 410); the paired network bodies are record-free",
32293235
"evidence": "screenshots + the 404/410 bodies"
32303236
},
32313237
{
@@ -3267,7 +3273,7 @@
32673273
},
32683274
"source": [
32693275
"objectui apps/console/src/App.tsx (the /s/:token route, mounted outside the authenticated shell)",
3270-
"objectui apps/console/src/pages/SharedRecordPage.tsx (status folds, password form, error states, generic record render + hidden-fields notice, ai_conversations branch,190-234)",
3276+
"objectui apps/console/src/pages/SharedRecordPage.tsx (status folds, password form, error states, generic record render + hidden-fields notice, ai_conversations branch)",
32713277
"objectui apps/console/src/pages/shared-record-shape.ts (normalizeResolvedShare — both envelopes, the #3983 rename)",
32723278
"cross-ref access-security.share-link-capability-tokens — the API half: mint/resolve/redaction/password/audience/revoke semantics and the #9308 showcase_client_brief fixture are asserted THERE; this item asserts only what the console renders of them",
32733279
"#9308 (the stock fixture), objectstack#3983 (the enveloped-branch rename regression the notice clause guards)"
@@ -3290,6 +3296,12 @@
32903296
"date": "2026-10-16",
32913297
"change": "knownGaps[2] stopped naming objectui 1fb1ced36243 as 'still this repo's .objectui-sha' (#23878 checklist-accuracy finding). The pin moved to daeb77fc84dd and then to f8267df93aba; at f8267df93aba SharedRecordPage.tsx still reads a 401 by its code through shared-record-shape.ts resolveGateOf, renders the 'Sign in required' state for SIGN_IN_REQUIRED, and both objectui pins (SharedRecordPage-11649.test.tsx, shared-record-shape.test.ts) are present. The revision 2 history row stays as written: it was true at its pin. Wording only, no re-run owed",
32923298
"ref": "#24036"
3299+
},
3300+
{
3301+
"revision": 4,
3302+
"date": "2026-10-20",
3303+
"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]). fixtures.knownGaps[1], acceptance[1].verify, acceptance[2].verify and source[1] cited objectui SharedRecordPage.tsx by the line ranges 190-234 (twice), 143-176 and 178-186 in the comma-range spelling (authored in 45397edf53). At the objectui pin that commit shipped, 9602dc820450, those lines were the ai_conversations branch, the password form and the error state; at the shipped pin 12dc7b47c340 all three have moved down, below a sign-in gate and a reworked fetchResolve, and the password branch now reads gate === 'password'. Each now names its branch of the SharedRecordPage component. 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",
3304+
"ref": "#24752"
32933305
}
32943306
]
32953307
},

‎docs/qa/platform-checklist/areas/ai.json‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,7 @@
631631
"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)",
632632
"since": "v16",
633633
"status": "active",
634-
"revision": 4,
634+
"revision": 5,
635635
"priority": "P2",
636636
"surface": "browser",
637637
"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)"],
@@ -705,7 +705,7 @@
705705
],
706706
"traps": ["hydration-race", "stale-console-bundle"],
707707
"source": [
708-
"objectui packages/app-shell/src/hooks/useAiSurface.ts,71-88 (the ONE signal: access-filtered GET /ai/agents, per-seat rationale, isLoading latch for the route guard; do-NOT-simplify-to-discovery warning)",
708+
"objectui packages/app-shell/src/hooks/useAiSurface.ts, its useAiSurfaceEnabled hook (the ONE signal: access-filtered GET /ai/agents, per-seat rationale, isLoading latch for the route guard; do-NOT-simplify-to-discovery warning)",
709709
"objectui packages/app-shell/src/layout/ConsoleLayout.tsx (FAB + chat dock + ⌘⇧I all gated on showChatbot/dockEnabled)",
710710
"objectui packages/app-shell/src/layout/AppHeader.tsx (top-bar AI entry gated on the same hook) + console/home/HomeLayout.tsx (Home CTAs)",
711711
"objectui packages/app-shell/src/console/ConsoleShell.tsx (RequireAiSurface — waits for resolve, splash-preserving redirect, objectui#6507)",
@@ -718,7 +718,8 @@
718718
{ "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" },
719719
{ "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" },
720720
{ "revision": 3, "date": "2026-10-06", "change": "partial-coverage gap of QA run #21782 parked on #22017 (row 9). A2's entitled side was blocked(fixture) as the clause prescribed. Disposition: recorded knownGap. The side cannot be reached in the open framework by design (service-ai is Cloud/Enterprise). The knownGap names the environment it needs, says CI lacks it, and reclassifies the verdict blocked(environment); A2's clause follows. It also names the objectui hook pins as mechanism-only", "ref": "#22017" },
721-
{ "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" }
721+
{ "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" },
722+
{ "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" }
722723
]
723724
},
724725
{

0 commit comments

Comments
 (0)