Repository navigation
docs(qa): unblock 4 fixture-blocked checklist items and re-point the clauses run 21845 found mis-asserted - #21891
Merged
objectstack-fleet[bot] merged 2 commits intoOct 5, 2026
Conversation
…he recipes run 21845 proved Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv
…un 21845 found mis-asserted Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv
objectstack-fleet
Bot
deleted the
claude/issue-21851-checklist-unblock-followup
branch
October 5, 2026 15:53
This was referenced Oct 5, 2026
This was referenced Oct 6, 2026
Closed
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Oct 7, 2026
…ecision in words instead of a tracker number (stage 20) (objectstack-ai#21895) Part of objectstack-ai#20749 Clause-②: no Stage 20 of this card: the next area of class (e), the test strings shipped under `packages/spec/src`, as ruled in `5902360492` on objectstack-ai#20513. This stage takes the first name-ordered `ui/` group: the 32 test files directly under `packages/spec/src/ui/` from `action-confirm-params-guard.test.ts` to `component-record-block-field-security.test.ts`. Those files carried 97 messages and 102 tracker ids, citing 65 records. 101 of those ids now either state what their record decided, in words (form D), or are dropped where the title already says it. One id stays, because an assertion in the same file reads it (below). Text only: no assertion, identifier, test count or code comment changes, and no file is renamed. ## Census at the base (`e085a8c3be`) Instruments: `census10.cjs` (md5 `9d08602ab972b4b8643c90d64d40fa41`), `census.cjs` (md5 `6e42a45a926d375013c32d62f16a296e`), `census-wide.cjs` (md5 `c98410a19529c439adb0afbfb00026a2`) and `dirtable.cjs` (md5 `dda605c54745b4a60cc14c9a686e4eff`), byte-identical to the copies stages 10 to 19 used. A literal counts as a test title when its folded message is argument 0 of a `describe` / `it` / `test` call, `.each` / `.skip` / `.only` chains included. Everything else is an "other" string. The base is `e085a8c3be`, stage 19's landing and the claim's base. Both instruments read **860 messages / 908 ids in 190 files**, the seat's reading and stage 19's head reading. | directory | files | messages / ids | titles | other | |:--|--:|--:|--:|--:| | `ui/` (this PR: 32 of the 84 files) | 84 | 396 / 419 | 378 / 401 | 18 / 18 | | `api/` | 40 | 189 / 201 | 181 / 193 | 8 / 8 | | `system/` | 34 | 154 / 165 | 128 / 138 | 26 / 27 | | (files directly in `src/`) | 30 | 118 / 120 | 117 / 119 | 1 / 1 | | `ai/` | 1 | 2 / 2 | 0 | 2 / 2 | | `contracts/` | 1 | 1 / 1 | 0 | 1 / 1 | | **total** | **190** | **860 / 908** | **804 / 851** | **56 / 57** | The group reads **97 messages / 102 ids in 32 files**, the seat's figures file for file: | file (under `ui/`) | messages / ids | titles | other | |:--|--:|--:|--:| | `action-confirm-params-guard.test.ts` | 4 / 4 | 4 / 4 | 0 | | `action-description.test.ts` | 3 / 3 | 3 / 3 | 0 | | `action-dispatch-contract.test.ts` | 4 / 4 | 4 / 4 | 0 | | `action-doubled-redirect.test.ts` | 1 / 1 | 1 / 1 | 0 | | `action-newtaburl-pair.test.ts` | 1 / 1 | 1 / 1 | 0 | | `action-on-success.test.ts` | 3 / 4 | 3 / 4 | 0 | | `action-param-carryover.test.ts` | 1 / 2 | 1 / 2 | 0 | | `action-param-default-value.test.ts` | 3 / 3 | 3 / 3 | 0 | | `action-params.test.ts` | 9 / 9 | 9 / 9 | 0 | | `action-requires-confirmation-docblock.pin.test.ts` | 5 / 6 | 3 / 4 | 2 / 2 | | `action-row-update.test.ts` | 5 / 5 | 5 / 5 | 0 | | `action.test.ts` | 12 / 12 | 12 / 12 | 0 | | `app-nav-expanded-alias.test.ts` | 1 / 1 | 1 / 1 | 0 | | `app-nav-target-exclusivity-export.test.ts` | 1 / 1 | 1 / 1 | 0 | | `app-strictness-batch19.test.ts` | 1 / 1 | 1 / 1 | 0 | | `app.test.ts` | 9 / 9 | 9 / 9 | 0 | | `aria-carrier-tombstones.test.ts` | 1 / 1 | 1 / 1 | 0 | | `assembled-view-artifact-type.test.ts` | 1 / 1 | 1 / 1 | 0 | | `assembled-views.test.ts` | 1 / 1 | 1 / 1 | 0 | | `bulk-action.test.ts` | 4 / 4 | 4 / 4 | 0 | | `calendar-config-allday-prescription-17054.test.ts` | 2 / 2 | 2 / 2 | 0 | | `chart-aggregate.test.ts` | 1 / 1 | 1 / 1 | 0 | | `chart.test.ts` | 7 / 9 | 7 / 9 | 0 | | `component-action-element-rows-20371.test.ts` | 1 / 1 | 1 / 1 | 0 | | `component-action-row-endpoint-21005.test.ts` | 1 / 1 | 1 / 1 | 0 | | `component-element-navigation-17987.test.ts` | 4 / 4 | 4 / 4 | 0 | | `component-form-custom-fields-sections-typed.pin.test.ts` | 1 / 1 | 1 / 1 | 0 | | `component-object-grid-default-filters.pin.test.ts` | 1 / 1 | 1 / 1 | 0 | | `component-object-grid-export-options-members.pin.test.ts` | 1 / 1 | 1 / 1 | 0 | | `component-object-grid-pagination-accept-set.pin.test.ts` | 1 / 1 | 1 / 1 | 0 | | `component-props-unknown-members.pin.test.ts` | 2 / 2 | 1 / 1 | 1 / 1 | | `component-record-block-field-security.test.ts` | 5 / 5 | 5 / 5 | 0 | | **32 files** | **97 / 102** | **94 / 99** | **3 / 3** | The three "other" strings are `action-requires-confirmation-docblock.pin.test.ts:168` and `:175` (two `expect` messages) and `component-props-unknown-members.pin.test.ts:322`. The claim calls the third one an `expect` message too; it is the `ruling` value of a ledger entry. 12 more test files in the same name range carry no id and are not touched. - **Controls.** Lit: `ui/component.test.ts`, outside the group, reads 70 ids at the base and at the head. Dark: `action-requires-confirmation-docblock.pin.test.ts` reads 0 at the head while 4 of its comment lines still carry a number. Planted in scratch copies of head files: an id put into an `app-nav-expanded-alias.test.ts` title reads 1 / 1, and an id put into a `bulk-action.test.ts` comment reads 0. - **A wider pattern** (any `#` plus digits) reads the same as the gate pattern in 29 of the 32 files at the base. The other three differ only by 16 hex colour literals (`'#0070F3'` in `app.test.ts`, `'#8B5CF6'` in `bulk-action.test.ts`, the `colors` arrays in `chart.test.ts`), which this PR does not touch. - **At the head:** 764 messages / 807 ids in 159 files. The 32 files read 1 / 1 (the kept `:322`), `ui/` reads 300 / 318, and no other file moved. ## How the area was chosen `ui/` has no subdirectory, so it is taken like `data/`, in name-ordered file groups near the ~100-id bound. Stage 19's re-cut named this group at 102 ids, and this census reads 102, so no re-cut was needed. **Named for the next stages** (cut from the head census, 764 / 807): - `ui/` 318 ids in 53 files, about three stages. The next group nearest 100 runs from `component-record-blocks.test.ts` to `dashboard.test.ts`: 7 files, 103 messages / 109 ids (98 titles / 103 ids, 5 other). `component.test.ts` alone carries 70. Cutting one file earlier gives 88. That group holds five "other" strings: `dashboard-chart-structure-refusal.test.ts:94` (two ids) and `dashboard.test.ts:124` (two ids), which read like placeholders (`objectstack-ai#111`, `objectstack-ai#222`), and `dashboard.test.ts:205` (objectstack-ai#5022). - `api/` 201, two stages. `system/` 165, two. The files directly in `src/`, 120, one. - The three docblock needles (`ai/build-progress.test.ts` ×2, `contracts/approval-service.test.ts` ×1), one stage, with an at-tier review. The id this PR keeps (`component-props-unknown-members.pin.test.ts:322`) fits there too. ## The three "other" strings: two rewritten, one kept - **`action-requires-confirmation-docblock.pin.test.ts:168` and `:175` are not needles.** Each one is the failure message (the second argument of `expect(value, message)`) of an assertion whose expected value carries no id: `.toEqual([])` over the docblock's positive `confirmText` claims, and `.not.toMatch(/confirmText/)` over the classifier's body. Nothing compares the message text. Both are rewritten and declared to the text-only tool. - **`component-props-unknown-members.pin.test.ts:322` is kept.** Its `ruling: 'decision card objectstack-ai#21704, fork 4, letter B (record 5979239990)'` is the expected value of the assertion at `:417` in the same file, `expect(reason.kind === 'opaque' && reason.ruling, key).toMatch(/objectstack-ai#21704/)`, and the `ruling` slot is typed as naming the record that holds the member (`:188`). Removing the id turns `:417` red, and moving that regex would change assertion text, which this stage does not do. It is not a docblock needle, since it reads a value in its own file, but it is held the same way. It is reported for the needle stage. ## What each id became - **36 literals (41 ids)** now state a decision in words. - **13 literals (13 ids)** get their subject back in words, where the number stood for a thing, such as "the objectstack-ai#7428 pair rule". - **47 literals (47 ids)** drop a number the title already explains. Every cited record was read with its comments through REST: 62 answer 200 and 3 answer 404. Three citations are cross-repo (`objectui#3139`, `objectui#3382`, `objectstack-ai/objectui#11610`); all three were read from objectui and answer 200. Where a record answers 404, or answers 200 without the decision, the decision was read from what landed: - **objectstack-ai#11753** (404): its ruling, recommendation A, is quoted in its spec-half card objectstack-ai#11992. - **objectstack-ai#17987** (404): the landing commit `e233db9dbb` ("declare element-level `navigation` on object-kanban / object-calendar and give object-timeline its ComponentPropsMap row"), executing objectui#8652's ruling B. - **objectstack-ai#18177** (404): the landing commit `adabccf5fb` ("BulkActionParamSchema is strict and declares dependsOn"), decision batch objectstack-ai#146 item 4, letter A. - **objectstack-ai#3896** (200): the record is a sharing-rule REST defect, closed with no comment. The "close-out" the title names is what landed under that citation: the `action-inert-keys-removed` conversion in `conversions/registry.ts` ("capability claims nothing enforced") and the `shortcut` / `bulkEnabled` tombstones in `ui/action.zod.ts`. - **objectstack-ai#3701** (200, closed with no comment): the convention as `ui/chart-aggregate.ts` writes it down. **Stated in words:** | record(s) | literal (under `ui/`) | now reads | the decision | |:--|:--|:--|:--| | objectstack-ai#7278 | `action-confirm-params-guard.test.ts:148` | "… — the one-dialog shape the confirm question migrated TO" | Maintainer option 1: drop `confirmText` and carry the question as the param dialog's description, one decision in one dialog. | | objectstack-ai#7367 | `action-description.test.ts:35` | "ActionSchema.description — the line the param dialog shows" | `description` joins the action contract, shaped like `label`, as the param dialog's description line. | | objectstack-ai#17319 | `action-dispatch-contract.test.ts:43`, `:77` | "… (and still true: the declared dispatch contract is an authoring key)"; "an action's dispatch contract speaks `bulkActionDefs`' own vocabulary" | Ruling A: an action declares its bulk dispatch contract in the bulk def's own `execution` vocabulary, and a view that wires it the other way is refused at validate. | | objectstack-ai#11519 | `action-doubled-redirect.test.ts:24` | "ActionSchema — doubled post-success navigation is refused, with no precedence field" | Refuse the doubled channel; no `precedence` field. | | objectstack-ai#9566, objectstack-ai#9474 | `action-on-success.test.ts:13` | "ActionSchema.onSuccess — one closed post-success navigation key for api and script actions" | Ruled together: one closed `onSuccess` key (`navigate` + `openIn`) for both types. | | objectstack-ai#4352 | `action-on-success.test.ts:141`; `action.test.ts:1365` | "type scope — api and script only, refused on any other type, never silently ignored"; "… the publish gate resolves to it, so a `body` off `script` is refused at publish" | Outlet 1: the runtime follows the spec, and contradictory type-scoped metadata errors at publish instead of being ignored. | | objectstack-ai#11992, objectstack-ai#11753 | `action-param-carryover.test.ts:17` | "ActionParamSchema.carryOver — seeded from the row, shown read-only, submitted verbatim" | Recommendation A on objectstack-ai#11753, executed by objectstack-ai#11992. | | objectstack-ai#20740 | `action-param-default-value.test.ts:250` | "… refuses a zone-suffixed `time` value — a time of day is a zone-less wall clock" | `ClockTimeValueSchema` narrowed to the zone-less wall clock. | | objectstack-ai#5568 | `action-params.test.ts:139` | "names `_selectedIds` when the caller sent `selectedIds` — the declared channel for a selection" | Verified and closed: `params._selectedIds` is the working declared channel; the reported gap was not one. | | objectstack-ai#5613 | `action-params.test.ts:294` | "accepts the DUAL-EMIT shape the runtime emits through the rename window — …" | Contract first, then `roles` → `positions` with a deprecation window in which the runtime emits both keys. | | objectstack-ai#7828, objectstack-ai#13865 | `action-requires-confirmation-docblock.pin.test.ts:128`, `:150`, `:168`, `:172`, `:175` | "… docblock names only the declared signals the classifier reads"; "would flag the retired sentence …"; "… re-seeds the retired leg — only declared semantics classify" (message); "… still does not read `confirmText` — only declared semantics classify"; "… reopen the declared-semantics ruling before the docblock …" (message) | objectstack-ai#7828 option A: `actionLooksDestructive` classifies on declared semantics only (`mode`, `variant`), never on UI copy. objectstack-ai#13865 retired the docblock sentence that still named `confirmText`. | | objectstack-ai#3405 | `action.test.ts:85`, `:141` | "inline lookup reference target — `reference`, the FieldSchema spelling"; "… at `reference`, the one key an author writes" | `ActionParamSchema` gains `reference`, named as `FieldSchema.reference` so the spelling authors already write is the legal one. | | objectstack-ai#15811 | `action.test.ts:217` | "… at the SLOT, which needs a `source` to evaluate, not at the lowering" | Ruling A: every engine-evaluated expression slot requires a non-blank `source`. | | objectstack-ai#20323 | `action.test.ts:1116` | "Action ARIA Integration (retired — no action surface ever read it)" | Retire `action.aria`: no action surface reads it. | | objectstack-ai#6888 | `action.test.ts:1513` | "`global_nav` is retired — it rendered nowhere" | Direction 2: retire `global_nav`, which rendered nowhere in the running app. | | objectstack-ai#3896 | `action.test.ts:1612` | "audit close-out — retired shortcut/bulkEnabled, capability claims nothing enforced" | Read from what landed (above). | | objectstack-ai#5016 | `action.test.ts:1650` | "action param option vocabulary — declared only where a renderer delivers it" | Ruling B, on condition that it lands with the renderer and after a per-key liveness audit; `visibleWhen` is declared, `color` / `default` / `icon` / `disabled` stay refused. | | objectstack-ai#4001 | `app-strictness-batch19.test.ts:99` | "批 19, unknown keys refused — the `verify` check …" | Every authorable surface goes strict; spelled as stage 18 spelled the 批 20 titles. | | objectstack-ai#5320 | `assembled-views.test.ts:51` | "AssembledViewArtifactSchema — the declared home for non-container view artifacts" | Fork ruling B: a declared, portable home for non-container view artifacts. | | objectstack-ai#4457 | `bulk-action.test.ts:20` | "BulkActionDefSchema — the def shape is typed, not `z.any()`" | Type the def that was `z.record(z.any())`. | | objectstack-ai#17054 | `calendar-config-allday-prescription-17054.test.ts:106` | "what declaring `allDayField` did NOT open" | Ruling A: `CalendarConfigSchema` declares `allDayField`. | | objectstack-ai#3701 | `chart-aggregate.test.ts:45` | "result-column naming convention — rows keyed by the raw field names" | Read from what landed (above). | | objectstack-ai#17751 | `chart.test.ts:271` | "Chart ARIA Integration — retired, no renderer ever applied it" | Retire `ChartConfigSchema.aria`. | | objectstack-ai#4001, objectstack-ai#5583 | `chart.test.ts:476` | "批 15 — the two chart sites left open on a measurement, since CLOSED as strict objects" | objectstack-ai#5583: both schemas became strict objects. | | objectstack-ai#5022 | `chart.test.ts:631` | "ChartDrillDownSchema — the honest subset, every key one ObjectChart reads" | Declare `drillDown` with only the keys `ObjectChart` reads. | | `objectui#3382`, objectstack-ai#5435 | `chart.test.ts:721` | "target: 'navigate' is ACCEPTED — the chart renderer delivers it now" | `ObjectChart` gained `'navigate'`, so the union gained the member. | | `objectui#11610` | `component-form-custom-fields-sections-typed.pin.test.ts:496` | "§5 the grid widget's eight field-level keys, camelCase since objectui renamed them" | objectui renamed the eight keys to camelCase with no dual read. | | objectstack-ai#19046 | `component-object-grid-pagination-accept-set.pin.test.ts:172` | "both arms refuse a … pageSize — the view and component arms no longer disagree" | The grid component arm refuses the page sizes the view arm refuses. | | objectstack-ai#18159 | `component-record-block-field-security.test.ts:174` | "`requiredPermissions` is declared on the three blocks as a capability set (instruments A and B)" | Ruling A: the key follows the objectstack-ai#19186 ruling, an ADR-0066 capability set. | **Subject back in words** (13 literals): "objectstack-ai#7428 —" becomes "the `confirmText` + `params` guard" or "the pair guard" (`action-confirm-params-guard.test.ts:130`, `:171`), and "the objectstack-ai#7428 pair rule" becomes "the `confirmText` + `params` pair rule" (`action-row-update.test.ts:280`); "the surfaces objectstack-ai#7367 …" becomes "the surfaces the action `description` key …"; two `objectstack-ai#17319 —` prefixes become "`execution` on an action" and "dispatch-contract default"; "the pre-existing probes (objectstack-ai#9474)" becomes "the spellings tried before `onSuccess` existed"; "the bare filter objectstack-ai#14175 declared" becomes "the bare filter the facade was first typed with"; "objectstack-ai#14092 — boundaries" becomes "the declarative row update — boundaries"; "objectstack-ai#17631's shape" becomes "the never-parsing gate it once built"; "§6 objectstack-ai#21464 is closed out" becomes "§6 the `z.unknown()` member sweep is closed out"; and two `objectstack-ai#18159 —` prefixes become "the three record blocks" and "the field-security pair". **Dropped where already stated** (47 literals, 47 ids). A number goes only where the title already says its decision. Examples: "ActionSchema — newTabUrl requires opensInNewTab: true (objectstack-ai#11842)"; "ActionSchema — the `execute` alias is REMOVED (objectstack-ai#3855)"; the `objectstack-ai#15124 —`, `objectstack-ai#15117 —` and `objectstack-ai#5779 —` prefixes, each in front of the rule it names; the four `[objectstack-ai#17987]` prefixes on `component-element-navigation-17987.test.ts`; "— the shape is closed, so its accept means something (objectstack-ai#18177)"; "retired fail-open area gates (objectstack-ai#4651)". `(ADR-0049)`, `(ADR-0066 D4)` and `(ADR-0078)` stay: they cite decision records by number, not tracker ids. **No file is renamed.** Four file names carry a number (`calendar-config-allday-prescription-17054`, `component-action-element-rows-20371`, `component-action-row-endpoint-21005`, `component-element-navigation-17987`); they are not this card's. ## Readers - **Test-name filters:** none. No tracked script, workflow or package config passes `-t` / `--testNamePattern`. - **Snapshots:** none. No `__snapshots__` directory is tracked under `packages/spec`, and none of the 32 files calls a snapshot matcher. - **Projects:** one of the 32 files runs in the `repo` project: `action-requires-confirmation-docblock.pin.test.ts` is listed in `packages/spec/vitest.repo-tests.json`, because it reads `packages/runtime/src/action-execution.ts`. The other 31 run in `local`. - **By substring:** every old literal, plus a window around each id (290 needles), was searched with `git grep` at the base, across the tracked tree outside its own file. No gate, doc, filter, snapshot or `scripts/check-*.mjs` self-test reads one. The 11 hits: - 7 code comments in `ui/component.zod.ts` citing `objectstack-ai/objectui#11610`, and one in `ai/agent.test.ts:193` ("objectstack-ai#3896 close-out"); - one release-owned line, `content/docs/releases/v17/17-0.mdx:326`; - one sibling title in `runtime` (`action-engine-facade-find-envelope.test.ts:80`, "objectstack-ai#15124 — …"). None reads a spec test title. - **The files by name:** a few gates and ledgers name group files by path, never by title: `scripts/check-parse-guard.mjs` reads a code line of `app.test.ts`, `packages/spec/test-typecheck-debt.json` keys `app.test.ts` and `chart.test.ts` on error signatures, and `vitest.repo-tests.json` lists the docblock pin. ## Text-only proof Stage 10's scratch tool (`textonly10.cjs`, md5 `d5e4801dbb4329ab1984da91e92fc47c`) compares base and head file by file on three legs: 1. **Skeleton:** the full AST, with string pieces masked. It must be identical. 2. **Comments:** every comment, byte-equal. 3. **Strings:** each changed string leaf must sit in a test-call title position or on a declared line, must carry a tracker id before, and must carry no `#` plus digits after. This stage declares two lines, `action-requires-confirmation-docblock.pin.test.ts:168` and `:175`. - **Result:** 32 of 32 files SAME on all three legs, with the per-file counts predicted in writing before the run. - **Totals:** 96 changed string leaves in 96 literals: 94 titles and 2 declared. The diff's `+` and `-` lines are exactly the 96 planned lines, and every file keeps its line count. - **Controls (12 of 12 as predicted, on scratch copies, each anchor hit once):** identifier rename DIFF; numeric literal DIFF; comment edit COMMENT DIFF; a non-title string given an id VIOLATION; a rewritten title given a new id VIOLATION; a title that was id-free at base edited VIOLATION; one title reverted to base SAME; an `it.each` row given an id VIOLATION; an undeclared `expect` message changed VIOLATION; a title re-split into a `+` chain DIFF; a declared message given an id back VIOLATION; the kept `:322` ruling value edited VIOLATION. - **Templates and tables:** the one template title (`component-object-grid-pagination-accept-set.pin.test.ts:172`) changes only after its `${label}` span. No `.each` title, `%s` / `$name` placeholder or table row changes. **Test counts:** the 32 files were run at the base, in a separate base worktree, and at the head, with `--project local --project repo`. Both sides read 959 tests in 32 files, all passed, with the same count and status sequence per file in 32 of 32. 513 full test names change, and no full name repeats on either side. Each changed name equals the base name with the planned replacements applied. The comparison script flags one name: its plan entry spells `’` as the source does, and the printed name carries the decoded character. With the escape decoded, that name matches too. ## Changeset: `skip-changeset` Measured, not assumed: - `npm pack --dry-run` of `@objectstack/spec` lists 2068 files. 0 of the 32 touched files are in it, and no `*.test.ts` at all. The controls `src/ui/action.zod.ts` and `dist/index.mjs` are in it. - In the built `dist/`, a new phrase and an old literal each read in 0 files. The control `Unrecognized key` reads in 42. So this PR publishes nothing, and no changeset is added. ## Verification (at `fa39bcf62e`) - `pnpm turbo run build` over all packages: 71 / 71, through the shared verify lock (`VERDICT command-exit 0`). - `@objectstack/spec`: - `vitest run --project local`: 616 files, 18426 passed, 1 todo. - `typecheck`: exit 0, including `check:test-typecheck` (52 files / 246 errors / 135 pinned signatures held). Its program holds all 32 touched files, counted with `tsc --listFilesOnly -p tsconfig.test.json`. - `check:generated`: all 15 generated artifacts up to date, against the `dist/` the build above wrote. - **Gates:** `dispatch-gates --commands` derived 79 families, the same set as stage 19, and all 79 exit 0. `--ran` reconciles: 79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN, every family with its exit code recorded. The five roster families whose rosters sit under a touched directory were also run, and each exits 0: `check:meta-url-spelling`, `check:spec-changes`, `check:authz-resolver`, `check:error-code-casing` and `check:filter-alias-parity`. - **ESLint, a proven narrowing:** `--no-inline-config` over the 32 files reads 0 errors and 0 warnings. The population comes from ESLint's own config: 32 configured, 0 ignored. No file sets `parserOptions.project` or `projectService`, so no untouched file's verdict can move. - `check-governed-merges --test`: NOT governed, 192 changed lines (+96 / -96). - A control-byte scan over the 32 files finds none. ## `main` since the base Re-fetched just before this PR opened, `origin/main` was two commits past the base (`67c544ccca`: objectstack-ai#21887, objectstack-ai#21891). Neither touches any of the 32 files, and neither touches `packages/spec`: their 9 files are in `service-datasource`, `qa/dogfood`, the QA checklist and one changeset. So `main` was not merged. `git merge-tree` onto `67c544ccca` is clean. None of the 13 open PRs touches the 32 files. ## Acceptance notes - **The kept id** (`component-props-unknown-members.pin.test.ts:322`, read by `:417`) is held for the needle stage, as above. - **Same-id test titles in this card's later stages** go with those stages. 30 lines in `packages/spec/src` cite ids this PR handled, for example `api/api-error-code-type.test.ts:71` ("[objectstack-ai#19920] …"), `system/job.test.ts:836` ("retired job.id (objectstack-ai#4667)"), `ui/view-strictness-batch18.test.ts:91` ("objectstack-ai#4001 批 18 — …"), `ui/view.test.ts:3144` ("(objectstack-ai#3896 close-out)") and `ui/component.test.ts:3518` ("(objectstack-ai#19514)"). `ui/view.test.ts:3386` cites `objectui#5435`, a different record. - **Same-id test titles in other packages** are their lanes' test-string shares. A search of `describe` / `it` / `test` lines outside `packages/spec/src` finds 44 lines citing ids this PR handled, in 11 packages: `lint` 16 (6 files), `runtime` 9 (6), `cli` 5 (3), `plugin-approvals` 3 (2), `spec/scripts` 3 (2), `objectql` 2 (2), `plugin-security` 2 (2), and one each in `plugin-sharing`, `qa/dogfood`, `rest` and `service-automation`. Examples: `runtime/src/action-execution-destructive.test.ts:86` ("(objectstack-ai#7828 Option A)"), `lint/src/validate-react-page-props.test.ts:1008` ("since objectui#3382 (objectstack-ai#5435)"), `cli/test/i18n-extract-action-description.test.ts:26` ("(objectstack-ai#7367)"). Some of the `objectstack-ai#3896` hits there (`plugin-sharing`, `qa/dogfood`) cite the sharing-rule record itself. - **Code comments still carry ids** in these files and their sources, for example the `[objectstack-ai#13865]` header of the docblock pin, the `objectstack-ai#20323` comment above `action.test.ts:1116` and the `objectstack-ai/objectui#11610` comments in `ui/component.zod.ts`. Comments are not this card's share, and none is touched here. --- _Generated by [Claude Code](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_ Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Oct 7, 2026
…jectstack-ai#21943) Part of objectstack-ai#21932 Clause-②: no ## What changes The platform checklist gains items for the rules the 17.7 pre-release security follow-up landed, and two re-checks from the card are resolved. All edits are in `docs/qa/platform-checklist/areas/*.json`. `automation.json` is untouched (open PR objectstack-ai#21928 holds it). | Card row | Disposition | Item | |---|---|---| | objectstack-ai#21792 (PR objectstack-ai#21809) settings audit and secret-valued settings | new item | `platform-core.settings-audit-secret-fingerprint` | | objectstack-ai#21846 (PR objectstack-ai#21872) implicit account linking | new item | `identity-auth.implicit-account-linking-ownership` | | objectstack-ai#21839 (PR objectstack-ai#21890) share-link password | three clauses added, rev 4 to 5 | `access-security.share-link-capability-tokens` | | objectstack-ai#21836 (PR objectstack-ai#21879) global search skips unreadable objects, plus the two cases objectstack-ai#21880 lists | new item | `search.global-search-skips-unreadable` | | re-check 1: A2 / A7 and the plugin-driver boundary | rev 2 to 3 | `integration-system.datasource-credential-refusal-matrix` | | re-check 2: the objectstack-ai#21845 CLI and quorum N1 notes | already applied by objectstack-ai#21891, no edit | `cli.scaffold-first-run`, `cli.scaffold-console-first-paint`, `approvals.quorum-m-of-n` | Each item states rules, not reproductions. Withheld security detail stays out. ### Grounding, per row - **Settings audit fingerprint.** Both ledgers record the keyed digest for a secret-valued setting, or no fingerprint when none is available, and never the value or an unkeyed hash. Grounded in `settings-service.ts#secretAuditDigest`, `config-change-audit.ts#CONFIG_CHANGE_ACTION` and the contract text at `crypto-provider.ts#keyedDigest`. The pin is `settings-audit-secret-digest.test.ts` (7 cases). The offline check carries a positive control: the non-secret key's unkeyed digest IS found, so a no-hit on the secret rows means something. The no-keyed-digest arm cannot be reached on a stock boot, so that clause is scored from the pin. - **Implicit account linking.** Four rules: no implicit link to an unverified local user; an unlink is honoured; an explicit, signed-in link still works and lifts the refusal; the platform IdP exception holds only on its OAuth path. Grounded in `implicit-account-linking.ts` (`decideImplicitLink`, `IMPLICIT_LINK_REFUSED`, `PLATFORM_IDP_PROVIDER_ID`, `recordUnlinkTombstone`, `refuseImplicitAccountLink`) and the published `sso.mdx` section. The pin is `implicit-account-linking.test.ts`. The item reuses the local OIDC provider recipe from `identity-auth.linked-accounts-social`. The platform-IdP clause and the operator override are pin-scored, and knownGaps says why. - **Share-link password.** The stored hash leaves on no exit (mint, list, redemption). The password is accepted from the `X-Share-Password` header, the query form is still accepted, and the default CORS allow-list carries the header. Both public routes answer `Cache-Control: no-store` and `Vary: X-Share-Password` on every outcome, and the authenticated routes do not. Grounded in `share-link-service.ts#withoutPasswordHash`, `share-link-routes.ts#SHARE_LINK_PUBLIC_RESPONSE_HEADERS`, the runtime `share-links.ts#PUBLIC_RESPONSE_HEADERS` and `adapter.ts#DEFAULT_CORS_ALLOW_HEADERS`. The pins are the `[objectstack-ai#21839]` blocks in `share-link-password.test.ts`, `share-links-public-cache-headers.test.ts` and the hono-plugin CORS case. Existing clause indices are unchanged. - **Global search.** An unreadable object is never queried, named or counted. An explicit `objects=` naming one answers exactly as a name that matches no object. The object stays refused at its own door. Row scope still narrows a searched object, and a term found only in a field hidden from the caller yields no hit. Grounded in `protocol.ts#searchAll` (the `canReadObject` pre-filter and the `getQueryableFields` narrowing). The pins are the dogfood `search-skip-unreadable.dogfood.test.ts` and the 12 unit cases in `protocol.search-skip-unreadable.test.ts`. The two objectstack-ai#21880 cases have no end-to-end pin yet, and knownGaps says so. The open pinyin-companion finding on objectstack-ai#21880 is recorded as a knownGap with a flag-off instruction, at class level only. The persona reuses the area recipe `qa-contributor-bound-member`. - **Datasource credential matrix.** A2 / A7 (`acceptance[1]` and `acceptance[6]`) are recorded as a known environment gap. They need a reachable credential-protected database of a shipped driver, which no run has had. No recipe is claimed, because none is proven. A successful publish alone may not score them, and the stored-credential half of A7 can be read as a partial reading. Separately, the unknown-driver clause, step 7, its negative and the title now state the ruled boundary from objectstack-ai#21921 and the docs note objectstack-ai#21927. For a plugin driver, only the fixed spellings are redacted (the canonical keys, the former aliases and URL credentials). A non-canonical key served as written is the boundary, not a FAIL. Grounded in `common.zod.ts#CANONICAL_CREDENTIAL_KEYS` and `datasource-credential-redaction.ts#redactableConfigKeys`. ### Re-check 2 evidence (no edit) At the claim ref `9dce635337`: - `cli.scaffold-first-run` (rev 3) step 0 and `cli.scaffold-console-first-paint` (rev 3) step 0 both drop the trailing `npm install` and warn against adding it. Their rev 3 history entries cite objectstack-ai#21845. No other `npm install` step remains in `cli.json`. - `approvals.quorum-m-of-n` (rev 4) `negative[0]` requires a NON-PRIVILEGED repeat actor and names the documented admin override (objectstack-ai#3424) as never a distinctness FAIL. ## Remaining on objectstack-ai#21932 (held, not in this PR) - The objectstack-ai#21864 row (public-form withdrawal layering). Its PR is still open. - The objectstack-ai#21928 row (run-state trigger record mask). That PR adds its own item in `automation.json`. objectstack-ai#21932 remains open for these two rows. ## Validation (at `a72b827e43`) - `pnpm check:platform-checklist`: exit 0. It reports 15 areas and 273 items (269 active, 2 planned). The baseline was 270. Symbol anchors resolve 674 of 684 (baseline 657 of 667): all 17 new anchors resolve, and the objectstack-ai#16898 residual is unchanged at 10. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 13 commands, and all 13 exit 0. `check:doc-formula-expressions` first exited 3 (PREREQUISITE NOT MET: `@objectstack/formula` and `@objectstack/lint` were not built). After building them it exited 0. `--ran` reconciliation: 13 derived, 13 run, 0 unrun. - No package source changed, so there is no package build, test or typecheck. No changeset: `docs/qa/**` publishes nothing. ## Acceptance notes - Source citations name test cases and symbols, never line numbers, because `check:platform-checklist` refuses a `file:line` pin. - `content/docs/data-modeling/drivers.mdx` says a plugin driver's `config` is "stored and served to administrators as written". The read redactor still withholds the canonical spellings (`password`, `authToken`), the former aliases and URL credentials for such a driver (`redactableConfigKeys`). So the docs sentence is slightly broader than the code, and the code is the more protective of the two. The checklist follows the code. This is noted only, with no card. Carrier: none. - A run of `search.global-search-skips-unreadable` picks the walled object and the hidden-field value on the live boot, behind premise guards. The item names likely candidates and does not assume them. --- _Generated by [Claude Code](https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv)_ Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #21851
Checklist-only change: five area files under
docs/qa/platform-checklist/areas/. There are no product, script or.changesetedits, becausedocs/qa/**is not published. Follow-up run #21845 built two fixtures, a wall-clock offset shim and a local OIDC provider. They go into item text as recipes and are not committed as code. Each revised item bumpsrevisionand gets ahistoryentry that cites the run. Entries 1-10 cite #21845. Entry 11 cites the #21782 re-run, which is where its evidence comes from.Entry → item → what changed
approvals.quorum-m-of-nblockedremoved.fixtures.requiresgains the runtime fixture: Ada bound tofinanceviaPOST /api/v1/data/sys_user_position, a third signed-up holder bound tolegal,showcase_expense_signoffdisabled viaPOST /api/v1/automation/showcase_expense_signoff/toggle {"enabled":false}(otherwiseDUPLICATE_REQUEST), and a fresh request (draft report + one 6000 line + PATCHsubmitted). The seed-gap knownGap is kept and marked CLOSED-for-runs. Steps open a fresh request and add a repeat-actor probeapprovals.sla-escalationblockedremoved.fixtures.requiresgains the offset-shim recipe (NODE_OPTIONS=--require,Date/Date.nowoffset read from a file,Date.prototype.constructorset to the wrapper, timers untouched). Flows are registered withPOST /api/v1/automation, one per action, and no writable package is needed. The item names the 5-minute sweep (ESCALATION_SCAN_INTERVAL_MS) and the boot catch-up sweepidentity-auth.linked-accounts-socialblockedremoved.fixtures.requiresgains the local auth-code + PKCE OIDC provider registered throughapplyConfigPatch({oidcProviders:[…]})from a scratch app plugin (auth:configurefires too early), which must be up before the first auth request. Step 2 links through the authenticatedPOST /api/v1/auth/link-socialand navigates to the returned URL. The nav label is "Linked Accounts" (Account app,nav_account_linked). The item no longer names thesys_accountlink action anywhere (see notes). The unlink, mine-view and read-only clauses are unchangedrecords-forms.import-job-undo-cancelblockedremoved. The stock console already meets the client gate at console pin2e818d0b51ec. The fixture runs objectuiimport-console-undo.spec.tswithIMPORT_CONSOLE_LIVE=1from a scratch copy whose goto path is/_console/apps/{app}/{object}. The API half names the/api/v1/data/import/jobsroutes.revertedAtis absent, not null, until a job is reverted (acceptance[0], acceptance[3] and step 4)approvals.quorum-m-of-nnegative[0]via_override:true) and finalizes by designapprovals.quorum-m-of-nacceptance[2]approvals.quorum-m-of-nacceptance[3] (+ [4], step 9)cli.scaffold-first-runstep 2 /cli.scaffold-console-first-paintstep 1npm install. The scaffolder installs itself (pnpm when found, npm otherwise), and its exit code is now the install evidencecli.scaffold-first-runfixtures + acceptance[3]@objectstack/*andcreate-objectstack, run the repo's bin, and prove the candidate via lockfile integrity. The published path measures only what is released. The "installed set" is unique skill namescli.scaffold-console-first-paintacceptance[3]studio-authoring.first-run-loopand says that it covers the app/nav half but not an actioni18n.surface-matrix/i18n.studio-follows-app-localeNotes on judgement calls
pnpm installwould, and it keeps the published scaffold command and the quick-start (which prescribes no install) unchanged, which--skip-installwould not. It is also what the published canary job inscaffold-e2e.ymldoes: it scaffolds@latestand then runs validate/build with no separate install.sys_accountlink action. To keep that true throughout, the provider-less clause (acceptance[3]), its negative (negative[3]), the degradation step and thesys-account.object.tssource note now say "the link affordance" instead of naming the action. The clause meaning and indices are unchanged, so the run'sExtracted:lines still point at the same clauses. identity: the "Link Social Account" action is dead on every boot — it navigates to a GET of the POST-only social sign-in route (404) and offers a fixed provider list regardless of configuration #21849 remains open and owns that action./api/v1/data/import/jobs. The route ledger has create atPOST /api/v1/data/:object/import/jobsand everything else under/api/v1/data/import/jobs, and the item states it that way.Validation (head
4f0fb3a8e0)pnpm check:platform-checklist→ exit 0:OK — 15 areas, 270 items (266 active, 2 planned) … symbol anchors: 657/667 resolved(baseline one864db56df: 653/663; the 4 new anchors all resolve).node scripts/pm/dispatch-gates.mjs --commandsderived 13 families. All 13 ran to exit 0, and--ranreports13 run, 0 NOT-MEASURED (a DERIVED zero).check:doc-formula-expressionsfirst exited 3 (PREREQUISITE NOT MET, unbuilt@objectstack/formula/@objectstack/lint) and exited 0 after building them.node scripts/checklist-select.mjsresolves each of the four unblocked items as1 runnable item(s).Acceptance notes (seen while editing, not changed here)
cli.scaffold-first-runacceptance[1] still says "each of the six templates" whilevariantsis["blank"]. This is stale wording. The person who next edits that item takes it on, and no card is filed.i18n.surface-matrixvariantsstill reads "view empty states … blocked(fixture): no showcase view authors one", although rev 4 made that variant runnable. QA run · surface:browser (61/61) · 316be321 · 2026-10-04 · 38 PASS / 8 PARTIAL / 15 FAIL / 0 BLOCKED / 0 NOT-RUN #21782 already raised this for the checklist owner.pending_approversis not de-duplicated when one person holds several routed positions, so the drawer's eligible-approver count can read high before the first vote while the tally stays correct. The run did not extract this, and it is not asserted here.Generated by Claude Code