@jevitate/cli 0.23.0 → 0.24.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 (40) 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/bin.js +153 -68
  26. package/dist/build-info.generated.js +3 -3
  27. package/dist/catalog-bundle.d.ts +4 -2
  28. package/dist/catalog-bundle.js +8 -6
  29. package/dist/catalog-bundle.js.map +1 -1
  30. package/dist/catalog-contract-identity.d.ts +5 -0
  31. package/dist/catalog-contract-identity.js +7 -0
  32. package/dist/catalog-contract-identity.js.map +1 -0
  33. package/dist/catalog.js +18 -7
  34. package/dist/catalog.js.map +1 -1
  35. package/dist/journey-review.d.ts +3 -0
  36. package/dist/journey-review.js +13 -2
  37. package/dist/journey-review.js.map +1 -1
  38. package/dist/journey-stale-api.js +4 -4
  39. package/dist/journey-stale-api.js.map +1 -1
  40. package/package.json +14 -11
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");
@@ -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;
@@ -42172,6 +42192,7 @@ async function judgeGoalCompletion(judge, input2) {
42172
42192
  ...input2.signInFacts === void 0 ? [] : [`SIGN-IN (observed by code, trusted): ${input2.signInFacts}`],
42173
42193
  ...input2.saveFacts === void 0 ? [] : [`SAVE (observed by code, trusted): ${input2.saveFacts}`],
42174
42194
  ...input2.writeFacts === void 0 ? [] : [`WRITES (observed by code, trusted): ${input2.writeFacts}`],
42195
+ ...input2.readBackFacts === void 0 ? [] : [`READ-BACK (observed by code, trusted): ${input2.readBackFacts}`],
42175
42196
  ...input2.pageStatus === void 0 || input2.pageStatus === "" ? [] : [`PAGE STATUS (untrusted): ${input2.pageStatus}`],
42176
42197
  `VISIBLE PAGE TEXT (untrusted): ${input2.pageText.replace(/\s+/g, " ").slice(0, GOAL_TEXT_CHARS)}`,
42177
42198
  ...input2.fieldValues === void 0 || input2.fieldValues.length === 0 ? [] : [
@@ -45607,6 +45628,7 @@ var OBSERVED_PAGE_CHARS = 2e4;
45607
45628
  var MIN_QUOTE_CHARS = 3;
45608
45629
  var MAX_OBSERVED_FIELDS = 40;
45609
45630
  var OBSERVED_FIELD_CHARS = 500;
45631
+ var MAX_READ_BACK_VALUES = 50;
45610
45632
  var MAX_OBSERVED_CONTROLS = 120;
45611
45633
  var HEADING_CHARS = 200;
45612
45634
  function controlFields(controls) {
@@ -45696,6 +45718,21 @@ var ObservedPages = class {
45696
45718
  this.#sourceRoute.set(v, pending.route);
45697
45719
  }
45698
45720
  }
45721
+ /**
45722
+ * #671: values the run typed into a field on one document load. A field holding one shows the run's
45723
+ * own entry, never what the app kept: retyping a sent message into its composer after a reload is
45724
+ * not a read-back.
45725
+ */
45726
+ #typedOn = null;
45727
+ /** #671: the run typed `value` into a field (a message composer too) on document load `document`. */
45728
+ noteTypedOn(value, document2) {
45729
+ const v = fold(redactContext(value, this.secrets).slice(0, OBSERVED_FIELD_CHARS));
45730
+ if (document2 === void 0 || v === "")
45731
+ return;
45732
+ if (this.#typedOn?.document !== document2)
45733
+ this.#typedOn = { document: document2, values: /* @__PURE__ */ new Set() };
45734
+ this.#typedOn.values.add(v);
45735
+ }
45699
45736
  /** #647: the run's sent values (folded) that no page loaded from the server since has shown. */
45700
45737
  unreadWrites() {
45701
45738
  return this.#unread;
@@ -45720,10 +45757,19 @@ var ObservedPages = class {
45720
45757
  this.#sourceRoute.delete(v);
45721
45758
  }
45722
45759
  this.#readBackCount += back.length;
45760
+ for (const v of back)
45761
+ if (this.#readBackValues.length < MAX_READ_BACK_VALUES)
45762
+ this.#readBackValues.push(v);
45723
45763
  return back;
45724
45764
  }
45725
45765
  /** #645: how many sent values a page loaded from the server has shown again. */
45726
45766
  #readBackCount = 0;
45767
+ /** #667: the sent values (folded) read back so far, bounded. */
45768
+ #readBackValues = [];
45769
+ /** #667: the sent values (folded) a page loaded from the server has shown again. */
45770
+ readBackValues() {
45771
+ return this.#readBackValues;
45772
+ }
45727
45773
  /**
45728
45774
  * #645: the app was seen to keep what the run's writes sent — at least one value was read back and
45729
45775
  * none is still unread. Code evidence the page judgment lacks (see `groundDone`).
@@ -45766,7 +45812,9 @@ var ObservedPages = class {
45766
45812
  this.#topNav = nav;
45767
45813
  }
45768
45814
  this.#settleSent(page.text);
45769
- this.readBack(headings2.document, [page.text, ...kept.map((f2) => f2.value)].join("\n"), url2);
45815
+ const typed = this.#typedOn !== null && this.#typedOn.document === headings2.document ? [...this.#typedOn.values] : [];
45816
+ const appFields = kept.map((f2) => f2.value).filter((value) => !typed.some((t) => fold(value).includes(t)));
45817
+ this.readBack(headings2.document, [page.text, ...appFields].join("\n"), url2);
45770
45818
  if (page.text.trim() === "" && kept.length === 0)
45771
45819
  return;
45772
45820
  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 +46479,7 @@ var ABSENCE_GOAL = /\bwhether\b|\b(?:is|are) there\b|\bthere (?:is|are) (?:no|no
46431
46479
  function goalAdmitsAbsence(goal) {
46432
46480
  return ABSENCE_GOAL.test(goal);
46433
46481
  }
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;
46482
+ 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
46483
  function goalAsksToWrite(goal) {
46436
46484
  return WRITE_GOAL.test(goal);
46437
46485
  }
@@ -48275,6 +48323,7 @@ function newStep(ctx, input2) {
48275
48323
  if (proposedDone)
48276
48324
  writes = jobWritesOf(ctx);
48277
48325
  const writeFacts = writes !== void 0 && saved === null && writesAllSucceeded(writes) ? writes.facts : void 0;
48326
+ const readBackFacts = ctx.observed.readBackConfirmed() ? readBackFactsOf(ctx.observed.readBackValues(), pageText) : void 0;
48278
48327
  const judged = await judgeGoalCompletion(cfg.judge, {
48279
48328
  goal: cfg.goal,
48280
48329
  url: snap.url,
@@ -48285,6 +48334,7 @@ function newStep(ctx, input2) {
48285
48334
  ...signIn === null ? {} : { signInFacts: signIn.facts },
48286
48335
  ...saved === null ? {} : { saveFacts: saved.facts },
48287
48336
  ...writeFacts === void 0 ? {} : { writeFacts },
48337
+ ...readBackFacts === void 0 ? {} : { readBackFacts },
48288
48338
  fieldValues: fieldValuesOf(snap.controls, ctx.isBound)
48289
48339
  }).catch(() => ({ goalMet: null, goalIsSignIn: null, goalIsSave: null, goalIsWrites: null }));
48290
48340
  goalMet = judged.goalMet;
@@ -48315,6 +48365,11 @@ function newStep(ctx, input2) {
48315
48365
  };
48316
48366
  return { ...input2, record: record2, signIn, pendingNote, acceptedBy, groundGoal };
48317
48367
  }
48368
+ function readBackFactsOf(values2, pageText) {
48369
+ const text2 = pageText.toLowerCase();
48370
+ const shown2 = values2.filter((v) => text2.includes(v)).length;
48371
+ 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`;
48372
+ }
48318
48373
 
48319
48374
  // ../explore/dist/goal-loop/min-effort.js
48320
48375
  function deferEnding(ctx, step, ending) {
@@ -48444,6 +48499,7 @@ async function handleReport(ctx, step) {
48444
48499
  }
48445
48500
 
48446
48501
  // ../explore/dist/goal-loop/handle-done.js
48502
+ 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
48503
  async function unreadWriteHold(ctx, step, outcome) {
48448
48504
  if (outcome.status === "completed" && outcome.verifiedBy === "success-condition")
48449
48505
  return null;
@@ -48535,12 +48591,13 @@ async function handleDone(ctx, step) {
48535
48591
  }
48536
48592
  }
48537
48593
  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}`, {
48594
+ const why = ctx.writeGoal && !ctx.wroteOk ? `${verdict.reason}; ${NO_WRITE_YET}` : verdict.reason;
48595
+ ctx.history.push(`done rejected: ${why} \u2014 keep working toward the goal`);
48596
+ record2(false, `done rejected (${ctx.doneRejections}/${MAX_DONE_REJECTIONS}): ${why}`, {
48540
48597
  ...judgments === void 0 ? {} : { judgments }
48541
48598
  });
48542
48599
  if (ctx.doneRejections >= MAX_DONE_REJECTIONS) {
48543
- ctx.incomplete = `the model proposed done ${ctx.doneRejections} times, but ${verdict.reason}`;
48600
+ ctx.incomplete = `the model proposed done ${ctx.doneRejections} times, but ${why}`;
48544
48601
  ctx.endedOnRejectedDone = true;
48545
48602
  ctx.stop = "done";
48546
48603
  return "stop";
@@ -48922,6 +48979,7 @@ async function handleCodeTypedField(ctx, step) {
48922
48979
  ctx.valueLog.typed(control.name || control.summary, value);
48923
48980
  ctx.save.noteTyped(control.name || control.summary, value);
48924
48981
  ctx.observed.noteOwnInput(value);
48982
+ ctx.observed.noteTypedOn(value, await documentOf(ctx.page));
48925
48983
  ctx.noteMutation(`type ${control.name}`, control.descriptor, snap.signature, at3, { field: keyOf(control), value });
48926
48984
  ctx.tracker.countAction();
48927
48985
  ctx.cleared(control);
@@ -49484,6 +49542,7 @@ async function handleFill(ctx, step) {
49484
49542
  ctx.typeProbe = { key: keyOf(control), label: quote(control.name || control.summary, 60), at: typedAt, background: typedBackground, state: typedState };
49485
49543
  ctx.recorder.fill(control.descriptor, text2, at3);
49486
49544
  ctx.valueLog.typed(control.name || control.summary, text2);
49545
+ ctx.observed.noteTypedOn(text2, await documentOf(ctx.page));
49487
49546
  if (!ctx.isBound(control) && !isCredentialField(control) && !sendable(control)) {
49488
49547
  ctx.save.noteTyped(control.name || control.summary, text2);
49489
49548
  ctx.observed.noteOwnInput(text2);
@@ -53033,14 +53092,14 @@ function ownAssertion(s3) {
53033
53092
  return { field: "resume", assertion: s3.resume };
53034
53093
  return "expect" in s3 ? { field: "expect", assertion: s3.expect } : null;
53035
53094
  }
53036
- function canonical2(d) {
53095
+ function canonical(d) {
53037
53096
  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
53097
  return JSON.stringify(sort(d));
53039
53098
  }
53040
53099
  function isOwnTargetVisible(step) {
53041
53100
  if (!("expect" in step) || !("target" in step))
53042
53101
  return false;
53043
- return step.expect.kind === "visible" && canonical2(step.expect.target) === canonical2(step.target);
53102
+ return step.expect.kind === "visible" && canonical(step.expect.target) === canonical(step.target);
53044
53103
  }
53045
53104
  function isNoClaimExpect(step) {
53046
53105
  if (!("expect" in step))
@@ -54930,10 +54989,10 @@ function bindParams(inputs, record2, request) {
54930
54989
  return params;
54931
54990
  }
54932
54991
  function recordFingerprint(outputs, record2) {
54933
- const canonical7 = {};
54992
+ const canonical6 = {};
54934
54993
  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")}`;
54994
+ canonical6[name] = record2[name] ?? null;
54995
+ return `sha256:${createHash5("sha256").update(JSON.stringify(canonical6)).digest("hex")}`;
54937
54996
  }
54938
54997
  function extractedNames(journey) {
54939
54998
  const names = /* @__PURE__ */ new Set();
@@ -63059,9 +63118,9 @@ function hostStarvedFailure(failure3, health) {
63059
63118
  }
63060
63119
 
63061
63120
  // 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";
63121
+ var GENERATED_COMMIT = "e0f6f96";
63122
+ var GENERATED_TREE = "d0b472e";
63123
+ var GENERATED_BUILT_AT = "2026-10-24T16:37:57.792Z";
63065
63124
 
63066
63125
  // src/version.ts
63067
63126
  import { readFileSync as readFileSync5 } from "node:fs";
@@ -63083,13 +63142,13 @@ function readCliVersion() {
63083
63142
 
63084
63143
  // src/engine.ts
63085
63144
  function bundledCommit() {
63086
- return true ? "ad72163" : void 0;
63145
+ return true ? "e0f6f96" : void 0;
63087
63146
  }
63088
63147
  function bundledBuiltAt() {
63089
- return true ? "2026-10-23T21:02:56.700Z" : void 0;
63148
+ return true ? "2026-10-24T16:37:57.792Z" : void 0;
63090
63149
  }
63091
63150
  function bundledTree() {
63092
- return true ? "379caf5" : void 0;
63151
+ return true ? "d0b472e" : void 0;
63093
63152
  }
63094
63153
  function currentBuildTree() {
63095
63154
  return bundledTree() ?? GENERATED_TREE;
@@ -63732,12 +63791,12 @@ function substituteDeep(v, sub) {
63732
63791
  return v;
63733
63792
  }
63734
63793
  function fixtureSpecHash(spec, hooks) {
63735
- return sha256(canonical3({ spec: spec ?? null, hooks: hooks ?? null })).slice(0, 16);
63794
+ return sha256(canonical2({ spec: spec ?? null, hooks: hooks ?? null })).slice(0, 16);
63736
63795
  }
63737
- function canonical3(v) {
63738
- if (Array.isArray(v)) return `[${v.map(canonical3).join(",")}]`;
63796
+ function canonical2(v) {
63797
+ if (Array.isArray(v)) return `[${v.map(canonical2).join(",")}]`;
63739
63798
  if (isRecord(v)) {
63740
- return `{${Object.keys(v).sort().filter((k) => v[k] !== void 0).map((k) => `${JSON.stringify(k)}:${canonical3(v[k])}`).join(",")}}`;
63799
+ return `{${Object.keys(v).sort().filter((k) => v[k] !== void 0).map((k) => `${JSON.stringify(k)}:${canonical2(v[k])}`).join(",")}}`;
63741
63800
  }
63742
63801
  return JSON.stringify(v);
63743
63802
  }
@@ -63859,7 +63918,7 @@ var MissionFixtures = class {
63859
63918
  return redactUrl(redactText(text2, [...this.#opts.secrets ?? [], ...bound2, ...this.secrets()]));
63860
63919
  }
63861
63920
  identity() {
63862
- return `fx-${sha256(canonical3({ spec: this.#specHash, outputs: this.publicOutputs(), secret: [...this.#secretNames].sort() })).slice(0, 16)}`;
63921
+ return `fx-${sha256(canonical2({ spec: this.#specHash, outputs: this.publicOutputs(), secret: [...this.#secretNames].sort() })).slice(0, 16)}`;
63863
63922
  }
63864
63923
  record() {
63865
63924
  return {
@@ -78957,14 +79016,14 @@ var GitSourceManager = class {
78957
79016
 
78958
79017
  // ../sources/dist/hash.js
78959
79018
  import { createHash as createHash9 } from "node:crypto";
78960
- function canonicalJson2(value) {
79019
+ function canonicalJson3(value) {
78961
79020
  if (value === null)
78962
79021
  return "null";
78963
79022
  if (Array.isArray(value))
78964
- return `[${value.map(canonicalJson2).join(",")}]`;
79023
+ return `[${value.map(canonicalJson3).join(",")}]`;
78965
79024
  if (typeof value === "object") {
78966
79025
  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(",")}}`;
79026
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson3(v)}`).join(",")}}`;
78968
79027
  }
78969
79028
  if (typeof value === "number") {
78970
79029
  if (!Number.isFinite(value))
@@ -78975,7 +79034,7 @@ function canonicalJson2(value) {
78975
79034
  }
78976
79035
  function canonicalJourneyHash(file2) {
78977
79036
  const validated = SharedJourneyFileSchema.parse(file2);
78978
- const hex3 = createHash9("sha256").update(canonicalJson2(validated), "utf8").digest("hex");
79037
+ const hex3 = createHash9("sha256").update(canonicalJson3(validated), "utf8").digest("hex");
78979
79038
  return `sha256:${hex3}`;
78980
79039
  }
78981
79040
 
@@ -80681,6 +80740,15 @@ function journeyReviewHash(journey) {
80681
80740
  for (const k of BOOKKEEPING) delete metadata[k];
80682
80741
  return contentHash({ metadata, recording: journey.recording });
80683
80742
  }
80743
+ function journeyLegacyReviewHash(journey) {
80744
+ const metadata = { ...journey.metadata };
80745
+ for (const k of BOOKKEEPING) delete metadata[k];
80746
+ return legacyContentHash({ metadata, recording: journey.recording });
80747
+ }
80748
+ function journeyApprovalMatches(journey) {
80749
+ const approved = journey.metadata.approval?.contentHash;
80750
+ return approved !== void 0 && (approved === journeyReviewHash(journey) || approved === journeyLegacyReviewHash(journey));
80751
+ }
80684
80752
  function targetOf3(step) {
80685
80753
  if (step.kind === "forEach") return step.items;
80686
80754
  return "target" in step ? step.target : void 0;
@@ -80887,7 +80955,7 @@ function buildChange(journey, hash3, snapshot2, stepIdOnly) {
80887
80955
  const idOnly = stepIdOnly ? { stepIdOnly: true } : {};
80888
80956
  if (snapshot2 === null || snapshot2 === void 0) {
80889
80957
  if (approval !== void 0) {
80890
- return { kind: "snapshot-missing", approvedHash: approval.contentHash, approvedAt: approval.at, changed: approval.contentHash !== hash3, ...idOnly };
80958
+ return { kind: "snapshot-missing", approvedHash: approval.contentHash, approvedAt: approval.at, changed: !journeyApprovalMatches(journey), ...idOnly };
80891
80959
  }
80892
80960
  return journey.metadata.promoted ? { kind: "no-record" } : { kind: "first-approval" };
80893
80961
  }
@@ -81241,9 +81309,9 @@ var UnknownCatalogItemError = class extends Error {
81241
81309
  return this.kind === "persona" ? "E_UNKNOWN_PERSONA" : "E_UNKNOWN_JOB";
81242
81310
  }
81243
81311
  };
81244
- function statusOf3(approval, hash3) {
81312
+ function statusOf3(approval, hash3, legacyHash) {
81245
81313
  if (approval === void 0) return "draft";
81246
- return approval.contentHash === hash3 ? "approved" : "stale";
81314
+ return approval.contentHash === hash3 || approval.contentHash === legacyHash ? "approved" : "stale";
81247
81315
  }
81248
81316
  async function readJsonFile(path2) {
81249
81317
  const raw = await readFile8(path2, "utf8");
@@ -81308,7 +81376,7 @@ function catalogPersona(p, path2) {
81308
81376
  hasLogin: p.fields.login !== void 0,
81309
81377
  ...approval === void 0 ? {} : { approval },
81310
81378
  contentHash: hash3,
81311
- status: statusOf3(approval, hash3)
81379
+ status: statusOf3(approval, hash3, personaLegacyContentHash(p.id, p.fields))
81312
81380
  };
81313
81381
  }
81314
81382
  function personaContentHash(id, fields) {
@@ -81316,6 +81384,11 @@ function personaContentHash(id, fields) {
81316
81384
  for (const [k, v] of Object.entries(fields)) if (k !== "approval" && !SESSION_KEYS.has(k)) described[k] = v;
81317
81385
  return contentHash(described);
81318
81386
  }
81387
+ function personaLegacyContentHash(id, fields) {
81388
+ const described = { id };
81389
+ for (const [k, v] of Object.entries(fields)) if (k !== "approval" && !SESSION_KEYS.has(k)) described[k] = v;
81390
+ return legacyContentHash(described);
81391
+ }
81319
81392
  function jobList(raw, path2) {
81320
81393
  if (Array.isArray(raw)) return raw;
81321
81394
  if (isRecord(raw) && Array.isArray(raw.jobs)) return raw.jobs;
@@ -81338,13 +81411,17 @@ function catalogJobOf(job) {
81338
81411
  personas: [.../* @__PURE__ */ new Set([...job.personas ?? [], ...job.persona === void 0 ? [] : [job.persona]])],
81339
81412
  ...job.approval === void 0 ? {} : { approval: job.approval },
81340
81413
  contentHash: hash3,
81341
- status: statusOf3(job.approval, hash3)
81414
+ status: statusOf3(job.approval, hash3, jobLegacyContentHash(job))
81342
81415
  };
81343
81416
  }
81344
81417
  function jobContentHash(job) {
81345
81418
  const { approval: _a3, ...rest } = job;
81346
81419
  return contentHash(rest);
81347
81420
  }
81421
+ function jobLegacyContentHash(job) {
81422
+ const { approval: _a3, ...rest } = job;
81423
+ return legacyContentHash(rest);
81424
+ }
81348
81425
  function catalogJourney(journey) {
81349
81426
  const m = journey.metadata;
81350
81427
  const hash3 = journeyReviewHash(journey);
@@ -81357,7 +81434,7 @@ function catalogJourney(journey) {
81357
81434
  ...m.persona === void 0 ? {} : { personaText: m.persona },
81358
81435
  ...m.approval === void 0 ? {} : { approval: m.approval },
81359
81436
  contentHash: hash3,
81360
- changedSinceApproval: m.promoted && m.approval !== void 0 && m.approval.contentHash !== hash3
81437
+ changedSinceApproval: m.promoted && m.approval !== void 0 && !journeyApprovalMatches(journey)
81361
81438
  };
81362
81439
  }
81363
81440
  var CatalogLoader = class {
@@ -82860,12 +82937,12 @@ function healFloor(recorded, riskOf) {
82860
82937
  }
82861
82938
  return { floor: "healable", reason: "" };
82862
82939
  }
82863
- function canonical4(v) {
82940
+ function canonical3(v) {
82864
82941
  if (Array.isArray(v))
82865
- return `[${v.map(canonical4).join(",")}]`;
82942
+ return `[${v.map(canonical3).join(",")}]`;
82866
82943
  if (v !== null && typeof v === "object") {
82867
82944
  const o = v;
82868
- return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonical4(o[k])}`).join(",")}}`;
82945
+ return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonical3(o[k])}`).join(",")}}`;
82869
82946
  }
82870
82947
  return JSON.stringify(v) ?? "undefined";
82871
82948
  }
@@ -82887,7 +82964,7 @@ function assertProofUntouched(before, after) {
82887
82964
  for (const k of /* @__PURE__ */ new Set([...Object.keys(b2), ...Object.keys(a)])) {
82888
82965
  if (free.has(k))
82889
82966
  continue;
82890
- if (canonical4(b2[k]) !== canonical4(a[k]))
82967
+ if (canonical3(b2[k]) !== canonical3(a[k]))
82891
82968
  return { code: "proof-field-changed", detail: `the candidate changes the step's \`${k}\`` };
82892
82969
  }
82893
82970
  return null;
@@ -82898,15 +82975,15 @@ function assertRecordedProofUntouched(before, after) {
82898
82975
  for (const k of /* @__PURE__ */ new Set([...Object.keys(b2), ...Object.keys(a)])) {
82899
82976
  if (k === "step")
82900
82977
  continue;
82901
- if (canonical4(b2[k]) !== canonical4(a[k]))
82978
+ if (canonical3(b2[k]) !== canonical3(a[k]))
82902
82979
  return { code: "proof-field-changed", detail: `the candidate changes the recorded step's \`${k}\`` };
82903
82980
  }
82904
- return canonical4(before.step) === canonical4(after.step) ? null : assertProofUntouched(before.step, after.step);
82981
+ return canonical3(before.step) === canonical3(after.step) ? null : assertProofUntouched(before.step, after.step);
82905
82982
  }
82906
82983
  function assertRecordingProofUntouched(base, proposed) {
82907
82984
  const { pages: bp, ...bRest } = base;
82908
82985
  const { pages: pp, ...pRest } = proposed;
82909
- if (canonical4(bRest) !== canonical4(pRest))
82986
+ if (canonical3(bRest) !== canonical3(pRest))
82910
82987
  return { code: "proof-field-changed", detail: "the revision changes the recording's own fields" };
82911
82988
  if (bp.length !== pp.length)
82912
82989
  return { code: "shape-changed", detail: `the revision has ${pp.length} pages; the Journey has ${bp.length}` };
@@ -82914,7 +82991,7 @@ function assertRecordingProofUntouched(base, proposed) {
82914
82991
  for (let i = 0; i < bp.length; i++) {
82915
82992
  const { steps: bs, ...bPage } = bp[i];
82916
82993
  const { steps: ps, ...pPage } = pp[i];
82917
- if (canonical4(bPage) !== canonical4(pPage))
82994
+ if (canonical3(bPage) !== canonical3(pPage))
82918
82995
  return { code: "proof-field-changed", detail: `the revision changes page ${i + 1}` };
82919
82996
  if (bs.length !== ps.length)
82920
82997
  return { code: "shape-changed", detail: `the revision has ${ps.length} steps on page ${i + 1}; the Journey has ${bs.length}` };
@@ -84500,11 +84577,11 @@ async function removeFile(path2) {
84500
84577
  await assertNoSymlink(path2);
84501
84578
  await rm4(path2, { force: true });
84502
84579
  }
84503
- function canonical5(v) {
84504
- if (Array.isArray(v)) return `[${v.map(canonical5).join(",")}]`;
84580
+ function canonical4(v) {
84581
+ if (Array.isArray(v)) return `[${v.map(canonical4).join(",")}]`;
84505
84582
  if (v !== null && typeof v === "object") {
84506
84583
  const o = v;
84507
- return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonical5(o[k])}`).join(",")}}`;
84584
+ return `{${Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonical4(o[k])}`).join(",")}}`;
84508
84585
  }
84509
84586
  return JSON.stringify(v) ?? "undefined";
84510
84587
  }
@@ -84529,7 +84606,7 @@ function proposalFloorViolation(stored, proposed) {
84529
84606
  for (let i = 0; i < after.length; i++) {
84530
84607
  const b2 = before[i];
84531
84608
  const a = after[i];
84532
- if (b2 !== void 0 && canonical5(b2.step) === canonical5(a.step)) continue;
84609
+ if (b2 !== void 0 && canonical4(b2.step) === canonical4(a.step)) continue;
84533
84610
  for (const [which, rec] of [["before", b2?.recorded], ["after", a.recorded]]) {
84534
84611
  if (rec === void 0) return `step ${i + 1} is not in the stored Journey`;
84535
84612
  const floor = healFloor(rec, riskOf);
@@ -100811,14 +100888,14 @@ var CREDENTIAL_KEY2 = /pass|secret|token|otp|totp|pin|cvc|cvv|card|auth|session|
100811
100888
  var MAX_DIGEST_BODY = 64e3;
100812
100889
  var CredentialBody = class extends Error {
100813
100890
  };
100814
- function canonical6(v) {
100815
- if (Array.isArray(v)) return v.map(canonical6);
100891
+ function canonical5(v) {
100892
+ if (Array.isArray(v)) return v.map(canonical5);
100816
100893
  if (v !== null && typeof v === "object") {
100817
100894
  const out = {};
100818
100895
  for (const k of Object.keys(v).sort()) {
100819
100896
  if (CREDENTIAL_KEY2.test(k)) throw new CredentialBody();
100820
100897
  if (VOLATILE_KEY.test(k)) continue;
100821
- out[k] = canonical6(v[k]);
100898
+ out[k] = canonical5(v[k]);
100822
100899
  }
100823
100900
  return out;
100824
100901
  }
@@ -100835,7 +100912,7 @@ function payloadDigest(method, endpoint2, body, secrets) {
100835
100912
  value = Object.fromEntries(new URLSearchParams(body));
100836
100913
  }
100837
100914
  try {
100838
- const canon = JSON.stringify(canonical6(value));
100915
+ const canon = JSON.stringify(canonical5(value));
100839
100916
  return createHash12("sha256").update(`${method.toUpperCase()} ${endpoint2}
100840
100917
  ${canon}`).digest("hex").slice(0, 32);
100841
100918
  } catch {
@@ -104264,10 +104341,17 @@ import { existsSync as existsSync25 } from "node:fs";
104264
104341
  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
104342
  import { basename as basename15, dirname as dirname24, join as join56, resolve as resolve18, sep as sep7 } from "node:path";
104266
104343
 
104344
+ // src/catalog-contract-identity.ts
104345
+ var CATALOG_CONTRACT_IDENTITY = {
104346
+ "kind": "jevitate.catalog-contract",
104347
+ "version": "2.0.0",
104348
+ "manifestSha256": "c1292a8727d7dc0dbe1d63dc768d34b6cc94ae60fab3a67aeb0a53837e1735ad"
104349
+ };
104350
+
104267
104351
  // src/catalog-bundle.ts
104268
104352
  var BUNDLE_KIND = "journeeze.catalog-bundle";
104269
104353
  var BUNDLE_VERSION = 1;
104270
- var BUNDLE_MINOR = 1;
104354
+ var BUNDLE_MINOR = 2;
104271
104355
  var CatalogBundleInputError = class extends CatalogInputError {
104272
104356
  constructor(issues) {
104273
104357
  super(`the catalog cannot be exported as a Journeeze bundle (${issues.length} problem(s)): ${issues.join("; ")}`);
@@ -104632,7 +104716,7 @@ function bundleChecks(input2, journeys, warn) {
104632
104716
  if (outcome.outcome !== "clean") why = `the run was ${outcome.outcome}, and a baseline comes only from a clean run`;
104633
104717
  else if (journeyId === void 0 || journeyHash === void 0) why = "the item names no Journey review hash";
104634
104718
  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`;
104719
+ 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
104720
  else {
104637
104721
  const b2 = bundleBaseline(item.baseline, cj.journey);
104638
104722
  if ("drop" in b2) why = b2.drop;
@@ -104686,13 +104770,13 @@ function bundleDemos(input2, journeys, warn) {
104686
104770
  warn.push(`${where}: its Journey is not in the bundle (not promoted, or left out) \u2014 the demo is left out`);
104687
104771
  continue;
104688
104772
  }
104689
- const approved = ex.cj.approval !== void 0 && ex.cj.approval.contentHash === ex.cj.contentHash;
104773
+ const approved = ex.cj.approval !== void 0 && !ex.cj.changedSinceApproval;
104690
104774
  if (!approved) {
104691
104775
  warn.push(`${where}: its Journey changed since it was approved \u2014 the demo is left out (approve the Journey and its demo again)`);
104692
104776
  continue;
104693
104777
  }
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)`);
104778
+ if (d.renderedFrom !== ex.cj.approval.contentHash) {
104779
+ 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
104780
  continue;
104697
104781
  }
104698
104782
  if (d.steps.length === 0 || d.steps.length > MEDIA_LIMITS.demoSteps) {
@@ -104844,6 +104928,7 @@ function buildCatalogBundle(input2) {
104844
104928
  version: BUNDLE_VERSION,
104845
104929
  minor: BUNDLE_MINOR,
104846
104930
  producer: { tool: "jevitate", version: input2.producer.version, ...commit === void 0 ? {} : { commit } },
104931
+ catalogContract: CATALOG_CONTRACT_IDENTITY,
104847
104932
  product: { name: input2.productName },
104848
104933
  catalog: { personas, jobs, journeys: journeys.map((j) => j.entry) },
104849
104934
  ...shown2.demos.length === 0 ? {} : { demos: shown2.demos },
@@ -109807,8 +109892,8 @@ var jevReadinessAnalyzer = {
109807
109892
  // src/journey-stale-api.ts
109808
109893
  function isStepIdOnlyStale(journey, snapshot2) {
109809
109894
  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);
109895
+ if (approval === void 0 || journeyApprovalMatches(journey)) return false;
109896
+ if (snapshot2 !== null && [journeyReviewHash(snapshot2), journeyLegacyReviewHash(snapshot2)].includes(approval.contentHash)) return isStepIdOnlyChange(snapshot2, journey);
109812
109897
  return isStepIdOnlyChange(approval.contentHash, journey);
109813
109898
  }
109814
109899
  async function listStaleJourneys(req) {
@@ -109822,7 +109907,7 @@ async function listStaleJourneys(req) {
109822
109907
  const approval = journey.metadata.approval;
109823
109908
  if (approval === void 0) continue;
109824
109909
  const contentHash2 = journeyReviewHash(journey);
109825
- const hashStale = approval.contentHash !== contentHash2;
109910
+ const hashStale = !journeyApprovalMatches(journey);
109826
109911
  const links = journeyLinks(catalog, catalogJourney(journey));
109827
109912
  const catalogStale = !hashStale && links.linked && [links.job?.status, links.persona?.status].includes("stale");
109828
109913
  if (!hashStale && !catalogStale) continue;
@@ -120261,17 +120346,17 @@ function isDefectStatus(v) {
120261
120346
  return typeof v === "string" && DEFECT_OUTCOME_STATUSES.includes(v);
120262
120347
  }
120263
120348
  function parseResultOutcome(missionOutcome, goalOutcome, defectStatus) {
120264
- const canonical7 = parseMissionOutcome(missionOutcome);
120265
- if (canonical7 !== null) {
120349
+ const canonical6 = parseMissionOutcome(missionOutcome);
120350
+ if (canonical6 !== null) {
120266
120351
  if (goalOutcome === void 0)
120267
- return { outcome: canonical7 };
120352
+ return { outcome: canonical6 };
120268
120353
  if (!isGoalOutcome(goalOutcome))
120269
120354
  return null;
120270
120355
  if (defectStatus === void 0)
120271
- return foldGoalOutcome(goalOutcome) === canonical7 ? { outcome: canonical7, goalOutcome } : null;
120356
+ return foldGoalOutcome(goalOutcome) === canonical6 ? { outcome: canonical6, goalOutcome } : null;
120272
120357
  if (!isDefectStatus(defectStatus))
120273
120358
  return null;
120274
- return goalMissionOutcome(goalOutcome, defectStatus) === canonical7 ? { outcome: canonical7, goalOutcome } : null;
120359
+ return goalMissionOutcome(goalOutcome, defectStatus) === canonical6 ? { outcome: canonical6, goalOutcome } : null;
120275
120360
  }
120276
120361
  if (goalOutcome === void 0 && typeof missionOutcome === "string" && Object.hasOwn(LEGACY_GOAL_OUTCOMES, missionOutcome)) {
120277
120362
  return { outcome: LEGACY_GOAL_OUTCOMES[missionOutcome], goalOutcome: missionOutcome };
@@ -5,7 +5,7 @@
5
5
  // this plain `tsc --build` dist (no bundler substitution), so it needs real committed values
6
6
  // here instead. See ./engine.ts, which prefers the esbuild-injected value when present and
7
7
  // falls back to these.
8
- export const GENERATED_COMMIT = "ad72163";
9
- export const GENERATED_TREE = "379caf5";
10
- export const GENERATED_BUILT_AT = "2026-10-23T21:02:48.517Z";
8
+ export const GENERATED_COMMIT = "e0f6f96";
9
+ export const GENERATED_TREE = "d0b472e";
10
+ export const GENERATED_BUILT_AT = "2026-10-24T16:37:51.277Z";
11
11
  //# sourceMappingURL=build-info.generated.js.map
@@ -1,5 +1,6 @@
1
1
  import { JOURNEY_RUN_OUTCOMES, type MissionOutcome } from "@jevitate/domain";
2
2
  import { CatalogInputError, type Catalog } from "./catalog.js";
3
+ import { CATALOG_CONTRACT_IDENTITY } from "./catalog-contract-identity.js";
3
4
  /**
4
5
  * #464 (d464a) — the PURE builder of a Journeeze catalog bundle, version 1.0 (journeeze-saas
5
6
  * `docs/contract/catalog-bundle-v1.md` at 61f8c92, schema `catalog-bundle.v1.json`). No I/O, no
@@ -40,8 +41,8 @@ import { CatalogInputError, type Catalog } from "./catalog.js";
40
41
  */
41
42
  export declare const BUNDLE_KIND: "journeeze.catalog-bundle";
42
43
  export declare const BUNDLE_VERSION: 1;
43
- /** The schema minor this producer is built against (minor 1, journeeze-saas #173: optional `finding.locator` and `check.appVersion`). */
44
- export declare const BUNDLE_MINOR: 1;
44
+ /** Minor 2 binds every new bundle to the exact immutable Jevitate catalog contract. */
45
+ export declare const BUNDLE_MINOR: 2;
45
46
  export interface BundleApproval {
46
47
  readonly contentHash: string;
47
48
  readonly at: string;
@@ -198,6 +199,7 @@ export interface CatalogBundleV1 {
198
199
  readonly version: string;
199
200
  readonly commit?: string;
200
201
  };
202
+ readonly catalogContract: typeof CATALOG_CONTRACT_IDENTITY;
201
203
  readonly product: {
202
204
  readonly name: string;
203
205
  };