@gitroll/tacitt-cli 0.6.10 → 0.6.11

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 (53) hide show
  1. package/README.md +43 -4
  2. package/dist/{agent-setup-LPd8WEsN.mjs → agent-setup-DdfF3zch.mjs} +2 -2
  3. package/dist/agent-setup-worker.mjs +5 -5
  4. package/dist/cli.mjs +22 -8
  5. package/dist/codex-title-worker.mjs +1 -1
  6. package/dist/configured-preview-worker.mjs +5 -5
  7. package/dist/configured-session-worker.mjs +9 -7
  8. package/dist/{frame-ByT1xayJ.mjs → frame-Bq2zRh-i.mjs} +1 -1
  9. package/dist/{heap-CM298Yu0.mjs → heap-CV8Eca8V.mjs} +212 -10
  10. package/dist/inspection-worker.mjs +4 -4
  11. package/dist/{job-BHIwElNF.mjs → job-BKsifhyv.mjs} +37 -17
  12. package/dist/{locate-Cwv6WH8n.mjs → locate-Cx52Zqxv.mjs} +179 -18
  13. package/dist/{model-zvn5GUJ9.mjs → model-C1UXkHh9.mjs} +1 -1
  14. package/dist/operation-executor.mjs +3 -3
  15. package/dist/preview-worker.mjs +1 -1
  16. package/dist/privacy-validator.mjs +29 -2
  17. package/dist/{query-By41gaKm.mjs → query-AMZerUh8.mjs} +312 -94
  18. package/dist/{run-ZQx7p4Rw.mjs → run-CaPktPhH.mjs} +4 -4
  19. package/dist/{src-BqtdFVq7.mjs → src-D1iONla3.mjs} +1902 -1606
  20. package/dist/{src-3jvMvSQn.mjs → src-DlVbGB32.mjs} +74 -37
  21. package/dist/{src-D_DfY--2.mjs → src-wZx_v0Ih.mjs} +297 -207
  22. package/dist/store-database-worker.mjs +1 -1
  23. package/dist/web/assets/index-CYl7eDUu.js +172 -0
  24. package/dist/web/assets/index-DzOozqms.css +1 -0
  25. package/dist/web/index.html +2 -2
  26. package/package.json +1 -1
  27. package/skills/tacitt/SKILL.md +11 -11
  28. package/skills/tacitt/references/b-method.md +2 -2
  29. package/skills/tacitt/references/bootstrap.md +2 -2
  30. package/skills/tacitt/references/classification.md +3 -3
  31. package/skills/tacitt/references/commands.md +29 -30
  32. package/skills/tacitt/references/journey.md +6 -6
  33. package/skills/tacitt/references/parallel-review.md +27 -33
  34. package/skills/tacitt/references/send.md +9 -4
  35. package/skills/tacitt/scripts/INDEX.md +37 -37
  36. package/skills/tacitt/scripts/answer.mjs +73 -15
  37. package/skills/tacitt/scripts/journey-classification-metadata.mjs +75 -26
  38. package/skills/tacitt/scripts/journey-classification-progress.mjs +1 -2
  39. package/skills/tacitt/scripts/journey-classification.mjs +6 -1
  40. package/skills/tacitt/scripts/journey-consolidation.mjs +1 -0
  41. package/skills/tacitt/scripts/journey-scan-timing.d.mts +1 -0
  42. package/skills/tacitt/scripts/journey-scan-timing.mjs +19 -4
  43. package/skills/tacitt/scripts/journey-state.d.mts +9 -1
  44. package/skills/tacitt/scripts/journey-state.mjs +84 -8
  45. package/skills/tacitt/scripts/journey-tasks.d.mts +1 -0
  46. package/skills/tacitt/scripts/journey-tasks.mjs +13 -5
  47. package/skills/tacitt/scripts/journey.d.mts +44 -31
  48. package/skills/tacitt/scripts/journey.mjs +24 -5
  49. package/skills/tacitt/scripts/privacy-validator.mjs +167 -11
  50. package/skills/tacitt/scripts/review.d.mts +7 -6
  51. package/skills/tacitt/scripts/review.mjs +562 -145
  52. package/dist/web/assets/index-B14e6_-z.js +0 -174
  53. package/dist/web/assets/index-CYJBq1px.css +0 -1
@@ -1,10 +1,11 @@
1
- import { $ as createCleanup, B as parallelismLimits, C as readOriginalSearchFields, F as SessionWorkerError, G as processJourneyMetadataFields, H as privateJourneyDirectory, I as sessionWorkerFailure, K as staticMaskCategory, O as captureLogicalJourneySources, P as createSharedSessionPool, Q as removeNamedTemporarySpools, R as defaultParallelism, T as createCollectionArtifacts, U as readLogicalJourneyFinalMasks, W as readConfiguredOutputInfo, Z as createTemporary, _ as processJourneyMetadata, a as scanReviewFields, at as windowsOwnerOnly, c as readSessionScanRecords, g as readCapturedOriginalRows, i as buildWorkScanBatches, it as restrictToOwner, j as runConfiguredSessionJob, k as capturedJourneyScope, l as scanPreparedSession, m as openConfiguredUpload, n as verifyPreparedSessionSources, nt as makePrivateDirectory, o as createWorkPieces, ot as isWindowsAclFailure, p as configuredSessionUploads, q as readWorkspacePair, r as prepareSessionScans, rt as openPrivateFile, s as readSessionScanFields, st as windowsAclCodes, t as preparedScanConfiguration, v as readStaticSessionMasks, w as readConfiguredSearchFields, y as staticSessionJobs, z as gitSeedsProvider } from "./src-3jvMvSQn.mjs";
2
- import { A as createExecutionPort, C as saveRemoteState, D as createPackagingProgressPort, E as exportDelivery, F as loadDeliveryResults, L as inBackgroundStoreLane, M as withStateMutex, N as createDeliveryService, O as createStore, P as preflight, R as isBackgroundStatusPoll, S as readRemoteState, T as collectRemoteWorkers, _ as staticBaseProduction, a as writeJourneyOutput, b as workApproval, c as mergeHideIndexTokens, f as writeJourneyBase, g as staticBaseInputs, h as staticSessions, l as packRenderHideIndex, m as saveStaticSession, o as createHideIndexTokens, p as readStaticResult, s as hideIndexTexts, t as journeyOutputJob, u as journeyBaseJob, v as readSelectedCaptures, w as journeyDirectory, x as isRemoteImport, y as prepareSourcePlan, z as retrySharingViolation } from "./job-BHIwElNF.mjs";
3
- import { $t as safeField, Bt as sessionPeriodSchema, Dn as createStaticProcessor, Fn as entityDictionaryVersion, Ft as termTypeInfo, Gt as ownerProfileSchema, Kt as passportAddressSchema, Ot as privacyTermKey, Qt as safeCode, Sn as currentDetectorVersion, T as bLiteralTerms, Un as frameRecords, Wt as mergeOwnerProfile, Xt as ApplicationError, Zt as UnfinishedJourneyError, an as observeSource, ar as admitCustomTerm, c as textDigest, ir as createEntityMatcher, nn as reviewFamilies, or as admitGitName, qt as profileHasChoices, tn as familySelected, ur as ProcessingError, zt as filterInventoryByPeriod } from "./locate-Cwv6WH8n.mjs";
4
- import { a as mainTextChunks, c as previewInWorker, i as nearestSortingRepository, l as createNativeAdapters, n as createProjectEvidenceCollector, o as mainTextFields, r as sortingRepositoryId, s as textChunks, t as createHarnesses, u as createGitNameSeedProvider } from "./src-D_DfY--2.mjs";
5
- import { $r as isClassifying, $t as searchScopeDigest, Ar as displayedScopeDigest, At as obsoleteJourney, B as createApplication, Br as staticReviewDigest, Bt as classificationPartition, Ct as sendActionSchema, Dt as sendStartSchema, Et as sendRequestSchema, F as readAccountAvatarUrl, Gr as agentSourcesSchema, H as inspectionStartSchema, Ht as applyClassification, I as storedCredentialSchema, Jn as workDecisionDigest, Jr as sendReceiptSchema, K as inspectionQuerySchema, Kr as journeySendSchema, Ln as bTermMatcher, M as deviceAuthorizationSchema, N as deviceProfileScopeSchema, Nr as assertJourneyPrivacyReady, O as createAccount, P as profileUrlMatchesIdentity, Pr as privacyDecisionDigest, Qr as hasCompleteWorkDescription, St as replaceSensitiveTermsSchema, Tt as sendPrepareSchema, U as validateInspectionBatch, Ut as retireScanGeneration, V as inspectionCancelSchema, Vn as confirmedHideKinds, Vt as classificationWorks, Wn as journeyConfirmedHides, Wr as agentSourceSchema, Wt as newJourneyWork, Xn as workCorrectionBinding, Xr as sendResolutionSchema, Yr as sendReplaceResultSchema, Zr as sendSettingsSchema, Zt as journeySearchResultSchema, _ as changeJourney, _t as manualUploadStartSchema, ai as withReadingProgress, at as appendSensitiveTerms, ci as selectionBatchSchema, cr as scanSessionResourceSchema, di as producerVersion, dr as verifiedScanGitSeeds, dt as validateFailureLocation, ei as isIncludedWork, fr as scanExclusionResourceSchema, ft as manualUploadActionSchema, gt as manualUploadSchema, h as hideScopeDigest, ht as manualUploadPrepareSchema, j as accountProfileSchema, jr as requestedScope, k as renew, kt as currentJourneyAdapters, li as selectionSchema, lr as scanGitSeedsId, lt as artifactFailureDetailSchema, m as activeJourneyTerms, mi as recommendParallelism, mt as manualUploadChoiceSchema, ni as isSelectedWork, nt as settingsQuerySchema, oi as commandSchema, qn as retireWorkDescription, qr as sendArtifactSchema, si as packageCommandSchema, sr as scanPreparationDigest, ti as isReadingComplete, tt as settingsBatchSchema, ur as scanGitSeedsResource, wt as sendChoiceSchema, xt as normalizeSensitiveTerms, yr as emptyPreparationFacts, yt as emptyTermsRevision, zr as hasCurrentStaticReview } from "./src-BqtdFVq7.mjs";
6
- import { _t as isCoordinationBusy } from "./heap-CM298Yu0.mjs";
7
- import { t as createAgentSetupPort } from "./agent-setup-LPd8WEsN.mjs";
1
+ import { $ as removeNamedTemporarySpools, B as parallelismLimits, C as readOriginalSearchFields, F as SessionWorkerError, G as processJourneyMetadataFields, H as privateJourneyDirectory, I as sessionWorkerFailure, K as staticMaskCategory, O as captureLogicalJourneySources, P as createSharedSessionPool, Q as createTemporary, R as defaultParallelism, T as createCollectionArtifacts, U as readLogicalJourneyFinalMasks, W as readConfiguredOutputInfo, Z as decodeZstd, _ as processJourneyMetadata, a as scanReviewFields, at as restrictToOwner, c as readSessionScanRecords, ct as windowsAclCodes, et as createCleanup, g as readCapturedOriginalRows, i as buildWorkScanBatches, it as openPrivateFile, j as runConfiguredSessionJob, k as capturedJourneyScope, l as scanPreparedSession, m as openConfiguredUpload, n as verifyPreparedSessionSources, o as createWorkPieces, ot as windowsOwnerOnly, p as configuredSessionUploads, q as readWorkspacePair, r as prepareSessionScans, rt as makePrivateDirectory, s as readSessionScanFields, st as isWindowsAclFailure, t as preparedScanConfiguration, v as readStaticSessionMasks, w as readConfiguredSearchFields, y as staticSessionJobs, z as gitSeedsProvider } from "./src-DlVbGB32.mjs";
2
+ import { A as createExecutionPort, C as saveRemoteState, D as createPackagingProgressPort, E as exportDelivery, F as loadDeliveryResults, L as inBackgroundStoreLane, M as withStateMutex, N as createDeliveryService, O as createStore, P as preflight, R as isBackgroundStatusPoll, S as readRemoteState, T as collectRemoteWorkers, _ as staticBaseProduction, a as writeJourneyOutput, b as workApproval, c as mergeHideIndexTokens, f as writeJourneyBase, g as staticBaseInputs, h as staticSessions, l as packRenderHideIndex, m as saveStaticSession, o as createHideIndexTokens, p as readStaticResult, s as hideIndexTexts, t as journeyOutputJob, u as journeyBaseJob, v as readSelectedCaptures, w as journeyDirectory, x as isRemoteImport, y as prepareSourcePlan, z as retrySharingViolation } from "./job-BKsifhyv.mjs";
3
+ import { $t as profileHasChoices, D as bLiteralTerms, Gt as filterInventoryByPeriod, Kt as sessionPeriodSchema, Mt as privacyTermKey, Nn as createStaticProcessor, On as currentDetectorVersion, Qt as passportAddressSchema, Vn as entityDictionaryVersion, Vt as termTypeInfo, Xt as mergeOwnerProfile, Yn as frameRecords, Zt as ownerProfileSchema, an as safeField, br as processingSite, cn as reviewFamilies, dn as observeSource, dr as admitCustomTerm, fr as admitGitName, gr as ProcessingError, in as safeCode, nn as ApplicationError, rn as UnfinishedJourneyError, sn as familySelected, u as textDigest, ur as createEntityMatcher, xr as siteFields } from "./locate-Cx52Zqxv.mjs";
4
+ import { a as mainTextChunks, c as previewInWorker, i as nearestSortingRepository, l as createNativeAdapters, n as createProjectEvidenceCollector, o as mainTextFields, r as sortingRepositoryId, s as textChunks, t as createHarnesses, u as createGitNameSeedProvider } from "./src-wZx_v0Ih.mjs";
5
+ import { $r as sendReplaceResultSchema, An as scanGitSeedsResource, At as obsoleteJourney, B as createApplication, Bn as journeySearchResultSchema, Bt as classificationPartition, Ct as sendActionSchema, Dn as scanPreparationDigest, Dt as sendStartSchema, Et as sendRequestSchema, F as readAccountAvatarUrl, H as inspectionStartSchema, Hn as searchScopeDigest, Ht as retireScanGeneration, I as storedCredentialSchema, Ir as assertJourneyPrivacyReady, Jr as agentSourceSchema, K as inspectionQuerySchema, Lr as privacyDecisionDigest, M as deviceAuthorizationSchema, Mn as scanExclusionResourceSchema, N as deviceProfileScopeSchema, Nr as displayedScopeDigest, O as createAccount, On as scanSessionResourceSchema, P as profileUrlMatchesIdentity, Pr as requestedScope, Qr as sendReceiptSchema, St as replaceSensitiveTermsSchema, Tt as sendPrepareSchema, U as validateInspectionBatch, Ur as hasCurrentStaticReview, Ut as newJourneyWork, V as inspectionCancelSchema, Vt as classificationWorks, Wr as staticReviewDigest, Xr as journeySendSchema, Yr as agentSourcesSchema, Zr as sendArtifactSchema, _ as changeJourney, _t as manualUploadStartSchema, ai as isReadingComplete, ar as bTermMatcher, at as appendSensitiveTerms, di as packageCommandSchema, dt as validateFailureLocation, ei as sendResolutionSchema, fi as selectionBatchSchema, fr as journeyConfirmedHides, ft as manualUploadActionSchema, gr as workDecisionDigest, gt as manualUploadSchema, h as hideScopeDigest, hi as producerVersion, hr as retireWorkDescription, ht as manualUploadPrepareSchema, ii as isIncludedWork, j as accountProfileSchema, jn as verifiedScanGitSeeds, k as renew, kn as scanGitSeedsId, kt as currentJourneyAdapters, li as withReadingProgress, lr as confirmedHideKinds, lt as artifactFailureDetailSchema, m as activeJourneyTerms, mt as manualUploadChoiceSchema, ni as hasCompleteWorkDescription, nt as settingsQuerySchema, oi as isSelectedWork, pi as selectionSchema, ri as isClassifying, ti as sendSettingsSchema, tt as settingsBatchSchema, ui as commandSchema, vi as recommendParallelism, vr as workCorrectionBinding, wt as sendChoiceSchema, xt as normalizeSensitiveTerms, yr as emptyPreparationFacts, yt as emptyTermsRevision, zn as applyClassification } from "./src-D1iONla3.mjs";
6
+ import { vt as isCoordinationBusy } from "./heap-CV8Eca8V.mjs";
7
+ import { d as checkOpenSource, h as openSource } from "./run-CaPktPhH.mjs";
8
+ import { t as createAgentSetupPort } from "./agent-setup-DdfF3zch.mjs";
8
9
  import { t as workerMessageSchema } from "./messages-D3m3bhva.mjs";
9
10
  import { constants, write } from "node:fs";
10
11
  import { z } from "zod";
@@ -92,7 +93,7 @@ function permissionDiagnostic(error) {
92
93
  function startupFailure(error, platform = process.platform) {
93
94
  const code = safeCode(error);
94
95
  const permission = permissionDiagnostic(error);
95
- const advice = code === "STATE_CLOSE_FAILED" ? " Keep the state directory intact and stop this CLI before retrying. Closing the state database could not be confirmed; state was not reset." : permission ? " Windows permissions could not be verified. Check that system Windows PowerShell can run and access the entry; retry after resolving the reported helper failure. Permissions were not established as unsafe." : code === "OBSOLETE_SCHEMA" ? " This control-plane database uses an older format. --overwrite-corrupted explicitly resets workflow progress, approvals and scan counts; source logs, completed ZIP files and private sensitive terms are retained." : code === "UNSAFE_STATE_PATH" ? platform === "win32" ? " State requires current-user ownership and a private Windows access control list. Repair permissions explicitly or use a private --state-dir." : " State requires current-user ownership, directory mode 0700 and file mode 0600. Repair permissions explicitly or use a private --state-dir." : ["CORRUPTED_STATE", "RESET_INCOMPLETE"].includes(code) ? " Inspect state first; --overwrite-corrupted explicitly resets only control-plane state." : code === "UNSUPPORTED_SCHEMA" ? " Stop this CLI, then run `npx --yes @gitroll/tacitt-cli@latest` with your original options to start the latest release. If you use the Skill, run `npx --yes @gitroll/tacitt-cli@latest skill.install`, then start a fresh conversation and invoke $tacitt in Codex or /tacitt in Claude Code. State was not reset." : code === "STATE_LAYOUT_INVALID" ? " Keep the state directory intact for inspection. Its storage layout cannot be recovered automatically; --overwrite-corrupted does not bypass this check." : code === "DEV_REQUIRES_SOURCE_CHECKOUT" ? " --dev serves the repository UI through Vite and only works from a source checkout; run without --dev." : code === "ARTIFACT_OUTPUT_UNSAFE" ? " Use a writable private --bundle-dir or repair the configured bundle library. The saved directory was not changed." : code === "STATE_IN_USE" ? " Another tacitt process still has this state open. Stop the other tacitt process and retry; state was not reset." : error instanceof UnfinishedJourneyError ? unfinishedJourneyAdvice(error) : " Run --help for usage.";
96
+ const advice = code === "STATE_CLOSE_FAILED" ? " Keep the state directory intact and stop this CLI before retrying. Closing the state database could not be confirmed; state was not reset." : permission ? " Windows permissions could not be verified. Check that system Windows PowerShell can run and access the entry; retry after resolving the reported helper failure. Permissions were not established as unsafe." : code === "OBSOLETE_SCHEMA" ? " This control-plane database uses an older format. --overwrite-corrupted explicitly resets workflow progress, approvals and scan counts; source logs, completed ZIP files and private sensitive terms are retained." : code === "UNSAFE_STATE_PATH" ? platform === "win32" ? " State requires current-user ownership and a private Windows access control list. Repair permissions explicitly or use a private --state-dir." : " State requires current-user ownership, directory mode 0700 and file mode 0600. Repair permissions explicitly or use a private --state-dir." : ["CORRUPTED_STATE", "RESET_INCOMPLETE"].includes(code) ? " Inspect state first; --overwrite-corrupted explicitly resets only control-plane state." : code === "UNSUPPORTED_SCHEMA" ? " Stop this CLI, then run the same command through `npx --yes @gitroll/tacitt-cli@latest` to use the latest release. If you use the Skill, run `npx --yes @gitroll/tacitt-cli@latest skill.install`, then start a fresh conversation and invoke $tacitt in Codex or /tacitt in Claude Code. State was not reset." : code === "STATE_LAYOUT_INVALID" ? " Keep the state directory intact for inspection. Its storage layout cannot be recovered automatically; --overwrite-corrupted does not bypass this check." : code === "DEV_REQUIRES_SOURCE_CHECKOUT" ? " --dev serves the repository UI through Vite and only works from a source checkout; run without --dev." : code === "ARTIFACT_OUTPUT_UNSAFE" ? " Use a writable private --bundle-dir or repair the configured bundle library. The saved directory was not changed." : code === "STATE_IN_USE" ? " Another tacitt process still has this state open. Stop the other tacitt process and retry; state was not reset." : error instanceof UnfinishedJourneyError ? unfinishedJourneyAdvice(error) : " Run --help for usage.";
96
97
  return `${code}.${code === "OPERATION_FAILED" ? failureCause(error) : ""}${permission}${advice}\n`;
97
98
  }
98
99
  const safeJourneyId = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
@@ -141,6 +142,7 @@ const businessCommands = [
141
142
  "journey.findingImpact",
142
143
  "journey.files",
143
144
  "journey.prepare",
145
+ "review.reopenEmpty",
144
146
  "review.terms",
145
147
  "review.status",
146
148
  "review.guidance",
@@ -342,13 +344,14 @@ function parseOptions(args, source = sourceCheckout) {
342
344
  }
343
345
  const fullHelp = `tacitt — review and package local session logs
344
346
 
345
- Usage: tacitt [--no-open] [--state-dir PATH] [--bundle-dir PATH] [--parallelism N]
346
- [--since DATE] [--until DATE]
347
- tacitt state dump [--state-dir PATH] [--output PATH]
348
- tacitt status [--state-dir PATH]
347
+ Usage: tacitt skill.install [--agent codex|claude|all] [--scope user|project] [--cli-path PATH]
349
348
  tacitt PROCEDURE [--input FILE|-] [--async] [--timeout-ms N]
350
349
  tacitt operation.get|operation.wait|operation.cancel ID [--timeout-ms N]
351
- tacitt skill.install [--agent codex|claude|all] [--scope user|project] [--cli-path PATH]
350
+ tacitt state dump [--state-dir PATH] [--output PATH]
351
+ tacitt status [--state-dir PATH]
352
+
353
+ Tacitt has three surfaces: the Skill (/tacitt in Claude Code, $tacitt in Codex), the Skill
354
+ panel that the Skill opens, and the CLI commands below. Bare tacitt prints how to start the Skill.
352
355
 
353
356
  Business procedures (JSON output):
354
357
  ${Array.from(businessCommands).join("\n")}
@@ -372,12 +375,11 @@ Workspace ZIPs and collection indexes use the saved bundle library directory.
372
375
  Native Skill Send uploads only the confirmed primary artifacts to the configured Tacitt profile API.
373
376
  status prints safe workspace outcomes from the latest saved packaging operation.
374
377
  Private packaging error details stay in <state-dir>/diagnostics/ (up to 4 MiB).
375
- Sign in from the Web UI to link this tool to your GitRoll Identity account; the CLI contacts
378
+ Sign in from the Skill panel to link this tool to your GitRoll Identity account; the CLI contacts
376
379
  only the Identity and profile API origins below, only when you ask, and keeps the issued
377
380
  token in <state-dir>/account/ (owner-only).
378
381
 
379
382
  Options:
380
- --no-open Print the URL without opening a browser
381
383
  --agent CLIENT skill.install only: codex, claude or all (default: all)
382
384
  --scope SCOPE skill.install only: user or project (default: user)
383
385
  --cli-path PATH skill.install only: bind an absolute installed package executable
@@ -388,10 +390,7 @@ Options:
388
390
  --claude-home PATH Override the Claude configuration root
389
391
  --codex-home PATH Override the Codex configuration root
390
392
  --overwrite-corrupted Explicitly reset unusable control-plane state
391
- --parallelism N Configured packaging worker count (default: the Package dialog recommendation)
392
- --since DATE Manual: preselect only sessions last active on or after DATE
393
- --until DATE Manual: preselect only sessions last active on or before DATE
394
- (DATE: YYYY-MM-DD local day, inclusive, or an ISO timestamp)
393
+ --parallelism N Preparation and packaging worker count (default: recommended for this computer)
395
394
  --identity-url URL Identity origin for sign-in (default: ${accountDefaults.identityUrl})
396
395
  --identity-client-id ID Public device client ID registered for this tool
397
396
  --profile-api-url URL Profile API origin the token is checked against
@@ -617,12 +616,13 @@ async function scanRemoteDispatches(sessions, signal) {
617
616
  const pending = /* @__PURE__ */ new Map();
618
617
  for (const part of session.parts) {
619
618
  signal.throwIfAborted();
620
- if (part.representation !== "jsonl") throw new Error("REMOTE_SOURCE_FORMAT_UNSUPPORTED");
621
- const file = await open(part.path, constants.O_RDONLY | constants.O_NOFOLLOW);
619
+ const file = await openSource(part);
622
620
  try {
623
- const before = await file.stat({ bigint: true });
624
- if (String(before.dev) !== part.snapshot.dev || String(before.ino) !== part.snapshot.ino || before.size !== BigInt(part.snapshot.size) || String(before.mtimeNs) !== part.snapshot.mtimeNs || String(before.ctimeNs) !== part.snapshot.ctimeNs) throw new Error("REMOTE_SOURCE_CHANGED");
625
- if (!part.snapshot.size) continue;
621
+ if (!part.snapshot.size) {
622
+ if (part.representation === "jsonl.zst") throw new Error("ARTIFACT_PROCESSING_DECODE_FAILED");
623
+ await checkOpenSource(part, file);
624
+ continue;
625
+ }
626
626
  const stream = file.createReadStream({
627
627
  autoClose: false,
628
628
  start: 0,
@@ -637,7 +637,7 @@ async function scanRemoteDispatches(sessions, signal) {
637
637
  unreadableLines++;
638
638
  cause ??= /* @__PURE__ */ new Error(`line ${location.line}, invalid UTF-8`);
639
639
  };
640
- const lines = frameRecords(numberedChunks(stream, location), "jsonl", 16777216, signal);
640
+ const lines = frameRecords(numberedChunks(part.representation === "jsonl.zst" ? decodeZstd(stream, signal) : stream, location), "jsonl", 16777216, signal);
641
641
  try {
642
642
  for await (const framed of skipLiveTail(lines, location, skipped)) {
643
643
  signal.throwIfAborted();
@@ -671,11 +671,10 @@ async function scanRemoteDispatches(sessions, signal) {
671
671
  end: end && end >= at ? end : at
672
672
  }));
673
673
  windows.push(...found);
674
- pending.set(command.id, found);
674
+ if (found.length) pending.set(command.id, found);
675
675
  }
676
676
  }
677
- const after = await file.stat({ bigint: true });
678
- if (after.mtimeNs !== before.mtimeNs || after.size !== before.size) throw new Error("REMOTE_SOURCE_CHANGED");
677
+ await checkOpenSource(part, file);
679
678
  } finally {
680
679
  stream.destroy();
681
680
  }
@@ -743,7 +742,10 @@ function createRemotePreflight(options) {
743
742
  } catch (error) {
744
743
  signal.throwIfAborted();
745
744
  options.reportFailure(error);
746
- state.failures.push("source-scan");
745
+ if (!state.failures.includes("source-scan")) state.failures.push("source-scan");
746
+ const reason = sourceFailureReason(error);
747
+ const counts = state.sourceFailureCounts ??= {};
748
+ counts[reason] = (counts[reason] ?? 0) + 1;
747
749
  }
748
750
  await saveRemoteState(options.stateDir, input.journeyId, state);
749
751
  }
@@ -780,9 +782,23 @@ function result(state) {
780
782
  })),
781
783
  copied: state.copied.length,
782
784
  failures: state.failures,
785
+ ...state.sourceFailureCounts ? { sourceFailureCounts: state.sourceFailureCounts } : {},
783
786
  ...state.failures.some((failure) => ["source-lines-unreadable", "source-scan"].includes(failure)) ? { warning: state.failures.includes("source-scan") ? "Some remote sources could not be scanned; remote worker discovery may be incomplete." : "Some remote dispatch lines were unreadable; remote worker discovery may be incomplete." } : {}
784
787
  };
785
788
  }
789
+ function sourceFailureReason(error) {
790
+ if (!(error instanceof Error)) return "other";
791
+ if (error instanceof SyntaxError) return "invalid-json";
792
+ if (error.message === "SOURCE_CHANGED") return "source-changed";
793
+ if (error.message === "UNSAFE_SOURCE_PATH") return "source-inaccessible";
794
+ if (error.message === "ARTIFACT_PROCESSING_DECODE_FAILED") return "compressed-decode";
795
+ const code = "code" in error ? error.code : null;
796
+ if (code === "PROCESSING_LIMIT") return "record-limit";
797
+ if (code === "PROCESSING_INVALID_RECORD") return "invalid-utf8";
798
+ if (code === "ENOENT" || code === "ENOTDIR") return "source-missing";
799
+ if (code === "EACCES" || code === "EPERM" || code === "ELOOP") return "source-inaccessible";
800
+ return "other";
801
+ }
786
802
  //#endregion
787
803
  //#region apps/cli/src/journey/confirmed-hides.ts
788
804
  /** The Work's explicit exception values; an occurrence inside one stays visible (ADR 0069). */
@@ -1144,7 +1160,10 @@ function bindJourneyDestination(storage, destination, options = {}) {
1144
1160
  return state;
1145
1161
  };
1146
1162
  const guard = async (id) => {
1147
- if (!verified.has(id)) await current(id);
1163
+ if (verified.has(id)) return;
1164
+ const permission = await storage.preparationPermission(id);
1165
+ if (permission && permission.destination !== destination) throw new ApplicationError("JOURNEY_DESTINATION_CHANGED");
1166
+ if (permission) verified.add(id);
1148
1167
  };
1149
1168
  const readStatus = async (input) => {
1150
1169
  await guard(input.journeyId);
@@ -1152,6 +1171,18 @@ function bindJourneyDestination(storage, destination, options = {}) {
1152
1171
  };
1153
1172
  return {
1154
1173
  current,
1174
+ async preparationState(id) {
1175
+ await guard(id);
1176
+ return storage.preparationState(id);
1177
+ },
1178
+ async preparationPermission(id) {
1179
+ await guard(id);
1180
+ return storage.preparationPermission(id);
1181
+ },
1182
+ async updatePreparation(input) {
1183
+ await guard(input.journeyId);
1184
+ return storage.updatePreparation(input);
1185
+ },
1155
1186
  readInventory: (input) => storage.readInventory({
1156
1187
  ...input,
1157
1188
  expectedDestination: destination
@@ -1270,6 +1301,10 @@ function bindJourneyDestination(storage, destination, options = {}) {
1270
1301
  await guard(input.journeyId);
1271
1302
  return storage.submitReview(input);
1272
1303
  },
1304
+ async reopenEmpty(input) {
1305
+ await guard(input.journeyId);
1306
+ return storage.reopenEmpty(input);
1307
+ },
1273
1308
  async recordTerms(input) {
1274
1309
  await guard(input.journeyId);
1275
1310
  return storage.recordTerms(input);
@@ -1321,6 +1356,31 @@ function bindSendDestination(storage, destination) {
1321
1356
  };
1322
1357
  }
1323
1358
  //#endregion
1359
+ //#region apps/cli/src/journey/prepare/update.ts
1360
+ /** Preparation owns only the changed Work rows, progress and package invalidation. */
1361
+ async function changePreparation(storage, journeyId, kind, change, attempts = Number.POSITIVE_INFINITY, clearPackageApproval = false) {
1362
+ for (let attempt = 0; attempt < attempts; attempt++) {
1363
+ const before = await storage.preparationState(journeyId);
1364
+ if (!before?.consent) throw new ApplicationError("CONFIRMATION_REQUIRED");
1365
+ const after = change(before);
1366
+ const previous = new Map(before.works.map((work) => [work.id, work]));
1367
+ const result = await storage.updatePreparation({
1368
+ journeyId,
1369
+ expectedRevision: before.revision,
1370
+ requestKey: randomUUID(),
1371
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1372
+ kind,
1373
+ works: after.works.filter((work) => work !== previous.get(work.id)),
1374
+ phase: after.phase,
1375
+ readingComplete: after.readingComplete,
1376
+ readingStartedAt: after.readingStartedAt,
1377
+ clearPackageApproval: clearPackageApproval && before !== after
1378
+ });
1379
+ if (result.status !== "stale") return result.state;
1380
+ }
1381
+ throw new ApplicationError("REVISION_CONFLICT");
1382
+ }
1383
+ //#endregion
1324
1384
  //#region apps/cli/src/journey/works/metadata.ts
1325
1385
  /** Original classification fields remain durable; only the derived display cache is replaced. */
1326
1386
  const classificationMetadataInput = (work) => ({
@@ -1368,7 +1428,7 @@ async function prepareClassificationMetadata(storage, state, work, processing, s
1368
1428
  fields: Object.values(raw).map((text) => ({ text }))
1369
1429
  }, signal);
1370
1430
  if (title === void 0 || projectName === void 0 || description === void 0 || reason === void 0) throw new ApplicationError("CORRUPTED_STATE");
1371
- await changeJourney(storage, state.journeyId, "snapshot", (before) => {
1431
+ await changePreparation(storage, state.journeyId, "snapshot", (before) => {
1372
1432
  const active = before.works.find((item) => item.id === work.id);
1373
1433
  if (before.scope.status !== "confirmed" || !active || textDigest(JSON.stringify([
1374
1434
  active.inputRevision,
@@ -1699,7 +1759,7 @@ async function isolateFailingRoots(storage, journeyId, work, error) {
1699
1759
  });
1700
1760
  if (roots.length === 0 || roots.length === work.rootSessionIds.length) return false;
1701
1761
  for (;;) {
1702
- const state = await storage.current(journeyId);
1762
+ const state = await storage.preparationState(journeyId);
1703
1763
  const current = state?.works.find((item) => item.id === work.id);
1704
1764
  if (!state || state.phase === "paused" || state.pendingHideId || current?.inputRevision !== work.inputRevision || current.status !== "pending") return false;
1705
1765
  if ((await storage.isolateRoots({
@@ -1801,7 +1861,7 @@ function scanFailureCode(reason) {
1801
1861
  }
1802
1862
  /** Resolve only the frozen inspection settings and captures; classification stays unchanged. */
1803
1863
  async function prepareScanContext(storage, journeyId, work, selected, processing, signal, workers) {
1804
- const current = await storage.current(journeyId);
1864
+ const current = await storage.preparationState(journeyId);
1805
1865
  if (!current?.consent || !current.snapshotId) throw new ApplicationError("CONFIRMATION_REQUIRED");
1806
1866
  const active = current.works.find((item) => item.id === work.id);
1807
1867
  if (current.scope.status !== "confirmed" || current.phase === "paused" || active?.access === "drop") throw new ApplicationError("OPERATION_CANCELED");
@@ -2127,7 +2187,7 @@ async function prepareScannedWork(storage, plan, work, directory, hooks, observa
2127
2187
  workspaceId: session.workspaceId,
2128
2188
  sessionId: session.id,
2129
2189
  ...failure.sourcePartId ? { sourcePartId: failure.sourcePartId } : {}
2130
- });
2190
+ }, void 0, failure.cause ?? new ApplicationError(scanFailureCode(failure.reason)));
2131
2191
  const root = families.owners.get(failure.sessionId);
2132
2192
  if (root) failures.set(root.id, failure.cause === void 0 ? scanFailureCode(failure.reason) : safeCode(failure.cause));
2133
2193
  }
@@ -2153,7 +2213,7 @@ async function prepareScannedWork(storage, plan, work, directory, hooks, observa
2153
2213
  ...proof,
2154
2214
  rootSessionIds: excludedRoots
2155
2215
  } : void 0;
2156
- await changeJourney(storage, plan.journeyId, "batch", (state) => {
2216
+ await changePreparation(storage, plan.journeyId, "batch", (state) => {
2157
2217
  const target = state.works.find((item) => item.id === work.id);
2158
2218
  if (state.scope.status !== "confirmed" || state.phase === "paused" || target?.access === "drop") throw new ApplicationError("OPERATION_CANCELED");
2159
2219
  if (target?.inputRevision !== work.inputRevision) throw new ApplicationError("REVISION_CONFLICT");
@@ -2212,7 +2272,7 @@ async function observePreparation(storage, input, observation, operation) {
2212
2272
  //#endregion
2213
2273
  //#region apps/cli/src/journey/prepare/work.ts
2214
2274
  async function prepareJourneyWork(storage, plan, work, directory, processing, hooks, workers, onGeneration, onSubmitted) {
2215
- let state = await storage.current(plan.journeyId);
2275
+ let state = await storage.preparationState(plan.journeyId);
2216
2276
  if (!state?.consent || !state.snapshotId) throw new ApplicationError("CONFIRMATION_REQUIRED");
2217
2277
  if (state.scope.status !== "confirmed" || state.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
2218
2278
  const selected = workApproval(plan.approval, work.rootSessionIds);
@@ -2270,9 +2330,9 @@ async function prepareJourneyWork(storage, plan, work, directory, processing, ho
2270
2330
  context ??= await resolveContext();
2271
2331
  return prepareScannedWork(storage, plan, work, directory, {
2272
2332
  ...hooks,
2273
- onFailureLocation(location, detail) {
2333
+ onFailureLocation(location, detail, cause) {
2274
2334
  observation.location(location);
2275
- hooks.onFailureLocation?.(location, detail);
2335
+ hooks.onFailureLocation?.(location, detail, cause);
2276
2336
  }
2277
2337
  }, observation, context, onSubmitted);
2278
2338
  });
@@ -2294,7 +2354,7 @@ async function prepareJourneyWork(storage, plan, work, directory, processing, ho
2294
2354
  */
2295
2355
  async function prepareStaticWork(options) {
2296
2356
  const { storage, journeyId, signal } = options;
2297
- const state = await storage.current(journeyId);
2357
+ const state = await storage.preparationState(journeyId);
2298
2358
  const work = state?.works.find((item) => item.id === options.workId);
2299
2359
  if (!state?.snapshotId || !work || !isSelectedWork(work) || !work.rootSessionIds.length) return;
2300
2360
  if (![
@@ -2366,7 +2426,7 @@ async function prepareStaticWork(options) {
2366
2426
  if (failure) throw failure.error;
2367
2427
  signal.throwIfAborted();
2368
2428
  if (resources.length !== jobs.length) throw new ApplicationError("SCOPE_NOT_READY");
2369
- await changeJourney(storage, journeyId, "batch", (current) => {
2429
+ await changePreparation(storage, journeyId, "batch", (current) => {
2370
2430
  const target = current.works.find((item) => item.id === work.id);
2371
2431
  if (current.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
2372
2432
  if (current.snapshotId !== snapshotId || target?.inputRevision !== work.inputRevision) throw new ApplicationError("REVISION_CONFLICT");
@@ -2403,7 +2463,7 @@ function observeJourneyPause(storage, journeyId, processing, onFailure, requireC
2403
2463
  const closing = new AbortController();
2404
2464
  async function readPermission() {
2405
2465
  for (let attempt = 1;; attempt++) try {
2406
- return await storage.current(journeyId);
2466
+ return await storage.preparationPermission(journeyId);
2407
2467
  } catch (error) {
2408
2468
  if (safeCode(error) !== "COORDINATION_BUSY" || attempt === 4) throw error;
2409
2469
  onFailure(error);
@@ -2476,6 +2536,16 @@ const preparationFailureBindingsSchema = z.object({
2476
2536
  workId: z.uuid(),
2477
2537
  inputRevision: z.number().int().positive().max(Number.MAX_SAFE_INTEGER)
2478
2538
  }).strict();
2539
+ /** Issue #59: the named fields are exactly what siteFields keeps, so no other text passes. */
2540
+ const siteShape = {
2541
+ site: z.string().optional(),
2542
+ harness: z.string().optional(),
2543
+ harnessVersion: z.string().optional()
2544
+ };
2545
+ const onlySiteFields = (value) => {
2546
+ const kept = siteFields(value);
2547
+ return kept?.site === value.site && kept?.harness === value.harness && kept?.harnessVersion === value.harnessVersion;
2548
+ };
2479
2549
  const preparationSourceFailureSchema = z.object({
2480
2550
  location: z.object({
2481
2551
  workspaceId: z.uuid(),
@@ -2483,10 +2553,19 @@ const preparationSourceFailureSchema = z.object({
2483
2553
  sourcePartId: z.uuid().optional()
2484
2554
  }).strict(),
2485
2555
  rootSessionId: z.uuid().nullable(),
2486
- detail: artifactFailureDetailSchema.nullable()
2487
- }).strict().refine(({ location, rootSessionId, detail }) => (location.sessionId === void 0 ? rootSessionId === null && location.sourcePartId === void 0 : rootSessionId !== null) && (detail === null || location.sourcePartId !== void 0));
2556
+ detail: artifactFailureDetailSchema.nullable(),
2557
+ ...siteShape
2558
+ }).strict().refine(onlySiteFields).refine(({ location, rootSessionId, detail }) => (location.sessionId === void 0 ? rootSessionId === null && location.sourcePartId === void 0 : rootSessionId !== null) && (detail === null || location.sourcePartId !== void 0));
2559
+ /** One count per refusal group over every reported source, unlike the bounded list. */
2560
+ const preparationFailureCountSchema = z.object({
2561
+ code: z.string().regex(/^[A-Z_]{3,80}$/u).optional(),
2562
+ ...siteShape,
2563
+ count: z.number().int().positive().max(Number.MAX_SAFE_INTEGER)
2564
+ }).strict().refine(onlySiteFields);
2488
2565
  const preparationFailureContextSchema = preparationFailureBindingsSchema.extend({
2489
2566
  sourceFailures: z.array(preparationSourceFailureSchema).max(16),
2567
+ sourceFailureCounts: z.array(preparationFailureCountSchema).max(32),
2568
+ /** The list or the counts left something out; the counts cover every source otherwise. */
2490
2569
  truncated: z.boolean()
2491
2570
  }).refine((context) => Buffer.byteLength(JSON.stringify(context)) <= maximumPreparationContextBytes);
2492
2571
  /** Invalid diagnostic input must never prevent the independent original-error write. */
@@ -2506,16 +2585,19 @@ function collectPreparationFailures(inventory, rootSessionIds, bindings) {
2506
2585
  let context;
2507
2586
  let families;
2508
2587
  const roots = new Set(rootSessionIds);
2588
+ const counted = [];
2509
2589
  try {
2510
2590
  const parsed = preparationFailureBindingsSchema.safeParse(bindings);
2511
2591
  if (parsed.success) context = {
2512
2592
  ...parsed.data,
2513
2593
  sourceFailures: [],
2594
+ sourceFailureCounts: [],
2514
2595
  truncated: false
2515
2596
  };
2516
2597
  } catch {}
2517
2598
  return {
2518
- report(location, detail) {
2599
+ /** `cause` contributes only its code and the fixed site fields (siteFields), never text. */
2600
+ report(location, detail, cause) {
2519
2601
  if (!context) return;
2520
2602
  try {
2521
2603
  const checked = validateFailureLocation(inventory, location);
@@ -2527,11 +2609,20 @@ function collectPreparationFailures(inventory, rootSessionIds, bindings) {
2527
2609
  sessionId: root.id
2528
2610
  }))) return;
2529
2611
  if (checked.sessionId === void 0 && !inventory.sessions.some((session) => session.workspaceId === checked.workspaceId && roots.has(families?.owners.get(session.id)?.id ?? ""))) return;
2612
+ const named = processingSite(cause);
2613
+ if (!counted.some((seen) => contains(seen, checked) || contains(checked, seen))) {
2614
+ counted.push(checked);
2615
+ context = countFailure(context, {
2616
+ ...cause === void 0 ? {} : { code: safeCode(cause) },
2617
+ ...named
2618
+ });
2619
+ }
2530
2620
  const facts = artifactFailureDetailSchema.safeParse(detail);
2531
2621
  const parsed = preparationSourceFailureSchema.safeParse({
2532
2622
  location: checked,
2533
2623
  rootSessionId: root?.id ?? null,
2534
- detail: facts.success ? facts.data : null
2624
+ detail: facts.success ? facts.data : null,
2625
+ ...named
2535
2626
  });
2536
2627
  if (!parsed.success) return;
2537
2628
  const entry = parsed.data;
@@ -2556,13 +2647,50 @@ function collectPreparationFailures(inventory, rootSessionIds, bindings) {
2556
2647
  }
2557
2648
  };
2558
2649
  }
2650
+ /** Adds one source to its (code, site, harness, version) group; a group past the bound truncates. */
2651
+ function countFailure(context, group) {
2652
+ const same = JSON.stringify({
2653
+ ...group,
2654
+ count: 0
2655
+ });
2656
+ const index = context.sourceFailureCounts.findIndex((entry) => JSON.stringify({
2657
+ ...entry,
2658
+ count: 0
2659
+ }) === same);
2660
+ if (index < 0 && context.sourceFailureCounts.length >= 32) return {
2661
+ ...context,
2662
+ truncated: true
2663
+ };
2664
+ const next = {
2665
+ ...context,
2666
+ sourceFailureCounts: index < 0 ? [...context.sourceFailureCounts, {
2667
+ ...group,
2668
+ count: 1
2669
+ }] : context.sourceFailureCounts.map((entry, at) => at === index ? {
2670
+ ...entry,
2671
+ count: entry.count + 1
2672
+ } : entry)
2673
+ };
2674
+ return readPreparationFailureContext(next) ? next : {
2675
+ ...context,
2676
+ truncated: true
2677
+ };
2678
+ }
2559
2679
  //#endregion
2560
2680
  //#region apps/cli/src/journey/prepare.ts
2681
+ const pendingSelected = (work) => work.status === "pending" && isSelectedWork(work);
2682
+ /** A retried Work is pending even if a previous generation produced a static receipt. */
2683
+ function initialScanWorks(state, scanRequested) {
2684
+ if (!scanRequested || state.scope.status !== "confirmed") return [];
2685
+ const selected = state.works.filter(isSelectedWork);
2686
+ if (!selected.some(pendingSelected)) return selected;
2687
+ return selected.filter((work) => work.status === "pending" || ["reading", "review"].includes(work.status) && !hasCurrentStaticReview(state, work));
2688
+ }
2561
2689
  function createJourneyPreparation(options) {
2562
2690
  return async (journeyId, signal) => {
2563
2691
  const { storage } = options;
2564
2692
  if (signal?.aborted) throw new ApplicationError("OPERATION_CANCELED");
2565
- const current = await storage.current(journeyId);
2693
+ const current = await storage.preparationState(journeyId);
2566
2694
  if (!current?.consent) throw new ApplicationError("CONFIRMATION_REQUIRED");
2567
2695
  if (obsoleteJourney(current)) throw new ApplicationError("JOURNEY_OBSOLETE");
2568
2696
  if (current.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
@@ -2585,10 +2713,11 @@ function createJourneyPreparation(options) {
2585
2713
  terms: []
2586
2714
  };
2587
2715
  const plan = await prepareSourcePlan(storage, options.harnesses, journeyId, terms, abort.signal, current.period, options.stateDir);
2588
- const initial = await storage.current(journeyId);
2716
+ const initial = await storage.preparationState(journeyId);
2589
2717
  if (!initial?.consent) throw new ApplicationError("CONFIRMATION_REQUIRED");
2590
2718
  const formed = initial.snapshotId ? [] : namedJourneyWorks(formJourneyWorks(plan.approval, options.processing.environment), options.processing, abort.signal);
2591
- const state = await changeJourney(storage, journeyId, "snapshot", (before) => ({
2719
+ let state;
2720
+ if (!initial.snapshotId) state = await changeJourney(storage, journeyId, "snapshot", (before) => ({
2592
2721
  ...before,
2593
2722
  snapshotId: before.snapshotId ?? textDigest(JSON.stringify(plan)),
2594
2723
  ...before.snapshotId ? {} : {
@@ -2596,17 +2725,22 @@ function createJourneyPreparation(options) {
2596
2725
  reviewMethod: "dictionary"
2597
2726
  },
2598
2727
  works: before.snapshotId ? before.works : formed,
2599
- phase: before.phase === "paused" ? "paused" : !before.snapshotId || before.works.some((work) => work.status === "pending" && isSelectedWork(work)) ? "processing" : before.phase
2728
+ phase: before.phase !== "paused" && (!before.snapshotId || before.works.some(pendingSelected)) ? "processing" : before.phase
2600
2729
  }));
2730
+ else if (initial.phase !== "processing" && initial.phase !== "paused" && initial.works.some(pendingSelected)) state = await changePreparation(storage, journeyId, "snapshot", (before) => before.phase !== "paused" && before.works.some(pendingSelected) ? {
2731
+ ...before,
2732
+ phase: "processing"
2733
+ } : before);
2734
+ else state = initial;
2601
2735
  if (!state.works.length) throw new ApplicationError("INVALID_SELECTION");
2602
2736
  if (!scanRequested) await captureSortingScope(storage, journeyId, workApproval(plan.approval, state.works.flatMap((work) => work.rootSessionIds)), directory, abort.signal);
2603
- const scanWorks = scanRequested && state.scope.status === "confirmed" ? state.works : [];
2737
+ const scanWorks = initialScanWorks(state, scanRequested);
2604
2738
  if (scanWorks.length) run = await runWorkers(options.workers);
2605
2739
  const frozen = state;
2606
2740
  const blockedForRetry = new Set(state.works.filter((work) => work.status === "blocked").map((work) => `${work.id}:${work.inputRevision}`));
2607
2741
  const deferredForHide = /* @__PURE__ */ new Set();
2608
2742
  const blockWork = async (work, error) => {
2609
- const blocked = await changeJourney(storage, journeyId, "snapshot", (before) => {
2743
+ const blocked = await changePreparation(storage, journeyId, "snapshot", (before) => {
2610
2744
  if (before.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
2611
2745
  if (before.phase === "sending" || before.phase === "completed") return before;
2612
2746
  return withReadingProgress({
@@ -2645,7 +2779,7 @@ function createJourneyPreparation(options) {
2645
2779
  abort.signal.throwIfAborted();
2646
2780
  let resumed = false;
2647
2781
  let deferred = false;
2648
- const committed = await changeJourney(storage, journeyId, "snapshot", (before) => {
2782
+ const committed = await changePreparation(storage, journeyId, "snapshot", (before) => {
2649
2783
  resumed = false;
2650
2784
  deferred = false;
2651
2785
  const target = before.works.find((item) => item.id === work.id);
@@ -2654,17 +2788,13 @@ function createJourneyPreparation(options) {
2654
2788
  deferred = !resumed;
2655
2789
  return withReadingProgress({
2656
2790
  ...before,
2657
- privacy: {
2658
- ...before.privacy,
2659
- packageApproval: null
2660
- },
2661
2791
  works: before.works.map((item) => item.id === work.id ? {
2662
2792
  ...item,
2663
2793
  status: resumed ? work.status : "pending",
2664
2794
  blockedCodes: []
2665
2795
  } : item)
2666
2796
  });
2667
- }, 100);
2797
+ }, 100, true);
2668
2798
  if (deferred && committed.pendingHideId && committed.works.some((item) => generation(item) === generation(work) && item.status === "pending")) deferredForHide.add(generation(work));
2669
2799
  return resumed;
2670
2800
  };
@@ -2684,7 +2814,7 @@ function createJourneyPreparation(options) {
2684
2814
  };
2685
2815
  /** D2: the person dropped this Work while it was prepared; the other Works go on. */
2686
2816
  const droppedMidScan = async (workId) => {
2687
- const now = await storage.current(journeyId);
2817
+ const now = await storage.preparationState(journeyId);
2688
2818
  const work = now?.works.find((item) => item.id === workId);
2689
2819
  return Boolean(now && now.phase !== "paused" && now.scope.status === "confirmed" && work && !isSelectedWork(work));
2690
2820
  };
@@ -2693,7 +2823,7 @@ function createJourneyPreparation(options) {
2693
2823
  /** True when failing roots left the Work, which is then prepared again (ADR 0188). */
2694
2824
  const prepareWorkAttempt = async (planned, onSubmitted, releaseRead) => {
2695
2825
  abort.signal.throwIfAborted();
2696
- const latest = await storage.current(journeyId) ?? frozen;
2826
+ const latest = await storage.preparationState(journeyId) ?? frozen;
2697
2827
  abort.signal.throwIfAborted();
2698
2828
  if (latest.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
2699
2829
  let work = latest.works.find((item) => item.id === planned.id);
@@ -2717,7 +2847,7 @@ function createJourneyPreparation(options) {
2717
2847
  signal: abort.signal,
2718
2848
  onTemporary: async () => {},
2719
2849
  onPhase: async () => {},
2720
- onFailureLocation: (location) => failures.report(location),
2850
+ onFailureLocation: (location, _detail, cause) => failures.report(location, void 0, cause),
2721
2851
  onTiming: (timing) => options.onTiming?.(planned.id, timing)
2722
2852
  }, run.workers, (revised) => {
2723
2853
  work = revised;
@@ -2745,7 +2875,7 @@ function createJourneyPreparation(options) {
2745
2875
  readRetries.set(generation(work), attempt + 1);
2746
2876
  return prepareWorkOnce(planned, onSubmitted);
2747
2877
  }
2748
- const now = await storage.current(journeyId);
2878
+ const now = await storage.preparationState(journeyId);
2749
2879
  if (now?.phase === "sending" || now?.phase === "completed") return false;
2750
2880
  if (blockedCodes(error, code).includes("PRIVACY_CHANGE_PENDING")) {
2751
2881
  if (work) await blockWork(work, error);
@@ -2775,7 +2905,7 @@ function createJourneyPreparation(options) {
2775
2905
  const staticWork = async (planned, passSubmitted) => {
2776
2906
  await passSubmitted;
2777
2907
  abort.signal.throwIfAborted();
2778
- const latest = await storage.current(journeyId);
2908
+ const latest = await storage.preparationState(journeyId);
2779
2909
  abort.signal.throwIfAborted();
2780
2910
  if (!latest?.consent || latest.phase === "paused" || latest.scope.status !== "confirmed") throw new ApplicationError("OPERATION_CANCELED");
2781
2911
  const work = latest.works.find((item) => item.id === planned.id);
@@ -2803,7 +2933,7 @@ function createJourneyPreparation(options) {
2803
2933
  staticRetries.set(generation(work), attempt + 1);
2804
2934
  return staticWork(planned, passSubmitted);
2805
2935
  }
2806
- const now = await storage.current(journeyId);
2936
+ const now = await storage.preparationState(journeyId);
2807
2937
  if (now?.phase !== "sending" && now?.phase !== "completed" && (attempt >= retryLimit || blockedCodes(error, safeCode(error)).includes("PRIVACY_CHANGE_PENDING"))) await blockWork(work, error);
2808
2938
  }
2809
2939
  };
@@ -2881,7 +3011,7 @@ function createJourneyPreparation(options) {
2881
3011
  for (;;) {
2882
3012
  abort.signal.throwIfAborted();
2883
3013
  const settled = workSettled.promise;
2884
- const latest = await storage.current(journeyId);
3014
+ const latest = await storage.preparationState(journeyId);
2885
3015
  if (!latest || latest.phase === "paused" || latest.scope.status !== "confirmed") return [];
2886
3016
  for (const work of latest.works) if (work.status === "blocked") blockedForRetry.add(generation(work));
2887
3017
  const ready = latest.works.filter((work) => isSelectedWork(work) && [
@@ -2902,7 +3032,8 @@ function createJourneyPreparation(options) {
2902
3032
  }
2903
3033
  };
2904
3034
  try {
2905
- for (let batch = scanWorks; batch.length && !fatal; batch = await later()) {
3035
+ const firstBatch = scanRequested && !scanWorks.length && isClassifying(state) ? await later() : scanWorks;
3036
+ for (let batch = firstBatch; batch.length && !fatal; batch = await later()) {
2906
3037
  if (!run.workers && !run.pool) run = await runWorkers(options.workers);
2907
3038
  await pass(batch);
2908
3039
  }
@@ -2921,7 +3052,7 @@ function createJourneyPreparation(options) {
2921
3052
  });
2922
3053
  if (fatal) throw fatal.error;
2923
3054
  abort.signal.throwIfAborted();
2924
- const result = await storage.current(journeyId);
3055
+ const result = await storage.preparationState(journeyId);
2925
3056
  await stopObserving();
2926
3057
  abort.signal.throwIfAborted();
2927
3058
  if (!result?.consent || result.phase === "paused" || scanRequested && result.scope.status !== "confirmed") throw new ApplicationError("OPERATION_CANCELED");
@@ -4786,14 +4917,18 @@ function createClassificationProposal(storage, accountAccess) {
4786
4917
  };
4787
4918
  const { works } = classificationWorks(state, prepared);
4788
4919
  const chunkSessions = await resolveChunkSessions(storage, state, works, signal);
4789
- const access = late ? await accountAccess?.({
4920
+ const read = late ? await accountAccess?.({
4790
4921
  ...state,
4791
4922
  works
4792
4923
  }, signal) : void 0;
4924
+ const sameAccount = read?.status === "read" && state.accountRead?.status === "read" && state.accountRead.subject === read.subject;
4793
4925
  return storage.commitClassification({
4794
4926
  ...prepared,
4795
4927
  chunkSessions,
4796
- ...access ? { accountAccess: Object.fromEntries(access) } : {}
4928
+ ...sameAccount ? {
4929
+ accountAccess: Object.fromEntries(read.access),
4930
+ accountSubject: read.subject
4931
+ } : read ? { accountRead: read.status === "read" ? "unavailable" : read.status } : {}
4797
4932
  });
4798
4933
  };
4799
4934
  }
@@ -6204,7 +6339,7 @@ async function sessionAccessBlocked(dependencies, journeyId, items, inventory, s
6204
6339
  for (const source of sources) if (source.logicalWorkId !== item.workId && source.access !== item.access) record(item, key, "held-elsewhere", source);
6205
6340
  const sha256 = item.sessionMembership ? sources[0]?.sessions.find((session) => session.key === key)?.sha256 : item.sessionUpload?.sha256;
6206
6341
  if (sha256) claimContent(key, sha256, item);
6207
- if (destination && destination.access !== item.access && !destination.sessions.some((entry) => entry.key === key && (item.sessionMembership || entry.sha256 === sha256))) record(item, key, "destination-access", destination);
6342
+ if (destination && destination.access !== item.access) record(item, key, "destination-access", destination);
6208
6343
  }
6209
6344
  }
6210
6345
  for (const item of items) {
@@ -6233,7 +6368,7 @@ async function sessionAccessBlocked(dependencies, journeyId, items, inventory, s
6233
6368
  observe(item, work.access);
6234
6369
  if (work.logicalWorkId !== item.workId && work.access !== item.access) record(item, entry.key, "held-elsewhere", work);
6235
6370
  }
6236
- if (destination && destination.access !== item.access && !destination.sessions.some((entry) => entry.key === key && entry.sha256 === session.sha256)) record(item, key, "destination-access", destination);
6371
+ if (destination && destination.access !== item.access) record(item, key, "destination-access", destination);
6237
6372
  }
6238
6373
  }
6239
6374
  return [...conflicts.values()].map((conflict) => (accountAccesses.get(conflict.workId)?.size ?? 0) > 1 ? {
@@ -7276,8 +7411,10 @@ async function stepSessionWork(dependencies, journeyId, context, item, connectio
7276
7411
  break;
7277
7412
  }
7278
7413
  }
7414
+ const ownKeys = /* @__PURE__ */ new Set();
7279
7415
  for await (const session of sessions(journeyId, item.artifact, signal)) {
7280
7416
  const identity = await keys.add(session);
7417
+ ownKeys.add(identity.key);
7281
7418
  if (recoverLegacy && (identity.legacyKey !== pending?.key || session.sha256 !== pending?.sha256)) continue;
7282
7419
  const key = recoverLegacy && pending ? pending.key : identity.key;
7283
7420
  if (stored.get(key)?.sha256 === session.sha256) {
@@ -7303,13 +7440,23 @@ async function stepSessionWork(dependencies, journeyId, context, item, connectio
7303
7440
  const assembly = await remote.assembly(item.workId, signal);
7304
7441
  const current = (await remote.inventory(signal)).works.find((work) => work.logicalWorkId === item.workId);
7305
7442
  if (current && current.access !== item.access) {
7306
- const resolution = await connection.resolve(resolveInput(item, context.bindingId), signal);
7307
- const [candidate] = resolution.candidates;
7308
- if (resolution.status === "blocked" || candidate?.access === "removed") throw new ApplicationError("WORK_REMOVED");
7309
- if (resolution.status !== "matched" || resolution.logicalWorkId !== item.workId || resolution.candidates.length !== 1 || !candidate || context.bindingId !== void 0 && candidate.bindingId !== context.bindingId) throw new ApplicationError("INVALID_STATE");
7310
- const settings = await connection.preview(candidate.workId, item.access, signal);
7443
+ if (current.access === "removed") throw new ApplicationError("WORK_REMOVED");
7444
+ const [ownKey] = ownKeys;
7445
+ if (!ownKey) throw new ApplicationError("INVALID_STATE");
7311
7446
  await guard();
7312
- await connection.confirm(settings, signal);
7447
+ return {
7448
+ ...item,
7449
+ status: "blocked",
7450
+ error: "SESSION_MOVE_ACCESS_REQUIRED",
7451
+ accessConflict: {
7452
+ sessionKey: ownKey,
7453
+ requestedAccess: item.access,
7454
+ accountWorkId: current.logicalWorkId,
7455
+ accountAccess: current.access,
7456
+ rule: "destination-access"
7457
+ },
7458
+ accessConflictSessionCount: ownKeys.size
7459
+ };
7313
7460
  }
7314
7461
  await guard();
7315
7462
  return {
@@ -7752,10 +7899,21 @@ async function advanceSend(dependencies, address, signal, chain) {
7752
7899
  error: "SEND_SCOPE_CHANGED"
7753
7900
  })
7754
7901
  });
7902
+ const invalidated = safeCode(error) === "SEND_SCOPE_INVALIDATED";
7903
+ const answered = (item) => {
7904
+ const work = snapshot.journey.works.find((work) => work.id === item.workId);
7905
+ return !work || work.access !== item.access;
7906
+ };
7907
+ const answeredSessions = new Set(snapshot.send.items.flatMap((item) => item.error === "SESSION_MOVE_ACCESS_REQUIRED" && item.accessConflict && answered(item) ? [item.accessConflict.sessionKey] : []));
7755
7908
  return blockSend({
7756
- send: safeCode(error) === "SEND_SCOPE_INVALIDATED" ? {
7909
+ send: invalidated ? {
7757
7910
  ...snapshot.send,
7758
- items: snapshot.send.items.filter((item) => !["SESSION_MOVE_ACCESS_REQUIRED", "SESSION_MOVE_ACCESS_HELD"].includes(item.error ?? ""))
7911
+ items: snapshot.send.items.filter((item) => {
7912
+ if (item.error === "SESSION_MOVE_ACCESS_HELD") return false;
7913
+ if (item.error !== "SESSION_MOVE_ACCESS_REQUIRED") return true;
7914
+ if (item.accessConflict?.accountAccess === null && answeredSessions.has(item.accessConflict.sessionKey)) return false;
7915
+ return !answered(item);
7916
+ })
7759
7917
  } : snapshot.send,
7760
7918
  failure: dependencies.failure,
7761
7919
  save
@@ -7803,7 +7961,7 @@ async function advanceSend(dependencies, address, signal, chain) {
7803
7961
  if (item.sessionAssembly || item.status === "completed") return item;
7804
7962
  const { accessConflict: _previous, accessConflictSessionCount: _count, ...current } = item;
7805
7963
  const itemConflicts = conflicts.filter((entry) => entry.workId === item.workId);
7806
- const conflict = itemConflicts[0];
7964
+ const conflict = itemConflicts.find((entry) => entry.accountAccess !== null) ?? itemConflicts[0];
7807
7965
  if (!conflict) return {
7808
7966
  ...current,
7809
7967
  status: "blocked",
@@ -9708,10 +9866,12 @@ function createJourneySender(dependencies, phases = 3, host = {
9708
9866
  try {
9709
9867
  const conflicts = inventory ? await sessionAccessBlocked(dependencies, input.journeyId, draft.items, inventory, signal) : [];
9710
9868
  for (const item of draft.items) {
9711
- const conflict = conflicts.find((entry) => entry.workId === item.workId && entry.accountAccess !== null) ?? conflicts.find((entry) => entry.workId === item.workId);
9869
+ const itemConflicts = conflicts.filter((entry) => entry.workId === item.workId);
9870
+ const conflict = itemConflicts.find((entry) => entry.accountAccess !== null) ?? itemConflicts[0];
9712
9871
  if (conflict) {
9713
9872
  const { workId: _, ...accessConflict } = conflict;
9714
9873
  item.accessConflict = accessConflict;
9874
+ item.accessConflictSessionCount = new Set(itemConflicts.map((entry) => entry.sessionKey)).size;
9715
9875
  }
9716
9876
  }
9717
9877
  } catch (error) {
@@ -9735,8 +9895,11 @@ function createJourneySender(dependencies, phases = 3, host = {
9735
9895
  }
9736
9896
  }
9737
9897
  for (const item of observed ? [] : draft.items) {
9738
- const previous = snapshot.send?.items.find((entry) => entry.workId === item.workId && entry.access === item.access)?.accessConflict;
9739
- if (previous && !item.accessConflict) item.accessConflict = previous;
9898
+ const previous = snapshot.send?.items.find((entry) => entry.workId === item.workId && entry.access === item.access);
9899
+ if (previous?.accessConflict && !item.accessConflict) {
9900
+ item.accessConflict = previous.accessConflict;
9901
+ item.accessConflictSessionCount = previous.accessConflictSessionCount;
9902
+ }
9740
9903
  }
9741
9904
  const canReuseExistingSend = !(snapshot.send?.confirmedAt && ["blocked", "paused"].includes(snapshot.send.status)) && !recover && snapshot.send && scopeDigest(snapshot.journey) === snapshot.send.scopeDigest && snapshot.send.subject === draft.subject;
9742
9905
  const next = pending ? {
@@ -10093,6 +10256,27 @@ function createJourneySendAuth(account, requestFetch) {
10093
10256
  }
10094
10257
  //#endregion
10095
10258
  //#region apps/cli/src/account/journey/account-access.ts
10259
+ /**
10260
+ * Whole scope-confirm account read: identity, every inventory page and a token refresh. A 203-Work,
10261
+ * 1,400-session account took 2.3 to 3.4 s for its pages plus a refresh, past the earlier 5 s
10262
+ * (issue #53). On expiry the read is `unavailable` (ADR 0277 section 5).
10263
+ */
10264
+ const accountReadBoundMs = 3e4;
10265
+ const ownerBodyErrors = /* @__PURE__ */ new Set(["OWNER_EMPTY_RESPONSE", "OWNER_RESPONSE_LIMIT"]);
10266
+ /** Why the read failed, from error types and codes only; messages are never logged. */
10267
+ function failureClass(error, depth = 0) {
10268
+ if (!(error instanceof Error)) return "other";
10269
+ if (error.name === "TimeoutError") return "timeout";
10270
+ if (isCode(error, "ACCOUNT_SIGNED_OUT") || isCode(error, "IDENTITY_REJECTED")) return "signed-out";
10271
+ if (error instanceof JourneyRemoteError) {
10272
+ if (error.status === 401 || error.status === 403) return "signed-out";
10273
+ return error.status >= 500 || error.status === 429 ? "network" : "protocol";
10274
+ }
10275
+ if (isCode(error, "IDENTITY_UNAVAILABLE") && isInterrupted(error.cause)) return failureClass(error.cause, 1);
10276
+ if (isCode(error, "IDENTITY_UNAVAILABLE") || error instanceof SyntaxError || ownerBodyErrors.has(error.message)) return "protocol";
10277
+ if (isInterrupted(error)) return "network";
10278
+ return depth === 0 && error.cause !== void 0 ? failureClass(error.cause, 1) : "other";
10279
+ }
10096
10280
  /** Current and legacy upload keys of every part of one local session. */
10097
10281
  async function keysOf(session) {
10098
10282
  const keys = [];
@@ -10146,9 +10330,13 @@ async function accountAccessForRoots(state, roots, inventory, members) {
10146
10330
  }
10147
10331
  return result;
10148
10332
  }
10149
- /** Existing authenticated owner ports supply inventory; signed-out scope confirmation stays local. */
10333
+ /**
10334
+ * Existing authenticated owner ports supply inventory. The result is three-way (ADR 0277 section
10335
+ * 5): a caller stops on `signed-out` or `unavailable` instead of using model access.
10336
+ */
10150
10337
  function createAccountAccess(storage, auth) {
10151
10338
  return async (state, signal) => {
10339
+ const started = Date.now();
10152
10340
  const controller = new AbortController();
10153
10341
  const boundedSignal = AbortSignal.any([signal, controller.signal]);
10154
10342
  let timer;
@@ -10157,12 +10345,12 @@ function createAccountAccess(storage, auth) {
10157
10345
  onAbort = () => reject(/* @__PURE__ */ new Error("ACCOUNT_ACCESS_CANCELLED"));
10158
10346
  boundedSignal.addEventListener("abort", onAbort, { once: true });
10159
10347
  if (boundedSignal.aborted) onAbort();
10160
- timer = setTimeout(() => controller.abort(), 5e3);
10348
+ timer = setTimeout(() => controller.abort(), accountReadBoundMs);
10161
10349
  });
10162
10350
  const read = async () => {
10163
10351
  boundedSignal.throwIfAborted();
10164
10352
  const connection = await auth.connect(boundedSignal);
10165
- if (!connection?.sessions) return /* @__PURE__ */ new Map();
10353
+ if (!connection?.sessions) return { status: "signed-out" };
10166
10354
  const plan = (await storage.resources({
10167
10355
  journeyId: state.journeyId,
10168
10356
  kind: "plan"
@@ -10171,13 +10359,24 @@ function createAccountAccess(storage, auth) {
10171
10359
  const inventory = await connection.sessions.inventory(boundedSignal);
10172
10360
  const access = await accountAccessForRoots(state, plan.approval.inventory.sessions, inventory, reviewFamilies(plan.approval.inventory).members);
10173
10361
  await connection.assertCurrent(boundedSignal);
10174
- return access;
10362
+ return {
10363
+ status: "read",
10364
+ subject: connection.subject,
10365
+ access
10366
+ };
10175
10367
  };
10176
10368
  try {
10177
10369
  return await Promise.race([read(), stopped]);
10178
- } catch {
10179
- console.error("ACCOUNT_ACCESS_UNAVAILABLE: using model access");
10180
- return /* @__PURE__ */ new Map();
10370
+ } catch (error) {
10371
+ if (error instanceof ApplicationError && ![
10372
+ "ACCOUNT_SIGNED_OUT",
10373
+ "IDENTITY_REJECTED",
10374
+ "IDENTITY_UNAVAILABLE",
10375
+ "CREDENTIAL_STORE_FAILED"
10376
+ ].includes(error.code)) throw error;
10377
+ const reason = signal.aborted ? "cancelled" : controller.signal.aborted ? "timeout" : failureClass(error);
10378
+ console.error(`ACCOUNT_ACCESS_UNAVAILABLE: account read failed (class=${reason} elapsedMs=${Date.now() - started})`);
10379
+ return { status: reason === "signed-out" ? "signed-out" : "unavailable" };
10181
10380
  } finally {
10182
10381
  clearTimeout(timer);
10183
10382
  boundedSignal.removeEventListener("abort", onAbort);
@@ -11313,7 +11512,8 @@ function createDiagnostics(stateDir, writeSummary, onFatal) {
11313
11512
  code: safeCode(error),
11314
11513
  at: (/* @__PURE__ */ new Date()).toISOString(),
11315
11514
  stage,
11316
- ...operationId ? { operationId } : {}
11515
+ ...operationId ? { operationId } : {},
11516
+ ...processingSite(error)
11317
11517
  };
11318
11518
  summary(diagnostic);
11319
11519
  if (closed) {
@@ -11349,14 +11549,18 @@ function createDiagnostics(stateDir, writeSummary, onFatal) {
11349
11549
  }
11350
11550
  },
11351
11551
  onDiagnostic(diagnostic, error) {
11352
- summary(diagnostic);
11552
+ const named = {
11553
+ ...diagnostic,
11554
+ ...processingSite(error)
11555
+ };
11556
+ summary(named);
11353
11557
  if (closed) {
11354
- unavailable(diagnostic);
11558
+ unavailable(named);
11355
11559
  return;
11356
11560
  }
11357
11561
  try {
11358
11562
  local.logger.error({
11359
- ...diagnostic,
11563
+ ...named,
11360
11564
  error: errorDetail(error)
11361
11565
  });
11362
11566
  } catch {
@@ -11872,6 +12076,13 @@ async function compose(options, callbacks, bundles) {
11872
12076
  }),
11873
12077
  journeyLocal: {
11874
12078
  accountAccess,
12079
+ async accountState() {
12080
+ const view = await account.status();
12081
+ return {
12082
+ configured: view.configuration.clientId !== null,
12083
+ subject: view.state === "signed-in" ? view.identityId : null
12084
+ };
12085
+ },
11875
12086
  titles: createJourneyTitles(journeyStorage, processing),
11876
12087
  describe: createJourneyDescription(journeyStorage, processing),
11877
12088
  works: createJourneyWorks(journeyStorage, processing, stateDir, accountAccess),
@@ -12143,7 +12354,14 @@ async function invoke(runtime, command, value, signal) {
12143
12354
  case "journey.findingImpact": return app.journey?.findingImpact(value);
12144
12355
  case "journey.preview": return app.journey?.preview(value);
12145
12356
  case "journey.files": return app.journey?.files(value);
12146
- case "journey.prepare": return app.journey?.prepare(value, signal);
12357
+ case "journey.prepare": {
12358
+ const input = z.object({
12359
+ journeyId: z.uuid(),
12360
+ result: z.literal("receipt").optional()
12361
+ }).strict().parse(value);
12362
+ return input.result === "receipt" ? app.journey?.prepareReceipt({ journeyId: input.journeyId }, signal) : app.journey?.prepare({ journeyId: input.journeyId }, signal);
12363
+ }
12364
+ case "review.reopenEmpty": return app.journey?.reopenEmpty(value);
12147
12365
  case "review.terms": return app.journey?.terms(value);
12148
12366
  case "review.status": return app.journey?.reviewStatus(value);
12149
12367
  case "review.guidance": return app.journey?.guidance(value);