Repository navigation
feat(spec,runtime): every served package row carries disableable, the disable door's own verdict, beside writable - #28371
Conversation
…table Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzWzEjBsCfzgaacoEm
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzWzEjBsCfzgaacoEm
… package read doors Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzWzEjBsCfzgaacoEm
…ixture ids Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzWzEjBsCfzgaacoEm
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzWzEjBsCfzgaacoEm
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzWzEjBsCfzgaacoEm
📓 Docs Drift CheckThis PR changes 2 package(s): 4 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 — 173 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 f54ef61fd48ce4dfbad4925632de2b7c86ab9e9b && git checkout f54ef61fd48ce4dfbad4925632de2b7c86ab9e9b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 02d6507275eaa7d821e98585df9bb31eed8dedca 138dbe7f222d07be15597922abcf9594a6d5f855 && git checkout -B drift-repro 02d6507275eaa7d821e98585df9bb31eed8dedca && git merge --no-ff 138dbe7f222d07be15597922abcf9594a6d5f855
node scripts/docs-audit/affected-docs.mjs --json 02d6507275eaa7d821e98585df9bb31eed8dedca
|
|
domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzWzEjBsCfzgaacoEm
|
domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract-review-claim: |
|
domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Contract reviewServed-tier: Inputs: card #28318 (body: the Seam from #28117 item 4 with its "⛔ ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Verdict reasoning. The key is declared where Implemented-by: VERDICT: PASS |
|
domain:spec#1 · os-tesla · session_01L9qibw58ehQ2gRHP2h2Puz Maintainer ruling recorded · 2026-10-31T09:32Z · head
|
|
domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm 维护者速读
席位核对 · ACCEPT ·
|
domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm
Requested by Tesla · project thread
Fixes #28318
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up #28118 (the Console half rides the doc:
content/docs/deployment/marketplace-handbook.mdx"Disabling a module" names the serveddisableablekey when the Console reads it; noskills/**page names the package read doors' verdicts —git grep -n "writable" -- skills/names no package row)domain:spec#1 · os-tesla · session_01L9qibzWzEjBsCfzgaacoEm
维护者速读
GET /api/v1/packages和GET /api/v1/packages/:id返回的每一行包,在writable旁边多了一个服务端判定disableable(布尔),回答「这个包在这里能不能停用」。已安装应用下的模块是disableable: true、writable: false。writable为真时才显示停用按钮,而模块的writable一直是false,所以服务端接受的停用,界面给不出来。按裁决不能把writable改成两个意思,于是单独给一个键。isDisableablePackage,门也改成通过它问),两边不可能不一致;writable与所有写门都不变。回滚即 revert。packages/runtime/src/domains/packages.ts(isDisableablePackage、withDisableableVerdict、停用门那一行);packages/spec/src/api/package-api-assembled.zod.ts(SERVED_ROW_VERDICTS);packages/runtime/src/domains/packages-disableable-verdict.test.ts(逐行一致性测试)。pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 src/domains/packages-disableable-verdict.test.ts,预期 22/22 通过:模块disableable: true且writable: false,11 个包逐行「返回的 disableable = 停用门 200/422」。What changes
1. The key (
@objectstack/spec)SERVED_ROW_VERDICTSinpackages/spec/src/api/package-api-assembled.zod.tsdeclared one server verdict on a served package row,writable. It now declares a second,disableable: z.boolean(), on both stage branches ofInstalledPackageAtEitherStageSchema(so onListInstalledPackagesResponseSchemaandGetInstalledPackageResponseSchematoo). Required, likewritable, because both read doors serve it on every row. Its describe says what a client does with it: offer Disable / Enable from it, never fromwritable. The block comment states that neither verdict is derived from the other.Name.
disableable, as triage ruled. The spec's served boolean verdicts are named VERB-able (writablehere;editable,deletable,resettableon the meta item envelope inapi/protocol.zod.ts;undoable,repairable,sortableelsewhere inapi/), so the convention measures the same name.2. Serving it (
@objectstack/runtime, triage's one-PR exception)isDisableablePackage(engine, id)is the disable door's verdict on a package:isDisableableAppModule(engine, id) || isWritablePackage(engine, id). The door (PATCH /packages/:id/disable) now asks it in place ofisDisableableAppModulealone. That is behaviour-identical, because the refusal branch,requireWritablePackage, already answersnullfor a writable package. The read doors serve the same call asdisableable. Neither site spells the rule.writable.withDisableableVerdictstamps it after the projection, besidewithWritableVerdict, on the two doors that servewritablefrom this file:GET /packagesandGET /packages/:id. The order is the same project-then-stamp order, for the same reason.manage_metadata) and a catalog wall, exactly aswritabledoes. The door still asks both.isDisableableAppModuleis unchanged. Its header now says the door asks it throughisDisableablePackage, in place of "the served row does not carry this verdict yet".Tests
packages/spec/src/api/package-api.test.ts):disableableis required as a boolean on both stages, is declared on both branches besidewritable, keepsdisableable: truebesidewritable: false, and is stripped by both record schemas. The sharedLIFECYCLEfixture gainsdisableable: false.packages/runtime/src/domains/packages-disableable-verdict.test.ts, new, 22 tests): a realSchemaRegistrythrough the real dispatcher, with an 11-package fixture set.scope: 'project') and a scope-less marketplace module of the same app:disableable: true,writable: false.systemorcloudscope givefalse. A module with no dependency, one whose app is not installed, and one whose installed dependency is a plugin givefalse. The app, a plugin, a writable base and asystembase givedisableable === writable, the door's answer.?type=module.disableableequals the door's answer: 200 with the package disabled, or 422WRITABLE_PACKAGE_REQUIREDwith it still enabled. A non-vacuity case checks that the set holds both answers and a disableable row that is not writable.ListInstalledPackagesResponseSchema/GetInstalledPackageResponseSchemawith both verdicts kept. The registry's records never gain the key. Uninstalling the app turns its module's verdict tofalseon the next read.packages-writable-verdict.test.tspin 5 sets asidedisableableby name besidewritable.package-door-producer-key-carry.test.tsregistersdisableableinDOOR_COMPUTED_STAMPS, with its reason. That set-equality register went red on the first full run, as it is meant to when a stamp arrives without a decision.Ablation (one-time, restored)
Through
node scripts/ablation-replace.mjsunder the verify lock, onpackages/runtime/src/domains/packages.ts. The suite imports the dispatcher fromsrc/, so no build sits between mutation and run. Anchordisableable: isDisableablePackage(engine, id) }, x1 → x0.disableable: true }. Blob24535658be09→6aa4c641424a.packages-disableable-verdict.test.ts: 15 failed, including the agreement case for every refused row (the app, both platform-scoped modules, the orphan, the module of an absent app, the plugin, the module of the plugin, thesystembase).disableable: false }. Blob24535658be09→176b3b97b534. 9 failed, including the agreement case for every accepted row (both suite modules, the writable base).Restored after each leg: blob
24535658be09= HEAD,git diff HEADempty.Package suites, typecheck, lint
Under the verify lock, after
pnpm --filter '@objectstack/runtime^...' buildon the merged tree:pnpm --filter @objectstack/runtime --filter @objectstack/spec --no-bail testatf559c37c: specTest Files 771 passed (771),Tests 22808 passed | 1 todo (22809). RuntimeTest Files 1 failed | 489 passed (490),Tests 1 failed | 7956 passed | 19 skipped (7976). The one red was theDOOR_COMPUTED_STAMPSregister (above), fixed inc2a97b40. That file and the response-conformance file then gaveTests 22 passed (22).mainthen moved 7 commits (spec and runtime among them, feat(spec,runtime,plugin-audit): an action run through MCP leaves an audit row naming its surface #28171), merged atb4d79818. Refreshed per AGENTS.md §9 (pnpm install --frozen-lockfile,pnpm build:Tasks: 116 successful, 116 total), then in one locked run:check:generatedgreen, typecheck exit 0, specTests 22808 passed | 1 todo (22809), runtimeTest Files 491 passed (491),Tests 7981 passed | 19 skipped (8000),VERDICT command-exit 0.pnpm exec eslint --no-inline-configon the five touched source and test files: exit 0.pnpm --filter @objectstack/spec check:generated: one artifact was stale (content/docs/references/**), regenerated with--fixand re-checked green.Acceptance notes
GET /api/v1/meta/packagealso stampswritable(packages/metadata-protocol/src/protocol.ts,getMetaItemsfortype: 'package'). It is a different producer: its rows are not declared withSERVED_ROW_VERDICTS, and it lives outside this card's file surface. The one predicate is private to the runtime, so servingdisableablethere would need the rule moved into@objectstack/metadata-protocol, which is a second card. The Console's Packages page reads/packages(PackagesPage.tsx, typedInstalledPackageAtEitherStage), which this PR serves. Noted, not filed.PATCH /packages/:id/enable) has no package gate, so any disabled package can be re-enabled. The describe therefore says "disable", and leaves the Console to offer Enable on a disabled row as it does today (writable || !enabled, read by Packages page: offer Enable / Disable on a suite's type:module rows once the server allows disabling them #28118).ui/.InstalledPackageRowpickssource | writablefrom the spec type, so the new required key does not redType Check UI.Verification
Gates from
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon this diff (105 commands), each run on the final headb4d79818with stdin from /dev/null and its exit code captured before any pipe: all 105 exit 0.dispatch-gates --ran: "105 derived famil(ies) accounted for — 105 run, 0 NOT-MEASURED". On the first sweep (headf559c37c), three answered exit 3, PREREQUISITE NOT MET, and measured nothing:check-plugin-teardown-shape --self-test(shallow clone),check:skill-examples(no client dist),check:dual-build-cjs-loads(no full build). The clone was deepened andpnpm buildrun, and all three exit 0 on the final head. The families CI runs are cited from this PR's check-runs in the report on the card.Human Minimum
packages/specholds no runtime logic. (The predicate lives in the runtime; spec declares the key.)pnpm lintpasses (scoped to the touched files; the repo sweep is CI's).main, one pull request..changeset/28318-served-disableable-verdict.md,minorfor spec and runtime), and both declaration lines are filled in here and in the changeset.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code