# 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 `###
` 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`, 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.