@gitroll/tacitt-cli 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-setup-Cb_spmGb.mjs → agent-setup-BZbgLgHg.mjs} +2 -2
- package/dist/agent-setup-worker.mjs +5 -5
- package/dist/cli.mjs +37 -10
- package/dist/codex-title-worker.mjs +1 -1
- package/dist/configured-preview-worker.mjs +5 -5
- package/dist/configured-session-worker.mjs +5 -5
- package/dist/{frame-waS5FnN5.mjs → frame-DGun31Uc.mjs} +1 -1
- package/dist/{heap-eZTo0VYj.mjs → heap-NDnGBXH6.mjs} +6 -6
- package/dist/inspection-worker.mjs +4 -4
- package/dist/{job-Bi57uRU-.mjs → job-BO0a67HQ.mjs} +383 -8
- package/dist/{locate-U3-nkird.mjs → locate-JYtFmKu-.mjs} +120 -3
- package/dist/{model-BDrntocp.mjs → model-CmHypB0p.mjs} +1 -1
- package/dist/operation-executor.mjs +3 -3
- package/dist/preview-worker.mjs +1 -1
- package/dist/privacy-validator.mjs +1 -1
- package/dist/{query-7uY266qY.mjs → query-BZvNsP1u.mjs} +1272 -265
- package/dist/{run-CV6Io7CS.mjs → run-DvRM-NR8.mjs} +4 -4
- package/dist/{src-DSDRF7i6.mjs → src-C1SjCDx0.mjs} +65 -4
- package/dist/{src-DIuh0LHk.mjs → src-CQeUBG2Y.mjs} +168 -7
- package/dist/{src-BESjwh7X.mjs → src-xidy0yF7.mjs} +5 -4
- package/dist/store-database-worker.mjs +1 -1
- package/dist/web/assets/index-Bbck2aOB.css +1 -0
- package/dist/web/assets/index-D0j-mvf1.js +69 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/skills/tacitt/SKILL.md +4 -4
- package/skills/tacitt/references/INDEX.md +11 -10
- package/skills/tacitt/references/commands.md +13 -0
- package/skills/tacitt/references/journey.md +4 -4
- package/skills/tacitt/references/remote-workers.md +46 -0
- package/skills/tacitt/references/send.md +41 -11
- package/skills/tacitt/scripts/INDEX.md +36 -36
- package/skills/tacitt/scripts/journey-card-consolidation.mjs +2 -4
- package/skills/tacitt/scripts/journey-state.d.mts +30 -0
- package/skills/tacitt/scripts/journey-state.mjs +56 -3
- package/skills/tacitt/scripts/journey-tasks.d.mts +8 -0
- package/skills/tacitt/scripts/journey-tasks.mjs +4 -1
- package/skills/tacitt/scripts/privacy-validator.mjs +78 -3
- package/skills/tacitt/scripts/review.mjs +36 -5
- package/dist/web/assets/index-D5DEG5Ue.css +0 -1
- package/dist/web/assets/index-Dh4iG6x2.js +0 -69
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Bt as sessionPeriodSchema, Dn as
|
|
3
|
-
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-
|
|
4
|
-
import { $ as settingsQuerySchema, $r as packageCommandSchema, A as deviceProfileScopeSchema, B as validateInspectionBatch, Bn as journeyConfirmedHides, Br as hasCompleteWorkDescription, Bt as newJourneyWork, C as rebindReviewSubmit, Cr as privacyDecisionDigest, Ct as sendStartSchema, D as createAccount, Dr as hasCurrentStaticReview, E as staticallyHiddenFindings, Er as nativeRespondSchema, Et as obsoleteJourney, Fn as confirmedHideGroupIds, Fr as sendArtifactSchema, Gr as withReadingProgress, Hr as isReadingComplete, Ht as journeyContextQuerySchema, Ir as sendReceiptSchema, Jn as reviewCandidateResourceSchema, Jr as journeyFindingsSchema, Kn as workCorrectionBinding, L as createApplication, Ln as confirmedHideKinds, Lr as sendReplaceResultSchema, Lt as classificationPartition, M as readAccountAvatarUrl, Mr as agentSourceSchema, Mt as applyClassification, N as storedCredentialSchema, Nn as bTermMatcher, Nr as agentSourcesSchema, O as accountProfileSchema, Or as staticReviewDigest, Pr as journeySendSchema, Pt as journeyReviewStorageQuerySchema, Q as settingsBatchSchema, Qn as bReviewDigest, Qr as commandSchema, Qt as journeyResourceQuerySchema, R as inspectionCancelSchema, Rr as sendResolutionSchema, Rt as classificationWorks, S as rebindReviewOverlay, Sn as readConfirmedHideChange, Sr as assertJourneyPrivacyReady, St as sendPrepareSchema, T as guardedFindingText, Tn as termHideAdditions, Tr as nativeGetSchema, Tt as currentJourneyAdapters, U as inspectionQuerySchema, Un as retireWorkDescription, Ur as isSelectedWork, Vn as maskedHitsStale, Vr as isClassifying, Vt as reviewCompileSchema, Wn as workDecisionDigest, Xn as reviewRepairResourceSchema, Xr as journeyRenderInputSchema, Xt as journeyPolicyRespondSchema, Yn as reviewRepairInputSchema, Yr as journeyPreviewSchema, Yt as searchScopeDigest, Zr as journeyRenderSchema, _t as maximumTermsFileBytes, a as bCrossSegmentReviewSchema, ar as scanGitSeedsId, b as bReviewScope, bn as confirmedHideAdditions, br as journeyClassificationSessionsSchema, bt as sendActionSchema, c as reconstructBFields, cr as scanExclusionResourceSchema, ct as validateFailureLocation, d as selectedValueMembers, dn as sessionReviewInputSchema, dr as journeyInventoryQuerySchema, dt as manualUploadChoiceSchema, ei as selectionBatchSchema, er as planBRepair, et as readWorkspaceOutcomes, f as splitSelectorValues, fn as sessionReviewQuerySchema, ft as manualUploadPrepareSchema, g as changeJourney, gt as emptyTermsRevision, hn as sessionUsefulnessInputSchema, hr as emptyPreparationFacts, i as validateBCrossSegmentReview, ii as stateSchema, ir as scanSessionResourceSchema, j as profileUrlMatchesIdentity, k as deviceAuthorizationSchema, ln as sessionRereadInputSchema, lt as manualUploadActionSchema, m as hideScopeDigest, mt as manualUploadStartSchema, n as scanBatchEquivalenceDigest, nt as appendSensitiveTerms, o as bRereadDigest, oi as recommendParallelism, on as sessionReconcileInputSchema, or as scanGitSeedsResource, ot as artifactFailureDetailSchema, p as activeJourneyTerms, pt as manualUploadSchema, qn as reviewCandidateInputSchema, qr as journeyFindingImpactSchema, qt as journeySearchResultSchema, r as locateTermEvidence, ri as producerVersion, rn as reviewAttemptResourceSchema, rr as scanPreparationDigest, s as reconcileBSession, sr as verifiedScanGitSeeds, st as resolveFailureLocation, t as scanBatchContextDigest, ti as selectionSchema, tn as journeyPlaceSchema, vt as normalizeSensitiveTerms, w as reviewBatchReuse, wr as nativeEventsSchema, x as rebindReviewAlignment, xt as sendChoiceSchema, y as isGenericPublicTerm, yt as replaceSensitiveTermsSchema, z as inspectionStartSchema, zr as sendSettingsSchema, zt as retireScanGeneration } from "./src-
|
|
5
|
-
import { $ as checkVersion, A as resourceSelectionQuerySchema, B as termReuseQuerySchema, C as loadAcceptedReview, D as aggregateUsefulness, E as loadPrivateResource, F as validateUnitCandidate, G as requireActiveBatch, H as batchHeader, I as reuseQuerySchema, J as validateDatabase, K as initializeAndClaim, L as batchQuery, M as evaluateUnitEvidence, N as sameCandidateRequest, O as sessionUsefulnessRecommendation, P as validateCandidateAttempt, Q as validateState, R as isPrivateResourceKind, S as validateSessionBindings, T as loadBatch, U as createJourneyPayloadFiles, V as privateReviewHash, W as validateBatchBody, X as readBundleDirectory, Y as withDatabase, Z as readStoredState, _ as loadReconciliation, _t as isCoordinationBusy, a as decodeError, at as hasResetMarker, b as validatePreparationSelection, c as dropsAnchor, ct as privatePath, d as parseDelivery, dt as stateDirectory, et as schemaVersion, f as validateCandidate, ft as syncDirectory, g as loadPreparedBodies, gt as errorCode$1, h as loadPreparation, ht as StoreError, i as createExecutor, it as finishReset, j as inspectionQuerySchema$1, k as sessionQuery, l as replySchema, lt as readResetMarker, m as loadStatus, mt as withFileCheckTimings, n as releaseInheritedHeapFlags, nt as createDatabaseFile, o as ProtocolError, ot as inspectStateFiles, p as classificationJourneyCommit, pt as withStateSnapshot, q as ownership, r as storeHeapLimits, rt as databaseName, s as allowedReplies, st as present, tt as beginReset, u as operations, ut as recheckPrivatePath, v as loadRereadBodies, vt as isSqliteCorruption, w as loadAuthenticatedReview, x as validateSessionReconciliation, y as loadSessionBodies, z as privateResourceHeader } from "./heap-
|
|
6
|
-
import { _ as staticBaseProduction, a as writeJourneyOutput, 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 prepareSourcePlan, y as workApproval } from "./job-
|
|
7
|
-
import { t as createAgentSetupPort } from "./agent-setup-
|
|
1
|
+
import { $ as createCleanup, B as parallelismLimits, C as readOriginalSearchFields, E as checkReusableBundle, F as SessionWorkerError, G as processJourneyMetadataFields, H as privateJourneyDirectory, I as sessionWorkerFailure, K as staticMaskCategory, L as privateCollectionDirectory, M as readCollectionIndex, N as parseCollectionWorkspaceSummary, 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-CQeUBG2Y.mjs";
|
|
2
|
+
import { $t as safeField, Bt as sessionPeriodSchema, Dn as createStaticProcessor, F as bClassificationKey, Fn as entityDictionaryVersion, Ft as termTypeInfo, Gt as ownerProfileSchema, H as codePointLength, Ht as journeyProfileAddressActionSchema, K as utf16Offset, Kt as passportAddressSchema, N as bUnitFragments, Ot as privacyTermKey, Qt as safeCode, Sn as currentDetectorVersion, T as bLiteralTerms, Un as frameRecords, Ut as journeyProfileLinksActionSchema, V as bDigest, Wt as mergeOwnerProfile, Xt as ApplicationError, Zt as UnfinishedJourneyError, an as observeSource, ar as admitCustomTerm, b as validateBReview, c as textDigest, h as reviewFindingsDigest, ht as journeyDescribeSchema, ir as createEntityMatcher, jt as reviewTermsInputSchema, l as validateReview, n as compileBFindings, nn as reviewFamilies, or as admitGitName, qt as profileHasChoices, r as reviewBatchChunks, s as reviewSubmitSchema, t as locateTerms, tn as familySelected, z as bAnchorKey, zt as filterInventoryByPeriod } from "./locate-JYtFmKu-.mjs";
|
|
3
|
+
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-xidy0yF7.mjs";
|
|
4
|
+
import { $ as settingsQuerySchema, $r as packageCommandSchema, A as deviceProfileScopeSchema, B as validateInspectionBatch, Bn as journeyConfirmedHides, Br as hasCompleteWorkDescription, Bt as newJourneyWork, C as rebindReviewSubmit, Cr as privacyDecisionDigest, Ct as sendStartSchema, D as createAccount, Dr as hasCurrentStaticReview, E as staticallyHiddenFindings, Er as nativeRespondSchema, Et as obsoleteJourney, Fn as confirmedHideGroupIds, Fr as sendArtifactSchema, Gr as withReadingProgress, Hr as isReadingComplete, Ht as journeyContextQuerySchema, Ir as sendReceiptSchema, Jn as reviewCandidateResourceSchema, Jr as journeyFindingsSchema, Kn as workCorrectionBinding, L as createApplication, Ln as confirmedHideKinds, Lr as sendReplaceResultSchema, Lt as classificationPartition, M as readAccountAvatarUrl, Mr as agentSourceSchema, Mt as applyClassification, N as storedCredentialSchema, Nn as bTermMatcher, Nr as agentSourcesSchema, O as accountProfileSchema, Or as staticReviewDigest, Pr as journeySendSchema, Pt as journeyReviewStorageQuerySchema, Q as settingsBatchSchema, Qn as bReviewDigest, Qr as commandSchema, Qt as journeyResourceQuerySchema, R as inspectionCancelSchema, Rr as sendResolutionSchema, Rt as classificationWorks, S as rebindReviewOverlay, Sn as readConfirmedHideChange, Sr as assertJourneyPrivacyReady, St as sendPrepareSchema, T as guardedFindingText, Tn as termHideAdditions, Tr as nativeGetSchema, Tt as currentJourneyAdapters, U as inspectionQuerySchema, Un as retireWorkDescription, Ur as isSelectedWork, Vn as maskedHitsStale, Vr as isClassifying, Vt as reviewCompileSchema, Wn as workDecisionDigest, Xn as reviewRepairResourceSchema, Xr as journeyRenderInputSchema, Xt as journeyPolicyRespondSchema, Yn as reviewRepairInputSchema, Yr as journeyPreviewSchema, Yt as searchScopeDigest, Zr as journeyRenderSchema, _t as maximumTermsFileBytes, a as bCrossSegmentReviewSchema, ar as scanGitSeedsId, b as bReviewScope, bn as confirmedHideAdditions, br as journeyClassificationSessionsSchema, bt as sendActionSchema, c as reconstructBFields, cr as scanExclusionResourceSchema, ct as validateFailureLocation, d as selectedValueMembers, dn as sessionReviewInputSchema, dr as journeyInventoryQuerySchema, dt as manualUploadChoiceSchema, ei as selectionBatchSchema, er as planBRepair, et as readWorkspaceOutcomes, f as splitSelectorValues, fn as sessionReviewQuerySchema, ft as manualUploadPrepareSchema, g as changeJourney, gt as emptyTermsRevision, hn as sessionUsefulnessInputSchema, hr as emptyPreparationFacts, i as validateBCrossSegmentReview, ii as stateSchema, ir as scanSessionResourceSchema, j as profileUrlMatchesIdentity, k as deviceAuthorizationSchema, ln as sessionRereadInputSchema, lt as manualUploadActionSchema, m as hideScopeDigest, mt as manualUploadStartSchema, n as scanBatchEquivalenceDigest, nt as appendSensitiveTerms, o as bRereadDigest, oi as recommendParallelism, on as sessionReconcileInputSchema, or as scanGitSeedsResource, ot as artifactFailureDetailSchema, p as activeJourneyTerms, pt as manualUploadSchema, qn as reviewCandidateInputSchema, qr as journeyFindingImpactSchema, qt as journeySearchResultSchema, r as locateTermEvidence, ri as producerVersion, rn as reviewAttemptResourceSchema, rr as scanPreparationDigest, s as reconcileBSession, sr as verifiedScanGitSeeds, st as resolveFailureLocation, t as scanBatchContextDigest, ti as selectionSchema, tn as journeyPlaceSchema, vt as normalizeSensitiveTerms, w as reviewBatchReuse, wr as nativeEventsSchema, x as rebindReviewAlignment, xt as sendChoiceSchema, y as isGenericPublicTerm, yt as replaceSensitiveTermsSchema, z as inspectionStartSchema, zr as sendSettingsSchema, zt as retireScanGeneration } from "./src-C1SjCDx0.mjs";
|
|
5
|
+
import { $ as checkVersion, A as resourceSelectionQuerySchema, B as termReuseQuerySchema, C as loadAcceptedReview, D as aggregateUsefulness, E as loadPrivateResource, F as validateUnitCandidate, G as requireActiveBatch, H as batchHeader, I as reuseQuerySchema, J as validateDatabase, K as initializeAndClaim, L as batchQuery, M as evaluateUnitEvidence, N as sameCandidateRequest, O as sessionUsefulnessRecommendation, P as validateCandidateAttempt, Q as validateState, R as isPrivateResourceKind, S as validateSessionBindings, T as loadBatch, U as createJourneyPayloadFiles, V as privateReviewHash, W as validateBatchBody, X as readBundleDirectory, Y as withDatabase, Z as readStoredState, _ as loadReconciliation, _t as isCoordinationBusy, a as decodeError, at as hasResetMarker, b as validatePreparationSelection, c as dropsAnchor, ct as privatePath, d as parseDelivery, dt as stateDirectory, et as schemaVersion, f as validateCandidate, ft as syncDirectory, g as loadPreparedBodies, gt as errorCode$1, h as loadPreparation, ht as StoreError, i as createExecutor, it as finishReset, j as inspectionQuerySchema$1, k as sessionQuery, l as replySchema, lt as readResetMarker, m as loadStatus, mt as withFileCheckTimings, n as releaseInheritedHeapFlags, nt as createDatabaseFile, o as ProtocolError, ot as inspectStateFiles, p as classificationJourneyCommit, pt as withStateSnapshot, q as ownership, r as storeHeapLimits, rt as databaseName, s as allowedReplies, st as present, tt as beginReset, u as operations, ut as recheckPrivatePath, v as loadRereadBodies, vt as isSqliteCorruption, w as loadAuthenticatedReview, x as validateSessionReconciliation, y as loadSessionBodies, z as privateResourceHeader } from "./heap-NDnGBXH6.mjs";
|
|
6
|
+
import { C as journeyDirectory, S as saveRemoteState, _ as staticBaseProduction, a as writeJourneyOutput, b as isRemoteImport, 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 prepareSourcePlan, w as collectRemoteWorkers, x as readRemoteState, y as workApproval } from "./job-BO0a67HQ.mjs";
|
|
7
|
+
import { t as createAgentSetupPort } from "./agent-setup-BZbgLgHg.mjs";
|
|
8
8
|
import { t as workerMessageSchema } from "./messages-D3m3bhva.mjs";
|
|
9
9
|
import { constants, write } from "node:fs";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import { copyFile, link, lstat, mkdir, mkdtemp, open, opendir, readFile, readdir, realpath, rename, rm, unlink, writeFile } from "node:fs/promises";
|
|
12
12
|
import { execFile, spawn } from "node:child_process";
|
|
13
|
-
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
13
|
+
import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
14
|
+
import { createInterface } from "node:readline";
|
|
14
15
|
import { Writable } from "node:stream";
|
|
15
16
|
import { parseArgs, promisify, types } from "node:util";
|
|
16
17
|
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -27,6 +28,7 @@ import { pipeline } from "node:stream/promises";
|
|
|
27
28
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
28
29
|
import { DatabaseSync } from "node:sqlite";
|
|
29
30
|
import { Worker } from "node:worker_threads";
|
|
31
|
+
import { parse } from "shell-quote";
|
|
30
32
|
import { rgPath } from "@vscode/ripgrep";
|
|
31
33
|
import { pino } from "pino";
|
|
32
34
|
import { SeverityNumber } from "@opentelemetry/api-logs";
|
|
@@ -166,14 +168,14 @@ async function readThroughContention(read, signal, delaysMs = [
|
|
|
166
168
|
}
|
|
167
169
|
//#endregion
|
|
168
170
|
//#region packages/local-host/src/native-inspection.ts
|
|
169
|
-
const count$
|
|
171
|
+
const count$5 = z.number().int().nonnegative();
|
|
170
172
|
const originalViewSchema = z.object({
|
|
171
173
|
journeyId: z.uuid(),
|
|
172
174
|
workId: z.uuid(),
|
|
173
175
|
cursor: z.object({
|
|
174
176
|
snapshotId: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
175
|
-
inputRevision: count$
|
|
176
|
-
partIndex: count$
|
|
177
|
+
inputRevision: count$5,
|
|
178
|
+
partIndex: count$5,
|
|
177
179
|
afterOrdinal: z.number().int().min(-1)
|
|
178
180
|
}).strict().optional()
|
|
179
181
|
}).strict();
|
|
@@ -195,7 +197,7 @@ const outputViewSchema = z.object({
|
|
|
195
197
|
const sourceViewSchema = z.object({
|
|
196
198
|
journeyId: z.uuid(),
|
|
197
199
|
workId: z.uuid(),
|
|
198
|
-
inputRevision: count$
|
|
200
|
+
inputRevision: count$5.positive(),
|
|
199
201
|
sessionId: z.string().min(1).max(128)
|
|
200
202
|
}).strict();
|
|
201
203
|
const masksViewSchema = z.object({
|
|
@@ -563,8 +565,8 @@ function createDeliveryService(application) {
|
|
|
563
565
|
//#endregion
|
|
564
566
|
//#region packages/local-host/src/current-view/model.ts
|
|
565
567
|
const sequenceHeader = "x-sess-pilot-ci-current-sequence";
|
|
566
|
-
const count$
|
|
567
|
-
const identifier$1 = count$
|
|
568
|
+
const count$4 = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
569
|
+
const identifier$1 = count$4.min(1);
|
|
568
570
|
const requestId = z.string().regex(/^req-[a-z0-9]{1,7}$/u);
|
|
569
571
|
const leg = z.enum(["ownership", "procedure"]);
|
|
570
572
|
const operation = z.enum([
|
|
@@ -708,27 +710,27 @@ const rowSchema = z.strictObject({
|
|
|
708
710
|
"stopped"
|
|
709
711
|
]),
|
|
710
712
|
phase: phase.nullable(),
|
|
711
|
-
elapsedMs: count$
|
|
712
|
-
queueMs: count$
|
|
713
|
-
activeMs: count$
|
|
714
|
-
phaseMs: count$
|
|
713
|
+
elapsedMs: count$4,
|
|
714
|
+
queueMs: count$4.nullable(),
|
|
715
|
+
activeMs: count$4.nullable(),
|
|
716
|
+
phaseMs: count$4.nullable()
|
|
715
717
|
});
|
|
716
718
|
const pendingSchema = z.strictObject({
|
|
717
719
|
accessId: identifier$1,
|
|
718
720
|
operation,
|
|
719
721
|
write: z.boolean(),
|
|
720
722
|
phase,
|
|
721
|
-
queueDepth: count$
|
|
722
|
-
queueMs: count$
|
|
723
|
-
activeMs: count$
|
|
724
|
-
phaseMs: count$
|
|
723
|
+
queueDepth: count$4,
|
|
724
|
+
queueMs: count$4,
|
|
725
|
+
activeMs: count$4,
|
|
726
|
+
phaseMs: count$4,
|
|
725
727
|
association: associationSchema.nullable()
|
|
726
728
|
});
|
|
727
729
|
const storeSchema = z.strictObject({
|
|
728
730
|
executor: z.enum(["worker", "inline"]),
|
|
729
|
-
lastAccessId: count$
|
|
730
|
-
dequeuedAccessId: count$
|
|
731
|
-
queueDepth: count$
|
|
731
|
+
lastAccessId: count$4,
|
|
732
|
+
dequeuedAccessId: count$4,
|
|
733
|
+
queueDepth: count$4,
|
|
732
734
|
pending: pendingSchema.nullable()
|
|
733
735
|
});
|
|
734
736
|
const legState = z.enum([
|
|
@@ -743,17 +745,17 @@ const disconnectSchema = z.strictObject({
|
|
|
743
745
|
procedure: z.literal("workflow.getCurrent"),
|
|
744
746
|
requestId,
|
|
745
747
|
sequence: identifier$1.nullable(),
|
|
746
|
-
elapsedMs: count$
|
|
748
|
+
elapsedMs: count$4,
|
|
747
749
|
ownership: legState,
|
|
748
750
|
procedureState: legState,
|
|
749
|
-
pendingAccesses: count$
|
|
750
|
-
truncatedAccesses: count$
|
|
751
|
-
stoppedBeforeEnqueue: count$
|
|
751
|
+
pendingAccesses: count$4,
|
|
752
|
+
truncatedAccesses: count$4,
|
|
753
|
+
stoppedBeforeEnqueue: count$4,
|
|
752
754
|
accesses: z.array(rowSchema).max(16),
|
|
753
755
|
store: storeSchema.nullable(),
|
|
754
|
-
droppedFrames: count$
|
|
755
|
-
droppedSnapshots: count$
|
|
756
|
-
observerFailures: count$
|
|
756
|
+
droppedFrames: count$4,
|
|
757
|
+
droppedSnapshots: count$4,
|
|
758
|
+
observerFailures: count$4
|
|
757
759
|
});
|
|
758
760
|
function parseSequence(value) {
|
|
759
761
|
if (typeof value !== "string" || !/^[1-9][0-9]{0,15}$/u.test(value)) return null;
|
|
@@ -858,7 +860,9 @@ const nativePanelRespondSchema = z.object({
|
|
|
858
860
|
z.object({
|
|
859
861
|
kind: z.literal("retry-work"),
|
|
860
862
|
workId: z.uuid()
|
|
861
|
-
}).strict()
|
|
863
|
+
}).strict(),
|
|
864
|
+
journeyProfileAddressActionSchema,
|
|
865
|
+
journeyProfileLinksActionSchema
|
|
862
866
|
])
|
|
863
867
|
}).strict();
|
|
864
868
|
function createRouter(application, account, stopping, delivery, nativeInspection, agentPresence) {
|
|
@@ -932,7 +936,10 @@ function createRouter(application, account, stopping, delivery, nativeInspection
|
|
|
932
936
|
pause: t.procedure.input(sendActionSchema).mutation(({ input }) => sending().pause(input)),
|
|
933
937
|
resume: t.procedure.input(sendActionSchema).mutation(({ input }) => sending().resume(input)),
|
|
934
938
|
withdraw: t.procedure.input(sendActionSchema).mutation(({ input }) => sending().withdraw(input)),
|
|
935
|
-
advance: t.procedure.input(sendAddress).mutation(({ input }) => sending().advance(input, stopping))
|
|
939
|
+
advance: t.procedure.input(sendAddress).mutation(({ input }) => sending().advance(input, stopping)),
|
|
940
|
+
ownerProfile: t.procedure.query(({ signal }) => sending().ownerProfile(signal ?? stopping)),
|
|
941
|
+
addressTaken: t.procedure.input(z.object({ address: passportAddressSchema }).strict()).query(({ input, signal }) => sending().addressTaken(input, signal ?? stopping)),
|
|
942
|
+
saveSentProfile: t.procedure.input(z.object({ journeyId: z.uuid() }).strict()).mutation(({ input }) => sending().saveSentProfile(input, stopping))
|
|
936
943
|
}),
|
|
937
944
|
manualUpload: t.router({
|
|
938
945
|
get: t.procedure.input(z.object({ uploadId: z.uuid().optional() }).strict()).query(({ input }) => uploading().get(input.uploadId)),
|
|
@@ -5078,10 +5085,10 @@ async function readStateDump(stateDir) {
|
|
|
5078
5085
|
//#endregion
|
|
5079
5086
|
//#region packages/local-host/src/execution/progress.ts
|
|
5080
5087
|
const maximumBytes = 65536;
|
|
5081
|
-
const count$
|
|
5088
|
+
const count$3 = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
5082
5089
|
const scope = z.object({
|
|
5083
|
-
completed: count$
|
|
5084
|
-
total: count$
|
|
5090
|
+
completed: count$3,
|
|
5091
|
+
total: count$3
|
|
5085
5092
|
}).strict().nullable();
|
|
5086
5093
|
const recordSchema = z.object({
|
|
5087
5094
|
databaseId: z.string(),
|
|
@@ -5090,14 +5097,14 @@ const recordSchema = z.object({
|
|
|
5090
5097
|
operationId: z.string(),
|
|
5091
5098
|
step: z.enum(["preparing", "processing"]),
|
|
5092
5099
|
activeWorkspace: z.string().nullable(),
|
|
5093
|
-
workspaceSequence: count$
|
|
5100
|
+
workspaceSequence: count$3,
|
|
5094
5101
|
workspaces: scope,
|
|
5095
5102
|
sessions: scope,
|
|
5096
5103
|
sourceBytes: z.object({
|
|
5097
|
-
processed: count$
|
|
5098
|
-
total: count$
|
|
5104
|
+
processed: count$3,
|
|
5105
|
+
total: count$3
|
|
5099
5106
|
}).strict().nullable(),
|
|
5100
|
-
archiveBytes: count$
|
|
5107
|
+
archiveBytes: count$3
|
|
5101
5108
|
}).strict()
|
|
5102
5109
|
}).strict();
|
|
5103
5110
|
/** Disposable progress only. The workflow claim serializes publishers and matching clears. */
|
|
@@ -5345,6 +5352,7 @@ const businessCommands = [
|
|
|
5345
5352
|
"send.resume",
|
|
5346
5353
|
"send.advance",
|
|
5347
5354
|
"journey.start",
|
|
5355
|
+
"journey.remoteWorkers",
|
|
5348
5356
|
"journey.serve",
|
|
5349
5357
|
"journey.get",
|
|
5350
5358
|
"journey.groupItems",
|
|
@@ -5634,6 +5642,334 @@ function helpText(source = sourceCheckout) {
|
|
|
5634
5642
|
return source ? fullHelp : fullHelp.replace(/^ {2}--dev .*\n/m, "");
|
|
5635
5643
|
}
|
|
5636
5644
|
//#endregion
|
|
5645
|
+
//#region apps/cli/src/remote/dispatch.ts
|
|
5646
|
+
function balancedQuotes(command) {
|
|
5647
|
+
let quote = "";
|
|
5648
|
+
let escaped = false;
|
|
5649
|
+
for (const character of command) {
|
|
5650
|
+
if (escaped) {
|
|
5651
|
+
escaped = false;
|
|
5652
|
+
continue;
|
|
5653
|
+
}
|
|
5654
|
+
if (character === "\\" && quote !== "'") escaped = true;
|
|
5655
|
+
else if (quote === character) quote = "";
|
|
5656
|
+
else if (!quote && (character === "'" || character === "\"")) quote = character;
|
|
5657
|
+
}
|
|
5658
|
+
return !quote && !escaped;
|
|
5659
|
+
}
|
|
5660
|
+
/** Parse inert simple commands. Expansions and unsupported compound syntax fail closed. */
|
|
5661
|
+
function commands$1(command) {
|
|
5662
|
+
if (Buffer.byteLength(command) > 65536 || /`|\$\(|[\0\r]/u.test(command) || !balancedQuotes(command)) return [];
|
|
5663
|
+
try {
|
|
5664
|
+
const segments = [[]];
|
|
5665
|
+
let redirect = false;
|
|
5666
|
+
for (const token of parse(command.replaceAll("\n", ";"), () => "\0")) if (typeof token === "string") {
|
|
5667
|
+
if (token.includes("\0")) return [];
|
|
5668
|
+
if (redirect) redirect = false;
|
|
5669
|
+
else segments.at(-1)?.push(token);
|
|
5670
|
+
} else if ("comment" in token) continue;
|
|
5671
|
+
else if ("op" in token && [
|
|
5672
|
+
">",
|
|
5673
|
+
">>",
|
|
5674
|
+
"<",
|
|
5675
|
+
">&",
|
|
5676
|
+
"<&"
|
|
5677
|
+
].includes(token.op)) {
|
|
5678
|
+
if (redirect) return [];
|
|
5679
|
+
redirect = true;
|
|
5680
|
+
} else if ("op" in token && [
|
|
5681
|
+
";",
|
|
5682
|
+
"&&",
|
|
5683
|
+
"||",
|
|
5684
|
+
"|",
|
|
5685
|
+
"&"
|
|
5686
|
+
].includes(token.op)) segments.push([]);
|
|
5687
|
+
else return [];
|
|
5688
|
+
return redirect ? [] : segments.filter((words) => words.length > 0);
|
|
5689
|
+
} catch {
|
|
5690
|
+
return [];
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5693
|
+
function agent(words) {
|
|
5694
|
+
const executable = posix.basename(words[0] ?? "");
|
|
5695
|
+
if (words.some((word) => [
|
|
5696
|
+
"--help",
|
|
5697
|
+
"-h",
|
|
5698
|
+
"--version"
|
|
5699
|
+
].includes(word))) return null;
|
|
5700
|
+
if (executable === "codex" && words[1] === "exec") return "codex";
|
|
5701
|
+
if (executable === "claude" && words.slice(1).some((word) => word === "-p" || word === "--print")) return "claude";
|
|
5702
|
+
return null;
|
|
5703
|
+
}
|
|
5704
|
+
const validHost = (host) => /^(?:[a-zA-Z0-9_][a-zA-Z0-9_.-]*@)?(?:[a-zA-Z0-9_][a-zA-Z0-9_.-]*|\[[a-fA-F0-9:]+\])$/u.test(host);
|
|
5705
|
+
/** Consent and import identity include every retained connection selector. */
|
|
5706
|
+
function endpoint(host, transport, args) {
|
|
5707
|
+
return transport === "ssh" && args.length === 1 ? host : `${host} (${transport}: ${args.join(" ")})`;
|
|
5708
|
+
}
|
|
5709
|
+
function ssh(words) {
|
|
5710
|
+
const args = [];
|
|
5711
|
+
let index = 1;
|
|
5712
|
+
for (; index < words.length; index++) {
|
|
5713
|
+
const word = words[index] ?? "";
|
|
5714
|
+
if (word === "--") {
|
|
5715
|
+
index++;
|
|
5716
|
+
break;
|
|
5717
|
+
}
|
|
5718
|
+
if (!word.startsWith("-")) break;
|
|
5719
|
+
if (word === "--help" || /^-[^-]*[NVGQOW]/u.test(word)) return null;
|
|
5720
|
+
if (/^-[pLlIiFJoObcDEeRSwWQ]/u.test(word)) {
|
|
5721
|
+
const flag = word.slice(0, 2);
|
|
5722
|
+
const value = word.length > 2 ? word.slice(2) : words[++index];
|
|
5723
|
+
if (!value) return null;
|
|
5724
|
+
if (flag === "-p" && /^\d{1,5}$/u.test(value)) args.push(flag, value);
|
|
5725
|
+
if (flag === "-l" && /^[a-zA-Z0-9_.-]+$/u.test(value)) args.push(flag, value);
|
|
5726
|
+
} else if (!/^-[46AaCfKkMnqsTtVvXxYy]+$/u.test(word)) return null;
|
|
5727
|
+
}
|
|
5728
|
+
const host = words[index];
|
|
5729
|
+
if (!host || !validHost(host) || index + 1 >= words.length) return null;
|
|
5730
|
+
return {
|
|
5731
|
+
host,
|
|
5732
|
+
transport: "ssh",
|
|
5733
|
+
args: [...args, host],
|
|
5734
|
+
command: words.slice(index + 1).join(" ")
|
|
5735
|
+
};
|
|
5736
|
+
}
|
|
5737
|
+
function gcloud(words) {
|
|
5738
|
+
if (words[1] !== "compute" || words[2] !== "ssh") return null;
|
|
5739
|
+
let host;
|
|
5740
|
+
let command;
|
|
5741
|
+
const selectors = [];
|
|
5742
|
+
for (let index = 3; index < words.length; index++) {
|
|
5743
|
+
const word = words[index] ?? "";
|
|
5744
|
+
if (word === "--help" || word === "-h" || word === "--dry-run") return null;
|
|
5745
|
+
if (word === "--command" || word.startsWith("--command=")) command = word === "--command" ? words[++index] : word.slice(10);
|
|
5746
|
+
else if (["--zone", "--project"].some((flag) => word === flag || word.startsWith(`${flag}=`))) {
|
|
5747
|
+
const separator = word.indexOf("=");
|
|
5748
|
+
const flag = separator === -1 ? word : word.slice(0, separator);
|
|
5749
|
+
const value = separator === -1 ? words[++index] : word.slice(separator + 1);
|
|
5750
|
+
if (!value || !/^[a-zA-Z0-9_.:-]+$/u.test(value)) return null;
|
|
5751
|
+
selectors.push(`${flag}=${value}`);
|
|
5752
|
+
} else if (word === "--tunnel-through-iap" || word === "--internal-ip") selectors.push(word);
|
|
5753
|
+
else if (word === "--quiet" || word === "-q") continue;
|
|
5754
|
+
else if (!word.startsWith("-") && !host) host = word;
|
|
5755
|
+
else return null;
|
|
5756
|
+
}
|
|
5757
|
+
if (!host || !validHost(host) || !command) return null;
|
|
5758
|
+
return {
|
|
5759
|
+
host,
|
|
5760
|
+
transport: "gcloud",
|
|
5761
|
+
args: [host, ...selectors],
|
|
5762
|
+
command
|
|
5763
|
+
};
|
|
5764
|
+
}
|
|
5765
|
+
/** Matches executables at shell command positions, never mentions in prose or other commands. */
|
|
5766
|
+
function parseRemoteDispatch(command) {
|
|
5767
|
+
return commands$1(command).flatMap((words) => {
|
|
5768
|
+
const executable = posix.basename(words[0] ?? "");
|
|
5769
|
+
const remote = executable === "ssh" ? ssh(words) : executable === "gcloud" ? gcloud(words) : null;
|
|
5770
|
+
if (!remote) return [];
|
|
5771
|
+
return [...new Set(commands$1(remote.command).map(agent))].flatMap((harness) => harness ? [{
|
|
5772
|
+
host: endpoint(remote.host, remote.transport, remote.args),
|
|
5773
|
+
harness,
|
|
5774
|
+
transport: remote.transport,
|
|
5775
|
+
args: remote.args
|
|
5776
|
+
}] : []);
|
|
5777
|
+
});
|
|
5778
|
+
}
|
|
5779
|
+
//#endregion
|
|
5780
|
+
//#region apps/cli/src/remote/scan.ts
|
|
5781
|
+
function object(value) {
|
|
5782
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5783
|
+
}
|
|
5784
|
+
function timestamp(value) {
|
|
5785
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value)) ? new Date(value).toISOString() : null;
|
|
5786
|
+
}
|
|
5787
|
+
function commands(row) {
|
|
5788
|
+
const payload = row["payload"];
|
|
5789
|
+
if (row["type"] === "response_item" && object(payload) && payload["type"] === "function_call" && typeof payload["name"] === "string" && [
|
|
5790
|
+
"exec_command",
|
|
5791
|
+
"shell_command",
|
|
5792
|
+
"shell"
|
|
5793
|
+
].includes(payload["name"])) {
|
|
5794
|
+
let args = payload["arguments"];
|
|
5795
|
+
if (typeof args === "string") try {
|
|
5796
|
+
args = JSON.parse(args);
|
|
5797
|
+
} catch {
|
|
5798
|
+
return [];
|
|
5799
|
+
}
|
|
5800
|
+
if (!object(args)) return [];
|
|
5801
|
+
const command = args["cmd"] ?? args["command"];
|
|
5802
|
+
return typeof command === "string" ? [{
|
|
5803
|
+
id: typeof payload["call_id"] === "string" ? payload["call_id"] : "",
|
|
5804
|
+
command
|
|
5805
|
+
}] : [];
|
|
5806
|
+
}
|
|
5807
|
+
const message = row["message"];
|
|
5808
|
+
if (row["type"] !== "assistant" || !object(message) || !Array.isArray(message["content"])) return [];
|
|
5809
|
+
return message["content"].flatMap((block) => {
|
|
5810
|
+
if (!object(block) || block["type"] !== "tool_use" || block["name"] !== "Bash" || !object(block["input"]) || typeof block["input"]["command"] !== "string") return [];
|
|
5811
|
+
return [{
|
|
5812
|
+
id: typeof block["id"] === "string" ? block["id"] : "",
|
|
5813
|
+
command: block["input"]["command"]
|
|
5814
|
+
}];
|
|
5815
|
+
});
|
|
5816
|
+
}
|
|
5817
|
+
function resultIds(row) {
|
|
5818
|
+
const payload = row["payload"];
|
|
5819
|
+
if (object(payload) && payload["type"] === "function_call_output" && typeof payload["call_id"] === "string") return [payload["call_id"]];
|
|
5820
|
+
const message = row["message"];
|
|
5821
|
+
if (!object(message) || !Array.isArray(message["content"])) return [];
|
|
5822
|
+
return message["content"].flatMap((block) => object(block) && block["type"] === "tool_result" && typeof block["tool_use_id"] === "string" ? [block["tool_use_id"]] : []);
|
|
5823
|
+
}
|
|
5824
|
+
/** Tool executable positions only. Source bytes stay inside this deterministic CLI scanner. */
|
|
5825
|
+
async function scanRemoteDispatches(sessions, signal) {
|
|
5826
|
+
const windows = [];
|
|
5827
|
+
for (const session of sessions) {
|
|
5828
|
+
const pending = /* @__PURE__ */ new Map();
|
|
5829
|
+
for (const part of session.parts) {
|
|
5830
|
+
signal.throwIfAborted();
|
|
5831
|
+
if (part.representation !== "jsonl") throw new Error("REMOTE_SOURCE_FORMAT_UNSUPPORTED");
|
|
5832
|
+
const file = await open(part.path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
5833
|
+
try {
|
|
5834
|
+
const before = await file.stat({ bigint: true });
|
|
5835
|
+
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");
|
|
5836
|
+
if (!part.snapshot.size) continue;
|
|
5837
|
+
const stream = file.createReadStream({
|
|
5838
|
+
autoClose: false,
|
|
5839
|
+
start: 0,
|
|
5840
|
+
end: part.snapshot.size - 1,
|
|
5841
|
+
signal
|
|
5842
|
+
});
|
|
5843
|
+
const lines = createInterface({
|
|
5844
|
+
input: stream,
|
|
5845
|
+
crlfDelay: Infinity
|
|
5846
|
+
});
|
|
5847
|
+
try {
|
|
5848
|
+
for await (const line of lines) {
|
|
5849
|
+
signal.throwIfAborted();
|
|
5850
|
+
if (line.length > 16777216) throw new Error("REMOTE_SOURCE_RECORD_TOO_LARGE");
|
|
5851
|
+
if (!line.trim()) continue;
|
|
5852
|
+
const row = JSON.parse(line);
|
|
5853
|
+
if (!object(row)) continue;
|
|
5854
|
+
const at = timestamp(row["timestamp"]);
|
|
5855
|
+
if (!at) continue;
|
|
5856
|
+
for (const id of resultIds(row)) {
|
|
5857
|
+
for (const window of pending.get(id) ?? []) if (at >= window.start) window.end = at;
|
|
5858
|
+
pending.delete(id);
|
|
5859
|
+
}
|
|
5860
|
+
for (const command of commands(row)) {
|
|
5861
|
+
const end = timestamp(session.lastInteractionAt);
|
|
5862
|
+
const found = parseRemoteDispatch(command.command).map((dispatch) => ({
|
|
5863
|
+
...dispatch,
|
|
5864
|
+
sourceSessionId: session.id,
|
|
5865
|
+
start: at,
|
|
5866
|
+
end: end && end >= at ? end : at
|
|
5867
|
+
}));
|
|
5868
|
+
windows.push(...found);
|
|
5869
|
+
pending.set(command.id, found);
|
|
5870
|
+
}
|
|
5871
|
+
}
|
|
5872
|
+
const after = await file.stat({ bigint: true });
|
|
5873
|
+
if (after.mtimeNs !== before.mtimeNs || after.size !== before.size) throw new Error("REMOTE_SOURCE_CHANGED");
|
|
5874
|
+
} finally {
|
|
5875
|
+
lines.close();
|
|
5876
|
+
stream.destroy();
|
|
5877
|
+
}
|
|
5878
|
+
} finally {
|
|
5879
|
+
await file.close();
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
return windows;
|
|
5884
|
+
}
|
|
5885
|
+
//#endregion
|
|
5886
|
+
//#region apps/cli/src/remote/preflight.ts
|
|
5887
|
+
const inputSchema = z.object({
|
|
5888
|
+
journeyId: z.uuid(),
|
|
5889
|
+
confirmations: z.record(z.string(), z.boolean()).optional()
|
|
5890
|
+
}).strict();
|
|
5891
|
+
function createRemotePreflight(options) {
|
|
5892
|
+
return async (value, signal = new AbortController().signal) => {
|
|
5893
|
+
const input = inputSchema.parse(value);
|
|
5894
|
+
const current = await options.storage.current(input.journeyId);
|
|
5895
|
+
if (!current?.consent) throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
5896
|
+
if (current.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
|
|
5897
|
+
if (current.snapshotId) return {
|
|
5898
|
+
hosts: [],
|
|
5899
|
+
copied: 0,
|
|
5900
|
+
failures: [],
|
|
5901
|
+
skipped: "already-frozen"
|
|
5902
|
+
};
|
|
5903
|
+
const lease = await options.execution.tryAcquire("workflow");
|
|
5904
|
+
if (!lease) throw new ApplicationError("DISCOVERY_BUSY");
|
|
5905
|
+
try {
|
|
5906
|
+
const admitted = await options.storage.current(input.journeyId);
|
|
5907
|
+
if (!admitted?.consent || admitted.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
|
|
5908
|
+
if (admitted.snapshotId) return {
|
|
5909
|
+
hosts: [],
|
|
5910
|
+
copied: 0,
|
|
5911
|
+
failures: [],
|
|
5912
|
+
skipped: "already-frozen"
|
|
5913
|
+
};
|
|
5914
|
+
let state = await readRemoteState(options.stateDir, input.journeyId);
|
|
5915
|
+
if (!state) {
|
|
5916
|
+
const discovered = await options.harnesses.discover(signal);
|
|
5917
|
+
if (!discovered.complete) throw new ApplicationError("SCOPE_NOT_READY");
|
|
5918
|
+
const inventory = filterInventoryByPeriod(discovered, current.period ?? {});
|
|
5919
|
+
const families = reviewFamilies(inventory);
|
|
5920
|
+
const sessions = inventory.sessions.filter((session) => familySelected(inventory, families, session) && !session.warnings.includes("NON_INTERACTIVE_LAUNCH") && session.parts.every((part) => !isRemoteImport(part.path)));
|
|
5921
|
+
state = {
|
|
5922
|
+
windows: [],
|
|
5923
|
+
decisions: {},
|
|
5924
|
+
parents: Object.fromEntries(sessions.map((session) => [session.id, session.sourceKey])),
|
|
5925
|
+
copied: [],
|
|
5926
|
+
failures: []
|
|
5927
|
+
};
|
|
5928
|
+
for (const session of sessions) try {
|
|
5929
|
+
state.windows.push(...await scanRemoteDispatches([session], signal));
|
|
5930
|
+
} catch (error) {
|
|
5931
|
+
signal.throwIfAborted();
|
|
5932
|
+
options.reportFailure(error);
|
|
5933
|
+
state.failures.push("source-scan");
|
|
5934
|
+
}
|
|
5935
|
+
await saveRemoteState(options.stateDir, input.journeyId, state);
|
|
5936
|
+
}
|
|
5937
|
+
const decisions = input.confirmations ?? {};
|
|
5938
|
+
const known = new Set(state.windows.map((window) => window.host));
|
|
5939
|
+
if (Object.keys(decisions).some((host) => !known.has(host))) throw new ApplicationError("INVALID_ARGUMENTS");
|
|
5940
|
+
const fresh = {};
|
|
5941
|
+
for (const [host, allow] of Object.entries(decisions)) {
|
|
5942
|
+
if (Object.hasOwn(state.decisions, host)) continue;
|
|
5943
|
+
fresh[host] = allow;
|
|
5944
|
+
state.decisions[host] = allow;
|
|
5945
|
+
}
|
|
5946
|
+
await saveRemoteState(options.stateDir, input.journeyId, state);
|
|
5947
|
+
if (Object.keys(fresh).length) {
|
|
5948
|
+
const collected = await collectRemoteWorkers(state.windows, fresh, options.home, options.transport, (_host, error) => options.reportFailure(new Error("REMOTE_COLLECTION_FAILED", { cause: error })), options.roots);
|
|
5949
|
+
state.copied.push(...collected.copied);
|
|
5950
|
+
state.failures.push(...collected.failures);
|
|
5951
|
+
await saveRemoteState(options.stateDir, input.journeyId, state);
|
|
5952
|
+
}
|
|
5953
|
+
return result(state);
|
|
5954
|
+
} finally {
|
|
5955
|
+
await lease.release();
|
|
5956
|
+
}
|
|
5957
|
+
};
|
|
5958
|
+
}
|
|
5959
|
+
function result(state) {
|
|
5960
|
+
return {
|
|
5961
|
+
hosts: [...new Set(state.windows.map((window) => window.host))].filter((host) => !Object.hasOwn(state.decisions, host)).map((host) => ({
|
|
5962
|
+
host,
|
|
5963
|
+
windows: state.windows.filter((window) => window.host === host).map(({ start, end }) => ({
|
|
5964
|
+
start,
|
|
5965
|
+
end
|
|
5966
|
+
}))
|
|
5967
|
+
})),
|
|
5968
|
+
copied: state.copied.length,
|
|
5969
|
+
failures: state.failures
|
|
5970
|
+
};
|
|
5971
|
+
}
|
|
5972
|
+
//#endregion
|
|
5637
5973
|
//#region apps/cli/src/journey/confirmed-hides.ts
|
|
5638
5974
|
/** The Work's explicit exception values; an occurrence inside one stays visible (ADR 0069). */
|
|
5639
5975
|
const workExceptions = (state, workId) => state.exceptions.filter((item) => item.workId === workId).map((item) => item.value);
|
|
@@ -7193,22 +7529,6 @@ async function prepareStaticWork(options) {
|
|
|
7193
7529
|
});
|
|
7194
7530
|
}
|
|
7195
7531
|
//#endregion
|
|
7196
|
-
//#region apps/cli/src/journey/directory.ts
|
|
7197
|
-
async function journeyDirectory(stateDir, journeyId) {
|
|
7198
|
-
let path = stateDir;
|
|
7199
|
-
for (const name of ["journeys", journeyId]) {
|
|
7200
|
-
path = join(path, name);
|
|
7201
|
-
try {
|
|
7202
|
-
await makePrivateDirectory(path);
|
|
7203
|
-
} catch (cause) {
|
|
7204
|
-
if (!(cause instanceof Error && "code" in cause && cause.code === "EEXIST")) throw cause;
|
|
7205
|
-
}
|
|
7206
|
-
const stat = await lstat(path, { bigint: true });
|
|
7207
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || await realpath(path) !== path || (process.platform === "win32" ? !await windowsOwnerOnly(path, "directory") : stat.uid !== BigInt(process.getuid?.() ?? -1) || (stat.mode & 63n) !== 0n)) throw new ApplicationError("UNSAFE_STATE_PATH");
|
|
7208
|
-
}
|
|
7209
|
-
return path;
|
|
7210
|
-
}
|
|
7211
|
-
//#endregion
|
|
7212
7532
|
//#region apps/cli/src/journey/observe.ts
|
|
7213
7533
|
/** Observe whole-journey pause while a single large Work is still being processed. */
|
|
7214
7534
|
function observeJourneyPause(storage, journeyId, processing, onFailure, requireConfirmedScope = false) {
|
|
@@ -7396,7 +7716,7 @@ function createJourneyPreparation(options) {
|
|
|
7396
7716
|
revision: "empty",
|
|
7397
7717
|
terms: []
|
|
7398
7718
|
};
|
|
7399
|
-
const plan = await prepareSourcePlan(storage, options.harnesses, journeyId, terms, abort.signal, current.period);
|
|
7719
|
+
const plan = await prepareSourcePlan(storage, options.harnesses, journeyId, terms, abort.signal, current.period, options.stateDir);
|
|
7400
7720
|
const initial = await storage.current(journeyId);
|
|
7401
7721
|
if (!initial?.consent) throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
7402
7722
|
const formed = initial.snapshotId ? [] : namedJourneyWorks(formJourneyWorks(plan.approval, options.processing.environment), options.processing, abort.signal);
|
|
@@ -8201,7 +8521,7 @@ async function draftSend(journey, dependencies, signal) {
|
|
|
8201
8521
|
const omitted = [];
|
|
8202
8522
|
for (const work of journey.works) {
|
|
8203
8523
|
signal.throwIfAborted();
|
|
8204
|
-
const reason = !isSelectedWork(work) ? work.access === "drop" ? "drop" : "empty" : work.status !== "ready" || !hasCompleteWorkDescription(work) || !work.accessConfirmed || !work.artifact || work.artifact.revision !== work.revision ? "not-ready" : work.binding?.sentBundleId === work.artifact.bundleId && work.binding.sentAccess === work.access ? "sent" : work.artifact.archiveBytes > 5368709120 ? "too-large" : null;
|
|
8524
|
+
const reason = !isSelectedWork(work) ? work.access === "drop" ? "drop" : "empty" : work.status !== "ready" || !hasCompleteWorkDescription(work) || !work.accessConfirmed || !work.artifact || work.artifact.revision !== work.revision ? "not-ready" : !dependencies.artifacts.sessions && work.binding?.sentBundleId === work.artifact.bundleId && work.binding.sentAccess === work.access ? "sent" : work.artifact.archiveBytes > 5368709120 ? "too-large" : null;
|
|
8205
8525
|
if (reason !== null || work.access === "drop") {
|
|
8206
8526
|
omitted.push({
|
|
8207
8527
|
workId: work.id,
|
|
@@ -8779,18 +9099,18 @@ function createFrozenPathRouter(repositories, folderPaths) {
|
|
|
8779
9099
|
}
|
|
8780
9100
|
//#endregion
|
|
8781
9101
|
//#region apps/cli/src/journey/works/project-evidence.ts
|
|
8782
|
-
const count$
|
|
9102
|
+
const count$2 = z.number().int().nonnegative();
|
|
8783
9103
|
const resultSchema = z.object({
|
|
8784
9104
|
repositories: z.array(z.object({
|
|
8785
9105
|
id: z.string(),
|
|
8786
9106
|
path: z.string(),
|
|
8787
|
-
references: count$
|
|
8788
|
-
reads: count$
|
|
8789
|
-
changes: count$
|
|
8790
|
-
commands: count$
|
|
9107
|
+
references: count$2,
|
|
9108
|
+
reads: count$2,
|
|
9109
|
+
changes: count$2,
|
|
9110
|
+
commands: count$2
|
|
8791
9111
|
})),
|
|
8792
9112
|
incomplete: z.boolean(),
|
|
8793
|
-
ignored: count$
|
|
9113
|
+
ignored: count$2
|
|
8794
9114
|
});
|
|
8795
9115
|
const cacheSchema = z.object({
|
|
8796
9116
|
key: z.string(),
|
|
@@ -9005,7 +9325,7 @@ function createClassificationSessions(storage, processing, stateDir) {
|
|
|
9005
9325
|
}
|
|
9006
9326
|
//#endregion
|
|
9007
9327
|
//#region apps/cli/src/journey/works/projection.ts
|
|
9008
|
-
const digest = (bytes) => createHash("sha256").update(bytes).digest();
|
|
9328
|
+
const digest$1 = (bytes) => createHash("sha256").update(bytes).digest();
|
|
9009
9329
|
async function buildProjection(directory, key, source, role, signal) {
|
|
9010
9330
|
const temporary = await mkdtemp(join(directory, ".classification-"));
|
|
9011
9331
|
const target = join(directory, `classification-${key}`);
|
|
@@ -9038,7 +9358,7 @@ async function buildProjection(directory, key, source, role, signal) {
|
|
|
9038
9358
|
const entry = Buffer.alloc(48);
|
|
9039
9359
|
entry.writeBigUInt64LE(BigInt(offset), 0);
|
|
9040
9360
|
entry.writeBigUInt64LE(BigInt(bytes.length), 8);
|
|
9041
|
-
digest(bytes).copy(entry, 16);
|
|
9361
|
+
digest$1(bytes).copy(entry, 16);
|
|
9042
9362
|
await data.writeFile(bytes);
|
|
9043
9363
|
await index.writeFile(entry);
|
|
9044
9364
|
offset += bytes.length;
|
|
@@ -9089,7 +9409,7 @@ async function buildProjection(directory, key, source, role, signal) {
|
|
|
9089
9409
|
/** Persisted derived content, never a scan receipt. Only the first read replays the capture. */
|
|
9090
9410
|
async function readClassificationProjection(source, options, signal) {
|
|
9091
9411
|
signal.throwIfAborted();
|
|
9092
|
-
const key = digest(JSON.stringify([
|
|
9412
|
+
const key = digest$1(JSON.stringify([
|
|
9093
9413
|
1,
|
|
9094
9414
|
options.snapshotId,
|
|
9095
9415
|
source,
|
|
@@ -9116,7 +9436,7 @@ async function readClassificationProjection(source, options, signal) {
|
|
|
9116
9436
|
const offset = Number(entry.readBigUInt64LE(0)), length = Number(entry.readBigUInt64LE(8));
|
|
9117
9437
|
if (!Number.isSafeInteger(offset) || !Number.isSafeInteger(length) || length > 1048576) throw new ApplicationError("CORRUPTED_STATE");
|
|
9118
9438
|
const bytes = await readProjectionBytes(dataPath, offset, length, manifest.data);
|
|
9119
|
-
if (!digest(bytes).equals(entry.subarray(16))) throw new ApplicationError("CORRUPTED_STATE");
|
|
9439
|
+
if (!digest$1(bytes).equals(entry.subarray(16))) throw new ApplicationError("CORRUPTED_STATE");
|
|
9120
9440
|
const row = projectionRowSchema.parse(JSON.parse(bytes.toString("utf8")));
|
|
9121
9441
|
if (row.sessionId !== source.sessionId || row.sourcePartId !== source.capture.partId) throw new ApplicationError("CORRUPTED_STATE");
|
|
9122
9442
|
rows.push(row);
|
|
@@ -9784,6 +10104,82 @@ function protectedSourceText(field, hidden) {
|
|
|
9784
10104
|
return text;
|
|
9785
10105
|
}
|
|
9786
10106
|
//#endregion
|
|
10107
|
+
//#region apps/cli/src/account/journey/remote-error.ts
|
|
10108
|
+
const refusalSchema = z.object({
|
|
10109
|
+
error: z.string(),
|
|
10110
|
+
preview: sendSettingsSchema.nullable().optional(),
|
|
10111
|
+
resolution: sendResolutionSchema.optional(),
|
|
10112
|
+
/** `unknown_keep_work_ids`: the kept IDs Passport did not accept (ADR 0204). */
|
|
10113
|
+
workIds: z.array(z.string()).optional()
|
|
10114
|
+
});
|
|
10115
|
+
var JourneyRemoteError = class extends Error {
|
|
10116
|
+
status;
|
|
10117
|
+
code;
|
|
10118
|
+
details;
|
|
10119
|
+
constructor(status, code, details, options) {
|
|
10120
|
+
super(code, options);
|
|
10121
|
+
this.name = "JourneyRemoteError";
|
|
10122
|
+
this.status = status;
|
|
10123
|
+
this.code = code;
|
|
10124
|
+
this.details = details;
|
|
10125
|
+
}
|
|
10126
|
+
};
|
|
10127
|
+
//#endregion
|
|
10128
|
+
//#region apps/cli/src/journey/send-sessions.ts
|
|
10129
|
+
/** Build upload identities from frozen local metadata and bytes from the final primary only. */
|
|
10130
|
+
async function* sessionArtifacts(storage, find, journeyId, artifact, signal) {
|
|
10131
|
+
const output = await find(journeyId, artifact.workId, artifact.revision);
|
|
10132
|
+
if (output.bundleId !== artifact.bundleId || output.primary.sha256 !== artifact.sha256 || output.primary.archiveBytes !== artifact.byteLength) throw new ApplicationError("ARTIFACT_IDENTITY_MISMATCH");
|
|
10133
|
+
const base = (await storage.resources({
|
|
10134
|
+
journeyId,
|
|
10135
|
+
kind: "base"
|
|
10136
|
+
})).find((item) => item.kind === "base" && item.workId === artifact.workId && item.inputRevision === output.finalMaskLedger.inputRevision && item.primary.sha256 === output.finalMaskLedger.basePrimarySha256);
|
|
10137
|
+
const plan = (await storage.resources({
|
|
10138
|
+
journeyId,
|
|
10139
|
+
kind: "plan"
|
|
10140
|
+
})).find((item) => item.kind === "plan");
|
|
10141
|
+
if (base?.kind !== "base" || plan?.kind !== "plan") throw new ApplicationError("ARTIFACT_IDENTITY_MISMATCH");
|
|
10142
|
+
const identities = base.sessions.map((binding) => {
|
|
10143
|
+
const session = plan.approval.inventory.sessions.find((item) => item.id === binding.applicationSessionId);
|
|
10144
|
+
const part = session?.parts[0];
|
|
10145
|
+
const lastAt = session?.lastInteractionAt ?? session?.createdAt;
|
|
10146
|
+
if (!session || !part || !lastAt || !Number.isFinite(Date.parse(lastAt))) throw new ApplicationError("ARTIFACT_IDENTITY_MISMATCH");
|
|
10147
|
+
const nativeId = part.nativeEvidence?.identity?.sessionId ?? session.sourceSessionId;
|
|
10148
|
+
const path = part.harnessRelativePath ?? "";
|
|
10149
|
+
if (!path && !nativeId?.trim() || isAbsolute(path) || path.includes("\\") || path.split("/").some((segment) => segment === ".." || segment === ".")) throw new ApplicationError("ARTIFACT_IDENTITY_MISMATCH");
|
|
10150
|
+
return {
|
|
10151
|
+
archiveSessionId: binding.archiveSessionId,
|
|
10152
|
+
relativePath: path,
|
|
10153
|
+
lastAt: new Date(lastAt).toISOString()
|
|
10154
|
+
};
|
|
10155
|
+
});
|
|
10156
|
+
for await (const session of configuredSessionUploads(output.primary, identities, signal)) yield {
|
|
10157
|
+
...session,
|
|
10158
|
+
async upload(upload, uploadSignal) {
|
|
10159
|
+
await find(journeyId, artifact.workId, artifact.revision);
|
|
10160
|
+
const url = new URL(upload.uploadUrl);
|
|
10161
|
+
if (url.protocol !== "https:" || url.username || url.password || url.hash) throw new ApplicationError("INVALID_ARGUMENTS");
|
|
10162
|
+
const request = {
|
|
10163
|
+
method: "PUT",
|
|
10164
|
+
body: session.stream(),
|
|
10165
|
+
signal: uploadSignal,
|
|
10166
|
+
redirect: "error",
|
|
10167
|
+
credentials: "omit",
|
|
10168
|
+
referrerPolicy: "no-referrer",
|
|
10169
|
+
headers: {
|
|
10170
|
+
"content-type": "application/zip",
|
|
10171
|
+
"content-length": String(session.byteLength),
|
|
10172
|
+
...upload.headers
|
|
10173
|
+
},
|
|
10174
|
+
duplex: "half"
|
|
10175
|
+
};
|
|
10176
|
+
const response = await fetch(url, request);
|
|
10177
|
+
await response.body?.cancel();
|
|
10178
|
+
if (!response.ok) throw new JourneyRemoteError(response.status, "UPLOAD_FAILED", null);
|
|
10179
|
+
}
|
|
10180
|
+
};
|
|
10181
|
+
}
|
|
10182
|
+
//#endregion
|
|
9787
10183
|
//#region apps/cli/src/journey/send-artifacts.ts
|
|
9788
10184
|
function createJourneySendArtifacts(storage) {
|
|
9789
10185
|
async function find(journeyId, workId, revision) {
|
|
@@ -9801,6 +10197,7 @@ function createJourneySendArtifacts(storage) {
|
|
|
9801
10197
|
return output;
|
|
9802
10198
|
}
|
|
9803
10199
|
return {
|
|
10200
|
+
sessions: (journeyId, artifact, signal) => sessionArtifacts(storage, find, journeyId, artifact, signal),
|
|
9804
10201
|
async describe(journey, work, signal) {
|
|
9805
10202
|
const output = await find(journey.journeyId, work.id, work.revision);
|
|
9806
10203
|
const info = await readConfiguredOutputInfo(output.primary, signal);
|
|
@@ -9884,69 +10281,61 @@ async function putPrimary(primary, byteLength, upload, signal) {
|
|
|
9884
10281
|
}
|
|
9885
10282
|
}
|
|
9886
10283
|
//#endregion
|
|
9887
|
-
//#region apps/cli/src/account/journey/
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
|
|
9895
|
-
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
return
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
|
|
9904
|
-
|
|
9905
|
-
|
|
9906
|
-
|
|
9907
|
-
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
|
|
9919
|
-
|
|
10284
|
+
//#region apps/cli/src/account/journey/session-key.ts
|
|
10285
|
+
/** Engine native identity, or harness-root-relative path identity when native identity is absent. */
|
|
10286
|
+
async function sessionKeyFor(input) {
|
|
10287
|
+
const native = input.nativeSessionId;
|
|
10288
|
+
const path = input.relativePath?.replaceAll("\\", "/");
|
|
10289
|
+
if (!input.harness || (!native || !native.trim()) && !path) throw new TypeError("SESSION_RELATIVE_PATH_REQUIRED");
|
|
10290
|
+
const source = native && native.trim() ? JSON.stringify([
|
|
10291
|
+
"native-v1",
|
|
10292
|
+
input.harness,
|
|
10293
|
+
"session",
|
|
10294
|
+
native
|
|
10295
|
+
]) : `path-v1|${input.harness}|${path}`;
|
|
10296
|
+
return createHash("sha256").update(source, "utf8").digest("hex");
|
|
10297
|
+
}
|
|
10298
|
+
//#endregion
|
|
10299
|
+
//#region apps/cli/src/account/journey/session-access.ts
|
|
10300
|
+
/** Check the entire pending Send before admitting any session mutation. */
|
|
10301
|
+
async function sessionAccessBlocked(dependencies, journeyId, items, inventory, signal) {
|
|
10302
|
+
const sessions = dependencies.artifacts.sessions;
|
|
10303
|
+
if (!sessions) return false;
|
|
10304
|
+
const requestedAccess = /* @__PURE__ */ new Map();
|
|
10305
|
+
const requestedDigests = /* @__PURE__ */ new Map();
|
|
10306
|
+
for (const item of items) {
|
|
10307
|
+
if (item.sessionAssembly || item.status === "completed") continue;
|
|
10308
|
+
const destination = inventory.works.find((work) => work.logicalWorkId === item.workId);
|
|
10309
|
+
if (destination?.access === "removed") throw new ApplicationError("WORK_REMOVED");
|
|
10310
|
+
for await (const session of sessions(journeyId, item.artifact, signal)) {
|
|
10311
|
+
const key = await sessionKeyFor(session);
|
|
10312
|
+
const otherAccess = requestedAccess.get(key);
|
|
10313
|
+
if (otherAccess !== void 0 && otherAccess !== item.access) return true;
|
|
10314
|
+
requestedAccess.set(key, item.access);
|
|
10315
|
+
const otherDigest = requestedDigests.get(key);
|
|
10316
|
+
if (otherDigest !== void 0 && otherDigest !== session.sha256) throw new ApplicationError("SESSION_CONTENT_CONFLICT");
|
|
10317
|
+
requestedDigests.set(key, session.sha256);
|
|
10318
|
+
const source = inventory.works.find((work) => work.sessions.some((entry) => entry.key === key));
|
|
10319
|
+
if (source && source.logicalWorkId !== item.workId && source.access !== item.access) return true;
|
|
10320
|
+
if (destination && destination.access !== item.access && !destination.sessions.some((entry) => entry.key === key && entry.sha256 === session.sha256)) return true;
|
|
10321
|
+
}
|
|
10322
|
+
}
|
|
10323
|
+
return false;
|
|
9920
10324
|
}
|
|
9921
|
-
/**
|
|
9922
|
-
function
|
|
9923
|
-
const
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
...
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
"OPERATION_FAILED",
|
|
9936
|
-
"UPLOAD_FAILED",
|
|
9937
|
-
"SEND_REMOTE_REFUSED",
|
|
9938
|
-
"IDENTITY_UNAVAILABLE",
|
|
9939
|
-
"ACCOUNT_SIGNED_OUT"
|
|
9940
|
-
].includes(item.error ?? "") ? {
|
|
9941
|
-
...item,
|
|
9942
|
-
status: item.receipt ? item.receipt.state === "awaiting_upload" ? "uploading" : "assessing" : "ready",
|
|
9943
|
-
error: null
|
|
9944
|
-
} : item)
|
|
9945
|
-
});
|
|
9946
|
-
return profileRequired && !["blocked", "completed"].includes(next.status) ? {
|
|
9947
|
-
...next,
|
|
9948
|
-
status: "sign-in"
|
|
9949
|
-
} : next;
|
|
10325
|
+
/** Keep overlapping session ownership serial through membership, including retained members. */
|
|
10326
|
+
async function sessionIndependentItems(dependencies, journeyId, items, inventory, signal) {
|
|
10327
|
+
const sessions = dependencies.artifacts.sessions;
|
|
10328
|
+
if (!sessions) return [];
|
|
10329
|
+
const pending = items.filter((item) => item.status === "uploading" || item.status === "ready").toSorted((left, right) => Number(right.status === "uploading") - Number(left.status === "uploading") || right.artifact.byteLength - left.artifact.byteLength);
|
|
10330
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
10331
|
+
const independent = [];
|
|
10332
|
+
for (const item of pending) {
|
|
10333
|
+
const keys = /* @__PURE__ */ new Set([...inventory.works.find((work) => work.logicalWorkId === item.workId)?.sessions.map((session) => session.key) ?? [], ...item.sessionMembership?.keys ?? []]);
|
|
10334
|
+
for await (const session of sessions(journeyId, item.artifact, signal)) keys.add(await sessionKeyFor(session));
|
|
10335
|
+
if (![...keys].some((key) => claimed.has(key))) independent.push(item);
|
|
10336
|
+
for (const key of keys) claimed.add(key);
|
|
10337
|
+
}
|
|
10338
|
+
return independent;
|
|
9950
10339
|
}
|
|
9951
10340
|
//#endregion
|
|
9952
10341
|
//#region apps/cli/src/account/journey/receipt.ts
|
|
@@ -10070,14 +10459,154 @@ async function checkPublicAgentSources(candidates, requestFetch = fetch, signal)
|
|
|
10070
10459
|
return results.filter((item) => item !== null);
|
|
10071
10460
|
}
|
|
10072
10461
|
//#endregion
|
|
10073
|
-
//#region apps/cli/src/account/journey/
|
|
10074
|
-
const
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10462
|
+
//#region apps/cli/src/account/journey/session-schema.ts
|
|
10463
|
+
const digest = z.string().regex(/^[0-9a-f]{64}$/u);
|
|
10464
|
+
const stored = z.strictObject({
|
|
10465
|
+
key: digest,
|
|
10466
|
+
sessionKey: z.string().regex(/^sess-pilot\/[^/]+\/[^/]+\/[^/]+$/u).nullable(),
|
|
10467
|
+
sha256: digest,
|
|
10468
|
+
byteLength: z.number().int().positive(),
|
|
10469
|
+
lastAt: z.iso.datetime()
|
|
10080
10470
|
});
|
|
10471
|
+
const work = z.strictObject({
|
|
10472
|
+
logicalWorkId: z.uuid(),
|
|
10473
|
+
access: z.enum([
|
|
10474
|
+
"public",
|
|
10475
|
+
"private",
|
|
10476
|
+
"removed"
|
|
10477
|
+
]),
|
|
10478
|
+
sessions: z.array(stored)
|
|
10479
|
+
});
|
|
10480
|
+
const cursor = z.string().nullable();
|
|
10481
|
+
const inventory$1 = z.strictObject({
|
|
10482
|
+
works: z.array(work),
|
|
10483
|
+
unassigned: z.array(stored),
|
|
10484
|
+
nextCursor: cursor,
|
|
10485
|
+
unassignedNextCursor: cursor
|
|
10486
|
+
});
|
|
10487
|
+
const reserve = z.discriminatedUnion("state", [z.strictObject({ state: z.literal("already_stored") }), z.strictObject({
|
|
10488
|
+
state: z.literal("awaiting_upload"),
|
|
10489
|
+
uploadUrl: z.url({ protocol: /^https?$/u }),
|
|
10490
|
+
expiresAt: z.iso.datetime(),
|
|
10491
|
+
headers: z.strictObject({ "x-amz-checksum-sha256": z.base64().length(44) })
|
|
10492
|
+
})]);
|
|
10493
|
+
const count$1 = z.number().int().nonnegative();
|
|
10494
|
+
const assembly = z.strictObject({
|
|
10495
|
+
revision: count$1,
|
|
10496
|
+
completedRevision: count$1,
|
|
10497
|
+
scanId: z.uuid().nullable(),
|
|
10498
|
+
assembledByteCount: count$1.nullable(),
|
|
10499
|
+
wallMs: count$1.nullable(),
|
|
10500
|
+
attempts: count$1,
|
|
10501
|
+
error: z.string().nullable()
|
|
10502
|
+
});
|
|
10503
|
+
//#endregion
|
|
10504
|
+
//#region apps/cli/src/account/journey/session-client.ts
|
|
10505
|
+
function continuation(seen, next) {
|
|
10506
|
+
if (next !== null && seen.has(next)) throw new JourneyRemoteError(200, "REMOTE_CURSOR_CYCLE", null);
|
|
10507
|
+
if (next !== null) seen.add(next);
|
|
10508
|
+
return next;
|
|
10509
|
+
}
|
|
10510
|
+
/** Session endpoints share the owner client's authentication, bounds and refusal classification. */
|
|
10511
|
+
function createSessionClient(request) {
|
|
10512
|
+
let writes = Promise.resolve();
|
|
10513
|
+
function mutate(signal, effect) {
|
|
10514
|
+
const result = writes.then(() => {
|
|
10515
|
+
signal.throwIfAborted();
|
|
10516
|
+
return effect();
|
|
10517
|
+
});
|
|
10518
|
+
writes = result.then(() => void 0, () => void 0);
|
|
10519
|
+
return result;
|
|
10520
|
+
}
|
|
10521
|
+
return {
|
|
10522
|
+
async inventory(signal) {
|
|
10523
|
+
const works = /* @__PURE__ */ new Map();
|
|
10524
|
+
const unassigned = /* @__PURE__ */ new Map();
|
|
10525
|
+
let workCursor = null;
|
|
10526
|
+
let unassignedCursor = null;
|
|
10527
|
+
let workDone = false;
|
|
10528
|
+
let unassignedDone = false;
|
|
10529
|
+
const workSeen = /* @__PURE__ */ new Set();
|
|
10530
|
+
const unassignedSeen = /* @__PURE__ */ new Set();
|
|
10531
|
+
do {
|
|
10532
|
+
const query = new URLSearchParams({ limit: "500" });
|
|
10533
|
+
if (workCursor !== null) query.set("cursor", workCursor);
|
|
10534
|
+
if (unassignedCursor !== null) query.set("unassignedCursor", unassignedCursor);
|
|
10535
|
+
const page = await request(`/me/sessions?${query}`, inventory$1, signal, void 0, "GET");
|
|
10536
|
+
if (!workDone) {
|
|
10537
|
+
for (const item of page.works) {
|
|
10538
|
+
const sessions = new Map((works.get(item.logicalWorkId)?.sessions ?? []).map((session) => [session.key, session]));
|
|
10539
|
+
for (const session of item.sessions) sessions.set(session.key, session);
|
|
10540
|
+
works.set(item.logicalWorkId, {
|
|
10541
|
+
...item,
|
|
10542
|
+
sessions: [...sessions.values()]
|
|
10543
|
+
});
|
|
10544
|
+
}
|
|
10545
|
+
workCursor = continuation(workSeen, page.nextCursor);
|
|
10546
|
+
workDone = workCursor === null;
|
|
10547
|
+
}
|
|
10548
|
+
if (!unassignedDone) {
|
|
10549
|
+
for (const session of page.unassigned) unassigned.set(session.key, session);
|
|
10550
|
+
unassignedCursor = continuation(unassignedSeen, page.unassignedNextCursor);
|
|
10551
|
+
unassignedDone = unassignedCursor === null;
|
|
10552
|
+
}
|
|
10553
|
+
} while (!workDone || !unassignedDone);
|
|
10554
|
+
return {
|
|
10555
|
+
works: [...works.values()],
|
|
10556
|
+
unassigned: [...unassigned.values()]
|
|
10557
|
+
};
|
|
10558
|
+
},
|
|
10559
|
+
async work(id, signal) {
|
|
10560
|
+
let next = null;
|
|
10561
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10562
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
10563
|
+
let value;
|
|
10564
|
+
do {
|
|
10565
|
+
const query = new URLSearchParams({ limit: "500" });
|
|
10566
|
+
if (next !== null) query.set("cursor", next);
|
|
10567
|
+
const page = await request(`/me/works/${encodeURIComponent(id)}/sessions?${query}`, work.extend({ nextCursor: cursor }).refine((item) => item.logicalWorkId === id), signal, void 0, "GET");
|
|
10568
|
+
for (const session of page.sessions) sessions.set(session.key, session);
|
|
10569
|
+
value = {
|
|
10570
|
+
logicalWorkId: page.logicalWorkId,
|
|
10571
|
+
access: page.access,
|
|
10572
|
+
sessions: [...sessions.values()]
|
|
10573
|
+
};
|
|
10574
|
+
next = continuation(seen, page.nextCursor);
|
|
10575
|
+
} while (next !== null);
|
|
10576
|
+
return value;
|
|
10577
|
+
},
|
|
10578
|
+
reserve: (input, signal) => request("/me/sessions/reserve", reserve, signal, input),
|
|
10579
|
+
complete: (input, signal) => mutate(signal, () => request("/me/sessions/complete", z.strictObject({ state: z.literal("stored") }), signal, input, "POST", void 0, 6e5)),
|
|
10580
|
+
membership: (id, input, signal) => mutate(signal, () => request(`/me/works/${encodeURIComponent(id)}/sessions`, z.strictObject({
|
|
10581
|
+
revision: count$1,
|
|
10582
|
+
affectedWorks: z.array(z.uuid())
|
|
10583
|
+
}), signal, input, "PUT", void 0, void 0, false, 202)),
|
|
10584
|
+
async assembly(id, signal) {
|
|
10585
|
+
try {
|
|
10586
|
+
return await request(`/me/works/${encodeURIComponent(id)}/assembly`, assembly, signal, void 0, "GET");
|
|
10587
|
+
} catch (error) {
|
|
10588
|
+
if (error instanceof JourneyRemoteError && error.status === 404 && error.code === "not_found") return null;
|
|
10589
|
+
throw error;
|
|
10590
|
+
}
|
|
10591
|
+
},
|
|
10592
|
+
async assessment(scanId, signal) {
|
|
10593
|
+
return (await request(`/me/tasks/${encodeURIComponent(scanId)}/log`, z.strictObject({
|
|
10594
|
+
taskId: z.literal(scanId),
|
|
10595
|
+
state: z.enum([
|
|
10596
|
+
"accepted",
|
|
10597
|
+
"running",
|
|
10598
|
+
"succeeded",
|
|
10599
|
+
"partial",
|
|
10600
|
+
"failed"
|
|
10601
|
+
]),
|
|
10602
|
+
progress: z.number().min(0).max(1),
|
|
10603
|
+
lines: z.array(z.string())
|
|
10604
|
+
}), signal, void 0, "GET")).state;
|
|
10605
|
+
}
|
|
10606
|
+
};
|
|
10607
|
+
}
|
|
10608
|
+
//#endregion
|
|
10609
|
+
//#region apps/cli/src/account/journey/client.ts
|
|
10081
10610
|
const publicProfileSchema = z.strictObject({
|
|
10082
10611
|
displayName: z.string().trim().min(1).max(300).nullable(),
|
|
10083
10612
|
avatarUrl: z.url().max(2048).refine((value) => {
|
|
@@ -10086,18 +10615,6 @@ const publicProfileSchema = z.strictObject({
|
|
|
10086
10615
|
return url.protocol === "https:" && !url.username && !url.password;
|
|
10087
10616
|
}).nullable()
|
|
10088
10617
|
});
|
|
10089
|
-
var JourneyRemoteError = class extends Error {
|
|
10090
|
-
status;
|
|
10091
|
-
code;
|
|
10092
|
-
details;
|
|
10093
|
-
constructor(status, code, details, options) {
|
|
10094
|
-
super(code, options);
|
|
10095
|
-
this.name = "JourneyRemoteError";
|
|
10096
|
-
this.status = status;
|
|
10097
|
-
this.code = code;
|
|
10098
|
-
this.details = details;
|
|
10099
|
-
}
|
|
10100
|
-
};
|
|
10101
10618
|
function apiBase(value) {
|
|
10102
10619
|
const url = new URL(value);
|
|
10103
10620
|
const local = [
|
|
@@ -10118,7 +10635,7 @@ const completeTimeoutMs = 6e5;
|
|
|
10118
10635
|
function createJourneyOwnerClient(options) {
|
|
10119
10636
|
const base = apiBase(options.apiUrl);
|
|
10120
10637
|
const requestFetch = options.fetch ?? fetch;
|
|
10121
|
-
async function
|
|
10638
|
+
async function requestWithHeaders(path, schema, signal, body, method = "POST", key, timeoutMs = requestTimeoutMs$2, checksumUpload = false, expectedStatus = 200) {
|
|
10122
10639
|
const token = await options.authorize();
|
|
10123
10640
|
signal.throwIfAborted();
|
|
10124
10641
|
const response = await requestFetch(new URL(path, base), {
|
|
@@ -10146,7 +10663,7 @@ function createJourneyOwnerClient(options) {
|
|
|
10146
10663
|
throw new JourneyRemoteError(response.status, "REMOTE_PROTOCOL", null);
|
|
10147
10664
|
}
|
|
10148
10665
|
const value = await readOwnerJson(response);
|
|
10149
|
-
if (response.status !==
|
|
10666
|
+
if (response.status !== expectedStatus) {
|
|
10150
10667
|
const refusal = refusalSchema.safeParse(value);
|
|
10151
10668
|
throw new JourneyRemoteError(response.status, refusal.success ? refusal.data.error : "REMOTE_PROTOCOL", refusal.success ? refusal.data : null);
|
|
10152
10669
|
}
|
|
@@ -10154,11 +10671,18 @@ function createJourneyOwnerClient(options) {
|
|
|
10154
10671
|
if (!parsed.success) throw new JourneyRemoteError(response.status, "REMOTE_PROTOCOL", null, { cause: parsed.error });
|
|
10155
10672
|
await options.authorize();
|
|
10156
10673
|
signal.throwIfAborted();
|
|
10157
|
-
return
|
|
10674
|
+
return {
|
|
10675
|
+
data: parsed.data,
|
|
10676
|
+
headers: response.headers
|
|
10677
|
+
};
|
|
10678
|
+
}
|
|
10679
|
+
async function request(...args) {
|
|
10680
|
+
return (await requestWithHeaders(...args)).data;
|
|
10158
10681
|
}
|
|
10159
10682
|
const receipt = (id) => sendReceiptSchema.refine((value) => value.receiptId === id);
|
|
10160
10683
|
return {
|
|
10161
10684
|
subject: options.subject,
|
|
10685
|
+
sessions: createSessionClient(request),
|
|
10162
10686
|
checkAgentSources(candidates, signal) {
|
|
10163
10687
|
return checkPublicAgentSources(candidates, requestFetch, signal);
|
|
10164
10688
|
},
|
|
@@ -10167,7 +10691,24 @@ function createJourneyOwnerClient(options) {
|
|
|
10167
10691
|
signal.throwIfAborted();
|
|
10168
10692
|
},
|
|
10169
10693
|
async syncProfile(signal) {
|
|
10170
|
-
await
|
|
10694
|
+
const { headers } = await requestWithHeaders("/me/profile", publicProfileSchema, signal);
|
|
10695
|
+
return { parallelCompletes: (headers.get("passport-capabilities") ?? "").split(",").some((token) => token.trim() === "parallel-completes") };
|
|
10696
|
+
},
|
|
10697
|
+
readProfile(signal) {
|
|
10698
|
+
return request("/me/profile", ownerProfileSchema, signal, void 0, "GET");
|
|
10699
|
+
},
|
|
10700
|
+
async saveProfile(profile, signal) {
|
|
10701
|
+
await request("/me/profile", ownerProfileSchema, signal, profile, "PUT");
|
|
10702
|
+
},
|
|
10703
|
+
async addressTaken(address, signal) {
|
|
10704
|
+
const path = `/addresses/${encodeURIComponent(passportAddressSchema.parse(address))}`;
|
|
10705
|
+
try {
|
|
10706
|
+
await request(path, z.object({ publicId: z.string() }).strict(), signal, void 0, "GET");
|
|
10707
|
+
return true;
|
|
10708
|
+
} catch (error) {
|
|
10709
|
+
if (error instanceof JourneyRemoteError && error.status === 404) return false;
|
|
10710
|
+
throw error;
|
|
10711
|
+
}
|
|
10171
10712
|
},
|
|
10172
10713
|
resolve(input, signal) {
|
|
10173
10714
|
return request("/me/work-bundles/resolve", sendResolutionSchema.refine((value) => value.logicalWorkId === input.logicalWorkId), signal, input);
|
|
@@ -10265,6 +10806,23 @@ async function applySettings(item, connection, signal, guard, auto = false) {
|
|
|
10265
10806
|
}
|
|
10266
10807
|
//#endregion
|
|
10267
10808
|
//#region apps/cli/src/account/journey/resolve.ts
|
|
10809
|
+
/**
|
|
10810
|
+
* Whether the reservation's target survives other Works' completions, which move the owner's
|
|
10811
|
+
* scope version: Passport then still admits a new Work that resolves new with no candidate, or
|
|
10812
|
+
* the same matched binding at the requested access (ADR 0235).
|
|
10813
|
+
*/
|
|
10814
|
+
function keepsTarget(resolution, reservation) {
|
|
10815
|
+
const [only] = resolution.candidates;
|
|
10816
|
+
return reservation.target.kind === "new" ? resolution.status === "new" && only === void 0 : resolution.status === "matched" && only?.bindingId === reservation.target.bindingId && only.access === reservation.access;
|
|
10817
|
+
}
|
|
10818
|
+
/**
|
|
10819
|
+
* Whether Passport still admits the reservation under this fresh resolution: the access version
|
|
10820
|
+
* must not move, and a moved scope version needs a kept target (ADR 0235).
|
|
10821
|
+
*/
|
|
10822
|
+
function admits(resolution, reservation) {
|
|
10823
|
+
if (resolution.accessVersion !== reservation.expectedAccessVersion) return false;
|
|
10824
|
+
return resolution.scopeVersion === reservation.expectedScopeVersion || keepsTarget(resolution, reservation);
|
|
10825
|
+
}
|
|
10268
10826
|
function resolveInput(item, bindingId) {
|
|
10269
10827
|
return {
|
|
10270
10828
|
logicalWorkId: item.workId,
|
|
@@ -10399,6 +10957,7 @@ async function perform(context, item, connection, signal) {
|
|
|
10399
10957
|
return next;
|
|
10400
10958
|
}
|
|
10401
10959
|
if (!item.receipt) {
|
|
10960
|
+
if (item.resolution && !keepsTarget(item.resolution, item.reservation) && !admits(await connection.resolve(item.reservation.work, signal), item.reservation)) return renewed(item, null);
|
|
10402
10961
|
const reserved = await connection.reserve(item.operationKey, item.reservation, signal, context.checksumUploads !== false);
|
|
10403
10962
|
const next = await observe(item, reserved, connection, signal, auto);
|
|
10404
10963
|
if (context.fresh?.has(item.workId) && reserved.upload && next.status === "uploading") context.fresh.set(item.workId, {
|
|
@@ -10427,24 +10986,24 @@ async function perform(context, item, connection, signal) {
|
|
|
10427
10986
|
return withoutUpload(item);
|
|
10428
10987
|
}
|
|
10429
10988
|
}
|
|
10430
|
-
|
|
10431
|
-
if (current.scopeVersion !== item.reservation.expectedScopeVersion || current.accessVersion !== item.reservation.expectedAccessVersion) return renewed(item, null);
|
|
10989
|
+
if (!admits(await connection.resolve(item.reservation.work, signal), item.reservation)) return renewed(item, null);
|
|
10432
10990
|
return put(context, item, receipt.receiptId, receipt.upload, connection, signal, guard);
|
|
10433
10991
|
}
|
|
10434
10992
|
/** Uploads the bytes to the receipt's signed URL and completes it. */
|
|
10435
10993
|
async function put(context, item, receiptId, upload, connection, signal, guard) {
|
|
10436
10994
|
await guard();
|
|
10437
10995
|
await context.upload(item.artifact, upload, signal);
|
|
10996
|
+
const sent = withoutBusy(item);
|
|
10438
10997
|
await guard();
|
|
10439
10998
|
try {
|
|
10440
|
-
return observe(
|
|
10999
|
+
return observe(sent, await connection.complete(receiptId, signal), connection, signal, context.snapshot === true);
|
|
10441
11000
|
} catch (error) {
|
|
10442
11001
|
if (upload.headers !== void 0 && error instanceof JourneyRemoteError && error.code === "bundle_mismatch") return {
|
|
10443
11002
|
...renewed(withoutUpload(item), null),
|
|
10444
11003
|
checksumUploadRefused: true
|
|
10445
11004
|
};
|
|
10446
11005
|
if (error instanceof Object) uploaded.set(error, {
|
|
10447
|
-
...
|
|
11006
|
+
...sent,
|
|
10448
11007
|
uploadedReceiptId: receiptId
|
|
10449
11008
|
});
|
|
10450
11009
|
throw error;
|
|
@@ -10456,10 +11015,24 @@ function uploadedItem(error) {
|
|
|
10456
11015
|
return error instanceof Object ? uploaded.get(error) : void 0;
|
|
10457
11016
|
}
|
|
10458
11017
|
function withoutUpload(item) {
|
|
10459
|
-
if (item.uploadedReceiptId === void 0) return item;
|
|
10460
|
-
const { uploadedReceiptId: _, ...rest } = item;
|
|
11018
|
+
if (item.uploadedReceiptId === void 0 && item.busyRefusals === void 0) return item;
|
|
11019
|
+
const { uploadedReceiptId: _, busyRefusals: __, ...rest } = item;
|
|
10461
11020
|
return rest;
|
|
10462
11021
|
}
|
|
11022
|
+
/** The busy count spans one upload and its completion: a Work that leaves the upload keeps none (ADR 0235). */
|
|
11023
|
+
function withoutBusy(item) {
|
|
11024
|
+
const { busyRefusals: _busy, ...rest } = item;
|
|
11025
|
+
return rest;
|
|
11026
|
+
}
|
|
11027
|
+
/**
|
|
11028
|
+
* A `settings_changed` without a resolution after the bytes were staged: other activations or
|
|
11029
|
+
* assessments moved the scope until Passport's retry budget ran out, which says nothing about this
|
|
11030
|
+
* Work's target. Completing again needs no second upload; the advance records `SEND_SCOPE_BUSY`
|
|
11031
|
+
* and waits longer before each further try (ADR 0235).
|
|
11032
|
+
*/
|
|
11033
|
+
function stagedRetry(error, item) {
|
|
11034
|
+
return error instanceof JourneyRemoteError && error.code === "settings_changed" && error.details?.resolution === void 0 && item.uploadedReceiptId !== void 0;
|
|
11035
|
+
}
|
|
10463
11036
|
/** A definite stale refusal can renew the intent; unknown outcomes always retry the same key. */
|
|
10464
11037
|
async function stepItem(context, item, connection, signal) {
|
|
10465
11038
|
try {
|
|
@@ -10472,10 +11045,11 @@ async function stepItem(context, item, connection, signal) {
|
|
|
10472
11045
|
"target_conflict",
|
|
10473
11046
|
"work_removed"
|
|
10474
11047
|
].includes(error.code)) throw error;
|
|
11048
|
+
if (stagedRetry(error, uploadedItem(error) ?? item)) throw error;
|
|
10475
11049
|
if (item.receipt) {
|
|
10476
11050
|
const observed = await connection.status(item.receipt.receiptId, signal);
|
|
10477
11051
|
await connection.assertCurrent(signal);
|
|
10478
|
-
if (observed.state !== "awaiting_upload") return observe(item, observed, connection, signal, context.snapshot === true);
|
|
11052
|
+
if (observed.state !== "awaiting_upload") return withoutBusy(await observe(item, observed, connection, signal, context.snapshot === true));
|
|
10479
11053
|
}
|
|
10480
11054
|
const candidate = error.details?.resolution?.candidates[0];
|
|
10481
11055
|
if (error.code === "target_conflict" && item.target?.kind === "existing" && candidate?.bindingId === item.target.bindingId) return {
|
|
@@ -10488,6 +11062,17 @@ async function stepItem(context, item, connection, signal) {
|
|
|
10488
11062
|
status: "blocked",
|
|
10489
11063
|
error: "WORK_REMOVED"
|
|
10490
11064
|
};
|
|
11065
|
+
if (error.code === "settings_changed" && error.details?.resolution === void 0) {
|
|
11066
|
+
const busyRefusals = (item.busyRefusals ?? 0) + 1;
|
|
11067
|
+
if (item.receipt) return {
|
|
11068
|
+
...item,
|
|
11069
|
+
busyRefusals
|
|
11070
|
+
};
|
|
11071
|
+
return {
|
|
11072
|
+
...renewed(item, null),
|
|
11073
|
+
busyRefusals
|
|
11074
|
+
};
|
|
11075
|
+
}
|
|
10491
11076
|
return renewed(item, "SETTINGS_CHANGED");
|
|
10492
11077
|
}
|
|
10493
11078
|
}
|
|
@@ -10504,18 +11089,274 @@ function renewed(item, error) {
|
|
|
10504
11089
|
error
|
|
10505
11090
|
};
|
|
10506
11091
|
}
|
|
11092
|
+
//#endregion
|
|
11093
|
+
//#region apps/cli/src/account/journey/session-step.ts
|
|
11094
|
+
/** Reconcile against completed server facts on every retry, including after process loss. */
|
|
11095
|
+
async function stepSessionWork(dependencies, journeyId, context, item, connection, signal) {
|
|
11096
|
+
const remote = connection.sessions;
|
|
11097
|
+
const sessions = dependencies.artifacts.sessions;
|
|
11098
|
+
if (!remote || !sessions) throw new ApplicationError("INVALID_STATE");
|
|
11099
|
+
async function guard() {
|
|
11100
|
+
await connection.assertCurrent(signal);
|
|
11101
|
+
await context.active();
|
|
11102
|
+
signal.throwIfAborted();
|
|
11103
|
+
}
|
|
11104
|
+
await guard();
|
|
11105
|
+
if (!item.sessionAssembly && (item.status !== "uploading" || item.receipt || item.reservation)) return {
|
|
11106
|
+
...item,
|
|
11107
|
+
receipt: null,
|
|
11108
|
+
reservation: null,
|
|
11109
|
+
target: null,
|
|
11110
|
+
previousReceipts: item.receipt ? [...item.previousReceipts, item.receipt] : item.previousReceipts,
|
|
11111
|
+
status: "uploading",
|
|
11112
|
+
error: null
|
|
11113
|
+
};
|
|
11114
|
+
if (item.sessionAssembly) {
|
|
11115
|
+
const assembly = await remote.assembly(item.workId, signal);
|
|
11116
|
+
if (!assembly) throw new ApplicationError("INVALID_STATE");
|
|
11117
|
+
if (assembly.revision < item.sessionAssembly.revision) return item;
|
|
11118
|
+
if (assembly.error) return {
|
|
11119
|
+
...item,
|
|
11120
|
+
status: "blocked",
|
|
11121
|
+
error: "ASSESSMENT_INCOMPLETE"
|
|
11122
|
+
};
|
|
11123
|
+
const scanId = assembly.completedRevision === assembly.revision ? assembly.scanId : null;
|
|
11124
|
+
const state = scanId ? await remote.assessment(scanId, signal) : "accepted";
|
|
11125
|
+
await guard();
|
|
11126
|
+
const failed = state === "failed" || state === "partial";
|
|
11127
|
+
return {
|
|
11128
|
+
...item,
|
|
11129
|
+
sessionAssembly: {
|
|
11130
|
+
revision: assembly.revision,
|
|
11131
|
+
scanId
|
|
11132
|
+
},
|
|
11133
|
+
status: state === "succeeded" ? "completed" : failed ? "blocked" : "assessing",
|
|
11134
|
+
error: failed ? "ASSESSMENT_INCOMPLETE" : null
|
|
11135
|
+
};
|
|
11136
|
+
}
|
|
11137
|
+
if (item.sessionMembership) {
|
|
11138
|
+
const intent = item.sessionMembership;
|
|
11139
|
+
const assembly = await remote.assembly(intent.workId, signal);
|
|
11140
|
+
const conflict = async () => {
|
|
11141
|
+
const current = await remote.inventory(signal);
|
|
11142
|
+
const revision = await remote.assembly(intent.workId, signal);
|
|
11143
|
+
await guard();
|
|
11144
|
+
return {
|
|
11145
|
+
...item,
|
|
11146
|
+
status: "blocked",
|
|
11147
|
+
error: "SESSION_MEMBERSHIP_CONFLICT",
|
|
11148
|
+
membershipConflict: {
|
|
11149
|
+
revision: revision?.revision ?? 0,
|
|
11150
|
+
keys: current.works.find((work) => work.logicalWorkId === intent.workId)?.sessions.map((session) => session.key) ?? []
|
|
11151
|
+
}
|
|
11152
|
+
};
|
|
11153
|
+
};
|
|
11154
|
+
if ((assembly?.revision ?? 0) !== intent.expectedRevision) return conflict();
|
|
11155
|
+
await guard();
|
|
11156
|
+
try {
|
|
11157
|
+
const { workId, ...input } = intent;
|
|
11158
|
+
const result = await remote.membership(workId, input, signal);
|
|
11159
|
+
await guard();
|
|
11160
|
+
return {
|
|
11161
|
+
...item,
|
|
11162
|
+
sessionAssembly: {
|
|
11163
|
+
revision: result.revision,
|
|
11164
|
+
scanId: null
|
|
11165
|
+
},
|
|
11166
|
+
status: "assessing",
|
|
11167
|
+
error: null
|
|
11168
|
+
};
|
|
11169
|
+
} catch (error) {
|
|
11170
|
+
if (error instanceof JourneyRemoteError && error.status === 409) return conflict();
|
|
11171
|
+
throw error;
|
|
11172
|
+
}
|
|
11173
|
+
}
|
|
11174
|
+
const assembly = await remote.assembly(item.workId, signal);
|
|
11175
|
+
const inventory = await remote.inventory(signal);
|
|
11176
|
+
const stored = new Map([...inventory.unassigned, ...inventory.works.flatMap((work) => work.sessions)].map((session) => [session.key, session]));
|
|
11177
|
+
const keys = [];
|
|
11178
|
+
for await (const session of sessions(journeyId, item.artifact, signal)) {
|
|
11179
|
+
const key = await sessionKeyFor(session);
|
|
11180
|
+
keys.push(key);
|
|
11181
|
+
if (stored.get(key)?.sha256 === session.sha256) continue;
|
|
11182
|
+
const input = {
|
|
11183
|
+
key,
|
|
11184
|
+
sha256: session.sha256,
|
|
11185
|
+
byteLength: session.byteLength,
|
|
11186
|
+
crc32: session.crc32,
|
|
11187
|
+
lastAt: session.lastAt,
|
|
11188
|
+
harness: session.harness
|
|
11189
|
+
};
|
|
11190
|
+
if (item.sessionUpload?.key !== key || item.sessionUpload.sha256 !== session.sha256) return {
|
|
11191
|
+
...item,
|
|
11192
|
+
sessionUpload: {
|
|
11193
|
+
key,
|
|
11194
|
+
sha256: session.sha256
|
|
11195
|
+
}
|
|
11196
|
+
};
|
|
11197
|
+
await guard();
|
|
11198
|
+
try {
|
|
11199
|
+
await remote.complete({
|
|
11200
|
+
...input,
|
|
11201
|
+
metadata: session.metadata
|
|
11202
|
+
}, signal);
|
|
11203
|
+
} catch (error) {
|
|
11204
|
+
if (!(error instanceof JourneyRemoteError) || error.status !== 409 || error.code !== "upload_incomplete") throw error;
|
|
11205
|
+
await guard();
|
|
11206
|
+
const reservation = await remote.reserve(input, signal);
|
|
11207
|
+
if (reservation.state === "awaiting_upload") {
|
|
11208
|
+
await guard();
|
|
11209
|
+
await session.upload(reservation, signal);
|
|
11210
|
+
await guard();
|
|
11211
|
+
await remote.complete({
|
|
11212
|
+
...input,
|
|
11213
|
+
metadata: session.metadata
|
|
11214
|
+
}, signal);
|
|
11215
|
+
}
|
|
11216
|
+
}
|
|
11217
|
+
await guard();
|
|
11218
|
+
return {
|
|
11219
|
+
...item,
|
|
11220
|
+
sessionUpload: null
|
|
11221
|
+
};
|
|
11222
|
+
}
|
|
11223
|
+
await guard();
|
|
11224
|
+
const current = inventory.works.find((work) => work.logicalWorkId === item.workId);
|
|
11225
|
+
if (current && current.access !== item.access) {
|
|
11226
|
+
const resolution = await connection.resolve(resolveInput(item, context.bindingId), signal);
|
|
11227
|
+
const [candidate] = resolution.candidates;
|
|
11228
|
+
if (resolution.status === "blocked" || candidate?.access === "removed") throw new ApplicationError("WORK_REMOVED");
|
|
11229
|
+
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");
|
|
11230
|
+
const settings = await connection.preview(candidate.workId, item.access, signal);
|
|
11231
|
+
await guard();
|
|
11232
|
+
await connection.confirm(settings, signal);
|
|
11233
|
+
}
|
|
11234
|
+
await guard();
|
|
11235
|
+
return {
|
|
11236
|
+
...item,
|
|
11237
|
+
sessionUpload: null,
|
|
11238
|
+
sessionMembership: {
|
|
11239
|
+
workId: item.workId,
|
|
11240
|
+
expectedRevision: assembly?.revision ?? 0,
|
|
11241
|
+
keys: [.../* @__PURE__ */ new Set([...current?.sessions.map((session) => session.key) ?? [], ...keys])],
|
|
11242
|
+
title: item.title,
|
|
11243
|
+
access: item.access
|
|
11244
|
+
}
|
|
11245
|
+
};
|
|
11246
|
+
}
|
|
11247
|
+
//#endregion
|
|
11248
|
+
//#region apps/cli/src/account/journey/state.ts
|
|
11249
|
+
function requireSend(snapshot) {
|
|
11250
|
+
if (!snapshot?.send) throw new ApplicationError("UNKNOWN_ITEM");
|
|
11251
|
+
return {
|
|
11252
|
+
...snapshot,
|
|
11253
|
+
send: snapshot.send
|
|
11254
|
+
};
|
|
11255
|
+
}
|
|
11256
|
+
function matchesAction(snapshot, action) {
|
|
11257
|
+
return snapshot.journey.revision === action.expectedJourneyRevision && snapshot.send.revision === action.expectedSendRevision;
|
|
11258
|
+
}
|
|
11259
|
+
/** Preserve an in-flight whole-Work protocol across upgrades to the session sender. */
|
|
11260
|
+
function usesSessionSend(dependencies, send) {
|
|
11261
|
+
if (!dependencies.artifacts.sessions) return false;
|
|
11262
|
+
if (send.items.some((item) => item.sessionUpload !== void 0 || item.sessionMembership || item.sessionAssembly)) return true;
|
|
11263
|
+
return !send.items.some((item) => item.receipt || item.reservation || item.previousReceipts.length > 0);
|
|
11264
|
+
}
|
|
11265
|
+
async function saveSend(dependencies, snapshot, next, action) {
|
|
11266
|
+
return dependencies.storage.commitSend({
|
|
11267
|
+
journeyId: snapshot.journey.journeyId,
|
|
11268
|
+
expectedJourneyRevision: action?.expectedJourneyRevision ?? snapshot.journey.revision,
|
|
11269
|
+
expectedSendRevision: action?.expectedSendRevision ?? (snapshot.send?.id === next.id ? snapshot.send.revision : null),
|
|
11270
|
+
requestKey: action?.idempotencyKey ?? randomUUID(),
|
|
11271
|
+
requestHash: hash$1(action ?? next),
|
|
11272
|
+
state: journeySendSchema.parse({
|
|
11273
|
+
...next,
|
|
11274
|
+
status: snapshot.send?.id === next.id && snapshot.send.status === "completed" ? "completed" : next.status,
|
|
11275
|
+
revision: (snapshot.send?.id === next.id ? snapshot.send.revision : -1) + 1
|
|
11276
|
+
}),
|
|
11277
|
+
at: dependencies.now()
|
|
11278
|
+
});
|
|
11279
|
+
}
|
|
11280
|
+
function aggregate(send) {
|
|
11281
|
+
if (send.status === "paused") return send;
|
|
11282
|
+
const statuses = send.items.map((item) => item.status);
|
|
11283
|
+
return {
|
|
11284
|
+
...send,
|
|
11285
|
+
status: statuses.length === 0 ? "blocked" : statuses.every((status) => status === "completed") ? "completed" : statuses.some((status) => status === "target" || status === "settings") ? "review" : statuses.some((status) => status === "ready" || status === "uploading") ? "sending" : statuses.some((status) => status === "assessing") ? "waiting" : "blocked"
|
|
11286
|
+
};
|
|
11287
|
+
}
|
|
11288
|
+
/**
|
|
11289
|
+
* A completed Send that Passport has not finished with: a Work is still assessed or reapplied
|
|
11290
|
+
* (ADR 0233). A blocked Work waits on the person's Retry, not on Passport.
|
|
11291
|
+
*/
|
|
11292
|
+
function awaitingPassport(send) {
|
|
11293
|
+
return send.status === "completed" && send.items.some((item) => !["completed", "blocked"].includes(item.status));
|
|
11294
|
+
}
|
|
11295
|
+
/** Explicit resume retries recoverable items and recaptures a pending sign-in or profile. */
|
|
11296
|
+
function resumeState(send) {
|
|
11297
|
+
if (send.status !== "completed" && send.items.length && send.items.every((item) => item.status === "completed") && send.items.some((item) => !item.sessionAssembly)) return {
|
|
11298
|
+
...send,
|
|
11299
|
+
status: "sign-in",
|
|
11300
|
+
error: null
|
|
11301
|
+
};
|
|
11302
|
+
const profileRequired = ["paused", "sign-in"].includes(send.status) || send.subject === null || [
|
|
11303
|
+
"ACCOUNT_PROFILE_UNAVAILABLE",
|
|
11304
|
+
"ACCOUNT_CHANGED",
|
|
11305
|
+
"PASSPORT_ADDRESS_TAKEN",
|
|
11306
|
+
"PASSPORT_ADDRESS_RESERVED",
|
|
11307
|
+
"PASSPORT_ADDRESS_LIMIT"
|
|
11308
|
+
].includes(send.error ?? "") || send.status === "blocked" && send.items.some((item) => [
|
|
11309
|
+
"ready",
|
|
11310
|
+
"uploading",
|
|
11311
|
+
"assessing",
|
|
11312
|
+
"target",
|
|
11313
|
+
"settings"
|
|
11314
|
+
].includes(item.status));
|
|
11315
|
+
const next = aggregate({
|
|
11316
|
+
...send,
|
|
11317
|
+
status: "sending",
|
|
11318
|
+
error: null,
|
|
11319
|
+
items: send.items.map((item) => item.status === "blocked" && [
|
|
11320
|
+
"OPERATION_FAILED",
|
|
11321
|
+
"UPLOAD_FAILED",
|
|
11322
|
+
"SEND_REMOTE_REFUSED",
|
|
11323
|
+
"SESSION_MOVE_ACCESS_REQUIRED",
|
|
11324
|
+
"IDENTITY_UNAVAILABLE",
|
|
11325
|
+
"ACCOUNT_SIGNED_OUT"
|
|
11326
|
+
].includes(item.error ?? "") ? {
|
|
11327
|
+
...item,
|
|
11328
|
+
status: item.receipt ? item.receipt.state === "awaiting_upload" ? "uploading" : "assessing" : "ready",
|
|
11329
|
+
error: null
|
|
11330
|
+
} : item)
|
|
11331
|
+
});
|
|
11332
|
+
return profileRequired && !["blocked", "completed"].includes(next.status) ? {
|
|
11333
|
+
...next,
|
|
11334
|
+
status: "sign-in"
|
|
11335
|
+
} : next;
|
|
11336
|
+
}
|
|
10507
11337
|
const confirmedSettings = (item) => item.settings !== null && item.settingsConfirmed;
|
|
11338
|
+
const started = (item) => Number(item.status === "uploading");
|
|
11339
|
+
/**
|
|
11340
|
+
* Works that share a workspace path or a git remote: once one completes, Passport offers it as
|
|
11341
|
+
* the other's candidate, so the other's target no longer resolves the same (ADR 0235).
|
|
11342
|
+
*/
|
|
11343
|
+
function related(left, right) {
|
|
11344
|
+
return left.artifact.sources.some((one) => right.artifact.sources.some((other) => one.pathHash !== null && one.pathHash === other.pathHash || one.remoteHashes.some((hash) => other.remoteHashes.includes(hash))));
|
|
11345
|
+
}
|
|
10508
11346
|
/**
|
|
10509
|
-
* The Works one advance steps together. Upload work comes before assessment checks
|
|
10510
|
-
* ZIP
|
|
10511
|
-
* an upload
|
|
10512
|
-
*
|
|
10513
|
-
*
|
|
10514
|
-
* linked Settings change
|
|
11347
|
+
* The Works one advance steps together. Upload work comes before assessment checks: up to three
|
|
11348
|
+
* Works, started uploads first, then the largest ZIP, so the slowest Work starts earliest.
|
|
11349
|
+
* Completing an upload moves the owner's scope version, which Passport accepts while a Work's
|
|
11350
|
+
* own target resolves the same (ADR 0235). A Work related to one already picked waits for the
|
|
11351
|
+
* next batch. A reserved Work whose target would not survive that steps alone, and so does a
|
|
11352
|
+
* confirmed linked Settings change, which moves the access version every reservation carries,
|
|
11353
|
+
* once no upload has started. Assessment checks change nothing and step up to three together.
|
|
10515
11354
|
*/
|
|
10516
|
-
function pickBatch(items, now
|
|
10517
|
-
const
|
|
10518
|
-
|
|
11355
|
+
function pickBatch(items, now, limit) {
|
|
11356
|
+
const uploads = items.filter((item) => item.status === "uploading" || item.status === "ready").toSorted((left, right) => started(right) - started(left) || right.artifact.byteLength - left.artifact.byteLength);
|
|
11357
|
+
const alone = uploads.find((item) => item.status === "uploading" && item.reservation !== null && item.resolution !== null && !keepsTarget(item.resolution, item.reservation)) ?? (uploads[0] && confirmedSettings(uploads[0]) ? uploads[0] : void 0);
|
|
11358
|
+
if (alone) return [alone];
|
|
11359
|
+
if (uploads.length) return uploads.filter((item) => !confirmedSettings(item)).reduce((batch, item) => batch.length < limit && !batch.some((picked) => related(picked, item)) ? [...batch, item] : batch, []);
|
|
10519
11360
|
const assessing = items.filter((item) => item.status === "assessing");
|
|
10520
11361
|
const first = assessing[0];
|
|
10521
11362
|
if (!first) return [];
|
|
@@ -10548,17 +11389,53 @@ async function prepareProfile(dependencies, snapshot, connection, signal) {
|
|
|
10548
11389
|
assertScope(latest.journey, latest.send);
|
|
10549
11390
|
signal.throwIfAborted();
|
|
10550
11391
|
}
|
|
10551
|
-
|
|
11392
|
+
const captured = await captureProfile(connection, guard, signal, dependencies.failure);
|
|
11393
|
+
const chosen = snapshot.journey.profile;
|
|
11394
|
+
if (captured.status !== "sending" || !chosen || !profileHasChoices(chosen)) return captured;
|
|
11395
|
+
return writeProfile(connection, chosen, guard, signal, dependencies.failure, captured);
|
|
11396
|
+
}
|
|
11397
|
+
/** Passport's refusal of the chosen address, as the code that re-shows the address card. */
|
|
11398
|
+
const addressRefusals = {
|
|
11399
|
+
address_taken: "PASSPORT_ADDRESS_TAKEN",
|
|
11400
|
+
address_reserved: "PASSPORT_ADDRESS_RESERVED",
|
|
11401
|
+
address_limit_reached: "PASSPORT_ADDRESS_LIMIT"
|
|
11402
|
+
};
|
|
11403
|
+
/**
|
|
11404
|
+
* The Journey's address and links over the owner's current settings, saved before the first
|
|
11405
|
+
* Work upload (ADR 0246). A refused address blocks the Send until the person keeps another.
|
|
11406
|
+
*/
|
|
11407
|
+
async function writeProfile(connection, chosen, guard, signal, failure, captured) {
|
|
11408
|
+
try {
|
|
11409
|
+
const current = await connection.readProfile(signal);
|
|
11410
|
+
await guard();
|
|
11411
|
+
await connection.saveProfile(mergeOwnerProfile(current.profile, chosen), signal);
|
|
11412
|
+
await guard();
|
|
11413
|
+
return captured;
|
|
11414
|
+
} catch (error) {
|
|
11415
|
+
if (signal.aborted) throw error;
|
|
11416
|
+
if (error instanceof JourneyRemoteError && error.status === 409 && addressRefusals[error.code]) return {
|
|
11417
|
+
status: "blocked",
|
|
11418
|
+
error: addressRefusals[error.code] ?? null
|
|
11419
|
+
};
|
|
11420
|
+
if (error instanceof ApplicationError) throw error;
|
|
11421
|
+
const unavailable = new ApplicationError("ACCOUNT_PROFILE_UNAVAILABLE", void 0, { cause: error });
|
|
11422
|
+
failure(unavailable.code, unavailable);
|
|
11423
|
+
return {
|
|
11424
|
+
status: "blocked",
|
|
11425
|
+
error: unavailable.code
|
|
11426
|
+
};
|
|
11427
|
+
}
|
|
10552
11428
|
}
|
|
10553
11429
|
/** Shared by native Send and Manual upload; `guard` rechecks the caller's own scope. */
|
|
10554
11430
|
async function captureProfile(connection, guard, signal, failure) {
|
|
10555
11431
|
try {
|
|
10556
11432
|
await guard();
|
|
10557
|
-
await connection.syncProfile(signal);
|
|
11433
|
+
const { parallelCompletes } = await connection.syncProfile(signal);
|
|
10558
11434
|
await guard();
|
|
10559
11435
|
return {
|
|
10560
11436
|
status: "sending",
|
|
10561
|
-
error: null
|
|
11437
|
+
error: null,
|
|
11438
|
+
parallelCompletes: parallelCompletes ? true : void 0
|
|
10562
11439
|
};
|
|
10563
11440
|
} catch (error) {
|
|
10564
11441
|
if (signal.aborted) throw error;
|
|
@@ -10583,6 +11460,8 @@ async function captureProfile(connection, guard, signal, failure) {
|
|
|
10583
11460
|
}
|
|
10584
11461
|
//#endregion
|
|
10585
11462
|
//#region apps/cli/src/account/journey/advance.ts
|
|
11463
|
+
/** A Work Passport is done with: assessed, or stopped unless Resume retries it. */
|
|
11464
|
+
const settled = (item) => ["completed", "blocked"].includes(item.status);
|
|
10586
11465
|
/** The stored Send still holds exactly this Work state, whatever else changed meanwhile. */
|
|
10587
11466
|
function sameItem(items, item) {
|
|
10588
11467
|
return JSON.stringify(items.find((value) => value.workId === item.workId)) === JSON.stringify(item);
|
|
@@ -10602,22 +11481,50 @@ async function advanceItems(host, signal) {
|
|
|
10602
11481
|
status: "blocked",
|
|
10603
11482
|
error: "ACCOUNT_CHANGED"
|
|
10604
11483
|
});
|
|
11484
|
+
if (send.items.length && send.items.every((item) => item.status === "completed")) {
|
|
11485
|
+
const items = send.items.map((item) => {
|
|
11486
|
+
if (item.sessionAssembly) return item;
|
|
11487
|
+
try {
|
|
11488
|
+
if (item.receipt && acceptReceipt(item, {
|
|
11489
|
+
...item.receipt,
|
|
11490
|
+
upload: null
|
|
11491
|
+
}).status === "completed") return item;
|
|
11492
|
+
} catch (error) {
|
|
11493
|
+
host.failure("LEGACY_SEND_RECOVERY_REQUIRED", error);
|
|
11494
|
+
}
|
|
11495
|
+
return {
|
|
11496
|
+
...item,
|
|
11497
|
+
status: "blocked",
|
|
11498
|
+
error: "LEGACY_SEND_RECOVERY_REQUIRED"
|
|
11499
|
+
};
|
|
11500
|
+
});
|
|
11501
|
+
return host.save({
|
|
11502
|
+
...send,
|
|
11503
|
+
items,
|
|
11504
|
+
status: items.every((item) => item.status === "completed") ? "completed" : "blocked",
|
|
11505
|
+
error: items.some((item) => item.status === "blocked") ? "LEGACY_SEND_RECOVERY_REQUIRED" : null
|
|
11506
|
+
});
|
|
11507
|
+
}
|
|
10605
11508
|
if (send.subject === null || send.status === "sign-in") {
|
|
11509
|
+
const { parallelCompletes: captured, ...profile } = await host.profile(connection, signal);
|
|
11510
|
+
const parallelCompletes = send.subject === null ? captured : send.parallelCompletes && captured;
|
|
10606
11511
|
const prepared = {
|
|
10607
11512
|
...send,
|
|
10608
11513
|
subject: connection.subject,
|
|
10609
|
-
...
|
|
11514
|
+
...profile
|
|
10610
11515
|
};
|
|
11516
|
+
if (parallelCompletes) prepared.parallelCompletes = true;
|
|
11517
|
+
else delete prepared.parallelCompletes;
|
|
10611
11518
|
return host.save(prepared.status === "sending" ? aggregate(prepared) : prepared);
|
|
10612
11519
|
}
|
|
10613
|
-
const
|
|
10614
|
-
if (
|
|
10615
|
-
|
|
10616
|
-
if (finished !== null) return finished;
|
|
10617
|
-
}
|
|
11520
|
+
const blocked = await host.preflight?.(connection, signal);
|
|
11521
|
+
if (blocked) return blocked;
|
|
11522
|
+
const batch = host.batch?.() ?? pickBatch(send.items, Date.now(), send.parallelCompletes ? 3 : 1);
|
|
10618
11523
|
if (!batch.length) return host.applied();
|
|
11524
|
+
const busy = Math.max(0, ...batch.map((item) => item.busyRefusals ?? 0));
|
|
11525
|
+
if (busy) await wait(busyDelay(busy), signal);
|
|
10619
11526
|
const checksumUploads = !send.items.some((item) => item.checksumUploadRefused);
|
|
10620
|
-
const results = await Promise.allSettled(batch.map((item) => stepItem({
|
|
11527
|
+
const results = await Promise.allSettled(batch.map((item) => (host.step ?? stepItem)({
|
|
10621
11528
|
...host.context(item),
|
|
10622
11529
|
checksumUploads
|
|
10623
11530
|
}, item, connection, signal)));
|
|
@@ -10641,7 +11548,15 @@ async function advanceItems(host, signal) {
|
|
|
10641
11548
|
if (progressed) next.set(item.workId, progressed);
|
|
10642
11549
|
if (cause instanceof JourneyRemoteError && cause.status === 401 || isCode(cause, "ACCOUNT_SIGNED_OUT")) signIn = true;
|
|
10643
11550
|
else if (isCode(cause, "IDENTITY_REJECTED")) changed = true;
|
|
10644
|
-
else if (
|
|
11551
|
+
else if (stagedRetry(cause, progressed ?? item)) {
|
|
11552
|
+
const staged = progressed ?? item;
|
|
11553
|
+
next.set(item.workId, {
|
|
11554
|
+
...staged,
|
|
11555
|
+
busyRefusals: (staged.busyRefusals ?? 0) + 1
|
|
11556
|
+
});
|
|
11557
|
+
host.failure("SEND_SCOPE_BUSY", cause);
|
|
11558
|
+
error = "SEND_SCOPE_BUSY";
|
|
11559
|
+
} else if (isInterrupted(cause) || isTransientRefusal(cause)) {
|
|
10645
11560
|
host.failure("SEND_INTERRUPTED", cause);
|
|
10646
11561
|
error = "SEND_INTERRUPTED";
|
|
10647
11562
|
} else {
|
|
@@ -10696,42 +11611,6 @@ function blockSend(host, error, signal) {
|
|
|
10696
11611
|
error: code
|
|
10697
11612
|
});
|
|
10698
11613
|
}
|
|
10699
|
-
/**
|
|
10700
|
-
* The public Work IDs this Send keeps on the owner's Passport: every sent Work's receipt and
|
|
10701
|
-
* every Work left out only because the same bundle and access were already sent to this
|
|
10702
|
-
* account. Dropped and other omitted Works are not kept (ADR 0204).
|
|
10703
|
-
*/
|
|
10704
|
-
function keepWorkIds(journey, send) {
|
|
10705
|
-
const unchanged = send.omitted.flatMap((omitted) => {
|
|
10706
|
-
const binding = journey.works.find((work) => work.id === omitted.workId)?.binding;
|
|
10707
|
-
return omitted.reason === "sent" && binding && binding.subject === send.subject ? [binding.publicWorkId] : [];
|
|
10708
|
-
});
|
|
10709
|
-
return [.../* @__PURE__ */ new Set([...send.items.flatMap((item) => item.receipt?.workId ?? []), ...unchanged])];
|
|
10710
|
-
}
|
|
10711
|
-
/**
|
|
10712
|
-
* Passport refused kept IDs it no longer has: a Work omitted as already sent whose Passport copy
|
|
10713
|
-
* an earlier Send retired. Those Works are sent again (a retired Work no longer matches, so each
|
|
10714
|
-
* becomes a new Work) and replace runs again once they are uploaded. Null when no refused ID is one.
|
|
10715
|
-
*/
|
|
10716
|
-
async function requeueRetired(journey, send, error, signal, dependencies) {
|
|
10717
|
-
if (error.code !== "unknown_keep_work_ids") return null;
|
|
10718
|
-
const refused = new Set(error.details?.workIds ?? []);
|
|
10719
|
-
const works = send.omitted.flatMap((omitted) => {
|
|
10720
|
-
const work = journey.works.find((value) => value.id === omitted.workId);
|
|
10721
|
-
return omitted.reason === "sent" && work?.binding?.subject === send.subject && refused.has(work.binding.publicWorkId) ? [work] : [];
|
|
10722
|
-
});
|
|
10723
|
-
if (!works.length) return null;
|
|
10724
|
-
const items = [];
|
|
10725
|
-
for (const work of works) items.push(await describeItem(journey, work, dependencies, signal));
|
|
10726
|
-
const ids = new Set(works.map((work) => work.id));
|
|
10727
|
-
return {
|
|
10728
|
-
...send,
|
|
10729
|
-
status: "sending",
|
|
10730
|
-
error: null,
|
|
10731
|
-
items: [...send.items, ...items],
|
|
10732
|
-
omitted: send.omitted.filter((omitted) => !ids.has(omitted.workId))
|
|
10733
|
-
};
|
|
10734
|
-
}
|
|
10735
11614
|
/** A Send saved before ADR 0204 may wait on a target or Settings question that is never asked. */
|
|
10736
11615
|
function withoutQuestions(send) {
|
|
10737
11616
|
if (!send.items.some((item) => ["target", "settings"].includes(item.status))) return send;
|
|
@@ -10749,6 +11628,18 @@ function withoutQuestions(send) {
|
|
|
10749
11628
|
};
|
|
10750
11629
|
return send.status === "review" ? aggregate(next) : next;
|
|
10751
11630
|
}
|
|
11631
|
+
/** Native completion follows acknowledged upload/membership, independently of assessment. */
|
|
11632
|
+
function completeAtUpload(send, sessions) {
|
|
11633
|
+
if (!send.confirmedAt || ![
|
|
11634
|
+
"sending",
|
|
11635
|
+
"waiting",
|
|
11636
|
+
"completed"
|
|
11637
|
+
].includes(send.status) || !send.items.length || !send.items.every((item) => sessions ? !!item.sessionAssembly : ["assessing", "completed"].includes(item.status) && !!item.receipt && item.receipt.state !== "awaiting_upload")) return send;
|
|
11638
|
+
return {
|
|
11639
|
+
...send,
|
|
11640
|
+
status: "completed"
|
|
11641
|
+
};
|
|
11642
|
+
}
|
|
10752
11643
|
async function advanceSend(dependencies, address, signal, chain) {
|
|
10753
11644
|
const snapshot = requireSend(await dependencies.storage.loadSend(address.journeyId, address.sendId));
|
|
10754
11645
|
if (chain) chain.revision = snapshot.send.revision;
|
|
@@ -10756,18 +11647,29 @@ async function advanceSend(dependencies, address, signal, chain) {
|
|
|
10756
11647
|
...snapshot,
|
|
10757
11648
|
status: "stale"
|
|
10758
11649
|
};
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
"completed",
|
|
10762
|
-
"prepared"
|
|
10763
|
-
].includes(snapshot.send.status)) return {
|
|
11650
|
+
const completed = snapshot.send.status === "completed";
|
|
11651
|
+
if (snapshot.journey.phase === "paused" || !snapshot.send.confirmedAt || ["paused", "prepared"].includes(snapshot.send.status) || completed && snapshot.send.items.every(settled)) return {
|
|
10764
11652
|
...snapshot,
|
|
10765
11653
|
status: "applied"
|
|
10766
11654
|
};
|
|
10767
|
-
const save = (next) => saveSend(dependencies, snapshot, next)
|
|
11655
|
+
const save = (next) => !completed ? saveSend(dependencies, snapshot, completeAtUpload(next, usesSessionSend(dependencies, next))) : JSON.stringify(next.items) === JSON.stringify(snapshot.send.items) ? Promise.resolve({
|
|
11656
|
+
...snapshot,
|
|
11657
|
+
status: "applied"
|
|
11658
|
+
}) : saveSend(dependencies, snapshot, {
|
|
11659
|
+
...snapshot.send,
|
|
11660
|
+
items: next.items
|
|
11661
|
+
});
|
|
10768
11662
|
try {
|
|
10769
11663
|
assertScope(snapshot.journey, snapshot.send);
|
|
10770
11664
|
} catch (error) {
|
|
11665
|
+
if (completed) return save({
|
|
11666
|
+
...snapshot.send,
|
|
11667
|
+
items: snapshot.send.items.map((item) => settled(item) ? item : {
|
|
11668
|
+
...item,
|
|
11669
|
+
status: "blocked",
|
|
11670
|
+
error: "SEND_SCOPE_CHANGED"
|
|
11671
|
+
})
|
|
11672
|
+
});
|
|
10771
11673
|
return blockSend({
|
|
10772
11674
|
send: snapshot.send,
|
|
10773
11675
|
failure: dependencies.failure,
|
|
@@ -10780,14 +11682,39 @@ async function advanceSend(dependencies, address, signal, chain) {
|
|
|
10780
11682
|
};
|
|
10781
11683
|
const send = withoutQuestions(snapshot.send);
|
|
10782
11684
|
if (send !== snapshot.send) return save(send);
|
|
11685
|
+
let independentItems = [];
|
|
10783
11686
|
return advanceItems({
|
|
10784
11687
|
send,
|
|
10785
11688
|
failure: dependencies.failure,
|
|
11689
|
+
...usesSessionSend(dependencies, send) ? {
|
|
11690
|
+
batch: () => {
|
|
11691
|
+
const limit = send.parallelCompletes ? 3 : 1;
|
|
11692
|
+
return independentItems.length ? pickBatch(independentItems, Date.now(), limit) : pickBatch(send.items, Date.now(), limit);
|
|
11693
|
+
},
|
|
11694
|
+
preflight: async (connection, preflightSignal) => {
|
|
11695
|
+
if (!connection.sessions) throw new ApplicationError("INVALID_STATE");
|
|
11696
|
+
const inventory = await connection.sessions.inventory(preflightSignal);
|
|
11697
|
+
if (!await sessionAccessBlocked(dependencies, snapshot.journey.journeyId, send.items, inventory, preflightSignal)) {
|
|
11698
|
+
independentItems = await sessionIndependentItems(dependencies, snapshot.journey.journeyId, send.items, inventory, preflightSignal);
|
|
11699
|
+
return null;
|
|
11700
|
+
}
|
|
11701
|
+
await connection.assertCurrent(preflightSignal);
|
|
11702
|
+
return save({
|
|
11703
|
+
...send,
|
|
11704
|
+
status: "blocked",
|
|
11705
|
+
items: send.items.map((item) => item.sessionAssembly || item.status === "completed" ? item : {
|
|
11706
|
+
...item,
|
|
11707
|
+
status: "blocked",
|
|
11708
|
+
error: "SESSION_MOVE_ACCESS_REQUIRED"
|
|
11709
|
+
})
|
|
11710
|
+
});
|
|
11711
|
+
},
|
|
11712
|
+
step: (context, item, connection, stepSignal) => stepSessionWork(dependencies, snapshot.journey.journeyId, context, item, connection, stepSignal)
|
|
11713
|
+
} : {},
|
|
10786
11714
|
connect: chain?.connect ?? dependencies.auth.connect,
|
|
10787
11715
|
profile: (connection, profileSignal) => prepareProfile(dependencies, snapshot, connection, profileSignal),
|
|
10788
11716
|
context: (item) => {
|
|
10789
11717
|
const binding = snapshot.journey.works.find((value) => value.id === item.workId)?.binding;
|
|
10790
|
-
const retired = !item.receipt && binding?.sentBundleId === item.artifact.bundleId && binding.sentAccess === item.access;
|
|
10791
11718
|
return {
|
|
10792
11719
|
snapshot: true,
|
|
10793
11720
|
...chain ? { fresh: chain.fresh } : {},
|
|
@@ -10796,7 +11723,7 @@ async function advanceSend(dependencies, address, signal, chain) {
|
|
|
10796
11723
|
if (current.send.id !== snapshot.send.id || !sameItem(current.send.items, item) || current.send.status === "paused" || current.journey.phase === "paused") throw new ApplicationError("REVISION_CONFLICT");
|
|
10797
11724
|
assertScope(current.journey, current.send);
|
|
10798
11725
|
},
|
|
10799
|
-
bindingId: binding?.subject === send.subject
|
|
11726
|
+
bindingId: binding?.subject === send.subject ? binding.bundleRecordId : void 0,
|
|
10800
11727
|
upload: async (artifact, upload, uploadSignal) => {
|
|
10801
11728
|
const started = performance.now();
|
|
10802
11729
|
await dependencies.artifacts.upload(snapshot.journey.journeyId, artifact, upload, uploadSignal);
|
|
@@ -10804,36 +11731,6 @@ async function advanceSend(dependencies, address, signal, chain) {
|
|
|
10804
11731
|
}
|
|
10805
11732
|
};
|
|
10806
11733
|
},
|
|
10807
|
-
finish: async (connection, finishSignal) => {
|
|
10808
|
-
if (send.replaced || !send.items.every((item) => ["assessing", "completed"].includes(item.status))) return null;
|
|
10809
|
-
let retired;
|
|
10810
|
-
try {
|
|
10811
|
-
({retiredWorkIds: retired} = await connection.replace(keepWorkIds(snapshot.journey, send), finishSignal));
|
|
10812
|
-
} catch (error) {
|
|
10813
|
-
if (error instanceof JourneyRemoteError && error.status === 401) return save({
|
|
10814
|
-
...send,
|
|
10815
|
-
status: "sign-in",
|
|
10816
|
-
error: null
|
|
10817
|
-
});
|
|
10818
|
-
if (!(error instanceof JourneyRemoteError) || error.status !== 400) throw error;
|
|
10819
|
-
const requeued = await requeueRetired(snapshot.journey, send, error, finishSignal, dependencies);
|
|
10820
|
-
if (requeued) return save(requeued);
|
|
10821
|
-
dependencies.failure("SEND_REPLACE_REFUSED", error);
|
|
10822
|
-
return save({
|
|
10823
|
-
...send,
|
|
10824
|
-
status: "blocked",
|
|
10825
|
-
error: "SEND_REPLACE_REFUSED"
|
|
10826
|
-
});
|
|
10827
|
-
}
|
|
10828
|
-
return save(aggregate({
|
|
10829
|
-
...send,
|
|
10830
|
-
error: null,
|
|
10831
|
-
replaced: {
|
|
10832
|
-
at: dependencies.now(),
|
|
10833
|
-
retiredWorkCount: retired.length
|
|
10834
|
-
}
|
|
10835
|
-
}));
|
|
10836
|
-
},
|
|
10837
11734
|
save,
|
|
10838
11735
|
applied: () => ({
|
|
10839
11736
|
...snapshot,
|
|
@@ -10845,6 +11742,28 @@ async function advanceSend(dependencies, address, signal, chain) {
|
|
|
10845
11742
|
})
|
|
10846
11743
|
}, signal);
|
|
10847
11744
|
}
|
|
11745
|
+
/** The first wait after a bare refusal of staged bytes, doubled per refusal up to the cap. */
|
|
11746
|
+
const busyBaseMs = 2e3;
|
|
11747
|
+
/** Short, because a waiting advance holds the panel's single advance in flight. */
|
|
11748
|
+
const busyMaxMs = 16e3;
|
|
11749
|
+
function busyDelay(refusals) {
|
|
11750
|
+
return Math.min(busyBaseMs * 2 ** (refusals - 1), busyMaxMs);
|
|
11751
|
+
}
|
|
11752
|
+
/** Waits `ms`; Pause aborts the wait with the advance. */
|
|
11753
|
+
function wait(ms, signal) {
|
|
11754
|
+
return new Promise((resolve, reject) => {
|
|
11755
|
+
signal.throwIfAborted();
|
|
11756
|
+
const stop = () => {
|
|
11757
|
+
clearTimeout(timer);
|
|
11758
|
+
reject(signal.reason);
|
|
11759
|
+
};
|
|
11760
|
+
const timer = setTimeout(() => {
|
|
11761
|
+
signal.removeEventListener("abort", stop);
|
|
11762
|
+
resolve();
|
|
11763
|
+
}, ms);
|
|
11764
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
11765
|
+
});
|
|
11766
|
+
}
|
|
10848
11767
|
/**
|
|
10849
11768
|
* A refusal Passport answers while it or Identity is briefly unavailable. It says nothing about
|
|
10850
11769
|
* the Work, so the step is retried instead of blocking it (the 2026-11-01 recorded run blocked a
|
|
@@ -12363,12 +13282,12 @@ function createJourneyFiles(storage) {
|
|
|
12363
13282
|
};
|
|
12364
13283
|
}
|
|
12365
13284
|
/**
|
|
12366
|
-
* One advance carries the
|
|
12367
|
-
* instead of one phase per panel round trip, and uploads a
|
|
12368
|
-
* without reading it again (ADR 0229). Each phase is still
|
|
12369
|
-
* revision change or a failure stops the chain at the
|
|
12370
|
-
*
|
|
12371
|
-
* caller's next advance
|
|
13285
|
+
* One advance carries the Works being uploaded, up to three at once (ADR 0235), through their
|
|
13286
|
+
* phases with one verified connection, instead of one phase per panel round trip, and uploads a
|
|
13287
|
+
* receipt it just resolved and reserved without reading it again (ADR 0229). Each phase is still
|
|
13288
|
+
* fenced and saved on its own, so Pause, a revision change or a failure stops the chain at the
|
|
13289
|
+
* next phase exactly as between advances. The advance returns after three phases or once no
|
|
13290
|
+
* picked Work is mid-upload, so later Works continue from the caller's next advance.
|
|
12372
13291
|
*/
|
|
12373
13292
|
async function advanceWork(dependencies, address, signal, phases = 3) {
|
|
12374
13293
|
let connection;
|
|
@@ -12386,7 +13305,7 @@ async function advanceWork(dependencies, address, signal, phases = 3) {
|
|
|
12386
13305
|
/** The phase saved progress and left its Work mid-upload, with nothing else to report. */
|
|
12387
13306
|
function uploading(result, before) {
|
|
12388
13307
|
const send = result.send;
|
|
12389
|
-
return result.status === "applied" && send !== null && before !== void 0 && send.revision > before && send.status === "sending" && send.error === null && pickBatch(send.items)[0]?.status === "uploading";
|
|
13308
|
+
return result.status === "applied" && send !== null && before !== void 0 && send.revision > before && send.status === "sending" && send.error === null && pickBatch(send.items, Date.now(), send.parallelCompletes ? 3 : 1)[0]?.status === "uploading";
|
|
12390
13309
|
}
|
|
12391
13310
|
//#endregion
|
|
12392
13311
|
//#region apps/cli/src/account/journey/send.ts
|
|
@@ -12409,6 +13328,45 @@ function createJourneySender(dependencies, phases = 3) {
|
|
|
12409
13328
|
get(address) {
|
|
12410
13329
|
return dependencies.storage.loadSend(address.journeyId, address.sendId);
|
|
12411
13330
|
},
|
|
13331
|
+
/** The owner's current address, links and account email; null while signed out. */
|
|
13332
|
+
async ownerProfile(signal) {
|
|
13333
|
+
const connection = await dependencies.auth.connect(signal);
|
|
13334
|
+
if (!connection) return null;
|
|
13335
|
+
const { profile, accountEmail } = await connection.readProfile(signal);
|
|
13336
|
+
return {
|
|
13337
|
+
address: profile.address,
|
|
13338
|
+
links: profile.links,
|
|
13339
|
+
accountEmail
|
|
13340
|
+
};
|
|
13341
|
+
},
|
|
13342
|
+
/** A best-effort availability check for the address card; Send's save decides. */
|
|
13343
|
+
async addressTaken(input, signal) {
|
|
13344
|
+
const address = passportAddressSchema.parse(input.address);
|
|
13345
|
+
const connection = await dependencies.auth.connect(signal);
|
|
13346
|
+
if (!connection) return null;
|
|
13347
|
+
return { taken: await connection.addressTaken(address, signal) };
|
|
13348
|
+
},
|
|
13349
|
+
/**
|
|
13350
|
+
* The address or links chosen on the Sent page, after the Send reached Passport (ADR 0246,
|
|
13351
|
+
* BL-230): an explicit Keep or Add saves them at once over the current settings.
|
|
13352
|
+
*/
|
|
13353
|
+
async saveSentProfile(input, signal) {
|
|
13354
|
+
const latest = requireSend(await dependencies.storage.loadSend(input.journeyId));
|
|
13355
|
+
const chosen = latest.journey.profile;
|
|
13356
|
+
if (latest.send.status !== "completed" || !chosen || !profileHasChoices(chosen)) throw new ApplicationError("INVALID_TRANSITION");
|
|
13357
|
+
const connection = await dependencies.auth.connect(signal);
|
|
13358
|
+
if (!connection) throw new ApplicationError("ACCOUNT_SIGNED_OUT");
|
|
13359
|
+
if (latest.send.subject !== connection.subject) throw new ApplicationError("IDENTITY_REJECTED");
|
|
13360
|
+
try {
|
|
13361
|
+
const current = await connection.readProfile(signal);
|
|
13362
|
+
await connection.saveProfile(mergeOwnerProfile(current.profile, chosen), signal);
|
|
13363
|
+
} catch (error) {
|
|
13364
|
+
if (signal.aborted || error instanceof ApplicationError) throw error;
|
|
13365
|
+
const code = error instanceof JourneyRemoteError && error.status === 409 ? addressRefusals[error.code] : void 0;
|
|
13366
|
+
if (code) throw new ApplicationError(code);
|
|
13367
|
+
throw new ApplicationError("ACCOUNT_PROFILE_UNAVAILABLE", void 0, { cause: error });
|
|
13368
|
+
}
|
|
13369
|
+
},
|
|
12412
13370
|
async prepare(raw, signal) {
|
|
12413
13371
|
const input = sendPrepareSchema.parse(raw);
|
|
12414
13372
|
const snapshot = await dependencies.storage.loadSend(input.journeyId);
|
|
@@ -12418,7 +13376,42 @@ function createJourneySender(dependencies, phases = 3) {
|
|
|
12418
13376
|
status: "stale"
|
|
12419
13377
|
};
|
|
12420
13378
|
const draft = await draftSend(snapshot.journey, dependencies, signal);
|
|
12421
|
-
const
|
|
13379
|
+
const recover = snapshot.send && [
|
|
13380
|
+
"blocked",
|
|
13381
|
+
"paused",
|
|
13382
|
+
"completed"
|
|
13383
|
+
].includes(snapshot.send.status) && snapshot.send.items.some((item) => [
|
|
13384
|
+
"SESSION_MEMBERSHIP_CONFLICT",
|
|
13385
|
+
"LEGACY_SEND_RECOVERY_REQUIRED",
|
|
13386
|
+
"ASSESSMENT_INCOMPLETE",
|
|
13387
|
+
"SESSION_PUBLICATION_FENCE_REQUIRED"
|
|
13388
|
+
].includes(item.error ?? ""));
|
|
13389
|
+
if (recover && snapshot.send) {
|
|
13390
|
+
const connection = await dependencies.auth.connect(signal);
|
|
13391
|
+
if (!connection) throw new ApplicationError("ACCOUNT_SIGNED_OUT");
|
|
13392
|
+
if (snapshot.send.subject !== null && snapshot.send.subject !== connection.subject) throw new ApplicationError("IDENTITY_REJECTED");
|
|
13393
|
+
const sessions = dependencies.artifacts.sessions;
|
|
13394
|
+
const remote = connection.sessions;
|
|
13395
|
+
if (sessions && remote) {
|
|
13396
|
+
const inventory = await remote.inventory(signal);
|
|
13397
|
+
for (const item of draft.items) {
|
|
13398
|
+
const current = inventory.works.find((work) => work.logicalWorkId === item.workId);
|
|
13399
|
+
const assembly = await remote.assembly(item.workId, signal);
|
|
13400
|
+
const selected = [];
|
|
13401
|
+
for await (const session of sessions(input.journeyId, item.artifact, signal)) selected.push(await sessionKeyFor(session));
|
|
13402
|
+
const currentKeys = current?.sessions.map((session) => session.key) ?? [];
|
|
13403
|
+
item.recoveryReview = {
|
|
13404
|
+
previousSendId: snapshot.send.id,
|
|
13405
|
+
revision: assembly?.revision ?? 0,
|
|
13406
|
+
currentAccess: current?.access ?? null,
|
|
13407
|
+
currentKeys,
|
|
13408
|
+
proposedKeys: [.../* @__PURE__ */ new Set([...currentKeys, ...selected])]
|
|
13409
|
+
};
|
|
13410
|
+
}
|
|
13411
|
+
await connection.assertCurrent(signal);
|
|
13412
|
+
}
|
|
13413
|
+
}
|
|
13414
|
+
const reuse = !recover && snapshot.send?.scopeDigest === draft.scopeDigest && snapshot.send.subject === draft.subject;
|
|
12422
13415
|
const next = reuse && snapshot.send ? {
|
|
12423
13416
|
...snapshot.send,
|
|
12424
13417
|
revision: snapshot.send.revision + 1
|
|
@@ -12476,6 +13469,7 @@ function createJourneySender(dependencies, phases = 3) {
|
|
|
12476
13469
|
async pause(raw) {
|
|
12477
13470
|
const input = sendActionSchema.parse(raw);
|
|
12478
13471
|
const snapshot = await load(input);
|
|
13472
|
+
if (snapshot.send.status === "completed" || snapshot.send.replaced) throw new ApplicationError("INVALID_TRANSITION");
|
|
12479
13473
|
const result = await saveSend(dependencies, snapshot, {
|
|
12480
13474
|
...snapshot.send,
|
|
12481
13475
|
status: "paused"
|
|
@@ -12488,7 +13482,7 @@ function createJourneySender(dependencies, phases = 3) {
|
|
|
12488
13482
|
const input = sendActionSchema.parse(raw);
|
|
12489
13483
|
const snapshot = await load(input);
|
|
12490
13484
|
if (!matchesAction(snapshot, input)) return saveSend(dependencies, snapshot, snapshot.send, input);
|
|
12491
|
-
if (snapshot.send.status !== "sign-in" || snapshot.send.items.some((item) => item.receipt)) throw new ApplicationError("INVALID_TRANSITION");
|
|
13485
|
+
if (snapshot.send.status !== "sign-in" || snapshot.send.items.some((item) => item.receipt || item.previousReceipts.length > 0 || item.sessionUpload !== void 0 || item.sessionMembership || item.sessionAssembly)) throw new ApplicationError("INVALID_TRANSITION");
|
|
12492
13486
|
const result = await saveSend(dependencies, snapshot, {
|
|
12493
13487
|
...snapshot.send,
|
|
12494
13488
|
status: "prepared",
|
|
@@ -14408,6 +15402,18 @@ async function compose(options, callbacks, bundles) {
|
|
|
14408
15402
|
})();
|
|
14409
15403
|
return {
|
|
14410
15404
|
application,
|
|
15405
|
+
remoteWorkers: createRemotePreflight({
|
|
15406
|
+
storage: journeyStorage,
|
|
15407
|
+
harnesses,
|
|
15408
|
+
execution,
|
|
15409
|
+
stateDir,
|
|
15410
|
+
home: homedir(),
|
|
15411
|
+
roots: {
|
|
15412
|
+
codexHome: options.codexHome,
|
|
15413
|
+
claudeHome: options.claudeHome
|
|
15414
|
+
},
|
|
15415
|
+
reportFailure: (error) => diagnostics.onFailure("remote-workers", error)
|
|
15416
|
+
}),
|
|
14411
15417
|
nativeInspection: {
|
|
14412
15418
|
originals: createOriginalView(journeyStorage, stateDir),
|
|
14413
15419
|
output: createOutputView(journeyStorage, processing),
|
|
@@ -14512,6 +15518,7 @@ async function invoke(runtime, command, value, signal) {
|
|
|
14512
15518
|
const app = runtime.application;
|
|
14513
15519
|
markAgentPresence(runtime.stateDir, value);
|
|
14514
15520
|
switch (command) {
|
|
15521
|
+
case "journey.remoteWorkers": return runtime.remoteWorkers(value, signal);
|
|
14515
15522
|
case "send.get": return app.journeySend?.get(sendAddress.partial({ sendId: true }).parse(value));
|
|
14516
15523
|
case "send.prepare": return app.journeySend?.prepare(sendPrepareSchema.parse(value));
|
|
14517
15524
|
case "send.start": return app.journeySend?.start(sendStartSchema.parse(value));
|
|
@@ -14745,4 +15752,4 @@ async function cancelOperation(runtime, id) {
|
|
|
14745
15752
|
return readOperation(runtime, id);
|
|
14746
15753
|
}
|
|
14747
15754
|
//#endregion
|
|
14748
|
-
export {
|
|
15755
|
+
export { readStateDump as _, readAgentPresence as a, traceStartup as b, executorRequestSchema as c, helpText as d, parseOptions as f, startupFailure as g, permissionDiagnostic as h, invoke as i, awaitingPassport as l, longCommands as m, readOperation as n, createRuntime as o, businessCommands as p, waitOperation as r, executorEventSchema as s, cancelOperation as t, createRenderActivity as u, createExecutionPort as v, startHost as y };
|