Skip to content

fix(metadata-protocol)!: an object saved back from a translated /meta read stores its source labels, and one the registry would refuse is refused at save and at publish - #29790

Merged
objectstack-fleet[bot] merged 13 commits into
mainfrom
claude/project-thread-knfz66
Nov 3, 2026
Merged

objectstack-fleet[bot] merged 13 commits into
mainfrom
claude/project-thread-knfz66

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread

Fixes #29673
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — skills/objectstack-data/SKILL.md (Ownership queues) already teaches that an object declaring its own queue field is refused, which stays true and is now said at save; no file under skills/** teaches saving a translated read back.

维护者速读

  • 改了什么:对象保存入口(PUT /api/v1/meta/object/NAME,草稿与直接保存)把"中文读取被翻译过的标签"还原成源文本再存,并在保存时就拒绝注册表本会拒绝的对象(422),发布前再查一次;os migrate meta --stored 可修复已存的坏行。
  • 为什么改:中文 Studio 读回对象时 queue 的标签是"队列",原样保存后被当成作者自定义字段,注册表拒绝注册,但发布仍报"成功",数据接口 404;owner_id 等列的中文标签还会被所有其他语言的用户看到。
  • 风险与回滚:收窄——作者自带 queue 字段等注册表本就拒绝的对象,现在保存即 422(以前保存成功但永远注册不上);作者恰好输入某语言对源标签的译文时会被还原为源文本。回滚即还原本 PR,无数据迁移。
  • 先看:packages/metadata-protocol/src/protocol-translated-echo.ts(还原规则)、packages/metadata-protocol/src/protocol.ts 中 saveMetaItemUnaudited 的两处还原调用(restoreInjectedColumnEchoes、restoreTranslatedObjectEchoes,后者以 getPackagedObjectBase 为打包基线)与规格校验之后的 objectRegistrationRefusal、packages/objectql/src/registry.ts 的 judgeRegistrationDoors(原有拒绝逻辑整体搬移)。
  • 两分钟验证:pnpm --filter @objectstack/objectql exec vitest run src/protocol-meta-effective-schema.test.ts,预期 20 个测试全绿,其中 [#29673] 一节 8 个。
  • 需要你做的:无,席位落地

What changes

The door fixed: saveMetaItem (packages/metadata-protocol), the server door every object PUT /api/v1/meta/object/NAME reaches, draft and live. Whatever the client sends, an injected column or a field no longer stores a label the read translated.

Where the translation came from (measured, below): the REST /meta read runs translateMetadataDocument, which serves each injected system column's label from the built-in table with no bundle needed (queue → 队列, owner_id → 负责人) and an author field's label from the catalog. Studio saves the whole body it read. The save door stored it verbatim; stripInjectedSystemColumns removes only a byte-identical platform definition, so the translated queue and owner_id stayed as the author's own fields. The registry refuses an author queue on a user-owned object, applyObjectRegistryMutation caught that refusal into a console.warn, and publish-drafts answered outcome: 'published'.

  1. Restore the source text (protocol-translated-echo.ts, new). saveMetaItemUnaudited calls it twice: a pure half (restoreInjectedColumnEchoes, the platform's injected-column definitions only, no read) just before stripServedObjectColumns, and the stored-row half (restoreTranslatedObjectEchoes) after every refusal, beside carryForwardRedactedCredentials, so a refused save costs no read. A slot is restored when the incoming value differs from its source and equals what translateObject makes of that source in some locale. Sources, asked in order: the stored row this save overwrites (the draft, else the active row a first draft overlays, read as the carry-forward reads it), else the packaged base (getPackagedObjectBase), then the platform's definition of an injected column (injectedSystemColumnDefs). Every read of the source is translated against that packaged base, passed as translateObject's packagedBase exactly as the REST read passes it, so a packaged object's overlay slot that diverged from the packaged text (served untranslated) is no translation's source. The packaged base is a registry lookup, not a MetadataService read, so a degraded metadata service fails no save here. Locales: the registered bundle's, plus the built-in table's (BUILTIN_SYSTEM_FIELD_LABEL_LOCALES, a new export of @objectstack/spec/system), since the read applies that table with no bundle. Slots: object label / pluralLabel / description; field label / description / inlineHelpText / inlineTitle; option label. Once restored, the injected columns are byte-identical again and the existing strip removes them. isInjectedColumnDefinition is unchanged.
  2. Refuse at save what the registry would refuse. SchemaRegistry.objectRegistrationRefusal (new, packages/objectql/src/registry.ts) answers the error registerObject would throw, registering nothing. registerObject's doors moved whole into a private judgeRegistrationDoors, which registerObject now calls first, so the question and the throw are one code path. It judges the body alone: the record-currency summary door, which reads the registry's other objects as they stand, is left to the registration (a bodyOnly stop in judgeRegistrationDoors), since a sibling drafted in the same batch can change its answer either way. The save door asks it in both modes, after the spec gate (so a body both refuse keeps the gate's structured issues[]): 422 INVALID_METADATA, nothing stored. The one refusal this makes new at the door is an author's own queue on a user-owned object; a field type the registry refuses was already refused by the object schema.
  3. Publish truth. promoteDraftForPublish asks the same question inside its D9.9 gate block, before repo.promoteDraft. A draft stored before this rule is refused there: pinned on publishMetaItem (refused by name, no active row, nothing registered); in publishPackageDrafts the promote gates' throws land in failed[], so the receipt does not report it published.
  4. Repair. migrateStoredMetadata (os migrate meta --stored [--apply]) runs the same restoration on each stored object row (comparing the row with the platform's definition) and reports each slot as translated-label-echo, e.g. fields.queue.label 队列 → Queue. --apply re-saves the row through the save door, which restores and strips. A restart does not heal such a row; the migration does, and so does the row's next save.
  5. Said on the save answer. Each author slot the stored-row half puts back is an info advisory on the save answer (rule: 'translated-label-echo', path: 'objects.NAME.fields.F.label', with the text stored and the text sent), beside the credential carry-forward's withheld-key-dropped notices. The platform's injected columns are not reported: the stored document never carries them.

Out of #29414's region. The promoteDraftForPublish hunk sits inside the D9.9 gate block only; no publishPackageDrafts commit-plan rows, prevVersion / existedBefore recording, revertCommit or resolveOverlayPackageBinding line is touched.

A ui/ change is also owed (the seat files the card).** Studio's object designer edits the locale-translated read: ui/packages/auth/src/createAuthenticatedFetch.ts attaches Accept-Language to every API call, so the designer's GET /api/v1/meta/object/NAME returns translated labels and it PUTs that body back. With this PR the stored document is right, but the designer still shows a translated label as editable source text, so an author who edits it in zh-CN writes a Chinese source label (kept, correctly, as the author's edit). The owed card: the designer loads and edits the untranslated document, and does not send injected system columns back.

Files (12):

  • packages/metadata-protocol/src/protocol-translated-echo.ts (new), protocol.ts: the restoration, the save and promote refusals, the migration notices.
  • packages/objectql/src/registry.ts: objectRegistrationRefusal, judgeRegistrationDoors (moved doors, no rule changed; bodyOnly stops before the record-currency door).
  • packages/spec/src/system/i18n-resolver.ts: BUILTIN_SYSTEM_FIELD_LABEL_LOCALES; api-surface/system.json and export-origins/system.json regenerated.
  • Tests: protocol-translated-echo.test.ts (new), protocol-meta-effective-schema.test.ts, ownership-queue.test.ts, record-currency-guards.test.ts, packages/qa/dogfood/test/studio-zh-cn-echo-publish.dogfood.test.ts (new).
  • .changeset/29673-translated-echo-save-door.md: patch for spec, objectql, metadata-protocol; ! with the ADR-0087 not-required disposition.

Tests

  • packages/objectql/src/protocol-meta-effective-schema.test.ts, [#29673] (real SchemaRegistry + real protocol):
    • zh-CN echo save: the stored fields are amount, memo; memo keeps its author label; the registry has the object; an en read is served Owner / Queue.
    • An author queue is refused at the save door in both modes (ObjectQueueColumnRefusedError, INVALID_METADATA, 422), and no row is written.
    • A stored draft carrying an author queue: publishMetaItem is refused by that name, no active row, nothing registered.
    • Migration: preview pending with the two translated-label-echo notices; apply rewritten; the stored fields are amount only and the object registers.
    • An author slot the catalog translated (amount served 金额) is stored as Amount, and the save answer carries one info translated-label-echo advisory at objects.crm_invoice.fields.amount.label.
    • The packaged base: a stored overlay slot Subject diverged from the packaged Title, with the author typing the catalog string 标题, is stored 标题 with no advisory; the control with no packaged base restores Subject.
    • A body the spec gate and the registry both refuse (an unknown field type) answers the spec gate's INVALID_METADATA with issues[].
    • A brand-new object saves while the metadata service reports itself degraded.
  • packages/metadata-protocol/src/protocol-translated-echo.test.ts (6): every slot in zh-CN; ja-JP with no bundle; author text kept; a stored echo repaired against the platform definition; the array fields shape, and the same reference back when nothing changed; a diverged overlay slot set to the catalog string is kept when the packaged base is passed, and the undiverged control is restored.
  • packages/objectql/src/ownership-queue.test.ts: objectRegistrationRefusal answers the throw registerObject makes, and registers nothing.
  • packages/objectql/src/record-currency-guards.test.ts: objectRegistrationRefusal answers null for a parent whose summary reads a record-currency child, while registerObject still refuses it.
  • Dogfood studio-zh-cn-echo-publish.dogfood.test.ts (4, over the booted showcase through the REST doors): the zh-CN read is translated and the en read is not; the zh-CN echo publishes published / failedCount: 0, stores neither column, serves data, and an en read gets Owner / Queue; the en control; an author queue is refused 422 and no draft is stored.

Acceptance notes

  • Clause-②: no (narrowing). The object save door refuses 422 INVALID_METADATA an object declaring its own queue field on a user-owned object (ADR-0142 D3), which it stored before and the registry then refused to register; the promote refuses such a draft. A label equal to what the /meta read serves for its source in some locale is stored as the source. Nothing is widened.
  • Metadata-protocol: none. No key is added, removed, renamed or re-shaped; every refused body was already one the registry refused.
  • The edge. An author who types exactly the translation of a label's source in some locale gets the source back. A reader in that locale is served the translation anyway, since the catalog translates it.
  • Residual: the post-commit catch stays for the record-currency door. The pre-flight judges the body alone, so the record-currency summary door, which reads the registry's other objects as they stand, stays the registration's own question in both directions: a draft whose sibling in the same batch takes the child's amount out of record-currency mode is not refused at save, and one whose sibling completes the pair passes save and promote and fails at registration, where applyObjectRegistryMutation still only logs it. Turning that catch into a failed[] row means changing the Phase 2 receipt, which is metadata-protocol: a pair commit's revert beside a pre-close base-bound row answers success while writing nothing, because the commit plan and the restore both read any package's active row #29414's region; left for a follow-up after it lands.
  • Residual: other translated slots. The restoration covers the slots translateObject writes for the object and its fields. Translated list-view labels and validation messages are not compared; none of them is a registration refusal.
  • The protocol.ts freeze. protocol.ts was frozen from 2026-11-03 03:39 UTC until the [maintainer] refactor(metadata-protocol): split src/protocol.ts (33,065 lines, +10,169 in 30 days) along its ObjectStackProtocolImplementation seams, one move-only slice per PR #25467 section-3 PR merged (339f63d). Main was merged into this branch after the lift with no conflicts, and the review fixes that touch protocol.ts landed after it (1fc44a4).
  • studio-authoring.first-run-loop, zh-CN leg (for the seat to route). Run the loop with the console in 中文 (Accept-Language: zh-CN): create the object in the designer, publish, and expect (a) the publish receipt published, (b) the new object's list serves records, not 404, (c) GET /api/v1/meta/object/NAME?layers=true has no queue / owner_id in the overlay, (d) the same read in en shows Owner / Queue.

Verification

Red first: measured on main fc55892, booted showcase, zh-CN vs en
step1 zh-CN queue 200 {"type":"text","label":"队列",...}
step1 en    queue 200 Queue
package create 201
qazh_q_en PUT draft 200
qazh_q_zh PUT draft 200
publish 200 {"outcome":"published","publishedCount":2,"failedCount":0,...}
qazh_q_en meta 200 data 200
qazh_q_zh meta 200 data 404 {"error":"Object 'qazh_q_zh' is not registered","code":"OBJECT_NOT_FOUND"}
server log: [Protocol] registerObject failed for qazh_q_zh: ... declares a field named `queue`
stored overlay: fields.queue.label 队列, fields.owner_id.label 负责人
Green: same script on this branch
publish 200 {"outcome":"published","publishedCount":2,"failedCount":0}
qazh_q_en meta 200 data 200
qazh_q_zh meta 200 data 200
qazh_q_zh stored overlay fields {"title":{"type":"text","label":"Title"}}
author queue PUT draft 422 ... declares a field named `queue` ...
qazh_q_zh en read owner_id/queue labels Owner Queue
Ablations (scripts/ablation-replace.mjs + ablation-dist-preflight, metadata-protocol dist rebuilt per leg)
  • Restoration ablated (the early return answers a marker): protocol-meta-effective-schema.test.ts 2 failed / 14 passed (the zh-CN echo and the migration repair).
  • Registration refusal ablated: 2 failed / 14 passed (the save-door refusal and the promote refusal). Its first run showed the promote pin was passing on the OWD gate's 422; the draft now carries a sharingModel and the pin asserts ObjectQueueColumnRefusedError by name, and the rerun was red.
  • Restore leg: rebuilt, marker absent, tree clean, 16/16.
Final head 1fc44a4 (after contract review 6595971055 and delta review): gates, lint, package tests, ablations, dogfood battery (exit codes captured to file)
  • dispatch-gates --commands: 85 derived; all 85 exit 0. --ran reconciliation: 85 run, 0 NOT-MEASURED. (check-plugin-teardown-shape --self-test needed git fetch origin 621a4876 in the shallow clone; check:dual-build-cjs-loads needed a whole-workspace turbo run build first.)
  • pnpm lint (eslint over the repo, then turbo run typecheck): exit 0.
  • Whole-workspace turbo run build: the first run failed only in @object-ui/app-shell on pinyin-pro, a dependency main added, missing from node_modules after the merge; after pnpm install --frozen-lockfile the app-shell build ran 65/65 tasks successful.
  • pnpm --filter @objectstack/metadata-protocol test: 311 files passed, 4 skipped; 22311 tests passed, 26 skipped.
  • pnpm --filter @objectstack/objectql test: 544 files, 10770 tests passed.
  • pnpm --filter @objectstack/spec exec vitest run src/system: 69 files, 2213 tests passed.
  • pnpm --filter @objectstack/dogfood test (the full battery, narrowing arm): 406 files passed, 3 skipped; 3356 tests passed, 28 skipped.
  • Ablations (scripts/ablation-replace.mjs + ablation-dist-preflight, metadata-protocol dist rebuilt per leg, marker present in the built files), each red as predicted and restored to blob == HEAD:
    • the packaged base not passed: the packaged-base door test, 1 of 20;
    • the advisories not added: the advisory test, 1 of 20;
    • the post-gate registry ask removed: the save-door queue refusal, 1 of 20;
    • the code-layer MetadataService read restored: the degraded-service test, 1 of 20;
    • in-module, the packaged base not passed to translateObject: 1 of 6;
    • the bodyOnly stop removed: the record-currency pre-flight test, 1 of 14.
    • Restore leg: marker absent from all 24 built files, 20/20, tree clean. The first attempts of three legs were no-ops (a comment marker the build strips, and a leg that did not type-check) and were rerun with markers that survive the build.
  • The first full pass at f1fa81f had 86 metadata-protocol and 5 objectql test failures: the stored-row read ran before the refusals, against test doubles without getItem / engine.find. 866fe6c moved that read after the refusals; 1fc44a4 replaced its code-layer fallback with the packaged base, and the interim getItem guard in readCodeLayerForCarryForward was reverted.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JQwxbZUVFvbHQ7Uq4qpCdd

… registration refusal before promotion

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbZUVFvbHQ7Uq4qpCdd
… refusals, and the stored-row repair

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbZUVFvbHQ7Uq4qpCdd
…storation runs after the refusals

The injected-column half is pure and stays before the served-column strip;
the half that reads the stored row now runs beside the credential
carry-forward, so a refused save costs no read. A registry with no
getItem has no code layer to read.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbZUVFvbHQ7Uq4qpCdd
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation protocol:system tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/spec, touching 33 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/spec/api-surface/system.json, packages/spec/export-origins/system.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

23 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 0e4b0fa411de344a0c2f38500255c68fc1cdfdf2.

⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/api-surface/system.json, packages/spec/export-origins/system.json) — pages documenting those are invisible to this run
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 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; 103 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 — 171 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 0e4b0fa411de344a0c2f38500255c68fc1cdfdf2 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 0e4b0fa411de344a0c2f38500255c68fc1cdfdf2

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-warren · session_01JQwxbXKRzgnVvjzxaYxLUf

Contract-review-claim: session_01JQwxbXKRzgnVvjzxaYxLUf · head 866fe6c · 2026-11-03T04:55Z

Face: narrowing (Clause-②: no (narrowing)) and schema (packages/spec/src/system/i18n-resolver.ts), at CONTRACT_REVIEW_TIER by the seat's at-tier reviewer. The PR stays draft: it touches packages/metadata-protocol/src/protocol.ts, under the #25467 section-3 freeze (6594835694); it is rebased after the lift and gets a delta review then.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-warren · session_01JQwxbXKRzgnVvjzxaYxLUf

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 866fe6cdc6ef32c73467a7b355de41e380f91055
Local-runs: none
Scope: packages/metadata-protocol/src/protocol.ts, packages/metadata-protocol/src/protocol-translated-echo.ts, packages/metadata-protocol/src/protocol-translated-echo.test.ts, packages/objectql/src/registry.ts, packages/objectql/src/ownership-queue.test.ts, packages/objectql/src/protocol-meta-effective-schema.test.ts, packages/qa/dogfood/test/studio-zh-cn-echo-publish.dogfood.test.ts, packages/spec/src/system/i18n-resolver.ts, packages/spec/api-surface/system.json, packages/spec/export-origins/system.json + packages/spec/src/data/injected-system-column-provenance.ts, packages/metadata-protocol/src/protocol-credential-carry-forward.ts, packages/metadata-protocol/src/metadata-redaction.ts, packages/metadata-protocol/src/protocol-meta-type-schema.ts, packages/metadata-core/src/object-field-type.ts, packages/objectql/src/record-currency-aggregate-door.ts, packages/rest/src/meta-item-read-gate.ts

Inputs read: card #29673 body and all 4 comments (triage, claim, claim amendment, os-dev-report); PR #29790 body; the net diff origin/main...866fe6cd (11 files, +987/-47); the cited code in the live tree at the head. Check-runs on the head at review time: 41 runs, none failed; 15 completed success (Auto Label, Dogfood Regression Gate, Plan Test Core shards, Lint · repo & tooling gates, Lint · docs & code guards, Lint · scoped · tooling self-tests, Lint · CI/release & engine gates, Lint · PM dispatch-gates, Lint · scoped · query-options erasure, Check PR Size, Flag docs affected, Check Changeset, PR-body guards, Governed Surface Queue Guard, filter, Spec property liveness); 8 skipped; 17 still in_progress (Test Core 1–8, Build Core, Build UI Deps, Live E2E UI, Type Check · workspace / source gates / consumer gates / debt ledger, Lint · ESLint, Lint · scoped · slot lookup & corpus). Not awaited; this record judges the tree.

① Derived judgments

  1. Save door refuses what the registry refuses — asked of one code path. SchemaRegistry.registerObject's four pre-materialization doors moved whole into private judgeRegistrationDoors; registerObject calls it as its first statement, objectRegistrationRefusal returns its answer without registering. The protocol asks it with { ...body, _provenance: 'org' }, layer.packageId, layer.kind — byte-identical to the write-through (applyObjectRegistryMutation, protocol.ts ~10576) and the boot hydration (~22345). Nothing rewrites request.item between the ask (~14063) and the put that the doors read: the later rewrites are protection block, operator/section grafts, parsed actions, label restoration, credential carry-forward (no-op for object, which has no redactor). Right, with two caveats judged in ④ (ordering before the Zod gate; the state-dependent door).
  2. The extend door is not reached by this PR. Both call sites are under singularType === 'object' with layer.kind of own | overlay; object_extension saves and publishes are not asked. The changeset and PR body list "an extend contribution it refuses" among what is now refused at save. Wrong as written — overclaims the narrowing (should-fix in ④).
  3. The field-type door adds no narrowing at the save door. resolveOverlaySchema('object') is the kernel ObjectSchema, whose field type is the closed FieldType enum and required; findUndeclarableFieldType admits exactly FieldType.options. The Zod gate at ~14600 already refused every such body with 422 INVALID_METADATA and structured issues[]. The registry door now answers the same body first, same code and status, no issues. Accept-set unchanged; envelope changed. Wrong as an accept-set claim, and a should-fix on the envelope (④).
  4. Promote door. promoteDraftForPublish asks the same question on draftForGate.body inside the D9.9 block, before repo.promoteDraft; publishPackageDrafts promotes through it and its per-item catch tags and rethrows into the batch catch that builds failed[] / batch_aborted (ADR-0067 D2). A pre-rule draft carrying an author queue is refused by name (pinned). Right.
  5. Existing stored rows still load. loadMetaFromDb is untouched: no refusal added at hydration; an owner_id-echo row registers as before (author field) until re-saved or migrated; a queue-echo row never registered on main and still does not until repaired. Boot registers the convertStoredItem-converted body while save/promote judge the raw body; no ADR-0087 conversion today rewrites an object field type or a summary roll-up (checked the conversion surfaces under packages/spec/src/conversions and migrations/entries), so no save/boot disagreement exists at this head. Right, noted as a latent coupling.
  6. Translated-echo restoration (pure half). Runs for every object save before stripServedObjectColumns, comparing only against injectedSystemColumnDefs(item) (gated by the same resolveInjectedSystemColumns plan the read uses). A translated queue/owner_id becomes the platform definition and the existing byte-identical strip removes it; isInjectedColumnDefinition unchanged. Right.
  7. Translated-echo restoration (stored-row half). Source = storedBodyForCarryForward (draft row → active row → code layer), after every refusal, before the put. The comparison is per slot, name-keyed, and requires value !== source and value === read(source) for some locale; a slot the source does not declare is never compared; the same-reference contract holds when nothing changes. Locale set = bundle keys ∪ BUILTIN_SYSTEM_FIELD_LABEL_LOCALES: the read resolves any Accept-Language to one of those rows (exact → case → base → variant, localeChain only reorders within the same keys) or translates nothing, so every string the read could have served is tried. Cost: at most 2 sources × (|bundle| + 4) translateObject calls over a document pruned to the incoming field names — bounded by field count. Right as to coverage and cost; wrong in one reachable case (must-fix, ④ item 1): the module translates the stored source without packagedBase, the read translates with it.
  8. New export BUILTIN_SYSTEM_FIELD_LABEL_LOCALES (@objectstack/spec/system, via export * from './i18n-resolver'): readonly string[], frozen, derived from SYSTEM_FIELD_LABELS by construction, placed beside the table whose read rule it describes; api-surface/system.json and export-origins/system.json carry the matching one-line additions; Spec property liveness green; one consumer in-tree (the echo module). Right.
  9. SchemaRegistry.objectRegistrationRefusal (@objectstack/objectql): additive public method, parity-pinned against the throw in ownership-queue.test.ts. Right.
  10. Migration (os migrate meta --stored): translated-label-echo notices per slot, comparing the row with itself and the platform's injected definitions; --apply re-saves through the door. Author-field translations stored before this rule are not repaired by it (the row is its own source) — the PR says so. surface: 'object.field.label' is stamped on object-level and option slots too — cosmetic. Right.

② Semver level

Changeset .changeset/29673-translated-echo-save-door.md: @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/spec all patch; title fix(metadata-protocol)!: …; Clause-②: no (narrowing); Metadata-protocol: none; one ADR-0087 marker not-required (no-migration-prescription); Skills-docs line present. PR body carries the same two declaration lines. Check Changeset: success.

! at patch is right under this repo's rule. AGENTS.md Post-Task Checklist §3: the level follows the Metadata-protocol: kind alone; none takes patch; "a non-protocol change, widening or breaking, ships patch and keeps its BREAKING banner and ADR-0087 disposition". A new export is not a metadata-protocol kind. none holds: no key, value or default an author can write changed meaning; the one refusal this PR makes real at the door (an author's own queue on a user-owned object, ADR-0142 D3; a record-currency summary pair) is the registry's on main already, so nothing an author could successfully publish is refused now. The alternative reading (narrowed ⇒ minor) would describe a value the protocol accepted, which it never did — the save stored it and the registry refused it.

Clause-②: no (narrowing) — right in kind; the sentence overstates what the tree narrows (①.2 and ①.3): the extend door is never asked, and the field-type door was already closed by the spec gate. Should-fix to make the declaration match the tree (④ item 3).

SCHEMA face (spec addition): judged in ①.8 — right, additive, generated surfaces regenerated and matching.

③ Boundary flags

  • open_questions: [] — nothing to answer.
  • Dev flag, freeze: protocol.ts frozen for [maintainer] refactor(metadata-protocol): split src/protocol.ts (33,065 lines, +10,169 in 30 days) along its ObjectStackProtocolImplementation seams, one move-only slice per PR #25467 section 3; every hunk pushed before the freeze; PR stays draft, rebases after the lift. The brief names the delta review. Answered — this record is of the head as it stands.
  • Dev flag, residual post-commit catch: applyObjectRegistryMutation's console.warn stays for failures the pre-flight cannot see (state-dependent record-currency door). Answered, and escalated in ④ item 5: the same door disagrees in the other direction too, which the PR does not name.
  • out_of_scope_findings: ui card owed (Studio edits the translated read and PUTs it back whole) — the seat files it; agreed it is not this PR's lane.
  • Card "Wanted" item 3: a repair for stored rows — delivered (migration) and pinned; a zh-CN leg on studio-authoring.first-run-loop — stated for the seat to route; the dogfood case drives the real REST doors on the booted showcase.
  • The Skills-docs: none line names skills/objectstack-data/SKILL.md (Ownership queues) as already teaching the queue refusal; not re-read here (prose face), accepted on its face since the rule it teaches is unchanged.

④ Design judgment

The narrowing against the published spec on main: ADR-0142 D3 refuses an author's own queue on a user-owned object at registration, and ADR-0029 D9.9's producer-side refusal is the stated pattern for "the receipt and the registry agree because the write never happens". Moving that refusal to the door that stores the body makes the spec's own refusal real; it removes no declared capability — nothing on main declares that an author may store a translated read as the document (the i18n-resolver header says display-name resolution is a read-exit concern). The restoration half is the write-side inverse the read exits owe (same door as stripReadDecorations / stripServedObjectColumns). The shape is right. Findings against the tree:

  1. must-fix — the stored-row restoration translates its source without packagedBase; the read it inverts translates with it. protocol-translated-echo.ts translatedSource calls translateObject(doc, bundle, { locale }); the REST read calls it with packagedBase: packagedObjectBaseOf(...) (packages/rest/src/meta-item-read-gate.ts:3040), under which a slot that diverged from the packaged text is served untranslated in every locale (valueOverridesPackagedBase, ADR-0029 D9.2a). The catalog is name-keyed, not value-keyed, so for a packaged object whose stored overlay slot already diverged (say title.label renamed from Title to Subject, bundle zh-CN objects.X.fields.title.label = 标题), an author who now types 标题 has the save store Subject again, with 200 and no notice: 标题 is not a translation of Subject and the read could never have served it for that slot, so this is outside the declared rule ("equal to the translation of its source") and outside the declared edge — it is an author's edit discarded silently on a non-echo path, which is the class the card's rule names (loud over silent). Reachable from Studio by any tenant customizing a packaged object with a catalog. Fix: hand the module the packaged base (the code layer storedBodyForCarryForward already falls back to) and pass it as packagedBase to every translateObject call in translatedSource, so the module's reads are the read's; or skip a stored slot whose value already differs from the packaged base. The pure half (platform definitions) is unaffected. Pin: a diverged overlay slot set to the catalog string is kept.
  2. should-fix — the restoration is silent on the save answer. restoreTranslatedEchoes returns restored[]; the door discards it. The sibling on the same door (droppedWithheldKeyNotices, [Decision] connector credential follow-ups after #28596: header-value advisory, legacy auth keys at rest, whole-map withholding #28640) puts every dropped position on advisories. Put each restored slot (path, from, to) on advisories so the author who hit the declared edge is told; the migration already says it per slot.
  3. should-fix — the changeset's Clause-② sentence names refusals the tree does not add. "an extend contribution it refuses" — no call site asks with kind extend (①.2); "a field type the registry refuses" — already refused by the Zod gate on main (①.3). Either tighten the sentence to the two doors this PR makes real (an author's own queue; a record-currency summary pair) or wire the object_extension save/publish doors to ask with 'extend', which widens the PR.
  4. should-fix — the registry ask precedes the spec gate, changing the envelope for an already-refused body. At ~14063 the field-type door answers before the Zod gate at ~14600; a body with an absent or unknown field type now gets ObjectFieldTypeRefusedError (no issues[]) instead of the spec gate's INVALID_METADATA with issues[].path that Studio highlights (元数据保存的 422 也丢掉 union 分支处方:一个 view 保存失败只回一条 path:"" message:"Invalid input",Studio 无字段可高亮 #5364, A publish failure's error string inlines the same validation prose that issues[] already carries, so every console renders each finding twice #10524). Same status and code, so no accept-set change; but the structured path is lost. Ask the registry after the spec gate (it costs no read), or keep the ask where it is and let the field-type door's answer fall through to the gate.
  5. should-fix — the state-dependent door disagrees with registration in both directions; the PR names one. recordCurrencySummaryRefusal reads the registry as it stands. Named: pre-flight accepts, registration refuses (a sibling in the same batch completes the pair) — stays in the console.warn. Not named: a draft save is refused 422 against the live registry although the batch it belongs to would register it (the child's amount leaves record-currency mode in a sibling draft promoted first). Rare, but it is a refusal at save of a draft a publish can serve, which metadata-protocol: over legacy object rows [package-less, Q], a draft save naming no package answers 200 into Q's draft, which no publish can serve: the save door's D9.9 check judges the request's package before the draft address resolves to Q #23712's premise does not cover. Either state it beside the named residual, or ask that one door in publish mode / at promote only (where pending / batchObjects could be consulted) and leave the two pure doors on the draft save.
  6. should-fix — a new, undeclared fail-closed read on every object save. restoreTranslatedObjectEchoes reads the stored row on every object save (before this PR the door read it only for types with a redactor — object has none). With no stored row it falls to readCodeLayerForCarryForward, which asks the metadata service first and throws throwMetadataServiceUnavailable when the service is degraded and the registry has no entry — the case of a brand-new object, which has no source to compare beyond the platform definitions the pure half already handled. That is a new 503 on a first object save under a degraded control-plane service, declared nowhere. Either treat "degraded and not in the registry" as "no source" for this restoration (the credential carry-forward's fail-closed reason does not transfer: there is no credential to lose), or name it in the changeset.

Observations, no fix owed: boot judges the converted body and save the raw one (①.5) — no conversion touches the doors today, but a future field-type conversion would open a save/boot disagreement; the migration stamps object.field.label as the surface of object-level and option slots; objectRegistrationRefusal fails open on a registry without the method (partial doubles only).

Implemented-by: claude/project-thread-knfz66
Reviewed-by: session_01JQwxbXKRzgnVvjzxaYxLUf

VERDICT: FAIL


Generated by Claude Code

…s source with the read's packaged base

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbZUVFvbHQ7Uq4qpCdd
…ving the record-currency door to the registration

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbZUVFvbHQ7Uq4qpCdd
…kaged base, answers on advisories, and the registry is asked after the spec gate

The stored-row half translates its source against getPackagedObjectBase,
the packaged base the REST read hands translateObject, and with no
stored row takes that base as its source instead of the carry-forward's
MetadataService code-layer read, so a degraded service fails no save.
Each restored author slot is an info advisory on the save answer. The
registry's refusal is asked after the spec gate, so a body both refuse
keeps the gate's issues.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbZUVFvbHQ7Uq4qpCdd
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-warren · session_01JQwxbXKRzgnVvjzxaYxLUf

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 1fc44a469f46d82643a528c0cd61cfca75af8737
Local-runs: none
Scope: packages/metadata-protocol/src/protocol.ts, packages/metadata-protocol/src/protocol-translated-echo.ts, packages/metadata-protocol/src/protocol-translated-echo.test.ts, packages/objectql/src/registry.ts, packages/objectql/src/ownership-queue.test.ts, packages/objectql/src/protocol-meta-effective-schema.test.ts, packages/objectql/src/record-currency-guards.test.ts, packages/qa/dogfood/test/studio-zh-cn-echo-publish.dogfood.test.ts, packages/spec/src/system/i18n-resolver.ts, packages/spec/api-surface/system.json, packages/spec/export-origins/system.json + packages/rest/src/meta-item-read-gate.ts, packages/metadata-protocol/src/protocol-credential-carry-forward.ts, packages/metadata-protocol/src/protocol-meta-type-schema.ts, packages/objectql/src/record-currency-aggregate-door.ts

Delta record on the FAIL record 6595971055 rendered on 866fe6cdc6ef32c73467a7b355de41e380f91055. Read: the branch's own commits since that head (2122d31b, 2c356102, 633de463, ee847d3e before the merge; 1fc44a46 after it), the merge commit ab2b4fb7 of origin/main 339f63da (its diff against main touches exactly the PR's twelve files — a clean merge carrying no foreign content, no protocol.ts conflict), the full net diff origin/main...1fc44a46 for the hits files, card comment 6598058279 (the dev's delta report), and the check-runs on 1fc44a46: 64 runs, 55 success (Test Core 1–8 and the aggregate, Test UI 1–16 and the aggregate, Build Core, Build UI Deps, Build & E2E UI, Live E2E UI, Type Check · workspace / source gates / consumer gates / debt ledger, Type Check UI, TypeScript Type Check, every Lint gate, Check Changeset, Governed Surface Queue Guard, PR-body guards, Spec property liveness, Dogfood Regression Gate, Check PR Size, Flag docs, Auto Label, Plan Test Core shards, filter), 9 skipped, none failed, none in progress. Judged on the tree.

① Derived judgments

Each finding of 6595971055, against the tree at this head:

  1. must-fix (packagedBase) — fixed. restoreTranslatedEchoes takes packagedBase and translatedSource passes it to every translateObject call, so the module's read of the stored source is the REST read's (valueOverridesPackagedBase withholds the catalog from a diverged slot). The door hands it this.getPackagedObjectBase(request.name) — the same accessor packagedObjectBaseOf in packages/rest/src/meta-item-read-gate.ts reads for translateObject's packagedBase; a registry lookup under try/catch, undefined for a tenant-authored or unknown object. With no stored row the source is that packaged base itself (translated against itself: nothing diverged, so every slot is a translation's source — the body a read of an unoverlaid packaged object served). Pinned at the module (diverged Total kept, undiverged control restored) and at the door (Subject + typed 标题 kept with no advisory; the no-base control restores Subject). Right.
  2. should-fix (silent restoration) — fixed. restoreTranslatedObjectEchoes returns { item, restored }; translatedEchoNotices builds one info RuntimeAuthoringIssue per restored author slot (rule: 'translated-label-echo', path of the form objects.NAME.fields.F.label, message and hint) and the door appends them to saveNotices beside droppedWithheldKeyNotices. The pure half's injected columns are not reported, which is right: the strip removes them and the stored document never carried them. Pinned (advisories carries exactly the one slot). Right.
  3. should-fix (changeset overclaim) — fixed. The Clause-② sentence now names the one refusal the door makes new (an author's own queue on a user-owned object, ADR-0142 D3) and the read-serves-for-its-source rule; the body says the field type was already refused by the object schema, drops the extend claim, and states the record-currency door is left to the registration. Matches the tree (②). Right.
  4. should-fix (ask before the spec gate) — fixed. The D9.9 block records objectRegistrationLayer; the registry is asked inside the spec-gate block after the parse-failure throw (protocol.ts ~14393), so a body both refuse keeps the gate's issues[]. Pinned with type: 'no_such_type' (name is not ObjectFieldTypeRefusedError, issues non-empty, message failed spec validation). Right. One observation, no fix owed: the ask sits inside if (schema), so on a host where resolveOverlaySchema('object') answers null it is skipped along with the gate; object's schema is the kernel's built-in ObjectSchema, so no production path reaches that arm.
  5. should-fix (state-dependent door) — fixed, by the second option offered. objectRegistrationRefusal calls judgeRegistrationDoors(..., { bodyOnly: true }), which returns before the record-currency summary door; registerObject still runs it. Neither save nor promote now refuses on state a sibling draft can change, in either direction; the registration's refusal stays in the write-through's console.warn, and the changeset says so. Pinned in record-currency-guards.test.ts (pre-flight null, registration refuses by name, nothing registered). Right, and the residual is declared.
  6. should-fix (degraded-service read) — fixed. The restoration calls storedBodyForCarryForward with a host whose readCodeLayerForCarryForward answers the packaged base, so readItemFromMetadataService is never reached on this path; the earlier getItem guard in readCodeLayerForCarryForward is reverted (the net diff against main no longer touches that method). Pinned: a brand-new object draft saves with a metadata service whose getDiagnosed reports degraded. Right.

Unchanged from 6595971055 and still right: one code path for pre-flight and registration (judgeRegistrationDoors first statement of registerObject), the same _provenance/package/kind as the write-through and boot; body judged equals body stored for door purposes (the rewrites after the ask touch protection, operator/section grafts, parsed actions, labels, credentials only); the promote asks draftForGate.body before repo.promoteDraft and a batch throw lands in failed[]; boot hydration unchanged, existing rows load; the migration compares a row with itself and the injected definitions (no packagedBase needed, and none passed); locale set and cost as before; the spec export BUILTIN_SYSTEM_FIELD_LABEL_LOCALES and its two generated-surface lines are byte-identical to the reviewed head (net spec diff unchanged).

② Semver level

Changeset still patch × 3 with the ! title, Clause-②: no (narrowing), Metadata-protocol: none, one ADR-0087 not-required marker; Check Changeset success on this head. The level judgment of 6595971055 stands: AGENTS.md §3 makes the level follow the protocol kind alone, none ⇒ patch, a non-protocol breaking change keeps its banner at patch; none holds because the one refusal made real is the registry's on main. The Clause-② sentence now matches the tree (①.3). Right.

③ Boundary flags

  • open_questions: [], out_of_scope_findings: [] in the delta report — nothing to answer.
  • Freeze: lifted; origin/main 339f63da merged clean as a merge commit; this record covers the merged tree at 1fc44a46.
  • Residual: the record-currency summary door at registration stays a console.warn in both directions — declared in the changeset body and the PR amendments; the ui card remains the seat's.
  • Body amendments the report lists (items 1–2 of What changes, the advisory, the residual, the verification readings): the PR body is the dev's account; the tree is what this record judges, and the tree matches the amendments as described.

④ Design judgment

The narrowing is as judged on 866fe6c: it makes ADR-0142 D3's and ADR-0029 D9.9's refusals real at the producer and removes no declared capability. The one defect that failed the previous head — a name-keyed restoration undoing an author's edit on a slot the read never translates — is closed at its cause (the module now reads with the read's own options), and the restoration is loud on the save answer. No new finding. The design holds.

Implemented-by: claude/project-thread-knfz66
Reviewed-by: session_01JQwxbXKRzgnVvjzxaYxLUf

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-warren · session_01JQwxbXKRzgnVvjzxaYxLUf

维护者速读

  • 改了什么:对象保存入口(PUT /api/v1/meta/object/NAME,草稿与直接保存)把中文读取时被翻译的系统列与作者标签还原成源文本再存,还原的作者标签在保存回答里以提示(translated-label-echo)列出;注册表本会拒绝的对象在保存时就 422,而不是保存成功、发布“成功”、却永远注册不上。
  • 为什么改:Studio in zh-CN: a new object publishes "successfully" and never reaches the data plane, because its echoed queue column carries a translated label #29673:中文 Studio 读回对象再保存,queue 等列被当成作者自定义字段,注册表拒绝注册,数据接口 404。
  • 风险与回滚:收窄——注册表本就拒绝的对象现在保存即 422;作者恰好把标签写成某语言对源标签的译文时会被还原,但会在回答里看到提示。回滚即 revert。
  • 先看:packages/metadata-protocol/src/protocol-translated-echo.ts,以及 protocol.ts 里 saveMetaItemUnaudited 调用它的两处。
  • 两分钟验证:pnpm --filter @objectstack/objectql exec vitest run src/protocol-meta-effective-schema.test.ts。
  • 需要你做的:无,席位落地。
  • 席位意见:达档契约复核首轮 FAIL(一条必改五条建议),六条全部修完,增量复核 PASS;可落地。

席位核对

ACCEPT · domain:server#2 · head 1fc44a469f46d82643a528c0cd61cfca75af8737 · 2026-11-03T07:43Z · contract review at CONTRACT_REVIEW_TIER (narrowing face, i18n-resolver.ts schema face): FAIL 6595971055 on 866fe6cd, delta PASS 6598122054 on this head, by an independent at-tier subagent adopted by this seat; not governed (check-governed-merges --pr 29790 0 of 12 paths)

  1. Shape: draft, base main, Fixes #29673, Clause-②: no (narrowing), Metadata-protocol: none, Skills-docs: none — skills/objectstack-data/SKILL.md (Ownership queues) already teaches the refusal; patch changeset. 12 files, +1186/−45, 1231 changed lines. Main 339f63da (the protocol.ts freeze lift) merged clean as ab2b4fb7; its diff vs main touches exactly the PR's 12 files.
  2. Contract review: round one FAIL: must-fix 1 (the stored-row half translated without packagedBase, unlike the REST read gate), should-fixes 2–6 (silent restoration, changeset overclaim, registry asked before the spec gate, a state-dependent record-currency door, a 503 on a brand-new object when the service is degraded). The delta record finds each fixed and pinned on this head, the design holding and no new finding.
  3. Gates on this head: check-runs pinned to 1fc44a46: 64 total, 55 success, 9 skipped, 0 failed. Dev: 85/85 derived families exit 0, --ran 0 NOT-MEASURED; pnpm lint 0; battery 3356 passed, 28 skipped; each new pin's ablation red as predicted; restore leg 20/20 (delta report 6598058279).
  4. Accepted deviations: the registry ask sits inside if (schema), a skip that production never takes (the object schema is the kernel built-in); observation only in the delta record. Registration still logs its own refusal (declared residual).
  5. Out of scope: the designer edits the translated read: filed as ui card studio(designer): the object designer edits the Accept-Language-translated /meta read and PUTs it back whole, injected columns included #29793. The zh-CN first-run loop (Studio read → save → publish → data route) is carried in the landing record. security_findings: none.
  6. Landing: not governed, under 3000 lines ⇒ the seat lands it (pr_ready + automerge_enable) after the refreshed PR body is applied. At MERGED: landing record on Studio in zh-CN: a new object publishes "successfully" and never reaches the data plane, because its echoed queue column carries a translated label #29673, pm:dispatched removed, card thread released.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 3, 2026 07:44
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 3, 2026 07:44
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 589049b Nov 3, 2026
69 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-knfz66 branch November 3, 2026 08:06
marchtian pushed a commit that referenced this pull request Nov 4, 2026
… round-trip item

The 18.3.0 wave-5 re-check (QA runs #29890, #29893, #29877 and #29870, at
589049b) found checklist steps, verifies and fixtures that no longer match
the product. Each line was re-read at base f68b47c before it was applied.

- Part A (W5-FAIL): 19 items, among them the action door for the aggregate
  bulk action, the ADR-0146 D11 pair recipe for staging a shipped view or
  email template, the 'Switched on / off' wording, the Invite outcome copy
  and the per-group approval read.
- Part B (W5-STUDIO): 24 studio-authoring items, among them the Studio
  routes for the picklist fixture and the default-currency prompt.
- Part C (W5-P0): first-run-loop knownGaps[3] is a regression row since
  #29790, and RUNNER's console-auth fact names the Packaged Actions page.
- Part D (W5-EXPORT): new item platform-core.package-export-round-trip.
- RUNNER: the overlay fact now gives the D11 pair recipe, and a new fact
  covers the first-page currency / timezone prompt.
- platform-core.manifest-install-contract source[1] is re-pointed, because
  its anchor no longer resolved and the checker refused the area file.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ihL2ebbZkr2QAEG1x7UC8
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:system size/xl tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Studio in zh-CN: a new object publishes "successfully" and never reaches the data plane, because its echoed queue column carries a translated label

1 participant