docs(bootstrap): correct the self-registration invariant and the isolated-context example - #725
Merged
sroussey merged 1 commit intoAug 8, 2026
Conversation
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||
…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
sroussey
force-pushed
the
claude/bootstrap-docs-fixes-p4d8qo
branch
from
August 8, 2026 18:21
99be055 to
6f80e70
Compare
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.
Stacked on #720 — base is
claude/wonderful-turing-rjtcnx-bootstrap-polish, notmain.#720's mechanical work was reviewed and is correct: the version bump, the
exports/filesshape, and makingregisterAllDefaults'sregistryparameter required all hold up. This PR is documentation corrections only, plus one import-style fix and a test that pins the corrected snippet.1. "Nothing self-registers at import time" is false
packages/bootstrap/README.md:5and.claude/CLAUDE.md:236stated the package's raison d'être as "Workglow does not self-register defaults at import time" / "Nothing self-registers at import time, so a runtime has to install the defaults before any task runs".All 14
register*Defaultsfunctions do self-register onglobalServiceRegistryat module scope, and each also defaults itsregistryparameter to it. Verified individually (line numbers as ofdd97b0d):packages/util/src/logging/LoggerRegistry.ts:59packages/util/src/di/InputResolverRegistry.ts:43packages/util/src/di/InputCompactorRegistry.ts:42packages/util/src/telemetry/TelemetryRegistry.ts:46packages/util/src/worker/WorkerManager.ts:616packages/util/src/media/imageHydrationResolver.ts:42packages/util/src/credentials/CredentialStoreRegistry.ts:96packages/ai/src/model/ModelRegistry.ts:94packages/ai/src/provider/AiProviderRegistry.ts:400packages/knowledge-base/src/knowledge-base/KnowledgeBaseRegistry.ts:177packages/mcp/src/util/_server-registry/McpServerRegistry.ts:124packages/storage/src/tabular/TabularStorageRegistry.ts:96packages/task-graph/src/task/TaskRegistry.ts:216packages/task-graph/src/task-graph/TransformRegistry.ts:38(Two entries differ from the numbers a reader might expect:
WorkerManager.tsis:616andAiProviderRegistry.tsis:400on this branch.)Why it matters: a reader who trusts the old text concludes the global registry is empty until
bootstrapWorkglow()runs, and will mis-diagnose ordering bugs.getLogger()(LoggerRegistry.ts:66-71) additionally self-registers lazily when the registry lacksLOGGER, so "it worked without calling bootstrap" looks impossible under the old doc.Replaced with the real invariant in both files: registrars self-register on the global registry when their module happens to be imported, so what is populated depends on the import graph and is import-order dependent;
bootstrapWorkglow()is the guarantee that installs the full set in dependency order, idempotently; an isolated registry gets nothing untilregisterAllDefaults(registry)(orcreateOrchestrationContext()) is called explicitly. The.claude/CLAUDE.mdedit is confined to that one inverted sentence — the "implementation lives here / shim" sentences are untouched.2. The isolated-context example passed an option no run API accepts
README.md:52-56(and thecreateOrchestrationContextJSDoc atbootstrapWorkglow.ts:73, inherited from main) showed:Task.run(overrides, runConfig)takes input overrides first (packages/task-graph/src/task/Task.ts:222), and neitherIRunConfig(ITask.ts:120) norTaskGraphRunConfig(TaskGraph.ts:39) has acontextkey —IRunConfigcarriesregistry?: ServiceRegistry(ITask.ts:252).Failure mode, confirmed by running it: with a loosely typed
Inputthe object is accepted as an input override namedcontext;TaskRunnerkeeps itsglobalServiceRegistrydefault (TaskRunner.ts:140) because it only overrides whenconfig.registryis set (TaskRunner.ts:1012); the run therefore mutates and reads process-wide state, andctx.dispose()tears down a registry nothing ever touched — the exact hazard the rest of the README argues against. With a strictly typedInputit is a compile error instead.Corrected in both places to
await task.run({}, { registry: ctx.registry }), with a sentence explaining that the registry travels in the run config (second argument).Pinned by a doc-conformance test
The repo already has the "transcribe the doc example into a test" pattern (
packages/test/src/test/util/readme.test.ts), so the corrected snippet is now executable:packages/test/src/test/util/BootstrapReadme.test.tsasserts both halves — thatrun({}, { registry: ctx.registry })reaches the isolated registry, and that the oldrun({ context: ctx })shape silently falls back to the global one. Mutation-checked: reverting the first test to the old snippet fails it withexpected true to be false.This required adding
@workglow/bootstraptopackages/test's devDependencies and tsconfig references (one line each). It is the only structural change in the PR — happy to drop the test and both lines if you'd rather keep this strictly docs-only.3. Import-style fix
registerAllDefaults.tskeptServiceRegistryin the value-import list afterglobalServiceRegistrywas removed, though it is used solely as a parameter type. Split into a top-levelimport type, per CLAUDE.md's convention.4. Lockfile sync (incidental)
bun.lockstill recordedpackages/bootstrapat0.3.37while #720 bumpedpackage.jsonto0.3.38. A plainbun installcorrects it; included here so the next contributor's install doesn't produce unrelated churn. CI runsbun i(not--frozen-lockfile), so this was not breaking anything.Two suggested LOW fixes I did not apply
Both were requested, but the stated premises do not survive checking, so applying them would have traded one wrong doc for another. Flagging rather than silently skipping.
packages/bootstrap/CHANGELOG.mdheading. The premise was that every sibling opens with its package name and that leaving# Changelogwould make the next release run produce a two-headed file. Neither holds:# Changelog, not one:bootstrap,browser-control,indexeddb,javascript,mcp. All are public and released (all at 0.3.38).bunset'swriteChangelog(node_modules/bunset/src/changelog.ts:98-122) writes# Changelogonly when the file does not exist; for an existing file it preserves the first line verbatim and inserts the new entry after it.browser-controlhas carried# Changelogacross six releases with no second heading. So# Changelogis exactly what the tool generated for this new package, and renaming it would fight the generator rather than align with it.The
↓separator in the.claude/CLAUDE.mddependency graph. The parenthetical reason given was thatproviders/*does not depend onbootstrap— which is true (verified: nothing underproviders/lists@workglow/bootstrap; the only dependent in the repo ispackages/workglow), but it argues against the change. In that graph↓means "tier below"; inserting one betweenbootstrapandproviders/*would assert that providers depend on bootstrap. They are genuine siblings: both sit belowai, neither depends on the other. Consecutive un-arrowed lines are already the graph's sibling notation — the bottom tier liststest/workglow/debugthe same way. The current rendering is correct as-is.Verification
The
utilsection is whereBootstrapPackageExports.test.tsand the newBootstrapReadme.test.tslive. Vitest runs were done inuse-sourcemode; the tree was returned to dist mode before committing, so nopackage.jsonexport churn is in the diff.🤖 Generated with Claude Code
https://claude.ai/code/session_01K6huUY7hSkRbjun1P9HKsz
Generated by Claude Code