Skip to content

feat(metadata-protocol): a metadata write's advisories and refusals come back in the caller's language (#32656, 1/2) - #33276

Merged
objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/project-thread-1hzwul
Nov 14, 2026
Merged

objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/project-thread-1hzwul

Conversation

@os-elon-musk

@os-elon-musk os-elon-musk commented Nov 14, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by Elon · project thread

Part of #32656 (PR 1 of 2; the follow-up PR #33290 closes it).

Maintainer quick read

A metadata write's advisories, and the issues of its 422 refusal, now carry userMessage written in the caller's language: the request's language, else the workspace default, else English (ruling C on #32440). rule, message, hint, path and where are unchanged. A rule with no user sentence carries no userMessage, as before.

What changes

  • spec (minor): SaveMetaItemRequestSchema and PublishMetaItemRequestSchema gain optional, server-stated userTextLocales (array, max 2, ladder order); the doors build these requests member by member, so a wire body never sets it (the client precedent). PublishPackageDraftsRequestSchema is the batch door's body parser, so it is unchanged: that door hands the locales to publishPackageDrafts directly, and a body userTextLocales is not read (pinned). ExecutionContextSchema gains optional workspaceLocale: the workspace default on its own, because locale folds the request locale and the workspace default into one value and the ladder needs both.
  • core: assembleExecutionContext* fills workspaceLocale from the entry localization (never on a guest envelope). New helper userTextLocalesOf(ctx) returns [locale, workspaceLocale], absent/empty values dropped.
  • metadata-protocol: evaluateRuntimeAuthoringGate composes userMessage per issue with findingUserMessage(finding, userTextLocales) (feat(spec, lint): user-facing refusal and advisory text is in the request locale; authoring findings carry a localized user sentence, security posture first #32790's catalog). userTextLocales is threaded from save, single-item publish, and the package publish promotion into the gate (pass-through only; checked: E1's open PR fix(metadata-protocol)!: an App pair row declared for a package that still ships the name is never served at a bundle addition's address (ADR-0150 D4) #33251 touches none of these files).
  • rest / runtime doors: PUT /meta/:type/:name, POST /meta/:type/:name/publish, the dispatcher's /meta save and the packages domain's publish-drafts state userTextLocalesOf(executionContext).

Why

Ruling C on #32440 (tracking #32654): the producer of a finding composes its user-facing sentence in the request locale, falling back to the workspace default and then en, and every client renders it as received. #32790 shipped the catalog and the userMessage member (layer 1); its acceptance note 7 left this layer: compose it at the door that knows both locales.

Verification

  • New packages/runtime/src/domains/meta-save-user-text-locale.test.ts, on both /meta save doors (runtime dispatcher and RestServer) over a real protocol and SQLite store: the stated locales are read off the execution context (['zh-CN','de'], and [] with none); a zh-CN request gets the security-private-no-readscope advisory in Simplified Chinese while rule/message/hint equal the en answer; an unsupported request locale (tlh) falls back to the workspace default, then to en. 6/6.
  • protocol-publish-drafts-advisories.test.ts: the package publish hands the stated locales to the gate for every promoted draft, and none when unstated.
  • assemble-execution-context.test.ts: the frozen parity pins subtract workspaceLocale and assert it positively (present exactly when an authenticated principal's localization names one); new pins for the request-locale-wins / workspace-default-kept split, the guest envelope, and userTextLocalesOf.
  • Exact pins updated for the new server-stated members: http-dispatcher.test.ts, packages-publish-drafts-request-conformance.test.ts (also: a body userTextLocales is not read), rest-exec-ctx-principal-kind.test.ts, rest-server-meta-door-closed-sets.test.ts, rest-server-meta-member-selector.test.ts.
  • Local: @objectstack/metadata-protocol full suite 22843 passed; @objectstack/core security 1394 passed; spec api+kernel 3546 passed; test-source alias gate clean; spec check:generated, check:liveness, check:authorable-surface, check:docs, check:strictness-ledger, ADR-0087 registration, changeset checks green.

Not in this PR (follow-up #33290 on #32656): refusal sentences written outside the authoring rules (the packaged permission-set lock's static English, and the permission middleware's denials, which today read only the folded locale) move onto the same ladder.

Clause-②: yes
Metadata-protocol: added
Skills-docs: none — skills/** and content/docs/** teach what a metadata author writes; userTextLocales and workspaceLocale are stated by the server from the caller's request and workspace, never authored or sent by a client, so no taught sentence moves. The generated reference pages are regenerated.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4cz2MxDkQYCC4U8indM

…aller's language (#32656)

Doors state userTextLocales (request locale, then workspace default) read
off the execution context, which now carries workspaceLocale unfolded.

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

github-actions Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 5 package(s): @objectstack/core, @objectstack/metadata-protocol, @objectstack/rest, @objectstack/runtime, @objectstack/spec, touching 23 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/core/src/security/index.ts, packages/spec/authorable-surface/api.json, packages/spec/authorable-surface/kernel.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/kernel/services-checklist.mdx (via SaveMetaItemRequest (symbol, a top-level type))
  • content/docs/permissions/system-context.mdx (via ExecutionContextSchema (symbol, a top-level const), handleMetadataRequest (symbol, a top-level function), handlePackagesRequest (symbol, a top-level function), registerMetaItemHistoryRoutes (symbol, a top-level function), registerMetaItemWriteRoutes (symbol, a top-level function))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-1.mdx (via publishPackageDrafts (symbol, a method of class ObjectStackProtocolImplementation; a method of class ProtocolHistoryAndPublishFacet))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/core/src/security/index.ts, packages/spec/authorable-surface/api.json, packages/spec/authorable-surface/kernel.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 65 of 247 client-bound route-ledger rows — the other 182 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 182: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 123 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 — 186 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 2797f81db7f78d0197f007b7ba502b5816e518c1 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 2797f81db7f78d0197f007b7ba502b5816e518c1

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Contract-review-claim: session_016WV4d1PGptBW3QRQNmy5e6 · head c3de690 · 2026-11-14T15:45:40Z

review#3 (area-lease pilot #33059, area A2).

…d locales on the doors' exact request pins

The batch publish body schema is the body parser, so userTextLocales
stays a protocol-call member there, never a body key. Exact request
and execution-context key pins now name the new server-stated members.

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

Copy link
Copy Markdown
Contributor

Contract-review-claim: session_016WV4d1PGptBW3QRQNmy5e6 · head caeb904 · 2026-11-14T16:24:02Z

review#3.

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: caeb90463be47d31b3e87f0d4f1880ea943787e2
Local-runs: none
Scope: packages/spec/src/api/protocol.zod.ts, packages/spec/src/kernel/execution-context.zod.ts + packages/spec/authorable-surface/api.json, packages/spec/authorable-surface/kernel.json, packages/core/src/security/assemble-execution-context.ts, packages/core/src/security/index.ts, packages/metadata-protocol/src/runtime-authoring-gate.ts, packages/metadata-protocol/src/protocol-authoring-gate.ts, packages/metadata-protocol/src/protocol-save.ts, packages/metadata-protocol/src/protocol-history-and-publish.ts, packages/metadata-protocol/src/protocol-door-requests.ts, packages/metadata-protocol/src/protocol.ts, packages/rest/src/meta-item-write-routes.ts, packages/rest/src/meta-item-history-routes.ts, packages/runtime/src/domains/meta.ts, packages/runtime/src/domains/packages.ts

① Derived judgments

  • Schema (hits). SaveMetaItemRequestSchema and PublishMetaItemRequestSchema gain an optional userTextLocales (array of strings, max 2). ExecutionContextSchema gains an optional workspaceLocale. Both are additive and optional, so nothing accepted before is refused. At this head the batch body PublishPackageDraftsRequestSchema is unchanged: its door passes the locales to publishPackageDrafts directly, and a body userTextLocales is pinned as not read. That is the right call, because that schema is the body parser.
  • Server-stated, never from a wire body. Both save doors build the request field by field: meta-item-write-routes.ts (the existing "never spreads the body" comment) and runtime/domains/meta.ts. Both set userTextLocales: userTextLocalesOf(ctx) from the resolved execution context, the client precedent. The closed-set and member-selector pins were updated to include it. A client cannot choose another caller's language or smuggle a value in. The value only selects a language, and no verdict depends on it (stated in the schema description and in the gate's jsdoc).
  • Context. workspaceLocale is filled only for a non-anonymous principal from localization.locale, beside the existing fold locale. userTextLocalesOf returns [locale, workspaceLocale] without empties. When no request locale is named, both rungs are the workspace default. That is harmless: the producer tries each once.
  • Gate. issueOf(locales) adds userMessage only when the rule authors a sentence (findingUserMessage, feat(spec, lint): user-facing refusal and advisory text is in the request locale; authoring findings carry a localized user sentence, security posture first #32790's catalog). The other six keys are copied unchanged. Threading through save, single publish and package publish is pass-through only.
  • nit N1: Metadata-protocol: added sits beside a Skills-docs: none reason that says nothing is authored. If added is there because the authorable-surface baseline (api.json, kernel.json) moved, say that in one clause. Otherwise none fits the stated reason better.
  • nit N2 (follow-up): other metadata write doors (MCP tools, CLI) state no locales and compose en. That is fine for now; it's worth one line in the changeset or a p3 card.

② Semver level

@objectstack/spec: minor for two additive optional members, patch elsewhere. Clause-②: yes (widening) is correct. No refusal is added and no member is removed (the batch body member was withdrawn before landing).

③ Boundary flags

④ Design judgment

Ruling C on #32440 puts sentence composition at the producer, with the request's language, then the workspace default, then en. The protocol never sees the request, so the door has to state both rungs. Keeping locale as the fold and adding workspaceLocale as the unfolded second rung is the smallest schema change that makes the ladder possible. Declaring the member server-stated on the request schema matches the client precedent. Holds against the cited code and the ruling's scenario (an unsupported request locale falls back to the workspace default, pinned with tlh).

Implemented-by: claude/project-thread-1hzwul
Reviewed-by: session_016WV4d1PGptBW3QRQNmy5e6

VERDICT: PASS

Nits only.

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Review carry — PASS stands

Head: fedb4843bb2f3942b1892b5f417c023cb31d26a2 · reviewer: review#3 (session_016WV4d1PGptBW3QRQNmy5e6)

The delta from my PASS at caeb90463be47d31b3e87f0d4f1880ea943787e2 is test-pin only: packages-seed-apply-org-scope.test.ts now expects userTextLocales: [] on the publish request (the server-stated member this PR adds) and pays the @objectstack/metadata load at collection for the alias gate. No source line moves, so the PASS (and its nits) carries to this head. The ACCEPT cites this note as Regen-provenance and re-reads every check at the landing head.

@objectstack-fleet

Copy link
Copy Markdown
Contributor

domain:server#5 · os-elon-musk · session_016WV4cz2MxDkQYCC4U8indM

CI note · head 085b953: Test Core (4/8) failed only on @objectstack/cloud-connection#test (objectql 595/595 files green in the same shard). This PR does not touch cloud-connection. Locally on this head, after building @objectstack/cloud-connection^... from this tree: vitest run → Test Files 80 passed (80), Tests 1206 passed (1206). Re-ran the failed job once; if it fails again I will chase the named case.

@objectstack-fleet

Copy link
Copy Markdown
Contributor

domain:server#5 · os-elon-musk · session_016WV4cz2MxDkQYCC4U8indM

ACCEPT · area A2 · Part of #32656 (PR1 of 2; #33290 carries Fixes) · head 1cffbad · 2026-11-14T19:22Z

  • Reviewer: review#3 contract record PASS, carried (comment 6881309798). Since the reviewed code head: merges of origin/main (main had changed files this PR touches: protocol.zod.ts, runtime-authoring-gate.ts, protocol-authoring-gate.ts, protocol.ts, api.json; all auto-merged) and 1cffbad, which regenerates content/docs/references/api/protocol.mdx and packages/spec/authorable-surface/api.json from the merged tree (check:generated --fix). Re-run on the merged tree: metadata-protocol authoring-gate/publish/advisor 33 files / 558 tests, runtime 4 files / 279, core assemble 221, all green.
  • Declaration: Clause-②: yes (widening), Metadata-protocol: added; @objectstack/spec minor, core / metadata-protocol / rest / runtime patch.
  • Governed: check-governed-merges --pr 33276 → NOT governed (0 of 29 paths); +490 / -16.
  • Checks @1cffbad8: 68 total = 58 success + 10 expected skips (check-expected-skips OK), 0 failed, 0 pending. (The earlier Test Core (4/8) cloud-connection failure at 085b953 did not recur.)
  • K = 3. Main has not changed any file this PR touches since its merge base e0ce1b5.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 14, 2026 19:23
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 14, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Nov 14, 2026
…-1hzwul

# Conflicts:
#	packages/metadata-protocol/src/protocol-publish-drafts-advisories.test.ts
@objectstack-fleet

Copy link
Copy Markdown
Contributor

domain:server#5 · os-elon-musk · session_016WV4cz2MxDkQYCC4U8indM

Area A2: re-queue after the merge queue dropped this PR on MERGE_CONFLICT.

  • New head 750be0fb is a merge of origin/main (2797f81d) only. No change to this PR's own diff.
  • The one text conflict was a comment line in packages/metadata-protocol/src/protocol-publish-drafts-advisories.test.ts. I took main's wording.
  • Regen-provenance: packages/spec/authorable-surface/api.json and content/docs/references/api/protocol.mdx were regenerated from the merged tree (gen:schema, gen:docs). The os-regen hook reports both current.
  • protocol-publish-drafts-advisories.test.ts: 11/11 passed.
  • The earlier ACCEPT and the review#3 verdict stand. Auto-merge is re-enabled; the queue enters once every check is green at 750be0fb.

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/l 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.

2 participants