Skip to content

chore(objectui): bump the console pin to 87af769e9a3e with 27 re-measured citations, and fold in the two-code sdui-parser port (#17645) - #19398

Merged
os-project-manager merged 11 commits into
mainfrom
claude/issue-17429-objectui-pin-bump
Sep 21, 2026
Merged

os-project-manager merged 11 commits into
mainfrom
claude/issue-17429-objectui-pin-bump

Conversation

@os-project-manager

@os-project-manager os-project-manager commented Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #17429
Part of #17645 — that card's deliverable (the two-code port) lands here; the director seat ends card 17645 with the landing record after the merge (a seat-written record, not a closing reference). The card's take-over from the offline domain:devx seat is the maintainer's instruction (「18723 os-try-charles 已下班,你也接手」), recorded on the card; the carrier reader's cross-author row (C9) cannot be cleared without the offline holder's own Release:, so the card is carried as Part of, not as a closing reference.

Clause-②: yes

Corrected from no under ruling batch #201 item 1 (maintainer 「201 A」, record 5753263180 on #17645): this PR now also carries #17645's two-code sdui-parser port, so the save gate's diagnostic set grows (inert-quick-add, member-type-mismatch); the pin bump and the 27 re-measured citations alone moved no accept set. Both landed on this branch: the fold-in commit 6ef8344645 and the citation-spelling fix fc25a5925d (the head); the lockstep record needed no re-recording (see the fold-in section).

(Pin-bump half, as originally declared:) Declared from the diff, not from the card: nothing in the pin bump adds, removes or renames an authorable key. The two packages/spec edits with a published face are .describe() SENTENCES (Dataset measure format, FormField.span); check:authorable-surface and check:api-surface both report their artefacts current with no regeneration, and pnpm --filter @objectstack/spec check:generated reports all 15 artefacts up to date after gen:docs and gen:migration-registry ran. The same authored metadata is accepted and refused as before, so neither arm of the closed pair applies.

What moved

.objectui-sha: 53ded82bf7a494f54e344e19099dbf00854b8694 → 87af769e9a3ee28ace099fdd653d3ebd79fe82e2 (objectui origin/main tip read 2026-09-20; cfcc17d9dd04 — the revision PR #18723 ported from — is an ancestor of it, git merge-base --is-ancestor exit 0).

Produced by scripts/bump-objectui.sh --no-commit 87af769e9a3e, never by hand. Everything the pin travels with:

Artefact How
.changeset/console-87af769e9a3e.md the bump script's own digest — 584 releasing changesets of 891 added across 1156 non-merge commits, @objectstack/console: minor, 98 declared-breaking entries listed
sdui.manifest.json + scripts/sdui-manifest.record.json node scripts/gen-sdui-manifest-node.mjs. The manifest is BYTE-IDENTICAL (sha256 49211fee7792…, 57 components): the pinned commit declares the same published @object-ui 17.6.0 the old pin did, so only the provenance record moves. check:sdui-manifest exit 0
packages/sdui-parser/objectui-lockstep.json pnpm gen:sdui-lockstep against a worktree parked at the new pin (--update refuses off the pin). See the RED below
27 .objectui-sha citations under packages/spec/src/** each re-MEASURED against the new pin's tree — see the table
content/docs/references/ui/{dataset,view}.mdx gen:docs, from the two corrected describes
.changeset/17429-pin-bump-describe-corrections.md @objectstack/spec: patch for those two sentences

Lockfile / override state: pnpm install in this worktree produced no pnpm-lock.yaml change, and the bump script writes none — the console SPA is vendored as a built dist/, not as a dependency, so this bump moves no dependency edge in this repo.

Fold-in: the two-code sdui-parser port (ruling batch #201 item 1, maintainer 「201 A」)

Commit 6ef8344645 brings PR #18723's port onto this branch verbatim. Eight files, each byte-identical to the blob on claude/issue-17645-sdui-lockstep-port (git hash-object here vs git rev-parse claude/issue-17645-sdui-lockstep-port:PATH there — all eight SAME, no hunk rewritten):

path blob
packages/sdui-parser/src/kanban-quick-add.ts 20f9f74b9f05
packages/sdui-parser/src/validate.ts 156a264073f7
packages/sdui-parser/src/types.ts 567bc1259bea
packages/sdui-parser/src/index.ts 2ea35795b9cf
packages/sdui-parser/src/codegen.ts 76a7d654f1fd
packages/sdui-parser/src/__tests__/inert-quick-add.test.ts 723cad21333a
packages/sdui-parser/src/__tests__/member-type-mismatch.test.ts 4b87cadaa067
.changeset/17645-sdui-parser-lockstep-port.md 7c171fddfc43

Deliberately NOT brought from that branch: .objectui-sha, scripts/sdui-manifest.record.json (both already this branch's, at 87af769e9a3e) and packages/sdui-parser/objectui-lockstep.json.

The lockstep record was neither text-merged nor re-recorded — and needed neither

This branch already carries the record taken at the live pin in the pin-bump round: objectui.rev = recordedAgainstPin = 87af769e9a3ee28ace099fdd653d3ebd79fe82e2, 26 diagnostic codes including inert-quick-add and member-type-mismatch. --update re-reads objectui's side only, so a re-record here would have rewritten the same bytes. Attempted anyway, and it REFUSED, which is the generator working: pnpm gen:sdui-lockstep exit 1, [head-off-pin] the objectui checkout is at c2f0f48329de, and .objectui-sha names 87af769e9a3e; record file byte-unchanged across the attempt (e0a6988b1c9a before and after). The remedy it prints — parking the sibling checkout on the pin — was not taken: that is a git write inside a read-only checkout this container shares with other agents, and the record it would produce is the one already committed.

Result — check:sdui-lockstep exit 0 on the head: OK — this copy is byte-identical to objectui@87af769e9a3e over 214 grammar line(s) [blob 0131f27cf86d] and agrees on all 26 diagnostic code(s), across 8 non-test source(s). --self-test exit 0 alongside. check:sdui-manifest exit 0 (manifest intact, sha256 49211fee7792, recorded at the live pin).

The type-check red PR #18723 carried is closed by the pin, not by a code change

The red was check:objectui-pin-citations inside the Type Check · source gates job. It reds exactly on the two heads of #18723 that claimed .objectui-sha = cfcc17d9dd04; on this branch, with the live pin, the gate is exit 0 (27 asserting citations match, 48 historical recorded and not checked, 7 anchor content assertions verified). Reverse-verified here through scripts/ablation-replace.mjs: rewriting .objectui-sha to cfcc17d9dd04 turns the gate RED with five anchor-content mismatches; restored and proven (blob back to 035b6937d641, git diff HEAD empty). So the fix is the fold direction itself: the port lands on the branch whose citations are already re-measured at the pin. No edit was made inside packages/sdui-parser/** for it, and no @ts-ignore or any was added anywhere; tsc --listFiles confirms both new test files are inside the package program.

Ported from cfcc17d9, re-verified against the LIVE pin

objectui DID move packages/sdui-parser between cfcc17d9dd04 and 87af769e9a3e: one unrelated feature, objectui#6771's retired body child-list dialect (body-dialect.ts +99, index.ts +1, validate.ts +31/−7, its own test). It touches neither ported code and stamps only not-a-container and unknown-prop, both already in the 26-code set — which is why 26 = 26 holds at the live pin with it unported. Against objectui at 87af769e9a3e the whole non-comment divergence of the five ported sources is four items, three pre-existing (ValidationResult vs ManifestValidationResult; the code: 'inert-quick-add' inline literal forced by check:dispatcher-error-vocabulary, pinned equal to the constant by a test row; provenance.ts and assertFullyLoaded / RegistryConfigLike.lazy absent here) and one new since cfcc17d9 (body-dialect.ts absent here — see Acceptance notes). The port strictly REDUCES divergence in every file it touches: validate.ts 146 → 73, index.ts 114 → 92, codegen.ts 30 → 3, types.ts 96 → 85 diff lines vs objectui at the live pin.

Ablations — re-run on this tree

Three, each through scripts/ablation-replace.mjs (anchor ×1, mutation proven on disk by anchor count and blob change, restore proven by blob == HEAD AND an empty git diff HEAD): (1) checkKanbanQuickAdd stamps nothing — 5 failed / 8 passed of 13; (2) the validator's member check reads no declared arms — 7 failed / 14 passed of 21; (3) the codegen narrows no member type — 3 failed / 18 passed of 21. Baselines 13 and 21 passed; in each leg the failures are the SUBJECT rows and every control row stays green. Both suites import ../index.js, a relative intra-package specifier, so no rebuild is in the resolution path.

Citation spelling fix (fc25a5925d)

CI's Lint & Repo Gates went red on check-issue-citations at 6ef8344645: two issue-number citations on docblock lines this branch rewrote. packages/spec/src/ui/view.zod.ts — objectstack#17328 respelled bare #17328 (the issue exists, closed completed by merged PR #17671; the gate probes only unqualified citations, so the this-repo-qualified spelling was judged against a board never asked for it). packages/spec/src/ui/component.zod.ts — #10274 measured deleted by --probe-cause (gone from the board and 404 on the web endpoint, not transferred); no replacement guessed — the sentence now states in prose that the number no longer resolves and names the live record (that block plus check:objectui-pin-citations). node scripts/check-issue-citations.mjs exit 0 at the head (31 judged, 0 findings); check:generated unchanged (TSDoc, not .describe()).

Post-landing smoke evidence (triage's condition, issue comment 5690507317)

bash scripts/pm/os-verify-lock.sh -c 'bash scripts/build-console.sh'
  os-verify-lock: VERDICT command-exit 0 · held the lock 653s (10m53s) · waited 0s
  ✓ 8786 modules transformed.
  ✓ Bundle canary 'import/jobs' present — framework client is in the bundle.
  ✓ Console bundle carries THIS tree's @objectstack/spec, and only it.
  ✓ @objectstack/console dist ready (60228 KB) from objectui@87af769e9a3e
Reading Value
exit code 0
packages/console/dist size 60228 KB — 51,702,779 bytes, 3,285 files
content hash sha256 259d2dfecaa12507fd3166a3f91c66c52581990e5ccac3899757027fa6ac108d over find … -type f | sort | xargs sha256sum | sha256sum
provenance stamp packages/console/dist/.objectui-sha = 87af769e9a3ee28ace099fdd653d3ebd79fe82e2
pnpm check:console-sha exit 0 — "Console dist matches the objectui pin"

The console build is GREEN at the new pin. 584 commits of objectui change came with it and none of them broke it.

The citations — re-MEASURED, never re-pointed

27 asserting citations, all of them stale at the new pin. Each was re-derived by reading objectui at BOTH shas and comparing content, not by arithmetic on a line number. pnpm --filter @objectstack/spec check:objectui-pin-citations exit 0; --verify-anchors exit 0 with 7 of 7 content assertions verified against objectui at 87af769e9.

Corpus-absence records (7 citations + 6 generated copies)

These cite a zero-hit count over the pinned tree, not a file:line. Re-counted with git grep -F -o at both shas; the METHOD was calibrated first by reproducing the old pin's own numbers exactly (6409 tracked files, useState 2304).

Record Cited Reads at 87af769e9 Verdict
18.kernel-health-check-and-hot-reload-durations-unit-in-key.ts:48 13 exports of plugin-lifecycle-advanced.zod.ts + debounceDelay = 0 / 6409 files; controls objectstack 10171, @objectstack/spec 3479 all still 0 / 8228 files; controls 12966, 4997 unchanged verdict, counts re-measured
18.kernel-runtime-config-timeout-unit-in-key.ts:45 resourceLimits.timeout 0; controls timeout 832, RuntimeConfig 236, resourceLimits 2 0; 1075, 240, 2 — and both resourceLimits hits are still prose in packages/app-shell unchanged
18.logging-durations-unit-in-key.ts:48 4 names 0; controls useState 2304, timeout 702 0; 2383, 1075 unchanged — ⚠️ but timeout 702 was WRONG AT THE PIN IT NAMED: that corpus reads 832. An error since written, found only by re-measuring, corrected here
18.system-metrics-jsdoc-durations-unit-in-key.ts:70 6 def names + 2 keys 0; controls window 2710, timeout 832, period 160, interval 156, metrics 301 all 0; 3464, 1075, 170, 170, 324 unchanged
18.system-tracing-otel-exporter-durations-unit-in-key.ts:66 37 exports of tracing.zod.ts + 4 key names 0; Span 404 / SpanSchema 40 unrelated; controls 10171, 3479 all 0; 486 / 53; 12966, 4997 unchanged
18.tenant-schema-cache-ttl-unit-in-key.ts:31 schemaCacheTTL 0; controls TTL 112, tenant 819 0; 156, 976 unchanged
migrations/registry.ts ×6 the generated concatenation of the six above regenerated with pnpm --filter @objectstack/spec gen:migration-registry follows its entries

Read-point records (20 citations + 7 content assertions)

Record Anchor, as cited At 87af769e9 Verdict
data/api-methods-batch-conformance.test.ts:64 ObjectGrid.tsx:3538-3553 :3940-3955 MOVED, span byte-identical (git hash-object 6133933199… both sides)
hooks/useBulkExecutor.ts:284-289 :298-303 MOVED, span byte-identical (0108334833…); ⚠️ the FILE is no longer byte-identical (+36/-22), so the record's old identity argument is gone and the span was re-read
ui/component.zod.ts tab-item icon + ui/component.test.ts:376 containers.tsx:730-736 :853-859 MOVED, 7 lines byte-identical
containers.tsx:789 (registration items input) :912 MOVED and the LINE CHANGED — it lost label: 'Tabs', gained of: 'object' and a longer description; the member list the record cites is unchanged
ui/component.zod.ts accordion-item icon + component.test.ts:286 containers.tsx:919-925 :1069-1075 MOVED, byte-identical
containers.tsx:966 :1116 MOVED and the LINE CHANGED, same way
ui/component.zod.ts button icon + component.test.ts:3489 form/button.tsx:43, :72, :74 unchanged UNCHANGED
form/button.tsx:85-102 (inputs), :103-107 (defaultProps) :85-97, :98-102 MOVED and SHRANK — every input dropped its label and defaultValue; the four input names and the ABSENCE of an icon input, which is what the record asserts, both hold
action/resolve-icon.ts:129-132 (resolveIcon) :322-328 MOVED and REWRITTEN — the tail no longer indexes lucide-react's icons record; it asks recordIconName and hands the pair to lazyIconComponent. Accept/reject behaviour unchanged
resolve-icon.ts:117-120, :100-105, :90-92 :302-305, :153-158, :143-145 MOVED, byte-identical
lib/lazy-icon.tsx:66-92 :98-124 MOVED, byte-identical
ui/component.zod.ts object-metric icon plugin-dashboard/src/index.tsx:204 :237 (registration now opens at :227) MOVED and the LINE CHANGED — the input's label: 'Icon (Lucide name)' is GONE; the key is still published, now as a bare { name: 'icon', type: 'string' }
ObjectMetricWidget.tsx:142 / :474; MetricWidget.tsx:312-321 / :373-382; lazy-icon.tsx:66-80 :174 / :483; :351-360 / :412-421; :98-112 MOVED, byte-identical
ui/component.zod.ts kanban limit ×2 + component.test.ts:3360 ObjectKanban.tsx:264 ($top: schema.limit ?? DEFAULT_KANBAN_LIMIT) :676 ($top: resolveRowLimit(schema.limit, DEFAULT_KANBAN_LIMIT)) MOVED and REWRITTEN — objectui#9925 put a refusal in front of it (a contract-refused cap is dropped and reported at :553). The pinned fact, that limit lowers into the query's top-level $top, holds
ObjectKanban.tsx:71; plugin-kanban/src/index.tsx:395-398 :84; :447-450 MOVED, byte-identical
ObjectKanban.tsx:143 + plugin-kanban/src/types.ts:134 (KanbanSchema.limit?: number) ⚠️ VANISHED KanbanSchema was RETIRED at this pin (maintainer ruling 2026-09-09) and types.ts declares the member no more. Re-derived rather than re-pointed: the published twin is ObjectKanbanSchema, imported at ObjectKanban.tsx:10, declaring limit?: number at packages/types/src/objectql.ts:3735
ElementDataSourceGate.tsx:236-241 :316-331 MOVED and EXTENDED — the branch gained objectui#9899's presence-is-not-authorship test and a describeDisplacedRowLimit report
ui/component.zod.ts kanban quickAdd + component.test.ts:3427 ObjectKanban.tsx:931; plugin-kanban/src/index.tsx:196; KanbanImpl.tsx:355 / :368 :1563; :313; :621 / :634 MOVED, byte-identical. The absence re-counted at this pin: quickAdd / onQuickAdd 0 each in ObjectKanban.tsx, against 11 (was 6) for the sibling onCardClick
ui/dataset.zod.ts:207 measure format dataset-format.ts:185-198, date-display.ts:131-164 / :117, :195 :229-264 (routed at :370), :198-233 / :152, :260-262 ⚠️ SUBSTANCE REVERSED — see below
ui/view.zod.ts:2945 FormField.span the widest-tier-only class claim at 53ded82bf7 spanLadderFor, form/form.tsx:204-231 ⚠️ SUBSTANCE REVERSED — see below
ui/view.zod.ts ListMapConfig, 7 content assertions ListView.tsx:113 / :67; ObjectView.tsx:1381; objectql.zod.ts:562 / :313; ObjectMap.tsx:373 / :378 :146 / :85; :1764; :1574 / :734; :385 / :390 all 7 MOVED; ⚠️ one LOST ITS SYMBOL — FLAT_MAP_CONFIG_KEYS became the total FLAT_MAP_CONFIG_SPELLING map (objectui#9950), so style now DOES reach the flat product, as mapStyle. The quote was re-read, not re-pointed

The two records whose SUBSTANCE the range reverses

Both are claims about what the SHIPPED renderer does, so the pin is what dates them, and both were re-stated from the new tree. Each also carried a published .describe() saying the same now-false thing, which this bump is what falsifies — so both sentences are corrected here and content/docs/references/ui/{dataset,view}.mdx regenerated from them. That is the @objectstack/spec: patch changeset in this PR.

  • Dataset measure format said "a datetime value ignores it". objectui#8352 is inside the range: formatMeasureDate's datetime arm now SELECTS a formatter — relative to formatRelativeDate (:260), short to formatDateTime(v, { locale, style: 'compact' }) (:261), everything else to the bare formatDateTime(v, { locale }) (:262). A date PATTERN is still ignored on both arms, which is the half that survives.
  • FormField.span said only the widest container-query tier's class is emitted, so a 'full' field was one cell of two at 720px ([finding] absolute-colspan-discouraged asserts a misalignment that does not happen — measured in Chromium at all three surface widths, and its recommended alternative span: 'full' is the spelling that misrenders #17328). objectui#9244 / objectui#9253 (bd09957380) are inside the range: spanLadderFor emits one clamped col-span class per multi-column tier. ⭐ The previous revision of this block asked in writing to be re-read at the bump that absorbed it; this is that re-read. The 'auto' half — textarea, markdown, html, richtext and repeater — re-measured UNCHANGED.

Gates

Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (122 commands for this 19-path, 757-line change set). Run locally, exit codes captured before any pipe:

pnpm check:sdui-lockstep                                        EXIT 0   (after the fold-in: 26 = 26 codes, grammar region byte-identical)
pnpm --filter @objectstack/spec check:objectui-pin-citations    EXIT 0   (27 asserting citations match, 7/7 anchors verified)
  … --verify-anchors                                            EXIT 0
node scripts/check-sdui-manifest.mjs                            EXIT 0
pnpm check:objectui-bump                                        EXIT 0
pnpm check:objectui-changeset                                   EXIT 0
pnpm check:console-sha                                          EXIT 0
pnpm --filter @objectstack/spec check:generated                 EXIT 0   (15/15 artefacts current)
MANIFEST=… check:react-declaration-parity                       EXIT 0   (reading unmoved: the manifest is byte-identical)
node scripts/check-adr-0087-registration.mjs --base origin/main EXIT 0
node scripts/check-changeset-no-major.mjs --base origin/main    EXIT 0
node scripts/check-empty-changeset.mjs --base origin/main       EXIT 0
pnpm lint                                                       EXIT 0   (repo-wide, eslint . --no-inline-config)
pnpm --filter @objectstack/spec test                            EXIT 0   (503 files, 14710 tests)
pnpm --filter @objectstack/spec --filter @objectstack/sdui-parser typecheck   EXIT 0

plus 27 further derived families, every one exit 0: check:nul-bytes, check:merge-driver, check:watch-hint-literal, check:comment-mask-adoption, check:comment-mask-corpus, check:doc-frontmatter, check:docs-section-name, check:scripts-symbol-anchors, check:spec-docblock-symbol-anchors, check:ci-filter-parity, check:keyed-text-bounds, check:undeclared-dep-imports, check:section-landing-index, check:liveness, check:duration-unit-keys, check:llms-txt, check:spec-changes, check:upgrade-guide, check:strictness-ledger, check:export-origins, check:api-surface, check:authorable-surface, check:docs, check:doc-anchors, check:docs-spec-enumerations, check:quick-reference-counts, check:corpus-claim-drift, check:changeset-gate-self-tests, check:pm-widening-tells, check:published-files, and both changeset self-tests.

The rest of the 122 are left to CI, which runs the whole farm. origin/main moved 7 commits while this was built and touches NONE of this diff's 19 paths, so no merge was taken; the queue's rebuilt generation is what validates the join.

Acceptance notes

  • check-issue-citations blind spot (measured on [finding] absolute-colspan-discouraged asserts a misalignment that does not happen — measured in Chromium at all three surface widths, and its recommended alternative span: 'full' is the spelling that misrenders #17328 at 6ef8344645): a citation qualified with this repository's own name (objectstack#N or owner/repo#N) is recognised as naming THIS repo and resolved against the board, but the board is only asked for unqualified citations and --probe-cause's guard is !cite.qualifier too — a live issue reads allocated-but-absent and NOT MEASURED under --probe-cause. Not fixed here (the gate is not this PR's surface); recorded for the next author of scripts/check-issue-citations.mjs.

  • objectui's packages/sdui-parser/body-dialect.ts (objectui#6771, landed between cfcc17d9dd04 and the live pin) has no counterpart in this copy: for an authored body key the two copies stamp different codes (not-a-container under a non-container there, the bare unknown-prop here), invisible to check:sdui-lockstep's code-SET comparison because both codes are already in the 26-set. Both severities are warning, so compile().ok and the save verdict are unchanged. A third code path the ruling does not name; carrier: the next sdui-parser port round.

  • provenance.ts and assertFullyLoaded / RegistryConfigLike.lazy remain absent from this copy (zero diagnostic codes; sdui-parser: port inert-quick-add and member-type-mismatch from objectui, re-record the lockstep and bump the pin #18723's acceptance notes already record provenance.ts). pnpm gen:sdui-lockstep is unrunnable in this container because the shared sibling checkout sits off the pin and the generator's remedy is a git write in someone else's tree; it cost nothing this round and will bite the next pin bump, which must park its own checkout.

  • .changeset/18516-span-auto-wide-types.md (pending, not yet released) describes the FormField.span describe text this PR corrects, including "At this pin only the widest tier's class is emitted". check:empty-changeset refuses any diff that MODIFIES a changeset present at the merge base, so it is deliberately left untouched here and reported instead.

  • The element:button icon .describe() still says the renderer resolves through lucide-react's icons map. At the new pin that is one hop indirect (recordIconName + lazyIconComponent) but the glyph set and the accepted spellings are unchanged, so it was left as written rather than churned.

Original author: the director seat's os-dev rounds (pin bump; fold-in 6ef8344645; citation fix fc25a5925d), session_012GcsUbuqFGBibkEDMRC1eE; body maintained by the director seat, session_012GcsUbuqFGBibkEDMRC1eE.


Generated by Claude Code

Pin bump produced by scripts/bump-objectui.sh --no-commit; the ADR-0087
disposition placeholder is still unanswered in this commit.

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

The pin bump's changeset now carries a real ADR-0087 disposition, and the
sdui-parser lockstep record is re-taken from objectui at the new pin.

Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE
Co-authored-by: Claude <noreply@anthropic.com>
The pinned commit declares the same published @object-ui 17.6.0 the old pin
did, so sdui.manifest.json is byte-identical (sha256 49211fee7792) and only
the provenance record moves.

Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE
Co-authored-by: Claude <noreply@anthropic.com>
The six corpus-absence records are re-counted against objectui 87af769e9a3e
(8228 tracked files): every subject still reads 0, and each lit control is
re-measured. The logging record's `timeout` control was wrong at the pin it
named (702 recorded, 832 measured there) and is corrected to the new pin's
1075. The api-methods batch record's two anchors were re-READ, not carried:
both spans are byte-identical and both moved.

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

Every anchor in the tab-item, accordion-item, button, object-metric,
object-kanban limit and quickAdd records was re-READ against objectui
87af769e9a3e rather than re-pointed by arithmetic. Four read points changed
substance and say so: the two container registrations lost their per-input
`label`, the button registration's ranges shrank, `resolveIcon` no longer
indexes lucide-react directly, the kanban `$top` now runs through
`resolveRowLimit`, and `KanbanSchema` was retired in favour of
`ObjectKanbanSchema` in `@object-ui/types`.

Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE
Co-authored-by: Claude <noreply@anthropic.com>
migrations/registry.ts is regenerated from the six entry files it concatenates
(gen:migration-registry), so its six citations follow theirs.

The dataset `format` and view `span` records both recorded behaviour the range
reverses, and each is re-stated from the new tree rather than re-pointed:
objectui#8352 makes the datetime measure arm honour `short` / `relative`, which
the old docblock and its describe both denied; objectui#9244 makes the form
renderer emit one clamped col-span class per multi-column tier, which the span
record itself asked to be re-read at the bump that absorbed it.

All seven ListMapConfig anchor quotes moved; one lost the symbol it quoted --
`FLAT_MAP_CONFIG_KEYS` became the total `FLAT_MAP_CONFIG_SPELLING` map under
objectui#9950, so `style` now reaches the flat product as `mapStyle`.

check:objectui-pin-citations and --verify-anchors both exit 0.

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

gen:docs output only; check:generated reports all 15 artifacts current.
The spec changeset declares the two sentences the pin bump corrected.

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

github-actions Bot commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

1 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))

⛔ 1 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/ 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
  • 1 changed file(s) yielded no anchor (packages/sdui-parser/objectui-lockstep.json) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 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; 100 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 — 137 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 50bae37f3df71e759f1730b906d5af35baa7b0f7 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from ff53bb6f7f47a676c6b47bd379df9e37c706ece0 — the merge of head fc25a5925d7d6b55ed25b81d5f1fcef4be836109 into base 50bae37f3df71e759f1730b906d5af35baa7b0f7, 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 ff53bb6f7f47a676c6b47bd379df9e37c706ece0 && git checkout ff53bb6f7f47a676c6b47bd379df9e37c706ece0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 50bae37f3df71e759f1730b906d5af35baa7b0f7 fc25a5925d7d6b55ed25b81d5f1fcef4be836109 && git checkout -B drift-repro 50bae37f3df71e759f1730b906d5af35baa7b0f7 && git merge --no-ff fc25a5925d7d6b55ed25b81d5f1fcef4be836109

node scripts/docs-audit/affected-docs.mjs --json 50bae37f3df71e759f1730b906d5af35baa7b0f7

⚠️ 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 50bae37f3df71e759f1730b906d5af35baa7b0f7 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator Author

Director seat (session_012GcsUbuqFGBibkEDMRC1eE), 2026-09-20T16:15Z — on the red Single-Claim Path Guard (No other open PR may claim the same single-writer path, job 106107682399, head 1a5b8f3547).

What is failing: .objectui-sha is a declared single-writer path and a second open PR modifies it — PR #18723 (draft, 53ded82b… → cfcc17d9…), which the gate counts by design (「a draft claimant still counts」). Not this PR's defect: under ruling batch #200 item 4 (5750928995 on #17645) the pin lands here first and #18723 takes main's pin afterwards, so #18723 is the one that must stop claiming the path. Fix in flight: an os-dev round is restoring .objectui-sha and scripts/sdui-manifest.record.json on #18723's branch to origin/main's blobs; it will comment here with the commit, after which this gate is re-run once. #18723 stays draft, red on its own lockstep gate by design until its post-#19398 merge lap.


Generated by Claude Code

os-project-manager pushed a commit that referenced this pull request Sep 20, 2026
…lands via #19398 first

The Single-Claim Path Guard on the pin-bump PR fails with "At most one open PR
may modify a declared single-writer path": .objectui-sha is a declared
single-writer path and this draft still rewrites it (53ded82bf7a4 to
cfcc17d9dd04), so it counts as an open claimant. Per ruling batch #200 item 4
the pin bump lands on its own PR first.

So this branch releases the claim: both pin-carried files are restored to
main's blobs byte for byte (.objectui-sha and scripts/sdui-manifest.record.json,
the latter because check-sdui-manifest requires record pin == .objectui-sha).
Nothing else is touched — the parser port, its tests and the changeset are
unchanged.

check:sdui-lockstep is RED in the interim, by design: [pin-moved], the lockstep
record was taken at cfcc17d9dd04 while .objectui-sha now reads 53ded82bf7a4.
This PR stays draft and re-records the lockstep at the new pin in a merge lap
after the pin bump lands.

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

Copy link
Copy Markdown
Collaborator Author

Director seat (session_012GcsUbuqFGBibkEDMRC1eE), 2026-09-20T16:21Z — on the red Lint & Repo Gates (job 106107682913, head 1a5b8f3547; failing step: sdui-parser stays in lockstep with objectui's copy).

What is failing: check:sdui-lockstep reads [code-drift] — objectui at the new pin 87af769e9a3e carries 26 sdui-parser diagnostic codes, this repo 24, and the two missing are exactly inert-quick-add and member-type-mismatch, the port PR #18723 carries (the dev's report on #17429 measured it: the 24 are a strict subset, grammar region byte-identical). This PR cannot be green on its own at any pin this card may take, and #18723 cannot be green without this pin: the gate is one invariant across two PRs. ⛔ Not a flake, ⛔ not re-run. Fix pending a decision: batch #201 is in front of the maintainer — recommended letter A folds #18723's reviewed port (with its tests and ablation) into this PR, re-records the lockstep, re-takes the contract review here (Clause-② becomes yes) and closes #18723 as superseded. The push follows the letter; this PR stays draft until then.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Director seat (session_012GcsUbuqFGBibkEDMRC1eE), 2026-09-20T16:24Z: the .objectui-sha collision is cleared on #18723's side as of commit 8a778067eb58d9b79241f6546016ea411c1269c8 — that PR no longer modifies the declared single-writer path (its file list is 9 paths, with neither .objectui-sha nor scripts/sdui-manifest.record.json among them; both restored byte-identical to main's blobs, check-sdui-manifest exit 0 there). The Single-Claim Path Guard re-judges this PR on its next event; the Lint & Repo Gates red (lockstep [code-drift]) stays as recorded above until batch #201 is answered.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Director seat (session_012GcsUbuqFGBibkEDMRC1eE), 2026-09-20T22:56Z — on the red Check Changeset (job 106164107369, step 「Guard against accidental major bumps (launch window)」, head 1a5b8f3547, run triggered by the needs:contract-review label this seat hung at 2026-09-20T22:54Z).

What is failing: the guard read the carrier (needs:contract-review on the PR) against the body line as it was then (Clause-②: no) and, taking the carrier as the declaration, asked for the widened package to be graded minor or above; the only package whose packages/**/src/** this head moves is @objectstack/spec, graded patch in .changeset/17429-pin-bump-describe-corrections.md (two .describe() sentences — correctly patch). Why it turns green without touching that grade: under ruling batch #201 A (5753263180 on #17645) the fold-in commit now in flight adds #17645's sdui-parser port with its own changeset .changeset/17645-sdui-parser-lockstep-port.md (@objectstack/sdui-parser: minor) and moves packages/sdui-parser/src/** — the package that actually grows — which is the 「one minor on a package this diff moved」 the gate names as clearing this red. The body's declaration line already reads Clause-②: yes (rewritten by this seat). ⛔ Not re-run before that push; the push re-runs it.


Generated by Claude Code

… objectui

Folds the two-code sdui-parser port onto the pin-bump branch, verbatim: the
`kanban-quick-add.ts` source, the `ManifestInput.of` member-kind check across
validator, serializer and codegen, both test files with their ablation legs,
and the port's minor changeset. Every file is byte-identical to the port
branch's blob.

The lockstep record is NOT text-merged from that branch: this branch already
carries the record re-taken at the live pin, and `--update` re-reads objectui's
side only, so nothing here can launder a divergence.

Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE
Co-authored-by: Claude <noreply@anthropic.com>
`check-issue-citations` judges only what a change ADDS, and this branch
rewrote both lines, so both are ours to answer.

view.zod.ts: spell the citation bare. The qualified form named this
repository, which the classifier recognises — and then looks the number up
on a board the probe never asked for it, because the probe only fetches
unqualified citations. The target exists; the bare spelling is the grammar's
form for this repository and it now resolves.

component.zod.ts: the number this sentence carried is DELETED, probed with
`--probe-cause` (minted, gone from the board, 404 on the web endpoint too),
so no target resolves and none is guessed. The sentence now states that in
prose and names the live record — this block, and the pin-citation gate
whose header carries the same rule. The later paragraph's unchanged mention
is left as it stands on main.

Claude-Session: https://claude.ai/code/session_012GcsUbuqFGBibkEDMRC1eE
Co-authored-by: Claude <noreply@anthropic.com>
@os-project-manager os-project-manager changed the title chore(objectui): bump the console pin to 87af769e9a3e, with its artefacts and 27 re-measured citations chore(objectui): bump the console pin to 87af769e9a3e with 27 re-measured citations, and fold in the two-code sdui-parser port (#17645) Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: fc25a5925d7d6b55ed25b81d5f1fcef4be836109

Isolated at-tier reviewer; reviewed 2026-09-21T00:13Z. Merge-base a88a97332fc90cb04e9dbae2d4e2f5aa56cab708; origin/main tip during the review 50bae37f3df71e759f1730b906d5af35baa7b0f7 (the PR payload's base sha c5d3d1d8b6 is older; no merge was judged); worktree ../objectstack-review-19398 detached at the head, pnpm install exit 0, removed at the end; objectui read READ-ONLY from the sibling checkout at 87af769e9a3ee28ace099fdd653d3ebd79fe82e2 (new pin), 53ded82bf7a494f54e344e19099dbf00854b8694 (old pin) and cfcc17d9dd04 (the revision PR #18723 ported from), via git show / git grep / git archive into scratch; the console build's own git worktree add of objectui at the pin under the review worktree's .cache/ is the script's documented mechanism and was removed with the worktree. Every exit code below was captured before any pipe; the three os-dev-report comments were treated as claims and re-derived, never adopted.

① Derived judgments

  1. The pin — RIGHT. .objectui-sha at the head reads 87af769e9a3ee28ace099fdd653d3ebd79fe82e2 (blob 035b6937d641). git -C ../objectui merge-base --is-ancestor cfcc17d9dd04 87af769e9a3e exit 0 (and 53ded82bf7a4 is an ancestor too; 1156 non-merge commits in the range, the number the console digest names). pnpm check:objectui-bump exit 0 (20 assertions / 5 cases); pnpm check:objectui-changeset exit 0; pnpm check:console-sha exit 0; node scripts/check-sdui-manifest.mjs exit 0 and --self-test exit 0 (11 cases). Manifest re-hashed by me: sha256sum sdui.manifest.json = 49211fee7792cf51174930dc2c1be2169d5175f77a83491f68f90b3f0c19e69d, 57 components, byte-identical to origin/main's manifest; scripts/sdui-manifest.record.json moves only objectuiSha and generatedAt, and @object-ui packages/core/package.json reads 17.6.0 at BOTH shas, so the byte-identical manifest is expected, not suspicious.

  2. The citations — RIGHT. pnpm --filter @objectstack/spec check:objectui-pin-citations exit 0 (27 asserting match 87af769e9, 48 historical, 1497 spec sources, 7 anchor content assertions verified); --verify-anchors exit 0. I re-derived 21 of the 27 rows myself by reading objectui at BOTH shas and comparing content, plus every one of the 7 content assertions:

    • Corpus-absence, all six records: the calibration reproduces the OLD pin's own numbers exactly with git ls-tree -r and git grep -F -o (6409 tracked files; useState 2304; objectstack 10171; @objectstack/spec 3479; RuntimeConfig 236; resourceLimits 2; TTL 112; tenant 819; window 2710; period 160; interval 156; metrics 301; Span 404; SpanSchema 40) and timeout reads 832 there, so the logging record's old control 702 was indeed WRONG at the pin it named. At 87af769e9 every control matches the PR's table (8228 files; 2383; 12966; 4997; 1075; 240; 2, both hits prose in packages/app-shell; 156; 976; 3464; 170; 170; 324; 486; 53) and every zero-hit token is still 0 (schemaCacheTTL, debounceDelay, resourceLimits.timeout, flushInterval, initialDelay, HttpDestinationConfig, LoggingConfig, and the seven distinctive metrics keys). Six rows RIGHT; the registry.ts ×6 rows follow them.
    • api-methods-batch-conformance.test.ts: ObjectGrid.tsx OLD 3538-3553 and NEW 3940-3955 both hash 6133933199230670e29d8c7f51c558d86a0af1d2 (file diffstat 721/159 as stated); useBulkExecutor.ts OLD 284-289 and NEW 298-303 both hash 01083348330f10a201cdf1078b4c21c236402b6a, file 36/22, :303 still label = 'bulk delete'. RIGHT ×2.
    • tab-item icon: layout/containers.tsx OLD 730-736 and NEW 853-859 hash c542d07a70bf both; registration OLD :789 carried label: 'Tabs', NEW :912 carries of: 'object' and no label, member list unchanged. Accordion-item: 919-925 and 1069-1075 hash 3934c0ae547e both; :966 to :1116 the same change. RIGHT ×2.
    • element:button icon: form/button.tsx :43, :72, :74 byte-unchanged; inputs OLD 85-102 and NEW 85-97 both name exactly label, variant, size, className (no icon input), label:/defaultValue: members 6 → 0; defaultProps NEW 98-102. resolve-icon.ts OLD 129-132 indexes icons[…], NEW 322-328 asks recordIconName then lazyIconComponent (0 icons[ in the tail); the three moved spans (117-120→302-305, 100-105→153-158, 90-92→143-145) and lazy-icon.tsx 66-92→98-124 hash identical. RIGHT.
    • object-metric icon: plugin-dashboard/src/index.tsx OLD :204 { name: 'icon', type: 'string', label: 'Icon (Lucide name)' }, NEW :237 { name: 'icon', type: 'string' }, registration opens at :227. RIGHT.
    • kanban limit: ObjectKanban.tsx OLD :264 $top: schema.limit ?? DEFAULT_KANBAN_LIMIT, NEW :676 $top: resolveRowLimit(schema.limit, DEFAULT_KANBAN_LIMIT) with the refusal message at :553; :71→:84 DEFAULT_KANBAN_LIMIT = 100; plugin-kanban/src/index.tsx 395-398→447-450 hash identical; plugin-kanban/src/types.ts at the pin declares NO KanbanSchema interface (its :35 is the by-name tombstone) and ObjectKanban.tsx:10 imports ObjectKanbanSchema from @object-ui/types, whose packages/types/src/objectql.ts:3735 reads limit?: number. ElementDataSourceGate.tsx OLD 236-241 → NEW 316-331 gained isUsableRowLimit and describeDisplacedRowLimit. RIGHT.
    • kanban quickAdd: plugin-kanban/src/index.tsx :196→:313 and KanbanImpl.tsx :355/:368→:621/:634 byte-identical; ObjectKanban.tsx :931→:1563 is the same ...effectiveSchema, spread (only JSX indentation moved two columns, the cited text is identical); quickAdd 0, onQuickAdd 0, onCardClick 11 (was 6) in ObjectKanban.tsx at the pin. RIGHT.
    • ListMapConfig, all 7 content assertions: ListView.tsx:146 function resolveListMapConfig…, :85 export const FLAT_MAP_CONFIG_SPELLING = { (OLD :67 FLAT_MAP_CONFIG_KEYS listed seven keys satisfies … Omit ObjectMapConfig, 'style'; NEW maps all eight with style: 'mapStyle'), ObjectView.tsx:1764 case 'map':, objectql.zod.ts:1574 ObjectMapConfigSchema = z.object({ declaring the eight keys and :734 LIST_VIEW_LOCAL_OVERRIDES, ObjectMap.tsx:377 / :385 / :390, plugin-map.mdx:143 — each line reads exactly as quoted, and each old anchor reads the same at the old pin. RIGHT ×7.
    • Dataset measure format (SUBSTANCE REVERSED) — RIGHT, checked against the code, not the prose: at 87af769e9 packages/core/src/utils/dataset-format.ts formatMeasureDate spans :229-264 (routed at :370) and its datetime arm now reads if (format === 'relative') return formatRelativeDate(v, { locale }); (:260), if (format === 'short') return formatDateTime(v, { locale, style: 'compact' }); (:261), else bare formatDateTime(v, { locale }) (:262); at 53ded82bf7 the same function (:185-198) returned formatDateTime(v, { locale }) unconditionally (:195). date-display.ts formatDate at :198 (was :131) and the ±7-day fallback at :152 (was :117, now through absoluteFallbackOptions). The new .describe() ("a date or datetime value reads format as a display style — short or relative, honoured on both"; a date pattern still ignored) states exactly that code.
    • FormField.span (SUBSTANCE REVERSED) — RIGHT, checked against the code: packages/components/src/renderers/form/form.tsx declares function spanLadderFor(containerClass, targetCols) at :204 (body through :231, closing brace :232), which walks the container class's grid-cols-N tiers and pushes one clamped SPAN_CLASS[tier:min(tier.cols, targetCols)] per tier that can give more cells than already spanned; autoLayout.ts:153 is export function resolveColSpan(. The old pin's form.tsx (:2482-2495) picked a single class. The new .describe() ("one clamped col-span class per multi-column tier … the whole row at every multi-column tier, not just the widest") states that code; the 'auto' half is unchanged.
  3. The fold-in — RIGHT. All eight files hash SAME between the head and origin/claude/issue-17645-sdui-lockstep-port (kanban-quick-add.ts 20f9f74b9f05, validate.ts 156a264073f7, types.ts 567bc1259bea, index.ts 2ea35795b9cf, codegen.ts 76a7d654f1fd, inert-quick-add.test.ts 723cad21333a, member-type-mismatch.test.ts 4b87cadaa067, the changeset 7c171fddfc43); the port branch's only other file, objectui-lockstep.json, was correctly not brought. node scripts/check-sdui-lockstep.mjs exit 0 and --self-test exit 0. Re-derived with the head's own fingerprintOf against git archive extracts of objectui: THIS COPY 26 codes / 8 files; objectui@87af769e9a3e 26 codes / 10 files (the two extra are body-dialect.ts and provenance.ts); objectui@cfcc17d9dd04 26 / 9. The 26 on both sides, identical: bad-attr bad-attr-value bad-self-close bad-tag expected-element expression-child forbidden-attr forbidden-tag inert-expression inert-quick-add invalid-enum member-type-mismatch mismatched-tag missing-required-prop multiple-roots no-root not-a-container type-mismatch unclosed-comment unclosed-element unconsumed-widget-option unknown-component unknown-prop unterminated-brace unterminated-open-tag unterminated-string; grammar region 214 lines, blob 0131f27cf86d on both sides. pnpm --filter @objectstack/sdui-parser test through the lock: VERDICT command-exit 0, 176 passed / 10 files; typecheck VERDICT command-exit 0. Ablations re-run here through scripts/ablation-replace.mjs (anchor ×1, mutation proven by anchor 1 → 0 and blob change, restore proven blob == HEAD and empty git diff HEAD): baselines 13 passed and 21 passed; (1) checkKanbanQuickAdd returns null unconditionally (20f9f74b9f05 → befe6d71594c) — 5 failed / 8 passed (13); (2) inputTypeArms(undefined) in the validator's member check (156a264073f7 → 6a6f79d7736c) — 7 failed / 14 passed (21); (3) inputTypeArms(undefined) in memberTsType (76a7d654f1fd → 2dcbc5b677bd) — 3 failed / 18 passed (21); all three blobs back at HEAD and git status clean. @ts-ignore / @ts-expect-error / : any / as any additions over the code files of the diff: 0 (the three as any matches in the raw diff are prose lines of the console changeset digest).

  4. The citation-spelling fix — RIGHT. git diff 6ef8344645 fc25a5925d --stat is exactly packages/spec/src/ui/component.zod.ts (+9/−1) and packages/spec/src/ui/view.zod.ts (+1/−1), 2 files, +10/−2. node scripts/check-issue-citations.mjs at the head exit 0 (31 judged / 15 files, 30 cross-repo-unjudged, 1 resolves, 0 findings); the proxy hint appeared and --use-env-proxy exit 0 with the same reading; --probe-cause --use-env-proxy exit 0 (nothing left to probe on the head's added lines, by design). My own probe of [finding] The #9881/#9972 read-point records still cite .objectui-sha = 82a94170c — #10137 moved the pin, so "the pin this repo builds against" is now false in spec prose and tests #10274: GET /repos/objectstack-ai/objectstack/issues/10274 HTTP 404 with no redirect, and the web endpoint HEAD 404 with no redirect — a transferred issue keeps a web redirect, so DELETED is the accurate class and the tombstone prose in component.zod.ts:2135-2143 ("minted, gone from the board and 404 on the web endpoint too — DELETED, not transferred", no replacement guessed, the live record named) states it accurately. GET /issues/17328 HTTP 200: closed, completed, an issue not a PR, so the bare #17328 is a resolving citation.

  5. Merge faithfulness — RIGHT. git diff origin/main...fc25a5925d --name-only is 27 paths and equals the PR's file list as a set (0 only-PR, 0 only-diff). node scripts/check-empty-changeset.mjs --base origin/main exit 0 (no merge-base changeset modified or deleted). pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated through the lock: VERDICT command-exit 0, held 197s; "Checking 15 generated artifacts (every gate runs)" then "✓ All 15 generated artifacts are up to date", with check:react-declaration-parity reported EXTERNAL_INPUT_REQUIRED by that aggregate as designed; git status --porcelain --untracked-files=no reads 0 tracked changes afterwards, so nothing was regenerated by the check and the committed content/docs/references/ui/{dataset,view}.mdx are the ones gen:docs produces from the two corrected sentences.

  6. The console — RIGHT. bash scripts/pm/os-verify-lock.sh -c 'bash scripts/build-console.sh' in the worktree: os-verify-lock: VERDICT command-exit 0 · held the lock 642s (10m42s) · waited 0s; ✓ 8786 modules transformed.; ✓ Bundle canary 'import/jobs' present; ✓ Console bundle carries THIS tree's @objectstack/spec, and only it.; ✓ @objectstack/console dist ready (60240 KB) from objectui@87af769e9a3e. packages/console/dist/.objectui-sha = 87af769e9a3ee28ace099fdd653d3ebd79fe82e2; pnpm check:console-sha exit 0 ("Console dist matches the objectui pin"). My dist is 51,713,761 bytes / 3285 files against the dev's 51,702,779 / 3285 — same file count, not byte-reproducible across two builds, which no gate asserts.

  7. CI on the head — RIGHT (all seven required contexts green). GET /commits/fc25a5925d…/check-runs read 2026-09-21T00:12Z, 42 runs, one page: TypeScript Type Check success (completed 2026-09-20T23:59Z), Test Core success (2026-09-21T00:02Z), Dogfood Regression Gate success (2026-09-20T23:53Z), Build Core success (2026-09-20T23:51Z), Temporal Conformance (live PG + MySQL) success (2026-09-20T23:53Z), Lint & Repo Gates success (2026-09-21T00:07Z; it was still in progress at my two earlier reads), Governed Surface Queue Guard success (2026-09-20T23:43Z). Check Changeset success (2026-09-20T23:51Z), not red. Console Pin Gate, Type Check · source gates, Spec property liveness, both single-writer / same-issue guards and "The card this PR closes must claim this branch" all success; no run on the head is red.

② Semver level

  • .changeset/console-87af769e9a3e.md @objectstack/console: minor — RIGHT by the repo's own rule: scripts/objectui-changeset-digest.mjs takes the highest level objectui declared across the 584 releasing changesets (LEVEL_RANK), and objectui's release window bans major, so the 98 BREAKING-annotated entries ride minor by construction; the console is a vendored SPA, not a spec surface, and node scripts/check-adr-0087-registration.mjs --base origin/main exit 0 accepts its not-required (no-migration-prescription) marker (no ObjectStack-authorable key moves).
  • .changeset/17645-sdui-parser-lockstep-port.md @objectstack/sdui-parser: minor — RIGHT: two new diagnostic codes, a new optional ManifestInput.of member and a codegen narrowing that fires only when of is declared are additive; nothing is removed or renamed; both codes are warning, so compile().ok is unchanged. Clause-②: yes is the correct declaration for a published save-gate vocabulary widening, and the rule "yes takes at least minor" is met by this changeset; minor, not major, is right because no accept set narrows.
  • .changeset/17429-pin-bump-describe-corrections.md @objectstack/spec: patch — RIGHT: the two .describe() sentences change documented renderer behaviour but no key, enum member, default or export moves (check:authorable-surface and check:api-surface current inside check:generated); a corrected sentence is a fix, and its own Clause-②: no line is accurate for the spec package's diff — the PR-level yes comes from the sdui-parser half.
  • Gates: node scripts/check-changeset-no-major.mjs --base origin/main exit 0 (no major); node scripts/check-adr-0087-registration.mjs --base origin/main exit 0; pnpm check:changeset-gate-self-tests exit 0.

③ Boundary flags

  • body-dialect divergence — CONFIRMED, not a blocker. objectui@87af769e9a3e packages/sdui-parser/src/body-dialect.ts (objectui#6771, commit 2acd8e109, +99 lines with index.ts +1 and validate.ts +31/−7 since cfcc17d9) makes an authored body child-list stamp not-a-container under a non-container (:82-83) and a named-replacement unknown-prop otherwise (:92-93), called from validate.ts:105 ahead of the bare unknown-prop; this copy's validate.ts:97 stamps the bare unknown-prop in both cases. Every one of those is severity: 'warning', so the save verdict and compile().ok agree — a message/code divergence inside the 26-code set, invisible to the SET comparison. Not this PR's: the ruling names two codes, and the dev's to-file class-(b) finding is the right carrier.
  • provenance.ts / assertFullyLoaded absence — CONFIRMED by the fingerprintOf file lists (objectui 10 files, this copy 8; both extra files stamp zero codes). Not a blocker: no diagnostic code, and the gate compares codes, not file lists.
  • check:skill-examples exit-code spelling — NOT MEASURED here (not run; nothing in this diff reaches packages/client-react). A gate-authoring note, not this PR's surface.
  • gen:sdui-lockstep unrunnable off-pin — CONFIRMED structurally: the shared sibling checkout /home/user/objectui is at c2f0f48329de on a branch, not the pin; I did not run the generator (its remedy is a checkout in that shared tree). The committed record is nonetheless the one the generator would write: objectui at the pin carries exactly the 10 files and 26 codes recorded, and only rev/revDate/two files/two codes moved vs origin/main. Not a blocker.
  • check-issue-citations blind spot — CONFIRMED in source: classifyCitation treats objectstack-ai/objectstack#N as this repo (:428) and falls through to the board, while the --probe-cause guard reads !cite.qualifier (:748), so a qualified this-repo citation is never probed. Not a blocker; fixed in the PR text only, as the dispatch allowed.
  • .changeset/18516-span-auto-wide-types.md (pending at the merge base) carries "At this pin only the widest tier's class is emitted" — CONFIRMED (one widest hit); correctly left untouched here because check-empty-changeset refuses merge-base changeset edits.
  • pnpm lint repo-wide exit 0 (run BEFORE the console build populated .cache/objectui-*, which eslint.config.mjs does not ignore). node scripts/pm/check-widening-tells.mjs --declaration yes --diff pr.diff exit 0 (a yes routes to contract review; nothing to block) and pnpm check:pm-widening-tells self-test exit 0 (508 cases).
  • Carrier hygiene for the dispatching seat — node scripts/pm/check-clause2-carriers.mjs --pair 19398 exit 2: pair PR chore(objectui): bump the console pin to 87af769e9a3e with 27 re-measured citations, and fold in the two-code sdui-parser port (#17645) #19398 / card Bump .objectui-sha — the console pin is 584 commits behind objectui main #17429 ✓; pair PR chore(objectui): bump the console pin to 87af769e9a3e with 27 re-measured citations, and fold in the two-code sdui-parser port (#17645) #19398 / card [finding] the sdui-parser lockstep has drifted again since the record was taken: objectui carries 26 diagnostic codes, this copy 24 — inert-quick-add and member-type-mismatch exist only on the renderer side #17645 UNJUDGED — the card's newest claim (5753263180) puts Branch: inside the Claim: sentence, which parses to zero branches, so the gate fell back to the older os-try-charles claim naming claude/issue-17645-sdui-lockstep-port. CI's "The card this PR closes must claim this branch" is success and the PR touches no governed surface, so this does not bind the review; the remedy the gate prints is one comment on [finding] the sdui-parser lockstep has drifted again since the record was taken: objectui carries 26 diagnostic codes, this copy 24 — inert-quick-add and member-type-mismatch exist only on the renderer side #17645 with Branch: \claude/issue-17429-objectui-pin-bump`on a line of its own. The same gate reads the PR body'sClause-②: yesline as NEAR-MISS/describing (prose after the value); a bareClause-②: yes` line in the body would make it a clean carrier.

Implemented-by: claude/issue-17429-objectui-pin-bump
Reviewed-by: session_012GcsUbuqFGBibkEDMRC1eE

VERDICT: PASS


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Provenance — director seat, summon #25 (session_012GcsUbuqFGBibkEDMRC1eE), 2026-09-21T00:17Z


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 21, 2026 00:18
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 21, 2026
Merged via the queue into main with commit fbc12be Sep 21, 2026
67 of 68 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-17429-objectui-pin-bump branch September 21, 2026 00:58
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…s from its displayed scale (objectstack-ai#19442)

Fixes objectstack-ai#19320

Clause-②: yes (widening)

⭐ Declared from the **measurement**, ⛔ not from the shape of the change:
the accept-set delta over 1950 cells is **36 ADDED / 0 REMOVED**, so the
narrowing arm is empty. ⚠️ The seat rewrote this line from a backticked,
prose-trailing spelling that the repo’s own `readClause2Line` reads as
`{kind: near-miss, reason: describing}` — a near-miss is ⛔ not a
declaration, and `Check Changeset`’s level axis would have had no input
from this body.

✅ **Both halves of the ruling are now here.** The behaviour half (the
validator's percent arm) and the `packages/spec` docblock half landed in
the same branch; the docblock half needed a file outside the boundary
this card was dispatched with, was reported rather than taken, and was
then authorized by the dispatching seat. The closing keyword is
therefore a closing keyword.

## The ruling this makes live

Maintainer ruling batch objectstack-ai#161 item 3 letter B (objectui#9810, comment
`5729749935`, 「其他同意」 2026-09-18T12:07Z). Quoted, not translated:

> - `packages/spec` `FieldSchema.scale` docblock (and the field
reference page): for `percent`, `scale` is the number of decimal places
of the percentage-point value as displayed and entered; stored precision
follows the storage scale (`fraction` ⇒ `scale + 2` places; `whole` ⇒
`scale`).
> - `record-validator.ts` `max_scale` branch: when `def.type ===
'percent'` and `percentScaleOf(def) === 'fraction'`, compare against
`def.scale + 2`; a pin per storage scale (fraction `scale: 2` accepts
`0.1234`, refuses `0.12345`; whole `scale: 2` unchanged).

## Premise re-verification — first-hand, on today's tip, with a lit
control

The card's premise was second-hand. Both halves were re-measured against
`origin/main` at base `24162f95`, through the **real** record validator
imported from the built `dist` of `@objectstack/objectql` — no harness,
no source shortcut.

| case | ruling B requires | measured BEFORE this PR |
| --- | --- | --- |
| fraction `scale: 2`, write `0.1234` (scale+2 places) | ACCEPT |
**REFUSE** `max_scale` `{scale:2, actual:4}` |
| fraction `scale: 2`, write `0.123` (scale+1) | ACCEPT | **REFUSE**
`max_scale` `{scale:2, actual:3}` |
| fraction `scale: 3`, write `0.33333` (the ruling's 33.333%) | ACCEPT |
**REFUSE** `max_scale` `{scale:3, actual:5}` |
| fraction `scale: 0`, write `0.33` | ACCEPT | **REFUSE** `max_scale`
`{scale:0, actual:2}` |
| fraction `scale: 2`, write `0.12345` (scale+3) | REFUSE | REFUSE
(agrees) |
| whole `max: 100, scale: 2`, write `12.34` / `12.345` | ACCEPT / REFUSE
| ACCEPT / REFUSE (agrees) |

**Lit control, same instrument, same run** — so the refusals above are a
reading and not a dead instrument: the validator ACCEPTED `0.12` and
`0.5` on the same field, and REFUSED for three *different* reasons —
`max_value` on `max: 1` with `5`, `min_value` on `min: 0` with `-0.5`,
and `invalid_number` on `'abc'`. `number` / `currency` / `slider` all
refused at `scale + 1` in the same run.

Docblock half, read at source: `FieldSchema.scale`'s `.describe()`
(`packages/spec/src/data/field.zod.ts`) states the 0-100 platform
ceiling and nothing about `percent`; `percentScaleOf`'s docblock
(`packages/spec/src/data/percent-scale.ts`) states the fraction/whole
rule and says nothing about `scale`. **Both halves of the card hold. The
premise is TRUE.**

## Accept-set delta — measured in BOTH directions

Both predicates (current and ruled) were run over an exhaustive corpus
of 1,950 cells: 5 numeric field types x 5 `max` declarations x 6 `scale`
values x 13 decimal-place counts.

```
corpus cells: 1950   unchanged: 1914   ADDED (accept set grows): 36   REMOVED (accept set shrinks): 0
declaration classes whose allowance moves: percent max=undefined, percent max=0.5, percent max=1
```

- The narrowing arm is **empty** — 0 of 1,950 cells. Nothing that writes
today stops writing; no stored value is re-read; no migration is
implied.
- Only fraction-stored `percent` moves. `percent` with `max` above 1,
and `number` / `currency` / `slider` / `rating` at every `max`, are
byte-identical in verdict.
- ⇒ `Clause-②: yes (widening)` is what the measurement supports. It was
dispatched as a claim to check; the claim survives the check.

⚠️ **One flag for the contract review, not a re-adjudication.** The
ruling's own Execution section declares `Clause-②: no`. The mechanical
criterion in `pm-dispatch` is 「本卡放宽接受集或扩大公开面吗」, and the accept set is
measurably relaxed, so the conservative routing arm is `yes`. The
declaration is by design provisional (「按设计临时…⛔ 非终审」), so this is a
routing difference to be recorded at review, not a change to the ruling.

## What this PR implements

`packages/objectql/src/validation/record-validator.ts` — the `max_scale`
branch gains its percent arm. The fraction/whole split is **read from
the spec's `percentScaleOf`**, not re-derived from `max` at this seam,
so the edit widget, the analytics wire and the validator keep answering
from one source.

The refusal envelope now reports the allowance that was **applied**: on
a fraction-stored `scale: 2` field, `0.12345` is still refused and
reports `constraint: { scale: 4, actual: 5 }`. Reporting the raw
declaration beside a stored fraction's place count would render "must
have at most 2 decimal places (got 5)" on a field that accepts four — a
true refusal described by a false constraint. This is the one detail the
ruling's letter leaves open; it is decided in the direction that keeps
the machine-readable surface honest, and it is pinned.

## The spec half — and the boundary that gated it

The ruling's first bullet is the `packages/spec` `FieldSchema.scale`
docblock **and the field reference page it generates**. That is
`packages/spec/src/data/field.zod.ts`, which was **outside** the
four-file boundary this card was dispatched with, so it was reported
before being touched rather than taken quietly. The two `packages/spec`
paths the dispatch originally named (`numeric-column-representation.ts`
and its test) are about the **DDL column** (`numeric_precision` /
`numeric_scale`) and mention `percentScaleOf` only inside a prose
comment — they are not part of this repair and are untouched.

What landed, after the seat authorized the corrected surface:

- `packages/spec/src/data/field.zod.ts` — `FieldSchema.scale`'s
`.describe()` now states **both** meanings: what the number counts on a
`percent` field (decimal places of the displayed percentage-point value)
and what it permits in storage (`fraction` ⇒ `scale + 2`, `whole` ⇒
`scale`, every other numeric type ⇒ `scale`). Both halves go in the
**describe**, not only in a source comment, because the reference page
is generated from the describe and an author who reads only that page is
the author the ruling is about.
- `content/docs/references/data/field.mdx`, `data/object.mdx`,
`system/migration.mdx` — regenerated by `pnpm --filter @objectstack/spec
gen:schema && gen:docs`, ⛔ never hand-edited. **Exactly those three
tracked files moved and nothing else**, which is what the pre-commit
source/regeneration split was arranged to make legible: the source edits
were committed first, so the regeneration commit's file list is the
regeneration's own output.
- `packages/spec/src/data/percent-scale.ts` — the optional
cross-reference, **taken**. The card's own measurement table named
*this* docblock as the one silent about `scale`, and `percentScaleOf` is
the function the validator calls, so a reader who lands here should find
the consequence rather than re-derive it. Written as a pointer, ⛔ not a
second copy: the rule is stated once on `FieldSchema.scale` and enforced
once in the validator.

**Serial constraint re-measured for those paths** before any of it was
written, same instrument as the dispatch used: 20 open PRs, `GET
/pulls/N/files` each, **0 unreadable file lists**, and no open PR
holding any of the eight paths. Lit control on the same run:
`packages/spec/src/**` matches 7 open PRs (objectstack-ai#19398, objectstack-ai#19374, objectstack-ai#19373,
objectstack-ai#19335, objectstack-ai#19314, objectstack-ai#19090, objectstack-ai#18319), so the zeros are absences the
instrument could see.

## Verification

**Ablation** — `scripts/ablation-replace.mjs`, anchor `? def.scale + 2`
in the production file.

- ⚠️ The **first attempt was a no-op and its reading is void**: the
replacement string was a prefix of the anchor, so its occurrence count
could not rise, and the tool refused before running anything. Recorded
rather than quietly retried.
- Second attempt landed: anchor `x1 -> x0`, blob `2e2d3981d29a ->
7b082941b44f`, command executed, restore proved `blob == HEAD
(2e2d398)` with `git diff HEAD` empty.
- Under ablation: **8 failed / 100 passed (108)**. All 8 are in the new
block and fail for the right reason — the fraction-stored writes are
refused with `max_scale`, and the envelope/message read `2` where the
ruling requires the applied `4`.
- ⭐ **5 of the 13 new cases cannot discriminate, and are not counted as
evidence**: the scale+3 refusal, the whole-percent pin, the
other-numeric-types controls, the other-refusal-reasons control and the
no-declared-scale control pass on both trees **by design** — they are
anti-vacuity and lit-control pins, there so that a branch which simply
stopped enforcing `scale` on percent fails this block too.

**Gate family** — derived from the real changed paths with `node
scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, every command run with its exit code
captured before any pipe, reconciled with `--ran` carrying the recorded
codes:

```
111 derived · 111 run · 107 green · 0 red · 4 NOT MEASURED · 0 unrun
```

- One real red was found and repaired in this PR:
`check:error-code-casing` read the envelope pin's bare `code:
'max_scale'` as an ADR-0112 D1 emission because its recognizer window
held no field-addressed neighbour. Naming the field is the repair and a
stronger assertion. Re-run exit 0: "no unlisted lowercase error codes in
6371 scanned file(s)".
- NOT MEASURED, each with its reason, none of them a red:
- `check:dual-build-cjs-loads` — exit 3, PREREQUISITE NOT MET: reads
built output, 66 packages have no `dist/` in this worktree.
- `check:type-check-debt` — exit 3, PREREQUISITE NOT MET: needs the
whole workspace closure built.
- `check-plugin-teardown-shape.mjs --self-test` — exit 3: its
positive-control fixture is pinned to a commit this shallow clone cannot
reach.
- `check-engine-split-ratio.mjs --days 90` — exit 2: refuses to compute
an ADR-0076 D7 ratio on a shallow clone whose oldest visible commit sits
inside the window. Counted as "run" by the reconciler (exit 2 is not the
prerequisite code) but it measured nothing, so it is reported here as
NOT MEASURED.
- One gate **refused its prerequisite while spelling it `exit 1`**:
`check:skill-examples` reported that `packages/client-react/dist` held
no declarations, which is a refusal and ⛔ not a finding. Rather than
report it as NOT MEASURED, the package closure was built and the gate
re-run to a real verdict: exit 0, **258 prose examples type-check across
3 surfaces**, including the 10 spec-source TSDoc blocks — the surface
this PR edits.
- The two remaining `exit 3` families were **deliberately left
unmeasured**: both need a whole-workspace build, and both are whole-tree
families unrelated to a describe string and a validator arm. CI builds
everything and measures them there. The `check:skill-examples` closure
was built because that gate reads the spec source surface this diff
touches — the choice is principled, ⛔ not a budget.
- The reconciler's own verdict, DERIVED from the recorded codes rather
than claimed: `111 derived famil(ies) accounted for — 108 run, 3
NOT-MEASURED (3 DERIVED from a recorded exit 3)`.

**Suites and lint**, at the final commit:

- `pnpm --filter @objectstack/objectql test` — **303 files / 5050 tests
passed**, exit 0.
- `pnpm --filter @objectstack/spec test` — **505 files / 14,752 tests
passed**, exit 0; `pnpm --filter @objectstack/spec typecheck` exit 0.
- `pnpm --filter @objectstack/objectql typecheck` — exit 0;
`check:test-typecheck` OK, ledger unchanged at 40 files / 234 errors /
65 pinned signatures.
- `pnpm --filter @objectstack/spec check:generated` — exit 0, **all 15
generated artifacts up to date** against the edited `FieldSchema.scale`.
Both gates the ruling's docblock half puts at risk are green by name:
**`check:docs`** (the three regenerated reference pages) and
**`check:authorable-surface`** (authorable surface + JSON schemas).
`authorable-surface.base.json` did not move — a regular build never
writes it.
- `pnpm lint` — repo-wide `eslint . --no-inline-config`, exit 0 at
`bb9f9274`, the final commit. The full union ran; no narrowing was
needed, so no narrowing is claimed.
- **The ablation reading still describes the shipped file**: `git
hash-object packages/objectql/src/validation/record-validator.ts` is
`2e2d3981d29a…`, byte-identical to the blob the ablation restored to, so
nothing landed on the production file after it was proved able to fail.

**Import-side pins**: the public surface of `@objectstack/objectql` is
byte-unchanged (no export added, removed or retyped), so only behaviour
could move a consumer pin. Every non-`objectql` test file mentioning
`'percent'` was checked for a co-occurring `scale`; the six hits are
`packages/spec` schema tests and two `service-analytics` wire tests,
none of which exercises the record validator. The grep returning six
files is its own lit control.

## Acceptance notes

Noted, not filed — neither meets the three filing classes, and the
carrier for each is named:

- The `max_scale` message template
(`packages/spec/src/system/validation-message.ts`) reads "must have at
most N decimal places", which on a fraction-stored percent now describes
the STORED fraction rather than the number the author typed. It is
accurate and it is not what the author sees in the widget. Whether a
percent-specific sentence is wanted is a display decision that belongs
with the ruling's author, not a defect. Carrier: the contract review on
this PR.
- `packages/spec/src/data/numeric-column-representation.ts` already
carries an accurate prose account of the fraction storage rule in its
`percent` entry. It is documentation of the column, not of `scale`, and
needs no change under this ruling. Carrier: none needed — recorded so
the next reader does not re-derive the same dead end.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01HnRAeVTLJevtQ5iCPX6JSm


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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…il-closed quick_actions requiredPermissions fix) with 46 re-measured citations, the FormField.span describe re-point and the lockstep re-record (objectstack-ai#19832)

Fixes objectstack-ai#19503

Clause-②: no

Moves the console pin to objectui `main` `62597c588072`. That tip
carries objectui#10153, the fail-closed
`record:quick_actions.requiredPermissions` fix. The PR also carries the
bump's two required follow-ups, folded in under the PM ruling on objectstack-ai#19503
(comment 5791879469, option A): the sdui-parser lockstep re-record, and
a re-MEASURE of the `packages/spec/src/**` citations that
`check:objectui-pin-citations` names. The citation edits touch comments
and migration-rationale prose only; there are no schema or export
changes. The one published sentence that moves is the `FormField.span`
`.describe()` pin citation, authorised by the second ruling (comment
5793509969) and carried with
`.changeset/19503-pin-bump-describe-corrections.md` (`@objectstack/spec:
patch`) plus a regenerated `content/docs/references/ui/view.mdx`.
Clause-②: no, because every authored document is accepted and refused
exactly as before.

`check:objectui-pin-citations` exits 0 on the head: "47 asserting
objectui pin citation(s) match .objectui-sha (62597c588) … 7 anchor
content assertion(s) verified".

## Ancestry reading (fix → new pin: AHEAD)

Measured in a FULL (non-shallow) clone of `objectstack-ai/objectui`.
`git rev-parse --is-shallow-repository` returns `false`, so an exit 0 is
self-proving:

| command | exit | reading |
|---|---|---|
| `git merge-base --is-ancestor 28b065800c7d1b6069c06aa906d0716377b5e281
62597c588072636e9c30ea35b3d89b1e46fd765d` | 0 | the fix is IN the new
pin |
| `git rev-list --count 28b065800c7d..62597c588` | 33 | the new pin is
33 commits ahead of the fix |
| `git merge-base --is-ancestor 87af769e9a3e 28b065800c7d` | 0 | the old
pin was 4 behind the fix (the card's REST `compare`: behind_by 4) |

The bump's own changeset lists the fix: `fix(plugin-detail):
record:quick_actions.requiredPermissions is an ADR-0066 capability set,
read fail-closed ... (objectui 28b065800)`.

## What moved, and how each artefact was produced

| artefact | producer |
|---|---|
| `.objectui-sha` + `.changeset/console-62597c588072.md` | `bash
scripts/bump-objectui.sh --no-commit 62597c588072…` exit 0 (27 releasing
changesets, 0 breaking, `@objectstack/console: minor`) |
| `scripts/sdui-manifest.record.json` | `node
scripts/gen-sdui-manifest-node.mjs --objectui-version 17.6.0` exit 0.
`sdui.manifest.json` came out BYTE-IDENTICAL (sha256 `49211fee7792`, 57
components), because `packages/core` ships `17.6.0` at both pins; only
the record moves |
| `packages/sdui-parser/objectui-lockstep.json` | `pnpm
gen:sdui-lockstep` with `OBJECTUI_ROOT` at a checkout of `62597c588`,
exit 0. Diff: 3+/3-, only `rev`, `revDate` and `recordedAgainstPin`.
Grammar blob `0131f27cf86d` unchanged, 214 lines, all 26 diagnostic
codes agree. `check:sdui-lockstep` exit 0: "byte-identical to
objectui@62597c588072". No parser port is owed |
| `packages/spec/src/migrations/registry.ts` | `pnpm --filter
@objectstack/spec gen:migration-registry`, from the six edited entries |
| the console | `bash scripts/build-console.sh` exit 0: "Console dist
ready (60524 KB) from objectui@62597c588072", bundle canary present.
`check:console-sha` exit 0 |

## Citation re-measure: method

- **Read-point records.** For every cited objectui file I ran `git diff
--quiet 87af769e9a3e 62597c588 -- FILE`.
- Unchanged file: the anchor holds by identity. The prose names that,
keeps the earlier hop's history, and says where the last content re-read
happened.
- Changed file: each anchor was mapped through the `git diff -U0` hunks
and its text compared at both pins. It was then re-read in the new tree.
- **Corpus counts.** These are the six migration entries plus
`registry.ts`. I re-counted with `git grep -o -F TOKEN SHA | wc -l`. The
method was calibrated first: at `87af769e9a3e` it reproduces every
recorded count exactly (8228 files, `useState` 2383, `timeout` 1075,
`objectstack` 12966, `@objectstack/spec` 4997, `window` 3464, `interval`
170, `tenant` 976, …).
- Every dark token still counts 0 at `62597c588`: the def exports and
key names each entry names, 95 tokens in all.
  - The `Span` / `SpanSchema` collisions are unchanged at 486 / 53.
  - Only the corpus size (8303) and the lit controls move.

## Re-read sites (all 46 the gate named)

| site(s) | objectui file(s) | reading at `62597c588` |
|---|---|---|
| 6 migration entries + their 6 `registry.ts` copies | whole corpus |
re-counted: dark tokens still 0. The `kernel-health-check…` entry keeps
its `87af769e9` lit-control clause verbatim (now dated) and adds the new
counts after it. `check:future-spec-major` holds a measurement exemption
to that exact sentence, and the new figure "`@objectstack/spec` 5043"
would read as a version |
| `component.test.ts` accordion/tabs icon; `component.zod.ts` `PageTabs`
/ `PageAccordion` `icon` | `containers.tsx` (changed, `4c6f549ef`) |
re-read: `853-859`, `912`, `1069-1075` and `1116` did NOT move, text
byte-identical |
| `component.test.ts` button icon; `component.zod.ts` `element:button`
`icon` | `resolve-icon.ts`, `button.tsx`, `lazy-icon.tsx` |
byte-identical, so no anchor moved |
| `component.zod.ts` metric `icon` | plugin-dashboard `index.tsx`,
`ObjectMetricWidget.tsx`, `MetricWidget.tsx` | byte-identical |
| `component.test.ts` / `component.zod.ts` kanban `limit` ×3, `quickAdd`
×2, `navigation` | plugin-kanban (all), `objectql.ts`,
`ElementDataSourceGate`, `ListView.tsx`, `ObjectView.tsx`,
`plugin-kanban.mdx` | byte-identical. ⚠️ The `quickAdd` block's lit
control said 6 `onCardClick`; the identical file counts 11 at both pins
(the test-side twin already says 11). It was miscounted, not moved, and
is corrected; the verdict rests on the 0 |
| `component.zod.ts` calendar `navigation` | `ObjectCalendar.tsx`
(changed, `afb228418`) | re-read: `921-935`, `936` and `938-939`
unmoved; `1293→1317`, `1218→1237` and `1228-1241→1250-1263`, text
byte-identical |
| `component.zod.ts` `object-map` header, `filter`, `sort`;
`component.test.ts` / `component.zod.ts` map/gantt/tree rows;
`functional-completeness.ts` map row | `ObjectMap.tsx` (changed,
`2252653d0`: only `warnOnTopLevelStyleUrl`'s docblock and dev-warning
text) | every anchor from `:289` on MOVED 16-19 lines with its text
byte-identical (e.g. `814→833`, `895→914`, `377→396`, `382→401`,
`1058→1077`); no read point or key set changed |
| `component.zod.ts` gantt / tree ×3 / timeline ×2 headers;
`functional-completeness.ts` gantt, timeline rows | plugin-gantt,
plugin-tree, plugin-timeline, `record-source.ts`, `ListView.tsx` |
byte-identical. The tree's `ElementDataSourceGate` count was re-taken:
tree 0, map/gantt/grid/calendar 3 each (`plugin-grid/src/index.tsx`
changed and still reads 3) |
| `api-methods-batch-conformance.test.ts` `sys_api_key` |
`ObjectGrid.tsx` (changed, `804831c2a` + `62597c588`) | ⚠️ the selection
block's CONTENT moved: objectui#10218 rewrote the `selection` arm to
"presence enables; an explicit off wins". The span is now `4024-4051`
(hash `c88443302d40`, was `6133933199…` over the last three hops).
Re-read, the record's claim holds: with neither `selection` nor
`selectable` authored, multi-select is enabled only by `hasBulkActions`,
and none of the four views declares `selection` |
| `dataset.zod.ts` measure `format` | `date-display.ts` (changed,
`516583b54`), `dataset-format.ts` | `formatDate` now parses via
`toDisplayDate` (date-only values keep their calendar day); the cited
style handling is unchanged; `198-233→271-306`, `152→225` |
| `view.zod.ts` view-face `limit` reach (objectstack-ai#19228) | 9 files | both
instruments re-run: the probe returns 0, and the control returns the
same 13 lines across 6 files, line for line. All 9 cited files are
byte-identical |
| `view.zod.ts:1975/:1978` ObjectMap anchors (content-asserted) |
`ObjectMap.tsx` | re-READ against `2252653d0`: it rewrote the docblock
and dev warning ABOVE `getMapConfig` (the remedy now names the declared
`map: { style }`, carried out as `mapStyle`). `getMapConfig` is
byte-identical, so `385→404` and `390→409`; the quoted `getMapConfig`
comment is intact (`:361→:380`) |
| `view.zod.ts` `FormField.span` docblock | `autoLayout.ts` (changed,
`e026e15f9`), `form.tsx` | `resolveColSpan` `153→154` (an import added
above it). `form.tsx` is byte-identical, so `spanLadderFor` still emits
the ladder |

`pnpm exec tsx scripts/check-objectui-pin-citations.ts --verify-anchors`
reads: all 7 content-asserted anchors pass at `62597c588`.

## The two sites first reported, then resolved under the second ruling
(comment 5793509969, option A)

1. **`packages/spec/src/kernel/functional-completeness.ts`, the calendar
row: its claim had become FALSE, so it is RESTATED.** The row said the
"Calendar configuration required" literal "is the text RENDERED at the
pin: objectui#10101 landed AFTER the pin". `git merge-base --is-ancestor
afb228418 62597c588` → 0 (and → 1 against `87af769e9a3e`).
- At the new pin the screen's copy goes through
`tt('calendar.configRequired', …)` (`ObjectCalendar.tsx:1207-1208`). Its
default and the `en` pack read the same English sentence; `zh`, `de` and
the other packs render their own words.
- The `if (!calendarConfig)` refusal arm is unchanged (the
`ObjectCalendar.tsx` diff on this hop is copy-only).
- The row now says what renders, names the arm rather than the English
sentence as the thing to match, and cites `.objectui-sha` = `62597c588`.
Comment only.
2. **`packages/spec/src/ui/view.zod.ts` `FormField.span` `.describe()`:
its claim still holds, so the citation is RE-POINTED.** The describe now
names `.objectui-sha` = `62597c588`.
- Re-read there: `plugin-form`'s `WIDE_FIELD_TYPES` is unchanged (it
shifted one line; repeater still reaches it through `field:grid`), and
`form.tsx` is byte-identical.
- Regenerated with `pnpm --filter @objectstack/spec gen:schema &&
gen:docs`, which touches only the two `span` rows in `view.mdx`.
- `.changeset/19503-pin-bump-describe-corrections.md`
(`@objectstack/spec: patch`) states the correction, after PR objectstack-ai#19398's
`17429-pin-bump-describe-corrections.md`.

## Gates

`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` on head `3fd40bedd` derives 124 families. The docs families
are added by `view.mdx`. Per-family exit codes are in the os-dev-report
comment on objectstack-ai#19503. Summary:

- `check:objectui-pin-citations` exit 0; `pnpm --filter
@objectstack/spec check:generated` exit 0 (all 15 artefacts)
- run as CI runs it and green: `check:react-declaration-parity`
(`MANIFEST=$PWD/sdui.manifest.json … --baseline
react-declaration-parity.baseline.json --strict`, "no new DECLARATION
divergence"); the derived spelling omits the manifest input
- `check:skill-examples` read exit 3 (PREREQUISITE NOT MET:
`@objectstack/client-react` unbuilt) in the sweep; after `pnpm --filter
'@objectstack/client-react...' build` it reads exit 0 (259 prose
examples type-check). `dispatch-gates --ran`: 124 derived, 122 run, 2
NOT-MEASURED, 0 UNRUN
- NOT MEASURED, left to CI: `check:dual-build-cjs-loads` and
`check:type-check-debt` (both exit 3, PREREQUISITE NOT MET: they need a
full workspace `pnpm build`; this diff changes only comments and
migration-rationale strings in spec)
- tests: `@objectstack/spec` vitest on `component.test.ts`,
`api-methods-batch-conformance.test.ts`, `src/migrations`,
`functional-completeness` and `src/ui/view*`: 22 files, 1562/1562 pass

Changed lines: 560 (+364 / -196, 19 files), under the 5000 threshold.
`origin/main` was merged in twice (`d3ff597a` before the PR was opened;
`16329059b` for this round, with `view.mdx` regenerated from the merged
tree as the pre-push `os-regen` check requires).

## Acceptance notes

- `component.zod.ts`'s flat-map guidance block also cites
`ObjectMap.tsx:382-396` without a pin. That spelling is outside the
gate's population, and it now reads `401-415` (byte-identical). It was
left as it is: the gate does not name it.
- objectstack-ai#18508 is not absorbed here: its trigger commit `bd09957380` is
already an ancestor of the OLD pin `87af769e9a3e`.

## 维护者速读(草稿)

- **改了什么**:把控制台钉住的 objectui 版本推进到 `62597c588`(包含权限修复
objectui#10153:`record:quick_actions.requiredPermissions` 改为失败即拒绝);同时按
PM 裁决 A,重录 sdui-parser 的对齐记录(只动了出处字段),并在新版本上逐条重新核对 spec 里引用 objectui
代码行号的注释。
- **为什么改**:当前发布的控制台仍按旧逻辑“失败即放行”,这是 objectstack-ai#19186
的解锁前提;钉住版本一动,引用旧版本的注释门禁必然变红,所以这些注释必须随版本重新测量,不能只替换哈希。
- **风险与代价(含回滚)**:不改任何 schema 或导出,已写的元数据接受/拒绝结果完全不变;唯一对外文字变化是
`FormField.span` 的 describe 把所引版本改为新钉住版本(说法本身仍成立,附 spec patch
changeset)。控制台行为只随 objectui 这 37 个提交变化(27
条带发布说明)。日历那一行说法在新版本上已不成立(提示文案改为按语言翻译),已按第二次裁决改写。回滚:还原本 PR 即可。
- **席位意见**:
- **你要做的**:本 PR 需要在最终 head 上做一次同档合约审查,记录后才能排队;PR 保持草稿。

Dev session: `session_01852ixPjE785Lt8MUeYGDEb` (PM seat
`domain:devx#1`, `session_01VDtqoecgES7ScQYGbFVDRv`).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01852ixPjE785Lt8MUeYGDEb

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

---------

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bump .objectui-sha — the console pin is 584 commits behind objectui main

2 participants