Skip to content

chore(bootstrap): publishing metadata, required registry param, exports test, docs - #720

Merged
sroussey merged 3 commits into
claude/libs-issues-triage-prs-mh6x2o-574from
claude/wonderful-turing-rjtcnx-bootstrap-polish
Aug 8, 2026
Merged

chore(bootstrap): publishing metadata, required registry param, exports test, docs#720
sroussey merged 3 commits into
claude/libs-issues-triage-prs-mh6x2o-574from
claude/wonderful-turing-rjtcnx-bootstrap-polish

Conversation

@sroussey

@sroussey sroussey commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

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:

  • the registration set and order are identical across the old production body, the old test body, and the new registerAllDefaults (14 calls, no security delta);
  • all 14 register*Defaults genuinely forward the registry parameter, with none falling back to the global;
  • there is no dependency cycle;
  • the re-export shim preserves the full public surface, including types.

A1 — missing license field (MEDIUM) — plan premise was wrong, adapted

packages/bootstrap/package.json had no license key while setting publishConfig.access: "public". Without it, bun run publish-workspaces pushes 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/* declare Apache-2.0". They do not. grep -n '"license"' packages/*/package.json providers/*/package.json package.json returns zero matches — no workspace in this repo declares a license field, and the cited key-order anchor (packages/knowledge-base/package.json:4-5) is version / 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 to packages/knowledge-base/LICENSE). 8 of 14 packages ship one; npm includes LICENSE regardless of the files field.

A2 — version drift (MEDIUM)

packages/bootstrap was 0.3.37; root and every other workspace are 0.3.38. Set to 0.3.38.

Left alone, bun run bunset (--patch --all) would bump bootstrap to 0.3.38 while everything else went to 0.3.39, so workglow@0.3.39 would publish a @workglow/bootstrap@0.3.38 dependency and the offset would persist across every future release.

A3 — restored the required registry parameter (MEDIUM)

registerAllDefaults was module-private on main with a required (registry: ServiceRegistry). #672 exported it from @workglow/bootstrap, from workglow/bootstrap, and from the workglow root barrel (packages/workglow/src/common.ts:30) — while making the argument optional (= globalServiceRegistry).

Dropped the default. globalServiceRegistry is no longer imported in that file.

Rationale: the repo's own convention is "T | undefined over T? 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 calls registerAllDefaults() 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 write registerAllDefaults(globalServiceRegistry) or, preferably, bootstrapWorkglow() / createOrchestrationContext(). @workglow/bootstrap has 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.ts to import { globalServiceRegistry } from "@workglow/util" and call registerAllDefaults(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's node_modules. Verified directly —

createRequire("<root>/vitest.setup.ts").resolve("@workglow/util")
  → MODULE_NOT_FOUND

Reaching into packages/util/src/di/ServiceRegistry instead would be worse: that loads a second copy of the module, so the setup file would populate a different globalServiceRegistry object 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 no logger option, bootstrapWorkglow is a call to registerAllDefaults(globalServiceRegistry) and nothing else — and it resolves @workglow/util correctly, because the import happens from inside packages/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 is workglow/build.ts's collision detector. So a typo in the exports map, or a build-browser that silently emitted no dist/browser.js, would ship to npm and import { bootstrapWorkglow } from "@workglow/bootstrap" would throw ERR_PACKAGE_PATH_NOT_EXPORTED with 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:79 maps section util to packages/test/src/test/util, and .github/workflows/test.yml:195-211 runs test-vitest-unit with needs: build, downloading the build-output artifact, so real dist/ is present.

It asserts:

  1. every leaf string under exports (recursing through the react-native / browser / bun condition objects) resolves to a file that exists — catching a typo'd path and a build that emitted nothing;
  2. the "." → default import target (dist/node.js) exports bootstrapWorkglow, createOrchestrationContext, and registerAllDefaults. Imported by file URL via pathToFileURL, not by the @workglow/bootstrap specifier, sidestepping the isolated-linker problem above.

bun run use-source stubs satisfy both assertions, so source mode is unaffected.

A5 — docs (LOW)

  • New packages/bootstrap/README.md and packages/bootstrap/CHANGELOG.md (13 of 14 packages already had both; bootstrap was the only one without).
  • .claude/CLAUDE.md: bootstrap added to the dependency-graph block, and a new "Key packages" entry stating that the implementation lives in @workglow/bootstrap and that packages/workglow/src/bootstrap.ts is now a pure re-export shim — otherwise a contributor adding a default registration edits the dead shim.

On the files array (["dist", "src/**/*.md"], which does not list root-level .md): all 13 sibling packages ship root-level README.md / CHANGELOG.md under the identical array, so bootstrap now matches sibling tarball behavior exactly — npm always includes README and LICENSE regardless of files. No files change made.

Note on the graph placement: bootstrap depends on ai / knowledge-base / mcp, not on providers/*, so it is listed as a sibling of providers/* on the tier below ai, rather than beneath it.

A6 — dependency weight (LOW, documented only)

@workglow/bootstrap pulls @workglow/mcp (which has a hard dependencies entry 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.json unchanged)

turbo.json:7-10 gives build-js only ^build-types, while packages/workglow/build.ts:30 runtime-imports every barrel star-spec via assertNoExportCollisions — now including ./bootstrap@workglow/bootstrapdist/node.js. On a cold bun run rebuild with 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.lock unchanged.
  • bun run build — 86/86 tasks, exit 0 (includes build-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/test had it cached otherwise).
  • npx vitest run packages/test/src/test/util/BootstrapPackageExports.test.ts — 3/3 passed.
  • A4 negative check, actually performed: temporarily rewrote the "." import target to ./dist/nodee.js; 2 of 3 assertions failed (missing reported "." [import] -> ./dist/nodee.js, and the dynamic import threw Cannot find module). Restored and re-verified.
  • rg -n 'registerAllDefaults\(\s*\)' --type ts — no matches outside dist/.
  • 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.jsonApache-2.0.
  • npx prettier --check on all changed files — clean. npx eslint on changed sources — clean (vitest.setup.ts is not covered by the eslint config).
  • Full vitest unit tier (bun run test:vitest:unit) — 434 files passed | 2 skipped (436), 5048 tests passed | 47 skipped, exit 0, including the new BootstrapPackageExports file. This also proves the reworked vitest.setup.ts still populates the global registry: the suite depends on those defaults being present, and a second-copy globalServiceRegistry would have failed it broadly.

Not verified: behavior of bun run bunset / publish-workspaces against a real npm registry (neither was executed); CI itself, which will run on this PR.


🤖 Generated with Claude Code


Generated by Claude Code

claude added 2 commits August 8, 2026 11:21
…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
sroussey force-pushed the claude/wonderful-turing-rjtcnx-bootstrap-polish branch from dd97b0d to c62b649 Compare August 8, 2026 18:21
…-p4d8qo

docs(bootstrap): correct the self-registration invariant and the isolated-context example
@sroussey
sroussey merged commit 4ab1fef into main Aug 8, 2026
3 checks passed
@sroussey
sroussey deleted the claude/wonderful-turing-rjtcnx-bootstrap-polish branch August 13, 2026 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants