@olivervea/pipelines 2.0.0-beta.3 → 2.0.0-beta.30

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.
Files changed (64) hide show
  1. package/README.md +163 -78
  2. package/manifest.schema.json +300 -16
  3. package/package.json +6 -4
  4. package/src/assets.d.ts +2 -2
  5. package/src/build/build.ts +98 -64
  6. package/src/build/cli.ts +33 -23
  7. package/src/build/elf.ts +16 -7
  8. package/src/build/hashes.ts +40 -51
  9. package/src/build/project.ts +55 -52
  10. package/src/builder/pipeline.ts +348 -89
  11. package/src/builder/refs.ts +15 -5
  12. package/src/core/brands.ts +6 -5
  13. package/src/core/config-refs.ts +103 -0
  14. package/src/core/config.ts +224 -0
  15. package/src/core/external.ts +86 -0
  16. package/src/core/handlers.ts +62 -15
  17. package/src/core/matrix.ts +150 -0
  18. package/src/core/models.ts +37 -15
  19. package/src/core/secrets.ts +2 -2
  20. package/src/core/steps.ts +113 -38
  21. package/src/core/types.ts +5 -1
  22. package/src/core/webhooks.ts +21 -7
  23. package/src/ctx/files.ts +119 -42
  24. package/src/ctx/index.ts +103 -38
  25. package/src/ctx/links.ts +30 -0
  26. package/src/ctx/sh.ts +104 -44
  27. package/src/ctx/types.ts +24 -4
  28. package/src/describe/config-schema.ts +98 -0
  29. package/src/describe/context.ts +187 -0
  30. package/src/describe/describe.ts +63 -355
  31. package/src/describe/failure-handlers.ts +34 -0
  32. package/src/describe/manifest-schema.ts +304 -61
  33. package/src/describe/mapper-source.ts +252 -105
  34. package/src/describe/record.ts +67 -37
  35. package/src/describe/schemas.ts +71 -0
  36. package/src/describe/stages.ts +166 -0
  37. package/src/describe/step.ts +544 -0
  38. package/src/describe/triggers.ts +72 -0
  39. package/src/dev/cli.ts +62 -35
  40. package/src/harness/env.ts +47 -25
  41. package/src/harness/execute.ts +74 -30
  42. package/src/harness/exit.ts +1 -1
  43. package/src/harness/index.ts +19 -8
  44. package/src/harness/main.ts +234 -98
  45. package/src/harness/result.ts +45 -24
  46. package/src/harness/validate.ts +82 -15
  47. package/src/index.ts +139 -10
  48. package/src/library/checks.ts +11 -6
  49. package/src/library/compose-deploy.ts +115 -0
  50. package/src/library/github-source.ts +100 -47
  51. package/src/library/helm-deploy-ssh.ts +426 -112
  52. package/src/library/helm-deploy.ts +529 -0
  53. package/src/library/http-health.ts +7 -5
  54. package/src/library/image-build.ts +510 -162
  55. package/src/library/index.ts +48 -14
  56. package/src/library/models.ts +35 -14
  57. package/src/library/ssh.ts +44 -18
  58. package/src/testing/index.ts +100 -35
  59. package/src/util/canonical.ts +7 -4
  60. package/src/util/code-hash.ts +17 -0
  61. package/src/util/hash.ts +5 -5
  62. package/src/util/tar.ts +83 -38
  63. package/src/library/script.ts +0 -85
  64. package/src/util/handler-hash.ts +0 -22
package/src/dev/cli.ts CHANGED
@@ -1,31 +1,42 @@
1
1
  #!/usr/bin/env bun
2
2
  // olve-dev — run a pipeline from source through the same harness path as the compiled binary.
3
3
  // olve-dev describe <dir> [--sentinels]
4
- // olve-dev run <dir> <step> [--input-file f] [--olve-dir d] [--secret NAME=value]...
4
+ // olve-dev run <dir> <step> [--input-file f] [--config-file f] [--olve-dir d] [--secret NAME=value]...
5
5
  // olve-dev map-trigger <dir> <trigger> --event-file f [--olve-dir d]
6
6
  // $OLVE_DIR defaults to <dir>/.olve; outputs land in <dir>/.olve/work/.
7
- import { copyFileSync, mkdirSync } from "node:fs";
8
- import { join, resolve } from "node:path";
9
- import { parseArgs } from "node:util";
10
- import { runHarness, type HarnessConfig } from "../harness/main.ts";
11
- import { EXIT } from "../harness/exit.ts";
12
- import { handlerHashes } from "../build/hashes.ts";
13
- import { harnessInfo, loadProject, ProjectError } from "../build/project.ts";
7
+ import {
8
+ copyFileSync,
9
+ existsSync,
10
+ mkdirSync,
11
+ readFileSync,
12
+ writeFileSync,
13
+ } from 'node:fs';
14
+ import { join, resolve } from 'node:path';
15
+ import { parseArgs } from 'node:util';
16
+ import { runHarness, type HarnessConfig } from '../harness/main.ts';
17
+ import { EXIT } from '../harness/exit.ts';
18
+ import { codeHash } from '../build/hashes.ts';
19
+ import {
20
+ harnessInfo,
21
+ loadPipeline,
22
+ loadProject,
23
+ ProjectError,
24
+ } from '../build/project.ts';
14
25
 
15
26
  const USAGE = `usage: olve-dev describe <dir> [--sentinels]
16
- olve-dev run <dir> <step> [--input-file f] [--olve-dir d] [--secret NAME=value]...
27
+ olve-dev run <dir> <step> [--input-file f] [--config-file f] [--olve-dir d] [--secret NAME=value]...
17
28
  olve-dev map-trigger <dir> <trigger> --event-file f [--olve-dir d]`;
18
29
 
19
- export async function devConfig(dir: string, withHashes: boolean): Promise<HarnessConfig> {
30
+ export async function devConfig(
31
+ dir: string,
32
+ withHashes: boolean,
33
+ ): Promise<HarnessConfig> {
20
34
  const project = await loadProject(dir);
21
- const mod = (await import(project.pipelineFile)) as { default?: unknown };
22
- if (!mod.default) throw new ProjectError(`${project.pipelineFile} has no default export (export default pipeline({...})...)`);
35
+ const pipeline = await loadPipeline(project);
23
36
  const harness = harnessInfo();
24
- const hashes = withHashes ? await handlerHashes(project, harness) : {};
25
37
  return {
26
- pipeline: mod.default,
27
- handlers: Object.fromEntries(project.handlers.map(([name, file]) => [name, () => import(file) as Promise<{ default: unknown }>])),
28
- handlerHashes: Object.fromEntries(Object.entries(hashes).map(([k, v]) => [k, v.handlerHash])),
38
+ pipeline,
39
+ ...(withHashes ? { codeHash: await codeHash(project, harness) } : {}),
29
40
  harness,
30
41
  };
31
42
  }
@@ -37,12 +48,13 @@ export async function devMain(argv: string[]): Promise<number> {
37
48
  args: argv,
38
49
  allowPositionals: true,
39
50
  options: {
40
- "input-file": { type: "string" },
41
- "event-file": { type: "string" },
42
- "olve-dir": { type: "string" },
43
- secret: { type: "string", multiple: true },
44
- sentinels: { type: "boolean", default: false },
45
- help: { type: "boolean", short: "h", default: false },
51
+ 'input-file': { type: 'string' },
52
+ 'config-file': { type: 'string' },
53
+ 'event-file': { type: 'string' },
54
+ 'olve-dir': { type: 'string' },
55
+ secret: { type: 'string', multiple: true },
56
+ sentinels: { type: 'boolean', default: false },
57
+ help: { type: 'boolean', short: 'h', default: false },
46
58
  },
47
59
  });
48
60
  } catch (e) {
@@ -50,28 +62,43 @@ export async function devMain(argv: string[]): Promise<number> {
50
62
  return EXIT.usage;
51
63
  }
52
64
  const { values, positionals } = parsed;
53
- if (values.help) return console.log(USAGE), 0;
65
+ if (values.help) return (console.log(USAGE), 0);
54
66
  const [mode, dir, name] = positionals;
55
- if (!mode || !dir) return console.error(USAGE), EXIT.usage;
67
+ if (!mode || !dir) return (console.error(USAGE), EXIT.usage);
56
68
  try {
57
- if (mode === "describe") return await runHarness(await devConfig(dir, true), ["describe", ...(values.sentinels ? ["--sentinels"] : [])]);
58
- if (mode !== "run" && mode !== "map-trigger") return console.error(USAGE), EXIT.usage;
59
- if (!name) return console.error(USAGE), EXIT.usage;
60
- const olveDir = resolve(values["olve-dir"] ?? join(dir, ".olve"));
69
+ if (mode === 'describe')
70
+ return await runHarness(await devConfig(dir, true), [
71
+ 'describe',
72
+ ...(values.sentinels ? ['--sentinels'] : []),
73
+ ]);
74
+ if (mode !== 'run' && mode !== 'map-trigger')
75
+ return (console.error(USAGE), EXIT.usage);
76
+ if (!name) return (console.error(USAGE), EXIT.usage);
77
+ const file = mode === 'run' ? values['input-file'] : values['event-file'];
78
+ if (mode === 'map-trigger' && !file)
79
+ return (console.error('--event-file is required'), EXIT.usage);
80
+ const olveDir = resolve(values['olve-dir'] ?? join(dir, '.olve'));
61
81
  mkdirSync(olveDir, { recursive: true });
62
82
  process.env.OLVE_DIR = olveDir;
63
- process.env.TMPDIR ??= join(olveDir, "tmp");
83
+ process.env.TMPDIR ??= join(olveDir, 'tmp');
64
84
  for (const s of values.secret ?? []) {
65
- const i = s.indexOf("=");
66
- if (i <= 0) return console.error(`--secret expects NAME=value, got '${s}'`), EXIT.usage;
85
+ const i = s.indexOf('=');
86
+ if (i <= 0)
87
+ return (console.error(`--secret expects NAME=value, got '${s}'`), EXIT.usage);
67
88
  process.env[s.slice(0, i)] = s.slice(i + 1);
68
89
  }
69
- const file = mode === "run" ? values["input-file"] : values["event-file"];
70
- if (file) copyFileSync(resolve(file), join(olveDir, "input.json"));
71
- else if (mode === "map-trigger") return console.error("--event-file is required"), EXIT.usage;
90
+ if (file) copyFileSync(resolve(file), join(olveDir, 'input.json'));
91
+ if (mode === 'run' && values['config-file']) {
92
+ const inputFile = join(olveDir, 'input.json');
93
+ const input = (
94
+ existsSync(inputFile) ? JSON.parse(readFileSync(inputFile, 'utf8')) : {}
95
+ ) as Record<string, unknown>;
96
+ input.$config = JSON.parse(readFileSync(resolve(values['config-file']), 'utf8'));
97
+ writeFileSync(inputFile, JSON.stringify(input));
98
+ }
72
99
  return await runHarness(await devConfig(dir, false), [mode, name]);
73
100
  } catch (e) {
74
- if (e instanceof ProjectError) return console.error(e.message), EXIT.configInvalid;
101
+ if (e instanceof ProjectError) return (console.error(e.message), EXIT.configInvalid);
75
102
  console.error((e as Error)?.stack ?? String(e));
76
103
  return EXIT.configInvalid;
77
104
  }
@@ -1,58 +1,80 @@
1
1
  // The environment contract between the service and the harness. Documented in README.md.
2
- import { join } from "node:path";
2
+ import { join } from 'node:path';
3
3
 
4
4
  export const ENV = {
5
5
  /** Root of the harness IO directory (default /olve). */
6
- dir: "OLVE_DIR",
7
- pipeline: "OLVE_PIPELINE",
8
- runId: "OLVE_RUN_ID",
9
- run: "OLVE_RUN",
10
- attempt: "OLVE_ATTEMPT",
11
- versionStamp: "OLVE_VERSION_STAMP",
6
+ dir: 'OLVE_DIR',
7
+ pipeline: 'OLVE_PIPELINE',
8
+ runId: 'OLVE_RUN_ID',
9
+ run: 'OLVE_RUN',
10
+ attempt: 'OLVE_ATTEMPT',
11
+ versionStamp: 'OLVE_VERSION_STAMP',
12
+ /** A matrix step's cell, as compact JSON in axis order (`{"os":"linux","arch":"x64"}`). */
13
+ cell: 'OLVE_CELL',
12
14
  /** Pre-signed PUT for result.tar.gz (run / map-trigger). Optional. */
13
- resultUrl: "OLVE_RESULT_URL",
15
+ resultUrl: 'OLVE_RESULT_URL',
14
16
  /** JSON object: file key (`<run>/<step>/<attempt>/<path>`) → pre-signed GET URL. Optional. */
15
- fileUrls: "OLVE_FILE_URLS",
17
+ fileUrls: 'OLVE_FILE_URLS',
18
+ /** JSON object: `<step>/<input>` → callback URL of that external input in this run (`ctx.inputUrl`). Optional. */
19
+ callbackUrls: 'OLVE_CALLBACK_URLS',
16
20
  /** Base URL for file downloads: GET `<base>/<run>/<step>/<attempt>/<object path>`. Optional. */
17
- filesBaseUrl: "OLVE_FILES_BASE_URL",
21
+ filesBaseUrl: 'OLVE_FILES_BASE_URL',
18
22
  /** Bearer token sent with OLVE_FILES_BASE_URL requests. Optional. */
19
- filesToken: "OLVE_FILES_TOKEN",
23
+ filesToken: 'OLVE_FILES_TOKEN',
20
24
  /** Local directory laid out as `<run>/<step>/<attempt>/files/<object path>` (dev). Optional. */
21
- filesDir: "OLVE_FILES_DIR",
25
+ filesDir: 'OLVE_FILES_DIR',
22
26
  } as const;
23
27
 
24
28
  /** Env vars never passed to child processes (they carry credentials for the harness only). */
25
- export const PRIVATE_ENV: readonly string[] = [ENV.resultUrl, ENV.fileUrls, ENV.filesToken];
29
+ export const PRIVATE_ENV: readonly string[] = [
30
+ ENV.resultUrl,
31
+ ENV.fileUrls,
32
+ ENV.filesToken,
33
+ ENV.callbackUrls,
34
+ ];
26
35
 
27
- export const olveDir = () => process.env[ENV.dir] || "/olve";
36
+ /** `OLVE_CALLBACK_URLS`, parsed; empty when unset or malformed. */
37
+ export function callbackUrlsFromEnv(): Record<string, string> {
38
+ const raw = process.env[ENV.callbackUrls];
39
+ if (!raw) return {};
40
+ try {
41
+ const parsed: unknown = JSON.parse(raw);
42
+ return parsed && typeof parsed === 'object' ? (parsed as Record<string, string>) : {};
43
+ } catch {
44
+ return {};
45
+ }
46
+ }
47
+
48
+ export const olveDir = () => process.env[ENV.dir] || '/olve';
28
49
  export const paths = () => {
29
50
  const root = olveDir();
30
51
  return {
31
52
  root,
32
- input: join(root, "input.json"),
33
- work: join(root, "work"),
34
- output: join(root, "work", "output.json"),
35
- files: join(root, "work", "files"),
36
- tmp: join(root, "tmp"),
37
- fileUrls: join(root, "files.json"),
53
+ input: join(root, 'input.json'),
54
+ work: join(root, 'work'),
55
+ output: join(root, 'work', 'output.json'),
56
+ files: join(root, 'work', 'files'),
57
+ tmp: join(root, 'tmp'),
58
+ fileUrls: join(root, 'files.json'),
38
59
  };
39
60
  };
40
61
 
41
62
  function int(name: string, fallback: number): number {
42
63
  const v = process.env[name];
43
- if (v === undefined || v === "") return fallback;
64
+ if (v === undefined || v === '') return fallback;
44
65
  const n = Number(v);
45
- if (!Number.isInteger(n) || n < 0) throw new Error(`${name} must be a non-negative integer, got '${v}'`);
66
+ if (!Number.isInteger(n) || n < 0)
67
+ throw new Error(`${name} must be a non-negative integer, got '${v}'`);
46
68
  return n;
47
69
  }
48
70
 
49
71
  export function runInfoFromEnv(step: string) {
50
72
  return {
51
- pipeline: process.env[ENV.pipeline] ?? "local",
52
- id: process.env[ENV.runId] ?? "local",
73
+ pipeline: process.env[ENV.pipeline] ?? 'local',
74
+ id: process.env[ENV.runId] ?? 'local',
53
75
  number: int(ENV.run, 0),
54
76
  attempt: Math.max(1, int(ENV.attempt, 1)),
55
77
  step,
56
- version: process.env[ENV.versionStamp] ?? "0.0.0-local",
78
+ version: process.env[ENV.versionStamp] ?? '0.0.0-local',
57
79
  };
58
80
  }
@@ -1,20 +1,22 @@
1
1
  // The shared execution path: validate input → handler → validate output → envelope.
2
2
  // Used by the compiled harness (`run`), the dev runner and testHandler.
3
- import { existsSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { isSkipped } from "../core/handlers.ts";
6
- import type { AnyStep } from "../core/steps.ts";
7
- import type { Ctx, RunInfo } from "../ctx/types.ts";
8
- import { objectPath } from "../ctx/files.ts";
9
- import { sha256File } from "../util/hash.ts";
10
- import { EXIT } from "./exit.ts";
11
- import { validateInput, validateOutput } from "./validate.ts";
3
+ import { existsSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { isSkipped } from '../core/handlers.ts';
6
+ import type { AnyStep } from '../core/steps.ts';
7
+ import type { Ctx, RunInfo } from '../ctx/types.ts';
8
+ import { objectPath } from '../ctx/files.ts';
9
+ import { createLinks, type StepLink } from '../ctx/links.ts';
10
+ import { sha256File } from '../util/hash.ts';
11
+ import { EXIT } from './exit.ts';
12
+ import { validateConfig, validateInput, validateOutput } from './validate.ts';
12
13
 
13
- export type FailureKind = "handler" | "input" | "output";
14
- export type Envelope =
15
- | { status: "succeeded"; output: unknown }
16
- | { status: "skipped"; reason: string }
17
- | { status: "failed"; error: { kind: FailureKind; message: string } };
14
+ export type FailureKind = 'handler' | 'input' | 'output';
15
+ export type Envelope = (
16
+ | { status: 'succeeded'; output: unknown }
17
+ | { status: 'skipped'; reason: string }
18
+ | { status: 'failed'; error: { kind: FailureKind; message: string } }
19
+ ) & { links?: StepLink[] };
18
20
 
19
21
  export interface Execution {
20
22
  envelope: Envelope;
@@ -26,7 +28,7 @@ export type StepFunction = (input: Record<string, unknown>, ctx: Ctx) => unknown
26
28
  /** Signal → exit code, when the handler failed after a cancellation. */
27
29
  export function cancelExitCode(signal: AbortSignal): number | undefined {
28
30
  if (!signal.aborted) return undefined;
29
- return signal.reason === "SIGINT" ? EXIT.sigint : EXIT.sigterm;
31
+ return signal.reason === 'SIGINT' ? EXIT.sigint : EXIT.sigterm;
30
32
  }
31
33
 
32
34
  const errorMessage = (e: unknown): string => (e instanceof Error ? e.message : String(e));
@@ -36,17 +38,29 @@ function checkOwnFileRefs(value: unknown, run: RunInfo, workFiles: string): stri
36
38
  const problems: string[] = [];
37
39
  const walk = (v: unknown, at: string) => {
38
40
  if (Array.isArray(v)) return v.forEach((x, i) => walk(x, `${at}[${i}]`));
39
- if (typeof v !== "object" || v === null) return;
41
+ if (typeof v !== 'object' || v === null) return;
40
42
  const o = v as Record<string, unknown>;
41
- if (typeof o.path === "string" && typeof o.sha256 === "string" && o.run === run.number && o.step === run.step && o.attempt === run.attempt) {
43
+ if (
44
+ typeof o.path === 'string' &&
45
+ typeof o.sha256 === 'string' &&
46
+ o.run === run.number &&
47
+ o.step === run.step &&
48
+ o.attempt === run.attempt
49
+ ) {
42
50
  const file = join(workFiles, objectPath(o.path));
43
- if (!existsSync(file)) problems.push(`${at}: file reference '${o.path}' points to this attempt but no such output file was produced`);
44
- else if (sha256File(file) !== o.sha256) problems.push(`${at}: file reference '${o.path}' has the wrong sha256 (the file changed after it was referenced)`);
51
+ if (!existsSync(file))
52
+ problems.push(
53
+ `${at}: file reference '${o.path}' points to this attempt but no such output file was produced`,
54
+ );
55
+ else if (sha256File(file) !== o.sha256)
56
+ problems.push(
57
+ `${at}: file reference '${o.path}' has the wrong sha256 (the file changed after it was referenced)`,
58
+ );
45
59
  return;
46
60
  }
47
61
  for (const [k, x] of Object.entries(o)) walk(x, `${at}.${k}`);
48
62
  };
49
- walk(value, "output");
63
+ walk(value, 'output');
50
64
  return problems;
51
65
  }
52
66
 
@@ -58,13 +72,29 @@ export async function executeStep(args: {
58
72
  workFiles: string;
59
73
  log: (line: string) => void;
60
74
  }): Promise<Execution> {
61
- const { step, ctx, log } = args;
75
+ const { step, log } = args;
62
76
  const input = validateInput(step, args.rawInput);
63
- if (!input.ok) {
64
- const message = `step '${step.name}': input does not match the step's inputs:\n ${input.issues.join("\n ")}`;
77
+ const config = validateConfig(step, args.rawInput);
78
+ if (!input.ok || !config.ok) {
79
+ const issues = [
80
+ ...(input.ok ? [] : input.issues),
81
+ ...(config.ok ? [] : config.issues),
82
+ ];
83
+ const message = `step '${step.name}': input does not match the step's inputs:\n ${issues.join('\n ')}`;
65
84
  log(message);
66
- return { envelope: { status: "failed", error: { kind: "input", message } }, exitCode: EXIT.inputInvalid };
85
+ return {
86
+ envelope: { status: 'failed', error: { kind: 'input', message } },
87
+ exitCode: EXIT.inputInvalid,
88
+ };
67
89
  }
90
+ const collected = createLinks();
91
+ const ctx: Ctx = {
92
+ ...args.ctx,
93
+ config: Object.freeze(config.value) as Ctx['config'],
94
+ link: collected.link,
95
+ };
96
+ const withLinks = (e: Envelope): Envelope =>
97
+ collected.links.length ? { ...e, links: [...collected.links] } : e;
68
98
 
69
99
  let value: unknown;
70
100
  try {
@@ -76,19 +106,33 @@ export async function executeStep(args: {
76
106
  : `step '${step.name}' failed: ${errorMessage(e)}`;
77
107
  log(message);
78
108
  if (e instanceof Error && e.stack && !cancelled) log(e.stack);
79
- return { envelope: { status: "failed", error: { kind: "handler", message } }, exitCode: cancelled ?? EXIT.handlerFailed };
109
+ return {
110
+ envelope: withLinks({ status: 'failed', error: { kind: 'handler', message } }),
111
+ exitCode: cancelled ?? EXIT.handlerFailed,
112
+ };
80
113
  }
81
114
 
82
115
  if (isSkipped(value)) {
83
116
  log(`step '${step.name}' skipped: ${value.reason}`);
84
- return { envelope: { status: "skipped", reason: value.reason }, exitCode: EXIT.ok };
117
+ return {
118
+ envelope: withLinks({ status: 'skipped', reason: value.reason }),
119
+ exitCode: EXIT.ok,
120
+ };
85
121
  }
86
122
  const out = validateOutput(step, value);
87
- const problems = out.ok ? checkOwnFileRefs(out.value, ctx.run, args.workFiles) : out.issues;
123
+ const problems = out.ok
124
+ ? checkOwnFileRefs(out.value, ctx.run, args.workFiles)
125
+ : out.issues;
88
126
  if (!out.ok || problems.length) {
89
- const message = `step '${step.name}': returned value does not match ${step.output?.name ?? "its (absent) output model"}:\n ${problems.join("\n ")}`;
127
+ const message = `step '${step.name}': returned value does not match ${step.output?.name ?? 'its (absent) output model'}:\n ${problems.join('\n ')}`;
90
128
  log(message);
91
- return { envelope: { status: "failed", error: { kind: "output", message } }, exitCode: EXIT.outputInvalid };
129
+ return {
130
+ envelope: withLinks({ status: 'failed', error: { kind: 'output', message } }),
131
+ exitCode: EXIT.outputInvalid,
132
+ };
92
133
  }
93
- return { envelope: { status: "succeeded", output: out.value }, exitCode: EXIT.ok };
134
+ return {
135
+ envelope: withLinks({ status: 'succeeded', output: out.value }),
136
+ exitCode: EXIT.ok,
137
+ };
94
138
  }
@@ -11,7 +11,7 @@ export const EXIT = {
11
11
  outputInvalid: 66,
12
12
  /** Uploading result.tar.gz to OLVE_RESULT_URL failed (after retries). The envelope was still printed. */
13
13
  uploadFailed: 74,
14
- /** The pipeline is invalid: describe problems, missing/invalid handler module. */
14
+ /** The pipeline is invalid: describe problems, a step without an implementation. */
15
15
  configInvalid: 78,
16
16
  /** Cancelled by SIGINT / SIGTERM (128 + signal). */
17
17
  sigint: 130,
@@ -1,9 +1,20 @@
1
1
  // @olivervea/pipelines/harness — the runtime the build tool compiles into the step binary.
2
- export { runHarness, defaultIO, allSteps, resolveStepFunction, type HarnessConfig, type HandlerRegistry, type HarnessIO } from "./main.ts";
3
- export { EXIT } from "./exit.ts";
4
- export { ENV, paths, runInfoFromEnv } from "./env.ts";
5
- export { executeStep, type Envelope, type Execution, type FailureKind } from "./execute.ts";
6
- export { RESULT_BEGIN, RESULT_END, MANIFEST_BEGIN, MANIFEST_END } from "./result.ts";
7
- export { describePipeline, DescribeError, manifestText } from "../describe/describe.ts";
8
- export type { ManifestT as Manifest } from "../describe/manifest-schema.ts";
9
- export type { HarnessInfo } from "../util/handler-hash.ts";
2
+ export {
3
+ runHarness,
4
+ defaultIO,
5
+ resolveStepFunction,
6
+ type HarnessConfig,
7
+ type HarnessIO,
8
+ } from './main.ts';
9
+ export { EXIT } from './exit.ts';
10
+ export { ENV, paths, runInfoFromEnv } from './env.ts';
11
+ export {
12
+ executeStep,
13
+ type Envelope,
14
+ type Execution,
15
+ type FailureKind,
16
+ } from './execute.ts';
17
+ export { RESULT_BEGIN, RESULT_END, MANIFEST_BEGIN, MANIFEST_END } from './result.ts';
18
+ export { describePipeline, DescribeError, manifestText } from '../describe/describe.ts';
19
+ export type { ManifestT as Manifest } from '../describe/manifest-schema.ts';
20
+ export type { HarnessInfo } from '../util/code-hash.ts';