Skip to content

fix(rest)!: the /meta item and list reads serve a view stored with no label without an invented label - #24013

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-23967-view-label-not-invented
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-23967-view-label-not-invented

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23967

Clause-②: no (narrowing)

What changes

GET /api/v1/meta/view/NAME and GET /api/v1/meta/view served a view stored with no label as if it had one: label equal to its qualified name (OBJECT.KEY). This happened on the standalone server and on the runtime dispatcher. The spec declares a view's label optional with no default (packages/spec/src/ui/view.zod.ts, I18nLabelSchema.optional()). The value came from the read-side translator: translateView answers view.label ?? view.name when the catalog carries no string for the view. The view-container read already dropped that fallback (#22614); the item and list reads did not.

The rule is now one module-private helper in packages/rest/src/meta-item-read-gate.ts, withoutInventedViewLabel(input, translated). All three view sites call it:

  • translateMetaDocument's single-view arm: the item read. translateMetaEnvelope wraps it, and RestServer.translateMetaItem delegates to it.
  • translateMetaList's per-element map: the list read. RestServer.translateMetaItems delegates to it, and the runtime dispatcher's /meta domain imports it.
  • projectViewTranslation: container slots and the container's own strings. Its inline check is replaced by the helper, with the same semantics. The reverse run below shows the container answer is unchanged.

The rule: a label equal to the view's name, where the stored view carried none, is not served. An authored label is served as authored, including one equal to the name. A catalog translation is still served, for a view with a label and for one without. A catalog string equal to the qualified name cannot be told apart from the fallback, so it is dropped with it, as it already was on the container path. No export is added. packages/spec, rest-server.ts and runtime/src/domains/meta.ts are not touched.

The console half is a separate card that the seat files on objectstack-ai/objectui when this lands: viewTabLabel (view.label || view.name || view.id) needs its own human fallback. It is not addressed here, and the console tab keeps showing a machine name for such a view until then.

Evidence

Live boot, BASE adce19297 vs HEAD (standalone os dev)

From a scratch directory outside the repo: examples/app-todo compiled with os compile -o SCRATCH/objectstack.json, booted with os dev --artifact ./objectstack.json --fresh -p PORT and placeholder admin credentials. Recipe: POST /packages (201), PUT /meta/object/recheck_gate?package=com.qa.recheck with label, plural label and sharingModel (200), PUT /meta/view/recheck_gate.default with no label (200), PUT /meta/view/recheck_gate.named with label: "All gates" (200).

read (no Accept-Language, en, and zh-CN: the same in all three) BASE HEAD
GET /meta/view/recheck_gate.default 200, label: "recheck_gate.default" 200, no label key
GET /meta/view, element recheck_gate.default 200, label: "recheck_gate.default" 200, no label key
GET /meta/view, elements whose label equals name [recheck_gate.default] []
GET /meta/view/recheck_gate.named and its list element 200, "All gates" 200, "All gates"
GET /meta/view/recheck_gate.default/layers, overlay and effective no label no label

Cloud host shape, BASE vs HEAD

new HttpDispatcher(kernel), which is what createHonoApp builds, loaded from the built @objectstack/runtime dist. That dist imports the built @objectstack/rest. The protocol double serves three stored views: label-less default, authored named, and label-less tagged, which the zh-CN catalog translates.

case BASE HEAD
default locale resolves, no Accept-Language: default / tagged, item and list label = own name no label key
zh-CN: default, item and list label: "recheck_gate.default" no label key
zh-CN: tagged / named 已标记 / 全部闸门 已标记 / 全部闸门
no default locale, or no i18n service, no Accept-Language no label key (nothing is translated) no label key

The last row is the PM's mechanism assumption 2: with no resolvable locale, no other layer adds the label.

Pins: packages/rest/src/meta-view-label-not-invented.test.ts, 39 rows

  • §1 runs through the standalone routes GET /api/v1/meta/:type/:name and GET /api/v1/meta/:type, over a real SchemaRegistry and the real ObjectStackProtocolImplementation, with the views seeded as stored rows. It has one row per locale and view: label-less (no key), authored, catalog-translated label-less, and authored equal to name. It also covers the card's read with no Accept-Language and no catalog.
  • §2 runs through translateMetaEnvelope and translateMetaList, the functions the dispatcher's /meta domain imports, with the same rows, a bare array and the { type, items } envelope. It also covers no catalog, no mutation, and no locale (the same document is returned).
  • §3 is a CONTROL: the container read answers what it answered before.

Reverse verification and ablation (one-shot, nothing left in the tree)

The subject is reached by relative import (./meta-item-read-gate.js, and ./rest-server.js imports it relatively), so it resolves to src/; no rebuild sits between legs. Each leg below was proven on disk. The reverse leg's blob equals the BASE blob. Each ablation went through scripts/ablation-replace.mjs: anchor 1 to 0, blob changed. Each restore was proven: blob equals the HEAD blob and git diff HEAD is empty.

leg result
CONTROL, HEAD unmutated 39 / 39 green
REVERSE, the whole file at BASE 15 red, exactly the label-less rows on both reads. All authored and translated rows and both §3 container rows stay green, so the container answer is unchanged.
item site ablated (return translated) 9 red, the item-side label-less rows only
list site ablated (return out) 9 red, the list-side label-less rows only
container site ablated (helper bypassed in projectViewTranslation) the 2 §3 rows red

Suites

These ran post-merge at 6ff4da0d8. The one later commit adds only the ledger row below.

  • pnpm --filter @objectstack/rest typecheck: exit 0. tsc -p tsconfig.test.json --listFiles includes the new file, with 0 error TS.
  • pnpm --filter @objectstack/rest test (local): 288 files, 5941 passed, 326 skipped.
  • pnpm --filter @objectstack/rest test:repo (repo): 5 files, 250 passed, 11 skipped.
  • @objectstack/runtime, the 25 src/domains/meta-*.test.ts files of the local project: 513 passed. The repo project's meta-list-projection-parity.test.ts: 739 passed. Runtime aliases @objectstack/rest to its source.
  • pnpm exec eslint --no-inline-config over the two changed TS files: exit 0.

Gates, at the final HEAD 203a8192f

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 72 commands from merge base d132140eb.
  • All 72 were run on 203a8192f, each with its exit code captured before any pipe, and all 72 exit 0.
  • --ran reconciliation over that record reads: 72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero: all 72 recorded an exit code).
  • Included: check:adr-0087-registration (the changeset holds not-required (no-migration-prescription)), check:changeset-no-major, check:empty-changeset, check:engine-double-contract (1017 pinned), check:test-source-alias, check:cross-package-test-inputs, check:nul-bytes and check:dual-build-cjs-loads (110 require entry points across 68 packages load).
  • The repo-wide lint and every family CI owns are read from this PR's check-runs.

Surface note

The claim's file surface was the source file, one test file and one changeset. A fourth file is needed: scripts/engine-double-contract.pinned.json gains one row (packages/rest/src/meta-view-label-not-invented.test.ts, findOne, pinned 1). check:engine-double-contract requires it for the new pin's fake engine and prescribes --write. The regeneration printed 0 rows lost; no other row moves.

Acceptance notes

  • Other translatable types (PM assumption 5). The object, app, dashboard, dataset, page and picklist translators serve label only from the catalog or the document; none falls back to name. action does: resolveActionLabel answers action.name and translateAction always writes label. But ActionSchema declares label required (packages/spec/src/ui/action.zod.ts:959), so a schema-valid stored action always carries one. No reach was measured, so this is not filed.
  • The fallback's source (PM assumption 6). resolveViewLabel's view.label ?? view.name (packages/spec/src/system/i18n-resolver.ts:481) is unchanged.
    • In this repo, translateView is called by this module's three sites and by the public-form route in rest-server.ts. That route takes only the translated config and keeps its own label reading.
    • packages/cli/src/utils/i18n-extract.ts names both functions in docblocks only, with no import.
    • objectui main (63d00da) calls neither.
    • After this PR, no served door carries the fallback. The exported function's answer is domain:spec ground; it is noted and not filed.
  • Consumers (PM assumption 4).
    • The published response types are GetMetaItemResponseSchema.item: z.unknown() and GetMetaItemsResponseSchema.items: z.array(z.unknown()).
    • No packages/client type requires a served view's label, and no test asserts that a served view's label equals its name.
    • The CLI's serve-view-container-read integration test asserts authored labels.
    • The dogfood view-container-cross-package-default test compares the item and list reads, which now take the same rule.
  • The card's object recipe now needs sharingModel. Without it the PUT answers 422 security-owd-unset, an authoring rule that postdates the card's run.
  • Booting a compiled artifact from a directory with no node_modules logged runtime module load FAILED ... Cannot find package '@objectstack/spec', and the todo app's hook was refused at boot. This was seen only in this scratch setup, and it does not touch /meta. Whether an artifact is meant to boot without its dependencies is not established here; noted, not filed.

Generated by Claude Code

…o label

The pins for the read-side rule: a label-less stored view is served with
no `label`, an authored or translated label is served, and the container
read is unchanged. Changeset included; the implementation follows.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
…hout an invented label

`translateView` answers `view.label ?? view.name` when the catalog carries
no string for a view, so a view stored with no `label` was served one equal
to its qualified name on `GET /meta/view/NAME` and `GET /meta/view`. The
container read already dropped that fallback. The rule is now one
module-private helper, `withoutInventedViewLabel`, which the item read,
the list read and the container projection all call.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
A default `list` that restates a named list key for key collapses into
that item (the composer's documented rule), so the control fixture now
keeps the two apart.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
A label-less row and an authored or translated row now fail apart, so a
reverse run shows which direction each one takes.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
`check:engine-double-contract --write` adds the one `findOne` double the
new route-level pin declares; no other row moves.

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

Coarse fallback — 18 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 19955fb73fe58d7971a1f0f1fc9c473f2dfdf179 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 19955fb73fe58d7971a1f0f1fc9c473f2dfdf179

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 203a8192f6219b165134efabaaae05730c803ab9
Local-runs: none
Scope: packages/rest/src/meta-item-read-gate.ts, packages/rest/src/meta-view-label-not-invented.test.ts, scripts/engine-double-contract.pinned.json + packages/spec/src/system/i18n-resolver.ts, packages/spec/src/ui/view.zod.ts, packages/spec/src/ui/action.zod.ts, packages/spec/src/api/protocol.zod.ts, packages/rest/src/rest-server.ts, packages/rest/src/index.ts, packages/rest/package.json, packages/runtime/src/domains/meta.ts, packages/cli/test/serve-view-container-read.integration.test.ts, packages/qa/dogfood/test/view-container-cross-package-default.dogfood.test.ts, scripts/check-engine-double-contract.mjs, scripts/check-adr-0087-registration.mjs, scripts/check-changeset-no-major.mjs, content/docs/ui/forms.mdx, and at objectui f8267df9 (the .objectui-sha pin, read with git show from the sibling checkout) packages/app-shell/src/views/ObjectView.tsx and packages/app-shell/src/providers/MetadataProvider.tsx

Net diff read against merge base d132140eb: 4 files, +417 / -11. The .changeset/23967-view-label-not-invented.md file is the prose face and is judged in ②. Card #23967 read whole: body, triage grade 6219439245, claim 6220711918, report 6222353857. PR #24013 read whole: body, 4-file list, timeline (labels size/m, documentation, tests, tooling; one bot comment 6222326415, the docs-drift check), and the cross-lane declaration 6222378944 on #6017.

① Derived judgments

1. Which served documents change — RIGHT. Type view only, two reads, both hosts.

  • Item read GET /meta/view/NAME: translateMetaDocument (meta-item-read-gate.ts:2866-2897). translateMetaEnvelope (:3106-3122) wraps it. RestServer.translateMetaEnvelope delegates there (rest-server.ts:4075), RestServer.translateMetaItem calls translateMetaDocument directly (rest-server.ts:4041), and the dispatcher's answerMetaItem calls translateMetaEnvelope (runtime/src/domains/meta.ts:661-662).
  • List read GET /meta/view: translateMetaList (:2804-2835). RestServer.translateMetaItems delegates there (rest-server.ts:4318); the dispatcher's list chain hands it in as translateList (meta.ts:582).
  • rest-server.ts, runtime/src/domains/meta.ts and packages/spec are not in the diff, as the claim's hold-offs require.

2. When label is now absent, and when it is still served — RIGHT. The helper withoutInventedViewLabel (:3069-3077) drops the key only when all three hold: a locale resolved (:2818, :2876 return the stored document untouched when none does), the stored view carried no label (input.label == null), and the translator's answer equals input.name. Then the served copy has no label key when the stored row had none; a stored label: null (not a schema-valid shape: view.zod.ts:5154 is I18nLabelSchema.optional(), not nullable) would be served back as null. Still served: an authored label, including one equal to the name (input.label != null returns translated as is, :3073; pinned by the four SELF rows); a catalog translation that differs from the name (translated.label !== input.name returns translated as is; pinned by the TAGGED and NAMED zh-CN rows); with no catalog but a default locale, no key (pinned twice, §1 and §2); with no locale, the stored document itself (pinned, toBe(view)). One edge is narrowed on purpose: a catalog string equal to the view's qualified name is dropped with the fallback. The PR body says so; the container path has done the same since #22614. Acceptable.

3. The container path keeps its answer byte-for-byte — RIGHT. Base projectViewTranslation (d132140eb :3010-3036) skipped label inside its loop under input.label == null && value === input.name. Head runs the helper before the loop under the same predicate. When the helper deletes the key, the loop never sees it. When input carried label: null, the helper writes served.label = input.label and the loop's value === input[key] skips it. When nothing is dropped the helper returns translated itself. out identity is decided only by set, which is reached by the same keys as before, so the SAME-container rule holds. The extra copy the helper allocates when it drops is not on the wire. §3 (2 rows) and the reverse run in the report (15 red, all §3 green) agree.

4. No other path serves the invented label — RIGHT. Non-test callers of translateView at the head: meta-item-read-gate.ts:2951 (container, through the helper), translateMetadataDocument's view arm (reached from :2828 and :2892, both through the helper), and rest-server.ts:9311 (public form route). That route takes only view?.config (:9315) and serves its top-level label as match.view?.label ?? match.form?.label (:9362); content/docs/ui/forms.mdx:195 already documents null when neither is set. resolveViewLabel (i18n-resolver.ts:476-485, the ?? view.name at :481) has no caller outside translateView. /layers and ?layers= are not translated on either host (rest-server.ts:4155-4160 "Not translated and not cached"; meta.ts:796), so they never carried the fallback. packages/cli/src/utils/i18n-extract.ts:388-436 and packages/metadata-protocol/src/protocol.ts:9184 name the functions in comments only.

5. No legitimate consumer reads a served view's label as always present — RIGHT.

  • Spec: GetMetaItemResponseSchema.item is z.unknown() (protocol.zod.ts:553); GetMetaItemsResponseSchema.items is z.array(z.unknown()) (:325); viewItemBaseShape().label is optional (view.zod.ts:5154).
  • packages/client/src: no read of a view's label (grep, non-test).
  • CLI serve-view-container-read.integration.test.ts asserts authored labels (:284-298, :319) and not.toHaveProperty('label') on the container (:287).
  • Dogfood view-container-cross-package-default.dogfood.test.ts types label?: unknown (:50), asserts authored labels (:91, :126, :128, :221) and item-vs-list equality (:103-104, :212-216). One helper on both reads keeps that equality.
  • No test under packages/**, examples/** or packages/qa asserts a served view's label equals its name (grep).
  • Console at the pin f8267df9: viewTabLabel (ObjectView.tsx:950-958) types label?: string and draws view.label || view.name || view.id; applyViewItem (MetadataProvider.tsx:435) is view.label ?? body.label; saved views (ObjectView.tsx:1079) sv.label || sv.name || id. None requires the key. One reader improves: viewOriginLabel (:962-975) is view.label ?? '' for a served view, and the empty string makes the record page's back-link fall back to the object's plural label (objectui#11733), so that link stops naming the machine name.

6. @objectstack/rest publication — RIGHT, no new export. withoutInventedViewLabel is a module-private function (:3069); index.ts:155-170 re-exports translateMetaEnvelope and translateMetaList only, as before. package.json exports unchanged. The published .d.ts changes by one JSDoc paragraph on the exported translateMetaList (:2797-2800); no signature moves.

7. The pinned ledger row — RIGHT, gate-prescribed, not a loosening. One row added: packages/rest/src/meta-view-label-not-invented.test.ts, findOne, pinned 1. The test's one findOne double calls assertEngineFindOnePredicate (test :155-156), which is what "pinned" means; its find double is not a scanned verb (the ledger holds delete 268, findOne 354, update 305 rows at the head; none for find). The row sits where --write sorts it, by file then verb (check-engine-double-contract.mjs:2627), between meta-published-overlay.test.ts and public-form-routes.stored-row.test.ts. 927 rows before, 928 after; none removed or moved. record-recognisers --paths over the four paths reads no governed path off them and defers to the changeset's Clause-② line, and Governed Surface Queue Guard is green on this head. The cross-lane declaration 6222378944 on #6017 says the same.

8. The pin file — RIGHT. 39 rows as reported: §1 8 cells (2 locales x 4 views) x 2 reads + 2 = 18, over a real SchemaRegistry and ObjectStackProtocolImplementation through the registered routes; §2 16 + 3 = 19 through the two functions the dispatcher imports; §3 2 control rows. The list site guards item && typeof item === 'object' (:2832); the item site relies on :2871's early return for a non-object. Both sound.

② Semver level

  • One changeset, '@objectstack/rest': minor. The diff publishes exactly one package: the read gate is in @objectstack/rest; the test file and the ledger row publish nothing. Right package, one bump.
  • BREAKING shipped as minor: the launch-window convention (check-changeset-no-major.mjs:58-60) and AGENTS.md step 3 ((narrowing) is BREAKING). Check Changeset is green on this head.
  • Clause-②: no (narrowing) on the PR body and in the changeset. It matches the diff: nothing is newly accepted, no key is added, one served value is removed. record-recognisers --paths on the four paths returns no face by path and says this line decides, so the narrowing face and CONTRACT_REVIEW_TIER are the right tier.
  • adr-0087 not-required (no-migration-prescription): the gate refuses this arm only when the changeset body itself carries a migration prescription (check-adr-0087-registration.mjs:147-154). The body carries none: no FROM to TO, no retired key, no export removed. The marker's reasons are facts of the diff (no spec key or stored shape moves; no exported declaration removed or narrowed; the package publishes; no ADR-0087 id is named). Lint · CI, release & engine gates is green.
  • Sentence by sentence against the diff: "label optional, no default" (view.zod.ts:5154) matches; "translator answered the view's own name" (i18n-resolver.ts:481) matches; "on the standalone server and on the runtime dispatcher alike" (① 1) matches; "the console drew that machine name" (ObjectView.tsx:956) matches; "the view-container read already dropped this value" (base :3023) matches; "no label key, in every locale, with or without a translation catalog" (① 2) matches; "an authored label is served, including one equal to the name" matches; "a catalog translation is still served, for a view with a label and for one without" matches; "a read that resolves no locale is served the stored document as before" (:2818, :2876) matches; "the layered read never carried the invented value" (① 4) matches; "Who reads differently" matches ① 5.
  • Two non-blocking wording notes, no action owed: (a) "served that label, unchanged" reads as the no-catalog case; the next bullet already says a catalog translation is served for a view with a label. (b) The changeset does not state the edge the PR body states, that a catalog string equal to the qualified name is dropped with the fallback. Neither sentence misleads a consumer about when the key is absent.

③ Boundary flags

  • Surface note (fourth file, the ledger row): answered in ① 7. Accepted.
  • Acceptance note, other translatable types (PM assumption 5): i18n-resolver.ts has one ?? X.name label fallback, :481, the view's. resolveActionLabel (:631-640) falls back after the catalog lookup and translateAction writes its answer (:1258), but ActionSchema.label is required (action.zod.ts:959), so no schema-valid stored action reaches it. Nothing to file. Accepted.
  • Acceptance note, the fallback's source (PM assumption 6): resolveViewLabel is unchanged; after this PR no served door carries its fallback (① 4). Whether the exported resolver should stop inventing at the source is domain:spec ground. Escalated to the seat as optional: a domain:spec card, not owed by this contract.
  • Acceptance note, consumers (PM assumption 4): confirmed in ① 5, with the one improvement at viewOriginLabel.
  • sharingModel 422 on the card's object PUT: the card's recipe predates that authoring rule. Not this PR's; no action.
  • os dev --artifact from a bare directory logging a bare @objectstack/spec import: outside this PR, reach unestablished, does not touch /meta. Escalated to the seat to decide whether to file; not blocking.
  • Report deviations: the merge of origin/main (6ff4da0d8) brings no change of its own into the net diff; commit trailers follow AGENTS.md's model-free pair; the live boot used app-todo instead of showcase; label-write zero. All fine. The scratchpad disk finding is the PM's container hygiene, not a repo matter.
  • The objectui half (viewTabLabel's fallback): the seat's to file at landing. Until then the console is the SAME, not worse: view.label || view.name yields recheck_gate.default whether the server invents the label or not. The record page's back-link improves (① 5). Console Pin Gate was skipped on this head and the card says the pin does not build on main (hourly full run: red on main (CI) #23885 / Console pin does not build on main: objectui daeb77fc imports checkPageComponentViewData, deleted from spec by #23784 #23908), so the console was read statically at the pin, not run: NOT MEASURED at runtime.
  • Docs: the docs-drift check (6222326415) found no page naming a derived anchor. No page promises the invented label; content/docs/ui/forms.mdx:195 already documents the public form's null. No doc change owed.
  • Check-runs on 203a8192f6, read at 2026-10-16T18:50Z, 39 runs: 28 success, among them Build Core, Test Core (1/6), Check Changeset, Governed Surface Queue Guard, Dogfood Regression Gate, every Lint · and Type Check · family, Lint · ESLint, Check PR Size, and the four claim checks. 6 skipped: Console Pin Gate, Build Docs, Dogfood Verify CLI, Dogfood Regression Gate matrix shard, Packed-tarball smoke (opt-in), Temporal Conformance (live PG + MySQL). 5 in progress at read: Test Core (2/6), Test Core (3/6), Test Core (4/6), Test Core (5/6), Test Core (6/6). The seat re-reads every check before landing.

Implemented-by: claude/issue-23967-view-label-not-invented
Reviewed-by: session_018t6a5rvqPRvEDMCVVW9kYT

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · #23967 · domain:cli seat 2 · os-tesla · session session_018t6a5rvqPRvEDMCVVW9kYT · 2026-10-16T19:05Z · reviewed head 203a8192f6219b165134efabaaae05730c803ab9, against GitHub and the branch, not against the report's prose. Holder of claim 6220711918; report 6222353857 on #23967.

  1. Shape.
    • Draft, base main, assignee os-tesla. Line 1 is Fixes #23967.
    • Clause-②: no (narrowing) is in the body and in the changeset.
    • 4 files, +417 / −11.
    • check-governed-merges --pr 24013: NOT governed (0 of 4 paths); 428 changed lines.
    • git merge-tree is clean against origin/main fd0e72abc.
  2. The change, read by this seat.
    • One module-private helper, withoutInventedViewLabel, in packages/rest/src/meta-item-read-gate.ts. All three view sites call it: the item read, the list read, and the container projection (which drops its own inline rule).
    • No export is added. rest-server.ts, runtime/src/domains/meta.ts and packages/spec are untouched, as the claim held them off.
  3. The review is the ## Contract review record 6222526815, served at CONTRACT_REVIEW_TIER for the (narrowing) face, isolated and read-only. It is PASS on this head.
    • It finds the container path's answer unchanged.
    • An authored label (one equal to the name included) and a catalog translation are still served. A read that resolves no locale is still the stored document.
    • No consumer reads a served view's label as always present.
    • The console is the same, not worse, until its half lands.
  4. Changeset: one, '@objectstack/rest': minor, marked BREAKING under the launch-window convention, with the adr-0087 not-required (no-migration-prescription) marker. The review read it sentence by sentence against the diff.
  5. Gates, pinned to the head. Check-runs on 203a8192f6 read at this stamp, latest run per name: 40 names, 34 success, 6 skipped, 0 failure, nothing running. The successes include Build Core, Test Core 1–6/6, TypeScript Type Check and its lanes, every Lint · lane, Lint & Repo Gates, Check Changeset and Governed Surface Queue Guard. The 6 skips are roster names: Build Docs, Console Pin Gate, Dogfood Regression Gate (matrix), Dogfood Verify CLI, Packed-tarball smoke and Temporal Conformance.
    • The dev's local subset: 72 of 72 derived gate commands exit 0 (--ran: 0 NOT-MEASURED, 0 UNRUN).
    • Tests: @objectstack/rest 5941 passed and repo 250 passed; runtime meta-* 513 passed and the list-projection parity 739 passed. Typecheck exit 0.
    • The new pin's 39 rows: 15 go red at base, exactly the label-less rows, and each site's ablation reddens only its own rows.
  6. Cross-lane, declared before review: scripts/engine-double-contract.pinned.json, one row for the new pin's findOne double, written by the gate's own --write (6222378944 on [PM seat] domain:spec — 🟢 os-project-manager · session_01TyRhWbVbbQpQJTDbW8VahJ · p1/p2 only (maintainer) #6017).
  7. Accepted deviations:

Seat answers to the flags the review escalated:

  • resolveViewLabel's view.label ?? view.name at the source (packages/spec): after this PR no served door carries it. Whether the exported resolver should stop inventing is domain:spec ground, and no reader is hurt today. This seat files nothing for it.
  • os dev --artifact from a directory with no node_modules: reach unestablished, and nothing under /meta. Not filed.
  • The consumer half: objectui's viewTabLabel (ObjectView.tsx about :956, view.label || view.name || view.id) still shows the machine name for a label-less view. This seat files it on objectstack-ai/objectui when this lands, as the card's Done-when says.

Landing: pr_ready + automerge_enable through the relay, in this act. The card closes on the merge (Fixes).


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 16, 2026 19:06
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 16, 2026 19:06
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit aefab7c Oct 16, 2026
41 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23967-view-label-not-invented branch October 16, 2026 19:33
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 size/m tests tooling

Projects

None yet

2 participants