Skip to content

chore(objectui): bump the console pin to 9dfaca654311 (carries objectui#11611, #11614 and #11619) - #21800

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-21772-objectui-pin-bump
Oct 5, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-21772-objectui-pin-bump

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21772
Clause-②: no

This moves the bundled Console's objectui pin from 2e818d0b51ec (#21710) to objectui main as of the write time, 9dfaca654311cddd81714153c4f82c241d7cdc54, which is past c096f03. The Console now carries objectui#11611 (the ref:dataset spec-form widget), objectui#11614 (the grid widget's camelCase keys) and objectui#11619 (the record chrome's picture). Those are the three merges #21714, #21768 and #21765 wait on.

⛔ This is not a release act. Version PR #21352 is untouched, and no tag, publish or Release was made. The card's "release first" hold is lifted by the maintainer's order recorded in the claim: 「浏览器测试已经在运行,还有很多问题在处理,服务端开发不应该被阻塞」.

Range

  • objectui git ls-remote origin refs/heads/main read 9dfaca654311cddd81714153c4f82c241d7cdc54 at 2026-10-05T00:51:29Z, just before the bump, and the same sha again before this PR was opened.
  • git merge-base --is-ancestor exits 0 against 9dfaca654311 for all three carried merges: b508ac50d9 (objectui#11611), 2abec3a96c (objectui#11614) and c096f03279 (objectui#11619). The objectui clone is shallow, but exit 0 proves itself there.
  • 2e818d0b51ec..9dfaca654311 has 17 commits and 0 merges.
  • objectui declared 24 changesets over the range, and all 24 release. There are 0 release-nothing changesets and 0 commits without a changeset. None declares major. Four carry the author's breaking annotation, and the highest declared level is minor, so the console changeset is minor. These counts come from scripts/objectui-range.mjs --from 2e818d0b51ec --to 9dfaca654311 --json and from the bump's own digest. They were measured, not copied from the card.
  • Four commit subjects carry !. git log --format='%h %s' 2e818d0b5..9dfaca654 | grep -E '^[0-9a-f]+ [a-z]+(\([^)]*\))?!:' gives 9db9ff3f9, 8b14aecbd, 2abec3a96 and b403bb36f.
objectui commit landing changesets declared disposition
9dfaca654 objectui#11615: the default simple form draws a self-describing inline section entry; ObjectFormSection.fields gains the form view's { field } arm 1, minor, BREAKING (for TypeScript readers) releasing; declared breaking, answered below
15f67025b objectui#11345: the inline grid's default columns derive through the spec rule deriveInlineGridColumns 1, patch releasing
4c127cdef objectui#11613: record:related_list stops requiring columns 1, minor releasing; moves the manifest
c096f0327 objectui#11383 (PR objectui#11619): the record chrome draws the record's picture from the object's imageField 1, minor releasing; smoke below; unlocks #21765
6e9090c26 objectui#11336: an object document's served listViews count as served views 1, patch releasing
9db9ff3f9 ! objectui#11266: a form view's subforms[].columns entry is the spec's InlineGridColumnSchema, by reference 1, minor, BREAKING releasing; declared breaking, answered below
7c9a6b194 objectui#11340: the docs portal renders the book resolver's answer 1, patch releasing
8b14aecbd ! objectui#11608: PartialSchema is retired from @object-ui/types 1, minor, BREAKING releasing; declared breaking, answered below
2abec3a96 ! objectui#11610 (PR objectui#11614): the grid widget's eight field-level keys are camelCase; the snake_case spellings are refused by name 1, minor, BREAKING releasing; declared breaking, answered below; unlocks #21768
b403bb36f ! objectui#8347: BaseSchema loses its index signature 1, minor (Clause-②: yes (narrowing), no BREAKING word) releasing; declared breaking by its !, answered below
fd060f076 objectui#11605: bound registrations stop requiring objectName, and a node with neither shows a no-object hint 8, minor releasing; moves the manifest
b508ac50d objectui#11601 (PR objectui#11611): the ref:dataset spec-form widget, fed by the report inspector's dataset catalog 1, minor releasing; unlocks #21714
d2e859936 objectui#11002: the console asks GET /usage/storage only when the runtime serves features.storageUsage 1, minor releasing; smoke below
b92329c89 objectui#11591: the Organization flows page copy 1, patch releasing
902ebab63 objectui#11569: record:line_items stops requiring childObject 1, minor releasing; moves the manifest
278d2444e objectui#11546: a node click on a read-only flow canvas opens the inspector read-only 1, patch releasing
b61c116b2 objectui#11577: record:details read mode keeps a textarea's line breaks 1, patch releasing

Declared-breaking changes, and how this repo answers each

The ADR-0087 disposition is not-required (no-migration-prescription). It replaces the bump's adr-0087: TODO placeholder in .changeset/console-9dfaca654311.md, and it covers all five entries below. check-adr-0087-registration --base origin/main gives "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition". check-changeset-no-major --base origin/main gives "This diff introduces no major bump".

  1. objectui#11610 / PR objectui#11614 (2abec3a96): the grid widget's eight keys. min_rows, max_rows, allow_add, allow_delete, allow_reorder, total_field, add_label and sort_field become minRows … sortField. objectui has no dual read: its zod faces refuse a snake_case key by name, and its GridField draws an inline alert instead of the grid.
  2. objectui#11615 (9dfaca654): the simple form's inline section entries. For TypeScript readers of ObjectFormSection.fields, the type gains the spec's FormFieldInput arm. No code in this repo imports @object-ui/types; the only @object-ui/* import is scripts/gen-sdui-manifest-node.mjs reading @object-ui/core from the built tree. The behaviour change is that the default simple form now draws an inline { name, … } entry, as the other five form types already did. The showcase's object-form nodes are wizard, drawer and modal forms whose sections list field names, so none of them draws differently.
  3. objectui#11266 (9db9ff3f9): subforms[].columns. objectui's validator now judges a column by @objectstack/spec's own InlineGridColumnSchema. This repo has enforced that closed shape since feat(spec)!: a form view's subform columns are the inline grid column contract, and an identity-only column is judged as the type it renders (#20901) #20927. objectui's verdict now matches os validate, and the ObjectStack accept set does not move.
  4. objectui#11608 (8b14aecbd): PartialSchema. It leaves @object-ui/types. Nothing in this repo names it (git grep PartialSchema: 0).
  5. objectui#8347 (b403bb36f): BaseSchema loses its index signature. This narrows the TypeScript face of objectui's node types, and nothing here compiles against them. objectui's zod faces keep their accept sets for every key except visibleWhen. That key widens to the { dialect, source } envelope this repo's own parse writes.

The three cards this unblocks

What changed here

  • .objectui-sha and .changeset/console-9dfaca654311.md. scripts/bump-objectui.sh 9dfaca654311cddd81714153c4f82c241d7cdc54 --no-commit wrote both, with OBJECTUI_ROOT set to the container's objectui clone after git fetch origin main. The range walked completely without a deepen, and the level was auto-set to minor.
  • sdui.manifest.json and scripts/sdui-manifest.record.json. node scripts/gen-sdui-manifest-node.mjs regenerated them over the tree that pnpm objectui:build built at the pin. There are 107 components at both pins, and the sha256 moves from 0ead67c1111d… to 6f921896ffac…. This time the manifest moves:
    • Eleven inputs lose required: true and gain a description. Nine are objectName, on object-grid, list-view, object-form, embeddable-form, object-master-detail-form, object-kanban, object-metric, object-chart and object-pivot (objectui#11605). The other two are record:related_list columns (objectui#11613) and record:line_items childObject (objectui#11569).
    • object-master-detail-form's fields description is rewritten for objectui#11615's inline entries.
    • Where the spec has a ComponentPropsMap row, these inputs are already optional there. Measured on the built spec: object-grid, object-form, object-kanban, object-metric and object-master-detail-form objectName, record:related_list columns and record:line_items childObject. So the manifest now agrees with the spec rows. The JSX page compile reads the manifest, and it stops refusing a node whose dataSource binding names the object.
    • The record moves its pin and modulesRoot. objectui's workspace version stays 17.7.0.
  • packages/sdui-parser/objectui-lockstep.json. pnpm gen:sdui-lockstep re-recorded it against OBJECTUI_ROOT=.cache/objectui-9dfaca654311. It records 214 grammar lines (blob 0131f27cf86d), 25 codes and containment predicate 76c18fb95d1f. All are unchanged, and objectui's packages/sdui-parser has no diff over the range, so no port is owed.
  • The 54 asserting pin citations in packages/spec/src. They were re-measured at the new pin, not restamped:
    • Each asserting record's anchors were resolved in objectui at 2e818d0b5, mapped through git diff -U0 2e818d0b5 9dfaca654, and re-read at the new pin. Each record gains a dated 2026-10-05 hop sentence and keeps its earlier history.
    • In every cited file that changed, the cited lines moved with their text byte-identical:
      • objectui#8347 re-worded docblocks in ObjectGrid.tsx, ObjectTree.tsx, ObjectGantt.tsx, ObjectCalendar.tsx, SchemaRenderer.tsx, plugin-view/src/ObjectView.tsx, plugin-map/src/index.tsx and plugin-gantt/src/index.tsx.
      • In ObjectKanban.tsx, objectui#8347 added a private GateBoundKanbanSchema read type, so its fetch, navigation reads and spread moved +30.
      • objectui#11605 touched plugin-kanban/src/index.tsx, plugin-dashboard/src/index.tsx and ElementDataSourceGate.tsx. The object-metric icon input moved 281 → 299 and is still { name: 'icon', type: 'string' }.
      • objectui#11619 moved the page:tabs and page:accordion icon anchors in containers.tsx by +3.
      • objectui#11615, objectui#11266 and objectui#8347 moved ObjectKanbanSchema.limit, ObjectMapConfigSchema and LIST_VIEW_LOCAL_OVERRIDES in objectql.ts and objectql.zod.ts.
      • objectui#11605 added view.noObject to the en, zh and de locale packs. Their cited calendar.configRequired strings did not change or move.
    • One cited line changed content. ObjectKanban.tsx:10, the type import, gained SortConfig beside the ObjectKanbanSchema the record cites.
    • Two counts were re-taken by their records' own methods, and both read the same: the keyboardNavigation hit lines (15, against the schema.editable control's 3) and the ElementDataSourceGate occurrences in five src/index.tsx shells (0, 3, 3, 3 and 4).
    • The three quoted anchors in ui/view.zod.ts's map record redded at this pin, and each was re-read and re-pointed: case 'map': moved 2299 → 2300, ObjectMapConfigSchema 2370 → 2388, and LIST_VIEW_LOCAL_OVERRIDES 1419 → 1437.
  • The six migration entries' corpus counts were re-taken with git grep -o -F. That method first reproduced every 2e818d0b5 number: 7579 files, objectstack 17227, @objectstack/spec 7134, timeout 1351, useState 2477, TTL 182, tenant 1317 and Span 505.
    • The new numbers are 7632 files, 17313, 7186, 1360, 2477, 182, 1318 and 508. The other controls read RuntimeConfig 276 → 293, resourceLimits 2 → 2, window 4175 → 4193, period 238, interval 195 and metrics 374 → 401.
    • Every zero is still zero: 98 tokens were checked, the export lists of the three cited spec files plus every named key. The only non-zero tokens are the expected Span (508) and SpanSchema (57). The three new Span hits are colSpan in objectui's object-form-section-field-entry-11615.test.ts. Both resourceLimits hits are still prose in packages/app-shell.
    • packages/spec/src/migrations/registry.ts was regenerated with gen:migration-registry.
  • .changeset/objectui-pin-citations-9dfaca654311.md is a @objectstack/spec patch, because the FormField.span describe and six migration descriptions name the pin. content/docs/references/ui/view.mdx was regenerated by check:generated --fix.

No example, test or gate needed adapting, and no code changed outside generated records, citations and changesets.

Console build and canaries

build-console.sh ran locally under the verify lock, after turbo run build --filter=@objectstack/client..., the same two steps the Console Pin Gate job runs. Exit 0, 9m02s on a shared four-core box. The build log reports:

  • "Bundle canary 'import/jobs' present".
  • "Single-zod canary: exactly one zod version literal {major:4,minor:6,patch:5}".
  • "Console bundle carries THIS tree's @objectstack/spec, and only it".
  • "@objectstack/console dist ready (64192 KB) from objectui@9dfaca654311".

check:console-sha and check:console-injection exit 0 against that dist.

Browser smoke: examples/app-showcase with the Console built at 9dfaca654311

The server ran pnpm dev -- --fresh --ui --no-watch --compile -p 41877 with OS_PORT=41877, on its own ephemeral DB with the seeded admin. Headless Chromium (/opt/pw-browsers/chromium) drove it, signing in through the console's own login form, with console messages, page errors and every 4xx/5xx response captured. Only the PIDs this run started were stopped.

No showcase object declares imageField, so the record picture needs one to exist. git grep imageField examples gives 0 hits. To exercise objectui#11619's read, the smoke made a temporary local edit:

  • It added imageField: 'f_image' to showcase_field_zoo, through node scripts/ablation-replace.mjs --hold.
  • It compiled and booted the server.
  • It restored the file at once with --restore. The tool reports the blob back to HEAD's 4130858b8f8b and git diff HEAD empty, and git status --porcelain is empty.
  • After the run, the showcase dist/ was restored from turbo's cache for the clean source: sha256 cc1034dd9d23…, as before the smoke. The stray runtime bundle of the edited compile was deleted.

Nothing of the edit is in this diff. A real sys_file was uploaded through /api/v1/storage/upload/presigned → PUT → /upload/complete (a 64×64 red PNG) and written to "Specimen — Full"'s f_image.

record page [data-record-picture] image
Field Zoo "Specimen — Full" (f_image set) 1, beside the title in the record header, rounded-md (an image field, not avatar) src="/api/v1/storage/files/b1f4e384-…", alt="", loaded, naturalWidth 64
Field Zoo "Specimen — Minimal" (f_image empty) 0 none, as objectui#11619 specifies for an empty value

Verdict: at this pin the record header draws the record's picture from the object's imageField, drawing the stored file through /api/v1/storage/files/:id, and draws nothing when the value is empty. showcase_task's record page is the custom task-detail page, which has no page:header, so it draws no record chrome to put a picture in. That page was smoked too: it renders its path bar, highlights and sections with 0 page errors.

Console messages across the run: 0 page errors. The only failed loads are a 401 on GET /api/v1/auth/get-session (the pre-login probe) and a 404 for /favicon.ico. There is no /api/v1/usage/storage 404. #21625's and #21710's smokes both recorded one, and objectui#11002 (d2e859936) stopped the request on a runtime that does not serve it.

Gates and tests (head 4b9519ea23)

  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 127 commands from the 22-path diff, and all were run at 4b9519ea23. --ran reports "127 derived, 127 run, 0 NOT-MEASURED, 0 UNRUN", with every exit code recorded.
    • Two exited 3 (PREREQUISITE NOT MET) on the first pass: check:skill-examples (no client-react dist) and check:dual-build-cjs-loads (no dist for 34 packages).
    • Both exited 0 after turbo run build --filter=!@objectstack/docs (71 of 72 tasks were cache hits), and those are the codes recorded.
  • Also exit 0:
    • check:objectui-pin-citations --verify-anchors with objectui at the pin: 54 asserting citations match 9dfaca654, and 7 anchor content assertions are verified.
    • check:objectui-bump (20 assertions across 5 cases), check:sdui-lockstep, check-sdui-manifest, check:console-sha, check:console-injection, check:migration-registry.
    • @objectstack/spec check:generated: all 15 artifacts current after the --fix.
  • pnpm --filter @objectstack/spec exec vitest run (both projects, local and repo): 668 files, 19259 passed, 1 todo. pnpm --filter @objectstack/spec typecheck: exit 0.
  • @objectstack/sdui-parser test (14 files, 225 passed) and typecheck: exit 0.
  • These suites read the regenerated manifest:
    • @objectstack/lint test: 119 files, 5627 passed. It declares sdui.manifest.json as a test input.
    • @objectstack/metadata-protocol src/protocol.runtime-authoring-gate.test.ts: 70 passed.
    • @objectstack/cli unit tier src/utils/sdui-manifest.test.ts and test/validate-build-gate-parity.test.ts: 2 files, 79 passed.
    • test/jsx-gate-manifest-notice.e2e.test.ts belongs to neither CLI tier; it runs nightly, so it is NOT MEASURED here and is left to CI.
  • eslint --no-inline-config --format json on the 15 changed TS files: 15 files, 0 errors and 0 warnings. The lint population is **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} (eslint.config.mjs:971). The config enables no type-aware linting (:327-328), so this diff cannot move a verdict on an untouched file. Repo-wide pnpm lint is left to CI.

Acceptance notes


Generated by Claude Code

claude added 8 commits October 5, 2026 00:53
Written by scripts/bump-objectui.sh 9dfaca654311cddd81714153c4f82c241d7cdc54 --no-commit.
objectui@9dfaca654311cddd81714153c4f82c241d7cdc54

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…at pin 9dfaca654311

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
… re-measure pin citations (part)

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…54311

Every anchor in each asserting record was mapped through the objectui diff
2e818d0b51ec..9dfaca654311 and re-read; moved anchors are re-pointed with
their text byte-identical, and each record gains a dated hop sentence.

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
…clared-breaking entries

Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/sdui-parser, @objectstack/spec, touching 14 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/sdui-parser/objectui-lockstep.json, packages/spec/src/kernel/functional-completeness.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/analytics.mdx (via DatasetMeasureSchema (symbol, a top-level const))
  • content/docs/protocol/objectui/layout-dsl.mdx (via ComponentPropsMap (symbol, a top-level const object))

⛔ 5 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v15.mdx (via PageTabsProps (symbol, a top-level const object))
  • content/docs/releases/v17/17-1.mdx (via ComponentPropsMap (symbol, a top-level const object))
  • content/docs/releases/v17/17-3.mdx (via ComponentPropsMap (symbol, a top-level const object))
  • content/docs/releases/v17/17-4.mdx (via ComponentPropsMap (symbol, a top-level const object))
  • content/docs/releases/v17/17-5.mdx (via ComponentPropsMap (symbol, a top-level const object))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/sdui-parser/objectui-lockstep.json, packages/spec/src/kernel/functional-completeness.ts) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e27a7c0c9e55db06a1d40912f89a0cce4cedfdd8 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1daaae3700752d9aeb2c9cbbdcba31be63a5a6d7 — the merge of head 4b9519ea23d31a3b7eb688f32bb52a895f8f7533 into base e27a7c0c9e55db06a1d40912f89a0cce4cedfdd8, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1daaae3700752d9aeb2c9cbbdcba31be63a5a6d7 && git checkout 1daaae3700752d9aeb2c9cbbdcba31be63a5a6d7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e27a7c0c9e55db06a1d40912f89a0cce4cedfdd8 4b9519ea23d31a3b7eb688f32bb52a895f8f7533 && git checkout -B drift-repro e27a7c0c9e55db06a1d40912f89a0cce4cedfdd8 && git merge --no-ff 4b9519ea23d31a3b7eb688f32bb52a895f8f7533

node scripts/docs-audit/affected-docs.mjs --json e27a7c0c9e55db06a1d40912f89a0cce4cedfdd8

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs e27a7c0c9e55db06a1d40912f89a0cce4cedfdd8 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 5, 2026 02:08
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 5, 2026 02:08
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 5, 2026
Merged via the queue into main with commit 1354e7b Oct 5, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21772-objectui-pin-bump branch October 5, 2026 03:05
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
… decision in words instead of a tracker number (stage 15) (objectstack-ai#21810)

Part of objectstack-ai#20749
Clause-②: no

Stage 15 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 file group directly under
`packages/spec/src/data/`: the 20 test files from
`aggregate-field-type-compatibility.test.ts` to
`date-range-presets.test.ts`. They carried 94 messages and 102 tracker
ids, citing 60 records. Every one of those ids now either states what
its record decided, in words (form D), or is dropped where the title
already says it. Text only: no assertion, identifier, test count or code
comment changes.

## Census at the base (`0a3480311a`)

Instruments: `census10.cjs` (md5 `9d08602ab972b4b8643c90d64d40fa41`),
`census.cjs` (md5 `6e42a45a926d375013c32d62f16a296e`), `census-wide.cjs`
(md5 `c98410a19529c439adb0afbfb00026a2`) and `dirtable.cjs` (md5
`dda605c54745b4a60cc14c9a686e4eff`). They are byte-identical to the
copies stages 10 to 14 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.

Both instruments read **1325 messages / 1406 ids in 282 files**, the
seat's reading at `0a3480311a` (stage 14's head).

| directory | files | messages / ids | titles | other |
|:--|--:|--:|--:|--:|
| `data/` (this PR: the first 20 files) | 95 | 468 / 501 | 445 / 475 |
23 / 26 |
| `ui/` | 81 | 393 / 416 | 375 / 398 | 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** | **282** | **1325 / 1406** | **1246 / 1323** | **79 / 83**
|

The group reads **94 messages / 102 ids in 20 files**, the seat's
figures, file for file:

| file (under `data/`) | messages / ids | titles | other |
|:--|--:|--:|--:|
| `aggregate-field-type-compatibility.test.ts` | 4 / 4 | 4 / 4 | 0 |
| `analytics-date-range-closed-vocabulary.test.ts` | 1 / 1 | 1 / 1 | 0 |
| `analytics-date-range-two-bound-window.test.ts` | 3 / 3 | 3 / 3 | 0 |
| `analytics-query-window-integer.test.ts` | 2 / 2 | 2 / 2 | 0 |
| `analytics-strictness-batchd.test.ts` | 9 / 9 | 9 / 9 | 0 |
| `analytics.test.ts` | 6 / 6 | 6 / 6 | 0 |
| `api-derivation.test.ts` | 6 / 6 | 6 / 6 | 0 |
| `api-methods-batch-conformance.test.ts` | 3 / 4 | 1 / 1 | 2 / 3 |
| `authoring-key-lint.test.ts` | 2 / 2 | 2 / 2 | 0 |
| `autonumber-format.test.ts` | 3 / 3 | 3 / 3 | 0 |
| `autonumber-unanchored-boundary.test.ts` | 3 / 4 | 3 / 4 | 0 |
| `bulk-write-hook-conformance.test.ts` | 2 / 2 | 2 / 2 | 0 |
| `calendar-day.test.ts` | 2 / 2 | 2 / 2 | 0 |
| `context-tokens.test.ts` | 1 / 1 | 1 / 1 | 0 |
| `currency-mode-family-closure.pin.test.ts` | 2 / 2 | 2 / 2 | 0 |
| `currency-precision-iso4217.test.ts` | 7 / 7 | 7 / 7 | 0 |
| `data-engine.test.ts` | 20 / 25 | 20 / 25 | 0 |
| `datasource-credential-redaction.test.ts` | 6 / 6 | 6 / 6 | 0 |
| `datasource.test.ts` | 10 / 10 | 10 / 10 | 0 |
| `date-range-presets.test.ts` | 2 / 3 | 2 / 3 | 0 |
| **20 files** | **94 / 102** | **92 / 99** | **2 / 3** |

- **Controls.** Lit, a title: `data/document.test.ts` reads 2 / 2 at the
head. Lit, "other" strings: the two in
`data/external-lookup-retirement.test.ts` (`:89`, `:130`) still read at
the head. Dark: the file comment at
`data/analytics-strictness-batchd.test.ts:4` (it names the strictness
batch by its number) reads 0. Planted in a scratch copy of the head
`data/calendar-day.test.ts`: an id put into a title reads 1 / 1, and an
id put into a comment reads 0.
- **A wider pattern** (any `#` plus digits) reads the same totals in 19
of the 20 files. In `aggregate-field-type-compatibility.test.ts` it
reads one more, a decision-batch number at `:150` that sits beside a
cited record in the same literal. The gate's pattern needs three to five
digits, so it is not counted there.
- **At the head:** 1231 messages / 1304 ids in 262 files. The 20 files
read 0 / 0 on both patterns, and no other file moved.

## How the area was chosen

`data/` has no subdirectory to split by (449 ids directly under it,
`data/driver/` 52), so its stages take name-ordered file groups near the
~100-id bound, as stage 14's report proposed. This census reads the
first group at exactly 102, the claim's figure, so the rule needed no
re-cut.

**Named for the next stages** (re-cut from the head census, 1231 / 1304;
`data/` 374 / 399 left):
- `data/` in four more stages, name-ordered:
1. `default-value-shape.test.ts` to `filter-comparand-shape.test.ts`: 20
files, 94 messages / 100 ids;
2. `filter-comparand-type.test.ts` to
`filter-view-operator-parity.test.ts`: 20 files, 95 / 99;
3. `filter.test.ts` to `object.test.ts`: 17 files, 103 / 114.
`object.test.ts` alone carries 42, so no cut lands nearer the bound;
4. `query-transport.test.ts` to `validation.test.ts` (11 files, 34 / 34)
with `data/driver/` (7 files, 48 / 52): 86 ids.
- `ui/` 416, about four stages. `api/` 201, two. `system/` 165, two. The
files directly in `src/`, 120, one.
- The three docblock needles (`ai/build-progress.test.ts:236`, `:237`,
`contracts/approval-service.test.ts:274`), one stage with their
docblocks.

## What each id became

24 literals (28 ids) now state a decision in words. 2 literals (2 ids)
get their subject back in words where the number stood in for it. 69
literals (72 ids) drop a number the title already explains. (95 literals
in 94 messages: the `sys_organization` reason string is one message over
two lines.)

Every cited record was read with its comments through REST: 55 answer
200. objectstack-ai#6345, objectstack-ai#8876, objectstack-ai#9040, objectstack-ai#10194 and objectstack-ai#17014 answer 404, and their
decisions were read from what landed: `e2798fa` (one driver vocabulary
for start and migrate), `d634e66` (the username half of the URL userinfo
grammar), `2420641` (a credential in the mongo `options` passthrough is
refused), `2306a76` (`theme` / `analytics_cube` validated at the `/meta`
write door) and `80aef80` (a one-day window for the one-day presets),
each with its CHANGELOG entry. No cross-repo record is cited in this
group.

| record(s) | literal (under `data/`) | now reads |
|:--|:--|:--|
| objectstack-ai#11152 | `aggregate-field-type-compatibility.test.ts:150` | "accepts
`sum` / `avg` / `min` / `max` over booleans — numbers on every backend,
a ruling that outranks the refused-by-default rule". The maintainer
ruled that booleans aggregate as numbers on every backend; decision
batch 80 held that ruling over batch 59's blanket refusal of unnamed
pairs. That batch number went with the id. |
| objectstack-ai#4001 (3) | `analytics-strictness-batchd.test.ts:83`, `:248`, `:306` |
"batch D, unknown keys refused — …" before "the doors the cube family is
reachable through", "alias claims are true of the surfaces they point
at" and "deliberate non-closures (re-verdicts, not omissions)". The
campaign's decision: an unknown key is refused, not stripped. |
| objectstack-ai#3878 (2) | `analytics-strictness-batchd.test.ts:270`, `:297` |
"matching the dispatcher's bespoke hint at the /analytics entry" and
"the retired-envelope tombstones still fire". The body is the bare
`AnalyticsQuery`; the `{ cube, query }` envelope was retired with
tombstones, and the entry answers 400 with a hint at `where`. |
| objectstack-ai#18612 | `analytics.test.ts:314` | "a persisted cube heals at the door
— the retired join `sql` / `relationship` are stripped (ADR-0087 D2)". |
| objectstack-ai#3391 | `api-derivation.test.ts:16` | "api-derivation — one table
resolves the effective operations from six primitives". The server is
the only adjudicator, through one derivation table. |
| objectstack-ai#3543 | `api-derivation.test.ts:286` | "vocabulary split — authors
write six primitives, the wire speaks operations". The authored enum
shrank; the wire vocabulary stayed byte-stable. |
| objectstack-ai#15873 | `api-methods-batch-conformance.test.ts:221` | A declared
reason string: "(a ruling grants `update`; both are column-clamped per
row by ADR-0092 D2)". Option (a), decision batch 64. |
| objectstack-ai#3786 | `authoring-key-lint.test.ts:37` | "lintAuthoredRecordKeys — an
unknown authoring key is reported, not swallowed", the decision its
source docblock records. |
| objectstack-ai#6555 | `autonumber-format.test.ts:23` | "DEFAULT_AUTONUMBER_FORMAT /
resolveAutonumberFormat — one declared default both sides read". Route
3: `{0000}` became the contract default, and both fallbacks went away. |
| objectstack-ai#5038 | `bulk-write-hook-conformance.test.ts:114` | "records the after
half as DELIVERED — the engine fires it once per row". |
| objectstack-ai#5574 | `bulk-write-hook-conformance.test.ts:119` | "records the
before half as DELIVERED — the engine dispatches it per row too". |
| objectstack-ai#20126 | `currency-mode-family-closure.pin.test.ts:348` |
"currency-mode family — the enumerating closure pin: `defaultCurrency`
holds only under `fixed`". |
| objectstack-ai#19992 | `currency-precision-iso4217.test.ts:163` | "the removed
`currencyConfig.precision` at rest: a stored row carrying the baked
`precision: 2` is served canonical". |
| objectstack-ai#7918 | `currency-precision-iso4217.test.ts:224` | "… where the ISO
4217 width check used to refuse it". That check was the record's option
A, later reversed. |
| objectstack-ai#3407, objectstack-ai#6437 | `data-engine.test.ts:1185` |
"DroppedFieldsEventSchema.reason — why a write dropped submitted fields,
widened past the readonly pair". |
| objectstack-ai#6262, objectstack-ai#6433, objectstack-ai#6435 | `data-engine.test.ts:1198` | "primary_key is the
value the engine reports when it strips a payload id it ruled is not an
identifier", the schema's own wording of that strip on the bulk and the
by-id paths. |
| objectstack-ai#8300 | `datasource-credential-redaction.test.ts:70` | "(the drift
guard on the one credential-key definition)". |
| objectstack-ai#8876 | `datasource-credential-redaction.test.ts:232` | "— the
username half of the same alignment". |
| objectstack-ai#8337 | `datasource-credential-redaction.test.ts:243` |
"redactUrlCredentialQueryParams — the read half: a credential query
parameter is never served back". |
| objectstack-ai#8153 | `datasource.test.ts:673` | "— unchanged by the managed-row
credentialsRef allowance". The ruling allowed `external.credentialsRef`,
and only it, on managed rows. |
| objectstack-ai#4614, objectstack-ai#8793 | `date-range-presets.test.ts:14` | "date-range preset
vocabulary — one source of truth, read by both the UI and the data
side". |

**Subject restored (2 ids):** objectstack-ai#20126 at
`currency-mode-family-closure.pin.test.ts:403` ("currency-mode closure
controls — each rule can fail, and passes what it must") and objectstack-ai#7918 at
`currency-precision-iso4217.test.ts:311` ("carries the measured anchors
— 0 digits for JPY, 2 for USD, 3 for KWD"). That literal moved from
double to single quotes, since it no longer holds an apostrophe.

**Dropped only (72 ids):** objectstack-ai#1603, objectstack-ai#2377, objectstack-ai#3026, objectstack-ai#3391, objectstack-ai#3543, objectstack-ai#3545,
objectstack-ai#3795 (9), objectstack-ai#4001 (2), objectstack-ai#4286, objectstack-ai#4346 (2), objectstack-ai#4538, objectstack-ai#4583, objectstack-ai#5586, objectstack-ai#6345,
objectstack-ai#6555, objectstack-ai#6560, objectstack-ai#7178 (5), objectstack-ai#7265, objectstack-ai#7287 (2), objectstack-ai#7802 (2), objectstack-ai#8032, objectstack-ai#8057 (2),
objectstack-ai#8153 (7), objectstack-ai#8336, objectstack-ai#8337, objectstack-ai#9040, objectstack-ai#10194, objectstack-ai#10414, objectstack-ai#13802, objectstack-ai#16041, objectstack-ai#16632,
objectstack-ai#17014, objectstack-ai#17296, objectstack-ai#17598 (2), objectstack-ai#18278, objectstack-ai#19992 (3), objectstack-ai#20011, objectstack-ai#20300 (2),
objectstack-ai#20550, objectstack-ai#20600, objectstack-ai#20808 (3), objectstack-ai#21365 (2).

- Each of these titles already states the decision it pins: for example
"empty array → deny-all (flipped semantics)" for objectstack-ai#3391, "accepts the
BARE query string — the canonical ADR-0061 D1 spelling" for objectstack-ai#7178, or
"`currencyConfig.precision` is removed: refused with the prescription,
whatever its value" for objectstack-ai#19992.
- **Small rewordings that carry no new claim:**
`analytics-strictness-batchd.test.ts:307` reads "are CLOSED now" where
it named the record; `autonumber-unanchored-boundary.test.ts:51` reads
"(ruled: mixed content is out of contract)"; `datasource.test.ts:553`
reads "(the happy path)". The circled part numbers after objectstack-ai#17598 went
with the id.
- **The two `api-methods-batch-conformance.test.ts` reason strings**
(`sys_api_key`, `sys_organization`) end "rather than hitting /batch."
now. The table is read only through `!== undefined`, so no assertion
reads their text.

## Readers

- **Test-name filters:** none. A tracked-tree search for `-t` and
`--testNamePattern` finds only `packages/qa/dogfood/README.md:142` (`-t
"owner-scoped"`), which is unrelated.
- **Snapshots:** none. No `__snapshots__` directory exists under
`data/`, and no `.snap` file is tracked under `packages/spec`.
- **Projects:** two touched files are listed in
`packages/spec/vitest.repo-tests.json`:
`api-methods-batch-conformance.test.ts` and
`currency-mode-family-closure.pin.test.ts`. Both were run in the `repo`
project at the base and at the head, and the other 18 in `local`.
- **By substring:** every old literal, plus a window around each id (289
needles), was searched across the tracked tree outside its own file. No
gate, doc, filter, snapshot or `scripts/check-*.mjs` self-test reads
one. The 14 hits are:
- **sibling titles in other lanes:** `service-analytics`
`aggregate-nontemporal-measure-refusal.test.ts:344` and `objectql`
`engine-autonumber-default-format.test.ts:248`;
- **this card's later `data/` stage:**
`data/driver/postgres.test.ts:169`, the same "placeholders are not
resolved here" title, already in the census;
- **comments, CHANGELOG, an audit ledger and liveness evidence:** `lint`
`validate-dataset-measure-aggregates.test.ts:179`, `service-analytics`
`dataset-compiler.ts:227`, `objectql` `engine.ts:6206` and `:6272`,
`analytics.zod.ts:1002`,
`docs/audits/2026-07-unknown-key-strictness-ledger.md:728`, two
`packages/spec/CHANGELOG.md` entries and the `liveness/field.json:218`
evidence string, which quotes the `engine.ts` comment. None reads a test
title.
- **Same-text titles named in stage 14's ACCEPT** (`(objectstack-ai#15680)`,
`(objectstack-ai#5955)`, `objectstack-ai#3896 close-out`): none falls in this group.

## 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. The declared lines are the three
reason-string leaves in `api-methods-batch-conformance.test.ts`.

- **Result:** 20 of 20 files SAME on all three legs, as predicted in
writing before the run.
- **Totals:** 95 changed literals, 92 titles and 3 declared. The diff's
`+` and `-` lines are exactly the 95 planned lines, and every file keeps
its line count.
- **Controls (10 of 10 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; a declared string given a
new id VIOLATION; an undeclared `expect` message changed VIOLATION; a
title re-split into a `+` chain DIFF.

**Test counts:** the 20 files were run at the base, in a separate base
worktree, and at the head, with `--project local --project repo`. Both
sides read 553 / 553 passed, with the same count and status sequence per
file in 20 of 20. 291 full test names change, and each equals the base
name with the planned replacements applied (0 mismatches). No full name
repeats on either side.

## `main` merged in, once

objectstack-ai#21800 (the console pin bump) landed while this branch was being
verified, and it rewrites the comment block at `:61-77` of
`api-methods-batch-conformance.test.ts`. This PR edits only string
literals in that file, more than 100 lines below the block, so
`origin/main` (`18c2ddc1ec`, which also carries objectstack-ai#21801) was merged in
with a plain merge, no rebase, and no conflict. The PR's delta against
`main` is still exactly the 20 files, +95 / -95. Every reading in this
body was re-taken on the merged head `bf16ad1190`, against `18c2ddc1ec`
as the base: the census (1325 / 1406 there, 1231 / 1304 here, unchanged
by the two commits), the text-only proof and its controls (the three
declared lines now sit at `:202`, `:230` and `:235`), the 20-file runs,
the full build, the suite, the typecheck and the gates. Re-fetched just
before this PR opened, `origin/main` was one commit further
(`75ddcd1b41`, objectstack-ai#21805, in `cloud-connection`, `metadata-core` and
`runtime`). It touches no `packages/spec` path and no file here, so it
was not merged.

## Changeset: `skip-changeset`

Measured, not assumed:
- `npm pack --dry-run` of `@objectstack/spec` lists 2068 files. 0 of the
20 touched files are in it, and no `*.test.ts` at all. Of `src/`, only
the `*.zod.ts` sources ship: the controls `src/data/analytics.zod.ts`,
`src/data/data-engine.zod.ts` and `dist/data/index.js` are in it.
- In the built `dist/`, five new phrases and four old literals each read
in 0 files. The control `Unrecognized key(s) on` reads in 42.

So this PR publishes nothing, and no changeset is added.

## Verification (at `bf16ad1190`)

- `pnpm turbo run build` over all packages: 71 / 71 (also 71 / 71 at the
pre-merge head `89c4b300c2`).
- `@objectstack/spec`:
  - `vitest run --project local`: 615 files, 18360 passed, 1 todo.
- `typecheck` exit 0, including `check:test-typecheck` (52 files / 246
errors / 135 pinned signatures held). Its program holds all 20 touched
files, counted with `tsc --listFilesOnly -p tsconfig.test.json`.
- `check:generated`: all 15 generated artifacts up to date after the
merge.
- **Gates:** `dispatch-gates --commands` derived 79 families, the same
set as stages 13 and 14, and all 79 exit 0. `--ran` reconciles: 79
derived, 79 run, 0 NOT-MEASURED, 0 UNRUN.
- 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 20 files
reads 0 errors and 0 warnings. The population comes from ESLint's own
config: 20 configured, 0 ignored. No file sets `parserOptions.project`
or `projectService`, so no untouched file's verdict can move.
- `check-governed-merges --test`: NOT governed, 190 changed lines.

## Acceptance notes

- **No needle in this group.** Every id was a title or a declared reason
string; no expected value of an assertion over a source docblock was
found. The three known needles are untouched.
- **Same-id test titles in other packages** are their lanes' test-string
shares. A search of `describe` / `it` / `test` lines outside
`packages/spec` finds 156 lines citing ids this PR handled, in 79 files
of 23 packages: `objectql` 73 (29 files), `rest` 18 (7), `runtime` 7
(4), `plugin-security` 6 (5), `cli` 6 (3), `lint` 6 (4),
`service-datasource` 6 (3), `driver-sql` 4 (4), `platform-objects` 4
(2), `plugin-approvals` 3 (2), `service-automation` 3 (2),
`driver-mongodb` 3 (1), `plugin-auth` 3 (1), `service-analytics` 3 (3),
`metadata-core` 2 (1), `plugin-hono-server` 2 (1), and one each in
`client`, `triggers`, `core`, `metadata-protocol`, `qa/dogfood`, `types`
and `driver-memory`.
- **Two spec test files outside `src/`** carry same-id titles:
`packages/spec/scripts/file-description.test.ts:66` and
`packages/spec/scripts/format-type.test.ts:85`. They are outside class
(e) as ruled ("the test strings shipped under `src/`").
- **Numeric delivery fields:**
`bulk-write-hook-conformance.test.ts:115-116` and `:129-130` assert
`engineDeliveryIssue: 5038` / `5574`, numbers in the source contract
table. They are not strings, the gate's pattern cannot see them, and
they are not this card's share.
- **Code comments still carry ids** in these files and their sources,
for example the header of `analytics-strictness-batchd.test.ts` and the
`SINGLE_RECORD_WRITE_ONLY` comments in
`api-methods-batch-conformance.test.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
…t at the objectui pin (objectstack-ai#21824)

Part of objectstack-ai#21765

Clause-②: no

Seam: `spec:ObjectSchema.imageField` → `renderer:objectui record chrome
(page:header, containers.tsx)`

**Why `Part of` and not a closing line.** The card's item 3 stays open:
whether Studio's object form offers `imageField`. Its `omit` row lives
in this repo, and a written rule says the row is stale once the key is
enforced. The same rule hands the offer itself to a decision, and the
gate refuses the row's deletion unless that decision is made in the same
change (measured below). So item 3 goes back to the seat as an open
question. This PR does items 1 and 2, and adds the declaring example the
director seat's unblock asked for.

## The reader, measured at the pin

`.objectui-sha` on `main` is `9dfaca654311`. `git merge-base
--is-ancestor c096f032793d 9dfaca654311` exits 0 in the objectui clone,
so the pin carries objectui PR 11619's merge. In objectui at
`9dfaca654311`,
`packages/components/src/renderers/layout/containers.tsx`:

- `:1474` `PageHeaderRenderer`. On its record-context branch
(`objectSchema` and `data` from the record context), `:2392` reads
`objSchema?.imageField`.
- `:1458` `recordPictureUrl(value)` resolves the served row's value of
that field. It takes the expanded `{ url }` form, a bare `sys_file` id
(served from `/api/v1/storage/files/`), a legacy URL string, or the
first entry of a `multiple` list that resolves.
- `:2449` `icon={recordPicture}` puts the picture in the record chip's
`icon` slot beside the H1. An `avatar` field is drawn round and cropped,
an `image` field whole in a rounded square. An empty value draws
nothing: no initials, no placeholder.

## What changed

1. **Liveness row** `packages/spec/liveness/object.json` `imageField`:
`planned` → `live`, `verifiedAt` 2026-10-05, `evidenceScope:
cross-repo`. The evidence has three parts: the authoring judgement
(`object.zod.ts#refuseNonPictureImageField`), plus `objectui
9dfaca654311` `containers.tsx#PageHeaderRenderer` and
`#recordPictureUrl` with the lines above. The note is rewritten to what
is true now. Both halves are live, the carrier is gone, and the note
names the reference-app declaration.
2. **Describe and TSDoc** `packages/spec/src/data/object.zod.ts`:
- The `.describe()` drops "Pending renderer: the record chrome does not
draw it yet." Its "is to draw" becomes "draws".
- The TSDoc paragraph that said the reader "does not read the key yet"
now names the reader (`PageHeaderRenderer`, the record chip's icon
slot). No shape, refusal or error text moves.
3. **Hand-written doc** `content/docs/data-modeling/objects.mdx` →
Display: "is to draw ... once the renderer reads it; no renderer draws
it yet" becomes "draws beside the title". The row also gains the
describe's own sentence: an empty field shows no picture.
4. **Regenerated by the repo's tooling, not by hand.** `pnpm --filter
@objectstack/spec check:generated` named two stale artifacts,
`check:docs` and `check:liveness`. `--fix` regenerated exactly those
two:
-
`content/docs/references/{api/metadata,data/object,system/migration}.mdx`;
- `liveness/state-counts/object.md`, where `object` moves to live 51 /
planned 1.
5. **Declaring example**
`examples/app-showcase/src/data/objects/field-zoo.object.ts`:
`imageField: 'f_image'`, on the existing `Field.image()`. This is the
object and field the pin-bump smoke drew with a temporary edit.
- Not seeded. The showcase seeds the field zoo, but a stored `image`
value is a managed `sys_file` id. The seed's own note on
`showcase_task.cover` (`src/data/seed/index.ts`, ADR-0104) says why a
seed cannot honestly mint one.
6. **Changeset** `.changeset/21765-object-image-field-live.md`:
`@objectstack/spec` `patch`, `Clause-②: no`. It is text only.

## Item 3: Studio's object-form `omit` row (measured, not decided)

- **Where it lives:** in this repo. The row is
`packages/spec/src/system/metadata-form-zod-reconciliation.test.ts:394-400`,
in the group "Declared, not enforced yet — no offer until it is
enforced" (`:386`). The form it excuses is
`packages/spec/src/data/object.form.ts`.
- **The written rule** is at `:307-309`: "The not-enforced-yet rows hold
only while the verdict does. Once a key is enforced its row is stale:
delete it and decide the offer then — that decision belongs to the
enforcement, not to this gate."
- **Deleting the row alone, measured** on the committed state with
`scripts/ablation-replace.mjs` in wrap mode:
  - The anchor hit 1 → 0, blob `26f47a279c33` → `bb160bb1b747`.
- The run went **1 failed / 75 passed**: `object: every top-level key
the author may write is offered, or its omission is recorded`, which
printed "object.(root): accepted by the Zod but unauthorable in the form
— offer it, or add a root ledger entry that records why it is not
offered: expected [ 'imageField' ] to deeply equal []".
- Restore: blob == HEAD (`26f47a279c33`) and `git diff HEAD` empty.
Baseline before the probe: 76/76.
- **Why it is left unchanged.** The rule's first half cannot land
without its second half, and the rule calls the second half a decision.
Neither way out is mechanical:
- A form row repeats the call `objectstack-ai#19331` made for 45 keys. It also takes
the four `metadata-forms.generated.ts` catalogs in
`packages/platform-objects`, with authored `zh-CN` / `ja-JP` / `es-ES`
leaves. Those files are outside this claim's file surface.
- A recorded reason fits no existing class. The ruled classes admit only
their ruled key lists.
- **Consequence while it is open:** from this PR's landing, the row's
reason says "liveness verdict `planned`" while the ledger says `live`.
The test reads liveness for no class except the three ruled ones, so it
stays green. The stale reason is what the open question is about.

## Tests and gates (tree `80a7677773`, the final commit)

- `pnpm --filter @objectstack/spec exec vitest run --project local
--maxWorkers=2`: **615 files passed, 18360 passed / 1 todo**, `VERDICT
command-exit 0`.
- `pnpm --filter @objectstack/spec run typecheck`: exit 0, "52 file(s) /
246 error(s) / 135 pinned signature(s) held".
- `pnpm --filter @objectstack/example-showcase verify`: `os validate`
"Validation passed", `tsc --noEmit` clean, **32 files / 399 tests
passed**, `VERDICT command-exit 0`. The dependency closure was built
first (`turbo run build --filter=@objectstack/example-showcase^...`,
60/60).
- `check:liveness`: exit 0. `object` reads 52 classified (live 51,
planned 1); the planned one is `externalSharingModel`. The repo total
reads 1000 live · 1 experimental · 1 live-elsewhere · 108 dead · 8
planned = 1118. The repo-local evidence path and the
`#refuseNonPictureImageField` anchor resolve. The `objectui:` paths are
attributed and counted, never resolved: that is the gate's boundary.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived **107** commands from 9 paths
against merge base `75ddcd1b4`. `--ran` reconciles **107 derived, 107
run, 0 NOT-MEASURED, 0 UNRUN**, and every one exited 0.
- Lint, a measured narrowing:
- `eslint --no-inline-config --format json` on the two changed TS files:
**2 files, 0 errors, 0 warnings**.
- The population is `eslint.config.mjs:971`
(`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`).
- No type-aware linting (`eslint.config.mjs:328`, no
`parserOptions.project`), so this diff cannot move a verdict on an
untouched file.
- Served-object check on a fresh showcase backend (my own port, torn
down after):
- `GET /api/v1/meta/object/showcase_field_zoo` serves `imageField:
'f_image'` with `fields.f_image.type: 'image'`. That is the
`objectSchema` the reader reads.
- I uploaded a 1×1 PNG through `/api/v1/storage/upload/presigned` +
`complete` and set `f_image` to its id. The record then serves `f_image`
as `{ id, name, size, mimeType, url: '/api/v1/storage/files/…' }`, the
form `recordPictureUrl` takes first. That URL answers 200 `image/png`
after the redirect, with bytes identical to the upload.
- Field-consumer warning, an `os validate` A/B on the declaration.
Without it, `f_image` carries "declared but nothing in this stack reads
or displays it" (71 warnings). With it, that warning goes and nothing
else moves (70).
- No ablation or reverse verification applies: this PR authors no
behaviour. It moves a ledger row and text, and adds one declaration the
parse already accepts.

## Acceptance notes

- **Browser check: NOT MEASURED.** It needs a console build at the pin,
about 9 minutes of the shared box. The pin-bump smoke on PR objectstack-ai#21800
already drew this picture on `showcase_field_zoo` / `f_image` at this
same pin. This PR makes that temporary declaration permanent, and the
served-object check above covers what the reader reads.
- `packages/spec/CHANGELOG.md` still says `imageField` "is accepted,
stored and served but nothing draws it". That entry is release-owned and
was true when it shipped, so it is not touched.
- The `omit` row's `why` text is stale from this PR's landing; see item
3 above.

---

_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
…ui#11636, objectstack-ai#11637, objectstack-ai#11635, objectstack-ai#11624 and objectstack-ai#11640) (objectstack-ai#21827)

Fixes objectstack-ai#21807
Clause-②: no

This moves the bundled Console's objectui pin from `9dfaca654311`
(objectstack-ai#21772, PR objectstack-ai#21800) to `0abd4f9f8769fc4c19ad2f96707684876f74c09f`, which
was objectui `main` at write time and is past `39a3e91fad`. The Console
now carries objectui#11636, the fix for objectui#11092: the screen-flow
runner names the flow by its served label, translated. That merge is
what objectstack-ai#20318 waits on. The range also carries objectui#11637
(objectui#11170), objectui#11635 (objectui#11095), objectui#11624
(objectui#11396) and objectui#11640 (objectui#11628).

⛔ This is not a release act. Version PR objectstack-ai#21352 is untouched, and no tag,
publish or Release was made.

## Range

- objectui `git ls-remote origin refs/heads/main` read
`0abd4f9f8769fc4c19ad2f96707684876f74c09f` at 2026-10-05T04:53:29Z, just
before the bump.
- Re-read at 05:37:53Z, it reads `59917c4b2` (objectui#11639, a served
view's toolbar change written inside `config`), one commit past the pin.
This PR does not carry it.
- The objectui clone is full (`--is-shallow-repository`: false). `git
merge-base --is-ancestor` exits 0 against `0abd4f9f8` for `39a3e91fa`,
`c4c506b9e`, `22ddcd5c5` and `1c2e2c46c`.
- `9dfaca654311..0abd4f9f8769` has 5 commits and 0 merges.
- objectui declared 5 changesets over the range: 3 release and 2 release
nothing. Every commit carries a changeset. None declares `major`, and
none carries the breaking annotation. The highest declared level is
`minor`, so the console changeset is `minor`. These counts come from the
bump's own digest and were re-read from the changeset blobs.
- **No commit subject in the range carries `!`.** `git log --format='%h
%s' 9dfaca654..0abd4f9f8 | grep -E '^[0-9a-f]+ [a-z]+(\([^)]*\))?!:'`
matches nothing (exit 1).

| objectui commit | landing | changeset | note |
|---|---|---|---|
| `1c2e2c46c` | objectui#11624 (objectui#11396):
`MasterDetailDetailConfig` is derived from the spec's `details` entry by
reference, member for member the same | release-nothing | moves the
manifest (below) |
| `22ddcd5c5` | objectui#11635 (objectui#11095): a dataset-bound KPI
tile's re-read on the data-invalidation bus gets a pin; tests and a doc
comment only | release-nothing | |
| `39a3e91fa` | objectui#11636 (objectui#11092): the flow runner names
the flow by `flows.FLOW.label`, then the served `flowLabel`, then the
API name | minor (`Clause-②: yes (widening)`) | smoke below; unlocks
objectstack-ai#20318 |
| `c4c506b9e` | objectui#11637 (objectui#11170): one declaration of
per-type NODE SLOTS; `objectui check`, core `validateSchema`, the SDUI
parser's `validateTree` and the `kind:'html'` compile walk them | minor
(`Clause-②: yes (narrowing)`) | answered below |
| `0abd4f9f8` | objectui#11640 (objectui#11628): the External Datasource
panel unwraps the `{ success, data }` envelope | patch | smoke below |

## Declared-breaking entries and the ADR-0087 disposition

**There are none to dispose of.** No commit subject carries `!`, no
changeset declares `major` or the breaking annotation, and the bump
wrote no `adr-0087: TODO` placeholder. `check-adr-0087-registration
--base origin/main` reports "this PR adds no declared-breaking
changeset". `check-changeset-no-major --base origin/main` reports "This
diff introduces no `major` bump".

One entry narrows by its own declaration, so here is how this repo
answers it. **objectui#11170 (`c4c506b9e`, `Clause-②: yes
(narrowing)`)** widens the reach of four objectui validators: each now
judges nodes held in a renderer's declared slots, not only in
`children`. It adds, removes or renames no ObjectStack-authorable key,
and no Zod schema or stored `sys_metadata` shape moves. Its sdui-parser
half only takes effect when a manifest is built with `slotsFor`. This
repo's `scripts/gen-sdui-manifest-node.mjs` calls
`manifestFromConfigs(configs)` without that option, and the regenerated
`sdui.manifest.json` carries 0 `slots` keys. So the save gate's walk
does not move with this bump. The lockstep reading is in Acceptance
notes.

## What changed here (22 files, +255 / -93)

- **`.objectui-sha` and `.changeset/console-0abd4f9f8769.md`.**
`scripts/bump-objectui.sh 0abd4f9f8769fc4c19ad2f96707684876f74c09f
--no-commit` wrote both, with `OBJECTUI_ROOT` set to a read-only
objectui clone in the scratchpad. The range walked completely and the
level was auto-set to `minor`.
- The digest rendered objectui#11170's bullet as its first line, the
bare `**Clause-②: yes (narrowing)**`. That bullet is rewritten to say
what landed.
- A closing paragraph states the range has no declared-breaking entry
and names the release-nothing pair.
- **`sdui.manifest.json` and `scripts/sdui-manifest.record.json`.**
`node scripts/gen-sdui-manifest-node.mjs` regenerated them over the tree
that `pnpm objectui:build` built at the pin. It still has 107
components, and the sha256 moves from `6f921896ffac…` to
`f95d406a584e…`.
- One input moved: `object-master-detail-form`'s `details` gains `of:
"object"` and a description of the spec's closed entry (objectui#11396).
`"of": "object"` occurrences go from 12 to 13.
- The record moves its pin and `modulesRoot`. objectui's workspace
version stays 17.7.0, confirmed against the pin by `check-sdui-manifest
--require-objectui`.
- **`packages/sdui-parser/objectui-lockstep.json`.** `gen:sdui-lockstep`
re-recorded it from the clone at the pin. It records 214 grammar lines
(blob `0131f27cf86d`), 25 diagnostic codes and containment predicate
`76c18fb95d1f`, all unchanged, so no port is owed by that gate.
- **The 54 asserting pin citations in `packages/spec/src`, re-measured,
not restamped.**
- **Method.** The range changes 50 paths. Each asserting record's cited
objectui paths were resolved against the tree at `9dfaca654`. Ambiguous
bare names were disambiguated by the record's own directory. Each
`index.tsx` and `types.ts` cited is a `plugin-kanban`,
`plugin-dashboard`, `plugin-map`, `plugin-gantt`, `plugin-grid`,
`plugin-tree` or `plugin-timeline` file. None is
`plugin-form/src/index.tsx` or `sdui-parser/src/types.ts`, the two
same-named files the range touches.
- **Result.** No asserting record cites a changed path. So every cited
file is byte-identical across the hop, and every anchor held unmoved.
- **Records.** Each of the 41 hand-written records gains a dated
2026-10-05 hop sentence and keeps its earlier history.
- **Counts.** Three records carry a count, and each was re-taken by its
own method; all three read the same at `2e818d0b5`, `9dfaca654` and
`0abd4f9f8`:
- the `keyboardNavigation` hit lines: 15, against 3 for the
`schema.editable` control;
- `ObjectKanban.tsx`'s `quickAdd` / `onQuickAdd`: 2 each, against 11 for
`onCardClick`;
- the `ElementDataSourceGate` occurrences in the five `src/index.tsx`
shells: 0, 3, 3, 3 and 4.
- **Corpus counts.** The six migration entries' counts were re-taken
with `git grep -o -F`. That method first reproduced every `9dfaca654`
number: 7632 files, `objectstack` 17313, `@objectstack/spec` 7186,
`timeout` 1360, `useState` 2477, `TTL` 182, `tenant` 1318,
`RuntimeConfig` 293, `resourceLimits` 2, `window` 4193, `period` 238,
`interval` 195, `metrics` 401 and `Span` 508.
- The new readings are 7650 files, `objectstack` 17390,
`@objectstack/spec` 7209 and `useState` 2478. `window` reads 4194. Every
other control is unchanged.
- All 98 checked tokens (the export lists of
`plugin-lifecycle-advanced.zod.ts`, `tracing.zod.ts` and
`metrics.zod.ts`, plus every named key) read the same at both pins:
every zero is still zero, and `Span` / `SpanSchema` read 508 / 57.
- `packages/spec/src/migrations/registry.ts` was regenerated with
`gen:migration-registry`.
- **`.changeset/objectui-pin-citations-0abd4f9f8769.md`** is a
`@objectstack/spec` patch, because the `FormField.span` describe and six
migration descriptions name the pin.
`content/docs/references/ui/view.mdx` was regenerated by
`check:generated --fix`.

No example, test or gate needed adapting, and no code changed outside
generated records, citations and changesets.

## Console build (the local Console Pin Gate equivalent)

`turbo run build --filter=@objectstack/client...
--filter=@objectstack/spec...` and then `pnpm objectui:build` ran as one
command under the verify lock. That is a clean build: mode 3
shallow-cloned objectui at the pin into `.cache/objectui-0abd4f9f8769`.
Exit 0, 10m51s on the shared box. The build log reports:
- "Bundle canary 'import/jobs' present".
- "Single-zod canary: exactly one zod version literal
{major:4,minor:6,patch:5}".
- **"Console bundle carries THIS tree's @objectstack/spec, and only
it"**: the spec-injection check passes.
- "@objectstack/console dist ready (64232 KB) from
objectui@0abd4f9f8769".

`check:console-sha` and `check:console-injection` (self-test 67
assertions, then the dist check) exit 0 against that dist.

## Browser smoke: `examples/app-showcase` with the Console built at
`0abd4f9f8769`

The server ran `pnpm dev -- --fresh --ui --no-watch -p 41907` with
`OS_PORT=41907`, on its own ephemeral DB with the seeded admin. Headless
Chromium (`/opt/pw-browsers/chromium`) drove it, signing in through the
console's login form. Page errors, console errors and every 4xx/5xx
response were captured. Only the PIDs this run started were stopped.

**Flow label (objectui#11636 / objectui#11092).** The launch was Tasks
list, select a row, then the toolbar's "Reassign…"
(`showcase_bulk_reassign`, which targets the screen flow
`showcase_reassign_wizard`, label "Reassign Task").

| leg | trigger answer | runner header line | dialog title (accessible
name) | API name shown | completion toast |
|---|---|---|---|---|---|
| `en` | 200, `status: paused`, `flowLabel: "Reassign Task"` | `Reassign
Task` | `New Assignee` (the screen's own title) | no | `Flow "Reassign
Task" completed` |
| `zh-CN`, with a bundle entry `flows.showcase_reassign_wizard.label` |
200, `flowLabel: "Reassign Task"` | `重新分配任务` | `New Assignee` | no |
`流程「重新分配任务」已完成` |

- The resume answered 200 with `flowLabel` on both legs.
- **The showcase bundle declares no `flows` translations** (`git grep`
finds none), so the `zh-CN` leg needed one to exist. It was made by a
temporary local edit, as objectstack-ai#21800's smoke did for `imageField`:
- `node scripts/ablation-replace.mjs --hold` added `flows: {
showcase_reassign_wizard: { label: '重新分配任务' } }` to the `zh-CN` block of
`examples/app-showcase/src/system/translations/index.ts`.
  - The server booted with `--compile`.
- The file was restored at once with `--restore`. The tool reports the
blob back to HEAD's `f0517049b565` and `git diff HEAD` empty, and `git
status --porcelain` is empty.
- `GET /api/v1/i18n/translations/zh-CN` served the entry, and the
session's `html[lang]` read `zh-CN`.
- The showcase `dist/` was rebuilt afterwards (`turbo run build
--filter=@objectstack/example-showcase --force`), and the held string
has 0 hits in `dist/objectstack.json`. Nothing of the edit is in this
diff.
- **Verdict:** at this pin the runner header and the completion toast
name the flow by the active language's `flows.FLOW.label`, then the
served label, and never by the API name. The launcher button still reads
the ACTION's label ("Reassign…"), which is the action's own string and
not the flow's.

**The range's other landings.**

| landing | surface | observed |
|---|---|---|
| objectui#11640 (objectui#11628) | Setup →
`metadata/datasource/showcase_external` | The External Datasource panel
lists the remote tables `customers` (7 columns) and `orders` (7), each
with Import. "Refresh catalog" (`POST …/external/refresh-catalog` 200)
shows `snapshot 10/5/2026, 5:25:23 AM`. "Run validation" (`POST
…/external/validate` 200) renders "All 2 objects match the remote
schema." with `showcase_ext_customer` and `showcase_ext_order`, and no
render error. |
| objectui#11624 (objectui#11396) | `page/showcase_project_workspace`
(`object-master-detail-form`, `details: [{ title: 'Tasks', childObject:
'showcase_task', addLabel: 'Add task' }]`) | The master form draws its 6
fields. The Tasks section draws, and "Add task" opens the child's inline
form with 14 more fields (Title*, Assignee, Priority, …). 0 page errors.
|
| objectui#11637 (objectui#11170) | the three `kind:'html'` pages:
`showcase_start_here`, `showcase_capability_map`,
`showcase_command_center_jsx` | All three render (1563 / 2426 / 882
characters of text), with no compile or validation text and 0 page
errors. |
| objectui#11635 (objectui#11095) | `dashboard/showcase_ops_dashboard`,
`showcase_revenue_pulse`, `showcase_chart_gallery` | The ops tiles read
Active Projects 2, At-Risk (Red) 1, Awaiting Review 2 and Total Budget
1,090,000, matching objectstack-ai#21710's REST cross-check. All three dashboards have
0 page errors. This landing changes no executable code (its changeset:
tests and one doc comment). An out-of-band REST `PATCH` of a task to
`in_review` did not re-read the open tile: it stayed 2 with 0 dataset
queries in 6s, and read 3 after a reload. That mutation never travels
the console's own invalidation bus, so this is no reading of
objectui#11095's pin. **NOT MEASURED** there. |

**Console messages across the run:** 0 page errors. The failed loads are
the pre-login `401 GET /api/v1/auth/get-session`, one `404` per page
load that the response listener did not attribute (`/favicon.ico`
answers 404 on this server, as objectstack-ai#21800 recorded), and `404 GET
/api/v1/meta/datasource/showcase_external?state=draft`, the panel's
draft probe for a datasource that has no draft.

## Gates and tests (head `e40526e564`)

- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` derived 127 commands from the 22-path diff, and all 127 were
run at `e40526e564` and exited 0. `--ran` reports "127 derived, 127 run,
0 NOT-MEASURED, 0 UNRUN", with every exit code recorded. The full
workspace build (`turbo run build --filter=!@objectstack/docs`) ran
first, so no dist-reading gate met a missing prerequisite.
- Also exit 0:
- `check:objectui-pin-citations --verify-anchors` with `OBJECTUI_ROOT`
at the pin: "54 asserting objectui pin citation(s) match .objectui-sha
(0abd4f9f8)", and "7 anchor content assertion(s) verified against
objectui at 0abd4f9f8".
- `check:objectui-bump` (20 assertions across 5 cases),
`check:sdui-lockstep`, `check-sdui-manifest --require-objectui`,
`check:console-sha`, `check:console-injection`.
- `check-adr-0087-registration --base origin/main` and
`check-changeset-no-major --base origin/main`.
- `@objectstack/spec check:generated`: all 15 artifacts current after
the `--fix`.
- `pnpm --filter @objectstack/spec exec vitest run`: 668 files, 19261
passed, 1 todo. `pnpm --filter @objectstack/spec typecheck`: exit 0.
- `@objectstack/sdui-parser` test (14 files, 225 passed) and typecheck:
exit 0.
- These suites read the regenerated manifest:
  - `@objectstack/lint` (119 files, 5627 passed);
- `@objectstack/metadata-protocol`
`src/protocol.runtime-authoring-gate.test.ts` (70 passed);
- `@objectstack/cli` unit tier `src/utils/sdui-manifest.test.ts` and
`test/validate-build-gate-parity.test.ts` (2 files, 79 passed).
  - The CLI integration tier is declared to CI.
- `eslint --no-inline-config --format json` on the 15 changed TS files:
15 files, 0 errors and 0 warnings.
- The lint population is `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`
(`eslint.config.mjs:971`).
- The config enables no type-aware linting (`:328`), so this diff cannot
move a verdict on an untouched file.
  - Repo-wide `pnpm lint` is left to CI.

## Acceptance notes

- **Lockstep, a reading and not a gate result.** objectui#11170 gives
objectui's `validateTree` a slot walk: `slotElements` plus the
`comp?.slots` loop in `packages/sdui-parser/src/validate.ts`. This
repo's port has none (`git grep -n slots packages/sdui-parser/src`: 0).
- The walk only fires for a manifest built with `slotsFor`, and this
repo's committed manifest carries no `slots`. So the save gate and
objectui's runtime parser still agree on that manifest.
- objectui's `kind:'html'` compile (`getJsxManifest`) is now built with
`slotsFor`. A slot-held node that fails validation could fail the
renderer's compile while this repo's save gate accepts it.
- `check:sdui-lockstep` compares the grammar region, the codes and the
containment predicate, so it cannot see a walk-depth change. That class
is outside its reach by its own header ("CANNOT see … WHEN a code fires"
beyond the predicate).
- Not measured through a public door here, so it is noted rather than
filed. Carrier: none.
- `main` moved three commits past this branch's base (objectstack-ai#21810, objectstack-ai#21808,
objectstack-ai#21809). `git merge-tree --write-tree HEAD origin/main` is clean. Two of
them touch files this diff touches:
`api-methods-batch-conformance.test.ts` (test titles) and
`component.test.ts` (objectstack-ai#21808 re-points a non-asserting anchor). The
merged tree still carries 1 and 6 citations at the new pin and 0 at the
old one. No merge was made; the merge queue rebuilds on the current
`main`.
- objectui `main` reached `59917c4b2` (objectui#11639) after the pin was
read. It is not in this range.
- Writes: one draft PR through the relay and the report comment on
objectstack-ai#21807. No label, PR assignee, ready flag or auto-merge was written;
this dispatch's write budget names none of them.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…objectstack-ai#21994)

Fixes objectstack-ai#21989
Clause-②: no

## What this is

This PR adds the curated release page for 17.7.0,
`content/docs/releases/v17/17-7.mdx` (1,402 lines). It was written after
the publish: npm `latest` moved on 2026-10-06, and
`@objectstack/spec@17.7.0` went out at 12:22:14Z. It also wires the page
in:

- `content/docs/releases/v17/meta.json`: `17-7` comes ahead of `17-6`.
- `content/docs/releases/v17/index.mdx`: the status blockquote, the
minors warning, the per-release list and the checklist links now name
17.7.0 as current. This is the same shape objectstack-ai#21362 used for 17.6.0.
- `scripts/docs-audit/handwritten-docs.json`: the page joins the
docs-accuracy audit scope.
- `content/docs/releases/v17/17-6.mdx`: one dated correction
(2026-10-06) on the anonymous-endpoints known issue. objectstack-ai#21158 was closed
as not planned on 2026-10-04.

The page follows the 17.6 page's structure:

1. Highlights.
2. What's new: the counts, and the runtime changes that happen silently.
3. Breaking changes and migration, triaged by door and subject. It
includes a coverage table that names the section carrying the migration
for every ADR-0087 entry added since 17.6.0 (8 conversions, 41 D3
entries).
4. New capabilities.
5. Notable fixes. Security fixes are described only as classes and
doors.
6. New in Console: each objectui declared-breaking change, with this
repo's answer.
7. The code that shipped but is not listed.
8. The upgrade checklist. Every line is marked *Not exercised.*

## Sources and counts

- The version commit `4e4e881427` (objectstack-ai#21352) consumed 323 changesets. 322
are new. One, `748b240` (objectstack-ai#21270), shipped in 17.6.0 and is listed again;
the page explains this in its own section.
- 69 CHANGELOGs carry a 17.7.0 section, and 48 of them have entries.
Their 444 entries (194 minor, 250 patch) de-duplicate to the 323
changesets.
- Two commits landed on `main` after the Version Packages PR's last
refresh and before it merged:
  - `8a399b2b15` (objectstack-ai#21977, for objectstack-ai#21923)
  - `04e776b39a` (objectstack-ai#21976, for objectstack-ai#21968)

Both are ancestors of the version commit (exit 0 for each), so the
17.7.0 packages carry their code. But the version commit did not consume
their changesets, so no 17.7.0 CHANGELOG line names them. The
release-integrity audit named both in a warning.
- objectui: the pin moved five times and ends at `0abd4f9f8769`:
  - `89cad75d5570` (objectstack-ai#21380)
  - `ab1879721595` (objectstack-ai#21625)
  - `2e818d0b51ec` (objectstack-ai#21710)
  - `9dfaca654311` (objectstack-ai#21800)
  - `0abd4f9f8769` (objectstack-ai#21827)

Across 173 commits, 254 changesets were added and 229 of them release
something. 65 are declared breaking, plus one commit marked `!`. The
Console table answers each.
- `PROTOCOL_VERSION` is still 17.0.0.

## Premise corrections

- The dispatch named two pin moves ending at `9dfaca654311`. The tree
has five, ending at `0abd4f9f8769` (`8832655`, objectstack-ai#21827). The page covers
all five.
- One new D3 id contains a word that `check:role-word` refuses on docs
pages, and release pages are not in its baseline. The coverage table
therefore names that entry by its subject and points at `os migrate meta
--from 17`.

## Fact-check

A second pass checked every cited SHA, PR number, key name and
behavioural claim against the commits, changesets and registry entries.
It corrected **31 claims** (commit `e4a6280b46`).

Two more corrections came earlier, while drafting:
- objectstack-ai#21361 is closed; it is not tracking the issue.
- There are seventeen `ui-object-*` members, not eighteen.

Mechanical checks on the final page:

- All 276 cited SHAs resolve, in objectstack or in an objectui clone
carrying the final pin's history.
- Each of the 216 distinct sha–PR pairs matches its commit subject.
- Every printable new D3 id (40) and all 8 conversions appear on the
page.
- The MDX for 17-7, 17-6 and index compiles with @mdx-js/mdx 3.1.1 and
remark-gfm 4.0.1.

After the fact-check, `main` gained `1abfc58` (objectstack-ai#21985), which lands the
read half of objectstack-ai#21922. It is not an ancestor of the version commit: the
test returned exit 1, and the control commit `753e7a1` returned exit 0.
So in 17.7.0, the metadata door's reads still serve a stored row under a
code-defined datasource name. Commit `8347e0d172` says so in the
datasource migration bullet.

## Independent fact-check and fix round

An independent, read-only fact-check of head `8347e0d172` returned
**FAIL** with 13 findings (record: objectstack-ai#21989 comment 6018402030): 2 wrong
facts (a flow's `get_record` node still reads the stored-metadata
tables, in projected form), 1 security line that named the filter shapes
and depth threshold evading 17.6.0's refusal, 1 breaking change missing
from the Breaking section (`0fc8087`, objectstack-ai#21626), 1 link whose label did
not match its target, 4 overstatements, 1 missing security fix
(`49524f6`, objectstack-ai#21420), 1 missing rollback caveat on `os secret rewrap
--apply`, and 2 minor wording issues.

All 13 were re-verified against their sources and applied in
`a53c972fa7`; none was refuted. A scan for any other line naming a
bypass shape of a fixed issue reduced two more lines to their class
(`0728cbf`, `fb69825`).

## Measured on `a53c972fa7`

- Derived gates: `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derives 57 commands; all 57
exited 0 (`--ran`: "57 run, 0 NOT-MEASURED (a DERIVED zero)"). The
verdicts include:
  - check-doc-anchors: 458 links resolve.
- check-issue-citations: 305 resolve as a PR, 9 resolve, 15 are
cross-repo; every citation this change adds resolves.
- `check:role-word`, `check:release-notes` and
`check:release-page-status`: OK.
- check-release-section-coverage: OK, both plain and with `--strict` (10
minors).
  - The docs-audit scope check and `check:nul-bytes`: OK.
- Docs production build: `TURBO_FORCE=true pnpm turbo run build
--filter=@objectstack/docs`, run under the verify lock, reports `Tasks:
2 successful, 2 total` and `Cached: 0 cached`. The built
`releases/v17/17-7.html` carries the corrected text and none of the
removed text.
- Mechanical checks: 231 distinct sha–PR pairs, 0 unresolved, 0 subject
mismatches; the MDX of 17-7, 17-6 and index compiles.
- Not measured locally: the repo-wide lint and the CI-only families. CI
owns them.

## Not in this PR

- No changeset. The PR touches only docs and a docs-audit list, nothing
a package ships (`skip-changeset`).
- Nobody has walked the 17.6.0 → 17.7.0 upgrade. The checklist says so
on each line.

---
_Generated by [Claude
Code](https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(objectui): bump the console pin past objectui c096f03 — it carries objectui#11611, #11614 and #11619, which unlock #21714, #21765 and #21768

1 participant