…404 (objectui#10755) (objectstack-ai#10766)
Fixes objectstack-ai#10755
Clause-②: no
Dispatched implementation of the `domain:ui` seat 2 claim (comment
`5853230223`) on objectui#10755, session
`https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN`. Citations
only, on three published docs pages: no sentence's claim moves, and the
frontmatter of all three files is byte-identical.
## What changed
- The ten objectui issue links that answer 404 now cite the landing
commit on `main`, as a 9-character backticked sha. That is the form
`content/docs/guide/ci-cd-pipeline.md` already uses for commits (five of
its six existing sha citations are 9 characters), and the form PR
objectui#10707 chose for the same class on objectui#10701.
- Two prose-only mentions of the same dead numbers, inside the same two
sentences, are re-pointed so each sentence stays coherent: "in objectstack-ai#7638's
card body" reads "in the second card's body", and "over the file objectstack-ai#8057
is entirely about" reads "over the file that third writer lives in".
Lychee does not read those; the reader does. Each sentence's claim is
unchanged.
- Commits, not PRs, at every site. Two of the landing PRs answer 404
themselves (PR 7637 for objectui#7627, PR 7805 for objectui#7753), so PR
numbers are not uniformly alive; commits on `main` are. The same ten
numbers answer 200 in objectstack, each an unrelated subject, so a
repo-qualified re-cite would send a reader to the wrong record.
- No lychee config or workflow change, and no changeset: `content/**` is
not a released package (`check-changeset-presence` below).
## H1 reproduction, at the base `704e05b09d` (origin/main at claim time)
- Lychee is not preinstalled in this container; the 0.18.1 release
binary was fetched into the scratchpad. `lychee --verbose --no-progress
--config lychee.toml` over the three files: `🔍 195 Total (in 15s) ✅ 182
OK 🚫 13 Errors`, exit 2. Ten `[404]` lines, one per objectui issue URL,
exactly the card's ten, on the same lines the card names (re-read at the
base). The other three errors are this container's egress proxy, not
link rot: `github.com/changesets/changesets` and
`github.com/lycheeverse/lychee` answer 403 ("GitHub access to this
repository is not enabled for this session") and `git-cliff.org` is a
CONNECT 403. The CI run `36294593837` had 10 errors and none of these;
they recur identically at the head.
- REST `GET /repos/objectstack-ai/objectui/issues/N`: all ten answer
404, on the API and on the HTML page. Lit controls: objectui#7013,
objectstack-ai#7015, objectstack-ai#9229 and objectstack-ai#7790 answer 200. Neighbours objectui#5792, objectstack-ai#5794,
objectstack-ai#7792 and objectstack-ai#9231 also answer 404 and are cited nowhere in the swept
trees.
- objectstack `GET /repos/objectstack-ai/objectstack/issues/N`: all ten
answer 200 and every subject is unrelated (a spec docs generator PR, a
bulkCreate card, a QA run, an email-template PR, and so on).
## H2 and H3, site by site
Each row: the quoted anchor in the sentence, the dead citation, the
replacement, and the evidence (the dead number's REST status is 404 in
every row; the landing commit's subject is quoted without its
conventional-commit prefix).
| file | sentence anchor | old | new | evidence |
|:--|:--|:--|:--|:--|
| `ci-cd-pipeline.md` | "three times so far, each found by a human and
never by a gate (…, the third in the same package as the first)" |
objectui#6943 | "and a third in the same package as the first, all three
recorded in `6aeba673c`, the commit that landed this gate" | `6aeba673c`
"require the directory form of the tooling exclude in every published
build tsconfig" (PR 7409 answers 200). Its message records
objectui#4006, objectstack-ai#4836 and objectstack-ai#6943 as the three incidents. objectstack-ai#4006 and objectstack-ai#4836
stay linked; both answer 200. |
| `ci-cd-pipeline.md` | "measured at two cards and three copied call
sites (…), both closed by pointing the prose at
`resolveRecordSourceObjectName`" | objectui#7627, objectui#7638 |
"measured at two cards and three copied call sites, both cards closed by
pointing the prose at `resolveRecordSourceObjectName` (`b041b9c0c`,
`2ce2612df`)" | `b041b9c0c` "one shared record-source object-name
reader, six plugins delegate" (PR 7637 answers 404); its message settles
objectui#7627 by name. `2ce2612df` "the record-page URL follows the
record source, not the top-level key" (PR 7648 answers 200);
`abdcd189c`'s message names PR 7648 as the change that removed the
spelling objectui#7638 was filed about, and its diff points the hook's
`@example` at `resolveRecordSourceObjectName` (7 occurrences). |
| `ci-cd-pipeline.md` | "in objectstack-ai#7638's card body and then in the dispatch
that repeated it" (prose, not a link) | objectui#7638 | "in the second
card's body and then in the dispatch that repeated it" | The two cards
are named in the preceding clause; the claim is unchanged. |
| `ci-cd-pipeline.md` | "a count ratchet would have been green too,
which is why … rejected that option on the instance itself" |
objectui#7753 | "which is why `72498f257`, the commit that landed this
gate, rejected that option on the instance itself" | `72498f257` "a
census derives every handler key a registered renderer reads and
requires its arm to declare it (objectui#7753)" (PR 7805 answers 404).
Its message records the substitution that held `RUNTIME_SLOT` at 44 and
`ALL_SITES` at 66, the count reading the sentence describes. |
| `ci-cd-pipeline.md` | "… then reproduced the identical defect on a
THIRD writer neither guard covered, in that card's own required dogfood"
| objectui#8057 | "The identical defect was then reproduced on a THIRD
writer neither guard covered, in that card's own required dogfood
(recorded in `9662aca56`, the commit that landed this gate)" |
`9662aca56` "apply the object-metadata write invariant at the write
doors, not at an enumeration of writers" (PR 9238 answers 200). Its
message: "objectui#8057 then reproduced the identical defect on a third
writer in that card's own required dogfood, and a sweep found nine
more". "That card" is objectui#7714, linked in the preceding sentence,
which answers 200. |
| `ci-cd-pipeline.md` | "returns ZERO over the file objectstack-ai#8057 is entirely
about" (prose, not a link) | objectui#8057 | "returns ZERO over the file
that third writer lives in" | The third writer is referenced two
sentences earlier; the claim is unchanged. |
| `ci-cd-pipeline.md` | "widened from `content/docs/**` alone by …" |
objectui#7878 | "widened from `content/docs/**` alone in `59a3a233d`" |
`59a3a233d` "widen the expression-carriage census to check:doc-types'
surface (objectui#7878)" (PR 8105 answers 200). |
| `ci-cd-pipeline.md` | "cwd the only variable (…). Root `AGENTS.md` had
taught" | objectui#7791 | "cwd the only variable (`223b1e4a3`). Root
`AGENTS.md` had taught" | `223b1e4a3` "root the capability spec-parity
pin at the file, not the cwd (objectui#7791)" (PR 7796 answers 200). Its
message carries the two-cwd measurement. |
| `ci-cd-pipeline.md` | "The defect it closes (…):" | objectui#5793 |
"The defect it closes, repaired in the gate's own landing commit
`111741454`:" | `111741454` "gate @objectstack/spec range floors against
published symbols" (PR 6076 answers 200). Its message describes the
`plugin-detail` `ReferenceRailEntry` defect in the sentence's own words,
and its diff bumps `packages/plugin-detail/package.json`'s
`@objectstack/spec` floor from `^17.0.0` to `^17.1.0`, so the same
commit landed the gate and repaired the defect. |
| `lookup.mdx` | "and must never reach authored object metadata (…)" |
objectui#7014 | "and must never reach authored object metadata
(`0e3b3be09`)" | `0e3b3be09` "correct three false spec-alignment claims
and pin the real boundary" (PR 7510 answers 200). `e1545cfe6` (PR 7994),
the commit that wrote this sentence, says it carries PR 7510's
attribution. The claim itself is stale; see Acceptance note 1 for why
the citation is kept resting on the landing commit rather than dropped.
|
| `select.mdx` | "on both the editable and the read-only path (…)" |
objectui#9230 | "on both the editable and the read-only path
(`20b507aff`)" | `20b507aff` "render a picklist option value when its
label is blank (objectui#9230)" (PR 9258 answers 200). |
All ten shas are unique at 9 characters (`git rev-parse --short=9`
returns 9 for each) and each is an ancestor of `origin/main` by
construction (they were taken from `git log origin/main`).
## H4 the whole tree, not only the ten
- `git grep` at `origin/main` over `content/**`, `docs/**`, `README.md`
and `skills/**` for
`github.com/objectstack-ai/objectui/(issues|pull)/N`: 191 distinct
numbers across 14 files. REST on every one: 181 answer 200, 10 answer
404, and the ten are the card's ten. Nothing outside the three files.
- Lychee, same config plus `--exclude '.*' --include` for objectui issue
and PR URLs (offline-safe: it reaches github.com only), over
`content/docs/**/*.md`, `content/docs/**/*.mdx`, `docs/**/*.md`,
`docs/**/*.mdx`, `README.md` and `skills/**/*.md` at the head
`08be3d7708`: `🔍 530 Total (in 14s) ✅ 188 OK 🚫 0 Errors 👻 342 Excluded`,
exit 0.
- Bare-number mentions of the same ten (`objectui#N` in prose, which
lychee cannot see) elsewhere in the tree at `origin/main`: 204 sites
outside the three files: 38 in `packages/*/src` non-test text (JSDoc and
comments, some of which ship in `dist` per PR objectui#10707's `dist`
reading), 20 in pending `.changeset/*.md` files (which publish verbatim
into the CHANGELOG at the next release), 81 in test files, 28 in
`scripts/`, 6 under `.github/`, 1 in governed agent text, 30 elsewhere.
Not widened into this PR; listed under Acceptance notes for a card.
## Gates, all on the head `08be3d7708`
- Lychee over the three files, repo config: `🔍 185 Total (in 17s) ✅ 182
OK 🚫 3 Errors`, exit 2 — the 3 are the container proxy artefacts named
under H1 and zero are objectui links (the base run had 13 errors, 10 of
them these links). Objectui-scoped (`--exclude '.*' --include` objectui
issue/PR URLs): `🔍 185 Total (in 13s) ✅ 173 OK 🚫 0 Errors 👻 12
Excluded`, exit 0.
- `pnpm docs:check-links` (`check-doc-links`): `Links are valid across
17 scan roots.`, exit 0.
- `pnpm check:control-bytes`: `check-control-bytes: OK (scanned 8943
tracked text file(s); skipped 85 binary).`, exit 0.
- `pnpm check:new-line-citations`: `VERDICT
new-cross-file-line-citations: 0 new citation(s), enforcement
report-only -> exit 0`.
- `node scripts/check-changeset-presence.mjs`: `3 file(s) changed, 0 of
them published source of a package the release covers … No source or
published contract of a released package changed in this range, so no
changeset is owed.`, exit 0.
- `pnpm check:doc-fences` exit 0; `pnpm check:doc-example-ids` exit 0
(`414 real reference(s) all resolve in the catalog registry`).
- The test readers `node scripts/markdown-test-inputs.mjs --list` names
for the classes `content/docs/**` and
`content/docs/guide/ci-cd-pipeline.md` (26 files, including
`ci-cd-pipeline-doc.test.ts` and `check-links-workflow.test.ts`), run as
one `pnpm exec vitest run` from the repo root: `Test Files 26 passed
(26)`, `Tests 1195 passed (1195)`.
- Frontmatter lines 1 to 4, md5 before and after, identical:
`ci-cd-pipeline.md` `6ca230b6b64dac22a2031d21ff311b13`, `lookup.mdx`
`289a5c887fd231f6b0b0824f53769fad`, `select.mdx`
`bae97ed7e436e0a06b8a154242ec15f1`.
- NOT MEASURED: the `Check Links` workflow itself, which runs on
`schedule` and `workflow_dispatch` only. Per the triage, re-run it via
`workflow_dispatch` once this lands to show the weekly sweep green.
## Serial
- At claim time (06:07Z) no open PR touched the three files. Before the
final push, `origin/main` was fetched into the private ref
`refs/issue-10755/main` = `f308a655b8`, two commits past the base
(`f308a655b8`, `baac95a261`). `git diff --stat` between the base and
that ref over the three files is empty, and `git merge-tree --write-tree
HEAD refs/issue-10755/main` exits 0, so no merge commit is owed.
## Acceptance notes
1. **The `lookup.mdx` paragraph around the re-cited sentence is a stale
contract claim (not widened; for a card).** It says the installed spec's
`SelectOptionSchema` "is strict over exactly `{label, value, color,
default, visibleWhen}` and refuses `description` by name", that
authoring it "fails the whole field with a 422", and that the key "must
never reach authored object metadata". Measured on the installed
`@objectstack/spec` 17.4.0 (`@objectstack/spec/data`):
`SelectOptionSchema.safeParse` of an option with `description` is
ACCEPTED, `FieldSchema` with that option is ACCEPTED, and the refusal
control `icon` is REFUSED with `unrecognized_keys`. Spec 17.3.0 declared
the key under the maintainer's 2026-08-25 ruling on objectui#6140 /
objectui#6153 (Option A); `544ecba84f` (PR 9588) already moved the
shipped docblocks off the refusal, and
`packages/types/src/__tests__/select-option-spec-extension-7014.test.ts`
pins the moved boundary. The repair is a paragraph rewrite, outside this
order's surface (citations only). The citation at that site is kept
resting on the landing commit `0e3b3be09`, the commit that made the
measurement the sentence still repeats, so a reader following it lands
on when, and against which spec version, the claim was true; dropping
the pointer would leave the stale sentence with nothing to date it by.
2. **Sibling class, one sweep card rather than ten:** the 204
bare-number mentions of the same ten numbers outside the three files
(H4, last bullet). The shipping subset is the 38 `packages/*/src`
non-test sites and the 20 pending changesets; objectui#7014 alone has 30
mentions, including three pending changesets named after it. This is the
class objectui#10701 was filed for, and that card itself suggested one
sweep card if more numbers in the range were gone.
3. **Neighbouring numbers that also answer 404** (objectui#5792, objectstack-ai#5794,
objectstack-ai#7792, objectstack-ai#9231) are cited nowhere in the swept trees; observation only.
4. **Instrument versions:** local lychee is 0.18.1;
`lycheeverse/lychee-action@v2` runs the current release. Same
`lychee.toml`, same globs; the CI reading is the authoritative one, and
the three residual errors above are container-specific.
---
_Generated by [Claude
Code](https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN)_
Co-authored-by: Claude <noreply@anthropic.com>
Part of #8442 — the ADDITIVE half of the objectui#8426 chain, under the director-seat ruling of decision batch #86 (option A, contract-first). ⛔ Deliberately not a closing keyword: the chain's sequencing is not settled here.
What this carries
ChatToolInvocationin@object-ui/typesgains an optionalapprovalenvelope —{ id, approved?, reason?, isAutomatic?, signature? }— with its hand-written Zod mirror, andChatbotEnhanced.ChatToolInvocationmirrors it.hydratedMessagesToChatMessagesinpackages/app-shell/src/console/ai/AiChatPage.tsxstops droppingapprovalandpendingActionId.Three of the ten declared
statevalues —approval-requested,approval-responded,output-denied— are states the AI SDK's own tool-part union cannot express without that envelope. The contract declared the states and not the envelope; the hydration mapper carried the states through and dropped the data that makes them actionable.⭐ What was MEASURED, not inherited from the card body
The card cited three line addresses from an older commit. All three were re-located by symbol on this branch's base (
b775500af) and both behavioural claims execute:mapMessages.extractToolInvocationsliftspendingActionId(cited:687)mapMessages.ts:694on the basependingActionId: pending?.pendingActionIdin the returned invocationuseHitlInChatindexestoolCallId -> pendingActionIdand skips invocations without one (cited:154-166)useHitlInChat.ts:157-166on the baseidMapmemo guards onif (tool.pendingActionId && tool.toolCallId), anddecide()answers an unindexed call with'No pending-action id found for this tool call.'output-deniedpass-through (cited:44/:49):45/:50Two further readings that change how the halves had to be lifted, and that the card did not have:
pendingActionIdis not a part key and never was. In rehydrated history it exists only INSIDE the tool result envelope. "Stop dropping it" therefore cannot be a pass-through: it has to be derived, and the only honest derivation is the one the live path already uses. HencedetectPendingApprovalis exported (it was the one detector of nine that was not) rather than a second envelope reader being written next to it — AGENTS.md Commandment #0.1.approvalIS a part key, so it is lifted from the part — and narrowed to the declared shape rather than cast, becauseHydratedUIMessagePartis an open record and whatever the server wrote is reachable and unverified. Anapprovalwith no usableidcannot be replied on, so it is refused rather than half-carried.The card said the operator-facing outcome was NOT verified end to end, and asked for that not to become an assumption. Measured here, at the source:
toUIMessages' merge step rewrites a part'sstatetooutput-availablewhenever a tool result is merged onto it. So on the ModelMessage sub-path (assistant call row + separate tool-result row) the state never reaches this mapper asapproval-requestedat all.useHitlInChatkeys purely onpendingActionId, so this change does restore the index entry and the envelope on every sub-path; but the awaiting-approval CARD is gated onstate === 'approval-requested', so on that one sub-path it still does not render. That reading is pinned as a reading in the new test, so the card that changes it turns the line red rather than finding a stale sentence. ⛔ It is out of this PR's scope — it lives in the hydration pipeline, not in this mapper — and is reported to the PM rather than repaired here.⛔ What was deliberately NOT pulled in
Every narrowing in the ruling belongs to objectui#8426 and none of it is here: the authoring
stateunion shedding the three runtime-only approval states; theUseObjectChatOptions.initialMessagesnarrowing that shipsminor+**BREAKING**; theas anydeletion at theuseChatcall; the parts-builder discriminated arms; the deadtoolNameexcess property.The PM's scope split HELD. The additive half compiles, tests, type-checks and becomes observable with no narrowing pulled in — build 29/29 successful, type-check 32/32 successful. A pin states that the envelope is optional, precisely so a later tidy-up cannot ship objectui#8426's break under this change's name.
Bump call —
patch, and the reason is sequencing, not diff sizeThe lane's test (does existing stored data render differently) answers no: the member is optional, every value that parsed before still parses, and nothing changes for data carrying no envelope. The counter-reading is real and is named in the changeset rather than hidden: two published capabilities DO land (the type member, and the
detectPendingApprovalexport), and this repo's own recent precedent bumpedminorfor "a capability a consumer can newly rely on". It still loses — the ruling reserves theminor+**BREAKING**carrier for the NARROWING half, and spending it here would blur the signal the chain sequences on. ⭐ Flagged for the reviewer to overrule if that reading is wrong.Verification
turbo run build --filter=@object-ui/app-shell...)Tasks: 29 successful, 29 totalTasks: 32 successful, 32 totalzod-mirror-parity)Test Files 6 passed (6)·Tests 123 passed (123)check-changeset-presence.mjs✅ 8 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)check-control-bytes.mjs✅ check-control-bytes: OK (scanned 7408 tracked text file(s); skipped 85 binary)check-new-cross-file-line-citations.mjsVERDICT new-cross-file-line-citations: 0 new citation(s)check-governed-queue-guard.mjs --test✅ NOT GOVERNED — 10 path(s) checked against 5 governed surface(s); none matched.check-changeset-fixed/-no-major/-claims/-overwrite✅markdown-test-inputs.mjs --audit47 candidate test files, all adjudicated; 41 declared entries, all present222 passed (222)/3969 passed· app-shell685 passed (685)/6634 passed/1 skipped@object-ui/console(the one consumer that calls the changed mapper)Every exit code captured by redirect-then-capture, never through a pipe. All numbers above are from the final commit
58e734cce.@object-ui/consoletype-check exited 2 withTS2307/TS2882across five plugins. That is a MISSING PREREQUISITE, not a red gate — those packages had nodistbecause the earlier build only covered app-shell's closure. Re-run afterturbo run build --filter=@object-ui/console^...(Tasks: 34 successful), it reads 0 errors, exit 0. The first result is not recorded as a failed measurement.Ablation — four legs, each with an on-disk mutation proof and a green control
⛔ No permanent test artefact: every leg mutates, measures, restores, and proves the restore by comparing
git hash-objectagainst theHEADblob (not by reading an exit code), with atrap ... EXIT INT TERMon absolute paths.git diff HEADwas empty after every leg.approvalarm from the Zod mirror (marker grep 1 -> 0,0 14numstat)Tests 3 failed/35 passed; the three that fail are the retention, minimal-envelope and refusal pinstsczod-mirror-parity.test.ts: Type '"complex.zod.ts#ChatToolInvocationSchema"' is not assignable to type 'never'0 2numstat)Tests 5 failed/8 passed; the 8 survivors are the pre-existing pins, untouchedapprovalfromChatbotEnhanced.ChatToolInvocation(marker 1 -> 0,0 18numstat)tscTS2344: Type 'false' does not satisfy the constraint 'true'at the two-sidedEqualpintsc⭐ A vs A2 is the load-bearing pair, and it corrects an assumption worth writing down. Under leg A the derived
zod-mirror-parity.test.tspassed. Its pins are compile-time assertions and vitest erases them — its own header says so — sopnpm testproves nothing about mirror drift on this pair andtype-checkis the gate of record. Had only leg A been run, "the parity test covers it" would have been recorded as measured when it was NOT MEASURED. A2 is what makes the claim true.Cards filed, not folded in
Two findings in this area, each measured, neither in this card's scope and neither repaired here:
sanitizeChatMessagesForCache(the localStorage cache WRITE side) rebuilds tool parts without either key, so once this lands the server path and the cache-fallback path disagree. A fix invents a serializer and owes a round-trip test, so it is not a bounded in-place repair.toUIMessages' merge step rewritesstatetooutput-availablefor every merged result, which is the other half of this card's consequence 2. Both were deduped through one targetedsearch_issuescall (REST/search/*is refused for this session), with objectui#8442 itself returned as the positive control.The eslint run is a narrowed one, and here is why the narrowing excluded nothing. (1) The repository-wide run is
turbo run lint, per package. (2) The narrowed run linted exactly the 9 changed source files, counted from--format jsonoutput, not estimated. (3)eslint.config.jsenables no type-aware linting — noprojectService, noparserOptions.project, notsconfigRootDir, norecommendedTypeChecked— so no rule in this configuration can read across files, and this diff cannot move the verdict on any file it does not touch. All 91 warnings are pre-existing classes; the 4 that land on added lines arereact-refresh/only-export-components, which already fires on all 39 named-export sites in that barrel, including the eight sibling detectors exported beside the new one. Zero errors anywhere.Review notes
needs:contract-reviewis on both carriers. Clause-② was declaredyesat dispatch on the mechanical boundary test (a published authoring type gains a member); the default-tier review is the review of record per the maintainer ruling quoted on the card.packages/specis untouched.packages/components/src/ui/**is untouched.https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ.PATCHto a pull-request body downgrades a session-URL attribution footer to the bare form and appends a second footer unconditionally (this body was measured gaining exactly 58 bytes on its first edit), so the session reference only survives in prose. ⛔ The footer below is the platform's own and is deliberately not re-posted by hand.Generated by Claude Code