9.8 KiB
9.8 KiB
Tasks — ETL completion
Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened.
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin 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.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
1. Spec authoring
- 1.1 Read
../suaveplan/enterprise/etl.mdend-to-end - 1.2 Read
openspec/specs/etl/spec.md(does not exist yet — confirm and start fresh) - 1.3 Read
openspec/AGENTS.mdSections 13 and 14 - 1.4 Draft
proposal.mdfromopenspec/templates/proposal.md - 1.5 Fill in
## Source mappingtable — every requirement cites a line range OR appears in the "no source line" table with justification - 1.6 Draft
specs/etl/spec.mdfromopenspec/templates/spec-delta.md - 1.7 Every requirement in the spec delta carries a
**Verified by:**line naming concreteit(...)titles
2. Implementation — core/context/context
- 2.1 Port
PipelineContextclass from upstreamsrc/core/context/context.tsintopackages/platform/etl/src/core/context/context.ts - 2.2 Port
createContext(options)factory; options:{ pipelineName, initialData?, clock?, logger?, abortSignal? } - 2.3 Replace upstream
Date.now()with the injectableClockfrom@suaveplan/types; default toClock.system()from existing genesis setup - 2.4 Wire context through every step —
Pipeline.run()instantiates aPipelineContextper run and passes it to each stage'srun(ctx, ...)method - 2.5 Add
context.mdsibling doc (≥200 words, basic + advanced examples)
3. Implementation — BatchConfig
- 3.1 Add
BatchConfiginterface (size,concurrency,perItemTimeoutMs,batchTimeoutMs,continueOnError) totypes/etl.types.ts - 3.2 Extend
PipelineOptionswith optionalbatch?: BatchConfig - 3.3 Update
Pipeline.runto honourbatch.concurrencyviaparallelMap(from §5) - 3.4 Update
Pipeline.runto honourbatch.perItemTimeoutMsviawithTimeout(from §5) - 3.5 Update
Pipeline.runto honourbatch.batchTimeoutMsviawithTimeoutaround the whole-batch promise - 3.6 Update
Pipeline.runto honourbatch.continueOnError(skip failed batch, record error in context) - 3.7 Add
BATCH_ITEM_TIMEOUT,BATCH_TIMEOUT,BATCH_CONCURRENCY_INVALIDcodes toerrors/etl-error.ts - 3.8 Update
pipeline.mdsibling doc to cover batch config
4. Implementation — types/etl-hooks
- 4.1 Create
packages/platform/etl/src/types/etl-hooks/etl-hooks.tsexportingEtlHookMapextendingHookMapfrom@suaveplan/events/hooks.typeswith all eight lifecycle keys - 4.2 Define payloads:
EtlPipelineHookData,EtlStageHookData,EtlBatchHookData,EtlTransformHookData - 4.3 Export named handler aliases (
EtlPipelineStartedHandler,EtlStageFailedHandler, etc.) for consumer ergonomics - 4.4 Update
Pipelineto accepthooks?: HookBus<EtlHookMap>in options; default to a fresh internal bus - 4.5 Emit each lifecycle event in the right place inside
Pipeline.runandRunnablePipeline.run - 4.6 Add
etl-hooks.mdsibling doc (≥200 words, basic + advanced examples)
5. Implementation — utils/utils
- 5.1 Create
packages/platform/etl/src/utils/utils.tswithapplyMappings,getNestedValue,setNestedValue,parallelMap,toBatches,withTimeout - 5.2
withTimeoutrejects withEtlError(TIMEOUT)(not a plainError); pass through the original promise's rejection reason incause - 5.3
parallelMaprejects withEtlError(BATCH_CONCURRENCY_INVALID)whenconcurrency < 1 - 5.4
toBatchesreturns[]for empty input; rejects onsize < 1withEtlError(BATCH_CONCURRENCY_INVALID) - 5.5 Add
utils.mdsibling doc (≥200 words, basic + advanced examples) - 5.6 Re-export every symbol from
index.ts
6. Implementation — FieldMapping + mapTransform integration
- 6.1 Add
FieldMappinginterface totypes/etl.types.ts - 6.2 Update
TransformConfigto accept optionalmappings?: readonly FieldMapping[] - 6.3 Update
mapTransformto applymappings(viaapplyMappings) BEFORE invoking the transform function - 6.4 Re-export
FieldMappingfromindex.ts - 6.5 Update
transform.mdsibling doc
7. Tests
- 7.1
core/context/context.test.ts— set / get / has / delete; hook register / off; errors / stepResults appended; AbortSignal propagates to abortable steps - 7.2
core/context/context.spec-conformance.test.ts—expectTypeOf<PipelineContextOptions>pinned; injectedClockdeterministic in stampingmetadata.startedAt - 7.3
core/pipeline/pipeline.test.ts(extended) —batch.sizechunks input correctly;batch.concurrencyruns N in parallel (timing assertion);batch.perItemTimeoutMsrejects slow items withBATCH_ITEM_TIMEOUT;batch.batchTimeoutMsrejects slow batches;batch.continueOnError: trueskips failure - 7.4
types/etl-hooks/etl-hooks.test.ts— all 8 lifecycle keys fire once per transition with correct payloads - 7.5
types/etl-hooks/etl-hooks.spec-conformance.test.ts—expectTypeOfpins each key string; pins payload shapes - 7.6
utils/utils.test.ts—applyMappings(basic, with transform, missing source),getNestedValue(nested, missing, null guard),setNestedValue(creates intermediates),parallelMap(concurrency 1, 2, equal-to-input),toBatches(empty, exact, remainder),withTimeout(resolves under, rejects over) - 7.7
utils/utils.spec-conformance.test.ts—parallelMaphonours concurrency empirically (max-in-flight assertion via shared counter);withTimeoutrejects withEtlError(TIMEOUT);parallelMaprejects withEtlError(BATCH_CONCURRENCY_INVALID)forconcurrency < 1 - 7.8
errors/etl-error.spec-conformance.test.ts—ETL_CODESregistered after import; idempotent re-registration; new codes (BATCH_ITEM_TIMEOUT,BATCH_TIMEOUT,BATCH_CONCURRENCY_INVALID,TIMEOUT) thrown in production paths - 7.9 Every
it(...)in 7.1–7.8 carries{ timeout: 5000 }(or 30000 for genuinely async)
8. Documentation
- 8.1 Sibling
.mdfor every new implementation file (context.md,etl-hooks.md,utils.md); each ≥200 words with basic + advanced examples - 8.2 Update existing sibling
.mds touched in §3, §4, §6 (pipeline.md,transform.md) - 8.3 Update
packages/platform/etl/README.md— add Modules links for the three new files; add Quick-start snippet showing context + hooks subscription - 8.4 Update
CLAUDE.mdpackage listing line for@suaveplan/etlto reflect the expanded surface
9. Pre-archive verification (the 5-gate triple)
- 9.1
bun run check— Biome 0 errors / 0 warnings / 0 info across the whole monorepo - 9.2
bunx turbo lint typecheck test --filter=@suaveplan/etl— green - 9.3
bunx turbo test:coverage --filter=@suaveplan/etl— 100/100/100/100 - 9.4
bun scripts/openspec-conformance.ts @suaveplan/etl— exit 0, noerrorfindings - 9.5
bunx turbo build --filter=@suaveplan/etl— green
10. Pre-archive 7-row checklist (AGENTS.md Section 13)
- 10.1 Generics threaded through
PipelineContext<TData>,parallelMap<T, U>,applyMappings<TIn, TOut> - 10.2 Zod schemas wired at the
BatchConfigandPipelineOptionsboundaries (already partially inetl.schemas.ts— extend) - 10.3 Every error code in
errors/etl-error.tsthrown by at least one production path - 10.4 Strict-mode behaviors tested both on and off (
continueOnError: truevsfalse) - 10.5 OTel instruments emitted from production paths and asserted with a real in-memory
MeterProvider - 10.6 Lifecycle dedup tested where claimed (idempotent
registerEtlErrorCodes) - 10.7 No retroactive tick — every box above reflects work done in this change
11. Archive
- 11.1 Fold spec delta into
openspec/specs/etl/spec.md(will be the first time this canonical spec exists) - 11.2 Move
openspec/changes/etl-completion/→openspec/archive/<yyyy-mm-dd>-etl-completion/ - 11.3 Refresh
MANIFEST.mdif package set or deps changed (no expected change here)
Audit Note (2026-05-02)
Audit verdict: DEFERRED — 0/65 tasks evidenced in code.
Evidence reviewed:
packages/platform/etl/src/containscore/pipeline/,stages/{transform,source,sink}/,errors/,schemas/,types/,constants/. Nocore/context/, noutils/, notypes/etl-hooks/.errors/etl-error.tsdoes not declareBATCH_ITEM_TIMEOUT,BATCH_TIMEOUT,BATCH_CONCURRENCY_INVALID, orTIMEOUTcodes.- No
BatchConfigshape intypes/etl.types.ts(no batch concurrency / per-item timeout wired intoPipeline.run).
Missing pieces:
core/context/context.tsPipelineContextwith injectableClock+AbortSignal.BatchConfig(size / concurrency / perItemTimeoutMs / batchTimeoutMs / continueOnError) wired intoPipeline.runvia the newutils/utils.ts(parallelMap,withTimeout,toBatches).types/etl-hooks/etl-hooks.tsEtlHookMap+ payloads + emission insidePipeline.run, plusFieldMappingintegration intomapTransform.
Decision: leave change open. The three new modules are interlocking (context threads through hooks, batch config relies on the new utils) and need fresh tests + sibling docs.