@jevitate/cli 0.18.1 → 0.20.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.
- package/dist/bin.js +533 -74
- package/dist/build-info.generated.js +3 -3
- package/dist/journey-api.js +5 -1
- package/dist/journey-api.js.map +1 -1
- package/dist/journey-chain.d.ts +6 -1
- package/dist/journey-chain.js +27 -10
- package/dist/journey-chain.js.map +1 -1
- package/dist/journey-cli/chain.js +4 -2
- package/dist/journey-cli/chain.js.map +1 -1
- package/dist/journey-cli/verify.js +2 -1
- package/dist/journey-cli/verify.js.map +1 -1
- package/dist/journey-network-checks.js +7 -1
- package/dist/journey-network-checks.js.map +1 -1
- package/dist/journey-readiness.js +93 -9
- package/dist/journey-readiness.js.map +1 -1
- package/dist/journey-review/render.js +14 -2
- package/dist/journey-review/render.js.map +1 -1
- package/dist/journey-review.d.ts +3 -1
- package/dist/journey-review.js +18 -3
- package/dist/journey-review.js.map +1 -1
- package/dist/journey-verify.d.ts +12 -2
- package/dist/journey-verify.js +65 -7
- package/dist/journey-verify.js.map +1 -1
- package/dist/mcp-cli-tools.js +2 -1
- package/dist/mcp-cli-tools.js.map +1 -1
- package/package.json +12 -12
package/dist/bin.js
CHANGED
|
@@ -39690,15 +39690,23 @@ var INSTRUMENT = `(() => {
|
|
|
39690
39690
|
})();`;
|
|
39691
39691
|
var MAX_CAPTURED = 2e4;
|
|
39692
39692
|
var RequestCapture = class {
|
|
39693
|
+
/**
|
|
39694
|
+
* #617: every request that finished during the capture (its whole completion history), each
|
|
39695
|
+
* tagged with whether THIS capture saw it begin. A request already in flight when the capture
|
|
39696
|
+
* started still finishes into it, but was not sent by the run the capture covers.
|
|
39697
|
+
*/
|
|
39693
39698
|
#requests = [];
|
|
39694
39699
|
/** #283: requests sent since the capture started that have not finished (keyed by the request). */
|
|
39695
39700
|
#inflight = /* @__PURE__ */ new Map();
|
|
39696
39701
|
/** #283: requests sent that never reported an end (their document went away first). */
|
|
39697
39702
|
#unfinished = [];
|
|
39698
39703
|
#truncated = false;
|
|
39699
|
-
/**
|
|
39700
|
-
|
|
39701
|
-
|
|
39704
|
+
/**
|
|
39705
|
+
* @internal — fed by the page's monitor. `sent` is false for a completion the capture never saw
|
|
39706
|
+
* begin (#617); it stays in the completion history but is not counted as sent by the capture.
|
|
39707
|
+
*/
|
|
39708
|
+
add(r, sent = true) {
|
|
39709
|
+
this.#requests.push({ r, sent });
|
|
39702
39710
|
if (this.#requests.length > MAX_CAPTURED) {
|
|
39703
39711
|
this.#requests.shift();
|
|
39704
39712
|
this.#truncated = true;
|
|
@@ -39716,22 +39724,27 @@ var RequestCapture = class {
|
|
|
39716
39724
|
const sent = this.#inflight.get(key2);
|
|
39717
39725
|
this.#inflight.delete(key2);
|
|
39718
39726
|
if (finished !== null)
|
|
39719
|
-
this.add(finished);
|
|
39727
|
+
this.add(finished, sent !== void 0);
|
|
39720
39728
|
else if (sent !== void 0 && this.#unfinished.length < MAX_CAPTURED)
|
|
39721
39729
|
this.#unfinished.push(sent);
|
|
39722
39730
|
}
|
|
39723
|
-
/** The requests captured so far, in the order they finished. */
|
|
39731
|
+
/** The requests captured so far, in the order they finished, including ones the capture never saw begin. */
|
|
39724
39732
|
requests() {
|
|
39725
|
-
return
|
|
39733
|
+
return this.#requests.map((e) => e.r);
|
|
39726
39734
|
}
|
|
39727
39735
|
/**
|
|
39728
|
-
* Every request SENT since the capture started (#283): the finished ones
|
|
39729
|
-
* ones still in flight or abandoned without an end event (`pending: true`, `status: null`).
|
|
39730
|
-
* `requestMade` check is judged over these — a request counts once it was sent, whether or not
|
|
39731
|
-
* its response has arrived.
|
|
39736
|
+
* Every request SENT since the capture started (#283): the finished ones this capture saw begin,
|
|
39737
|
+
* then the ones still in flight or abandoned without an end event (`pending: true`, `status: null`).
|
|
39738
|
+
* A `requestMade` check is judged over these — a request counts once it was sent, whether or not
|
|
39739
|
+
* its response has arrived. A request that began before the capture started is never among them
|
|
39740
|
+
* (#617): only its completion shows up, in `requests()`.
|
|
39732
39741
|
*/
|
|
39733
39742
|
sent() {
|
|
39734
|
-
return [
|
|
39743
|
+
return [
|
|
39744
|
+
...this.#requests.filter((e) => e.sent).map((e) => e.r),
|
|
39745
|
+
...this.#unfinished,
|
|
39746
|
+
...this.#inflight.values()
|
|
39747
|
+
];
|
|
39735
39748
|
}
|
|
39736
39749
|
/** True when more requests finished than the capture keeps (the oldest were dropped). */
|
|
39737
39750
|
get truncated() {
|
|
@@ -53640,6 +53653,72 @@ function mutationPairIssues(journey) {
|
|
|
53640
53653
|
});
|
|
53641
53654
|
return out;
|
|
53642
53655
|
}
|
|
53656
|
+
function targetedProofPlanIssues(journey) {
|
|
53657
|
+
const plans = journey.metadata.targetedProofs ?? [];
|
|
53658
|
+
if (plans.length === 0)
|
|
53659
|
+
return [];
|
|
53660
|
+
const sites = new Set(journeyAssertions(journey).map(assertionSiteKey));
|
|
53661
|
+
const steps = flat(journey);
|
|
53662
|
+
const out = [];
|
|
53663
|
+
const seen = /* @__PURE__ */ new Set();
|
|
53664
|
+
plans.forEach((plan2, index) => {
|
|
53665
|
+
const key2 = `${plan2.check}\0${plan2.mustFailWhen}`;
|
|
53666
|
+
if (seen.has(key2))
|
|
53667
|
+
out.push({ index, field: "check", message: `targetedProofs: duplicate plan for ${plan2.check} and ${plan2.mustFailWhen}` });
|
|
53668
|
+
seen.add(key2);
|
|
53669
|
+
if (!SITE_RE.test(plan2.check)) {
|
|
53670
|
+
out.push({ index, field: "check", message: `targetedProofs: ${JSON.stringify(plan2.check)} is not an assertion site \u2014 expected step:<n>, step-request:<n>:<i> or end-state:<i>` });
|
|
53671
|
+
} else if (!sites.has(plan2.check)) {
|
|
53672
|
+
out.push({ index, field: "check", message: `targetedProofs: the Journey has no assertion at ${plan2.check}` });
|
|
53673
|
+
}
|
|
53674
|
+
let mutated;
|
|
53675
|
+
try {
|
|
53676
|
+
mutated = resolveMutationRef(journey, plan2.mustFailWhen);
|
|
53677
|
+
} catch (err) {
|
|
53678
|
+
out.push({ index, field: "mustFailWhen", message: `targetedProofs: ${err instanceof Error ? err.message : String(err)}` });
|
|
53679
|
+
}
|
|
53680
|
+
const check3 = /^step:([1-9][0-9]*)$/.exec(plan2.check);
|
|
53681
|
+
const targetStep = check3 === null ? null : Number(check3[1]);
|
|
53682
|
+
const relaxed2 = /* @__PURE__ */ new Set();
|
|
53683
|
+
for (const entry of plan2.relax) {
|
|
53684
|
+
if (!/^step:[1-9][0-9]*$/.test(entry)) {
|
|
53685
|
+
out.push({ index, field: "relax", message: `targetedProofs: ${JSON.stringify(entry)} is not a step assertion site \u2014 expected step:<n>` });
|
|
53686
|
+
continue;
|
|
53687
|
+
}
|
|
53688
|
+
if (relaxed2.has(entry)) {
|
|
53689
|
+
out.push({ index, field: "relax", message: `targetedProofs: ${entry} is relaxed twice` });
|
|
53690
|
+
continue;
|
|
53691
|
+
}
|
|
53692
|
+
relaxed2.add(entry);
|
|
53693
|
+
const n3 = Number(entry.slice("step:".length));
|
|
53694
|
+
const recorded = steps[n3 - 1];
|
|
53695
|
+
if (recorded === void 0) {
|
|
53696
|
+
out.push({ index, field: "relax", message: `targetedProofs: the Journey has no step ${n3}` });
|
|
53697
|
+
continue;
|
|
53698
|
+
}
|
|
53699
|
+
if (targetStep === n3) {
|
|
53700
|
+
out.push({ index, field: "relax", message: `targetedProofs: ${entry} is the target assertion \u2014 a declared proof never relaxes it` });
|
|
53701
|
+
continue;
|
|
53702
|
+
}
|
|
53703
|
+
if (targetStep !== null && n3 > targetStep) {
|
|
53704
|
+
out.push({ index, field: "relax", message: `targetedProofs: ${entry} is not before the target step:${targetStep}` });
|
|
53705
|
+
continue;
|
|
53706
|
+
}
|
|
53707
|
+
if (recorded.step.kind === "handback") {
|
|
53708
|
+
out.push({ index, field: "relax", message: `targetedProofs: ${entry} is a resume assertion \u2014 a declared proof never relaxes it` });
|
|
53709
|
+
continue;
|
|
53710
|
+
}
|
|
53711
|
+
if (recorded.step.kind !== "assert" && !("expect" in recorded.step)) {
|
|
53712
|
+
out.push({ index, field: "relax", message: `targetedProofs: ${entry} has no page postcondition to relax` });
|
|
53713
|
+
continue;
|
|
53714
|
+
}
|
|
53715
|
+
if (mutated !== void 0 && n3 < mutated.step) {
|
|
53716
|
+
out.push({ index, field: "relax", message: `targetedProofs: ${entry} is before the mutated step ${mutated.step} (${plan2.mustFailWhen})` });
|
|
53717
|
+
}
|
|
53718
|
+
}
|
|
53719
|
+
});
|
|
53720
|
+
return out;
|
|
53721
|
+
}
|
|
53643
53722
|
function expectedText(a) {
|
|
53644
53723
|
if (a.kind === "valueEquals")
|
|
53645
53724
|
return typeof a.value === "string" ? { kind: a.kind, text: a.value } : null;
|
|
@@ -53836,13 +53915,14 @@ function relaxedMutationReplay(journey, m, opts = {}) {
|
|
|
53836
53915
|
return { journey, skipIndex: index };
|
|
53837
53916
|
if (m.kind === "swap-param" || m.kind === "truncate-param" || m.kind === "guard-param") {
|
|
53838
53917
|
const how = paramReplay(journey, m, opts.params ?? {});
|
|
53839
|
-
const
|
|
53918
|
+
const copy2 = how.journey === journey ? structuredClone(journey) : how.journey;
|
|
53919
|
+
const target2 = flat(copy2)[index];
|
|
53840
53920
|
if (target2 !== void 0 && "expect" in target2.step) {
|
|
53841
53921
|
const step = { ...target2.step, expect: relaxedExpect(target2.step) };
|
|
53842
53922
|
delete step.waitFor;
|
|
53843
53923
|
target2.step = step;
|
|
53844
53924
|
}
|
|
53845
|
-
return how;
|
|
53925
|
+
return { ...how, journey: copy2 };
|
|
53846
53926
|
}
|
|
53847
53927
|
if (m.kind === "block-write") {
|
|
53848
53928
|
const requests = journeyWriteSteps(journey, opts).find((w) => w.step === m.step)?.requests ?? [];
|
|
@@ -53866,6 +53946,174 @@ function relaxedMutationReplay(journey, m, opts = {}) {
|
|
|
53866
53946
|
}
|
|
53867
53947
|
return { journey: copy };
|
|
53868
53948
|
}
|
|
53949
|
+
var MAX_TARGETED_RELAXATIONS = 4;
|
|
53950
|
+
var MAX_DECLARED_RELAXATIONS = 8;
|
|
53951
|
+
function failedSiteKey(f2) {
|
|
53952
|
+
if (f2.where === "step")
|
|
53953
|
+
return `step:${f2.step}`;
|
|
53954
|
+
if (f2.where === "step-request")
|
|
53955
|
+
return `step-request:${f2.step}:${f2.checkIndex}`;
|
|
53956
|
+
if (f2.where === "end-state")
|
|
53957
|
+
return `end-state:${f2.index}`;
|
|
53958
|
+
return "unevaluable";
|
|
53959
|
+
}
|
|
53960
|
+
function targetedMutationReplay(journey, m, targetSiteKey, relaxedSiteKeys, opts = {}) {
|
|
53961
|
+
if (relaxedSiteKeys.includes(targetSiteKey))
|
|
53962
|
+
throw new Error(`${targetSiteKey} is the target assertion \u2014 a targeted proof never relaxes it`);
|
|
53963
|
+
const base = relaxedMutationReplay(journey, m, opts);
|
|
53964
|
+
if (relaxedSiteKeys.length === 0)
|
|
53965
|
+
return base;
|
|
53966
|
+
return { ...base, journey: relaxStepSites(journey, base.journey, relaxedSiteKeys) };
|
|
53967
|
+
}
|
|
53968
|
+
function targetedControlReplay(journey, m, relaxedSiteKeys) {
|
|
53969
|
+
const own2 = flat(journey)[m.step - 1]?.step;
|
|
53970
|
+
const ownKey = own2 !== void 0 && (own2.kind === "assert" || "expect" in own2) ? [`step:${m.step}`] : [];
|
|
53971
|
+
return { journey: relaxStepSites(journey, journey, [.../* @__PURE__ */ new Set([...ownKey, ...relaxedSiteKeys])]) };
|
|
53972
|
+
}
|
|
53973
|
+
function relaxStepSites(journey, from, keys) {
|
|
53974
|
+
const byKey = new Map(journeyAssertions(journey).map((s3) => [assertionSiteKey(s3), s3]));
|
|
53975
|
+
const copy = structuredClone(from);
|
|
53976
|
+
const steps = flat(copy);
|
|
53977
|
+
for (const key2 of keys) {
|
|
53978
|
+
const match2 = /^step:([1-9][0-9]*)$/.exec(key2);
|
|
53979
|
+
if (match2 === null)
|
|
53980
|
+
throw new Error(`${key2} is not a step assertion site \u2014 a targeted proof only relaxes step:<n>`);
|
|
53981
|
+
const site = byKey.get(key2);
|
|
53982
|
+
if (site !== void 0 && site.where === "step" && site.field === "resume")
|
|
53983
|
+
throw new Error(`${key2} is a resume assertion \u2014 a targeted proof never relaxes it`);
|
|
53984
|
+
const recorded = steps[Number(match2[1]) - 1];
|
|
53985
|
+
if (recorded === void 0)
|
|
53986
|
+
throw new Error(`journey '${journey.metadata.id}' has no step ${match2[1]} to relax`);
|
|
53987
|
+
const step = { ...recorded.step };
|
|
53988
|
+
if (recorded.step.kind === "assert")
|
|
53989
|
+
step.check = { kind: "urlIncludes", text: "" };
|
|
53990
|
+
else if ("expect" in recorded.step)
|
|
53991
|
+
step.expect = relaxedExpect(recorded.step);
|
|
53992
|
+
else
|
|
53993
|
+
throw new Error(`${key2} has no page postcondition to relax`);
|
|
53994
|
+
delete step.waitFor;
|
|
53995
|
+
recorded.step = step;
|
|
53996
|
+
}
|
|
53997
|
+
return copy;
|
|
53998
|
+
}
|
|
53999
|
+
function nextTargetedRelaxation(journey, plan2, m, targetSiteKey, result, relaxedSoFar) {
|
|
54000
|
+
const target = journeyAssertions(journey).find((s3) => assertionSiteKey(s3) === targetSiteKey);
|
|
54001
|
+
if (target === void 0)
|
|
54002
|
+
return { refuse: `${targetSiteKey} is not an assertion site of this Journey` };
|
|
54003
|
+
if (classifyMutation(target, m, result) !== "cascade")
|
|
54004
|
+
return null;
|
|
54005
|
+
const first = result.failedSites[0];
|
|
54006
|
+
if (first === void 0)
|
|
54007
|
+
return { refuse: "no failed site to relax" };
|
|
54008
|
+
if (first.where === "unevaluable")
|
|
54009
|
+
return { refuse: "unevaluable end state" };
|
|
54010
|
+
if (first.where !== "step")
|
|
54011
|
+
return { refuse: `${failedSiteKey(first)} is not a step postcondition` };
|
|
54012
|
+
const key2 = `step:${first.step}`;
|
|
54013
|
+
if (!first.postcondition)
|
|
54014
|
+
return { refuse: `${key2} action failed (not a postcondition)` };
|
|
54015
|
+
if (first.step < m.step)
|
|
54016
|
+
return { refuse: "failure before the mutated step" };
|
|
54017
|
+
const step = flat(journey)[first.step - 1]?.step;
|
|
54018
|
+
if (step === void 0)
|
|
54019
|
+
return { refuse: `${key2} does not exist` };
|
|
54020
|
+
if (step.kind === "handback")
|
|
54021
|
+
return { refuse: `${key2} is a resume assertion (not a relaxable page postcondition)` };
|
|
54022
|
+
if (target.where === "step" && target.step <= first.step)
|
|
54023
|
+
return { refuse: `${key2} is not before the target ${targetSiteKey}` };
|
|
54024
|
+
if (!plan2.pairs.some((p) => p.site === key2 && p.mutation === m.id))
|
|
54025
|
+
return { refuse: `${key2} is not paired with ${m.id}` };
|
|
54026
|
+
if (relaxedSoFar.includes(key2))
|
|
54027
|
+
return { refuse: `${key2} is already relaxed` };
|
|
54028
|
+
if (relaxedSoFar.length >= MAX_TARGETED_RELAXATIONS)
|
|
54029
|
+
return { refuse: `relaxation bound ${MAX_TARGETED_RELAXATIONS} reached` };
|
|
54030
|
+
return { relax: key2 };
|
|
54031
|
+
}
|
|
54032
|
+
async function runTargetedProof(journey, plan2, m, target, initial, replay, opts = {}) {
|
|
54033
|
+
const site = assertionSiteKey(target);
|
|
54034
|
+
const relaxedSites = [];
|
|
54035
|
+
const relaxedFirst = initial.relaxed !== void 0 && initial.failedSites.some((f2) => f2.where === "step" && f2.step === m.step && f2.postcondition) && !(target.where === "step" && target.step === m.step);
|
|
54036
|
+
let run = relaxedFirst ? { outcome: initial.relaxed.outcome, failedSites: initial.relaxed.failedSites } : { outcome: initial.outcome, failedSites: initial.failedSites };
|
|
54037
|
+
for (; ; ) {
|
|
54038
|
+
const next = nextTargetedRelaxation(journey, plan2, m, site, run, relaxedSites);
|
|
54039
|
+
if (next === null)
|
|
54040
|
+
break;
|
|
54041
|
+
if ("refuse" in next)
|
|
54042
|
+
return { site, relaxedSites, outcome: run.outcome, failedSites: run.failedSites, refused: next.refuse };
|
|
54043
|
+
relaxedSites.push(next.relax);
|
|
54044
|
+
const relaxedNow = [...relaxedSites];
|
|
54045
|
+
run = await replay((j) => targetedMutationReplay(j, m, site, relaxedNow, opts), "targeted");
|
|
54046
|
+
}
|
|
54047
|
+
if (relaxedSites.length > 0 && classifyMutationPlain(target, m, run) === "sensitive") {
|
|
54048
|
+
const control = await replay((j) => targetedControlReplay(j, m, relaxedSites), "control");
|
|
54049
|
+
if (control.outcome !== "passed") {
|
|
54050
|
+
return {
|
|
54051
|
+
site,
|
|
54052
|
+
relaxedSites,
|
|
54053
|
+
outcome: run.outcome,
|
|
54054
|
+
failedSites: run.failedSites,
|
|
54055
|
+
control: control.outcome,
|
|
54056
|
+
refused: `the unmutated control replay with ${relaxedSites.join(", ")} relaxed did not pass (${control.outcome})`
|
|
54057
|
+
};
|
|
54058
|
+
}
|
|
54059
|
+
return { site, relaxedSites, outcome: run.outcome, failedSites: run.failedSites, control: control.outcome };
|
|
54060
|
+
}
|
|
54061
|
+
return { site, relaxedSites, outcome: run.outcome, failedSites: run.failedSites };
|
|
54062
|
+
}
|
|
54063
|
+
async function runDeclaredTargetedProof(journey, plan2, m, target, declared, replay, opts = {}) {
|
|
54064
|
+
const site = assertionSiteKey(target);
|
|
54065
|
+
const relax = declared.relax;
|
|
54066
|
+
const unpaired = relax.find((key2) => !plan2.pairs.some((p) => p.site === key2 && p.mutation === m.id));
|
|
54067
|
+
if (unpaired !== void 0) {
|
|
54068
|
+
return { site, relaxedSites: relax, outcome: "error", failedSites: [], declared: true, refused: `${unpaired} is not paired with ${m.id}` };
|
|
54069
|
+
}
|
|
54070
|
+
const relaxedNow = [...relax];
|
|
54071
|
+
const run = await replay((j) => targetedMutationReplay(j, m, site, relaxedNow, opts), "targeted");
|
|
54072
|
+
const first = run.failedSites[0];
|
|
54073
|
+
if (first !== void 0 && first.where === "step" && first.postcondition && !relax.includes(`step:${first.step}`) && (target.where !== "step" || first.step < target.step)) {
|
|
54074
|
+
const key2 = `step:${first.step}`;
|
|
54075
|
+
return { site, relaxedSites: relax, outcome: run.outcome, failedSites: run.failedSites, declared: true, refused: `${key2} blocked the declared proof (not in relax)` };
|
|
54076
|
+
}
|
|
54077
|
+
if (classifyMutationPlain(target, m, run) === "sensitive") {
|
|
54078
|
+
const control = await replay((j) => targetedControlReplay(j, m, relaxedNow), "control");
|
|
54079
|
+
if (control.outcome !== "passed") {
|
|
54080
|
+
return {
|
|
54081
|
+
site,
|
|
54082
|
+
relaxedSites: relax,
|
|
54083
|
+
outcome: run.outcome,
|
|
54084
|
+
failedSites: run.failedSites,
|
|
54085
|
+
declared: true,
|
|
54086
|
+
control: control.outcome,
|
|
54087
|
+
refused: `the unmutated control replay with ${relax.join(", ")} relaxed did not pass (${control.outcome})`
|
|
54088
|
+
};
|
|
54089
|
+
}
|
|
54090
|
+
return { site, relaxedSites: relax, outcome: run.outcome, failedSites: run.failedSites, declared: true, control: control.outcome };
|
|
54091
|
+
}
|
|
54092
|
+
return { site, relaxedSites: relax, outcome: run.outcome, failedSites: run.failedSites, declared: true };
|
|
54093
|
+
}
|
|
54094
|
+
function suggestTargetedProofPlan(proof, m) {
|
|
54095
|
+
if (proof.refused !== `relaxation bound ${MAX_TARGETED_RELAXATIONS} reached`)
|
|
54096
|
+
return null;
|
|
54097
|
+
const first = proof.failedSites[0];
|
|
54098
|
+
if (first === void 0 || first.where !== "step")
|
|
54099
|
+
return null;
|
|
54100
|
+
const relax = [...proof.relaxedSites, `step:${first.step}`];
|
|
54101
|
+
if (relax.length > MAX_DECLARED_RELAXATIONS)
|
|
54102
|
+
return null;
|
|
54103
|
+
return { check: proof.site, mustFailWhen: m.id, relax };
|
|
54104
|
+
}
|
|
54105
|
+
function declaredTargetedProofFor(journey, m, siteKey) {
|
|
54106
|
+
for (const plan2 of journey.metadata.targetedProofs ?? []) {
|
|
54107
|
+
if (plan2.check !== siteKey)
|
|
54108
|
+
continue;
|
|
54109
|
+
try {
|
|
54110
|
+
if (resolveMutationRef(journey, plan2.mustFailWhen).id === m.id)
|
|
54111
|
+
return plan2;
|
|
54112
|
+
} catch {
|
|
54113
|
+
}
|
|
54114
|
+
}
|
|
54115
|
+
return void 0;
|
|
54116
|
+
}
|
|
53869
54117
|
function needsRelaxedReplay(plan2, m, result) {
|
|
53870
54118
|
if (result.outcome !== "failed")
|
|
53871
54119
|
return false;
|
|
@@ -53875,8 +54123,18 @@ function needsRelaxedReplay(plan2, m, result) {
|
|
|
53875
54123
|
return plan2.pairs.some((p) => p.mutation === m.id && p.site !== own2);
|
|
53876
54124
|
}
|
|
53877
54125
|
function classifyMutation(site, m, result) {
|
|
54126
|
+
const verdict = classifyMutationPlain(site, m, result);
|
|
54127
|
+
if (verdict !== "cascade")
|
|
54128
|
+
return verdict;
|
|
54129
|
+
const proof = result.targeted?.find((t) => t.site === assertionSiteKey(site));
|
|
54130
|
+
if (proof === void 0 || proof.refused !== void 0)
|
|
54131
|
+
return "cascade";
|
|
54132
|
+
const targeted = classifyMutationPlain(site, m, { outcome: proof.outcome, failedSites: proof.failedSites });
|
|
54133
|
+
return targeted === "sensitive" || targeted === "insensitive" ? targeted : "cascade";
|
|
54134
|
+
}
|
|
54135
|
+
function classifyMutationPlain(site, m, result) {
|
|
53878
54136
|
if (result.relaxed !== void 0 && result.failedSites.some((f2) => f2.where === "step" && f2.step === m.step && f2.postcondition) && !(site.where === "step" && site.step === m.step)) {
|
|
53879
|
-
return
|
|
54137
|
+
return classifyMutationPlain(site, m, { outcome: result.relaxed.outcome, failedSites: result.relaxed.failedSites });
|
|
53880
54138
|
}
|
|
53881
54139
|
if (result.outcome === "passed")
|
|
53882
54140
|
return "insensitive";
|
|
@@ -53901,18 +54159,52 @@ function classifyMutation(site, m, result) {
|
|
|
53901
54159
|
return fs.some((f2) => f2.where === "end-state" && f2.index === site.index) ? "sensitive" : "cascade";
|
|
53902
54160
|
}
|
|
53903
54161
|
var PRECEDENCE = ["sensitive", "insensitive", "cascade", "not-applied", "error"];
|
|
54162
|
+
function provenSensitive(journey, m, results, key2, visiting) {
|
|
54163
|
+
if (visiting.has(key2))
|
|
54164
|
+
return false;
|
|
54165
|
+
const site = journeyAssertions(journey).find((s3) => assertionSiteKey(s3) === key2);
|
|
54166
|
+
const result = results.get(m.id) ?? { outcome: "error", failedSites: [] };
|
|
54167
|
+
if (site === void 0 || classifyMutation(site, m, result) !== "sensitive")
|
|
54168
|
+
return false;
|
|
54169
|
+
const proof = result.targeted?.find((t) => t.site === key2);
|
|
54170
|
+
if (proof === void 0 || proof.refused !== void 0)
|
|
54171
|
+
return true;
|
|
54172
|
+
const next = /* @__PURE__ */ new Set([...visiting, key2]);
|
|
54173
|
+
return proof.relaxedSites.every((relaxed2) => provenSensitive(journey, m, results, relaxed2, next));
|
|
54174
|
+
}
|
|
53904
54175
|
function judgeAssertions(journey, plan2, results) {
|
|
54176
|
+
const proofFor = (m, key2) => results.get(m.id)?.targeted?.find((t) => t.site === key2);
|
|
53905
54177
|
return journeyAssertions(journey).map((site) => {
|
|
53906
54178
|
const key2 = assertionSiteKey(site);
|
|
53907
54179
|
const paired = plan2.mutations.filter((m) => plan2.pairs.some((p) => p.site === key2 && p.mutation === m.id));
|
|
53908
54180
|
if (paired.length === 0)
|
|
53909
54181
|
return { site: key2, verdict: "unpaired" };
|
|
53910
54182
|
const verdicts = paired.map((m) => ({ m, v: classifyMutation(site, m, results.get(m.id) ?? { outcome: "error", failedSites: [] }) }));
|
|
54183
|
+
let unsound;
|
|
53911
54184
|
for (const v of PRECEDENCE) {
|
|
53912
54185
|
const first = verdicts.find((x) => x.v === v);
|
|
53913
|
-
if (first === void 0)
|
|
54186
|
+
if (first === void 0 && !(v === "cascade" && unsound !== void 0))
|
|
53914
54187
|
continue;
|
|
53915
|
-
|
|
54188
|
+
if (v === "sensitive") {
|
|
54189
|
+
for (const { m } of verdicts.filter((x) => x.v === "sensitive")) {
|
|
54190
|
+
const proof = proofFor(m, key2);
|
|
54191
|
+
if (proof === void 0 || proof.refused !== void 0)
|
|
54192
|
+
return { site: key2, verdict: v, provedBy: m.id };
|
|
54193
|
+
const bad = proof.relaxedSites.find((relaxed2) => !provenSensitive(journey, m, results, relaxed2, /* @__PURE__ */ new Set([key2])));
|
|
54194
|
+
if (bad === void 0)
|
|
54195
|
+
return { site: key2, verdict: v, provedBy: m.id, relaxedSites: proof.relaxedSites };
|
|
54196
|
+
unsound ??= `relaxed site ${bad} is not itself proven sensitive to ${m.id}`;
|
|
54197
|
+
}
|
|
54198
|
+
continue;
|
|
54199
|
+
}
|
|
54200
|
+
if (v === "cascade" && unsound !== void 0)
|
|
54201
|
+
return { site: key2, verdict: v, targetedRefusal: unsound };
|
|
54202
|
+
if (v === "cascade") {
|
|
54203
|
+
const refusal = paired.map((m) => proofFor(m, key2)).find((t) => t !== void 0 && t.refused !== void 0);
|
|
54204
|
+
if (refusal !== void 0)
|
|
54205
|
+
return { site: key2, verdict: v, targetedRefusal: refusal.refused };
|
|
54206
|
+
}
|
|
54207
|
+
return { site: key2, verdict: v };
|
|
53916
54208
|
}
|
|
53917
54209
|
return { site: key2, verdict: "error" };
|
|
53918
54210
|
});
|
|
@@ -53930,6 +54222,10 @@ function mutationProofVerdict(input2) {
|
|
|
53930
54222
|
return { verdict: "insensitive" };
|
|
53931
54223
|
if (paired.every((a) => a.verdict === "sensitive"))
|
|
53932
54224
|
return { verdict: "proven" };
|
|
54225
|
+
const refusals = input2.assertions.map((a) => a.targetedRefusal).filter((r) => r !== void 0);
|
|
54226
|
+
if (refusals.length > 0) {
|
|
54227
|
+
return { verdict: "inconclusive", reason: `some paired assertions were neither proven nor shown vacuous (cascade, not-applied or error); a targeted proof was refused: ${refusals[0]}` };
|
|
54228
|
+
}
|
|
53933
54229
|
return { verdict: "inconclusive", reason: "some paired assertions were neither proven nor shown vacuous (cascade, not-applied or error)" };
|
|
53934
54230
|
}
|
|
53935
54231
|
|
|
@@ -54496,6 +54792,12 @@ var JourneySchema = external_exports.object({
|
|
|
54496
54792
|
endState: external_exports.array(OutcomeCheckSchema).max(50).optional(),
|
|
54497
54793
|
acceptedWeak: external_exports.object({ reason: external_exports.string().min(1), rules: external_exports.array(external_exports.string()) }).strict().optional(),
|
|
54498
54794
|
mutationPairs: external_exports.array(external_exports.object({ check: external_exports.string().min(1).max(100), mustFailWhen: external_exports.string().min(1).max(200) }).strict()).max(100).optional(),
|
|
54795
|
+
// #616: a declared targeted-proof plan; the relax list is bounded tighter than the automatic proof.
|
|
54796
|
+
targetedProofs: external_exports.array(external_exports.object({
|
|
54797
|
+
check: external_exports.string().min(1).max(100),
|
|
54798
|
+
mustFailWhen: external_exports.string().min(1).max(200),
|
|
54799
|
+
relax: external_exports.array(external_exports.string().min(1).max(60)).min(1).max(MAX_DECLARED_RELAXATIONS)
|
|
54800
|
+
}).strict()).max(20).optional(),
|
|
54499
54801
|
approval: external_exports.object({
|
|
54500
54802
|
contentHash: external_exports.string().regex(/^[0-9a-f]{64}$/, "approval.contentHash: a sha256 hex digest"),
|
|
54501
54803
|
at: external_exports.string().min(1),
|
|
@@ -54521,6 +54823,9 @@ var JourneySchema = external_exports.object({
|
|
|
54521
54823
|
for (const issue3 of mutationPairIssues(j)) {
|
|
54522
54824
|
ctx.addIssue({ code: "custom", path: ["metadata", "mutationPairs", issue3.index, issue3.field], message: issue3.message });
|
|
54523
54825
|
}
|
|
54826
|
+
for (const issue3 of targetedProofPlanIssues(j)) {
|
|
54827
|
+
ctx.addIssue({ code: "custom", path: ["metadata", "targetedProofs", issue3.index, issue3.field], message: issue3.message });
|
|
54828
|
+
}
|
|
54524
54829
|
const declared = /* @__PURE__ */ new Set([...j.metadata.params, ...(j.metadata.parameters ?? []).map((p) => p.name)]);
|
|
54525
54830
|
j.recording.pages.forEach((page, pi) => page.steps.forEach((rs, si) => {
|
|
54526
54831
|
if (rs.step.kind !== "navigate")
|
|
@@ -55360,7 +55665,9 @@ var ReviewVerifySchema = external_exports.discriminatedUnion("status", [
|
|
|
55360
55665
|
/** True when the Journey changed after this proof ran (the verdict is for other content). */
|
|
55361
55666
|
stale: external_exports.boolean(),
|
|
55362
55667
|
reason: external_exports.string().optional(),
|
|
55363
|
-
summary: external_exports.record(external_exports.string(), external_exports.number())
|
|
55668
|
+
summary: external_exports.record(external_exports.string(), external_exports.number()),
|
|
55669
|
+
/** #616: a reviewable declared targeted-proof plan the automatic bound refused (names only). */
|
|
55670
|
+
suggestedTargetedProofs: external_exports.array(external_exports.object({ check: external_exports.string(), mustFailWhen: external_exports.string(), relax: external_exports.array(external_exports.string()) }).strict()).optional()
|
|
55364
55671
|
}).strict()
|
|
55365
55672
|
]);
|
|
55366
55673
|
var DiffSchema = external_exports.object({ added: external_exports.array(external_exports.string()), removed: external_exports.array(external_exports.string()) }).strict();
|
|
@@ -55380,10 +55687,18 @@ var ReviewChangeSchema = external_exports.discriminatedUnion("kind", [
|
|
|
55380
55687
|
stepIdOnly: external_exports.boolean().optional(),
|
|
55381
55688
|
steps: DiffSchema,
|
|
55382
55689
|
assertions: DiffSchema,
|
|
55383
|
-
sideEffects: DiffSchema
|
|
55690
|
+
sideEffects: DiffSchema,
|
|
55691
|
+
/** #616: proof-plan changes (`metadata.mutationPairs` / `metadata.targetedProofs`); omitted when neither side declares any. */
|
|
55692
|
+
proofPlans: DiffSchema.optional()
|
|
55384
55693
|
}).strict()
|
|
55385
55694
|
]);
|
|
55386
|
-
var ReviewAttentionSchema = external_exports.object({
|
|
55695
|
+
var ReviewAttentionSchema = external_exports.object({
|
|
55696
|
+
severity: external_exports.enum(["warn", "info"]),
|
|
55697
|
+
text: external_exports.string(),
|
|
55698
|
+
hint: external_exports.string().optional(),
|
|
55699
|
+
/** #608: a person's catalog approval settles this item — readiness lists it as awaiting-human, never as a failure. */
|
|
55700
|
+
settledBy: external_exports.enum(["catalog-approval"]).optional()
|
|
55701
|
+
}).strict();
|
|
55387
55702
|
var JourneyReviewSchema = external_exports.object({
|
|
55388
55703
|
id: external_exports.string(),
|
|
55389
55704
|
name: external_exports.string(),
|
|
@@ -55419,6 +55734,8 @@ var JourneyReviewSchema = external_exports.object({
|
|
|
55419
55734
|
endStateText: external_exports.array(external_exports.string()).optional(),
|
|
55420
55735
|
lint: external_exports.object({ errors: external_exports.number().int(), warnings: external_exports.number().int(), findings: external_exports.array(ReviewLintFindingSchema) }).strict(),
|
|
55421
55736
|
acceptedWeak: external_exports.object({ reason: external_exports.string(), rules: external_exports.array(external_exports.string()) }).strict().optional(),
|
|
55737
|
+
/** #616: the declared targeted-proof plans (`metadata.targetedProofs`) a reviewer approves with the Journey. Additive. */
|
|
55738
|
+
targetedProofs: external_exports.array(external_exports.object({ check: external_exports.string(), mustFailWhen: external_exports.string(), relax: external_exports.array(external_exports.string()) }).strict()).optional(),
|
|
55422
55739
|
verify: ReviewVerifySchema
|
|
55423
55740
|
}).strict(),
|
|
55424
55741
|
changeSinceApproval: ReviewChangeSchema,
|
|
@@ -62390,9 +62707,9 @@ function hostStarvedFailure(failure3, health) {
|
|
|
62390
62707
|
}
|
|
62391
62708
|
|
|
62392
62709
|
// src/build-info.generated.ts
|
|
62393
|
-
var GENERATED_COMMIT = "
|
|
62394
|
-
var GENERATED_TREE = "
|
|
62395
|
-
var GENERATED_BUILT_AT = "2026-10-
|
|
62710
|
+
var GENERATED_COMMIT = "c9b33ab";
|
|
62711
|
+
var GENERATED_TREE = "35c694a";
|
|
62712
|
+
var GENERATED_BUILT_AT = "2026-10-19T23:00:10.969Z";
|
|
62396
62713
|
|
|
62397
62714
|
// src/version.ts
|
|
62398
62715
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
@@ -62414,13 +62731,13 @@ function readCliVersion() {
|
|
|
62414
62731
|
|
|
62415
62732
|
// src/engine.ts
|
|
62416
62733
|
function bundledCommit() {
|
|
62417
|
-
return true ? "
|
|
62734
|
+
return true ? "c9b33ab" : void 0;
|
|
62418
62735
|
}
|
|
62419
62736
|
function bundledBuiltAt() {
|
|
62420
|
-
return true ? "2026-10-
|
|
62737
|
+
return true ? "2026-10-19T23:00:10.969Z" : void 0;
|
|
62421
62738
|
}
|
|
62422
62739
|
function bundledTree() {
|
|
62423
|
-
return true ? "
|
|
62740
|
+
return true ? "35c694a" : void 0;
|
|
62424
62741
|
}
|
|
62425
62742
|
function currentBuildTree() {
|
|
62426
62743
|
return bundledTree() ?? GENERATED_TREE;
|
|
@@ -79842,9 +80159,11 @@ function renderSteps(r, w, opts) {
|
|
|
79842
80159
|
function renderProvesAtTheEnd(r, w, opts) {
|
|
79843
80160
|
void opts;
|
|
79844
80161
|
const endState = r.proof.endStateText ?? r.proof.endState;
|
|
79845
|
-
|
|
80162
|
+
const plans = r.proof.targetedProofs ?? [];
|
|
80163
|
+
if (endState.length > 0 || plans.length > 0) {
|
|
79846
80164
|
w.head("PROVES AT THE END", "Proves at the end");
|
|
79847
80165
|
for (const e of endState) w.put(`${w.bullet}\u2713 `, e, w.bullet.length + 2);
|
|
80166
|
+
for (const p of plans) w.put(`${w.bullet}\u21BA `, `declared targeted proof: ${p.check} against ${p.mustFailWhen} may relax ${p.relax.join(", ")}`, w.bullet.length + 2);
|
|
79848
80167
|
w.gap();
|
|
79849
80168
|
}
|
|
79850
80169
|
}
|
|
@@ -79935,7 +80254,13 @@ function renderFullDetail(r, w, opts) {
|
|
|
79935
80254
|
w.put(w.bullet, `Approved${c.approvedAt === void 0 ? "" : ` ${humanTime(c.approvedAt)}`} as ${nb(w.code(c.approvedHash))}${c.kind === "snapshot-missing" ? "; its snapshot is missing" : ""} \u2014 ${c.changed ? "CHANGED since" : "unchanged since"}`);
|
|
79936
80255
|
if (c.stepIdOnly === true) w.put(w.bullet, STEP_ID_ONLY_LABEL);
|
|
79937
80256
|
if (c.kind === "diff") {
|
|
79938
|
-
|
|
80257
|
+
const blocks = [
|
|
80258
|
+
["Steps", c.steps],
|
|
80259
|
+
["Assertions", c.assertions],
|
|
80260
|
+
["Side effects", c.sideEffects]
|
|
80261
|
+
];
|
|
80262
|
+
if (c.proofPlans !== void 0) blocks.push(["Proof plans", c.proofPlans]);
|
|
80263
|
+
for (const [label2, d] of blocks) {
|
|
79939
80264
|
if (d.added.length === 0 && d.removed.length === 0) continue;
|
|
79940
80265
|
w.put(w.bullet, `${label2}:`);
|
|
79941
80266
|
for (const l2 of d.removed) w.put(`${w.bullet} `, `\u2212 ${l2}`, w.bullet.length + 4);
|
|
@@ -80216,6 +80541,8 @@ function buildChange(journey, hash3, snapshot2, stepIdOnly) {
|
|
|
80216
80541
|
}
|
|
80217
80542
|
const approvedHash = journeyReviewHash(snapshot2);
|
|
80218
80543
|
const approvedAt = approval?.at ?? snapshot2.metadata.approval?.at;
|
|
80544
|
+
const beforePlans = proofPlanLines(snapshot2);
|
|
80545
|
+
const afterPlans = proofPlanLines(journey);
|
|
80219
80546
|
return {
|
|
80220
80547
|
kind: "diff",
|
|
80221
80548
|
approvedHash,
|
|
@@ -80224,7 +80551,9 @@ function buildChange(journey, hash3, snapshot2, stepIdOnly) {
|
|
|
80224
80551
|
...idOnly,
|
|
80225
80552
|
steps: multisetDiff(stepLines(snapshot2), stepLines(journey)),
|
|
80226
80553
|
assertions: multisetDiff(assertionLines(snapshot2), assertionLines(journey)),
|
|
80227
|
-
sideEffects: multisetDiff(sideEffectLines(snapshot2), sideEffectLines(journey))
|
|
80554
|
+
sideEffects: multisetDiff(sideEffectLines(snapshot2), sideEffectLines(journey)),
|
|
80555
|
+
// #616: only present when a side declares a proof plan, so an unrelated diff stays as before.
|
|
80556
|
+
...beforePlans.length === 0 && afterPlans.length === 0 ? {} : { proofPlans: multisetDiff(beforePlans, afterPlans) }
|
|
80228
80557
|
};
|
|
80229
80558
|
}
|
|
80230
80559
|
function stepLines(j) {
|
|
@@ -80236,6 +80565,12 @@ function assertionLines(j) {
|
|
|
80236
80565
|
...journeyAssertions(j).filter((s3) => s3.where === "step-request").map((s3) => `step request: ${describeCheck(s3.check)}`)
|
|
80237
80566
|
];
|
|
80238
80567
|
}
|
|
80568
|
+
function proofPlanLines(j) {
|
|
80569
|
+
return [
|
|
80570
|
+
...(j.metadata.mutationPairs ?? []).map((p) => `mutation pair: ${p.check} must fail when ${p.mustFailWhen}`),
|
|
80571
|
+
...(j.metadata.targetedProofs ?? []).map((p) => `targeted proof: ${p.check} against ${p.mustFailWhen} may relax ${p.relax.join(", ")}`)
|
|
80572
|
+
];
|
|
80573
|
+
}
|
|
80239
80574
|
function sideEffectLines(j) {
|
|
80240
80575
|
return [
|
|
80241
80576
|
...[...new Set(buildWriteRequests(j).map((w) => `write ${w.method} ${w.endpoint}`))],
|
|
@@ -80273,7 +80608,8 @@ function buildVerify(journey, hash3, rec) {
|
|
|
80273
80608
|
// #523: an executable-content change stales the proof; an old record without it compares the full hash.
|
|
80274
80609
|
stale: rec.proofHash === void 0 ? rec.contentHash !== hash3 : rec.proofHash !== journeyProofHash(journey),
|
|
80275
80610
|
...rec.reason === void 0 ? {} : { reason: rec.reason },
|
|
80276
|
-
summary: { ...rec.summary }
|
|
80611
|
+
summary: { ...rec.summary },
|
|
80612
|
+
...rec.suggestedTargetedProofs === void 0 ? {} : { suggestedTargetedProofs: rec.suggestedTargetedProofs.map((p) => ({ ...p, relax: [...p.relax] })) }
|
|
80277
80613
|
};
|
|
80278
80614
|
}
|
|
80279
80615
|
function buildJourneyReview(journey, ctx = {}) {
|
|
@@ -80351,6 +80687,8 @@ function buildJourneyReview(journey, ctx = {}) {
|
|
|
80351
80687
|
findings: findings.map((f2) => ({ rule: f2.rule, level: f2.level, ...f2.step === void 0 ? {} : { step: f2.step }, message: f2.message }))
|
|
80352
80688
|
},
|
|
80353
80689
|
...m.acceptedWeak === void 0 ? {} : { acceptedWeak: { reason: m.acceptedWeak.reason, rules: [...m.acceptedWeak.rules] } },
|
|
80690
|
+
// #616: the declared targeted-proof plans, copied out of the metadata for review.
|
|
80691
|
+
...m.targetedProofs === void 0 || m.targetedProofs.length === 0 ? {} : { targetedProofs: m.targetedProofs.map((p) => ({ check: p.check, mustFailWhen: p.mustFailWhen, relax: [...p.relax] })) },
|
|
80354
80692
|
verify: buildVerify(journey, hash3, ctx.lastVerify)
|
|
80355
80693
|
},
|
|
80356
80694
|
changeSinceApproval: buildChange(journey, hash3, ctx.approvedSnapshot, ctx.stepIdOnly === true),
|
|
@@ -80381,7 +80719,7 @@ function describeFile(step) {
|
|
|
80381
80719
|
}
|
|
80382
80720
|
function buildAttention(r, weakEndState, goal) {
|
|
80383
80721
|
const out = [];
|
|
80384
|
-
const warn = (text2, hint2) => void out.push({ severity: "warn", text: text2, ...hint2 === void 0 ? {} : { hint: hint2 } });
|
|
80722
|
+
const warn = (text2, hint2, settledBy) => void out.push({ severity: "warn", text: text2, ...hint2 === void 0 ? {} : { hint: hint2 }, ...settledBy === void 0 ? {} : { settledBy } });
|
|
80385
80723
|
const info = (text2, hint2) => void out.push({ severity: "info", text: text2, ...hint2 === void 0 ? {} : { hint: hint2 } });
|
|
80386
80724
|
const weakSteps = [...new Set(r.proof.stepAssertions.filter((a) => a.weak).map((a) => a.step))];
|
|
80387
80725
|
if (weakSteps.length === 1) warn(`Step ${stepRanges(weakSteps)} proves nothing: its check passes even if the step fails`);
|
|
@@ -80402,12 +80740,12 @@ function buildAttention(r, weakEndState, goal) {
|
|
|
80402
80740
|
else info(`${text2}, exempted by allowControl`);
|
|
80403
80741
|
}
|
|
80404
80742
|
if (r.catalog !== void 0) {
|
|
80405
|
-
if (r.catalog.unvetted.length > 0) warn(`Unvetted catalog links: ${r.catalog.unvetted.join(", ")}`, `approve them, or promote with --accept-unvetted "<reason>"
|
|
80743
|
+
if (r.catalog.unvetted.length > 0) warn(`Unvetted catalog links: ${r.catalog.unvetted.join(", ")}`, `approve them, or promote with --accept-unvetted "<reason>"`, "catalog-approval");
|
|
80406
80744
|
if (r.catalog.needsReReview.length > 0) warn(`Catalog links need re-review: ${r.catalog.needsReReview.join("; ")}`);
|
|
80407
80745
|
}
|
|
80408
80746
|
for (const f2 of r.findings ?? []) {
|
|
80409
80747
|
if (f2.analyzer === "catalog-analysis") continue;
|
|
80410
|
-
if (f2.severity !== "info") warn(f2.message, f2.fix);
|
|
80748
|
+
if (f2.severity !== "info") warn(f2.message, f2.fix, f2.analyzer === "catalog-links" && f2.code === "journey.unvetted" ? "catalog-approval" : void 0);
|
|
80411
80749
|
else if (f2.analyzer === "readiness" || f2.analyzer === "readiness-jev") info(f2.message, f2.fix);
|
|
80412
80750
|
}
|
|
80413
80751
|
const c = r.changeSinceApproval;
|
|
@@ -94594,6 +94932,7 @@ var JourneyOutcomeChecks = class {
|
|
|
94594
94932
|
async #judge(s3, fallback) {
|
|
94595
94933
|
const window2 = s3.capture ?? fallback;
|
|
94596
94934
|
if (window2 === void 0) return [];
|
|
94935
|
+
const ownCapture = s3.capture !== void 0;
|
|
94597
94936
|
const since = s3.startedAt;
|
|
94598
94937
|
const until = s3.until;
|
|
94599
94938
|
const out = [];
|
|
@@ -94602,7 +94941,7 @@ var JourneyOutcomeChecks = class {
|
|
|
94602
94941
|
await evaluateNetworkCheckAwaiting(
|
|
94603
94942
|
c,
|
|
94604
94943
|
() => ({
|
|
94605
|
-
requests: window2.sent().filter((q) => (since === void 0 || q.startedAt !== void 0 && q.startedAt >= since) && (until === void 0 || q.startedAt !== void 0 && q.startedAt <= until)),
|
|
94944
|
+
requests: window2.sent().filter((q) => (ownCapture || since === void 0 || q.startedAt !== void 0 && q.startedAt >= since) && (until === void 0 || q.startedAt !== void 0 && q.startedAt <= until)),
|
|
94606
94945
|
truncated: window2.truncated
|
|
94607
94946
|
}),
|
|
94608
94947
|
s3.responseTimeoutMs ?? this.#responseTimeoutMs
|
|
@@ -95099,7 +95438,8 @@ async function runJourneyProgrammatically(opts, mutation2 = {}) {
|
|
|
95099
95438
|
let result;
|
|
95100
95439
|
let runSecrets2 = secrets;
|
|
95101
95440
|
try {
|
|
95102
|
-
const
|
|
95441
|
+
const replayParams = replayed === journey ? params : prefixParams(journey, replayed, params);
|
|
95442
|
+
const raw = await outcomeChecks.run(actor, () => runner.run({ journey: replayed, params: replayParams, policy }));
|
|
95103
95443
|
runSecrets2 = runSecretValues(secrets, raw);
|
|
95104
95444
|
result = redactSecretValues(raw, runSecrets2);
|
|
95105
95445
|
} finally {
|
|
@@ -108463,6 +108803,9 @@ function redactReason(reason, params, names = TEXT_PARAMS) {
|
|
|
108463
108803
|
}
|
|
108464
108804
|
return out;
|
|
108465
108805
|
}
|
|
108806
|
+
function redactProducerParams(reason, params) {
|
|
108807
|
+
return params === void 0 ? reason : redactReason(reason, params, Object.keys(params));
|
|
108808
|
+
}
|
|
108466
108809
|
function chainSucceeded(result) {
|
|
108467
108810
|
return result.unmatched.length === 0 && result.runs.every((run) => run.outcome === "ok");
|
|
108468
108811
|
}
|
|
@@ -108553,10 +108896,10 @@ async function runHandoffChain(inputs, outputs, fromJourney, fromOutput, request
|
|
|
108553
108896
|
...identity,
|
|
108554
108897
|
outcome: result.outcome,
|
|
108555
108898
|
...stamp,
|
|
108556
|
-
..."reason" in result ? { reason: redactReason(result.reason, params, redactors) } : {}
|
|
108899
|
+
..."reason" in result ? { reason: redactProducerParams(redactReason(result.reason, params, redactors), opts.params) } : {}
|
|
108557
108900
|
});
|
|
108558
108901
|
} catch (err) {
|
|
108559
|
-
runs.push({ ...identity, outcome: "error", ...stamp, reason: redactReason(err instanceof Error ? err.message : String(err), params, redactors) });
|
|
108902
|
+
runs.push({ ...identity, outcome: "error", ...stamp, reason: redactProducerParams(redactReason(err instanceof Error ? err.message : String(err), params, redactors), opts.params) });
|
|
108560
108903
|
}
|
|
108561
108904
|
}
|
|
108562
108905
|
return { from: { journey: opts.from, outcome: "ok", records: records.length }, runs, unmatched, rejected };
|
|
@@ -108568,9 +108911,15 @@ async function runJourneyChain(opts, deps = {}) {
|
|
|
108568
108911
|
const toJourney = await registry3.get(opts.to);
|
|
108569
108912
|
if (toJourney === null) throw new UnknownJourneyError(`unknown journey '${opts.to}'`);
|
|
108570
108913
|
const inputs = toJourney.metadata.inputs;
|
|
108571
|
-
const
|
|
108572
|
-
|
|
108573
|
-
|
|
108914
|
+
const fromJourney = await registry3.get(opts.from);
|
|
108915
|
+
if (fromJourney === null) throw new UnknownJourneyError(`unknown journey '${opts.from}'`);
|
|
108916
|
+
const producerParams = { ...opts.params ?? {} };
|
|
108917
|
+
try {
|
|
108918
|
+
validateParams(journeyParamSchema(fromJourney), producerParams);
|
|
108919
|
+
} catch (err) {
|
|
108920
|
+
if (err instanceof ParamValidationError) throw new JourneyChainError("E_CHAIN_PARAMS", err.message);
|
|
108921
|
+
throw err;
|
|
108922
|
+
}
|
|
108574
108923
|
if (opts.requirePromoted === true) {
|
|
108575
108924
|
for (const j of [fromJourney, toJourney]) {
|
|
108576
108925
|
if (!j.metadata.promoted) throw new JourneyChainError("E_CHAIN_NOT_PROMOTED", `journey '${j.metadata.id}' is not promoted \u2014 a chain runs promoted Journeys (journey chain --allow-draft runs drafts on the CLI)`);
|
|
@@ -108585,9 +108934,9 @@ async function runJourneyChain(opts, deps = {}) {
|
|
|
108585
108934
|
handoff2 = { inputs, outputs };
|
|
108586
108935
|
}
|
|
108587
108936
|
const requests = handoff2 === void 0 ? parseChainRequests(opts.requests) : void 0;
|
|
108588
|
-
const fromResult = await runner({ ...shared3, id: opts.from, params:
|
|
108937
|
+
const fromResult = await runner({ ...shared3, id: opts.from, params: producerParams });
|
|
108589
108938
|
if (fromResult.outcome !== "ok") {
|
|
108590
|
-
const reason = "reason" in fromResult ? fromResult.reason : void 0;
|
|
108939
|
+
const reason = "reason" in fromResult ? redactProducerParams(fromResult.reason, opts.params) : void 0;
|
|
108591
108940
|
throw new JourneyChainError(
|
|
108592
108941
|
"E_CHAIN_FROM_FAILED",
|
|
108593
108942
|
`discover Journey '${opts.from}' did not run ok (${fromResult.outcome})${reason === void 0 ? "" : `: ${reason}`}`
|
|
@@ -108621,10 +108970,10 @@ async function runJourneyChain(opts, deps = {}) {
|
|
|
108621
108970
|
commentId: request.commentId,
|
|
108622
108971
|
account: request.account,
|
|
108623
108972
|
outcome: result.outcome,
|
|
108624
|
-
..."reason" in result ? { reason: redactReason(result.reason, mapped.params) } : {}
|
|
108973
|
+
..."reason" in result ? { reason: redactProducerParams(redactReason(result.reason, mapped.params), opts.params) } : {}
|
|
108625
108974
|
});
|
|
108626
108975
|
} catch (err) {
|
|
108627
|
-
runs.push({ commentId: request.commentId, account: request.account, outcome: "error", reason: redactReason(err instanceof Error ? err.message : String(err), mapped.params) });
|
|
108976
|
+
runs.push({ commentId: request.commentId, account: request.account, outcome: "error", reason: redactProducerParams(redactReason(err instanceof Error ? err.message : String(err), mapped.params), opts.params) });
|
|
108628
108977
|
}
|
|
108629
108978
|
}
|
|
108630
108979
|
return { from: { journey: opts.from, outcome: "ok", records: count }, runs, unmatched };
|
|
@@ -108632,12 +108981,12 @@ async function runJourneyChain(opts, deps = {}) {
|
|
|
108632
108981
|
|
|
108633
108982
|
// src/journey-cli/chain.ts
|
|
108634
108983
|
function registerJourneyChain(journey, program2, deps) {
|
|
108635
|
-
withReplayAllowFlag(withEnvironmentFlags(withBrowserLaunchFlags(withEmulationFlags(withProjectFlags(journey.command("chain")))))).requiredOption("--from <id>", "the discover Journey, run once to produce the records").requiredOption("--to <id>", "the prepare-only reply Journey, run once per matched request").requiredOption("--requests <file>", "JSON Lines file of approved requests ({commentId, account, replyText})").option("--dir <path>", DEPRECATED_DIR_HELP).option("--storage-state <file>", "Playwright storageState JSON to start each run authenticated; must exist").option("--allow-draft", "also run Journeys that are not promoted (authoring); without it both must be promoted").option("--json", "emit a JSON envelope").action(async function() {
|
|
108984
|
+
withReplayAllowFlag(withEnvironmentFlags(withBrowserLaunchFlags(withEmulationFlags(withProjectFlags(journey.command("chain")))))).requiredOption("--from <id>", "the discover Journey, run once to produce the records").requiredOption("--to <id>", "the prepare-only reply Journey, run once per matched request").requiredOption("--requests <file>", "JSON Lines file of approved requests ({commentId, account, replyText})").option("--param <kv>", "param as key=value (repeatable)", collectParam, {}).option("--dir <path>", DEPRECATED_DIR_HELP).option("--storage-state <file>", "Playwright storageState JSON to start each run authenticated; must exist").option("--allow-draft", "also run Journeys that are not promoted (authoring); without it both must be promoted").option("--json", "emit a JSON envelope").action(async function() {
|
|
108636
108985
|
const locationFlags = this.opts();
|
|
108637
108986
|
const layout = journeyLayout(program2, deps, { dir: locationFlags.dir, project: locationFlags.project, journeysDir: locationFlags.journeysDir });
|
|
108638
108987
|
if (layout === null) return;
|
|
108639
108988
|
const chainJourneysDir = layout.journeysDir;
|
|
108640
|
-
const { from, to, requests: requestsFile, storageState: storageStateFlag, json: json2, allowDraft } = this.opts();
|
|
108989
|
+
const { from, to, requests: requestsFile, storageState: storageStateFlag, json: json2, allowDraft, param } = this.opts();
|
|
108641
108990
|
const { env: envName, baseUrl } = this.opts();
|
|
108642
108991
|
let environment;
|
|
108643
108992
|
let allow;
|
|
@@ -108685,6 +109034,7 @@ function registerJourneyChain(journey, program2, deps) {
|
|
|
108685
109034
|
from,
|
|
108686
109035
|
to,
|
|
108687
109036
|
requests,
|
|
109037
|
+
...Object.keys(param).length === 0 ? {} : { params: param },
|
|
108688
109038
|
dir: chainJourneysDir,
|
|
108689
109039
|
requirePromoted: allowDraft !== true,
|
|
108690
109040
|
...siteGate === void 0 ? {} : { siteGate },
|
|
@@ -109082,14 +109432,16 @@ async function verifyJourneyMutations(opts, run = runJourneyProgrammatically) {
|
|
|
109082
109432
|
const checkOf = new Map(sites.map((s3) => [assertionSiteKey(s3), describeCheck(s3.check)]));
|
|
109083
109433
|
const plan2 = planJourneyMutations(journey, { ...lintOpts, params: opts.params });
|
|
109084
109434
|
const mutations = [];
|
|
109435
|
+
const suggested = [];
|
|
109436
|
+
const seenSuggestions = /* @__PURE__ */ new Set();
|
|
109085
109437
|
if (base.outcome === "ok") {
|
|
109086
109438
|
const replayOpts2 = { ...lintOpts, params: opts.params };
|
|
109087
|
-
const
|
|
109439
|
+
const runReplay = async (m, describe6) => {
|
|
109088
109440
|
try {
|
|
109089
|
-
const how = describe6(journey
|
|
109441
|
+
const how = describe6(journey);
|
|
109090
109442
|
const r = await run(how.params === void 0 ? runOpts : { ...runOpts, params: { ...how.params } }, {
|
|
109091
109443
|
structuredFailures: true,
|
|
109092
|
-
...how.journey === journey ? {} : { mutateJourney: (j) => describe6(j
|
|
109444
|
+
...how.journey === journey ? {} : { mutateJourney: (j) => describe6(j).journey },
|
|
109093
109445
|
...how.skipIndex === void 0 ? {} : { skipStep: (i) => i === how.skipIndex },
|
|
109094
109446
|
...how.swapSelectAtStep === void 0 ? {} : { selectAlternativeAtStep: how.swapSelectAtStep },
|
|
109095
109447
|
...how.blockIndex === void 0 ? {} : { blockWritesAtStep: how.blockIndex }
|
|
@@ -109117,9 +109469,30 @@ async function verifyJourneyMutations(opts, run = runJourneyProgrammatically) {
|
|
|
109117
109469
|
return { outcome: "error", failedSites: [], reason: err instanceof Error ? err.message : String(err) };
|
|
109118
109470
|
}
|
|
109119
109471
|
};
|
|
109472
|
+
const replay = (m, describe6) => runReplay(m, (j) => describe6(j, m, replayOpts2));
|
|
109473
|
+
const siteByKey = new Map(sites.map((s3) => [assertionSiteKey(s3), s3]));
|
|
109120
109474
|
for (const m of plan2.mutations) {
|
|
109121
109475
|
const first = await replay(m, mutationReplay);
|
|
109122
109476
|
const relaxed2 = needsRelaxedReplay(plan2, m, first) ? await replay(m, relaxedMutationReplay) : void 0;
|
|
109477
|
+
const initial = { outcome: first.outcome, failedSites: first.failedSites, ...relaxed2 === void 0 ? {} : { relaxed: relaxed2 } };
|
|
109478
|
+
const targeted = [];
|
|
109479
|
+
for (const pair of plan2.pairs) {
|
|
109480
|
+
if (pair.mutation !== m.id) continue;
|
|
109481
|
+
const site = siteByKey.get(pair.site);
|
|
109482
|
+
if (site === void 0 || classifyMutation(site, m, initial) !== "cascade") continue;
|
|
109483
|
+
const declared = declaredTargetedProofFor(journey, m, pair.site);
|
|
109484
|
+
targeted.push(
|
|
109485
|
+
declared === void 0 ? await runTargetedProof(journey, plan2, m, site, initial, (describe6) => runReplay(m, describe6), replayOpts2) : await runDeclaredTargetedProof(journey, plan2, m, site, declared, (describe6) => runReplay(m, describe6), replayOpts2)
|
|
109486
|
+
);
|
|
109487
|
+
const suggestion = suggestTargetedProofPlan(targeted[targeted.length - 1], m);
|
|
109488
|
+
if (suggestion !== null) {
|
|
109489
|
+
const key2 = `${suggestion.check}\0${suggestion.mustFailWhen}\0${suggestion.relax.join(",")}`;
|
|
109490
|
+
if (!seenSuggestions.has(key2)) {
|
|
109491
|
+
seenSuggestions.add(key2);
|
|
109492
|
+
suggested.push(suggestion);
|
|
109493
|
+
}
|
|
109494
|
+
}
|
|
109495
|
+
}
|
|
109123
109496
|
mutations.push({
|
|
109124
109497
|
kind: m.kind,
|
|
109125
109498
|
step: m.step,
|
|
@@ -109127,15 +109500,30 @@ async function verifyJourneyMutations(opts, run = runJourneyProgrammatically) {
|
|
|
109127
109500
|
...m.param === void 0 ? {} : { param: m.param },
|
|
109128
109501
|
...m.source === void 0 ? {} : { source: m.source },
|
|
109129
109502
|
...first,
|
|
109130
|
-
...relaxed2 === void 0 ? {} : { relaxed: relaxed2 }
|
|
109503
|
+
...relaxed2 === void 0 ? {} : { relaxed: relaxed2 },
|
|
109504
|
+
...targeted.length === 0 ? {} : { targeted }
|
|
109131
109505
|
});
|
|
109132
109506
|
}
|
|
109133
109507
|
}
|
|
109134
109508
|
const results = new Map(
|
|
109135
|
-
mutations.map((m) => [
|
|
109509
|
+
mutations.map((m) => [
|
|
109510
|
+
m.id,
|
|
109511
|
+
{ outcome: m.outcome, failedSites: m.failedSites, ...m.relaxed === void 0 ? {} : { relaxed: m.relaxed }, ...m.targeted === void 0 ? {} : { targeted: m.targeted } }
|
|
109512
|
+
])
|
|
109136
109513
|
);
|
|
109137
109514
|
const judged = judgeAssertions(journey, plan2, results);
|
|
109138
|
-
const assertions = judged.map((a) => ({
|
|
109515
|
+
const assertions = judged.map((a) => ({
|
|
109516
|
+
site: a.site,
|
|
109517
|
+
check: checkOf.get(a.site) ?? a.site,
|
|
109518
|
+
verdict: a.verdict,
|
|
109519
|
+
...a.provedBy === void 0 ? {} : { provedBy: a.provedBy },
|
|
109520
|
+
...a.relaxedSites === void 0 ? {} : { relaxedSites: a.relaxedSites },
|
|
109521
|
+
...a.targetedRefusal === void 0 ? {} : { targetedRefusal: a.targetedRefusal },
|
|
109522
|
+
// #616: the proving proof was a declared plan, not the automatic relaxation search.
|
|
109523
|
+
...a.provedBy !== void 0 && results.get(a.provedBy)?.targeted?.some((t) => t.site === a.site && t.declared === true) === true ? { declaredProof: true } : {}
|
|
109524
|
+
}));
|
|
109525
|
+
const order = new Map(journeyAssertions(journey).map((s3, i) => [assertionSiteKey(s3), i]));
|
|
109526
|
+
const siteOrder = (key2) => order.get(key2) ?? Number.MAX_SAFE_INTEGER;
|
|
109139
109527
|
const { verdict, reason } = mutationProofVerdict({ basePassed: base.outcome === "ok", mutations, assertions: judged });
|
|
109140
109528
|
const count = (v) => assertions.filter((a) => a.verdict === v).length;
|
|
109141
109529
|
const report2 = {
|
|
@@ -109152,8 +109540,12 @@ async function verifyJourneyMutations(opts, run = runJourneyProgrammatically) {
|
|
|
109152
109540
|
cascade: count("cascade"),
|
|
109153
109541
|
notApplied: count("not-applied"),
|
|
109154
109542
|
error: count("error"),
|
|
109155
|
-
unpaired: count("unpaired")
|
|
109156
|
-
|
|
109543
|
+
unpaired: count("unpaired"),
|
|
109544
|
+
// #609: assertions proven sensitive through a targeted proof that relaxed earlier sites.
|
|
109545
|
+
targeted: assertions.filter((a) => (a.relaxedSites?.length ?? 0) > 0).length
|
|
109546
|
+
},
|
|
109547
|
+
// #616: in assertion order, so the first suggestion is the one for the refusal the verdict quotes.
|
|
109548
|
+
...suggested.length === 0 ? {} : { suggestedTargetedProofs: [...suggested].sort((a, b2) => siteOrder(a.check) - siteOrder(b2.check)) }
|
|
109157
109549
|
};
|
|
109158
109550
|
const redacted = redactSecretParams(report2, journey, opts.params);
|
|
109159
109551
|
await writeVerifyRecord(opts.dir, {
|
|
@@ -109163,12 +109555,16 @@ async function verifyJourneyMutations(opts, run = runJourneyProgrammatically) {
|
|
|
109163
109555
|
verdict: redacted.verdict,
|
|
109164
109556
|
...redacted.reason === void 0 ? {} : { reason: redacted.reason },
|
|
109165
109557
|
summary: { ...redacted.summary },
|
|
109558
|
+
...redacted.suggestedTargetedProofs === void 0 ? {} : { suggestedTargetedProofs: redacted.suggestedTargetedProofs },
|
|
109166
109559
|
at: clock.nowIso()
|
|
109167
109560
|
});
|
|
109168
109561
|
return redacted;
|
|
109169
109562
|
}
|
|
109170
109563
|
function verifyAssertionLine(a) {
|
|
109171
|
-
|
|
109564
|
+
const relaxed2 = a.relaxedSites === void 0 || a.relaxedSites.length === 0 ? "" : a.declaredProof === true ? `, declared plan relaxed ${a.relaxedSites.join(", ")}` : `, relaxed ${a.relaxedSites.join(", ")}`;
|
|
109565
|
+
const proved = a.provedBy === void 0 ? "" : ` (proved by ${a.provedBy}${relaxed2})`;
|
|
109566
|
+
const refused = a.targetedRefusal === void 0 ? "" : ` (targeted proof refused: ${a.targetedRefusal})`;
|
|
109567
|
+
return `${a.verdict.padEnd(11)} ${a.site.padEnd(18)} ${a.check}${proved}${refused}`;
|
|
109172
109568
|
}
|
|
109173
109569
|
|
|
109174
109570
|
// src/journey-readiness.ts
|
|
@@ -109181,7 +109577,7 @@ var ReadinessError = class extends Error {
|
|
|
109181
109577
|
}
|
|
109182
109578
|
};
|
|
109183
109579
|
var REQUIRED_PROOF_CHECK = "proof";
|
|
109184
|
-
function resolveTarget2(opts, deps, required2) {
|
|
109580
|
+
function resolveTarget2(opts, deps, required2, declared) {
|
|
109185
109581
|
let environment;
|
|
109186
109582
|
if (opts.env !== void 0 || opts.baseUrl !== void 0) {
|
|
109187
109583
|
environment = environmentFromFlags(
|
|
@@ -109199,6 +109595,10 @@ function resolveTarget2(opts, deps, required2) {
|
|
|
109199
109595
|
if (missing.length > 0) {
|
|
109200
109596
|
throw new ReadinessError("E_READINESS_PARAMS", `missing required param(s): ${missing.join(", ")}`);
|
|
109201
109597
|
}
|
|
109598
|
+
const unknown2 = Object.keys(opts.params ?? {}).filter((name) => !declared.includes(name));
|
|
109599
|
+
if (unknown2.length > 0) {
|
|
109600
|
+
throw new ReadinessError("E_READINESS_PARAMS", `unknown param(s): ${unknown2.join(", ")}`);
|
|
109601
|
+
}
|
|
109202
109602
|
return environment;
|
|
109203
109603
|
}
|
|
109204
109604
|
function requiredParams(journey, pair, hasRequests) {
|
|
@@ -109241,7 +109641,7 @@ function boundaryCheck(ctx) {
|
|
|
109241
109641
|
return ctx.journey.metadata.boundary === void 0 ? { check: "boundary", journey: ctx.id, state: "not-run", detail: "no prepare-only boundary declared" } : { check: "boundary", journey: ctx.id, state: "pass", detail: "a prepare-only boundary is declared" };
|
|
109242
109642
|
}
|
|
109243
109643
|
function reviewStrictCheck(ctx) {
|
|
109244
|
-
const warns = (ctx.review.attention ?? []).filter((a) => a.severity === "warn");
|
|
109644
|
+
const warns = (ctx.review.attention ?? []).filter((a) => a.severity === "warn" && !(a.settledBy === "catalog-approval" && ctx.review.catalog !== void 0));
|
|
109245
109645
|
if (warns.length > 0) {
|
|
109246
109646
|
return {
|
|
109247
109647
|
check: { check: "review-strict", journey: ctx.id, state: "fail", detail: warns.map((a) => a.text).join("; ") },
|
|
@@ -109308,8 +109708,10 @@ function proofCheck(ctx, journeysDir, cwd) {
|
|
|
109308
109708
|
};
|
|
109309
109709
|
}
|
|
109310
109710
|
if (verify2.verdict === "proven") {
|
|
109311
|
-
const
|
|
109312
|
-
|
|
109711
|
+
const targeted = verify2.summary.targeted ?? 0;
|
|
109712
|
+
const passed = Object.entries(verify2.summary).filter(([k]) => k !== "targeted").reduce((a, [, b2]) => a + b2, 0);
|
|
109713
|
+
const detail = targeted === 0 ? `proven ${passed}/${passed}` : `proven ${passed}/${passed}, ${targeted} through a targeted proof (earlier defenses relaxed one at a time; see the verify report)`;
|
|
109714
|
+
return { check: { check: "proof", journey: ctx.id, state: "pass", detail, evidence } };
|
|
109313
109715
|
}
|
|
109314
109716
|
if (verify2.verdict === "insensitive") {
|
|
109315
109717
|
return {
|
|
@@ -109320,10 +109722,36 @@ function proofCheck(ctx, journeysDir, cwd) {
|
|
|
109320
109722
|
if (nothingToMutate(ctx.journey, verify2.summary)) {
|
|
109321
109723
|
return { check: { check: "proof", journey: ctx.id, state: "pass", detail: "read-only with no inputs: nothing a mutation could break", evidence } };
|
|
109322
109724
|
}
|
|
109323
|
-
|
|
109725
|
+
const suggested = verify2.status === "recorded" ? verify2.suggestedTargetedProofs : void 0;
|
|
109726
|
+
return {
|
|
109727
|
+
check: { check: "proof", journey: ctx.id, state: "inconclusive", detail: verify2.reason ?? `proof verdict ${verify2.verdict}`, evidence },
|
|
109728
|
+
blocker: { check: "proof", journey: ctx.id, why: inconclusiveProofGuidance(verify2.reason ?? "", suggested), next: `jevitate journey verify ${ctx.id} --mutate` }
|
|
109729
|
+
};
|
|
109730
|
+
}
|
|
109731
|
+
var TARGETED_REFUSAL_MARKER = "a targeted proof was refused: ";
|
|
109732
|
+
function inconclusiveProofGuidance(reason, suggested) {
|
|
109733
|
+
const at3 = reason.indexOf(TARGETED_REFUSAL_MARKER);
|
|
109734
|
+
if (at3 < 0) return "inspect the cascade assertions in the verify report";
|
|
109735
|
+
const refusal = reason.slice(at3 + TARGETED_REFUSAL_MARKER.length).trim();
|
|
109736
|
+
const unpaired = /^(\S+) is not paired with (\S+)$/.exec(refusal);
|
|
109737
|
+
if (unpaired !== null) {
|
|
109738
|
+
return `declare the earlier check ${unpaired[1]} as a defense of ${unpaired[2]} with a metadata.mutationPairs entry {check:'${unpaired[1]}', mustFailWhen:'${unpaired[2]}'}, then re-run verify`;
|
|
109739
|
+
}
|
|
109740
|
+
const bound2 = /relaxation bound (\d+)/.exec(refusal);
|
|
109741
|
+
if (bound2 !== null) {
|
|
109742
|
+
const first = suggested?.[0];
|
|
109743
|
+
const plan2 = first === void 0 ? `declare a metadata.targetedProofs plan (at most ${MAX_DECLARED_RELAXATIONS} earlier postconditions)` : `declare exactly this metadata.targetedProofs entry: ${JSON.stringify(first)} \u2014 a human reviews it like any other proof change (the review hash changes, so the Journey needs re-approval)`;
|
|
109744
|
+
return `${plan2}, then re-run verify (relaxation bound ${bound2[1]})`;
|
|
109745
|
+
}
|
|
109746
|
+
const blocked = /^(\S+) blocked the declared proof \(not in relax\)$/.exec(refusal);
|
|
109747
|
+
if (blocked !== null) return `add ${blocked[1]} to that plan's relax list (metadata.targetedProofs), then re-run verify`;
|
|
109748
|
+
const unsound = /^relaxed site (\S+) is not itself proven sensitive to (\S+)$/.exec(refusal);
|
|
109749
|
+
if (unsound !== null) return `pair ${unsound[1]} with ${unsound[2]} (metadata.mutationPairs) or make it a real defense, then re-run verify`;
|
|
109750
|
+
if (refusal.includes("action failed")) return "the mutated value makes an earlier action impossible, so that target cannot be proven independently \u2014 keep the check; it stays inconclusive by design";
|
|
109751
|
+
return "inspect the cascade assertions in the verify report";
|
|
109324
109752
|
}
|
|
109325
109753
|
function nothingToMutate(journey, summary2) {
|
|
109326
|
-
const paired = Object.entries(summary2).filter(([k]) => k !== "unpaired").reduce((n3, [, v]) => n3 + v, 0);
|
|
109754
|
+
const paired = Object.entries(summary2).filter(([k]) => k !== "unpaired" && k !== "targeted").reduce((n3, [, v]) => n3 + v, 0);
|
|
109327
109755
|
return (summary2.unpaired ?? 0) > 0 && paired === 0 && journeyWriteSteps(journey).length === 0 && journeyParamSchema(journey).required.length === 0;
|
|
109328
109756
|
}
|
|
109329
109757
|
function relativeEvidence(cwd, path2) {
|
|
@@ -109409,21 +109837,38 @@ async function runJourneyReadiness(opts, deps = {}) {
|
|
|
109409
109837
|
const pair = opts.with !== void 0;
|
|
109410
109838
|
let environment;
|
|
109411
109839
|
let replay;
|
|
109840
|
+
let boundConsumerParams;
|
|
109412
109841
|
if (mode !== "report") {
|
|
109413
|
-
|
|
109842
|
+
const producerSchema = journeyParamSchema(contexts[0].journey).required;
|
|
109843
|
+
environment = resolveTarget2(opts, deps, requiredParams(contexts[0].journey, pair, opts.requests !== void 0), producerSchema);
|
|
109414
109844
|
const params = { ...opts.params ?? {} };
|
|
109415
109845
|
try {
|
|
109416
109846
|
if (pair) {
|
|
109417
|
-
const chained = await chain(
|
|
109418
|
-
|
|
109419
|
-
|
|
109420
|
-
|
|
109421
|
-
|
|
109422
|
-
|
|
109423
|
-
|
|
109424
|
-
|
|
109425
|
-
|
|
109426
|
-
|
|
109847
|
+
const chained = await chain(
|
|
109848
|
+
{
|
|
109849
|
+
from: opts.id,
|
|
109850
|
+
to: opts.with,
|
|
109851
|
+
requests: opts.requests ?? "",
|
|
109852
|
+
params: { ...opts.params ?? {} },
|
|
109853
|
+
dir: opts.journeysDir,
|
|
109854
|
+
environment,
|
|
109855
|
+
...opts.storageState === void 0 ? {} : { storageState: opts.storageState },
|
|
109856
|
+
...opts.browser === void 0 ? {} : { browser: opts.browser },
|
|
109857
|
+
...opts.browserPortFactory === void 0 ? {} : { browserPortFactory: opts.browserPortFactory }
|
|
109858
|
+
},
|
|
109859
|
+
{
|
|
109860
|
+
// #608: the consumer's proof must run with the params the chain actually bound for it
|
|
109861
|
+
// (record fields + request), captured from the first consumer run; names and outcomes
|
|
109862
|
+
// are all the chain itself returns.
|
|
109863
|
+
runJourney: async (chainOpts) => {
|
|
109864
|
+
const result = await run(chainOpts);
|
|
109865
|
+
if (chainOpts.id === opts.with && boundConsumerParams === void 0) {
|
|
109866
|
+
boundConsumerParams = { ...chainOpts.params };
|
|
109867
|
+
}
|
|
109868
|
+
return result;
|
|
109869
|
+
}
|
|
109870
|
+
}
|
|
109871
|
+
);
|
|
109427
109872
|
replay = pairReplay(chained);
|
|
109428
109873
|
} else {
|
|
109429
109874
|
const result = await run({
|
|
@@ -109443,11 +109888,23 @@ async function runJourneyReadiness(opts, deps = {}) {
|
|
|
109443
109888
|
}
|
|
109444
109889
|
if (mode === "prove") {
|
|
109445
109890
|
for (const ctx of contexts) {
|
|
109891
|
+
const consumer = pair && ctx.id === opts.with;
|
|
109892
|
+
if (consumer && boundConsumerParams === void 0) {
|
|
109893
|
+
ctx.review = {
|
|
109894
|
+
...ctx.review,
|
|
109895
|
+
proof: {
|
|
109896
|
+
...ctx.review.proof,
|
|
109897
|
+
verify: { status: "not-verified", hint: `the chain bound no request for '${ctx.id}' \u2014 supply a --requests line that matches a discovered record` }
|
|
109898
|
+
}
|
|
109899
|
+
};
|
|
109900
|
+
continue;
|
|
109901
|
+
}
|
|
109902
|
+
const proofParams = consumer ? boundConsumerParams : params;
|
|
109446
109903
|
try {
|
|
109447
109904
|
await verify2({
|
|
109448
109905
|
dir: opts.journeysDir,
|
|
109449
109906
|
id: ctx.id,
|
|
109450
|
-
params,
|
|
109907
|
+
params: proofParams,
|
|
109451
109908
|
environment,
|
|
109452
109909
|
...opts.storageState === void 0 ? {} : { storageState: opts.storageState },
|
|
109453
109910
|
...opts.browser === void 0 ? {} : { browser: opts.browser },
|
|
@@ -110800,8 +111257,9 @@ function registerJourneyVerify(journey, program2, deps) {
|
|
|
110800
111257
|
for (const a of report2.assertions) out?.(`${verifyAssertionLine(a)}
|
|
110801
111258
|
`);
|
|
110802
111259
|
const s3 = report2.summary;
|
|
111260
|
+
const targeted = s3.targeted > 0 ? `, ${s3.targeted} targeted` : "";
|
|
110803
111261
|
out?.(
|
|
110804
|
-
`${report2.verdict} \u2014 journey '${report2.journeyId}': ${s3.sensitive} sensitive, ${s3.insensitive} insensitive, ${s3.cascade} cascade, ${s3.notApplied} not applied, ${s3.error} error, ${s3.unpaired} unpaired${report2.reason === void 0 ? "" : ` (${report2.reason})`}
|
|
111262
|
+
`${report2.verdict} \u2014 journey '${report2.journeyId}': ${s3.sensitive} sensitive, ${s3.insensitive} insensitive, ${s3.cascade} cascade, ${s3.notApplied} not applied, ${s3.error} error, ${s3.unpaired} unpaired${targeted}${report2.reason === void 0 ? "" : ` (${report2.reason})`}
|
|
110805
111263
|
`
|
|
110806
111264
|
);
|
|
110807
111265
|
}
|
|
@@ -115046,13 +115504,14 @@ var CLI_TOOL_SPECS = [
|
|
|
115046
115504
|
},
|
|
115047
115505
|
{
|
|
115048
115506
|
name: "chain_journeys",
|
|
115049
|
-
description: "`jevitate journey chain --from <discover> --to <reply> --requests <file>` (#580): run a discover Journey once, then a prepare-only reply Journey once per approved request in the JSON Lines file ({commentId, account, replyText}) matched to a discovered record by commentId AND account. Both Journeys must be promoted. Unmatched requests never run; an empty discovery stops the chain (no target is guessed). The result names ids and outcomes only, never record or reply text. Exit 0 only when every request matched and its run was ok.",
|
|
115507
|
+
description: "`jevitate journey chain --from <discover> --to <reply> --requests <file>` (#580): run a discover Journey once, then a prepare-only reply Journey once per approved request in the JSON Lines file ({commentId, account, replyText}) matched to a discovered record by commentId AND account. Both Journeys must be promoted. #608: params supplies explicit producer (discover) params, validated against the producer's schema before anything runs. Unmatched requests never run; an empty discovery stops the chain (no target is guessed). The result names ids and outcomes only, never record or reply text. Exit 0 only when every request matched and its run was ok.",
|
|
115050
115508
|
command: {
|
|
115051
115509
|
path: "journey chain",
|
|
115052
115510
|
params: {
|
|
115053
115511
|
from: s2("--from", { required: true }),
|
|
115054
115512
|
to: s2("--to", { required: true }),
|
|
115055
115513
|
requests: path("--requests", { required: true }),
|
|
115514
|
+
params: { kind: "params", flag: "--param" },
|
|
115056
115515
|
storageState: session("--storage-state"),
|
|
115057
115516
|
...EXTENSION,
|
|
115058
115517
|
...EMULATION,
|