statelyai 0.14.3 → 0.15.1

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.
@@ -1,20 +1,21 @@
1
- import { Args, Command, Flags, flush, handle, run } from "@oclif/core";
2
- import fsPromises from "node:fs/promises";
1
+ import fsPromises, { mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
3
2
  import * as path$1 from "node:path";
4
- import path from "node:path";
5
- import { changeSet, createFileStore, createLoop, parseModelGraph, proposalOutline, verdictLines, verify } from "@statelyai/mcp/loop";
6
- import { z, z as z$1 } from "zod";
3
+ import path, { dirname, resolve } from "node:path";
4
+ import * as crypto from "node:crypto";
5
+ import { createHash, randomUUID } from "node:crypto";
7
6
  import { execFile, execFileSync, spawn } from "node:child_process";
8
7
  import { promisify } from "node:util";
9
- import * as crypto from "node:crypto";
10
- import { randomUUID } from "node:crypto";
8
+ import { fileURLToPath, pathToFileURL } from "node:url";
9
+ import { z, z as z$1 } from "zod";
10
+ import { Args, Command, Flags, flush, handle, run } from "@oclif/core";
11
+ import { changeSet, createFileStore, createLoop, editorGraphRevision, graphPathGenerationId, graphPathNextCursor, graphPathsPageSchema, parseModelGraph, proposalOutline, stableJson, verdictLines, verify } from "@statelyai/mcp/loop";
12
+ import { campaignExitCode, modelTestReplaySchema, reportReplays, runCampaignFile } from "@statelyai/model-testing";
11
13
  import { StudioApiError, buildAuthorizeUrl, createPkcePair, createStatelyClient, discoverAuthorizationServer, discoverProtectedResource, exchangeCodeForToken, refreshToken, registerOAuthClient } from "@statelyai/sdk";
12
14
  import * as ts$1 from "typescript";
13
15
  import ts from "typescript";
14
16
  import fs, { promises, statSync, watch } from "node:fs";
15
17
  import { createInterface } from "node:readline/promises";
16
18
  import { Writable } from "node:stream";
17
- import { fileURLToPath, pathToFileURL } from "node:url";
18
19
  import * as http from "node:http";
19
20
  import * as fs$1 from "fs";
20
21
  import * as path$2 from "path";
@@ -28,6 +29,1327 @@ import os from "node:os";
28
29
  //#region \0rolldown/runtime.js
29
30
  var __commonJSMin$1 = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
30
31
 
32
+ //#endregion
33
+ //#region src/version.ts
34
+ const CLI_VERSION = "0.15.1";
35
+
36
+ //#endregion
37
+ //#region src/behaviorCi.ts
38
+ const exec = promisify(execFile);
39
+ const digest = z.string().regex(/^[a-f0-9]{64}$/);
40
+ const relative = z.string().min(1).max(1e3).refine((s) => !path.isAbsolute(s) && !s.includes("\\") && s.split("/").every((p) => p !== ".." && p !== ""), "Expected repository-relative path");
41
+ const reference = z.strictObject({
42
+ machineId: z.string().regex(/^[a-z0-9][a-z0-9-]{0,79}$/),
43
+ versionId: z.string().regex(/^v\d{4,9}$/),
44
+ sha256: digest
45
+ });
46
+ const id = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$/);
47
+ const text = z.string().trim().min(1).max(4e3);
48
+ const configFields = {
49
+ name: text,
50
+ spec: relative,
51
+ model: relative,
52
+ adapter: relative,
53
+ stateRoot: relative,
54
+ baseline: reference.nullable(),
55
+ command: z.strictObject({
56
+ executable: z.string().min(1),
57
+ args: z.array(z.string()).max(100),
58
+ cwd: relative
59
+ }),
60
+ inputs: z.array(relative).min(1).max(100),
61
+ checks: z.array(z.strictObject({
62
+ id,
63
+ file: relative,
64
+ fullName: text
65
+ })).min(1).max(500),
66
+ timeoutMs: z.number().int().min(100).max(6e5)
67
+ };
68
+ const behaviorCiConfigSchema = z.discriminatedUnion("schemaVersion", [z.strictObject({
69
+ schemaVersion: z.literal(1),
70
+ ...configFields
71
+ }), z.strictObject({
72
+ schemaVersion: z.literal(2),
73
+ ...configFields,
74
+ assurance: z.strictObject({
75
+ plan: relative,
76
+ calibration: relative
77
+ })
78
+ })]);
79
+ const specSchema = z.strictObject({
80
+ requirements: z.array(z.strictObject({
81
+ id,
82
+ text,
83
+ source: z.strictObject({
84
+ authority: z.enum([
85
+ "cited-requirement",
86
+ "observation",
87
+ "inference",
88
+ "assumption"
89
+ ]),
90
+ citation: text
91
+ }),
92
+ entityIds: z.array(text).max(100).default([]),
93
+ checks: z.array(z.strictObject({
94
+ id,
95
+ acceptance: text
96
+ })).max(100)
97
+ })).max(100),
98
+ retire: z.array(z.strictObject({
99
+ kind: z.enum(["requirement", "check"]),
100
+ id,
101
+ reason: text
102
+ })).max(500).default([])
103
+ });
104
+ const behaviorCiResultsSchema = z.array(z.strictObject({
105
+ file: relative,
106
+ fullName: text,
107
+ outcome: z.enum([
108
+ "passed",
109
+ "failed",
110
+ "skipped",
111
+ "error"
112
+ ]),
113
+ detail: text
114
+ })).max(1e4);
115
+ const fileHash = z.strictObject({
116
+ path: relative,
117
+ sha256: digest
118
+ });
119
+ const manifestSchema = z.strictObject({
120
+ revision: z.string().regex(/^[a-f0-9]{40,64}$/),
121
+ dirty: z.boolean(),
122
+ files: z.array(fileHash).min(1).max(200),
123
+ runner: z.strictObject({
124
+ executable: z.string(),
125
+ version: z.string(),
126
+ node: z.string(),
127
+ cli: z.strictObject({
128
+ version: z.string(),
129
+ moduleSha256: digest
130
+ }),
131
+ platform: z.string(),
132
+ arch: z.string()
133
+ }),
134
+ declaredGithub: z.strictObject({
135
+ runId: z.string().nullable(),
136
+ attempt: z.string().nullable(),
137
+ repository: z.string().nullable(),
138
+ sha: z.string().nullable()
139
+ })
140
+ });
141
+ const processSchema = z.strictObject({
142
+ executable: z.string(),
143
+ args: z.array(z.string()),
144
+ cwd: z.string(),
145
+ exitCode: z.number().nullable(),
146
+ signal: z.string().nullable(),
147
+ timedOut: z.boolean()
148
+ });
149
+ const runSchema = z.strictObject({
150
+ schemaVersion: z.literal(1),
151
+ runId: id,
152
+ status: z.enum([
153
+ "PASS",
154
+ "FAIL",
155
+ "ERROR"
156
+ ]),
157
+ errors: z.array(z.string()),
158
+ configPath: relative,
159
+ root: z.string().min(1).optional(),
160
+ config: behaviorCiConfigSchema,
161
+ manifest: manifestSchema,
162
+ parent: reference.nullable(),
163
+ next: reference,
164
+ prepared: reference,
165
+ model: reference,
166
+ process: processSchema,
167
+ current: z.array(z.strictObject({
168
+ checkId: id,
169
+ outcome: z.enum([
170
+ "passed",
171
+ "failed",
172
+ "skipped",
173
+ "error"
174
+ ]),
175
+ detail: text
176
+ })),
177
+ missing: z.array(id),
178
+ changes: z.array(z.string()),
179
+ artifacts: z.array(fileHash).max(1e3),
180
+ assurance: z.enum([
181
+ "unavailable",
182
+ "calibration",
183
+ "calibrated"
184
+ ]).default("unavailable"),
185
+ assuranceGaps: z.array(text).default([]),
186
+ coverage: z.strictObject({
187
+ kind: z.enum(["selected-checks", "bounded-paths"]),
188
+ generatedPaths: z.number().int().nonnegative(),
189
+ nativeCases: z.number().int().nonnegative(),
190
+ generatorCompleteness: z.enum(["not-requested", "reported"])
191
+ }).default({
192
+ kind: "selected-checks",
193
+ generatedPaths: 0,
194
+ nativeCases: 0,
195
+ generatorCompleteness: "not-requested"
196
+ })
197
+ });
198
+ const hash = (data) => createHash("sha256").update(data).digest("hex");
199
+ const equal = (a, b) => stableJson(a) === stableJson(b);
200
+ const readJson = async (file) => {
201
+ const stat = await fsPromises.stat(file);
202
+ if (!stat.isFile() || stat.size > 16 * 1024 * 1024) throw new Error(`Invalid or oversized JSON file: ${file}`);
203
+ return JSON.parse(await fsPromises.readFile(file, "utf8"));
204
+ };
205
+ async function writeArtifact(file, value) {
206
+ const parent = path.dirname(file);
207
+ if ((await fsPromises.lstat(parent)).isSymbolicLink()) throw new Error("Artifact parent is a symlink");
208
+ const tmp = `${file}.${randomUUID()}.tmp`;
209
+ await fsPromises.writeFile(tmp, value, { flag: "wx" });
210
+ await fsPromises.rename(tmp, file);
211
+ }
212
+ const writeJson = (file, value) => writeArtifact(file, stableJson(value));
213
+ function unique(values, label) {
214
+ if (new Set(values).size !== values.length) throw new Error(`Duplicate ${label}`);
215
+ }
216
+ function overlaps$2(first, second) {
217
+ const a = path.posix.normalize(first);
218
+ const b = path.posix.normalize(second);
219
+ return a === "." || b === "." || a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`);
220
+ }
221
+ function inside(root, file) {
222
+ const rel = path.relative(root, file);
223
+ if (rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) throw new Error(`Outside root: ${file}`);
224
+ return rel.split(path.sep).join("/");
225
+ }
226
+ async function safe(root, rel, missing = false) {
227
+ relative.parse(rel);
228
+ try {
229
+ if ((await fsPromises.lstat(root)).isSymbolicLink()) throw new Error(`Symlink forbidden: ${root}`);
230
+ } catch (e) {
231
+ if (!(missing && e instanceof Error && "code" in e && e.code === "ENOENT")) throw e;
232
+ }
233
+ const target = path.resolve(root, rel);
234
+ inside(root, target);
235
+ let current = root;
236
+ for (const part of path.relative(root, target).split(path.sep).filter(Boolean)) {
237
+ current = path.join(current, part);
238
+ try {
239
+ if ((await fsPromises.lstat(current)).isSymbolicLink()) throw new Error(`Symlink forbidden: ${current}`);
240
+ } catch (e) {
241
+ if (missing && e instanceof Error && "code" in e && e.code === "ENOENT") continue;
242
+ throw e;
243
+ }
244
+ }
245
+ return target;
246
+ }
247
+ async function filesUnder(root, rel) {
248
+ const file = await safe(root, rel);
249
+ const stat = await fsPromises.stat(file);
250
+ if (stat.isFile()) {
251
+ if (stat.size > 16 * 1024 * 1024) throw new Error(`File too large: ${rel}`);
252
+ return [rel];
253
+ }
254
+ if (!stat.isDirectory()) throw new Error(`Not a regular file/directory: ${rel}`);
255
+ const entries = await fsPromises.readdir(file);
256
+ const result = [];
257
+ for (const entry of entries.sort()) {
258
+ if (entry === ".git" || entry === "node_modules") throw new Error(`Select bounded inputs, excluding ${entry}`);
259
+ result.push(...await filesUnder(root, `${rel}/${entry}`));
260
+ if (result.length > 1e3) throw new Error("Too many files");
261
+ }
262
+ return result;
263
+ }
264
+ async function traceArtifacts(root) {
265
+ try {
266
+ await fsPromises.lstat(path.join(root, "traces"));
267
+ } catch (error) {
268
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return [];
269
+ throw error;
270
+ }
271
+ return filesUnder(root, "traces");
272
+ }
273
+ async function hashes(root, files) {
274
+ return Promise.all([...new Set(files)].sort().map(async (p) => ({
275
+ path: p,
276
+ sha256: hash(await fsPromises.readFile(await safe(root, p)))
277
+ })));
278
+ }
279
+ async function git(root, ...args) {
280
+ return (await exec("git", [
281
+ "-C",
282
+ root,
283
+ ...args
284
+ ], { maxBuffer: 8 * 1024 * 1024 })).stdout.trim();
285
+ }
286
+ async function machineFiles(root, machineId) {
287
+ const prefix = `.statelyai/machines/${machineId}`;
288
+ const versions = await filesUnder(root, `${prefix}/versions`);
289
+ for (const file of versions) if (!new RegExp(`^${prefix.replaceAll(".", "\\.")}\/versions\/v[0-9]{4,9}\\.json$`).test(file)) throw new Error(`Unexpected store file: ${file}`);
290
+ return [`${prefix}.json`, ...versions];
291
+ }
292
+ async function copyFiles(from, to, files, immutable = false) {
293
+ for (const rel of files) {
294
+ const source = await safe(from, rel);
295
+ const dest = await safe(to, rel, true);
296
+ const bytes = await fsPromises.readFile(source);
297
+ await fsPromises.mkdir(path.dirname(dest), { recursive: true });
298
+ try {
299
+ await fsPromises.writeFile(dest, bytes, { flag: "wx" });
300
+ } catch (e) {
301
+ if (!(e instanceof Error && "code" in e && e.code === "EEXIST")) throw e;
302
+ if (!immutable || !bytes.equals(await fsPromises.readFile(dest))) throw new Error(`Conflicting immutable file: ${rel}`);
303
+ }
304
+ }
305
+ }
306
+ async function loadConfig(configFile) {
307
+ const configAbsolute = path.resolve(configFile);
308
+ const root = await git(path.dirname(configAbsolute), "rev-parse", "--show-toplevel");
309
+ const configPath = inside(root, configAbsolute);
310
+ const config = behaviorCiConfigSchema.parse(await readJson(await safe(root, configPath)));
311
+ unique(config.checks.map((c) => c.id), "check IDs");
312
+ unique(config.checks.map((c) => `${c.file}\0${c.fullName}`), "test mappings");
313
+ if (config.command.args.reduce((n, a) => n + a.split("{report}").length - 1, 0) !== 1) throw new Error("Command args require exactly one {report} placeholder");
314
+ if (!config.adapter.endsWith(".mjs")) throw new Error("Adapter must be a self-contained .mjs module");
315
+ if (config.schemaVersion === 2) {
316
+ if ([
317
+ configPath,
318
+ config.spec,
319
+ config.model,
320
+ config.adapter,
321
+ config.stateRoot,
322
+ ...config.inputs,
323
+ ...config.checks.map((c) => c.file),
324
+ ...await assuranceInputs(root, config),
325
+ ...(await selectedInputs(root, configPath, config)).map((file) => file.path)
326
+ ].some((input) => overlaps$2(config.assurance.calibration, input))) throw new Error("Calibration receipt must be isolated from every bound input and checked state");
327
+ }
328
+ if (config.stateRoot === "." || config.inputs.includes(".")) throw new Error("Select bounded inputs and a dedicated stateRoot");
329
+ return {
330
+ root,
331
+ configPath,
332
+ config
333
+ };
334
+ }
335
+ async function selectedInputs(root, configPath, config) {
336
+ const selected = [
337
+ configPath,
338
+ config.spec,
339
+ config.model,
340
+ config.adapter,
341
+ ...config.checks.map((c) => c.file),
342
+ ...config.schemaVersion === 2 ? await assuranceInputs(root, config) : []
343
+ ];
344
+ for (const input of config.inputs) selected.push(...await filesUnder(root, input));
345
+ for (const dir of new Set([".", config.command.cwd])) for (const name of [
346
+ "package.json",
347
+ "pnpm-lock.yaml",
348
+ "package-lock.json",
349
+ "yarn.lock"
350
+ ]) {
351
+ const rel = dir === "." ? name : `${dir}/${name}`;
352
+ try {
353
+ await fsPromises.access(path.join(root, rel));
354
+ selected.push(rel);
355
+ } catch {}
356
+ }
357
+ if (config.baseline) {
358
+ const state = await safe(root, config.stateRoot);
359
+ selected.push(...(await machineFiles(state, config.baseline.machineId)).map((p) => `${config.stateRoot}/${p}`));
360
+ }
361
+ return hashes(root, selected);
362
+ }
363
+ async function capture(root, configPath, config) {
364
+ const files = await selectedInputs(root, configPath, config);
365
+ if (files.length > 200) throw new Error("Input scope exceeds core limit of 200 files");
366
+ const tracked = new Set((await git(root, "ls-files", "-z")).split("\0"));
367
+ for (const file of files) if (!tracked.has(file.path)) throw new Error(`Input must be tracked (git add first): ${file.path}`);
368
+ const revision = await git(root, "rev-parse", "HEAD");
369
+ const dirty = !!await git(root, "status", "--porcelain", "--", ...files.map((f) => f.path));
370
+ if (process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true") {
371
+ if (!process.env.GITHUB_SHA || process.env.GITHUB_SHA !== revision) throw new Error("CI requires GITHUB_SHA matching actual HEAD");
372
+ if (dirty) throw new Error("CI bound inputs must be clean");
373
+ }
374
+ const cwd = await safe(root, config.command.cwd);
375
+ const version = (await exec(config.command.executable, ["--version"], {
376
+ cwd,
377
+ timeout: 1e4,
378
+ maxBuffer: 64 * 1024
379
+ })).stdout.trim();
380
+ return {
381
+ revision,
382
+ dirty,
383
+ files,
384
+ runner: {
385
+ executable: config.command.executable,
386
+ version,
387
+ node: process.version,
388
+ cli: {
389
+ version: CLI_VERSION,
390
+ moduleSha256: hash(await fsPromises.readFile(fileURLToPath(import.meta.url)))
391
+ },
392
+ platform: process.platform,
393
+ arch: process.arch
394
+ },
395
+ declaredGithub: {
396
+ runId: process.env.GITHUB_RUN_ID ?? null,
397
+ attempt: process.env.GITHUB_RUN_ATTEMPT ?? null,
398
+ repository: process.env.GITHUB_REPOSITORY ?? null,
399
+ sha: process.env.GITHUB_SHA ?? null
400
+ }
401
+ };
402
+ }
403
+ async function execute(config, root, output) {
404
+ const cwd = await safe(root, config.command.cwd);
405
+ const args = config.command.args.map((a) => a.replace("{report}", path.join(output, "report.json")));
406
+ const stdout = await fsPromises.open(path.join(output, "stdout.log"), "wx");
407
+ const stderr = await fsPromises.open(path.join(output, "stderr.log"), "wx");
408
+ try {
409
+ return await new Promise((resolve, reject) => {
410
+ const child = spawn(config.command.executable, args, {
411
+ cwd,
412
+ shell: false,
413
+ env: {
414
+ ...process.env,
415
+ STATELY_BEHAVIOR_OUTPUT: output
416
+ },
417
+ detached: process.platform !== "win32",
418
+ stdio: [
419
+ "ignore",
420
+ stdout.fd,
421
+ stderr.fd
422
+ ]
423
+ });
424
+ let timedOut = false;
425
+ const stop = () => {
426
+ if (child.pid) try {
427
+ if (process.platform !== "win32") process.kill(-child.pid, "SIGKILL");
428
+ else child.kill("SIGKILL");
429
+ } catch {}
430
+ };
431
+ const timer = setTimeout(() => {
432
+ timedOut = true;
433
+ stop();
434
+ }, config.timeoutMs);
435
+ child.once("error", (e) => {
436
+ clearTimeout(timer);
437
+ reject(e);
438
+ });
439
+ child.once("close", (exitCode, signal) => {
440
+ clearTimeout(timer);
441
+ stop();
442
+ resolve({
443
+ executable: config.command.executable,
444
+ args,
445
+ cwd,
446
+ exitCode,
447
+ signal,
448
+ timedOut
449
+ });
450
+ });
451
+ });
452
+ } finally {
453
+ await stdout.close();
454
+ await stderr.close();
455
+ }
456
+ }
457
+ async function exactRecord(loop, ref) {
458
+ const view = await loop.getBehaviorRecord({
459
+ machineId: ref.machineId,
460
+ versionId: ref.versionId
461
+ });
462
+ if (view.kind !== "behavior-record" || !equal(view.reference, ref)) throw new Error("Exact record digest mismatch");
463
+ return view;
464
+ }
465
+ function active(record) {
466
+ return record.requirements.filter((r) => !r.retired).flatMap((r) => r.checks.filter((c) => !c.retired));
467
+ }
468
+ const base = (ref) => ({
469
+ versionId: ref.versionId,
470
+ recordSha256: ref.sha256
471
+ });
472
+ function summary(run, view) {
473
+ return [
474
+ `# ${run.status}: ${run.config.name}`,
475
+ `Git ${run.manifest.revision}; bound inputs ${run.manifest.dirty ? "dirty local bytes" : "clean"}. Run ${run.runId}.`,
476
+ "Execution is reported, not attested. Review identity unavailable. GitHub environment is declared metadata; authenticate remote run/artifact evidence separately.",
477
+ `Model ${stableJson(run.model).trim()}`,
478
+ `Specification ${view.record.specificationSha256}`,
479
+ ...run.changes,
480
+ `Exact parent ${stableJson(run.parent).trim()}`,
481
+ `Exact next ${stableJson(run.next).trim()}`,
482
+ `Current failures/gaps: ${[
483
+ ...run.errors,
484
+ ...run.missing.map((c) => `Missing check ${c}`),
485
+ ...run.current.filter((c) => c.outcome !== "passed").map((c) => `${c.checkId}: ${c.outcome}: ${c.detail}`)
486
+ ].join("; ") || "none"}`,
487
+ `Historical/core aggregate gaps (not this run verdict): ${view.gaps.map((g) => g.checkId).join(", ") || "none"}`,
488
+ `Setup assurance: ${run.assurance}. Discovery gaps: ${run.assuranceGaps.join("; ") || "none declared"}.`,
489
+ `Coverage: ${run.coverage.kind}; archived generated paths ${run.coverage.generatedPaths}; native passed/failed cases ${run.coverage.nativeCases}; generator completeness ${run.coverage.generatorCompleteness}.`,
490
+ ...run.coverage.kind === "bounded-paths" ? ["Complete retrieval of the declared bounded generator result covers only the modeled scope, not every feature input. Generator completeness is reported, not independently recomputed or attested. Archive checksums cannot authenticate a malicious trusted producer."] : [],
491
+ "Input scope is selected, not a complete transitive dependency proof. Generated paths and executed native cases are separate evidence.",
492
+ view.display
493
+ ].join("\n\n") + "\n";
494
+ }
495
+ const inputCategories = [
496
+ "user",
497
+ "host-update",
498
+ "async-result",
499
+ "cancellation",
500
+ "timer",
501
+ "server-event",
502
+ "lifecycle"
503
+ ];
504
+ const discoverySource = z.strictObject({
505
+ authority: z.enum([
506
+ "cited-requirement",
507
+ "observation",
508
+ "inference",
509
+ "assumption"
510
+ ]),
511
+ citation: text
512
+ });
513
+ const planInput = z.discriminatedUnion("kind", [
514
+ z.strictObject({
515
+ kind: z.literal("mapped"),
516
+ category: z.enum(inputCategories),
517
+ source: discoverySource,
518
+ requirementIds: z.array(id).min(1),
519
+ checkIds: z.array(id).min(1),
520
+ entityIds: z.array(text).min(1)
521
+ }),
522
+ z.strictObject({
523
+ kind: z.literal("gap"),
524
+ category: z.enum(inputCategories),
525
+ source: discoverySource,
526
+ reason: text
527
+ }),
528
+ z.strictObject({
529
+ kind: z.literal("not-applicable"),
530
+ category: z.enum(inputCategories),
531
+ source: discoverySource,
532
+ reason: text
533
+ })
534
+ ]);
535
+ const planSchema = z.strictObject({
536
+ schemaVersion: z.literal(1),
537
+ inputs: z.array(planInput).min(7).max(100),
538
+ runnerInputs: z.array(relative).min(1).max(100),
539
+ authorityInputs: z.array(relative).min(1).max(100),
540
+ dispositions: z.array(z.strictObject({
541
+ checkId: id,
542
+ kind: z.literal("implementation-defect"),
543
+ reason: text,
544
+ source: z.strictObject({
545
+ authority: z.literal("cited-requirement"),
546
+ citation: text
547
+ })
548
+ })).max(500).default([]),
549
+ coverage: z.discriminatedUnion("kind", [z.strictObject({ kind: z.literal("selected-checks") }), z.strictObject({
550
+ kind: z.literal("bounded-paths"),
551
+ artifact: relative,
552
+ caseMappings: z.array(z.strictObject({
553
+ pathId: text,
554
+ checkIds: z.array(id).min(1)
555
+ })).min(1).max(1e3)
556
+ })])
557
+ });
558
+ const calibrationReceiptSchema = z.strictObject({
559
+ schemaVersion: z.literal(1),
560
+ kind: z.literal("native-calibration"),
561
+ contractSha256: digest,
562
+ config: behaviorCiConfigSchema,
563
+ manifest: manifestSchema,
564
+ process: processSchema,
565
+ root: z.string().min(1),
566
+ report: z.unknown(),
567
+ results: behaviorCiResultsSchema,
568
+ dispositions: planSchema.shape.dispositions,
569
+ snapshots: z.array(z.strictObject({
570
+ path: relative,
571
+ base64: z.string()
572
+ })).max(200)
573
+ });
574
+ const calibrationEnvelopeSchema = z.strictObject({
575
+ receipt: calibrationReceiptSchema,
576
+ sha256: digest
577
+ });
578
+ const sealCalibration = (receipt) => ({
579
+ receipt,
580
+ sha256: hash(stableJson(receipt))
581
+ });
582
+ async function assuranceInputs(root, config) {
583
+ if (config.schemaVersion !== 2) return [];
584
+ const plan = planSchema.parse(await readJson(await safe(root, config.assurance.plan)));
585
+ return [
586
+ config.assurance.plan,
587
+ ...plan.runnerInputs,
588
+ ...plan.authorityInputs,
589
+ ...plan.coverage.kind === "bounded-paths" ? [plan.coverage.artifact] : []
590
+ ];
591
+ }
592
+ async function loadAssurancePlan(root, config, spec, graph) {
593
+ if (config.schemaVersion !== 2) throw new Error("Setup assurance requires schema 2");
594
+ const plan = planSchema.parse(await readJson(await safe(root, config.assurance.plan)));
595
+ unique(plan.runnerInputs, "runner inputs");
596
+ unique(plan.authorityInputs, "authority inputs");
597
+ unique(plan.dispositions.map((d) => d.checkId), "calibration dispositions");
598
+ unique(plan.inputs.map((i) => stableJson(i)), "discovery entries");
599
+ for (const category of inputCategories) if (!plan.inputs.some((i) => i.category === category)) throw new Error(`Discovery inventory missing input category: ${category}`);
600
+ const requirements = new Map(spec.requirements.map((r) => [r.id, r]));
601
+ const checkIds = new Set(spec.requirements.flatMap((r) => r.checks.map((c) => c.id)));
602
+ const selected = new Set(config.checks.map((c) => c.id));
603
+ const entities = new Set([...graph.nodes.map((n) => n.id), ...graph.edges.map((e) => e.id)]);
604
+ for (const entry of plan.inputs) if (entry.kind === "mapped") {
605
+ unique(entry.requirementIds, "discovery requirement references");
606
+ unique(entry.checkIds, "discovery check references");
607
+ unique(entry.entityIds, "discovery entity references");
608
+ if (entry.requirementIds.some((r) => !requirements.has(r)) || entry.checkIds.some((c) => !checkIds.has(c) || !selected.has(c)) || entry.entityIds.some((e) => !entities.has(e))) throw new Error(`Unknown discovery reference: ${entry.category}`);
609
+ const related = new Set(entry.requirementIds.flatMap((r) => requirements.get(r)?.checks.map((c) => c.id) ?? []));
610
+ if (entry.checkIds.some((c) => !related.has(c))) throw new Error(`Discovery checks do not belong to referenced requirements: ${entry.category}`);
611
+ }
612
+ for (const disposition of plan.dispositions) if (!selected.has(disposition.checkId)) throw new Error(`Unknown calibration disposition: ${disposition.checkId}`);
613
+ if (plan.coverage.kind === "bounded-paths") await validatePathCoverage(root, config, plan.coverage, graph);
614
+ return plan;
615
+ }
616
+ const runnerContract = ({ platform: _platform, arch: _arch, ...runner }) => runner;
617
+ async function contractDigest(root, config, plan, manifest) {
618
+ return hash(stableJson({
619
+ files: await hashes(root, [
620
+ config.spec,
621
+ config.model,
622
+ config.adapter,
623
+ ...config.schemaVersion === 2 ? [config.assurance.plan] : [],
624
+ ...config.checks.map((c) => c.file),
625
+ ...plan.runnerInputs,
626
+ ...plan.authorityInputs,
627
+ ...plan.coverage.kind === "bounded-paths" ? [plan.coverage.artifact] : []
628
+ ]),
629
+ command: config.command,
630
+ checks: config.checks,
631
+ timeoutMs: config.timeoutMs,
632
+ runner: runnerContract(manifest.runner)
633
+ }));
634
+ }
635
+ async function normalizeReport(root, config, report, originalRoot = root) {
636
+ const module = await import(`${pathToFileURL(await safe(root, config.adapter)).href}?validation=${randomUUID()}`);
637
+ if (!module || typeof module !== "object" || !("default" in module) || typeof module.default !== "function") throw new Error("Adapter must export a default function");
638
+ return behaviorCiResultsSchema.parse(await module.default(report, {
639
+ root: originalRoot,
640
+ cwd: (path.win32.isAbsolute(originalRoot) && !path.posix.isAbsolute(originalRoot) ? path.win32 : path.posix).resolve(originalRoot, config.command.cwd)
641
+ }));
642
+ }
643
+ async function validateCalibration(root, config, contract, supplied) {
644
+ if (config.schemaVersion !== 2) throw new Error("Calibration requires config schemaVersion 2");
645
+ const envelope = calibrationEnvelopeSchema.parse(supplied ?? await readJson(await safe(root, config.assurance.calibration)));
646
+ const { receipt } = envelope;
647
+ if (envelope.sha256 !== hash(stableJson(receipt))) throw new Error("Calibration receipt checksum mismatch");
648
+ if (!contract || receipt.contractSha256 !== contract) throw new Error("Calibration stale: model, specification, discovery plan, mapping, adapter or runner changed; recalibrate explicitly");
649
+ unique(receipt.snapshots.map((s) => s.path), "calibration snapshots");
650
+ if (!equal(receipt.manifest.files.map((f) => f.path).sort(), receipt.snapshots.map((s) => s.path).sort())) throw new Error("Calibration snapshots do not match original manifest");
651
+ for (const file of receipt.manifest.files) {
652
+ const snapshot = receipt.snapshots.find((s) => s.path === file.path);
653
+ if (!snapshot || hash(Buffer.from(snapshot.base64, "base64")) !== file.sha256) throw new Error(`Calibration original input checksum mismatch: ${file.path}`);
654
+ }
655
+ if (!equal({
656
+ ...receipt.config,
657
+ baseline: null
658
+ }, {
659
+ ...config,
660
+ baseline: null
661
+ })) throw new Error("Calibration config differs from selected contract");
662
+ const plan = planSchema.parse(await readJson(await safe(root, config.assurance.plan)));
663
+ if (!equal(receipt.dispositions, plan.dispositions)) throw new Error("Calibration dispositions differ from reviewed discovery plan");
664
+ const contractPaths = new Set([
665
+ config.spec,
666
+ config.model,
667
+ config.adapter,
668
+ config.assurance.plan,
669
+ ...config.checks.map((c) => c.file),
670
+ ...plan.runnerInputs,
671
+ ...plan.authorityInputs,
672
+ ...plan.coverage.kind === "bounded-paths" ? [plan.coverage.artifact] : []
673
+ ]);
674
+ const originalFiles = receipt.manifest.files.filter((f) => contractPaths.has(f.path));
675
+ if (!equal([...contractPaths].sort(), originalFiles.map((f) => f.path).sort()) || receipt.contractSha256 !== hash(stableJson({
676
+ files: originalFiles,
677
+ command: receipt.config.command,
678
+ checks: receipt.config.checks,
679
+ timeoutMs: receipt.config.timeoutMs,
680
+ runner: runnerContract(receipt.manifest.runner)
681
+ }))) throw new Error("Calibration original contract binding mismatch");
682
+ if (path.posix.join(receipt.root.replaceAll("\\", "/"), receipt.config.command.cwd) !== receipt.process.cwd.replaceAll("\\", "/")) throw new Error("Calibration original checkout/cwd binding mismatch");
683
+ if (receipt.process.executable !== receipt.config.command.executable || receipt.process.args.length !== receipt.config.command.args.length || receipt.config.command.args.some((arg, i) => {
684
+ const actual = receipt.process.args[i];
685
+ if (!arg.includes("{report}")) return actual !== arg;
686
+ const [prefix, suffix] = arg.split("{report}");
687
+ return !actual?.startsWith(prefix) || !actual.endsWith(suffix ?? "") || !actual.includes("report.json");
688
+ })) throw new Error("Calibration process differs from bound native command");
689
+ const before = await hashes(root, [...contractPaths]);
690
+ const normalized = await normalizeReport(root, config, receipt.report, receipt.root);
691
+ if (!equal(before, await hashes(root, [...contractPaths]))) throw new Error("Calibration adapter changed bound contract during validation");
692
+ if (!equal(normalized, receipt.results)) throw new Error("Calibration native report differs from normalized results");
693
+ unique(normalized.map((r) => `${r.file}\0${r.fullName}`), "calibration tests");
694
+ if (!equal(normalized.map((r) => `${r.file}\0${r.fullName}`).sort(), config.checks.map((r) => `${r.file}\0${r.fullName}`).sort())) throw new Error("Calibration native inventory differs from selected mappings");
695
+ const failures = normalized.filter((r) => r.outcome === "failed");
696
+ if (receipt.process.timedOut || receipt.process.signal || receipt.process.exitCode !== (failures.length ? 1 : 0) || normalized.some((r) => !["passed", "failed"].includes(r.outcome))) throw new Error("Calibration native process/report is invalid or incomplete");
697
+ for (const failed of failures) {
698
+ const check = config.checks.find((c) => c.file === failed.file && c.fullName === failed.fullName);
699
+ if (!check || !receipt.dispositions.some((d) => d.checkId === check.id)) throw new Error(`Unresolved calibration mismatch: ${check?.id ?? failed.fullName}; resolve requirements or cite an implementation-defect disposition`);
700
+ }
701
+ return envelope;
702
+ }
703
+ async function validatePathCoverage(root, config, coverage, graph) {
704
+ const archive = z.strictObject({
705
+ schemaVersion: z.literal(1),
706
+ source: z.strictObject({
707
+ graph: z.unknown(),
708
+ kind: z.enum(["shortest", "simple"])
709
+ }),
710
+ pages: z.array(graphPathsPageSchema).min(1).max(1e3)
711
+ }).parse(await readJson(await safe(root, coverage.artifact)));
712
+ const source = parseModelGraph(archive.source.graph);
713
+ if (editorGraphRevision(source) !== editorGraphRevision(graph)) throw new Error("Generated path archive belongs to a different model");
714
+ const first = archive.pages[0];
715
+ const last = archive.pages.at(-1);
716
+ if (!first || !last) throw new Error("Missing generated path pages");
717
+ let offset = 0;
718
+ for (let i = 0; i < archive.pages.length; i++) {
719
+ const page = archive.pages[i];
720
+ if (!page) throw new Error("Missing generated path page");
721
+ const p = page.pagination;
722
+ if (p.offset !== offset || p.modelRevision !== editorGraphRevision(graph) || p.generationId !== first.pagination.generationId || p.budget !== first.pagination.budget || p.availablePaths !== first.pagination.availablePaths || p.generationComplete !== first.pagination.generationComplete || page.totalPaths !== first.totalPaths || !equal(page.coverage, first.coverage) || !equal(page.execution, first.execution) || page.coverageScope !== "generation-budget" || page.paths.length > p.appliedLimit || p.availablePaths > p.budget || p.generationComplete !== (p.availablePaths === page.totalPaths) || p.status !== (!p.retrievalComplete ? "more-pages" : p.generationComplete ? "complete" : "budget-exhausted")) throw new Error("Generated path pages are not one contiguous model-bound generation");
723
+ offset += page.paths.length;
724
+ const nextPage = archive.pages[i + 1];
725
+ if (nextPage && !p.nextCursor) throw new Error("Generated path cursor chain ends before next page");
726
+ if (p.retrievalComplete !== (offset === p.availablePaths) || page.truncated !== page.paths.length < page.totalPaths) throw new Error("Generated path completion metadata disagrees with archived paths");
727
+ const expectedCursor = offset < p.availablePaths ? graphPathNextCursor({
728
+ generationId: p.generationId,
729
+ offset
730
+ }) : null;
731
+ if (p.nextCursor !== expectedCursor || nextPage && offset !== nextPage.pagination.offset) throw new Error("Generated path continuation cursor does not bind next page");
732
+ }
733
+ if (!first.pagination.generationComplete || !last.pagination.retrievalComplete || last.pagination.nextCursor !== null || last.pagination.status !== "complete" || offset !== first.pagination.availablePaths || offset !== first.totalPaths) throw new Error("Incomplete bounded-path generation or retrieval cannot claim complete coverage");
734
+ const paths = archive.pages.flatMap((p) => p.paths);
735
+ unique(paths.map((p) => p.id), "generated path IDs");
736
+ if (graphPathGenerationId({
737
+ input: { graph: source },
738
+ kind: archive.source.kind,
739
+ budget: first.pagination.budget
740
+ }, {
741
+ paths,
742
+ coverage: first.coverage,
743
+ totalPaths: first.totalPaths,
744
+ truncated: false,
745
+ execution: first.execution
746
+ }) !== first.pagination.generationId) throw new Error("Generated path archive digest mismatch");
747
+ const nodes = new Map(graph.nodes.map((n) => [n.id, n]));
748
+ const edges = new Map(graph.edges.map((e) => [e.id, e]));
749
+ const includesAncestor = (stateIds, ancestor) => stateIds.some((stateId) => {
750
+ let current = stateId;
751
+ const seen = /* @__PURE__ */ new Set();
752
+ while (current && !seen.has(current)) {
753
+ if (current === ancestor) return true;
754
+ seen.add(current);
755
+ current = nodes.get(current)?.parentId;
756
+ }
757
+ return false;
758
+ });
759
+ for (const generated of paths) {
760
+ const lastStep = generated.steps.at(-1);
761
+ if (!lastStep || !equal(generated.finalStateIds, lastStep.stateIds) || generated.finalStatePath !== lastStep.statePath) throw new Error(`Generated path final state mismatch: ${generated.id}`);
762
+ for (let i = 0; i < generated.steps.length; i++) {
763
+ const step = generated.steps[i];
764
+ if (!step || !step.stateIds.length || step.stateIds.some((stateId) => !nodes.has(stateId))) throw new Error(`Generated path contains unknown states: ${generated.id}`);
765
+ if (i === 0) {
766
+ if (step.edgeId !== null || step.eventType !== "xstate.init") throw new Error("Generated path does not begin at initialization");
767
+ } else {
768
+ const edge = step.edgeId ? edges.get(step.edgeId) : void 0;
769
+ const previous = generated.steps[i - 1];
770
+ if (!edge || !previous || !includesAncestor(previous.stateIds, edge.sourceId) || edge.targetId && !includesAncestor(step.stateIds, edge.targetId) || (z.object({ eventType: z.string().nullish() }).parse(edge.data ?? {}).eventType ?? null) !== step.eventType) throw new Error(`Generated path transition is not in bound model: ${generated.id}`);
771
+ }
772
+ }
773
+ }
774
+ unique(coverage.caseMappings.map((m) => m.pathId), "generated path mappings");
775
+ if (!equal(paths.map((p) => p.id).sort(), coverage.caseMappings.map((m) => m.pathId).sort()) || coverage.caseMappings.some((m) => m.checkIds.some((checkId) => !config.checks.some((c) => c.id === checkId)))) throw new Error("Every generated path requires exact selected native check mappings");
776
+ }
777
+ async function checkBehavior(options) {
778
+ return runBehavior(options, false);
779
+ }
780
+ async function calibrateBehavior(options) {
781
+ return runBehavior(options, true);
782
+ }
783
+ async function runBehavior(options, calibration) {
784
+ const output = path.resolve(options.output);
785
+ let ready = false;
786
+ let processResult;
787
+ try {
788
+ await safe(path.parse(output).root, inside(path.parse(output).root, output), true);
789
+ await fsPromises.mkdir(output, { recursive: true });
790
+ if ((await fsPromises.readdir(output)).length) throw new Error("Output must be empty and unique");
791
+ ready = true;
792
+ const { root, configPath, config } = await loadConfig(options.config);
793
+ const outputRel = inside(root, output);
794
+ if (overlaps$2(outputRel, config.stateRoot)) throw new Error("Output and checked state must be isolated");
795
+ for (const input of [
796
+ ...config.inputs,
797
+ config.spec,
798
+ config.model,
799
+ config.adapter,
800
+ configPath,
801
+ ...config.schemaVersion === 2 ? [...await assuranceInputs(root, config), config.assurance.calibration] : []
802
+ ]) if (overlaps$2(outputRel, input)) throw new Error("Output overlaps bound inputs");
803
+ const manifest = await capture(root, configPath, config);
804
+ await writeJson(path.join(output, "manifest.json"), manifest);
805
+ await writeArtifact(path.join(output, "config.json"), await fsPromises.readFile(await safe(root, configPath), "utf8"));
806
+ const spec = specSchema.parse(await readJson(await safe(root, config.spec)));
807
+ const graph = parseModelGraph(await readJson(await safe(root, config.model)));
808
+ if (calibration && config.schemaVersion !== 2) throw new Error("Calibration requires config schemaVersion 2");
809
+ const plan = config.schemaVersion === 2 ? await loadAssurancePlan(root, config, spec, graph) : null;
810
+ const contract = plan ? await contractDigest(root, config, plan, manifest) : null;
811
+ let calibrationEnvelope = null;
812
+ if (config.schemaVersion === 2 && !calibration) {
813
+ calibrationEnvelope = await validateCalibration(root, config, contract);
814
+ await writeJson(path.join(output, "calibration.json"), calibrationEnvelope);
815
+ }
816
+ const storeRoot = path.join(output, "store");
817
+ await fsPromises.mkdir(storeRoot);
818
+ if (config.baseline) {
819
+ const checked = await safe(root, config.stateRoot);
820
+ await copyFiles(checked, storeRoot, await machineFiles(checked, config.baseline.machineId));
821
+ }
822
+ const store = createFileStore(storeRoot);
823
+ const loop = createLoop({ store });
824
+ const implementation = {
825
+ revision: `git:${manifest.revision}${manifest.dirty ? ":dirty" : ""}`,
826
+ environment: stableJson(manifest.runner).trim(),
827
+ files: manifest.files
828
+ };
829
+ let prepared;
830
+ if (config.baseline) {
831
+ const prior = await exactRecord(loop, config.baseline);
832
+ if (!equal(prior.reference, config.baseline)) throw new Error("Checked baseline digest mismatch");
833
+ const model = editorGraphRevision(graph) === prior.record.model.sha256 ? prior.record.model : {
834
+ machineId: config.baseline.machineId,
835
+ versionId: (await store.createProposal(config.baseline.machineId, config.name, graph)).machineVersionId,
836
+ sha256: editorGraphRevision(graph)
837
+ };
838
+ prepared = await loop.saveBehaviorRecord({
839
+ machineId: config.baseline.machineId,
840
+ base: base(config.baseline),
841
+ change: {
842
+ kind: "revise",
843
+ upsert: spec.requirements,
844
+ retire: spec.retire.filter((retirement) => {
845
+ const target = retirement.kind === "requirement" ? prior.record.requirements.find((r) => r.id === retirement.id) : prior.record.requirements.flatMap((r) => r.checks).find((c) => c.id === retirement.id);
846
+ if (target?.retired && target.retired !== retirement.reason) throw new Error(`Retirement reason changed: ${retirement.id}`);
847
+ return !target?.retired;
848
+ }),
849
+ implementation,
850
+ model
851
+ }
852
+ });
853
+ } else {
854
+ if (spec.retire.length) throw new Error("Cannot retire before creating a baseline");
855
+ const machine = await store.createMachine(await store.getDraftsProject(), config.name, graph);
856
+ prepared = await loop.saveBehaviorRecord({
857
+ machineId: machine.machineId,
858
+ change: {
859
+ kind: "create",
860
+ expectedModelRevision: (await loop.openModel(machine.machineId)).model.revision,
861
+ title: config.name,
862
+ implementation,
863
+ requirements: spec.requirements
864
+ }
865
+ });
866
+ }
867
+ const obligations = active(prepared.record);
868
+ const ids = new Set(obligations.map((c) => c.id));
869
+ if (config.checks.some((c) => !ids.has(c.id))) throw new Error("Mapping references unknown or retired checks");
870
+ processResult = {
871
+ executable: config.command.executable,
872
+ args: config.command.args.map((a) => a.replace("{report}", path.join(output, "report.json"))),
873
+ cwd: path.resolve(root, config.command.cwd),
874
+ exitCode: null,
875
+ signal: null,
876
+ timedOut: false
877
+ };
878
+ await writeJson(path.join(output, "process.json"), processResult);
879
+ processResult = await execute(config, root, output);
880
+ await writeJson(path.join(output, "process.json"), processResult);
881
+ if (!equal(manifest.files, await selectedInputs(root, configPath, config)) || await git(root, "rev-parse", "HEAD") !== manifest.revision) throw new Error("Bound inputs changed during execution");
882
+ if (processResult.timedOut || processResult.signal || processResult.exitCode === null || ![0, 1].includes(processResult.exitCode)) throw new Error("Runner timed out, signalled, or returned an unsupported exit code");
883
+ const raw = await readJson(await safe(output, "report.json"));
884
+ const module = await import(`${pathToFileURL(await safe(root, config.adapter)).href}?run=${randomUUID()}`);
885
+ if (!module || typeof module !== "object" || !("default" in module) || typeof module.default !== "function") throw new Error("Adapter must export a default function");
886
+ const results = behaviorCiResultsSchema.parse(await module.default(raw, {
887
+ root,
888
+ cwd: path.resolve(root, config.command.cwd)
889
+ }));
890
+ unique(results.map((r) => `${r.file}\0${r.fullName}`), "reported tests");
891
+ const current = [];
892
+ const missing = obligations.filter((c) => !config.checks.some((m) => m.id === c.id)).map((c) => c.id);
893
+ for (const mapping of config.checks) {
894
+ const result = results.find((r) => r.file === mapping.file && r.fullName === mapping.fullName);
895
+ if (!result) missing.push(mapping.id);
896
+ else current.push({
897
+ checkId: mapping.id,
898
+ outcome: result.outcome,
899
+ detail: result.detail
900
+ });
901
+ }
902
+ const errors = [];
903
+ if (config.schemaVersion === 2 && results.some((r) => !config.checks.some((m) => m.file === r.file && m.fullName === r.fullName))) errors.push("Native test inventory differs from exact selected mappings");
904
+ if (results.some((r) => r.outcome === "error")) errors.push("Reporter contains error outcomes");
905
+ if (results.some((r) => r.outcome === "failed" && !config.checks.some((m) => m.file === r.file && m.fullName === r.fullName))) errors.push("Failure outside mapped tests");
906
+ const hasFailure = results.some((r) => r.outcome === "failed" || r.outcome === "error");
907
+ if (processResult.exitCode === 1 !== hasFailure) errors.push("Process exit disagrees with report");
908
+ if (!equal(manifest.files, await selectedInputs(root, configPath, config)) || await git(root, "rev-parse", "HEAD") !== manifest.revision || hash(await fsPromises.readFile(fileURLToPath(import.meta.url))) !== manifest.runner.cli.moduleSha256) errors.push("Bound inputs or CLI changed during adapter execution");
909
+ const reportResults = [...current, ...missing.map((checkId) => ({
910
+ checkId,
911
+ outcome: "skipped",
912
+ detail: "Required check omitted or missing from this run"
913
+ }))];
914
+ await exactRecord(loop, prepared.reference);
915
+ const runId = `ci-${randomUUID()}`;
916
+ const next = await loop.saveBehaviorRecord({
917
+ machineId: prepared.reference.machineId,
918
+ base: base(prepared.reference),
919
+ change: {
920
+ kind: "attach-report",
921
+ report: {
922
+ runId,
923
+ binding: prepared.reportBinding,
924
+ results: reportResults
925
+ }
926
+ }
927
+ });
928
+ const status = errors.length ? "ERROR" : missing.length || current.some((r) => r.outcome !== "passed") ? "FAIL" : "PASS";
929
+ if (calibration && plan && contract) {
930
+ calibrationEnvelope = sealCalibration({
931
+ schemaVersion: 1,
932
+ kind: "native-calibration",
933
+ root,
934
+ contractSha256: contract,
935
+ config,
936
+ manifest,
937
+ process: processResult,
938
+ report: raw,
939
+ results,
940
+ dispositions: plan.dispositions,
941
+ snapshots: await Promise.all(manifest.files.map(async (f) => ({
942
+ path: f.path,
943
+ base64: (await fsPromises.readFile(await safe(root, f.path))).toString("base64")
944
+ })))
945
+ });
946
+ await writeJson(path.join(output, "calibration.json"), calibrationEnvelope);
947
+ }
948
+ const run = {
949
+ schemaVersion: 1,
950
+ assurance: config.schemaVersion === 1 ? "unavailable" : calibration ? "calibration" : "calibrated",
951
+ coverage: {
952
+ kind: plan?.coverage.kind ?? "selected-checks",
953
+ generatedPaths: plan?.coverage.kind === "bounded-paths" ? plan.coverage.caseMappings.length : 0,
954
+ nativeCases: results.filter((r) => r.outcome === "passed" || r.outcome === "failed").length,
955
+ generatorCompleteness: plan?.coverage.kind === "bounded-paths" ? "reported" : "not-requested"
956
+ },
957
+ assuranceGaps: plan ? plan.inputs.filter((i) => i.kind === "gap").map((i) => `${i.category}: ${i.reason}`) : ["Legacy schema 1 has no calibrated discovery plan"],
958
+ runId,
959
+ status,
960
+ errors,
961
+ configPath,
962
+ root,
963
+ config,
964
+ manifest,
965
+ parent: config.baseline,
966
+ next: next.reference,
967
+ prepared: prepared.reference,
968
+ model: next.record.model,
969
+ process: processResult,
970
+ current,
971
+ missing,
972
+ changes: ["requirements", "checks"].flatMap((kind) => Object.entries(prepared.changeImpact[kind]).map(([change, ids]) => `${kind} ${change}: ${ids.join(", ") || "none"}`))
973
+ };
974
+ await writeArtifact(path.join(output, "summary.md"), summary(run, next));
975
+ const artifactFiles = [
976
+ "config.json",
977
+ "manifest.json",
978
+ "process.json",
979
+ "stdout.log",
980
+ "stderr.log",
981
+ "report.json",
982
+ "summary.md",
983
+ ...calibrationEnvelope ? ["calibration.json"] : [],
984
+ ...await traceArtifacts(output),
985
+ ...(await machineFiles(storeRoot, next.reference.machineId)).map((p) => `store/${p}`)
986
+ ];
987
+ if (artifactFiles.length > 1e3) throw new Error("Artifact scope exceeds 1000 files");
988
+ await writeJson(path.join(output, "run.json"), {
989
+ ...run,
990
+ artifacts: await hashes(output, artifactFiles)
991
+ });
992
+ return {
993
+ exitCode: status === "PASS" ? 0 : status === "FAIL" ? 1 : 2,
994
+ output
995
+ };
996
+ } catch (error) {
997
+ const message = error instanceof Error ? error.message : String(error);
998
+ if (ready) {
999
+ await writeJson(path.join(output, "run.json"), {
1000
+ schemaVersion: 1,
1001
+ status: "ERROR",
1002
+ errors: [message],
1003
+ process: processResult ?? null
1004
+ });
1005
+ await writeArtifact(path.join(output, "summary.md"), `# ERROR\n\n${message}\n\nNo valid adoptable record.\n`);
1006
+ }
1007
+ return {
1008
+ exitCode: 2,
1009
+ output
1010
+ };
1011
+ }
1012
+ }
1013
+ async function adoptBehavior(options) {
1014
+ const loaded = await loadConfig(options.config);
1015
+ const lockFile = await safe(loaded.root, `${loaded.configPath}.adopt.lock`, true);
1016
+ const lock = await fsPromises.open(lockFile, "wx");
1017
+ try {
1018
+ return await adoptLocked(options, loaded);
1019
+ } finally {
1020
+ await lock.close();
1021
+ await fsPromises.unlink(lockFile);
1022
+ }
1023
+ }
1024
+ async function adoptLocked(options, { root, configPath, config }) {
1025
+ let calibrationToInstall = null;
1026
+ const artifact = path.resolve(options.artifact);
1027
+ await safe(path.parse(artifact).root, inside(path.parse(artifact).root, artifact));
1028
+ const run = runSchema.parse(await readJson(await safe(artifact, "run.json")));
1029
+ if (run.status === "ERROR") throw new Error("Error artifacts cannot advance the baseline");
1030
+ if (configPath !== run.configPath || !equal({
1031
+ ...config,
1032
+ baseline: null
1033
+ }, {
1034
+ ...run.config,
1035
+ baseline: null
1036
+ })) throw new Error("Config differs from inspected run");
1037
+ if (!equal(config.baseline, run.parent) && !equal(config.baseline, run.next)) throw new Error(`Divergent parent: checked ${stableJson(config.baseline)} artifact ${stableJson(run.parent)}`);
1038
+ unique(run.artifacts.map((f) => f.path), "artifact files");
1039
+ if (!equal(run.artifacts, await hashes(artifact, run.artifacts.map((f) => f.path)))) throw new Error("Artifact checksum mismatch");
1040
+ const state = await safe(root, config.stateRoot, true);
1041
+ const sourceRoot = await safe(artifact, "store");
1042
+ const sourceFiles = await machineFiles(sourceRoot, run.next.machineId);
1043
+ if (!equal([
1044
+ "config.json",
1045
+ "manifest.json",
1046
+ "process.json",
1047
+ "stdout.log",
1048
+ "stderr.log",
1049
+ "report.json",
1050
+ "summary.md",
1051
+ ...run.config.schemaVersion === 2 ? ["calibration.json"] : [],
1052
+ ...await traceArtifacts(artifact),
1053
+ ...sourceFiles.map((p) => `store/${p}`)
1054
+ ].sort(), run.artifacts.map((f) => f.path).sort())) throw new Error("Unexpected or missing artifact files");
1055
+ if (!equal(run.manifest, manifestSchema.parse(await readJson(await safe(artifact, "manifest.json")))) || !equal(run.process, await readJson(await safe(artifact, "process.json")))) throw new Error("Artifact metadata mismatch");
1056
+ const capturedConfig = await fsPromises.readFile(await safe(artifact, "config.json"));
1057
+ const configInput = run.manifest.files.find((file) => file.path === run.configPath);
1058
+ if (!configInput || hash(capturedConfig) !== configInput.sha256 || !equal(behaviorCiConfigSchema.parse(JSON.parse(capturedConfig.toString("utf8"))), run.config) || !equal(run.config.baseline, run.parent)) throw new Error("Recorded config binding mismatch");
1059
+ const available = run.manifest.dirty ? (await git(root, "ls-files", "-z")).split("\0") : (await git(root, "ls-tree", "-r", "--name-only", "-z", run.manifest.revision)).split("\0");
1060
+ const required = new Set([
1061
+ run.configPath,
1062
+ config.spec,
1063
+ config.model,
1064
+ config.adapter,
1065
+ ...config.checks.map((c) => c.file),
1066
+ ...config.schemaVersion === 2 ? await assuranceInputs(root, config) : []
1067
+ ]);
1068
+ for (const input of config.inputs) {
1069
+ const matches = available.filter((p) => p === input || p.startsWith(`${input}/`));
1070
+ if (!matches.length) throw new Error(`Missing recorded input scope: ${input}`);
1071
+ matches.forEach((p) => required.add(p));
1072
+ }
1073
+ for (const dir of new Set([".", config.command.cwd])) for (const name of [
1074
+ "package.json",
1075
+ "pnpm-lock.yaml",
1076
+ "package-lock.json",
1077
+ "yarn.lock"
1078
+ ]) {
1079
+ const p = dir === "." ? name : `${dir}/${name}`;
1080
+ if (available.includes(p)) required.add(p);
1081
+ }
1082
+ if (run.parent) {
1083
+ const prefix = `${config.stateRoot}/.statelyai/machines/${run.parent.machineId}`;
1084
+ const recordedParentPaths = run.manifest.files.map((f) => f.path).filter((p) => p === `${prefix}.json` || p.startsWith(`${prefix}/versions/`));
1085
+ if (!recordedParentPaths.includes(`${prefix}.json`) || !recordedParentPaths.includes(`${prefix}/versions/${run.parent.versionId}.json`)) throw new Error("Missing checked parent files");
1086
+ recordedParentPaths.forEach((p) => required.add(p));
1087
+ }
1088
+ if (!equal([...required].sort(), run.manifest.files.map((f) => f.path).sort())) throw new Error("Recorded manifest does not match selected scope");
1089
+ for (const file of run.manifest.files) if (run.manifest.dirty) {
1090
+ if (file.path === configPath && equal(config.baseline, run.next)) continue;
1091
+ if (hash(await fsPromises.readFile(await safe(root, file.path))) !== file.sha256) throw new Error(`Dirty run inputs no longer match: ${file.path}`);
1092
+ } else if (hash((await exec("git", [
1093
+ "-C",
1094
+ root,
1095
+ "show",
1096
+ `${run.manifest.revision}:${file.path}`
1097
+ ], {
1098
+ encoding: "buffer",
1099
+ maxBuffer: 16 * 1024 * 1024
1100
+ })).stdout) !== file.sha256) throw new Error(`Recorded Git input mismatch: ${file.path}`);
1101
+ const loop = createLoop({ store: createFileStore(sourceRoot) });
1102
+ const next = await exactRecord(loop, run.next);
1103
+ const prepared = await exactRecord(loop, run.prepared);
1104
+ if (!equal(next.reference, run.next) || !equal(next.record.parent, run.prepared) || !equal(prepared.reference, run.prepared) || !equal(prepared.record.parent, run.parent) || !equal(next.record.model, run.model)) throw new Error("Record ancestry/digest mismatch");
1105
+ if (editorGraphRevision(parseModelGraph((await createFileStore(sourceRoot).getProposal(run.model.machineId, run.model.versionId)).definition)) !== run.model.sha256) throw new Error("Model digest mismatch");
1106
+ const implementation = {
1107
+ revision: `git:${run.manifest.revision}${run.manifest.dirty ? ":dirty" : ""}`,
1108
+ environment: stableJson(run.manifest.runner).trim(),
1109
+ files: run.manifest.files
1110
+ };
1111
+ if (!equal(next.record.implementation, implementation) || !equal(prepared.record.implementation, implementation)) throw new Error("Implementation manifest binding mismatch");
1112
+ const report = next.record.reports.find((r) => r.report.runId === run.runId);
1113
+ if (!report || !equal(report.report.binding, next.reportBinding)) throw new Error("Current report binding missing");
1114
+ const expectedResults = [...run.current, ...run.missing.map((checkId) => ({
1115
+ checkId,
1116
+ outcome: "skipped",
1117
+ detail: "Required check omitted or missing from this run"
1118
+ }))];
1119
+ if (!equal(report.report.results.map(({ replayRefs: _refs, ...result }) => result), expectedResults)) throw new Error("Current report mismatch");
1120
+ if (!equal(active(next.record).map((c) => c.id).sort(), expectedResults.map((r) => r.checkId).sort()) || run.status === "PASS" !== expectedResults.every((r) => r.outcome === "passed") || run.errors.length) throw new Error("Current verdict mismatch");
1121
+ if (config.schemaVersion === 2) {
1122
+ const plan = await loadAssurancePlan(root, config, specSchema.parse(await readJson(await safe(root, config.spec))), parseModelGraph(await readJson(await safe(root, config.model))));
1123
+ const currentManifest = await capture(root, configPath, config);
1124
+ const contract = await contractDigest(root, config, plan, currentManifest);
1125
+ const envelope = await validateCalibration(root, config, contract, await readJson(await safe(artifact, "calibration.json")));
1126
+ if (!run.root) throw new Error("Schema 2 artifact is missing original checkout root");
1127
+ const normalized = await normalizeReport(root, config, await readJson(await safe(artifact, "report.json")), run.root);
1128
+ if (!equal(config.checks.flatMap((mapping) => {
1129
+ const result = normalized.find((r) => r.file === mapping.file && r.fullName === mapping.fullName);
1130
+ return result ? [{
1131
+ checkId: mapping.id,
1132
+ outcome: result.outcome,
1133
+ detail: result.detail
1134
+ }] : [];
1135
+ }), run.current) || normalized.some((r) => !config.checks.some((m) => m.file === r.file && m.fullName === r.fullName))) throw new Error("Adopted current native report differs from recorded results or exact selected inventory");
1136
+ const expectedCoverage = {
1137
+ kind: plan.coverage.kind,
1138
+ generatedPaths: plan.coverage.kind === "bounded-paths" ? plan.coverage.caseMappings.length : 0,
1139
+ nativeCases: normalized.filter((r) => r.outcome === "passed" || r.outcome === "failed").length,
1140
+ generatorCompleteness: plan.coverage.kind === "bounded-paths" ? "reported" : "not-requested"
1141
+ };
1142
+ if (!equal(run.coverage, expectedCoverage)) throw new Error("Coverage claim differs from bound plan and native report");
1143
+ if (contract !== await contractDigest(root, config, plan, currentManifest)) throw new Error("Bound contract changed during adoption validation");
1144
+ if (run.assurance === "calibration") {
1145
+ if (!equal(envelope.receipt.manifest, run.manifest) || !equal(envelope.receipt.process, run.process) || !equal(envelope.receipt.report, await readJson(await safe(artifact, "report.json")))) throw new Error("Calibration receipt differs from actual native run");
1146
+ } else if (run.assurance !== "calibrated") throw new Error("Schema 2 run missing setup assurance");
1147
+ calibrationToInstall = {
1148
+ file: await safe(root, config.assurance.calibration, true),
1149
+ envelope
1150
+ };
1151
+ }
1152
+ const files = sourceFiles.filter((p) => p.includes("/versions/"));
1153
+ const sourceStore = createFileStore(sourceRoot);
1154
+ for (const file of files) {
1155
+ const versionId = path.basename(file, ".json");
1156
+ const saved = await sourceStore.getProposal(run.next.machineId, versionId);
1157
+ if (saved.machineId !== run.next.machineId || saved.machineVersionId !== versionId) throw new Error("Stored version identity mismatch");
1158
+ parseModelGraph(saved.definition);
1159
+ }
1160
+ await fsPromises.mkdir(state, { recursive: true });
1161
+ if (config.baseline) {
1162
+ if (!equal((await exactRecord(createLoop({ store: createFileStore(state) }), config.baseline)).reference, config.baseline)) throw new Error("Checked parent digest mismatch");
1163
+ }
1164
+ await copyFiles(sourceRoot, state, files, true);
1165
+ const head = `.statelyai/machines/${run.next.machineId}.json`;
1166
+ try {
1167
+ await fsPromises.access(await safe(state, head, true));
1168
+ } catch {
1169
+ await copyFiles(sourceRoot, state, [head]);
1170
+ }
1171
+ if (!equal(behaviorCiConfigSchema.parse(await readJson(await safe(root, configPath))), config)) throw new Error("Config changed during adoption; copied versions remain unselected");
1172
+ if (calibrationToInstall) {
1173
+ await fsPromises.mkdir(path.dirname(calibrationToInstall.file), { recursive: true });
1174
+ await writeJson(calibrationToInstall.file, calibrationToInstall.envelope);
1175
+ }
1176
+ const configFile = await safe(root, configPath);
1177
+ const temporary = `${configFile}.${randomUUID()}.tmp`;
1178
+ await fsPromises.writeFile(temporary, stableJson({
1179
+ ...config,
1180
+ baseline: run.next
1181
+ }), { flag: "wx" });
1182
+ await fsPromises.rename(temporary, configFile);
1183
+ return run.next;
1184
+ }
1185
+ var BehaviorCheckCommand = class BehaviorCheckCommand extends Command {
1186
+ static summary = "Run checked tests and save an immutable behavior evidence artifact.";
1187
+ static flags = {
1188
+ config: Flags.string({ required: true }),
1189
+ output: Flags.string({ required: true }),
1190
+ help: Flags.help({ char: "h" })
1191
+ };
1192
+ async run() {
1193
+ const { flags } = await this.parse(BehaviorCheckCommand);
1194
+ const result = await checkBehavior(flags);
1195
+ this.log(result.output);
1196
+ if (result.exitCode) this.exit(result.exitCode);
1197
+ }
1198
+ };
1199
+ var BehaviorAdoptCommand = class BehaviorAdoptCommand extends Command {
1200
+ static summary = "Adopt an inspected behavior run into the checked baseline.";
1201
+ static args = { artifact: Args.string({ required: true }) };
1202
+ static flags = {
1203
+ config: Flags.string({ required: true }),
1204
+ help: Flags.help({ char: "h" })
1205
+ };
1206
+ async run() {
1207
+ const { flags, args } = await this.parse(BehaviorAdoptCommand);
1208
+ try {
1209
+ this.log(stableJson(await adoptBehavior({
1210
+ config: flags.config,
1211
+ artifact: args.artifact
1212
+ })));
1213
+ } catch (error) {
1214
+ this.error(error instanceof Error ? error.message : String(error), { exit: 2 });
1215
+ }
1216
+ }
1217
+ };
1218
+ var BehaviorCalibrateCommand = class BehaviorCalibrateCommand extends Command {
1219
+ static summary = "Execute native checks to calibrate a reviewed behavior contract.";
1220
+ static flags = {
1221
+ config: Flags.string({ required: true }),
1222
+ output: Flags.string({ required: true }),
1223
+ help: Flags.help({ char: "h" })
1224
+ };
1225
+ async run() {
1226
+ const { flags } = await this.parse(BehaviorCalibrateCommand);
1227
+ const result = await calibrateBehavior(flags);
1228
+ this.log(result.output);
1229
+ if (result.exitCode) this.exit(result.exitCode);
1230
+ }
1231
+ };
1232
+
1233
+ //#endregion
1234
+ //#region src/testOutputs.ts
1235
+ async function identity(filename) {
1236
+ const path = resolve(filename);
1237
+ try {
1238
+ const info = await stat(path);
1239
+ if (!info.isFile()) throw new Error(`Expected a file path: ${path}`);
1240
+ return {
1241
+ path: await realpath(path),
1242
+ inode: `${info.dev}:${info.ino}`
1243
+ };
1244
+ } catch (error) {
1245
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") throw error;
1246
+ let ancestor = dirname(path);
1247
+ const segments = [path.slice(ancestor.length + 1)];
1248
+ for (;;) try {
1249
+ return { path: resolve(await realpath(ancestor), ...segments) };
1250
+ } catch (error) {
1251
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") throw error;
1252
+ const parent = dirname(ancestor);
1253
+ if (parent === ancestor) throw error;
1254
+ segments.unshift(ancestor.slice(parent.length + 1));
1255
+ ancestor = parent;
1256
+ }
1257
+ }
1258
+ }
1259
+ async function prepareTestOutputs(options) {
1260
+ const inputs = [options.campaign, ...options.replay ? [options.replay] : []];
1261
+ const outputs = [...options.report === "-" ? [] : [options.report], ...options.fixture ? [options.fixture] : []];
1262
+ const seen = [];
1263
+ for (const filename of [...inputs, ...outputs]) {
1264
+ const id = await identity(filename);
1265
+ if (seen.some((other) => other.path === id.path || id.inode && id.inode === other.inode)) throw new Error("Campaign, replay input, report and fixture must use distinct file paths.");
1266
+ seen.push(id);
1267
+ }
1268
+ for (const filename of outputs) await mkdir(dirname(resolve(filename)), { recursive: true });
1269
+ }
1270
+
1271
+ //#endregion
1272
+ //#region src/testCommand.ts
1273
+ var TestCommand = class TestCommand extends Command {
1274
+ static enableJsonFlag = false;
1275
+ static summary = "Run a trusted local model-based testing campaign.";
1276
+ static description = "Imports and executes a trusted .mjs campaign with ordinary local permissions. This is not a sandbox. Install @statelyai/model-testing in the campaign project. Runs graph paths and property tests; --replay checks a saved counterexample as a regression. Exit 0 passes, 1 is a model/app discrepancy, 2 is inconclusive or an infrastructure failure.";
1277
+ static args = { campaign: Args.string({
1278
+ required: true,
1279
+ description: "Trusted local .mjs campaign module"
1280
+ }) };
1281
+ static flags = {
1282
+ report: Flags.string({
1283
+ default: "statelyai-test-report.json",
1284
+ description: "JSON report destination, or - for stdout"
1285
+ }),
1286
+ replay: Flags.string({ description: "JSON replay fixture from a previous campaign" }),
1287
+ fixture: Flags.string({ description: "Write the property counterexample, or first available path counterexample, to this JSON file" }),
1288
+ seed: Flags.integer({
1289
+ description: "Generator seed",
1290
+ default: 1
1291
+ }),
1292
+ runs: Flags.integer({
1293
+ description: "Property trial budget",
1294
+ default: 100
1295
+ }),
1296
+ "max-commands": Flags.integer({
1297
+ description: "Maximum generated commands per trial",
1298
+ default: 30
1299
+ }),
1300
+ "path-limit": Flags.integer({
1301
+ description: "Maximum graph traversal steps",
1302
+ default: 1e3
1303
+ }),
1304
+ timeout: Flags.integer({
1305
+ description: "Whole worker deadline in milliseconds",
1306
+ default: 12e4
1307
+ })
1308
+ };
1309
+ async run() {
1310
+ const { args, flags } = await this.parse(TestCommand);
1311
+ try {
1312
+ await prepareTestOutputs({
1313
+ campaign: args.campaign,
1314
+ replay: flags.replay,
1315
+ report: flags.report,
1316
+ fixture: flags.fixture
1317
+ });
1318
+ const replay = flags.replay ? modelTestReplaySchema.parse(JSON.parse(await readFile(flags.replay, "utf8"))) : void 0;
1319
+ const report = await runCampaignFile(args.campaign, {
1320
+ replay,
1321
+ budget: {
1322
+ seed: flags.seed,
1323
+ numRuns: flags.runs,
1324
+ maxCommands: flags["max-commands"],
1325
+ pathLimit: flags["path-limit"],
1326
+ timeoutMs: flags.timeout
1327
+ },
1328
+ onLog: (text) => process.stderr.write(text)
1329
+ });
1330
+ const json = `${JSON.stringify(report, null, 2)}\n`;
1331
+ if (flags.report === "-") this.log(json.trimEnd());
1332
+ else {
1333
+ await writeFile(flags.report, json);
1334
+ this.log(`${report.status}: ${report.phases.map((p) => `${p.phase}=${p.status}`).join(", ")}. Report ${flags.report}`);
1335
+ }
1336
+ if (flags.fixture) {
1337
+ const fixture = reportReplays(report).at(-1);
1338
+ if (fixture) await writeFile(flags.fixture, `${JSON.stringify(fixture, null, 2)}\n`);
1339
+ else {
1340
+ const existing = await readFile(flags.fixture).then(() => true, () => false);
1341
+ process.stderr.write(existing ? "No new counterexample available. Existing fixture preserved; it does not belong to this run.\n" : "No portable counterexample available; fixture file was not written.\n");
1342
+ }
1343
+ }
1344
+ const exit = campaignExitCode(report);
1345
+ if (exit) this.exit(exit);
1346
+ } catch (error) {
1347
+ if (error instanceof Error && "oclif" in error) throw error;
1348
+ this.error(error instanceof Error ? error.message : String(error), { exit: 2 });
1349
+ }
1350
+ }
1351
+ };
1352
+
31
1353
  //#endregion
32
1354
  //#region src/loopCommands.ts
33
1355
  /** An XGraph file, or a `.statelyai` head or version file (its `graph`). */
@@ -4279,7 +5601,185 @@ function createMachine(config, implementations) {
4279
5601
  }
4280
5602
 
4281
5603
  //#endregion
4282
- //#region ../graph-tools/dist/validation-Ci3ri12U.mjs
5604
+ //#region ../graph-tools/dist/validation-D8ipdyfF.mjs
5605
+ function resolveProfileData(data, namespace) {
5606
+ const prefix = `${namespace}.`;
5607
+ return Object.fromEntries(Object.entries(data).filter(([key]) => key.startsWith(prefix)).map(([key, value]) => [key.slice(prefix.length), value]));
5608
+ }
5609
+ const ARCHITECTURE_PROFILE = "@statelyai/architecture@1.0.0";
5610
+ const ARCHITECTURE_NAMESPACE = "@statelyai.architecture";
5611
+ const architectureTargetSchema = z.discriminatedUnion("kind", [z.object({
5612
+ kind: z.literal("machine"),
5613
+ machineId: z.string().min(1),
5614
+ projectId: z.string().min(1).optional()
5615
+ }), z.object({
5616
+ kind: z.literal("view"),
5617
+ viewId: z.string().min(1)
5618
+ })]);
5619
+ const architectureViewSchema = z.object({
5620
+ id: z.string().min(1),
5621
+ name: z.string().min(1),
5622
+ kind: z.enum([
5623
+ "system-context",
5624
+ "container",
5625
+ "component",
5626
+ "custom"
5627
+ ]),
5628
+ nodeIds: z.array(z.string()),
5629
+ edgeIds: z.array(z.string())
5630
+ });
5631
+ const architectureGraphDataSchema = z.object({
5632
+ version: z.literal(1),
5633
+ views: z.array(architectureViewSchema),
5634
+ activeViewId: z.string().optional(),
5635
+ projectOverview: z.boolean().optional()
5636
+ });
5637
+ const architectureNodeDataSchema = z.object({
5638
+ kind: z.string().optional(),
5639
+ externalId: z.string().optional(),
5640
+ technology: z.string().optional(),
5641
+ target: architectureTargetSchema.optional()
5642
+ });
5643
+ const architectureEdgeDataSchema = z.object({
5644
+ kind: z.string().optional(),
5645
+ externalId: z.string().optional(),
5646
+ technology: z.string().optional()
5647
+ });
5648
+ function architectureData(data) {
5649
+ return resolveProfileData(Object.fromEntries(Object.entries(data)), ARCHITECTURE_NAMESPACE);
5650
+ }
5651
+ function getArchitectureNodeData(node) {
5652
+ const data = architectureData(node.data);
5653
+ if (!Object.keys(data).length) return void 0;
5654
+ const parsed = architectureNodeDataSchema.safeParse(data);
5655
+ return parsed.success ? parsed.data : void 0;
5656
+ }
5657
+ const jsonSchema7Schema = z.lazy(() => z.object({
5658
+ $schema: z.string().optional(),
5659
+ type: z.enum([
5660
+ "object",
5661
+ "array",
5662
+ "string",
5663
+ "number",
5664
+ "integer",
5665
+ "boolean",
5666
+ "null"
5667
+ ]).optional(),
5668
+ properties: z.record(z.string(), jsonSchema7Schema).optional(),
5669
+ items: jsonSchema7Schema.optional(),
5670
+ additionalProperties: z.union([z.boolean(), jsonSchema7Schema]).optional(),
5671
+ required: z.array(z.string()).optional(),
5672
+ anyOf: z.array(jsonSchema7Schema).optional(),
5673
+ oneOf: z.array(jsonSchema7Schema).optional(),
5674
+ enum: z.array(z.union([
5675
+ z.string(),
5676
+ z.number(),
5677
+ z.boolean(),
5678
+ z.null()
5679
+ ])).optional(),
5680
+ const: z.union([
5681
+ z.string(),
5682
+ z.number(),
5683
+ z.boolean(),
5684
+ z.null()
5685
+ ]).optional(),
5686
+ minLength: z.number().optional()
5687
+ }).strict());
5688
+ function draft7Schema(schema) {
5689
+ return jsonSchema7Schema.parse(z.toJSONSchema(schema, { target: "draft-7" }));
5690
+ }
5691
+ const ARCHITECTURE_PROFILE_MANIFEST = {
5692
+ profile: ARCHITECTURE_PROFILE,
5693
+ namespace: ARCHITECTURE_NAMESPACE,
5694
+ lineage: "@statelyai/architecture",
5695
+ projection: {
5696
+ readOnlyPaths: [
5697
+ "initialNodeId",
5698
+ "nodes.*.initialNodeId",
5699
+ "nodes.*.data.initialId",
5700
+ "nodes.*.data.entry",
5701
+ "nodes.*.data.exit",
5702
+ "nodes.*.data.invokes",
5703
+ "nodes.*.data.history",
5704
+ "edges.*.data.guard",
5705
+ "edges.*.data.actions",
5706
+ "edges.*.data.transitionType"
5707
+ ],
5708
+ adapt: (graph) => ({ data: {
5709
+ nodes: Object.fromEntries(graph.nodes.map((node) => [node.id, { kind: getArchitectureNodeData(node)?.kind ?? "component" }])),
5710
+ edges: Object.fromEntries(graph.edges.map((edge) => [edge.id, { kind: "relationship" }]))
5711
+ } }),
5712
+ analyzeCompatibility: (graph) => graph.nodes.some((node) => node.data.initialId || Array.isArray(node.data.entry) && node.data.entry.length || Array.isArray(node.data.exit) && node.data.exit.length || Array.isArray(node.data.invokes) && node.data.invokes.length) || graph.edges.some((edge) => edge.data.guard || Array.isArray(edge.data.actions) && edge.data.actions.length || edge.data.transitionType && edge.data.transitionType !== "normal") ? [{
5713
+ level: "warning",
5714
+ message: "State-machine initial states, effects, guards and transition behavior are retained but read-only in Architecture. Relationships describe structure; architecture simulation is unavailable."
5715
+ }] : []
5716
+ },
5717
+ name: "Architecture",
5718
+ description: "Components, relationships, and navigable system views.",
5719
+ selectable: true,
5720
+ restrictFormatsToListed: true,
5721
+ defaultFormat: "xgraph",
5722
+ graph: { dataSchema: draft7Schema(architectureGraphDataSchema) },
5723
+ authoring: {
5724
+ terminology: {
5725
+ node: "component",
5726
+ edge: "relationship",
5727
+ graph: "architecture"
5728
+ },
5729
+ nodeTypes: [
5730
+ "element",
5731
+ "actor",
5732
+ "person",
5733
+ "system",
5734
+ "container",
5735
+ "component",
5736
+ "service",
5737
+ "database",
5738
+ "queue",
5739
+ "group",
5740
+ "junction"
5741
+ ].map((kind) => ({
5742
+ kind,
5743
+ label: kind.charAt(0).toUpperCase() + kind.slice(1),
5744
+ authorable: true,
5745
+ canContain: true,
5746
+ dataSchema: draft7Schema(architectureNodeDataSchema)
5747
+ })),
5748
+ edgeTypes: [{
5749
+ kind: "relationship",
5750
+ label: "Relationship",
5751
+ authorable: true,
5752
+ dataSchema: draft7Schema(architectureEdgeDataSchema)
5753
+ }],
5754
+ attachments: {
5755
+ actions: false,
5756
+ guards: false,
5757
+ invokes: false,
5758
+ tags: false
5759
+ },
5760
+ supportsInitialNode: false,
5761
+ supportsTargetlessEdges: false,
5762
+ supportsReenterEdges: false
5763
+ },
5764
+ operations: {
5765
+ simulate: false,
5766
+ validate: true,
5767
+ export: true
5768
+ },
5769
+ formats: [{
5770
+ format: "xgraph",
5771
+ import: true,
5772
+ export: true
5773
+ }, ...[
5774
+ "structurizr-json",
5775
+ "d2",
5776
+ "mermaid"
5777
+ ].map((format) => ({
5778
+ format,
5779
+ import: true,
5780
+ export: false
5781
+ }))]
5782
+ };
4283
5783
  const XSTATE_V5_PROFILE = "@statelyai/xstate@5.30.0";
4284
5784
  const XSTATE_V6_PROFILE = "@statelyai/xstate@6.0.0-alpha.51";
4285
5785
  const XSTATE_V6_BRANCH_DESCRIPTION_META = "@statelyai.branchDescription";
@@ -5535,7 +7035,11 @@ const XSTATE_PROFILE_MANIFESTS = [{
5535
7035
  export: true
5536
7036
  }
5537
7037
  }];
5538
- const PROFILE_MANIFESTS = new Map([...XSTATE_PROFILE_MANIFESTS, LANGGRAPH_PROFILE_MANIFEST].map((manifest) => [manifest.profile, manifest]));
7038
+ const PROFILE_MANIFESTS = new Map([
7039
+ ...XSTATE_PROFILE_MANIFESTS,
7040
+ LANGGRAPH_PROFILE_MANIFEST,
7041
+ ARCHITECTURE_PROFILE_MANIFEST
7042
+ ].map((manifest) => [manifest.profile, manifest]));
5539
7043
  function getProfileManifest(profile) {
5540
7044
  return PROFILE_MANIFESTS.get(profile);
5541
7045
  }
@@ -11905,6 +13409,14 @@ function builtInActionFromCall(call, bindings, context, depth) {
11905
13409
  ...options(2)
11906
13410
  }
11907
13411
  };
13412
+ case "sendParent": return {
13413
+ type: "xstate.sendTo",
13414
+ params: {
13415
+ to: "#_parent",
13416
+ event: value(0),
13417
+ ...options(1)
13418
+ }
13419
+ };
11908
13420
  case "emit": return {
11909
13421
  type: "xstate.emit",
11910
13422
  params: { event: value(0) }
@@ -21347,10 +22859,6 @@ function sourceLocationUris(sourceLocations) {
21347
22859
  ].filter((uri) => Boolean(uri)))];
21348
22860
  }
21349
22861
 
21350
- //#endregion
21351
- //#region src/version.ts
21352
- const CLI_VERSION = "0.14.3";
21353
-
21354
22862
  //#endregion
21355
22863
  //#region src/cliHost.ts
21356
22864
  const DEFAULT_SYNC_MAX_FILES = 1e3;
@@ -24809,6 +26317,10 @@ var WhoamiCommand = class extends Command {
24809
26317
  }
24810
26318
  };
24811
26319
  const COMMANDS = {
26320
+ "behavior:calibrate": BehaviorCalibrateCommand,
26321
+ "behavior:check": BehaviorCheckCommand,
26322
+ "behavior:adopt": BehaviorAdoptCommand,
26323
+ test: TestCommand,
24812
26324
  verify: VerifyCommand,
24813
26325
  plan: PlanCommand,
24814
26326
  diff: DiffCommand,
@@ -24848,4 +26360,4 @@ function isDirectExecution() {
24848
26360
  if (isDirectExecution()) run$1();
24849
26361
 
24850
26362
  //#endregion
24851
- export { formatVerificationJUnit as A, run$1 as C, resourceId as D, linkProject as E, readGraphFile as F, verifyReport as I, briefReport as M, changesReport as N, resolveVerificationRequest as O, pendingReport as P, resolveProjectStatus as S, scanProjectSources as T, isFileGitDirty as _, discoverMachinesInSource as a, resolveCredentialStatus as b, formatDiscoveredMachines as c, formatProjectStatus as d, formatStoredCredentialSource as f, initProject as g, inferInitProjectName as h, discoverLinkedPullTargets as i, createStatelyProjectConfig as j, verifyProject as k, formatMissingNewMachineDirMessage as l, getEnvCredential as m, buildOAuthLoginStart as n, discoverOAuthLogin as o, getEnvApiKey as p, classifyPushCandidates as r, discoverRemoteProjectMachineTargets as s, COMMANDS as t, formatPlanSummary as u, resolveApiKey as v, scanProjectMachines as w, resolveOpenRootDir as x, resolveConfiguredPullTargets as y };
26363
+ export { formatVerificationJUnit as A, calibrateBehavior as B, run$1 as C, resourceId as D, linkProject as E, readGraphFile as F, verifyReport as I, adoptBehavior as L, briefReport as M, changesReport as N, resolveVerificationRequest as O, pendingReport as P, behaviorCiConfigSchema as R, resolveProjectStatus as S, scanProjectSources as T, checkBehavior as V, isFileGitDirty as _, discoverMachinesInSource as a, resolveCredentialStatus as b, formatDiscoveredMachines as c, formatProjectStatus as d, formatStoredCredentialSource as f, initProject as g, inferInitProjectName as h, discoverLinkedPullTargets as i, createStatelyProjectConfig as j, verifyProject as k, formatMissingNewMachineDirMessage as l, getEnvCredential as m, buildOAuthLoginStart as n, discoverOAuthLogin as o, getEnvApiKey as p, classifyPushCandidates as r, discoverRemoteProjectMachineTargets as s, COMMANDS as t, formatPlanSummary as u, resolveApiKey as v, scanProjectMachines as w, resolveOpenRootDir as x, resolveConfiguredPullTargets as y, behaviorCiResultsSchema as z };