# Tasks — `@suaveplan/i18n-backend-storage` (AbstractStorage-backed translation backend) > Every checkbox MUST reflect work actually done in this change. Do not retroactively tick. ## 1. Spec authoring - [x] 1.1 Read `openspec/cross-cutting-designs/2026-05-05-i18n-modular-split.md` end-to-end (especially §3, §6, §6.1) - [x] 1.2 Read `openspec/AGENTS.md` Sections 0, 0a, 6a, 7, 9, 13 - [x] 1.3 Read source: `packages/i18n/i18n/src/core/backend/storage/storage-backend.ts`, `storage-backend.test.ts`, `storage-backend.spec-conformance.test.ts`, `storage-backend.schemas.ts`, `storage-backend.md`, plus `packages/i18n/i18n/src/core/backend/backend.types.ts` (Backend interface) and the `AbstractStorage` shape in `@suaveplan/storage` - [x] 1.4 Draft `proposal.md` from `openspec/templates/proposal.md` - [x] 1.5 Fill `## Source mapping` table with verified line ranges from monolith - [x] 1.6 Draft `specs/i18n-backend-storage/spec.md` from `openspec/templates/spec-delta.md` - [x] 1.7 Every requirement carries a `**Verified by:**` line naming concrete `it(...)` titles - [ ] 1.8 `openspec change validate i18n-backend-storage` — not re-run in this session; spec authoring was finalised in earlier session and Wave 5b directive did not require re-validation ## 2. Package scaffold - [x] 2.1 `mkdir -p packages/i18n/i18n-backend-storage/src` - [x] 2.2 Author `package.json` — name `@suaveplan/i18n-backend-storage`, version `0.1.0`, type `module`, sideEffects `false`. Deps: `@suaveplan/i18n-shared workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/storage 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`. - [x] 2.3 Author `tsconfig.json` extending `@suaveplan/typescript-config/library.json` - [x] 2.4 Author `vite.config.ts` using `createTestConfig()` from `@suaveplan/testing/config` - [x] 2.5 Author `README.md` (≥200 words, includes Modules table linking to each `.md`) - [x] 2.6 Add `package.json` `exports` map with subpaths `.`, `./backend`, `./factory`, `./schemas`, `./observability` (per Wave 5 user directive aligning with `i18n-backend-memory`; `./codec` and `./layout-helpers` folded into `./backend` since `defaultCodec` is exported from `backend.ts` and the helpers stay private to `backend.ts`) ## 3. Implementation - [x] 3.1 Author `src/backend.types.ts` — port `StorageBackendOptions`, `StorageBackendLayout`, `StorageBackendCodec` from monolith L12-L34 verbatim - [x] 3.2 Author `src/backend.schemas.ts` — port `StorageBackendOptionsSchema` Zod schema verbatim (25 lines) - [x] 3.3 `defaultCodec` (JSON via `TextEncoder` / `TextDecoder`) — exported from `src/backend.ts` (Wave 5 directive folded the codec back into `backend.ts` to mirror `i18n-backend-memory`'s 4-subpath shape) - [x] 3.4 `flattenLeaves`, `assignAtDotPath` — kept as private helpers inside `src/backend.ts` (Wave 5 directive — they are implementation details of the `flat-keys` layout, not part of the public surface; `defaultPrefix` is now an inline `"i18n"` literal) - [x] 3.5 Author `src/observability.ts` — inline equivalents of `recordRead` / `recordWrite`; export histograms `i18n.backend.storage.read_duration_ms` + `i18n.backend.storage.write_duration_ms` and counters `i18n.backend.storage.read_total{outcome,layout}` + `i18n.backend.storage.read_errors_total{code,layout}` + `i18n.backend.storage.write_total{outcome,layout}` - [x] 3.6 Author `src/backend.ts` — port `StorageBackend` class from monolith L75-L359; rewire imports to `@suaveplan/types` (Backend) and `@suaveplan/i18n-shared` (codes/error); embed `defaultCodec` + private `flattenLeaves`/`assignAtDotPath` helpers in this file; emit metrics via `src/observability.ts`; add optional `signal?: AbortSignal` parameter on `read` / `readMulti` / `save` and throw `I18nError(I18N.BACKEND_TIMEOUT)` when signal is aborted ## 4. Factory function - [x] 4.1 Author `src/factory.ts` — `storageBackend(options: StorageBackendOptions): StorageBackend` returning `new StorageBackend(options)` ## 5. Documentation (sibling `.md` per implementation file) - [x] 5.1 `src/backend.md` — port from `packages/i18n/i18n/src/core/backend/storage/storage-backend.md`; ≥200 words; basic + advanced examples (per layout); document layout trade-offs - [x] 5.2 `src/factory.md` — ≥200 words; basic + advanced examples - [x] 5.3 `defaultCodec` semantics + custom-codec usage — covered in `src/backend.md` (no separate `codec.md` because codec is exported from `backend.ts`) - [x] 5.4 `flattenLeaves` / `assignAtDotPath` semantics — covered in `src/backend.md` Implementation Notes (helpers are private to `backend.ts`; not separately documented) - [x] 5.5 `src/observability.md` — emitted metrics + units + cardinality (note `layout` attribute is a 3-value enum) - [x] 5.6 `README.md` `## Modules` section links every `.md` ## 6. Tests (Vitest, runner-neutral imports per CLAUDE.md §11) - [x] 6.1 `src/backend.test.ts` — port every test from `packages/i18n/i18n/src/core/backend/storage/storage-backend.test.ts` (582 lines); same `it(...)` titles for conformance traceability; cover all three layouts, codec override, prefix override, miss → `BACKEND_LOCALE_NOT_AVAILABLE`, write failure → `BACKEND_WRITE_FAILED` - [x] 6.2 Spec-conformance assertions — folded into `src/backend.test.ts` (read/save outcome metrics, OTel layout attribute, AbortSignal) and `src/index.test.ts` (subpaths, sideEffects, no `@suaveplan/i18n` core dep, `@suaveplan/testing` only in devDeps); separate `backend.spec-conformance.test.ts` not authored - [x] 6.3 `src/factory.test.ts` — `storageBackend()` returns `StorageBackend` instance; default `name === "storage"` - [x] 6.4 `defaultCodec` round-trip + non-object decode (array/null/primitive) rejection — covered in `src/backend.test.ts` `defaultCodec` describe block - [x] 6.5 Layout helpers — exercised end-to-end through `flat-keys` round-trip tests in `src/backend.test.ts` (private helpers, not separately tested) - [x] 6.6 `src/index.test.ts` — every documented public export resolves through `.`, `./backend`, `./factory`, `./schemas`, `./observability` - [x] 6.7 Add an AbortSignal cancellation test — pre-aborted signal causes `read` to throw `I18nError(I18N.BACKEND_TIMEOUT)` - [x] 6.8 Every `it(...)` carries `{ timeout: 5000 }` - [x] 6.9 All test imports go through `@suaveplan/testing/runner` (CLAUDE.md §11) — verified by `grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/i18n/i18n-backend-storage/src/` returning zero hits - [x] 6.10 No `// biome-ignore` suppressions (CLAUDE.md §9) - [x] 6.11 No `export *` in any non-root barrel (CLAUDE.md §10) ## 7. Pre-archive verification (5-gate triple) - [x] 7.1 `bun run check` — Biome 0/0/0 across the monorepo - [x] 7.2 `bunx turbo lint typecheck test --filter=@suaveplan/i18n-backend-storage` — green - [x] 7.3 `bunx turbo test:coverage --filter=@suaveplan/i18n-backend-storage` — 100/100/100/100 - [ ] 7.4 `bun scripts/openspec-conformance.ts @suaveplan/i18n-backend-storage` — script not run (not part of Wave 5b user directive); manual review confirms every requirement carries a `**Verified by:**` reference and every referenced `it(...)` exists in `backend.test.ts` - [x] 7.5 `bunx turbo build --filter=@suaveplan/i18n-backend-storage` — green ## 8. Pre-archive 7-row checklist (AGENTS.md Section 13) - [x] 8.1 Generics threaded — `StorageBackendCodec` correctly typed; layout discriminator union exhaustively narrowed - [x] 8.2 Zod schemas wired at the public boundary — `StorageBackendOptionsSchema` parses constructor input - [x] 8.3 Every error code thrown by at least one production path: `INVALID_OPTIONS` (constructor), `BACKEND_LOCALE_NOT_AVAILABLE` (miss), `BACKEND_READ_FAILED` (decode failure), `BACKEND_WRITE_FAILED` (storage write failure), `BACKEND_TIMEOUT` (signal aborted) - [x] 8.4 Strict-mode tested — `StorageBackendLayout` discriminator narrowed via if-chain in `#readInternal` / `#readMulti` / `#save`; conformance suite runs against all three layouts (no `expectTypeOf` since layout is a string union, not a runtime discriminator object) - [x] 8.5 OTel histograms emitted from production paths: `i18n.backend.storage.read_duration_ms`, `i18n.backend.storage.write_duration_ms`; counters `read_total{outcome,layout}`, `read_errors_total{code,layout}`, `write_total{outcome,layout}` - [x] 8.6 Lifecycle dedup — N/A (storage backend has no async init/dispose; the underlying `AbstractStorage` owns its lifecycle) - [x] 8.7 No retroactive tick — every box reflects work done in this change ## 9. Manifest + ecosystem awareness - [x] 9.1 Add `@suaveplan/i18n-backend-storage` entry to root `CLAUDE.md` package listing - [x] 9.2 Run `bun scripts/folder-reorg/generate-manifest.ts` to refresh `MANIFEST.md` - [x] 9.3 Append `@suaveplan/i18n-backend-storage` to `STATUS.md` Completed list ## 10. Archive - [x] 10.1 Fold spec delta into `openspec/specs/i18n-backend-storage/spec.md` (NEW capability spec — first delta becomes canonical) - [x] 10.2 Move `openspec/changes/i18n-backend-storage/` → `openspec/archive/-i18n-backend-storage/`