Rename migration verify -> migration emit#353
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (4)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe PR replaces the "migration verify" attestation workflow with a new "migration emit" command that executes Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant CLI
participant EmitLib as emitMigration
participant Target as Target.migrations
participant MigrationTS
participant Attestation
User->>CLI: migration emit --dir <dir>
CLI->>EmitLib: emitMigration(dir, ctx)
EmitLib->>MigrationTS: hasMigrationTs(dir)?
MigrationTS-->>EmitLib: true
alt Target provides emit()
EmitLib->>Target: emit({ dir, frameworkComponents })
Target->>MigrationTS: dynamic import migration.ts
MigrationTS-->>Target: exports.default
alt default export is Migration subclass
Target->>MigrationTS: new MigrationExport()
Target->>MigrationTS: instance.plan()
else default export is factory function
Target->>MigrationTS: default()
MigrationTS-->>Target: { plan() }
Target->>MigrationTS: object.plan()
end
MigrationTS-->>Target: MigrationPlanOperation[]
Target-->>EmitLib: operations
else Target provides resolveDescriptors
EmitLib->>Target: resolveDescriptors(...)
Target-->>EmitLib: operations
end
EmitLib->>Attestation: attestMigration(dir)
Attestation-->>EmitLib: migrationId
EmitLib-->>CLI: { operations, migrationId }
CLI-->>User: Emitted ops.json and attested migration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/middleware-telemetry
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/emitter
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/runtime-executor
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-pipeline-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
122903f to
4be4a19
Compare
3bde981 to
53f8188
Compare
4be4a19 to
7482e70
Compare
53f8188 to
ba88a32
Compare
migration verify -> migration emit, add class-based migration scaffolding
migration verify -> migration emit, add class-based migration scaffoldingmigration verify -> migration emit
ba88a32 to
c97e2dc
Compare
…ame verify->emit migration verify becomes migration emit and now does the actual work (in-process dynamic-import the migration.ts file, run the emit helper, write attested ops.json) rather than just validating an external emit step. migration plan always runs emit inline as part of plan, dropping the needsDataMigration branch that the descriptor planner used to expose. Adds the emit capability to TargetMigrationsCapability and extracts an emitMigration helper that centrally owns attestation. The mongo family plugs in via mongoEmit, which dynamic-imports the class-flow migration.ts and writes ops.json. Structured error codes for emit-path validation: - PN-MIG-2002 errorMigrationFileMissing - PN-MIG-2003 errorMigrationInvalidDefaultExport - PN-MIG-2004 errorMigrationPlanNotArray Migration identity is now storage-only (migrationId derived from a content hash of the operations); the framework attestMigration helper extracts that single owner. This PR is structural cleanup that unblocks the class-flow scaffolding work in PR B and the data transforms work in PR C. No new authoring strategies or new operation kinds. Part 2/4 of tml-2219; depends on #352.
…rors The CliStructuredError class only supported CLI and RUN domains, so MIG-domain errors produced wrong envelope prefixes and failed the is() type guard. Widen the domain union, fix toEnvelope() prefix mapping, and wire the new src/exports/migration.ts entry point into tsdown + package.json exports.
Corrections already made in ba88a32 but not yet committed separately: - F01: fix emit-helper header docstring (attestation ownership) - F02: tighten CLI test to mock errorMigrationFileMissing / PN-MIG-2002 - F07: document migrationId identity algorithm and link ADR 199 - F08: replace defensive throw with assert in emitDescriptorFlow - F09: accept function-shape default exports in mongoEmit - F10: remove dead "an array" branch from describeValue - F11: collapse emit thunk in mongo-target-descriptor
Cover all five dispatcher branches directly: missing migration.ts (PN-MIG-2002), descriptor-flow routing, class-flow routing, tiebreak when both capabilities present, and unsupported-target error. Also verify attestMigration call ordering in the class flow.
Replace the tautological no-op envelope test with a proper mocked command test that exercises executeMigrationPlanCommand when fromHash === toStorageHash, and add an emit-after-scaffold ordering test that locks the AC2 behaviour change at the test level.
The else branch in mongoEmit treated the exported function as returning raw operations. The intended design is a factory function returning an object with a plan() method — both shapes now funnel through plan(). Error message and fix hint for PN-MIG-2003 updated to mention both accepted shapes (class subclass + factory function).
ADR 027, ADR 196, and Migration System subsystem doc now describe both
accepted migration.ts shapes: class subclass and factory function
returning { plan() }. Aligns with the implementation fix in mongoEmit.
50911ef to
75531bc
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/1-framework/3-tooling/migration/src/migration-ts.ts (1)
103-107:⚠️ Potential issue | 🟡 MinorStale
verifywording remains in scaffolding docs.This block still says “On verify,” which is now outdated after the command rename.
📝 Suggested doc fix
- * On verify, this file is re-evaluated to produce the final ops. + * On emit, this file is re-evaluated to produce the final ops.As per coding guidelines:
**/*.{md,ts,tsx}: Keep docs current (READMEs, rules, links) and prefer links to canonical docs over long comments.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/1-framework/3-tooling/migration/src/migration-ts.ts` around lines 103 - 107, Update the top-of-file scaffolding comment in migration-ts.ts: replace the outdated phrase "On verify," with a current, command-agnostic wording such as "When running the migration verification command" and, if you know the new command name, append it in parentheses (for example "(use <new-command-name>)"); edit the comment block that begins with "Scaffolds a migration.ts file..." so the documentation no longer references the old "verify" command.
🧹 Nitpick comments (3)
packages/1-framework/3-tooling/cli/test/lib/migration-emit.test.ts (1)
61-75: Preferawait expect(...).rejects.toMatchObject()for async error assertions.Since
emitMigrationis an async function that throws, use the async rejection assertion pattern instead of manual try/catch.♻️ Proposed refactor
- it('throws errorMigrationFileMissing when hasMigrationTs returns false', async () => { - mocks.hasMigrationTs.mockResolvedValue(false); - - let thrown: unknown; - try { - await emitMigration(DIR, makeCtx() as unknown as Parameters<EmitMigration>[1]); - } catch (error) { - thrown = error; - } - - expect(thrown).toMatchObject({ - code: '2002', - message: 'migration.ts not found', - }); - }); + it('throws errorMigrationFileMissing when hasMigrationTs returns false', async () => { + mocks.hasMigrationTs.mockResolvedValue(false); + + await expect( + emitMigration(DIR, makeCtx() as unknown as Parameters<EmitMigration>[1]), + ).rejects.toMatchObject({ + code: '2002', + message: 'migration.ts not found', + }); + });The same pattern applies to lines 144-164 for
errorTargetMigrationNotSupported.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/1-framework/3-tooling/cli/test/lib/migration-emit.test.ts` around lines 61 - 75, Replace the manual try/catch assertions with Jest's async rejection pattern: for the test that calls emitMigration when mocks.hasMigrationTs.mockResolvedValue(false), change the assertion to await expect(emitMigration(DIR, makeCtx() as unknown as Parameters<EmitMigration>[1])).rejects.toMatchObject({ code: '2002', message: 'migration.ts not found' }); and apply the same change for the other test that expects errorTargetMigrationNotSupported (lines 144-164) so both use await expect(...).rejects.toMatchObject(...) instead of capturing thrown via try/catch; keep references to emitMigration, hasMigrationTs, and the error shape the same.packages/1-framework/3-tooling/cli/test/commands/migration-emit.test.ts (1)
133-144: Preferawait expect(...).rejects.toThrow()over manual try/catch in async tests.The error propagation tests use manual try/catch blocks, but the coding guidelines prefer
expect().toThrow()patterns. SinceexecuteCommandis async and the structured errors are thrown/rejected, useawait expect(...).rejects.toMatchObject(...).♻️ Proposed refactor for async error assertions
- it('propagates class-flow emit errors (e.g. invalid default export) structurally', async () => { - mockMigrationCapableConfig(); - mocks.emitMigrationMock.mockRejectedValue( - errorMigrationInvalidDefaultExport('migrations/20260101_test', 'undefined'), - ); - - const command = createMigrationEmitCommand(); - try { - await executeCommand(command, ['--dir', 'migrations/20260101_test', '--json']); - } catch { - /* expected non-zero exit */ - } - - const jsonLine = consoleOutput.find((line) => line.trimStart().startsWith('{')); + it('propagates class-flow emit errors (e.g. invalid default export) structurally', async () => { + mockMigrationCapableConfig(); + mocks.emitMigrationMock.mockRejectedValue( + errorMigrationInvalidDefaultExport('migrations/20260101_test', 'undefined'), + ); + + const command = createMigrationEmitCommand(); + await executeCommand(command, ['--dir', 'migrations/20260101_test', '--json']); + + const jsonLine = consoleOutput.find((line) => line.trimStart().startsWith('{'));The same refactor pattern applies to lines 153-157, 175-179, and 194-198.
Note: If
executeCommandcatches errors internally and returns exit codes (rather than throwing), this pattern may be intentional. Verify howexecuteCommandhandles errors.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/1-framework/3-tooling/cli/test/commands/migration-emit.test.ts` around lines 133 - 144, Replace the manual try/catch used to assert that executeCommand(command, ['--dir', 'migrations/20260101_test', '--json']) fails with the preferred Jest async assertion: use await expect(executeCommand(...)).rejects.toMatchObject(...) (or .rejects.toThrow/.toMatchObject with the structured error) so the test asserts the rejection directly; do the same refactor for the other occurrences mentioned (around lines 153-157, 175-179, 194-198) and first verify that executeCommand actually rejects on error (if it returns an exit code instead, adjust the test to assert that return value instead).packages/2-mongo-family/9-family/test/mongo-emit.test.ts (1)
188-313: Replace manual try/catch rejection assertions withexpect(...).rejectspattern.Line 188 onward repeats manual error capture in several tests; using promise rejection assertions is shorter and more consistent with test guidance.
♻️ Proposed refactor pattern
- let thrown: unknown; - try { - await mongoEmit({ dir: pkgDir, frameworkComponents: [] }); - } catch (error) { - thrown = error; - } - - expect(CliStructuredError.is(thrown)).toBe(true); - expect(thrown).toMatchObject({ + await expect( + mongoEmit({ dir: pkgDir, frameworkComponents: [] }), + ).rejects.toMatchObject({ code: '2004', domain: 'MIG', meta: { dir: pkgDir }, });+ async function expectMigError(code: '2002' | '2003' | '2004') { + await expect( + mongoEmit({ dir: pkgDir, frameworkComponents: [] }), + ).rejects.toMatchObject({ + code, + domain: 'MIG', + meta: { dir: pkgDir }, + }); + }As per coding guidelines:
**/*.{test,spec}.{js,ts,tsx}and**/*.test.tsrequire using throw/rejection expectations instead of manual try/catch blocks in tests.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/2-mongo-family/9-family/test/mongo-emit.test.ts` around lines 188 - 313, Tests repeatedly capture thrown errors via try/catch then assert CliStructuredError.is(thrown) and thrown matches an object; replace each manual capture (around calls to mongoEmit({...}) in these cases) with the promise rejection assertion form: await expect(mongoEmit({ dir: pkgDir, frameworkComponents: [] })).rejects.toMatchObject({ code: '2004'|'2003'|'2002', domain: 'MIG', meta: { dir: pkgDir } }); and (where you still need the CliStructuredError.is check) use a rejects predicate or a second rejects assertion that validates CliStructuredError.is on the rejection; update the tests that reference the migration.ts variants (function-form, missing file, non-constructor, non-Migration subclass, class-plan not array) to use this pattern instead of the try/catch + thrown variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/architecture` docs/subsystems/7. Migration System.md:
- Around line 122-123: The class-based migration example is inconsistent: it
documents that subclasses of Migration should override plan() and describe(),
but the example implements operations; update the example to implement plan()
(returning the array of migration operations) and remove/replace any operations
implementation so it matches the contract described for the Migration subclass
(also update the other example instances referenced around the same section,
e.g., the paragraph covering lines 134-138, to use plan() instead of operations
and ensure describe() still returns the from/to manifest metadata).
In `@packages/1-framework/3-tooling/cli/src/commands/migration-apply.ts`:
- Line 293: The remediation command string currently interpolates the migration
path using migrationsRelative and matchingDraft.dirName without quoting, which
breaks when the path contains spaces; update the template to wrap the
interpolated path in single quotes (e.g. change `--dir
${migrationsRelative}/${matchingDraft.dirName}` to `--dir
'${migrationsRelative}/${matchingDraft.dirName}'`) so the generated `prisma-next
migration emit` command is copy-safe.
In `@packages/1-framework/3-tooling/cli/src/commands/migration-new.ts`:
- Line 251: The printed command currently injects the raw path (${value.dir})
into the template literal, which breaks for paths with spaces; update the
template to quote and properly escape the dir value (e.g., change `--dir
${value.dir}` to `--dir "${value.dir}"` or use JSON.stringify(value.dir) in the
template) so the emitted line `\nEdit migration.ts, then run \`prisma-next
migration emit --dir ${value.dir}\` to attest.` becomes safe to copy/paste.
---
Outside diff comments:
In `@packages/1-framework/3-tooling/migration/src/migration-ts.ts`:
- Around line 103-107: Update the top-of-file scaffolding comment in
migration-ts.ts: replace the outdated phrase "On verify," with a current,
command-agnostic wording such as "When running the migration verification
command" and, if you know the new command name, append it in parentheses (for
example "(use <new-command-name>)"); edit the comment block that begins with
"Scaffolds a migration.ts file..." so the documentation no longer references the
old "verify" command.
---
Nitpick comments:
In `@packages/1-framework/3-tooling/cli/test/commands/migration-emit.test.ts`:
- Around line 133-144: Replace the manual try/catch used to assert that
executeCommand(command, ['--dir', 'migrations/20260101_test', '--json']) fails
with the preferred Jest async assertion: use await
expect(executeCommand(...)).rejects.toMatchObject(...) (or
.rejects.toThrow/.toMatchObject with the structured error) so the test asserts
the rejection directly; do the same refactor for the other occurrences mentioned
(around lines 153-157, 175-179, 194-198) and first verify that executeCommand
actually rejects on error (if it returns an exit code instead, adjust the test
to assert that return value instead).
In `@packages/1-framework/3-tooling/cli/test/lib/migration-emit.test.ts`:
- Around line 61-75: Replace the manual try/catch assertions with Jest's async
rejection pattern: for the test that calls emitMigration when
mocks.hasMigrationTs.mockResolvedValue(false), change the assertion to await
expect(emitMigration(DIR, makeCtx() as unknown as
Parameters<EmitMigration>[1])).rejects.toMatchObject({ code: '2002', message:
'migration.ts not found' }); and apply the same change for the other test that
expects errorTargetMigrationNotSupported (lines 144-164) so both use await
expect(...).rejects.toMatchObject(...) instead of capturing thrown via
try/catch; keep references to emitMigration, hasMigrationTs, and the error shape
the same.
In `@packages/2-mongo-family/9-family/test/mongo-emit.test.ts`:
- Around line 188-313: Tests repeatedly capture thrown errors via try/catch then
assert CliStructuredError.is(thrown) and thrown matches an object; replace each
manual capture (around calls to mongoEmit({...}) in these cases) with the
promise rejection assertion form: await expect(mongoEmit({ dir: pkgDir,
frameworkComponents: [] })).rejects.toMatchObject({ code: '2004'|'2003'|'2002',
domain: 'MIG', meta: { dir: pkgDir } }); and (where you still need the
CliStructuredError.is check) use a rejects predicate or a second rejects
assertion that validates CliStructuredError.is on the rejection; update the
tests that reference the migration.ts variants (function-form, missing file,
non-constructor, non-Migration subclass, class-plan not array) to use this
pattern instead of the try/catch + thrown variable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 70b5a2f8-3c08-4b50-ab86-975d80d32b85
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (47)
docs/architecture docs/adrs/ADR 027 - Error Envelope Stable Codes.mddocs/architecture docs/adrs/ADR 196 - In-process emit for class-flow targets.mddocs/architecture docs/subsystems/7. Migration System.mdexamples/mongo-demo/migrations/20260409T1030_migration/migration.jsonpackages/1-framework/1-core/errors/package.jsonpackages/1-framework/1-core/errors/src/control.tspackages/1-framework/1-core/errors/src/exports/migration.tspackages/1-framework/1-core/errors/src/migration.tspackages/1-framework/1-core/errors/test/migration.test.tspackages/1-framework/1-core/errors/tsdown.config.tspackages/1-framework/1-core/framework-components/src/control-migration-types.tspackages/1-framework/3-tooling/cli/README.mdpackages/1-framework/3-tooling/cli/package.jsonpackages/1-framework/3-tooling/cli/src/cli.tspackages/1-framework/3-tooling/cli/src/commands/migration-apply.tspackages/1-framework/3-tooling/cli/src/commands/migration-emit.tspackages/1-framework/3-tooling/cli/src/commands/migration-new.tspackages/1-framework/3-tooling/cli/src/commands/migration-plan.tspackages/1-framework/3-tooling/cli/src/commands/migration-show.tspackages/1-framework/3-tooling/cli/src/commands/migration-status.tspackages/1-framework/3-tooling/cli/src/commands/migration-verify.tspackages/1-framework/3-tooling/cli/src/lib/migration-emit.tspackages/1-framework/3-tooling/cli/src/utils/cli-errors.tspackages/1-framework/3-tooling/cli/src/utils/formatters/help.tspackages/1-framework/3-tooling/cli/src/utils/formatters/migrations.tspackages/1-framework/3-tooling/cli/test/commands/migration-e2e.test.tspackages/1-framework/3-tooling/cli/test/commands/migration-emit.test.tspackages/1-framework/3-tooling/cli/test/commands/migration-plan-command.test.tspackages/1-framework/3-tooling/cli/test/commands/migration-verify.test.tspackages/1-framework/3-tooling/cli/test/lib/migration-emit.test.tspackages/1-framework/3-tooling/cli/test/output.migration-commands.test.tspackages/1-framework/3-tooling/cli/tsdown.config.tspackages/1-framework/3-tooling/cli/vitest.config.tspackages/1-framework/3-tooling/migration/src/attestation.tspackages/1-framework/3-tooling/migration/src/migration-ts.tspackages/1-framework/3-tooling/migration/test/attestation.test.tspackages/2-mongo-family/9-family/package.jsonpackages/2-mongo-family/9-family/src/core/mongo-emit.tspackages/2-mongo-family/9-family/src/core/mongo-target-descriptor.tspackages/2-mongo-family/9-family/test/mongo-emit.test.tspackages/3-targets/3-targets/postgres/src/core/migrations/descriptor-planner.tspackages/3-targets/3-targets/postgres/src/exports/control.tspackages/3-targets/3-targets/postgres/test/migrations/descriptor-planner.scenarios.mdpackages/3-targets/3-targets/postgres/test/migrations/descriptor-planner.scenarios.test.tstest/integration/test/cli-journeys/data-transform.e2e.test.tstest/integration/test/cli-journeys/schema-evolution-migrations.e2e.test.tstest/integration/test/utils/journey-test-helpers.ts
💤 Files with no reviewable changes (5)
- packages/3-targets/3-targets/postgres/src/exports/control.ts
- packages/3-targets/3-targets/postgres/test/migrations/descriptor-planner.scenarios.test.ts
- packages/1-framework/3-tooling/cli/test/commands/migration-verify.test.ts
- packages/3-targets/3-targets/postgres/src/core/migrations/descriptor-planner.ts
- packages/1-framework/3-tooling/cli/src/commands/migration-verify.ts
- Fix class-flow example: replace `get operations()` with `plan()` to match the Migration base class contract (subsystem doc + ADR 196) - Quote interpolated dir paths in remediation commands so they are copy-safe when the path contains spaces (migration-apply, migration-new) - Replace stale "On verify" wording with "On emit" in migration-ts.ts
Stack
Summary
migration verifybecomesmigration emitand now does the actual work(in-process dynamic-import the
migration.tsfile, run the emit helper,write attested
ops.json) rather than just validating an external emitstep.
migration planalways runs emit inline as part of plan, droppingthe
needsDataMigrationbranch the descriptor planner used to expose.Adds the
emitcapability toTargetMigrationsCapabilityand extracts anemitMigrationhelper that centrally owns attestation. The mongo familyplugs in via
mongoEmit, which dynamic-imports the class-flowmigration.tsand writesops.json.This PR is structural cleanup that unblocks the class-flow scaffolding
work in PR B and the data transforms work in PR C. No new authoring
strategies or new operation kinds.
Structured error codes
PN-MIG-2002errorMigrationFileMissingPN-MIG-2003errorMigrationInvalidDefaultExportPN-MIG-2004errorMigrationPlanNotArrayMigration identity
migrationIdis now storage-only (a content hash of operations); the frameworkattestMigrationhelper is the single owner.Known caveat:
Migration.runstill writes drafts at this PR's tip (fixed in PR B)ADR 196 (added in PR D and present here) describes
Migration.runas thecanonical self-emitting path that produces an attested
migration.jsonbyte-identical to what the CLI emit path produces. At PR A's tip, that
is aspirational. The current
migration-base.tsimplementation:migration.jsonwithmigrationId: null(a draft), not anattested manifest;
computeMigrationIdorattestMigration;migration.json's contract bookends,hints, or labels (it overwrites with a fresh draft envelope built from
describe()).So in PR A, the CLI emit path (the
emitMigrationhelper) is the onlypath that attests; the shebang/
node migration.tspath leaves a draft.The "single source of truth for
migrationId" invariant the JSDoc onemit?advertises is correct in code today becauseMigration.rundoesn't attest yet — not because the design enforces single ownership.
PR B (class-flow scaffolding spine) brings
Migration.runup to theADR-described behavior: it will read any pre-existing manifest, compute
migrationIdin-process viacomputeMigrationId, and write an attestedmigration.json. At that point both emit paths converge on byte-identicalartifacts and the ADR's "no draft state ever reaches disk" invariant
holds end-to-end.
ADR 196 is being landed in PR A so the design intent is on disk while
the implementation catches up across A → B. Reviewers should read the
ADR as the target state, not as a description of A's tip.
Test plan
migration emitandmigration planexercised via CLI command testsReviewer note
Base of this PR is PR D (
tml-2219-d-adrs-and-subsystem-docs), notmain. GitHub will retarget tomainautomatically once D merges.Summary by CodeRabbit
New Features
Documentation
Refactor
Bug Fixes / UX
Tests