Repository navigation
fix(runtime)!: a package export refuses when the read of a type the runtime serves fails, naming the package and the type - #29824
Conversation
…ime serves fails assemblePackageManifest skipped every per-type read failure as an unknown type, so GET /packages/:id/export answered 200 without the collection. Only a type absent from the runtime's live vocabulary (getMetaTypes) is skipped now; a failed read of a served type refuses the export, naming the package and the type, with the read's declared status and code carried. Claude-Session: https://claude.ai/code/session_01JQwxbXpF6XoEdQQbPsGPBQ Co-authored-by: Claude <noreply@anthropic.com>
…type Claude-Session: https://claude.ai/code/session_01JQwxbXpF6XoEdQQbPsGPBQ Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbXpF6XoEdQQbPsGPBQ Co-authored-by: Claude <noreply@anthropic.com>
…the export Claude-Session: https://claude.ai/code/session_01JQwxbXpF6XoEdQQbPsGPBQ Co-authored-by: Claude <noreply@anthropic.com>
…rved type Claude-Session: https://claude.ai/code/session_01JQwxbXpF6XoEdQQbPsGPBQ Co-authored-by: Claude <noreply@anthropic.com>
…ckage export on a failed read of a served type Claude-Session: https://claude.ai/code/session_01JQwxbXpF6XoEdQQbPsGPBQ Co-authored-by: Claude <noreply@anthropic.com>
…iled read of a served type Claude-Session: https://claude.ai/code/session_01JQwxbXpF6XoEdQQbPsGPBQ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f71ab18addf57f66d101abc6b7e7b2a4bf21144c && git checkout f71ab18addf57f66d101abc6b7e7b2a4bf21144c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 42ff86f67723f5cd6b275bc4ddbb10f05e8c4209 725594f6196338ffd9149b6c8b82907446708b73 && git checkout -B drift-repro 42ff86f67723f5cd6b275bc4ddbb10f05e8c4209 && git merge --no-ff 725594f6196338ffd9149b6c8b82907446708b73
node scripts/docs-audit/affected-docs.mjs --json 42ff86f67723f5cd6b275bc4ddbb10f05e8c4209
|
|
domain:server#5 · os-warren · session_01JQwxbZUR3xvHyDPCjNL5P4 Contract-review-claim: session_01JQwxbZUR3xvHyDPCjNL5P4 · head 4378aeb · 2026-11-03T06:22Z |
|
domain:server#5 · os-warren · session_01JQwxbZUR3xvHyDPCjNL5P4 Contract reviewServed-tier: Inputs read: card #29760 body and every comment (triage audit 6595093120, claim 6595325946, os-dev-report 6596938569); PR #29824 body, file list (4 paths, +452/-4) and the net diff against Check-runs on the head at review time: concluded success — Check Changeset, Governed Surface Queue Guard, PR-body guards, Check PR Size, Lint · ESLint, Lint · PM dispatch-gates, Lint · scoped · tooling self-tests, Type Check · debt ledger, Spec property liveness, Flag docs affected, Plan Test Core shards, Test UI, Dogfood Regression Gate, Auto Label, filter; still ① Derived judgmentsAccept-set changes the diff implies, each judged against the live code:
② Semver levelChangeset Clause-②: ③ Boundary flags
④ Design judgmentThe narrowing, read against the published spec on It makes the spec's own refusal real; it takes no declared capability away at the consumer. The spec never declared "an export minus a collection"; the old catch delivered one by accident, and an install from it would land without the type with no word. The head draws the line where the card asked (wanted #1): the one throw the old catch may keep swallowing is a type this runtime does not serve, and the authority for "serves" is the runtime's own live vocabulary, the one accessor the Attempts to break it, each answered by the tree:
Findings:
No must-fix. Implemented-by: VERDICT: PASS |
…he served check getMetaTypes lists each type as its source stores it, singular or plural; the export loop asks with the canonical singular, so a plural-only listing read as unserved and a failed read of that type was skipped. Claude-Session: https://claude.ai/code/session_01JQwxbXpF6XoEdQQbPsGPBQ Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#5 · os-warren · session_01JQwxbZUR3xvHyDPCjNL5P4 Contract reviewServed-tier: Delta review over the record on Check-runs on ① Derived judgments
② Semver levelUnchanged from the first record: the changeset is untouched by the delta ( ③ Boundary flags
④ Design judgmentThe delta is the should-fix taken verbatim and a clean merge. The fold reads the vocabulary the way its own accessor says to read it ( Findings:
Implemented-by: VERDICT: PASS |
|
domain:server#5 · os-warren · session_01JQwxbZUR3xvHyDPCjNL5P4 席位核对ACCEPT ·
|
Requested by Warren · project thread
Fixes #29760
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — the skills and docs teach what a package export carries, never how it fails; the refusal is a retryable server answer whose message names the type, and nothing a metadata author writes is read differently.
维护者速读
assemblePackageManifest(packages/runtime/src/domains/packages.ts)逐类型读取的catch不再一律当「未知类型」跳过。只有运行时实时类型表(getMetaTypes)里没有的类型照旧跳过;运行时服务的类型读取失败,则拒绝整个导出,消息点名包与类型,并沿用读取错误自带的 status/code(实测存储故障为503 SERVICE_UNAVAILABLE,未声明的抛错为500)。整库导出GET /packages/export组合同一生产者,随之拒绝,无第二处检查。GET /packages/:id/export照样答 200,只是少了整个集合(实测 showcase 少了dashboards),据此重装会静默丢掉该类型,违背导出权承诺与 bundle 的「要么完整、要么没有」。getMetaTypes或类型表读不出来时按「已服务」处理(失败即拒绝,fail closed)。回滚即 revert 本 PR。packages.ts的runtimeServesMetadataType与packageExportTypeReadRefusal(判定与拒绝的构造);② 逐类型循环里改写后的catch;③ dogfoodpackage-export-type-read-failure.dogfood.test.ts的注入点(协议自身的readActiveOverlayRows)。pnpm --filter @objectstack/runtime exec vitest run src/domains/packages-export-type-read-failure.test.ts,预期 11/11 通过;撤掉修复后 8 条转红(读到的是 200 而非拒绝)。What changes
assemblePackageManifest's per-typecatch(packages/runtime/src/domains/packages.ts). It caught every failure ofgetMetaItemsPerPackage/getMetaItemsas "unknown/unsupported type for this runtime" and skipped the type. Now, after a read throws, it asks one question: does this runtime serve the type?getMetaTypes, each listed name folded throughPLURAL_TO_SINGULAR, so a plural spelling counts; the one accessor that answers "which types exist here" for the/metalisting too): skipped, as before.packageExportTypeReadRefusalbuilds the error: the message names the package and the type (Reading the 'dashboard' items of package 'P' failed, so its export is refused rather than answered without them), the read's own declaredstatus/statusCodeand stringcoderide on unchanged, and the read's error stays oncause. The read's sentence is quoted only when it is a declared client refusal (clientFacingFailureText, the existing ADR-0112 disclosure decision this file already imports).getMetaTypes, or whose listing throws, has not shown the type unknown, so its failed read refuses too. The listing is asked only after a read threw; a successful export never calls it.catchanswerserrorFromThrown(e, 500), so the refusal is the read's declared status and code, or500 INTERNAL_ERRORfor an undeclared throw. Measured on the real protocol: a failed overlay read is rethrown as a declared503 SERVICE_UNAVAILABLE, and the export answers exactly that.GET /packages/export(feat(runtime,client,cli): export a deployment's whole ontology as installable packages in one read (GET /packages/export, os package export) #29755) composesassemblePackageManifestand already turns a throw into a refusal naming the package. So it refuses through the per-package path, naming the package and the type, with no second check in the bundle (the one-producer rule).PackagesDomainProtocolpicksgetMetaTypesfrom the declaredMetadataProtocolcontract, beside the three it already picked. Nopackages/metadata-protocol/src/protocol.tsand no spec file is touched.@objectstack/runtimepatch,fix(runtime)!:with the BREAKING banner, both declaration lines and the ADR-0087 dispositionnot-required (no-migration-prescription).Tests
packages/runtime/src/domains/packages-export-type-read-failure.test.ts(real dispatcher, protocol double), 11 cases:500 INTERNAL_ERRORnaming package and type, the throw's sentence not quoted; declared503 SERVICE_UNAVAILABLEcarried; a declared400 INVALID_REQUEST's sentence quoted after the package and the type; CONTROL a type the vocabulary does not list still skipped, the rest answered 200; fail closed with nogetMetaTypesand with a throwing listing; CONTROL the listing is not asked when no read throws; a vocabulary listing the type only by its plural still serves it, so the failed read refuses;503and undeclared500refuseGET /packages/export, naming package and type, no body; CONTROL an unserved type leaves the bundle 200 with the entry.packages/qa/dogfood/test/package-export-type-read-failure.dogfood.test.ts, one showcase boot with the install-local plugin and one installed package, the fault injected on the real protocol's own overlay read (readActiveOverlayRows), 5 cases: CONTROL both doors 200 with dashboards; a faileddashboardread refusesGET /packages/com.example.showcase/export503 SERVICE_UNAVAILABLEnaming both, no manifest; the same refusesGET /packages/exportnaming the installed package anddashboard, no bundle; CONTROL restored, both 200 again; an unserved type (rag_pipeline, asserted absent from the boot's live vocabulary) still skipped, both doors 200 with the same collections.Red first, before the fix
origin/main7ba8b11f(the fix's source restored from the base): 6 of 7 failed, every one reading200({"success":true,"data":{"objects":[…],"id":"com.example.ledger",…}}, nodashboards). The seventh, the no-throw CONTROL, passed.origin/mainwith feat(runtime,client,cli): export a deployment's whole ontology as installable packages in one read (GET /packages/export, os package export) #29755 (b3d14150), the fix commit reverse-applied in the tree: 8 of 10 failed, the two bundle refusal cases among them (bundle200).ablation-dist-preflightproved the marker absent frompackages/runtime/dist; restore leg rebuilt and proved it present, tree clean): the refusal case failed withexpected 200 to be 503; the body carried 20 collections and nodashboardskey.Green, on the final head `725594f6`
packages-export-type-read-failure.test.ts,packages-export-bundle.test.ts,packages-export-per-package-layer.test.ts: 3 files, 24/24 on725594f6(23/23 on4378aebe).pnpm --filter @objectstack/runtime test(pre-merge headcac7e010): 534 files passed, 8547 passed, 19 skipped. On4378aebe, after mergingorigin/main(feat(runtime,client,cli): export a deployment's whole ontology as installable packages in one read (GET /packages/export, os package export) #29755 touches the same function): 535 files passed, 8564 passed, 19 skipped. On the final head725594f6(the plural fold, andorigin/mainmerged with feat(runtime): a package export names each connector map it withheld; install-local names each as incomplete #29746): 536 files, 8578 passed, 19 skipped.pnpm --filter @objectstack/runtime typecheck(tsc +check:test-typecheck) andpnpm --filter @objectstack/dogfood typecheck: exit 0. Re-run on4378aebe: runtime typecheck exit 0.pnpm --filter @objectstack/dogfood test, the narrowing arm):725594f6· held 2125 s · Test Files 408 passed, 3 skipped (411) · Tests 3364 passed, 28 skipped (3392) · no failure (on4378aebe: 405 files / 3331 tests).Acceptance notes
rag_pipeline. That is the only type whose failed read a real showcase still skips; every other per-type failure now refuses. A protocol double without the type, or withoutgetMetaTypesat all, is the other place the arm is reachable.[], and an unsupported metadata-service loader is caught inside the protocol), so no error shape marks "unknown type". Asking the runtime's own type listing, only after a throw, is the one authority available withoutprotocol.ts(frozen).clientFacingFailureText), so a driver dump never reaches the wire through it and the dispatcher's 5xx withhold never swallows the package and type names.ownDocumentsOf'scatcharoundregistry.getArtifactItemand theregistry.getPackagelookup'scatchin the same function still answerundefinedon a throw; neither drops a collection (the first keeps the layer's items, the second only loses the label/namespace lookup). Read-only inference; carrier: none.Co-Authored-Byline, which the pre-push hook refuses, so the AGENTS.md form was used.Verification
node scripts/pm/dispatch-gates.mjs --commandson this diff (head725594f6, 4 paths): 63 commands, all run, every exit 0 after thecheck:type-check-debtlock re-run (as on4378aebe, merge baseb3d14150, +452 / -4).check:type-check-debtfirst answered exit 3 (PREREQUISITE NOT MET: its closure build queued behind this PR's own dogfood battery on the shared lock and timed out, nothing measured); re-run under the lock, exit 0 (0 ledger entries above their recorded numbers).--ranreconciliation: 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN.cac7e010: 61 exit 0 at first;check-plugin-teardown-shape --self-testexit 3 (its pinned positive-control commit was outside the shallow clone; fetched, re-run exit 0) andcheck:dual-build-cjs-loadsexit 3 (PREREQUISITE NOT MET, unrelated packages had nodist/; exit 0 on the final head).pnpm lint(repo-wide eslint,--no-inline-config) on4378aebe: exit 0; CI's Lint family is green on725594f6.Human Minimum
packages/specholds no runtime logic. (No schema touched.)pnpm lintpasses. (pnpm lintexit 0 on4378aebe.)main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed. (Not touched.)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.维护者速读block is the os-dev maintainer brief, in Chinese by that file's rule.)Generated by Claude Code