@jevitate/cli 0.19.0 → 0.21.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 +317 -42
- package/dist/build-info.generated.js +3 -3
- package/dist/journey-chain.d.ts +10 -1
- package/dist/journey-chain.js +11 -4
- package/dist/journey-chain.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 +42 -10
- 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 +14 -0
- package/dist/journey-review.js.map +1 -1
- package/dist/journey-verify.d.ts +5 -1
- package/dist/journey-verify.js +27 -3
- package/dist/journey-verify.js.map +1 -1
- package/package.json +8 -8
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;
|
|
@@ -53868,6 +53947,7 @@ function relaxedMutationReplay(journey, m, opts = {}) {
|
|
|
53868
53947
|
return { journey: copy };
|
|
53869
53948
|
}
|
|
53870
53949
|
var MAX_TARGETED_RELAXATIONS = 4;
|
|
53950
|
+
var MAX_DECLARED_RELAXATIONS = 8;
|
|
53871
53951
|
function failedSiteKey(f2) {
|
|
53872
53952
|
if (f2.where === "step")
|
|
53873
53953
|
return `step:${f2.step}`;
|
|
@@ -53980,6 +54060,60 @@ async function runTargetedProof(journey, plan2, m, target, initial, replay, opts
|
|
|
53980
54060
|
}
|
|
53981
54061
|
return { site, relaxedSites, outcome: run.outcome, failedSites: run.failedSites };
|
|
53982
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
|
+
}
|
|
53983
54117
|
function needsRelaxedReplay(plan2, m, result) {
|
|
53984
54118
|
if (result.outcome !== "failed")
|
|
53985
54119
|
return false;
|
|
@@ -54025,6 +54159,19 @@ function classifyMutationPlain(site, m, result) {
|
|
|
54025
54159
|
return fs.some((f2) => f2.where === "end-state" && f2.index === site.index) ? "sensitive" : "cascade";
|
|
54026
54160
|
}
|
|
54027
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
|
+
}
|
|
54028
54175
|
function judgeAssertions(journey, plan2, results) {
|
|
54029
54176
|
const proofFor = (m, key2) => results.get(m.id)?.targeted?.find((t) => t.site === key2);
|
|
54030
54177
|
return journeyAssertions(journey).map((site) => {
|
|
@@ -54033,14 +54180,25 @@ function judgeAssertions(journey, plan2, results) {
|
|
|
54033
54180
|
if (paired.length === 0)
|
|
54034
54181
|
return { site: key2, verdict: "unpaired" };
|
|
54035
54182
|
const verdicts = paired.map((m) => ({ m, v: classifyMutation(site, m, results.get(m.id) ?? { outcome: "error", failedSites: [] }) }));
|
|
54183
|
+
let unsound;
|
|
54036
54184
|
for (const v of PRECEDENCE) {
|
|
54037
54185
|
const first = verdicts.find((x) => x.v === v);
|
|
54038
|
-
if (first === void 0)
|
|
54186
|
+
if (first === void 0 && !(v === "cascade" && unsound !== void 0))
|
|
54039
54187
|
continue;
|
|
54040
54188
|
if (v === "sensitive") {
|
|
54041
|
-
const
|
|
54042
|
-
|
|
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;
|
|
54043
54199
|
}
|
|
54200
|
+
if (v === "cascade" && unsound !== void 0)
|
|
54201
|
+
return { site: key2, verdict: v, targetedRefusal: unsound };
|
|
54044
54202
|
if (v === "cascade") {
|
|
54045
54203
|
const refusal = paired.map((m) => proofFor(m, key2)).find((t) => t !== void 0 && t.refused !== void 0);
|
|
54046
54204
|
if (refusal !== void 0)
|
|
@@ -54283,7 +54441,8 @@ var HANDOFF_FIELD_TYPES = ["id", "text", "path", "url", "timestamp"];
|
|
|
54283
54441
|
var HANDOFF_CONTRACT_RE = /^[a-z0-9][a-z0-9.-]{0,63}@[1-9][0-9]{0,3}$/;
|
|
54284
54442
|
var HandoffFieldSchema = external_exports.object({
|
|
54285
54443
|
type: external_exports.enum(HANDOFF_FIELD_TYPES, { message: `E_HANDOFF_SCHEMA: a field type is one of ${HANDOFF_FIELD_TYPES.join("|")}` }),
|
|
54286
|
-
required: external_exports.boolean().optional()
|
|
54444
|
+
required: external_exports.boolean().optional(),
|
|
54445
|
+
origins: external_exports.array(external_exports.string()).optional()
|
|
54287
54446
|
}).strict();
|
|
54288
54447
|
var HandoffFieldsSchema = external_exports.record(external_exports.string().min(1), HandoffFieldSchema);
|
|
54289
54448
|
var JourneyOutputsSchema = external_exports.object({
|
|
@@ -54391,7 +54550,8 @@ function fieldErrors(fields, bag, ctx) {
|
|
|
54391
54550
|
errors.push({ field: name, code: "missing" });
|
|
54392
54551
|
continue;
|
|
54393
54552
|
}
|
|
54394
|
-
const
|
|
54553
|
+
const fieldCtx = decl.type === "url" && decl.origins !== void 0 ? { allowedOrigins: decl.origins } : ctx;
|
|
54554
|
+
const err = validateFieldValue(decl.type, value, fieldCtx);
|
|
54395
54555
|
if (err !== null)
|
|
54396
54556
|
errors.push({ field: name, code: err.code });
|
|
54397
54557
|
}
|
|
@@ -54496,6 +54656,39 @@ function forEachNames(journey) {
|
|
|
54496
54656
|
names.add(step.as);
|
|
54497
54657
|
return names;
|
|
54498
54658
|
}
|
|
54659
|
+
var LOOPBACK_ORIGIN_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
54660
|
+
function originEntryError(entry) {
|
|
54661
|
+
let url2;
|
|
54662
|
+
try {
|
|
54663
|
+
url2 = new URL(entry);
|
|
54664
|
+
} catch {
|
|
54665
|
+
return "is not an origin";
|
|
54666
|
+
}
|
|
54667
|
+
if (url2.origin !== entry || url2.hostname.includes("*"))
|
|
54668
|
+
return "is not an exact origin";
|
|
54669
|
+
if (url2.protocol === "https:")
|
|
54670
|
+
return null;
|
|
54671
|
+
if (url2.protocol === "http:" && LOOPBACK_ORIGIN_HOSTS.has(url2.hostname))
|
|
54672
|
+
return null;
|
|
54673
|
+
return "must be https, or http only for a loopback host";
|
|
54674
|
+
}
|
|
54675
|
+
function originsFieldIssues(path2, fields) {
|
|
54676
|
+
const issues = [];
|
|
54677
|
+
for (const [name, decl] of Object.entries(fields)) {
|
|
54678
|
+
if (decl.origins === void 0)
|
|
54679
|
+
continue;
|
|
54680
|
+
if (decl.type !== "url") {
|
|
54681
|
+
issues.push({ path: [...path2, name], message: `E_HANDOFF_SCHEMA: origins is only allowed on url fields ("${name}" is ${decl.type})` });
|
|
54682
|
+
continue;
|
|
54683
|
+
}
|
|
54684
|
+
decl.origins.forEach((entry, i) => {
|
|
54685
|
+
const reason = originEntryError(entry);
|
|
54686
|
+
if (reason !== null)
|
|
54687
|
+
issues.push({ path: [...path2, name, i], message: `E_HANDOFF_SCHEMA: origins entry "${entry}" ${reason}` });
|
|
54688
|
+
});
|
|
54689
|
+
}
|
|
54690
|
+
return issues;
|
|
54691
|
+
}
|
|
54499
54692
|
function handoffSchemaIssues(journey) {
|
|
54500
54693
|
const issues = [];
|
|
54501
54694
|
const outputs = journey.metadata.outputs;
|
|
@@ -54508,6 +54701,7 @@ function handoffSchemaIssues(journey) {
|
|
|
54508
54701
|
if (outputs.collection !== void 0 && !forEachNames(journey).has(outputs.collection)) {
|
|
54509
54702
|
issues.push({ path: ["metadata", "outputs", "collection"], message: `E_HANDOFF_SCHEMA: outputs.collection "${outputs.collection}" names no forEach as` });
|
|
54510
54703
|
}
|
|
54704
|
+
issues.push(...originsFieldIssues(["metadata", "outputs", "fields"], outputs.fields));
|
|
54511
54705
|
if (outputs.key !== void 0) {
|
|
54512
54706
|
if (new Set(outputs.key).size !== outputs.key.length)
|
|
54513
54707
|
issues.push({ path: ["metadata", "outputs", "key"], message: "E_HANDOFF_SCHEMA: outputs.key: duplicate field" });
|
|
@@ -54546,6 +54740,7 @@ function handoffSchemaIssues(journey) {
|
|
|
54546
54740
|
if (!(param in inputs.bindings))
|
|
54547
54741
|
issues.push({ path: ["metadata", "inputs", "bindings"], message: `E_HANDOFF_SCHEMA: required param "${param}" has no binding` });
|
|
54548
54742
|
}
|
|
54743
|
+
issues.push(...originsFieldIssues(["metadata", "inputs", "request", "fields"], inputs.request.fields));
|
|
54549
54744
|
inputs.request.key.forEach((field3, i) => {
|
|
54550
54745
|
const decl = inputs.request.fields[field3];
|
|
54551
54746
|
if (decl === void 0)
|
|
@@ -54634,6 +54829,12 @@ var JourneySchema = external_exports.object({
|
|
|
54634
54829
|
endState: external_exports.array(OutcomeCheckSchema).max(50).optional(),
|
|
54635
54830
|
acceptedWeak: external_exports.object({ reason: external_exports.string().min(1), rules: external_exports.array(external_exports.string()) }).strict().optional(),
|
|
54636
54831
|
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(),
|
|
54832
|
+
// #616: a declared targeted-proof plan; the relax list is bounded tighter than the automatic proof.
|
|
54833
|
+
targetedProofs: external_exports.array(external_exports.object({
|
|
54834
|
+
check: external_exports.string().min(1).max(100),
|
|
54835
|
+
mustFailWhen: external_exports.string().min(1).max(200),
|
|
54836
|
+
relax: external_exports.array(external_exports.string().min(1).max(60)).min(1).max(MAX_DECLARED_RELAXATIONS)
|
|
54837
|
+
}).strict()).max(20).optional(),
|
|
54637
54838
|
approval: external_exports.object({
|
|
54638
54839
|
contentHash: external_exports.string().regex(/^[0-9a-f]{64}$/, "approval.contentHash: a sha256 hex digest"),
|
|
54639
54840
|
at: external_exports.string().min(1),
|
|
@@ -54659,6 +54860,9 @@ var JourneySchema = external_exports.object({
|
|
|
54659
54860
|
for (const issue3 of mutationPairIssues(j)) {
|
|
54660
54861
|
ctx.addIssue({ code: "custom", path: ["metadata", "mutationPairs", issue3.index, issue3.field], message: issue3.message });
|
|
54661
54862
|
}
|
|
54863
|
+
for (const issue3 of targetedProofPlanIssues(j)) {
|
|
54864
|
+
ctx.addIssue({ code: "custom", path: ["metadata", "targetedProofs", issue3.index, issue3.field], message: issue3.message });
|
|
54865
|
+
}
|
|
54662
54866
|
const declared = /* @__PURE__ */ new Set([...j.metadata.params, ...(j.metadata.parameters ?? []).map((p) => p.name)]);
|
|
54663
54867
|
j.recording.pages.forEach((page, pi) => page.steps.forEach((rs, si) => {
|
|
54664
54868
|
if (rs.step.kind !== "navigate")
|
|
@@ -55498,7 +55702,9 @@ var ReviewVerifySchema = external_exports.discriminatedUnion("status", [
|
|
|
55498
55702
|
/** True when the Journey changed after this proof ran (the verdict is for other content). */
|
|
55499
55703
|
stale: external_exports.boolean(),
|
|
55500
55704
|
reason: external_exports.string().optional(),
|
|
55501
|
-
summary: external_exports.record(external_exports.string(), external_exports.number())
|
|
55705
|
+
summary: external_exports.record(external_exports.string(), external_exports.number()),
|
|
55706
|
+
/** #616: a reviewable declared targeted-proof plan the automatic bound refused (names only). */
|
|
55707
|
+
suggestedTargetedProofs: external_exports.array(external_exports.object({ check: external_exports.string(), mustFailWhen: external_exports.string(), relax: external_exports.array(external_exports.string()) }).strict()).optional()
|
|
55502
55708
|
}).strict()
|
|
55503
55709
|
]);
|
|
55504
55710
|
var DiffSchema = external_exports.object({ added: external_exports.array(external_exports.string()), removed: external_exports.array(external_exports.string()) }).strict();
|
|
@@ -55518,7 +55724,9 @@ var ReviewChangeSchema = external_exports.discriminatedUnion("kind", [
|
|
|
55518
55724
|
stepIdOnly: external_exports.boolean().optional(),
|
|
55519
55725
|
steps: DiffSchema,
|
|
55520
55726
|
assertions: DiffSchema,
|
|
55521
|
-
sideEffects: DiffSchema
|
|
55727
|
+
sideEffects: DiffSchema,
|
|
55728
|
+
/** #616: proof-plan changes (`metadata.mutationPairs` / `metadata.targetedProofs`); omitted when neither side declares any. */
|
|
55729
|
+
proofPlans: DiffSchema.optional()
|
|
55522
55730
|
}).strict()
|
|
55523
55731
|
]);
|
|
55524
55732
|
var ReviewAttentionSchema = external_exports.object({
|
|
@@ -55563,6 +55771,8 @@ var JourneyReviewSchema = external_exports.object({
|
|
|
55563
55771
|
endStateText: external_exports.array(external_exports.string()).optional(),
|
|
55564
55772
|
lint: external_exports.object({ errors: external_exports.number().int(), warnings: external_exports.number().int(), findings: external_exports.array(ReviewLintFindingSchema) }).strict(),
|
|
55565
55773
|
acceptedWeak: external_exports.object({ reason: external_exports.string(), rules: external_exports.array(external_exports.string()) }).strict().optional(),
|
|
55774
|
+
/** #616: the declared targeted-proof plans (`metadata.targetedProofs`) a reviewer approves with the Journey. Additive. */
|
|
55775
|
+
targetedProofs: external_exports.array(external_exports.object({ check: external_exports.string(), mustFailWhen: external_exports.string(), relax: external_exports.array(external_exports.string()) }).strict()).optional(),
|
|
55566
55776
|
verify: ReviewVerifySchema
|
|
55567
55777
|
}).strict(),
|
|
55568
55778
|
changeSinceApproval: ReviewChangeSchema,
|
|
@@ -62534,9 +62744,9 @@ function hostStarvedFailure(failure3, health) {
|
|
|
62534
62744
|
}
|
|
62535
62745
|
|
|
62536
62746
|
// src/build-info.generated.ts
|
|
62537
|
-
var GENERATED_COMMIT = "
|
|
62538
|
-
var GENERATED_TREE = "
|
|
62539
|
-
var GENERATED_BUILT_AT = "2026-10-
|
|
62747
|
+
var GENERATED_COMMIT = "f37c33b";
|
|
62748
|
+
var GENERATED_TREE = "cd26ad0";
|
|
62749
|
+
var GENERATED_BUILT_AT = "2026-10-22T22:17:55.712Z";
|
|
62540
62750
|
|
|
62541
62751
|
// src/version.ts
|
|
62542
62752
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
@@ -62558,13 +62768,13 @@ function readCliVersion() {
|
|
|
62558
62768
|
|
|
62559
62769
|
// src/engine.ts
|
|
62560
62770
|
function bundledCommit() {
|
|
62561
|
-
return true ? "
|
|
62771
|
+
return true ? "f37c33b" : void 0;
|
|
62562
62772
|
}
|
|
62563
62773
|
function bundledBuiltAt() {
|
|
62564
|
-
return true ? "2026-10-
|
|
62774
|
+
return true ? "2026-10-22T22:17:55.712Z" : void 0;
|
|
62565
62775
|
}
|
|
62566
62776
|
function bundledTree() {
|
|
62567
|
-
return true ? "
|
|
62777
|
+
return true ? "cd26ad0" : void 0;
|
|
62568
62778
|
}
|
|
62569
62779
|
function currentBuildTree() {
|
|
62570
62780
|
return bundledTree() ?? GENERATED_TREE;
|
|
@@ -79986,9 +80196,11 @@ function renderSteps(r, w, opts) {
|
|
|
79986
80196
|
function renderProvesAtTheEnd(r, w, opts) {
|
|
79987
80197
|
void opts;
|
|
79988
80198
|
const endState = r.proof.endStateText ?? r.proof.endState;
|
|
79989
|
-
|
|
80199
|
+
const plans = r.proof.targetedProofs ?? [];
|
|
80200
|
+
if (endState.length > 0 || plans.length > 0) {
|
|
79990
80201
|
w.head("PROVES AT THE END", "Proves at the end");
|
|
79991
80202
|
for (const e of endState) w.put(`${w.bullet}\u2713 `, e, w.bullet.length + 2);
|
|
80203
|
+
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);
|
|
79992
80204
|
w.gap();
|
|
79993
80205
|
}
|
|
79994
80206
|
}
|
|
@@ -80079,7 +80291,13 @@ function renderFullDetail(r, w, opts) {
|
|
|
80079
80291
|
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"}`);
|
|
80080
80292
|
if (c.stepIdOnly === true) w.put(w.bullet, STEP_ID_ONLY_LABEL);
|
|
80081
80293
|
if (c.kind === "diff") {
|
|
80082
|
-
|
|
80294
|
+
const blocks = [
|
|
80295
|
+
["Steps", c.steps],
|
|
80296
|
+
["Assertions", c.assertions],
|
|
80297
|
+
["Side effects", c.sideEffects]
|
|
80298
|
+
];
|
|
80299
|
+
if (c.proofPlans !== void 0) blocks.push(["Proof plans", c.proofPlans]);
|
|
80300
|
+
for (const [label2, d] of blocks) {
|
|
80083
80301
|
if (d.added.length === 0 && d.removed.length === 0) continue;
|
|
80084
80302
|
w.put(w.bullet, `${label2}:`);
|
|
80085
80303
|
for (const l2 of d.removed) w.put(`${w.bullet} `, `\u2212 ${l2}`, w.bullet.length + 4);
|
|
@@ -80360,6 +80578,8 @@ function buildChange(journey, hash3, snapshot2, stepIdOnly) {
|
|
|
80360
80578
|
}
|
|
80361
80579
|
const approvedHash = journeyReviewHash(snapshot2);
|
|
80362
80580
|
const approvedAt = approval?.at ?? snapshot2.metadata.approval?.at;
|
|
80581
|
+
const beforePlans = proofPlanLines(snapshot2);
|
|
80582
|
+
const afterPlans = proofPlanLines(journey);
|
|
80363
80583
|
return {
|
|
80364
80584
|
kind: "diff",
|
|
80365
80585
|
approvedHash,
|
|
@@ -80368,7 +80588,9 @@ function buildChange(journey, hash3, snapshot2, stepIdOnly) {
|
|
|
80368
80588
|
...idOnly,
|
|
80369
80589
|
steps: multisetDiff(stepLines(snapshot2), stepLines(journey)),
|
|
80370
80590
|
assertions: multisetDiff(assertionLines(snapshot2), assertionLines(journey)),
|
|
80371
|
-
sideEffects: multisetDiff(sideEffectLines(snapshot2), sideEffectLines(journey))
|
|
80591
|
+
sideEffects: multisetDiff(sideEffectLines(snapshot2), sideEffectLines(journey)),
|
|
80592
|
+
// #616: only present when a side declares a proof plan, so an unrelated diff stays as before.
|
|
80593
|
+
...beforePlans.length === 0 && afterPlans.length === 0 ? {} : { proofPlans: multisetDiff(beforePlans, afterPlans) }
|
|
80372
80594
|
};
|
|
80373
80595
|
}
|
|
80374
80596
|
function stepLines(j) {
|
|
@@ -80380,6 +80602,12 @@ function assertionLines(j) {
|
|
|
80380
80602
|
...journeyAssertions(j).filter((s3) => s3.where === "step-request").map((s3) => `step request: ${describeCheck(s3.check)}`)
|
|
80381
80603
|
];
|
|
80382
80604
|
}
|
|
80605
|
+
function proofPlanLines(j) {
|
|
80606
|
+
return [
|
|
80607
|
+
...(j.metadata.mutationPairs ?? []).map((p) => `mutation pair: ${p.check} must fail when ${p.mustFailWhen}`),
|
|
80608
|
+
...(j.metadata.targetedProofs ?? []).map((p) => `targeted proof: ${p.check} against ${p.mustFailWhen} may relax ${p.relax.join(", ")}`)
|
|
80609
|
+
];
|
|
80610
|
+
}
|
|
80383
80611
|
function sideEffectLines(j) {
|
|
80384
80612
|
return [
|
|
80385
80613
|
...[...new Set(buildWriteRequests(j).map((w) => `write ${w.method} ${w.endpoint}`))],
|
|
@@ -80417,7 +80645,8 @@ function buildVerify(journey, hash3, rec) {
|
|
|
80417
80645
|
// #523: an executable-content change stales the proof; an old record without it compares the full hash.
|
|
80418
80646
|
stale: rec.proofHash === void 0 ? rec.contentHash !== hash3 : rec.proofHash !== journeyProofHash(journey),
|
|
80419
80647
|
...rec.reason === void 0 ? {} : { reason: rec.reason },
|
|
80420
|
-
summary: { ...rec.summary }
|
|
80648
|
+
summary: { ...rec.summary },
|
|
80649
|
+
...rec.suggestedTargetedProofs === void 0 ? {} : { suggestedTargetedProofs: rec.suggestedTargetedProofs.map((p) => ({ ...p, relax: [...p.relax] })) }
|
|
80421
80650
|
};
|
|
80422
80651
|
}
|
|
80423
80652
|
function buildJourneyReview(journey, ctx = {}) {
|
|
@@ -80495,6 +80724,8 @@ function buildJourneyReview(journey, ctx = {}) {
|
|
|
80495
80724
|
findings: findings.map((f2) => ({ rule: f2.rule, level: f2.level, ...f2.step === void 0 ? {} : { step: f2.step }, message: f2.message }))
|
|
80496
80725
|
},
|
|
80497
80726
|
...m.acceptedWeak === void 0 ? {} : { acceptedWeak: { reason: m.acceptedWeak.reason, rules: [...m.acceptedWeak.rules] } },
|
|
80727
|
+
// #616: the declared targeted-proof plans, copied out of the metadata for review.
|
|
80728
|
+
...m.targetedProofs === void 0 || m.targetedProofs.length === 0 ? {} : { targetedProofs: m.targetedProofs.map((p) => ({ check: p.check, mustFailWhen: p.mustFailWhen, relax: [...p.relax] })) },
|
|
80498
80729
|
verify: buildVerify(journey, hash3, ctx.lastVerify)
|
|
80499
80730
|
},
|
|
80500
80731
|
changeSinceApproval: buildChange(journey, hash3, ctx.approvedSnapshot, ctx.stepIdOnly === true),
|
|
@@ -94738,6 +94969,7 @@ var JourneyOutcomeChecks = class {
|
|
|
94738
94969
|
async #judge(s3, fallback) {
|
|
94739
94970
|
const window2 = s3.capture ?? fallback;
|
|
94740
94971
|
if (window2 === void 0) return [];
|
|
94972
|
+
const ownCapture = s3.capture !== void 0;
|
|
94741
94973
|
const since = s3.startedAt;
|
|
94742
94974
|
const until = s3.until;
|
|
94743
94975
|
const out = [];
|
|
@@ -94746,7 +94978,7 @@ var JourneyOutcomeChecks = class {
|
|
|
94746
94978
|
await evaluateNetworkCheckAwaiting(
|
|
94747
94979
|
c,
|
|
94748
94980
|
() => ({
|
|
94749
|
-
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)),
|
|
94981
|
+
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)),
|
|
94750
94982
|
truncated: window2.truncated
|
|
94751
94983
|
}),
|
|
94752
94984
|
s3.responseTimeoutMs ?? this.#responseTimeoutMs
|
|
@@ -108612,7 +108844,7 @@ function redactProducerParams(reason, params) {
|
|
|
108612
108844
|
return params === void 0 ? reason : redactReason(reason, params, Object.keys(params));
|
|
108613
108845
|
}
|
|
108614
108846
|
function chainSucceeded(result) {
|
|
108615
|
-
return result.unmatched.length === 0 && result.runs.every((run) => run.outcome === "ok");
|
|
108847
|
+
return result.unmatched.length === 0 && (result.rejected?.length ?? 0) === 0 && result.runs.every((run) => run.outcome === "ok");
|
|
108616
108848
|
}
|
|
108617
108849
|
function sharedRunOptions(opts) {
|
|
108618
108850
|
return {
|
|
@@ -108707,7 +108939,8 @@ async function runHandoffChain(inputs, outputs, fromJourney, fromOutput, request
|
|
|
108707
108939
|
runs.push({ ...identity, outcome: "error", ...stamp, reason: redactProducerParams(redactReason(err instanceof Error ? err.message : String(err), params, redactors), opts.params) });
|
|
108708
108940
|
}
|
|
108709
108941
|
}
|
|
108710
|
-
|
|
108942
|
+
const total = requestValidation.valid.length + requestValidation.rejected.length;
|
|
108943
|
+
return { from: { journey: opts.from, outcome: "ok", records: records.length }, runs, unmatched, requests: { total, ran: runs.length }, rejected };
|
|
108711
108944
|
}
|
|
108712
108945
|
async function runJourneyChain(opts, deps = {}) {
|
|
108713
108946
|
const runner = deps.runJourney ?? runJourneyProgrammatically;
|
|
@@ -108758,6 +108991,7 @@ async function runJourneyChain(opts, deps = {}) {
|
|
|
108758
108991
|
}
|
|
108759
108992
|
const runs = [];
|
|
108760
108993
|
const unmatched = [];
|
|
108994
|
+
let ran = 0;
|
|
108761
108995
|
for (const request of requests ?? []) {
|
|
108762
108996
|
const record2 = byKey.get(recordKey2(request.commentId, request.account));
|
|
108763
108997
|
if (record2 === void 0) {
|
|
@@ -108769,6 +109003,7 @@ async function runJourneyChain(opts, deps = {}) {
|
|
|
108769
109003
|
runs.push({ commentId: request.commentId, account: request.account, outcome: E_CHAIN_FIELD, reason: mapped.detail });
|
|
108770
109004
|
continue;
|
|
108771
109005
|
}
|
|
109006
|
+
ran += 1;
|
|
108772
109007
|
try {
|
|
108773
109008
|
const result = await runner({ ...shared3, id: opts.to, params: mapped.params });
|
|
108774
109009
|
runs.push({
|
|
@@ -108781,7 +109016,7 @@ async function runJourneyChain(opts, deps = {}) {
|
|
|
108781
109016
|
runs.push({ commentId: request.commentId, account: request.account, outcome: "error", reason: redactProducerParams(redactReason(err instanceof Error ? err.message : String(err), mapped.params), opts.params) });
|
|
108782
109017
|
}
|
|
108783
109018
|
}
|
|
108784
|
-
return { from: { journey: opts.from, outcome: "ok", records: count }, runs, unmatched };
|
|
109019
|
+
return { from: { journey: opts.from, outcome: "ok", records: count }, runs, unmatched, requests: { total: (requests ?? []).length, ran } };
|
|
108785
109020
|
}
|
|
108786
109021
|
|
|
108787
109022
|
// src/journey-cli/chain.ts
|
|
@@ -109237,6 +109472,8 @@ async function verifyJourneyMutations(opts, run = runJourneyProgrammatically) {
|
|
|
109237
109472
|
const checkOf = new Map(sites.map((s3) => [assertionSiteKey(s3), describeCheck(s3.check)]));
|
|
109238
109473
|
const plan2 = planJourneyMutations(journey, { ...lintOpts, params: opts.params });
|
|
109239
109474
|
const mutations = [];
|
|
109475
|
+
const suggested = [];
|
|
109476
|
+
const seenSuggestions = /* @__PURE__ */ new Set();
|
|
109240
109477
|
if (base.outcome === "ok") {
|
|
109241
109478
|
const replayOpts2 = { ...lintOpts, params: opts.params };
|
|
109242
109479
|
const runReplay = async (m, describe6) => {
|
|
@@ -109283,7 +109520,18 @@ async function verifyJourneyMutations(opts, run = runJourneyProgrammatically) {
|
|
|
109283
109520
|
if (pair.mutation !== m.id) continue;
|
|
109284
109521
|
const site = siteByKey.get(pair.site);
|
|
109285
109522
|
if (site === void 0 || classifyMutation(site, m, initial) !== "cascade") continue;
|
|
109286
|
-
|
|
109523
|
+
const declared = declaredTargetedProofFor(journey, m, pair.site);
|
|
109524
|
+
targeted.push(
|
|
109525
|
+
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)
|
|
109526
|
+
);
|
|
109527
|
+
const suggestion = suggestTargetedProofPlan(targeted[targeted.length - 1], m);
|
|
109528
|
+
if (suggestion !== null) {
|
|
109529
|
+
const key2 = `${suggestion.check}\0${suggestion.mustFailWhen}\0${suggestion.relax.join(",")}`;
|
|
109530
|
+
if (!seenSuggestions.has(key2)) {
|
|
109531
|
+
seenSuggestions.add(key2);
|
|
109532
|
+
suggested.push(suggestion);
|
|
109533
|
+
}
|
|
109534
|
+
}
|
|
109287
109535
|
}
|
|
109288
109536
|
mutations.push({
|
|
109289
109537
|
kind: m.kind,
|
|
@@ -109310,8 +109558,12 @@ async function verifyJourneyMutations(opts, run = runJourneyProgrammatically) {
|
|
|
109310
109558
|
verdict: a.verdict,
|
|
109311
109559
|
...a.provedBy === void 0 ? {} : { provedBy: a.provedBy },
|
|
109312
109560
|
...a.relaxedSites === void 0 ? {} : { relaxedSites: a.relaxedSites },
|
|
109313
|
-
...a.targetedRefusal === void 0 ? {} : { targetedRefusal: a.targetedRefusal }
|
|
109561
|
+
...a.targetedRefusal === void 0 ? {} : { targetedRefusal: a.targetedRefusal },
|
|
109562
|
+
// #616: the proving proof was a declared plan, not the automatic relaxation search.
|
|
109563
|
+
...a.provedBy !== void 0 && results.get(a.provedBy)?.targeted?.some((t) => t.site === a.site && t.declared === true) === true ? { declaredProof: true } : {}
|
|
109314
109564
|
}));
|
|
109565
|
+
const order = new Map(journeyAssertions(journey).map((s3, i) => [assertionSiteKey(s3), i]));
|
|
109566
|
+
const siteOrder = (key2) => order.get(key2) ?? Number.MAX_SAFE_INTEGER;
|
|
109315
109567
|
const { verdict, reason } = mutationProofVerdict({ basePassed: base.outcome === "ok", mutations, assertions: judged });
|
|
109316
109568
|
const count = (v) => assertions.filter((a) => a.verdict === v).length;
|
|
109317
109569
|
const report2 = {
|
|
@@ -109331,7 +109583,9 @@ async function verifyJourneyMutations(opts, run = runJourneyProgrammatically) {
|
|
|
109331
109583
|
unpaired: count("unpaired"),
|
|
109332
109584
|
// #609: assertions proven sensitive through a targeted proof that relaxed earlier sites.
|
|
109333
109585
|
targeted: assertions.filter((a) => (a.relaxedSites?.length ?? 0) > 0).length
|
|
109334
|
-
}
|
|
109586
|
+
},
|
|
109587
|
+
// #616: in assertion order, so the first suggestion is the one for the refusal the verdict quotes.
|
|
109588
|
+
...suggested.length === 0 ? {} : { suggestedTargetedProofs: [...suggested].sort((a, b2) => siteOrder(a.check) - siteOrder(b2.check)) }
|
|
109335
109589
|
};
|
|
109336
109590
|
const redacted = redactSecretParams(report2, journey, opts.params);
|
|
109337
109591
|
await writeVerifyRecord(opts.dir, {
|
|
@@ -109341,12 +109595,13 @@ async function verifyJourneyMutations(opts, run = runJourneyProgrammatically) {
|
|
|
109341
109595
|
verdict: redacted.verdict,
|
|
109342
109596
|
...redacted.reason === void 0 ? {} : { reason: redacted.reason },
|
|
109343
109597
|
summary: { ...redacted.summary },
|
|
109598
|
+
...redacted.suggestedTargetedProofs === void 0 ? {} : { suggestedTargetedProofs: redacted.suggestedTargetedProofs },
|
|
109344
109599
|
at: clock.nowIso()
|
|
109345
109600
|
});
|
|
109346
109601
|
return redacted;
|
|
109347
109602
|
}
|
|
109348
109603
|
function verifyAssertionLine(a) {
|
|
109349
|
-
const relaxed2 = a.relaxedSites === void 0 || a.relaxedSites.length === 0 ? "" : `, relaxed ${a.relaxedSites.join(", ")}`;
|
|
109604
|
+
const relaxed2 = a.relaxedSites === void 0 || a.relaxedSites.length === 0 ? "" : a.declaredProof === true ? `, declared plan relaxed ${a.relaxedSites.join(", ")}` : `, relaxed ${a.relaxedSites.join(", ")}`;
|
|
109350
109605
|
const proved = a.provedBy === void 0 ? "" : ` (proved by ${a.provedBy}${relaxed2})`;
|
|
109351
109606
|
const refused = a.targetedRefusal === void 0 ? "" : ` (targeted proof refused: ${a.targetedRefusal})`;
|
|
109352
109607
|
return `${a.verdict.padEnd(11)} ${a.site.padEnd(18)} ${a.check}${proved}${refused}`;
|
|
@@ -109507,13 +109762,14 @@ function proofCheck(ctx, journeysDir, cwd) {
|
|
|
109507
109762
|
if (nothingToMutate(ctx.journey, verify2.summary)) {
|
|
109508
109763
|
return { check: { check: "proof", journey: ctx.id, state: "pass", detail: "read-only with no inputs: nothing a mutation could break", evidence } };
|
|
109509
109764
|
}
|
|
109765
|
+
const suggested = verify2.status === "recorded" ? verify2.suggestedTargetedProofs : void 0;
|
|
109510
109766
|
return {
|
|
109511
109767
|
check: { check: "proof", journey: ctx.id, state: "inconclusive", detail: verify2.reason ?? `proof verdict ${verify2.verdict}`, evidence },
|
|
109512
|
-
blocker: { check: "proof", journey: ctx.id, why: inconclusiveProofGuidance(verify2.reason ?? ""), next: `jevitate journey verify ${ctx.id} --mutate` }
|
|
109768
|
+
blocker: { check: "proof", journey: ctx.id, why: inconclusiveProofGuidance(verify2.reason ?? "", suggested), next: `jevitate journey verify ${ctx.id} --mutate` }
|
|
109513
109769
|
};
|
|
109514
109770
|
}
|
|
109515
109771
|
var TARGETED_REFUSAL_MARKER = "a targeted proof was refused: ";
|
|
109516
|
-
function inconclusiveProofGuidance(reason) {
|
|
109772
|
+
function inconclusiveProofGuidance(reason, suggested) {
|
|
109517
109773
|
const at3 = reason.indexOf(TARGETED_REFUSAL_MARKER);
|
|
109518
109774
|
if (at3 < 0) return "inspect the cascade assertions in the verify report";
|
|
109519
109775
|
const refusal = reason.slice(at3 + TARGETED_REFUSAL_MARKER.length).trim();
|
|
@@ -109522,7 +109778,15 @@ function inconclusiveProofGuidance(reason) {
|
|
|
109522
109778
|
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`;
|
|
109523
109779
|
}
|
|
109524
109780
|
const bound2 = /relaxation bound (\d+)/.exec(refusal);
|
|
109525
|
-
if (bound2 !== null)
|
|
109781
|
+
if (bound2 !== null) {
|
|
109782
|
+
const first = suggested?.[0];
|
|
109783
|
+
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)`;
|
|
109784
|
+
return `${plan2}, then re-run verify (relaxation bound ${bound2[1]})`;
|
|
109785
|
+
}
|
|
109786
|
+
const blocked = /^(\S+) blocked the declared proof \(not in relax\)$/.exec(refusal);
|
|
109787
|
+
if (blocked !== null) return `add ${blocked[1]} to that plan's relax list (metadata.targetedProofs), then re-run verify`;
|
|
109788
|
+
const unsound = /^relaxed site (\S+) is not itself proven sensitive to (\S+)$/.exec(refusal);
|
|
109789
|
+
if (unsound !== null) return `pair ${unsound[1]} with ${unsound[2]} (metadata.mutationPairs) or make it a real defense, then re-run verify`;
|
|
109526
109790
|
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";
|
|
109527
109791
|
return "inspect the cascade assertions in the verify report";
|
|
109528
109792
|
}
|
|
@@ -109570,10 +109834,18 @@ function oneJourneyReplay(result) {
|
|
|
109570
109834
|
if (result.outcome === "inconclusive") return { state: "inconclusive", detail: result.reason ?? "the replay had no verdict" };
|
|
109571
109835
|
return { state: "fail", detail: `the replay ended ${result.outcome}` };
|
|
109572
109836
|
}
|
|
109837
|
+
function firstRejection(result) {
|
|
109838
|
+
const rejection = result.rejected?.[0];
|
|
109839
|
+
if (rejection === void 0) return void 0;
|
|
109840
|
+
const error62 = rejection.errors[0];
|
|
109841
|
+
return error62 === void 0 ? rejection.key : `${rejection.key}: ${error62.field} ${error62.code}`;
|
|
109842
|
+
}
|
|
109573
109843
|
function pairReplay(result) {
|
|
109574
109844
|
if (chainSucceeded(result)) return { state: "pass", detail: "the chain held for every request" };
|
|
109575
109845
|
if (result.runs.some((r) => r.outcome === "inconclusive")) return { state: "inconclusive", detail: "a chained run had no verdict" };
|
|
109576
|
-
|
|
109846
|
+
const counts2 = `${result.unmatched.length} unmatched request(s), ${result.rejected?.length ?? 0} rejected, ${result.runs.filter((r) => r.outcome !== "ok").length} failed run(s)`;
|
|
109847
|
+
const first = firstRejection(result);
|
|
109848
|
+
return { state: "fail", detail: first === void 0 ? counts2 : `${counts2}; first rejection ${first}` };
|
|
109577
109849
|
}
|
|
109578
109850
|
function verdictOf2(checks, mode) {
|
|
109579
109851
|
if (checks.some((c) => c.state === "fail")) return "blocked";
|
|
@@ -109614,13 +109886,14 @@ async function runJourneyReadiness(opts, deps = {}) {
|
|
|
109614
109886
|
let environment;
|
|
109615
109887
|
let replay;
|
|
109616
109888
|
let boundConsumerParams;
|
|
109889
|
+
let chained;
|
|
109617
109890
|
if (mode !== "report") {
|
|
109618
109891
|
const producerSchema = journeyParamSchema(contexts[0].journey).required;
|
|
109619
109892
|
environment = resolveTarget2(opts, deps, requiredParams(contexts[0].journey, pair, opts.requests !== void 0), producerSchema);
|
|
109620
109893
|
const params = { ...opts.params ?? {} };
|
|
109621
109894
|
try {
|
|
109622
109895
|
if (pair) {
|
|
109623
|
-
|
|
109896
|
+
chained = await chain(
|
|
109624
109897
|
{
|
|
109625
109898
|
from: opts.id,
|
|
109626
109899
|
to: opts.with,
|
|
@@ -109666,11 +109939,13 @@ async function runJourneyReadiness(opts, deps = {}) {
|
|
|
109666
109939
|
for (const ctx of contexts) {
|
|
109667
109940
|
const consumer = pair && ctx.id === opts.with;
|
|
109668
109941
|
if (consumer && boundConsumerParams === void 0) {
|
|
109942
|
+
const rejection = chained === void 0 ? void 0 : firstRejection(chained);
|
|
109943
|
+
const hint2 = rejection === void 0 ? `the chain bound no request for '${ctx.id}' \u2014 supply a --requests line that matches a discovered record` : `the chain rejected the consumer's matching record (${rejection})`;
|
|
109669
109944
|
ctx.review = {
|
|
109670
109945
|
...ctx.review,
|
|
109671
109946
|
proof: {
|
|
109672
109947
|
...ctx.review.proof,
|
|
109673
|
-
verify: { status: "not-verified", hint:
|
|
109948
|
+
verify: { status: "not-verified", hint: hint2 }
|
|
109674
109949
|
}
|
|
109675
109950
|
};
|
|
109676
109951
|
continue;
|