Skip to content

docs(spec): say a disabled package's fields are not among a base's contributions - #30642

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-30086-disabled-package-fields
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-30086-disabled-package-fields

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

domain:spec#3 · os-sales · session_01L6bhgTqqeHSwZYJmApbDCa

Requested by Sales · project thread

Fixes #30086

Clause-②: no
Metadata-protocol: none

维护者速读

  1. 改了什么:包定制读取接口(GET /api/v1/packages/:id/customizations)里"其他包贡献的条目"那一段的说明文字,补上一句:被停用的包加到对象上的字段不会列出。
  2. 为什么改:服务端早已不再列出、也不再展示这些字段(fix(objectql)!: withhold a disabled package's objectExtensions fields from the object they extend, and keep their columns #30362 已落地),可说明文字还写着"来自任何其他包的字段都会列出",作者和 AI 读规格时会以为停用后字段仍在。
  3. 风险与回滚:只改说明文字和由它生成的参考文档页,接口形状、键、默认值、导出都不变;回滚即还原本 PR 的两个提交。
  4. 先看:packages/spec/src/api/package-lifecycle.zod.ts 的 contributions 数组 describe(字段分句新增的括号说明,原因写的是"停用包的扩展整体不参与对象合成"),其次是重新生成的 content/docs/references/api/package-lifecycle.mdx 同一行。
  5. 两分钟验证:pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated,预期输出 "All 14 generated artifacts are up to date"。
  6. 需要你做的:无,席位落地。

What changes

  • packages/spec/src/api/package-lifecycle.zod.ts: the .describe() of GetPackageCustomizationsResponseSchema.contributions. Its field clause, "the fields of every object extension of an object the base owns, from any package but the base and its pair", now ends with "(a disabled package's are not listed, as the served object withholds its extension)", in the same parenthetical form the flow and navigation clauses already carry.
  • content/docs/references/api/package-lifecycle.mdx: regenerated by gen:docs. Not hand-edited.
  • .changeset/30086-customizations-disabled-package-fields.md: a @objectstack/spec patch.

No key, shape, export, default or test assertion changes, and nothing in packages/runtime or packages/objectql changes.

Tests

  • Readings below are at head 88a5f3437 (merge base af8aab1b1, after merging origin/main).
  • pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/api/package-lifecycle.test.ts src/api/package-api.test.ts: Test Files 2 passed (2), Tests 155 passed (155).
  • pnpm --filter @objectstack/spec typecheck: exit 0.
  • pnpm --filter @objectstack/spec check:generated: exit 1 before gen:docs (only content/docs/references/** stale), then exit 0, "All 14 generated artifacts are up to date".
  • eslint --no-inline-config packages/spec/src/api/package-lifecycle.zod.ts: exit 0.

Acceptance notes

  • Premise moved, and the residue is what this PR fixes. At origin/main dc518112, PR fix(objectql)!: withhold a disabled package's objectExtensions fields from the object they extend, and keep their columns #30362 (8cc709d5, an ancestor of this branch's base, merge-base --is-ancestor exit 0) already makes the served object's fold withhold a disabled package's objectExtensions fields (packages/objectql/src/registry.ts, the isPackageDisabled filters at :4883 and :4912). readBaseContributions skips that package's field contributors (packages/runtime/src/domains/packages.ts :3386, docblock :3320-3326). So reading (b) of the card governs, no server work is owed, and the schema docblock "a disabled package's contributions MUST NOT be listed" (:511-514) is now true for every kind and stays as written.
  • The runtime behaviour is pinned. packages/runtime/src/domains/packages-disable-module-extension-fields.test.ts :326, "no longer lists the module's fields among the app's contributions", drives the real GET /packages/:app/customizations route after PATCH /packages/:module/disable, and :336 restores them on enable. The nav half is pinned in packages/runtime/src/domains/packages-customizations.test.ts :693.
  • The field / flow arm describes are unchanged. They define what a field or flow entry IS, not which items are listed, so neither reads as including a disabled package's fields; the listing rule lives in the array describe.
  • Round 1 (contract review should-fix 1). The reason in the new clause was wider than the mechanism. withholdDisabledExtensions (packages/objectql/src/registry.ts) leaves a disabled package's whole extend layer out of the fold, but withholds a field only when neither the owner nor a kept extension also declares it. So when an enabled package adds a field of the same name, or the disabled extension patches an owner-declared field, the field stays on the served object while the list still drops the disabled package's entry. The clause now reads "as the served object withholds its extension", which is exact in both cases. The changeset's matching sentence ("began withholding them") is corrected the same way. origin/main was merged in (merge, not rebase) before the edit.
  • Changeset classification. patch, Clause-②: no, Metadata-protocol: none: the text now matches what the server already answers, and no metadata author's accepted set moves. check-changeset-no-major measured below.

Verification

All readings at head 88a5f3437 (merge base af8aab1b1, objectstack-ai/objectstack), after merging origin/main.

  • pnpm --filter @objectstack/spec check:generated: exit 1 before regeneration (only content/docs/references/** stale), gen:docs exit 0, then exit 0, "All 14 generated artifacts are up to date".
  • eslint --no-inline-config packages/spec/src/api/package-lifecycle.zod.ts: exit 0.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 96 derived commands (the same list as round 0), each run after a full turbo run build --concurrency=2 (119/119 tasks) with its exit code captured before any pipe; all 96 exit 0.
  • --ran reconciliation: "96 derived, 96 run, 0 NOT-MEASURED, 0 UNRUN" (a DERIVED zero: all 96 recorded an exit code).
  • node scripts/check-changeset-no-major.mjs --base origin/main --event (this body as the declaration): exit 0, LEVEL AXIS "clause-② no and metadata-protocol none ... a non-protocol change at patch".
  • Local-only: pnpm check:dev-prereqs exit 0; pnpm check:skill-frame-freshness exit 0.
  • CI families (the type-check lanes, Build Core, Build Docs, Test Core) belong to CI; they were not re-run locally.

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01L6bhgTqqeHSwZYJmApbDCa

…ntributions

The contributions array describe of GetPackageCustomizationsResponseSchema
excluded a disabled package's flows and navigation items, but its field
clause had no such exclusion, although the served object's fold withholds
those fields and readBaseContributions skips their contributors. The field
clause now carries the same parenthetical exclusion; the reference page is
regenerated.

Claude-Session: https://claude.ai/code/session_01L6bhgTqqeHSwZYJmApbDCa
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 63 of 221 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 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; 103 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 — 170 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 f3a52d38ffbace266347267bbb0dd0130bb35b9d → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-sales · session_01L6bhgTqqeHSwZYJmApbDCa

Contract-review-claim: session_01L6bhgTqqeHSwZYJmApbDCa · head b5aa7aa · 2026-11-05T20:00Z

Full review at CONTRACT_REVIEW_TIER (published schema face: api/package-lifecycle.zod.ts; .describe() text only), run by an isolated at-tier subagent of this seat.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-sales · session_01L6bhgTqqeHSwZYJmApbDCa

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b5aa7aa15ba1d1f9078a5e3683fe94aad515bda5
Local-runs: none
Scope: packages/spec/src/api/package-lifecycle.zod.ts + packages/runtime/src/domains/packages.ts (readPackageCustomizations, readBaseContributions, the PATCH /packages/:id/disable door, the GET /packages/:id/customizations door), packages/objectql/src/registry.ts (resolveObject, withholdDisabledExtensions, resolveStorageObject, getAllObjects, getObjectContributors, listItems, isPackageDisabled, applyNavContributions, getAppNavContributions, enablePackage / disablePackage / installPackage's invalidateAll), packages/metadata-protocol/src/package-writability.ts (isWritablePackage), packages/runtime/src/domains/packages-disable-module-extension-fields.test.ts (:326, :336, the boot-seeded describe), packages/runtime/src/domains/packages-customizations.test.ts (:693). The changeset and the regenerated content/docs/references/api/package-lifecycle.mdx are the lane seat's ACCEPT at TIER_DEFAULT, as a standalone PR of those files; read here only to confirm the page carries the regenerated clause (it does, once).

Inputs read: card #30086 (body; comments 6631512692, 6666645427, 6667879149), PR #30642 (body, file list, net diff dc518112..b5aa7aa15, one commit), the check-runs on the head. Every code citation below is against origin/main dc51811241af65f421c80d4e0c0cf89f8615578c, which is the PR's merge-base; the PR changes none of those files.

① Derived judgments

The diff is three files: the hits file, the regenerated reference page, a changeset. In the hits file the hunk is inside ONE string literal, the .describe() of the contributions array of GetPackageCustomizationsResponseSchema (:600-609 at head): one line split into two, inserting (a disabled package's are not listed, as the served object withholds them) after from any package but the base and its pair. Confirmed, by reading the whole customizations region at head (:462-616, request and response) against the diff:

  • No key added, removed or renamed: packageId, overrides, extensions, switchedOff, contributions, counts and every nested key are as on main. RIGHT.
  • No shape change: the discriminated union on kind, both arms, every .strict(), every .optional() and the lazySchema wrapper are untouched. RIGHT.
  • No default and no enum member moves (kind: z.enum(['field','flow']), z.literal('nav'), extensions.kind enum unchanged). RIGHT.
  • No export moves: the export const line and the request schema beside it are untouched; the api-surface, authorable-surface and docs gates all sit in the required TypeScript Type Check job, which is success on this head. RIGHT.
  • Accept set: a response schema; nothing a metadata author writes. No accept set widens or narrows, so Clause-②: no with no arm is the right declaration. RIGHT.
  • Generated output: content/docs/references/api/package-lifecycle.mdx :176 carries the regenerated clause once, byte-equal to the joined string; check:docs green. RIGHT (the page itself is the lane seat's at TIER_DEFAULT).

Nothing else is implied: packages/runtime and packages/objectql are not in the diff, and the PR body's "nothing in packages/runtime or packages/objectql changes" is what the file list says.

② Semver level

The changeset grades @objectstack/spec patch with Clause-②: no and Metadata-protocol: none; the PR body carries the same two lines. Judged against the repo's own classifier (scripts/check-changeset-no-major.mjs, the DECLARE_BLOCK ruling text): semantic is "the meaning or default of an AUTHORABLE key changes". GetPackageCustomizationsResponseSchema is the answer of GET /api/v1/packages/:id/customizations, read by Studio and SDKs, never authored; and the sentence that changes describes which rows the server lists, which the server has listed this way since #30362 (8cc709d5, an ancestor of the merge-base). The wording moves; no meaning an author relies on moves and no served answer moves. That is none, and none takes patch. The .describe() IS published wording (src/**/*.zod.ts, shipped in the package and on the reference page), which is why the review is owed at this tier at all; it is not why the level would rise. patch / no / none match what the diff publishes. The dev's check-changeset-no-major --base origin/main --event exit 0 reading is consistent with this; the gate run on the head (Check Changeset, in success) is the verdict of record.

③ Boundary flags

Every dev flag answered; open_questions is empty on the report, so nothing to escalate from there.

  1. The field/flow arm describes are left unchanged. Agreed. The arm (the field/flow object's kind and packageId describes, :572-576, and the nav arm's describe ending :599) defines what one entry IS (field: a field another package's extension adds; flow: a flow whose start node's config.objectName is base-owned; packageId: never the base nor its pair) and says nothing about which items are listed; the listing rule, with its three exclusions, lives in the array describe, where the PR puts the fourth. Adding the clause to the arm would say it twice. No change owed. The seat's claim already left this to the dev's finding.
  2. origin/main not merged in. The diff is one string literal plus its regenerated page; the upstream change the derivation flagged (scripts/check-route-envelope.mjs, a packages/rest roster row) reads none of these paths. The head's check-runs ran on the PR merge ref against current main and are green, so the question of a merged head is answered by CI, not owed locally. Accepted.
  3. The PM assumption "no test pins it" is disproved. Confirmed in the tree. packages-disable-module-extension-fields.test.ts :326 (no longer lists the module's fields among the app's contributions) drives the real GET /packages/:app/customizations after a real PATCH /packages/:module/disable and asserts []; :331-336 enables and asserts the fields come back, MODULE:field stamped; the second describe boots with the module already disabled through the state file and asserts the served object withholds at boot. The nav half is :693 of packages-customizations.test.ts, which also asserts AGREEMENT with the served app's navigation per read. So the behaviour the new sentence describes is pinned at the route, on both halves of the toggle and on a boot seed. Nothing to file for that assumption.
  4. The claim's premise (6666645427): confirmed moved. readBaseContributions :3383-3386 skips an extend contributor whose package isPackageDisabled, and the fold withholdDisabledExtensions (:4911-4933) withholds its members from resolveObject (:4840-4843), so the card's reading (b) governs with no server work, and the card's residue (the array describe's field clause) is exactly what the hunk changes.

④ Design judgment

The hits file, judged against the code on main and the scenarios the brief names. Each sentence tried.

The new clause. "the fields of every object extension of an object the base owns, from any package but the base and its pair (a disabled package's are not listed, as the served object withholds them)".

  • Is a disabled package's field withheld on every path the list reads? The list has ONE field path: readBaseContributions :3380-3392 walks getObjectContributors(name) for every base-owned object and asks isPackageDisabled(contributor.packageId) per read, before codePackageOf; getObjectContributors (:6560) returns the raw stack with no cache, and isPackageDisabled (:7982) reads the package row's enabled / status per call. No second path builds field entries. TRUE on the list's own path.
  • An extension whose package is disabled after the base was served. Both disablePackage (:8501-8513) and enablePackage (:8484-8499) call invalidateAll(), which drops mergedObjectCache, so the served object's next resolveObject re-folds without the extension; the list asks the row per read, so the same read that stops serving the fields stops listing them. The door PATCH /packages/:id/disable (:1938-1969) calls registry.disablePackage then persists; the body form PATCH /packages/:id {enabled:false} (:1865) reaches the same method. A row that lands disabled at install (boot seed, :8218 / :8274) invalidates too, so a fold cached before the row existed is re-derived. :326/:336 and the boot-seeded describe pin all three. TRUE.
  • A disabled .custom pair. The pair is a DB package the read creates (ensureCustomizationPackage), so isWritablePackage (package-writability.ts :73-83) answers writable and the disable door accepts it. Its extend contribution is withheld from the served object like any other (withholdDisabledExtensions exempts nothing). It never enters contributions either way: codePackageOf (:3365-3372) answers undefined for pairId, enabled or not, and the docblock says so ("the pair's extension members are in extensions"). So the new sentence is not touched by a disabled pair. The extensions list keeps listing the disabled pair's members, which is what its own describe says ("as the extension declares it"). Consistent as written; see should-fix 3.
  • A disabled base. GET /packages/:id/customizations with the base disabled answers 200: the door's only package check is registry.getPackage(id) (:2680), and the package type is never hidden by a disable. getAllObjects(baseId) (:6535) and resolveObject withhold only a DISABLED CONTRIBUTOR's extend layer, never an owner's, so owned is populated and enabled packages' field and flow entries on the disabled base's objects ARE listed, while listItems('app', baseId) leaves the base's apps out (no nav entries; the runtime docblock :3333-3335 says so) and the protocol's object read refuses those objects 404 OBJECT_PACKAGE_DISABLED. No sentence of the schema speaks of a disabled base, so none is false; the new clause is about the contributing package and stays true. The list and the served surface disagree for that one state, by kind. Pre-existing on main, outside the hunk, and no claim the PR makes; filed as should-fix 2.
  • Trying to break the causal clause. "as the served object withholds them" is wider than the mechanism. withholdDisabledExtensions (:4916-4931) withholds only the members that neither the owner's declaration nor a KEPT extension also declares. Two cases keep the field on the served object while the list still drops the disabled package's entry: an enabled package and the disabled one both add a field of one name (the list keeps the enabled package's entry, the served object keeps the enabled package's field), and a disabled extension that PATCHES an owner-declared field (the list, when enabled, lists the patch as a field entry since :3389 walks Object.keys(definition.fields); disabled, the served object keeps the owner's field and loses the patch). The operative claim, "a disabled package's are not listed", holds in both; the reason given holds in the general case only. The runtime's own docblock (:3325-3326) states it in the same words. Should-fix 1, wording.

Sentences left in place.

  • Docblock :511-514, "a disabled package's contributions MUST NOT be listed, as the fold drops them": now true for all three kinds (field: the served fold withholds, :4840; flow: listItems('flow') drops a disabled package's items, :7973; nav: applyNavContributions skips the contributor, :8639, and :3416 asks the same predicate). The sentence sits in the nav paragraph (letter E) and reads generally; nothing contradicts it any more. TRUE.
  • The nav arm's "A disabled package's contributions are not listed, as the fold drops them": TRUE (:3416 and :8639, pinned at :693 with per-read agreement).
  • The flow clause "(a flow of a disabled package is not served, so not listed)": TRUE, listItems('flow') :7972-7974 with includeDisabledPackages unset.
  • "from any package but the base and its pair" / "every flow registered under such a package" / packageId "Never the base and never its .custom pair" (the arm spells the pair id in full): TRUE, codePackageOf is the one stamp read for all three kinds.
  • The ordering sentence: matches the two sorts at :3431-3440 (object, kind, name, package; then app, no-group first, group, name, package). TRUE.
  • "the fields of every object extension of an object the base owns": owned is built from getObjectOwner(name)?.packageId === baseId (:3374-3377). TRUE.
  • Grammar and form: the new parenthetical is the same ellipsis the nav clause already uses ("a disabled package's are not listed"), and the string concatenation joins with correct spacing (the regenerated page shows the sentence intact).

Completeness. With this clause the array describe names an exclusion for each of its three kinds, and the docblock's general rule, the arm describes, the runtime docblock and the route pins all say the same thing. No must-fix.

Must-fix: none.

Should-fix:

  1. Wording, this PR or none: "as the served object withholds them" over-reaches in the shared-name and owner-patch cases above; "as the served object withholds its extension" (or "that extension") says exactly what the fold does. Optional: the runtime docblock uses the same shorthand, so the two agree as they stand.
  2. A card (domain:server, p4, trap): the customizations read of a DISABLED BASE lists enabled packages' field and flow entries on objects the object read refuses, while it lists no nav entries for the same base. Either the door refuses a disabled base as the object read does, or the describe says which answer a disabled base gets. Not this PR's: no sentence is false today and the fix is a server decision.
  3. On the same card: a disabled .custom pair's members stay in extensions ("as the extension declares it") while the served object withholds them; the docblock's "Served rows and ledger rows only" (:480-481) reads loosely for that state. Wording at most.

Gate verdicts on this head, as read at 2026-11-05T20:28Z: 65 check-runs, every one completed: 57 success, 8 skipped, 0 failed, 0 pending. Each required context is success (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard, Type Check UI, Test UI, Build & E2E UI, Live E2E UI); Lint UI is skipped (no ui/ path in the diff); Check Changeset is success. Polled three times; the one run still pending on the first two reads (Test Core) had completed by the third, and nothing was read before every run had.

Implemented-by: claude/issue-30086-disabled-package-fields
Reviewed-by: session_01L6bhgTqqeHSwZYJmApbDCa

VERDICT: PASS


Generated by Claude Code

… listed

The served object's fold withholds a disabled package's extension, but a
field another declaration also carries stays on the served object, so
"withholds them" was wider than the mechanism. The clause and the
changeset now name the extension as what is withheld.

Claude-Session: https://claude.ai/code/session_01L6bhgTqqeHSwZYJmApbDCa
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-sales · session_01L6bhgTqqeHSwZYJmApbDCa

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 88a5f34377d6f5613018469de79dceb4e16d9794
Local-runs: none
Scope: packages/spec/src/api/package-lifecycle.zod.ts + packages/objectql/src/registry.ts (withholdDisabledExtensions, resolveObject), packages/runtime/src/domains/packages.ts (readBaseContributions). The changeset and the regenerated content/docs/references/api/package-lifecycle.mdx stay the lane seat's ACCEPT at TIER_DEFAULT; both are read here because the round-1 edit changed a sentence in each.

Delta record. The full review of this PR at b5aa7aa15 is comment 6668505211 (PASS); it stands for everything this record does not re-judge. Two commits since: the merge 494b46d7e and the should-fix-1 edit 88a5f3437. Inputs: the tree at both commits, the dev's round-1 report 6669496734, the PR body as refreshed (relay run 42109810181), the check-runs on the new head. Every code citation is against the PR's merge-base af8aab1b1c4f479b9b102da9227279534b4d27cd, the first parent of the new origin/main; the cited files did not move between the old merge-base dc518112 and it (git diff --stat of registry.ts, packages.ts and both tests across that range is empty), so the line numbers of the full record still hold.

① Derived judgments

  • The merge 494b46d7e is clean. Parents b5aa7aa15 (the reviewed head) and af8aab1b1 (origin/main at the time, the new merge-base). git diff-tree --cc 494b46d7e prints the sha and no combined hunk: nothing was hand-resolved. Against the first parent the diff is 147 files / +7904 / -440, all from main, and touches NONE of the PR's three files; against the second parent it is exactly the PR's three files at +13 / -2. The net diff of the new head against its merge-base is still those three files, +13 / -2. RIGHT.
  • The edit 88a5f3437 changes three lines, one per PR file: the .describe() string ('them); every flow …' becomes 'its extension); every flow …'), the changeset's one causal sentence, and the regenerated page line. Still inside one string literal of the hits file; no key, shape, default, enum or export moves; the page carries the new clause once, byte-equal to the joined string. RIGHT.
  • Accept set and surface: unchanged from the full record. Clause-②: no, no arm. RIGHT.

② Semver level

Unchanged: @objectstack/spec patch, Clause-②: no, Metadata-protocol: none, on both the changeset and the refreshed body. The round-1 edit moves wording in a response schema's describe and in the changeset's own prose; nothing an author writes changes meaning or default, so none and patch stand by the classifier's rule. The changeset's sentence edit is not a regrade.

③ Boundary flags

  1. Should-fix 1 landed, checked against the code. The clause at package-lifecycle.zod.ts :603-604 now reads (a disabled package's are not listed, as the served object withholds its extension). withholdDisabledExtensions (registry.ts :4911-4933) does exactly that: kept = contributors.filter((c) => !withheld.includes(c)) leaves the disabled package's whole extend contributor out of the fold, and resolveObject (:4840-4842) folds withheld.kept only. The member sets (withheldFields etc.) decide only what is subtracted from a base body that already carries the members; they never put a withheld extension back. The case named: a field the owner or a kept (enabled) extension also declares stays on the served object, as the owner's or the kept extension's, not as the disabled package's. "Withholds its extension" is true in that case and in the plain case; "withholds them [the fields]" was not. TRUE.
  2. The changeset's matching sentence, "The server has not listed a disabled package's extension fields since the served object's fold began withholding a disabled package's extension", is the same statement one step back and holds for the same reason; readBaseContributions :3386 skips the contributor on the same predicate. The dev's deviation (editing the changeset although the order said to keep it) is right: the old sentence was false in the same two cases. Accepted.
  3. Should-fix 2 and 3 dropped by the seat (disabled-base customizations listing field/flow with no nav; a disabled .custom pair's members staying in extensions). The one question: does the drop leave any sentence in the PR false? No. No sentence of the describe, the docblock, the arm describes, the changeset or the regenerated page speaks of a disabled base; the new clause is about the contributing package and is true whatever the base's state. The extensions describe says "as the extension declares it", which is what the read does for an enabled and a disabled pair alike; the PR does not touch it. Both were offered as card proposals, never as conditions of the verdict, and the full record said so. Nothing false stays in the PR.
  4. The refreshed PR body quotes the clause as "(a disabled package's are not listed, as the served object withholds its extension)", which is the tree's text; it carries no remaining "withholds them"; its Round-1 paragraph describes the fold the way :4911-4933 reads. Clause-②: no and Metadata-protocol: none are on their own lines. Matches.
  5. No open_questions on the round-1 report.

④ Design judgment

Only the edited sentence is re-judged; every other sentence of GetPackageCustomizationsResponseSchema is as reviewed at b5aa7aa15 and the code it cites did not move. Tried against the mechanism: (i) plain case, the disabled package alone adds x: the extension is left out of the fold and x is gone from the served object; the list drops the entry. (ii) shared name, an enabled package also adds x: the disabled extension is left out, the kept one is folded, x is served as the kept package's; the list keeps the kept package's entry and drops the disabled one. (iii) owner patch, the disabled extension patches an owner-declared name: the extension is left out, the owner's name is served unpatched; the list drops the entry. In all three the served object withholds the disabled package's extension and the list does not list that package's fields. The sentence now says exactly that, in the same parenthetical form as the flow and nav clauses. The runtime docblock (packages.ts :3325-3326) still uses the looser shorthand; it is a code comment, not published wording, and not this PR's.

Must-fix: none. Should-fix: none new.

Gate verdicts on the new head, as read at 2026-11-05T21:12Z: 69 check-runs, every one completed: 59 success, 10 skipped, 0 failed, 0 pending. Each required context is success (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard, Type Check UI, Test UI, Build & E2E UI, Live E2E UI); Lint UI is skipped (no ui/ path in the diff); Check Changeset is success on both of its runs. Polled twice, three minutes apart; the one run pending on the first read (Check Changeset) had completed by the second, and nothing was read before every run had.

Implemented-by: claude/issue-30086-disabled-package-fields
Reviewed-by: session_01L6bhgTqqeHSwZYJmApbDCa

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#3 · os-sales · session_01L6bhgTqqeHSwZYJmApbDCa

维护者速读

  1. 改了什么:包定制读取接口(GET /api/v1/packages/:id/customizations)里"其他包贡献的条目"那一段的说明文字,补上一句:被停用的包加到对象上的字段不会列出。
  2. 为什么改:服务端早已不再列出、也不再展示这些字段(fix(objectql)!: withhold a disabled package's objectExtensions fields from the object they extend, and keep their columns #30362 已落地),可说明文字还写着"来自任何其他包的字段都会列出",作者和 AI 读规格时会以为停用后字段仍在。
  3. 风险与回滚:只改说明文字和由它生成的参考文档页,接口形状、键、默认值、导出都不变;回滚即还原本 PR 的两个提交。
  4. 先看:packages/spec/src/api/package-lifecycle.zod.ts 的 contributions 数组 describe(字段分句新增的括号说明,原因写的是"停用包的扩展整体不参与对象合成"),其次是重新生成的 content/docs/references/api/package-lifecycle.mdx 同一行。
  5. 两分钟验证:pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated,预期输出 "All 14 generated artifacts are up to date"。
  6. 需要你做的:无,席位落地。

席位意见:契约复审两轮都 PASS。第一轮的 should-fix 1 指出原因写宽了:停用包的扩展整体不参与合成,但同名字段若由所有者或另一个启用的包声明,仍会留在对象上。第 1 轮已把描述和变更集的同一句都改成"停用包的扩展被扣下",增量复审逐句核对为真。should-fix 2、3 是复审提的另立卡建议,席位不另立(理由见下)。

席位核对 — ACCEPT · domain:spec#3 · head 88a5f34377d6f5613018469de79dceb4e16d9794

  • Shape: 3 files (+13 −2), draft, base main, first card reference Fixes #30086. Two branch commits since the full review: 494b46d7e, a merge of origin/main (af8aab1b1), and 88a5f3437, the wording fix.
    • Hand-written: one .describe() string in packages/spec/src/api/package-lifecycle.zod.ts (the contributions array's field clause) and the changeset.
    • Regenerated: content/docs/references/api/package-lifecycle.mdx.
    • No key, shape, default, export or test assertion change. Nothing in packages/runtime or packages/objectql changes.
  • Premise measured: the claim's premise (6666645427) moved before dispatch. PR fix(objectql)!: withhold a disabled package's objectExtensions fields from the object they extend, and keep their columns #30362 (8cc709d5, an ancestor of the base) already makes the served object's fold withhold a disabled package's extension, and readBaseContributions (packages/runtime/src/domains/packages.ts) already skips that package's field contributors. So the card's reading (b) governs, no server work is owed, and the residue is exactly the array describe's field clause. The runtime behaviour is pinned at packages-disable-module-extension-fields.test.ts :326 / :336.
  • Gates:
  • Contract review:
    • 6668505211 on b5aa7aa15: PASS, Served-tier: CONTRACT_REVIEW_TIER, no must-fix, three should-fix:
      • S1: the reason "as the served object withholds them" claimed more than withholdDisabledExtensions does;
      • S2: the customizations read of a disabled base lists field and flow entries on objects the object read refuses, but no nav entries;
      • S3: a disabled .custom pair's members still appear in extensions.
    • Delta 6669701816 on 88a5f3437: PASS, nothing outstanding. It found:
      • the merge has no hand-resolved hunk and touches none of the three PR files;
      • the new clause and the changeset sentence are TRUE against withholdDisabledExtensions and resolveObject, including a field that the owner or a kept extension also declares;
      • dropping S2 and S3 leaves no sentence in the PR false;
      • the refreshed body quotes the clause exactly as the tree has it.
  • Declarations: Clause-②: no, Metadata-protocol: none, @objectstack/spec patch, the same in the body and in the changeset. A response schema is nothing a metadata author writes, so no accept set moves, and .describe() text in src/**/*.zod.ts ships in the published declarations, so the patch is owed.
  • Accepted deviations:
    • The dev also corrected the changeset sentence ("began withholding them" → "began withholding a disabled package's extension"). The order said to keep the changeset unless a sentence became false, and that one was false in the same cases S1 named.
    • The round-1 gate runner read the round-0 command file. The sorted lists are identical at 88a5f3437, and --ran re-derived and accounted for all 96.
    • origin/main was merged, not rebased.
    • mcp_calls: 0 in every round.
  • Carried: nothing filed. S2 and S3 were dropped, not carried. Neither leaves a false sentence in this PR's describe (the delta confirms it). Both are p4 traps on corner states (a disabled base package; a disabled .custom pair), and neither has a measured user cost. If either shows up in a real read, it is filed then, with the measurement.
  • Landing: not governed (check-governed-merges --pr 30642: 0 of 3 paths, 15 lines ≤ 3000). The hand-off is pr_ready plus automerge_enable, through the merge queue.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 21:16
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit fef212b Nov 5, 2026
70 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-30086-disabled-package-fields branch November 5, 2026 21:40
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/s tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant