@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.
- package/README.md +43 -4
- package/dist/{agent-setup-LPd8WEsN.mjs → agent-setup-DdfF3zch.mjs} +2 -2
- package/dist/agent-setup-worker.mjs +5 -5
- package/dist/cli.mjs +22 -8
- package/dist/codex-title-worker.mjs +1 -1
- package/dist/configured-preview-worker.mjs +5 -5
- package/dist/configured-session-worker.mjs +9 -7
- package/dist/{frame-ByT1xayJ.mjs → frame-Bq2zRh-i.mjs} +1 -1
- package/dist/{heap-CM298Yu0.mjs → heap-CV8Eca8V.mjs} +212 -10
- package/dist/inspection-worker.mjs +4 -4
- package/dist/{job-BHIwElNF.mjs → job-BKsifhyv.mjs} +37 -17
- package/dist/{locate-Cwv6WH8n.mjs → locate-Cx52Zqxv.mjs} +179 -18
- package/dist/{model-zvn5GUJ9.mjs → model-C1UXkHh9.mjs} +1 -1
- package/dist/operation-executor.mjs +3 -3
- package/dist/preview-worker.mjs +1 -1
- package/dist/privacy-validator.mjs +29 -2
- package/dist/{query-By41gaKm.mjs → query-AMZerUh8.mjs} +312 -94
- package/dist/{run-ZQx7p4Rw.mjs → run-CaPktPhH.mjs} +4 -4
- package/dist/{src-BqtdFVq7.mjs → src-D1iONla3.mjs} +1902 -1606
- package/dist/{src-3jvMvSQn.mjs → src-DlVbGB32.mjs} +74 -37
- package/dist/{src-D_DfY--2.mjs → src-wZx_v0Ih.mjs} +297 -207
- package/dist/store-database-worker.mjs +1 -1
- package/dist/web/assets/index-CYl7eDUu.js +172 -0
- package/dist/web/assets/index-DzOozqms.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/skills/tacitt/SKILL.md +11 -11
- package/skills/tacitt/references/b-method.md +2 -2
- package/skills/tacitt/references/bootstrap.md +2 -2
- package/skills/tacitt/references/classification.md +3 -3
- package/skills/tacitt/references/commands.md +29 -30
- package/skills/tacitt/references/journey.md +6 -6
- package/skills/tacitt/references/parallel-review.md +27 -33
- package/skills/tacitt/references/send.md +9 -4
- package/skills/tacitt/scripts/INDEX.md +37 -37
- package/skills/tacitt/scripts/answer.mjs +73 -15
- package/skills/tacitt/scripts/journey-classification-metadata.mjs +75 -26
- package/skills/tacitt/scripts/journey-classification-progress.mjs +1 -2
- package/skills/tacitt/scripts/journey-classification.mjs +6 -1
- package/skills/tacitt/scripts/journey-consolidation.mjs +1 -0
- package/skills/tacitt/scripts/journey-scan-timing.d.mts +1 -0
- package/skills/tacitt/scripts/journey-scan-timing.mjs +19 -4
- package/skills/tacitt/scripts/journey-state.d.mts +9 -1
- package/skills/tacitt/scripts/journey-state.mjs +84 -8
- package/skills/tacitt/scripts/journey-tasks.d.mts +1 -0
- package/skills/tacitt/scripts/journey-tasks.mjs +13 -5
- package/skills/tacitt/scripts/journey.d.mts +44 -31
- package/skills/tacitt/scripts/journey.mjs +24 -5
- package/skills/tacitt/scripts/privacy-validator.mjs +167 -11
- package/skills/tacitt/scripts/review.d.mts +7 -6
- package/skills/tacitt/scripts/review.mjs +562 -145
- package/dist/web/assets/index-B14e6_-z.js +0 -174
- package/dist/web/assets/index-CYJBq1px.css +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { $ as
|
|
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-
|
|
3
|
-
import { $t as
|
|
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-
|
|
5
|
-
import { $r as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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`
|
|
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 [--
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
621
|
-
const file = await open(part.path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
619
|
+
const file = await openSource(part);
|
|
622
620
|
try {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
...
|
|
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
|
|
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
|
|
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
|
-
|
|
7307
|
-
const [
|
|
7308
|
-
if (
|
|
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
|
-
|
|
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:
|
|
7909
|
+
send: invalidated ? {
|
|
7757
7910
|
...snapshot.send,
|
|
7758
|
-
items: snapshot.send.items.filter((item) =>
|
|
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
|
|
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)
|
|
9739
|
-
if (previous && !item.accessConflict)
|
|
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
|
-
/**
|
|
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(),
|
|
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
|
|
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
|
|
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
|
-
|
|
10180
|
-
|
|
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
|
-
|
|
11552
|
+
const named = {
|
|
11553
|
+
...diagnostic,
|
|
11554
|
+
...processingSite(error)
|
|
11555
|
+
};
|
|
11556
|
+
summary(named);
|
|
11353
11557
|
if (closed) {
|
|
11354
|
-
unavailable(
|
|
11558
|
+
unavailable(named);
|
|
11355
11559
|
return;
|
|
11356
11560
|
}
|
|
11357
11561
|
try {
|
|
11358
11562
|
local.logger.error({
|
|
11359
|
-
...
|
|
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":
|
|
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);
|