@jevitate/cli 0.7.0 → 0.9.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/ai-cli.d.ts +3 -1
- package/dist/ai-cli.js +60 -16
- package/dist/ai-cli.js.map +1 -1
- package/dist/approval-provenance.d.ts +136 -0
- package/dist/approval-provenance.js +317 -0
- package/dist/approval-provenance.js.map +1 -0
- package/dist/author-journey-cli.js +4 -2
- package/dist/author-journey-cli.js.map +1 -1
- package/dist/bin.js +30544 -20851
- package/dist/browser-cli.d.ts +10 -0
- package/dist/browser-cli.js +50 -0
- package/dist/browser-cli.js.map +1 -0
- package/dist/build-info.generated.js +2 -2
- package/dist/builtin-analyzers.d.ts +7 -0
- package/dist/builtin-analyzers.js +19 -0
- package/dist/builtin-analyzers.js.map +1 -0
- package/dist/campaign-cli.d.ts +6 -0
- package/dist/campaign-cli.js +9 -4
- package/dist/campaign-cli.js.map +1 -1
- package/dist/catalog-analysis.d.ts +70 -0
- package/dist/catalog-analysis.js +507 -0
- package/dist/catalog-analysis.js.map +1 -0
- package/dist/catalog-api.d.ts +73 -0
- package/dist/catalog-api.js +97 -0
- package/dist/catalog-api.js.map +1 -0
- package/dist/catalog-cli.d.ts +12 -0
- package/dist/catalog-cli.js +224 -0
- package/dist/catalog-cli.js.map +1 -0
- package/dist/catalog-review.d.ts +29 -0
- package/dist/catalog-review.js +290 -0
- package/dist/catalog-review.js.map +1 -0
- package/dist/catalog.d.ts +133 -0
- package/dist/catalog.js +352 -0
- package/dist/catalog.js.map +1 -0
- package/dist/change-context.d.ts +44 -0
- package/dist/change-context.js +203 -0
- package/dist/change-context.js.map +1 -0
- package/dist/check-api.d.ts +1 -1
- package/dist/check-api.js +68 -9
- package/dist/check-api.js.map +1 -1
- package/dist/check-cli.d.ts +5 -1
- package/dist/check-cli.js +64 -10
- package/dist/check-cli.js.map +1 -1
- package/dist/check-execute.d.ts +16 -1
- package/dist/check-execute.js +78 -31
- package/dist/check-execute.js.map +1 -1
- package/dist/check-plan.d.ts +14 -0
- package/dist/check-plan.js +28 -2
- package/dist/check-plan.js.map +1 -1
- package/dist/check-suite.js +6 -3
- package/dist/check-suite.js.map +1 -1
- package/dist/check-types.d.ts +51 -6
- package/dist/check-types.js +8 -0
- package/dist/check-types.js.map +1 -1
- package/dist/cli-output.js +85 -5
- package/dist/cli-output.js.map +1 -1
- package/dist/cli-shared.d.ts +22 -2
- package/dist/cli-shared.js +34 -5
- package/dist/cli-shared.js.map +1 -1
- package/dist/demo-aspect-api.d.ts +15 -0
- package/dist/demo-aspect-api.js +26 -1
- package/dist/demo-aspect-api.js.map +1 -1
- package/dist/demo-aspect-cli.js +64 -5
- package/dist/demo-aspect-cli.js.map +1 -1
- package/dist/exit-codes.d.ts +6 -3
- package/dist/exit-codes.js +13 -3
- package/dist/exit-codes.js.map +1 -1
- package/dist/explore-adversarial.d.ts +3 -1
- package/dist/explore-adversarial.js +4 -4
- package/dist/explore-adversarial.js.map +1 -1
- package/dist/explore-author.js +5 -2
- package/dist/explore-author.js.map +1 -1
- package/dist/explore-cli.js +145 -9
- package/dist/explore-cli.js.map +1 -1
- package/dist/explore-coverage.d.ts +6 -2
- package/dist/explore-coverage.js +7 -6
- package/dist/explore-coverage.js.map +1 -1
- package/dist/explore-feature.d.ts +3 -1
- package/dist/explore-feature.js +5 -5
- package/dist/explore-feature.js.map +1 -1
- package/dist/explore-goal.d.ts +37 -6
- package/dist/explore-goal.js +69 -50
- package/dist/explore-goal.js.map +1 -1
- package/dist/explore-shared.d.ts +12 -7
- package/dist/explore-shared.js +9 -1
- package/dist/explore-shared.js.map +1 -1
- package/dist/findings-filing.d.ts +6 -0
- package/dist/findings-filing.js +15 -1
- package/dist/findings-filing.js.map +1 -1
- package/dist/goal-run-flags.d.ts +2 -0
- package/dist/goal-run-flags.js +8 -1
- package/dist/goal-run-flags.js.map +1 -1
- package/dist/heal-probe-guard.d.ts +9 -0
- package/dist/heal-probe-guard.js +113 -0
- package/dist/heal-probe-guard.js.map +1 -0
- package/dist/host-health-run.d.ts +32 -1
- package/dist/host-health-run.js +38 -1
- package/dist/host-health-run.js.map +1 -1
- package/dist/http-5xx-outcome.d.ts +8 -3
- package/dist/http-5xx-outcome.js +5 -15
- package/dist/http-5xx-outcome.js.map +1 -1
- package/dist/init-cli.d.ts +7 -0
- package/dist/init-cli.js +79 -9
- package/dist/init-cli.js.map +1 -1
- package/dist/init-codeowners.d.ts +39 -0
- package/dist/init-codeowners.js +91 -0
- package/dist/init-codeowners.js.map +1 -0
- package/dist/init-keys.d.ts +3 -1
- package/dist/init-keys.js +20 -8
- package/dist/init-keys.js.map +1 -1
- package/dist/init-skills.d.ts +85 -7
- package/dist/init-skills.js +303 -36
- package/dist/init-skills.js.map +1 -1
- package/dist/jev-advisor.d.ts +72 -0
- package/dist/jev-advisor.js +172 -0
- package/dist/jev-advisor.js.map +1 -0
- package/dist/journey-api.d.ts +76 -5
- package/dist/journey-api.js +238 -29
- package/dist/journey-api.js.map +1 -1
- package/dist/journey-cli.js +257 -23
- package/dist/journey-cli.js.map +1 -1
- package/dist/journey-heal.d.ts +74 -0
- package/dist/journey-heal.js +180 -0
- package/dist/journey-heal.js.map +1 -0
- package/dist/journey-network-checks.d.ts +5 -1
- package/dist/journey-network-checks.js +15 -4
- package/dist/journey-network-checks.js.map +1 -1
- package/dist/journey-prefix.js +2 -2
- package/dist/journey-prefix.js.map +1 -1
- package/dist/journey-proposal-store.d.ts +100 -0
- package/dist/journey-proposal-store.js +342 -0
- package/dist/journey-proposal-store.js.map +1 -0
- package/dist/journey-result-record.d.ts +40 -0
- package/dist/journey-result-record.js +28 -0
- package/dist/journey-result-record.js.map +1 -0
- package/dist/journey-review-api.d.ts +19 -0
- package/dist/journey-review-api.js +53 -0
- package/dist/journey-review-api.js.map +1 -0
- package/dist/journey-review-store.d.ts +16 -0
- package/dist/journey-review-store.js +95 -0
- package/dist/journey-review-store.js.map +1 -0
- package/dist/journey-review.d.ts +54 -0
- package/dist/journey-review.js +538 -0
- package/dist/journey-review.js.map +1 -0
- package/dist/journey-verify.js +14 -1
- package/dist/journey-verify.js.map +1 -1
- package/dist/key-report.d.ts +20 -5
- package/dist/key-report.js +32 -9
- package/dist/key-report.js.map +1 -1
- package/dist/kill-signal.js +10 -2
- package/dist/kill-signal.js.map +1 -1
- package/dist/launch-armed.js +5 -1
- package/dist/launch-armed.js.map +1 -1
- package/dist/load-cli.js +4 -2
- package/dist/load-cli.js.map +1 -1
- package/dist/log-classes.d.ts +73 -0
- package/dist/log-classes.js +167 -0
- package/dist/log-classes.js.map +1 -0
- package/dist/log-correlation.d.ts +29 -10
- package/dist/log-correlation.js +44 -21
- package/dist/log-correlation.js.map +1 -1
- package/dist/login-cli.d.ts +17 -0
- package/dist/login-cli.js +161 -0
- package/dist/login-cli.js.map +1 -0
- package/dist/logs-cli.js +3 -1
- package/dist/logs-cli.js.map +1 -1
- package/dist/mcp-api.d.ts +6 -1
- package/dist/mcp-api.js +110 -16
- package/dist/mcp-api.js.map +1 -1
- package/dist/mcp-cli-runner.d.ts +2 -0
- package/dist/mcp-cli-runner.js +3 -1
- package/dist/mcp-cli-runner.js.map +1 -1
- package/dist/mcp-cli-tools.d.ts +5 -0
- package/dist/mcp-cli-tools.js +178 -23
- package/dist/mcp-cli-tools.js.map +1 -1
- package/dist/mission-cli.js +8 -4
- package/dist/mission-cli.js.map +1 -1
- package/dist/mission-journal.d.ts +4 -1
- package/dist/mission-journal.js +9 -3
- package/dist/mission-journal.js.map +1 -1
- package/dist/mission-queue-cli.js +21 -2
- package/dist/mission-queue-cli.js.map +1 -1
- package/dist/mission-queue-runner.d.ts +6 -0
- package/dist/mission-queue-runner.js +33 -3
- package/dist/mission-queue-runner.js.map +1 -1
- package/dist/multi-run-cli.d.ts +7 -1
- package/dist/multi-run-cli.js +10 -3
- package/dist/multi-run-cli.js.map +1 -1
- package/dist/multi-run.d.ts +44 -8
- package/dist/multi-run.js +118 -25
- package/dist/multi-run.js.map +1 -1
- package/dist/persona-login.d.ts +256 -0
- package/dist/persona-login.js +884 -0
- package/dist/persona-login.js.map +1 -0
- package/dist/pre-approval.d.ts +98 -0
- package/dist/pre-approval.js +175 -0
- package/dist/pre-approval.js.map +1 -0
- package/dist/program.js +12 -0
- package/dist/program.js.map +1 -1
- package/dist/project-dir.d.ts +2 -1
- package/dist/project-dir.js +3 -1
- package/dist/project-dir.js.map +1 -1
- package/dist/readiness.d.ts +29 -0
- package/dist/readiness.js +291 -0
- package/dist/readiness.js.map +1 -0
- package/dist/regression-cli.js +4 -2
- package/dist/regression-cli.js.map +1 -1
- package/dist/report-api.d.ts +25 -2
- package/dist/report-api.js +34 -5
- package/dist/report-api.js.map +1 -1
- package/dist/report-cli.js +8 -1
- package/dist/report-cli.js.map +1 -1
- package/dist/resource-preflight.js +5 -3
- package/dist/resource-preflight.js.map +1 -1
- package/dist/result-schema.d.ts +12 -0
- package/dist/result-schema.js +8 -0
- package/dist/result-schema.js.map +1 -1
- package/dist/run-index.d.ts +4 -2
- package/dist/run-index.js +3 -1
- package/dist/run-index.js.map +1 -1
- package/dist/run-key-preflight.d.ts +2 -1
- package/dist/run-key-preflight.js +3 -2
- package/dist/run-key-preflight.js.map +1 -1
- package/dist/run-metadata.d.ts +29 -0
- package/dist/run-metadata.js +48 -0
- package/dist/run-metadata.js.map +1 -0
- package/dist/run-tags-cli.d.ts +14 -0
- package/dist/run-tags-cli.js +41 -0
- package/dist/run-tags-cli.js.map +1 -0
- package/dist/self-heal-adapter.d.ts +43 -27
- package/dist/self-heal-adapter.js +196 -62
- package/dist/self-heal-adapter.js.map +1 -1
- package/dist/site-cli.d.ts +1 -1
- package/dist/site-cli.js +24 -1
- package/dist/site-cli.js.map +1 -1
- package/dist/source-cli.js +9 -5
- package/dist/source-cli.js.map +1 -1
- package/dist/suite-explore-options.d.ts +14 -0
- package/dist/suite-explore-options.js +8 -0
- package/dist/suite-explore-options.js.map +1 -1
- package/dist/sweep-api.d.ts +289 -0
- package/dist/sweep-api.js +859 -0
- package/dist/sweep-api.js.map +1 -0
- package/dist/sweep-cli.d.ts +9 -0
- package/dist/sweep-cli.js +117 -0
- package/dist/sweep-cli.js.map +1 -0
- package/dist/target-config.d.ts +5 -2
- package/dist/target-config.js +15 -0
- package/dist/target-config.js.map +1 -1
- package/dist/ux-cli.js +3 -1
- package/dist/ux-cli.js.map +1 -1
- package/dist/ux-usability.d.ts +9 -3
- package/dist/ux-usability.js +8 -5
- package/dist/ux-usability.js.map +1 -1
- package/dist/verify-fix-cli.js +4 -2
- package/dist/verify-fix-cli.js.map +1 -1
- package/package.json +9 -9
- package/skills/jevitate-ci-check/SKILL.md +9 -1
- package/skills/jevitate-demo/SKILL.md +5 -2
- package/skills/jevitate-getting-started/SKILL.md +14 -1
- package/skills/jevitate-mission-scope/SKILL.md +2 -2
- package/skills/jevitate-run-journey/SKILL.md +42 -9
- package/skills/jevitate-test-campaign/SKILL.md +26 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journey-review-store.js","sourceRoot":"","sources":["../src/journey-review-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { JourneyProposal } from "@jevitate/journey";
|
|
2
|
+
import { type Journey, type JourneyReview, type JourneyCatalogLinks, type Finding } from "@jevitate/journey";
|
|
3
|
+
import { type SafetyConfig } from "@jevitate/explore";
|
|
4
|
+
/**
|
|
5
|
+
* #432: the content hash a review sheet shows and an approval binds to — `contentHash` (the same
|
|
6
|
+
* function annotation drafts are bound with) of the Journey WITHOUT its approval bookkeeping
|
|
7
|
+
* (`promoted`, `approval`, `acceptedWeak`), so promoting does not itself change what was approved.
|
|
8
|
+
*/
|
|
9
|
+
export declare function journeyReviewHash(journey: Journey): string;
|
|
10
|
+
/** #432: the last `journey verify --mutate` verdict, as recorded beside the Journey. */
|
|
11
|
+
export interface JourneyVerifyRecord {
|
|
12
|
+
readonly journeyId: string;
|
|
13
|
+
/** `journeyReviewHash` of the Journey the proof ran on. */
|
|
14
|
+
readonly contentHash: string;
|
|
15
|
+
readonly verdict: string;
|
|
16
|
+
readonly reason?: string;
|
|
17
|
+
readonly summary: Readonly<Record<string, number>>;
|
|
18
|
+
readonly at: string;
|
|
19
|
+
}
|
|
20
|
+
export interface JourneyReviewContext {
|
|
21
|
+
/** The site's safety config (targets.json `safety` for the Journey's origin): `deny` / `paid` / `allowControl`. */
|
|
22
|
+
readonly safety?: Pick<SafetyConfig, "deny" | "paid" | "allowControl">;
|
|
23
|
+
/** The Journey as last approved (`.approved/<id>.json`), or null when there is none. */
|
|
24
|
+
readonly approvedSnapshot?: Journey | null;
|
|
25
|
+
/** The last recorded mutation-proof verdict, or null when there is none. */
|
|
26
|
+
readonly lastVerify?: JourneyVerifyRecord | null;
|
|
27
|
+
/** #433: the Journey's catalog links and its pre-approval findings (the CLI loads the catalog). */
|
|
28
|
+
readonly catalog?: {
|
|
29
|
+
readonly links: JourneyCatalogLinks;
|
|
30
|
+
readonly findings: readonly Finding[];
|
|
31
|
+
};
|
|
32
|
+
/** #453: the pending self-heal proposal, with whether it is stale and why it cannot be accepted. */
|
|
33
|
+
readonly proposal?: {
|
|
34
|
+
readonly proposal: JourneyProposal;
|
|
35
|
+
readonly stale: boolean;
|
|
36
|
+
readonly problem?: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** #432: the review sheet of one Journey — pure. */
|
|
40
|
+
export declare function buildJourneyReview(journey: Journey, ctx?: JourneyReviewContext): JourneyReview;
|
|
41
|
+
/** #432: the review sheet as Markdown (`journey review --markdown`). */
|
|
42
|
+
export declare function renderReviewMarkdown(review: JourneyReview): string;
|
|
43
|
+
/** #432: the review sheet as plain text (`journey review`, and `journey promote` in human mode). */
|
|
44
|
+
export declare function renderReviewText(review: JourneyReview): string;
|
|
45
|
+
/** #432: a review sheet file's content hash refused (none, or two different ones). */
|
|
46
|
+
export declare class ReviewSheetError extends Error {
|
|
47
|
+
readonly code = "E_JOURNEY_REVIEW_ARGS";
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* #432: the content hash a review sheet file names — the `contentHash` of a JSON sheet
|
|
51
|
+
* (`journey review --json --out`, its envelope or the bare sheet), or the `Content hash:` line of a
|
|
52
|
+
* Markdown / text one. A sheet naming no hash, or two different ones, is refused.
|
|
53
|
+
*/
|
|
54
|
+
export declare function reviewSheetHash(text: string): string;
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import { contentHash } from "@jevitate/domain";
|
|
2
|
+
import { describeProvenance } from "./approval-provenance.js";
|
|
3
|
+
import { SECRET_PARAM_NAME_RE, describeStep, flatJourneySteps, isNoClaimExpect, isOwnTargetVisible, journeyAssertions, journeyEndState, journeyRunParams, journeyWriteSteps, lintJourney, secretParamNames, } from "@jevitate/journey";
|
|
4
|
+
import { renderFindings } from "./catalog-review.js";
|
|
5
|
+
import { SafetyPolicy, describeCheck } from "@jevitate/explore";
|
|
6
|
+
import { navigateUrlParams, writeClassifier } from "@jevitate/recording";
|
|
7
|
+
/**
|
|
8
|
+
* #432 — `jevitate journey review <id>`: one human-readable review sheet for promotion sign-off.
|
|
9
|
+
* `buildJourneyReview` is PURE (no I/O, no clock): the CLI loads what it needs (the targets.json
|
|
10
|
+
* safety config, the approved snapshot, the last mutation-proof verdict) into the context.
|
|
11
|
+
*
|
|
12
|
+
* Secrets: the sheet is built from the Journey alone, which never carries a secret parameter's value
|
|
13
|
+
* (values only arrive as `--param`). Defense in depth: a literal typed into a field whose name reads
|
|
14
|
+
* as a credential is shown «redacted», and a secret reference is listed by field and manager only.
|
|
15
|
+
*/
|
|
16
|
+
/** The approval bookkeeping a review hash leaves out: promoting writes them, so they cannot be part of what was reviewed. */
|
|
17
|
+
const BOOKKEEPING = ["promoted", "approval", "acceptedWeak"];
|
|
18
|
+
/**
|
|
19
|
+
* #432: the content hash a review sheet shows and an approval binds to — `contentHash` (the same
|
|
20
|
+
* function annotation drafts are bound with) of the Journey WITHOUT its approval bookkeeping
|
|
21
|
+
* (`promoted`, `approval`, `acceptedWeak`), so promoting does not itself change what was approved.
|
|
22
|
+
*/
|
|
23
|
+
export function journeyReviewHash(journey) {
|
|
24
|
+
const metadata = { ...journey.metadata };
|
|
25
|
+
for (const k of BOOKKEEPING)
|
|
26
|
+
delete metadata[k];
|
|
27
|
+
return contentHash({ metadata, recording: journey.recording });
|
|
28
|
+
}
|
|
29
|
+
function targetOf(step) {
|
|
30
|
+
if (step.kind === "forEach")
|
|
31
|
+
return step.items;
|
|
32
|
+
return "target" in step ? step.target : undefined;
|
|
33
|
+
}
|
|
34
|
+
function controlName(t) {
|
|
35
|
+
return (t.name ?? t.label ?? t.text ?? "").replace(/\s+/g, " ").trim();
|
|
36
|
+
}
|
|
37
|
+
function reviewTarget(t) {
|
|
38
|
+
if (t === undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
const name = controlName(t);
|
|
41
|
+
const out = { ...(t.role === undefined ? {} : { role: t.role }), ...(name === "" ? {} : { name }) };
|
|
42
|
+
return Object.keys(out).length === 0 ? undefined : out;
|
|
43
|
+
}
|
|
44
|
+
/** Does the field read as a credential (its name, label or test id)? Its literal value is then never shown. */
|
|
45
|
+
function secretLooking(t) {
|
|
46
|
+
return [t.name, t.label, t.testId, t.text].some((s) => s !== undefined && SECRET_PARAM_NAME_RE.test(s));
|
|
47
|
+
}
|
|
48
|
+
function hideValue(v) {
|
|
49
|
+
return "var" in v || v.redacted ? v : { redacted: true, length: 0 };
|
|
50
|
+
}
|
|
51
|
+
/** The step with any credential-looking literal value hidden — what `describeStep` may show. */
|
|
52
|
+
function sanitized(step) {
|
|
53
|
+
if ((step.kind === "fill" || step.kind === "select") && secretLooking(step.target))
|
|
54
|
+
return { ...step, value: hideValue(step.value) };
|
|
55
|
+
if (step.kind === "editText" && step.value !== undefined && secretLooking(step.target))
|
|
56
|
+
return { ...step, value: hideValue(step.value) };
|
|
57
|
+
return step;
|
|
58
|
+
}
|
|
59
|
+
function varName(v) {
|
|
60
|
+
return v !== undefined && "var" in v ? [v.var] : [];
|
|
61
|
+
}
|
|
62
|
+
/** The parameters (by name) a step uses, its `forEach` children included. */
|
|
63
|
+
function paramsOf(step) {
|
|
64
|
+
switch (step.kind) {
|
|
65
|
+
case "navigate":
|
|
66
|
+
return navigateUrlParams(step.url);
|
|
67
|
+
case "fill":
|
|
68
|
+
case "select":
|
|
69
|
+
return varName(step.value);
|
|
70
|
+
case "editText":
|
|
71
|
+
return varName(step.value);
|
|
72
|
+
case "upload":
|
|
73
|
+
return varName(step.file);
|
|
74
|
+
case "forEach":
|
|
75
|
+
return step.steps.flatMap(paramsOf);
|
|
76
|
+
default:
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function ownAssertion(step) {
|
|
81
|
+
if (step.kind === "assert")
|
|
82
|
+
return describeCheck({ kind: "page", assertion: step.check });
|
|
83
|
+
if (step.kind === "handback")
|
|
84
|
+
return describeCheck({ kind: "page", assertion: step.resume });
|
|
85
|
+
return "expect" in step ? describeCheck({ kind: "page", assertion: step.expect }) : undefined;
|
|
86
|
+
}
|
|
87
|
+
function evidenceLine(e) {
|
|
88
|
+
const change = e.before !== undefined || e.after !== undefined ? ` '${e.before ?? ""}' → '${e.after ?? ""}'` : "";
|
|
89
|
+
const where = e.file === undefined ? "" : ` (${e.file}${e.line === undefined ? "" : `:${e.line}`})`;
|
|
90
|
+
return `${e.kind}${change}${where}`;
|
|
91
|
+
}
|
|
92
|
+
function buildProposal(ctx, journeyId) {
|
|
93
|
+
const p = ctx.proposal;
|
|
94
|
+
return {
|
|
95
|
+
proposalId: p.proposalId,
|
|
96
|
+
baseHash: p.baseHash,
|
|
97
|
+
proposedHash: p.proposedHash,
|
|
98
|
+
createdAt: p.createdAt,
|
|
99
|
+
stale: ctx.stale,
|
|
100
|
+
...(ctx.problem === undefined ? {} : { problem: ctx.problem }),
|
|
101
|
+
changes: { ...(p.changes.range === undefined ? {} : { range: p.changes.range }), ...(p.changes.baseSha === undefined ? {} : { baseSha: p.changes.baseSha }), ...(p.changes.headSha === undefined ? {} : { headSha: p.changes.headSha }), notes: [...p.changes.notes] },
|
|
102
|
+
steps: p.steps.map((s) => ({
|
|
103
|
+
number: s.index + 1,
|
|
104
|
+
before: describeStep(s.before),
|
|
105
|
+
after: describeStep(s.after),
|
|
106
|
+
hypothesis: s.justification.hypothesis + (s.justification.anchorNotInChange === true ? " (the new anchor is not named by any change evidence)" : ""),
|
|
107
|
+
evidence: s.justification.evidence.map(evidenceLine),
|
|
108
|
+
screenshots: [...(s.screenshots ?? [])],
|
|
109
|
+
})),
|
|
110
|
+
attempts: { total: p.attempts.length, rejected: p.attempts.filter((a) => a.result === "rejected").length },
|
|
111
|
+
acceptCommand: `jevitate journey promote ${journeyId} --proposal ${p.proposalId} --reviewed-hash ${p.proposedHash}`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function buildSteps(journey) {
|
|
115
|
+
return flatJourneySteps(journey).map(({ index, recorded }) => {
|
|
116
|
+
const step = recorded.step;
|
|
117
|
+
const target = reviewTarget(targetOf(step));
|
|
118
|
+
const assertion = ownAssertion(step);
|
|
119
|
+
return {
|
|
120
|
+
number: index + 1,
|
|
121
|
+
kind: step.kind,
|
|
122
|
+
action: describeStep(sanitized(step)),
|
|
123
|
+
...(target === undefined ? {} : { target }),
|
|
124
|
+
...(recorded.objective === undefined || recorded.objective.trim() === "" ? {} : { objective: recorded.objective }),
|
|
125
|
+
...(recorded.expectedResult === undefined || recorded.expectedResult.trim() === "" ? {} : { expectedResult: recorded.expectedResult }),
|
|
126
|
+
...(assertion === undefined ? {} : { assertion }),
|
|
127
|
+
params: [...new Set(paramsOf(step))],
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const REQUEST_LINE = /^(\S+)\s+(\S+)/;
|
|
132
|
+
function buildWriteRequests(journey) {
|
|
133
|
+
const classify = writeClassifier();
|
|
134
|
+
const out = [];
|
|
135
|
+
const seen = new Set();
|
|
136
|
+
const add = (w) => {
|
|
137
|
+
const key = `${w.source}|${w.step ?? ""}|${w.method.toUpperCase()}|${w.endpoint}`;
|
|
138
|
+
if (seen.has(key))
|
|
139
|
+
return;
|
|
140
|
+
seen.add(key);
|
|
141
|
+
out.push(w);
|
|
142
|
+
};
|
|
143
|
+
for (const w of journeyWriteSteps(journey)) {
|
|
144
|
+
for (const line of w.requests) {
|
|
145
|
+
const m = REQUEST_LINE.exec(line.trim());
|
|
146
|
+
if (m !== null)
|
|
147
|
+
add({ method: m[1].toUpperCase(), endpoint: m[2], source: "recorded", step: w.step });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
flatJourneySteps(journey).forEach(({ index, recorded }) => {
|
|
151
|
+
for (const check of recorded.expectRequests ?? []) {
|
|
152
|
+
if (classify({ method: check.method, path: check.pathGlob }))
|
|
153
|
+
add({ method: check.method.toUpperCase(), endpoint: check.pathGlob, source: "expect-request", step: index + 1 });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
for (const check of journeyEndState(journey)) {
|
|
157
|
+
if (check.kind !== "requestMade" && check.kind !== "responseStatus")
|
|
158
|
+
continue;
|
|
159
|
+
if (classify({ method: check.method, path: check.pathGlob }))
|
|
160
|
+
add({ method: check.method.toUpperCase(), endpoint: check.pathGlob, source: "end-state" });
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
/** Every click (a `forEach` child's too, under its parent's number) whose control a safety rule matches. */
|
|
165
|
+
function buildRiskyControls(journey, safety) {
|
|
166
|
+
const policy = new SafetyPolicy({
|
|
167
|
+
...(safety?.deny === undefined ? {} : { deny: safety.deny }),
|
|
168
|
+
...(safety?.paid === undefined ? {} : { paid: safety.paid }),
|
|
169
|
+
...(safety?.allowControl === undefined ? {} : { allowControl: safety.allowControl }),
|
|
170
|
+
});
|
|
171
|
+
const out = [];
|
|
172
|
+
const visit = (step, number) => {
|
|
173
|
+
if (step.kind === "forEach") {
|
|
174
|
+
for (const child of step.steps)
|
|
175
|
+
visit(child, number);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (step.kind !== "click")
|
|
179
|
+
return;
|
|
180
|
+
const name = controlName(step.target);
|
|
181
|
+
// A nameless control cannot be matched against a name rule: the sheet lists only named ones.
|
|
182
|
+
if (name === "")
|
|
183
|
+
return;
|
|
184
|
+
const control = { name, role: step.target.role ?? "", descriptor: step.target };
|
|
185
|
+
const role = step.target.role === undefined ? {} : { role: step.target.role };
|
|
186
|
+
const refused = policy.refuses(control);
|
|
187
|
+
if (refused !== null) {
|
|
188
|
+
out.push({ step: number, control: name, ...role, risk: refused.risk, ruleId: refused.refusal.ruleId });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const waiver = policy.waiver(control);
|
|
192
|
+
if (waiver !== null)
|
|
193
|
+
out.push({ step: number, control: name, ...role, risk: policy.riskOf(control) ?? "paid", ruleId: waiver.ruleId, waivedBy: waiver.regex });
|
|
194
|
+
};
|
|
195
|
+
for (const { index, recorded } of flatJourneySteps(journey))
|
|
196
|
+
visit(recorded.step, index + 1);
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
function originOf(url, base) {
|
|
200
|
+
try {
|
|
201
|
+
const u = new URL(url, base);
|
|
202
|
+
return u.protocol === "http:" || u.protocol === "https:" ? u.origin : null;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function buildOrigins(journey) {
|
|
209
|
+
const site = journey.recording.site;
|
|
210
|
+
const origins = new Set();
|
|
211
|
+
const add = (url) => {
|
|
212
|
+
const o = originOf(url, site);
|
|
213
|
+
if (o !== null)
|
|
214
|
+
origins.add(o);
|
|
215
|
+
};
|
|
216
|
+
add(site);
|
|
217
|
+
for (const page of journey.recording.pages) {
|
|
218
|
+
add(page.url);
|
|
219
|
+
for (const { step, delta } of page.steps) {
|
|
220
|
+
if (step.kind === "navigate")
|
|
221
|
+
add(step.url);
|
|
222
|
+
for (const line of delta?.requests ?? []) {
|
|
223
|
+
const m = REQUEST_LINE.exec(line.trim());
|
|
224
|
+
if (m !== null && /^https?:\/\//i.test(m[2]))
|
|
225
|
+
add(m[2]);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return [...origins];
|
|
230
|
+
}
|
|
231
|
+
function buildChange(journey, hash, snapshot) {
|
|
232
|
+
const approval = journey.metadata.approval;
|
|
233
|
+
if (snapshot === null || snapshot === undefined) {
|
|
234
|
+
if (approval !== undefined) {
|
|
235
|
+
return { kind: "snapshot-missing", approvedHash: approval.contentHash, approvedAt: approval.at, changed: approval.contentHash !== hash };
|
|
236
|
+
}
|
|
237
|
+
return journey.metadata.promoted ? { kind: "no-record" } : { kind: "first-approval" };
|
|
238
|
+
}
|
|
239
|
+
const approvedHash = journeyReviewHash(snapshot);
|
|
240
|
+
const approvedAt = approval?.at ?? snapshot.metadata.approval?.at;
|
|
241
|
+
return {
|
|
242
|
+
kind: "diff",
|
|
243
|
+
approvedHash,
|
|
244
|
+
...(approvedAt === undefined ? {} : { approvedAt }),
|
|
245
|
+
changed: approvedHash !== hash,
|
|
246
|
+
steps: multisetDiff(stepLines(snapshot), stepLines(journey)),
|
|
247
|
+
assertions: multisetDiff(assertionLines(snapshot), assertionLines(journey)),
|
|
248
|
+
sideEffects: multisetDiff(sideEffectLines(snapshot), sideEffectLines(journey)),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/** Step lines for a diff: the action and its check, without the number (an inserted step shifts every number). */
|
|
252
|
+
function stepLines(j) {
|
|
253
|
+
return buildSteps(j).map((s) => `${s.action}${s.assertion === undefined ? "" : ` — expect ${s.assertion}`}`);
|
|
254
|
+
}
|
|
255
|
+
function assertionLines(j) {
|
|
256
|
+
return [
|
|
257
|
+
...journeyEndState(j).map((c) => `end state: ${describeCheck(c)}`),
|
|
258
|
+
...journeyAssertions(j)
|
|
259
|
+
.filter((s) => s.where === "step-request")
|
|
260
|
+
.map((s) => `step request: ${describeCheck(s.check)}`),
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
function sideEffectLines(j) {
|
|
264
|
+
return [
|
|
265
|
+
...[...new Set(buildWriteRequests(j).map((w) => `write ${w.method} ${w.endpoint}`))],
|
|
266
|
+
...buildRiskyControls(j, undefined).map((c) => `control "${c.control}" [${c.ruleId}]`),
|
|
267
|
+
...buildOrigins(j).map((o) => `origin ${o}`),
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
function multisetDiff(before, after) {
|
|
271
|
+
const left = new Map();
|
|
272
|
+
for (const l of before)
|
|
273
|
+
left.set(l, (left.get(l) ?? 0) + 1);
|
|
274
|
+
const added = [];
|
|
275
|
+
for (const l of after) {
|
|
276
|
+
const n = left.get(l) ?? 0;
|
|
277
|
+
if (n > 0)
|
|
278
|
+
left.set(l, n - 1);
|
|
279
|
+
else
|
|
280
|
+
added.push(l);
|
|
281
|
+
}
|
|
282
|
+
const removed = [];
|
|
283
|
+
for (const l of before) {
|
|
284
|
+
const n = left.get(l) ?? 0;
|
|
285
|
+
if (n > 0) {
|
|
286
|
+
removed.push(l);
|
|
287
|
+
left.set(l, n - 1);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return { added, removed };
|
|
291
|
+
}
|
|
292
|
+
function buildVerify(journey, hash, rec) {
|
|
293
|
+
if (rec === null || rec === undefined) {
|
|
294
|
+
return { status: "not-verified", hint: `run \`jevitate journey verify ${journey.metadata.id} --mutate\` to prove each assertion can fail` };
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
status: "recorded",
|
|
298
|
+
verdict: rec.verdict,
|
|
299
|
+
at: rec.at,
|
|
300
|
+
stale: rec.contentHash !== hash,
|
|
301
|
+
...(rec.reason === undefined ? {} : { reason: rec.reason }),
|
|
302
|
+
summary: { ...rec.summary },
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/** #432: the review sheet of one Journey — pure. */
|
|
306
|
+
export function buildJourneyReview(journey, ctx = {}) {
|
|
307
|
+
const m = journey.metadata;
|
|
308
|
+
const hash = journeyReviewHash(journey);
|
|
309
|
+
const steps = buildSteps(journey);
|
|
310
|
+
const flat = flatJourneySteps(journey);
|
|
311
|
+
const missingIntent = [];
|
|
312
|
+
const annotate = `draft it with \`jevitate journey annotate ${m.id}\``;
|
|
313
|
+
if ((m.goal ?? "").trim() === "")
|
|
314
|
+
missingIntent.push(`no goal — ${annotate}`);
|
|
315
|
+
if ((m.successCriteria ?? []).length === 0)
|
|
316
|
+
missingIntent.push(`no success criteria — ${annotate}`);
|
|
317
|
+
const withoutObjective = steps.filter((s) => s.objective === undefined).length;
|
|
318
|
+
if (withoutObjective > 0)
|
|
319
|
+
missingIntent.push(`${withoutObjective} of ${steps.length} step(s) have no objective — ${annotate}`);
|
|
320
|
+
const secret = new Set(secretParamNames(journey));
|
|
321
|
+
const described = new Map((m.parameters ?? []).map((p) => [p.name, p.description]));
|
|
322
|
+
const paramNames = [...new Set([...journeyRunParams(journey), ...(m.parameters ?? []).map((p) => p.name)])];
|
|
323
|
+
const findings = lintJourney(journey);
|
|
324
|
+
const stepAssertions = journeyAssertions(journey).flatMap((site) => {
|
|
325
|
+
if (site.where === "end-state")
|
|
326
|
+
return [];
|
|
327
|
+
if (site.where === "step-request")
|
|
328
|
+
return [{ step: site.step, check: describeCheck(site.check), weak: false }];
|
|
329
|
+
const step = flat[site.step - 1]?.recorded.step;
|
|
330
|
+
const weak = site.check.assertion.kind === "visible" || (site.field === "expect" && step !== undefined && (isOwnTargetVisible(step) || isNoClaimExpect(step)));
|
|
331
|
+
return [{ step: site.step, check: describeCheck(site.check), weak }];
|
|
332
|
+
});
|
|
333
|
+
return {
|
|
334
|
+
id: m.id,
|
|
335
|
+
name: m.name,
|
|
336
|
+
promoted: m.promoted,
|
|
337
|
+
summary: {
|
|
338
|
+
...(m.description === undefined ? {} : { description: m.description }),
|
|
339
|
+
...(m.goal === undefined ? {} : { goal: m.goal }),
|
|
340
|
+
...(m.persona === undefined ? {} : { persona: m.persona }),
|
|
341
|
+
...(m.role === undefined ? {} : { role: m.role }),
|
|
342
|
+
requiresAuth: m.requiresAuth === true,
|
|
343
|
+
preconditions: (m.preconditions ?? []).map((p) => p.description),
|
|
344
|
+
successCriteria: (m.successCriteria ?? []).map((c) => ({
|
|
345
|
+
description: c.description,
|
|
346
|
+
...(c.check === undefined ? {} : { check: describeCheck({ kind: "page", assertion: c.check }) }),
|
|
347
|
+
})),
|
|
348
|
+
missingIntent,
|
|
349
|
+
},
|
|
350
|
+
steps,
|
|
351
|
+
sideEffects: { writeRequests: buildWriteRequests(journey), riskyControls: buildRiskyControls(journey, ctx.safety), origins: buildOrigins(journey) },
|
|
352
|
+
inputs: {
|
|
353
|
+
params: paramNames.map((name) => {
|
|
354
|
+
const description = described.get(name);
|
|
355
|
+
return { name, ...(description === undefined ? {} : { description }), secret: secret.has(name) };
|
|
356
|
+
}),
|
|
357
|
+
secrets: (m.secretRefs ?? []).map((r) => ({ field: r.field, manager: r.manager, origin: r.origin })),
|
|
358
|
+
},
|
|
359
|
+
proof: {
|
|
360
|
+
endState: journeyEndState(journey).map(describeCheck),
|
|
361
|
+
stepAssertions,
|
|
362
|
+
lint: {
|
|
363
|
+
errors: findings.filter((f) => f.level === "error").length,
|
|
364
|
+
warnings: findings.filter((f) => f.level === "warning").length,
|
|
365
|
+
findings: findings.map((f) => ({ rule: f.rule, level: f.level, ...(f.step === undefined ? {} : { step: f.step }), message: f.message })),
|
|
366
|
+
},
|
|
367
|
+
...(m.acceptedWeak === undefined ? {} : { acceptedWeak: { reason: m.acceptedWeak.reason, rules: [...m.acceptedWeak.rules] } }),
|
|
368
|
+
verify: buildVerify(journey, hash, ctx.lastVerify),
|
|
369
|
+
},
|
|
370
|
+
changeSinceApproval: buildChange(journey, hash, ctx.approvedSnapshot),
|
|
371
|
+
...(m.approval === undefined
|
|
372
|
+
? {}
|
|
373
|
+
: {
|
|
374
|
+
approval: {
|
|
375
|
+
contentHash: m.approval.contentHash,
|
|
376
|
+
at: m.approval.at,
|
|
377
|
+
...(m.approval.provenance === undefined ? {} : { provenance: copyProvenance(m.approval.provenance) }),
|
|
378
|
+
...(m.approval.acceptedWeak === undefined
|
|
379
|
+
? {}
|
|
380
|
+
: { acceptedWeak: { reason: m.approval.acceptedWeak.reason, rules: [...m.approval.acceptedWeak.rules], ...provenanceField(m.approval.acceptedWeak.provenance) } }),
|
|
381
|
+
...(m.approval.waivers === undefined ? {} : { waivers: m.approval.waivers.map((w) => ({ kind: w.kind, reason: w.reason, items: [...w.items], ...provenanceField(w.provenance) })) }),
|
|
382
|
+
...(m.approval.acceptedFindings === undefined
|
|
383
|
+
? {}
|
|
384
|
+
: { acceptedFindings: { reason: m.approval.acceptedFindings.reason, findings: [...m.approval.acceptedFindings.findings], ...provenanceField(m.approval.acceptedFindings.provenance) } }),
|
|
385
|
+
},
|
|
386
|
+
}),
|
|
387
|
+
...(ctx.catalog === undefined ? {} : { catalog: ctx.catalog.links, findings: [...ctx.catalog.findings] }),
|
|
388
|
+
...(ctx.proposal === undefined ? {} : { proposal: buildProposal(ctx.proposal, m.id) }),
|
|
389
|
+
contentHash: hash,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
/** #437: a copy of an approval's provenance (the sheet never shares the Journey's objects). */
|
|
393
|
+
function copyProvenance(p) {
|
|
394
|
+
return { channel: p.channel, agentSignals: [...p.agentSignals], ...(p.user === undefined ? {} : { user: p.user }), ...(p.reason === undefined ? {} : { reason: p.reason }) };
|
|
395
|
+
}
|
|
396
|
+
function provenanceField(p) {
|
|
397
|
+
return p === undefined ? {} : { provenance: copyProvenance(p) };
|
|
398
|
+
}
|
|
399
|
+
function renderSheet(r, style) {
|
|
400
|
+
const md = style === "markdown";
|
|
401
|
+
const code = (s) => (md ? `\`${s.replace(/`/g, "'")}\`` : s);
|
|
402
|
+
const h1 = (s) => (md ? `# ${s}` : `${s}\n${"=".repeat(Math.min(s.length, 80))}`);
|
|
403
|
+
const h2 = (s) => (md ? `## ${s}` : `${s.toUpperCase()}`);
|
|
404
|
+
const h3 = (s) => (md ? `### ${s}` : `${s}:`);
|
|
405
|
+
const em = (s) => (md ? `_${s}_` : s);
|
|
406
|
+
const li = (s, depth = 0) => `${" ".repeat(md ? depth : depth + 1)}- ${s}`;
|
|
407
|
+
const out = [];
|
|
408
|
+
const section = (...lines) => {
|
|
409
|
+
out.push(...lines, "");
|
|
410
|
+
};
|
|
411
|
+
const list = (items, none) => (items.length === 0 ? [li(em(none))] : items.map((i) => li(i)));
|
|
412
|
+
section(h1(`Journey review: ${r.name} (${r.id})`), "", `Content hash: ${code(r.contentHash)}`, `Status: ${r.promoted ? "promoted" : "not promoted"}${r.approval === undefined ? "" : ` · last approved ${r.approval.at} — ${describeProvenance(r.approval.provenance)}`}`);
|
|
413
|
+
const s = r.summary;
|
|
414
|
+
section(h2("Summary"), "", ...(s.description === undefined ? [] : [li(`Description: ${s.description}`)]), li(`Goal: ${s.goal ?? em("none")}`), ...(s.persona === undefined ? [] : [li(`Persona: ${s.persona}`)]), ...(s.role === undefined ? [] : [li(`Runs as: ${s.role}`)]), ...(s.requiresAuth ? [li("Needs an authenticated session")] : []), ...(s.preconditions.length === 0 ? [] : [li("Preconditions:"), ...s.preconditions.map((p) => li(p, 1))]), li("Success criteria:"), ...(s.successCriteria.length === 0
|
|
415
|
+
? [li(em("none"), 1)]
|
|
416
|
+
: s.successCriteria.map((c) => li(`${c.description}${c.check === undefined ? "" : ` (checked by ${code(c.check)})`}`, 1))), ...(s.missingIntent.length === 0 ? [] : [li("Missing intent:"), ...s.missingIntent.map((w) => li(`⚠ ${w}`, 1))]));
|
|
417
|
+
const stepLinesOut = [];
|
|
418
|
+
for (const st of r.steps) {
|
|
419
|
+
const target = st.target === undefined ? "" : ` — target: ${[st.target.role, st.target.name === undefined ? undefined : `"${st.target.name}"`].filter((x) => x !== undefined).join(" ")}`;
|
|
420
|
+
stepLinesOut.push(`${md ? "" : " "}${st.number}. ${md ? `**${st.action}**` : st.action}${target}`);
|
|
421
|
+
const sub = (t) => {
|
|
422
|
+
stepLinesOut.push(`${md ? " " : " "}- ${t}`);
|
|
423
|
+
};
|
|
424
|
+
sub(`Objective: ${st.objective ?? em("none")}`);
|
|
425
|
+
if (st.expectedResult !== undefined)
|
|
426
|
+
sub(`Expected result: ${st.expectedResult}`);
|
|
427
|
+
if (st.assertion !== undefined)
|
|
428
|
+
sub(`Checks: ${code(st.assertion)}`);
|
|
429
|
+
if (st.params.length > 0)
|
|
430
|
+
sub(`Params: ${st.params.map(code).join(", ")}`);
|
|
431
|
+
}
|
|
432
|
+
section(h2("Steps"), "", ...(stepLinesOut.length === 0 ? [li(em("no steps"))] : stepLinesOut));
|
|
433
|
+
const fx = r.sideEffects;
|
|
434
|
+
section(h2("Side effects"), "", h3("Write requests"), ...list(fx.writeRequests.map((w) => `${code(`${w.method} ${w.endpoint}`)} — ${w.source === "recorded" ? `recorded at step ${w.step}` : w.source === "expect-request" ? `expected at step ${w.step}` : "end-state check"}`), "none expected"), "", h3("Controls matching safety rules"), ...list(fx.riskyControls.map((c) => `step ${c.step}: ${c.role === undefined ? "" : `${c.role} `}"${c.control}" — ${c.risk} [${code(c.ruleId)}]${c.waivedBy === undefined ? "" : ` (exempted by allowControl ${code(c.waivedBy)})`}`), "none"), "", h3("Origins"), ...list(fx.origins.map(code), "none"));
|
|
435
|
+
section(h2("Inputs"), "", h3("Parameters"), ...list(r.inputs.params.map((p) => `${code(p.name)}${p.secret ? " (secret)" : ""}${p.description === undefined ? "" : ` — ${p.description}`}`), "none"), "", h3("Secret references"), ...list(r.inputs.secrets.map((x) => `${code(x.field)} from ${x.manager} on ${x.origin}`), "none"));
|
|
436
|
+
const p = r.proof;
|
|
437
|
+
const verify = p.verify.status === "not-verified"
|
|
438
|
+
? `not verified — ${p.verify.hint}`
|
|
439
|
+
: `${p.verify.verdict} (${p.verify.at})${p.verify.stale ? " — STALE: the Journey changed after this proof ran" : ""}${p.verify.reason === undefined ? "" : ` — ${p.verify.reason}`}: ${Object.entries(p.verify.summary)
|
|
440
|
+
.map(([k, v]) => `${v} ${k}`)
|
|
441
|
+
.join(", ")}`;
|
|
442
|
+
section(h2("Proof"), "", h3("End-state checks"), ...list(p.endState.map(code), "none"), "", h3("Per-step assertions"), ...list(p.stepAssertions.map((a) => `step ${a.step}: ${code(a.check)}${a.weak ? " (weak: proves nothing about the step's effect)" : ""}`), "none"), "", h3(`Lint: ${p.lint.errors} error(s), ${p.lint.warnings} warning(s)`), ...list(p.lint.findings.map((f) => `${f.level} ${f.rule}${f.step === undefined ? "" : ` (step ${f.step})`}: ${f.message}`), "no findings"), ...(p.acceptedWeak === undefined ? [] : ["", `Accepted weak: "${p.acceptedWeak.reason}" (waived: ${p.acceptedWeak.rules.join(", ")})`]), "", `Mutation proof: ${verify}`);
|
|
443
|
+
const c = r.changeSinceApproval;
|
|
444
|
+
const change = [];
|
|
445
|
+
if (c.kind === "first-approval")
|
|
446
|
+
change.push(em("first approval — nothing approved before"));
|
|
447
|
+
else if (c.kind === "no-record")
|
|
448
|
+
change.push(em("promoted before approvals were recorded — no approved version to compare with"));
|
|
449
|
+
else if (c.kind === "snapshot-missing") {
|
|
450
|
+
change.push(`Approved${c.approvedAt === undefined ? "" : ` ${c.approvedAt}`} as ${code(c.approvedHash)}; its snapshot is missing — ${c.changed ? "the Journey CHANGED since" : "unchanged since"}`);
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
change.push(`Approved${c.approvedAt === undefined ? "" : ` ${c.approvedAt}`} as ${code(c.approvedHash)} — ${c.changed ? "CHANGED since" : "unchanged since"}`);
|
|
454
|
+
for (const [label, d] of [
|
|
455
|
+
["Steps", c.steps],
|
|
456
|
+
["Assertions", c.assertions],
|
|
457
|
+
["Side effects", c.sideEffects],
|
|
458
|
+
]) {
|
|
459
|
+
if (d.added.length === 0 && d.removed.length === 0)
|
|
460
|
+
continue;
|
|
461
|
+
change.push("", h3(label), ...d.removed.map((l) => li(`− ${l}`)), ...d.added.map((l) => li(`+ ${l}`)));
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
section(h2("Change since last approval"), "", ...change);
|
|
465
|
+
// #433: the catalog links (job, persona), then the pre-approval findings — before the approval line.
|
|
466
|
+
if (r.catalog !== undefined) {
|
|
467
|
+
const k = r.catalog;
|
|
468
|
+
const status = (st) => (st === "stale" ? "STALE — needs re-review" : st === "unknown" ? "UNKNOWN — not declared in the catalog" : st);
|
|
469
|
+
section(h2("Catalog"), "", ...(k.linked
|
|
470
|
+
? [
|
|
471
|
+
li(k.job === undefined ? `Job: ${em("none")}` : `Job: ${code(k.job.id)} (${status(k.job.status)})${k.job.story === undefined ? "" : ` — ${k.job.story}`}`),
|
|
472
|
+
li(k.persona === undefined ? `Persona: ${em("none")}` : `Persona: ${code(k.persona.id)} (${status(k.persona.status)})`),
|
|
473
|
+
...(k.unvetted.length === 0 ? [] : [li(`Unvetted: ${k.unvetted.join(", ")} — promote needs them approved, or --accept-unvetted "<reason>"`)]),
|
|
474
|
+
...(k.needsReReview.length === 0 ? [] : [li(`NEEDS RE-REVIEW: ${k.needsReReview.join("; ")}`)]),
|
|
475
|
+
]
|
|
476
|
+
: [li(em("not linked to a job/persona"))]));
|
|
477
|
+
}
|
|
478
|
+
if (r.findings !== undefined)
|
|
479
|
+
section(...renderFindings(r.findings, style));
|
|
480
|
+
if (r.approval?.waivers !== undefined || r.approval?.acceptedFindings !== undefined) {
|
|
481
|
+
section(h3("Recorded with the last approval"), ...(r.approval.waivers ?? []).map((w) => li(`waived (${w.kind}): "${w.reason}" — ${w.items.join(", ")}`)), ...(r.approval.acceptedFindings === undefined ? [] : [li(`acknowledged findings: "${r.approval.acceptedFindings.reason}" — ${r.approval.acceptedFindings.findings.join(", ")}`)]));
|
|
482
|
+
}
|
|
483
|
+
if (r.proposal !== undefined) {
|
|
484
|
+
const pr = r.proposal;
|
|
485
|
+
const scope = pr.changes.range ?? (pr.changes.notes.length > 0 ? "change notes" : "no change context");
|
|
486
|
+
section(h2("Proposed revision (self-heal)"), "", li(`Proposal ${code(pr.proposalId)} from ${pr.createdAt}, made against ${code(pr.baseHash)}; accepting it makes the content hash ${code(pr.proposedHash)}`), li(`Change context: ${scope}${pr.changes.baseSha === undefined ? "" : ` (${pr.changes.baseSha.slice(0, 8)}..${(pr.changes.headSha ?? "").slice(0, 8)})`}`), ...pr.changes.notes.map((n) => li(`note: ${n}`, 1)), li(`Heal attempts: ${pr.attempts.total} (${pr.attempts.rejected} rejected)`), ...(pr.stale ? [li(`STALE — the Journey changed after this proposal was made; it cannot be accepted. Re-run: jevitate journey run ${r.id} --self-heal`)] : []), ...(pr.problem !== undefined && !pr.stale ? [li(`REFUSED — ${pr.problem}`)] : []), "", ...pr.steps.flatMap((st) => [
|
|
487
|
+
`${md ? "" : " "}${st.number}. ${md ? `**${st.hypothesis}**` : st.hypothesis}`,
|
|
488
|
+
`${md ? " " : " "}- Before: ${code(st.before)}`,
|
|
489
|
+
`${md ? " " : " "}- After: ${code(st.after)}`,
|
|
490
|
+
...st.evidence.map((e) => `${md ? " " : " "}- Evidence: ${e}`),
|
|
491
|
+
...st.screenshots.map((sh) => `${md ? " " : " "}- Screenshot: ${sh}`),
|
|
492
|
+
]), "", pr.stale || pr.problem !== undefined ? em("not acceptable as it stands") : `Accept exactly this revision: ${code(pr.acceptCommand)}`, `Reject it: ${code(`jevitate journey promote ${r.id} --reject-proposal ${pr.proposalId} --reason "<why>"`)}`);
|
|
493
|
+
}
|
|
494
|
+
section(h2("Content hash"), "", `Content hash: ${code(r.contentHash)}`, "", `Approve exactly this version: ${code(`jevitate journey promote ${r.id} --reviewed-hash ${r.contentHash}`)}`);
|
|
495
|
+
return `${out.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd()}\n`;
|
|
496
|
+
}
|
|
497
|
+
/** #432: the review sheet as Markdown (`journey review --markdown`). */
|
|
498
|
+
export function renderReviewMarkdown(review) {
|
|
499
|
+
return renderSheet(review, "markdown");
|
|
500
|
+
}
|
|
501
|
+
/** #432: the review sheet as plain text (`journey review`, and `journey promote` in human mode). */
|
|
502
|
+
export function renderReviewText(review) {
|
|
503
|
+
return renderSheet(review, "text");
|
|
504
|
+
}
|
|
505
|
+
/** #432: a review sheet file's content hash refused (none, or two different ones). */
|
|
506
|
+
export class ReviewSheetError extends Error {
|
|
507
|
+
code = "E_JOURNEY_REVIEW_ARGS";
|
|
508
|
+
}
|
|
509
|
+
const HASH_LINE = /Content hash:\s*`?([0-9a-f]{64})`?/gi;
|
|
510
|
+
/**
|
|
511
|
+
* #432: the content hash a review sheet file names — the `contentHash` of a JSON sheet
|
|
512
|
+
* (`journey review --json --out`, its envelope or the bare sheet), or the `Content hash:` line of a
|
|
513
|
+
* Markdown / text one. A sheet naming no hash, or two different ones, is refused.
|
|
514
|
+
*/
|
|
515
|
+
export function reviewSheetHash(text) {
|
|
516
|
+
const trimmed = text.trim();
|
|
517
|
+
if (trimmed.startsWith("{")) {
|
|
518
|
+
let parsed;
|
|
519
|
+
try {
|
|
520
|
+
parsed = JSON.parse(trimmed);
|
|
521
|
+
}
|
|
522
|
+
catch {
|
|
523
|
+
throw new ReviewSheetError("the review sheet is not valid JSON");
|
|
524
|
+
}
|
|
525
|
+
const o = parsed;
|
|
526
|
+
const hash = o.contentHash ?? o.data?.contentHash;
|
|
527
|
+
if (typeof hash !== "string" || !/^[0-9a-f]{64}$/.test(hash))
|
|
528
|
+
throw new ReviewSheetError("the review sheet names no contentHash");
|
|
529
|
+
return hash;
|
|
530
|
+
}
|
|
531
|
+
const hashes = new Set([...text.matchAll(HASH_LINE)].map((m) => m[1].toLowerCase()));
|
|
532
|
+
if (hashes.size === 0)
|
|
533
|
+
throw new ReviewSheetError("the review sheet names no content hash (a `Content hash:` line)");
|
|
534
|
+
if (hashes.size > 1)
|
|
535
|
+
throw new ReviewSheetError("the review sheet names more than one content hash");
|
|
536
|
+
return [...hashes][0];
|
|
537
|
+
}
|
|
538
|
+
//# sourceMappingURL=journey-review.js.map
|