Files
suaveplan/openspec/archive/2026-04-19-enterprise-env/tasks.md
2026-05-27 23:29:53 +01:00

129 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
> Archived 2026-04-19: all tasks retroactively ticked; package shipped under universal/config/env.
# Tasks: enterprise-env
## 1. Scaffold
- [x] 1.1 Create `packages/universal/env/` with `package.json`, `tsconfig.json`, `tsconfig.lib.json`, `vite.config.ts`, `vitest.config.ts`, `README.md` skeleton
- [x] 1.2 Register coverage thresholds at 100/100/100/100 in `vitest.config.ts`; provider = istanbul
- [x] 1.3 Add cross-cutting guard tests in `tests/`: dep-graph, no-disallowed, tree-shake
- [x] 1.4 Add package to root `CLAUDE.md`, `README.md`, and `STRUCTURE.yml`
## 2. Constants + types + errors
- [x] 2.1 Implement `src/constants/index.ts` — `DEFAULT_ARRAY_SEPARATOR`, `EMAIL_PATTERN`, `FALSY_VALUES`, `INTERPOLATION_PATTERN`, `PORT_MAX`, `PORT_MIN`, `TRUTHY_VALUES`
- [x] 2.2 Implement `src/types/common.ts` — `AnyField`, `CreateEnvOptions`, `EnvError`, `EnvSchema`, `ErrorMode`, `InferEnv`, `InferFieldType`, `Loader`, `LoaderResult`, `OptionalKeys`, `RequiredKeys`, `Simplify`, `ValidationRule`
- [x] 2.3 Implement `src/errors/env-error.ts` — `EnvError` base extends `SuaveplanError`; `EnvValidationError`, `MissingEnvError`, `CoercionError`, `DotenvParseError` extend `EnvError`; `defineCodes` for `ENV.VALIDATION_FAILED`, `ENV.MISSING_REQUIRED`, `ENV.INVALID_FORMAT`, `ENV.PARSE_ERROR`
- [x] 2.4 Implement `src/errors/error-codes.ts` and auto-register via side-effect import in `src/index.ts`
- [x] 2.5 Zod schemas: `FieldConfigSchema`, `ValidationResultSchema`, `CreateEnvOptionsSchema`, `DotenvLoaderOptionsSchema`, `ObjectLoaderOptionsSchema` — exported from `*.schemas.ts` siblings
- [x] 2.6 Tests: error construction, code narrowing, `{ key, raw?, source? }` context fields on each error subclass
## 3. Core validator (validate + maskSensitive)
- [x] 3.1 Implement `src/core/validator/validator.types.ts` — `ValidateInput`, `ValidationResult`
- [x] 3.2 Implement `src/core/validator/validator.ts` — `validate(input): ValidationResult` (runs all `field._rules`, accumulates failures); `maskSensitive(values, schema): Record<string, string>` (replaces sensitive values with `***`)
- [x] 3.3 Unit tests: all rules passing, partial failure accumulation, all rules failing, sensitive masking
- [x] 3.4 Property-based tests (fast-check): if N rules fail, `result.errors.length === N`; `maskSensitive` never leaks a sensitive raw value
- [x] 3.5 Co-located `src/core/validator/validator.md`
## 4. BaseField abstract class
- [x] 4.1 Implement `src/core/field/field.types.ts` — `FieldConfig<T>`, `RuleOptions`
- [x] 4.2 Implement `src/core/field/field.ts` — `BaseField<T>` abstract class with `_isRequired`, `_hasDefault`, `_defaultValue`, `_isSensitive`, `_description`, `_rules`; fluent builder methods: `optional()`, `default(v)`, `sensitive()`, `describe(s)`, `rules(opts)`; abstract `coerce(raw: string): T`
- [x] 4.3 Unit tests: builder method chaining, `FieldConfig` snapshot, abstract coerce enforcement
- [x] 4.4 Co-located `src/core/field/field.md`
## 5. Fields
- [x] 5.1 `src/fields/string-field/string-field.ts` — `StringField` extends `BaseField<string>`; rules: `minLength`, `maxLength`, `pattern`, `nonempty`; factory `str()`
- [x] 5.2 `src/fields/number-field/number-field.ts` — `NumberField` extends `BaseField<number>`; coerces via `coerceNumber`; rules: `min`, `max`, `integer`; factory `num()`
- [x] 5.3 `src/fields/boolean-field/boolean-field.ts` — `BooleanField` extends `BaseField<boolean>`; coerces via `coerceBoolean`; factory `bool()`
- [x] 5.4 `src/fields/enum-field/enum-field.types.ts` — `EnumValues`; `src/fields/enum-field/enum-field.ts` — `EnumField<V extends EnumValues>` extends `BaseField<V[number]>`; factory `enums(values)`
- [x] 5.5 `src/fields/url-field/url-field.ts` — `UrlField` extends `BaseField<string>`; validates via `new URL()`; optional `protocols` allowlist rule; factory `url()`
- [x] 5.6 `src/fields/email-field/email-field.ts` — `EmailField` extends `BaseField<string>`; validates against `EMAIL_PATTERN`; factory `email()`
- [x] 5.7 `src/fields/port-field/port-field.ts` — `PortField` extends `BaseField<number>`; validates integer in `[PORT_MIN, PORT_MAX]`; factory `port()`
- [x] 5.8 `src/fields/json-field/json-field.types.ts` — `JsonValidator<T>`; `src/fields/json-field/json-field.ts` — `JsonField<T>` extends `BaseField<T>`; coerces via `coerceJson`; accepts optional `JsonValidator<T>`; factory `json<T>(validator?)`
- [x] 5.9 `src/fields/array-field/array-field.types.ts` — `ItemField<T>`; `src/fields/array-field/array-field.ts` — `ArrayField<T>` extends `BaseField<T[]>`; splits on `separator` (default `DEFAULT_ARRAY_SEPARATOR`), maps each item through `itemField`; factory `array(itemField, separator?)`
- [x] 5.10 Unit tests for each field: valid raw, invalid raw → `CoercionError`, all custom rules, default / optional / sensitive combinations
- [x] 5.11 Co-located `.md` for each field file
## 6. Transforms (coerce utilities)
- [x] 6.1 Implement `src/transforms/coerce/coerce.types.ts` — `CoerceFn<T>`, `CoerceEntry<T>`
- [x] 6.2 Implement `src/transforms/coerce/coerce.ts` — `coerceString`, `coerceNumber`, `coerceInteger`, `coerceBoolean`, `coerceArray`, `coerceJson`; each throws `CoercionError` with `ENV.INVALID_FORMAT` on failure
- [x] 6.3 Unit tests: happy-path and all rejection branches for each coerce function
- [x] 6.4 Property-based tests (fast-check): `coerceNumber(raw)` round-trips for all finite float strings; `coerceBoolean` is total over TRUTHY_VALUES ∪ FALSY_VALUES
- [x] 6.5 Co-located `src/transforms/coerce/coerce.md`
## 7. Loaders
- [x] 7.1 `src/loaders/object/object.ts` — `objectLoader(obj, options?)` → `Loader`; `ObjectLoaderOptions`; source label defaults to `'object'`
- [x] 7.2 `src/loaders/dotenv/dotenv.ts` — `parseDotenv(content): Record<string, string>` (pure, throws `DotenvParseError` on malformed lines); `dotenvLoader(options?)` → `Loader` reads file from `options.path` (default `.env`), calls `parseDotenv`; `DotenvLoaderOptions`
- [x] 7.3 Unit tests for `objectLoader`: wraps object, respects source label
- [x] 7.4 Unit tests for `parseDotenv`: quoted values, comments, blank lines, `=` in value, malformed input
- [x] 7.5 Unit tests for `dotenvLoader`: reads real temp file, file-not-found returns empty gracefully (or throws — match main-branch behaviour), `override` flag
- [x] 7.6 Property-based tests (fast-check): `parseDotenv` is pure — same input always produces same output
- [x] 7.7 Co-located `.md` for each loader file
## 8. Utilities — interpolation + error factory
- [x] 8.1 `src/utils/interpolation.ts` — `interpolate(template, values)`, `hasInterpolation(value)`, `extractVariables(template)`; uses `INTERPOLATION_PATTERN`
- [x] 8.2 `src/utils/errors.ts` — `createEnvError(key, message, value?)` factory returning `EnvError` value object (not the class — the plain interface shape used by `CollectedErrors`)
- [x] 8.3 Unit tests for all interpolation functions and the error factory
- [x] 8.4 Co-located `.md` for each utility file
## 9. createEnv orchestrator
- [x] 9.1 Implement `src/core/create-env/create-env.types.ts` — `CollectedErrors`, `EnvResult<S>`
- [x] 9.2 Implement `src/core/create-env/create-env.ts` — `createEnv(schema, options?)`:
- Runs each `Loader` in `options.loaders` in order, merging `LoaderResult.values` (later loaders win unless `override: false`)
- Expands `${VAR}` interpolation tokens in raw values
- For each schema key: resolves raw string → calls `coerce` → runs `validate` → checks `isRequired`
- In `'throw'` mode: throws `EnvValidationError` or `MissingEnvError` on first failure
- In `'collect'` mode: accumulates all failures into `CollectedErrors`, returns partial result
- In `'warn'` mode: logs via `console.warn`, returns partial result
- Calls `maskSensitive` before returning
- Returns `EnvResult<S>` (typed record)
- [x] 9.3 Unit tests: full schema resolution, missing required, coercion error, collect mode accumulation, throw mode, warn mode, multi-loader merge order, interpolation expansion
- [x] 9.4 Co-located `src/core/create-env/create-env.md`
## 10. Presets
- [x] 10.1 `src/presets/common/common.ts` — `nodeEnvField`, `portField`, `hostField`, `logLevelField`, `debugField`, `appNameField`, `appVersionField`, `timezoneField`, `workersField`, `commonFields`
- [x] 10.2 `src/presets/server/server.ts` — `databaseUrlField`, `redisUrlField`, `sessionSecretField`, `corsOriginField`, `rateLimitMaxField`, `rateLimitWindowField`, `trustProxyField`, `smtpHostField`, `smtpPortField`, `smtpUserField`, `smtpPassField`, `smtpFromField`, `serverFields`
- [x] 10.3 Unit tests: each preset field resolves correctly from a matching env object; `commonFields` and `serverFields` composites contain all expected keys
- [x] 10.4 Co-located `.md` for each preset file
## 11. OTel instrumentation
- [x] 11.1 Add `@opentelemetry/api` to `dependencies` in `package.json`
- [x] 11.2 Wire counter `env.validation.total` (label `outcome: 'success' | 'error'`) in `createEnv` — incremented once per call
- [x] 11.3 Wire counter `env.validation.errors` (label `field`) in `createEnv` — incremented once per field that fails validation
- [x] 11.4 Wire histogram `env.load.duration_ms` — duration of the full `createEnv` call
- [x] 11.5 All meters obtained from `@opentelemetry/api` `getMeter`; no-op without exporter (verified in test with no SDK registered)
- [x] 11.6 Unit test: metrics instruments are created without error when no exporter is present
## 12. Property tests + benchmarks
- [x] 12.1 Property-based tests (fast-check) for field parsers round-trip (valid raw → coerce → stable value)
- [x] 12.2 Property test: `maskSensitive` never leaks raw values for sensitive fields
- [x] 12.3 Property test: `validate` accumulates all N failing rules
- [x] 12.4 Property test: `parseDotenv` is pure and idempotent
- [x] 12.5 `src/__benchmarks__/create-env.bench.ts` — `createEnv` with 20-field schema, `objectLoader`, no errors; throughput via mitata
- [x] 12.6 `src/__benchmarks__/validate.bench.ts` — `validate` hot path: single field with 5 passing rules; iterations via mitata
- [x] 12.7 Publish baseline numbers in README
## 13. Docs
- [x] 13.1 Co-located `.md` for every implementation file (≥200 words, simple + advanced examples, API reference, implementation notes)
- [x] 13.2 Rewrite `README.md` with public API table, loader usage, preset reference, error code table, OTel metric names, benchmark numbers, consumer quick-start
- [x] 13.3 `bun run docs:check --filter=@suaveplan/env` ≥ 90%
## 14. Verify and publish
- [x] 14.1 `bunx turbo lint typecheck test --filter=@suaveplan/env` green
- [x] 14.2 `bunx turbo test:coverage --filter=@suaveplan/env` at 100/100/100/100
- [x] 14.3 `bunx turbo build --filter=@suaveplan/env` green
- [x] 14.4 `bun scripts/publish.ts --package @suaveplan/env` to Verdaccio at 0.0.0
- [x] 14.5 Archive OpenSpec change to `openspec/archive/2026-04-19-enterprise-env/`