Files
2026-05-27 23:29:53 +01:00

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

  1. Annotates every requirement in openspec/specs/env/spec.md with a real **Verified by:** line. No deferred markers.
  2. Aligns implementation to spec for the contracts where the spec was the source of truth:
    • CoercionError now throws code ENV.INVALID_FORMAT (not ENV.COERCE_ERROR); the dead COERCE_ERROR registry entry is removed.
    • createEnv performs a fixpoint ${VAR} interpolation pass over the merged values map before coercion, so cross-loader chains resolve.
    • createEnv, dotenvLoader, and objectLoader invoke their respective Zod schemas at the public boundary; invalid options surface as ENV.VALIDATION_FAILED.
    • createEnv records OTel instruments (env.validation.total, env.validation.errors, env.load.duration_ms) via metrics.getMeter('suaveplan.env'). @opentelemetry/api is now a runtime dependency.
  3. 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.
    • parseDotenv silently skips malformed lines (matches dotenv-ecosystem convention).
    • DotenvLoaderOptions.override defaults to false (safer; explicit source wins).
    • BaseField.addRule() on the base class; optional()/default() lifted to each concrete subclass because their return type narrows.
    • ValidationResult shape is { success, data, errors: ReadonlyArray<EnvError> }.
    • maskSensitive(schema, values) argument order; mask glyph is '****'.
  4. Adds boundary tests in src/core/create-env/create-env.spec-conformance.test.ts covering: cross-loader interpolation, unresolved-token default, Zod boundary failures for all three public APIs, and OTel non-throwing instrumentation.
  5. Records retroactively ticked rectification — the historical hazard in openspec/archive/2026-04-19-enterprise-env/tasks.md is 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/api added as a runtime dep (catalog: observability).
  • CoercionError.code changed from ENV.COERCE_ERROR → ENV.INVALID_FORMAT. ENV.COERCE_ERROR is removed from the registry. Genesis pre-1.0 — no external consumers depend on the old code.
  • createEnv now performs interpolation across the merged map; values that previously contained literal ${X} text will now expand if X is present. Documented as ENV-06 behaviour.
  • New Zod boundary checks turn invalid runtime options into EnvValidationError instead of TypeError / 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).