89 lines
4.3 KiB
Markdown
89 lines
4.3 KiB
Markdown
# OpenSpec Change: templates-spec-conformance
|
|
|
|
- **Change ID**: templates-spec-conformance
|
|
- **Package**: `@suaveplan/templates`
|
|
- **Status**: PROPOSED
|
|
- **Date**: 2026-04-26
|
|
|
|
## Summary
|
|
|
|
Bring `@suaveplan/templates` into spec conformance with
|
|
`openspec/specs/templates/spec.md`. Closes the parsing gap (the previous
|
|
spec used `### <Section>` plus `REQ-TPL-NNN` bullets so the conformance
|
|
parser saw zero requirements), wires the missing OpenTelemetry instruments
|
|
the spec mandated but the engine never emitted, and adds boundary
|
|
validation of `TemplateEngineOptions` / `ParseOptions` so the exported Zod
|
|
schemas are actually called by the engine.
|
|
|
|
## Current state
|
|
|
|
`@suaveplan/templates` ships an enterprise-grade plugin engine
|
|
(`TemplateEngine<TContext>`, four preset families, ten plugins, a
|
|
structured error hierarchy backed by `defineCodes("TEMPLATE", …)`,
|
|
`fast-check` property tests, and a mitata bench suite). The spec gaps
|
|
are mechanical / observability:
|
|
|
|
- The folded spec used non-parseable section headings (`### Package
|
|
identity`, `### Dependencies`, …) so `bun scripts/openspec-conformance.ts
|
|
@suaveplan/templates` reported "0 requirements parsed".
|
|
- The constructor advertised `TemplateEngineOptionsSchema` but never
|
|
invoked it; same for `ParseOptionsSchema`. Both schemas were exported
|
|
yet had zero non-test call sites.
|
|
- The OTel contract (`template.render.total`, `template.render.duration_ms`,
|
|
`template.plugin.calls.total`) was specced but the engine had no
|
|
`@opentelemetry/api` import at all.
|
|
- `tasks.md` of `2026-04-19-enterprise-templates/` carried the
|
|
bulk-archive marker (`> Archived YYYY-MM-DD: all tasks …`) banned by
|
|
AGENTS.md section 12.
|
|
- Several behaviours were exercised only indirectly: ParseState clone
|
|
independence, reverse-index splice ordering inside `applyPlugin`,
|
|
`throwOnError=false` → `onError` forwarding, `Clock` / `Rng` injection
|
|
on dynamic plugins, and preset super-set relationships.
|
|
|
|
## What changes
|
|
|
|
1. Add `@opentelemetry/api` as a runtime dependency
|
|
(`catalog:observability`).
|
|
2. Add `src/otel/templates-otel.ts` exporting `recordRender`,
|
|
`recordPluginCall`, `startRenderTimer`, and `_resetInstruments`. Wire
|
|
it from `TemplateEngine.parse` / `parseAsync` (render counter +
|
|
duration histogram) and from `applyPlugin` / `applyPluginAsync`
|
|
(plugin counter, attribute `outcome` ∈ `{"success","skip","error"}`).
|
|
3. `TemplateEngine` constructor now calls
|
|
`TemplateEngineOptionsSchema.safeParse(options)` and throws
|
|
`TemplateError(TEMPLATE.PARSE_ERROR)` on failure. `parse` / `parseAsync`
|
|
call `ParseOptionsSchema.safeParse(options)` when options are passed.
|
|
4. Mark `PluginPrioritySchema` as `// schema: boundary-only` (consumer
|
|
facing only; the engine accepts any numeric priority).
|
|
5. Rewrite `openspec/specs/templates/spec.md` from
|
|
`### Package identity` + `REQ-TPL-NNN` bullets to fourteen
|
|
`### TPL-NN — name` requirement headings, each with a
|
|
`**Verified by:**` line that names the specific test case.
|
|
6. Add seven `*.spec-conformance.test.ts` files pinning the implicit
|
|
scenarios:
|
|
- `src/__tests__/package-shape.spec-conformance.test.ts` — TPL-01 / TPL-02.
|
|
- `src/__tests__/types.spec-conformance.test.ts` — TPL-04.
|
|
- `src/engine/ParseState.spec-conformance.test.ts` — TPL-05.
|
|
- `src/engine/TemplateEngine.spec-conformance.test.ts` — TPL-06 / TPL-07 / TPL-09.
|
|
- `src/engine/apply-plugin.spec-conformance.test.ts` — TPL-08.
|
|
- `src/errors/template-error.spec-conformance.test.ts` — TPL-03.
|
|
- `src/otel/templates-otel.spec-conformance.test.ts` — TPL-13.
|
|
- `src/plugins/plugins.spec-conformance.test.ts` — TPL-10.
|
|
- `src/presets/presets.spec-conformance.test.ts` — TPL-11.
|
|
7. Replace the `retroactively ticked` line in
|
|
`archive/2026-04-19-enterprise-templates/tasks.md` with prose pointing
|
|
at this change folder.
|
|
|
|
## Impact
|
|
|
|
- Public API gains `recordRender`, `recordPluginCall`,
|
|
`startRenderTimer`, `_resetInstruments`, `RenderOutcome`,
|
|
`PluginOutcome` re-exports. No removed exports.
|
|
- `TemplateEngine` constructor now validates options at the boundary —
|
|
invalid configs throw `TemplateError(TEMPLATE.PARSE_ERROR)` instead of
|
|
silently coercing.
|
|
- Nine new test files (~600 lines combined).
|
|
- One new module + sibling `.md` (`src/otel/`).
|
|
- `@opentelemetry/api` added as runtime dep; ships no-op when no
|
|
`MeterProvider` is registered.
|