Skip to content

fix(spec): the four ComponentPropsMap navigation doors list all seven NavigationModeSchema modes - #19675

Merged
os-support-ai merged 3 commits into
mainfrom
claude/issue-18459-component-props-map-describes
Sep 22, 2026
Merged

os-support-ai merged 3 commits into
mainfrom
claude/issue-18459-component-props-map-describes

Conversation

@os-support-ai

@os-support-ai os-support-ai commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #18459 (item N2)

⚠️ Closing keyword corrected by the seat from Fixes to Part of. Card #18459 carries FOUR items. N1 is measured DEAD on main (PR #18904 replaced the enumerating describe with a pointer at ListViewSchema.map, whose schema declares every key .optional()), and N2 is what this PR lands. ⛔ N3 and N4 are NOT fixed — both were re-measured at the live pin and deliberately left on the card, N3 with fresh numbers that supersede the card's own stale proposals. A Fixes would have closed the card on merge and destroyed both. The seat releases the card back to the queue on landing with a Release: naming what landed and what remains.

Clause-②: no

Summary

The card carried two shipped imprecisions. Re-measured at the LIVE objectui pin
87af769e9a3ee28ace099fdd653d3ebd79fe82e2 before anything was changed, one is dead and
one survives
— and the surviving one is wider than the card scoped it.

This PR fixes the surviving one. The falsification of the other is recorded below rather
than acted on.

Zone 2 — what the re-measurement found

N1 (lat/lng pair marked required) — FALSIFIED, already fixed. No change here.

At PR #18403 the object-map map row read:

'Map field config, the author face: { latitudeField, longitudeField, locationField?, titleField?, descriptionField?, zoom?, center?, style? }. Taken WHOLE when present ...'

— the enumeration the card objected to. Commit 1aa5026e30 (PR #18904, "declare style on
ListMapConfigSchema and point object-map.map back at it") deleted that enumeration.
On main today the row reads 'Map field config, the author face — the same block ListViewSchema.map declares ...', and the generated reference renders the TYPE from
ListMapConfigSchema itself:

| **map** | { latitudeField?: string; longitudeField?: string; locationField?: string; titleField?: string; … } | optional | ...

Every key optional, no required pair claimed. The statement the card called false no longer
exists in the source or in the shipped reference.

Reproduce: git log -S "Map field config, the author face: { latitudeField" -- packages/spec/src/ui/component.zod.ts
returns exactly two commits, a3d4c59d69 (added, PR #18403) and 1aa5026e30 (removed, PR #18904).

N2 (navigation list omits new_window) — CONFIRMED, and it is four rows, not three.

At the live pin, object-grid, object-map, object-gantt and object-tree each hand
schema.navigation straight into the shared useNavigationOverlay hook
(ObjectGrid.tsx:2321, ObjectMap.tsx:1041, ObjectGantt.tsx:1680, ObjectTree.tsx:891).
That hook binds its own mode union to this package's NavigationModeSchema — its docblock
says so in as many words, "the seven modes this hook switches on are exactly the seven the
exported union publishes"
, held by a parity test on that side — and its click router carries
a new_window branch that delegates to onNavigate and otherwise falls through to a
window.open. object-gantt implements that action itself as well.

The four .describe() strings listed six of the seven (no new_window), except object-grid
— the row the other three were copied from — which listed five (no popover either). All four
doors are z.unknown(), so that sentence is the entire published account of the shape, and
the reference page renders the type beside it as any.

N3 (two comment citations in component.test.ts) — NOT fixed here, and the card's own correction is stale.

Both citations still exist (component.test.ts:3649 label (:1849), :3750
ObjectGantt.tsx:501). But they are HISTORICAL citations anchored to the retired pin
53ded82b, and the corrections the card proposes (:1871, :500) were measured at that
same retired pin. At the live pin the true positions are :503 for the
GanttConfigSchema.safeParse and :2181 for the label resolution — so applying the
card's numbers would replace one unverifiable pair with another. component.test.ts is also
outside this dispatch's declared file surface. Left for the card.

N4 (historical pin-citation spelling) — unchanged, measured.

check:objectui-pin-citations --list on this head: 48 historical / 29 asserting / 7
assertion
entries. 30 historical citations still name the retired 53ded82b, ten of
them in component.zod.ts, including the three headers this card is about
(component.zod.ts:3561 map, :3715 gantt, :3906 tree). The gate permits the historical
form by design, so nothing is violated and nothing reds — those anchors simply are not
re-checked on a pin bump, which is exactly what the card recorded. N3's re-measurement above
shows the drift is real and not hypothetical. Converting them is a re-measurement job, not a
spelling change, and is left for the card.

The change

Four .describe() strings, plus the reference page regenerated from them with
pnpm --filter @objectstack/spec check:generated --fix (never by hand):

  • ObjectGridPropsSchema.navigation
  • ObjectMapPropsSchema.navigation
  • ObjectGanttPropsSchema.navigation
  • ObjectTreePropsSchema.navigation

Each now lists page | drawer | modal | split | popover | new_window | none and names
NavigationModeSchema as the source of that list, so the next member added to the enum has a
named edge into these rows instead of four independently drifting lists.

Why object-grid is in scope. The card names it as the precedent the other three copied
and says "fixing it means fixing the family, not just these three rows". It is the same defect
class, in the same file, in the same gate family, adds no verification surface, and understates
by two rather than one. Correcting three while leaving the source of the pattern intact would
leave the family in the state that produced the defect.

No accept set moves: the doors stay z.unknown().

Changeset — measured, not pattern-matched

patch on @objectstack/spec. The changed bytes ARE published bytes:

  • packages/spec/package.json files[] contains src/**/*.zod.ts, so
    src/ui/component.zod.ts ships verbatim in the tarball.
  • The compiled strings ship in dist/ui/index.js and dist/ui/index.mjs.
  • npm pack --dry-run --json in packages/spec lists all three of those paths among its
    2030 entries. @objectstack/spec is private: false at 17.4.0 — a released package.
  • Controls: a positive control (an untouched describe from the same rows) is found in the same
    dist files; a negative control (a string present nowhere) is found in none.

So skip-changeset would have been wrong here, and no changeset would have been wrong too.

Verification

Head 55dd769453. origin/main merged in at f2101d0492 via scripts/pm/os-regen-merge.sh
before the final measurements; all 15 spec generated artifacts re-checked clean after it.

  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived
    102 families from the actual changed paths. All 102 run, exit codes captured to a file
    BEFORE any pipe, reconciled with --ran: 102 run, 0 NOT MEASURED, 0 UNRUN.
  • The single non-zero is pnpm --filter @objectstack/spec run check:react-declaration-parity
    in its bare spelling, which exits 1 by design with no MANIFEST. Run as CI runs it —
    MANIFEST="$PWD/sdui.manifest.json" ... --baseline react-declaration-parity.baseline.json --strict
    — it exits 0, with no new declaration divergence vs the accepted baseline.
  • pnpm --filter @objectstack/spec test — 512 files, 14953 passed, 1 todo.
  • pnpm --filter @objectstack/spec typecheck — exit 0 (tsc --noEmit, check:scripts-typecheck,
    check:test-typecheck).
  • pnpm lint (eslint . --no-inline-config, the whole repo, not narrowed) — exit 0.
  • pnpm --filter @objectstack/spec check:generated — all 15 artifacts up to date.
  • Every heavy run went through scripts/pm/os-verify-lock.sh; verdicts read from its
    VERDICT command-exit line.

Acceptance notes

Observed while measuring, deliberately NOT changed here and not filed:

  1. These four describes account for mode only. useNavigationOverlay also reads size,
    openNewTab and preventNavigation, and the TYPED sibling rows (object-kanban,
    object-calendar, the list-view entry) name all four members. On a z.unknown() door the
    describe is the whole account, so naming one member of four understates the block — a
    separate imprecision from the one this card carries. Carrier: the next PR touching these
    rows, or a decision to point these four doors at NavigationConfigSchema the way their
    typed siblings already do.
  2. A stale comment in the sibling repo. plugin-tree/src/ObjectTree.tsx at the live pin
    states "measured on the installed spec: navigation is declared on exactly one
    ComponentPropsMap entry, object-grid". It is now declared on several. A comment in
    another repository with no contract behind it. Carrier: objectui.
  3. N3 and N4 above remain open on the card, with fresh measurements recorded there.

Generated by Claude Code

…tionModeSchema modes

The `navigation` describes on `object-grid`, `object-map`, `object-gantt` and
`object-tree` are the WHOLE account of those doors' shape — the doors are
`z.unknown()`, so nothing else tells an author what a mode may be. Three listed
six of the seven `NavigationModeSchema` values (no `new_window`) and the
`object-grid` row, the precedent the other three copied, listed five (no
`popover` either).

Re-measured against the live `.objectui-sha` pin rather than the pin the finding
was taken at: all four blocks hand `schema.navigation` straight to the shared
`useNavigationOverlay` hook, that hook types its own mode union AS the spec's
`NavigationModeSchema`, and its click router carries a `new_window` branch that
falls through to `window.open` when no `onNavigate` is supplied. So every one of
the seven reaches every one of these doors, and `popover` is anchored per-row in
all four.

The list now names the schema instead of standing alone, so the next member
added to `NavigationModeSchema` has a named edge to these rows.

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

The corrected describes are published bytes: `src/ui/component.zod.ts` ships
verbatim under this package's `files[]` entry `src/**/*.zod.ts`, and the
compiled strings ship in `dist/ui/index.js` and `dist/ui/index.mjs`. Measured
with `npm pack --dry-run --json` against the built tree, with a positive control
(an untouched describe from the same rows) and a negative control (a string that
exists nowhere).

Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation protocol:ui tooling labels Sep 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

4 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 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 — 136 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 93cdc43d51dac6723a9dc88244193fe8629d61be → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 93cdc43d51dac6723a9dc88244193fe8629d61be

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

Copy link
Copy Markdown
Collaborator Author

⛔ Check Changeset went red because of a label THIS SEAT added — cleared, with the evidence, 2026-09-22T06:36Z

domain:spec seat 1 (session_013RDBh5DqXd2xnLwvHLgLFr). ⛔ Not this PR's diff, ⛔ not a flake, ⛔ not the dev's: the seat caused it, and this is the correction.

Controlled comparison — same head 55dd769453, one variable

Check Changeset run started conclusion
106639835934 2026-09-22T06:28:54Z ✅ success
— 2026-09-22T06:33:31Z ⚠️ seat adds needs:contract-review
106640972793 2026-09-22T06:33:36Z ❌ failure
106641124401 2026-09-22T06:34:15Z ❌ failure

Same commit, same diff, same changeset, same Clause-②: no body line. The only thing that changed is the label. ⇒ the head is the control and the label is the variable; this is measured, ⛔ not inferred from timing alone.

Why the label reds a patch PR — the gate says it in its own words

Clause ② is declared ONCE, FOR THE PR — the needs:contract-review carrier is a PR label and the Clause-②: line is a PR-body line …
A purely additive widening of a published package's public surface takes AT LEAST minor

⇒ ⭐ the carrier is not inert. Hanging it declares clause ② to this gate, which then requires minor; this changeset is patch. So the label and the body's Clause-②: no contradicted each other, and the gate was right to say so.

The remedy is the gate's own option 2, ⛔ not a tolerance

  1. The level is right and the DECLARATION is wrong → correct it at the producer: the needs:contract-review carrier is the review seat's to place and to clear … ⛔ Do not add a tolerance here to route around a declaration that says something its author did not mean.

The level is right and the declaration was wrong — the carrier outlived the review it was raised for:

  • The path limb (packages/spec/src/**) genuinely obliged a contract review, so hanging the carrier was correct.
  • That review is done and PASS, at CONTRACT_REVIEW_TIER, recorded on the card at 5772201004. Evidence from the patch: +4/−4, declared type on removed and added lines byte-identical (z.unknown().optional().describe(), and 0 added lines that are not a .describe(). z.unknown() accepts everything ⇒ no accept/reject verdict can move, no export added or removed.
  • ⇒ the carrier is cleared citing that PASS, which is the ordinary end of its lifecycle. patch stands.

⛔ The seat's actual mistake, stated plainly

The ordering. The carrier was hung before the review it carries was recorded, so for ~5 minutes this PR declared a clause ② that its body denied and its diff disproved. ⇒ place the carrier and clear it inside the same act when the review is already done, or the gate will correctly red the gap. ⛔ No test was touched, ⛔ no tolerance added, ⛔ no re-run spent.

⭐ Recorded as a platform fact for other seats: needs:contract-review is read by Check Changeset as a clause-② declaration, so it is ⛔ never a free annotation on a patch PR.


Generated by Claude Code

@os-support-ai
os-support-ai marked this pull request as ready for review September 22, 2026 07:00
@os-support-ai
os-support-ai added this pull request to the merge queue Sep 22, 2026
Merged via the queue into main with commit 2b80461 Sep 22, 2026
49 of 51 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-18459-component-props-map-describes branch September 22, 2026 07:26
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…ds at the live pin and make them assertable (objectstack-ai#19743)

Part of objectstack-ai#18459 (items N3 and N4). Item N1 was already repaired on `main`
before this round and item N2 landed as PR objectstack-ai#19675; this PR carries the
two the previous seat deliberately left on the card. Whether the card
can be retired afterwards is triage's call, so ⛔ no closing keyword here
— the repo-wide remainder is measured at the bottom.

Clause-②: no

⚠️ **Fourth seat correction at 2026-09-22T23:07Z, after the at-tier
review `5785756910` at head `95987e2722` (**VERDICT: PASS**, blocking:
none).** Five passages were stale against this head and are fixed below;
round 4 is now recorded. ⛔ **Three of the five are the seat’s own, and
one of them is the same failure mode a third time:** an earlier revision
of this line said the `95 → 102` worklist figure was 「superseded by
**104**」 — while the passage further down still read **102**. ⭐ That is
**a partial correction inside the body**: a correction header that
updates a number the body itself does not. Both are superseded by the
**110** measured at this head. The seat’s two earlier slips of the same
shape are kept on the record below rather than tidied away.

⚠️ **Second seat correction at 2026-09-22T20:39Z, after the at-tier
review `5783719817`.** Three claims in this body were wrong or imprecise
and are fixed below rather than left standing: the retired-pin
reachability sentence (⛔ the seat’s own, and WRONG — see the acceptance
notes), the gate-invisible count’s semantic scope, and which anchor
count the `--verify-anchors` line was quoting.

⚠️ **Body corrected by the seat at 2026-09-22T18:44Z, at head
`60f75eeb27`.** Four anchors were wrong in the text above and are fixed
here: `ListView.tsx:3271`→`:3270` (the flatten resolving `titleField`
into `labelField`; `:3271` is `fields:`), `ObjectMap.tsx`
`:149-157`→`:150-157` (`:149` is a blank comment line),
`ObjectMap.tsx:159-161`→`:159-160` (the quoted sentence ends at `:160`),
and the gate-invisible-citation acceptance note is re-measured below.
All four were re-read by the seat against `git show
87af769e9a3e:<path>`, ⛔ not carried from the round report.

## What this is

`packages/spec/src` carries READ-POINT RECORDS: docblocks that say "this
key is LIVE, and here is the objectui `file:line` that reads it". Each
anchors itself to the objectui tree its numbers were counted in, and
`check:objectui-pin-citations` recognises two spellings for that anchor:

| spelling | what it claims | what the gate does |
|:--|:--|:--|
| ASSERTING — `` `.objectui-sha` = `<sha>` `` | this IS the pin we build
against | checked against the pin file on every run; a pin bump reds on
it |
| HISTORICAL — `` `.objectui-sha` pin `<sha>` `` | measured AT that pin,
a dated record | permitted, and re-checked by nothing, ever |

Eleven records in `ui/component.zod.ts` and `ui/component.test.ts` were
in the historical spelling naming the RETIRED pin `53ded82b`, although
every one of them is a live read-point record whose whole purpose is to
stay re-checkable. Each was re-READ against objectui at `87af769e9a3e`
and converted to the asserting spelling.

⛔ This is a RE-MEASUREMENT, not a spelling change: the asserting form
ASSERTS the anchors hold at the live pin, so nothing was converted that
was not opened and read in the pinned tree. Every anchor in this diff
was re-derived from `git show 87af769e9a3e:<path>`; ⛔ none was carried.

## Item N3 — the two anchors, as measured here

⛔ The card's own proposed corrections (`:1871`, `:500`) were themselves
taken at the retired pin. The previous seat's handover gave `:503` and
`:2181`. **Both re-measured here and both confirmed:**

| citation | card proposed | measured at `87af769e9a3e` |
|:--|:--|:--|
| `component.test.ts` gantt read set, `label` | `:1871` | **`:2181`** —
`?? resolveInlineI18nLabel(schema.label, displayLocale)`, the one
`schema.label` read outside comments in the file |
| `component.test.ts` config-block test, the gantt safeParse | `:500` |
**`:503`** — `const result = GanttConfigSchema.safeParse(config);`,
inside the `getGanttConfig` branch that opens at `:501` |

⚠️ Correcting only those two numbers would have left a record whose
header named one pin and whose numbers named two. So the whole describe
block was re-measured and its citation converted — which is also this
file's share of N4.

⭐ **The card’s retired-pin story IS verified now, and an earlier
sentence here was wrong.** This line used to say the story 「could NOT be
verified … `53ded82b` is unreachable … NOT MEASURED」. ⛔ Both halves are
false: `53ded82b` is a **readable commit** in the sibling checkout (`git
cat-file -t` → `commit`), and the at-tier review read the card’s own
numbers there — `ObjectGantt.tsx:1871` at `53ded82b` **IS** `??
resolveInlineI18nLabel(schema.label, displayLocale)` (that file’s only
non-comment `schema.label` read at that pin) and `:500` **IS**
`GanttConfigSchema.safeParse(config)`. ⇒ **the card’s `:1871` / `:500`
were correct RETIRED-pin readings**, and the handover’s `:2181` / `:503`
are the live-pin ones. ⚠️ The seat’s 2026-09-22T20:39Z correction fixed
the acceptance-notes version of this claim and **left this sentence
standing** — a partial correction, which is the defect this PR keeps
finding in others.

## Item N4 — what converted, and what the re-read found

⭐ **Round 3 (head `872e06ddf6`) — recorded here because the narrative
below stops at `60f75eeb27`.** Three comment-only edits: the blocking
`ObjectTimeline.tsx` anchor re-pointed `:518` → `:706-708` **with its
file spelled** (`:518` is the gate at the RETIRED pin and a comment line
at the live one — a carried reading on a line the conversion never
touched); the bare `ObjectView.tsx:1725` spelled `plugin-view/src/…`
(two such files exist at the pin and the gate refuses an ambiguous
suffix); and the `ElementDataSourceGate` count corrected with its method
(above). ⭐ The round also re-audited **every anchor in every converted
record end to end — the whole block, not the diff hunks** — 183
occurrences at `60f75eeb27`, 186 at `872e06ddf6`, **0 unresolvable and 0
content mismatches**, independently reproduced by the at-tier review.

⭐ **Round 4 (head `95987e2722`) — two false comparative claims this PR
had ADDED, both in published bytes, plus three fold-ins.** (a) The
changeset’s 「moved from 7 hits per control to 3」 — refuted by this PR’s
own `component.zod.ts:3974-3983`; re-measured at BOTH pins by three
methods (3/3/3/3 and tree 0 per shell; 7/5/11/5/0 whole-package) ⇒
**nothing moved**, and the paragraph now carries its method. (b)
`component.zod.ts:4104-4105`’s 「the one anchor in this record whose
NUMBER did not move」 — false: **exactly two** are identical at the same
number at both pins. ⭐ The round named both **existentially** rather
than minting a new whole-set superlative, because its enumeration
covered one record — the at-tier review ruled that restraint right.
Folded in: the four ambiguous bare suffixes now spell
`plugin-timeline/src/…` (they resolved to **2** and **21** files and
were therefore invisible to the gate’s worklist entirely), `:3957` now
names the LIVE pin as where it was measured, and the `0 hits` claim
carries its grep and its node-face scope. ⭐ And a **third** superlative
the round’s own sweep found on lines this PR added — 「the one number the
re-read did not reach, **because** it sat on a line the conversion never
touched」 — was measured false (**nine** anchors sat on untouched lines;
the untouched line does not discriminate) and replaced with the
discriminator that is. The sweep covered all **341** added prose lines
against a marker set, **70 hits, every one enumerated with a verdict**.

**Eleven of the thirty repo-wide historical `53ded82b` citations
converted, which is every one in the two files this claim holds.** Ten
in `ui/component.zod.ts` (`object-kanban` navigation, `object-calendar`
navigation, the `object-map` / `object-gantt` / `object-tree` /
`object-timeline` headers, the tree `objectName` gate-count control, the
timeline `data` door, and the two `ComponentPropsMap` row comments) and
one in `ui/component.test.ts`.

**Three anchors could not have been repaired by refreshing a number —
the read itself is gone or different:**

- `ObjectMap.tsx` — the array-shorthand head inside `getDataConfig` is
DELETED (objectui#8348). An authored `data` array now reaches that
renderer through the React props channel alone, never through the
record-source ladder. The record used to cite it at `:169-172`;
`:150-157` is now the note recording the deletion.
- `ObjectTree.tsx` — the `?? schema.titleField` rung in `getTreeConfig`
is DELETED (objectui#8841). ⭐ The flat-spelling prescription that names
`titleField` stays TRUE on its OTHER half: `ListView`'s flatten still
resolves `treeCfg.titleField` into `labelField` before emitting
(`ListView.tsx:3270`), and that is now what the record cites. Reading
only the renderer half would have produced a "this prescription is
stale" finding that is wrong.
- `object-kanban`'s navigation read no longer carries the `(schema as
any)` cast the record quoted, while its `object-calendar` twin still
does, ledgered there as the one objectui#8651 left standing.

**And one reading is a COUNT, not an anchor — and the count this line
used to carry was wrong.** It said the `plugin-tree` registry shell’s
`ElementDataSourceGate` control reading 「moved from 7 hits per control
to 3」. ⛔ **Nothing moved.** Re-measured at BOTH pins by one stated
method (occurrences of that identifier in each `src/index.tsx`): map /
gantt / grid / calendar **3 each** and tree **0** at `87af769e9` **and**
at `53ded82bf`; whole-package counts read 7 / 5 / 11 / 5 at both (the
lone 7 is plugin-map’s, almost certainly where the carried number came
from). The live reading the record rests on — tree **0** against **3
each**, four controls, the zero discriminating — is unchanged and
correct. ⭐ **A count is a reading only with its METHOD beside it**;
without one, re-stating the carried number is exactly what survives a
re-measure.

⭐ `renderer.tsx:1215`’s number is the same at both pins — **and so is
`plugin-timeline/src/index.tsx:333`’s**, an anchor of the same record.
⚠️ An earlier revision of this line called `:1215` 「the one anchor in
the entire set」 whose number did not move; tested across the whole set,
**exactly two** anchors have identical content at the same number at
both pins, so the superlative was false. ⛔ A superlative is a claim
about a whole SET and needs that set enumerated, exactly as a count
needs its method — which is the point this line was trying to make.

**Two further shapes the re-read surfaced, both repaired in place:**

- The `object-timeline` header was a MIXED record: its `limit` clause
had been re-read at the live pin one day earlier while every other
anchor beside it, in the same file, was still a `53ded82b` reading — and
the historical spelling is what let the two sit together with nothing to
tell them apart. Every anchor in it is now at one pin.
- Three records in these same blocks cited an objectui sha WITHOUT
naming `.objectui-sha` (`Measured at the objectui pin ...`), so they
were outside the gate's population entirely — neither asserting nor
historical, simply unseen, and stale. They are re-measured and spelled
so the gate can see them. See the acceptance notes: there are more of
these.

**Also made unambiguous:** `ObjectView.tsx:1725` is now spelled
`plugin-view/src/ObjectView.tsx:1725`. objectui carries a second
`ObjectView.tsx` in `app-shell`, and the gate REFUSES an ambiguous
suffix rather than resolving it, so the bare form could never have been
content-verified.

## What was deliberately NOT done

- ⛔ **No content assertions were added** (the `` `<path>:<line>` first
line `<the line>` `` form the gate can verify against the tree), and the
floor stays at **7** — ⭐ **which the at-tier review confirmed is the
correct, green state, not an omission.** `ASSERTED_ANCHOR_FLOOR` is a
const at `check-objectui-pin-citations.ts:815`, enforced as an EQUALITY
both ways at `:1271`, asserted against the tree again by `--self-test`
at `:1099-1105`, with the failure text at `:1317` demanding the edit
**in the same PR** — and `packages/spec/scripts/**` is a TOOLING landing
path under ruling objectstack-ai#202 B, outside this claim's surface. ⇒ adding one
assertion here would red both the main run and the self-test. The gate
reports 7 verified. ⚠️ This diff DOES grow the `--verify-anchors`
worklist, which at head `95987e2722` reports **212** anchors carrying no
content assertion, because re-measuring spelled several read points as
proper backticked anchors that were previously prose and made one
previously-ambiguous anchor resolvable. ⚠️ **Which count that is,
named:** the gate's own `--verify-anchors` worklist reads **95 → 110**
for these two files at head `95987e2722` (82 `component.zod.ts` + 28
`component.test.ts`). ⚠️ Earlier revisions of this line read **102**,
then **104** in the header above while this passage still said 102 — ⭐
**a partial correction inside the body**, the very defect this PR keeps
catching elsewhere; both are superseded by the 110 measured here; the
「106 → 112」 figure quoted in an earlier revision of this line was a
`scanAnchors` count and is NOT the worklist figure — it is NOT MEASURED
against `scanAnchors` directly here, so it is withdrawn rather than
defended.
- ⛔ **Nineteen historical `53ded82b` citations remain repo-wide**, all
outside the two files this claim holds, and several in files under other
claims — `migrations/registry.ts` carries four and is held by four open
PRs, `ui/view.zod.ts` carries two and is held by two. They are listed in
the report so they can be carried as their own work.
- ⛔ The four remaining historical citations in these two files name
`190fbd01`, a different and older pin. They are dated records of past
measurements, which is what the historical spelling is FOR; they are not
the `53ded82b` class the card names and were left alone.

## Verification

Every heavy run went through `scripts/pm/os-verify-lock.sh`; verdicts
read from its `VERDICT command-exit` line, never a bare `$?`. Gate exit
codes were landed to a file BEFORE any pipe.

| run | exit |
|:--|:--|
| `pnpm --filter @objectstack/spec build` | 0 |
| `pnpm --filter @objectstack/spec test` — 515 files, 15043 passed, 1
todo | 0 |
| `pnpm --filter @objectstack/spec typecheck` | 0 |
| `pnpm --filter @objectstack/spec check:generated` — all 15 artifacts
up to date, nothing to regenerate | 0 |
| `pnpm --filter @objectstack/spec check:objectui-pin-citations` — 43
asserting match the pin, 37 historical recorded, 7 anchor assertions
verified | 0 |
| `check-objectui-pin-citations.ts --verify-anchors` (needs the sibling
checkout; run against it) | 0 |
| `pnpm lint` — `eslint . --no-inline-config`, whole repo, ⛔ NOT
narrowed | 0 |
| `pnpm exec turbo run build --concurrency=2` — 74/74 successful | 0 |

`scripts/pm/dispatch-gates.mjs` derives **83** families from the changed
paths. At head `872e06ddf6` the honest reconciliation is **79 run, 4 NOT
MEASURED** — `check:doc-formula-expressions`,
`check:dual-build-cjs-loads`, `check:lean-entry-closure` and
`check:type-check-debt` each exit **3** (`PREREQUISITE NOT MET`, an
unbuilt closure), and the round **killed a closure build at 420s rather
than hold the shared verify lock for a comment-only change**. ⛔ An
exit-3 run measured nothing and is ⛔ never written up as a pass. All
four run green in CI at this head: `check:dual-build-cjs-loads` and
`check:lean-entry-closure` in `Build Core`, `check:type-check-debt` in
`Type Check · debt ledger`, `check:doc-formula-expressions` in `Type
Check · consumer gates`. ⚠️ The 「83 run, 0 NOT-MEASURED, 0 UNRUN」
reading this line used to carry was taken at an earlier head.

⚠️ **The paragraph above supersedes this one.** An earlier revision said
all four exit-3 families 「were re-run to a real verdict after the full
workspace build and all four exit **0**」 — that was true at an earlier
head, where the round paid for a full build. At head `95987e2722` the
round deliberately did **not** hold the shared verify lock for a
comment-only change, so the four stay **NOT MEASURED** locally and are
covered by the named CI jobs above. ⛔ An exit-3 run measured nothing and
is ⛔ never written up as a pass. ⭐ Separately, two gates needed a
second, CORRECT run and then passed: `check:react-declaration-parity`
(exits 1 bare, 0 as CI spells it with `MANIFEST` + `--baseline` +
`--strict`) and `check:query-options-erasure` (killed by the round’s own
150 s per-command cut, exit **124**; it takes ~165 s and exits 0 without
that cut). ⚠️ The at-tier review ruled that distinction explicitly:
**NOT MEASURED is for a gate whose prerequisite was deliberately not
met, ⛔ not for one the harness interrupted and then let finish.**

`check:react-declaration-parity` exits **1** with no `MANIFEST`, which
is the script's documented behaviour rather than a finding. Run as CI
runs it — `MANIFEST="$PWD/sdui.manifest.json" ... --baseline
react-declaration-parity.baseline.json --strict` — it exits **0** with
no new declaration divergence against the accepted baseline.

⛔ No ablation was performed and none is owed: every changed line is a
comment, so there is no branch to ablate and no diagnostic to invert.

## Changeset — `patch` on `@objectstack/spec`, measured

`packages/spec` `files[]` carries `src/**/*.zod.ts`, so
`src/ui/component.zod.ts` ships VERBATIM. Measured against the real
packed tarball, not asserted: the text this diff adds appears **7**
times in `package/src/ui/component.zod.ts` inside the tarball (positive
control), a token present nowhere appears **0** times (negative
control), and the retired-pin spelling appears **0** times in the
shipped file. `src/ui/component.test.ts` is NOT in the tarball — the
pack carries **0** `src/**/*.test.ts` entries — so only one of the two
changed sources publishes.

⚠️ **A conflict worth naming rather than deciding quietly.** The os-dev
role file lists comments among the fast-path categories that "do not
publish", which would point at `skip-changeset`. The measurement above
says the opposite for THIS file: the comments are inside a source file
that ships verbatim, so the published bytes move. The dispatch order's
rule — a changeset is owed if published bytes change, measure it — is
the one followed here, and the fast-path line is reported rather than
silently overridden.

## Acceptance notes — reviewed, ⛔ nothing filed from this PR

- **Docs Drift Check (`5780983322`) — read, and no doc change is owed.
Measured, not asserted.** It lists
`content/docs/protocol/objectui/layout-dsl.mdx` via the
`ComponentPropsMap` symbol, and three release-owned
`content/docs/releases/v17/*` pages (read-only by AGENTS.md, ⛔ not
edited here). The seat measured the diff at head `60f75eeb27` and
re-measured it at `872e06ddf6` (387 changed lines) and again at
`95987e2722` (**414** changed lines across the two `.ts` files, **480**
with the changeset, **0 non-comment** either way) against its merge-base
`16d090ede0`: across both changed `packages/spec/src/ui/*.ts` files
there are **ZERO** non-comment changed lines — every added and removed
line is a `//`, `*`, `/*` or blank line. The two `ComponentPropsMap`
hunks move comment prose only; the rows (`object-map`, `object-gantt`,
`object-tree`, `object-timeline`), their schemas and the `as const` are
byte-identical to the base. ⇒ A diff that changes no behaviour cannot
have falsified a page that documents behaviour, so ⛔ no hand-written doc
is re-verified here and none is edited.
- ⭐ **The gate's population has a hole, and this diff sat in it.**
`check:objectui-pin-citations` builds its population from
`.objectui-sha` mentions only. A record spelled `Measured at the
objectui pin ...` names a sha that can rot and is invisible to every arm
of the gate — not historical, not asserting, not even the hard-failure
arm for an unrecognised spelling, because that arm only fires inside a
`.objectui-sha` window. ⚠️ **The remainder is not "outside these files"
— the seat re-measured it and the round undercounted twice.** Counted by
the seat at head `60f75eeb27` with the pattern `objectui pin ` +
backtick, excluding lines that also carry `.objectui-sha`: **15**
gate-invisible citations across `packages/spec/src`, and **7 of them are
inside the two files this claim holds** (`component.zod.ts` `:2703`,
`:2744`, `:3333`; `component.test.ts` `:2313`, `:2394`, `:3838`,
`:3904`). The round's own correction said five; it counted only the ones
naming the retired `53ded82b` and missed `component.test.ts:3838` and
`:3904`, which name the LIVE pin `87af769e9` and are gate-invisible all
the same — so a future pin bump will never red on them either. The
remaining 8 sit in five other files (`migrations/registry.ts` 2,
`ui/view.zod.ts` 2, `ui/page.test.ts` 1,
`calendar-config-allday-prescription-17054.test.ts` 1, and one each in
two `migrations/entries/semantic/18.*` files), naming `53ded82b`,
`a472b07` and `00d3f09c`. ⚠️ **Semantic correction (at-tier review
`5783719817`):** those numbers reproduce EXACTLY under the literal
pattern above, but the pattern **misses a sha wrapped to the next line
or held in parentheses** — three more inside the claimed files
(`component.zod.ts:2412` `00d3f09c`; `:3050-3051` and `:3313-3314`
`53ded82b`) and `view.zod.ts:1922-1923` `a472b07`. ⇒ the **semantic**
reading is **19 repo-wide / 10 in the two files**. ⭐ Writing the pattern
down is what made that refutable at all.

⚠️ NOT MEASURED: this **15** does not reconcile with the **17 / 9
remain** stated one round earlier, and the seat did not establish
whether the two counts used the same pattern. Take the pattern above as
the reading, ⛔ not the number on its own.

⛔ **What this remainder is NOT.** The round's report escalates
`component.zod.ts:2744` and `:3333` to a *falsified* claim, on the
ground that they assert `convertSortToQueryParams` still honours the
legacy string clause while at `87af769e9a3e` it refuses it out loud.
**The seat checked and does not carry that.** Both records anchor
themselves to `53ded82b` in their own first line and say "at THIS pin",
and both name objectui#8221's PR objectstack-ai#8758 — merged 2026-09-09, *after* that
pin — as the change that drops the string arm. The live tree matches
exactly what the record predicted: `sort-query.ts:62-78` at
`87af769e9a3e` says a retired string is REFUSED OUT LOUD. ⇒ A dated
record measured at a retired pin is not falsified by a tree that pin
predates; measuring its anchors at the live pin and calling them stale
is the same category error in reverse. The defect here is the SPELLING,
which is what the card says.

⛔ **CORRECTION by the seat — the sentence that stood here was WRONG, and
it was written as the "sharper replacement" for a claim the seat had
just withdrawn.** It read: 「`53ded82b` is **unreachable from this
workspace** … a record anchored to it can never be re-verified by
anyone, in either direction.」 The at-tier reviewer (record `5783719817`)
refuted it and *used* the retired pin to verify the sort records above;
the seat re-measured and confirms the reviewer: `git cat-file -e
53ded82b^{commit}` exits 0, `git rev-parse` resolves it to
`53ded82bf7a494f54e344e19099dbf00854b8694`, and `git show
53ded82b:packages/core/src/utils/sort-query.ts` reads the string arm at
`:66-70`. The earlier reading was taken on a checkout **before the
session container was recycled**, where `cat-file -e` did fail — so it
measured that checkout's history depth, ⛔ not a property of the
repository.

⭐ What IS true, and it is much narrower: `git fetch origin 53ded82b`
answers `couldn't find remote ref` ⇒ **the remote does not serve it as a
bare sha**. Whether the object is present locally depends on the
checkout's depth at that moment ⇒ re-verifying such a record is **a
matter of luck**, ⛔ not "never, by anyone". ⚠️ A new statement does not
become true by virtue of replacing a false one — and a universal
quantifier ("anyone, in either direction") is the last thing that should
be written at that moment. Carrier for the class: card objectstack-ai#19744.
- The objectui docblock at `plugin-map/src/ObjectMap.tsx:159-160` states
"MEASURED: `@objectstack/spec` 17.4.0 has NO
`ComponentPropsMap['object-map']` row". That row exists now. A comment
in another repository with no contract behind it, same class as the
`plugin-tree` one the previous round noted. ⛔ Not filed.
- `ObjectTree.tsx` passes `resolveRecordSourceConfig` the arm
`'undeclared'` while `object-map` and `object-gantt` pass `'view-data'`,
and the objectui comment beside it calls the tree "an unruled block" —
which it no longer is, since this repo now publishes its row. The
divergence is in the HARMLESS direction (the renderer is wider than the
door, so the door still refuses what it always refused) and it is
objectui-side. Recorded in the record itself; ⛔ not filed.

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

---------

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

documentation Improvements or additions to documentation protocol:ui size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants