Skip to content

feat(runtime): export the package disable door's dependent-rule verdict and its sentence on the package entry - #31157

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-31067-export-disable-verdict
Nov 7, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-31067-export-disable-verdict

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by 田騏 · project thread
Fixes #31067
Clause-②: yes
Metadata-protocol: none

维护者速读

  • 改了什么:平台把「停用包」时检查依赖校验规则的判定函数和拒绝提示语,作为 @objectstack/runtime 的公开导出开放出来(readPackageDisableDependents、formatPackageDisableDependents、DisableDependentRule 类型);平台自己的停用接口用的就是同一份。
  • 为什么改:cloud 控制面的「按安装停用」和软卸载会绕过这道检查,导致对象之后的写入全被拒;按裁决「平台开放检查,cloud 接上(推荐)」,平台开放、cloud 接上,检查逻辑和提示语只保留一份。
  • 风险与回滚:只是新增导出,现有接口行为不变;公开面变宽(Clause-② yes),需要席位做契约复审。回滚即撤掉这次导出。
  • 先看:packages/runtime/src/index.ts(新增的三项导出)、packages/runtime/src/domains/packages.ts(两个函数加 export 和说明)、测试文件末尾新增的 describe。
  • 两分钟验证:pnpm --filter @objectstack/runtime exec vitest run src/domains/packages-disable-module-extension-fields.test.ts,预期 33 个用例全部通过(其中 4 个是新增的)。
  • 需要你做的:无,席位落地。

What changes

  • packages/runtime/src/domains/packages.ts: readDisableDependentRules and disableDependentRulesMessage gain export and a JSDoc paragraph naming their public spelling. No line of either body changes, and both call sites (PATCH /packages/:id/disable and the POST /packages enableOnInstall: false arm) still call them directly.
  • packages/runtime/src/index.ts: the package entry re-exports them as readPackageDisableDependents and formatPackageDisableDependents, named after what each answers, with the DisableDependentRule type beside them. The door's function and the entry's export are the same function object, so there is one scan and one sentence. The test pins that identity.
  • .changeset/31067-runtime-export-disable-dependents.md: @objectstack/runtime patch, carrying both declaration lines.

Clause-②: the package entry's public surface widens by two functions and one type. The seat owes the contract review.

Tests

A new top-level describe in the existing harness file packages/runtime/src/domains/packages-disable-module-extension-fields.test.ts, [#31067] the verdict the disable door asks is the package entry's export. It uses a bare SchemaRegistry plus the real HttpDispatcher and boots no kernel. It has four cases:

  1. Identity: runtime.readPackageDisableDependents is readDisableDependentRules, and runtime.formatPackageDisableDependents is disableDependentRulesMessage.
  2. The verdict answers the one active rule (the switched-off rule is left out), writes nothing (the package stays enabled and the field stays served), and answers [] for a package that withholds nothing and for a registry it cannot read.
  3. The door's 409 PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE message equals exactly what the exported builder spells over the exported verdict. The sentence is also spelled out literally once.
  4. Control: once the reading package is disabled, the verdict is [] and the door answers 200.

The existing #30825 / #30379 / #30756 batteries in the same file are untouched and green.

Acceptance notes

Verification

Tree objectstack-ai/objectstack. The runtime suite and the first gate sweep ran on aae146a8 (merge base 5f003744). origin/main (cadd6aa2) was then merged as 72296d6c. Its incoming paths touch formula, plugin-auth, service-settings and platform-objects, and none touches runtime or objectql. The delta against main is still exactly these 4 files. After a rebuild, the scoped re-checks and the whole derived gate sweep ran again on 72296d6c.

Runtime package
  • pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 :: exit 0 (on aae146a8). Test Files 554 passed (554), Tests 8918 passed / 19 skipped.
  • After the merge, on 72296d6c: pnpm build (119/119) && pnpm --filter @objectstack/runtime typecheck && vitest run of the three disable-door files (packages-disable-module-extension-fields, packages-disableable-verdict, packages-disable-app-module) :: exit 0. Test Files 3/3, Tests 82/82.
  • pnpm --filter @objectstack/runtime typecheck :: exit 0. The test layer holds 27 files / 186 errors / 66 pinned signatures, unchanged.
  • eslint packages/runtime --no-inline-config :: exit 0.
  • Harness file wall time: base 17.02s (29 tests) against head 17.90s (33 tests), measured back to back under the lock.
Derived gates (dispatch-gates --commands)

On 72296d6c, all 62 derived commands exit 0, each exit code captured before any pipe (check:type-check-debt ran under the verify lock). dispatch-gates --ran: 62 derived, 62 run, 0 NOT-MEASURED (a derived zero: every entry carries an exit code).

On the first sweep (aae146a8), three commands exited 3 and were rerun green. check-plugin-teardown-shape --self-test could not reach its control commit on the shallow clone; that commit was fetched into a private ref. check:dual-build-cjs-loads needed a full pnpm build. check:type-check-debt timed out in the lock queue.

node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/ci/snapshot-next.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:durability-log-level :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:route-envelope :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:vouch-for-reference-callers :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

🤖 Generated with Claude Code

…ct and its sentence

readPackageDisableDependents and formatPackageDisableDependents (with the
DisableDependentRule type) on the @objectstack/runtime entry: the same two
functions PATCH /packages/:id/disable and the install arm ask, so a host door
that takes a package out of service another way answers the same 409.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ihL2dLQEXWGsE2SZ3Qn5F
@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 7, 2026
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/api/client-sdk.mdx (via packages.disable (sdk, the route ledger binds it to PATCH /packages/:id/disable))
  • content/docs/api/error-catalog.mdx (via /packages/:id/disable (route, a path literal in a comment on a changed line))
  • content/docs/api/metadata-api.mdx (via /packages/:id/disable (route, a path literal in a comment on a changed line))
  • content/docs/deployment/marketplace-handbook.mdx (via /packages/:id/disable (route, a path literal in a comment on a changed line))
What this run could not see
  • the SDK route bridge reached 63 of 226 client-bound route-ledger rows — the other 163 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 163: 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; 104 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 cbe20ab0c4d2b20d84db8a88a85698eadcd0a5b7 → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 cbe20ab0c4d2b20d84db8a88a85698eadcd0a5b7 → 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#4 · marchtian · session_017ihL2dLQEXWGsE2SZ3Qn5F

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 72296d6c61dd32ff7b45236b4f39c4f51c322a21
Local-runs: none

① Derived judgments

  • Face: declaration only (Clause-②: yes on the claim 6706872785, PR body and changeset). No path under packages/spec/**, no governed path, no new error code, no route. Diff read in full: 4 files, +138/−2.
  • The widening is three new names on the @objectstack/runtime entry: readPackageDisableDependents and formatPackageDisableDependents (re-export aliases of the module functions readDisableDependentRules / disableDependentRulesMessage, whose bodies are unchanged — the only edit in domains/packages.ts is export plus doc comments) and the type DisableDependentRule. Identity is pinned: the entry exports are toBe the functions the disable door calls, so "declared is served" holds with one scan and one sentence.
  • Purity holds as declared: the verdict reads the registry and writes nothing (pinned: package stays enabled, field stays served; an unreadable registry answers []). The 409 code it pairs with, PACKAGE_DISABLE_RESTRICTED_BY_VALIDATION_RULE, is already in the spec error-code ledger; the door answers byte-identical text to the exported builder (pinned), and the control case (reader disabled ⇒ empty verdict ⇒ door 200) is pinned.
  • Alias over rename is the narrower choice: the internal names stay as other pins cite them.

② Semver level

  • Additive exports only ⇒ minor-compatible; changeset declares @objectstack/runtime patch, consistent with fleet practice for additive host seams. No narrowing arm: every existing door answers as before.

③ Boundary flags

  • Security/permission boundary: none moved. The functions are read-only over a registry the host already holds; the doors that write keep their own permission checks. A host that adopts them gains the refusal, never a bypass.

Implemented-by: claude/issue-31067-export-disable-verdict
Reviewed-by: session_017ihL2dLQEXWGsE2SZ3Qn5F

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · marchtian · session_017ihL2dLQEXWGsE2SZ3Qn5F

席位核对 — ACCEPT · domain:server#4 · Head-sha: 72296d6c61dd32ff7b45236b4f39c4f51c322a21

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

runtime: export the disable door's dependent-validation-rule verdict for host doors, so a host that removes a package another way answers the same 409

2 participants