lite-bootstrap 1.1.0 — Lifecycle hardening, config validation, CI gate
1.1.0 is a minor release. No intentional public-API breakage. The two behavior changes that could affect existing code are fixes to genuine bugs and are called out in Behavior changes below.
This release closes a 26-finding bug-audit cycle (audits + retro live under planning/specs/2026-06-05-bug-audit-v2*.md). The changes split across four shipped PRs:
- #108 — Lifecycle & teardown correctness (10 findings)
- #109 — Config UX & security validation (6 findings)
- #110 — Hygiene + CI gate (4 findings)
- #111 — Generalized
TeardownErroraggregation + cascade tests + README lifecycle docs (3 deferred follow-ups)
Test suite grew from 153 → 194 (+27%) at 100% line coverage throughout. pip-audit now runs on every PR and weekly via cron; a new filterwarnings config catches accidental InstrumentSkippedWarning emissions.
New features
- Injectable
prometheus_collector_registryonFastStreamConfig. Pass an existingprometheus_client.CollectorRegistryto expose counters registered elsewhere through FastStream's/metricsendpoint. Defaults to a fresh per-instance registry — fully backward compatible. opentelemetry_excluded_urlsfield onFastStreamConfig. Was agetattrfallback before; now a discoverable, IDE-completable config field matchingFastAPIConfigandLitestarConfig.SentryInstrument.teardown(). Callssentry_sdk.flush(timeout=2)thensentry_sdk.init()(no args) to reset the SDK to a no-op state. Previously the SDK stayed globally configured after bootstrapper teardown, leaking state across process-local tests.FastStreamLoggingInstrument.teardown(). Restoresbroker.config.logger.params_storageto its pre-bootstrap value. The bootstrap mutated broker state; teardown didn't reverse it.
Bug fixes
Lifecycle & teardown (PR #108)
- OpenTelemetry teardown now flushes spans and shuts down the tracer provider (LOG-1, LOG-2).
bootstrap()stored theTracerProvideronly as a local;teardown()couldn't reach it to callshutdown(). Buffered spans inBatchSpanProcessorwere dropped on graceful shutdown. Teardown also restores the two OTel-namespace stdlib loggers (opentelemetry.instrumentation.instrumentor,opentelemetry.trace) to their pre-bootstrapdisabledstate. LoggingInstrument.teardown()runs all cleanup steps even on partial failure (LOG-3). A raise from anyhandler.close()previously left remaining handlers attached, skipped the root-level reset, and never calledclose_handlers()on the memory factory. Now wrapped intry/finallywith per-handler error capture; all collected errors raise together viaTeardownError(errors).LitestarOpenTelemetryInstrumentationMiddlewarecache evicts dead refs (LOG-6). The olddict[int, ASGIApp]keyed byid()never evicted, holding wrapperOpenTelemetryMiddlewareinstances alive after Litestar droppednext_app. Replaced withweakref.WeakKeyDictionary; non-weakrefable apps fall through to the un-cached path.- Double-bootstrap on the same application is now detected and warned (LOG-7, LOG-8). Constructing two
FastMcpBootstrappers around the sameFastMCP(or twoFastAPIBootstrappers around the sameFastAPI) previously stacked teardown hooks. The second construction now emits aUserWarning, skips the re-attachment, and tells you the second bootstrapper'steardown()won't fire on ASGI shutdown. - Generalized
TeardownErroraggregation across all instruments (PR #111).OpenTelemetryInstrument,FastStreamLoggingInstrument, andSentryInstrumentnow run their full cleanup sequence even if an early step raises — aggregating errors into a singleTeardownErroror lettingsuper().teardown()run viatry/finally. Previously a misbehaving instrumentor / broker / Sentry flush silently skipped subsequent cleanup. - Pyroscope's precondition check survives
python -O(LOG-5/SEC-4). Replaced twoassertstatements (in_narrow_appandPyroscopeInstrument.bootstrap) with explicitraise TypeError(...)andraise RuntimeError(...).python -Ostrips asserts; the invariants now hold under all optimization levels. Closes the twobanditB101 findings.
Config & security (PR #109)
FastAPIConfigno longer stomps user-supplied app'stitle/debug/version(UX-1). Previously the three assignments ran unconditionally; a user passingFastAPI(title="My API", version="3.0.0")would silently have those clobbered by lite-bootstrap defaults. Now the assignments only run in theUnsetTypebranch (when lite-bootstrap constructed the app). See Behavior changes below.enable_offline_docsvalidatesrequest.scope["root_path"]against the existing path allowlist (SEC-1). Invalid root paths (e.g., HTML-injection payloads via a malicious upstream proxy'sX-Forwarded-Prefix) now fall back to empty and emit a warning instead of being reflected into Swagger/Redoc HTML script tags. Threat model: not an issue in default ASGI deployments; only matters ifProxyHeadersMiddlewaretrusts upstream prefix headers.- OpenTelemetry endpoint with
insecure=Trueemits a warning for non-local hosts (SEC-2). NewOpenTelemetryConfig.__post_init__parses the endpoint (handles bothhost:portandscheme://host:portforms, including IPv6 brackets andunix://) and warns when traces would ship unencrypted to a non-localhost/127.0.0.1/::1/unix://target. CorsConfigrejects unsafe wildcard + credentials combos at construction (SEC-3).cors_allowed_credentials=Truecombined withcors_allowed_origins=["*"](or a permissive regex like".*"/".+") is the canonical CORS misconfiguration — browsers reject the response. Now raisesConfigurationErrorimmediately instead of silently building a non-functional CORS layer. See Behavior changes below.
Hygiene & process (PR #110)
- Missing-dependency events now log via stdlib
loggingin addition towarnings.warn(UX-4). Users running underpython -W ignoreorPYTHONWARNINGS=ignorepreviously saw nothing when a configured instrument's optional dep was missing. The newlogger.warningline onlite_bootstrap.bootstrappers.baseis unaffected by warning filters.
Behavior changes
Two changes could affect existing code in ways the previous release wouldn't have. Both fix real bugs; surfaced here so you can audit.
-
User-supplied
FastAPIinstance retains its owntitle,debug,version. If you were relying on lite-bootstrap to overwrite these fromservice_name/service_debug/service_versionafter handing it a pre-builtFastAPI(), you'll now see your original values. Migration: set these on yourFastAPI()directly, or useFastAPIConfig(application_kwargs={...})to have lite-bootstrap construct the app. -
CorsConfig(cors_allowed_origins=["*"], cors_allowed_credentials=True)now raisesConfigurationError. This combo was never functional — browsers reject responses withAccess-Control-Allow-Credentials: trueandAccess-Control-Allow-Origin: *. If your code constructed this combo and worked anyway (because the credentials header was silently dropped by FastAPI's CORSMiddleware), construction now fails with a clear message. Migration: enumerate allowed origins explicitly, or setcors_allowed_credentials=False.
New constraints documented
Three lifecycle constraints surfaced by the audit are now documented in README.md and CLAUDE.md:
- One bootstrapper per application instance. Second construction emits a warning and skips re-attachment (see LOG-7/LOG-8 above).
- One
OpenTelemetryInstrumentper process. The OTel SDK enforcesset_tracer_provideras set-once via_TRACER_PROVIDER_SET_ONCE.do_once(...)(verified againstopentelemetry/trace/__init__.py:548-556);teardown()cannot reset the global pointer. __post_init__cascade invariant. Every config-class__post_init__must callsuper().__post_init__().BaseConfigships a no-op as the chain terminator.FastAPIConfiguses the explicitsuper(FastAPIConfig, self).__post_init__()form because@dataclass(slots=True)breaks baresuper().
CI changes
security-audit.ymlworkflow added.pip-auditruns on every PR and weekly via cron against the lockfile (uv export --all-extras --no-hashes). The default-branch run is purely informational; PRs that introduce CVEs will block until resolved.InstrumentSkippedWarningescalated to error in tests. Any unexpected emission outside apytest.warns(...)block now fails the test (registered viapytest_configure()intests/conftest.py; can't live inpyproject.tomlbecause that import order breaks pytest-cov tracing).
Backwards compatibility
Aside from the two Behavior changes called out above, every public API behaves identically. New fields default to their old behavior (prometheus_collector_registry=None → fresh registry as before; opentelemetry_excluded_urls=[] → empty set as before). New warnings/validators trigger only on configurations that were already broken or risky.
The 26-fix list with full file:line references and rationale is in:
planning/specs/2026-06-05-bug-audit-v2.md— the auditplanning/specs/2026-06-05-bug-audit-v2-sequencing.md— the 3-PR breakdownplanning/specs/2026-06-05-bug-audit-v2-retro.md— what the cycle taught us
References
- Audit:
planning/specs/2026-06-05-bug-audit-v2.md - Sequencing:
planning/specs/2026-06-05-bug-audit-v2-sequencing.md - Retro:
planning/specs/2026-06-05-bug-audit-v2-retro.md - PRs: #108, #109, #110, #111