Skip to content

feat(spec): declare the publish-drafts request body, so the change note is part of the published API contract - #28447

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-28406-publish-drafts-request
Oct 31, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-28406-publish-drafts-request

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

🤖 Generated with Claude Code

https://claude.ai/code/session_017ihL2fi9zfJhUTubyJYb6V

Fixes #28406

Clause-②: yes (widening)
Metadata-protocol: added

维护者速读

  • 改了什么:POST /packages/:id/publish-drafts(Studio「发布整个应用」)的请求体第一次写进了 @objectstack/spec 的公开契约:PublishPackageDraftsRequestSchema 声明 message(修改说明)和 backfill;路由台账这一行也写明了它。
  • 为什么改:feat(runtime,metadata-protocol,client,console): Studio publish takes a change note; item history names the person #28422 让 Studio 发布时带上修改说明并写进每条历史记录,但这个字段只是服务端在读,契约里没有;SDK 和 API 用户看不到它。现在他们在类型和参考文档里都能看到。
  • 风险与回滚:线上行为一个字节都不变(服务端照旧读请求体);新契约不严格,旧 SDK 仍发送的 actor 被忽略、不会被拒。回滚即还原本 PR。
  • 先看:packages/spec/src/api/protocol.zod.ts(新 schema 与说明),packages/runtime/src/domains/packages-publish-drafts-request-conformance.test.ts(每个字段在服务端实测的行为),packages/runtime/src/route-ledger.ts(台账新增的 requestSchema 字段)。
  • 两分钟验证:pnpm --filter @objectstack/runtime exec vitest run src/domains/packages-publish-drafts-request-conformance.test.ts,预期 8 个测试全过。
  • 需要你做的:无,席位落地。

What changes

  • @objectstack/spec/api: PublishPackageDraftsRequestSchema and its type PublishPackageDraftsRequest, beside PublishPackageDraftsResponseSchema. Two members, measured at the door (packages/runtime/src/domains/packages.ts, publish-drafts branch), not assumed:
    • message: optional string. The door forwards it only when it is a string.
    • backfill: optional array of { object, field }, both non-empty strings, entries strict. No default: absent or [] asks for nothing. The door forwards it as sent; the protocol's shape judgement (readPublishBackfillRequest) refuses a malformed entry 400 VALIDATION_ERROR before anything is published. The "named once" and "field is new and has a literal default" rules are described, not encoded: a refinement would be dropped from the published JSON Schema, and the second is judged against server state.
    • The object is not strict: unknown members are stripped, not refused. actor is deliberately not declared (the door records the authenticated caller), and @objectstack/client's packages.publishDrafts still sends its deprecated opts.actor, so a strict object would describe that call as invalid.
  • Route ledger (packages/runtime/src/route-ledger.ts): the dispatcher ledger had no requestSchema field (only responseSchema). The field is added, runtime-internal like the rest of the ledger, with the same rules as responseSchema (a name, resolved against the spec exports, never filled without conformance coverage). Its JSDoc says what a filled row claims and what it does not. The publish-drafts row names PublishPackageDraftsRequestSchema.
  • Resolver guard (packages/client/src/route-ledger-response-schema.test.ts): ledgered requestSchema names resolve against the live spec exports, using the existing resolver, with an anti-vacuity pin.
  • Regenerated by repo tooling: api-surface, export-origins, declaration-map, authorable-surface, json-schema.manifest, content/docs/references/**, strictness-ledger counts. The SDK client is hand-written, not generated, and packages/client/src/index.ts is not touched. gen:openapi emits a fixed list of nine contract schemas and its output is gitignored, so nothing there moves.
  • Changeset: @objectstack/spec minor.

Tests

  • packages/spec/src/api/protocol.test.ts: the schema accepts {}, { message }, { backfill } and both; strips the SDK's actor; refuses a non-string message and each malformed backfill shape with the zod issue code and path; declares exactly the two members.
  • packages/runtime/src/domains/packages-publish-drafts-request-conformance.test.ts drives the real handler:
    • each schema-accepted body reaches publishPackageDrafts exactly as the schema parses it; an absent body reads as {};
    • a body actor gets 200 and never reaches the publish (the session user is recorded);
    • aiModel, context and packageId in the body are not read;
    • each malformed backfill is refused 400 VALIDATION_ERROR naming the entry, through the real ObjectStackProtocolImplementation (its engine is a proxy that throws if touched, which shows the judgement runs before any read);
    • the ledger row names the schema.
  • packages/spec/src/type-alias-convention.pin.test.ts: the isomorphism pin for the new schema (805 → 806).

Acceptance notes

Verification

Local gates and tests (head sha in the os-dev-report comment on the card)

Filled in the os-dev-report comment on #28406: dispatch-gates --commands families with exit codes, --ran reconciliation, and the reverse verification of the non-strict choice.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V

维护者速读(席位终稿 · head 671e9f57)

  • 改了什么: “整包发布草稿”接口(POST /packages/:id/publish-drafts)的请求体,现在在规格里有了正式声明 PublishPackageDraftsRequestSchema:message 是变更说明(可选文本),backfill 是可选的“新字段回填旧数据”清单。路由台账这一行也标明了它读的请求体。
  • 为什么改: Studio 发布时填的变更说明(feat(runtime,metadata-protocol,client,console): Studio publish takes a change note; item history names the person #28422 已上线)之前只是接口私下读取的字段,SDK 和 API 调用方在契约里看不到。现在写进契约,调用方知道可以传什么。
  • 风险与回滚: 只加声明,接口行为不变。请求体里的其他字段(比如 SDK 还在发的已废弃 actor)会被忽略,不会被拒,所以老的 SDK 调用不会出错。回退本 PR 即可。
  • 先看: packages/spec/src/api/protocol.zod.ts(PublishPackageDraftsRequestSchema);packages/runtime/src/route-ledger.ts(新增的 requestSchema 字段)。
  • 两分钟验证: pnpm --filter @objectstack/runtime exec vitest run src/domains/packages-publish-drafts-request-conformance.test.ts,应全部通过。
  • 需要你做的: 无。Tier S,契约复审 PASS 后由席位落地。

席位意见: ACCEPT(车道部分)。非严格对象是对的:接口只读两个字段,SDK 还会带上已废弃的 actor,严格校验会把现有调用判成不合法。接口本身还没用这个声明去拒绝非文本的 message,已另立卡 #28452,排在 #28414 之后。

席位核对 · ACCEPT (lane part) · card #28406 item 4 · 2026-10-31T17:16Z

  1. 形态: 15 files, +428 / −15 at head 671e9f57 (dev head de4fe2c0 plus this seat's merge of main 16e7a808dc with two references pages regenerated), draft, Fixes #28406 once (item 4 is the last open item), Clause-②: yes (widening) / Metadata-protocol: added, changeset .changeset/28406-publish-drafts-request-schema.md (@objectstack/spec minor). check-governed-merges.mjs --pr 28447: NOT governed.
  2. 前提实测: holds. On main, packages/spec had PublishPackageDraftsResponseSchema and no request schema; the door (packages/runtime/src/domains/packages.ts, feat(runtime,metadata-protocol,client,console): Studio publish takes a change note; item history names the person #28422) reads body.message (forwarded only when a string) and body.backfill (forwarded as sent; the protocol refuses a malformed entry 400 VALIDATION_ERROR). The seat read the schema and the ledger hunks in full: message?: string, backfill?: strict { object, field }[], top level non-strict, actor / aiModel / context deliberately undeclared.
  3. 门禁 check-runs: on 671e9f57 CI has just started; the seat re-reads every check before any landing stroke. Dev: 105 gate families, 104 exit 0, check:dual-build-cjs-loads exited 3 (37 packages without dist/; CI runs it); --ran 105 of 105. Two gate reds fixed in the branch (check:doc-authoring, check:cross-package-test-inputs). runtime 30/30, client 11/11, spec 233/233, typecheck spec/runtime/client 0. A strict-schema ablation turns exactly two pins red. os-dev-report 6546543076. After this seat's merge: check:generated --fix, check:liveness, check:authorable-surface exit 0.
  4. 接受的偏差:
  5. 范围外去向: door enforcement of message → packages: the publish-drafts door accepts a non-string message and publishes with the default note instead of refusing it 400 #28452 (finding, domain:server, after runtime: the package doors record whatever actor the request body names — publish, save, discard, revert, rollback, reassign and duplicate write a caller-chosen name into metadata history #28414). Skills-docs: none owed: no skill teaches the package publish door's body.
  6. 落地条款: Tier S path. Owed before any landing stroke: green CI on the landing head; a same-shape CONTRACT_REVIEW_TIER PASS (packages/spec/src/** non-test); main merged again if runtime: the package doors record whatever actor the request body names — publish, save, discard, revert, rollback, reassign and duplicate write a caller-chosen name into metadata history #28414 lands first (its fix flips one pin here). This seat cannot serve that tier now and hands the review over below.

Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (HTTP 429 "You've reached your Fable limit", 2026-10-30T04:26Z) · head 671e9f5

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/runtime, @objectstack/spec, touching 10 documentable anchor(s). ⚠️ 5 changed file(s) yielded no anchor (packages/spec/api-surface/api.json, packages/spec/authorable-surface/api.json, packages/spec/declaration-map/api.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/api/client-sdk.mdx (via data.query (sdk, the route ledger binds it to POST /api/v1/data/:object/query, selected by route anchor /:object/query; the route ledger binds it to POST /data/:object/query, selected by route anchor /:object/query))
  • content/docs/api/data-api.mdx (via /:object/query (route, bridged from symbol requestSchema — its route source's handler names it))
  • content/docs/api/wire-format.mdx (via /:object/query (route, bridged from symbol requestSchema — its route source's handler names it))
  • content/docs/concepts/metadata-lifecycle.mdx (via /packages/:id/publish-drafts (route, a path literal in a comment on a changed line))
  • content/docs/data-modeling/analytics.mdx (via /:object/query (route, bridged from symbol requestSchema — its route source's handler names it))
  • content/docs/data-modeling/queries.mdx (via /:object/query (route, bridged from symbol requestSchema — its route source's handler names it))
  • content/docs/deployment/cli.mdx (via /:object/query (route, bridged from symbol requestSchema — its route source's handler names it))
  • content/docs/getting-started/quick-reference.mdx (via /:object/query (route, bridged from symbol requestSchema — its route source's handler names it))
  • content/docs/kernel/runtime-services/data-service.mdx (via data.query (sdk, the route ledger binds it to POST /api/v1/data/:object/query, selected by route anchor /:object/query; the route ledger binds it to POST /data/:object/query, selected by route anchor /:object/query), /:object/query (route, bridged from symbol requestSchema — its route source's handler names it))
  • content/docs/kernel/services-checklist.mdx (via /:object/query (route, bridged from symbol requestSchema — its route source's handler names it))
  • content/docs/protocol/objectql/query-syntax.mdx (via /:object/query (route, bridged from symbol requestSchema — its route source's handler names it))

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

  • content/docs/releases/v17/17-4.mdx (via /:object/query (route, bridged from symbol requestSchema — its route source's handler names it))
  • content/docs/releases/v17/17-5.mdx (via /:object/query (route, bridged from symbol requestSchema — its route source's handler names it))
  • content/docs/releases/v17/17-6.mdx (via /:object/query (route, bridged from symbol requestSchema — its route source's handler names it))
  • content/docs/releases/v17/17-7.mdx (via /:object/query (route, bridged from symbol requestSchema — its route source's handler names it))
  • content/docs/releases/v18/18-2.mdx (via packages.publishDrafts (sdk, the route ledger binds it to POST /packages/:id/publish-drafts), publishDrafts (sdk, the bare tail of client method packages.publishDrafts, bound to POST /packages/:id/publish-drafts), /api/v1/packages/:id/publish-drafts (route, a path literal in a comment on a changed line), /packages/:id/publish-drafts (route, a path literal in a comment on a changed line))

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
  • 5 changed file(s) yielded no anchor (packages/spec/api-surface/api.json, packages/spec/authorable-surface/api.json, packages/spec/declaration-map/api.json, …) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 212 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 — 173 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 776c1d875b2d140b4c2419103a7ad8681c1d2610 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 780154abd6e8027c2ff8c38d5c8a5e04374b3df8 — the merge of head 671e9f572f0db69bf31a123344304d511f809737 into base 776c1d875b2d140b4c2419103a7ad8681c1d2610, 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 780154abd6e8027c2ff8c38d5c8a5e04374b3df8 && git checkout 780154abd6e8027c2ff8c38d5c8a5e04374b3df8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 776c1d875b2d140b4c2419103a7ad8681c1d2610 671e9f572f0db69bf31a123344304d511f809737 && git checkout -B drift-repro 776c1d875b2d140b4c2419103a7ad8681c1d2610 && git merge --no-ff 671e9f572f0db69bf31a123344304d511f809737

node scripts/docs-audit/affected-docs.mjs --json 776c1d875b2d140b4c2419103a7ad8681c1d2610

⚠️ 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 776c1d875b2d140b4c2419103a7ad8681c1d2610 → 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

triage#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract-review-claim: session_01L9qibw58ehQ2gRHP2h2Puz · head 671e9f572f0db69bf31a123344304d511f809737 · 2026-10-31T17:46Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

triage#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 671e9f572f0db69bf31a123344304d511f809737
Local-runs: none
Scope: packages/spec/src/api/protocol.zod.ts (PublishPackageDraftsRequestSchema + type PublishPackageDraftsRequest), the type-alias isomorphism pin (805 → 806), packages/runtime/src/route-ledger.ts (new optional RouteLedgerEntry.requestSchema with its TSDoc; the publish-drafts row filled), the new runtime conformance suite packages-publish-drafts-request-conformance.test.ts (8), the client resolver pin for requestSchema, spec protocol.test.ts cases, regenerated api-surface / export-origins / declaration-map / authorable-surface / json-schema.manifest / references docs / strictness-ledger count, the changeset (@objectstack/spec minor). 15 files, +428/−15. Fixes #28406 item 4 (split note 6543345540; seat hand-off 6546628862 ACCEPT lane part; dev report 6546543076 on the card). Not governed.
Inputs: card #28406 body and thread (ui seat's claim, split note and #28422 report), the hand-off, the PR body, the PR diff at this head (schema, ledger, pin and changeset hunks read in full; conformance and spec tests by case), git show on origin/main: the door at packages.ts L1891–L1922 (message forwarded only when typeof body?.message === 'string'; backfill forwarded whenever the key is present; actor from resolvePackageWriteActor, a body actor ignored) and readPublishBackfillRequest at protocol.ts L1434 (array required; each entry an object with exactly object and field, both non-empty strings; duplicates refused), open PRs (no #28414 PR open, so the serial constraint is clear; #28452 filed as finding), check-runs (read 2026-10-31T17:54Z: all 50 latest-per-name check-runs completed, 39 success + 11 skipped, none failed, none running) — 50 checks. Nothing built or run here; the seat's numbers (105 gate families, runtime 30/30, client 11/11, spec 233/233, strict-schema ablation 2 red) are the hand-off's.

① Derived judgments

② Rulings cited

③ Narrowing / widening

  • Widening of the published contract only: the body of POST /packages/:id/publish-drafts is now typed and documented. Nothing changes on the wire; the door's reads are unchanged.

④ Behaviour / flags

Implemented-by: claude/issue-28406-publish-drafts-request
Reviewed-by: session_01L9qibw58ehQ2gRHP2h2Puz
VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#4 · marchtian · session_017ihL2fi9zfJhUTubyJYb6V

Landing stroke · 2026-10-31T17:55Z · head 671e9f572f0db69bf31a123344304d511f809737.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 31, 2026 17:56
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 31, 2026 17:56
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 31, 2026
Merged via the queue into main with commit 462935d Oct 31, 2026
59 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-28406-publish-drafts-request branch October 31, 2026 18:17
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

1 participant