chore(bootstrap): publishing metadata, required registry param, exports test, docs - #720
Merged
sroussey merged 3 commits intoAug 8, 2026
Conversation
…ated-context example
The README and `.claude/CLAUDE.md` both stated the package's raison d'être as
"Workglow does not self-register defaults at import time" / "Nothing
self-registers at import time". That is inverted: all 14 `register*Defaults`
functions run at module scope against `globalServiceRegistry`, and each defaults
its `registry` parameter to it. A reader who trusts the old text concludes the
global registry is empty until `bootstrapWorkglow()` runs and will mis-diagnose
ordering bugs — `getLogger()` also lazily self-registers, so "it worked without
bootstrap" looks impossible.
State the real invariant instead: registrars self-register on the global
registry when their module happens to be imported, so what is populated is
import-order dependent; `bootstrapWorkglow()` is the guarantee that installs the
full set in dependency order; an isolated registry gets nothing until
`registerAllDefaults(registry)` is called explicitly.
Second defect: the isolated-context example passed an option no run API accepts.
`Task.run(overrides, runConfig)` takes input overrides first, and neither
`IRunConfig` nor `TaskGraphRunConfig` has a `context` key — `IRunConfig` carries
`registry?: ServiceRegistry`. With a loosely typed Input, `run({ context: ctx })`
is treated as an input override named `context`, `TaskRunner` keeps its
`globalServiceRegistry` default, and `ctx.dispose()` tears down a registry the
run never touched — the exact process-wide-mutation hazard the rest of the
README argues against. Corrected to `run({}, { registry: ctx.registry })` in the
README and in the `createOrchestrationContext` JSDoc, and pinned by a new
doc-conformance test that asserts both the correct routing and the old snippet's
silent fallback to the global registry.
Also splits `ServiceRegistry` into a top-level `import type` in
`registerAllDefaults.ts` (it is used solely as a parameter type), and syncs
`bun.lock` with the 0.3.38 version bump.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6huUY7hSkRbjun1P9HKsz
…ts test, docs Follow-up polish on the @workglow/bootstrap extraction. No correctness, security, or data-integrity change — the registration set and order are untouched. - Add the missing `license` field (Apache-2.0) and a LICENSE file. The package sets `publishConfig.access: "public"` and its sources carry SPDX Apache-2.0 headers, but npm would have listed it as unlicensed. (Note: no other workspace in this repo declares a `license` field either — a repo-wide sweep is worth doing separately.) - Align the version to 0.3.38. It was 0.3.37 while root and all 13 siblings were 0.3.38, so `bunset --patch --all` would have opened a permanent one-patch offset between bootstrap and everything that depends on it. - Make `registerAllDefaults(registry)` required again, as it was when the function was module-private. It mutates whichever container it is handed, and it is now exported three ways; a defaulted global turns "installed defaults on the wrong registry" from a compile error into silent behavior. This matches the repo convention of forcing callers to be explicit rather than defaulting. - `vitest.setup.ts` now calls `bootstrapWorkglow()` (equivalent with no logger option). It cannot pass `globalServiceRegistry` explicitly: `@workglow/util` does not resolve from the repo root under the isolated linker, and importing `packages/util/src` directly would load a second copy of the module and populate a different registry object than the tests read. - Add `packages/test/src/test/util/BootstrapPackageExports.test.ts`, covering the published contract: every `exports` leaf resolves to a file that exists, and the node entry point really exports the bootstrap API. Nothing in CI exercised the build output before, so a typo'd exports path or a build that emitted nothing would have shipped silently. - Add README (including the all-defaults dependency-weight tradeoff) and CHANGELOG, and document the package in .claude/CLAUDE.md so contributors add registrations here rather than in the now-inert re-export shim. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
sroussey
force-pushed
the
claude/wonderful-turing-rjtcnx-bootstrap-polish
branch
from
August 8, 2026 18:21
dd97b0d to
c62b649
Compare
…-p4d8qo docs(bootstrap): correct the self-registration invariant and the isolated-context example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Severity
Every item here is MEDIUM or LOW. This is publishing hygiene, API shape, CI coverage, and docs — polish, not a gate.
PR #672 has no correctness, security, or data-integrity defect. A reviewer verified that:
registerAllDefaults(14 calls, no security delta);register*Defaultsgenuinely forward theregistryparameter, with none falling back to the global;A1 — missing
licensefield (MEDIUM) — plan premise was wrong, adaptedpackages/bootstrap/package.jsonhad nolicensekey while settingpublishConfig.access: "public". Without it,bun run publish-workspacespushes to npm with license metadata absent, so the registry page and every SBOM/license scanner report it unlicensed — contradicting the SPDX Apache-2.0 headers in its own source.Correction to the plan I was given: it asserted "all 13 existing
packages/*declareApache-2.0". They do not.grep -n '"license"' packages/*/package.json providers/*/package.json package.jsonreturns zero matches — no workspace in this repo declares alicensefield, and the cited key-order anchor (packages/knowledge-base/package.json:4-5) isversion/repository, not a license key.I applied the fix anyway, because it stands on its own merits for a package that is
access: "public"and carries SPDX headers — but flagging that this is now the only package with the field, and that the same gap exists across the other 13 packages and 28 providers. Worth a separate repo-wide sweep; deliberately out of scope here.Also added
packages/bootstrap/LICENSE(byte-identical topackages/knowledge-base/LICENSE). 8 of 14 packages ship one; npm includesLICENSEregardless of thefilesfield.A2 — version drift (MEDIUM)
packages/bootstrapwas0.3.37; root and every other workspace are0.3.38. Set to0.3.38.Left alone,
bun run bunset(--patch --all) would bump bootstrap to 0.3.38 while everything else went to 0.3.39, soworkglow@0.3.39would publish a@workglow/bootstrap@0.3.38dependency and the offset would persist across every future release.A3 — restored the required
registryparameter (MEDIUM)registerAllDefaultswas module-private on main with a required(registry: ServiceRegistry). #672 exported it from@workglow/bootstrap, fromworkglow/bootstrap, and from theworkglowroot barrel (packages/workglow/src/common.ts:30) — while making the argument optional (= globalServiceRegistry).Dropped the default.
globalServiceRegistryis no longer imported in that file.Rationale: the repo's own convention is "
T | undefinedoverT?optional — force callers to be explicit", and a defaulted global-container argument is exactly the implicit ambient state that rule prevents. Making the parameter optional at the same moment the function goes from module-private to triple-exported converts a compile error into a silent cross-tenant registry mutation: a caller who builds an isolated context and then callsregisterAllDefaults()gets no diagnostic, just defaults installed on the wrong registry. The convenience saved one identifier at one call site in the entire repo.The root-barrel export is kept — with a required parameter it is no longer hazardous.
Consumer consequence:
registerAllDefaults()with no argument is now a TypeScript error. Callers writeregisterAllDefaults(globalServiceRegistry)or, preferably,bootstrapWorkglow()/createOrchestrationContext().@workglow/bootstraphas never been published, so this breaks zero real consumers — and the window to fix it closes at first publish.A3 caller update — plan instruction was wrong, adapted
The plan said to update
vitest.setup.tstoimport { globalServiceRegistry } from "@workglow/util"and callregisterAllDefaults(globalServiceRegistry). That does not work, for the same reason #672's own comment in that file gives for importing by source path: the isolated linker keeps workspace packages out of the repo root'snode_modules. Verified directly —Reaching into
packages/util/src/di/ServiceRegistryinstead would be worse: that loads a second copy of the module, so the setup file would populate a differentglobalServiceRegistryobject than the one tests read through@workglow/util, and every test depending on registered defaults would fail.So the setup file now calls
bootstrapWorkglow()from the bootstrap package's source path. It is exactly equivalent — with nologgeroption,bootstrapWorkglowis a call toregisterAllDefaults(globalServiceRegistry)and nothing else — and it resolves@workglow/utilcorrectly, because the import happens from insidepackages/bootstrap, where the linker did place it. It is also the public entry point, which is what a bootstrap harness should be using.A4 — CI coverage of the published contract (MEDIUM)
Nothing in CI exercised the package's build output. The vitest harness imports the raw source module; the package ships no test files despite
"test": "bun test"; the only dist consumer isworkglow/build.ts's collision detector. So a typo in theexportsmap, or abuild-browserthat silently emitted nodist/browser.js, would ship to npm andimport { bootstrapWorkglow } from "@workglow/bootstrap"would throwERR_PACKAGE_PATH_NOT_EXPORTEDwith no CI signal.Added one file:
packages/test/src/test/util/BootstrapPackageExports.test.ts. Both routing claims verified before relying on them —scripts/test.ts:79maps sectionutiltopackages/test/src/test/util, and.github/workflows/test.yml:195-211runstest-vitest-unitwithneeds: build, downloading thebuild-outputartifact, so realdist/is present.It asserts:
exports(recursing through thereact-native/browser/buncondition objects) resolves to a file that exists — catching a typo'd path and a build that emitted nothing;"."→ defaultimporttarget (dist/node.js) exportsbootstrapWorkglow,createOrchestrationContext, andregisterAllDefaults. Imported by file URL viapathToFileURL, not by the@workglow/bootstrapspecifier, sidestepping the isolated-linker problem above.bun run use-sourcestubs satisfy both assertions, so source mode is unaffected.A5 — docs (LOW)
packages/bootstrap/README.mdandpackages/bootstrap/CHANGELOG.md(13 of 14 packages already had both; bootstrap was the only one without)..claude/CLAUDE.md:bootstrapadded to the dependency-graph block, and a new "Key packages" entry stating that the implementation lives in@workglow/bootstrapand thatpackages/workglow/src/bootstrap.tsis now a pure re-export shim — otherwise a contributor adding a default registration edits the dead shim.On the
filesarray (["dist", "src/**/*.md"], which does not list root-level.md): all 13 sibling packages ship root-levelREADME.md/CHANGELOG.mdunder the identical array, so bootstrap now matches sibling tarball behavior exactly — npm always includesREADMEandLICENSEregardless offiles. Nofileschange made.Note on the graph placement: bootstrap depends on
ai/knowledge-base/mcp, not onproviders/*, so it is listed as a sibling ofproviders/*on the tier belowai, rather than beneath it.A6 — dependency weight (LOW, documented only)
@workglow/bootstrappulls@workglow/mcp(which has a harddependenciesentry on@modelcontextprotocol/sdk— verified),@workglow/ai, and@workglow/knowledge-base. A light consumer wanting only logger/task/storage defaults installs the MCP SDK and the full AI layer.Entry points deliberately not split — the package registers all defaults by design, and splitting would reintroduce the drift #672 was extracted to fix. The tradeoff is recorded in the new README, with the guidance that consumers wanting a subset should call the individual
register*Defaults(registry)functions directly.A7 — build ordering (LOW, note only —
turbo.jsonunchanged)turbo.json:7-10givesbuild-jsonly^build-types, whilepackages/workglow/build.ts:30runtime-imports every barrel star-spec viaassertNoExportCollisions— now including./bootstrap→@workglow/bootstrap→dist/node.js. On a coldbun run rebuildwith unlucky scheduling this can fail to resolve. Pre-existing for@workglow/ai; this PR's parent adds one more edge.Proposed follow-up (not applied here):
"build-js": { "dependsOn": ["^build-types", "^build-js"] }. The concurrency cost should be measured before adopting it.Verification
Ran, and observed passing:
bun install— clean,bun.lockunchanged.bun run build— 86/86 tasks, exit 0 (includesbuild-types).bun run build:types— 42/42 tasks.npx turbo run build-types --filter @workglow/test --force— 37/37, confirming the new test file type-checks (packages/testhad it cached otherwise).npx vitest run packages/test/src/test/util/BootstrapPackageExports.test.ts— 3/3 passed."."import target to./dist/nodee.js; 2 of 3 assertions failed (missingreported"." [import] -> ./dist/nodee.js, and the dynamic import threwCannot find module). Restored and re-verified.rg -n 'registerAllDefaults\(\s*\)' --type ts— no matches outsidedist/.jq -r .version packages/*/package.json providers/*/package.json examples/*/package.json package.json | sort -u— exactly one line,0.3.38.jq -r .license packages/bootstrap/package.json—Apache-2.0.npx prettier --checkon all changed files — clean.npx eslinton changed sources — clean (vitest.setup.tsis not covered by the eslint config).bun run test:vitest:unit) — 434 files passed | 2 skipped (436), 5048 tests passed | 47 skipped, exit 0, including the newBootstrapPackageExportsfile. This also proves the reworkedvitest.setup.tsstill populates the global registry: the suite depends on those defaults being present, and a second-copyglobalServiceRegistrywould have failed it broadly.Not verified: behavior of
bun run bunset/publish-workspacesagainst a real npm registry (neither was executed); CI itself, which will run on this PR.🤖 Generated with Claude Code
Generated by Claude Code