@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,342 @@
|
|
|
1
|
+
import { lstat, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, relative, sep } from "node:path";
|
|
3
|
+
import { contentHash, clock } from "@jevitate/domain";
|
|
4
|
+
import { JourneyProposalSchema, RejectedProposalSchema, } from "@jevitate/journey";
|
|
5
|
+
import { assertRecordingProofUntouched, flattenRecording, healFloor, sanitizeStep } from "@jevitate/runtime";
|
|
6
|
+
import { journeyReviewHash } from "./journey-review.js";
|
|
7
|
+
import { journeyStepRisk } from "./journey-heal.js";
|
|
8
|
+
import { redactCredentialShapes, redactText, redactUrl } from "@jevitate/ai-core";
|
|
9
|
+
/**
|
|
10
|
+
* #453 — proposed Journey revisions, beside the journeys directory (`.proposals/`, committed with
|
|
11
|
+
* the PR like `.approved/`; a dot-folder, so never listed as a Journey). One PENDING proposal per
|
|
12
|
+
* Journey, keyed by the Journey id: `.proposals/<id>.json`; a newer one replaces it. A rejected one
|
|
13
|
+
* moves to `.proposals/<id>.rejected/<proposalId>.json` with who rejected it and why. A run never
|
|
14
|
+
* writes the stored Journey — only `journey promote --proposal` does.
|
|
15
|
+
*
|
|
16
|
+
* Nothing here can hold a secret: step values arrive through `sanitizeStep` (hidden), every string
|
|
17
|
+
* passes the redaction of the run's secret values, and the recording is the Journey's own. The free
|
|
18
|
+
* text (hypotheses, evidence, notes, rejection details, paths) is also scrubbed of credential-shaped
|
|
19
|
+
* values and sensitive URL parameters. It DOES hold control labels and change evidence — the files
|
|
20
|
+
* are committed (docs/journeys.md).
|
|
21
|
+
*
|
|
22
|
+
* #453 review: the store never follows a symlink — the `.proposals/` directories and files are
|
|
23
|
+
* `lstat`ed on read and write and a link is refused (`JourneyProposalInvalidError`); a write goes
|
|
24
|
+
* to a temporary file renamed into place (atomic).
|
|
25
|
+
*/
|
|
26
|
+
/** No pending proposal with that id (or none at all). Exit 64. */
|
|
27
|
+
export class JourneyProposalNotFoundError extends Error {
|
|
28
|
+
code = "E_JOURNEY_PROPOSAL_NOT_FOUND";
|
|
29
|
+
}
|
|
30
|
+
/** The proposal was made against a Journey that has changed since, or its proof fields were touched. Exit 64. */
|
|
31
|
+
export class JourneyProposalStaleError extends Error {
|
|
32
|
+
code = "E_JOURNEY_PROPOSAL_STALE";
|
|
33
|
+
}
|
|
34
|
+
/** A proposal that changes more than retargets (an assertion, a wait, a request check…) — refused. */
|
|
35
|
+
export class JourneyProposalProofError extends Error {
|
|
36
|
+
code = "E_JOURNEY_PROPOSAL_PROOF";
|
|
37
|
+
}
|
|
38
|
+
/** A proposal file that is not valid. */
|
|
39
|
+
export class JourneyProposalInvalidError extends Error {
|
|
40
|
+
code = "E_JOURNEY_PROPOSAL_INVALID";
|
|
41
|
+
}
|
|
42
|
+
const PROPOSAL_ID = /^[0-9a-f]{12}$/;
|
|
43
|
+
/** True for a well-formed proposal id (12 hex characters) — also what keeps `../x` out of every path. */
|
|
44
|
+
export function isProposalId(id) {
|
|
45
|
+
return PROPOSAL_ID.test(id);
|
|
46
|
+
}
|
|
47
|
+
function parts(id) {
|
|
48
|
+
const p = id.split("/");
|
|
49
|
+
return { dir: p.slice(0, -1), base: p[p.length - 1] ?? id };
|
|
50
|
+
}
|
|
51
|
+
export function proposalPath(journeysDir, journeyId) {
|
|
52
|
+
const { dir, base } = parts(journeyId);
|
|
53
|
+
return join(journeysDir, ".proposals", ...dir, `${base}.json`);
|
|
54
|
+
}
|
|
55
|
+
export function rejectedProposalPath(journeysDir, journeyId, proposalId) {
|
|
56
|
+
const { dir, base } = parts(journeyId);
|
|
57
|
+
return join(journeysDir, ".proposals", ...dir, `${base}.rejected`, `${proposalId}.json`);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Refuses (`JourneyProposalInvalidError`) when `.proposals/` or any directory or file on the way to
|
|
61
|
+
* `path` under it is a symbolic link — never followed, for a read or a write. Components that do not
|
|
62
|
+
* exist yet are fine (a write creates them).
|
|
63
|
+
*/
|
|
64
|
+
async function assertNoSymlink(path) {
|
|
65
|
+
const marker = `${sep}.proposals${sep}`;
|
|
66
|
+
const at = path.lastIndexOf(marker);
|
|
67
|
+
if (at < 0)
|
|
68
|
+
throw new JourneyProposalInvalidError(`${path} is not under a .proposals directory`);
|
|
69
|
+
const root = path.slice(0, at + marker.length - 1);
|
|
70
|
+
const parts = relative(root, path).split(sep).filter((p) => p !== "");
|
|
71
|
+
let cur = root;
|
|
72
|
+
for (const next of [null, ...parts]) {
|
|
73
|
+
if (next !== null)
|
|
74
|
+
cur = join(cur, next);
|
|
75
|
+
let st;
|
|
76
|
+
try {
|
|
77
|
+
st = await lstat(cur);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT")
|
|
81
|
+
return;
|
|
82
|
+
throw err;
|
|
83
|
+
}
|
|
84
|
+
if (st.isSymbolicLink())
|
|
85
|
+
throw new JourneyProposalInvalidError(`refusing to follow a symbolic link at ${cur} — proposals are never read or written through a link`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function readJson(path) {
|
|
89
|
+
await assertNoSymlink(path);
|
|
90
|
+
let raw;
|
|
91
|
+
try {
|
|
92
|
+
raw = await readFile(path, "utf8");
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT")
|
|
96
|
+
return null;
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(raw);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
throw new JourneyProposalInvalidError(`${path} is not valid JSON`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function writeJson(path, value) {
|
|
107
|
+
await assertNoSymlink(path);
|
|
108
|
+
await mkdir(dirname(path), { recursive: true });
|
|
109
|
+
await assertNoSymlink(path);
|
|
110
|
+
const tmp = `${path}.${process.pid}.${clock.monotonicMs().toString(36)}.tmp`;
|
|
111
|
+
await writeFile(tmp, `${JSON.stringify(value, null, 2)}\n`, { flag: "wx" });
|
|
112
|
+
try {
|
|
113
|
+
await rename(tmp, path);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
await rm(tmp, { force: true });
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function removeFile(path) {
|
|
121
|
+
await assertNoSymlink(path);
|
|
122
|
+
await rm(path, { force: true });
|
|
123
|
+
}
|
|
124
|
+
/** Deterministic JSON (keys sorted, undefined dropped): two steps are the same when these agree. */
|
|
125
|
+
function canonical(v) {
|
|
126
|
+
if (Array.isArray(v))
|
|
127
|
+
return `[${v.map(canonical).join(",")}]`;
|
|
128
|
+
if (v !== null && typeof v === "object") {
|
|
129
|
+
const o = v;
|
|
130
|
+
return `{${Object.keys(o)
|
|
131
|
+
.filter((k) => o[k] !== undefined)
|
|
132
|
+
.sort()
|
|
133
|
+
.map((k) => `${JSON.stringify(k)}:${canonical(o[k])}`)
|
|
134
|
+
.join(",")}}`;
|
|
135
|
+
}
|
|
136
|
+
return JSON.stringify(v) ?? "undefined";
|
|
137
|
+
}
|
|
138
|
+
function originOf(url) {
|
|
139
|
+
if (!/^[a-z][a-z0-9+.-]*:/i.test(url))
|
|
140
|
+
return null;
|
|
141
|
+
try {
|
|
142
|
+
return new URL(url).origin;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return "invalid";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* #453 review: re-runs the heal floor on every step a revision changes (on write, on read for review
|
|
150
|
+
* and on accept) — a proposal file edited by hand, or written by an older build, never lets
|
|
151
|
+
* `journey promote --proposal` store what the runner would not have healed:
|
|
152
|
+
* - the floor (`healFloor` with the built-in `SafetyPolicy` classification, `journeyStepRisk`) holds
|
|
153
|
+
* for the step before AND after: never a proof / write / risky-control step;
|
|
154
|
+
* - a retargeted click/fill expects only GET/HEAD/OPTIONS requests (part of the floor);
|
|
155
|
+
* - a retargeted navigate URL stays on the Journey's allowed origin (its recorded site).
|
|
156
|
+
*/
|
|
157
|
+
export function proposalFloorViolation(stored, proposed) {
|
|
158
|
+
const riskOf = journeyStepRisk();
|
|
159
|
+
const before = flattenRecording(stored.recording);
|
|
160
|
+
const after = flattenRecording(proposed);
|
|
161
|
+
let site = null;
|
|
162
|
+
try {
|
|
163
|
+
site = new URL(stored.recording.site).origin;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
site = null;
|
|
167
|
+
}
|
|
168
|
+
for (let i = 0; i < after.length; i++) {
|
|
169
|
+
const b = before[i];
|
|
170
|
+
const a = after[i];
|
|
171
|
+
if (b !== undefined && canonical(b.step) === canonical(a.step))
|
|
172
|
+
continue;
|
|
173
|
+
for (const [which, rec] of [["before", b?.recorded], ["after", a.recorded]]) {
|
|
174
|
+
if (rec === undefined)
|
|
175
|
+
return `step ${i + 1} is not in the stored Journey`;
|
|
176
|
+
const floor = healFloor(rec, riskOf);
|
|
177
|
+
if (floor.floor !== "healable")
|
|
178
|
+
return `step ${i + 1} (${which}): ${floor.reason}`;
|
|
179
|
+
}
|
|
180
|
+
if (a.step.kind === "navigate") {
|
|
181
|
+
const o = originOf(a.step.url);
|
|
182
|
+
if (o !== null && o !== site)
|
|
183
|
+
return `step ${i + 1}: the retargeted URL is on ${o}, not the Journey's origin ${site ?? "(none)"}`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
/** The Journey as it would be stored on accept: the stored one with the proposed recording. */
|
|
189
|
+
export function proposedJourney(stored, proposal) {
|
|
190
|
+
return { ...stored, recording: proposal.recording };
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Writes the proposal for `journeyId`, replacing a pending one. Refused (`JourneyProposalProofError`)
|
|
194
|
+
* when the revision changes anything but retargets (`assertRecordingProofUntouched`).
|
|
195
|
+
*/
|
|
196
|
+
export async function writeJourneyProposal(journeysDir, input) {
|
|
197
|
+
const violation = assertRecordingProofUntouched(input.base.recording, input.draft.recording);
|
|
198
|
+
if (violation !== null)
|
|
199
|
+
throw new JourneyProposalProofError(`refusing to store a proposal that touches the Journey's proof (${violation.code}): ${violation.detail}`);
|
|
200
|
+
const floor = proposalFloorViolation(input.base, input.draft.recording);
|
|
201
|
+
if (floor !== null)
|
|
202
|
+
throw new JourneyProposalProofError(`refusing to store a proposal the heal floor refuses: ${floor}`);
|
|
203
|
+
const baseHash = journeyReviewHash(input.base);
|
|
204
|
+
const secrets = [...(input.secrets ?? [])];
|
|
205
|
+
// Every string: the run's secret values. Free text (not the recording or a step): also
|
|
206
|
+
// credential-shaped values and sensitive URL parameters (#453 review).
|
|
207
|
+
const scrub = (v, free = true) => typeof v === "string"
|
|
208
|
+
? free
|
|
209
|
+
? redactUrl(redactCredentialShapes(secrets.length === 0 ? v : redactText(v, secrets)))
|
|
210
|
+
: secrets.length === 0
|
|
211
|
+
? v
|
|
212
|
+
: redactText(v, secrets)
|
|
213
|
+
: Array.isArray(v)
|
|
214
|
+
? v.map((x) => scrub(x, free))
|
|
215
|
+
: v !== null && typeof v === "object"
|
|
216
|
+
? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, scrub(x, free)]))
|
|
217
|
+
: v;
|
|
218
|
+
const screenshotOf = (n) => input.attempts.find((a) => a.n === n)?.observation?.screenshot;
|
|
219
|
+
const steps = input.draft.steps.map((s) => {
|
|
220
|
+
const shot = screenshotOf(s.attempt);
|
|
221
|
+
const before = s.evidence[0]?.before;
|
|
222
|
+
const after = s.evidence[0]?.after;
|
|
223
|
+
return {
|
|
224
|
+
index: s.index,
|
|
225
|
+
before: sanitizeStep(s.before),
|
|
226
|
+
after: sanitizeStep(s.after),
|
|
227
|
+
justification: {
|
|
228
|
+
hypothesis: s.hypothesis,
|
|
229
|
+
evidence: [...s.evidence],
|
|
230
|
+
...(s.anchorNotInChange === true ? { anchorNotInChange: true } : {}),
|
|
231
|
+
},
|
|
232
|
+
evidence: { ...(before === undefined ? {} : { before }), ...(after === undefined ? {} : { after }) },
|
|
233
|
+
...(shot === undefined ? {} : { screenshots: [shot] }),
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
const proposalId = contentHash({ baseHash, steps: steps.map((s) => ({ index: s.index, after: s.after })) }).slice(0, 12);
|
|
237
|
+
const proposed = proposedJourney(input.base, { recording: input.draft.recording });
|
|
238
|
+
const body = {
|
|
239
|
+
v: 1,
|
|
240
|
+
kind: "journey-proposal",
|
|
241
|
+
proposalId,
|
|
242
|
+
journeyId: input.journeyId,
|
|
243
|
+
baseHash,
|
|
244
|
+
proposedHash: journeyReviewHash(proposed),
|
|
245
|
+
createdAt: clock.nowIso(),
|
|
246
|
+
recording: input.draft.recording,
|
|
247
|
+
steps,
|
|
248
|
+
changes: {
|
|
249
|
+
...(input.changes.range === undefined ? {} : { range: input.changes.range }),
|
|
250
|
+
...(input.changes.baseSha === undefined ? {} : { baseSha: input.changes.baseSha }),
|
|
251
|
+
...(input.changes.headSha === undefined ? {} : { headSha: input.changes.headSha }),
|
|
252
|
+
notes: [...(input.changes.notes ?? [])],
|
|
253
|
+
},
|
|
254
|
+
attempts: input.attempts.map((a) => ({ ...a, candidate: a.candidate === null ? null : sanitizeStep(a.candidate) })),
|
|
255
|
+
run: input.runResultPath === undefined ? {} : { resultPath: input.runResultPath },
|
|
256
|
+
};
|
|
257
|
+
const plain = JSON.parse(JSON.stringify(body));
|
|
258
|
+
const strict = scrub(plain, false);
|
|
259
|
+
const free = scrub(plain, true);
|
|
260
|
+
const scrubbed = {
|
|
261
|
+
...free,
|
|
262
|
+
...Object.fromEntries(["v", "kind", "proposalId", "journeyId", "baseHash", "proposedHash", "createdAt", "recording"].map((k) => [k, strict[k]])),
|
|
263
|
+
steps: free.steps.map((st, i) => ({ ...st, before: strict.steps[i].before, after: strict.steps[i].after })),
|
|
264
|
+
changes: { ...free.changes, ...("baseSha" in strict.changes ? { baseSha: strict.changes.baseSha } : {}), ...("headSha" in strict.changes ? { headSha: strict.changes.headSha } : {}) },
|
|
265
|
+
attempts: free.attempts.map((a, i) => ({ ...a, candidate: strict.attempts[i].candidate })),
|
|
266
|
+
};
|
|
267
|
+
const parsed = JourneyProposalSchema.safeParse(scrubbed);
|
|
268
|
+
if (!parsed.success)
|
|
269
|
+
throw new JourneyProposalInvalidError(`the proposal does not satisfy its schema: ${parsed.error.issues[0]?.path.join(".") ?? ""} ${parsed.error.issues[0]?.message ?? "invalid"}`);
|
|
270
|
+
const path = proposalPath(journeysDir, input.journeyId);
|
|
271
|
+
const previous = await readJourneyProposal(journeysDir, input.journeyId).catch(() => null);
|
|
272
|
+
await writeJson(path, parsed.data);
|
|
273
|
+
return {
|
|
274
|
+
proposalId,
|
|
275
|
+
path,
|
|
276
|
+
...(previous !== null && previous.proposalId !== proposalId ? { supersededProposal: previous.proposalId } : {}),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
/** The pending proposal for a Journey, or null. An unreadable or invalid file is refused, never ignored. */
|
|
280
|
+
export async function readJourneyProposal(journeysDir, journeyId) {
|
|
281
|
+
const path = proposalPath(journeysDir, journeyId);
|
|
282
|
+
const raw = await readJson(path);
|
|
283
|
+
if (raw === null)
|
|
284
|
+
return null;
|
|
285
|
+
const parsed = JourneyProposalSchema.safeParse(raw);
|
|
286
|
+
if (!parsed.success)
|
|
287
|
+
throw new JourneyProposalInvalidError(`${path} is not a valid Journey proposal: ${parsed.error.issues[0]?.message ?? "invalid"}`);
|
|
288
|
+
return parsed.data;
|
|
289
|
+
}
|
|
290
|
+
/** The pending proposal named `proposalId` for the Journey; `JourneyProposalNotFoundError` when none matches. */
|
|
291
|
+
export async function requireJourneyProposal(journeysDir, journeyId, proposalId) {
|
|
292
|
+
const proposal = isProposalId(proposalId) ? await readJourneyProposal(journeysDir, journeyId) : null;
|
|
293
|
+
if (proposal === null || proposal.proposalId !== proposalId || proposal.journeyId !== journeyId) {
|
|
294
|
+
throw new JourneyProposalNotFoundError(`journey '${journeyId}' has no pending proposal '${proposalId}' — see: jevitate journey review ${journeyId}`);
|
|
295
|
+
}
|
|
296
|
+
return proposal;
|
|
297
|
+
}
|
|
298
|
+
/** True when the proposal was made against a different Journey than the stored one. */
|
|
299
|
+
export function isProposalStale(stored, proposal) {
|
|
300
|
+
return journeyReviewHash(stored) !== proposal.baseHash;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Re-checks a proposal against the stored Journey (on read for review and again on accept): stale
|
|
304
|
+
* when the Journey changed since, proof-touched when the file was edited to change anything but
|
|
305
|
+
* retargets, or when its hash no longer matches its recording.
|
|
306
|
+
*/
|
|
307
|
+
export function checkProposal(stored, proposal) {
|
|
308
|
+
if (isProposalStale(stored, proposal)) {
|
|
309
|
+
return new JourneyProposalStaleError(`proposal '${proposal.proposalId}' was made against journey '${stored.metadata.id}' ${proposal.baseHash.slice(0, 8)}, which has changed since (now ${journeyReviewHash(stored).slice(0, 8)}) — re-run the self-heal: jevitate journey run ${stored.metadata.id} --self-heal`);
|
|
310
|
+
}
|
|
311
|
+
const violation = assertRecordingProofUntouched(stored.recording, proposal.recording);
|
|
312
|
+
if (violation !== null)
|
|
313
|
+
return new JourneyProposalProofError(`proposal '${proposal.proposalId}' touches the Journey's proof (${violation.code}): ${violation.detail}`);
|
|
314
|
+
const floor = proposalFloorViolation(stored, proposal.recording);
|
|
315
|
+
if (floor !== null)
|
|
316
|
+
return new JourneyProposalProofError(`proposal '${proposal.proposalId}' changes a step the heal floor refuses: ${floor}`);
|
|
317
|
+
if (journeyReviewHash(proposedJourney(stored, proposal)) !== proposal.proposedHash) {
|
|
318
|
+
return new JourneyProposalProofError(`proposal '${proposal.proposalId}' does not match its recorded hash — the file was edited`);
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
/** Deletes the pending proposal (after it was accepted). */
|
|
323
|
+
export async function deleteJourneyProposal(journeysDir, journeyId) {
|
|
324
|
+
await removeFile(proposalPath(journeysDir, journeyId));
|
|
325
|
+
}
|
|
326
|
+
/** Moves the pending proposal to `.rejected/`, recording when, why and how it was rejected. */
|
|
327
|
+
export async function rejectJourneyProposal(journeysDir, journeyId, proposalId, rejection) {
|
|
328
|
+
const proposal = await requireJourneyProposal(journeysDir, journeyId, proposalId);
|
|
329
|
+
const record = RejectedProposalSchema.parse({
|
|
330
|
+
v: 1,
|
|
331
|
+
kind: "journey-proposal-rejected",
|
|
332
|
+
rejectedAt: clock.nowIso(),
|
|
333
|
+
reason: rejection.reason,
|
|
334
|
+
provenance: rejection.provenance,
|
|
335
|
+
proposal,
|
|
336
|
+
});
|
|
337
|
+
const path = rejectedProposalPath(journeysDir, journeyId, proposalId);
|
|
338
|
+
await writeJson(path, record);
|
|
339
|
+
await removeFile(proposalPath(journeysDir, journeyId));
|
|
340
|
+
return { path };
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=journey-proposal-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journey-proposal-store.js","sourceRoot":"","sources":["../src/journey-proposal-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type MissionOutcome } from "@jevitate/domain";
|
|
2
|
+
import type { Journey } from "@jevitate/journey";
|
|
3
|
+
import type { HealReport, JourneyRunResult } from "@jevitate/runtime";
|
|
4
|
+
/**
|
|
5
|
+
* #453: the `*.result.json` record of one Journey run — what `jevitate report`, the run index and a
|
|
6
|
+
* `check` suite read. Shared by `runJourneyProgrammatically` (a self-heal run persists it) and,
|
|
7
|
+
* once it switches over, `check`. Every string passes `redactSecretParams`.
|
|
8
|
+
*/
|
|
9
|
+
/** The proposal a `healed-pending-review` run wrote (a person reviews it; the stored Journey is untouched). */
|
|
10
|
+
export interface JourneyResultProposal {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly path: string;
|
|
13
|
+
/** The retargeted steps, 1-based, as the review prints them. */
|
|
14
|
+
readonly steps: readonly {
|
|
15
|
+
readonly number: number;
|
|
16
|
+
readonly before: string;
|
|
17
|
+
readonly after: string;
|
|
18
|
+
}[];
|
|
19
|
+
readonly reviewCommand?: string;
|
|
20
|
+
readonly acceptCommand?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface JourneyResultContext {
|
|
23
|
+
/** The Journey as stored (names the secret params; locates the failing step's page). */
|
|
24
|
+
readonly journey: Journey;
|
|
25
|
+
/** The run's params (secret ones are redacted from the record). */
|
|
26
|
+
readonly params: Readonly<Record<string, string>>;
|
|
27
|
+
readonly startedAt: string;
|
|
28
|
+
readonly engine?: unknown;
|
|
29
|
+
readonly suite?: unknown;
|
|
30
|
+
readonly targetBuild?: unknown;
|
|
31
|
+
readonly proposal?: JourneyResultProposal;
|
|
32
|
+
/** The run's heal report when the result does not already carry it. */
|
|
33
|
+
readonly heal?: HealReport;
|
|
34
|
+
}
|
|
35
|
+
export interface JourneyResultRecord {
|
|
36
|
+
readonly missionOutcome: MissionOutcome;
|
|
37
|
+
readonly exitCode: number;
|
|
38
|
+
readonly result: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
export declare function journeyResultRecord(r: JourneyRunResult, ctx: JourneyResultContext): JourneyResultRecord;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { JOURNEY_MISSION_OUTCOME, journeyExitCode } from "@jevitate/domain";
|
|
2
|
+
import { redactSecretParams } from "./journey-api.js";
|
|
3
|
+
import { journeyStepUrl } from "./check-plan.js";
|
|
4
|
+
export function journeyResultRecord(r, ctx) {
|
|
5
|
+
const at = r.outcome === "quarantined" || r.outcome === "heal-exhausted" ? r.at : undefined;
|
|
6
|
+
const url = journeyStepUrl(ctx.journey, at);
|
|
7
|
+
const heal = r.heal ?? ctx.heal;
|
|
8
|
+
const record = {
|
|
9
|
+
missionOutcome: JOURNEY_MISSION_OUTCOME[r.outcome],
|
|
10
|
+
exitCode: journeyExitCode(r.outcome),
|
|
11
|
+
result: {
|
|
12
|
+
mode: "journey",
|
|
13
|
+
journeyId: ctx.journey.metadata.id,
|
|
14
|
+
outcome: r.outcome,
|
|
15
|
+
...(r.outcome === "quarantined" || r.outcome === "heal-exhausted" ? { reason: r.reason, ...(r.at === undefined ? {} : { at: r.at }) } : {}),
|
|
16
|
+
...(url === undefined ? {} : { url }),
|
|
17
|
+
...(heal === undefined ? {} : { heal }),
|
|
18
|
+
...(ctx.proposal === undefined ? {} : { proposal: ctx.proposal }),
|
|
19
|
+
startedAt: ctx.startedAt,
|
|
20
|
+
target: { seedUrl: ctx.journey.recording.site, allowlist: [new URL(ctx.journey.recording.site).origin] },
|
|
21
|
+
...(ctx.engine === undefined ? {} : { engine: ctx.engine }),
|
|
22
|
+
...(ctx.suite === undefined ? {} : { suite: ctx.suite }),
|
|
23
|
+
...(ctx.targetBuild === undefined ? {} : { targetBuild: ctx.targetBuild }),
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
return redactSecretParams(record, ctx.journey, ctx.params);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=journey-result-record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journey-result-record.js","sourceRoot":"","sources":["../src/journey-result-record.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAuB,MAAM,kBAAkB,CAAC;AAGjG,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAsCjD,MAAM,UAAU,mBAAmB,CAAC,CAAmB,EAAE,GAAyB;IAChF,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,KAAK,aAAa,IAAI,CAAC,CAAC,OAAO,KAAK,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5F,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC5C,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC;IAChC,MAAM,MAAM,GAAwB;QAClC,cAAc,EAAE,uBAAuB,CAAC,CAAC,CAAC,OAAO,CAAC;QAClD,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC;QACpC,MAAM,EAAE;YACN,IAAI,EAAE,SAAS;YACf,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;YAClC,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,GAAG,CAAC,CAAC,CAAC,OAAO,KAAK,aAAa,IAAI,CAAC,CAAC,OAAO,KAAK,gBAAgB,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3I,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;YACrC,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;YACvC,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;YACjE,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,MAAM,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE;YACxG,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC;YACxD,GAAG,CAAC,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC;SAC3E;KACF,CAAC;IACF,OAAO,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Journey, type JourneyReview } from "@jevitate/journey";
|
|
2
|
+
import { type ApprovalAction } from "./pre-approval.js";
|
|
3
|
+
import { type JevSetup } from "./jev-advisor.js";
|
|
4
|
+
/**
|
|
5
|
+
* #432: loads one Journey and everything its review sheet needs — the targets.json safety config of
|
|
6
|
+
* its site's origin, its approved snapshot, its last mutation-proof verdict — and builds the sheet.
|
|
7
|
+
* An unknown id is `UnknownJourneyError`, exactly as `journey promote` refuses one. #433: also its
|
|
8
|
+
* catalog links and the pre-approval findings (`catalogDir`: default the project's `.jevitate/`).
|
|
9
|
+
*/
|
|
10
|
+
export declare function reviewJourneyById(journeysDir: string, id: string, opts?: {
|
|
11
|
+
readonly targetsFile?: string;
|
|
12
|
+
readonly catalogDir?: string | null;
|
|
13
|
+
readonly readiness?: boolean;
|
|
14
|
+
readonly jev?: JevSetup;
|
|
15
|
+
readonly action?: ApprovalAction;
|
|
16
|
+
}): Promise<{
|
|
17
|
+
journey: Journey;
|
|
18
|
+
review: JourneyReview;
|
|
19
|
+
}>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { FsJourneyStore, JourneyReviewSchema } from "@jevitate/journey";
|
|
2
|
+
import { buildJourneyReview } from "./journey-review.js";
|
|
3
|
+
import { readApprovedSnapshot, readVerifyRecord } from "./journey-review-store.js";
|
|
4
|
+
import { UnknownJourneyError } from "./journey-api.js";
|
|
5
|
+
import { loadCatalog, resolveCatalogDir } from "./catalog-api.js";
|
|
6
|
+
import { catalogJourney, journeyLinks } from "./catalog.js";
|
|
7
|
+
import { preApprovalFindings } from "./pre-approval.js";
|
|
8
|
+
import { jevLayerOf } from "./jev-advisor.js";
|
|
9
|
+
import { checkProposal, readJourneyProposal } from "./journey-proposal-store.js";
|
|
10
|
+
import { loadTargetsFile, resolveTargetConfig } from "./target-config.js";
|
|
11
|
+
/**
|
|
12
|
+
* #432: loads one Journey and everything its review sheet needs — the targets.json safety config of
|
|
13
|
+
* its site's origin, its approved snapshot, its last mutation-proof verdict — and builds the sheet.
|
|
14
|
+
* An unknown id is `UnknownJourneyError`, exactly as `journey promote` refuses one. #433: also its
|
|
15
|
+
* catalog links and the pre-approval findings (`catalogDir`: default the project's `.jevitate/`).
|
|
16
|
+
*/
|
|
17
|
+
export async function reviewJourneyById(journeysDir, id, opts = {}) {
|
|
18
|
+
const journey = await new FsJourneyStore(journeysDir).get(id);
|
|
19
|
+
if (journey === null)
|
|
20
|
+
throw new UnknownJourneyError(`unknown journey '${id}'`);
|
|
21
|
+
let origin = null;
|
|
22
|
+
try {
|
|
23
|
+
origin = new URL(journey.recording.site).origin;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
origin = null;
|
|
27
|
+
}
|
|
28
|
+
const safety = origin === null ? undefined : resolveTargetConfig(loadTargetsFile(opts.targetsFile), origin).safety;
|
|
29
|
+
// #433: the catalog links and the shared pre-approval findings, as `journey promote` gates on them.
|
|
30
|
+
const catalog = await loadCatalog(opts.catalogDir === undefined ? resolveCatalogDir(undefined) : opts.catalogDir, journeysDir);
|
|
31
|
+
const links = journeyLinks(catalog, catalogJourney(journey));
|
|
32
|
+
const findings = await preApprovalFindings({ kind: "journey", id: journey.metadata.id }, catalog, {
|
|
33
|
+
action: opts.action ?? "review",
|
|
34
|
+
journey,
|
|
35
|
+
...(opts.readiness === true ? { readiness: true } : {}),
|
|
36
|
+
...(opts.jev === undefined ? {} : { jev: opts.jev }),
|
|
37
|
+
});
|
|
38
|
+
// #453: a pending self-heal proposal, re-checked against the stored Journey (stale, or its proof touched).
|
|
39
|
+
const pending = await readJourneyProposal(journeysDir, id);
|
|
40
|
+
const proposalProblem = pending === null ? null : checkProposal(journey, pending);
|
|
41
|
+
const review = buildJourneyReview(journey, {
|
|
42
|
+
...(pending === null
|
|
43
|
+
? {}
|
|
44
|
+
: { proposal: { proposal: pending, stale: proposalProblem?.code === "E_JOURNEY_PROPOSAL_STALE", ...(proposalProblem === null ? {} : { problem: proposalProblem.message }) } }),
|
|
45
|
+
...(safety === undefined ? {} : { safety }),
|
|
46
|
+
catalog: { links, findings },
|
|
47
|
+
approvedSnapshot: await readApprovedSnapshot(journeysDir, id),
|
|
48
|
+
lastVerify: await readVerifyRecord(journeysDir, id),
|
|
49
|
+
});
|
|
50
|
+
// Fail closed: what is emitted always matches the published schema.
|
|
51
|
+
return { journey, review: JourneyReviewSchema.parse(opts.jev === undefined ? review : { ...review, jev: jevLayerOf(opts.jev) }) };
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=journey-review-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journey-review-api.js","sourceRoot":"","sources":["../src/journey-review-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Journey } from "@jevitate/journey";
|
|
2
|
+
import type { JourneyVerifyRecord } from "./journey-review.js";
|
|
3
|
+
export declare function approvedSnapshotPath(journeysDir: string, id: string): string;
|
|
4
|
+
export declare function verifyRecordPath(journeysDir: string, id: string): string;
|
|
5
|
+
/** A sidecar that exists but cannot be read as what it should be — refused, never silently ignored. */
|
|
6
|
+
export declare class ReviewSidecarError extends Error {
|
|
7
|
+
readonly code = "E_JOURNEY_REVIEW";
|
|
8
|
+
}
|
|
9
|
+
/** #432: keeps the Journey as approved — the next review's "change since last approval" diffs against it. */
|
|
10
|
+
export declare function writeApprovedSnapshot(journeysDir: string, journey: Journey): Promise<void>;
|
|
11
|
+
/** #432: the Journey as last approved, or null when none was kept. */
|
|
12
|
+
export declare function readApprovedSnapshot(journeysDir: string, id: string): Promise<Journey | null>;
|
|
13
|
+
/** #432: records the last mutation-proof verdict beside the Journey. */
|
|
14
|
+
export declare function writeVerifyRecord(journeysDir: string, record: JourneyVerifyRecord): Promise<void>;
|
|
15
|
+
/** #432: the last recorded mutation-proof verdict, or null when `journey verify --mutate` never ran. */
|
|
16
|
+
export declare function readVerifyRecord(journeysDir: string, id: string): Promise<JourneyVerifyRecord | null>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { JourneySchema } from "@jevitate/journey";
|
|
4
|
+
/**
|
|
5
|
+
* #432 — the review sheet's sidecars beside a journeys directory (dot-folders: never listed as a
|
|
6
|
+
* Journey, never discovered as a namespace):
|
|
7
|
+
* - `.approved/<id>.json` — the Journey exactly as last approved (`journey promote`, `demo approve`),
|
|
8
|
+
* what "change since last approval" diffs against;
|
|
9
|
+
* - `.verify/<id>.json` — the last `journey verify --mutate` verdict, bound to the review hash.
|
|
10
|
+
* `<ns>/<id>` for a shared Journey. Neither ever holds a secret value: a Journey never carries one.
|
|
11
|
+
*/
|
|
12
|
+
function sidecarPath(journeysDir, folder, id) {
|
|
13
|
+
const parts = id.split("/");
|
|
14
|
+
return join(journeysDir, folder, ...parts.slice(0, -1), `${parts[parts.length - 1] ?? id}.json`);
|
|
15
|
+
}
|
|
16
|
+
export function approvedSnapshotPath(journeysDir, id) {
|
|
17
|
+
return sidecarPath(journeysDir, ".approved", id);
|
|
18
|
+
}
|
|
19
|
+
export function verifyRecordPath(journeysDir, id) {
|
|
20
|
+
return sidecarPath(journeysDir, ".verify", id);
|
|
21
|
+
}
|
|
22
|
+
/** A sidecar that exists but cannot be read as what it should be — refused, never silently ignored. */
|
|
23
|
+
export class ReviewSidecarError extends Error {
|
|
24
|
+
code = "E_JOURNEY_REVIEW";
|
|
25
|
+
}
|
|
26
|
+
async function readJson(path) {
|
|
27
|
+
let raw;
|
|
28
|
+
try {
|
|
29
|
+
raw = await readFile(path, "utf8");
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT")
|
|
33
|
+
return null;
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(raw);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
throw new ReviewSidecarError(`${path} is not valid JSON`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function writeJson(path, value) {
|
|
44
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
45
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
46
|
+
}
|
|
47
|
+
/** The file's own (plain) id, as `FsJourneyStore` writes a namespaced Journey. */
|
|
48
|
+
function baseId(id) {
|
|
49
|
+
const parts = id.split("/");
|
|
50
|
+
return parts[parts.length - 1] ?? id;
|
|
51
|
+
}
|
|
52
|
+
/** #432: keeps the Journey as approved — the next review's "change since last approval" diffs against it. */
|
|
53
|
+
export async function writeApprovedSnapshot(journeysDir, journey) {
|
|
54
|
+
const id = journey.metadata.id;
|
|
55
|
+
await writeJson(approvedSnapshotPath(journeysDir, id), JourneySchema.parse({ ...journey, metadata: { ...journey.metadata, id: baseId(id) } }));
|
|
56
|
+
}
|
|
57
|
+
/** #432: the Journey as last approved, or null when none was kept. */
|
|
58
|
+
export async function readApprovedSnapshot(journeysDir, id) {
|
|
59
|
+
const path = approvedSnapshotPath(journeysDir, id);
|
|
60
|
+
const raw = await readJson(path);
|
|
61
|
+
if (raw === null)
|
|
62
|
+
return null;
|
|
63
|
+
const parsed = JourneySchema.safeParse(raw);
|
|
64
|
+
if (!parsed.success)
|
|
65
|
+
throw new ReviewSidecarError(`${path} is not a valid Journey snapshot`);
|
|
66
|
+
return { ...parsed.data, metadata: { ...parsed.data.metadata, id } };
|
|
67
|
+
}
|
|
68
|
+
/** #432: records the last mutation-proof verdict beside the Journey. */
|
|
69
|
+
export async function writeVerifyRecord(journeysDir, record) {
|
|
70
|
+
await writeJson(verifyRecordPath(journeysDir, record.journeyId), record);
|
|
71
|
+
}
|
|
72
|
+
function isVerifyRecord(v) {
|
|
73
|
+
if (v === null || typeof v !== "object")
|
|
74
|
+
return false;
|
|
75
|
+
const o = v;
|
|
76
|
+
return (typeof o.journeyId === "string" &&
|
|
77
|
+
typeof o.contentHash === "string" &&
|
|
78
|
+
typeof o.verdict === "string" &&
|
|
79
|
+
typeof o.at === "string" &&
|
|
80
|
+
(o.reason === undefined || typeof o.reason === "string") &&
|
|
81
|
+
o.summary !== null &&
|
|
82
|
+
typeof o.summary === "object" &&
|
|
83
|
+
Object.values(o.summary).every((n) => typeof n === "number"));
|
|
84
|
+
}
|
|
85
|
+
/** #432: the last recorded mutation-proof verdict, or null when `journey verify --mutate` never ran. */
|
|
86
|
+
export async function readVerifyRecord(journeysDir, id) {
|
|
87
|
+
const path = verifyRecordPath(journeysDir, id);
|
|
88
|
+
const raw = await readJson(path);
|
|
89
|
+
if (raw === null)
|
|
90
|
+
return null;
|
|
91
|
+
if (!isVerifyRecord(raw))
|
|
92
|
+
throw new ReviewSidecarError(`${path} is not a valid verify record`);
|
|
93
|
+
return raw;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=journey-review-store.js.map
|