5.6 KiB
5.6 KiB
OpenSpec Change: env-spec-conformance
- Change ID: env-spec-conformance
- Package:
@suaveplan/env - Status: PROPOSED
- Date: 2026-04-26
- Reference:
openspec/archive/2026-04-19-enterprise-env/specs/env/spec.md
Summary
Close the loop on @suaveplan/env spec conformance: every requirement in openspec/specs/env/spec.md now carries a real **Verified by:** line, the implementation has been realigned with the spec where the spec was the contract, and the spec has been realigned with the implementation where the implementation was the intended design. The bun scripts/openspec-conformance.ts @suaveplan/env gate exits 0 with no deferred annotations and no retroactively ticked lies.
This change folds the previously-deferred env-implementation-realignment work in scope. Every divergence the original audit flagged is closed below.
Current-state audit (post-change)
| Spec ID | Requirement | Status |
|---|---|---|
| ENV-01 | createEnv schema-first, type-inferred result |
shipped + literal-type narrowing pinned |
| ENV-02 | Three error modes (throw / collect / warn) |
shipped |
| ENV-03 | Loader contract; ordered merge | shipped |
| ENV-04 | objectLoader shape |
shipped (single-options form, Zod boundary check) |
| ENV-05 | dotenvLoader + parseDotenv purity |
shipped (silent-skip for malformed lines, override defaults false, Zod boundary check) |
| ENV-06 | interpolate / hasInterpolation / extractVariables + createEnv interpolation pass |
shipped (fixpoint pass over merged values pre-coercion) |
| ENV-07 | BaseField builder contract |
shipped (addRule on base, optional()/default() per subclass) |
| ENV-08 | Field-type coercion + built-in rules | shipped |
| ENV-09 | validate accumulates all errors |
shipped ({success, data, errors} shape) |
| ENV-10 | maskSensitive(schema, values) never leaks |
shipped (**** glyph, schema-first arg order) |
| ENV-11 | ENV.* error codes via defineCodes |
shipped (CoercionError now uses ENV.INVALID_FORMAT) |
| ENV-12 | Zod schemas at boundaries | shipped (createEnv / dotenvLoader / objectLoader invoke their schemas; remaining schemas marked boundary-only) |
| ENV-13 | Presets (common + server) | shipped |
| ENV-14 | OTel meters / counters / histogram | shipped (@opentelemetry/api runtime dep + getMeter('suaveplan.env') instruments in createEnv) |
| ENV-15 | sideEffects: false + 100% coverage gate |
shipped |
| ENV-16 | Property-based tests | shipped |
| ENV-17 | No Node/browser-only built-ins outside dotenvLoader |
shipped |
What this change does
- Annotates every requirement in
openspec/specs/env/spec.mdwith a real**Verified by:**line. Nodeferredmarkers. - Aligns implementation to spec for the contracts where the spec was the source of truth:
CoercionErrornow throws codeENV.INVALID_FORMAT(notENV.COERCE_ERROR); the deadCOERCE_ERRORregistry entry is removed.createEnvperforms a fixpoint${VAR}interpolation pass over the merged values map before coercion, so cross-loader chains resolve.createEnv,dotenvLoader, andobjectLoaderinvoke their respective Zod schemas at the public boundary; invalid options surface asENV.VALIDATION_FAILED.createEnvrecords OTel instruments (env.validation.total,env.validation.errors,env.load.duration_ms) viametrics.getMeter('suaveplan.env').@opentelemetry/apiis now a runtime dependency.
- Aligns spec to implementation for design choices the implementation had already made, where the implementation was clearly the intended design and the original spec wording was accidentally divergent:
objectLoader({ source, sourceName })single-options form.parseDotenvsilently skips malformed lines (matches dotenv-ecosystem convention).DotenvLoaderOptions.overridedefaults tofalse(safer; explicitsourcewins).BaseField.addRule()on the base class;optional()/default()lifted to each concrete subclass because their return type narrows.ValidationResultshape is{ success, data, errors: ReadonlyArray<EnvError> }.maskSensitive(schema, values)argument order; mask glyph is'****'.
- Adds boundary tests in
src/core/create-env/create-env.spec-conformance.test.tscovering: cross-loader interpolation, unresolved-token default, Zod boundary failures for all three public APIs, and OTel non-throwing instrumentation. - Records
retroactively tickedrectification — the historical hazard inopenspec/archive/2026-04-19-enterprise-env/tasks.mdis recognised as resolved by this change.
Available workspace deps
| Package | Available |
|---|---|
@suaveplan/error |
yes |
@suaveplan/types |
yes |
@opentelemetry/api (catalog: observability) |
yes — now a runtime dep |
fast-check (devDep) |
yes |
Impact
@opentelemetry/apiadded as a runtime dep (catalog:observability).CoercionError.codechanged fromENV.COERCE_ERROR→ENV.INVALID_FORMAT.ENV.COERCE_ERRORis removed from the registry. Genesis pre-1.0 — no external consumers depend on the old code.createEnvnow performs interpolation across the merged map; values that previously contained literal${X}text will now expand ifXis present. Documented as ENV-06 behaviour.- New Zod boundary checks turn invalid runtime options into
EnvValidationErrorinstead ofTypeError/ silent failure. - Public API surface preserved: function signatures unchanged, only behaviour and error codes tightened to match the spec.
- Version stays at
0.1.0(genesis pre-1.0).