Initial Commit
This commit is contained in:
71
openspec/archive/2026-05-02-env-spec-conformance/proposal.md
Normal file
71
openspec/archive/2026-05-02-env-spec-conformance/proposal.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# 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).
|
||||
33
openspec/archive/2026-05-02-env-spec-conformance/specs/env/spec.md
vendored
Normal file
33
openspec/archive/2026-05-02-env-spec-conformance/specs/env/spec.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# Capability Spec Delta: env-spec-conformance
|
||||
|
||||
**Package**: `@suaveplan/env`
|
||||
**Change**: env-spec-conformance
|
||||
**Date**: 2026-04-26
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
This change does two things in scope:
|
||||
|
||||
1. **Folds `**Verified by:**` lines** into every `### ENV-NN —` block of `openspec/specs/env/spec.md` (ENV-01 through ENV-17). Each annotation points at real tests — there are no `deferred` / `follow-up` markers. The new `src/core/create-env/create-env.spec-conformance.test.ts` pins the implicit type-level and wire-level scenarios the original suite did not (literal-type narrowing, loader-chain merge order, cross-loader `${VAR}` interpolation, unresolved-token default, Zod boundary failures for the three public APIs, OTel non-throwing instrumentation, `EnvValidationError.code === ENV.VALIDATION_FAILED`, and `ENV_CODES.*` literal typing).
|
||||
|
||||
2. **Realigns spec ↔ impl** for the nine divergences the original audit flagged, plus ENV-12 and ENV-14:
|
||||
|
||||
| Divergence | Direction | Change |
|
||||
|---|---|---|
|
||||
| `objectLoader` signature | spec → impl | ENV-04 documents the single-options `objectLoader({ source, sourceName })` form. |
|
||||
| `parseDotenv` malformed-line handling | spec → impl | ENV-05 documents silent-skip for lines without `=` (dotenv-ecosystem convention). |
|
||||
| `DotenvLoaderOptions.override` default | spec → impl | ENV-05 specifies `false` (safer; explicit `source` wins). |
|
||||
| `createEnv` interpolation pass | impl → spec | ENV-06 contract restored: `createEnv` runs a fixpoint interpolation pass over the merged values map before coercion. |
|
||||
| `BaseField.rules()` naming | spec → impl | ENV-07 specs `addRule()` on the base; `optional()`/`default()` lifted per subclass (return-type narrows). |
|
||||
| `ValidationResult` shape | spec → impl | ENV-09 specs `{ success, data, errors: ReadonlyArray<EnvError> }`. |
|
||||
| `maskSensitive` arg-order / glyph | spec → impl | ENV-10 specs `maskSensitive(schema, values)` and the `'****'` glyph. |
|
||||
| `CoercionError` code | impl → spec | `CoercionError` now uses `ENV.INVALID_FORMAT`. The dead `ENV.COERCE_ERROR` registry entry is removed. |
|
||||
| Zod-schema boundary wiring (ENV-12) | impl → spec | `createEnv`, `dotenvLoader`, `objectLoader` invoke `CreateEnvOptionsSchema`, `DotenvLoaderOptionsSchema`, `ObjectLoaderOptionsSchema` respectively; invalid options throw `EnvValidationError` with code `ENV.VALIDATION_FAILED`. Remaining schemas are marked `// schema: boundary-only` (consumer-facing). |
|
||||
| OTel instrumentation (ENV-14) | impl → spec | `createEnv` records `env.validation.total` (counter, `outcome` label), `env.validation.errors` (counter, `field` label), and `env.load.duration_ms` (histogram, ms) via `metrics.getMeter('suaveplan.env')`. `@opentelemetry/api` added as a runtime dep. |
|
||||
|
||||
After this change:
|
||||
|
||||
- `bun scripts/openspec-conformance.ts @suaveplan/env` exits 0 with no `error` findings.
|
||||
- `bunx turbo lint typecheck test --filter=@suaveplan/env` is green.
|
||||
- `bunx turbo test:coverage --filter=@suaveplan/env` meets the 100/100/100/100 threshold.
|
||||
- The historical `retroactively ticked` hazard in `openspec/archive/2026-04-19-enterprise-env/tasks.md` is recognised as resolved by this change.
|
||||
32
openspec/archive/2026-05-02-env-spec-conformance/tasks.md
Normal file
32
openspec/archive/2026-05-02-env-spec-conformance/tasks.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Tasks: env-spec-conformance
|
||||
|
||||
## 1. Spec annotations
|
||||
|
||||
- [x] 1.1 Add a real `**Verified by:**` line under every `### ENV-NN —` block in `openspec/specs/env/spec.md` for ENV-01 through ENV-17. No `deferred` / `follow-up` markers remain.
|
||||
|
||||
## 2. Spec → impl alignment (spec is the contract)
|
||||
|
||||
- [x] 2.1 `CoercionError` throws `ENV.INVALID_FORMAT` (not `ENV.COERCE_ERROR`); `COERCE_ERROR` removed from the registry. Tests + docs updated.
|
||||
- [x] 2.2 `createEnv` runs a fixpoint `${VAR}` interpolation pass over the merged values map before coercion (cross-loader interpolation works).
|
||||
- [x] 2.3 Wire `CreateEnvOptionsSchema`, `DotenvLoaderOptionsSchema`, `ObjectLoaderOptionsSchema` at the public boundaries of `createEnv`, `dotenvLoader`, `objectLoader`. Invalid options throw `EnvValidationError` with code `ENV.VALIDATION_FAILED`.
|
||||
- [x] 2.4 Wire OTel instruments in `createEnv` via `metrics.getMeter('suaveplan.env')`: `env.validation.total` counter, `env.validation.errors` counter, `env.load.duration_ms` histogram. Add `@opentelemetry/api` as a runtime dep.
|
||||
|
||||
## 3. Impl → spec alignment (impl is the intended design)
|
||||
|
||||
- [x] 3.1 `ENV-04` updated to spec the single-options `objectLoader({source, sourceName})` form.
|
||||
- [x] 3.2 `ENV-05` updated: `parseDotenv` silently skips malformed lines; `DotenvLoaderOptions.override` defaults to `false`.
|
||||
- [x] 3.3 `ENV-07` updated: `addRule()` on `BaseField`; `optional()`/`default()` lifted to each subclass because their return type narrows.
|
||||
- [x] 3.4 `ENV-09` updated: `ValidationResult` shape is `{ success, data, errors: ReadonlyArray<EnvError> }`.
|
||||
- [x] 3.5 `ENV-10` updated: `maskSensitive(schema, values)` argument order; mask glyph is `'****'`.
|
||||
|
||||
## 4. New tests
|
||||
|
||||
- [x] 4.1 `src/core/create-env/create-env.spec-conformance.test.ts` covers literal-type narrowing, loader-chain merge order, `parseDotenv` purity + interpolation chain, `dotenvLoader` missing-file fallback, `maskSensitive` non-mutation, `EnvValidationError.code === ENV.VALIDATION_FAILED`, `ENV_CODES.*` literal typing, cross-loader `${VAR}` interpolation, unresolved-token default, Zod boundary failures (createEnv / dotenvLoader / objectLoader), and OTel non-throwing instrumentation.
|
||||
- [x] 4.2 `tests/dep-graph.test.ts` flipped to assert `@opentelemetry/api` IS a runtime dep (ENV-14 contract).
|
||||
- [x] 4.3 `tests/tree-shake.test.ts` updated to reference `INVALID_FORMAT` instead of the removed `COERCE_ERROR`.
|
||||
|
||||
## 5. Verify
|
||||
|
||||
- [x] 5.1 `bunx turbo lint typecheck test --filter=@suaveplan/env` green.
|
||||
- [x] 5.2 `bunx turbo test:coverage --filter=@suaveplan/env` 100/100/100/100.
|
||||
- [x] 5.3 `bun scripts/openspec-conformance.ts @suaveplan/env` exits 0.
|
||||
Reference in New Issue
Block a user