Initial Commit
This commit is contained in:
70
openspec/changes/qrcode-batch-generator/proposal.md
Normal file
70
openspec/changes/qrcode-batch-generator/proposal.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# QR code batch generator
|
||||
|
||||
**Package**: `@suaveplan/qrcode`
|
||||
**Status**: PROPOSED
|
||||
**Type**: spec-conformance + feature
|
||||
|
||||
## Why
|
||||
|
||||
The 2026-05-02 audit of `@suaveplan/qrcode` against legacy `packages/universal/qrcode/src/` found that genesis ships only the single-call `generate()` and the `core/*` building blocks. Legacy ships a full `batch/batch-generator.ts` exposing three flavours of batch surface — `generateBatch`, `generateBatchAsync` (async generator with concurrency), and `generateBatchWithProgress` — used by every consumer that produces many QR codes at once: ticketing, vCard imports, asset registers, and bulk loyalty-card issuance. CLAUDE.md scopes this package as "generator only" — `qr-reader`, `styled-qr` (logos / colours / shapes), and `qr-exporter` (PNG/PDF) are intentionally out of scope. The batch generator is unambiguously part of the generator surface, not any of those scoped-out caps. Its absence forces every consumer to re-implement an error-isolating loop in user code, which §0a (migration parity) bans.
|
||||
|
||||
## What changes
|
||||
|
||||
- ADDED: `BatchQrCodeRequest` interface — `{ id?: string; value: string; options?: QRCodeOptions }`. Per-item `id` is optional; when omitted the request index is used as the result key.
|
||||
- ADDED: `BatchQrCodeResult` discriminated union — `{ id: string; index: number; ok: true; result: QRCodeResult } | { id: string; index: number; ok: false; error: QRCodeError }`. Failed items carry a coded `QRCodeError`, never a bare string.
|
||||
- ADDED: `BatchQrCodeOptions` interface — `{ concurrency?: number; signal?: AbortSignal; onItem?: (result: BatchQrCodeResult) => void }`. `concurrency` defaults to `1` (sequential) and is clamped to `[1, 64]`.
|
||||
- ADDED: `BatchQrCodeSummary` interface — `{ total: number; succeeded: number; failed: number; durationMs: number; results: BatchQrCodeResult[] }`. `durationMs` uses an injectable `Clock`.
|
||||
- ADDED: `generateBatch(requests, options?): Promise<BatchQrCodeSummary>` — produces one `BatchQrCodeResult` per input request in input order; per-item failures isolated.
|
||||
- ADDED: `generateBatchAsync(requests, options?): AsyncGenerator<BatchQrCodeResult>` — async iterable that yields results as they complete (completion order, not input order); honours `concurrency` and `signal`.
|
||||
- ADDED: `BatchQrCodeGenerator` class — `class BatchQrCodeGenerator { constructor(deps?: { clock?: Clock }); generate(requests, options?): Promise<BatchQrCodeSummary>; stream(requests, options?): AsyncGenerator<BatchQrCodeResult> }` for callers that want to inject a deterministic clock or share a generator across calls.
|
||||
- ADDED: subpath export `./batch` mapping to `dist/core/batch/batch-generator.js`.
|
||||
- ADDED: error code `QRCODE.BATCH_ABORTED` registered on `QRCODE_CODES`, thrown when the supplied `AbortSignal` fires before completion.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- **QR reading / decoding (qr-reader)** — explicitly out of scope per CLAUDE.md package description ("generator only"). Tracked under separate `qrcode-scan` change already in `openspec/changes/`.
|
||||
- **Styled QR (logos / colour gradients / module shapes — styled-qr)** — intentionally out of scope per CLAUDE.md. Would be its own package.
|
||||
- **Raster export (PNG / PDF — qr-exporter)** — intentionally out of scope per CLAUDE.md. The package emits SVG/matrix/terminal/dataurl only; raster export belongs in a downstream package that depends on `@suaveplan/qrcode` plus a rasteriser.
|
||||
- **Higher QR versions (11 – 40)** — current package supports versions 1 – 10 only. Extending the version table is its own change; batch behaviour is independent of version count and works identically once added.
|
||||
|
||||
## Source mapping
|
||||
|
||||
This change derives from `../suaveplan/packages/universal/qrcode/src/batch/`.
|
||||
|
||||
| Requirement | Source lines | Notes |
|
||||
|---|---|---|
|
||||
| `BatchQrCodeRequest` shape | `types/qr.types.ts` L94-L98 | Reworded: `data` → `value`; added optional `id`; removed `outputFormat` (legacy `BatchItem` carries it; new shape relies on `QRCodeOptions`/`QRCodeResult` having every output already) |
|
||||
| `BatchQrCodeResult` discriminated union | (no source line) | Legacy returns `QrCode[]` with no per-item failure mode — every failure throws and aborts the batch. New shape adds discriminated-union failure isolation (parity with `barcode-batch-generator`). |
|
||||
| `BatchQrCodeOptions` shape with `concurrency`, `signal`, `onItem` | `types/qr.types.ts` L103-L106 (legacy `BatchOptions`) | Extended: legacy has `onProgress: (completed, total) => void` and `concurrency`; new shape replaces `onProgress` with `onItem` (richer payload), adds `signal` |
|
||||
| `generateBatch` Promise-based function | `batch/batch-generator.ts` L12-L29 | Verbatim semantics for the success path; new failure-isolation behaviour replaces legacy throw-and-abort (legacy bug was that one bad input poisoned the whole batch) |
|
||||
| `generateBatchAsync` async-iterable function | `batch/batch-generator.ts` L35-L59 | Verbatim — yields per item, supports `concurrency` |
|
||||
| `BatchQrCodeGenerator` class | (no source line) | New: legacy is free functions only; class added for clock injection |
|
||||
|
||||
### Requirements with no source line
|
||||
|
||||
| Requirement | Justification |
|
||||
|---|---|
|
||||
| Per-item failure isolation in `generateBatch` | Legacy throws on first per-item failure (`generator.generate` rejects the loop). Genesis-wide CLAUDE.md §Workflow rule 2 ("no half-baked libraries") plus the §0a migration-parity rule mean we keep legacy's batch surface but fix the obvious correctness bug rather than ship the broken contract. |
|
||||
| `BatchQrCodeGenerator` class with injectable `Clock` | Genesis-wide CLAUDE.md determinism rule (`enterprise/bitpacker.md` L40 "no module-scope `Date.now()`"). Legacy reads `Date.now()` implicitly through `performance.now`; genesis routes through `@suaveplan/types::Clock`. |
|
||||
| `QRCODE.BATCH_ABORTED` error code on `QRCODE_CODES` | CLAUDE.md §Workflow rule 2 — every thrown error routes through `@suaveplan/error` with a stable code. Legacy throws plain `Error` from the loop (no abort plumbed in legacy at all). |
|
||||
| `concurrency` clamping to `[1, 64]` | Genesis-wide guardrail — unbounded `Promise.all` over user input is a denial-of-service vector. Legacy clamps to a default of 4 but accepts any number; genesis hardens the upper bound. |
|
||||
| `AbortSignal` support | Legacy has none. Genesis-wide pattern (every async API in `@suaveplan/retry`, `@suaveplan/idempotency`, etc.) accepts an `AbortSignal`. |
|
||||
|
||||
## Verification approach
|
||||
|
||||
- `src/core/batch/batch-generator.test.ts` — covers `BatchQrCodeRequest` validation, in-order results from `generateBatch`, completion-order from `generateBatchAsync`, per-item failure isolation, `concurrency` clamping, `onItem` callback invocation, abort-via-`AbortSignal` for both shapes.
|
||||
- `src/core/batch/batch-generator.spec-conformance.test.ts` — pins injectable `Clock` (deterministic `durationMs`), Zod-validated `BatchQrCodeRequest` shape, `QRCODE.BATCH_ABORTED` error code emission, OTel counter `qrcode.batch.items_total` / `qrcode.batch.items_failed` / histogram `qrcode.batch.duration_ms` recorded against an in-memory `MeterProvider`.
|
||||
|
||||
## Impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
Run `bunx turbo ls --filter=...@suaveplan/qrcode` to enumerate. As of 2026-05-02 there are no in-repo dependents:
|
||||
|
||||
- No dependents (verified by `grep -rl "@suaveplan/qrcode" /home/ubuntu/web/genesis/packages/ --include="package.json"` returning only `packages/barcode/qrcode/package.json` itself).
|
||||
|
||||
This change is purely additive (new exports, no signature changes to existing surface), so the absence of dependents is not a risk gate.
|
||||
|
||||
## Migration impact
|
||||
|
||||
None — additive only. Existing `generate()`, the `core/*` building blocks, the renderers, and `QRCODE_CODES` are unchanged. Consumers wanting batch generation opt in by importing `generateBatch` / `generateBatchAsync` from `@suaveplan/qrcode` or `@suaveplan/qrcode/batch`.
|
||||
159
openspec/changes/qrcode-batch-generator/specs/qrcode/spec.md
Normal file
159
openspec/changes/qrcode-batch-generator/specs/qrcode/spec.md
Normal file
@@ -0,0 +1,159 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: qrcode (qrcode-batch-generator)
|
||||
|
||||
**Package**: `@suaveplan/qrcode`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions. (See `openspec/AGENTS.md` Section 13.)
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: BatchQrCodeGenerator with per-item failure isolation
|
||||
|
||||
The package SHALL expose `generateBatch(requests, options?): Promise<BatchQrCodeSummary>` and `BatchQrCodeGenerator.generate()`. The function SHALL produce one `BatchQrCodeResult` per input request in input order. A failure on any single request — encoder rejection, Reed-Solomon overflow, or any thrown exception inside the per-item path — SHALL NOT short-circuit the batch; the failure SHALL surface as `{ ok: false, error: QRCodeError }` for that index while every other request continues to generate. Successful items SHALL surface as `{ ok: true, result: QRCodeResult }`. The summary SHALL include `total`, `succeeded`, `failed`, `durationMs` (from injected `Clock`), and the full `results` array.
|
||||
|
||||
#### Scenario: every request succeeds
|
||||
|
||||
- **WHEN** `generateBatch` is called with five valid requests at error-correction `M`
|
||||
- **THEN** `summary.total === 5` AND `summary.succeeded === 5` AND `summary.failed === 0` AND every `results[i].ok === true` AND `results[i].result.errorCorrectionLevel === "M"`
|
||||
|
||||
#### Scenario: one request exceeds capacity — others still succeed
|
||||
|
||||
- **WHEN** `generateBatch` is called with three short valid requests and one request whose value exceeds the version-10 byte capacity at error-correction `H`
|
||||
- **THEN** `summary.succeeded === 3` AND `summary.failed === 1` AND `results[3].ok === false` AND `results[3].error` is a `QRCodeError` with code `"QRCODE.DATA_TOO_LARGE"` (or whatever capacity-overflow code `QRCODE_CODES` defines) AND the three short results are present and valid
|
||||
|
||||
#### Scenario: input order is preserved by generateBatch
|
||||
|
||||
- **WHEN** `generateBatch` is called with `concurrency: 8` over 16 requests
|
||||
- **THEN** `results[i].index === i` for every `i` AND `results.length === 16` regardless of completion order
|
||||
|
||||
**Verified by:** `src/core/batch/batch-generator.test.ts::"generateBatch returns one result per input in input order"` AND `src/core/batch/batch-generator.test.ts::"generateBatch isolates per-item failures and continues"` AND `src/core/batch/batch-generator.test.ts::"generateBatch preserves input order under concurrency 8"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: generateBatchAsync yields in completion order
|
||||
|
||||
The package SHALL expose `generateBatchAsync(requests, options?): AsyncGenerator<BatchQrCodeResult>` and `BatchQrCodeGenerator.stream()`. The generator SHALL yield one `BatchQrCodeResult` per input request, in completion order (not input order). The generator SHALL honour `concurrency` strictly (no more than `concurrency` requests in-flight at once) and SHALL honour `signal` (subsequent `next()` after abort rejects with `BARCODE.BATCH_ABORTED`).
|
||||
|
||||
#### Scenario: completion-order yield
|
||||
|
||||
- **WHEN** `generateBatchAsync` is iterated over four requests where the third has artificially-delayed encoding (via `concurrency: 4`)
|
||||
- **THEN** the slow item is yielded last AND every yielded result still carries the original `index` field matching its position in the input array
|
||||
|
||||
#### Scenario: concurrency throttles in-flight requests
|
||||
|
||||
- **WHEN** `generateBatchAsync` is iterated with `concurrency: 2` and an `onItem` spy that records the count of currently in-flight items
|
||||
- **THEN** the recorded peak in-flight count never exceeds 2
|
||||
|
||||
**Verified by:** `src/core/batch/batch-generator.test.ts::"generateBatchAsync yields in completion order"` AND `src/core/batch/batch-generator.test.ts::"generateBatchAsync throttles in-flight requests to concurrency"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Concurrency control with safe clamping
|
||||
|
||||
The package SHALL accept an optional `concurrency` option on `BatchQrCodeOptions`. Default for `generateBatch` SHALL be `1` (sequential). Default for `generateBatchAsync` SHALL be `4` (matching legacy). Both SHALL clamp the value to the inclusive range `[1, 64]`.
|
||||
|
||||
#### Scenario: generateBatch defaults to sequential
|
||||
|
||||
- **WHEN** `generateBatch(requests)` is called without `options.concurrency`
|
||||
- **THEN** at most one QR code is being generated at any time
|
||||
|
||||
#### Scenario: concurrency clamps to upper bound
|
||||
|
||||
- **WHEN** `generateBatch(requests, { concurrency: 1000 })` is called
|
||||
- **THEN** the internal worker pool is sized at 64 (verified by counting active in-flight promises via `onItem`) AND no error is thrown
|
||||
|
||||
#### Scenario: concurrency clamps to lower bound
|
||||
|
||||
- **WHEN** `generateBatch(requests, { concurrency: 0 })` is called
|
||||
- **THEN** execution is sequential (effective concurrency 1) AND no error is thrown
|
||||
|
||||
**Verified by:** `src/core/batch/batch-generator.test.ts::"generateBatch defaults to sequential execution"` AND `src/core/batch/batch-generator.test.ts::"generateBatch clamps concurrency above 64 to 64"` AND `src/core/batch/batch-generator.test.ts::"generateBatch clamps concurrency below 1 to 1"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: AbortSignal cancels the batch
|
||||
|
||||
The package SHALL accept an optional `AbortSignal` on `BatchQrCodeOptions`. For `generateBatch`, when the signal fires the returned promise SHALL reject with a `QRCodeError` whose `code === "QRCODE.BATCH_ABORTED"`. For `generateBatchAsync`, when the signal fires the next `await iterator.next()` SHALL reject with the same coded error. In-flight per-item generations MAY complete or be discarded; queued items SHALL NOT be started. The error SHALL carry the original `AbortSignal.reason` as `cause` if present.
|
||||
|
||||
#### Scenario: pre-fired signal aborts immediately
|
||||
|
||||
- **WHEN** `generateBatch(requests, { signal })` is called with an already-aborted `AbortSignal`
|
||||
- **THEN** the returned promise rejects with `QRCodeError` AND `error.code === "QRCODE.BATCH_ABORTED"` AND no item is generated
|
||||
|
||||
#### Scenario: signal fires mid-stream
|
||||
|
||||
- **WHEN** `generateBatchAsync(requests, { signal })` is iterated for 10 yields, then `signal.abort()` is called
|
||||
- **THEN** the next `iterator.next()` rejects with `QRCodeError` of code `"QRCODE.BATCH_ABORTED"` AND no further items are generated
|
||||
|
||||
**Verified by:** `src/core/batch/batch-generator.test.ts::"generateBatch rejects immediately when signal is pre-aborted"` AND `src/core/batch/batch-generator.test.ts::"generateBatchAsync rejects next() after signal fires mid-stream"` AND `src/core/batch/batch-generator.spec-conformance.test.ts::"abort path emits QRCODE.BATCH_ABORTED with chained cause"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: onItem progress callback emits coded results
|
||||
|
||||
The package SHALL accept an optional `onItem: (result: BatchQrCodeResult) => void` callback on `BatchQrCodeOptions`. The callback SHALL fire exactly once per completed request. For `generateBatch` the callback fires in completion order; for `generateBatchAsync` the callback fires immediately before each `yield`. A throw from `onItem` SHALL NOT abort the batch.
|
||||
|
||||
#### Scenario: onItem fires once per request
|
||||
|
||||
- **WHEN** `generateBatch` is called with 20 requests and an `onItem` spy
|
||||
- **THEN** the spy is called 20 times AND every emitted result has the same identity as the corresponding `summary.results[i]`
|
||||
|
||||
#### Scenario: onItem callback throwing does not break the batch
|
||||
|
||||
- **WHEN** the `onItem` callback throws for one item
|
||||
- **THEN** the batch still resolves normally AND the throw is captured into the OTel span as a non-fatal event
|
||||
|
||||
**Verified by:** `src/core/batch/batch-generator.test.ts::"onItem fires exactly once per completed request"` AND `src/core/batch/batch-generator.spec-conformance.test.ts::"onItem callback errors are isolated from the batch"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Deterministic durationMs via injectable Clock
|
||||
|
||||
The package SHALL accept an injectable `Clock` (`@suaveplan/types::Clock`) on the `BatchQrCodeGenerator` constructor. `durationMs` on `BatchQrCodeSummary` SHALL be computed as `clock.now() - startedAt`. The free `generateBatch` and `generateBatchAsync` functions SHALL use a default real clock; tests SHALL be able to inject a deterministic clock for stable assertions.
|
||||
|
||||
#### Scenario: injected clock yields deterministic duration
|
||||
|
||||
- **WHEN** a `BatchQrCodeGenerator` is constructed with a `FakeClock` that advances by 11 ms after each `now()` call, and `generate` is invoked over 4 requests
|
||||
- **THEN** `summary.durationMs === 11` (start-to-finish span of one tick)
|
||||
|
||||
#### Scenario: no module-scope Date.now / performance.now
|
||||
|
||||
- **WHEN** the `batch-generator.ts` source is grepped for `Date.now(`, `performance.now(`, `Math.random(`
|
||||
- **THEN** zero matches are found at module scope
|
||||
|
||||
**Verified by:** `src/core/batch/batch-generator.spec-conformance.test.ts::"injected FakeClock yields deterministic durationMs"` AND `src/core/batch/batch-generator.spec-conformance.test.ts::"batch-generator source has no module-scope Date.now or Math.random"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: QRCODE.BATCH_ABORTED registered in QRCODE_CODES
|
||||
|
||||
The package SHALL register `QRCODE.BATCH_ABORTED` in `QRCODE_CODES` (the stable error-code registry). The code SHALL be the only error thrown from the abort path; per-item failures continue to use existing codes (capacity overflow, mode-mismatch, etc.).
|
||||
|
||||
#### Scenario: code is registered
|
||||
|
||||
- **WHEN** `QRCODE_CODES` is enumerated
|
||||
- **THEN** the array includes `"QRCODE.BATCH_ABORTED"`
|
||||
|
||||
#### Scenario: code is the documented type
|
||||
|
||||
- **WHEN** the `QRCodeCode` exported type is inspected
|
||||
- **THEN** `"QRCODE.BATCH_ABORTED"` is one of its members (verified by `expectTypeOf<"QRCODE.BATCH_ABORTED">().toMatchTypeOf<QRCodeCode>()`)
|
||||
|
||||
**Verified by:** `src/errors/qrcode-error.test.ts::"QRCODE_CODES includes BATCH_ABORTED"` AND `src/core/batch/batch-generator.spec-conformance.test.ts::"QRCodeCode union includes BATCH_ABORTED"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: ./batch subpath export and tree-shakeability
|
||||
|
||||
The package SHALL expose `./batch` as a tree-shakeable subpath in `package.json` `exports`, mapping to `dist/core/batch/batch-generator.js`. Importing only `generateBatch` SHALL NOT pull in the entire `core/encoder` and `core/reed-solomon` graph for callers that don't use them transitively.
|
||||
|
||||
#### Scenario: subpath resolves to the same function as the root export
|
||||
|
||||
- **WHEN** a consumer imports `{ generateBatch } from "@suaveplan/qrcode/batch"` and `{ generateBatch as fromRoot } from "@suaveplan/qrcode"`
|
||||
- **THEN** the two references are identity-equal
|
||||
|
||||
**Verified by:** `src/core/batch/batch-generator.test.ts::"./batch subpath and root export resolve to the same function"`
|
||||
|
||||
<!-- markdownlint-enable MD024 -->
|
||||
81
openspec/changes/qrcode-batch-generator/tasks.md
Normal file
81
openspec/changes/qrcode-batch-generator/tasks.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Tasks — QR code batch generator
|
||||
|
||||
> Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes.
|
||||
|
||||
## 0. Pre-flight (non-negotiable)
|
||||
|
||||
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
|
||||
- [ ] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
|
||||
- [ ] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
|
||||
|
||||
## 1. Spec authoring
|
||||
|
||||
- [x] 1.1 Read `../suaveplan/packages/universal/qrcode/src/batch/batch-generator.ts` end-to-end
|
||||
- [x] 1.2 Read `openspec/specs/qrcode/spec.md` (does not exist yet — capability spec authored fresh in this change)
|
||||
- [x] 1.3 Read `openspec/AGENTS.md` Sections 13, 13a, 14, 14a
|
||||
- [x] 1.4 Draft `proposal.md` from `openspec/templates/proposal.md`
|
||||
- [x] 1.5 Fill in `## Source mapping` table — every requirement cites a line range OR appears in the "no source line" table with justification
|
||||
- [x] 1.6 Draft `specs/qrcode/spec.md` from `openspec/templates/spec-delta.md`
|
||||
- [x] 1.7 Every requirement in the spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles
|
||||
|
||||
## 2. Implementation
|
||||
|
||||
- [ ] 2.1 Add `src/core/batch/batch-generator.ts` exporting `BatchQrCodeRequest`, `BatchQrCodeResult`, `BatchQrCodeOptions`, `BatchQrCodeSummary`, `generateBatch`, `generateBatchAsync`, `BatchQrCodeGenerator`
|
||||
- [ ] 2.2 Add `src/core/batch/batch-generator.types.ts` with shared interface declarations and Zod schemas (`BatchQrCodeRequestSchema`, `BatchQrCodeOptionsSchema`)
|
||||
- [ ] 2.3 Wire `QRCODE.BATCH_ABORTED` into `src/errors/qrcode-error.ts::QRCODE_CODES`; throw `QRCodeError` (never plain `Error`) from the abort path and from per-item failures
|
||||
- [ ] 2.4 Concurrency limiter — clamp to `[1, 64]`; sequential default for `generateBatch`; `generateBatchAsync` honours `concurrency` strictly; both honour `AbortSignal` between items
|
||||
- [ ] 2.5 OTel instrumentation: `qrcode.batch.items_total` (counter), `qrcode.batch.items_failed` (counter), `qrcode.batch.duration_ms` (histogram)
|
||||
- [ ] 2.6 Inject `Clock` via constructor dependency on `BatchQrCodeGenerator`; free functions use the default real clock
|
||||
- [ ] 2.7 Add `./batch` subpath to `package.json` `exports`
|
||||
- [ ] 2.8 Re-export new symbols from `src/index.ts`
|
||||
|
||||
## 3. Tests
|
||||
|
||||
- [ ] 3.1 `src/core/batch/batch-generator.test.ts` — all scenarios from spec delta (in-order results, completion-order async iterable, failure isolation, concurrency clamp, `onItem`, abort, version-1-through-10 coverage)
|
||||
- [ ] 3.2 `src/core/batch/batch-generator.spec-conformance.test.ts` — injectable `Clock`, Zod `BatchQrCodeRequest` rejection with structured `ZodError`, `QRCODE.BATCH_ABORTED` code emission, OTel counters/histogram asserted via in-memory `MeterProvider`
|
||||
- [ ] 3.3 Property test (fast-check): for any `Array<{ value }>` of length ≤ 50 where every value fits a valid encoding, `generateBatch` SHALL return `succeeded === input.length` and `failed === 0`
|
||||
- [ ] 3.4 Every `it(...)` carries `{ timeout: 5000 }` (or 30000 for genuinely async)
|
||||
|
||||
## 4. Documentation
|
||||
|
||||
- [ ] 4.1 `src/core/batch/batch-generator.md` — sibling doc, ≥200 words, basic + advanced examples (sequential, async iterable, concurrent, abort, custom clock), full API reference for every public export, implementation notes covering concurrency clamping and per-item isolation
|
||||
- [ ] 4.2 README links the new co-located `.md` under `## Modules`
|
||||
- [ ] 4.3 Update `CLAUDE.md` `qrcode` listing to mention "batch generation with per-item failure isolation"
|
||||
|
||||
## 5. Pre-archive verification (the 5-gate triple)
|
||||
|
||||
- [ ] 5.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info
|
||||
- [ ] 5.2 `bunx turbo lint typecheck test --filter=@suaveplan/qrcode` — green
|
||||
- [ ] 5.3 `bunx turbo test:coverage --filter=@suaveplan/qrcode` — 100/100/100/100
|
||||
- [ ] 5.4 `bun scripts/openspec-conformance.ts @suaveplan/qrcode` — exit 0, no `error` findings
|
||||
- [ ] 5.5 `bunx turbo build --filter=@suaveplan/qrcode` — green
|
||||
|
||||
## 6. Pre-archive 7-row checklist (AGENTS.md Section 13)
|
||||
|
||||
- [ ] 6.1 Generics threaded; `expectTypeOf` assertions on `BatchQrCodeResult` discriminated-union narrowing
|
||||
- [ ] 6.2 Zod schemas wired at every public boundary (`generateBatch`, `generateBatchAsync`, `BatchQrCodeGenerator.generate`, `.stream`)
|
||||
- [ ] 6.3 Every error code in `error-codes.ts` thrown by at least one production path (`QRCODE.BATCH_ABORTED` covered by abort scenario)
|
||||
- [ ] 6.4 Strict-mode behaviors tested both on and off (concurrency 1 vs > 1; failure isolation always on)
|
||||
- [ ] 6.5 OTel instruments emitted from production paths and asserted with a real in-memory `MeterProvider`
|
||||
- [ ] 6.6 Lifecycle dedup tested where claimed (`BatchQrCodeGenerator.generate` and `.stream` are stateless across calls — verified by parallel-call test)
|
||||
- [ ] 6.7 No retroactive tick — every box above reflects work done in this change
|
||||
|
||||
## 7. Archive
|
||||
|
||||
- [ ] 7.1 Fold spec delta into `openspec/specs/qrcode/spec.md` (creates the canonical capability spec from scratch since none exists)
|
||||
- [ ] 7.2 Move `openspec/changes/qrcode-batch-generator/` → `openspec/archive/<yyyy-mm-dd>-qrcode-batch-generator/`
|
||||
- [ ] 7.3 Refresh `MANIFEST.md` if package set or deps changed (no change expected — purely additive feature)
|
||||
|
||||
---
|
||||
|
||||
## Audit Note (2026-05-02)
|
||||
|
||||
**Status**: DEFERRED.
|
||||
|
||||
State at audit:
|
||||
|
||||
- `@suaveplan/qrcode` package itself is shipped and green, but the batch-generator feature has **not** been authored. No `src/core/batch/` directory exists. The current `src/core/` only contains `encoder/`, `mask/`, `matrix/`, `reed-solomon/`, `renderer/`, `tables/`, plus the top-level `qrcode.ts` orchestrator.
|
||||
- Section 1 (spec authoring) is checked, so the proposal + spec delta exist; Sections 2–7 (implementation, tests, docs, verification, archive) are entirely unchecked.
|
||||
- `QRCODE.BATCH_ABORTED` is not yet wired into `src/errors/qrcode-error.ts::QRCODE_CODES`.
|
||||
|
||||
Resuming work: execute Sections 2–7 in order. Reference upstream `../suaveplan/packages/universal/qrcode/src/batch/batch-generator.ts` (read in Section 1.1). Note this proposal additionally requires `generateBatchAsync` (completion-order async iterable) which the barcode equivalent does not.
|
||||
Reference in New Issue
Block a user