praxis-agent 0.65.0 → 0.66.0

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.
@@ -7,7 +7,7 @@ import { createProjectEvalIdentity, } from './project-eval-identity.js';
7
7
  import { cleanupProjectEvalWorkspace, createProjectEvalWorkspace, diffProjectEvalWorkspace, } from './project-eval-workspace.js';
8
8
  import { minimatch } from 'minimatch';
9
9
  import { join } from 'node:path';
10
- const SAFETY_CHECKS = [
10
+ export const PROJECT_EVAL_SAFETY_CHECKS = [
11
11
  'trace-bounds',
12
12
  'runtime-close',
13
13
  'workspace-manifest',
@@ -33,7 +33,7 @@ function runEvidence(trace, checks) {
33
33
  retries += 1;
34
34
  }
35
35
  return {
36
- safety_passed: SAFETY_CHECKS.every((name) => checks.find((check) => check.name === name)?.passed === true),
36
+ safety_passed: PROJECT_EVAL_SAFETY_CHECKS.every((name) => checks.find((check) => check.name === name)?.passed === true),
37
37
  permission_decisions,
38
38
  tool_errors,
39
39
  retries,
@@ -144,6 +144,7 @@ export async function runProjectEvalCase(options) {
144
144
  effectiveTools: allowedTools,
145
145
  runVerification: options.runVerification ?? false,
146
146
  praxisVersion: options.version,
147
+ buildIdentity: options.buildIdentity,
147
148
  });
148
149
  }
149
150
  catch (error) {
@@ -224,67 +225,114 @@ export async function runProjectEvalCase(options) {
224
225
  cleanupErrors.push(`Runtime cleanup failed: ${runtimeCloseError}`);
225
226
  }
226
227
  }
227
- if (runtimeCompleted && !runtimeCloseError && !termination) {
228
- for (const verifier of options.case.verification) {
229
- const verifierStarted = Date.now();
230
- try {
231
- const result = await new BoundedProcessRunner({
232
- cwd: workspace.cwd,
233
- maxOutputBytes: 64 * 1024,
234
- }).run({
235
- command: verifier.command,
236
- args: [...verifier.args],
237
- cwd: workspace.cwd,
238
- timeoutMs: verifier.timeoutSeconds * 1000,
239
- ...(options.signal === undefined ? {} : { signal: options.signal }),
240
- env: verifierEnvironment(options.case.execution.env, workspace.home, workspace.root),
241
- inheritEnvironment: false,
242
- redactExplicitEnvironment: true,
243
- });
244
- const passed = result.code === 0 && !result.timedOut;
245
- const verifierError = result.timedOut
228
+ const addNotRunVerifier = (verifier, reason) => {
229
+ verifications.push({
230
+ schema_version: '1.1',
231
+ name: verifier.name,
232
+ required: true,
233
+ expect: 'pass',
234
+ status: 'not_run',
235
+ command: verifier.command,
236
+ argv: [...verifier.args],
237
+ exit_code: null,
238
+ timed_out: false,
239
+ stdout: '',
240
+ stderr: '',
241
+ truncated: false,
242
+ duration_ms: 0,
243
+ passed: false,
244
+ error: reason,
245
+ });
246
+ verifierChecks.push(check(`verifier:${verifier.name}`, false, reason));
247
+ };
248
+ let verifierInterrupted = false;
249
+ for (const verifier of options.case.verification) {
250
+ if (verifierInterrupted ||
251
+ !runtimeCompleted ||
252
+ runtimeCloseError ||
253
+ termination) {
254
+ addNotRunVerifier(verifier, verifierInterrupted
255
+ ? 'Verifier not run: previous verifier interrupted'
256
+ : runtimeCloseError
257
+ ? 'Verifier not run: runtime cleanup failed'
258
+ : termination
259
+ ? `Verifier not run: runtime ${termination}`
260
+ : 'Verifier not run: runtime did not complete');
261
+ continue;
262
+ }
263
+ const verifierStarted = Date.now();
264
+ try {
265
+ const result = await new BoundedProcessRunner({
266
+ cwd: workspace.cwd,
267
+ maxOutputBytes: 64 * 1024,
268
+ }).run({
269
+ command: verifier.command,
270
+ args: [...verifier.args],
271
+ cwd: workspace.cwd,
272
+ timeoutMs: verifier.timeoutSeconds * 1000,
273
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
274
+ env: verifierEnvironment(options.case.execution.env, workspace.home, workspace.root),
275
+ inheritEnvironment: false,
276
+ redactExplicitEnvironment: true,
277
+ });
278
+ const interrupted = options.signal?.aborted === true;
279
+ if (interrupted) {
280
+ termination = 'interrupted';
281
+ verifierInterrupted = true;
282
+ }
283
+ const passed = result.code === 0 && !result.timedOut && !interrupted;
284
+ const verifierError = interrupted
285
+ ? 'Verifier interrupted'
286
+ : result.timedOut
246
287
  ? 'Verifier timed out'
247
288
  : result.code === 0
248
289
  ? null
249
290
  : `Verifier exited with code ${result.code}`;
250
- verifications.push({
251
- schema_version: '1.0',
252
- name: verifier.name,
253
- command: verifier.command,
254
- argv: [...verifier.args],
255
- exit_code: result.code,
256
- timed_out: result.timedOut,
257
- stdout: result.stdout,
258
- stderr: result.stderr,
259
- truncated: result.truncated,
260
- duration_ms: Date.now() - verifierStarted,
261
- passed,
262
- error: verifierError,
263
- });
264
- verifierChecks.push(check(`verifier:${verifier.name}`, passed, passed ? 'Verifier passed' : (verifierError ?? 'Verifier failed')));
265
- }
266
- catch (error) {
267
- const message = errorText(error);
268
- if (options.signal?.aborted)
269
- termination = 'interrupted';
270
- verifications.push({
271
- schema_version: '1.0',
272
- name: verifier.name,
273
- command: verifier.command,
274
- argv: [...verifier.args],
275
- exit_code: null,
276
- timed_out: false,
277
- stdout: '',
278
- stderr: '',
279
- truncated: false,
280
- duration_ms: Date.now() - verifierStarted,
281
- passed: false,
282
- error: message,
283
- });
284
- verifierChecks.push(check(`verifier:${verifier.name}`, false, message));
285
- if (termination === 'interrupted')
286
- break;
291
+ const status = passed ? 'passed' : 'failed';
292
+ verifications.push({
293
+ schema_version: '1.1',
294
+ name: verifier.name,
295
+ required: true,
296
+ expect: 'pass',
297
+ status,
298
+ command: verifier.command,
299
+ argv: [...verifier.args],
300
+ exit_code: result.code,
301
+ timed_out: result.timedOut,
302
+ stdout: result.stdout,
303
+ stderr: result.stderr,
304
+ truncated: result.truncated,
305
+ duration_ms: Date.now() - verifierStarted,
306
+ passed,
307
+ error: verifierError,
308
+ });
309
+ verifierChecks.push(check(`verifier:${verifier.name}`, passed, passed ? 'Verifier passed' : (verifierError ?? 'Verifier failed')));
310
+ }
311
+ catch (error) {
312
+ const message = errorText(error);
313
+ const interrupted = options.signal?.aborted === true;
314
+ if (interrupted) {
315
+ termination = 'interrupted';
316
+ verifierInterrupted = true;
287
317
  }
318
+ verifications.push({
319
+ schema_version: '1.1',
320
+ name: verifier.name,
321
+ required: true,
322
+ expect: 'pass',
323
+ status: 'failed',
324
+ command: verifier.command,
325
+ argv: [...verifier.args],
326
+ exit_code: null,
327
+ timed_out: false,
328
+ stdout: '',
329
+ stderr: '',
330
+ truncated: false,
331
+ duration_ms: Date.now() - verifierStarted,
332
+ passed: false,
333
+ error: interrupted ? 'Verifier interrupted' : message,
334
+ });
335
+ verifierChecks.push(check(`verifier:${verifier.name}`, false, interrupted ? 'Verifier interrupted' : message));
288
336
  }
289
337
  }
290
338
  const verifiersPassed = verifierChecks.every((item) => item.passed);
@@ -374,6 +422,15 @@ export async function runProjectEvalCase(options) {
374
422
  : `Temporary workspace cleanup failed: ${tempCleanupError}`));
375
423
  const passed = checks.every((item) => item.passed);
376
424
  const evidence = runEvidence(trace, checks);
425
+ const verification = {
426
+ outcomes: verifications.map(({ name, status }) => ({
427
+ name,
428
+ required: true,
429
+ expect: 'pass',
430
+ status,
431
+ })),
432
+ satisfied: verifications.every((verification) => verification.status === 'passed'),
433
+ };
377
434
  const primaryError = runtimeError ??
378
435
  graderError ??
379
436
  verifications.find((verification) => verification.error)?.error ??
@@ -381,8 +438,9 @@ export async function runProjectEvalCase(options) {
381
438
  checks.find((item) => !item.passed)?.explanation ??
382
439
  null;
383
440
  const result = {
384
- schema_version: '1.1',
441
+ schema_version: '1.2',
385
442
  case: options.case.name,
443
+ risk: options.case.risk,
386
444
  run: options.run,
387
445
  version: options.version,
388
446
  model: identity.model_id,
@@ -398,6 +456,7 @@ export async function runProjectEvalCase(options) {
398
456
  ...evidence,
399
457
  graders,
400
458
  checks,
459
+ verification,
401
460
  artifacts: {
402
461
  trace: 'trace.jsonl',
403
462
  workspace_diff: 'workspace-diff.json',
@@ -1,8 +1,12 @@
1
1
  import type { EvalDeterministicGrader } from './eval-contract.js';
2
- export declare const PROJECT_EVAL_SCHEMA_VERSION = "1.0";
2
+ export declare const PROJECT_EVAL_SCHEMA_VERSION = "1.1";
3
+ export declare const PROJECT_EVAL_RISKS: readonly ["low", "medium", "high", "release"];
4
+ export type ProjectEvalRisk = (typeof PROJECT_EVAL_RISKS)[number];
5
+ export declare const PROJECT_EVAL_MAX_ITEMS = 256;
3
6
  export interface ProjectEvalCase {
4
- schemaVersion: '1.0';
7
+ schemaVersion: '1.1';
5
8
  name: string;
9
+ risk: ProjectEvalRisk;
6
10
  dir: string;
7
11
  fixture: string;
8
12
  tags: readonly string[];
@@ -21,6 +25,8 @@ export interface ProjectEvalCase {
21
25
  command: string;
22
26
  args: readonly string[];
23
27
  timeoutSeconds: number;
28
+ required: true;
29
+ expect: 'pass';
24
30
  }[];
25
31
  graders: readonly EvalDeterministicGrader[];
26
32
  expect: {
@@ -2,9 +2,10 @@ import { lstat, readFile, readdir, realpath } from 'node:fs/promises';
2
2
  import { isAbsolute, join, resolve, sep } from 'node:path';
3
3
  import { parse as parseYaml } from 'yaml';
4
4
  import { Minimatch, minimatch } from 'minimatch';
5
- export const PROJECT_EVAL_SCHEMA_VERSION = '1.0';
5
+ export const PROJECT_EVAL_SCHEMA_VERSION = '1.1';
6
+ export const PROJECT_EVAL_RISKS = ['low', 'medium', 'high', 'release'];
6
7
  const NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
7
- const MAX_ITEMS = 256;
8
+ export const PROJECT_EVAL_MAX_ITEMS = 256;
8
9
  const MAX_STRING = 16 * 1024;
9
10
  const MAX_BYTES = 1024 * 1024;
10
11
  const DEFAULT_PROJECT_EVAL_ALLOWED_TOOLS = ['Read', 'Glob', 'Grep'];
@@ -17,14 +18,14 @@ function validateBounds(value, label = 'case', depth = 0, state = { nodes: 0 })
17
18
  if (typeof value === 'string' && value.length > MAX_STRING)
18
19
  throw new Error(`${label} contains oversized string`);
19
20
  if (Array.isArray(value)) {
20
- if (value.length > MAX_ITEMS)
21
+ if (value.length > PROJECT_EVAL_MAX_ITEMS)
21
22
  throw new Error(`${label} contains oversized array`);
22
23
  for (const item of value)
23
24
  validateBounds(item, label, depth + 1, state);
24
25
  }
25
26
  else if (value && typeof value === 'object') {
26
27
  const entries = Object.entries(value);
27
- if (entries.length > MAX_ITEMS)
28
+ if (entries.length > PROJECT_EVAL_MAX_ITEMS)
28
29
  throw new Error(`${label} contains oversized object`);
29
30
  for (const [key, item] of entries) {
30
31
  if (key.length > 256)
@@ -64,7 +65,7 @@ function list(v, label) {
64
65
  if (v === undefined)
65
66
  return [];
66
67
  if (!Array.isArray(v) ||
67
- v.length > MAX_ITEMS ||
68
+ v.length > PROJECT_EVAL_MAX_ITEMS ||
68
69
  v.some((x) => typeof x !== 'string' || !x || x.length > MAX_STRING))
69
70
  throw new Error(`${label} must be a bounded string array`);
70
71
  return v.map((x) => String(x));
@@ -216,6 +217,7 @@ export function parseProjectEvalCase(raw, dir) {
216
217
  strict(r, [
217
218
  'schema_version',
218
219
  'name',
220
+ 'risk',
219
221
  'tags',
220
222
  'runs',
221
223
  'fixture',
@@ -224,11 +226,13 @@ export function parseProjectEvalCase(raw, dir) {
224
226
  'graders',
225
227
  'expect',
226
228
  ], 'case');
227
- if (r.schema_version !== '1.0')
229
+ if (r.schema_version !== '1.1')
228
230
  throw new Error(`Unsupported schema_version: ${String(r.schema_version)}`);
229
231
  const name = str(r.name, 'name');
230
232
  if (!NAME.test(name))
231
233
  throw new Error('name must be a safe eval identifier');
234
+ if (!PROJECT_EVAL_RISKS.includes(r.risk))
235
+ throw new Error('risk must be one of: low, medium, high, release');
232
236
  const ex = obj(r.execution, 'execution');
233
237
  strict(ex, [
234
238
  'prompt',
@@ -252,27 +256,39 @@ export function parseProjectEvalCase(raw, dir) {
252
256
  if (!/^EVAL_[A-Z0-9_]+$/u.test(k) || typeof v !== 'string')
253
257
  throw new Error(`Invalid eval environment variable: ${k}`);
254
258
  const vr = r.verification === undefined ? [] : r.verification;
255
- if (!Array.isArray(vr) || vr.length > MAX_ITEMS)
259
+ if (!Array.isArray(vr) || vr.length > PROJECT_EVAL_MAX_ITEMS)
256
260
  throw new Error('verification must be a bounded array');
257
261
  const verification = vr.map((x, i) => {
258
262
  const q = obj(x, `verification[${i}]`);
259
- strict(q, ['name', 'command', 'args', 'timeout_seconds'], `verification[${i}]`);
263
+ strict(q, ['name', 'command', 'args', 'timeout_seconds', 'required', 'expect'], `verification[${i}]`);
264
+ const verifierName = str(q.name, `verification[${i}].name`);
265
+ if (!NAME.test(verifierName))
266
+ throw new Error(`verification[${i}].name must be a safe eval identifier`);
267
+ if (q.required !== true)
268
+ throw new Error(`verification[${i}].required must be true`);
269
+ if (q.expect !== 'pass')
270
+ throw new Error(`verification[${i}].expect must be pass`);
260
271
  return {
261
- name: str(q.name, `verification[${i}].name`),
272
+ name: verifierName,
262
273
  command: str(q.command, `verification[${i}].command`),
263
274
  args: list(q.args, `verification[${i}].args`),
264
275
  timeoutSeconds: integer(q.timeout_seconds, `verification[${i}].timeout_seconds`, 120, 1, 3600),
276
+ required: true,
277
+ expect: 'pass',
265
278
  };
266
279
  });
280
+ if (new Set(verification.map((item) => item.name)).size !== verification.length)
281
+ throw new Error('Project eval verifier names must be unique');
267
282
  const gradersRaw = r.graders === undefined ? [] : r.graders;
268
- if (!Array.isArray(gradersRaw) || gradersRaw.length > MAX_ITEMS)
283
+ if (!Array.isArray(gradersRaw) || gradersRaw.length > PROJECT_EVAL_MAX_ITEMS)
269
284
  throw new Error('graders must be a bounded array');
270
285
  const graders = gradersRaw.map(projectGrader);
271
286
  if (new Set(graders.map((item) => item.name)).size !== graders.length)
272
287
  throw new Error('Project grader names must be unique');
273
288
  return {
274
- schemaVersion: '1.0',
289
+ schemaVersion: '1.1',
275
290
  name,
291
+ risk: r.risk,
276
292
  dir,
277
293
  fixture,
278
294
  tags: list(r.tags, 'tags'),
@@ -1,10 +1,13 @@
1
1
  import type { ModelUsage } from '../core/runtime.js';
2
2
  import type { IdentifiedEvalRuntimeFactory } from './eval-contract.js';
3
- import { type ProjectEvalRunResult } from './project-eval-runner.js';
3
+ import { type ProjectEvalCheckSummary, type ProjectEvalVerificationEvidence, type ProjectEvalRunResult } from './project-eval-runner.js';
4
+ export type { ProjectEvalCheckSummary, ProjectEvalVerifierOutcome, ProjectEvalVerificationEvidence, } from './project-eval-runner.js';
4
5
  import { type ProjectEvalIdentity } from './project-eval-identity.js';
6
+ import type { PraxisBuildIdentity } from '../platform/praxis-build-identity.js';
5
7
  export declare const PROJECT_EVAL_HELP = "Usage: praxis eval [options] <target>\n\nRun deterministic project outcome evaluations in isolated workspaces.\n\nOptions:\n --case <glob> Filter case names\n --tag <tag[,tag]> Filter tags; repeatable\n --runs <1..50> Override run count\n --model <model> Override model\n --allow-tools <rules> Grant gated tools; comma-separated and repeatable\n --run-verification Enable verifier subprocesses\n --output-dir <dir> Write artifacts to this directory\n --keep-temp Preserve temporary workspaces\n --json Print exactly one aggregate JSON value\n --verbose Print run progress to stderr\n -h, --help Display help\n\nUse praxis eval compare --help to compare two completed aggregate artifacts.";
6
8
  export interface ProjectEvalDependencies {
7
9
  runtimeFactory: IdentifiedEvalRuntimeFactory;
10
+ loadBuildIdentity: () => Promise<PraxisBuildIdentity>;
8
11
  version?: string;
9
12
  configRoot: string;
10
13
  }
@@ -30,8 +33,10 @@ export interface ProjectEvalUsageTotals {
30
33
  web_search_requests: number;
31
34
  }
32
35
  export interface ProjectEvalRunSummary {
36
+ schema_version: '1.2';
33
37
  case: string;
34
38
  run: number;
39
+ risk: ProjectEvalRunResult['risk'];
35
40
  model: string;
36
41
  passed: boolean;
37
42
  score: 0 | 1;
@@ -52,9 +57,11 @@ export interface ProjectEvalRunSummary {
52
57
  error: string | null;
53
58
  artifact_dir: string;
54
59
  identity: ProjectEvalIdentity;
60
+ verification: ProjectEvalVerificationEvidence;
61
+ checks: readonly ProjectEvalCheckSummary[];
55
62
  }
56
63
  export interface ProjectEvalAggregate {
57
- schema_version: '1.1';
64
+ schema_version: '1.2';
58
65
  version: string;
59
66
  start: string;
60
67
  duration_ms: number;
@@ -84,6 +91,19 @@ export interface ProjectEvalAggregate {
84
91
  };
85
92
  tool_errors: number;
86
93
  retries: number;
94
+ verification_totals: {
95
+ declared: number;
96
+ passed: number;
97
+ failed: number;
98
+ not_run: number;
99
+ satisfied_runs: number;
100
+ unsatisfied_runs: number;
101
+ };
102
+ risk_tiers: Record<ProjectEvalRunResult['risk'], {
103
+ runs: number;
104
+ passed: number;
105
+ failed: number;
106
+ }>;
87
107
  terminations: {
88
108
  completed: number;
89
109
  timeout: number;
@@ -118,8 +118,10 @@ function aggregateUsage(results) {
118
118
  }
119
119
  function runSummary(result, outputDirectory) {
120
120
  return {
121
+ schema_version: '1.2',
121
122
  case: result.case,
122
123
  run: result.run,
124
+ risk: result.risk,
123
125
  model: result.model,
124
126
  passed: result.passed,
125
127
  score: result.score,
@@ -136,6 +138,8 @@ function runSummary(result, outputDirectory) {
136
138
  error: result.error,
137
139
  artifact_dir: relative(outputDirectory, join(outputDirectory, result.case, `run-${result.run}`)).replaceAll('\\', '/'),
138
140
  identity: result.identity,
141
+ verification: result.verification,
142
+ checks: result.checks.map(({ name, passed }) => ({ name, passed })),
139
143
  };
140
144
  }
141
145
  export async function executeProjectEvalCommand(argv, io, dependencies, callerCwd = process.cwd(), signal) {
@@ -150,6 +154,7 @@ export async function executeProjectEvalCommand(argv, io, dependencies, callerCw
150
154
  throw new Error('eval requires one target');
151
155
  const target = resolve(callerCwd, options.target);
152
156
  const cases = await discoverProjectEvalCases(target, options.caseGlob, options.tags);
157
+ const buildIdentity = await dependencies.loadBuildIdentity();
153
158
  const outputDirectory = options.outputDir
154
159
  ? resolve(callerCwd, options.outputDir)
155
160
  : join(dependencies.configRoot, 'evals', 'results', new Date().toISOString().replaceAll(':', '-'));
@@ -173,6 +178,7 @@ export async function executeProjectEvalCommand(argv, io, dependencies, callerCw
173
178
  runVerification: options.runVerification,
174
179
  outputDir: outputDirectory,
175
180
  version: dependencies.version ?? 'unknown',
181
+ buildIdentity,
176
182
  ...(signal === undefined ? {} : { signal }),
177
183
  });
178
184
  results.push(result);
@@ -187,8 +193,33 @@ export async function executeProjectEvalCommand(argv, io, dependencies, callerCw
187
193
  const passed = results.filter((result) => result.passed).length;
188
194
  const knownCostResults = results.filter((result) => result.cost_known);
189
195
  const summaries = results.map((result) => runSummary(result, outputDirectory));
196
+ const verificationTotals = {
197
+ declared: summaries.reduce((total, summary) => total + summary.verification.outcomes.length, 0),
198
+ passed: summaries.reduce((total, summary) => total +
199
+ summary.verification.outcomes.filter(({ status }) => status === 'passed').length, 0),
200
+ failed: summaries.reduce((total, summary) => total +
201
+ summary.verification.outcomes.filter(({ status }) => status === 'failed').length, 0),
202
+ not_run: summaries.reduce((total, summary) => total +
203
+ summary.verification.outcomes.filter(({ status }) => status === 'not_run').length, 0),
204
+ satisfied_runs: summaries.filter((summary) => summary.verification.satisfied).length,
205
+ unsatisfied_runs: summaries.filter((summary) => !summary.verification.satisfied).length,
206
+ };
207
+ const riskTiers = {
208
+ low: { runs: 0, passed: 0, failed: 0 },
209
+ medium: { runs: 0, passed: 0, failed: 0 },
210
+ high: { runs: 0, passed: 0, failed: 0 },
211
+ release: { runs: 0, passed: 0, failed: 0 },
212
+ };
213
+ for (const summary of summaries) {
214
+ const tier = riskTiers[summary.risk];
215
+ tier.runs += 1;
216
+ if (summary.passed)
217
+ tier.passed += 1;
218
+ else
219
+ tier.failed += 1;
220
+ }
190
221
  const aggregate = {
191
- schema_version: '1.1',
222
+ schema_version: '1.2',
192
223
  version: dependencies.version ?? 'unknown',
193
224
  start: new Date(started).toISOString(),
194
225
  duration_ms: Date.now() - started,
@@ -224,6 +255,8 @@ export async function executeProjectEvalCommand(argv, io, dependencies, callerCw
224
255
  },
225
256
  tool_errors: results.reduce((total, result) => total + result.tool_errors, 0),
226
257
  retries: results.reduce((total, result) => total + result.retries, 0),
258
+ verification_totals: verificationTotals,
259
+ risk_tiers: riskTiers,
227
260
  terminations: {
228
261
  completed: results.filter((result) => result.termination === null).length,
229
262
  timeout: results.filter((result) => result.termination === 'timeout')
@@ -0,0 +1,14 @@
1
+ export declare const PRAXIS_BUILD_IDENTITY_SCHEMA_VERSION: "1.0";
2
+ export interface PraxisBuildIdentity {
3
+ schema_version: typeof PRAXIS_BUILD_IDENTITY_SCHEMA_VERSION;
4
+ source_revision: `git:${string}` | 'unavailable';
5
+ source_dirty: boolean | null;
6
+ artifact_sha256: `sha256:${string}`;
7
+ }
8
+ export declare function writePraxisBuildIdentity({ sourceRoot, outputRoot, }: {
9
+ sourceRoot: string;
10
+ outputRoot: string;
11
+ }): Promise<PraxisBuildIdentity>;
12
+ export declare function validatePraxisBuildIdentity(value: unknown): PraxisBuildIdentity;
13
+ export declare function loadPraxisBuildIdentity(outputRoot?: string): Promise<PraxisBuildIdentity>;
14
+ //# sourceMappingURL=praxis-build-identity.d.ts.map