Repository navigation
feat(spec, cli)!: bundled is the one plugin packaging strategy — manifest-deps is retired, and os plugin build refuses a native addon (#27123, slice 3 of 3, server half) - #27679
Conversation
…on refusal (red) Claude-Session: https://claude.ai/code/session_01L6bhgWLCWkh3MaLj1qTLCe Co-authored-by: Claude <noreply@anthropic.com>
… plugin build` refuses a native addon ADR-0025 v2 §3.3 (ruling B′, slice 3, server half): - spec: PluginPackagingSchema narrows to `bundled`; `manifest-deps` is a retired value refused at every manifest door with its prescription. D2 conversion manifest-packaging-manifest-deps-removed strips it from a stack's manifest and each packages[].manifest; D3 entry manifest-packaging-manifest-deps-retired and its STEP18 fragment carry it to the upgrade guide. The removal-version pin gains its JOIN row. The `packaging` liveness row is `planned` (one-member enum, no fork to read); the manifest state-count shard is regenerated. - cli: os plugin build drops both manifest-deps forks (externalize + pack package.json / lockfile) and refuses a native addon before writing the artifact: a `.node` module in the graph, a bundled package that declares a node-gyp build or ships a `.node` file, a `.node` file under assets/. - The archive layout docblock, the platform checklist item cli.plugin-manifest-build-contract (revision 4) and the generated reference pages follow. Claude-Session: https://claude.ai/code/session_01L6bhgWLCWkh3MaLj1qTLCe Co-authored-by: Claude <noreply@anthropic.com>
Its tree-scoped absence walk reads outside the package, so it runs under `@objectstack/spec#test:repo`, whose hash moves with the globs it walks (already declared in cross-package-test-inputs and turbo.json). Claude-Session: https://claude.ai/code/session_01L6bhgWLCWkh3MaLj1qTLCe Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 23 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 176 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 90e591f4893601fb57f29506683f6d3cb15af57c && git checkout 90e591f4893601fb57f29506683f6d3cb15af57c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e16a7af2e0fe3ab02d610733f76d68325b730e4f b0bfccdd74d06f63a04a92bfc6350d26c8e42218 && git checkout -B drift-repro e16a7af2e0fe3ab02d610733f76d68325b730e4f && git merge --no-ff b0bfccdd74d06f63a04a92bfc6350d26c8e42218
node scripts/docs-audit/affected-docs.mjs --json e16a7af2e0fe3ab02d610733f76d68325b730e4f
|
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F Contract-review-claim: session_01L6bhgUCizoYWQVJWjhdR6F · head b0bfccd · 2026-10-29T17:30Z |
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F Contract reviewServed-tier: Inputs: card #27123 body and all 21 comments (triage 6463804227 and 6498767357, ruling 6467932167, claim 6499525158 with amendments 6499637007 and 6503048893, the slice-1 and slice-2 records and hand-over 6496079064, os-dev-report 6503774579); PR #27679 body and file list (19 files, +929/−77); the net diff File surface. Sixteen of the nineteen files sit inside the claim plus its two amendments by name. Three do not, in letter: Tier. Check-runs at this reading. 32 success — among them ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each judged:
② Semver level
③ Boundary flagsos-dev-report 6503774579
④ Design judgmentEach in-scope file against the code it cites and the scenario it is accepted against:
Follow-ups for the seat to file (none blocks this head):
Implemented-by: VERDICT: PASS |
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F 维护者速读(席位终稿)
席位意见: ACCEPT。收窄覆盖每个 席位核对ACCEPT · head
|
Requested by Sales · project thread
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Part of #27123 (slice 3)
Ruling-ref: 6467932167
Clause-②: no (narrowing) —
PluginPackagingSchemadropsmanifest-deps, so every door that parses a plugin manifest now refuses a value it accepted, andos plugin buildrefuses a native.nodeaddon it used to pack (or fail on, unnamed)Metadata-protocol: narrowed
Skills-docs: none — no skill under
skills/**and no docs page outside the generated references teachesmanifest-depsor a native addon.content/docs/protocol/kernel/plugin-spec.mdxlistspackagingamong the optional manifest keys (still true) and its.ospluginformat list already names nopackage.json; the five generated reference pages are regenerated bygen:docs维护者速读
packaging只剩bundled一个值。manifest-deps改为退役值:所有解析插件清单的入口(os plugin build读的objectstack.plugin.json、defineStack的manifest、制品的packages[].manifest、POST /packages安装请求体)都按名拒绝它并给出迁移说明,TypeScript 也不再接受。os plugin build删掉了manifest-deps的两个分叉(依赖外置、打包package.json和锁文件),并在写制品之前拒绝原生.node插件:入口导入的.node文件、带 node-gyp 构建或自带.node的依赖包、assets/下的.node文件,逐个点名,不留制品。配套 D2 转换、D3 条目、liveness 行(改为planned)、平台清单条目和参考页。manifest-deps构建出的制品把依赖留在包外,但从来没有任何环节去安装它们,插件加载必然缺依赖;原生插件既不能打进 bundle,也不是每个宿主都能运行。packaging: 'manifest-deps'的清单会被拒绝;仓库内没有任何来源这样写(树级缺席扫描为零)。stack 源码可用os migrate meta --from 17 --write自动删掉这个键,objectstack.plugin.json需手工删。带原生插件的插件不能再构建。已声明Clause-②: no (narrowing)、Metadata-protocol: narrowed,changeset 带 BREAKING 横幅和 ADR-0087registered处置,spec 为 minor、cli 为 patch。回滚 = revert 本 PR。packages/cli/src/commands/plugin/build.ts的findNativeAddons和拒绝块;packages/spec/src/kernel/manifest.zod.ts的PACKAGING_RETIRED/PluginPackagingSchema;packages/spec/src/conversions/registry.ts的manifestPackagingManifestDepsRemoved;packages/spec/liveness/manifest.json的packaging行。pnpm --filter @objectstack/cli exec vitest run --project unit test/plugin-build-bundled-only.test.ts,预期 9/9 通过(它从 spec 的dist/读ManifestSchema,先pnpm --filter @objectstack/spec build);再运行pnpm --filter @objectstack/spec exec vitest run --project repo src/kernel/manifest-packaging-retirement.test.ts,预期 15/15 通过。What changes
@objectstack/specPluginPackagingSchemaisenumWithRetiredValues(['bundled'], PACKAGING_RETIRED).manifest-depsis refused atpackagingwith its prescription (removed in 18.3.0; delete the key;os migrate meta --from 17for a stack source; anobjectstack.plugin.jsonis edited by hand) at every door that parses a manifest:ManifestSchema, a stack'smanifestthroughdefineStack(STACK_SCHEMA_INVALID, pathmanifest.packaging),ArtifactPackageEntrySchema(packages[].manifest), and the install body (PackageInstallManifestSchemaextendsManifestSchema). The value is out of the input type, so authoring it is a compile error.manifest-packaging-manifest-deps-removed(toMajor: 18,retiredFromLoadPath: true,retiredAfter: '18.2.0', order 80): deletespackaging: 'manifest-deps'from a stack'smanifestand from eachpackages[].manifest, one notice each (to: '(removed)'), copy-on-write, idempotent, with its fixture.manifest-packaging-manifest-deps-retirednaming the conversion, and itsSTEP18_RATIONALEfragment (order 114) inregistry.ts.template.retired-key-removal-version.pin.test.tsgains the conversion's JOIN row.packagingrow isplannedon the one-member precedent (sharing_rule.type,connector.authentication): nothing dispatches on the value any more, and the refusal is real, so neitherlivenordead. The note keeps the history of the 2026-08-28livegrade, which rested on the two forks this PR deletes, and the flip condition. Themanifeststate-count shard is regenerated (gen:liveness-counts: live 29 → 28, planned 0 → 1).vitest.repo-tests.jsonlists the new pin: its tree-scoped absence walk reads outside the package, over globs already declared inscripts/cross-package-test-inputs.mjsandturbo.json.@objectstack/clios plugin build: the esbuild call externalizes@objectstack/*only; thepackaging === 'manifest-deps'externalize fork and thepackage.json/pnpm-lock.yamlpacking fork are gone. TheLoadedline still echoespackaging(defaultbundled)..nodemodule as empty so the graph completes, and the metafile is read for three signals: a.nodemodule the bundle reaches (named with its importers); a bundled package (the directory after the lastnode_modulesin an input path) that hasbinding.gyp,gypfile: trueor a shipped.nodefile; and a.nodefile staged fromassets/. Every hit is listed under✗ Native addon refused: …, exit 1, no.osplugin.osplugin.ts: the archive-layout docblock no longer lists themanifest-depsrows.Ledgers and generated pages
cli.plugin-manifest-build-contract, revision 4: the fork clause becomes the bundled-only clause (with themanifest-depsrefusal), a native-addon clause and probes 6f–6h are added, and steps, fixtures, knownGaps, negatives and sources follow. The integrity and trust-tier negatives are untouched and were not re-verified here.gen:docsregeneratedcontent/docs/references/{kernel/manifest, kernel/package-registry, api/package-api, api/package-api-assembled, api/protocol}.mdx:Enum<'bundled'>and the new describe text. No other generated artifact moved (check:generatednames only these).Why
ADR-0025 v2 §3.3 (maintainer decision Q6, ruling B′) makes
bundledthe only packaging strategy and disallows native addons, with M1 owing the build refusal.manifest-depsproduced an artifact that kept its dependencies outside the bundle and carriedpackage.jsonfor an install step that nothing ever ran (os plugin installand the boot-time loader importdist/index.mjsas packed), so such a plugin could not load its dependencies.Tests
Red first on
df778ab4(tests only, through the real doors), then green on the fix.packages/cli/test/plugin-build-bundled-only.test.ts(new, 9 cases) drivesPluginBuild.run([dir])over real on-disk projects with their ownnode_modules. Every refusal asserts exit 1, the subject named, and no artifact on disk.bundledand unset: the dependency is inlined, the archive is exactlySIGNATURE,dist/index.mjsand the manifest, with nopackage.jsonor lockfile and no surviving import.packaging: 'manifest-deps': refused underobjectstack.plugin.json is invalid, the✗line namespackaging..nodefile the entry imports (file and importer named); a dependency whosemainisaddon.node; a bundled dependency withbinding.gyp, withgypfile: true, and withprebuilds/…/fake.node, each locating its binary at run time (the package is named, the pure dependency beside it is not);assets/bin/addon.node(named;assets/icon.svgis not).packages/spec/src/kernel/manifest-packaging-retirement.test.ts(new, 15 cases,repoproject): the vocabulary is exactlybundled; each door refuses with the prescription (ManifestSchema,ArtifactPackageEntrySchema,defineStack's 422 envelope atmanifest.packaging), with a control that a near miss (bundle) keeps zod's own message;bundledparses and absence stays absent;@ts-expect-errorat the authoring site; the D2 conversion (strips both places, leavesbundled, notices, copy-on-write, idempotent, not replayed bynormalizeStackInput, wired intoMIGRATIONS_BY_MAJOR[18]withretiredAfter: '18.2.0'); the D3 entry names it; and a tree-scoped absence walk overpackages,examples,skills,content,scripts(anti-vacuity: more than 1000 files visited, more than 3packaging: 'bundled'mentions seen), with each structural exclusion's reason stated.retired-key-removal-version.pin.test.ts: the JOIN row for the new conversion.Acceptance notes
registered. The changeset carries<!-- adr-0087: registered manifest-packaging-manifest-deps-removed, manifest-packaging-manifest-deps-retired -->. The narrowing has a mechanical migration for a stack source, sonot-requiredwould contradict the BREAKING FROM→TO. The conversion, the D3 entry, the STEP18 fragment, the pin row and the checklist edit were added under the seat's claim increment 6499637007, and thevitest.repo-tests.jsonline under increment 6503048893.@objectstack/specisminor(Metadata-protocol: narrowedmust grade at least one packageminor);@objectstack/cliispatchwith the BREAKING banner, the slice-2 precedent for a non-protocol narrowing.planned, notliveordead. Argued in the row's note: no fork reads the value, the refusal is enforcement, and the ADR keeps the key with one live value. The liveness README'smanifestnotes cell names nopackagingset, so it was left alone (it is outside this PR's claim)..nodefile it never loads on the plugin's code path is still refused; the remedy in the message (replace or drop it) applies.os migrate metaandobjectstack.plugin.json. The D2 conversion runs over stack sources. Anobjectstack.plugin.jsonis not a sourceos migrate metareads, so the prescription tells the author to delete the key there by hand.claude/project-thread-c3umeu, which claim 6499525158 names.Known limitations
ui/packages/app-shell/src/console/marketplace/marketplaceApi.tsstill types cloud listing data as'bundled' | 'manifest-deps', anddocs/design/plugin-distribution.mdand ADR-0025 mentionmanifest-deps. All three are outside this slice (the ADR edit is the Tier H half), and the absence walk does not reachui/,docs/or.tsx.Verification
Head
b0bfccdd= fixc349e372+ one merge oforigin/main(b3b4af13, merge baseefa2915e) + the roster line. Heavy runs underscripts/pm/os-verify-lock.sh(slotissue-27123,NODE_OPTIONS=--max-old-space-size=4096).Red, then green
df778ab4(fix reverted in the tree, specdist/from before the fix): spec pinTests 8 failed | 7 passed (15)(expected [ 'bundled', 'manifest-deps' ] to deeply equal [ 'bundled' ], each door accepting the value, the conversion and the D3 entry absent); cli pinTests 7 failed | 2 passed (9)(manifest-depsbuilt an artifact; the.nodeimport failed as a bareBundle failed; the run-time-located andassets/addons built an artifact). The twobundled/unset controls were green ondf778ab4too.15 passed (15)(repoproject, final head); cli pin9 passed (9)after the spec rebuild.Suites and typecheck
@objectstack/spec--project local, full, on the fix: 751 files passed; the 24 failures were all inscripts/liveness/check-liveness.test.ts, the stalemanifestcount shard. Aftergen:liveness-counts:check:livenessexit 0 andscripts/liveness/12 passed (12),359 passed (359).src/kernel/manifest-packaging-retirement.test.ts,src/shared/,src/conversions/,src/migrations/,scripts/conversions-major18-merge.test.ts,scripts/step18-rationale-merge.test.ts,scripts/liveness/:81 passed (81),2321 passed (2321). cliplugin-build-bundled-only,osplugin,plugin-sign,plugin-publish:25 passed (25).@objectstack/cli--project unit, full:350 passed (350),5063 passed (5063).--project integrationonserve-installed-plugins,plugin/installandschema-migrate.one-shot-family:153 passed (153). (A whole-package run of both projects exceeded the 30-minute background limit and was stopped, so it is not counted.)typecheckof spec and cli, each withcheck:test-typecheck: exit 0.@objectstack/spectest:repoon the final head (the new pin now runs here):87 passed (87),1448 passed (1448).Ablations (scripts/ablation-replace.mjs, restored blob == HEAD)
if (nativeAddons.length > 0) {→if (false && …), build.ts onc349e372): mutation landed (anchor 1 → 0) → cli pin6 failed | 3 passed (9), exactly the six addon cases → restored, blobf5cb8ec5== HEAD,git diff HEADempty.enumWithRetiredValues(['bundled'], PACKAGING_RETIRED)→enumWithRetiredValues(['bundled', 'manifest-deps'], { 'ablation-27123-marker': … })): spec pins6 failed | 68 passed (74)(the vocabulary, the three doors, the authoring-site case and the removal-version JOIN row); spec rebuilt,ablation-dist-preflight.mjs @objectstack/spec ablation-27123-marker→ marker present in 2 built files, exit 0; cli pin1 failed | 8 passed (9), exactly themanifest-depscase → restored, blobed95910f== HEAD,git diff HEADempty; after the rebuild,--absent→ marker absent from all 580 built files, tree clean, exit 0.Derived gates and the --ran reconciliation
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack→ 111 commands, the same list onb3b4af13and on the final head; each run with its exit captured before any pipe.check:cross-package-test-inputsexit 1 (the new pin was missing fromvitest.repo-tests.json; fixed inb0bfccddunder increment 6503048893),check-plugin-teardown-shape --self-testexit 3 (its pinned fixture commit was not in the shallow clone; fetched),check:dual-build-cjs-loadsexit 3 (six packages unbuilt; built).check-changeset-no-major,check-adr-0087-registrationandcheck-empty-changesetwere also rerun on the final head.--ran:111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0.check-adr-0087-registration --base origin/main: "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition … [BREAKING+bang+clause-②-narrowing+metadata-protocol-narrowed] registered manifest-packaging-manifest-deps-removed, manifest-packaging-manifest-deps-retired".check-changeset-no-major --base origin/main --eventwith this body as thepull_requestpayload: nomajor; LEVEL AXIS ✓ ("declares clause-②no (narrowing)and metadata-protocolnarrowed, and it grades a package whose published source … it moves atminoror above":@objectstack/spec: minor;@objectstack/cli: patchnot refused), exit 0.Dogfood, lint, checklist, merge-tree
OS_TEST_SHARD: closure built withturbo run build --filter='@objectstack/dogfood^...'(68/68 tasks), thenpnpm --filter @objectstack/dogfood test: VERDICTcommand-exit 0,351 passed | 3 skipped (354)files,2822 passed | 20 skipped (2842)tests.pnpm lint(repo-wide,--no-inline-config): exit 0. eslint overpackages/cli packages/spec: exit 0.check-platform-checklist: full gate OK (352 items);--changed-from origin/mainOK.check:generated: onlycontent/docs/references/**was stale, regenerated withgen:docs; the thirteen source audits it names exit 0 (check:skill-examplesonce@objectstack/client-reactwas built,check:scripts-typecheckinside the spectypecheck).git merge-treeagainst every open PR: none changes a file this PR changes. 41 of 43 merge clean; feat(platform-objects): sys_exchange_rate — an organization's dated exchange rates, admin-written and audited, with a Setup list under Localization #27246 and fix(fields,components): a date typed in a form that the picker cannot read is a field error that refuses submit #27623 conflict withorigin/mainitself, in files this PR does not touch.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F