@yaag/runtime 0.8.1 → 0.8.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/package.json +1 -1
- package/src/cassette/index.ts +1 -0
- package/src/cassette/run-checkpoint.ts +49 -13
- package/src/errors.ts +1 -0
- package/src/run/run.ts +4 -0
package/package.json
CHANGED
package/src/cassette/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ export { programIdentityWarning } from "./resume-identity.ts";
|
|
|
30
30
|
export { resumeTransport } from "./resume-transport.ts";
|
|
31
31
|
export {
|
|
32
32
|
type ProgramIdentity,
|
|
33
|
+
prepareRecordDestination,
|
|
33
34
|
publishRunCheckpoint,
|
|
34
35
|
type RunCheckpointResult,
|
|
35
36
|
resolveRunIdentity,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, unlink } from "node:fs/promises";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
+
import { YaagError } from "../errors.ts";
|
|
3
4
|
import type { RunOutcome } from "../events.ts";
|
|
4
5
|
import type { CassetteCollector, CassetteRun } from "./cassette.ts";
|
|
5
6
|
import { publishCassette } from "./cassette-publish.ts";
|
|
@@ -29,7 +30,8 @@ export interface RunCheckpointOptions {
|
|
|
29
30
|
* What one publication attempt did. `published` covers both a written artifact
|
|
30
31
|
* and a Run that keeps none. `displaced` carries the error that must become the
|
|
31
32
|
* Run's outcome. `lost` reports a Checkpoint the Run asked for and did not get,
|
|
32
|
-
* while a
|
|
33
|
+
* while the Run keeps its own outcome — a completed result, or a primary
|
|
34
|
+
* program error (ADR-0021 amendment).
|
|
33
35
|
*/
|
|
34
36
|
export type RunCheckpointResult =
|
|
35
37
|
| { readonly kind: "published" }
|
|
@@ -65,15 +67,24 @@ export async function publishRunCheckpoint(
|
|
|
65
67
|
writeRecordingDiagnostic(error);
|
|
66
68
|
return { kind: "lost", message: String(error) };
|
|
67
69
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
// A completed Run keeps its outcome and its result: the work is done, and
|
|
71
|
+
// no restorability promise is broken. The lost Cassette rides
|
|
72
|
+
// run_end.checkpointLost and the stderr diagnostic instead of displacing
|
|
73
|
+
// the outcome (ADR-0021 amendment).
|
|
74
|
+
if (options.outcome === "completed") {
|
|
75
|
+
const message = `run completed but its Cassette could not be written: ${String(error)}`;
|
|
76
|
+
writeRecordingDiagnostic(message);
|
|
77
|
+
return { kind: "lost", message };
|
|
73
78
|
}
|
|
74
|
-
// A
|
|
75
|
-
//
|
|
76
|
-
|
|
79
|
+
// A pausing or stopped Run promises restorability, and a Checkpoint that
|
|
80
|
+
// did not land makes that acknowledgement a lie, so the failure takes the
|
|
81
|
+
// outcome (ADR-0021: a flush failure is a hard failure there).
|
|
82
|
+
return {
|
|
83
|
+
kind: "displaced",
|
|
84
|
+
error: new Error(`run ${options.outcome} but is not restorable: ${String(error)}`, {
|
|
85
|
+
cause: error,
|
|
86
|
+
}),
|
|
87
|
+
};
|
|
77
88
|
}
|
|
78
89
|
}
|
|
79
90
|
|
|
@@ -82,15 +93,40 @@ function keepsArtifact(options: RunCheckpointOptions): boolean {
|
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
/**
|
|
85
|
-
* Creates the
|
|
86
|
-
*
|
|
96
|
+
* Creates the directory the Run's artifact goes into. A `--record` path names
|
|
97
|
+
* where the artifact goes, so yaag creates the chain that path implies rather
|
|
98
|
+
* than refusing. ADR-0021 makes `--record` fix the destination path only, and a
|
|
99
|
+
* path yaag can make is a destination yaag makes. The checkpoint directory yaag owns stays
|
|
100
|
+
* private; a user-named `--record` directory takes the process umask, because it
|
|
101
|
+
* lives in the user's own tree.
|
|
87
102
|
*/
|
|
88
103
|
export async function ensureCheckpointDirectory(
|
|
89
104
|
record: string | undefined,
|
|
90
105
|
destination: string,
|
|
91
106
|
): Promise<void> {
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
const directory = dirname(destination);
|
|
108
|
+
if (record === undefined) {
|
|
109
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
await mkdir(directory, { recursive: true });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Makes the directory chain of the `--record` path at Run start, and rejects the
|
|
117
|
+
* Run before the first spawn when that chain cannot exist. It does not prove the
|
|
118
|
+
* destination writable: an existing directory that refuses a write, or a name
|
|
119
|
+
* already taken, still fails later, at publication.
|
|
120
|
+
*/
|
|
121
|
+
export async function prepareRecordDestination(record: string): Promise<void> {
|
|
122
|
+
try {
|
|
123
|
+
await ensureCheckpointDirectory(record, record);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
throw new YaagError(
|
|
126
|
+
"RECORD_PATH_UNUSABLE",
|
|
127
|
+
`cannot create the Cassette directory ${dirname(record)}: ${String(error)}`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
94
130
|
}
|
|
95
131
|
|
|
96
132
|
/** Which program a Run executes: a file on disk, or inline source text (ADR-0033). */
|
package/src/errors.ts
CHANGED
|
@@ -48,6 +48,7 @@ export type YaagErrorCode =
|
|
|
48
48
|
| "ARGS_INVALID" // arguments failed schema validation before the Run started
|
|
49
49
|
| "CONFIG_INVALID" // a config layer is missing, unparsable, or violates the schema
|
|
50
50
|
| "OPTIONS_CONFLICT" // incompatible Run options were supplied
|
|
51
|
+
| "RECORD_PATH_UNUSABLE" // the --record directory could not be created at Run start
|
|
51
52
|
| "REPLAY_DIVERGED" // replayed program differed from its Cassette
|
|
52
53
|
| "RESUME_REFUSED" // resume metadata is absent or the recorded tree moved
|
|
53
54
|
| "RUN_CLOSED" // spawn was requested after the Run began settling
|
package/src/run/run.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
interruptedResumeWarning,
|
|
15
15
|
loadCassette,
|
|
16
16
|
type ProgramIdentity,
|
|
17
|
+
prepareRecordDestination,
|
|
17
18
|
programIdentityWarning,
|
|
18
19
|
publishRunCheckpoint,
|
|
19
20
|
type RunCheckpointResult,
|
|
@@ -133,6 +134,9 @@ export async function executeRun<Args, Result>(
|
|
|
133
134
|
await publishRunCheckpoint({ outcome: "failed", ...checkpoint });
|
|
134
135
|
throw error;
|
|
135
136
|
}
|
|
137
|
+
// The directory of the --record path is made before any Agent starts, so a Run
|
|
138
|
+
// whose Cassette can never land does not do its work first and fail last.
|
|
139
|
+
if (options.record !== undefined) await prepareRecordDestination(options.record);
|
|
136
140
|
|
|
137
141
|
let summary: RunSummary = initialSummary();
|
|
138
142
|
const sink: StampedEventSink = options.events ?? (() => {});
|