9.0 KiB
9.0 KiB
Tasks — @suaveplan/i18n-backend-chained (chained-fallback backend orchestrator)
Every checkbox MUST reflect work actually done in this change. Do not retroactively tick.
1. Spec authoring
- 1.1 Read
openspec/cross-cutting-designs/2026-05-05-i18n-modular-split.mdend-to-end (especially §3, §6, §6.1) - 1.2 Read
openspec/AGENTS.mdSections 0, 0a, 6a, 7, 9, 13 - 1.3 Read source:
packages/i18n/i18n/src/core/backend/chained/chained-backend.ts,chained-backend.test.ts,chained-backend.spec-conformance.test.ts,chained-backend.schemas.ts,chained-backend.md, pluspackages/i18n/i18n/src/core/backend/backend.types.ts - 1.4 Confirm Wave 1b (
@suaveplan/i18n-shared) registry includesI18N.CHAINED_BACKEND_ALL_FAILEDso this package can throw it without a registry-change dependency - 1.5 Draft
proposal.mdfromopenspec/templates/proposal.md - 1.6 Fill
## Source mappingtable with verified line ranges from monolith - 1.7 Draft
specs/i18n-backend-chained/spec.mdfromopenspec/templates/spec-delta.md - 1.8 Every requirement carries a
**Verified by:**line naming concreteit(...)titles - 1.9
openspec change validate i18n-backend-chainedexits with no errors
2. Package scaffold
- 2.1
mkdir -p packages/i18n/i18n-backend-chained/src - 2.2 Author
package.json— name@suaveplan/i18n-backend-chained, version0.1.0, typemodule, sideEffectsfalse. Deps:@suaveplan/i18n-shared workspace:^,@suaveplan/types workspace:^,@suaveplan/error workspace:^,@opentelemetry/api catalog:observability,tslib catalog:build,zod catalog:forms. devDeps:@suaveplan/testing workspace:^,@suaveplan/typescript-config workspace:^,@vitest/coverage-istanbul catalog:testing,vitest catalog:testing. No workspace dep on any other backend package. - 2.3 Author
tsconfig.jsonextending@suaveplan/typescript-config/library.json - 2.4 Author
vite.config.tsusingcreateTestConfig()from@suaveplan/testing/config - 2.5 Author
README.md(≥200 words, includes Modules table linking to each.md) - 2.6 Add
package.jsonexportsmap with subpaths.,./backend,./factory,./write-through,./merge
3. Implementation
- 3.1 Author
src/backend.types.ts— portChainedBackendOptionsandMergeStrategyfrom monolith L15-L28 verbatim - 3.2 Author
src/backend.schemas.ts— portChainedBackendOptionsSchemaZod schema verbatim (11 lines); validatesbackendsis a non-empty array - 3.3 Author
src/write-through.ts— extract#writeThroughlogic into a standalonewriteThrough(backends, successIndex, locale, namespace, record): Promise<void>(best-effort; swallows errors) - 3.4 Author
src/merge.ts— extract inline merge logic fromreadMultiinto a standalonemergeReadMulti(perBackend, strategy): Record<string, TranslationRecord> - 3.5 Author
src/observability.ts— inline equivalents ofrecordRead/recordWrite; exporti18n.backend.chained.read_duration_ms,i18n.backend.chained.write_duration_mshistograms; countersread_total{outcome},read_errors_total{code},write_total{outcome},fallthrough_total{from_backend,to_backend}(low-cardinality — backends identified by their.name) - 3.6 Author
src/backend.ts— portChainedBackendclass from monolith L43-L230; rewire imports; usewriteThroughandmergeReadMultihelpers; emit metrics including the newfallthrough_totalcounter on everyBACKEND_LOCALE_NOT_AVAILABLE-drivencontinue; add optionalsignal?: AbortSignalparameter onread/readMulti/saveand thread it into every child call; throwI18nError(I18N.CHAINED_BACKEND_ALL_FAILED)ifbackends.length === 0is reached at runtime (defence-in-depth past the schema check)
4. Factory function
- 4.1 Author
src/factory.ts—chainedBackend(backendsOrOptions: readonly Backend[] | ChainedBackendOptions): ChainedBackendsupporting both array shorthand and full options object (per monolith L233-L242)
5. Documentation (sibling .md per implementation file)
- 5.1
src/backend.md— port frompackages/i18n/i18n/src/core/backend/chained/chained-backend.md; ≥200 words; document fallthrough semantics, write-through cache pattern, BACKEND_LOCALE_NOT_AVAILABLE vs CHAINED_BACKEND_ALL_FAILED distinction - 5.2
src/factory.md— ≥200 words; basic + advanced examples (array shorthand vs options object) - 5.3
src/write-through.md— best-effort semantics; per-backend save-capability gating - 5.4
src/merge.md— first-wins vs last-wins examples; gap-filling behaviour - 5.5
src/observability.md— emitted metrics + units;fallthrough_totalcardinality (= number of unique chain links observed) - 5.6
README.md## Modulessection links every.md
6. Tests (Vitest, runner-neutral imports per CLAUDE.md §11)
- 6.1
src/backend.test.ts— port every test frompackages/i18n/i18n/src/core/backend/chained/chained-backend.test.ts(495 lines); sameit(...)titles for conformance traceability; cover fallthrough, propagation ofBACKEND_READ_FAILED, write-through, readMulti merge strategies, save fan-out, lastModified first-defined, init/dispose lifecycle, capabilities union - 6.2
src/backend.spec-conformance.test.ts— port from monolith spec-conformance suite (102 lines); add new requirements (CHAINED_BACKEND_ALL_FAILED on empty backends, AbortSignal cancellation, fallthrough_total counter emission) - 6.3
src/factory.test.ts— array form returnsChainedBackend; options form returnsChainedBackend; defaultname === "chained" - 6.4
src/write-through.test.ts— writes only to backends withsave; saves at indices 0..N-1; never writes to the success backend; failures swallowed (best-effort) - 6.5
src/merge.test.ts— first-wins picks earlier; last-wins picks later; gaps filled from later - 6.6
src/index.test.ts— every documented public export resolves through.,./backend,./factory,./write-through,./merge - 6.7 Add an AbortSignal cancellation test — pre-aborted signal causes
readto throwI18nError(I18N.BACKEND_TIMEOUT); signal threaded into child reads (verify via stub backend that records its received signal) - 6.8 Add a
CHAINED_BACKEND_ALL_FAILEDtest — bypass schema validation viaas unknown as ChainedBackendOptionscast and passbackends: []; runtime throw on firstreadcarries the new code - 6.9 Every
it(...)carries{ timeout: 5000 } - 6.10 All test imports go through
@suaveplan/testing/runner(CLAUDE.md §11) — verified bygrep -rnE "from ['\"](vitest|bun:test)['\"]" packages/i18n/i18n-backend-chained/src/returning zero hits - 6.11 No
// biome-ignoresuppressions (CLAUDE.md §9) - 6.12 No
export *in any non-root barrel (CLAUDE.md §10)
7. Pre-archive verification (5-gate triple)
- 7.1
bun run check— Biome 0/0/0 across the monorepo - 7.2
bunx turbo lint typecheck test --filter=@suaveplan/i18n-backend-chained— green - 7.3
bunx turbo test:coverage --filter=@suaveplan/i18n-backend-chained— 100/100/100/100 - 7.4
bun scripts/openspec-conformance.ts @suaveplan/i18n-backend-chained— exit 0 - 7.5
bunx turbo build --filter=@suaveplan/i18n-backend-chained— green
8. Pre-archive 7-row checklist (AGENTS.md Section 13)
- 8.1 Generics threaded —
Backend[]correctly typed;MergeStrategydiscriminator narrowed exhaustively - 8.2 Zod schemas wired at the public boundary —
ChainedBackendOptionsSchemaparses constructor input AND rejects emptybackendsarray withINVALID_OPTIONS - 8.3 Every error code thrown by at least one production path:
INVALID_OPTIONS(constructor),BACKEND_LOCALE_NOT_AVAILABLE(all children miss with non-empty chain),CHAINED_BACKEND_ALL_FAILED(defence-in-depth empty-chain throw),BACKEND_READ_FAILED(propagation from child),BACKEND_WRITE_FAILED(save fan-out failure),BACKEND_TIMEOUT(signal aborted) - 8.4 Strict-mode tested — exhaustive switch on
MergeStrategyenforced viaexpectTypeOf - 8.5 OTel histograms emitted from production paths:
i18n.backend.chained.read_duration_ms,i18n.backend.chained.write_duration_ms; countersread_total,read_errors_total,write_total,fallthrough_total{from_backend,to_backend} - 8.6 Lifecycle dedup —
initis idempotent (re-calling forwards to children but does not duplicate state);disposeis idempotent and reverse-order - 8.7 No retroactive tick — every box reflects work done in this change
9. Manifest + ecosystem awareness
- 9.1 Add
@suaveplan/i18n-backend-chainedentry to rootCLAUDE.mdpackage listing - 9.2 Run
bun scripts/folder-reorg/generate-manifest.tsto refreshMANIFEST.md - 9.3 Append
@suaveplan/i18n-backend-chainedtoSTATUS.mdCompleted list
10. Archive
- 10.1 Fold spec delta into
openspec/specs/i18n-backend-chained/spec.md(NEW capability spec — first delta becomes canonical) - 10.2 Move
openspec/changes/i18n-backend-chained/→openspec/archive/<yyyy-mm-dd>-i18n-backend-chained/