Skip to content

fix(runtime): GET /packages/:id/customizations answers over HTTP, mounted beside the other package reads - #28635

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-sfkpj0
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-sfkpj0

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Fixes #28622

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:给已发布的「查看基础包定制」读接口 GET /api/v1/packages/:id/customizations 补上 HTTP 挂载,一行,挨着同类的 /:id/export。
  • 为什么改:feat(runtime,spec): GET /packages/:id/customizations answers a base's overrides, extensions and switched-off items in one read #28595 上线了这个接口的路由台账和处理逻辑,却漏了挂载,线上请求一律 404;main 从 519089e1 起每次推送都红在 Dogfood 回归门禁上,挡住所有车道的合并后读数。
  • 风险与回滚:极低。只新增一条 GET 路由,不改任何已有路由的顺序或行为;回滚即删这一行。
  • 先看:packages/runtime/src/dispatcher-plugin.ts(新增的那一行挂载);packages/runtime/src/dispatcher-plugin.package-customizations-mount.test.ts(经挂载路由打到接口的探针)。
  • 两分钟验证:pnpm --filter @objectstack/dogfood exec vitest run test/route-ledger-live-mount-parity.dogfood.test.ts,预期 8/8 通过(main 上是 1 失败)。
  • 需要你做的:无,席位落地。

What changes

  • packages/runtime/src/dispatcher-plugin.ts: one mountPackagesRoute('get', '/:id/customizations', (req) => '/' + id + '/customizations') call, beside its two-segment sibling GET /:id/export. It is the exact shape every sibling uses (same registrar, same dispatcher.dispatch(...) pipeline: kernel resolution, identity, auth gate), so it matches the card's sketch with no difference. Because registerPackageRoutes is called for both bases, the route is mounted at /api/v1/packages/:id/customizations and, with project scoping on, at /api/v1/environments/:environmentId/packages/:id/customizations. /:id is single-segment, so registration order shadows nothing.
  • packages/runtime/src/dispatcher-plugin.package-customizations-mount.test.ts (new): starts createDispatcherPlugin() against a recording IHttpServer (the harness of analytics-empty-cube-refusal.test.ts), takes the handler registered for GET /api/v1/packages/:id/customizations, and calls it the way the transport does. It asserts 200, the success envelope, the domain handler's data (one served override from the protocol stand-in, empty extensions and switched-off lists, counts 1/0/0), and that the data parses through GetPackageCustomizationsResponseSchema. Identity is stubbed to a studio.access caller, as domain-handler-registry.test.ts does for the other /packages routes. The read's own behaviour stays pinned in domains/packages-customizations.test.ts.
  • .changeset/28622-package-customizations-mount.md: @objectstack/runtime patch. files[] ships dist, and dist/index.js carries the new route after a build.

Untouched, as ruled: route-ledger.ts (the row stays), UNEXERCISED_BY_THIS_BOOT, and the dogfood gate. No generated file moved.

Why

PR #28595 (#28411) added the ledger row (route-ledger.ts:544) and the domain branch (domains/packages.ts:2532) but no mount. The dispatcher mounts every /packages sub-route explicitly, so the read 404'd for every caller on a host without @objectstack/hono's catch-all. Its unit test calls the domain handler directly, so it stayed green. The Dogfood Regression Gate (n/3) shards run only on push, so the first reading was post-merge on main.

Tests

Red on origin/main 0e78922, green at head 67e1f91 (the dogfood gate)

pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/route-ledger-live-mount-parity.dogfood.test.ts, after building the @objectstack/dogfood^... closure.

At origin/main 0e78922e, the card's sentence exactly, and nothing else:

× every ledgered route is reachably mounted
runtime/route-ledger.ts: GET /packages/:id/customizations — LEDGERED BUT NOT MOUNTED. The live router answers nothing for GET /api/v1/packages/__parity_probe_0/customizations; this URL 404s at runtime while every ledger-reading guard passes it.
Test Files  1 failed (1)
     Tests  1 failed | 7 passed (8)
os-verify-lock: VERDICT command-exit 1

At head 67e1f910, after pnpm --filter @objectstack/runtime build (dist/index.js holds /:id/customizations):

Test Files  1 passed (1)
     Tests  8 passed (8)
os-verify-lock: VERDICT command-exit 0
The HTTP-level probe: red with the mount ablated, green with it

The probe imports ./dispatcher-plugin.js from src/ (a relative import, no dist/ hop), so the ablation needs no rebuild.

Green at head: Test Files 1 passed (1) · Tests 1 passed (1) · VERDICT command-exit 0.

Ablation via node scripts/ablation-replace.mjs --file packages/runtime/src/dispatcher-plugin.ts --anchor ANCHOR --delete -- bash abl-inner.sh:

ablation-replace: ok mutation landed: anchor 1 -> 0, blob 8c421f8946a7 -> 82127f5f7dce
anchor count now: 0
AssertionError: GET /api/v1/packages/:id/customizations must be mounted: expected undefined to be type of 'function'
Test Files  1 failed (1)
     Tests  1 failed (1)
ablation-replace: ok restored: blob == HEAD (8c421f8946a7) and `git diff HEAD` is empty
Runtime package
  • pnpm --filter @objectstack/runtime test: Test Files 503 passed (503) · Tests 8159 passed | 19 skipped (8178) · VERDICT command-exit 0.
  • pnpm --filter @objectstack/runtime typecheck (tsc --noEmit && pnpm check:test-typecheck, so the new test file is type-checked too): exit 0.
  • eslint packages/runtime --no-inline-config: exit 0.

Acceptance notes

  • test:repo was not run locally (it belongs to CI's Test Core); the diff touches no file of that project.
  • The full dogfood battery was not run: no ! in the title and no (narrowing) arm. The one gate the card names was run red and green.

Verification

Derived gates at 67e1f91 (58 families)

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 58 families from the 3 changed paths. Each was run with its exit code captured before any pipe, and reconciled with --ran:

Run reconciliation — 58 derived, 57 run, 1 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 58 derived famil(ies) accounted for — 57 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).
  • 57 exit 0. That includes node scripts/check-plugin-teardown-shape.mjs --self-test: exit 3 on the first run (shallow clone, positive-control commit missing), exit 0 after git fetch --depth=1 origin 621a4876….
  • NOT MEASURED: pnpm check:dual-build-cjs-loads, exit 3 PREREQUISITE NOT MET (it needs every package's dist/, which only the dogfood closure was built for). CI runs it.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No spec change.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes (scoped to packages/runtime).
  • 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: n/a.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English. (The 维护者速读 block is the os-dev contract's maintainer summary.)
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FEkQ5kwT5zvfww7MNoyovM


Generated by Claude Code

…r plugin

The read shipped with its route-ledger row and its domain handler but no
mountPackagesRoute() call, so the live router answered nothing for it and
the Dogfood route-ledger live-mount parity gate went red on main.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Nov 1, 2026
@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/runtime, touching 3 documentable anchor(s).

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

  • content/docs/deployment/production-readiness.mdx (via createDispatcherPlugin (symbol, a top-level function))
  • content/docs/plugins/packages.mdx (via createDispatcherPlugin (symbol, a top-level function))
What this run could not see
  • the SDK route bridge reached 60 of 214 client-bound route-ledger rows — the other 154 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 154: 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; 99 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 — 28 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 d77dc07a5e5a4b5478763fb3a2126913c92f8c01 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json d77dc07a5e5a4b5478763fb3a2126913c92f8c01

⚠️ 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 d77dc07a5e5a4b5478763fb3a2126913c92f8c01 → 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#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC

席位核对 · ACCEPT · domain:server seat 1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC · 2026-11-01T10:03Z

Head 67e1f910, report 6556345017 (on #28622), claim 6555910361 (p0, order order-28622.md). No contract review is owed: no packages/spec/** path, no governed path, no narrowing, Clause-②: no. The read's ledger row and response schema already shipped in #28595; this adds only the missing mount.

item reading
Shape Draft, base main, Fixes #28622. Three files: dispatcher-plugin.ts (+3), one new probe test, one @objectstack/runtime patch changeset. Clause-②: no, Metadata-protocol: none.
Diff check (seat) The added line is mountPackagesRoute('get', '/:id/customizations', (req) => \/${req.params.id}/customizations`), placed beside /:id/exportand before the single-segment/:id, the exact shape of its siblings and the order's sketch. registerPackageRoutesruns for both bases, so the scoped prefix gets it too.route-ledger.ts, UNEXERCISED_BY_THIS_BOOTand the dogfood gate are untouched. The probe startscreateDispatcherPlugin()on a recordingIHttpServer, takes the registered GET /api/v1/packages/:id/customizationshandler, and asserts 200 and adatathat parses throughGetPackageCustomizationsResponseSchema`.
Evidence CI green on head 67e1f910 (35 success, 13 skipped, 0 failed; the CI and Lint & Type Check workflow runs both success). Governed check: NOT governed, 121 changed lines. The report gives the dogfood gate route-ledger-live-mount-parity red at origin/main 0e78922e (1 failed / 7 passed, the card's sentence) and green at head (8/8); the probe red with the mount ablated ("must be mounted") and green restored; runtime suite 503/503 files; 57 of 58 derived gates exit 0 and 1 NOT MEASURED (check:dual-build-cjs-loads, a prerequisite-only exit 3).
Landing 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready and auto-merge through the relay, once every check on this head is green or an expected skip. p0 main-red fix, landed ahead of the lane's other PRs.

Generated by Claude Code

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

Development

Successfully merging this pull request may close these issues.

main red: Dogfood Regression Gate — GET /packages/:id/customizations is ledgered but not mounted (#28595), so the route 404s at runtime

1 participant