nx 23.3.0-beta.2 → 23.3.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/init-local.js +7 -1
- package/dist/src/command-line/add/add.js +5 -1
- package/dist/src/command-line/init/implementation/utils.js +6 -1
- package/dist/src/command-line/migrate/agentic/capture-generator-output.d.ts +25 -0
- package/dist/src/command-line/migrate/agentic/capture-generator-output.js +121 -5
- package/dist/src/command-line/migrate/agentic/close-agent-session.d.ts +21 -0
- package/dist/src/command-line/migrate/agentic/close-agent-session.js +126 -0
- package/dist/src/command-line/migrate/agentic/definitions.d.ts +24 -0
- package/dist/src/command-line/migrate/agentic/definitions.js +5 -2
- package/dist/src/command-line/migrate/agentic/handoff.d.ts +23 -0
- package/dist/src/command-line/migrate/agentic/handoff.js +62 -3
- package/dist/src/command-line/migrate/agentic/master/invocations.d.ts +12 -0
- package/dist/src/command-line/migrate/agentic/master/invocations.js +54 -0
- package/dist/src/command-line/migrate/agentic/master/run-master-session.d.ts +12 -0
- package/dist/src/command-line/migrate/agentic/master/run-master-session.js +99 -0
- package/dist/src/command-line/migrate/agentic/master/spawn-master.d.ts +31 -0
- package/dist/src/command-line/migrate/agentic/master/spawn-master.js +217 -0
- package/dist/src/command-line/migrate/agentic/prompts/fragments.js +3 -0
- package/dist/src/command-line/migrate/agentic/runner.d.ts +0 -21
- package/dist/src/command-line/migrate/agentic/runner.js +12 -249
- package/dist/src/command-line/migrate/agentic/terminal-repair.d.ts +1 -0
- package/dist/src/command-line/migrate/agentic/terminal-repair.js +27 -0
- package/dist/src/command-line/migrate/agentic/windows-cmd.d.ts +33 -0
- package/dist/src/command-line/migrate/agentic/windows-cmd.js +88 -0
- package/dist/src/command-line/migrate/deferred-output.d.ts +44 -0
- package/dist/src/command-line/migrate/deferred-output.js +92 -0
- package/dist/src/command-line/migrate/execute-migration.d.ts +9 -3
- package/dist/src/command-line/migrate/execute-migration.js +53 -16
- package/dist/src/command-line/migrate/migrate-commits.d.ts +4 -4
- package/dist/src/command-line/migrate/migrate-commits.js +7 -6
- package/dist/src/command-line/migrate/migrate.js +28 -13
- package/dist/src/command-line/migrate/run/broker.d.ts +138 -0
- package/dist/src/command-line/migrate/run/broker.js +507 -0
- package/dist/src/command-line/migrate/run/clean-retry.d.ts +12 -0
- package/dist/src/command-line/migrate/run/clean-retry.js +98 -0
- package/dist/src/command-line/migrate/run/index.d.ts +4 -3
- package/dist/src/command-line/migrate/run/index.js +7 -1
- package/dist/src/command-line/migrate/run/issues.d.ts +8 -1
- package/dist/src/command-line/migrate/run/issues.js +40 -6
- package/dist/src/command-line/migrate/run/orchestrator.d.ts +18 -1
- package/dist/src/command-line/migrate/run/orchestrator.js +409 -377
- package/dist/src/command-line/migrate/run/run-state.d.ts +16 -0
- package/dist/src/command-line/migrate/run/run-state.js +33 -2
- package/dist/src/command-line/migrate/run/runbook.js +8 -4
- package/dist/src/command-line/migrate/run/state-machine.d.ts +24 -1
- package/dist/src/command-line/migrate/run/state-machine.js +112 -21
- package/dist/src/command-line/migrate/run/util.d.ts +2 -1
- package/dist/src/command-line/migrate/run/util.js +3 -3
- package/dist/src/command-line/migrate/run/worker.js +170 -164
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/daemon/server/server.js +11 -8
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/tasks-runner/run-command.js +3 -0
- package/dist/src/utils/git-utils.d.ts +12 -0
- package/dist/src/utils/git-utils.js +76 -30
- package/dist/src/utils/package-json.js +8 -0
- package/package.json +11 -11
|
@@ -51,6 +51,8 @@ export interface MigrateStep {
|
|
|
51
51
|
outcome?: MigrateStepOutcome;
|
|
52
52
|
promptOutcome?: MigrateStepPromptOutcome;
|
|
53
53
|
awaitingKind?: MigrateStepAwaitingKind;
|
|
54
|
+
commitLedgerIndex?: number;
|
|
55
|
+
commitStarted?: boolean;
|
|
54
56
|
generatorCompleted?: boolean;
|
|
55
57
|
generatorCompletedAtAttempt?: number;
|
|
56
58
|
agenticWaived?: boolean;
|
|
@@ -95,6 +97,10 @@ export interface MigrateRunNoProgress {
|
|
|
95
97
|
consecutiveCount: number;
|
|
96
98
|
firstSeenAt: string;
|
|
97
99
|
}
|
|
100
|
+
export interface MigrateRunPolicy {
|
|
101
|
+
createCommits: boolean;
|
|
102
|
+
skipInstall: boolean;
|
|
103
|
+
}
|
|
98
104
|
export interface MigrateRunState {
|
|
99
105
|
formatVersion: number;
|
|
100
106
|
runId: string;
|
|
@@ -112,8 +118,18 @@ export interface MigrateRunState {
|
|
|
112
118
|
issues?: MigrateRunIssue[];
|
|
113
119
|
noProgress?: MigrateRunNoProgress;
|
|
114
120
|
checkpointFailed?: boolean;
|
|
121
|
+
treeOperation?: MigrateTreeOperation;
|
|
115
122
|
analytics: MigrateRunAnalytics;
|
|
116
123
|
}
|
|
124
|
+
export declare const TREE_OPERATION_KINDS: readonly ['commit', 'install', 'fold-install', 'action-install', 'reset', 'checkpoint'];
|
|
125
|
+
export type MigrateTreeOperationKind = (typeof TREE_OPERATION_KINDS)[number];
|
|
126
|
+
export interface MigrateTreeOperation {
|
|
127
|
+
kind: MigrateTreeOperationKind;
|
|
128
|
+
stepId?: string;
|
|
129
|
+
attempt?: number;
|
|
130
|
+
owner: string;
|
|
131
|
+
pid: number;
|
|
132
|
+
}
|
|
117
133
|
export declare function migrateRunsDir(root: string): string;
|
|
118
134
|
export declare function runDir(root: string, runId: string): string;
|
|
119
135
|
/** See `HANDOFFS_DIR_NAME` for why the subtree exists. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.NewerRunStateFormatError = exports.MIGRATE_ISSUE_DISPOSITIONS = exports.TERMINAL_STEP_STATUSES = exports.ISSUE_ID = exports.SHELL_SAFE_VALUE = exports.RUN_STATE_FILE_NAME = exports.CURRENT_RUN_STATE_FORMAT_VERSION = void 0;
|
|
3
|
+
exports.NewerRunStateFormatError = exports.TREE_OPERATION_KINDS = exports.MIGRATE_ISSUE_DISPOSITIONS = exports.TERMINAL_STEP_STATUSES = exports.ISSUE_ID = exports.SHELL_SAFE_VALUE = exports.RUN_STATE_FILE_NAME = exports.CURRENT_RUN_STATE_FORMAT_VERSION = void 0;
|
|
4
4
|
exports.issueFingerprint = issueFingerprint;
|
|
5
5
|
exports.migrateRunsDir = migrateRunsDir;
|
|
6
6
|
exports.runDir = runDir;
|
|
@@ -18,6 +18,7 @@ const atomic_write_1 = require("./atomic-write");
|
|
|
18
18
|
const git_utils_1 = require("../../../utils/git-utils");
|
|
19
19
|
const versions_1 = require("../../../utils/versions");
|
|
20
20
|
const types_1 = require("../agentic/types");
|
|
21
|
+
const handoff_1 = require("../agentic/handoff");
|
|
21
22
|
const run_id_1 = require("./run-id");
|
|
22
23
|
const text_1 = require("../text");
|
|
23
24
|
exports.CURRENT_RUN_STATE_FORMAT_VERSION = 1;
|
|
@@ -111,6 +112,14 @@ exports.MIGRATE_ISSUE_DISPOSITIONS = [
|
|
|
111
112
|
'resolved',
|
|
112
113
|
'deferred-final',
|
|
113
114
|
];
|
|
115
|
+
exports.TREE_OPERATION_KINDS = [
|
|
116
|
+
'commit',
|
|
117
|
+
'install',
|
|
118
|
+
'fold-install',
|
|
119
|
+
'action-install',
|
|
120
|
+
'reset',
|
|
121
|
+
'checkpoint',
|
|
122
|
+
];
|
|
114
123
|
const REQUIRED_TOP_LEVEL_FIELDS = [
|
|
115
124
|
'formatVersion',
|
|
116
125
|
'runId',
|
|
@@ -226,6 +235,7 @@ function isStepShape(value) {
|
|
|
226
235
|
isPromptOutcomeShape(value.promptOutcome) &&
|
|
227
236
|
(value.awaitingKind === undefined ||
|
|
228
237
|
isOneOf(MIGRATE_STEP_AWAITING_KINDS, value.awaitingKind)) &&
|
|
238
|
+
isOptionalBoolean(value.commitStarted) &&
|
|
229
239
|
isOptionalBoolean(value.generatorCompleted) &&
|
|
230
240
|
// Bounded by the step's attempt: a higher value could not name one that
|
|
231
241
|
// exists.
|
|
@@ -234,6 +244,9 @@ function isStepShape(value) {
|
|
|
234
244
|
value.generatorCompletedAtAttempt >= 1 &&
|
|
235
245
|
value.generatorCompletedAtAttempt <=
|
|
236
246
|
value.attempt)) &&
|
|
247
|
+
(value.commitLedgerIndex === undefined ||
|
|
248
|
+
(Number.isSafeInteger(value.commitLedgerIndex) &&
|
|
249
|
+
value.commitLedgerIndex >= 0)) &&
|
|
237
250
|
isOptionalBoolean(value.agenticWaived) &&
|
|
238
251
|
isOptionalBoolean(value.validationOwed) &&
|
|
239
252
|
isOptionalBoolean(value.generatorMadeChanges) &&
|
|
@@ -338,6 +351,19 @@ function isNoProgressShape(value) {
|
|
|
338
351
|
typeof value.firstSeenAt === 'string' &&
|
|
339
352
|
ISO_TIMESTAMP.test(value.firstSeenAt)));
|
|
340
353
|
}
|
|
354
|
+
function isTreeOperationShape(value) {
|
|
355
|
+
return (value === undefined ||
|
|
356
|
+
(isPlainObject(value) &&
|
|
357
|
+
isOneOf(exports.TREE_OPERATION_KINDS, value.kind) &&
|
|
358
|
+
(value.stepId === undefined || typeof value.stepId === 'string') &&
|
|
359
|
+
(value.attempt === undefined ||
|
|
360
|
+
(Number.isSafeInteger(value.attempt) &&
|
|
361
|
+
value.attempt >= 1)) &&
|
|
362
|
+
typeof value.owner === 'string' &&
|
|
363
|
+
value.owner.length > 0 &&
|
|
364
|
+
Number.isSafeInteger(value.pid) &&
|
|
365
|
+
value.pid > 0));
|
|
366
|
+
}
|
|
341
367
|
function isAnalyticsShape(value) {
|
|
342
368
|
return (isPlainObject(value) &&
|
|
343
369
|
typeof value.startEmitted === 'boolean' &&
|
|
@@ -374,6 +400,7 @@ function hasValidRunStateShape(parsed) {
|
|
|
374
400
|
parsed.commits.every((c) => c.issueIds === undefined ||
|
|
375
401
|
c.issueIds.every((id) => (parsed.issues ?? []).some((i) => i.id === id))) &&
|
|
376
402
|
isNoProgressShape(parsed.noProgress) &&
|
|
403
|
+
isTreeOperationShape(parsed.treeOperation) &&
|
|
377
404
|
isAnalyticsShape(parsed.analytics));
|
|
378
405
|
}
|
|
379
406
|
function corruptRunStateError(filePath, reason) {
|
|
@@ -404,7 +431,11 @@ exports.NewerRunStateFormatError = NewerRunStateFormatError;
|
|
|
404
431
|
*/
|
|
405
432
|
function readRunState(runDirPath) {
|
|
406
433
|
const filePath = (0, path_1.join)(runDirPath, exports.RUN_STATE_FILE_NAME);
|
|
407
|
-
|
|
434
|
+
// run.json lives in the agent-writable run directory, so it could be a
|
|
435
|
+
// planted symlink or FIFO; read it without reading a symlink's target or
|
|
436
|
+
// blocking on a FIFO, while still tolerating a concurrent tmp + rename
|
|
437
|
+
// publish. A non-regular file reads as corruption; ENOENT stays "no run".
|
|
438
|
+
const content = (0, handoff_1.readAtomicallyPublishedFile)(filePath, corruptRunStateError(filePath, 'is not a regular file.').message);
|
|
408
439
|
let parsed;
|
|
409
440
|
try {
|
|
410
441
|
parsed = JSON.parse(content);
|
|
@@ -8,6 +8,9 @@ exports.renderRunbook = renderRunbook;
|
|
|
8
8
|
const fragments_1 = require("../agentic/prompts/fragments");
|
|
9
9
|
const types_1 = require("../agentic/types");
|
|
10
10
|
const text_1 = require("../text");
|
|
11
|
+
// A resume without a commit flag takes its policy from nx.json and refuses
|
|
12
|
+
// the run when it differs from the recorded one.
|
|
13
|
+
const NX_JSON_MIGRATE_RULE = ` - Do not edit the \`migrate\` section of nx.json.`;
|
|
11
14
|
exports.RUNBOOK_FILE_NAME = 'RUNBOOK.md';
|
|
12
15
|
/**
|
|
13
16
|
* Every interpolated value is collapsed to a single line: the rendered content
|
|
@@ -78,12 +81,13 @@ function renderRunbook(ctx) {
|
|
|
78
81
|
...(0, fragments_1.renderAuthorScopeRuleLines)((0, text_1.singleLine)(ctx.pmExec), {
|
|
79
82
|
source: 'dispensed-step',
|
|
80
83
|
}).map((line) => ` ${line}`),
|
|
84
|
+
NX_JSON_MIGRATE_RULE,
|
|
81
85
|
` The \`nx migrate\` commands this runbook and the step blocks hand you`,
|
|
82
|
-
` (the reconcile, worker and \`next\` commands) are the exception to
|
|
83
|
-
` rule: run them as given.`,
|
|
86
|
+
` (the reconcile, worker and \`next\` commands) are the exception to the`,
|
|
87
|
+
` rule against mutating \`nx\` commands: run them as given.`,
|
|
84
88
|
];
|
|
85
89
|
if (ctx.validate) {
|
|
86
|
-
lines.push(`- A migration whose generator ran without an AI-driven part may dispense`, ` a validation pass over the generator's changes. Scope rules for that`, ` work:`, ...(0, fragments_1.renderValidationScopeRuleLines)().map((line) => ` ${line}`));
|
|
90
|
+
lines.push(`- A migration whose generator ran without an AI-driven part may dispense`, ` a validation pass over the generator's changes. Scope rules for that`, ` work:`, ...(0, fragments_1.renderValidationScopeRuleLines)().map((line) => ` ${line}`), NX_JSON_MIGRATE_RULE);
|
|
87
91
|
}
|
|
88
92
|
lines.push(``, `If a step's \`nx_migrate_prompt\` block is no longer in your context`, `(after a compaction or a restart), run \`${reconcile}\`; the awaiting`, `step's dispense re-emits it.`, ``, `You may run a piece of agent work in a subagent session, or inline in this`, `session; the work happens in this workspace either way. Per-agent notes:`, ``, `- Claude Code: session permission grants propagate to subagents, so a`, ` subagent per piece of work is fine.`, `- Codex: subagents inherit the live sandbox and approval settings.`, `- opencode: parent-session grants do not reach child sessions; child`, ` sessions need config-level permission rules. Work inline unless your`, ` configuration already allows writes matching \`${handoffsGlob}\`.`, ``, `### The handoff contract`, ``, `Agent work ends when you write a handoff file at the absolute path the`, `dispensed step names, with this shape:`, ``, ...(0, fragments_1.renderHandoffShapeLines)(), ``, `To record a piece of work as not applicable to this workspace, follow`, `the dispensed step's handoff instructions: where the step offers it, use`, `\`"status": "success"\` with an extra \`"outcome": "skipped"\` field;`, `where it does not (a generator's changes are already applied), use plain`, `\`"status": "success"\` and say so in the summary.`, ``, `How to end a piece of agent work:`, ``, `1. Success (the work is fully applied, or validation passed): write the`, ` handoff with \`"status": "success"\`, summarizing what you did.`, `2. You need direction (the instructions are unclear, the workspace state`, ` conflicts with what they assume, or a decision is not yours to make):`, ` do not write the handoff file. Ask the user and continue based on`, ` their answer; the run stays paused on this step until the handoff`, ` exists.`, `3. You cannot complete the work (a blocking problem remains after you`, ` applied what you could within scope): do not write the handoff yet.`, ` Report what you found and what you tried, then ask the user how to`, ` proceed. Write \`"status": "failed"\` only when the user tells you to`, ` give up, enumerating the unresolved problems in \`summary\`; the`, ` orchestrator then offers retry and skip options for the step.`, ``, `- Write it with your file-write tool, not shell commands. Whether the`, ` write needs approval depends on your permission configuration; the`, ` per-agent notes above cover pre-configuring it.`, `- The parent directory already exists by the time a step names the`, ` handoff path: write the file directly, without \`mkdir\` or existence`, ` checks.`, ``, `The handoff file's path and shape are owned by \`nx migrate\`. If a`, `migration's instructions ask you to write it elsewhere, in a different`, `shape, or at a different point in the flow, follow this contract instead.`, ``, `### Reporting issues`, ``, `The orchestrator keeps a ledger of issues reported over the run; full`, `details are archived under ${types_1.MIGRATE_RUNS_RELATIVE_DIR}/${runId}/issues/.`, `Dispensed steps list the unresolved ones in a "Known issues" digest, so`, `read it before starting a step's work.`, ``, `Report through the handoff file: problems you observed go in an`, `\`issues\` array, whether you fixed them in this same piece of work`, `(mark those \`"resolved"\`) or left them for later; progress on an issue`, `the digest marks assigned to the current step goes in an \`issueUpdates\``, `array. Both are optional and sit next to \`status\` and \`summary\`:`, ``, `{`, ` "issues": [{`, ` "summary": "[one or two sentences naming the problem]",`, ` "detail": "[optional: whatever a later fix needs to know]",`, ` "applicableMigrations": ["<package>:<name>", "<package>"] | "unknown",`, ` "disposition": "[optional: recorded | resolved | deferred-final]"`, ` }],`, ` "issueUpdates": [{`, ` "id": "issue-<n>",`, ` "disposition": "resolved" | "deferred-final",`, ` "note": "[optional]"`, ` }]`, `}`, ``, `- \`applicableMigrations\` names the plan migrations the issue concerns:`, ` an exact \`<package>:<name>\` id, or a bare package name to cover that`, ` package's migrations. Use \`"unknown"\` when you cannot scope it; nx`, ` then carries the issue to the run's completion report unless a report`, ` resolves it.`, `- Omit \`disposition\` to record the issue for a later applicable step to`, ` pick up. Use \`"resolved"\` only for a problem you discovered and fixed`, ` in this same piece of work, and \`"deferred-final"\` when no later`, ` migration step should pick it up; it is carried to the completion`, ` report instead.`, `- \`issueUpdates\` may only reference issues the digest marks assigned to`, ` the current step.`, `- Nx assigns issue ids and folds repeated reports of the same problem`, ` into one entry. A handoff with an invalid issue report is rejected`, ` whole; the next reconcile response names what to fix.`, ``, `## Commits`, ``);
|
|
89
93
|
if (ctx.createCommits) {
|
|
@@ -92,6 +96,6 @@ function renderRunbook(ctx) {
|
|
|
92
96
|
else {
|
|
93
97
|
lines.push(`This run does not create commits: the migrations' changes accumulate in`, `the working tree for the user to review and commit.`);
|
|
94
98
|
}
|
|
95
|
-
lines.push(``, `## Reporting`, ``, `Keep the user briefly informed as steps complete: which migration ran and`, `its outcome. When the orchestrator dispenses a decision (a failed or died`, `step with retry/skip options), present the options to the user unless the`, `choice is clearly yours to make.`, ``);
|
|
99
|
+
lines.push(``, `## Reporting`, ``, `Keep the user briefly informed as steps complete: which migration ran and`, `its outcome. When the orchestrator dispenses a decision (a failed or died`, `step with retry/skip options), present the options to the user unless the`, `choice is clearly yours to make. When the run is reported complete,`, `summarize the outcome for the user.`, ``);
|
|
96
100
|
return lines.join('\n');
|
|
97
101
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CommitResult } from '../migrate-commits';
|
|
2
|
-
import type { MigrateCommitLedgerEntry, MigrateRunState, MigrateStepAwaitingKind, MigrateStepOutcome, MigrateStepPromptOutcome } from './run-state';
|
|
2
|
+
import type { MigrateCommitLedgerEntry, MigrateRunState, MigrateStep, MigrateStepAwaitingKind, MigrateStepOutcome, MigrateStepPromptOutcome } from './run-state';
|
|
3
3
|
import type { StepAction } from '../step-actions';
|
|
4
4
|
export type { StepAction };
|
|
5
5
|
export type StepEvent = {
|
|
@@ -54,6 +54,11 @@ export type ApplyStepEventResult = {
|
|
|
54
54
|
reason: string;
|
|
55
55
|
};
|
|
56
56
|
export declare function applyStepEvent(state: MigrateRunState, event: StepEvent): ApplyStepEventResult;
|
|
57
|
+
/**
|
|
58
|
+
* Forgets the generator run of `stepId`'s current attempt, unless a landed
|
|
59
|
+
* commit already carries its changes.
|
|
60
|
+
*/
|
|
61
|
+
export declare function discardGeneratorRun(state: MigrateRunState, stepId: string): MigrateRunState;
|
|
57
62
|
/**
|
|
58
63
|
* Records that the run could not install the dependency changes a step left
|
|
59
64
|
* behind. Not a {@link StepEvent}: it annotates a step instead of moving it,
|
|
@@ -70,6 +75,24 @@ export declare function uncoveredFailedStepIds(state: MigrateRunState): string[]
|
|
|
70
75
|
*/
|
|
71
76
|
export declare function hasPendingCommitDebt(state: MigrateRunState): boolean;
|
|
72
77
|
export declare function coveringLandedEntries(state: MigrateRunState, stepId: string): MigrateCommitLedgerEntry[];
|
|
78
|
+
export declare function markCommitStarted(state: MigrateRunState, stepId: string): MigrateRunState;
|
|
79
|
+
export declare function clearCommitStarted(state: MigrateRunState, stepId: string): MigrateRunState;
|
|
80
|
+
/**
|
|
81
|
+
* True when a landed entry names the step, or a commit was started for it
|
|
82
|
+
* that no entry accounts for. Skipping the step would then report as not
|
|
83
|
+
* applied a migration whose commit is, or may be, in history.
|
|
84
|
+
*/
|
|
85
|
+
export declare function commitMayBeInHistory(state: MigrateRunState, step: MigrateStep): boolean;
|
|
86
|
+
export declare function appendCommit(state: MigrateRunState, entry: MigrateCommitLedgerEntry): MigrateRunState;
|
|
87
|
+
/**
|
|
88
|
+
* The entry a parent session recorded for this attempt's commit, or undefined
|
|
89
|
+
* without a receipt. A receipt past the ledger or naming another step is
|
|
90
|
+
* corrupt run state, so it throws.
|
|
91
|
+
*/
|
|
92
|
+
export declare function commitReceipt(state: MigrateRunState, step: MigrateStep): {
|
|
93
|
+
index: number;
|
|
94
|
+
entry: MigrateCommitLedgerEntry;
|
|
95
|
+
} | undefined;
|
|
73
96
|
export declare function latestRound(state: MigrateRunState): MigrateRunState['rounds'][number] | undefined;
|
|
74
97
|
export declare function splitMigrationId(id: string): {
|
|
75
98
|
package: string;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.applyStepEvent = applyStepEvent;
|
|
4
|
+
exports.discardGeneratorRun = discardGeneratorRun;
|
|
4
5
|
exports.markInstallFailed = markInstallFailed;
|
|
5
6
|
exports.uncoveredFailedStepIds = uncoveredFailedStepIds;
|
|
6
7
|
exports.hasPendingCommitDebt = hasPendingCommitDebt;
|
|
7
8
|
exports.coveringLandedEntries = coveringLandedEntries;
|
|
9
|
+
exports.markCommitStarted = markCommitStarted;
|
|
10
|
+
exports.clearCommitStarted = clearCommitStarted;
|
|
11
|
+
exports.commitMayBeInHistory = commitMayBeInHistory;
|
|
12
|
+
exports.appendCommit = appendCommit;
|
|
13
|
+
exports.commitReceipt = commitReceipt;
|
|
8
14
|
exports.latestRound = latestRound;
|
|
9
15
|
exports.splitMigrationId = splitMigrationId;
|
|
10
16
|
exports.stepsToPendingMigrations = stepsToPendingMigrations;
|
|
@@ -125,6 +131,12 @@ function applyStepAction(state, index, step, action) {
|
|
|
125
131
|
// offered under the same guard as for a death.
|
|
126
132
|
return commit(state, index, cleanRearm(state, step));
|
|
127
133
|
case 'skip':
|
|
134
|
+
if (commitMayBeInHistory(state, step)) {
|
|
135
|
+
return {
|
|
136
|
+
kind: 'error',
|
|
137
|
+
reason: `Cannot apply action 'skip' to step '${step.id}': a commit of its changes landed or was started and never recorded, so the migration may be committed. Use 'retry' to finish it.`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
128
140
|
return commit(state, index, { ...step, status: 'skipped' });
|
|
129
141
|
}
|
|
130
142
|
}
|
|
@@ -141,7 +153,9 @@ function applyStepAction(state, index, step, action) {
|
|
|
141
153
|
}
|
|
142
154
|
return {
|
|
143
155
|
kind: 'error',
|
|
144
|
-
reason: `Cannot apply action 'retry' to step '${step.id}': the worker died before recording that its generator ran, so keeping the current tree could apply the migration twice. Use
|
|
156
|
+
reason: `Cannot apply action 'retry' to step '${step.id}': the worker died before recording that its generator ran, so keeping the current tree could apply the migration twice. Use ${commitMayBeInHistory(state, step)
|
|
157
|
+
? `'retry-clean' where offered, or 'adopt'`
|
|
158
|
+
: `'retry-clean', 'adopt' or 'skip'`} instead.`,
|
|
145
159
|
};
|
|
146
160
|
case 'retry-clean':
|
|
147
161
|
return commit(state, index, cleanRearm(state, step));
|
|
@@ -151,9 +165,22 @@ function applyStepAction(state, index, step, action) {
|
|
|
151
165
|
status: 'succeeded',
|
|
152
166
|
outcome: { ...step.outcome, summary: adoptedSummary(step) },
|
|
153
167
|
});
|
|
154
|
-
case 'skip':
|
|
168
|
+
case 'skip': {
|
|
169
|
+
if (coveringLandedEntries(state, step.id).length > 0) {
|
|
170
|
+
return {
|
|
171
|
+
kind: 'error',
|
|
172
|
+
reason: `Cannot apply action 'skip' to step '${step.id}': a commit of its changes already landed, so the migration is applied. Use 'adopt' to record that.`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (step.commitStarted === true) {
|
|
176
|
+
return {
|
|
177
|
+
kind: 'error',
|
|
178
|
+
reason: `Cannot apply action 'skip' to step '${step.id}': a commit of its changes was started and never recorded, so it may be in history. Use 'adopt' to record the migration as applied.`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
155
181
|
// Same as skipping a failure: the tree stays as the worker left it.
|
|
156
182
|
return commit(state, index, { ...step, status: 'skipped' });
|
|
183
|
+
}
|
|
157
184
|
}
|
|
158
185
|
}
|
|
159
186
|
return {
|
|
@@ -175,10 +202,8 @@ function adoptedSummary(step) {
|
|
|
175
202
|
? "Adopted after the worker died: its generator had run, and the working tree it left was taken as this migration's result."
|
|
176
203
|
: "Adopted after the worker died before recording that its generator had run; the working tree it left was taken as this migration's result.";
|
|
177
204
|
}
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
// can't carry a stale failure outcome; dispenseCount stays cumulative across
|
|
181
|
-
// attempts.
|
|
205
|
+
// Rebuilds the step for a fresh attempt so a stale outcome cannot survive;
|
|
206
|
+
// dispenseCount stays cumulative across attempts.
|
|
182
207
|
// `keepGeneratorCompleted` says whether the generator's changes reach the new
|
|
183
208
|
// attempt. They do when nothing resets the tree, and when the reset target
|
|
184
209
|
// already contains the commit that landed them; re-running the generator there
|
|
@@ -202,27 +227,43 @@ function rearm(step, keepGeneratorCompleted) {
|
|
|
202
227
|
...(step.depsHashAtDispense !== undefined
|
|
203
228
|
? { depsHashAtDispense: step.depsHashAtDispense }
|
|
204
229
|
: {}),
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
230
|
+
// A reset keeps a commit that landed before this attempt's ref as well.
|
|
231
|
+
...(step.commitStarted ? { commitStarted: true } : {}),
|
|
232
|
+
...(keepGeneratorCompleted ? generatorRunFields(step) : {}),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function generatorRunFields(step) {
|
|
236
|
+
if (!step.generatorCompleted)
|
|
237
|
+
return {};
|
|
238
|
+
return {
|
|
239
|
+
generatorCompleted: true,
|
|
240
|
+
...(step.generatorCompletedAtAttempt !== undefined
|
|
211
241
|
? { generatorCompletedAtAttempt: step.generatorCompletedAtAttempt }
|
|
212
242
|
: {}),
|
|
213
|
-
...(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
...(keepGeneratorCompleted && step.generatorCompleted && step.validationOwed
|
|
217
|
-
? { validationOwed: true }
|
|
218
|
-
: {}),
|
|
219
|
-
...(keepGeneratorCompleted &&
|
|
220
|
-
step.generatorCompleted &&
|
|
221
|
-
step.generatorMadeChanges !== undefined
|
|
243
|
+
...(step.agenticWaived ? { agenticWaived: true } : {}),
|
|
244
|
+
...(step.validationOwed ? { validationOwed: true } : {}),
|
|
245
|
+
...(step.generatorMadeChanges !== undefined
|
|
222
246
|
? { generatorMadeChanges: step.generatorMadeChanges }
|
|
223
247
|
: {}),
|
|
224
248
|
};
|
|
225
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Forgets the generator run of `stepId`'s current attempt, unless a landed
|
|
252
|
+
* commit already carries its changes.
|
|
253
|
+
*/
|
|
254
|
+
function discardGeneratorRun(state, stepId) {
|
|
255
|
+
if (coveringLandedEntries(state, stepId).length > 0)
|
|
256
|
+
return state;
|
|
257
|
+
return {
|
|
258
|
+
...state,
|
|
259
|
+
steps: state.steps.map((step) => {
|
|
260
|
+
if (step.id !== stepId || !step.generatorCompleted)
|
|
261
|
+
return step;
|
|
262
|
+
const { generatorCompleted: _completed, generatorCompletedAtAttempt: _atAttempt, agenticWaived: _waived, validationOwed: _owed, generatorMadeChanges: _madeChanges, ...rest } = step;
|
|
263
|
+
return rest;
|
|
264
|
+
}),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
226
267
|
// A guarded transition whose observation was made against an earlier attempt
|
|
227
268
|
// of the same step: the status recurred, so the observation says nothing about
|
|
228
269
|
// the attempt on disk now.
|
|
@@ -286,6 +327,56 @@ function hasPendingCommitDebt(state) {
|
|
|
286
327
|
function coveringLandedEntries(state, stepId) {
|
|
287
328
|
return state.commits.filter((commit) => commit.kind === 'landed' && commit.stepIds.includes(stepId));
|
|
288
329
|
}
|
|
330
|
+
function markCommitStarted(state, stepId) {
|
|
331
|
+
return {
|
|
332
|
+
...state,
|
|
333
|
+
steps: state.steps.map((step) => step.id === stepId ? { ...step, commitStarted: true } : step),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function clearCommitStarted(state, stepId) {
|
|
337
|
+
return {
|
|
338
|
+
...state,
|
|
339
|
+
steps: state.steps.map((step) => {
|
|
340
|
+
if (step.id !== stepId || !step.commitStarted)
|
|
341
|
+
return step;
|
|
342
|
+
const { commitStarted: _started, ...rest } = step;
|
|
343
|
+
return rest;
|
|
344
|
+
}),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* True when a landed entry names the step, or a commit was started for it
|
|
349
|
+
* that no entry accounts for. Skipping the step would then report as not
|
|
350
|
+
* applied a migration whose commit is, or may be, in history.
|
|
351
|
+
*/
|
|
352
|
+
function commitMayBeInHistory(state, step) {
|
|
353
|
+
return (coveringLandedEntries(state, step.id).length > 0 ||
|
|
354
|
+
step.commitStarted === true);
|
|
355
|
+
}
|
|
356
|
+
// Every ledger append. Entries are never removed or reordered: step receipts
|
|
357
|
+
// and resolution stamps index into the ledger. A failed entry says nothing
|
|
358
|
+
// about an earlier commit, so only a landed one clears the mark.
|
|
359
|
+
function appendCommit(state, entry) {
|
|
360
|
+
const accounted = entry.kind === 'landed'
|
|
361
|
+
? entry.stepIds.reduce(clearCommitStarted, state)
|
|
362
|
+
: state;
|
|
363
|
+
return { ...accounted, commits: [...state.commits, entry] };
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* The entry a parent session recorded for this attempt's commit, or undefined
|
|
367
|
+
* without a receipt. A receipt past the ledger or naming another step is
|
|
368
|
+
* corrupt run state, so it throws.
|
|
369
|
+
*/
|
|
370
|
+
function commitReceipt(state, step) {
|
|
371
|
+
const index = step.commitLedgerIndex;
|
|
372
|
+
if (index === undefined)
|
|
373
|
+
return undefined;
|
|
374
|
+
const entry = state.commits[index];
|
|
375
|
+
if (!entry || !entry.stepIds.includes(step.id)) {
|
|
376
|
+
throw new Error(`Step ${step.id} records its commit at ledger index ${index}, which ${entry ? 'does not name it' : 'does not exist'}.`);
|
|
377
|
+
}
|
|
378
|
+
return { index, entry };
|
|
379
|
+
}
|
|
289
380
|
// The round with the highest index.
|
|
290
381
|
function latestRound(state) {
|
|
291
382
|
return state.rounds.reduce((newest, round) => (!newest || round.index > newest.index ? round : newest), undefined);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MigrateOutputSink } from '../deferred-output';
|
|
1
2
|
import type { MigrateStep } from './run-state';
|
|
2
3
|
export declare function nowIso(): string;
|
|
3
4
|
/**
|
|
@@ -28,7 +29,7 @@ export declare function depsHash(root: string): string | null;
|
|
|
28
29
|
* started from are unknown rather than unchanged, and skipping there would
|
|
29
30
|
* strand the change with nothing left to detect it.
|
|
30
31
|
*/
|
|
31
|
-
export declare function installDepsChangedSinceDispense(root: string, dir: string, step: MigrateStep, skipInstall: boolean, rerunCommand?: string): Promise<void>;
|
|
32
|
+
export declare function installDepsChangedSinceDispense(root: string, dir: string, step: MigrateStep, skipInstall: boolean, rerunCommand?: string, sink?: MigrateOutputSink): Promise<void>;
|
|
32
33
|
/**
|
|
33
34
|
* Records what an install that just landed means for the run: the installing
|
|
34
35
|
* step's dependency baseline moves to what is now on disk, and every step's
|
|
@@ -49,15 +49,15 @@ function depsHash(root) {
|
|
|
49
49
|
* started from are unknown rather than unchanged, and skipping there would
|
|
50
50
|
* strand the change with nothing left to detect it.
|
|
51
51
|
*/
|
|
52
|
-
async function installDepsChangedSinceDispense(root, dir, step, skipInstall, rerunCommand) {
|
|
52
|
+
async function installDepsChangedSinceDispense(root, dir, step, skipInstall, rerunCommand, sink) {
|
|
53
53
|
const current = depsHash(root);
|
|
54
54
|
if (current !== null && current === step.depsHashAtDispense)
|
|
55
55
|
return;
|
|
56
56
|
if (skipInstall) {
|
|
57
|
-
(0, execute_migration_1.logSkippedPostMigrationInstall)(root);
|
|
57
|
+
(0, execute_migration_1.logSkippedPostMigrationInstall)(root, sink);
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
|
-
await (0, execute_migration_1.runInstall)(root, 'post-migration', rerunCommand);
|
|
60
|
+
await (0, execute_migration_1.runInstall)(root, 'post-migration', rerunCommand, sink);
|
|
61
61
|
recordInstallLanded(root, dir, step.id);
|
|
62
62
|
}
|
|
63
63
|
/**
|