@jevitate/cli 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/author-journey-cli.d.ts +2 -0
  2. package/dist/author-journey-cli.js +93 -15
  3. package/dist/author-journey-cli.js.map +1 -1
  4. package/dist/bin.js +4249 -1396
  5. package/dist/build-info.generated.js +2 -2
  6. package/dist/cli-output.js +4 -0
  7. package/dist/cli-output.js.map +1 -1
  8. package/dist/demo-aspect-api.d.ts +6 -5
  9. package/dist/demo-aspect-api.js +7 -1
  10. package/dist/demo-aspect-api.js.map +1 -1
  11. package/dist/environments.js +6 -1
  12. package/dist/environments.js.map +1 -1
  13. package/dist/explore-adversarial.js +1 -1
  14. package/dist/explore-adversarial.js.map +1 -1
  15. package/dist/explore-author.d.ts +21 -3
  16. package/dist/explore-author.js +64 -32
  17. package/dist/explore-author.js.map +1 -1
  18. package/dist/explore-cli.js +73 -92
  19. package/dist/explore-cli.js.map +1 -1
  20. package/dist/explore-coverage.js +1 -1
  21. package/dist/explore-coverage.js.map +1 -1
  22. package/dist/explore-goal.js +9 -4
  23. package/dist/explore-goal.js.map +1 -1
  24. package/dist/goal-run-flags.d.ts +120 -0
  25. package/dist/goal-run-flags.js +220 -0
  26. package/dist/goal-run-flags.js.map +1 -0
  27. package/dist/journey-api.d.ts +72 -5
  28. package/dist/journey-api.js +125 -14
  29. package/dist/journey-api.js.map +1 -1
  30. package/dist/journey-cli.d.ts +1 -1
  31. package/dist/journey-cli.js +168 -5
  32. package/dist/journey-cli.js.map +1 -1
  33. package/dist/journey-demo-api.d.ts +7 -1
  34. package/dist/journey-demo-api.js +8 -3
  35. package/dist/journey-demo-api.js.map +1 -1
  36. package/dist/journey-demo-cli.js +3 -0
  37. package/dist/journey-demo-cli.js.map +1 -1
  38. package/dist/journey-lint-sarif.d.ts +19 -0
  39. package/dist/journey-lint-sarif.js +48 -0
  40. package/dist/journey-lint-sarif.js.map +1 -0
  41. package/dist/journey-network-checks.d.ts +45 -9
  42. package/dist/journey-network-checks.js +117 -30
  43. package/dist/journey-network-checks.js.map +1 -1
  44. package/dist/journey-prefix.d.ts +24 -15
  45. package/dist/journey-prefix.js +53 -7
  46. package/dist/journey-prefix.js.map +1 -1
  47. package/dist/journey-verify.d.ts +61 -0
  48. package/dist/journey-verify.js +131 -0
  49. package/dist/journey-verify.js.map +1 -0
  50. package/dist/load-api.js +9 -3
  51. package/dist/load-api.js.map +1 -1
  52. package/dist/mcp-cli-tools.d.ts +1 -0
  53. package/dist/mcp-cli-tools.js +61 -3
  54. package/dist/mcp-cli-tools.js.map +1 -1
  55. package/dist/promotions.d.ts +15 -0
  56. package/dist/promotions.js +52 -0
  57. package/dist/promotions.js.map +1 -0
  58. package/dist/self-heal-adapter.js +3 -1
  59. package/dist/self-heal-adapter.js.map +1 -1
  60. package/dist/signal-triage.js +5 -1
  61. package/dist/signal-triage.js.map +1 -1
  62. package/dist/source-run-api.js +17 -7
  63. package/dist/source-run-api.js.map +1 -1
  64. package/dist/usability-capture.js +11 -1
  65. package/dist/usability-capture.js.map +1 -1
  66. package/dist/ux-usability.js +1 -1
  67. package/dist/ux-usability.js.map +1 -1
  68. package/package.json +10 -10
  69. package/skills/jevitate-explore/SKILL.md +11 -0
@@ -0,0 +1,220 @@
1
+ /**
2
+ * #369: the goal run's run-shaping flags, shared by `jevitate explore` (goal/usability) and
3
+ * `jevitate explore-author-journey` (whose discovery and corroborating takes ARE goal runs) — one
4
+ * definition of each flag (text, parser, default) and one resolver, so the two commands accept and
5
+ * validate the same setup and an author run can reproduce what a goal run needed.
6
+ */
7
+ import { Option } from "commander";
8
+ import { parseSecretField, validateDenyPatterns, } from "@jevitate/explore";
9
+ import { intArg, nonNegativeIntArg, positiveIntArg } from "./cli-args.js";
10
+ import { LITERAL_SECRET_WARNING, resolveSecretArgs } from "./secret-args.js";
11
+ import { secretCommandRunner } from "./secret-command.js";
12
+ import { loadTypeFixtures } from "./type-fixture-file.js";
13
+ import { SessionFileInProjectError, assertSessionFileOutsideProject } from "./project-dir.js";
14
+ import { parseScreenshotsArg } from "./run-screenshots.js";
15
+ import { TargetConfigError, loadTargetsFile, resolveTargetConfig } from "./target-config.js";
16
+ import { emulationFromFlags } from "./cli-shared.js";
17
+ const collect = (v, prev) => [...prev, v];
18
+ /** Each shared flag, built fresh per command (commander owns an `Option` once added). */
19
+ export const GOAL_RUN_OPTIONS = {
20
+ successWhen: () => new Option("--success-when <when>", "when the --success page checks must hold: final (default; on the final page) | held (on the final page, or all together at any settled step — a one-time secret, a toast) | " +
21
+ "each (each went from not holding to holding at some settled step, in any order — checks on different pages; the run stops once all have). reloadThen is always final"),
22
+ allowVacuousChecks: () => new Option("--allow-vacuous-checks", "downgrade a vacuous --success check to a warning. By default a check satisfied before the run's first action — a page check that held on the seed page and never changed " +
23
+ "(an empty result container), a requestMade/responseStatus matched only by a page-load or polling request — FAILS: it cannot verify the goal"),
24
+ actionDeltas: () => new Option("--action-deltas", "opt-in (#303; every --strategy, not --feature): record what each action changed on the page — an accessibility snapshot before and after, announcements, " +
25
+ "the action's requests — redacted, with a code verdict per step (no-change | relevant-change | inconclusive) used by the goal loop's no-progress check and a persistence re-check after writes (goal), and as defect evidence (adversarial, coverage); " +
26
+ "adds `delta` to every transcript step (and Recording step, goal) and `actionDeltas` to the result. Costs about 50-100 ms per action on a small page, 0.3-0.5 s on a large one"),
27
+ secret: () => new Option("--secret <value|env:VAR>", "REDACTION ONLY: a secret/PII value kept out of every model call and artifact (repeatable); env:VAR reads it from the environment (preferred: a literal is visible in the process list and shell history). It is never typed into a field — to log in, bind it with --secret-field (or start from --storage-state)")
28
+ .default([])
29
+ .argParser(collect),
30
+ secretField: () => new Option("--secret-field <binding>", "goal/usability strategy: '<label|testId|type|id|name>=<value>=env:<VAR>' (repeatable), e.g. 'label=Password=env:APP_PASSWORD'. When the run types into a matching field, code types $VAR itself; the model sees only «secret:VAR» and the Recording {redacted:true}. " +
31
+ "A value delivered during the run (an emailed code): '<descriptor>=cmd:<command>' runs the command when the field is typed and types its stdout (needs --allow-secret-cmd)")
32
+ .default([])
33
+ .argParser(collect),
34
+ totp: () => new Option("--totp <binding>", "goal/usability strategy: '<descriptor>=env:<VAR>' with $VAR a base32 TOTP seed (repeatable), e.g. 'label=Authentication code=env:APP_TOTP_SEED'. The 6-digit code is computed locally (RFC 6238) when the field is typed; the seed never reaches a model or disk")
35
+ .default([])
36
+ .argParser(collect),
37
+ typeFixture: () => new Option("--type-fixture <binding>", "goal strategy: '<label|testId|type|id|name>=<value>=<file>' (repeatable), e.g. 'label=Paste your text=./fixtures/import.txt'. When the run types into a matching field, code types the file's exact text verbatim (line breaks kept, never paraphrased or capped); the model sees only «fixture:<file name>». Recorded as typed unless it holds a --secret")
38
+ .default([])
39
+ .argParser(collect),
40
+ fixture: () => new Option("--fixture <path>", "local file the upload op attaches to a file input (goal and usability strategies); must exist"),
41
+ saveStorageState: () => new Option("--save-storage-state <file>", "write the context's storageState (cookies + origin storage) here when the run ends; mode 0600, contents never logged. " +
42
+ "Useful with a rotating refresh token: --storage-state's file goes stale after one authenticated run refreshes it, " +
43
+ "so point --save-storage-state at the SAME file (or a new one) to keep it usable for the next run. " +
44
+ "Written on every exit path -- a crash or a SIGTERM/SIGINT kill included (#159), not only a clean end -- but " +
45
+ "never over a good file with a session that already looks lost/logged-out; the last known-good state is used " +
46
+ "instead, or nothing is written if none was ever captured."),
47
+ replyWaitMs: () => new Option("--reply-wait-ms <ms>", "conversational pages: how long to keep waiting for a reply while the page shows no sign of working on one " +
48
+ "(goal and usability; default 60000). While a request the message started is in flight, a busy indicator shows, " +
49
+ "or the reply is still growing, the wait continues up to --reply-ceiling-ms").argParser(positiveIntArg),
50
+ replyQuietMs: () => new Option("--reply-quiet-ms <ms>", "conversational pages: how long a reply must hold still (no new text, no busy sign) before it is read as complete " +
51
+ "(goal and usability; default 1000). Raise it for an assistant that answers in several parts (a sentence, then a card a moment later)").argParser(positiveIntArg),
52
+ replyCeilingMs: () => new Option("--reply-ceiling-ms <ms>", "conversational pages: hard ceiling on the TOTAL wait for one sent message's reply — the send's own wait plus every later 'wait' — " +
53
+ "however busy the page stays; once spent the run ends naming the missing reply (default 180000; never below --reply-wait-ms) (default 180000; never below --reply-wait-ms)").argParser(positiveIntArg),
54
+ replyMaxChars: () => new Option("--reply-max-chars <n>", "conversational pages: cap on each generated chat message (goal and usability; default 300)").argParser(intArg({ min: 20, max: 2000 })),
55
+ jobWaitMs: () => new Option("--job-wait-ms <ms>", "goal and usability: while the page shows an in-progress status (\"Simulating…\", aria-busy, a job \"is running\"), " +
56
+ "waits keep waiting with backoff — and a model 'blocked' is deferred — up to this budget (default: --reply-ceiling-ms, 180000); " +
57
+ "it also bounds a busy indicator the app visibly keeps working behind (live progress, a job poll) before it is a hang, " +
58
+ "and a wait the page documents (\"usually takes a minute\") can raise it").argParser(positiveIntArg),
59
+ deny: () => new Option("--deny <pattern>", "a control no mission may click (repeatable): an accessible-name regex (/Archive/i or Archive) or a descriptor role=button;name=Archive. " +
60
+ "Session-ending (Sign out), destructive (Delete, Revoke, Rotate) and paid (Buy, Run simulation, Generate, Send invite) controls are refused by default")
61
+ .default([])
62
+ .argParser(collect),
63
+ paid: () => new Option("--paid <pattern>", "an app control that costs money or credits (repeatable; same syntax as --deny), e.g. /^(Analyze|Draft|Improve)\\b/i: treated like the built-in paid " +
64
+ "vocabulary — the budget guard sees it, hang replays never repeat it, and a goal that asks for it may still click it")
65
+ .default([])
66
+ .argParser(collect),
67
+ allowDestructive: () => new Option("--allow-destructive", "let missions click session-ending, destructive and paid controls (a --deny pattern still holds). A goal run already may click one its goal asks for"),
68
+ dialogs: () => new Option("--dialogs <policy>", "native window.confirm/prompt dialogs: dismiss (default) or accept. accept still dismisses one whose message names a session-ending, " +
69
+ "destructive or paid action the run may not take (without --allow-destructive or a goal asking for it); every dialog is logged"),
70
+ readRpc: () => new Option("--read-rpc <glob>", "a POST request that only READS (repeatable): an RPC-method glob (Estimate*, pkg.Service/Preview*) or a path glob (/api/search*). " +
71
+ "gRPC-web/Connect Get*/List*/Search*/Find*/Watch*/Stream*/Count*/Describe*/Read* methods are reads already. Reads are never guarded or reported as duplicate writes")
72
+ .default([])
73
+ .argParser(collect),
74
+ hangReplays: () => new Option("--hang-replays <n>", "fresh-context replays that confirm a hang (default 2; 0 = don't replay, the hang is reported unconfirmed)").argParser(nonNegativeIntArg),
75
+ settleIgnore: () => new Option("--settle-ignore <pattern>", "a request URL pattern the target marks as background (never pending work; repeatable, * wildcard)")
76
+ .default([])
77
+ .argParser(collect),
78
+ longPollMs: () => new Option("--long-poll-ms <n>", "a request pending this long on an interactive page is a long-poll (default 5000)").argParser(nonNegativeIntArg),
79
+ apiPrefix: () => new Option("--api-prefix <path>", "a path prefix whose requests are the app's API in the timing summary (repeatable), e.g. /api/")
80
+ .default([])
81
+ .argParser(collect),
82
+ ignoreNoProgress: () => new Option("--ignore-no-progress <pattern>", "a route / action label / busy indicator where ui-no-progress is expected (repeatable, * wildcard)")
83
+ .default([])
84
+ .argParser(collect),
85
+ allowSecretCmd: () => new Option("--allow-secret-cmd", "opt-in: a --secret-field <descriptor>=cmd:<command> may run its command (in a shell, at type time, 60s timeout) and type its output (operator-declared only; refused otherwise)").default(false),
86
+ secretCmdAttempts: () => new Option("--secret-cmd-attempts <n>", "#359: how many times one cmd: secret field's command may run in this run (default 3); past it, typing that field fails without running the command again (read-the-code commands usually have side effects)").argParser(positiveIntArg),
87
+ };
88
+ /** A goal-run flag the command must refuse (exit 64): `code` + the user-facing message. */
89
+ export class GoalRunFlagError extends Error {
90
+ code;
91
+ constructor(code, message) {
92
+ super(message);
93
+ this.code = code;
94
+ this.name = "GoalRunFlagError";
95
+ }
96
+ }
97
+ /** A refusal keeps its own code when it has a typed one (a session file in the project, a bad targets.json); the rest are E_EXPLORE_ARGS. */
98
+ const asError = (err) => err instanceof GoalRunFlagError
99
+ ? err
100
+ : new GoalRunFlagError(err instanceof SessionFileInProjectError || err instanceof TargetConfigError ? err.code : "E_EXPLORE_ARGS", err instanceof Error ? err.message : String(err));
101
+ /** `--dialogs`: a mistyped value never silently means "dismiss" (#334). */
102
+ export function dialogsFromFlags(o) {
103
+ if (o.dialogs === undefined)
104
+ return undefined;
105
+ if (o.dialogs !== "dismiss" && o.dialogs !== "accept") {
106
+ throw new GoalRunFlagError("E_EXPLORE_ARGS", `--dialogs must be dismiss or accept, got ${JSON.stringify(o.dialogs)}`);
107
+ }
108
+ return o.dialogs;
109
+ }
110
+ /** `--success-when`: final | held | each. */
111
+ export function successWhenFromFlags(o) {
112
+ if (o.successWhen === undefined)
113
+ return undefined;
114
+ if (o.successWhen !== "held" && o.successWhen !== "final" && o.successWhen !== "each") {
115
+ throw new GoalRunFlagError("E_EXPLORE_ARGS", `--success-when must be "final", "held" or "each", got ${JSON.stringify(o.successWhen)}`);
116
+ }
117
+ return o.successWhen;
118
+ }
119
+ /** The conversational-page tuning (`--reply-*`, `--job-wait-ms`), validated. */
120
+ export function conversationFromFlags(o) {
121
+ const conversation = {
122
+ ...(o.replyWaitMs === undefined ? {} : { replyWaitMs: Number(o.replyWaitMs) }),
123
+ ...(o.replyQuietMs === undefined ? {} : { replyQuietMs: Number(o.replyQuietMs) }),
124
+ ...(o.replyCeilingMs === undefined ? {} : { replyCeilingMs: Number(o.replyCeilingMs) }),
125
+ ...(o.replyMaxChars === undefined ? {} : { replyMaxChars: Number(o.replyMaxChars) }),
126
+ ...(o.jobWaitMs === undefined ? {} : { jobWaitMs: Number(o.jobWaitMs) }),
127
+ };
128
+ if ((conversation.replyWaitMs !== undefined && !(Number.isInteger(conversation.replyWaitMs) && conversation.replyWaitMs > 0)) ||
129
+ (conversation.replyCeilingMs !== undefined && !(Number.isInteger(conversation.replyCeilingMs) && conversation.replyCeilingMs > 0)) ||
130
+ (conversation.replyMaxChars !== undefined &&
131
+ !(Number.isInteger(conversation.replyMaxChars) && conversation.replyMaxChars >= 20 && conversation.replyMaxChars <= 2000))) {
132
+ throw new GoalRunFlagError("E_EXPLORE_ARGS", "--reply-wait-ms and --reply-ceiling-ms must be positive integers; --reply-max-chars an integer in 20..2000");
133
+ }
134
+ if (conversation.jobWaitMs !== undefined && !(Number.isInteger(conversation.jobWaitMs) && conversation.jobWaitMs > 0)) {
135
+ throw new GoalRunFlagError("E_EXPLORE_ARGS", "--job-wait-ms must be a positive integer");
136
+ }
137
+ return conversation;
138
+ }
139
+ /** `--secret-field` / `--totp` bindings, resolved from the environment (#72; `cmd:` needs `--allow-secret-cmd`, #324). */
140
+ export function secretFieldsFromFlags(o, env = process.env) {
141
+ try {
142
+ return [
143
+ ...o.secretField.map((s) => parseSecretField(s, "value", env, { allowCmd: o.allowSecretCmd === true })),
144
+ ...o.totp.map((s) => parseSecretField(s, "totp", env)),
145
+ ];
146
+ }
147
+ catch (err) {
148
+ throw asError(err);
149
+ }
150
+ }
151
+ /** The `targets.json` flag overlay (patterns added, numbers and opt-ins winning). */
152
+ export function targetFlagsFromFlags(o, dialogs) {
153
+ return {
154
+ settleIgnore: o.settleIgnore ?? [],
155
+ ignoreNoProgress: o.ignoreNoProgress ?? [],
156
+ apiPrefixes: o.apiPrefix ?? [],
157
+ deny: o.deny ?? [],
158
+ paid: o.paid ?? [],
159
+ readRpc: o.readRpc ?? [],
160
+ ...(o.allowDestructive === true ? { allowDestructive: true } : {}),
161
+ ...(dialogs === undefined ? {} : { dialogs }),
162
+ ...(o.allowWrites === true ? { allowWrites: true } : {}),
163
+ allowWrite: o.allowWrite ?? [],
164
+ ...(o.hangReplayWrites === true ? { hangReplayWrites: true } : {}),
165
+ ...(o.longPollMs === undefined ? {} : { longPollMs: Number(o.longPollMs) }),
166
+ };
167
+ }
168
+ /**
169
+ * Validates and resolves every shared goal-run flag — before any browser opens — into the goal
170
+ * run's options. Throws `GoalRunFlagError` (exit 64) on the first unusable one.
171
+ */
172
+ export function resolveGoalRunFlags(o, url, deps = {}) {
173
+ const env = deps.env ?? process.env;
174
+ try {
175
+ const dialogs = dialogsFromFlags(o);
176
+ const successWhen = successWhenFromFlags(o);
177
+ if (o.saveStorageState !== undefined)
178
+ assertSessionFileOutsideProject(o.saveStorageState, "--save-storage-state");
179
+ const resolved = resolveSecretArgs(o.secret, env, "--secret");
180
+ if (resolved.literals > 0)
181
+ deps.warn?.(LITERAL_SECRET_WARNING);
182
+ const screenshots = parseScreenshotsArg(o.screenshots);
183
+ const conversation = conversationFromFlags(o);
184
+ validateDenyPatterns(o.deny);
185
+ validateDenyPatterns(o.paid, "--paid");
186
+ const emulation = emulationFromFlags(o);
187
+ const secretFields = secretFieldsFromFlags(o, env);
188
+ const typeFixtures = o.typeFixture.length === 0 ? [] : loadTypeFixtures(o.typeFixture);
189
+ let target;
190
+ try {
191
+ target = resolveTargetConfig(loadTargetsFile(deps.targetsConfigPath), new URL(url).origin, targetFlagsFromFlags(o, dialogs));
192
+ }
193
+ catch (err) {
194
+ // An unparseable URL is refused by the authorized-target guard.
195
+ if (!(err instanceof TypeError))
196
+ throw err;
197
+ }
198
+ return {
199
+ ...(successWhen === undefined ? {} : { successWhen }),
200
+ ...(o.allowVacuousChecks === true ? { allowVacuousChecks: true } : {}),
201
+ ...(o.actionDeltas === true ? { actionDeltas: true } : {}),
202
+ ...(resolved.secrets.length === 0 ? {} : { secrets: resolved.secrets }),
203
+ ...(secretFields.length === 0 ? {} : { secretFields }),
204
+ ...(secretFields.some((f) => f.kind === "cmd") ? { secretCommand: secretCommandRunner() } : {}),
205
+ ...(o.secretCmdAttempts === undefined ? {} : { secretCommandAttempts: o.secretCmdAttempts }),
206
+ ...(typeFixtures.length === 0 ? {} : { typeFixtures }),
207
+ ...(o.fixture === undefined ? {} : { fixture: o.fixture }),
208
+ ...(o.saveStorageState === undefined ? {} : { saveStorageState: o.saveStorageState }),
209
+ ...(screenshots === undefined ? {} : { screenshots }),
210
+ ...(emulation === undefined ? {} : { emulation }),
211
+ ...(target === undefined ? {} : { target }),
212
+ ...(o.hangReplays === undefined ? {} : { hangReplays: o.hangReplays }),
213
+ ...(Object.keys(conversation).length === 0 ? {} : { conversation }),
214
+ };
215
+ }
216
+ catch (err) {
217
+ throw asError(err);
218
+ }
219
+ }
220
+ //# sourceMappingURL=goal-run-flags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"goal-run-flags.js","sourceRoot":"","sources":["../src/goal-run-flags.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,15 @@
1
- import { type Journey } from "@jevitate/journey";
1
+ import { type Journey, type JourneyLintFinding } from "@jevitate/journey";
2
2
  import { type RunPolicy } from "@jevitate/domain";
3
3
  import { type BrowserPort, type BrowserSession, type EmulationSpec } from "@jevitate/playwright";
4
4
  import { type BrowserRunOptions } from "./browser-run-options.js";
5
5
  import { RecordingInterpreter, type StepObserver } from "@jevitate/interpreter";
6
- import { type ReplayDeltaSummary } from "@jevitate/explore";
6
+ import { type BlockedWrite, type ReplayDeltaSummary } from "@jevitate/explore";
7
7
  import { SecretPixelMask } from "./demo-capture.js";
8
8
  import { type ScreenshotsResult, type ScreenshotsSpec } from "./run-screenshots.js";
9
9
  import { type JourneyRunResult, type SelfHealer, type SiteGateDeps } from "@jevitate/runtime";
10
10
  import { type FixtureRecord, type MissionFixtures } from "./mission-fixtures.js";
11
11
  import { type ResolvedJourneyEnvironment } from "./environments.js";
12
+ import { type OutcomeCheckFailure } from "./journey-network-checks.js";
12
13
  /**
13
14
  * Distinct from `@jevitate/journey`'s `ParamValidationError` so CLI/API callers
14
15
  * can tell "no such journey" apart from "params didn't match the journey's
@@ -23,6 +24,21 @@ export declare class UnknownJourneyError extends Error {
23
24
  */
24
25
  export declare class JourneyRequiresAuthError extends Error {
25
26
  }
27
+ /**
28
+ * #401: the Journey's assertions cannot prove its outcome (the `lintJourney` errors). `promote`
29
+ * refuses unless the reviewer accepts it with a non-empty `--accept-weak <reason>`.
30
+ */
31
+ export declare class WeakJourneyError extends Error {
32
+ readonly findings: readonly JourneyLintFinding[];
33
+ constructor(message: string, findings?: readonly JourneyLintFinding[]);
34
+ }
35
+ /** #401: the assertion-strength lint's verdict for one Journey (`journey lint`). */
36
+ export interface JourneyLintResult {
37
+ id: string;
38
+ findings: JourneyLintFinding[];
39
+ errors: number;
40
+ warnings: number;
41
+ }
26
42
  export interface RunJourneyProgrammaticallyOptions {
27
43
  /** Directory a `FsJourneyStore` reads Journey JSON files from. */
28
44
  dir: string;
@@ -108,12 +124,55 @@ export interface RunJourneyProgrammaticallyOptions {
108
124
  */
109
125
  actionDeltas?: boolean;
110
126
  }
127
+ /**
128
+ * #402: how a mutation-proof replay (`journey verify --mutate`) differs from `journey run` — a
129
+ * separate argument, never a surface option: no other caller passes it.
130
+ */
131
+ export interface MutationReplayOptions {
132
+ /**
133
+ * #402 `journey verify --mutate`: replay a MUTATED copy of the Journey. Applied after params are
134
+ * validated against the stored Journey (a mutation may stop a step reading a param), and never to
135
+ * an anchored prefix. Indices must be kept: a mutation changes a step, never removes one.
136
+ */
137
+ mutateJourney?: (journey: Journey) => Journey;
138
+ /** #402: leave this flat step (0-based) out of the replay — `RecordingInterpreter`'s `skipStep`. */
139
+ skipStep?: (index: number) => boolean;
140
+ /**
141
+ * #402: ABORT the write requests (the #110 classifier, first-party only — `ReadOnlyGuard`) that
142
+ * start while this flat step (0-based) runs, until the network settles after it. Never answered,
143
+ * never sent; what was blocked comes back as `blockedWrites`.
144
+ */
145
+ blockWritesAtStep?: number;
146
+ /** #402: also return the outcome checks' structured failures (`outcomeFailures`). */
147
+ structuredFailures?: boolean;
148
+ }
149
+ /** #402: what a mutation-proof replay returns beyond `journey run`'s result. */
150
+ export interface MutationReplayFields {
151
+ /** The step-request / end-state checks that failed (`structuredFailures`). */
152
+ outcomeFailures?: OutcomeCheckFailure[];
153
+ /** The writes aborted in `blockWritesAtStep`'s window. */
154
+ blockedWrites?: BlockedWrite[];
155
+ }
156
+ /**
157
+ * #246: a secret parameter's value (declared `secret: true`, or a credential-like name) never comes
158
+ * back in a run's output — the interpreter's vars start as the params, so the value is masked there.
159
+ */
160
+ export declare function redactSecretParams<T>(result: T, journey: Journey, params: Record<string, string>): T;
161
+ /** Every string in `result` (deeply) with each secret, in each of its URL forms, masked. */
162
+ export declare function redactSecretValues<T>(result: T, secrets: readonly string[]): T;
111
163
  /**
112
164
  * #293: the params a Journey PREFIX runs with — a `--param` the whole Journey does not take is
113
165
  * refused (`ParamValidationError`), one only a later step uses is dropped; the prefix's own required
114
166
  * params are then checked by `validateParams` as usual.
115
167
  */
116
168
  export declare function prefixParams(full: Journey, prefix: Journey, params: Record<string, string>): Record<string, string>;
169
+ /**
170
+ * #401: the assertion-strength lint (#401) for one Journey by id — an unknown id is refused,
171
+ * exactly as `journey run`/`promote` refuse one (`UnknownJourneyError`). Pure read; no browser.
172
+ */
173
+ export declare function lintJourneyById(dir: string, id: string, opts?: {
174
+ readRequests?: readonly string[];
175
+ }): Promise<JourneyLintResult>;
117
176
  /**
118
177
  * Promotes a local Journey (#124, mirrors `promoteMissionTarget` in
119
178
  * `mission-api.ts`) so it becomes discoverable via `journey find`/MCP
@@ -121,8 +180,14 @@ export declare function prefixParams(full: Journey, prefix: Journey, params: Rec
121
180
  * same as `mission target promote`. An unknown id is refused with
122
181
  * `UnknownJourneyError` (never silently created). Returns the persisted,
123
182
  * now-promoted Journey.
183
+ *
184
+ * #401: lints first — a Journey whose assertions cannot prove its outcome is refused
185
+ * (`WeakJourneyError`) unless the reviewer accepts it with a non-empty `--accept-weak <reason>`,
186
+ * which is recorded on the Journey (`metadata.acceptedWeak`). Warnings never block.
124
187
  */
125
- export declare function promoteJourney(dir: string, id: string): Promise<Journey>;
188
+ export declare function promoteJourney(dir: string, id: string, opts?: {
189
+ acceptWeak?: string;
190
+ }): Promise<Journey>;
126
191
  /**
127
192
  * The one programmatic surface for "run this published Journey by id" —
128
193
  * used by BOTH the CLI `journey run` action and external callers. Builds
@@ -132,8 +197,10 @@ export declare function promoteJourney(dir: string, id: string): Promise<Journey
132
197
  * `ParamValidationError`, from `@jevitate/journey`) BEFORE any browser is
133
198
  * launched, then builds the real Actor + `JourneyRunner` and runs.
134
199
  */
135
- export declare function runJourneyProgrammatically(opts: RunJourneyProgrammaticallyOptions): Promise<JourneyRunResult & {
200
+ export declare function runJourneyProgrammatically(opts: RunJourneyProgrammaticallyOptions, mutation?: MutationReplayOptions): Promise<JourneyRunResult & {
136
201
  fixtures?: FixtureRecord;
137
202
  videoPaths?: string[];
138
203
  actionDeltas?: ReplayDeltaSummary;
139
- } & Partial<ScreenshotsResult>>;
204
+ } & Partial<ScreenshotsResult> & MutationReplayFields>;
205
+ /** `err` with every secret (and its URL-encoded forms) masked in its message and stack — same object, same class. */
206
+ export declare function redactErrorSecrets(err: unknown, secrets: readonly string[]): unknown;
@@ -1,4 +1,4 @@
1
- import { FsJourneyStore, JourneyRegistry, ParamValidationError, deriveParamSchema, describeStep, flatJourneySteps, journeyPrefix, secretParamValues, validateParams } from "@jevitate/journey";
1
+ import { FsJourneyStore, JourneyRegistry, ParamValidationError, deriveParamSchema, describeStep, flatJourneySteps, journeyPrefix, lintJourney, secretParamValues, validateParams } from "@jevitate/journey";
2
2
  import { redactText } from "@jevitate/ai-core";
3
3
  import { safeRunPolicy, clock } from "@jevitate/domain";
4
4
  import { join } from "node:path";
@@ -8,7 +8,8 @@ import { artifactStamp } from "./mission-journal.js";
8
8
  import { logsDirFor } from "./project-dir.js";
9
9
  import { CastActor, BrowseTheWeb } from "@jevitate/screenplay";
10
10
  import { RecordingInterpreter } from "@jevitate/interpreter";
11
- import { ReplayDeltas, replayDeltaSummary } from "@jevitate/explore";
11
+ import { ReadOnlyGuard, ReplayDeltas, monitorFor, replayDeltaSummary } from "@jevitate/explore";
12
+ import { writeClassifier } from "@jevitate/recording";
12
13
  import { BrowseTheWebToken } from "@jevitate/screenplay";
13
14
  import { SecretPixelMask, maskingPort } from "./demo-capture.js";
14
15
  import { RunScreenshots, composeObservers, screenshotObserver, screenshotsDirFor } from "./run-screenshots.js";
@@ -16,7 +17,7 @@ import { JourneyRunner } from "@jevitate/runtime";
16
17
  import { gateJourney } from "./site-gate-cli.js";
17
18
  import { substituteSetupRefs } from "./mission-fixtures.js";
18
19
  import { applyJourneyEnvironment } from "./environments.js";
19
- import { withNetworkChecks } from "./journey-network-checks.js";
20
+ import { JourneyOutcomeChecks } from "./journey-network-checks.js";
20
21
  /**
21
22
  * Distinct from `@jevitate/journey`'s `ParamValidationError` so CLI/API callers
22
23
  * can tell "no such journey" apart from "params didn't match the journey's
@@ -31,12 +32,26 @@ export class UnknownJourneyError extends Error {
31
32
  */
32
33
  export class JourneyRequiresAuthError extends Error {
33
34
  }
35
+ /**
36
+ * #401: the Journey's assertions cannot prove its outcome (the `lintJourney` errors). `promote`
37
+ * refuses unless the reviewer accepts it with a non-empty `--accept-weak <reason>`.
38
+ */
39
+ export class WeakJourneyError extends Error {
40
+ findings;
41
+ constructor(message, findings = []) {
42
+ super(message);
43
+ this.findings = findings;
44
+ }
45
+ }
34
46
  /**
35
47
  * #246: a secret parameter's value (declared `secret: true`, or a credential-like name) never comes
36
48
  * back in a run's output — the interpreter's vars start as the params, so the value is masked there.
37
49
  */
38
- function redactSecretParams(result, journey, params) {
39
- const secrets = secretParamValues(journey, params);
50
+ export function redactSecretParams(result, journey, params) {
51
+ return redactSecretValues(result, secretParamValues(journey, params));
52
+ }
53
+ /** Every string in `result` (deeply) with each secret, in each of its URL forms, masked. */
54
+ export function redactSecretValues(result, secrets) {
40
55
  if (secrets.length === 0)
41
56
  return result;
42
57
  const scrub = (v) => typeof v === "string"
@@ -61,6 +76,24 @@ export function prefixParams(full, prefix, params) {
61
76
  const needed = deriveParamSchema(prefix.recording).required;
62
77
  return Object.fromEntries(Object.entries(params).filter(([k]) => needed.includes(k)));
63
78
  }
79
+ /**
80
+ * #401: the assertion-strength lint (#401) for one Journey by id — an unknown id is refused,
81
+ * exactly as `journey run`/`promote` refuse one (`UnknownJourneyError`). Pure read; no browser.
82
+ */
83
+ export async function lintJourneyById(dir, id, opts = {}) {
84
+ const registry = new JourneyRegistry(new FsJourneyStore(dir));
85
+ const journey = await registry.get(id);
86
+ if (!journey) {
87
+ throw new UnknownJourneyError(`unknown journey '${id}'`);
88
+ }
89
+ const findings = lintJourney(journey, opts.readRequests === undefined ? {} : { readRequests: opts.readRequests });
90
+ return {
91
+ id,
92
+ findings,
93
+ errors: findings.filter((f) => f.level === "error").length,
94
+ warnings: findings.filter((f) => f.level === "warning").length,
95
+ };
96
+ }
64
97
  /**
65
98
  * Promotes a local Journey (#124, mirrors `promoteMissionTarget` in
66
99
  * `mission-api.ts`) so it becomes discoverable via `journey find`/MCP
@@ -68,14 +101,32 @@ export function prefixParams(full, prefix, params) {
68
101
  * same as `mission target promote`. An unknown id is refused with
69
102
  * `UnknownJourneyError` (never silently created). Returns the persisted,
70
103
  * now-promoted Journey.
104
+ *
105
+ * #401: lints first — a Journey whose assertions cannot prove its outcome is refused
106
+ * (`WeakJourneyError`) unless the reviewer accepts it with a non-empty `--accept-weak <reason>`,
107
+ * which is recorded on the Journey (`metadata.acceptedWeak`). Warnings never block.
71
108
  */
72
- export async function promoteJourney(dir, id) {
109
+ export async function promoteJourney(dir, id, opts = {}) {
73
110
  const store = new FsJourneyStore(dir);
74
111
  const registry = new JourneyRegistry(store);
75
112
  const existing = await registry.get(id);
76
113
  if (!existing) {
77
114
  throw new UnknownJourneyError(`unknown journey '${id}'`);
78
115
  }
116
+ const errors = lintJourney(existing).filter((f) => f.level === "error");
117
+ const reason = opts.acceptWeak?.trim() ?? "";
118
+ if (errors.length > 0 && reason === "") {
119
+ throw new WeakJourneyError([
120
+ `journey '${id}' has ${errors.length} assertion-strength error(s):`,
121
+ ...errors.map((f) => f.message),
122
+ "strengthen the Journey or pass --accept-weak <reason>",
123
+ ].join("\n"), errors);
124
+ }
125
+ if (errors.length > 0) {
126
+ const acceptedWeak = { reason, rules: [...new Set(errors.map((f) => f.rule))] };
127
+ await store.put({ ...existing, metadata: { ...existing.metadata, promoted: true, acceptedWeak } });
128
+ return (await registry.get(id)) ?? { ...existing, metadata: { ...existing.metadata, promoted: true, acceptedWeak } };
129
+ }
79
130
  await registry.promote(id);
80
131
  const promoted = await registry.get(id);
81
132
  return promoted ?? { ...existing, metadata: { ...existing.metadata, promoted: true } };
@@ -89,7 +140,7 @@ export async function promoteJourney(dir, id) {
89
140
  * `ParamValidationError`, from `@jevitate/journey`) BEFORE any browser is
90
141
  * launched, then builds the real Actor + `JourneyRunner` and runs.
91
142
  */
92
- export async function runJourneyProgrammatically(opts) {
143
+ export async function runJourneyProgrammatically(opts, mutation = {}) {
93
144
  const store = new FsJourneyStore(opts.dir);
94
145
  const registry = new JourneyRegistry(store);
95
146
  const stored = await registry.get(opts.id);
@@ -113,6 +164,8 @@ export async function runJourneyProgrammatically(opts) {
113
164
  // the cost (or risk) of opening a browser.
114
165
  const inputParams = journey === full ? opts.params : prefixParams(full, journey, opts.params);
115
166
  validateParams(deriveParamSchema(journey.recording), inputParams);
167
+ // #402: the mutated copy replays (and is judged); the stored Journey still names, redacts and gates.
168
+ const replayed = mutation.mutateJourney === undefined || journey !== full ? journey : mutation.mutateJourney(journey);
116
169
  const policy = opts.policy ?? safeRunPolicy();
117
170
  // The site policy (`jevitate site policy set <origin>`): pacing, throttles, budgets, quiet hours —
118
171
  // decided before any fixture or browser; a refusal says why and when to retry.
@@ -169,33 +222,91 @@ export async function runJourneyProgrammatically(opts) {
169
222
  try {
170
223
  const actor = CastActor.named("cli-runner").whoCan(new BrowseTheWeb(session, allowedOrigins), ...gate.abilities);
171
224
  const replayDeltas = opts.actionDeltas === true ? new ReplayDeltas({ secrets, recorded: flat.map((f) => f.recorded) }) : undefined;
172
- const observer = composeObservers(replayDeltas?.observer(), opts.observer, shots === undefined ? undefined : screenshotObserver(shots, (a) => a.ability(BrowseTheWebToken).session.page, whatOf));
173
- const interpreter = opts.interpreter ?? (opts.observer === undefined && shots === undefined && replayDeltas === undefined ? new RecordingInterpreter() : new RecordingInterpreter({ observer }));
225
+ // #322/#400: a full replay (never an anchored prefix) must also satisfy the Journey's end state
226
+ // and each step's request expectations.
227
+ const outcomeChecks = new JourneyOutcomeChecks(session.page, journey === full ? replayed : undefined, { secrets });
228
+ const blocker = mutation.blockWritesAtStep === undefined ? undefined : await stepWriteBlocker(session.page, mutation.blockWritesAtStep, allowedOrigins);
229
+ const observer = composeObservers(outcomeChecks.observer(), blocker?.observer, replayDeltas?.observer(), opts.observer, shots === undefined ? undefined : screenshotObserver(shots, (a) => a.ability(BrowseTheWebToken).session.page, whatOf));
230
+ const skipStep = mutation.skipStep;
231
+ const interpreter = opts.interpreter ??
232
+ (opts.observer === undefined && shots === undefined && replayDeltas === undefined && outcomeChecks.observer() === undefined && blocker === undefined && skipStep === undefined
233
+ ? new RecordingInterpreter()
234
+ : new RecordingInterpreter({ observer, ...(skipStep === undefined ? {} : { skipStep: (i) => skipStep(i) }) }));
174
235
  const runner = new JourneyRunner(actor, interpreter, undefined, undefined, opts.selfHealer);
175
236
  let result;
176
237
  try {
177
- // #322: a full replay (never an anchored prefix) must also satisfy the Journey's network checks.
178
- const networkChecks = journey === full ? journey.metadata.networkChecks : undefined;
179
- result = redactSecretParams(await withNetworkChecks(session.page, networkChecks, () => runner.run({ journey, params, policy })), journey, params);
238
+ result = redactSecretParams(await outcomeChecks.run(actor, () => runner.run({ journey: replayed, params, policy })), journey, params);
180
239
  }
181
240
  finally {
182
241
  await gate.done();
242
+ await blocker?.guard.disarm();
183
243
  }
244
+ const mutationFields = {
245
+ ...(mutation.structuredFailures === true ? { outcomeFailures: redactSecretParams([...outcomeChecks.lastFailures], journey, params) } : {}),
246
+ ...(blocker === undefined ? {} : { blockedWrites: blocker.guard.drain() }),
247
+ };
184
248
  const shotFields = shots === undefined ? {} : await shots.finish();
185
249
  const deltaFields = replayDeltas === undefined ? {} : { actionDeltas: redactSecretParams(replayDeltaSummary(replayDeltas), journey, params) };
186
250
  // #245: the context closed (its video finalized) before the result naming it is returned.
187
251
  const videos = await finalizeVideos(videoDir, closeSession);
188
252
  if (fx === undefined)
189
- return { ...result, ...videos, ...shotFields, ...deltaFields };
253
+ return { ...result, ...videos, ...shotFields, ...deltaFields, ...mutationFields };
190
254
  await fx.restore();
191
- return { ...result, ...videos, ...shotFields, ...deltaFields, fixtures: fx.record() };
255
+ // #399: a fixture output passed in as a secret param (`--param t='${setup.t}'`) is redacted here too.
256
+ return { ...result, ...videos, ...shotFields, ...deltaFields, ...mutationFields, fixtures: redactSecretParams(fx.record(), journey, params) };
192
257
  }
193
258
  finally {
194
259
  await closeSession();
195
260
  }
196
261
  }
262
+ catch (err) {
263
+ // #399: an error escaping the run (a crash, a closed page) may echo a navigated URL that
264
+ // carried a secret parameter — its message and stack are redacted, its class kept.
265
+ throw redactErrorSecrets(err, secretParamValues(journey, { ...inputParams, ...params }));
266
+ }
197
267
  finally {
198
268
  await fx?.restore();
199
269
  }
200
270
  }
271
+ /** How long one blocked step's window stays open for the writes its action triggers (network settle). */
272
+ const BLOCK_WINDOW_SETTLE_MS = 5_000;
273
+ /**
274
+ * #402 block-write: a `ReadOnlyGuard` armed on the page whose action window is ONLY the given step —
275
+ * opened as it begins, closed once the network settled after it. Every held write is aborted (a
276
+ * write navigation too: never answered with a status that could read as a success).
277
+ */
278
+ async function stepWriteBlocker(page, index, allowedOrigins) {
279
+ const guard = new ReadOnlyGuard(writeClassifier({}), { allowlist: [...allowedOrigins], navigationWrites: "abort" });
280
+ await guard.arm(page);
281
+ const monitor = monitorFor(page);
282
+ await monitor.instrument();
283
+ return {
284
+ guard,
285
+ observer: {
286
+ beforeStep: async ({ index: i }) => {
287
+ if (i === index)
288
+ guard.beginAction();
289
+ },
290
+ afterStep: async ({ index: i }) => {
291
+ if (i !== index)
292
+ return;
293
+ try {
294
+ await monitor.waitSettled({ ceilingMs: BLOCK_WINDOW_SETTLE_MS });
295
+ }
296
+ finally {
297
+ guard.settled();
298
+ }
299
+ },
300
+ },
301
+ };
302
+ }
303
+ /** `err` with every secret (and its URL-encoded forms) masked in its message and stack — same object, same class. */
304
+ export function redactErrorSecrets(err, secrets) {
305
+ if (!(err instanceof Error) || secrets.length === 0)
306
+ return err;
307
+ err.message = redactText(err.message, secrets);
308
+ if (err.stack !== undefined)
309
+ err.stack = redactText(err.stack, secrets);
310
+ return err;
311
+ }
201
312
  //# sourceMappingURL=journey-api.js.map