@jevitate/cli 0.23.0 → 0.25.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.
Files changed (82) hide show
  1. package/contracts/jevitate-catalog/v1/README.md +10 -0
  2. package/contracts/jevitate-catalog/v1/approval-hash-vectors.json +64 -0
  3. package/contracts/jevitate-catalog/v1/fixtures/invalid/jobs-unknown-field.json +12 -0
  4. package/contracts/jevitate-catalog/v1/fixtures/invalid/journey-unknown-field.json +15 -0
  5. package/contracts/jevitate-catalog/v1/fixtures/valid/jobs.json +11 -0
  6. package/contracts/jevitate-catalog/v1/fixtures/valid/journey.json +14 -0
  7. package/contracts/jevitate-catalog/v1/fixtures/valid/personas.json +7 -0
  8. package/contracts/jevitate-catalog/v1/manifest.json +69 -0
  9. package/contracts/jevitate-catalog/v1/schemas/job.schema.json +605 -0
  10. package/contracts/jevitate-catalog/v1/schemas/jobs.schema.json +28 -0
  11. package/contracts/jevitate-catalog/v1/schemas/journey.schema.json +10871 -0
  12. package/contracts/jevitate-catalog/v1/schemas/personas.schema.json +223 -0
  13. package/contracts/jevitate-catalog/v2/README.md +18 -0
  14. package/contracts/jevitate-catalog/v2/approval-hash-vectors.json +75 -0
  15. package/contracts/jevitate-catalog/v2/fixtures/invalid/jobs-unknown-field.json +12 -0
  16. package/contracts/jevitate-catalog/v2/fixtures/invalid/journey-unknown-field.json +15 -0
  17. package/contracts/jevitate-catalog/v2/fixtures/valid/jobs.json +11 -0
  18. package/contracts/jevitate-catalog/v2/fixtures/valid/journey.json +14 -0
  19. package/contracts/jevitate-catalog/v2/fixtures/valid/personas.json +7 -0
  20. package/contracts/jevitate-catalog/v2/manifest.json +78 -0
  21. package/contracts/jevitate-catalog/v2/schemas/job.schema.json +605 -0
  22. package/contracts/jevitate-catalog/v2/schemas/jobs.schema.json +28 -0
  23. package/contracts/jevitate-catalog/v2/schemas/journey.schema.json +10871 -0
  24. package/contracts/jevitate-catalog/v2/schemas/personas.schema.json +223 -0
  25. package/dist/acceptance-audit-approval-script.d.ts +6 -0
  26. package/dist/acceptance-audit-approval-script.js +20 -0
  27. package/dist/acceptance-audit-approval-script.js.map +1 -0
  28. package/dist/acceptance-audit-cli.d.ts +2 -0
  29. package/dist/acceptance-audit-cli.js +34 -0
  30. package/dist/acceptance-audit-cli.js.map +1 -0
  31. package/dist/acceptance-audit-render.d.ts +4 -0
  32. package/dist/acceptance-audit-render.js +12 -0
  33. package/dist/acceptance-audit-render.js.map +1 -0
  34. package/dist/acceptance-audit-schema.d.ts +32 -0
  35. package/dist/acceptance-audit-schema.js +19 -0
  36. package/dist/acceptance-audit-schema.js.map +1 -0
  37. package/dist/acceptance-audit-suite.d.ts +14 -0
  38. package/dist/acceptance-audit-suite.js +34 -0
  39. package/dist/acceptance-audit-suite.js.map +1 -0
  40. package/dist/acceptance-audit.d.ts +16 -0
  41. package/dist/acceptance-audit.js +36 -0
  42. package/dist/acceptance-audit.js.map +1 -0
  43. package/dist/bin.js +1155 -509
  44. package/dist/build-info.generated.js +3 -3
  45. package/dist/catalog-bundle.d.ts +4 -2
  46. package/dist/catalog-bundle.js +8 -6
  47. package/dist/catalog-bundle.js.map +1 -1
  48. package/dist/catalog-contract-identity.d.ts +5 -0
  49. package/dist/catalog-contract-identity.js +7 -0
  50. package/dist/catalog-contract-identity.js.map +1 -0
  51. package/dist/catalog.js +18 -7
  52. package/dist/catalog.js.map +1 -1
  53. package/dist/explore-goal.d.ts +3 -1
  54. package/dist/explore-goal.js +1 -0
  55. package/dist/explore-goal.js.map +1 -1
  56. package/dist/goal-run-flags.js +2 -2
  57. package/dist/goal-run-flags.js.map +1 -1
  58. package/dist/journeeze-hint.d.ts +35 -0
  59. package/dist/journeeze-hint.js +77 -0
  60. package/dist/journeeze-hint.js.map +1 -0
  61. package/dist/journey-cli/review.js +16 -2
  62. package/dist/journey-cli/review.js.map +1 -1
  63. package/dist/journey-cli/verify.js +33 -7
  64. package/dist/journey-cli/verify.js.map +1 -1
  65. package/dist/journey-review.d.ts +3 -0
  66. package/dist/journey-review.js +13 -2
  67. package/dist/journey-review.js.map +1 -1
  68. package/dist/journey-stale-api.js +4 -4
  69. package/dist/journey-stale-api.js.map +1 -1
  70. package/dist/journey-verify.d.ts +23 -0
  71. package/dist/journey-verify.js +34 -0
  72. package/dist/journey-verify.js.map +1 -1
  73. package/dist/mcp-cli-tools.js +2 -1
  74. package/dist/mcp-cli-tools.js.map +1 -1
  75. package/dist/program.js +2 -0
  76. package/dist/program.js.map +1 -1
  77. package/dist/report-cli.js +21 -2
  78. package/dist/report-cli.js.map +1 -1
  79. package/dist/ux-usability/done-adjudication.d.ts +2 -2
  80. package/package.json +14 -11
  81. package/skills/jevitate-promotion-ready/SKILL.md +5 -1
  82. package/skills/jevitate-record/SKILL.md +3 -2
package/dist/bin.js CHANGED
@@ -10,16 +10,36 @@ import { mkdir, stat } from "node:fs/promises";
10
10
 
11
11
  // ../domain/dist/content-hash.js
12
12
  import { createHash } from "node:crypto";
13
- function canonical(value) {
13
+ function compareUtf16CodeUnits(a, b2) {
14
+ const length = Math.min(a.length, b2.length);
15
+ for (let index = 0; index < length; index += 1) {
16
+ const difference = a.charCodeAt(index) - b2.charCodeAt(index);
17
+ if (difference !== 0)
18
+ return difference < 0 ? -1 : 1;
19
+ }
20
+ return a.length === b2.length ? 0 : a.length < b2.length ? -1 : 1;
21
+ }
22
+ function canonicalJson(value) {
14
23
  if (value === null || typeof value !== "object")
15
24
  return JSON.stringify(value);
16
25
  if (Array.isArray(value))
17
- return `[${value.map(canonical).join(",")}]`;
26
+ return `[${value.map(canonicalJson).join(",")}]`;
27
+ const entries = Object.entries(value).sort(([a], [b2]) => compareUtf16CodeUnits(a, b2));
28
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
29
+ }
30
+ function legacyCanonicalJson(value) {
31
+ if (value === null || typeof value !== "object")
32
+ return JSON.stringify(value);
33
+ if (Array.isArray(value))
34
+ return `[${value.map(legacyCanonicalJson).join(",")}]`;
18
35
  const entries = Object.entries(value).sort(([a], [b2]) => a.localeCompare(b2));
19
- return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(",")}}`;
36
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${legacyCanonicalJson(v)}`).join(",")}}`;
20
37
  }
21
38
  function contentHash(value) {
22
- return createHash("sha256").update(canonical(value)).digest("hex");
39
+ return createHash("sha256").update(canonicalJson(value)).digest("hex");
40
+ }
41
+ function legacyContentHash(value) {
42
+ return createHash("sha256").update(legacyCanonicalJson(value)).digest("hex");
23
43
  }
24
44
  function sha256(text2) {
25
45
  return createHash("sha256").update(text2, "utf8").digest("hex");
@@ -3611,8 +3631,8 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
3611
3631
  const _normalized = cached(() => normalizeDef(def));
3612
3632
  const memo2 = globalConfig.memoizer;
3613
3633
  const generateFastpass = (shape3) => {
3614
- const normalized = _normalized.value;
3615
- const syms = normalized.symbolKeys;
3634
+ const normalized2 = _normalized.value;
3635
+ const syms = normalized2.symbolKeys;
3616
3636
  const doc = new Doc(["payload", "ctx"], { shape: shape3, inst, memo: memo2, syms });
3617
3637
  const parseStr = (k) => `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;
3618
3638
  const prefixStr = (id, k) => `
@@ -3630,11 +3650,11 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
3630
3650
  doc.write(`const input = payload.value;`);
3631
3651
  const ids = /* @__PURE__ */ Object.create(null);
3632
3652
  let counter = 0;
3633
- for (const key2 of normalized.allKeys) {
3653
+ for (const key2 of normalized2.allKeys) {
3634
3654
  ids[key2] = `key_${counter++}`;
3635
3655
  }
3636
3656
  doc.write(memo2 ? `const newResult = memo.alloc(inst, payload, {}, ctx);` : `const newResult = {};`);
3637
- for (const key2 of normalized.allKeys) {
3657
+ for (const key2 of normalized2.allKeys) {
3638
3658
  if (key2 === "__proto__")
3639
3659
  continue;
3640
3660
  const id = ids[key2];
@@ -19463,23 +19483,23 @@ function fromJSONSchema(schema, params) {
19463
19483
  if (typeof schema === "boolean") {
19464
19484
  return schema ? z.any() : z.never();
19465
19485
  }
19466
- let normalized;
19486
+ let normalized2;
19467
19487
  try {
19468
- normalized = JSON.parse(JSON.stringify(schema));
19488
+ normalized2 = JSON.parse(JSON.stringify(schema));
19469
19489
  } catch {
19470
19490
  throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas");
19471
19491
  }
19472
- const version2 = detectVersion(normalized, params?.defaultTarget);
19473
- const defs = normalized.$defs || normalized.definitions || {};
19492
+ const version2 = detectVersion(normalized2, params?.defaultTarget);
19493
+ const defs = normalized2.$defs || normalized2.definitions || {};
19474
19494
  const ctx = {
19475
19495
  version: version2,
19476
19496
  defs,
19477
19497
  refs: /* @__PURE__ */ new Map(),
19478
19498
  processing: /* @__PURE__ */ new Set(),
19479
- rootSchema: normalized,
19499
+ rootSchema: normalized2,
19480
19500
  registry: params?.registry ?? globalRegistry
19481
19501
  };
19482
- return convertSchema(normalized, ctx);
19502
+ return convertSchema(normalized2, ctx);
19483
19503
  }
19484
19504
 
19485
19505
  // ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/visit.js
@@ -21403,15 +21423,15 @@ function asNumber(value) {
21403
21423
  return numericValue;
21404
21424
  }
21405
21425
  function normalizeObject(obj, normalizer) {
21406
- const normalized = {};
21426
+ const normalized2 = {};
21407
21427
  for (const u in obj) {
21408
21428
  if (hasOwnProperty(obj, u)) {
21409
21429
  const v = obj[u];
21410
21430
  if (v === void 0 || v === null) continue;
21411
- normalized[normalizer(u)] = asNumber(v);
21431
+ normalized2[normalizer(u)] = asNumber(v);
21412
21432
  }
21413
21433
  }
21414
- return normalized;
21434
+ return normalized2;
21415
21435
  }
21416
21436
  function formatOffset(offset2, format) {
21417
21437
  const hours = Math.trunc(Math.abs(offset2 / 60)), minutes = Math.trunc(Math.abs(offset2 % 60)), sign = offset2 >= 0 ? "+" : "-";
@@ -22412,7 +22432,7 @@ var Duration = class _Duration {
22412
22432
  * @private
22413
22433
  */
22414
22434
  static normalizeUnit(unit) {
22415
- const normalized = {
22435
+ const normalized2 = {
22416
22436
  year: "years",
22417
22437
  years: "years",
22418
22438
  quarter: "quarters",
@@ -22432,8 +22452,8 @@ var Duration = class _Duration {
22432
22452
  millisecond: "milliseconds",
22433
22453
  milliseconds: "milliseconds"
22434
22454
  }[unit ? unit.toLowerCase() : unit];
22435
- if (!normalized) throw new InvalidUnitError(unit);
22436
- return normalized;
22455
+ if (!normalized2) throw new InvalidUnitError(unit);
22456
+ return normalized2;
22437
22457
  }
22438
22458
  /**
22439
22459
  * Check if an object is a Duration. Works across context boundaries
@@ -24336,7 +24356,7 @@ var orderedWeekUnits = [
24336
24356
  ];
24337
24357
  var orderedOrdinalUnits = ["year", "ordinal", "hour", "minute", "second", "millisecond"];
24338
24358
  function normalizeUnit(unit) {
24339
- const normalized = {
24359
+ const normalized2 = {
24340
24360
  year: "year",
24341
24361
  years: "year",
24342
24362
  month: "month",
@@ -24362,8 +24382,8 @@ function normalizeUnit(unit) {
24362
24382
  weekyears: "weekYear",
24363
24383
  ordinal: "ordinal"
24364
24384
  }[unit.toLowerCase()];
24365
- if (!normalized) throw new InvalidUnitError(unit);
24366
- return normalized;
24385
+ if (!normalized2) throw new InvalidUnitError(unit);
24386
+ return normalized2;
24367
24387
  }
24368
24388
  function normalizeUnitWithLocalWeeks(unit) {
24369
24389
  switch (unit.toLowerCase()) {
@@ -24662,9 +24682,9 @@ var DateTime = class _DateTime {
24662
24682
  return _DateTime.invalid(unsupportedZone(zoneToUse));
24663
24683
  }
24664
24684
  const loc = Locale.fromObject(opts);
24665
- const normalized = normalizeObject(obj, normalizeUnitWithLocalWeeks);
24666
- const { minDaysInFirstWeek, startOfWeek } = usesLocalWeekValues(normalized, loc);
24667
- const tsNow = Settings.now(), offsetProvis = !isUndefined(opts.specificOffset) ? opts.specificOffset : zoneToUse.offset(tsNow), containsOrdinal = !isUndefined(normalized.ordinal), containsGregorYear = !isUndefined(normalized.year), containsGregorMD = !isUndefined(normalized.month) || !isUndefined(normalized.day), containsGregor = containsGregorYear || containsGregorMD, definiteWeekDef = normalized.weekYear || normalized.weekNumber;
24685
+ const normalized2 = normalizeObject(obj, normalizeUnitWithLocalWeeks);
24686
+ const { minDaysInFirstWeek, startOfWeek } = usesLocalWeekValues(normalized2, loc);
24687
+ const tsNow = Settings.now(), offsetProvis = !isUndefined(opts.specificOffset) ? opts.specificOffset : zoneToUse.offset(tsNow), containsOrdinal = !isUndefined(normalized2.ordinal), containsGregorYear = !isUndefined(normalized2.year), containsGregorMD = !isUndefined(normalized2.month) || !isUndefined(normalized2.day), containsGregor = containsGregorYear || containsGregorMD, definiteWeekDef = normalized2.weekYear || normalized2.weekNumber;
24668
24688
  if ((containsGregor || containsOrdinal) && definiteWeekDef) {
24669
24689
  throw new ConflictingSpecificationError(
24670
24690
  "Can't mix weekYear/weekNumber units with year/month/day or ordinals"
@@ -24673,7 +24693,7 @@ var DateTime = class _DateTime {
24673
24693
  if (containsGregorMD && containsOrdinal) {
24674
24694
  throw new ConflictingSpecificationError("Can't mix ordinal dates with month/day");
24675
24695
  }
24676
- const useWeekData = definiteWeekDef || normalized.weekday && !containsGregor;
24696
+ const useWeekData = definiteWeekDef || normalized2.weekday && !containsGregor;
24677
24697
  let units, defaultValues, objNow = tsToObj(tsNow, offsetProvis);
24678
24698
  if (useWeekData) {
24679
24699
  units = orderedWeekUnits;
@@ -24689,29 +24709,29 @@ var DateTime = class _DateTime {
24689
24709
  }
24690
24710
  let foundFirst = false;
24691
24711
  for (const u of units) {
24692
- const v = normalized[u];
24712
+ const v = normalized2[u];
24693
24713
  if (!isUndefined(v)) {
24694
24714
  foundFirst = true;
24695
24715
  } else if (foundFirst) {
24696
- normalized[u] = defaultValues[u];
24716
+ normalized2[u] = defaultValues[u];
24697
24717
  } else {
24698
- normalized[u] = objNow[u];
24718
+ normalized2[u] = objNow[u];
24699
24719
  }
24700
24720
  }
24701
- const higherOrderInvalid = useWeekData ? hasInvalidWeekData(normalized, minDaysInFirstWeek, startOfWeek) : containsOrdinal ? hasInvalidOrdinalData(normalized) : hasInvalidGregorianData(normalized), invalid = higherOrderInvalid || hasInvalidTimeData(normalized);
24721
+ const higherOrderInvalid = useWeekData ? hasInvalidWeekData(normalized2, minDaysInFirstWeek, startOfWeek) : containsOrdinal ? hasInvalidOrdinalData(normalized2) : hasInvalidGregorianData(normalized2), invalid = higherOrderInvalid || hasInvalidTimeData(normalized2);
24702
24722
  if (invalid) {
24703
24723
  return _DateTime.invalid(invalid);
24704
24724
  }
24705
- const gregorian = useWeekData ? weekToGregorian(normalized, minDaysInFirstWeek, startOfWeek) : containsOrdinal ? ordinalToGregorian(normalized) : normalized, [tsFinal, offsetFinal] = objToTS(gregorian, offsetProvis, zoneToUse), inst = new _DateTime({
24725
+ const gregorian = useWeekData ? weekToGregorian(normalized2, minDaysInFirstWeek, startOfWeek) : containsOrdinal ? ordinalToGregorian(normalized2) : normalized2, [tsFinal, offsetFinal] = objToTS(gregorian, offsetProvis, zoneToUse), inst = new _DateTime({
24706
24726
  ts: tsFinal,
24707
24727
  zone: zoneToUse,
24708
24728
  o: offsetFinal,
24709
24729
  loc
24710
24730
  });
24711
- if (normalized.weekday && containsGregor && obj.weekday !== inst.weekday) {
24731
+ if (normalized2.weekday && containsGregor && obj.weekday !== inst.weekday) {
24712
24732
  return _DateTime.invalid(
24713
24733
  "mismatched weekday",
24714
- `you can't specify both a weekday of ${normalized.weekday} and a date of ${inst.toISO()}`
24734
+ `you can't specify both a weekday of ${normalized2.weekday} and a date of ${inst.toISO()}`
24715
24735
  );
24716
24736
  }
24717
24737
  if (!inst.isValid) {
@@ -25360,9 +25380,9 @@ var DateTime = class _DateTime {
25360
25380
  */
25361
25381
  set(values2) {
25362
25382
  if (!this.isValid) return this;
25363
- const normalized = normalizeObject(values2, normalizeUnitWithLocalWeeks);
25364
- const { minDaysInFirstWeek, startOfWeek } = usesLocalWeekValues(normalized, this.loc);
25365
- const settingWeekStuff = !isUndefined(normalized.weekYear) || !isUndefined(normalized.weekNumber) || !isUndefined(normalized.weekday), containsOrdinal = !isUndefined(normalized.ordinal), containsGregorYear = !isUndefined(normalized.year), containsGregorMD = !isUndefined(normalized.month) || !isUndefined(normalized.day), containsGregor = containsGregorYear || containsGregorMD, definiteWeekDef = normalized.weekYear || normalized.weekNumber;
25383
+ const normalized2 = normalizeObject(values2, normalizeUnitWithLocalWeeks);
25384
+ const { minDaysInFirstWeek, startOfWeek } = usesLocalWeekValues(normalized2, this.loc);
25385
+ const settingWeekStuff = !isUndefined(normalized2.weekYear) || !isUndefined(normalized2.weekNumber) || !isUndefined(normalized2.weekday), containsOrdinal = !isUndefined(normalized2.ordinal), containsGregorYear = !isUndefined(normalized2.year), containsGregorMD = !isUndefined(normalized2.month) || !isUndefined(normalized2.day), containsGregor = containsGregorYear || containsGregorMD, definiteWeekDef = normalized2.weekYear || normalized2.weekNumber;
25366
25386
  if ((containsGregor || containsOrdinal) && definiteWeekDef) {
25367
25387
  throw new ConflictingSpecificationError(
25368
25388
  "Can't mix weekYear/weekNumber units with year/month/day or ordinals"
@@ -25374,15 +25394,15 @@ var DateTime = class _DateTime {
25374
25394
  let mixed;
25375
25395
  if (settingWeekStuff) {
25376
25396
  mixed = weekToGregorian(
25377
- { ...gregorianToWeek(this.c, minDaysInFirstWeek, startOfWeek), ...normalized },
25397
+ { ...gregorianToWeek(this.c, minDaysInFirstWeek, startOfWeek), ...normalized2 },
25378
25398
  minDaysInFirstWeek,
25379
25399
  startOfWeek
25380
25400
  );
25381
- } else if (!isUndefined(normalized.ordinal)) {
25382
- mixed = ordinalToGregorian({ ...gregorianToOrdinal(this.c), ...normalized });
25401
+ } else if (!isUndefined(normalized2.ordinal)) {
25402
+ mixed = ordinalToGregorian({ ...gregorianToOrdinal(this.c), ...normalized2 });
25383
25403
  } else {
25384
- mixed = { ...this.toObject(), ...normalized };
25385
- if (isUndefined(normalized.day)) {
25404
+ mixed = { ...this.toObject(), ...normalized2 };
25405
+ if (isUndefined(normalized2.day)) {
25386
25406
  mixed.day = Math.min(daysInMonth(mixed.year, mixed.month), mixed.day);
25387
25407
  }
25388
25408
  }
@@ -28116,8 +28136,8 @@ function idFromDigest(bytes) {
28116
28136
  function extensionIdForPath(absDir, platform2 = process.platform) {
28117
28137
  if (platform2 !== "win32")
28118
28138
  return idFromDigest(Buffer.from(absDir, "utf8"));
28119
- const normalized = /^[a-z]:/.test(absDir) ? absDir[0].toUpperCase() + absDir.slice(1) : absDir;
28120
- return idFromDigest(Buffer.from(normalized, "utf16le"));
28139
+ const normalized2 = /^[a-z]:/.test(absDir) ? absDir[0].toUpperCase() + absDir.slice(1) : absDir;
28140
+ return idFromDigest(Buffer.from(normalized2, "utf16le"));
28121
28141
  }
28122
28142
  function extensionIdForKey(base64Key) {
28123
28143
  return idFromDigest(Buffer.from(base64Key.replace(/\s+/g, ""), "base64"));
@@ -33945,18 +33965,18 @@ function stepIdsOf(rec) {
33945
33965
  ids.add(s3.stepId);
33946
33966
  return ids;
33947
33967
  }
33948
- function canonicalJson(v) {
33968
+ function canonicalJson2(v) {
33949
33969
  if (Array.isArray(v))
33950
- return `[${v.map(canonicalJson).join(",")}]`;
33970
+ return `[${v.map(canonicalJson2).join(",")}]`;
33951
33971
  if (v !== null && typeof v === "object") {
33952
33972
  const o = v;
33953
- return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(o[k])}`).join(",")}}`;
33973
+ return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson2(o[k])}`).join(",")}}`;
33954
33974
  }
33955
33975
  return JSON.stringify(v) ?? "null";
33956
33976
  }
33957
33977
  function seededRandom(rec) {
33958
33978
  let h = 2166136261;
33959
- const text2 = canonicalJson(rec);
33979
+ const text2 = canonicalJson2(rec);
33960
33980
  for (let i = 0; i < text2.length; i++) {
33961
33981
  h ^= text2.charCodeAt(i);
33962
33982
  h = Math.imul(h, 16777619) >>> 0;
@@ -37195,8 +37215,8 @@ function isSecretField(inputType, autocomplete, hints) {
37195
37215
  const raw = list2[i];
37196
37216
  if (typeof raw !== "string" || raw === "")
37197
37217
  continue;
37198
- const normalized = raw.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/([A-Za-z])([0-9])/g, "$1 $2").replace(/[_\-.:[\]/]+/g, " ").toLowerCase();
37199
- if (credential.test(normalized))
37218
+ const normalized2 = raw.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/([A-Za-z])([0-9])/g, "$1 $2").replace(/[_\-.:[\]/]+/g, " ").toLowerCase();
37219
+ if (credential.test(normalized2))
37200
37220
  return true;
37201
37221
  }
37202
37222
  return false;
@@ -38621,8 +38641,8 @@ async function readSnapshot(page, opts) {
38621
38641
  // ../explore/dist/settle-config.js
38622
38642
  var DEFAULT_LONG_POLL_MS = 5e3;
38623
38643
  function globToRegExp2(glob) {
38624
- const escaped = glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
38625
- return new RegExp(`^${escaped}$`);
38644
+ const escaped2 = glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
38645
+ return new RegExp(`^${escaped2}$`);
38626
38646
  }
38627
38647
  function urlMatcher(patterns) {
38628
38648
  const compiled = (patterns ?? []).map((p) => ({ full: p.includes("://"), re: globToRegExp2(p) }));
@@ -39557,6 +39577,31 @@ function visibleBusyIndicatorContainer() {
39557
39577
  }
39558
39578
  return null;
39559
39579
  }
39580
+ function visibleBusyIndicatorText() {
39581
+ const shown2 = (el) => {
39582
+ const r = el.getBoundingClientRect();
39583
+ const s3 = window.getComputedStyle(el);
39584
+ return r.width > 0 && r.height > 0 && s3.visibility !== "hidden" && s3.display !== "none" && s3.opacity !== "0";
39585
+ };
39586
+ const progressbar = '[role="progressbar"]:not([aria-valuenow])';
39587
+ const selectors = ['[aria-busy="true"]', progressbar, '[class*="spinner" i]', '[class*="animate-spin" i]'];
39588
+ const notificationContainer = '[role="alert"], [data-sonner-toast], [class*="toast" i], [class*="snackbar" i], [class*="notification" i]';
39589
+ const timerLabel = /timer|countdown|auto.?close/i;
39590
+ const isNotificationTimer = (el) => el.closest(notificationContainer) !== null || timerLabel.test(`${el.getAttribute("aria-label") ?? ""} ${el.getAttribute("title") ?? ""}`);
39591
+ const words3 = (el) => el === null ? "" : (el.getAttribute("aria-label") ?? el.innerText ?? el.textContent ?? "").replace(/\s+/g, " ").trim();
39592
+ for (const sel of selectors) {
39593
+ for (const el of Array.from(document.querySelectorAll(sel))) {
39594
+ if (!shown2(el))
39595
+ continue;
39596
+ if (sel === progressbar && isNotificationTimer(el))
39597
+ continue;
39598
+ const own2 = words3(el);
39599
+ const text2 = own2 !== "" ? own2 : words3(el.closest('[role="status"], [aria-live]:not([aria-live="off"])'));
39600
+ return text2 === "" ? null : text2.slice(0, 200);
39601
+ }
39602
+ }
39603
+ return null;
39604
+ }
39560
39605
  function pendingEvidence(pending, now2) {
39561
39606
  return [...pending].sort((a, b2) => a.startedAt - b2.startedAt).map((r) => ({ endpoint: endpointOf(r.method, r.url), url: redactUrl(r.url), ageMs: Math.max(0, now2 - r.startedAt) }));
39562
39607
  }
@@ -40808,7 +40853,8 @@ async function perceive(page, opts = {}) {
40808
40853
  const busySince = clock.now();
40809
40854
  const remaining = Math.max(1, ceiling - (clock.now() - started));
40810
40855
  const gone = await clockBounded(page.waitForFunction(visibleBusyIndicator, true, { timeout: remaining, polling: "raf" }).then(() => true, (e) => !(e instanceof Error && e.name === "TimeoutError")), remaining, false);
40811
- if (!gone && !ignoreNoProgress(busy) && !ignoreNoProgress(hangRoute(page.url()))) {
40856
+ const busyText = gone ? null : await page.evaluate(visibleBusyIndicatorText).catch(() => null);
40857
+ if (!gone && !ignoreNoProgress(busy) && !(busyText !== null && ignoreDeclared(busyText)) && !ignoreNoProgress(hangRoute(page.url()))) {
40812
40858
  stuckBusy = busy;
40813
40859
  stuckBusyContainer = await page.evaluate(visibleBusyIndicatorContainer).catch(() => null);
40814
40860
  const now2 = await page.evaluate(visibleBusyIndicator).catch(() => null);
@@ -42172,6 +42218,7 @@ async function judgeGoalCompletion(judge, input2) {
42172
42218
  ...input2.signInFacts === void 0 ? [] : [`SIGN-IN (observed by code, trusted): ${input2.signInFacts}`],
42173
42219
  ...input2.saveFacts === void 0 ? [] : [`SAVE (observed by code, trusted): ${input2.saveFacts}`],
42174
42220
  ...input2.writeFacts === void 0 ? [] : [`WRITES (observed by code, trusted): ${input2.writeFacts}`],
42221
+ ...input2.readBackFacts === void 0 ? [] : [`READ-BACK (observed by code, trusted): ${input2.readBackFacts}`],
42175
42222
  ...input2.pageStatus === void 0 || input2.pageStatus === "" ? [] : [`PAGE STATUS (untrusted): ${input2.pageStatus}`],
42176
42223
  `VISIBLE PAGE TEXT (untrusted): ${input2.pageText.replace(/\s+/g, " ").slice(0, GOAL_TEXT_CHARS)}`,
42177
42224
  ...input2.fieldValues === void 0 || input2.fieldValues.length === 0 ? [] : [
@@ -44090,6 +44137,7 @@ var MAX_REPEAT_TYPE_SIGNALS = 3;
44090
44137
  var MAX_QUIET_WAITS = 3;
44091
44138
  var MAX_MOVING_SCROLLS = 12;
44092
44139
  var LAST_CHANCE_NOTE = "no progress: the last steps left the page unchanged and you have seen the whole page \u2014 act on a visible control, report the answer, or say done/blocked now";
44140
+ var LAST_CHANCE_NO_WRITE_NOTE = "no progress: the last steps left the page unchanged and you have seen the whole page, but no write of this run has succeeded yet \u2014 the goal asks for a change the app must receive: go back and press the control that submits it (Import, Save, Send\u2026), or say blocked if the app offers none";
44093
44141
 
44094
44142
  // ../explore/dist/run-depth.js
44095
44143
  var OPEN_ENDED_MIN_ACTIONS = 12;
@@ -44964,11 +45012,11 @@ function hostProbe(signals = createResourceSignals(), thresholds = DEFAULT_PRESS
44964
45012
  function initHost(ctx, cfg) {
44965
45013
  ctx.probeHost = cfg.hostProbe ?? hostProbe();
44966
45014
  ctx.judgeHost = async () => cfg.hostHealth === void 0 ? { host: await ctx.probeHost(), starved: null } : cfg.hostHealth.judge();
44967
- ctx.degradedStop = (finding3, detail, starved) => {
44968
- cfg.hostHealth?.markDegraded({ finding: finding3, detail, step: Math.max(0, ctx.transcript.entries().length - 1) }, starved);
45015
+ ctx.degradedStop = (finding4, detail, starved) => {
45016
+ cfg.hostHealth?.markDegraded({ finding: finding4, detail, step: Math.max(0, ctx.transcript.entries().length - 1) }, starved);
44969
45017
  ctx.failure = {
44970
45018
  kind: "degraded-environment",
44971
- message: `environment-degraded ${finding3} (${detail}) while the host was starved: ${starved} \u2014 not an app finding`
45019
+ message: `environment-degraded ${finding4} (${detail}) while the host was starved: ${starved} \u2014 not an app finding`
44972
45020
  };
44973
45021
  ctx.stop = "inconclusive";
44974
45022
  };
@@ -45379,6 +45427,7 @@ function initLoopState(ctx) {
45379
45427
  ctx.fixtureAttached = false;
45380
45428
  ctx.hang = void 0;
45381
45429
  ctx.outcome = null;
45430
+ ctx.actionableControl = null;
45382
45431
  ctx.incomplete = null;
45383
45432
  ctx.endedOnRejectedDone = false;
45384
45433
  }
@@ -45607,6 +45656,7 @@ var OBSERVED_PAGE_CHARS = 2e4;
45607
45656
  var MIN_QUOTE_CHARS = 3;
45608
45657
  var MAX_OBSERVED_FIELDS = 40;
45609
45658
  var OBSERVED_FIELD_CHARS = 500;
45659
+ var MAX_READ_BACK_VALUES = 50;
45610
45660
  var MAX_OBSERVED_CONTROLS = 120;
45611
45661
  var HEADING_CHARS = 200;
45612
45662
  function controlFields(controls) {
@@ -45696,6 +45746,21 @@ var ObservedPages = class {
45696
45746
  this.#sourceRoute.set(v, pending.route);
45697
45747
  }
45698
45748
  }
45749
+ /**
45750
+ * #671: values the run typed into a field on one document load. A field holding one shows the run's
45751
+ * own entry, never what the app kept: retyping a sent message into its composer after a reload is
45752
+ * not a read-back.
45753
+ */
45754
+ #typedOn = null;
45755
+ /** #671: the run typed `value` into a field (a message composer too) on document load `document`. */
45756
+ noteTypedOn(value, document2) {
45757
+ const v = fold(redactContext(value, this.secrets).slice(0, OBSERVED_FIELD_CHARS));
45758
+ if (document2 === void 0 || v === "")
45759
+ return;
45760
+ if (this.#typedOn?.document !== document2)
45761
+ this.#typedOn = { document: document2, values: /* @__PURE__ */ new Set() };
45762
+ this.#typedOn.values.add(v);
45763
+ }
45699
45764
  /** #647: the run's sent values (folded) that no page loaded from the server since has shown. */
45700
45765
  unreadWrites() {
45701
45766
  return this.#unread;
@@ -45720,10 +45785,19 @@ var ObservedPages = class {
45720
45785
  this.#sourceRoute.delete(v);
45721
45786
  }
45722
45787
  this.#readBackCount += back.length;
45788
+ for (const v of back)
45789
+ if (this.#readBackValues.length < MAX_READ_BACK_VALUES)
45790
+ this.#readBackValues.push(v);
45723
45791
  return back;
45724
45792
  }
45725
45793
  /** #645: how many sent values a page loaded from the server has shown again. */
45726
45794
  #readBackCount = 0;
45795
+ /** #667: the sent values (folded) read back so far, bounded. */
45796
+ #readBackValues = [];
45797
+ /** #667: the sent values (folded) a page loaded from the server has shown again. */
45798
+ readBackValues() {
45799
+ return this.#readBackValues;
45800
+ }
45727
45801
  /**
45728
45802
  * #645: the app was seen to keep what the run's writes sent — at least one value was read back and
45729
45803
  * none is still unread. Code evidence the page judgment lacks (see `groundDone`).
@@ -45766,7 +45840,9 @@ var ObservedPages = class {
45766
45840
  this.#topNav = nav;
45767
45841
  }
45768
45842
  this.#settleSent(page.text);
45769
- this.readBack(headings2.document, [page.text, ...kept.map((f2) => f2.value)].join("\n"), url2);
45843
+ const typed = this.#typedOn !== null && this.#typedOn.document === headings2.document ? [...this.#typedOn.values] : [];
45844
+ const appFields = kept.map((f2) => f2.value).filter((value) => !typed.some((t) => fold(value).includes(t)));
45845
+ this.readBack(headings2.document, [page.text, ...appFields].join("\n"), url2);
45770
45846
  if (page.text.trim() === "" && kept.length === 0)
45771
45847
  return;
45772
45848
  const same = (p) => JSON.stringify(p.fields ?? []) === JSON.stringify(page.fields ?? []) && p.heading === page.heading && p.title === page.title && p.status === page.status && JSON.stringify(p.controls ?? []) === JSON.stringify(page.controls ?? []) && JSON.stringify(p.contentLinks ?? []) === JSON.stringify(page.contentLinks ?? []) && p.inventoryIncomplete === page.inventoryIncomplete;
@@ -46431,7 +46507,7 @@ var ABSENCE_GOAL = /\bwhether\b|\b(?:is|are) there\b|\bthere (?:is|are) (?:no|no
46431
46507
  function goalAdmitsAbsence(goal) {
46432
46508
  return ABSENCE_GOAL.test(goal);
46433
46509
  }
46434
- var WRITE_GOAL = /(?:^\s*|[.!?;:]\s+|\b(?:then|and|please)\s+)(?:record|save|create|add|invite|submit|send|post|publish|register|book|schedule)\b/i;
46510
+ var WRITE_GOAL = /(?:^\s*|[.!?;:]\s+|\b(?:then|and|please)\s+)(?:record|save|create|add|invite|submit|send|post|publish|register|book|schedule|import|upload|update|change|edit|rename|delete|remove)\b/i;
46435
46511
  function goalAsksToWrite(goal) {
46436
46512
  return WRITE_GOAL.test(goal);
46437
46513
  }
@@ -46718,7 +46794,7 @@ function busyIndicatorOf(hang) {
46718
46794
  return null;
46719
46795
  if (hang.element !== void 0 && hang.element !== "")
46720
46796
  return hang.element;
46721
- const m = /^a busy indicator \((.+)\) never went away within \d+ms(?: \(still so after .*\))?$/.exec(hang.detail.trim());
46797
+ const m = /^a busy indicator \((.+)\) never went away within \d+ms(?: \((?:still so after|the page stopped showing work) .*\))?$/.exec(hang.detail.trim());
46722
46798
  return m?.[1] ?? null;
46723
46799
  }
46724
46800
  function legacySelectorForm(indicator) {
@@ -46939,8 +47015,8 @@ async function recordCoverageHang(p) {
46939
47015
  ...p.perceive === void 0 ? {} : { perceive: p.perceive },
46940
47016
  ...p.safety === void 0 ? {} : { safety: p.safety }
46941
47017
  });
46942
- const finding3 = hangFinding(hang, p.steps, index, reproduction);
46943
- p.found.set(fingerprint2, { ...finding3, repro: { ...finding3.repro, recording: p.recording } });
47018
+ const finding4 = hangFinding(hang, p.steps, index, reproduction);
47019
+ p.found.set(fingerprint2, { ...finding4, repro: { ...finding4.repro, recording: p.recording } });
46944
47020
  return "recorded";
46945
47021
  }
46946
47022
 
@@ -48130,6 +48206,7 @@ function initGuards(ctx, cfg) {
48130
48206
  ctx.jobWaitedMs = 0;
48131
48207
  ctx.overlayWaitedMs = 0;
48132
48208
  ctx.hangWorkWaitedMs = 0;
48209
+ ctx.hangWorkSince = null;
48133
48210
  ctx.documentedBudgetMs = 0;
48134
48211
  }
48135
48212
 
@@ -48275,6 +48352,7 @@ function newStep(ctx, input2) {
48275
48352
  if (proposedDone)
48276
48353
  writes = jobWritesOf(ctx);
48277
48354
  const writeFacts = writes !== void 0 && saved === null && writesAllSucceeded(writes) ? writes.facts : void 0;
48355
+ const readBackFacts = ctx.observed.readBackConfirmed() ? readBackFactsOf(ctx.observed.readBackValues(), pageText) : void 0;
48278
48356
  const judged = await judgeGoalCompletion(cfg.judge, {
48279
48357
  goal: cfg.goal,
48280
48358
  url: snap.url,
@@ -48285,6 +48363,7 @@ function newStep(ctx, input2) {
48285
48363
  ...signIn === null ? {} : { signInFacts: signIn.facts },
48286
48364
  ...saved === null ? {} : { saveFacts: saved.facts },
48287
48365
  ...writeFacts === void 0 ? {} : { writeFacts },
48366
+ ...readBackFacts === void 0 ? {} : { readBackFacts },
48288
48367
  fieldValues: fieldValuesOf(snap.controls, ctx.isBound)
48289
48368
  }).catch(() => ({ goalMet: null, goalIsSignIn: null, goalIsSave: null, goalIsWrites: null }));
48290
48369
  goalMet = judged.goalMet;
@@ -48315,6 +48394,11 @@ function newStep(ctx, input2) {
48315
48394
  };
48316
48395
  return { ...input2, record: record2, signIn, pendingNote, acceptedBy, groundGoal };
48317
48396
  }
48397
+ function readBackFactsOf(values2, pageText) {
48398
+ const text2 = pageText.toLowerCase();
48399
+ const shown2 = values2.filter((v) => text2.includes(v)).length;
48400
+ return `every value this run's writes sent (${values2.length}) was shown again on a page loaded from the server after the write \u2014 the app kept them; this page shows ${shown2} of them`;
48401
+ }
48318
48402
 
48319
48403
  // ../explore/dist/goal-loop/min-effort.js
48320
48404
  function deferEnding(ctx, step, ending) {
@@ -48363,87 +48447,8 @@ function unreadShown(ctx, step) {
48363
48447
  return ctx.observed.unreadShownIn(shownOn(step));
48364
48448
  }
48365
48449
 
48366
- // ../explore/dist/goal-loop/handle-report.js
48367
- async function handleReport(ctx, step) {
48368
- const { cfg } = ctx;
48369
- const { snap, record: record2 } = step;
48370
- if (ctx.replyGoal) {
48371
- if (ctx.awaitingReply && ctx.lastTurn !== null && ctx.replyWaitedMs < ctx.replyWaitMs) {
48372
- const t0 = ctx.now();
48373
- const listen = ctx.replyWaitMs - ctx.replyWaitedMs;
48374
- const reply = await waitForReply(ctx.page, { secrets: ctx.secrets, ...ctx.lastTurn, timeoutMs: listen, ceilingMs: listen, quietMs: ctx.replyQuietMs });
48375
- ctx.replyWaitedMs += ctx.now() - t0;
48376
- if (reply.received) {
48377
- ctx.conversation.latestReply = reply.text;
48378
- ctx.replies.add(snap.url, reply.text);
48379
- ctx.awaitingReply = false;
48380
- ctx.busyWaitedMs = 0;
48381
- ctx.history.push(`waited for the reply \u2192 reply: ${quote(reply.text, 300)}`);
48382
- }
48383
- }
48384
- ctx.noteReplyText(snap.url, await readPageText(ctx.page, ctx.secrets));
48385
- }
48386
- const replyPages = ctx.replyGoal ? ctx.replies.pages() : null;
48387
- if (replyPages === null)
48388
- await readBackProbe(ctx, step);
48389
- const verdict = replyPages !== null && replyPages.length === 0 ? {
48390
- accept: false,
48391
- reason: ctx.preSend === null ? "no reply observed: no message was sent yet \u2014 text on the page before the conversation is not a reply" : `no reply observed: no new message appeared after the send within the reply wait (${Math.round(ctx.replyWaitMs / 1e3)}s)`,
48392
- answer: null
48393
- } : await reportAnswer(cfg.gen, {
48394
- goal: cfg.goal,
48395
- url: snap.url,
48396
- pages: replyPages ?? ctx.observed.pages(),
48397
- history: ctx.history,
48398
- secrets: ctx.secrets,
48399
- judge: cfg.judge,
48400
- vetoes: ctx.vetoes,
48401
- // #238: "none exists" is an answer only on observed pages that cover the app enough.
48402
- ...replyPages === null ? { topNav: ctx.observed.topNavigation(), ownInputs: ctx.observed.ownInputs(), unreadWrites: ctx.observed.unreadWrites() } : {}
48403
- }).then((v) => v.accept && ctx.writeGoal && !ctx.wroteOk && v.answer.absent !== true ? { accept: false, reason: UNSAVED_WRITE_REASON, answer: v.answer } : v).catch((e) => ({ accept: false, reason: `no answer could be generated: ${firstLine(e)}`, answer: null }));
48404
- if (verdict.accept) {
48405
- const deferred = deferEnding(ctx, step, "report");
48406
- if (deferred !== null) {
48407
- ctx.deferredAnswer = verdict.answer;
48408
- record2(false, deferred, { answer: verdict.answer, origin: "engine" });
48409
- return "continue";
48410
- }
48411
- }
48412
- if (verdict.accept) {
48413
- const on = replyPages === null ? "the observed pages" : "the reply observed after the send";
48414
- record2(true, `report accepted: answer grounded on ${on} (${verdict.answer.evidence.length} claim(s))`, {
48415
- answer: verdict.answer
48416
- });
48417
- ctx.answer = verdict.answer;
48418
- ctx.outcome = { status: "completed", verifiedBy: "grounded-answer" };
48419
- ctx.stop = "done";
48420
- return "stop";
48421
- }
48422
- if (verdict.unconfirmedWrite === true && !ctx.readBackAsked) {
48423
- ctx.readBackAsked = true;
48424
- ctx.history.push(`report deferred: ${verdict.reason}`);
48425
- record2(false, `report deferred: ${verdict.reason}`, { answer: verdict.answer, origin: "engine" });
48426
- return "continue";
48427
- }
48428
- ctx.reportRejections += 1;
48429
- for (const e of verdict.answer?.evidence ?? [])
48430
- if (e.grounded)
48431
- ctx.reportClaims.push(e);
48432
- ctx.lastReportNotFound = verdict.answer === null && verdict.reason === NO_ANSWER_REASON || verdict.notAnswer === true;
48433
- ctx.lastAbsenceUncovered = verdict.absenceUncovered === true ? verdict.reason : null;
48434
- ctx.history.push(`report rejected: ${verdict.reason} \u2014 find the answer on the page before reporting`);
48435
- record2(false, `report rejected (${ctx.reportRejections}/${MAX_REPORT_REJECTIONS}): ${verdict.reason}`, {
48436
- answer: verdict.answer
48437
- });
48438
- if (ctx.reportRejections >= MAX_REPORT_REJECTIONS && deferEnding(ctx, step, "report") === null) {
48439
- ctx.incomplete = `the model reported an answer ${ctx.reportRejections} times, but ${verdict.reason}`;
48440
- ctx.stop = "blocked";
48441
- return "stop";
48442
- }
48443
- return "continue";
48444
- }
48445
-
48446
48450
  // ../explore/dist/goal-loop/handle-done.js
48451
+ var NO_WRITE_YET = "no write of this run has succeeded yet \u2014 the goal asks for a change the app must receive: go back and press the control that submits it (Import, Save, Send\u2026)";
48447
48452
  async function unreadWriteHold(ctx, step, outcome) {
48448
48453
  if (outcome.status === "completed" && outcome.verifiedBy === "success-condition")
48449
48454
  return null;
@@ -48535,12 +48540,13 @@ async function handleDone(ctx, step) {
48535
48540
  }
48536
48541
  }
48537
48542
  ctx.doneRejections += 1;
48538
- ctx.history.push(`done rejected: ${verdict.reason} \u2014 keep working toward the goal`);
48539
- record2(false, `done rejected (${ctx.doneRejections}/${MAX_DONE_REJECTIONS}): ${verdict.reason}`, {
48543
+ const why = ctx.writeGoal && !ctx.wroteOk ? `${verdict.reason}; ${NO_WRITE_YET}` : verdict.reason;
48544
+ ctx.history.push(`done rejected: ${why} \u2014 keep working toward the goal`);
48545
+ record2(false, `done rejected (${ctx.doneRejections}/${MAX_DONE_REJECTIONS}): ${why}`, {
48540
48546
  ...judgments === void 0 ? {} : { judgments }
48541
48547
  });
48542
48548
  if (ctx.doneRejections >= MAX_DONE_REJECTIONS) {
48543
- ctx.incomplete = `the model proposed done ${ctx.doneRejections} times, but ${verdict.reason}`;
48549
+ ctx.incomplete = `the model proposed done ${ctx.doneRejections} times, but ${why}`;
48544
48550
  ctx.endedOnRejectedDone = true;
48545
48551
  ctx.stop = "done";
48546
48552
  return "stop";
@@ -48550,7 +48556,146 @@ async function handleDone(ctx, step) {
48550
48556
  return "next";
48551
48557
  }
48552
48558
 
48559
+ // ../explore/dist/goal-loop/handle-report.js
48560
+ async function handleReport(ctx, step) {
48561
+ const { cfg } = ctx;
48562
+ const { snap, record: record2 } = step;
48563
+ if (ctx.replyGoal) {
48564
+ if (ctx.awaitingReply && ctx.lastTurn !== null && ctx.replyWaitedMs < ctx.replyWaitMs) {
48565
+ const t0 = ctx.now();
48566
+ const listen = ctx.replyWaitMs - ctx.replyWaitedMs;
48567
+ const reply = await waitForReply(ctx.page, { secrets: ctx.secrets, ...ctx.lastTurn, timeoutMs: listen, ceilingMs: listen, quietMs: ctx.replyQuietMs });
48568
+ ctx.replyWaitedMs += ctx.now() - t0;
48569
+ if (reply.received) {
48570
+ ctx.conversation.latestReply = reply.text;
48571
+ ctx.replies.add(snap.url, reply.text);
48572
+ ctx.awaitingReply = false;
48573
+ ctx.busyWaitedMs = 0;
48574
+ ctx.history.push(`waited for the reply \u2192 reply: ${quote(reply.text, 300)}`);
48575
+ }
48576
+ }
48577
+ ctx.noteReplyText(snap.url, await readPageText(ctx.page, ctx.secrets));
48578
+ }
48579
+ const replyPages = ctx.replyGoal ? ctx.replies.pages() : null;
48580
+ if (replyPages === null)
48581
+ await readBackProbe(ctx, step);
48582
+ const verdict = replyPages !== null && replyPages.length === 0 ? {
48583
+ accept: false,
48584
+ reason: ctx.preSend === null ? "no reply observed: no message was sent yet \u2014 text on the page before the conversation is not a reply" : `no reply observed: no new message appeared after the send within the reply wait (${Math.round(ctx.replyWaitMs / 1e3)}s)`,
48585
+ answer: null
48586
+ } : await reportAnswer(cfg.gen, {
48587
+ goal: cfg.goal,
48588
+ url: snap.url,
48589
+ pages: replyPages ?? ctx.observed.pages(),
48590
+ history: ctx.history,
48591
+ secrets: ctx.secrets,
48592
+ judge: cfg.judge,
48593
+ vetoes: ctx.vetoes,
48594
+ // #238: "none exists" is an answer only on observed pages that cover the app enough.
48595
+ ...replyPages === null ? { topNav: ctx.observed.topNavigation(), ownInputs: ctx.observed.ownInputs(), unreadWrites: ctx.observed.unreadWrites() } : {}
48596
+ }).then((v) => v.accept && ctx.writeGoal && !ctx.wroteOk && v.answer.absent !== true ? { accept: false, reason: UNSAVED_WRITE_REASON, answer: v.answer } : v).catch((e) => ({ accept: false, reason: `no answer could be generated: ${firstLine(e)}`, answer: null }));
48597
+ if (verdict.accept) {
48598
+ const deferred = deferEnding(ctx, step, "report");
48599
+ if (deferred !== null) {
48600
+ ctx.deferredAnswer = verdict.answer;
48601
+ record2(false, deferred, { answer: verdict.answer, origin: "engine" });
48602
+ return "continue";
48603
+ }
48604
+ }
48605
+ if (verdict.accept) {
48606
+ const on = replyPages === null ? "the observed pages" : "the reply observed after the send";
48607
+ record2(true, `report accepted: answer grounded on ${on} (${verdict.answer.evidence.length} claim(s))`, {
48608
+ answer: verdict.answer
48609
+ });
48610
+ ctx.answer = verdict.answer;
48611
+ ctx.outcome = { status: "completed", verifiedBy: "grounded-answer" };
48612
+ ctx.stop = "done";
48613
+ return "stop";
48614
+ }
48615
+ if (verdict.unconfirmedWrite === true && !ctx.readBackAsked) {
48616
+ ctx.readBackAsked = true;
48617
+ ctx.history.push(`report deferred: ${verdict.reason}`);
48618
+ record2(false, `report deferred: ${verdict.reason}`, { answer: verdict.answer, origin: "engine" });
48619
+ return "continue";
48620
+ }
48621
+ ctx.reportRejections += 1;
48622
+ for (const e of verdict.answer?.evidence ?? [])
48623
+ if (e.grounded)
48624
+ ctx.reportClaims.push(e);
48625
+ ctx.lastReportNotFound = verdict.answer === null && verdict.reason === NO_ANSWER_REASON || verdict.notAnswer === true;
48626
+ ctx.lastAbsenceUncovered = verdict.absenceUncovered === true ? verdict.reason : null;
48627
+ const noWrite = ctx.writeGoal && !ctx.wroteOk && verdict.reason !== UNSAVED_WRITE_REASON;
48628
+ const why = noWrite ? `${verdict.reason}; ${NO_WRITE_YET}` : verdict.reason;
48629
+ ctx.history.push(`report rejected: ${why} \u2014 ${noWrite ? "make the change first, then report" : "find the answer on the page before reporting"}`);
48630
+ record2(false, `report rejected (${ctx.reportRejections}/${MAX_REPORT_REJECTIONS}): ${why}`, {
48631
+ answer: verdict.answer
48632
+ });
48633
+ if (ctx.reportRejections >= MAX_REPORT_REJECTIONS && deferEnding(ctx, step, "report") === null) {
48634
+ ctx.incomplete = `the model reported an answer ${ctx.reportRejections} times, but ${verdict.reason}`;
48635
+ ctx.stop = "blocked";
48636
+ return "stop";
48637
+ }
48638
+ return "continue";
48639
+ }
48640
+
48553
48641
  // ../explore/dist/goal-loop/handle-blocked.js
48642
+ var RECOVERY_TURNS = /* @__PURE__ */ new WeakMap();
48643
+ function takeRecoveryTurn(ctx) {
48644
+ const turn = RECOVERY_TURNS.get(ctx);
48645
+ if (turn === void 0 || !turn.pending)
48646
+ return null;
48647
+ turn.pending = false;
48648
+ return turn.safe;
48649
+ }
48650
+ function resumeIdentity(e) {
48651
+ return JSON.stringify(e.resume);
48652
+ }
48653
+ var NON_FORWARD = /^(?:back|go back|previous|return|cancel|close|dismiss|×|x|←|‹|arrow[- ]?left)\b/i;
48654
+ var DIRECTIVE = /\b(?:choose|select|pick|apply|submit|continue|confirm|save|open|click|press|start|create|send|next|review|accept|approve|add|complete|proceed|generate|upload|finish|sign|log|book|buy|upgrade|run|try|edit|set)\b/i;
48655
+ var normalized = (text2) => text2.replace(/\s+/g, " ").trim().toLowerCase();
48656
+ var escaped = (text2) => text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
48657
+ function instructionText(controls, visibleText) {
48658
+ const counts2 = /* @__PURE__ */ new Map();
48659
+ for (const c of controls) {
48660
+ const label2 = normalized(c.name);
48661
+ if (label2 !== "")
48662
+ counts2.set(label2, (counts2.get(label2) ?? 0) + 1);
48663
+ }
48664
+ let text2 = normalized(visibleText);
48665
+ for (const [label2, count] of counts2)
48666
+ for (let i = 0; i < count; i++) {
48667
+ const matches2 = [...text2.matchAll(new RegExp(`\\b${escaped(label2)}\\b`, "g"))];
48668
+ const last = matches2.at(-1);
48669
+ if (last?.index === void 0)
48670
+ break;
48671
+ text2 = `${text2.slice(0, last.index)} ${text2.slice(last.index + last[0].length)}`.replace(/\s+/g, " ").trim();
48672
+ }
48673
+ return text2;
48674
+ }
48675
+ function primaryForwardControl(ctx, step) {
48676
+ const goalTerms = terms(ctx.goalText);
48677
+ if (goalTerms.size === 0)
48678
+ return null;
48679
+ const instruction = instructionText(step.modelControls, step.visibleText);
48680
+ const instructionTerms = terms(instruction);
48681
+ if (instructionTerms.size === 0 || !DIRECTIVE.test(instruction))
48682
+ return null;
48683
+ for (const control of step.modelControls) {
48684
+ if (!control.enabled)
48685
+ continue;
48686
+ const name = control.name.trim();
48687
+ if (name === "" || NON_FORWARD.test(name))
48688
+ continue;
48689
+ if (ctx.depth.tried(step.snap.url, name))
48690
+ continue;
48691
+ const controlTerms = terms(name);
48692
+ if (controlTerms.size === 0)
48693
+ continue;
48694
+ if ([...controlTerms].some((t) => goalTerms.has(t)) && [...controlTerms].every((t) => instructionTerms.has(t)))
48695
+ return control;
48696
+ }
48697
+ return null;
48698
+ }
48554
48699
  async function handleBlocked(ctx, step) {
48555
48700
  const { modelControls, record: record2 } = step;
48556
48701
  const job = await readInProgressStatus(ctx.page);
@@ -48600,16 +48745,221 @@ async function handleBlocked(ctx, step) {
48600
48745
  ctx.stop = "inconclusive";
48601
48746
  return "stop";
48602
48747
  }
48748
+ const forward = ctx.actionAttempts > 0 ? primaryForwardControl(ctx, step) : null;
48749
+ if (forward !== null) {
48750
+ ctx.earlyBlocked += 1;
48751
+ if (ctx.earlyBlocked <= MAX_EARLY_BLOCKED_REFUSALS) {
48752
+ const reason = `blocked refused: ${quote(forward.name || forward.summary, 60)} is an obvious forward control, still untried on this page, and it matches the goal and the page's instruction \u2014 use it before giving up`;
48753
+ ctx.history.push(reason);
48754
+ record2(false, reason, { origin: "engine" });
48755
+ return "continue";
48756
+ }
48757
+ record2(false, `model blocked while ${quote(forward.name || forward.summary, 60)} remained untried`, { origin: "engine" });
48758
+ ctx.failure = {
48759
+ kind: "insufficient-coverage",
48760
+ message: `the model gave up while ${quote(forward.name || forward.summary, 60)} \u2014 the obvious forward control the goal and the page's instruction name \u2014 was still untried after earlier actions \u2014 too little exploration to conclude the goal cannot be done`
48761
+ };
48762
+ ctx.stop = "inconclusive";
48763
+ return "stop";
48764
+ }
48603
48765
  const deferred = deferEnding(ctx, step, "blocked");
48604
48766
  if (deferred !== null) {
48605
48767
  record2(false, deferred, { origin: "engine" });
48606
48768
  return "continue";
48607
48769
  }
48770
+ const evidence = await collectActionableControlEvidence(ctx.page, { screenshots: false }).catch(() => []);
48771
+ const safe = evidence.filter((e) => e.kind === "safe" && e.name !== "");
48772
+ const turn = RECOVERY_TURNS.get(ctx);
48773
+ if ((turn === void 0 || !turn.given) && safe.length > 0) {
48774
+ RECOVERY_TURNS.set(ctx, { safe, given: true, pending: true });
48775
+ const note = `blocked: one recovery turn is available through the safe control(s) ${safe.map((e) => `${quote(e.name, 40)} (${e.locator})`).join(", ")} \u2014 act through one of them or the run ends blocked`;
48776
+ ctx.history.push(note);
48777
+ record2(false, note, { origin: "engine" });
48778
+ return "continue";
48779
+ }
48608
48780
  record2(true, "model blocked");
48609
- ctx.incomplete = `the model reported the goal cannot be advanced from this page${ctx.lastRefusal === null ? "" : ` (${ctx.lastRefusal})`}`;
48781
+ const turnSafe = turn !== void 0 && turn.given && turn.safe.length > 0 ? turn.safe : null;
48782
+ const excluded = evidence.filter((e) => e.kind !== "safe");
48783
+ const classes = [...new Set(excluded.map((e) => e.kind))].join(", ");
48784
+ const best = turnSafe !== null ? turnSafe[0] : excluded.find((e) => e.kind !== "hidden" && e.kind !== "disabled") ?? evidence[0];
48785
+ if (best !== void 0) {
48786
+ const captured = (await collectActionableControlEvidence(ctx.page).catch(() => evidence)).find((e) => e.locator === best.locator);
48787
+ ctx.actionableControl = actionableControlOutcome(captured ?? best);
48788
+ }
48789
+ const refusal = ctx.lastRefusal === null ? "" : ` (${ctx.lastRefusal})`;
48790
+ const base = `the model reported the goal cannot be advanced from this page${refusal}`;
48791
+ const recovery = turnSafe !== null ? `the one recovery turn through the safe control ${turnSafe.map(resumeIdentity).join(", ")} did not advance the goal` : `no visible enabled safe control is available to recover through${classes === "" ? "" : ` (excluded control classes: ${classes})`}${best === void 0 ? "" : `; resume identity ${resumeIdentity(best)}`}`;
48792
+ const cause = ctx.blockingCause();
48793
+ ctx.incomplete = cause === null || base.includes(cause) ? `${base}; ${recovery}` : `${base} \u2014 last blocker: ${cause}; ${recovery}`;
48610
48794
  ctx.stop = "blocked";
48611
48795
  return "stop";
48612
48796
  }
48797
+ var RECOVERY_SELECTOR = [
48798
+ "a[href]",
48799
+ "button",
48800
+ "input:not([type=hidden])",
48801
+ "select",
48802
+ "textarea",
48803
+ "[role=button]",
48804
+ "[role=link]",
48805
+ "[role=menuitem]",
48806
+ "[role=tab]",
48807
+ "[role=checkbox]",
48808
+ "[role=radio]",
48809
+ "[role=switch]",
48810
+ "[contenteditable=true]"
48811
+ ].join(", ");
48812
+ var APPROVAL = /\b(?:approve|approval|authorize|authorise|accept|grant|sign[ -]?off|confirm)\b/i;
48813
+ var SAFE_PREDICATE = "visible && enabled && !ambiguous && !destructive && !financial && !approval";
48814
+ function readRecoveryCandidates(els) {
48815
+ const nameOf = (el) => {
48816
+ const aria = el.getAttribute("aria-label");
48817
+ if (aria !== null && aria.trim() !== "")
48818
+ return aria.replace(/\s+/g, " ").trim();
48819
+ const labelled = el.getAttribute("aria-labelledby");
48820
+ if (labelled !== null) {
48821
+ const text2 = labelled.split(/\s+/).map((id) => document.getElementById(id)?.textContent ?? "").join(" ");
48822
+ if (text2.trim() !== "")
48823
+ return text2.replace(/\s+/g, " ").trim();
48824
+ }
48825
+ const title = el.getAttribute("title");
48826
+ if (title !== null && title.trim() !== "")
48827
+ return title.replace(/\s+/g, " ").trim();
48828
+ if (el instanceof HTMLInputElement && el.type !== "password" && el.value !== "")
48829
+ return el.value.replace(/\s+/g, " ").trim();
48830
+ return (el.textContent ?? "").replace(/\s+/g, " ").trim();
48831
+ };
48832
+ return els.map((el) => {
48833
+ const style = getComputedStyle(el);
48834
+ const rect = el.getBoundingClientRect();
48835
+ const visible3 = style.display !== "none" && style.visibility !== "hidden" && el.getClientRects().length > 0 && rect.width > 0 && rect.height > 0;
48836
+ const disabled = el.disabled === true || el.getAttribute("aria-disabled") === "true";
48837
+ return {
48838
+ name: nameOf(el),
48839
+ role: el.getAttribute("role") ?? el.tagName.toLowerCase(),
48840
+ testId: el.getAttribute("data-testid") ?? el.getAttribute("data-test"),
48841
+ id: el.id === "" ? null : el.id,
48842
+ visible: visible3,
48843
+ enabled: !disabled,
48844
+ box: visible3 ? { x: rect.x + window.scrollX, y: rect.y + window.scrollY, width: rect.width, height: rect.height } : null
48845
+ };
48846
+ });
48847
+ }
48848
+ function classifyActionableControl(input2) {
48849
+ if (!input2.visible)
48850
+ return "hidden";
48851
+ if (!input2.enabled)
48852
+ return "disabled";
48853
+ if (input2.duplicateName)
48854
+ return "ambiguous";
48855
+ const risk = controlRisk(input2.name, input2.role);
48856
+ if (risk !== null && (risk.risk === "destructive" || risk.risk === "session-end"))
48857
+ return "destructive";
48858
+ if (risk !== null && risk.risk === "paid")
48859
+ return "financial";
48860
+ if (APPROVAL.test(input2.name))
48861
+ return "approval";
48862
+ return "safe";
48863
+ }
48864
+ function recoveryPredicate(kind) {
48865
+ switch (kind) {
48866
+ case "safe":
48867
+ return SAFE_PREDICATE;
48868
+ case "hidden":
48869
+ return "visible";
48870
+ case "disabled":
48871
+ return "enabled";
48872
+ case "ambiguous":
48873
+ return "uniquely named";
48874
+ case "destructive":
48875
+ return "!destructive";
48876
+ case "financial":
48877
+ return "!financial";
48878
+ case "approval":
48879
+ return "!approval";
48880
+ }
48881
+ }
48882
+ function recoveryLocator(c) {
48883
+ if (c.testId !== null && c.testId !== "")
48884
+ return `[data-testid="${c.testId}"]`;
48885
+ if (c.id !== null && c.id !== "")
48886
+ return `#${c.id}`;
48887
+ const role = c.role === "" ? "*" : c.role;
48888
+ return `${role}[name="${c.name.replace(/"/g, '\\"')}"]`;
48889
+ }
48890
+ var SAFE_CLAUSES = ["visible", "enabled", "!ambiguous", "!destructive", "!financial", "!approval"];
48891
+ function failingClause(kind) {
48892
+ switch (kind) {
48893
+ case "safe":
48894
+ return null;
48895
+ case "hidden":
48896
+ return "visible";
48897
+ case "disabled":
48898
+ return "enabled";
48899
+ case "ambiguous":
48900
+ return "!ambiguous";
48901
+ case "destructive":
48902
+ return "!destructive";
48903
+ case "financial":
48904
+ return "!financial";
48905
+ case "approval":
48906
+ return "!approval";
48907
+ }
48908
+ }
48909
+ function actionableControlOutcome(e) {
48910
+ const missing = failingClause(e.kind);
48911
+ return {
48912
+ kind: "author_stopped_with_actionable_control",
48913
+ candidate: {
48914
+ locator: e.locator,
48915
+ label: e.name,
48916
+ visible: e.kind !== "hidden",
48917
+ enabled: e.kind !== "disabled",
48918
+ screenshot: e.screenshot,
48919
+ resume: e.resume
48920
+ },
48921
+ satisfiedPredicates: SAFE_CLAUSES.filter((c) => c !== missing),
48922
+ unsatisfiedPredicates: missing === null ? [] : [missing]
48923
+ };
48924
+ }
48925
+ async function recoveryScreenshot(page, c) {
48926
+ try {
48927
+ const buf = c.box === null ? await page.screenshot({ animations: "disabled" }) : await page.screenshot({ clip: c.box, animations: "disabled" });
48928
+ return `data:image/png;base64,${buf.toString("base64")}`;
48929
+ } catch {
48930
+ return null;
48931
+ }
48932
+ }
48933
+ async function collectActionableControlEvidence(page, opts = {}) {
48934
+ const candidates = await page.locator(RECOVERY_SELECTOR).evaluateAll(readRecoveryCandidates).catch(() => []);
48935
+ const url2 = page.url();
48936
+ const state = await snapshot(page).then((s3) => s3.signature, () => url2);
48937
+ const visibleNames = /* @__PURE__ */ new Map();
48938
+ for (const c of candidates) {
48939
+ if (c.visible && c.enabled && c.name !== "")
48940
+ visibleNames.set(c.name, (visibleNames.get(c.name) ?? 0) + 1);
48941
+ }
48942
+ const out = [];
48943
+ for (const c of candidates) {
48944
+ const kind = classifyActionableControl({
48945
+ name: c.name,
48946
+ role: c.role,
48947
+ visible: c.visible,
48948
+ enabled: c.enabled,
48949
+ duplicateName: c.name !== "" && (visibleNames.get(c.name) ?? 0) > 1
48950
+ });
48951
+ const locator = recoveryLocator(c);
48952
+ out.push({
48953
+ kind,
48954
+ name: c.name,
48955
+ locator,
48956
+ screenshot: opts.screenshots === false ? null : await recoveryScreenshot(page, c),
48957
+ predicate: recoveryPredicate(kind),
48958
+ resume: { url: url2, state, locator }
48959
+ });
48960
+ }
48961
+ return out;
48962
+ }
48613
48963
 
48614
48964
  // ../explore/dist/goal-loop/handle-idle.js
48615
48965
  async function handleWaitOrScroll(ctx, step) {
@@ -48922,6 +49272,7 @@ async function handleCodeTypedField(ctx, step) {
48922
49272
  ctx.valueLog.typed(control.name || control.summary, value);
48923
49273
  ctx.save.noteTyped(control.name || control.summary, value);
48924
49274
  ctx.observed.noteOwnInput(value);
49275
+ ctx.observed.noteTypedOn(value, await documentOf(ctx.page));
48925
49276
  ctx.noteMutation(`type ${control.name}`, control.descriptor, snap.signature, at3, { field: keyOf(control), value });
48926
49277
  ctx.tracker.countAction();
48927
49278
  ctx.cleared(control);
@@ -49484,6 +49835,7 @@ async function handleFill(ctx, step) {
49484
49835
  ctx.typeProbe = { key: keyOf(control), label: quote(control.name || control.summary, 60), at: typedAt, background: typedBackground, state: typedState };
49485
49836
  ctx.recorder.fill(control.descriptor, text2, at3);
49486
49837
  ctx.valueLog.typed(control.name || control.summary, text2);
49838
+ ctx.observed.noteTypedOn(text2, await documentOf(ctx.page));
49487
49839
  if (!ctx.isBound(control) && !isCredentialField(control) && !sendable(control)) {
49488
49840
  ctx.save.noteTyped(control.name || control.summary, text2);
49489
49841
  ctx.observed.noteOwnInput(text2);
@@ -49605,6 +49957,21 @@ async function statusWhilePending(page) {
49605
49957
  const status = await readInProgressStatus(page);
49606
49958
  return status === null ? null : `${status} while its request is in flight (within --job-wait-ms)`;
49607
49959
  }
49960
+ async function declaredBusy(page) {
49961
+ const status = await readInProgressStatus(page);
49962
+ return status?.startsWith("aria-busy region") === true ? `${status}, declared by the app (within --job-wait-ms)` : null;
49963
+ }
49964
+ function stoodAfterWork(ctx, workBudgetMs) {
49965
+ const s3 = (ms) => `${Math.round(ms / 1e3)}s`;
49966
+ const now2 = clock.now();
49967
+ const since = ctx.hangWorkSince === null ? "" : `, ${s3(now2 - ctx.hangWorkSince)} since the first deferral`;
49968
+ const last = monitorFor(ctx.page).completedSince(ctx.hangWorkSince ?? 0).filter((r) => r.resourceType !== "document" && r.ignored !== true).at(-1);
49969
+ const activity = last === void 0 ? "no app request completed since" : `the app's last request completed ${s3(now2 - last.endedAt)} ago`;
49970
+ if (ctx.hangWorkWaitedMs >= workBudgetMs) {
49971
+ return `still so after ${s3(ctx.hangWorkWaitedMs)} of the page showing work${since} \u2014 past the ${s3(workBudgetMs)} job-wait budget; raise --job-wait-ms for longer jobs; ${activity}`;
49972
+ }
49973
+ return `the page stopped showing work code recognizes (an acknowledged or declared in-progress status, a documented wait, a pending request under a status, or requests completing) after ${s3(ctx.hangWorkWaitedMs)} of the ${s3(workBudgetMs)} job-wait budget${since}; ${activity}`;
49974
+ }
49608
49975
  async function checkHang(ctx, step) {
49609
49976
  const { perceiveStartedAt, perception, snap } = step;
49610
49977
  const documented = perception.hang !== null && perception.hang.kind !== "main-thread-unresponsive" ? await readDocumentedWait(ctx.page) : null;
@@ -49615,8 +49982,9 @@ async function checkHang(ctx, step) {
49615
49982
  const working = await readWorkingStatus(ctx.page) ?? (documented === null ? null : `a documented wait ("${documented.text}")`) ?? (perception.hang.kind === "ui-no-progress" ? await liveBusyWork(ctx.page, perception.busyWait) : null) ?? // #330: a request still in flight while the page SAYS it is working ("Designing variations…")
49616
49983
  // is the long job the operator sized with --job-wait-ms: waited out within that budget. Without
49617
49984
  // the flag a bare status is not enough (a stuck page looks exactly like that, #153).
49618
- (perception.hang.kind === "request-pending" && ctx.jobWaitExplicit ? await statusWhilePending(ctx.page) : null);
49985
+ (perception.hang.kind === "request-pending" && ctx.jobWaitExplicit ? await statusWhilePending(ctx.page) : null) ?? (perception.hang.kind === "ui-no-progress" && ctx.jobWaitExplicit ? await declaredBusy(ctx.page) : null);
49619
49986
  if (working !== null) {
49987
+ ctx.hangWorkSince ??= perceiveStartedAt;
49620
49988
  const w = await waitOutJob(ctx.page, Math.min(workBudgetMs - ctx.hangWorkWaitedMs, JOB_WAIT_SLICE_MS), stillShowsWork);
49621
49989
  ctx.hangWorkWaitedMs += clock.now() - perceiveStartedAt;
49622
49990
  const note = `not a hang yet (${perception.hang.kind}): the page shows ${working} \u2014 the app is still working; waited ${(w.waitedMs / 1e3).toFixed(1)}s (${w.cleared ? "the status cleared" : `still in progress; ${Math.round(ctx.hangWorkWaitedMs / 1e3)}s of the ${Math.round(workBudgetMs / 1e3)}s job-wait budget used`})`;
@@ -49656,7 +50024,7 @@ async function checkHang(ctx, step) {
49656
50024
  const heapNow = await sampleHeap(ctx.page, 1e3);
49657
50025
  const stood = ctx.hangWorkWaitedMs > 0 ? {
49658
50026
  ...perception.hang,
49659
- detail: `${perception.hang.detail} (still so after ${Math.round(ctx.hangWorkWaitedMs / 1e3)}s of the page showing work \u2014 past the ${Math.round(workBudgetMs / 1e3)}s job-wait budget; raise --job-wait-ms for longer jobs)`
50027
+ detail: `${perception.hang.detail} (${stoodAfterWork(ctx, workBudgetMs)})`
49660
50028
  } : perception.hang;
49661
50029
  const withHost = { ...stood, host: judged.host };
49662
50030
  ctx.hang = {
@@ -49933,7 +50301,7 @@ async function checkProgress(ctx, step) {
49933
50301
  if (!ctx.lastChanceGiven) {
49934
50302
  ctx.lastChanceGiven = true;
49935
50303
  ctx.lastChanceTurn = true;
49936
- ctx.history.push(LAST_CHANCE_NOTE);
50304
+ ctx.history.push(ctx.writeGoal && !ctx.wroteOk ? LAST_CHANCE_NO_WRITE_NOTE : LAST_CHANCE_NOTE);
49937
50305
  } else {
49938
50306
  ctx.stop = "no-progress";
49939
50307
  return "stop";
@@ -50137,7 +50505,12 @@ async function viewPage(ctx, step) {
50137
50505
  async function decideStep(ctx, step, view) {
50138
50506
  const { cfg } = ctx;
50139
50507
  const { perception, snap } = step;
50140
- const { modelControls, offered, unsubmitted, visibleText } = view;
50508
+ const { offered, unsubmitted, visibleText } = view;
50509
+ const recovery = takeRecoveryTurn(ctx);
50510
+ const safeNames = new Set((recovery ?? []).map((e) => e.name).filter((n3) => n3 !== ""));
50511
+ const modelControls = recovery === null ? view.modelControls : view.modelControls.filter((c) => safeNames.has(c.name));
50512
+ if (recovery !== null)
50513
+ ctx.history.push("recovery turn: act through one of the safe controls offered, or the run ends blocked");
50141
50514
  let decision;
50142
50515
  ctx.goalFocus.noteUrl(snap.url);
50143
50516
  const offGoal = ctx.goalFocus.offGoal(modelControls, ctx.chrome);
@@ -50388,6 +50761,7 @@ async function finishRun(ctx) {
50388
50761
  ...ctx.answer !== void 0 && finalOutcome.status === "completed" ? { answer: ctx.answer } : {},
50389
50762
  ...cause === null ? {} : { blockingCause: cause },
50390
50763
  ...missCause === void 0 ? {} : { missCause },
50764
+ ...ctx.actionableControl === null ? {} : { actionableControl: ctx.actionableControl },
50391
50765
  ...ctx.endedOnRejectedDone && ctx.stop === "done" ? { doneRejected: true } : {},
50392
50766
  ...ctx.stop === "crashed" && ctx.failure !== void 0 ? { crash: buildCrashReport(ctx.failure, ctx.crashWatch.signals(), ctx.heap.samples(), { host: await ctx.probeHost() }) } : {}
50393
50767
  };
@@ -52443,7 +52817,7 @@ async function reproduceSeedHang(cfg, run, h) {
52443
52817
  });
52444
52818
  }
52445
52819
  function hangResult(run, h, reproduction) {
52446
- const finding3 = hangFinding(h.signal, run.transcript, h.recordingStepIndex, reproduction);
52820
+ const finding4 = hangFinding(h.signal, run.transcript, h.recordingStepIndex, reproduction);
52447
52821
  return {
52448
52822
  // A hang whose replays could not run at all is `inconclusive`, never a non-reproduction.
52449
52823
  outcome: reproduction.status === "reproduced" ? "hang" : reproduction.status,
@@ -52453,8 +52827,8 @@ function hangResult(run, h, reproduction) {
52453
52827
  recording: run.recording,
52454
52828
  transcript: run.transcript,
52455
52829
  finalUrl: run.finalUrl,
52456
- hang: finding3,
52457
- reason: reproduction.withheld !== void 0 ? `${finding3.title} (${withheldReason(reproduction.withheld)})` : reproduction.attempts === 0 ? `${finding3.title} (unconfirmed: not replayed)` : `${finding3.title} (reproduced ${reproduction.reproduced}/${reproduction.attempts})`
52830
+ hang: finding4,
52831
+ reason: reproduction.withheld !== void 0 ? `${finding4.title} (${withheldReason(reproduction.withheld)})` : reproduction.attempts === 0 ? `${finding4.title} (unconfirmed: not replayed)` : `${finding4.title} (reproduced ${reproduction.reproduced}/${reproduction.attempts})`
52458
52832
  };
52459
52833
  }
52460
52834
 
@@ -53033,14 +53407,14 @@ function ownAssertion(s3) {
53033
53407
  return { field: "resume", assertion: s3.resume };
53034
53408
  return "expect" in s3 ? { field: "expect", assertion: s3.expect } : null;
53035
53409
  }
53036
- function canonical2(d) {
53410
+ function canonical(d) {
53037
53411
  const sort = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort(([a], [b2]) => a.localeCompare(b2)).map(([k, x]) => [k, sort(x)])) : v;
53038
53412
  return JSON.stringify(sort(d));
53039
53413
  }
53040
53414
  function isOwnTargetVisible(step) {
53041
53415
  if (!("expect" in step) || !("target" in step))
53042
53416
  return false;
53043
- return step.expect.kind === "visible" && canonical2(step.expect.target) === canonical2(step.target);
53417
+ return step.expect.kind === "visible" && canonical(step.expect.target) === canonical(step.target);
53044
53418
  }
53045
53419
  function isNoClaimExpect(step) {
53046
53420
  if (!("expect" in step))
@@ -54930,10 +55304,10 @@ function bindParams(inputs, record2, request) {
54930
55304
  return params;
54931
55305
  }
54932
55306
  function recordFingerprint(outputs, record2) {
54933
- const canonical7 = {};
55307
+ const canonical6 = {};
54934
55308
  for (const name of Object.keys(outputs.fields).sort())
54935
- canonical7[name] = record2[name] ?? null;
54936
- return `sha256:${createHash5("sha256").update(JSON.stringify(canonical7)).digest("hex")}`;
55309
+ canonical6[name] = record2[name] ?? null;
55310
+ return `sha256:${createHash5("sha256").update(JSON.stringify(canonical6)).digest("hex")}`;
54937
55311
  }
54938
55312
  function extractedNames(journey) {
54939
55313
  const names = /* @__PURE__ */ new Set();
@@ -58073,23 +58447,23 @@ function createFrontierContext(params, declared, safety, budget, overlay = null,
58073
58447
  const vp = ctx.sessions.page.viewportSize();
58074
58448
  if (!shouldCheckOverflow(vp?.width, params.overflow?.checkOverflow ?? false))
58075
58449
  return;
58076
- const finding3 = await detectOverflow(ctx.sessions.page, {
58450
+ const finding4 = await detectOverflow(ctx.sessions.page, {
58077
58451
  viewport: vp ?? { width: 1280, height: 720 },
58078
58452
  ...params.overflow?.device === void 0 ? {} : { device: params.overflow.device },
58079
58453
  ...params.overflow?.toleranceCss === void 0 ? {} : { toleranceCss: params.overflow.toleranceCss },
58080
58454
  ...params.overflow?.ignoreSelectors === void 0 ? {} : { ignoreSelectors: params.overflow.ignoreSelectors },
58081
58455
  ...params.overflow?.secrets === void 0 ? {} : { secrets: params.overflow.secrets }
58082
58456
  });
58083
- if (finding3 !== null && !ctx.seenOverflow.has(finding3.fingerprint)) {
58084
- ctx.seenOverflow.add(finding3.fingerprint);
58457
+ if (finding4 !== null && !ctx.seenOverflow.has(finding4.fingerprint)) {
58458
+ ctx.seenOverflow.add(finding4.fingerprint);
58085
58459
  ctx.defects.push({
58086
- fingerprint: finding3.fingerprint,
58460
+ fingerprint: finding4.fingerprint,
58087
58461
  kind: "horizontal-overflow",
58088
58462
  stateFingerprint: stateFp,
58089
58463
  url: url2,
58090
- reason: `horizontal-overflow: ${finding3.element.descriptor} overflows the ${finding3.viewport.width}px viewport by ${finding3.overflowPx}px at ${finding3.route}`,
58464
+ reason: `horizontal-overflow: ${finding4.element.descriptor} overflows the ${finding4.viewport.width}px viewport by ${finding4.overflowPx}px at ${finding4.route}`,
58091
58465
  recording,
58092
- overflow: finding3
58466
+ overflow: finding4
58093
58467
  });
58094
58468
  }
58095
58469
  const clipped = await detectClipping(ctx.sessions.page, {
@@ -60660,22 +61034,22 @@ function installOracle(ctx, params) {
60660
61034
  url: c.url,
60661
61035
  descriptor: c.element.descriptor
60662
61036
  }));
60663
- const finding3 = await detectOverflow(ctx.sessions.page, {
61037
+ const finding4 = await detectOverflow(ctx.sessions.page, {
60664
61038
  viewport: vp ?? { width: 1280, height: 720 },
60665
61039
  ...params.overflow?.device === void 0 ? {} : { device: params.overflow.device },
60666
61040
  ...params.overflow?.toleranceCss === void 0 ? {} : { toleranceCss: params.overflow.toleranceCss },
60667
61041
  ...params.overflow?.ignoreSelectors === void 0 ? {} : { ignoreSelectors: params.overflow.ignoreSelectors },
60668
61042
  ...params.overflow?.secrets === void 0 ? {} : { secrets: params.overflow.secrets }
60669
61043
  });
60670
- if (finding3 === null)
61044
+ if (finding4 === null)
60671
61045
  return clipped;
60672
61046
  const horizontal = {
60673
61047
  kind: "horizontal-overflow",
60674
- detail: `horizontal-overflow: ${finding3.element.descriptor} overflows the ${finding3.viewport.width}px viewport by ${finding3.overflowPx}px at ${finding3.route}`,
60675
- overflowPx: finding3.overflowPx,
60676
- route: finding3.route,
60677
- url: finding3.url,
60678
- descriptor: finding3.element.descriptor
61048
+ detail: `horizontal-overflow: ${finding4.element.descriptor} overflows the ${finding4.viewport.width}px viewport by ${finding4.overflowPx}px at ${finding4.route}`,
61049
+ overflowPx: finding4.overflowPx,
61050
+ route: finding4.route,
61051
+ url: finding4.url,
61052
+ descriptor: finding4.element.descriptor
60679
61053
  };
60680
61054
  return [horizontal, ...clipped];
60681
61055
  };
@@ -60844,9 +61218,9 @@ function installOracle(ctx, params) {
60844
61218
  }
60845
61219
  const summary2 = f2.kind === "invariant" ? f2.invariantReason ?? f2.title : f2.kind === "markup-injection" ? f2.title : f2.signals.map((s3) => s3.detail).join("; ");
60846
61220
  const triage = await tryTriage(params.generation, { failureSummary: summary2, url: f2.url });
60847
- const { origin: _origin, ...finding3 } = f2;
61221
+ const { origin: _origin, ...finding4 } = f2;
60848
61222
  ctx.defects.set(f2.fingerprint, {
60849
- ...finding3,
61223
+ ...finding4,
60850
61224
  related: new Set(f2.related),
60851
61225
  epoch: ctx.segments.indexOf(ctx.recorder),
60852
61226
  firstSeenStep: step,
@@ -60924,9 +61298,9 @@ function installSessions(ctx, params) {
60924
61298
  perceive: ctx.perceiveOpts,
60925
61299
  ...params.safety === void 0 ? {} : { safety: params.safety }
60926
61300
  });
60927
- const finding3 = hangFinding(h, ctx.transcript.entries(), recordingStepIndex, reproduction);
61301
+ const finding4 = hangFinding(h, ctx.transcript.entries(), recordingStepIndex, reproduction);
60928
61302
  const segment = ctx.segments.indexOf(ctx.recorder);
60929
- ctx.hangs.set(finding3.fingerprint, segment > 0 && partial2.ok ? { ...finding3, repro: { ...finding3.repro, recording: partial2.recording } } : finding3);
61303
+ ctx.hangs.set(finding4.fingerprint, segment > 0 && partial2.ok ? { ...finding4, repro: { ...finding4.repro, recording: partial2.recording } } : finding4);
60930
61304
  };
60931
61305
  ctx.restartSpend = 0;
60932
61306
  ctx.restartAtSeed = async () => {
@@ -61066,21 +61440,21 @@ function createHuntContext(params) {
61066
61440
  }
61067
61441
  return warning;
61068
61442
  };
61069
- ctx.blockedConsequence = (finding3, candidate) => {
61443
+ ctx.blockedConsequence = (finding4, candidate) => {
61070
61444
  const causedBy = blockedCauseOf(ctx.blockedRoots, candidate);
61071
61445
  if (causedBy === void 0)
61072
61446
  return false;
61073
- const known = ctx.blockedConsequences.get(finding3.fingerprint);
61447
+ const known = ctx.blockedConsequences.get(finding4.fingerprint);
61074
61448
  if (known !== void 0) {
61075
61449
  if (!known.occurrenceSteps.includes(candidate.step))
61076
61450
  known.occurrenceSteps.push(candidate.step);
61077
61451
  return true;
61078
61452
  }
61079
- ctx.blockedConsequences.set(finding3.fingerprint, {
61080
- fingerprint: finding3.fingerprint,
61081
- kind: finding3.kind,
61082
- title: finding3.title,
61083
- route: finding3.route,
61453
+ ctx.blockedConsequences.set(finding4.fingerprint, {
61454
+ fingerprint: finding4.fingerprint,
61455
+ kind: finding4.kind,
61456
+ title: finding4.title,
61457
+ route: finding4.route,
61084
61458
  firstSeenStep: candidate.step,
61085
61459
  occurrenceSteps: [candidate.step],
61086
61460
  causedBy
@@ -63059,9 +63433,9 @@ function hostStarvedFailure(failure3, health) {
63059
63433
  }
63060
63434
 
63061
63435
  // src/build-info.generated.ts
63062
- var GENERATED_COMMIT = "ad72163";
63063
- var GENERATED_TREE = "379caf5";
63064
- var GENERATED_BUILT_AT = "2026-10-23T21:02:56.700Z";
63436
+ var GENERATED_COMMIT = "ffe0da9";
63437
+ var GENERATED_TREE = "bc64c13";
63438
+ var GENERATED_BUILT_AT = "2026-10-26T13:27:18.971Z";
63065
63439
 
63066
63440
  // src/version.ts
63067
63441
  import { readFileSync as readFileSync5 } from "node:fs";
@@ -63083,13 +63457,13 @@ function readCliVersion() {
63083
63457
 
63084
63458
  // src/engine.ts
63085
63459
  function bundledCommit() {
63086
- return true ? "ad72163" : void 0;
63460
+ return true ? "ffe0da9" : void 0;
63087
63461
  }
63088
63462
  function bundledBuiltAt() {
63089
- return true ? "2026-10-23T21:02:56.700Z" : void 0;
63463
+ return true ? "2026-10-26T13:27:18.971Z" : void 0;
63090
63464
  }
63091
63465
  function bundledTree() {
63092
- return true ? "379caf5" : void 0;
63466
+ return true ? "bc64c13" : void 0;
63093
63467
  }
63094
63468
  function currentBuildTree() {
63095
63469
  return bundledTree() ?? GENERATED_TREE;
@@ -63732,12 +64106,12 @@ function substituteDeep(v, sub) {
63732
64106
  return v;
63733
64107
  }
63734
64108
  function fixtureSpecHash(spec, hooks) {
63735
- return sha256(canonical3({ spec: spec ?? null, hooks: hooks ?? null })).slice(0, 16);
64109
+ return sha256(canonical2({ spec: spec ?? null, hooks: hooks ?? null })).slice(0, 16);
63736
64110
  }
63737
- function canonical3(v) {
63738
- if (Array.isArray(v)) return `[${v.map(canonical3).join(",")}]`;
64111
+ function canonical2(v) {
64112
+ if (Array.isArray(v)) return `[${v.map(canonical2).join(",")}]`;
63739
64113
  if (isRecord(v)) {
63740
- return `{${Object.keys(v).sort().filter((k) => v[k] !== void 0).map((k) => `${JSON.stringify(k)}:${canonical3(v[k])}`).join(",")}}`;
64114
+ return `{${Object.keys(v).sort().filter((k) => v[k] !== void 0).map((k) => `${JSON.stringify(k)}:${canonical2(v[k])}`).join(",")}}`;
63741
64115
  }
63742
64116
  return JSON.stringify(v);
63743
64117
  }
@@ -63859,7 +64233,7 @@ var MissionFixtures = class {
63859
64233
  return redactUrl(redactText(text2, [...this.#opts.secrets ?? [], ...bound2, ...this.secrets()]));
63860
64234
  }
63861
64235
  identity() {
63862
- return `fx-${sha256(canonical3({ spec: this.#specHash, outputs: this.publicOutputs(), secret: [...this.#secretNames].sort() })).slice(0, 16)}`;
64236
+ return `fx-${sha256(canonical2({ spec: this.#specHash, outputs: this.publicOutputs(), secret: [...this.#secretNames].sort() })).slice(0, 16)}`;
63863
64237
  }
63864
64238
  record() {
63865
64239
  return {
@@ -66535,10 +66909,10 @@ var QueryNode = freeze2({
66535
66909
  where: node2.where ? WhereNode.cloneWithOperation(node2.where, "And", operation) : WhereNode.create(operation)
66536
66910
  });
66537
66911
  },
66538
- cloneWithJoin(node2, join83) {
66912
+ cloneWithJoin(node2, join84) {
66539
66913
  return freeze2({
66540
66914
  ...node2,
66541
- joins: node2.joins ? freeze2([...node2.joins, join83]) : freeze2([join83])
66915
+ joins: node2.joins ? freeze2([...node2.joins, join84]) : freeze2([join84])
66542
66916
  });
66543
66917
  },
66544
66918
  cloneWithReturning(node2, selections) {
@@ -70194,8 +70568,8 @@ var WithSchemaTransformer = class extends OperationNodeTransformer {
70194
70568
  this.#collectSchemableIdsFromTableExpr(node2.table, schemableIds);
70195
70569
  }
70196
70570
  if ("joins" in node2 && node2.joins) {
70197
- for (const join83 of node2.joins) {
70198
- this.#collectSchemableIdsFromTableExpr(join83.table, schemableIds);
70571
+ for (const join84 of node2.joins) {
70572
+ this.#collectSchemableIdsFromTableExpr(join84.table, schemableIds);
70199
70573
  }
70200
70574
  }
70201
70575
  if ("using" in node2 && node2.using) {
@@ -78957,14 +79331,14 @@ var GitSourceManager = class {
78957
79331
 
78958
79332
  // ../sources/dist/hash.js
78959
79333
  import { createHash as createHash9 } from "node:crypto";
78960
- function canonicalJson2(value) {
79334
+ function canonicalJson3(value) {
78961
79335
  if (value === null)
78962
79336
  return "null";
78963
79337
  if (Array.isArray(value))
78964
- return `[${value.map(canonicalJson2).join(",")}]`;
79338
+ return `[${value.map(canonicalJson3).join(",")}]`;
78965
79339
  if (typeof value === "object") {
78966
79340
  const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b2]) => a < b2 ? -1 : a > b2 ? 1 : 0);
78967
- return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson2(v)}`).join(",")}}`;
79341
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson3(v)}`).join(",")}}`;
78968
79342
  }
78969
79343
  if (typeof value === "number") {
78970
79344
  if (!Number.isFinite(value))
@@ -78975,7 +79349,7 @@ function canonicalJson2(value) {
78975
79349
  }
78976
79350
  function canonicalJourneyHash(file2) {
78977
79351
  const validated = SharedJourneyFileSchema.parse(file2);
78978
- const hex3 = createHash9("sha256").update(canonicalJson2(validated), "utf8").digest("hex");
79352
+ const hex3 = createHash9("sha256").update(canonicalJson3(validated), "utf8").digest("hex");
78979
79353
  return `sha256:${hex3}`;
78980
79354
  }
78981
79355
 
@@ -80681,6 +81055,15 @@ function journeyReviewHash(journey) {
80681
81055
  for (const k of BOOKKEEPING) delete metadata[k];
80682
81056
  return contentHash({ metadata, recording: journey.recording });
80683
81057
  }
81058
+ function journeyLegacyReviewHash(journey) {
81059
+ const metadata = { ...journey.metadata };
81060
+ for (const k of BOOKKEEPING) delete metadata[k];
81061
+ return legacyContentHash({ metadata, recording: journey.recording });
81062
+ }
81063
+ function journeyApprovalMatches(journey) {
81064
+ const approved = journey.metadata.approval?.contentHash;
81065
+ return approved !== void 0 && (approved === journeyReviewHash(journey) || approved === journeyLegacyReviewHash(journey));
81066
+ }
80684
81067
  function targetOf3(step) {
80685
81068
  if (step.kind === "forEach") return step.items;
80686
81069
  return "target" in step ? step.target : void 0;
@@ -80887,7 +81270,7 @@ function buildChange(journey, hash3, snapshot2, stepIdOnly) {
80887
81270
  const idOnly = stepIdOnly ? { stepIdOnly: true } : {};
80888
81271
  if (snapshot2 === null || snapshot2 === void 0) {
80889
81272
  if (approval !== void 0) {
80890
- return { kind: "snapshot-missing", approvedHash: approval.contentHash, approvedAt: approval.at, changed: approval.contentHash !== hash3, ...idOnly };
81273
+ return { kind: "snapshot-missing", approvedHash: approval.contentHash, approvedAt: approval.at, changed: !journeyApprovalMatches(journey), ...idOnly };
80891
81274
  }
80892
81275
  return journey.metadata.promoted ? { kind: "no-record" } : { kind: "first-approval" };
80893
81276
  }
@@ -81241,9 +81624,9 @@ var UnknownCatalogItemError = class extends Error {
81241
81624
  return this.kind === "persona" ? "E_UNKNOWN_PERSONA" : "E_UNKNOWN_JOB";
81242
81625
  }
81243
81626
  };
81244
- function statusOf3(approval, hash3) {
81627
+ function statusOf3(approval, hash3, legacyHash) {
81245
81628
  if (approval === void 0) return "draft";
81246
- return approval.contentHash === hash3 ? "approved" : "stale";
81629
+ return approval.contentHash === hash3 || approval.contentHash === legacyHash ? "approved" : "stale";
81247
81630
  }
81248
81631
  async function readJsonFile(path2) {
81249
81632
  const raw = await readFile8(path2, "utf8");
@@ -81308,7 +81691,7 @@ function catalogPersona(p, path2) {
81308
81691
  hasLogin: p.fields.login !== void 0,
81309
81692
  ...approval === void 0 ? {} : { approval },
81310
81693
  contentHash: hash3,
81311
- status: statusOf3(approval, hash3)
81694
+ status: statusOf3(approval, hash3, personaLegacyContentHash(p.id, p.fields))
81312
81695
  };
81313
81696
  }
81314
81697
  function personaContentHash(id, fields) {
@@ -81316,6 +81699,11 @@ function personaContentHash(id, fields) {
81316
81699
  for (const [k, v] of Object.entries(fields)) if (k !== "approval" && !SESSION_KEYS.has(k)) described[k] = v;
81317
81700
  return contentHash(described);
81318
81701
  }
81702
+ function personaLegacyContentHash(id, fields) {
81703
+ const described = { id };
81704
+ for (const [k, v] of Object.entries(fields)) if (k !== "approval" && !SESSION_KEYS.has(k)) described[k] = v;
81705
+ return legacyContentHash(described);
81706
+ }
81319
81707
  function jobList(raw, path2) {
81320
81708
  if (Array.isArray(raw)) return raw;
81321
81709
  if (isRecord(raw) && Array.isArray(raw.jobs)) return raw.jobs;
@@ -81338,13 +81726,17 @@ function catalogJobOf(job) {
81338
81726
  personas: [.../* @__PURE__ */ new Set([...job.personas ?? [], ...job.persona === void 0 ? [] : [job.persona]])],
81339
81727
  ...job.approval === void 0 ? {} : { approval: job.approval },
81340
81728
  contentHash: hash3,
81341
- status: statusOf3(job.approval, hash3)
81729
+ status: statusOf3(job.approval, hash3, jobLegacyContentHash(job))
81342
81730
  };
81343
81731
  }
81344
81732
  function jobContentHash(job) {
81345
81733
  const { approval: _a3, ...rest } = job;
81346
81734
  return contentHash(rest);
81347
81735
  }
81736
+ function jobLegacyContentHash(job) {
81737
+ const { approval: _a3, ...rest } = job;
81738
+ return legacyContentHash(rest);
81739
+ }
81348
81740
  function catalogJourney(journey) {
81349
81741
  const m = journey.metadata;
81350
81742
  const hash3 = journeyReviewHash(journey);
@@ -81357,7 +81749,7 @@ function catalogJourney(journey) {
81357
81749
  ...m.persona === void 0 ? {} : { personaText: m.persona },
81358
81750
  ...m.approval === void 0 ? {} : { approval: m.approval },
81359
81751
  contentHash: hash3,
81360
- changedSinceApproval: m.promoted && m.approval !== void 0 && m.approval.contentHash !== hash3
81752
+ changedSinceApproval: m.promoted && m.approval !== void 0 && !journeyApprovalMatches(journey)
81361
81753
  };
81362
81754
  }
81363
81755
  var CatalogLoader = class {
@@ -82860,12 +83252,12 @@ function healFloor(recorded, riskOf) {
82860
83252
  }
82861
83253
  return { floor: "healable", reason: "" };
82862
83254
  }
82863
- function canonical4(v) {
83255
+ function canonical3(v) {
82864
83256
  if (Array.isArray(v))
82865
- return `[${v.map(canonical4).join(",")}]`;
83257
+ return `[${v.map(canonical3).join(",")}]`;
82866
83258
  if (v !== null && typeof v === "object") {
82867
83259
  const o = v;
82868
- return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonical4(o[k])}`).join(",")}}`;
83260
+ return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonical3(o[k])}`).join(",")}}`;
82869
83261
  }
82870
83262
  return JSON.stringify(v) ?? "undefined";
82871
83263
  }
@@ -82887,7 +83279,7 @@ function assertProofUntouched(before, after) {
82887
83279
  for (const k of /* @__PURE__ */ new Set([...Object.keys(b2), ...Object.keys(a)])) {
82888
83280
  if (free.has(k))
82889
83281
  continue;
82890
- if (canonical4(b2[k]) !== canonical4(a[k]))
83282
+ if (canonical3(b2[k]) !== canonical3(a[k]))
82891
83283
  return { code: "proof-field-changed", detail: `the candidate changes the step's \`${k}\`` };
82892
83284
  }
82893
83285
  return null;
@@ -82898,15 +83290,15 @@ function assertRecordedProofUntouched(before, after) {
82898
83290
  for (const k of /* @__PURE__ */ new Set([...Object.keys(b2), ...Object.keys(a)])) {
82899
83291
  if (k === "step")
82900
83292
  continue;
82901
- if (canonical4(b2[k]) !== canonical4(a[k]))
83293
+ if (canonical3(b2[k]) !== canonical3(a[k]))
82902
83294
  return { code: "proof-field-changed", detail: `the candidate changes the recorded step's \`${k}\`` };
82903
83295
  }
82904
- return canonical4(before.step) === canonical4(after.step) ? null : assertProofUntouched(before.step, after.step);
83296
+ return canonical3(before.step) === canonical3(after.step) ? null : assertProofUntouched(before.step, after.step);
82905
83297
  }
82906
83298
  function assertRecordingProofUntouched(base, proposed) {
82907
83299
  const { pages: bp, ...bRest } = base;
82908
83300
  const { pages: pp, ...pRest } = proposed;
82909
- if (canonical4(bRest) !== canonical4(pRest))
83301
+ if (canonical3(bRest) !== canonical3(pRest))
82910
83302
  return { code: "proof-field-changed", detail: "the revision changes the recording's own fields" };
82911
83303
  if (bp.length !== pp.length)
82912
83304
  return { code: "shape-changed", detail: `the revision has ${pp.length} pages; the Journey has ${bp.length}` };
@@ -82914,7 +83306,7 @@ function assertRecordingProofUntouched(base, proposed) {
82914
83306
  for (let i = 0; i < bp.length; i++) {
82915
83307
  const { steps: bs, ...bPage } = bp[i];
82916
83308
  const { steps: ps, ...pPage } = pp[i];
82917
- if (canonical4(bPage) !== canonical4(pPage))
83309
+ if (canonical3(bPage) !== canonical3(pPage))
82918
83310
  return { code: "proof-field-changed", detail: `the revision changes page ${i + 1}` };
82919
83311
  if (bs.length !== ps.length)
82920
83312
  return { code: "shape-changed", detail: `the revision has ${ps.length} steps on page ${i + 1}; the Journey has ${bs.length}` };
@@ -83890,15 +84282,15 @@ function originOnly(url2) {
83890
84282
  var LOCKFILES = /* @__PURE__ */ new Set(["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "Cargo.lock", "poetry.lock", "Gemfile.lock", "composer.lock"]);
83891
84283
  var SECRET_EXTENSION = /\.(pem|key|p12|pfx)$/i;
83892
84284
  function isSkippedChangePath(path2) {
83893
- const normalized = path2.replace(/\\/g, "/");
83894
- const base = normalized.split("/").pop() ?? normalized;
84285
+ const normalized2 = path2.replace(/\\/g, "/");
84286
+ const base = normalized2.split("/").pop() ?? normalized2;
83895
84287
  if (base === ".env" || base.startsWith(".env."))
83896
84288
  return true;
83897
84289
  if (SECRET_EXTENSION.test(base))
83898
84290
  return true;
83899
84291
  if (LOCKFILES.has(base))
83900
84292
  return true;
83901
- return normalized.toLowerCase().split("/").some((segment) => segment.includes("secret"));
84293
+ return normalized2.toLowerCase().split("/").some((segment) => segment.includes("secret"));
83902
84294
  }
83903
84295
  function extractChangeEvidence(diff2, notes) {
83904
84296
  const evidence = [];
@@ -84500,11 +84892,11 @@ async function removeFile(path2) {
84500
84892
  await assertNoSymlink(path2);
84501
84893
  await rm4(path2, { force: true });
84502
84894
  }
84503
- function canonical5(v) {
84504
- if (Array.isArray(v)) return `[${v.map(canonical5).join(",")}]`;
84895
+ function canonical4(v) {
84896
+ if (Array.isArray(v)) return `[${v.map(canonical4).join(",")}]`;
84505
84897
  if (v !== null && typeof v === "object") {
84506
84898
  const o = v;
84507
- return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonical5(o[k])}`).join(",")}}`;
84899
+ return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonical4(o[k])}`).join(",")}}`;
84508
84900
  }
84509
84901
  return JSON.stringify(v) ?? "undefined";
84510
84902
  }
@@ -84529,7 +84921,7 @@ function proposalFloorViolation(stored, proposed) {
84529
84921
  for (let i = 0; i < after.length; i++) {
84530
84922
  const b2 = before[i];
84531
84923
  const a = after[i];
84532
- if (b2 !== void 0 && canonical5(b2.step) === canonical5(a.step)) continue;
84924
+ if (b2 !== void 0 && canonical4(b2.step) === canonical4(a.step)) continue;
84533
84925
  for (const [which, rec] of [["before", b2?.recorded], ["after", a.recorded]]) {
84534
84926
  if (rec === void 0) return `step ${i + 1} is not in the stored Journey`;
84535
84927
  const floor = healFloor(rec, riskOf);
@@ -90939,9 +91331,9 @@ async function attachDefectEvidence(result, opts) {
90939
91331
  continue;
90940
91332
  }
90941
91333
  const signal = defectSignalText(d, secrets);
90942
- const finding3 = findFinding(mission, fp);
90943
- const recording = finding3?.recording ?? mission.recording;
90944
- if (finding3 === void 0 || recording === null) {
91334
+ const finding4 = findFinding(mission, fp);
91335
+ const recording = finding4?.recording ?? mission.recording;
91336
+ if (finding4 === void 0 || recording === null) {
90945
91337
  byFp.set(fp, { screenshots: [], signal, skipped: "the defect has no repro Recording to replay" });
90946
91338
  continue;
90947
91339
  }
@@ -90959,9 +91351,9 @@ async function attachDefectEvidence(result, opts) {
90959
91351
  fp,
90960
91352
  await replayWithEvidence({
90961
91353
  recording,
90962
- stepIndex: finding3.repro.recordingStepIndex,
90963
- fingerprints: [fp, ...finding3.related ?? []],
90964
- signalCheckable: SIGNAL_KINDS.has(finding3.kind),
91354
+ stepIndex: finding4.repro.recordingStepIndex,
91355
+ fingerprints: [fp, ...finding4.related ?? []],
91356
+ signalCheckable: SIGNAL_KINDS.has(finding4.kind),
90965
91357
  signal,
90966
91358
  seedUrl: mission.target.seedUrl,
90967
91359
  allowlist: mission.target.allowlist,
@@ -92548,6 +92940,7 @@ async function runExploration(opts) {
92548
92940
  timing: mission.run.timing,
92549
92941
  outcome: goalOutcome,
92550
92942
  runOutcome: mission.run.outcome,
92943
+ ...mission.run.actionableControl === void 0 ? {} : { actionableControl: mission.run.actionableControl },
92551
92944
  ...mission.run.answer === void 0 ? {} : { answer: mission.run.answer },
92552
92945
  ...mission.run.partialReport === void 0 ? {} : { partialReport: mission.run.partialReport },
92553
92946
  depth: mission.run.depth,
@@ -96259,8 +96652,8 @@ async function runVerifyFix(opts) {
96259
96652
  const persisted = parsePersistedMission(raw);
96260
96653
  const replay = replayAllowlist(persisted.target.allowlist, opts);
96261
96654
  const mission = replay.override === void 0 ? persisted : { ...persisted, target: { ...persisted.target, allowlist: [...replay.allowlist] } };
96262
- const finding3 = findFinding(mission, opts.fingerprint);
96263
- if (finding3 === void 0) {
96655
+ const finding4 = findFinding(mission, opts.fingerprint);
96656
+ if (finding4 === void 0) {
96264
96657
  throw new VerifyFixInputError(`no finding with fingerprint ${opts.fingerprint} in ${opts.resultPath}`);
96265
96658
  }
96266
96659
  const origin = assertAuthorizedExploreTarget(mission.target.seedUrl, mission.target.allowlist);
@@ -96278,8 +96671,8 @@ async function runVerifyFix(opts) {
96278
96671
  ...target.settle === void 0 ? {} : { settleConfig: target.settle },
96279
96672
  ...target.hangs === void 0 ? {} : { hangConfig: target.hangs }
96280
96673
  };
96281
- const found = finding3.recording ?? mission.recording;
96282
- if (found === null) throw new VerifyFixInputError(`finding ${finding3.fingerprint} has no Recording to replay`);
96674
+ const found = finding4.recording ?? mission.recording;
96675
+ if (found === null) throw new VerifyFixInputError(`finding ${finding4.fingerprint} has no Recording to replay`);
96283
96676
  const branch = recordedBranchOf(raw?.result);
96284
96677
  let prefix;
96285
96678
  if (branch !== void 0) {
@@ -96292,7 +96685,7 @@ async function runVerifyFix(opts) {
96292
96685
  }
96293
96686
  const recording = prefix === void 0 ? found : anchoredRecording(found);
96294
96687
  try {
96295
- assertSameExtensionBuild(recording.extensions, opts.browser, `finding ${finding3.fingerprint}`);
96688
+ assertSameExtensionBuild(recording.extensions, opts.browser, `finding ${finding4.fingerprint}`);
96296
96689
  } catch (e) {
96297
96690
  throw new VerifyFixInputError(e instanceof Error ? e.message : String(e));
96298
96691
  }
@@ -96325,9 +96718,9 @@ async function runVerifyFix(opts) {
96325
96718
  const authTokenValues = [...invariantAuthTokens?.values() ?? []];
96326
96719
  const primaryActor = mission.target.actors?.find((a) => a.role === "primary")?.name;
96327
96720
  const fx = missionFixtures(mission, opts.fixtureFlags ?? {}, storageState, [...opts.secrets ?? [], ...authTokenValues], target.personas);
96328
- const declared = finding3.kind === "invariant" && finding3.invariantId !== void 0 && invariantSpec !== void 0 ? {
96721
+ const declared = finding4.kind === "invariant" && finding4.invariantId !== void 0 && invariantSpec !== void 0 ? {
96329
96722
  spec: invariantSpec,
96330
- id: finding3.invariantId,
96723
+ id: finding4.invariantId,
96331
96724
  allowlist: mission.target.allowlist,
96332
96725
  baseUrl: mission.target.seedUrl,
96333
96726
  ...invariantAuthTokens === void 0 || invariantAuthTokens.size === 0 ? {} : { authTokens: invariantAuthTokens },
@@ -96356,10 +96749,10 @@ async function runVerifyFix(opts) {
96356
96749
  const replaySession = fx === void 0 ? branchOpen : fixtureReplayOpener(branchOpen, fx, recording.fixture?.outputs ?? mission.fixtures?.outputs ?? {});
96357
96750
  let result;
96358
96751
  try {
96359
- if (finding3.kind === "server-log") {
96360
- if (finding3.serverLog === void 0) {
96752
+ if (finding4.kind === "server-log") {
96753
+ if (finding4.serverLog === void 0) {
96361
96754
  result = {
96362
- fingerprint: finding3.fingerprint,
96755
+ fingerprint: finding4.fingerprint,
96363
96756
  verdict: "inconclusive",
96364
96757
  observedFingerprints: [],
96365
96758
  replay: { outcome: "failed", at: -1, error: "not replayed" },
@@ -96368,13 +96761,13 @@ async function runVerifyFix(opts) {
96368
96761
  } else {
96369
96762
  result = await verifyServerLogDefect({
96370
96763
  recording,
96371
- recordingStepIndex: finding3.repro.recordingStepIndex,
96372
- fingerprint: finding3.fingerprint,
96373
- sources: finding3.serverLog.sources,
96374
- matcher: finding3.serverLog.matcher,
96375
- normalizedMessage: finding3.serverLog.normalizedMessage,
96376
- drainMs: finding3.serverLog.drainMs,
96377
- ...finding3.serverLog.scope === void 0 ? {} : { scope: finding3.serverLog.scope },
96764
+ recordingStepIndex: finding4.repro.recordingStepIndex,
96765
+ fingerprint: finding4.fingerprint,
96766
+ sources: finding4.serverLog.sources,
96767
+ matcher: finding4.serverLog.matcher,
96768
+ normalizedMessage: finding4.serverLog.normalizedMessage,
96769
+ drainMs: finding4.serverLog.drainMs,
96770
+ ...finding4.serverLog.scope === void 0 ? {} : { scope: finding4.serverLog.scope },
96378
96771
  // #142 follow-up: an explicit --allow-log-cmd wins; otherwise the operator's own
96379
96772
  // ~/.jevitate/targets.json entry for this origin may opt in (never an MCP argument).
96380
96773
  allowLogCmd: opts.allowLogCmd === true || target.allowLogCmd === true,
@@ -96388,15 +96781,15 @@ async function runVerifyFix(opts) {
96388
96781
  fixtureBaseDir: journeyFixtureBaseDir(),
96389
96782
  perceive: perceiveOpts,
96390
96783
  recording,
96391
- recordingStepIndex: finding3.repro.recordingStepIndex,
96392
- fingerprint: finding3.fingerprint,
96393
- defectKind: finding3.kind,
96784
+ recordingStepIndex: finding4.repro.recordingStepIndex,
96785
+ fingerprint: finding4.fingerprint,
96786
+ defectKind: finding4.kind,
96394
96787
  ...declared === void 0 ? {} : { invariant: declared },
96395
- ...finding3.hang === void 0 ? {} : { hang: finding3.hang },
96788
+ ...finding4.hang === void 0 ? {} : { hang: finding4.hang },
96396
96789
  // #153: never re-send a paid/destructive write unless the operator opted in.
96397
96790
  safety: { ...target.safety ?? {}, ...opts.hangReplayWrites === true ? { hangReplayWrites: true } : {} },
96398
96791
  writtenBy: recordedWriters(raw),
96399
- ...finding3.occurrences === void 0 ? {} : { occurrences: finding3.occurrences },
96792
+ ...finding4.occurrences === void 0 ? {} : { occurrences: finding4.occurrences },
96400
96793
  ...opts.settleCeilingMs === void 0 ? {} : { settleCeilingMs: opts.settleCeilingMs },
96401
96794
  ...opts.replays === void 0 ? {} : { replays: opts.replays },
96402
96795
  ...opts.actionDeltas === true ? { actionDeltas: true, secrets: [...opts.secrets ?? [], ...authTokenValues] } : {},
@@ -96414,18 +96807,18 @@ async function runVerifyFix(opts) {
96414
96807
  let shotFields = {};
96415
96808
  if (opts.browser?.recordVideo !== void 0 || opts.screenshots !== void 0) {
96416
96809
  const secrets = [...opts.secrets ?? [], ...authTokenValues];
96417
- const stored = persistedDefect(raw, finding3.fingerprint);
96810
+ const stored = persistedDefect(raw, finding4.fingerprint);
96418
96811
  const storedEvidence = stored !== void 0 && isRecord(stored.evidence) ? stored.evidence : void 0;
96419
96812
  const beforeVideo = typeof storedEvidence?.videoPath === "string" ? storedEvidence.videoPath : void 0;
96420
96813
  const beforeShots = Array.isArray(storedEvidence?.screenshots) ? storedEvidence.screenshots.filter((x) => typeof x === "string") : [];
96421
- const shots = opts.screenshots === void 0 ? void 0 : new RunScreenshots({ spec: opts.screenshots, dir: screenshotsDirFor(opts.screenshots, vfArtifact), secrets, title: `verify-fix ${finding3.fingerprint}` });
96814
+ const shots = opts.screenshots === void 0 ? void 0 : new RunScreenshots({ spec: opts.screenshots, dir: screenshotsDirFor(opts.screenshots, vfArtifact), secrets, title: `verify-fix ${finding4.fingerprint}` });
96422
96815
  const verdictCard = () => result.verdict === "fixed" ? { text: "After the fix: the defect no longer reproduces", ok: true } : { text: `After: ${result.verdict === "still-reproduces" ? "still reproduces" : result.verdict}`, ok: false };
96423
96816
  const after = prefix !== void 0 ? { screenshots: [], skipped: "a finding from a Journey branch point: the after-clip replay does not replay the prefix" } : fx !== void 0 || observers.length > 0 ? { screenshots: [], skipped: fx !== void 0 ? "the run used mission fixtures: the after-clip replay does not restore them" : "a cross-actor defect: no after-clip replay" } : await replayWithEvidence({
96424
96817
  recording,
96425
- stepIndex: finding3.repro.recordingStepIndex,
96426
- fingerprints: [finding3.fingerprint, ...finding3.related ?? []],
96427
- signalCheckable: signalCheckable(finding3.kind),
96428
- signal: defectSignalText(stored ?? { kind: finding3.kind, ...finding3.title === void 0 ? {} : { title: finding3.title } }, secrets),
96818
+ stepIndex: finding4.repro.recordingStepIndex,
96819
+ fingerprints: [finding4.fingerprint, ...finding4.related ?? []],
96820
+ signalCheckable: signalCheckable(finding4.kind),
96821
+ signal: defectSignalText(stored ?? { kind: finding4.kind, ...finding4.title === void 0 ? {} : { title: finding4.title } }, secrets),
96429
96822
  seedUrl: mission.target.seedUrl,
96430
96823
  allowlist: mission.target.allowlist,
96431
96824
  ...storageState === void 0 ? {} : { storageState },
@@ -96454,7 +96847,7 @@ async function runVerifyFix(opts) {
96454
96847
  ...stale === void 0 ? {} : { failure: { kind: "journey-stale", message: stale.message } },
96455
96848
  ...evidence === void 0 ? {} : { evidence },
96456
96849
  ...shotFields,
96457
- ...finding3.title === void 0 ? {} : { title: finding3.title },
96850
+ ...finding4.title === void 0 ? {} : { title: finding4.title },
96458
96851
  ...replay.override === void 0 ? {} : { allowlistOverride: replay.override },
96459
96852
  ...fx === void 0 ? {} : { fixtures: { ...fx.record(), missionIdentity: mission.fixtures?.identity ?? "none" } },
96460
96853
  // Every replay closed its own session (finalizing its video) before verifyFix returned.
@@ -96554,17 +96947,17 @@ function ledgerAdd(opts) {
96554
96947
  } catch (e) {
96555
96948
  throw new LedgerError("E_LEDGER_INPUT", `${opts.resultPath}: ${e instanceof Error ? e.message : String(e)}`);
96556
96949
  }
96557
- const finding3 = findFinding(mission, opts.fingerprint);
96950
+ const finding4 = findFinding(mission, opts.fingerprint);
96558
96951
  const result = isRecord(raw) && isRecord(raw.result) ? raw.result : {};
96559
- const item = finding3 === void 0 ? void 0 : rawFinding(result, finding3.fingerprint);
96560
- if (finding3 === void 0 || item === void 0) {
96952
+ const item = finding4 === void 0 ? void 0 : rawFinding(result, finding4.fingerprint);
96953
+ if (finding4 === void 0 || item === void 0) {
96561
96954
  throw new LedgerError("E_LEDGER_INPUT", `no finding with fingerprint ${opts.fingerprint} in ${opts.resultPath}`);
96562
96955
  }
96563
96956
  const ownRecording = isRecord(item.repro) && item.repro.recording !== void 0;
96564
96957
  if (!ownRecording && mission.recording === null) {
96565
- throw new LedgerError("E_LEDGER_INPUT", `finding ${finding3.fingerprint} has no Recording to replay \u2014 nothing verify-fix could re-check`);
96958
+ throw new LedgerError("E_LEDGER_INPUT", `finding ${finding4.fingerprint} has no Recording to replay \u2014 nothing verify-fix could re-check`);
96566
96959
  }
96567
- const list2 = finding3.kind === "hang" ? "hangs" : "defects";
96960
+ const list2 = finding4.kind === "hang" ? "hangs" : "defects";
96568
96961
  const persistedFixtures = isRecord(result.fixtures) ? result.fixtures : void 0;
96569
96962
  const material = {
96570
96963
  ...typeof result.schemaVersion === "number" ? { schemaVersion: result.schemaVersion } : {},
@@ -96574,7 +96967,7 @@ function ledgerAdd(opts) {
96574
96967
  recording: ownRecording ? null : mission.recording,
96575
96968
  defects: list2 === "defects" ? [item] : [],
96576
96969
  hangs: list2 === "hangs" ? [item] : [],
96577
- ...finding3.kind === "invariant" && mission.invariantSpec !== void 0 ? { invariantSpec: mission.invariantSpec } : {},
96970
+ ...finding4.kind === "invariant" && mission.invariantSpec !== void 0 ? { invariantSpec: mission.invariantSpec } : {},
96578
96971
  // The fixture's spec, hook HASHES and non-secret outputs — what a replay restores; hooks are never stored.
96579
96972
  ...persistedFixtures === void 0 ? {} : {
96580
96973
  fixtures: {
@@ -96591,14 +96984,14 @@ function ledgerAdd(opts) {
96591
96984
  };
96592
96985
  assertNoSecrets(material, opts.secrets ?? []);
96593
96986
  const dir = ledgerDir(opts.regressionsDir);
96594
- const path2 = entryPath(dir, finding3.fingerprint);
96987
+ const path2 = entryPath(dir, finding4.fingerprint);
96595
96988
  const previous = existsSync20(path2) ? readEntry(path2) : void 0;
96596
96989
  const ticket = opts.ticket ?? previous?.ledger.ticket;
96597
96990
  const meta3 = {
96598
96991
  version: LEDGER_ENTRY_VERSION,
96599
- fingerprint: finding3.fingerprint,
96600
- kind: finding3.kind,
96601
- ...finding3.title === void 0 ? {} : { title: finding3.title },
96992
+ fingerprint: finding4.fingerprint,
96993
+ kind: finding4.kind,
96994
+ ...finding4.title === void 0 ? {} : { title: finding4.title },
96602
96995
  ...ticket === void 0 ? {} : { ticket },
96603
96996
  addedAt: previous?.ledger.addedAt ?? (opts.nowIso ?? (() => clock.nowIso()))(),
96604
96997
  source: {
@@ -96610,16 +97003,16 @@ function ledgerAdd(opts) {
96610
97003
  addedBy: currentEngineInfo()
96611
97004
  };
96612
97005
  const entry = { ledger: meta3, result: material };
96613
- const check3 = findFinding(parsePersistedMission(entry), finding3.fingerprint);
96614
- if (check3 === void 0) throw new LedgerError("E_LEDGER_INPUT", `finding ${finding3.fingerprint} did not survive into its ledger entry`);
97006
+ const check3 = findFinding(parsePersistedMission(entry), finding4.fingerprint);
97007
+ if (check3 === void 0) throw new LedgerError("E_LEDGER_INPUT", `finding ${finding4.fingerprint} did not survive into its ledger entry`);
96615
97008
  mkdirSync9(dir, { recursive: true });
96616
97009
  writeFileSync12(path2, `${JSON.stringify(entry, null, 2)}
96617
97010
  `, "utf8");
96618
97011
  return {
96619
97012
  entryPath: path2,
96620
- fingerprint: finding3.fingerprint,
96621
- kind: finding3.kind,
96622
- ...finding3.title === void 0 ? {} : { title: finding3.title },
97013
+ fingerprint: finding4.fingerprint,
97014
+ kind: finding4.kind,
97015
+ ...finding4.title === void 0 ? {} : { title: finding4.title },
96623
97016
  ...ticket === void 0 ? {} : { ticket },
96624
97017
  updated: previous !== void 0
96625
97018
  };
@@ -96837,14 +97230,14 @@ function realSecureIO() {
96837
97230
  return readMaskedLine(process.stdin, process.stdout, message2);
96838
97231
  },
96839
97232
  async persist(key2, value) {
96840
- const { mkdir: mkdir38, writeFile: writeFile45, readFile: readFile41 } = await import("node:fs/promises");
97233
+ const { mkdir: mkdir40, writeFile: writeFile47, readFile: readFile46 } = await import("node:fs/promises");
96841
97234
  const { dirname: dirname34 } = await import("node:path");
96842
97235
  const path2 = resolveDataDir(["credentials.json"]);
96843
- await mkdir38(dirname34(path2), { recursive: true });
97236
+ await mkdir40(dirname34(path2), { recursive: true });
96844
97237
  let existing = {};
96845
97238
  let raw;
96846
97239
  try {
96847
- raw = await readFile41(path2, "utf8");
97240
+ raw = await readFile46(path2, "utf8");
96848
97241
  } catch (err) {
96849
97242
  const missing = typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
96850
97243
  if (!missing) throw err;
@@ -96857,7 +97250,7 @@ function realSecureIO() {
96857
97250
  }
96858
97251
  }
96859
97252
  existing[key2] = value;
96860
- await writeFile45(path2, JSON.stringify(existing, null, 2), { mode: 384 });
97253
+ await writeFile47(path2, JSON.stringify(existing, null, 2), { mode: 384 });
96861
97254
  }
96862
97255
  };
96863
97256
  }
@@ -97241,7 +97634,7 @@ function makeFinding(input2, rubric, evidence) {
97241
97634
  if (!Number.isInteger(occurrences3) || occurrences3 < 1) {
97242
97635
  throw new UxFindingError(`occurrences for '${entry.id}' must be a positive integer, got ${occurrences3}`);
97243
97636
  }
97244
- const finding3 = {
97637
+ const finding4 = {
97245
97638
  rubricItemId: entry.id,
97246
97639
  citation: { source: entry.citation.source, ref: entry.citation.ref },
97247
97640
  severity: input2.severity,
@@ -97261,7 +97654,7 @@ function makeFinding(input2, rubric, evidence) {
97261
97654
  ...input2.quality ? { quality: { ...input2.quality } } : {},
97262
97655
  ...input2.predictedAttention ? { predictedAttention: input2.predictedAttention } : {}
97263
97656
  };
97264
- return Object.freeze(finding3);
97657
+ return Object.freeze(finding4);
97265
97658
  }
97266
97659
 
97267
97660
  // ../ux/dist/redact.js
@@ -97722,9 +98115,9 @@ function dedupeObjectiveFindings(items, rubric) {
97722
98115
  }
97723
98116
 
97724
98117
  // ../ux/dist/recommend.js
97725
- function evidenceSummaryFor(finding3, evidence) {
98118
+ function evidenceSummaryFor(finding4, evidence) {
97726
98119
  const parts2 = [];
97727
- for (const ref of finding3.evidenceRefs) {
98120
+ for (const ref of finding4.evidenceRefs) {
97728
98121
  if (ref.id.startsWith("control:")) {
97729
98122
  const idx = Number(ref.id.slice("control:".length));
97730
98123
  const c = evidence.controls.find((ctrl) => ctrl.index === idx);
@@ -97736,19 +98129,19 @@ function evidenceSummaryFor(finding3, evidence) {
97736
98129
  }
97737
98130
  return parts2.join(" | ").slice(0, 4e3);
97738
98131
  }
97739
- async function recommend(gen, finding3, evidence, secrets = []) {
98132
+ async function recommend(gen, finding4, evidence, secrets = []) {
97740
98133
  const input2 = {
97741
- principle: finding3.rubricItemId,
97742
- citationSource: finding3.citation.source,
97743
- citationRef: finding3.citation.ref,
97744
- judgmentSummary: finding3.recommendation,
97745
- evidenceSummary: evidenceSummaryFor(finding3, evidence),
98134
+ principle: finding4.rubricItemId,
98135
+ citationSource: finding4.citation.source,
98136
+ citationRef: finding4.citation.ref,
98137
+ judgmentSummary: finding4.recommendation,
98138
+ evidenceSummary: evidenceSummaryFor(finding4, evidence),
97746
98139
  appClass: evidence.appContext.appClass
97747
98140
  };
97748
98141
  assertNoSecretInPayload(input2, secrets);
97749
98142
  const result = await gen.generate("ux.recommendation", input2);
97750
98143
  const body = result.output.recommendation.trim();
97751
- return `${body} (${finding3.citation.source}: ${finding3.citation.ref})`;
98144
+ return `${body} (${finding4.citation.source}: ${finding4.citation.ref})`;
97752
98145
  }
97753
98146
 
97754
98147
  // ../ux/dist/a11y.js
@@ -98095,7 +98488,7 @@ function makeSignalFinding(input2) {
98095
98488
  ...input2.evidence.requests.map((r) => ({ id: `request:${r.id}` })),
98096
98489
  ...input2.evidence.screenshot === void 0 ? [] : [{ id: `screenshot:${input2.evidence.screenshot}` }]
98097
98490
  ];
98098
- const finding3 = {
98491
+ const finding4 = {
98099
98492
  rubricItemId: rule.id,
98100
98493
  citation: { ...rule.citation },
98101
98494
  severity: rule.severity,
@@ -98113,7 +98506,7 @@ function makeSignalFinding(input2) {
98113
98506
  screenIds: [...input2.screenIds ?? [input2.screenId]],
98114
98507
  signal: input2.evidence
98115
98508
  };
98116
- return Object.freeze(finding3);
98509
+ return Object.freeze(finding4);
98117
98510
  }
98118
98511
 
98119
98512
  // ../ux/dist/signals/shared.js
@@ -99871,9 +100264,9 @@ async function analyzeClaims(request, deps) {
99871
100264
  const dupOptions = /* @__PURE__ */ new Map();
99872
100265
  for (const d of group) {
99873
100266
  const i = indexOf.get(d);
99874
- const finding3 = describeDraft(d, prose[i]);
99875
- questions[`grade::${i}::need`] = { kind: "noul", instructions: prompts.grade.need.replace("{finding}", finding3).replace("{persona}", persona2) };
99876
- questions[`grade::${i}::ship`] = { kind: "noul", instructions: prompts.grade.ship.replace("{finding}", finding3).replace("{feature}", feature(d)) };
100267
+ const finding4 = describeDraft(d, prose[i]);
100268
+ questions[`grade::${i}::need`] = { kind: "noul", instructions: prompts.grade.need.replace("{finding}", finding4).replace("{persona}", persona2) };
100269
+ questions[`grade::${i}::ship`] = { kind: "noul", instructions: prompts.grade.ship.replace("{finding}", finding4).replace("{feature}", feature(d)) };
99877
100270
  const earlier = drafts.slice(0, i).map((e, j) => ({ e, j })).filter(({ e }) => d.target !== void 0 && e.target !== void 0 && controlKey2(d.target) === controlKey2(e.target) && e.screen.route === d.screen.route || e.type === d.type && e.screen.route === d.screen.route).map(({ j }) => j).slice(0, MAX_CHOICE_OPTIONS - 1);
99878
100271
  if (earlier.length > 0) {
99879
100272
  dupOptions.set(i, earlier);
@@ -99881,7 +100274,7 @@ async function analyzeClaims(request, deps) {
99881
100274
  kind: "choice",
99882
100275
  options: ["new", ...earlier.map((j) => `same:${j}`)],
99883
100276
  descriptions: Object.fromEntries([["new", "a different problem"], ...earlier.map((j) => [`same:${j}`, describeDraft(drafts[j], prose[j])])]),
99884
- instructions: prompts.duplicate.replace("{finding}", finding3)
100277
+ instructions: prompts.duplicate.replace("{finding}", finding4)
99885
100278
  };
99886
100279
  }
99887
100280
  }
@@ -99930,9 +100323,9 @@ async function analyzeClaims(request, deps) {
99930
100323
  const impact = d.friction?.impact ?? v.impact ?? "confused";
99931
100324
  const severity2 = v.severity ?? (d.friction === void 0 ? "minor" : IMPACT_SEVERITY2[d.friction.impact]);
99932
100325
  const evidenceRefs = d.target !== void 0 ? [{ id: `control:${d.target.index}` }] : (v.quotes ?? []).length > 0 ? [{ id: "visibleText" }] : [{ id: "url" }];
99933
- let finding3;
100326
+ let finding4;
99934
100327
  try {
99935
- finding3 = makeFinding({
100328
+ finding4 = makeFinding({
99936
100329
  rubricItemId,
99937
100330
  evidenceRefs,
99938
100331
  severity: severity2,
@@ -99951,8 +100344,8 @@ async function analyzeClaims(request, deps) {
99951
100344
  if (deps.polish === true) {
99952
100345
  if (deps.gen === void 0)
99953
100346
  throw new Error("polish requested without a generation gateway");
99954
- const polished = await recommend(deps.gen, finding3, d.screen.red, secrets);
99955
- finding3 = Object.freeze({ ...finding3, recommendation: polished });
100347
+ const polished = await recommend(deps.gen, finding4, d.screen.red, secrets);
100348
+ finding4 = Object.freeze({ ...finding4, recommendation: polished });
99956
100349
  }
99957
100350
  } catch (cause) {
99958
100351
  return { kind: "failed", reason: `claim finding failed: ${message(cause)}`, screenId: d.screen.raw.screenId, rubricItemId };
@@ -99965,7 +100358,7 @@ async function analyzeClaims(request, deps) {
99965
100358
  ...d.target === void 0 ? {} : { target: label(d.target) }
99966
100359
  };
99967
100360
  built.set(i, Object.freeze({
99968
- ...finding3,
100361
+ ...finding4,
99969
100362
  claim: claim2,
99970
100363
  impact,
99971
100364
  ...grade === void 0 ? {} : { grade },
@@ -99974,7 +100367,7 @@ async function analyzeClaims(request, deps) {
99974
100367
  const r = root2(i);
99975
100368
  if (r !== i) {
99976
100369
  const list2 = contributions.get(r) ?? [];
99977
- list2.push({ rubricItemId, observation: finding3.observation, confidence: finding3.confidence, occurrences: finding3.occurrences, citation: finding3.citation, ...finding3.quality ? { quality: finding3.quality } : {} });
100370
+ list2.push({ rubricItemId, observation: finding4.observation, confidence: finding4.confidence, occurrences: finding4.occurrences, citation: finding4.citation, ...finding4.quality ? { quality: finding4.quality } : {} });
99978
100371
  contributions.set(r, list2);
99979
100372
  }
99980
100373
  }
@@ -100005,8 +100398,8 @@ async function analyzeClaims(request, deps) {
100005
100398
  if (ok2.length === 0)
100006
100399
  continue;
100007
100400
  a11yEvaluated += ok2.length;
100008
- for (const finding3 of deps.a11yChecker(s3.red, request.rubric).findings)
100009
- a11yFound.push({ finding: finding3, refs: s3.red.refs });
100401
+ for (const finding4 of deps.a11yChecker(s3.red, request.rubric).findings)
100402
+ a11yFound.push({ finding: finding4, refs: s3.red.refs });
100010
100403
  }
100011
100404
  }
100012
100405
  const ledger = summarizeLedger(records);
@@ -100811,14 +101204,14 @@ var CREDENTIAL_KEY2 = /pass|secret|token|otp|totp|pin|cvc|cvv|card|auth|session|
100811
101204
  var MAX_DIGEST_BODY = 64e3;
100812
101205
  var CredentialBody = class extends Error {
100813
101206
  };
100814
- function canonical6(v) {
100815
- if (Array.isArray(v)) return v.map(canonical6);
101207
+ function canonical5(v) {
101208
+ if (Array.isArray(v)) return v.map(canonical5);
100816
101209
  if (v !== null && typeof v === "object") {
100817
101210
  const out = {};
100818
101211
  for (const k of Object.keys(v).sort()) {
100819
101212
  if (CREDENTIAL_KEY2.test(k)) throw new CredentialBody();
100820
101213
  if (VOLATILE_KEY.test(k)) continue;
100821
- out[k] = canonical6(v[k]);
101214
+ out[k] = canonical5(v[k]);
100822
101215
  }
100823
101216
  return out;
100824
101217
  }
@@ -100835,7 +101228,7 @@ function payloadDigest(method, endpoint2, body, secrets) {
100835
101228
  value = Object.fromEntries(new URLSearchParams(body));
100836
101229
  }
100837
101230
  try {
100838
- const canon = JSON.stringify(canonical6(value));
101231
+ const canon = JSON.stringify(canonical5(value));
100839
101232
  return createHash12("sha256").update(`${method.toUpperCase()} ${endpoint2}
100840
101233
  ${canon}`).digest("hex").slice(0, 32);
100841
101234
  } catch {
@@ -104264,10 +104657,17 @@ import { existsSync as existsSync25 } from "node:fs";
104264
104657
  import { copyFile as copyFile4, lstat as lstat4, mkdir as mkdir25, open as open3, readdir as readdir9, readFile as readFile24, rename as rename4, rm as rm10, writeFile as writeFile29 } from "node:fs/promises";
104265
104658
  import { basename as basename15, dirname as dirname24, join as join56, resolve as resolve18, sep as sep7 } from "node:path";
104266
104659
 
104660
+ // src/catalog-contract-identity.ts
104661
+ var CATALOG_CONTRACT_IDENTITY = {
104662
+ "kind": "jevitate.catalog-contract",
104663
+ "version": "2.0.0",
104664
+ "manifestSha256": "c1292a8727d7dc0dbe1d63dc768d34b6cc94ae60fab3a67aeb0a53837e1735ad"
104665
+ };
104666
+
104267
104667
  // src/catalog-bundle.ts
104268
104668
  var BUNDLE_KIND = "journeeze.catalog-bundle";
104269
104669
  var BUNDLE_VERSION = 1;
104270
- var BUNDLE_MINOR = 1;
104670
+ var BUNDLE_MINOR = 2;
104271
104671
  var CatalogBundleInputError = class extends CatalogInputError {
104272
104672
  constructor(issues) {
104273
104673
  super(`the catalog cannot be exported as a Journeeze bundle (${issues.length} problem(s)): ${issues.join("; ")}`);
@@ -104632,7 +105032,7 @@ function bundleChecks(input2, journeys, warn) {
104632
105032
  if (outcome.outcome !== "clean") why = `the run was ${outcome.outcome}, and a baseline comes only from a clean run`;
104633
105033
  else if (journeyId === void 0 || journeyHash === void 0) why = "the item names no Journey review hash";
104634
105034
  else if (cj === void 0) why = `Journey ${journeyId} is not in the bundle`;
104635
- else if (cj.approval?.contentHash !== journeyHash || cj.contentHash !== journeyHash) why = `it was measured on a Journey revision (${journeyHash.slice(0, 12)}\u2026) that is not ${journeyId}'s approved one`;
105035
+ else if (cj.approval?.contentHash !== journeyHash || cj.changedSinceApproval) why = `it was measured on a Journey revision (${journeyHash.slice(0, 12)}\u2026) that is not ${journeyId}'s approved one`;
104636
105036
  else {
104637
105037
  const b2 = bundleBaseline(item.baseline, cj.journey);
104638
105038
  if ("drop" in b2) why = b2.drop;
@@ -104686,13 +105086,13 @@ function bundleDemos(input2, journeys, warn) {
104686
105086
  warn.push(`${where}: its Journey is not in the bundle (not promoted, or left out) \u2014 the demo is left out`);
104687
105087
  continue;
104688
105088
  }
104689
- const approved = ex.cj.approval !== void 0 && ex.cj.approval.contentHash === ex.cj.contentHash;
105089
+ const approved = ex.cj.approval !== void 0 && !ex.cj.changedSinceApproval;
104690
105090
  if (!approved) {
104691
105091
  warn.push(`${where}: its Journey changed since it was approved \u2014 the demo is left out (approve the Journey and its demo again)`);
104692
105092
  continue;
104693
105093
  }
104694
- if (d.renderedFrom !== ex.cj.contentHash) {
104695
- warn.push(`${where}: rendered from another version of its Journey (${d.renderedFrom.slice(0, 12)}, approved ${ex.cj.contentHash.slice(0, 12)}) \u2014 the demo is left out (run \`jevitate demo approve\` again)`);
105094
+ if (d.renderedFrom !== ex.cj.approval.contentHash) {
105095
+ warn.push(`${where}: rendered from another version of its Journey (${d.renderedFrom.slice(0, 12)}, approved ${ex.cj.approval.contentHash.slice(0, 12)}) \u2014 the demo is left out (run \`jevitate demo approve\` again)`);
104696
105096
  continue;
104697
105097
  }
104698
105098
  if (d.steps.length === 0 || d.steps.length > MEDIA_LIMITS.demoSteps) {
@@ -104844,6 +105244,7 @@ function buildCatalogBundle(input2) {
104844
105244
  version: BUNDLE_VERSION,
104845
105245
  minor: BUNDLE_MINOR,
104846
105246
  producer: { tool: "jevitate", version: input2.producer.version, ...commit === void 0 ? {} : { commit } },
105247
+ catalogContract: CATALOG_CONTRACT_IDENTITY,
104847
105248
  product: { name: input2.productName },
104848
105249
  catalog: { personas, jobs, journeys: journeys.map((j) => j.entry) },
104849
105250
  ...shown2.demos.length === 0 ? {} : { demos: shown2.demos },
@@ -105531,7 +105932,7 @@ ${route ?? ""}
105531
105932
  ${key2}`, "utf8").digest("hex").slice(0, 16);
105532
105933
  const step = first.index + 1;
105533
105934
  const fix = s3.route !== void 0 && route !== void 0 && s3.route !== route ? s3.fix.split(s3.route).join(route) : s3.fix;
105534
- const finding3 = {
105935
+ const finding4 = {
105535
105936
  fingerprint: fingerprint2,
105536
105937
  kind: "ux",
105537
105938
  claim: "other",
@@ -105547,12 +105948,12 @@ ${key2}`, "utf8").digest("hex").slice(0, 16);
105547
105948
  recommendation: fix,
105548
105949
  locator: { element: s3.element, attribute: s3.attribute, testId: s3.testId, fix, steps: s3.occurrences.length }
105549
105950
  };
105550
- const issues = findingIssues(finding3);
105951
+ const issues = findingIssues(finding4);
105551
105952
  if (issues.length > 0) {
105552
- warnings.push(findingDropWarning(fingerprint2, finding3.kind, issues));
105953
+ warnings.push(findingDropWarning(fingerprint2, finding4.kind, issues));
105553
105954
  continue;
105554
105955
  }
105555
- findings.push(finding3);
105956
+ findings.push(finding4);
105556
105957
  }
105557
105958
  return { findings, warnings };
105558
105959
  }
@@ -107534,8 +107935,51 @@ function registerLocatorHealthCommand(program2, deps) {
107534
107935
  }
107535
107936
 
107536
107937
  // src/report-cli.ts
107537
- import { mkdir as mkdir27, writeFile as writeFile32 } from "node:fs/promises";
107938
+ import { mkdir as mkdir28, readFile as readFile28, writeFile as writeFile33 } from "node:fs/promises";
107538
107939
  import { join as join58 } from "node:path";
107940
+
107941
+ // src/journeeze-hint.ts
107942
+ import { createHash as createHash18 } from "node:crypto";
107943
+ import { mkdir as mkdir27, readFile as readFile27, rename as rename6, writeFile as writeFile32 } from "node:fs/promises";
107944
+ async function loadJourneezeHintPreference(path2 = resolveDataDir(["config.json"])) {
107945
+ try {
107946
+ const parsed = JSON.parse(await readFile27(path2, "utf8"));
107947
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
107948
+ const hints = parsed.hints;
107949
+ if (hints === null || typeof hints !== "object" || Array.isArray(hints)) return {};
107950
+ const value = hints.journeeze;
107951
+ if (value === false) return { enabled: false };
107952
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return {};
107953
+ const record2 = value;
107954
+ return { ...record2.enabled === false ? { enabled: false } : {}, ...typeof record2.dismissedSignal === "string" ? { dismissedSignal: record2.dismissedSignal } : {} };
107955
+ } catch (err) {
107956
+ if (err.code === "ENOENT") return {};
107957
+ throw err;
107958
+ }
107959
+ }
107960
+ function decideJourneezeHint(signal, preference = {}) {
107961
+ const authors = [...new Set(signal.journeyAuthors.map((author) => author.trim()).filter(Boolean))].sort();
107962
+ const reasons = [
107963
+ ...signal.sharedSource ? ["shared Journey source configured"] : [],
107964
+ ...authors.length >= 2 ? [`${authors.length} Journey/approval authors`] : [],
107965
+ ...signal.unresolvedFindings >= 3 ? [`${signal.unresolvedFindings} persistent unresolved findings`] : [],
107966
+ ...signal.strictReviewApproved ? ["human-approved strict Journey review"] : []
107967
+ ];
107968
+ const eligible = signal.sharedSource || reasons.length >= 2;
107969
+ const hash3 = createHash18("sha256").update(JSON.stringify({ sharedSource: signal.sharedSource, authors, unresolvedFindings: signal.unresolvedFindings, strictReviewApproved: signal.strictReviewApproved })).digest("hex");
107970
+ return { show: preference.enabled !== false && eligible && preference.dismissedSignal !== hash3, signal: hash3, reasons };
107971
+ }
107972
+ function renderJourneezeHint(signal, preference = {}, context = { isTTY: process.stdout.isTTY === true }) {
107973
+ const env = context.env ?? process.env;
107974
+ if (context.isTTY !== true || context.json === true || (context.mcp ?? inMcpInvocation()) || CI_ENV_MARKERS.some((key2) => env[key2] !== void 0)) return null;
107975
+ const decision = decideJourneezeHint(signal, preference);
107976
+ if (!decision.show) return null;
107977
+ return `
107978
+ Tip: Journeeze can coordinate this shared testing work (${decision.reasons.join(", ")}). Run \`jevitate connect journeeze\` to connect it.
107979
+ `;
107980
+ }
107981
+
107982
+ // src/report-cli.ts
107539
107983
  function emit3(program2, envelope, exitCode) {
107540
107984
  emitJsonOrRefusal(program2, envelope, exitCode);
107541
107985
  }
@@ -107544,6 +107988,25 @@ function failure2(program2, err, code) {
107544
107988
  else emit3(program2, fail(code, err instanceof Error ? err.message : String(err)));
107545
107989
  }
107546
107990
  var collect3 = (v, prev) => [...prev, v];
107991
+ async function hasSharedSource() {
107992
+ try {
107993
+ const raw = JSON.parse(await readFile28(join58(process.cwd(), "jevitate.lock"), "utf8"));
107994
+ return raw !== null && typeof raw === "object" && !Array.isArray(raw) && Array.isArray(raw.sources) && raw.sources.length > 0;
107995
+ } catch {
107996
+ return false;
107997
+ }
107998
+ }
107999
+ async function reportJourneezeHint(json2) {
108000
+ try {
108001
+ return renderJourneezeHint(
108002
+ { sharedSource: await hasSharedSource(), journeyAuthors: [], unresolvedFindings: 0, strictReviewApproved: false },
108003
+ await loadJourneezeHintPreference(),
108004
+ { isTTY: process.stdout.isTTY === true, json: json2 }
108005
+ ) ?? "";
108006
+ } catch {
108007
+ return "";
108008
+ }
108009
+ }
107547
108010
  function registerReportCommands(program2, deps) {
107548
108011
  program2.command("report").description("one deduped defect list for a target across every mode and run (markdown + JSON envelope)").option("--target <origin|name>", "the target: an origin (or URL on it), a suite target name, or a registered mission target").option("--since <run|date>", "only runs that started at/after this ISO date or this run").option("--dir <dir>", "results dir to read (repeatable). Default: this project's runs \u2014 its .jevitate/logs plus every run recorded for it in ~/.jevitate/run-index.jsonl (including --out dirs); with --target, every .jevitate/logs dir (project and ~/.jevitate) and the 0.1.0 recordings/ux-reports dirs too", collect3, []).option("--baseline <run|tag|last>", "add a diff section against a baseline: a run, a `baseline tag`, or `last` (the previous run per target+mode)").option("--tag <key=value>", "only runs carrying this tag (repeatable; every tag must match)", collect3, []).option("--out <dir>", "also write report.md and report.json here").option("--json", "emit the JSON envelope instead of markdown").action(async function() {
107549
108012
  const o = this.opts();
@@ -107559,14 +108022,14 @@ function registerReportCommands(program2, deps) {
107559
108022
  ...deps.baselinesDir === void 0 ? {} : { baselinesDir: deps.baselinesDir }
107560
108023
  });
107561
108024
  if (o.out !== void 0) {
107562
- await mkdir27(o.out, { recursive: true });
107563
- await writeFile32(join58(o.out, "report.md"), report2.markdown, "utf8");
107564
- await writeFile32(join58(o.out, "report.json"), `${JSON.stringify(ok(report2), null, 2)}
108025
+ await mkdir28(o.out, { recursive: true });
108026
+ await writeFile33(join58(o.out, "report.md"), report2.markdown, "utf8");
108027
+ await writeFile33(join58(o.out, "report.json"), `${JSON.stringify(ok(report2), null, 2)}
107565
108028
  `, "utf8");
107566
108029
  }
107567
108030
  if (o.json) emit3(program2, ok(report2), 0);
107568
108031
  else {
107569
- program2.configureOutput().writeOut?.(report2.markdown);
108032
+ program2.configureOutput().writeOut?.(`${report2.markdown}${await reportJourneezeHint(o.json)}`);
107570
108033
  process.exitCode = 0;
107571
108034
  }
107572
108035
  } catch (err) {
@@ -107747,7 +108210,7 @@ async function collectAllMissingKeys(store, io, opts = {}) {
107747
108210
  }
107748
108211
 
107749
108212
  // src/init-mcp.ts
107750
- import { readFile as readFile27, writeFile as writeFile33, mkdir as mkdir28 } from "node:fs/promises";
108213
+ import { readFile as readFile29, writeFile as writeFile34, mkdir as mkdir29 } from "node:fs/promises";
107751
108214
  import { homedir as realHomedir2 } from "node:os";
107752
108215
  import { join as join59, dirname as dirname26 } from "node:path";
107753
108216
  var MCP_SERVER_NAME = "jevitate";
@@ -107880,7 +108343,7 @@ async function registerMcp(runtimes, paths, opts = {}) {
107880
108343
  for (const unit of units) {
107881
108344
  let existing;
107882
108345
  try {
107883
- existing = await readFile27(unit.path, "utf8");
108346
+ existing = await readFile29(unit.path, "utf8");
107884
108347
  } catch {
107885
108348
  existing = void 0;
107886
108349
  }
@@ -107890,8 +108353,8 @@ async function registerMcp(runtimes, paths, opts = {}) {
107890
108353
  entry.instruction = renderPrintConfig(unit.harness);
107891
108354
  }
107892
108355
  if (!opts.dryRun && result.content !== void 0) {
107893
- await mkdir28(dirname26(unit.path), { recursive: true });
107894
- await writeFile33(unit.path, result.content, "utf8");
108356
+ await mkdir29(dirname26(unit.path), { recursive: true });
108357
+ await writeFile34(unit.path, result.content, "utf8");
107895
108358
  }
107896
108359
  report2.push(entry);
107897
108360
  }
@@ -108211,7 +108674,7 @@ function registerProfileCommands(program2, deps) {
108211
108674
  }
108212
108675
 
108213
108676
  // src/site-cli.ts
108214
- import { readFile as readFile28 } from "node:fs/promises";
108677
+ import { readFile as readFile30 } from "node:fs/promises";
108215
108678
  function registerSiteCommands(program2, deps) {
108216
108679
  const site = program2.command("site").description("per-site policies for Journey runs: human-like pacing, throttles, run budgets and quiet hours");
108217
108680
  const sitePolicy = site.command("policy").description("read or set a site's policy (the site is the Journey's origin, e.g. https://app.example.com)");
@@ -108269,7 +108732,7 @@ function registerSiteCommands(program2, deps) {
108269
108732
  const { account, db, file: file2, json: json2 } = this.opts();
108270
108733
  let policy;
108271
108734
  try {
108272
- const raw = await readFile28(file2, "utf8");
108735
+ const raw = await readFile30(file2, "utf8");
108273
108736
  policy = SitePolicySchema.parse(JSON.parse(raw));
108274
108737
  } catch (err) {
108275
108738
  emitJson(program2, fail("E_INVALID_POLICY", String(err)));
@@ -108297,7 +108760,7 @@ function registerSiteCommands(program2, deps) {
108297
108760
  const { account, db, script, seed, json: json2 } = this.opts();
108298
108761
  let plannedScript;
108299
108762
  try {
108300
- const raw = await readFile28(script, "utf8");
108763
+ const raw = await readFile30(script, "utf8");
108301
108764
  plannedScript = parsePlannedScript(raw);
108302
108765
  } catch (err) {
108303
108766
  emitJson(program2, fail("E_INVALID_SCRIPT", String(err)));
@@ -108333,12 +108796,12 @@ function registerSiteCommands(program2, deps) {
108333
108796
  }
108334
108797
 
108335
108798
  // src/recording-cli.ts
108336
- import { readFile as readFile29, writeFile as writeFile34 } from "node:fs/promises";
108799
+ import { readFile as readFile31, writeFile as writeFile35 } from "node:fs/promises";
108337
108800
  import { resolve as resolvePath14 } from "node:path";
108338
108801
  import * as clack from "@clack/prompts";
108339
108802
  function registerRecordingCommands(program2, deps) {
108340
108803
  async function readTakeJson(file2) {
108341
- const raw = await readFile29(file2, "utf8");
108804
+ const raw = await readFile31(file2, "utf8");
108342
108805
  let json2;
108343
108806
  try {
108344
108807
  json2 = JSON.parse(raw);
@@ -108357,7 +108820,7 @@ function registerRecordingCommands(program2, deps) {
108357
108820
  return { recording: parsed.recording, values: new Map(Object.entries(parsed.values)) };
108358
108821
  }
108359
108822
  async function readRecordingOrTake(file2) {
108360
- const raw = await readFile29(file2, "utf8");
108823
+ const raw = await readFile31(file2, "utf8");
108361
108824
  const json2 = JSON.parse(raw);
108362
108825
  if (json2 !== null && typeof json2 === "object" && !Array.isArray(json2) && "recording" in json2) {
108363
108826
  return AuthoringTakeSchema.parse(json2).recording;
@@ -108432,7 +108895,7 @@ function registerRecordingCommands(program2, deps) {
108432
108895
  }
108433
108896
  const result = applyPostdoc(takes[0], diff2, decisions);
108434
108897
  if (out !== void 0) {
108435
- await writeFile34(out, `${JSON.stringify(result, null, 2)}
108898
+ await writeFile35(out, `${JSON.stringify(result, null, 2)}
108436
108899
  `, "utf8");
108437
108900
  }
108438
108901
  if (json2) {
@@ -108510,7 +108973,7 @@ var DraftSaveError = class extends Error {
108510
108973
  async function saveDraft(draft, out, journeysDir) {
108511
108974
  const steps = draft.recording.pages.reduce((n3, p) => n3 + p.steps.length, 0);
108512
108975
  if (out !== void 0) {
108513
- await writeFile34(out, `${JSON.stringify(draft, null, 2)}
108976
+ await writeFile35(out, `${JSON.stringify(draft, null, 2)}
108514
108977
  `, "utf8");
108515
108978
  return { id: draft.metadata.id, path: resolvePath14(out), savedTo: "file", promoted: false, steps };
108516
108979
  }
@@ -108609,7 +109072,7 @@ async function loadDecisions(file2) {
108609
109072
  let raw;
108610
109073
  let parsed;
108611
109074
  try {
108612
- raw = await readFile29(file2, "utf8");
109075
+ raw = await readFile31(file2, "utf8");
108613
109076
  parsed = JSON.parse(raw);
108614
109077
  } catch (err) {
108615
109078
  throw new DecisionsParseError(err);
@@ -108686,9 +109149,9 @@ function targetsOpts(deps) {
108686
109149
  const file2 = deps.explore?.targetsConfigPath;
108687
109150
  return file2 === void 0 ? {} : { targetsFile: file2 };
108688
109151
  }
108689
- function lintFindingLine(finding3) {
108690
- const message2 = finding3.message.replace(/^step \d+: /, "");
108691
- return `${finding3.level} ${finding3.step === void 0 ? "" : `step ${finding3.step} `}${finding3.rule} ${message2}${finding3.fix === void 0 ? "" : ` \u2014 fix: ${finding3.fix}`}`;
109152
+ function lintFindingLine(finding4) {
109153
+ const message2 = finding4.message.replace(/^step \d+: /, "");
109154
+ return `${finding4.level} ${finding4.step === void 0 ? "" : `step ${finding4.step} `}${finding4.rule} ${message2}${finding4.fix === void 0 ? "" : ` \u2014 fix: ${finding4.fix}`}`;
108692
109155
  }
108693
109156
  var FULL_FLAG_HELP = "#496: show every detail section: catalog analysis, the full lint list, locator health, provenance, the full change diff";
108694
109157
  var DEPRECATED_DIR_HELP = "deprecated: use --journeys-dir <path> (same meaning); default: the repo's .jevitate/journeys, outside a repo ~/.jevitate/journeys";
@@ -108780,7 +109243,7 @@ function registerJourneyFind(journey, program2, deps) {
108780
109243
  import { existsSync as existsSync27 } from "node:fs";
108781
109244
 
108782
109245
  // src/journey-annotate-api.ts
108783
- import { mkdir as mkdir29, readFile as readFile30, rm as rm12, writeFile as writeFile35 } from "node:fs/promises";
109246
+ import { mkdir as mkdir30, readFile as readFile32, rm as rm12, writeFile as writeFile36 } from "node:fs/promises";
108784
109247
  import { dirname as dirname28, join as join62 } from "node:path";
108785
109248
  var EVIDENCE_TEXT_CHARS = 1500;
108786
109249
  var AnnotationDraftNotFoundError = class extends Error {
@@ -108928,8 +109391,8 @@ async function annotateJourney(opts) {
108928
109391
  });
108929
109392
  assertNoSecretInPayload(draft, secrets, "journey annotation draft");
108930
109393
  const draftPath = annotationDraftPath(opts.dir, opts.id);
108931
- await mkdir29(dirname28(draftPath), { recursive: true, mode: 448 });
108932
- await writeFile35(draftPath, `${JSON.stringify(draft, null, 2)}
109394
+ await mkdir30(dirname28(draftPath), { recursive: true, mode: 448 });
109395
+ await writeFile36(draftPath, `${JSON.stringify(draft, null, 2)}
108933
109396
  `, { mode: 384 });
108934
109397
  return {
108935
109398
  id: opts.id,
@@ -108952,7 +109415,7 @@ async function readAnnotationDraft(dir, id) {
108952
109415
  const draftPath = annotationDraftPath(dir, id);
108953
109416
  let raw;
108954
109417
  try {
108955
- raw = await readFile30(draftPath, "utf8");
109418
+ raw = await readFile32(draftPath, "utf8");
108956
109419
  } catch (err) {
108957
109420
  if (err.code === "ENOENT") {
108958
109421
  throw new AnnotationDraftNotFoundError(`no annotation draft for journey '${id}' (${draftPath}) \u2014 run \`jevitate journey annotate ${id}\` first`);
@@ -109807,8 +110270,8 @@ var jevReadinessAnalyzer = {
109807
110270
  // src/journey-stale-api.ts
109808
110271
  function isStepIdOnlyStale(journey, snapshot2) {
109809
110272
  const approval = journey.metadata.approval;
109810
- if (approval === void 0 || approval.contentHash === journeyReviewHash(journey)) return false;
109811
- if (snapshot2 !== null && journeyReviewHash(snapshot2) === approval.contentHash) return isStepIdOnlyChange(snapshot2, journey);
110273
+ if (approval === void 0 || journeyApprovalMatches(journey)) return false;
110274
+ if (snapshot2 !== null && [journeyReviewHash(snapshot2), journeyLegacyReviewHash(snapshot2)].includes(approval.contentHash)) return isStepIdOnlyChange(snapshot2, journey);
109812
110275
  return isStepIdOnlyChange(approval.contentHash, journey);
109813
110276
  }
109814
110277
  async function listStaleJourneys(req) {
@@ -109822,7 +110285,7 @@ async function listStaleJourneys(req) {
109822
110285
  const approval = journey.metadata.approval;
109823
110286
  if (approval === void 0) continue;
109824
110287
  const contentHash2 = journeyReviewHash(journey);
109825
- const hashStale = approval.contentHash !== contentHash2;
110288
+ const hashStale = !journeyApprovalMatches(journey);
109826
110289
  const links = journeyLinks(catalog, catalogJourney(journey));
109827
110290
  const catalogStale = !hashStale && links.linked && [links.job?.status, links.persona?.status].includes("stale");
109828
110291
  if (!hashStale && !catalogStale) continue;
@@ -109891,6 +110354,35 @@ async function reviewJourneyById(journeysDir, id, opts = {}) {
109891
110354
  }
109892
110355
 
109893
110356
  // src/journey-verify.ts
110357
+ async function planJourneyVerification(opts) {
110358
+ const journey = await new JourneyRegistry(new FsJourneyStore(opts.dir)).get(opts.id);
110359
+ if (!journey) throw new UnknownJourneyError(`unknown journey '${opts.id}'`);
110360
+ const sites = journeyAssertions(journey);
110361
+ const checkOf = new Map(sites.map((site) => [assertionSiteKey(site), describeCheck(site.check)]));
110362
+ const plan2 = planJourneyMutations(journey, {
110363
+ ...opts.readRequests === void 0 ? {} : { readRequests: opts.readRequests },
110364
+ params: opts.params
110365
+ });
110366
+ const pairs = plan2.pairs.map((pair) => ({ ...pair, check: checkOf.get(pair.site) ?? "unknown assertion" }));
110367
+ const assertions = sites.map((site) => {
110368
+ const key2 = assertionSiteKey(site);
110369
+ return { site: key2, check: describeCheck(site.check), mutations: pairs.filter((pair) => pair.site === key2).map((pair) => pair.mutation) };
110370
+ });
110371
+ return {
110372
+ journeyId: journey.metadata.id,
110373
+ journeyHash: journeyContentHash(journey),
110374
+ mutations: plan2.mutations,
110375
+ pairs,
110376
+ assertions,
110377
+ summary: {
110378
+ mutations: plan2.mutations.length,
110379
+ pairs: pairs.length,
110380
+ assertions: assertions.length,
110381
+ unpaired: assertions.filter((assertion) => assertion.mutations.length === 0).length,
110382
+ minimumReplays: plan2.mutations.length + 1
110383
+ }
110384
+ };
110385
+ }
109894
110386
  function verifyExitCode(verdict) {
109895
110387
  return verdict === "proven" ? 0 : verdict === "insensitive" ? 1 : 2;
109896
110388
  }
@@ -110593,7 +111085,7 @@ function registerJourneyReadiness(journey, program2, deps) {
110593
111085
 
110594
111086
  // src/journey-bundle.ts
110595
111087
  import { existsSync as existsSync30 } from "node:fs";
110596
- import { mkdir as mkdir30, readFile as readFile31, writeFile as writeFile36 } from "node:fs/promises";
111088
+ import { mkdir as mkdir31, readFile as readFile33, writeFile as writeFile37 } from "node:fs/promises";
110597
111089
  import { join as join63, resolve as resolve19 } from "node:path";
110598
111090
  var BundleStaleError = class extends Error {
110599
111091
  constructor(changed) {
@@ -110784,9 +111276,9 @@ async function buildJourneyBundle(opts, deps = {}) {
110784
111276
  verdict: receipt.verdict,
110785
111277
  generatedAt: clock.nowIso()
110786
111278
  };
110787
- await mkdir30(out, { recursive: true });
110788
- await writeFile36(bundlePath, renderJourneyBundle(receipt, entries, catalog, drafts), "utf8");
110789
- await writeFile36(manifestPath, `${JSON.stringify(manifest, null, 2)}
111279
+ await mkdir31(out, { recursive: true });
111280
+ await writeFile37(bundlePath, renderJourneyBundle(receipt, entries, catalog, drafts), "utf8");
111281
+ await writeFile37(manifestPath, `${JSON.stringify(manifest, null, 2)}
110790
111282
  `, "utf8");
110791
111283
  return { out, files: [bundlePath, manifestPath], verdict: receipt.verdict };
110792
111284
  }
@@ -110794,7 +111286,7 @@ async function checkJourneyBundle(dir, opts, deps = {}) {
110794
111286
  const review = deps.review ?? reviewJourneyById;
110795
111287
  const catalogOf = deps.loadCatalog ?? loadCatalog;
110796
111288
  const manifestPath = join63(resolve19(dir), "manifest.json");
110797
- const manifest = JSON.parse(await readFile31(manifestPath, "utf8"));
111289
+ const manifest = JSON.parse(await readFile33(manifestPath, "utf8"));
110798
111290
  const reviewOpts = { strict: true, ...opts.catalogDir === void 0 ? {} : { catalogDir: opts.catalogDir }, ...opts.targetsFile === void 0 ? {} : { targetsFile: opts.targetsFile } };
110799
111291
  const changed = [];
110800
111292
  for (const recorded of manifest.journeys) {
@@ -110861,7 +111353,7 @@ function registerJourneyBundle(journey, program2, deps) {
110861
111353
  }
110862
111354
 
110863
111355
  // src/journey-cli/promote.ts
110864
- import { readFile as readFile32 } from "node:fs/promises";
111356
+ import { readFile as readFile34 } from "node:fs/promises";
110865
111357
 
110866
111358
  // src/review-render-options.ts
110867
111359
  function processTerminal2() {
@@ -110933,7 +111425,7 @@ function registerJourneyPromote(journey, program2, deps) {
110933
111425
  if (reviewSheet !== void 0) {
110934
111426
  let text2;
110935
111427
  try {
110936
- text2 = await readFile32(reviewSheet, "utf8");
111428
+ text2 = await readFile34(reviewSheet, "utf8");
110937
111429
  } catch (e) {
110938
111430
  emitJson(program2, fail("E_JOURNEY_REVIEW_ARGS", `cannot read --review-sheet ${reviewSheet}: ${e instanceof Error ? e.message : String(e)}`));
110939
111431
  return;
@@ -110990,7 +111482,7 @@ function registerJourneyPromote(journey, program2, deps) {
110990
111482
  } else if (err instanceof WeakJourneyError) {
110991
111483
  if (!json2) {
110992
111484
  const out = program2.configureOutput().writeOut;
110993
- for (const finding3 of err.findings) out?.(`${lintFindingLine(finding3)}
111485
+ for (const finding4 of err.findings) out?.(`${lintFindingLine(finding4)}
110994
111486
  `);
110995
111487
  }
110996
111488
  emitJson(program2, fail("E_JOURNEY_WEAK", String(err.message)));
@@ -111010,7 +111502,22 @@ function registerJourneyPromote(journey, program2, deps) {
111010
111502
  }
111011
111503
 
111012
111504
  // src/journey-cli/review.ts
111013
- import { writeFile as writeFile37 } from "node:fs/promises";
111505
+ import { readFile as readFile35, writeFile as writeFile38 } from "node:fs/promises";
111506
+ import { join as join64 } from "node:path";
111507
+ async function reviewJourneezeHint(json2, outFile) {
111508
+ if (json2 || outFile !== void 0) return "";
111509
+ try {
111510
+ const raw = JSON.parse(await readFile35(join64(process.cwd(), "jevitate.lock"), "utf8"));
111511
+ const sharedSource = raw !== null && typeof raw === "object" && !Array.isArray(raw) && Array.isArray(raw.sources) && raw.sources.length > 0;
111512
+ return renderJourneezeHint(
111513
+ { sharedSource, journeyAuthors: [], unresolvedFindings: 0, strictReviewApproved: false },
111514
+ await loadJourneezeHintPreference(),
111515
+ { isTTY: process.stdout.isTTY === true, json: json2 }
111516
+ ) ?? "";
111517
+ } catch {
111518
+ return "";
111519
+ }
111520
+ }
111014
111521
  function registerJourneyReview(journey, program2, deps) {
111015
111522
  withProjectFlags(journey.command("review [id]")).description(
111016
111523
  "a human-readable review sheet for promotion sign-off: summary, steps, side effects, inputs (names only), proof, change since last approval, content hash. #467: --stale (no id) lists every promoted Journey whose approval is stale, labelling step-id-only changes"
@@ -111051,14 +111558,14 @@ function registerJourneyReview(journey, program2, deps) {
111051
111558
  const renderOpts = reviewRenderOptions({ full: full === true, toFile: outFile !== void 0 });
111052
111559
  const rendered = json2 ? `${JSON.stringify(review, null, 2)}
111053
111560
  ` : markdown ? renderReviewMarkdown(review, renderOpts) : renderReviewText(review, renderOpts);
111054
- if (outFile !== void 0) await writeFile37(outFile, rendered, { mode: 384 });
111561
+ if (outFile !== void 0) await writeFile38(outFile, rendered, { mode: 384 });
111055
111562
  if (json2) {
111056
111563
  emitJson(program2, strict === true ? Object.assign(ok(review), { strict: strictVerdict }) : ok(review));
111057
111564
  } else if (outFile !== void 0) {
111058
111565
  program2.configureOutput().writeOut?.(`review sheet for journey '${review.id}' written to ${outFile} (content hash ${review.contentHash})
111059
111566
  `);
111060
111567
  } else {
111061
- program2.configureOutput().writeOut?.(rendered);
111568
+ program2.configureOutput().writeOut?.(`${rendered}${await reviewJourneezeHint(false, outFile)}`);
111062
111569
  }
111063
111570
  emitUsageLine(program2, review.jev);
111064
111571
  process.exitCode = strict === true && !strictVerdict.passed ? EXIT_CODES.defects : 0;
@@ -111084,8 +111591,8 @@ function registerJourneyReview(journey, program2, deps) {
111084
111591
  import { existsSync as existsSync31 } from "node:fs";
111085
111592
 
111086
111593
  // src/journey-migrate-api.ts
111087
- import { readdir as readdir11, readFile as readFile33, writeFile as writeFile38 } from "node:fs/promises";
111088
- import { join as join64 } from "node:path";
111594
+ import { readdir as readdir11, readFile as readFile36, writeFile as writeFile39 } from "node:fs/promises";
111595
+ import { join as join65 } from "node:path";
111089
111596
  async function migrateStepIds(req) {
111090
111597
  const store = new FsJourneyStore(req.journeysDir);
111091
111598
  const journeys = [];
@@ -111100,7 +111607,7 @@ async function migrateStepIds(req) {
111100
111607
  if (!req.dryRun) await store.put(after);
111101
111608
  journeys.push({ id: meta3.id, stepsMinted, anchorsLinked, needsReapproval: before.metadata.promoted });
111102
111609
  }
111103
- const recordings = req.projectDir === null ? [] : await migrateRegressions(join64(req.projectDir, "regressions"), req.dryRun);
111610
+ const recordings = req.projectDir === null ? [] : await migrateRegressions(join65(req.projectDir, "regressions"), req.dryRun);
111104
111611
  return {
111105
111612
  dryRun: req.dryRun,
111106
111613
  journeys,
@@ -111121,11 +111628,11 @@ async function migrateRegressions(dir, dryRun) {
111121
111628
  }
111122
111629
  const out = [];
111123
111630
  for (const name of names) {
111124
- const path2 = join64(dir, name);
111125
- const rec = RecordingSchema.parse(JSON.parse(await readFile33(path2, "utf8")));
111631
+ const path2 = join65(dir, name);
111632
+ const rec = RecordingSchema.parse(JSON.parse(await readFile36(path2, "utf8")));
111126
111633
  const next = ensureStepIds(rec);
111127
111634
  if (next === rec) continue;
111128
- if (!dryRun) await writeFile38(path2, `${JSON.stringify(RecordingSchema.parse(next), null, 2)}
111635
+ if (!dryRun) await writeFile39(path2, `${JSON.stringify(RecordingSchema.parse(next), null, 2)}
111129
111636
  `);
111130
111637
  out.push({ path: path2, stepsMinted: countRecIds(next) - countRecIds(rec) });
111131
111638
  }
@@ -111379,7 +111886,7 @@ function registerJourneyMigrate(journey, program2, deps) {
111379
111886
  }
111380
111887
 
111381
111888
  // src/journey-cli/lint.ts
111382
- import { writeFile as writeFile39 } from "node:fs/promises";
111889
+ import { writeFile as writeFile40 } from "node:fs/promises";
111383
111890
 
111384
111891
  // src/journey-anchor-api.ts
111385
111892
  async function load(dir, id) {
@@ -111546,16 +112053,16 @@ function renderJourneyLintSarif(input2) {
111546
112053
  shortDescription: { text: rule.id },
111547
112054
  defaultConfiguration: { level: rule.level }
111548
112055
  }));
111549
- const results = input2.findings.map((finding3) => ({
111550
- ruleId: finding3.rule,
111551
- level: finding3.level,
111552
- message: { text: finding3.fix === void 0 ? finding3.message : `${finding3.message} \u2014 fix: ${finding3.fix}` },
111553
- ...finding3.fix === void 0 ? {} : { properties: { fix: finding3.fix } },
112056
+ const results = input2.findings.map((finding4) => ({
112057
+ ruleId: finding4.rule,
112058
+ level: finding4.level,
112059
+ message: { text: finding4.fix === void 0 ? finding4.message : `${finding4.message} \u2014 fix: ${finding4.fix}` },
112060
+ ...finding4.fix === void 0 ? {} : { properties: { fix: finding4.fix } },
111554
112061
  locations: [
111555
112062
  {
111556
112063
  logicalLocations: [
111557
112064
  { name: input2.id, kind: "module" },
111558
- ...finding3.step === void 0 ? [] : [{ name: `step ${finding3.step}`, kind: "function" }]
112065
+ ...finding4.step === void 0 ? [] : [{ name: `step ${finding4.step}`, kind: "function" }]
111559
112066
  ]
111560
112067
  }
111561
112068
  ]
@@ -111573,7 +112080,7 @@ function renderJourneyLintSarif(input2) {
111573
112080
  }
111574
112081
 
111575
112082
  // src/journey-link-hints.ts
111576
- import { join as join65 } from "node:path";
112083
+ import { join as join66 } from "node:path";
111577
112084
  var MAX_LISTED = 10;
111578
112085
  function listed(ids) {
111579
112086
  return ids.length <= MAX_LISTED ? ids.join(", ") : `${ids.slice(0, MAX_LISTED).join(", ")}, \u2026 (${ids.length - MAX_LISTED} more: jevitate catalog status)`;
@@ -111582,7 +112089,7 @@ async function loadLinkCatalog(catalogDir) {
111582
112089
  const empty = { dir: catalogDir, personasFile: null, jobsFile: null, personas: [], jobs: [], journeys: [] };
111583
112090
  if (catalogDir === null) return empty;
111584
112091
  try {
111585
- const loader = new CatalogLoader({ catalogDir, journeysDir: join65(catalogDir, "journeys") });
112092
+ const loader = new CatalogLoader({ catalogDir, journeysDir: join66(catalogDir, "journeys") });
111586
112093
  const [{ file: personasFile, personas }, { file: jobsFile, jobs }] = await Promise.all([loader.loadPersonas(), loader.loadJobs()]);
111587
112094
  return { ...empty, personasFile, jobsFile, personas, jobs };
111588
112095
  } catch {
@@ -111649,7 +112156,7 @@ function registerJourneyLint(journey, program2, deps) {
111649
112156
  const result = { ...linted, findings: [...linted.findings, ...linkFindings], warnings: linted.warnings + linkFindings.length };
111650
112157
  if (sarif !== void 0) {
111651
112158
  const log = renderJourneyLintSarif({ id: result.id, findings: result.findings, version: currentEngineInfo().version });
111652
- await writeFile39(sarif, `${JSON.stringify(log, null, 2)}
112159
+ await writeFile40(sarif, `${JSON.stringify(log, null, 2)}
111653
112160
  `);
111654
112161
  }
111655
112162
  if (json2) {
@@ -111667,7 +112174,7 @@ function registerJourneyLint(journey, program2, deps) {
111667
112174
  `);
111668
112175
  }
111669
112176
  }
111670
- for (const finding3 of result.findings) out?.(`${lintFindingLine(finding3)}
112177
+ for (const finding4 of result.findings) out?.(`${lintFindingLine(finding4)}
111671
112178
  `);
111672
112179
  out?.(`${result.errors} error(s), ${result.warnings} warning(s) \u2014 journey '${result.id}'
111673
112180
  `);
@@ -111686,14 +112193,38 @@ function registerJourneyLint(journey, program2, deps) {
111686
112193
  // src/journey-cli/verify.ts
111687
112194
  import { existsSync as existsSync32 } from "node:fs";
111688
112195
  function registerJourneyVerify(journey, program2, deps) {
111689
- withReplayAllowFlag(withEnvironmentFlags(withBrowserLaunchFlags(withFixtureFlags(withProjectFlags(journey.command("verify <id>")))))).description("prove each assertion of a Journey can fail: --mutate replays it with each write step skipped or blocked, and each checked fill emptied").option("--mutate", "run the mutation proof (required)").option("--dir <path>", DEPRECATED_DIR_HELP).option("--param <kv>", "param as key=value (repeatable)", collectParam, {}).option("--storage-state <file>", "Playwright storageState JSON to start each replay authenticated (as journey run)").option("--json", "emit a JSON envelope").action(async function(id) {
112196
+ withReplayAllowFlag(withEnvironmentFlags(withBrowserLaunchFlags(withFixtureFlags(withProjectFlags(journey.command("verify <id>")))))).description("preview or prove that each Journey assertion fails when its outcome is absent").option("--mutate", "run the mutation proof").option("--plan", "print the static mutation/assertion plan without opening a browser").option("--dir <path>", DEPRECATED_DIR_HELP).option("--param <kv>", "param as key=value (repeatable)", collectParam, {}).option("--storage-state <file>", "Playwright storageState JSON to start each replay authenticated (as journey run)").option("--json", "emit a JSON envelope").action(async function(id) {
111690
112197
  const locationFlags = this.opts();
111691
112198
  const layout = journeyLayout(program2, deps, { dir: locationFlags.dir, project: locationFlags.project, journeysDir: locationFlags.journeysDir });
111692
112199
  if (layout === null) return;
111693
112200
  const verifyJourneysDir = layout.journeysDir;
111694
- const { mutate, param, storageState: storageStateFlag, json: json2, env: envName, baseUrl } = this.opts();
111695
- if (mutate !== true) {
111696
- emitJson(program2, fail("E_JOURNEY_VERIFY_ARGS", "journey verify needs --mutate (the mutation proof is the only verification it runs today)"));
112201
+ const { mutate, plan: plan2, param, storageState: storageStateFlag, json: json2, env: envName, baseUrl } = this.opts();
112202
+ if (mutate === true === (plan2 === true)) {
112203
+ emitJson(program2, fail("E_JOURNEY_VERIFY_ARGS", "journey verify needs exactly one of --plan or --mutate"));
112204
+ return;
112205
+ }
112206
+ if (plan2 === true) {
112207
+ try {
112208
+ const report2 = await planJourneyVerification({ dir: verifyJourneysDir, id, params: param });
112209
+ if (json2) emitJson(program2, ok(withEngine(report2)));
112210
+ else {
112211
+ const out = program2.configureOutput().writeOut;
112212
+ for (const mutation2 of report2.mutations) {
112213
+ const sites = report2.pairs.filter((pair) => pair.mutation === mutation2.id).map((pair) => pair.site);
112214
+ out?.(`${mutation2.id} (step ${mutation2.step}) -> ${sites.length === 0 ? "unpaired" : sites.join(", ")}
112215
+ `);
112216
+ }
112217
+ for (const assertion of report2.assertions) if (assertion.mutations.length === 0) out?.(`unpaired ${assertion.site} ${assertion.check}
112218
+ `);
112219
+ out?.(
112220
+ `planned \u2014 journey '${report2.journeyId}': ${report2.summary.mutations} mutations, ${report2.summary.pairs} pairs, ${report2.summary.assertions} assertions, ${report2.summary.unpaired} unpaired, at least ${report2.summary.minimumReplays} replays
112221
+ `
112222
+ );
112223
+ }
112224
+ } catch (err) {
112225
+ if (err instanceof UnknownJourneyError) emitJson(program2, fail("E_UNKNOWN_JOURNEY", String(err.message)));
112226
+ else throw err;
112227
+ }
111697
112228
  return;
111698
112229
  }
111699
112230
  let environment;
@@ -111835,7 +112366,7 @@ function registerJourneyAnchors(journey, program2, deps) {
111835
112366
 
111836
112367
  // src/source-api.ts
111837
112368
  import { execFile as execFile6 } from "node:child_process";
111838
- import { mkdir as mkdir31 } from "node:fs/promises";
112369
+ import { mkdir as mkdir32 } from "node:fs/promises";
111839
112370
  import { promisify as promisify5 } from "node:util";
111840
112371
  var execFileAsync5 = promisify5(execFile6);
111841
112372
  var NotPromotedError = class extends Error {
@@ -111873,7 +112404,7 @@ async function requireEntry(lockPath, name) {
111873
112404
  }
111874
112405
  async function addSource(deps, req) {
111875
112406
  const mgr = mgrOf(deps);
111876
- await mkdir31(deps.sourcesDir, { recursive: true });
112407
+ await mkdir32(deps.sourcesDir, { recursive: true });
111877
112408
  const pinnedCommit = await mgr.add(req.name, req.gitUrl);
111878
112409
  const manifest = await loadManifest(mgr.resolveDir(req.name));
111879
112410
  const touSurface = surfaceForAck(manifest, req.gitUrl);
@@ -112290,7 +112821,7 @@ review or edit it, then apply it: jevitate journey annotate ${id} --approve
112290
112821
 
112291
112822
  // src/journey-demo-cli.ts
112292
112823
  import { existsSync as existsSync34 } from "node:fs";
112293
- import { join as join66, resolve as resolvePath15 } from "node:path";
112824
+ import { join as join67, resolve as resolvePath15 } from "node:path";
112294
112825
  function registerJourneyDemoCommand(journey, program2, deps) {
112295
112826
  withScreenshotsFlag(withEnvironmentFlags(withDemoFlags(withBrowserLaunchFlags(withEmulationFlags(withFixtureFlags(withProjectFlags(journey.command("demo <id>")))))))).description(
112296
112827
  "replay a Journey as a narrated demo (goal, step objectives as captions, target highlights) \u2192 a WebM video with .vtt subtitles and/or a Markdown step-by-step guide with screenshots; a Journey that no longer replays fails (exit 1)"
@@ -112333,9 +112864,9 @@ function registerJourneyDemoCommand(journey, program2, deps) {
112333
112864
  let video = videoFlag === void 0 ? void 0 : resolvePath15(videoFlag);
112334
112865
  let guide = guideFlag === void 0 ? void 0 : resolvePath15(guideFlag);
112335
112866
  if (video === void 0 && guide === void 0) {
112336
- const folder = join66(logsDirFor(), `journey-demo-${id.replace(/[^A-Za-z0-9._-]/g, "_")}-${artifactStamp(clock.nowIso())}`);
112337
- video = join66(folder, "demo.webm");
112338
- guide = join66(folder, "guide.md");
112867
+ const folder = join67(logsDirFor(), `journey-demo-${id.replace(/[^A-Za-z0-9._-]/g, "_")}-${artifactStamp(clock.nowIso())}`);
112868
+ video = join67(folder, "demo.webm");
112869
+ guide = join67(folder, "guide.md");
112339
112870
  }
112340
112871
  try {
112341
112872
  const result = await withSiteGate(
@@ -112428,7 +112959,7 @@ function registerJourneyCommands(program2, deps) {
112428
112959
  }
112429
112960
 
112430
112961
  // src/project-where.ts
112431
- import { join as join67 } from "node:path";
112962
+ import { join as join68 } from "node:path";
112432
112963
  function render(data) {
112433
112964
  const dir = (p) => p ?? "(none)";
112434
112965
  return [
@@ -112445,7 +112976,7 @@ function registerWhere(program2, deps) {
112445
112976
  const { project, journeysDir, json: json2 } = this.opts();
112446
112977
  const layout = journeyLayout(program2, deps, { project, journeysDir });
112447
112978
  if (layout === null) return;
112448
- const logsDir = layout.projectDir === null ? logsRoot() : join67(layout.projectDir, "logs");
112979
+ const logsDir = layout.projectDir === null ? logsRoot() : join68(layout.projectDir, "logs");
112449
112980
  const data = { ...layout, logsDir };
112450
112981
  if (json2 === true) {
112451
112982
  emitJson(program2, ok(data));
@@ -113263,7 +113794,7 @@ function resolveSecretArgs(values2, env, flag) {
113263
113794
  }
113264
113795
 
113265
113796
  // src/multi-run-cli.ts
113266
- import { join as join68 } from "node:path";
113797
+ import { join as join69 } from "node:path";
113267
113798
  var MULTI_RUN_ATTRS = /* @__PURE__ */ new Set(["repeat", "minAgreement", "persona", "personas", "out", "json"]);
113268
113799
  function forwardedArgv(cmd, omit3 = /* @__PURE__ */ new Set()) {
113269
113800
  const opts = cmd.opts();
@@ -113311,7 +113842,7 @@ function lastEnvelope(lines) {
113311
113842
  }
113312
113843
  async function runExploreMultiRun(args) {
113313
113844
  const { cmd, plan: plan2 } = args;
113314
- const outDir = args.out ?? join68(logsDirFor((args.nowIso ?? (() => clock.nowIso()))()), `multi-${artifactStamp((args.nowIso ?? (() => clock.nowIso()))())}`);
113845
+ const outDir = args.out ?? join69(logsDirFor((args.nowIso ?? (() => clock.nowIso()))()), `multi-${artifactStamp((args.nowIso ?? (() => clock.nowIso()))())}`);
113315
113846
  const omit3 = /* @__PURE__ */ new Set([...plan2.personas === null ? [] : ["storageState"], ...args.authPreflight === void 0 ? [] : ["authCheck"]]);
113316
113847
  const base = [...forwardedArgv(cmd, omit3), ...args.authPreflight === void 0 ? [] : ["--auth-check", "off"]];
113317
113848
  return runMultiRun({
@@ -113350,9 +113881,9 @@ import { resolve as resolvePath16 } from "node:path";
113350
113881
 
113351
113882
  // src/campaign-api.ts
113352
113883
  import { existsSync as existsSync37, readFileSync as readFileSync36 } from "node:fs";
113353
- import { mkdir as mkdir32, writeFile as writeFile40 } from "node:fs/promises";
113884
+ import { mkdir as mkdir33, writeFile as writeFile41 } from "node:fs/promises";
113354
113885
  import { homedir as homedir5 } from "node:os";
113355
- import { dirname as dirname29, isAbsolute as isAbsolute11, join as join69, resolve as resolve21 } from "node:path";
113886
+ import { dirname as dirname29, isAbsolute as isAbsolute11, join as join70, resolve as resolve21 } from "node:path";
113356
113887
 
113357
113888
  // src/campaign-preflight.ts
113358
113889
  import { basename as basename17 } from "node:path";
@@ -113477,7 +114008,7 @@ async function validateCampaignSpec(raw, specPath, opts) {
113477
114008
  if (!parsed.success) throw new CampaignSpecError(parsed.error.issues.map((i) => `${at2(i.path)}: ${i.message}`));
113478
114009
  const spec = parsed.data;
113479
114010
  const base = dirname29(resolve21(specPath));
113480
- const abs = (p) => p.startsWith("~/") ? join69(homedir5(), p.slice(2)) : isAbsolute11(p) ? p : resolve21(base, p);
114011
+ const abs = (p) => p.startsWith("~/") ? join70(homedir5(), p.slice(2)) : isAbsolute11(p) ? p : resolve21(base, p);
113481
114012
  const problems = [];
113482
114013
  let environment;
113483
114014
  try {
@@ -113663,8 +114194,8 @@ function commonArgs(plan2, job) {
113663
114194
  async function runCampaign(plan2, opts) {
113664
114195
  const now2 = opts.nowIso ?? (() => clock.nowIso());
113665
114196
  const startedAt = now2();
113666
- const outDir = opts.outDir ?? join69(logsDirFor(startedAt), `campaign-${artifactStamp(startedAt)}`);
113667
- await mkdir32(outDir, { recursive: true });
114197
+ const outDir = opts.outDir ?? join70(logsDirFor(startedAt), `campaign-${artifactStamp(startedAt)}`);
114198
+ await mkdir33(outDir, { recursive: true });
113668
114199
  const ai = opts.real === true ? ["--real"] : opts.fakeAi === true ? ["--fake-ai"] : [];
113669
114200
  const discovery = [];
113670
114201
  const replaySlowness = [];
@@ -113722,8 +114253,8 @@ async function runCampaign(plan2, opts) {
113722
114253
  }
113723
114254
  }
113724
114255
  const preflight = opts.preflight === void 0 ? void 0 : opts.preflight === "skip" ? { ok: true, problems: [], skipped: true } : preflightOf(preflightProblems);
113725
- const resultPath = join69(outDir, "campaign.json");
113726
- const reportPath = join69(outDir, "campaign.md");
114256
+ const resultPath = join70(outDir, "campaign.json");
114257
+ const reportPath = join70(outDir, "campaign.md");
113727
114258
  if (preflight !== void 0 && !preflight.ok) {
113728
114259
  const refused = {
113729
114260
  ...plan2.name === void 0 ? {} : { name: plan2.name },
@@ -113736,7 +114267,7 @@ async function runCampaign(plan2, opts) {
113736
114267
  missions: [],
113737
114268
  resultPath
113738
114269
  };
113739
- await writeFile40(resultPath, `${JSON.stringify(refused, null, 2)}
114270
+ await writeFile41(resultPath, `${JSON.stringify(refused, null, 2)}
113740
114271
  `, "utf8");
113741
114272
  throw new CampaignPreflightError(preflight.problems, resultPath);
113742
114273
  }
@@ -113757,7 +114288,7 @@ async function runCampaign(plan2, opts) {
113757
114288
  });
113758
114289
  continue;
113759
114290
  }
113760
- const runDir = join69(outDir, job.id, `${anchor2.atStep}-${strategy}`);
114291
+ const runDir = join70(outDir, job.id, `${anchor2.atStep}-${strategy}`);
113761
114292
  const argv = [
113762
114293
  "explore",
113763
114294
  "--from-journey",
@@ -113849,9 +114380,9 @@ async function runCampaign(plan2, opts) {
113849
114380
  missionOutcome,
113850
114381
  exitCode: exitCodeForOutcome(missionOutcome)
113851
114382
  };
113852
- await writeFile40(resultPath, `${JSON.stringify(result, null, 2)}
114383
+ await writeFile41(resultPath, `${JSON.stringify(result, null, 2)}
113853
114384
  `, "utf8");
113854
- await writeFile40(reportPath, campaignMarkdown(result, report2.markdown), "utf8");
114385
+ await writeFile41(reportPath, campaignMarkdown(result, report2.markdown), "utf8");
113855
114386
  return result;
113856
114387
  }
113857
114388
  function campaignMarkdown(r, reportMarkdown) {
@@ -114016,7 +114547,7 @@ var GOAL_RUN_OPTIONS = {
114016
114547
  ),
114017
114548
  jobWaitMs: () => new Option(
114018
114549
  "--job-wait-ms <ms>",
114019
- `goal and usability: while the page shows an in-progress status ("Simulating\u2026", aria-busy, a job "is running"), waits keep waiting with backoff \u2014 and a model 'blocked' is deferred \u2014 up to this budget (default: --reply-ceiling-ms, 180000); it also bounds a busy indicator the app visibly keeps working behind (live progress, a job poll) before it is a hang, and a wait the page documents ("usually takes a minute") can raise it`
114550
+ `goal and usability: while the page shows an in-progress status ("Simulating\u2026", aria-busy, a job "is running"), waits keep waiting with backoff \u2014 and a model 'blocked' is deferred \u2014 up to this budget (default: --reply-ceiling-ms, 180000); it also bounds a busy indicator the app visibly keeps working behind (live progress, a job poll) or declares (aria-busy, when this flag is set) before it is a hang, and a wait the page documents ("usually takes a minute") can raise it`
114020
114551
  ).argParser(positiveIntArg),
114021
114552
  deny: () => new Option(
114022
114553
  "--deny <pattern>",
@@ -114052,7 +114583,7 @@ var GOAL_RUN_OPTIONS = {
114052
114583
  settleIgnore: () => new Option("--settle-ignore <pattern>", "a request URL pattern the target marks as background (never pending work; repeatable, * wildcard)").default([]).argParser(collect7),
114053
114584
  longPollMs: () => new Option("--long-poll-ms <n>", "a request pending this long on an interactive page is a long-poll (default 5000)").argParser(nonNegativeIntArg),
114054
114585
  apiPrefix: () => new Option("--api-prefix <path>", "a path prefix whose requests are the app's API in the timing summary (repeatable), e.g. /api/").default([]).argParser(collect7),
114055
- ignoreNoProgress: () => new Option("--ignore-no-progress <pattern>", "a route / action label / busy indicator where ui-no-progress is expected (repeatable, * wildcard)").default([]).argParser(collect7),
114586
+ ignoreNoProgress: () => new Option("--ignore-no-progress <pattern>", "a route / action label / busy indicator (its description or visible words) where ui-no-progress is expected (repeatable, * wildcard)").default([]).argParser(collect7),
114056
114587
  allowSecretCmd: () => new Option(
114057
114588
  "--allow-secret-cmd",
114058
114589
  "opt-in: a --secret-field <descriptor>=cmd:<command> may run its command (in a shell, at type time, 60s timeout) and type its output (operator-declared only; refused otherwise)"
@@ -115577,11 +116108,11 @@ import { resolve as resolve24 } from "node:path";
115577
116108
 
115578
116109
  // src/sweep-api.ts
115579
116110
  import { existsSync as existsSync40, mkdirSync as mkdirSync11, readFileSync as readFileSync37, realpathSync as realpathSync5, statSync as statSync12, writeFileSync as writeFileSync13 } from "node:fs";
115580
- import { basename as basename19, dirname as dirname31, extname as extname2, join as join71, resolve as resolve23 } from "node:path";
116111
+ import { basename as basename19, dirname as dirname31, extname as extname2, join as join72, resolve as resolve23 } from "node:path";
115581
116112
 
115582
116113
  // src/mcp-paths.ts
115583
116114
  import { existsSync as existsSync39, realpathSync as realpathSync4 } from "node:fs";
115584
- import { basename as basename18, dirname as dirname30, isAbsolute as isAbsolute12, join as join70, relative as relative10, resolve as resolve22, sep as sep10 } from "node:path";
116115
+ import { basename as basename18, dirname as dirname30, isAbsolute as isAbsolute12, join as join71, relative as relative10, resolve as resolve22, sep as sep10 } from "node:path";
115585
116116
  var McpPathError = class extends Error {
115586
116117
  error = "invalid_args";
115587
116118
  constructor(message2) {
@@ -115600,7 +116131,7 @@ function realish(p) {
115600
116131
  for (; ; ) {
115601
116132
  if (existsSync39(head2)) {
115602
116133
  try {
115603
- return join70(realpathSync4(head2), ...tail);
116134
+ return join71(realpathSync4(head2), ...tail);
115604
116135
  } catch {
115605
116136
  return p;
115606
116137
  }
@@ -115960,13 +116491,14 @@ var CLI_TOOL_SPECS = [
115960
116491
  },
115961
116492
  {
115962
116493
  name: "verify_journey",
115963
- description: "`jevitate journey verify <id> --mutate` (#402): prove each assertion can fail. Replays the Journey once (it must pass), then once per mutation \u2014 a write step skipped, its write request aborted, a typed value left empty \u2014 and reports each assertion as sensitive (failed for the right reason), insensitive (still passed: vacuous), cascade, not-applied or unpaired. Mutations only skip steps or abort the app's own writes; nothing is fabricated. Exit 1 when any paired assertion is insensitive, 2 when nothing could be proven. Set mutate: true.",
116494
+ description: "`jevitate journey verify <id> --plan|--mutate` (#402): plan previews the exact mutations, assertion pairings, unpaired assertions and minimum replay count without opening a browser; mutate proves each assertion can fail by replaying the Journey once, then once per mutation. Mutations only skip steps, abort the app's own writes or alter checked non-secret inputs; nothing is fabricated. Set exactly one of plan or mutate.",
115964
116495
  command: {
115965
116496
  path: "journey verify",
115966
116497
  params: {
115967
116498
  id: pos(),
115968
116499
  ...EXTENSION,
115969
116500
  mutate: b("--mutate"),
116501
+ plan: b("--plan"),
115970
116502
  params: { kind: "params", flag: "--param" },
115971
116503
  storageState: session("--storage-state"),
115972
116504
  fixtures: path("--fixtures"),
@@ -117425,7 +117957,7 @@ function aggregateSweep(args) {
117425
117957
  kind: "sweep",
117426
117958
  targetsPath: plan2.targetsPath,
117427
117959
  outDir: plan2.outDir,
117428
- resultPath: join71(plan2.outDir, "sweep.result.json"),
117960
+ resultPath: join72(plan2.outDir, "sweep.result.json"),
117429
117961
  startedAt: args.startedAt,
117430
117962
  ...args.finishedAt === void 0 ? {} : { finishedAt: args.finishedAt },
117431
117963
  complete: args.complete && args.aborted === void 0,
@@ -117466,19 +117998,19 @@ async function awaitLowHostLoad(probe = hostProbe(), waitMs = HOST_STARVED_WAIT_
117466
117998
  }
117467
117999
  }
117468
118000
  function defaultSweepOutDir(iso = clock.nowIso()) {
117469
- return join71(logsDirFor(iso), `sweep-${artifactStamp(iso)}`);
118001
+ return join72(logsDirFor(iso), `sweep-${artifactStamp(iso)}`);
117470
118002
  }
117471
118003
  async function runSweep(opts) {
117472
118004
  const { plan: plan2 } = opts;
117473
118005
  const now2 = opts.nowIso ?? (() => clock.nowIso());
117474
118006
  const startedAt = now2();
117475
118007
  mkdirSync11(plan2.outDir, { recursive: true });
117476
- const resultPath = join71(plan2.outDir, "sweep.result.json");
118008
+ const resultPath = join72(plan2.outDir, "sweep.result.json");
117477
118009
  const rows = plan2.targets.map(() => void 0);
117478
118010
  const finished = [];
117479
118011
  const queue = [];
117480
118012
  plan2.targets.forEach((t, i) => {
117481
- const envelopePath = join71(plan2.outDir, t.id, "run.envelope.json");
118013
+ const envelopePath = join72(plan2.outDir, t.id, "run.envelope.json");
117482
118014
  const prior = plan2.resume ? finishedEnvelope(envelopePath) : void 0;
117483
118015
  if (prior === void 0) {
117484
118016
  queue.push(i);
@@ -117500,9 +118032,9 @@ async function runSweep(opts) {
117500
118032
  });
117501
118033
  const runOne = async (i) => {
117502
118034
  const target = plan2.targets[i];
117503
- const runDir = join71(plan2.outDir, target.id);
118035
+ const runDir = join72(plan2.outDir, target.id);
117504
118036
  mkdirSync11(runDir, { recursive: true });
117505
- const envelopePath = join71(runDir, "run.envelope.json");
118037
+ const envelopePath = join72(runDir, "run.envelope.json");
117506
118038
  const attempt2 = async () => {
117507
118039
  try {
117508
118040
  return await opts.runOnce({ target, argv: targetArgv(plan2, target, runDir), runDir });
@@ -117978,13 +118510,13 @@ function registerAuthorJourneyCommands(program2, deps) {
117978
118510
 
117979
118511
  // src/demo-aspect-cli.ts
117980
118512
  import { existsSync as existsSync43 } from "node:fs";
117981
- import { join as join74, resolve as resolvePath18 } from "node:path";
118513
+ import { join as join75, resolve as resolvePath18 } from "node:path";
117982
118514
 
117983
118515
  // src/demo-aspect-api.ts
117984
118516
  import { existsSync as existsSync42 } from "node:fs";
117985
- import { mkdir as mkdir34, mkdtemp as mkdtemp5, readFile as readFile34, rm as rm13, writeFile as writeFile42 } from "node:fs/promises";
118517
+ import { mkdir as mkdir35, mkdtemp as mkdtemp5, readFile as readFile37, rm as rm13, writeFile as writeFile43 } from "node:fs/promises";
117986
118518
  import { tmpdir as tmpdir5 } from "node:os";
117987
- import { dirname as dirname32, join as join73 } from "node:path";
118519
+ import { dirname as dirname32, join as join74 } from "node:path";
117988
118520
 
117989
118521
  // ../regression/dist/fingerprint.js
117990
118522
  function flattenWithUrls(rec) {
@@ -118129,8 +118661,8 @@ async function minimizeRecording(recording, reproduces) {
118129
118661
  }
118130
118662
 
118131
118663
  // ../regression/dist/commit.js
118132
- import { join as join72 } from "node:path";
118133
- import { mkdir as mkdir33, writeFile as writeFile41 } from "node:fs/promises";
118664
+ import { join as join73 } from "node:path";
118665
+ import { mkdir as mkdir34, writeFile as writeFile42 } from "node:fs/promises";
118134
118666
  var FlakyNotCommittableError = class extends Error {
118135
118667
  };
118136
118668
  async function commitRegression(dir, id, minimized, report2, bugSummary, oracle) {
@@ -118138,9 +118670,9 @@ async function commitRegression(dir, id, minimized, report2, bugSummary, oracle)
118138
118670
  throw new FlakyNotCommittableError(`refusing to commit '${id}': reproduction rate ${report2.rate} (${report2.reproducedCount}/${report2.attempts}) is flaky, not reproducible`);
118139
118671
  }
118140
118672
  const validated = RecordingSchema.parse(minimized);
118141
- await mkdir33(dir, { recursive: true });
118142
- const recordingPath = join72(dir, `${id}.recording.json`);
118143
- const metaPath = join72(dir, `${id}.meta.json`);
118673
+ await mkdir34(dir, { recursive: true });
118674
+ const recordingPath = join73(dir, `${id}.recording.json`);
118675
+ const metaPath = join73(dir, `${id}.meta.json`);
118144
118676
  const meta3 = {
118145
118677
  id,
118146
118678
  capturedAtIso: clock.nowIso(),
@@ -118149,8 +118681,8 @@ async function commitRegression(dir, id, minimized, report2, bugSummary, oracle)
118149
118681
  bugSummary,
118150
118682
  ...oracle === void 0 ? {} : { oracle }
118151
118683
  };
118152
- await writeFile41(recordingPath, JSON.stringify(validated, null, 2) + "\n");
118153
- await writeFile41(metaPath, JSON.stringify(meta3, null, 2) + "\n");
118684
+ await writeFile42(recordingPath, JSON.stringify(validated, null, 2) + "\n");
118685
+ await writeFile42(metaPath, JSON.stringify(meta3, null, 2) + "\n");
118154
118686
  return { recordingPath, metaPath };
118155
118687
  }
118156
118688
 
@@ -118223,7 +118755,7 @@ function demoJourneyId(aspect) {
118223
118755
  }
118224
118756
  var DEMO_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
118225
118757
  function demoDraftPath(journeysDir, id) {
118226
- return join73(journeysDir, ".drafts", `${id}.demo.json`);
118758
+ return join74(journeysDir, ".drafts", `${id}.demo.json`);
118227
118759
  }
118228
118760
  function demoDraftProblem(v) {
118229
118761
  if (v === null || typeof v !== "object" || Array.isArray(v)) return "not an object";
@@ -118244,7 +118776,7 @@ async function readDemoDraft(journeysDir, id) {
118244
118776
  const path2 = demoDraftPath(journeysDir, id);
118245
118777
  let raw;
118246
118778
  try {
118247
- raw = await readFile34(path2, "utf8");
118779
+ raw = await readFile37(path2, "utf8");
118248
118780
  } catch (err) {
118249
118781
  if (err.code === "ENOENT") throw new DemoDraftNotFoundError(`no demo draft '${id}' (${path2}) \u2014 make one with \`jevitate demo "<aspect>" --env <name>\``);
118250
118782
  throw err;
@@ -118324,16 +118856,16 @@ async function demoAspect(opts) {
118324
118856
  const maxReplays = opts.maxReplays ?? DEMO_MINIMIZE_MAX_REPLAYS;
118325
118857
  const now2 = opts.now ?? (() => clock.nowIso());
118326
118858
  const base = { id, aspect, environment: env.name ?? env.baseUrl };
118327
- const work = await mkdtemp5(join73(tmpdir5(), "jevitate-demo-aspect-"));
118859
+ const work = await mkdtemp5(join74(tmpdir5(), "jevitate-demo-aspect-"));
118328
118860
  try {
118329
118861
  const authored = await runAuthorJourney({
118330
118862
  url: new URL(start, env.baseUrl).toString(),
118331
118863
  goal: aspect,
118332
118864
  successAssertion: opts.successAssertion,
118333
118865
  allowlist: env.allowedOrigins,
118334
- journeysDir: join73(work, "explored"),
118866
+ journeysDir: join74(work, "explored"),
118335
118867
  // #369: the discovery take's own artifacts stay in the scratch dir with the rest.
118336
- outDir: join73(work, "explored-runs"),
118868
+ outDir: join74(work, "explored-runs"),
118337
118869
  journeyId: id,
118338
118870
  journeyName: aspect,
118339
118871
  takes: 1,
@@ -118354,10 +118886,10 @@ async function demoAspect(opts) {
118354
118886
  ...opts.persona === void 0 ? {} : { persona: opts.persona },
118355
118887
  successCriteria: [{ description: `The success check holds: ${opts.successSpec}`, check: opts.successAssertion }]
118356
118888
  };
118357
- const scratch = new FsJourneyStore(join73(work, "candidates"));
118889
+ const scratch = new FsJourneyStore(join74(work, "candidates"));
118358
118890
  const replayOk = async (recording2) => {
118359
118891
  await scratch.put({ metadata, recording: withSuccessAssert(recording2, opts.successAssertion) });
118360
- const run = await runJourneyProgrammatically({ dir: join73(work, "candidates"), id, params: {}, ...replayOpts(opts) });
118892
+ const run = await runJourneyProgrammatically({ dir: join74(work, "candidates"), id, params: {}, ...replayOpts(opts) });
118361
118893
  return run.outcome === "ok";
118362
118894
  };
118363
118895
  const explored = withoutSuccessAssert(authored.journey.recording, opts.successAssertion);
@@ -118388,7 +118920,7 @@ async function demoAspect(opts) {
118388
118920
  };
118389
118921
  const recording = withSuccessAssert(minimized, opts.successAssertion);
118390
118922
  const journey = { metadata: { ...metadata, params: deriveParamSchema(recording).required, createdAtIso: now2() }, recording };
118391
- const stageDir = join73(work, "stage");
118923
+ const stageDir = join74(work, "stage");
118392
118924
  await new FsJourneyStore(stageDir).put(journey);
118393
118925
  const annotated = await annotateJourney({ dir: stageDir, id, params: {}, gen: opts.gen, now: now2, ...replayOpts(opts) });
118394
118926
  if (annotated.replay.outcome !== "completed") {
@@ -118401,8 +118933,8 @@ async function demoAspect(opts) {
118401
118933
  }
118402
118934
  await store.put(journey);
118403
118935
  const draftPath = annotationDraftPath(opts.journeysDir, id);
118404
- await mkdir34(dirname32(draftPath), { recursive: true, mode: 448 });
118405
- await writeFile42(draftPath, `${JSON.stringify(annotations, null, 2)}
118936
+ await mkdir35(dirname32(draftPath), { recursive: true, mode: 448 });
118937
+ await writeFile43(draftPath, `${JSON.stringify(annotations, null, 2)}
118406
118938
  `, { mode: 384 });
118407
118939
  const record2 = {
118408
118940
  kind: "jevitate.demo.draft",
@@ -118416,7 +118948,7 @@ async function demoAspect(opts) {
118416
118948
  draft: outputsOf(demo)
118417
118949
  };
118418
118950
  assertNoSecretInPayload(record2, [], "demo draft record");
118419
- await writeFile42(recordPath, `${JSON.stringify(record2, null, 2)}
118951
+ await writeFile43(recordPath, `${JSON.stringify(record2, null, 2)}
118420
118952
  `, { mode: 384 });
118421
118953
  return {
118422
118954
  ...base,
@@ -118445,8 +118977,8 @@ function renderDemo(dir, id, opts, annotations, draft, outDir, jzMask = false) {
118445
118977
  dir,
118446
118978
  id,
118447
118979
  params: {},
118448
- video: join73(outDir, "demo.webm"),
118449
- guide: join73(outDir, "guide.md"),
118980
+ video: join74(outDir, "demo.webm"),
118981
+ guide: join74(outDir, "guide.md"),
118450
118982
  annotations,
118451
118983
  draft,
118452
118984
  environment: opts.environment,
@@ -118524,7 +119056,7 @@ async function approveDemo(opts) {
118524
119056
  ...title === "" ? {} : { title },
118525
119057
  demo
118526
119058
  });
118527
- journeeze = { media: true, record: join73(opts.journeysDir, ".demos", opts.id), ...record2.leftOut.length === 0 ? {} : { leftOut: record2.leftOut } };
119059
+ journeeze = { media: true, record: join74(opts.journeysDir, ".demos", opts.id), ...record2.leftOut.length === 0 ? {} : { leftOut: record2.leftOut } };
118528
119060
  } else {
118529
119061
  await clearApprovedDemo(opts.journeysDir, opts.id);
118530
119062
  journeeze = { media: false, reason: `environment '${environment}' does not declare synthetic: true \u2014 no demo media is exported to Journeeze` };
@@ -118594,7 +119126,7 @@ function withReplayFlags(cmd) {
118594
119126
  }
118595
119127
  function outDirFor(flag, id, kind) {
118596
119128
  if (flag !== void 0) return resolvePath18(flag);
118597
- return join74(logsDirFor(), `demo-${id}-${kind}-${artifactStamp(clock.nowIso())}`);
119129
+ return join75(logsDirFor(), `demo-${id}-${kind}-${artifactStamp(clock.nowIso())}`);
118598
119130
  }
118599
119131
  function stepLines2(steps) {
118600
119132
  return steps.map((s3) => ` ${s3.number}. ${s3.objective ?? s3.step}${s3.expectedResult === void 0 ? "" : `
@@ -118779,8 +119311,8 @@ ${(j.leftOut ?? []).map((l2) => ` left out: ${l2}
118779
119311
  }
118780
119312
 
118781
119313
  // src/record-api.ts
118782
- import { mkdir as mkdir35, writeFile as writeFile43 } from "node:fs/promises";
118783
- import { join as join75 } from "node:path";
119314
+ import { mkdir as mkdir36, writeFile as writeFile44 } from "node:fs/promises";
119315
+ import { join as join76 } from "node:path";
118784
119316
  async function runRecording(opts) {
118785
119317
  const origin = assertAuthorizedExploreTarget(opts.url, opts.allowlist);
118786
119318
  const portFactory = opts.browserPortFactory ?? (() => new PlaywrightBrowserPort());
@@ -118803,10 +119335,10 @@ async function runRecording(opts) {
118803
119335
  const values2 = Object.fromEntries(authoring.values);
118804
119336
  const take = AuthoringTakeSchema.parse({ recording, values: values2 });
118805
119337
  const outDir = opts.outDir ?? logsDirFor();
118806
- await mkdir35(outDir, { recursive: true });
119338
+ await mkdir36(outDir, { recursive: true });
118807
119339
  const iso = (opts.nowIso ?? (() => clock.nowIso()))();
118808
- const recordingPath = join75(outDir, `record-${iso.replace(/[:.]/g, "-")}.json`);
118809
- await writeFile43(recordingPath, `${JSON.stringify(take, null, 2)}
119340
+ const recordingPath = join76(outDir, `record-${iso.replace(/[:.]/g, "-")}.json`);
119341
+ await writeFile44(recordingPath, `${JSON.stringify(take, null, 2)}
118810
119342
  `, "utf8");
118811
119343
  const steps = recording.pages.reduce((n3, p) => n3 + p.steps.length, 0);
118812
119344
  return { recording, recordingPath, steps, pages: recording.pages.length, finalUrl };
@@ -118919,12 +119451,12 @@ function registerRecordCommands(program2, deps) {
118919
119451
 
118920
119452
  // src/regression-cli.ts
118921
119453
  import { existsSync as existsSync45 } from "node:fs";
118922
- import { readFile as readFile36, writeFile as writeFile44 } from "node:fs/promises";
118923
- import { join as join76 } from "node:path";
119454
+ import { readFile as readFile39, writeFile as writeFile45 } from "node:fs/promises";
119455
+ import { join as join77 } from "node:path";
118924
119456
 
118925
119457
  // src/regression-api.ts
118926
119458
  import { existsSync as existsSync44 } from "node:fs";
118927
- import { readFile as readFile35 } from "node:fs/promises";
119459
+ import { readFile as readFile38 } from "node:fs/promises";
118928
119460
  var RegressionExistsError = class extends Error {
118929
119461
  code = "E_REGRESSION_EXISTS";
118930
119462
  constructor(id, dir) {
@@ -118950,9 +119482,9 @@ var NoOracleInResultError = class extends Error {
118950
119482
  };
118951
119483
  var RegressionHardSignalOracleError = class extends Error {
118952
119484
  code = "E_REGRESSION_HARD_SIGNAL";
118953
- constructor(resultPath, finding3) {
119485
+ constructor(resultPath, finding4) {
118954
119486
  super(
118955
- `--fingerprint ${finding3.fingerprint} is a ${finding3.kind} defect in ${resultPath} \u2014 a hard signal with its own oracle, which 'regression capture' cannot turn into a page/success-check regression. Keep its repro material with 'jevitate ledger add ${resultPath} ${finding3.fingerprint}', then re-check it with 'jevitate ledger verify' or 'jevitate verify-fix ${finding3.fingerprint}'.`
119487
+ `--fingerprint ${finding4.fingerprint} is a ${finding4.kind} defect in ${resultPath} \u2014 a hard signal with its own oracle, which 'regression capture' cannot turn into a page/success-check regression. Keep its repro material with 'jevitate ledger add ${resultPath} ${finding4.fingerprint}', then re-check it with 'jevitate ledger verify' or 'jevitate verify-fix ${finding4.fingerprint}'.`
118956
119488
  );
118957
119489
  this.name = "RegressionHardSignalOracleError";
118958
119490
  }
@@ -119014,14 +119546,14 @@ async function runRegressionCapture(opts) {
119014
119546
  if (opts.force !== true && existsSync44(recordingPath)) {
119015
119547
  throw new RegressionExistsError(opts.id, opts.regressionsDir);
119016
119548
  }
119017
- const raw = JSON.parse(await readFile35(opts.failingRecordingPath, "utf8"));
119549
+ const raw = JSON.parse(await readFile38(opts.failingRecordingPath, "utf8"));
119018
119550
  const parsed = RecordingSchema.parse(raw);
119019
119551
  const recording = opts.anchored === true ? anchoredRecording(parsed) : parsed;
119020
119552
  const attempts = opts.attempts ?? 3;
119021
119553
  let missionResult;
119022
119554
  let persistedMission;
119023
119555
  if (opts.resultPath) {
119024
- const rawResult = JSON.parse(await readFile35(opts.resultPath, "utf8"));
119556
+ const rawResult = JSON.parse(await readFile38(opts.resultPath, "utf8"));
119025
119557
  missionResult = rawResult;
119026
119558
  try {
119027
119559
  persistedMission = parsePersistedMission(rawResult);
@@ -119030,12 +119562,12 @@ async function runRegressionCapture(opts) {
119030
119562
  }
119031
119563
  }
119032
119564
  if (opts.fingerprint !== void 0 && persistedMission !== void 0) {
119033
- const finding3 = findFinding(persistedMission, opts.fingerprint);
119034
- if (finding3 !== void 0 && finding3.kind === "invariant" && finding3.invariantId !== void 0 && persistedMission.invariantSpec !== void 0) {
119035
- return captureInvariantRegression(opts, recording, persistedMission, finding3, attempts);
119565
+ const finding4 = findFinding(persistedMission, opts.fingerprint);
119566
+ if (finding4 !== void 0 && finding4.kind === "invariant" && finding4.invariantId !== void 0 && persistedMission.invariantSpec !== void 0) {
119567
+ return captureInvariantRegression(opts, recording, persistedMission, finding4, attempts);
119036
119568
  }
119037
- if (finding3 !== void 0 && finding3.kind !== "invariant") {
119038
- throw new RegressionHardSignalOracleError(opts.resultPath, finding3);
119569
+ if (finding4 !== void 0 && finding4.kind !== "invariant") {
119570
+ throw new RegressionHardSignalOracleError(opts.resultPath, finding4);
119039
119571
  }
119040
119572
  }
119041
119573
  let working = recording;
@@ -119114,16 +119646,16 @@ async function captureNetworkCheckRegression(opts, recording, check3, attempts)
119114
119646
  };
119115
119647
  return commitRegression(opts.regressionsDir, opts.id, recording, report2, opts.bugSummary, { kind: "network", check: check3 });
119116
119648
  }
119117
- async function captureInvariantRegression(opts, fallbackRecording, mission, finding3, attempts) {
119649
+ async function captureInvariantRegression(opts, fallbackRecording, mission, finding4, attempts) {
119118
119650
  const invariantSpec = mission.invariantSpec;
119119
- const invariantId = finding3.invariantId;
119120
- const recording = finding3.recording ?? mission.recording ?? fallbackRecording;
119651
+ const invariantId = finding4.invariantId;
119652
+ const recording = finding4.recording ?? mission.recording ?? fallbackRecording;
119121
119653
  assertAuthorizedExploreTarget(mission.target.seedUrl, mission.target.allowlist);
119122
119654
  const verifyResult = await verifyFix({
119123
119655
  fixtureBaseDir: journeyFixtureBaseDir(),
119124
119656
  recording,
119125
- recordingStepIndex: finding3.repro.recordingStepIndex,
119126
- fingerprint: finding3.fingerprint,
119657
+ recordingStepIndex: finding4.repro.recordingStepIndex,
119658
+ fingerprint: finding4.fingerprint,
119127
119659
  defectKind: "invariant",
119128
119660
  invariant: { spec: invariantSpec, id: invariantId, allowlist: mission.target.allowlist, baseUrl: mission.target.seedUrl },
119129
119661
  replays: attempts,
@@ -119141,16 +119673,16 @@ async function captureInvariantRegression(opts, fallbackRecording, mission, find
119141
119673
  rate: 1,
119142
119674
  label: "reproducible",
119143
119675
  fingerprint: { stepSignature: `invariant:${invariantId}` },
119144
- firstFailureAt: finding3.repro.recordingStepIndex
119676
+ firstFailureAt: finding4.repro.recordingStepIndex
119145
119677
  };
119146
119678
  return commitRegression(opts.regressionsDir, opts.id, recording, report2, opts.bugSummary, {
119147
119679
  kind: "invariant",
119148
119680
  invariantId,
119149
119681
  invariantSpec,
119150
- recordingStepIndex: finding3.repro.recordingStepIndex,
119682
+ recordingStepIndex: finding4.repro.recordingStepIndex,
119151
119683
  allowlist: mission.target.allowlist,
119152
119684
  baseUrl: mission.target.seedUrl,
119153
- defectFingerprint: finding3.fingerprint
119685
+ defectFingerprint: finding4.fingerprint
119154
119686
  });
119155
119687
  }
119156
119688
  if (verifyResult.verdict === "intermittent") {
@@ -119173,8 +119705,8 @@ async function runRegressionRun(opts) {
119173
119705
  let recording;
119174
119706
  let meta3;
119175
119707
  try {
119176
- recording = RecordingSchema.parse(JSON.parse(await readFile35(recordingPath, "utf8")));
119177
- meta3 = JSON.parse(await readFile35(metaPath, "utf8"));
119708
+ recording = RecordingSchema.parse(JSON.parse(await readFile38(recordingPath, "utf8")));
119709
+ meta3 = JSON.parse(await readFile38(metaPath, "utf8"));
119178
119710
  } catch {
119179
119711
  throw new RegressionNotFoundError(opts.id, opts.regressionsDir);
119180
119712
  }
@@ -119272,17 +119804,17 @@ function registerRegressionCommands(program2, deps) {
119272
119804
  const opened = [];
119273
119805
  let fx;
119274
119806
  try {
119275
- const raw = JSON.parse(await readFile36(from, "utf8"));
119807
+ const raw = JSON.parse(await readFile39(from, "utf8"));
119276
119808
  const recording = RecordingSchema.parse(raw);
119277
119809
  const captureEmulation = captureEmulationFlag ?? (recording.emulation === void 0 ? void 0 : recording.emulation.device !== void 0 ? { device: recording.emulation.device } : { viewport: recording.emulation.viewport });
119278
119810
  fx = regressionFixtures(
119279
119811
  flags,
119280
119812
  recording,
119281
- resultPath === void 0 ? void 0 : JSON.parse(await readFile36(resultPath, "utf8")),
119813
+ resultPath === void 0 ? void 0 : JSON.parse(await readFile39(resultPath, "utf8")),
119282
119814
  storageState
119283
119815
  );
119284
119816
  const replayFixture = fx;
119285
- const branch = resultPath === void 0 ? void 0 : recordedBranchOf(JSON.parse(await readFile36(resultPath, "utf8")).result);
119817
+ const branch = resultPath === void 0 ? void 0 : recordedBranchOf(JSON.parse(await readFile39(resultPath, "utf8")).result);
119286
119818
  const prefix = branch === void 0 ? void 0 : await branchPrefixOrRefuse(branch, param, storageState);
119287
119819
  if (prefix === null) return;
119288
119820
  const result = await runRegressionCapture({
@@ -119308,8 +119840,8 @@ function registerRegressionCommands(program2, deps) {
119308
119840
  }).then((r) => withEngine(r));
119309
119841
  await fx?.restore();
119310
119842
  if (branch !== void 0 && "metaPath" in result) {
119311
- const meta3 = JSON.parse(await readFile36(result.metaPath, "utf8"));
119312
- await writeFile44(result.metaPath, `${JSON.stringify({ ...meta3, branch }, null, 2)}
119843
+ const meta3 = JSON.parse(await readFile39(result.metaPath, "utf8"));
119844
+ await writeFile45(result.metaPath, `${JSON.stringify({ ...meta3, branch }, null, 2)}
119313
119845
  `);
119314
119846
  }
119315
119847
  const envelope = ok(fx === void 0 ? result : { ...result, fixtures: fx.record() });
@@ -119368,17 +119900,17 @@ function registerRegressionCommands(program2, deps) {
119368
119900
  }
119369
119901
  if (refuseUnsafeName(program2, id, "regression id")) return;
119370
119902
  const regressionsDir = resolveRegressionsDir(dir);
119371
- if (!existsSync45(join76(regressionsDir, `${id}.recording.json`))) {
119903
+ if (!existsSync45(join77(regressionsDir, `${id}.recording.json`))) {
119372
119904
  const notFound = new RegressionNotFoundError(id, regressionsDir);
119373
119905
  emitJson(program2, fail(notFound.code, notFound.message));
119374
119906
  return;
119375
119907
  }
119376
119908
  const opened = [];
119377
119909
  try {
119378
- const recording = RecordingSchema.parse(JSON.parse(await readFile36(join76(regressionsDir, `${id}.recording.json`), "utf8")));
119910
+ const recording = RecordingSchema.parse(JSON.parse(await readFile39(join77(regressionsDir, `${id}.recording.json`), "utf8")));
119379
119911
  let branch;
119380
119912
  try {
119381
- branch = recordedBranchOf(JSON.parse(await readFile36(join76(regressionsDir, `${id}.meta.json`), "utf8")));
119913
+ branch = recordedBranchOf(JSON.parse(await readFile39(join77(regressionsDir, `${id}.meta.json`), "utf8")));
119382
119914
  } catch {
119383
119915
  branch = void 0;
119384
119916
  }
@@ -119652,9 +120184,9 @@ async function queueExploration(targets, queue, args) {
119652
120184
  }
119653
120185
 
119654
120186
  // ../inbox/dist/atomic.js
119655
- import { open as open4, rename as rename6, rm as rm14 } from "node:fs/promises";
120187
+ import { open as open4, rename as rename7, rm as rm14 } from "node:fs/promises";
119656
120188
  import { randomBytes as randomBytes6 } from "node:crypto";
119657
- import { dirname as dirname33, join as join77 } from "node:path";
120189
+ import { dirname as dirname33, join as join78 } from "node:path";
119658
120190
 
119659
120191
  // ../inbox/dist/durability.js
119660
120192
  function durableWritesEnabled(env = process.env) {
@@ -119667,7 +120199,7 @@ async function flushIfDurable(handle) {
119667
120199
 
119668
120200
  // ../inbox/dist/atomic.js
119669
120201
  async function writeFileAtomic(path2, data, mode = 384) {
119670
- const tmp = join77(dirname33(path2), `.${randomBytes6(8).toString("hex")}.tmp`);
120202
+ const tmp = join78(dirname33(path2), `.${randomBytes6(8).toString("hex")}.tmp`);
119671
120203
  try {
119672
120204
  const handle = await open4(tmp, "wx", mode);
119673
120205
  try {
@@ -119676,7 +120208,7 @@ async function writeFileAtomic(path2, data, mode = 384) {
119676
120208
  } finally {
119677
120209
  await handle.close();
119678
120210
  }
119679
- await rename6(tmp, path2);
120211
+ await rename7(tmp, path2);
119680
120212
  } catch (err) {
119681
120213
  await rm14(tmp, { force: true });
119682
120214
  throw err;
@@ -119684,9 +120216,9 @@ async function writeFileAtomic(path2, data, mode = 384) {
119684
120216
  }
119685
120217
 
119686
120218
  // ../inbox/dist/lock.js
119687
- import { mkdir as mkdir36, open as open5, rename as rename7, rm as rm15, stat as stat6 } from "node:fs/promises";
120219
+ import { mkdir as mkdir37, open as open5, rename as rename8, rm as rm15, stat as stat6 } from "node:fs/promises";
119688
120220
  import { randomBytes as randomBytes7 } from "node:crypto";
119689
- import { join as join78 } from "node:path";
120221
+ import { join as join79 } from "node:path";
119690
120222
  var LockTimeoutError = class extends Error {
119691
120223
  constructor(id) {
119692
120224
  super(`could not acquire inbox lock for '${id}'`);
@@ -119697,8 +120229,8 @@ async function withIdLock(dir, id, fn, opts = {}) {
119697
120229
  const staleMs = opts.staleMs ?? 3e4;
119698
120230
  const retryMs = opts.retryMs ?? 20;
119699
120231
  const maxWaitMs = opts.maxWaitMs ?? 1e4;
119700
- const lockPath = join78(dir, `${id}.lock`);
119701
- await mkdir36(dir, { recursive: true, mode: 448 });
120232
+ const lockPath = join79(dir, `${id}.lock`);
120233
+ await mkdir37(dir, { recursive: true, mode: 448 });
119702
120234
  const deadline = clock.now() + maxWaitMs;
119703
120235
  for (; ; ) {
119704
120236
  try {
@@ -119712,7 +120244,7 @@ async function withIdLock(dir, id, fn, opts = {}) {
119712
120244
  const st = await stat6(lockPath);
119713
120245
  if (clock.now() - st.mtimeMs > staleMs) {
119714
120246
  const claimed = `${lockPath}.reclaim.${randomBytes7(6).toString("hex")}`;
119715
- await rename7(lockPath, claimed);
120247
+ await rename8(lockPath, claimed);
119716
120248
  await rm15(claimed, { force: true });
119717
120249
  continue;
119718
120250
  }
@@ -119805,8 +120337,8 @@ function resolveTransition(kind, action) {
119805
120337
  }
119806
120338
 
119807
120339
  // ../inbox/dist/store.js
119808
- import { mkdir as mkdir37, open as open6, readFile as readFile37, readdir as readdir12, stat as stat7, unlink } from "node:fs/promises";
119809
- import { join as join79 } from "node:path";
120340
+ import { mkdir as mkdir38, open as open6, readFile as readFile40, readdir as readdir12, stat as stat7, unlink } from "node:fs/promises";
120341
+ import { join as join80 } from "node:path";
119810
120342
  var InboxItemNotFoundError = class extends Error {
119811
120343
  constructor(id) {
119812
120344
  super(`inbox item not found: ${id}`);
@@ -119847,19 +120379,19 @@ var FsInboxStore = class {
119847
120379
  this.identity = typeof build === "string" ? { version: build } : { version: build.version, commit: build.commit, builtAt: build.builtAt };
119848
120380
  }
119849
120381
  hotPath(id) {
119850
- return join79(this.dir, `${id}.json`);
120382
+ return join80(this.dir, `${id}.json`);
119851
120383
  }
119852
120384
  archivePath(id) {
119853
- return join79(this.dir, "archive", `${id}.json`);
120385
+ return join80(this.dir, "archive", `${id}.json`);
119854
120386
  }
119855
120387
  archiveDir() {
119856
- return join79(this.dir, "archive");
120388
+ return join80(this.dir, "archive");
119857
120389
  }
119858
120390
  /** Reads and validates the hot file, or null on ENOENT. Throws on malformed content (fail-closed). */
119859
120391
  async readHot(id) {
119860
120392
  let raw;
119861
120393
  try {
119862
- raw = await readFile37(this.hotPath(id), "utf8");
120394
+ raw = await readFile40(this.hotPath(id), "utf8");
119863
120395
  } catch (err) {
119864
120396
  if (isNodeError(err, "ENOENT"))
119865
120397
  return null;
@@ -119871,7 +120403,7 @@ var FsInboxStore = class {
119871
120403
  async readArchive(id) {
119872
120404
  let raw;
119873
120405
  try {
119874
- raw = await readFile37(this.archivePath(id), "utf8");
120406
+ raw = await readFile40(this.archivePath(id), "utf8");
119875
120407
  } catch (err) {
119876
120408
  if (isNodeError(err, "ENOENT"))
119877
120409
  return null;
@@ -119883,7 +120415,7 @@ var FsInboxStore = class {
119883
120415
  const validated = InboxItemSchema.parse(item);
119884
120416
  assertSafeInboxId(validated.id);
119885
120417
  await withIdLock(this.dir, validated.id, async () => {
119886
- await mkdir37(this.dir, { recursive: true, mode: 448 });
120418
+ await mkdir38(this.dir, { recursive: true, mode: 448 });
119887
120419
  const serialized = JSON.stringify(validated);
119888
120420
  let fh;
119889
120421
  try {
@@ -119917,7 +120449,7 @@ var FsInboxStore = class {
119917
120449
  if (entry.startsWith("."))
119918
120450
  continue;
119919
120451
  try {
119920
- const raw = await readFile37(join79(this.dir, entry), "utf8");
120452
+ const raw = await readFile40(join80(this.dir, entry), "utf8");
119921
120453
  const parsed = InboxItemSchema.parse(JSON.parse(raw));
119922
120454
  if (parsed.status === "pending")
119923
120455
  items.push(parsed);
@@ -119986,7 +120518,7 @@ var FsInboxStore = class {
119986
120518
  const decision = transition === "approved" || transition === "rejected" || transition === "resolved" ? transition : "resolved";
119987
120519
  updated.resolution = { by: "human", decision, at: now2 };
119988
120520
  const validated = InboxItemSchema.parse(updated);
119989
- await mkdir37(this.archiveDir(), { recursive: true, mode: 448 });
120521
+ await mkdir38(this.archiveDir(), { recursive: true, mode: 448 });
119990
120522
  await writeFileAtomic(this.archivePath(id), JSON.stringify(validated));
119991
120523
  await unlink(this.hotPath(id));
119992
120524
  return validated;
@@ -120038,7 +120570,7 @@ var FsInboxStore = class {
120038
120570
  const updated = { ...item, status: "expired", resolvedAt: at3 };
120039
120571
  delete updated.humanInput;
120040
120572
  const validated = InboxItemSchema.parse(updated);
120041
- await mkdir37(this.archiveDir(), { recursive: true, mode: 448 });
120573
+ await mkdir38(this.archiveDir(), { recursive: true, mode: 448 });
120042
120574
  await writeFileAtomic(this.archivePath(id), JSON.stringify(validated));
120043
120575
  await unlink(this.hotPath(id));
120044
120576
  return true;
@@ -120066,7 +120598,7 @@ var FsInboxStore = class {
120066
120598
  for (const entry of entries) {
120067
120599
  if (!entry.endsWith(".json") || entry.startsWith("."))
120068
120600
  continue;
120069
- const full = join79(this.dir, entry);
120601
+ const full = join80(this.dir, entry);
120070
120602
  try {
120071
120603
  const st = await stat7(full);
120072
120604
  if (!st.isFile())
@@ -120261,17 +120793,17 @@ function isDefectStatus(v) {
120261
120793
  return typeof v === "string" && DEFECT_OUTCOME_STATUSES.includes(v);
120262
120794
  }
120263
120795
  function parseResultOutcome(missionOutcome, goalOutcome, defectStatus) {
120264
- const canonical7 = parseMissionOutcome(missionOutcome);
120265
- if (canonical7 !== null) {
120796
+ const canonical6 = parseMissionOutcome(missionOutcome);
120797
+ if (canonical6 !== null) {
120266
120798
  if (goalOutcome === void 0)
120267
- return { outcome: canonical7 };
120799
+ return { outcome: canonical6 };
120268
120800
  if (!isGoalOutcome(goalOutcome))
120269
120801
  return null;
120270
120802
  if (defectStatus === void 0)
120271
- return foldGoalOutcome(goalOutcome) === canonical7 ? { outcome: canonical7, goalOutcome } : null;
120803
+ return foldGoalOutcome(goalOutcome) === canonical6 ? { outcome: canonical6, goalOutcome } : null;
120272
120804
  if (!isDefectStatus(defectStatus))
120273
120805
  return null;
120274
- return goalMissionOutcome(goalOutcome, defectStatus) === canonical7 ? { outcome: canonical7, goalOutcome } : null;
120806
+ return goalMissionOutcome(goalOutcome, defectStatus) === canonical6 ? { outcome: canonical6, goalOutcome } : null;
120275
120807
  }
120276
120808
  if (goalOutcome === void 0 && typeof missionOutcome === "string" && Object.hasOwn(LEGACY_GOAL_OUTCOMES, missionOutcome)) {
120277
120809
  return { outcome: LEGACY_GOAL_OUTCOMES[missionOutcome], goalOutcome: missionOutcome };
@@ -120345,8 +120877,8 @@ var tagsArg = (args) => {
120345
120877
 
120346
120878
  // src/mcp-tools/findings.ts
120347
120879
  import { existsSync as existsSync47 } from "node:fs";
120348
- import { access, readFile as readFile38 } from "node:fs/promises";
120349
- import { join as join80 } from "node:path";
120880
+ import { access, readFile as readFile41 } from "node:fs/promises";
120881
+ import { join as join81 } from "node:path";
120350
120882
  function findingTools(ctx) {
120351
120883
  const { deps, pathRoots, verifyFixImpl } = ctx;
120352
120884
  const resolveResultId = async (id, tool) => {
@@ -120389,7 +120921,7 @@ function findingTools(ctx) {
120389
120921
  const resultPathsFor = (resultId) => {
120390
120922
  const file2 = missionResultFileName(resultId);
120391
120923
  const dirs = deps.resultDirsFor?.(resultId) ?? [deps.recordingsDir, ...resultId.startsWith("usability-") ? [deps.uxReportsDir] : []];
120392
- return dirs.filter((d) => d !== void 0).map((d) => join80(d, file2));
120924
+ return dirs.filter((d) => d !== void 0).map((d) => join81(d, file2));
120393
120925
  };
120394
120926
  const getMissionResult = async (args) => {
120395
120927
  if (!deps.recordingsDir) {
@@ -120401,7 +120933,7 @@ function findingTools(ctx) {
120401
120933
  let raw;
120402
120934
  for (const path2 of resultPathsFor(ref.resultId)) {
120403
120935
  try {
120404
- raw = await readFile38(path2, "utf8");
120936
+ raw = await readFile41(path2, "utf8");
120405
120937
  break;
120406
120938
  } catch {
120407
120939
  }
@@ -121467,9 +121999,9 @@ function registerMissionCommands(program2, deps) {
121467
121999
  // src/ui-api.ts
121468
122000
  import { createServer } from "node:http";
121469
122001
  import { randomBytes as randomBytes8 } from "node:crypto";
121470
- import { readFile as readFile39, realpath as realpath2 } from "node:fs/promises";
122002
+ import { readFile as readFile42, realpath as realpath2 } from "node:fs/promises";
121471
122003
  import { fileURLToPath as fileURLToPath4 } from "node:url";
121472
- import { join as join81, sep as sep11 } from "node:path";
122004
+ import { join as join82, sep as sep11 } from "node:path";
121473
122005
  import { spawn as spawn6 } from "node:child_process";
121474
122006
  var DEFAULT_PORT = 4180;
121475
122007
  var MAX_PORT_ATTEMPTS = 20;
@@ -121571,7 +122103,7 @@ async function startUiServer(deps) {
121571
122103
  const shouldOpen = deps.open ?? true;
121572
122104
  const indexHtmlPath = fileURLToPath4(new URL("../ui/index.html", import.meta.url));
121573
122105
  const appJsPath = fileURLToPath4(new URL("../ui/app.js", import.meta.url));
121574
- const screenshotsDir = join81(deps.inboxDir, "screenshots");
122106
+ const screenshotsDir = join82(deps.inboxDir, "screenshots");
121575
122107
  const server = createServer();
121576
122108
  let boundPort = 0;
121577
122109
  function send(res, status, body, headers = {}) {
@@ -121583,7 +122115,7 @@ async function startUiServer(deps) {
121583
122115
  async function serveIndex(req, res) {
121584
122116
  const reqUrl = new URL(req.url ?? "/", `http://127.0.0.1:${boundPort}`);
121585
122117
  const t = reqUrl.searchParams.get("t");
121586
- const html = await readFile39(indexHtmlPath, "utf8");
122118
+ const html = await readFile42(indexHtmlPath, "utf8");
121587
122119
  const headers = {
121588
122120
  "Content-Type": "text/html; charset=utf-8",
121589
122121
  "Content-Security-Policy": "default-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline'"
@@ -121596,7 +122128,7 @@ async function startUiServer(deps) {
121596
122128
  return 200;
121597
122129
  }
121598
122130
  async function serveAppJs(res) {
121599
- const js = await readFile39(appJsPath, "utf8");
122131
+ const js = await readFile42(appJsPath, "utf8");
121600
122132
  res.writeHead(200, { "Content-Type": "text/javascript" });
121601
122133
  res.end(js);
121602
122134
  return 200;
@@ -121609,7 +122141,7 @@ async function startUiServer(deps) {
121609
122141
  } catch {
121610
122142
  return send(res, 404, { error: "not_found" });
121611
122143
  }
121612
- const candidate = join81(screenshotsDir, `${id}.png`);
122144
+ const candidate = join82(screenshotsDir, `${id}.png`);
121613
122145
  let resolved;
121614
122146
  try {
121615
122147
  resolved = await realpath2(candidate);
@@ -121620,7 +122152,7 @@ async function startUiServer(deps) {
121620
122152
  return send(res, 404, { error: "not_found" });
121621
122153
  }
121622
122154
  try {
121623
- const data = await readFile39(resolved);
122155
+ const data = await readFile42(resolved);
121624
122156
  res.writeHead(200, { "Content-Type": "image/png" });
121625
122157
  res.end(data);
121626
122158
  return 200;
@@ -121860,7 +122392,7 @@ function registerServeCommands(program2, deps, buildProgram2) {
121860
122392
  }
121861
122393
 
121862
122394
  // src/ux-cli.ts
121863
- import { readFile as readFile40 } from "node:fs/promises";
122395
+ import { readFile as readFile43 } from "node:fs/promises";
121864
122396
  function registerUxCommands(program2, deps) {
121865
122397
  program2.command("ux <recording>").description("offline UX review of a saved Recording \u2014 ranked, cited usability findings").option("--app-class <class>", "app class for calibration (required), e.g. consumer|admin|internal").option(
121866
122398
  "--show <labels>",
@@ -121888,7 +122420,7 @@ function registerUxCommands(program2, deps) {
121888
122420
  }
121889
122421
  let recording;
121890
122422
  try {
121891
- recording = RecordingSchema.parse(JSON.parse(await readFile40(recordingPath, "utf8")));
122423
+ recording = RecordingSchema.parse(JSON.parse(await readFile43(recordingPath, "utf8")));
121892
122424
  } catch (err) {
121893
122425
  emitJson(program2, fail("E_UX_INPUT", String(err instanceof Error ? err.message : err)));
121894
122426
  return;
@@ -121948,7 +122480,7 @@ function registerUxCommands(program2, deps) {
121948
122480
 
121949
122481
  // src/logs-retention.ts
121950
122482
  import { readFileSync as readFileSync39, readdirSync as readdirSync11, rmSync as rmSync2, rmdirSync as rmdirSync2, statSync as statSync13 } from "node:fs";
121951
- import { join as join82 } from "node:path";
122483
+ import { join as join83 } from "node:path";
121952
122484
  var DEFAULT_LOGS_RETENTION = { ttlDays: 14, keepLatest: 50 };
121953
122485
  var LogsConfigError = class extends Error {
121954
122486
  code = "E_LOGS_CONFIG";
@@ -121990,8 +122522,8 @@ function runsIn(root2) {
121990
122522
  }
121991
122523
  const byKey = /* @__PURE__ */ new Map();
121992
122524
  for (const date5 of dates) {
121993
- for (const name of readdirSync11(join82(root2, date5))) {
121994
- const path2 = join82(root2, date5, name);
122525
+ for (const name of readdirSync11(join83(root2, date5))) {
122526
+ const path2 = join83(root2, date5, name);
121995
122527
  const key2 = `${date5}/${RUN_KEY.exec(name)?.[1] ?? name}`;
121996
122528
  const run = byKey.get(key2) ?? { paths: [], mtimeMs: 0 };
121997
122529
  run.paths.push(path2);
@@ -121999,7 +122531,7 @@ function runsIn(root2) {
121999
122531
  byKey.set(key2, run);
122000
122532
  }
122001
122533
  }
122002
- return { runs: [...byKey].map(([key2, r]) => ({ key: key2, ...r })), dateDirs: dates.map((d) => join82(root2, d)) };
122534
+ return { runs: [...byKey].map(([key2, r]) => ({ key: key2, ...r })), dateDirs: dates.map((d) => join83(root2, d)) };
122003
122535
  }
122004
122536
  function pruneLogs(root2, retention, opts = {}) {
122005
122537
  const nowMs = opts.nowMs ?? clock.now();
@@ -122247,6 +122779,119 @@ function registerInboxCommands(program2, deps) {
122247
122779
  }
122248
122780
  }
122249
122781
 
122782
+ // src/acceptance-audit-cli.ts
122783
+ import { readFile as readFile45, mkdir as mkdir39, writeFile as writeFile46 } from "node:fs/promises";
122784
+
122785
+ // src/acceptance-audit.ts
122786
+ import { access as access2, readFile as readFile44 } from "node:fs/promises";
122787
+ var CORE = [
122788
+ ["requirements", "requirements"],
122789
+ ["persona", "persona"],
122790
+ ["source-journey", "sourceJourney"],
122791
+ ["approved-hash", "approvedHash"],
122792
+ ["verify-fix", "verifyFix"],
122793
+ ["strict-review", "strictReview"]
122794
+ ];
122795
+ async function finding3(asset, path2) {
122796
+ if (path2 === void 0 || path2 === "") return { asset, state: "missing", reason: "no explicit local path supplied" };
122797
+ try {
122798
+ await access2(path2);
122799
+ } catch {
122800
+ return { asset, state: "missing", path: path2, reason: "local file does not exist" };
122801
+ }
122802
+ try {
122803
+ if ((await readFile44(path2, "utf8")).trim() === "") return { asset, state: "inconclusive", path: path2, reason: "local file is empty" };
122804
+ } catch {
122805
+ return { asset, state: "inconclusive", path: path2, reason: "local file cannot be read as text" };
122806
+ }
122807
+ return { asset, state: "complete", path: path2 };
122808
+ }
122809
+ async function auditCoreAcceptanceAssets(assets) {
122810
+ return Promise.all(CORE.map(([asset, key2]) => finding3(asset, assets[key2])));
122811
+ }
122812
+
122813
+ // src/acceptance-audit-schema.ts
122814
+ var ACCEPTANCE_AUDIT_SCHEMA_VERSION = 1;
122815
+ function acceptanceAuditResult(request, findings) {
122816
+ const passed = findings.every((finding4) => finding4.state === "complete");
122817
+ return { schemaVersion: ACCEPTANCE_AUDIT_SCHEMA_VERSION, request, findings, passed, exitCode: passed ? 0 : 1 };
122818
+ }
122819
+
122820
+ // src/acceptance-audit-suite.ts
122821
+ function auditSuiteIntegrity(entries) {
122822
+ const duplicates = entries.filter((entry, index) => entries.findIndex((other) => other.id === entry.id) !== index).map((entry) => entry.id);
122823
+ const missingSidecar = entries.filter((entry) => !entry.sidecar).map((entry) => entry.id);
122824
+ const missingPartition = entries.filter((entry) => entry.partition.trim() === "").map((entry) => entry.id);
122825
+ const missingRevision = entries.filter((entry) => entry.revision.trim() === "").map((entry) => entry.id);
122826
+ const hasNegativeControl = entries.some((entry) => entry.negativeControl);
122827
+ return [
122828
+ {
122829
+ asset: "suite-sidecar",
122830
+ state: missingSidecar.length === 0 ? "complete" : "missing",
122831
+ ...missingSidecar.length === 0 ? {} : { reason: `missing sidecar: ${missingSidecar.join(", ")}` }
122832
+ },
122833
+ {
122834
+ asset: "negative-control",
122835
+ state: hasNegativeControl ? "complete" : "missing",
122836
+ ...hasNegativeControl ? {} : { reason: "suite has no declared negative control" }
122837
+ },
122838
+ {
122839
+ asset: "partition",
122840
+ state: duplicates.length === 0 && missingPartition.length === 0 ? "complete" : "inconclusive",
122841
+ ...duplicates.length === 0 && missingPartition.length === 0 ? {} : { reason: [...duplicates.length === 0 ? [] : [`duplicate ids: ${duplicates.join(", ")}`], ...missingPartition.length === 0 ? [] : [`unpartitioned: ${missingPartition.join(", ")}`]].join("; ") }
122842
+ },
122843
+ {
122844
+ asset: "exact-revision",
122845
+ state: missingRevision.length === 0 ? "complete" : "stale",
122846
+ ...missingRevision.length === 0 ? {} : { reason: `missing exact revision: ${missingRevision.join(", ")}` }
122847
+ }
122848
+ ];
122849
+ }
122850
+
122851
+ // src/acceptance-audit-render.ts
122852
+ var xml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
122853
+ function renderAcceptanceAuditMarkdown(result) {
122854
+ return `# Acceptance audit
122855
+
122856
+ ${result.passed ? "Passed" : "Failed"} (exit ${result.exitCode}).
122857
+
122858
+ | asset | state | reason |
122859
+ | --- | --- | --- |
122860
+ ${result.findings.map((f2) => `| ${f2.asset} | ${f2.state} | ${f2.reason ?? ""} |`).join("\n")}
122861
+ `;
122862
+ }
122863
+ function renderAcceptanceAuditJunit(result) {
122864
+ const failures = result.findings.filter((f2) => f2.state !== "complete");
122865
+ return `<?xml version="1.0" encoding="UTF-8"?><testsuite name="jevitate.acceptance-audit" tests="${result.findings.length}" failures="${failures.length}">${result.findings.map((f2) => `<testcase name="${xml(f2.asset)}">${f2.state === "complete" ? "" : `<failure message="${xml(f2.state)}">${xml(f2.reason ?? "")}</failure>`}</testcase>`).join("")}</testsuite>
122866
+ `;
122867
+ }
122868
+ function renderAcceptanceAuditSarif(result) {
122869
+ return `${JSON.stringify({ version: "2.1.0", runs: [{ tool: { driver: { name: "jevitate acceptance audit", rules: result.findings.map((f2) => ({ id: f2.asset })) } }, results: result.findings.filter((f2) => f2.state !== "complete").map((f2) => ({ ruleId: f2.asset, level: "error", message: { text: f2.reason ?? f2.state } })) }] }, null, 2)}
122870
+ `;
122871
+ }
122872
+
122873
+ // src/acceptance-audit-cli.ts
122874
+ function registerAcceptanceAuditCommands(program2) {
122875
+ const acceptance = program2.command("acceptance").description("read-only acceptance evidence tools");
122876
+ acceptance.command("audit").description("audit release acceptance evidence and emit CI artifacts").requiredOption("--requirements <file>").requiredOption("--suite <file>").option("--persona <file>").option("--journey <file>").option("--approval <file>").option("--verify <file>").option("--review <file>").option("--out <dir>").option("--json").action(async function() {
122877
+ const o = this.opts();
122878
+ try {
122879
+ const core = await auditCoreAcceptanceAssets({ requirements: o.requirements, persona: o.persona, sourceJourney: o.journey, approvedHash: o.approval, verifyFix: o.verify, strictReview: o.review });
122880
+ const entries = JSON.parse(await readFile45(o.suite, "utf8"));
122881
+ if (!Array.isArray(entries)) throw new Error("suite must be a JSON array of acceptance entries");
122882
+ const result = acceptanceAuditResult({ requirementsFile: o.requirements, suiteFile: o.suite, strict: true }, [...core, ...auditSuiteIntegrity(entries)]);
122883
+ if (o.out !== void 0) {
122884
+ await mkdir39(o.out, { recursive: true });
122885
+ await Promise.all([writeFile46(`${o.out}/acceptance-audit.md`, renderAcceptanceAuditMarkdown(result)), writeFile46(`${o.out}/acceptance-audit.junit.xml`, renderAcceptanceAuditJunit(result)), writeFile46(`${o.out}/acceptance-audit.sarif`, renderAcceptanceAuditSarif(result)), writeFile46(`${o.out}/acceptance-audit.json`, `${JSON.stringify(result, null, 2)}
122886
+ `)]);
122887
+ }
122888
+ emitEnvelope(program2, ok(result), { json: o.json === true, exitCode: result.exitCode, command: "acceptance audit", human: (r) => renderAcceptanceAuditMarkdown(r) });
122889
+ } catch (err) {
122890
+ emitEnvelope(program2, fail("E_ACCEPTANCE_AUDIT", err instanceof Error ? err.message : String(err)), { json: o.json === true, command: "acceptance audit" });
122891
+ }
122892
+ });
122893
+ }
122894
+
122250
122895
  // src/program.ts
122251
122896
  function versionString() {
122252
122897
  return formatVersion(currentEngineInfo());
@@ -122286,6 +122931,7 @@ function buildProgram(deps) {
122286
122931
  registerMissionCommands(program2, deps);
122287
122932
  registerServeCommands(program2, deps, buildProgram);
122288
122933
  registerInboxCommands(program2, deps);
122934
+ registerAcceptanceAuditCommands(program2);
122289
122935
  registerUxCommands(program2, deps);
122290
122936
  registerAiCommands(program2, deps);
122291
122937
  registerCheckCommand(