@gitroll/tacitt-cli 0.2.0 → 0.3.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-B8DAiQ6Z.mjs → agent-setup-CLXdKr2I.mjs} +1 -1
- package/dist/agent-setup-worker.mjs +4 -4
- package/dist/cli.mjs +65 -8
- package/dist/codex-title-worker.mjs +1 -1
- package/dist/configured-preview-worker.mjs +4 -4
- package/dist/configured-session-worker.mjs +25 -4
- package/dist/{heap-BHcvxWCw.mjs → heap-eO_uWpD1.mjs} +45 -3
- package/dist/inspection-worker.mjs +3 -3
- package/dist/job-rK-Ho9OD.mjs +613 -0
- package/dist/{model-BJdTLNjT.mjs → model-BBAnsFAx.mjs} +1 -1
- package/dist/operation-executor.mjs +2 -2
- package/dist/preview-worker.mjs +1 -1
- package/dist/{query-BA0XCwnR.mjs → query-DN99jiRc.mjs} +273 -551
- package/dist/{run-BeLHsZWb.mjs → run-4G_CvdDj.mjs} +2 -2
- package/dist/session-process.mjs +6 -1
- package/dist/{src-CC95LYYp.mjs → src-B4R5XMEP.mjs} +370 -176
- package/dist/{src-k0Ya1Bri.mjs → src-CPbdnGrk.mjs} +118 -47
- package/dist/{src-B5SArpVY.mjs → src-qoLRvX-A.mjs} +17 -5
- package/dist/store-database-worker.mjs +1 -1
- package/dist/web/assets/index-BkuUI3Eu.js +67 -0
- package/dist/web/assets/index-Clg6Pfv_.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/skills/tacitt/SKILL.md +8 -6
- package/skills/tacitt/references/commands.md +14 -9
- package/skills/tacitt/references/journey.md +10 -6
- package/skills/tacitt/references/send.md +1 -1
- package/skills/tacitt/scripts/INDEX.md +6 -2
- package/skills/tacitt/scripts/journey-card-consolidation.mjs +90 -4
- package/skills/tacitt/scripts/journey-card-values.d.mts +17 -0
- package/skills/tacitt/scripts/journey-card-values.mjs +116 -0
- package/skills/tacitt/scripts/journey-classification.mjs +6 -2
- package/skills/tacitt/scripts/journey-panel-host.d.mts +9 -0
- package/skills/tacitt/scripts/journey-panel-host.mjs +56 -0
- package/skills/tacitt/scripts/journey-state.mjs +10 -4
- package/skills/tacitt/scripts/journey-tasks.mjs +1 -1
- package/skills/tacitt/scripts/journey.d.mts +2 -0
- package/skills/tacitt/scripts/journey.mjs +120 -53
- package/skills/tacitt/scripts/review-stream-client.mjs +1 -1
- package/dist/job-J3NFw3MH.mjs +0 -194
- package/dist/web/assets/index-Bjos73wK.js +0 -67
- package/dist/web/assets/index-Bl4C2b2_.css +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { A as runConfiguredSessionJob, C as readConfiguredSearchFields, D as captureLogicalJourneySources,
|
|
2
|
-
import { Bt as sessionPeriodSchema, Dn as entityDictionaryVersion, F as bClassificationKey, Fn as frameRecords, Ft as termTypeInfo, Gt as safeField, H as codePointLength, Ht as ApplicationError, Jt as reviewFamilies, K as utf16Offset, N as bUnitFragments, Ot as privacyTermKey, Qn as admitGitName, T as bLiteralTerms, Ut as UnfinishedJourneyError, V as bDigest, Wt as safeCode, Xn as createEntityMatcher, Zn as admitCustomTerm, Zt as observeSource, b as validateBReview, c as textDigest, h as reviewFindingsDigest, ht as journeyDescribeSchema, jt as reviewTermsInputSchema, l as validateReview, mn as currentDetectorVersion, n as compileBFindings, r as reviewBatchChunks, s as reviewSubmitSchema, t as locateTerms, yn as createStaticProcessor, z as bAnchorKey
|
|
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 { $
|
|
5
|
-
import { $ as
|
|
6
|
-
import { a as writeJourneyOutput, c as mergeHideIndexTokens, l as packRenderHideIndex, o as createHideIndexTokens, s as hideIndexTexts, t as journeyOutputJob } from "./job-
|
|
7
|
-
import { t as createAgentSetupPort } from "./agent-setup-
|
|
1
|
+
import { A as runConfiguredSessionJob, C as readConfiguredSearchFields, D as captureLogicalJourneySources, F as sessionWorkerFailure, G as staticMaskCategory, H as readLogicalJourneyFinalMasks, I as privateCollectionDirectory, K as readWorkspacePair, L as defaultParallelism, M as parseCollectionWorkspaceSummary, N as createSharedSessionPool, O as capturedJourneyScope, P as SessionWorkerError, Q as createCleanup, R as gitSeedsProvider, S as readOriginalSearchFields, T as checkReusableBundle, U as readConfiguredOutputInfo, V as privateJourneyDirectory, W as processJourneyMetadataFields, X as createTemporary, Z as removeNamedTemporarySpools, _ as readStaticSessionMasks, a as scanReviewFields, at as isWindowsAclFailure, c as readSessionScanRecords, g as processJourneyMetadata, h as readCapturedOriginalRows, i as buildWorkScanBatches, it as windowsOwnerOnly, j as readCollectionIndex, l as scanPreparedSession, n as verifyPreparedSessionSources, nt as openPrivateFile, o as createWorkPieces, ot as windowsAclCodes, p as openConfiguredUpload, r as prepareSessionScans, rt as restrictToOwner, s as readSessionScanFields, t as preparedScanConfiguration, tt as makePrivateDirectory, v as staticSessionJobs, w as createCollectionArtifacts, z as parallelismLimits } from "./src-CPbdnGrk.mjs";
|
|
2
|
+
import { Bt as sessionPeriodSchema, Dn as entityDictionaryVersion, F as bClassificationKey, Fn as frameRecords, Ft as termTypeInfo, Gt as safeField, H as codePointLength, Ht as ApplicationError, Jt as reviewFamilies, K as utf16Offset, N as bUnitFragments, Ot as privacyTermKey, Qn as admitGitName, T as bLiteralTerms, Ut as UnfinishedJourneyError, V as bDigest, Wt as safeCode, Xn as createEntityMatcher, Zn as admitCustomTerm, Zt as observeSource, b as validateBReview, c as textDigest, h as reviewFindingsDigest, ht as journeyDescribeSchema, jt as reviewTermsInputSchema, l as validateReview, mn as currentDetectorVersion, n as compileBFindings, r as reviewBatchChunks, s as reviewSubmitSchema, t as locateTerms, yn as createStaticProcessor, z as bAnchorKey } from "./locate-U3-nkird.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-qoLRvX-A.mjs";
|
|
4
|
+
import { $ as settingsQuerySchema, $n as reviewCandidateInputSchema, $t as journeySearchResultSchema, A as deviceProfileScopeSchema, Ar as nativeEventsSchema, B as validateInspectionBatch, Br as isSelectedWork, Bt as journeyReviewStorageQuerySchema, C as rebindReviewSubmit, Ct as sendStartSchema, D as createAccount, Dt as sendReplaceResultSchema, E as staticallyHiddenFindings, En as confirmedHideAdditions, Er as journeyClassificationSessionsSchema, Et as sendReceiptSchema, Gr as journeyFindingsSchema, Gt as retireScanGeneration, Hr as withReadingProgress, Jr as journeyRenderSchema, Jt as journeyContextQuerySchema, Kn as journeyConfirmedHides, Kr as journeyPreviewSchema, Kt as newJourneyWork, L as createApplication, Lr as hasCompleteWorkDescription, M as readAccountAvatarUrl, Mr as nativeRespondSchema, Mt as obsoleteJourney, N as storedCredentialSchema, Nr as hasCurrentStaticReview, O as accountProfileSchema, On as readConfirmedHideChange, Or as assertJourneyPrivacyReady, Ot as sendResolutionSchema, Pr as staticReviewDigest, Q as settingsBatchSchema, Qn as workCorrectionBinding, Qr as selectionSchema, R as inspectionCancelSchema, Rr as isClassifying, Rt as applyClassification, S as rebindReviewOverlay, St as sendPrepareSchema, T as guardedFindingText, Tt as sendArtifactSchema, U as inspectionQuerySchema, Un as confirmedHideKinds, Ut as classificationPartition, Vn as confirmedHideGroupIds, Wr as journeyFindingImpactSchema, Wt as classificationWorks, Xn as workDecisionDigest, Xr as packageCommandSchema, Yn as retireWorkDescription, Yr as commandSchema, Zr as selectionBatchSchema, _n as sessionReviewInputSchema, _r as journeyInventoryQuerySchema, _t as maximumTermsFileBytes, a as bCrossSegmentReviewSchema, b as bReviewScope, bt as sendActionSchema, c as reconstructBFields, ct as validateFailureLocation, d as selectedValueMembers, dr as scanGitSeedsId, dt as manualUploadChoiceSchema, ei as producerVersion, er as reviewCandidateResourceSchema, et as readWorkspaceOutcomes, f as splitSelectorValues, fn as sessionReconcileInputSchema, fr as scanGitSeedsResource, ft as manualUploadPrepareSchema, g as changeJourney, gt as emptyTermsRevision, hn as sessionRereadInputSchema, i as validateBCrossSegmentReview, in as journeyResourceQuerySchema, ir as bReviewDigest, j as profileUrlMatchesIdentity, jn as termHideAdditions, jr as nativeGetSchema, jt as currentJourneyAdapters, k as deviceAuthorizationSchema, kr as privacyDecisionDigest, kt as sendSettingsSchema, ln as reviewAttemptResourceSchema, lr as scanPreparationDigest, lt as manualUploadActionSchema, m as hideScopeDigest, mr as scanExclusionResourceSchema, mt as manualUploadStartSchema, n as scanBatchEquivalenceDigest, nn as journeyPolicyRespondSchema, nr as reviewRepairResourceSchema, nt as appendSensitiveTerms, o as bRereadDigest, or as planBRepair, ot as artifactFailureDetailSchema, p as activeJourneyTerms, pr as verifiedScanGitSeeds, pt as manualUploadSchema, qn as maskedHitsStale, qr as journeyRenderInputSchema, qt as reviewCompileSchema, r as locateTermEvidence, ri as recommendParallelism, s as reconcileBSession, sn as journeyPlaceSchema, st as resolveFailureLocation, t as scanBatchContextDigest, ti as stateSchema, tn as searchScopeDigest, tr as reviewRepairInputSchema, ur as scanSessionResourceSchema, vn as sessionReviewQuerySchema, vt as normalizeSensitiveTerms, w as reviewBatchReuse, wt as journeySendSchema, x as rebindReviewAlignment, xn as sessionUsefulnessInputSchema, xr as emptyPreparationFacts, xt as sendChoiceSchema, y as isGenericPublicTerm, yt as replaceSensitiveTermsSchema, z as inspectionStartSchema, zn as bTermMatcher, zr as isReadingComplete } from "./src-B4R5XMEP.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-eO_uWpD1.mjs";
|
|
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-rK-Ho9OD.mjs";
|
|
7
|
+
import { t as createAgentSetupPort } from "./agent-setup-CLXdKr2I.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";
|
|
@@ -651,6 +651,7 @@ const operation = z.enum([
|
|
|
651
651
|
"journey.terms.select-aliases",
|
|
652
652
|
"journey.commit",
|
|
653
653
|
"journey.commit.checked",
|
|
654
|
+
"journey.classification.commit",
|
|
654
655
|
"journey.events",
|
|
655
656
|
"journey.inventory",
|
|
656
657
|
"journey.preparation.progress",
|
|
@@ -853,7 +854,11 @@ const nativePanelRespondSchema = z.object({
|
|
|
853
854
|
remember: z.boolean().optional(),
|
|
854
855
|
excludedTermKeys: z.array(z.string().min(1)).max(2e4).optional()
|
|
855
856
|
}).strict(),
|
|
856
|
-
z.object({ kind: z.literal("package-confirm") }).strict()
|
|
857
|
+
z.object({ kind: z.literal("package-confirm") }).strict(),
|
|
858
|
+
z.object({
|
|
859
|
+
kind: z.literal("retry-work"),
|
|
860
|
+
workId: z.uuid()
|
|
861
|
+
}).strict()
|
|
857
862
|
])
|
|
858
863
|
}).strict();
|
|
859
864
|
function createRouter(application, account, stopping, delivery, nativeInspection, agentPresence) {
|
|
@@ -944,6 +949,7 @@ function createRouter(application, account, stopping, delivery, nativeInspection
|
|
|
944
949
|
cardsRead: t.procedure.input(nativeGetSchema.required()).query(({ input }) => native().cardsRead(input)),
|
|
945
950
|
files: t.procedure.input(journeyRenderSchema).query(({ input, signal }) => native().files(input, signal)),
|
|
946
951
|
searchResult: t.procedure.input(nativeGetSchema.required()).query(({ input }) => native().searchResult(input)),
|
|
952
|
+
prepare: t.procedure.input(nativeGetSchema.required()).mutation(({ input }) => native().prepare(input, stopping)),
|
|
947
953
|
render: t.procedure.input(journeyRenderInputSchema).mutation(({ input, signal }) => native().render(input, signal)),
|
|
948
954
|
findings: t.procedure.input(journeyFindingsSchema).query(({ input, signal }) => native().findings(input, signal)),
|
|
949
955
|
findingImpact: t.procedure.input(journeyFindingImpactSchema).query(({ input, signal }) => native().findingImpact(input, signal)),
|
|
@@ -4722,6 +4728,19 @@ function createStagedJourney(access, root, databaseId) {
|
|
|
4722
4728
|
});
|
|
4723
4729
|
});
|
|
4724
4730
|
},
|
|
4731
|
+
/** BL-216: as `commit`, for a proposal the store applies to its current state. */
|
|
4732
|
+
commitClassification(input) {
|
|
4733
|
+
return access.staged(async (leg, signal) => {
|
|
4734
|
+
const first = await leg.run("journey.classification.commit", input);
|
|
4735
|
+
if (first.status !== "hide-delta") return first;
|
|
4736
|
+
const key = await files.hideKey(first.state.journeyId, signal);
|
|
4737
|
+
const delta = await readConfirmedHideChange({ journeyTerms: (query) => leg.run("journey.terms.read", query) }, key, first.state, first.next);
|
|
4738
|
+
return leg.run("journey.commit", {
|
|
4739
|
+
...classificationJourneyCommit(input, first.next, first.state.revision),
|
|
4740
|
+
confirmedHideDelta: delta
|
|
4741
|
+
});
|
|
4742
|
+
});
|
|
4743
|
+
},
|
|
4725
4744
|
resources: createStagedResources(access, files, (input) => inspection.protectedBase(input)),
|
|
4726
4745
|
saveResource: createStagedResourceSave(access, files),
|
|
4727
4746
|
async putBatch(batch) {
|
|
@@ -4996,6 +5015,7 @@ async function openStore(options, backupObsolete) {
|
|
|
4996
5015
|
acknowledgeSessionUsefulness: (input) => stagedJourney.acknowledgeSessionUsefulness(input),
|
|
4997
5016
|
current: (journeyId) => access.run("journey.current", journeyId ? { journeyId } : {}),
|
|
4998
5017
|
commit: (input) => stagedJourney.commit(input),
|
|
5018
|
+
commitClassification: (input) => stagedJourney.commitClassification(input),
|
|
4999
5019
|
hideIndexKey: (journeyId) => stagedJourney.hideIndexKey(journeyId),
|
|
5000
5020
|
recordTerms: (input) => stagedJourney.recordTerms(input),
|
|
5001
5021
|
journeyTerms: (input) => stagedJourney.journeyTerms(input),
|
|
@@ -5566,7 +5586,8 @@ ${Array.from(businessCommands).join("\n")}
|
|
|
5566
5586
|
Business commands invoke the shared Application directly without an HTTP server.
|
|
5567
5587
|
journey.serve starts a focused confirmation host, emits JSON readiness, and waits until stopped.
|
|
5568
5588
|
journey.serve --detach starts that host in its own process, prints its readiness with the host's
|
|
5569
|
-
pid, and exits; stop the host with that pid.
|
|
5589
|
+
pid, and exits; stop the host with that pid. A detached host already serving the journey is
|
|
5590
|
+
reused and reported with reused: true.
|
|
5570
5591
|
It does not discover sessions or open a browser. The agent opens its private URL in the right pane.
|
|
5571
5592
|
Workflow mutations require protocolVersion, runId and expectedRevision in JSON input.
|
|
5572
5593
|
Long commands emit the operation ID immediately as NDJSON, then wait up to 30000 ms.
|
|
@@ -5775,105 +5796,6 @@ function createJourneyFindings(storage) {
|
|
|
5775
5796
|
};
|
|
5776
5797
|
}
|
|
5777
5798
|
//#endregion
|
|
5778
|
-
//#region apps/cli/src/journey/plan.ts
|
|
5779
|
-
async function prepareSourcePlan(storage, harnesses, journeyId, sensitiveTerms, signal, period = {}) {
|
|
5780
|
-
const saved = (await storage.resources({
|
|
5781
|
-
journeyId,
|
|
5782
|
-
kind: "plan"
|
|
5783
|
-
}))[0];
|
|
5784
|
-
if (saved?.kind === "plan") return saved;
|
|
5785
|
-
const discovered = await harnesses.discover(signal);
|
|
5786
|
-
if (!discovered.complete) throw new ApplicationError("SCOPE_NOT_READY");
|
|
5787
|
-
const inventory = filterInventoryByPeriod(discovered, period);
|
|
5788
|
-
if (inventory.sessions.length === 0) throw new ApplicationError("INVALID_SELECTION");
|
|
5789
|
-
const families = reviewFamilies(inventory);
|
|
5790
|
-
const defaults = defaultScopedSettings();
|
|
5791
|
-
const settings = {
|
|
5792
|
-
...defaults,
|
|
5793
|
-
overall: {
|
|
5794
|
-
...defaults.overall,
|
|
5795
|
-
cleanup: {
|
|
5796
|
-
ref: {
|
|
5797
|
-
id: "native-evidence",
|
|
5798
|
-
version: "2"
|
|
5799
|
-
},
|
|
5800
|
-
options: { assessedSessionsOnly: true }
|
|
5801
|
-
}
|
|
5802
|
-
},
|
|
5803
|
-
workspaces: Object.fromEntries(inventory.workspaces.map((work) => [work.id, {
|
|
5804
|
-
parameters: {
|
|
5805
|
-
"screened-identifiers": { enabled: false },
|
|
5806
|
-
"ip-addresses": { enabled: false },
|
|
5807
|
-
hostnames: { enabled: false },
|
|
5808
|
-
"cloud-resources": { enabled: false },
|
|
5809
|
-
"mac-addresses": { enabled: false },
|
|
5810
|
-
"public-key-containers": { enabled: false },
|
|
5811
|
-
"geographic-coordinates": { enabled: false },
|
|
5812
|
-
urls: { enabled: false },
|
|
5813
|
-
"filesystem-paths": { mode: "identity-roots" }
|
|
5814
|
-
},
|
|
5815
|
-
optionalSteps: { "phone-numbers": true }
|
|
5816
|
-
}]))
|
|
5817
|
-
};
|
|
5818
|
-
const configuration = freezeConfiguration(settings, inventory.sessions.flatMap((session) => {
|
|
5819
|
-
const owner = families.owners.get(session.id);
|
|
5820
|
-
return owner ? [{
|
|
5821
|
-
sessionId: session.id,
|
|
5822
|
-
workspaceId: session.workspaceId,
|
|
5823
|
-
settingsOwner: {
|
|
5824
|
-
sessionId: owner.id,
|
|
5825
|
-
workspaceId: owner.workspaceId
|
|
5826
|
-
}
|
|
5827
|
-
}] : [];
|
|
5828
|
-
}));
|
|
5829
|
-
const resource = {
|
|
5830
|
-
kind: "plan",
|
|
5831
|
-
journeyId,
|
|
5832
|
-
approval: {
|
|
5833
|
-
runId: journeyId,
|
|
5834
|
-
revision: 0,
|
|
5835
|
-
approvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5836
|
-
inventory,
|
|
5837
|
-
sourceChangePolicy: "fail",
|
|
5838
|
-
treatment: {
|
|
5839
|
-
kind: "configured",
|
|
5840
|
-
configuration,
|
|
5841
|
-
dictionaryRevision: sensitiveTerms.revision
|
|
5842
|
-
}
|
|
5843
|
-
},
|
|
5844
|
-
replacementKey: randomBytes(32).toString("hex"),
|
|
5845
|
-
sensitiveTerms: {
|
|
5846
|
-
revision: sensitiveTerms.revision,
|
|
5847
|
-
terms: [...sensitiveTerms.terms]
|
|
5848
|
-
}
|
|
5849
|
-
};
|
|
5850
|
-
await storage.saveResource(resource);
|
|
5851
|
-
return resource;
|
|
5852
|
-
}
|
|
5853
|
-
function workApproval(approval, rootIds) {
|
|
5854
|
-
if (approval.treatment?.kind !== "configured") throw new ApplicationError("PROCESSING_PLAN_UNAVAILABLE");
|
|
5855
|
-
const families = reviewFamilies(approval.inventory);
|
|
5856
|
-
const ids = new Set(rootIds.flatMap((id) => {
|
|
5857
|
-
const members = families.members.get(id);
|
|
5858
|
-
if (!members) throw new ApplicationError("UNKNOWN_ITEM");
|
|
5859
|
-
return members.map((session) => session.id);
|
|
5860
|
-
}));
|
|
5861
|
-
const sessions = approval.inventory.sessions.filter((session) => ids.has(session.id));
|
|
5862
|
-
const workspaces = new Set(sessions.map((session) => session.workspaceId));
|
|
5863
|
-
return {
|
|
5864
|
-
...approval,
|
|
5865
|
-
inventory: {
|
|
5866
|
-
...approval.inventory,
|
|
5867
|
-
sessions,
|
|
5868
|
-
workspaces: approval.inventory.workspaces.filter((workspace) => workspaces.has(workspace.id))
|
|
5869
|
-
},
|
|
5870
|
-
treatment: {
|
|
5871
|
-
...approval.treatment,
|
|
5872
|
-
configuration: selectFrozenConfiguration(approval.treatment.configuration, ids)
|
|
5873
|
-
}
|
|
5874
|
-
};
|
|
5875
|
-
}
|
|
5876
|
-
//#endregion
|
|
5877
5799
|
//#region apps/cli/src/journey/metadata/processing.ts
|
|
5878
5800
|
/** Load the frozen plan and active terms once; each Work then resolves its own policy. */
|
|
5879
5801
|
async function metadataPolicy(storage, state, processing) {
|
|
@@ -6075,6 +5997,10 @@ function bindJourneyDestination(storage, destination) {
|
|
|
6075
5997
|
await guard(input.journeyId);
|
|
6076
5998
|
return storage.saveResource(input);
|
|
6077
5999
|
},
|
|
6000
|
+
async commitClassification(input) {
|
|
6001
|
+
await guard(input.journeyId);
|
|
6002
|
+
return storage.commitClassification(input);
|
|
6003
|
+
},
|
|
6078
6004
|
async respondPolicy(input) {
|
|
6079
6005
|
await guard(input.request.journeyId);
|
|
6080
6006
|
return storage.respondPolicy(input);
|
|
@@ -6799,6 +6725,14 @@ async function publishWorkBatches(storage, plan, work, resources, signal) {
|
|
|
6799
6725
|
}
|
|
6800
6726
|
//#endregion
|
|
6801
6727
|
//#region apps/cli/src/journey/prepare/scan-sessions.ts
|
|
6728
|
+
/** Scan refusals caused by one session's own sources, the codes admission refusals also isolate. */
|
|
6729
|
+
const rootScanRefusals = /* @__PURE__ */ new Set([
|
|
6730
|
+
"ARTIFACT_PROCESSING_UNSUPPORTED",
|
|
6731
|
+
"PROCESSING_UNSUPPORTED",
|
|
6732
|
+
"ARTIFACT_SOURCE_CHANGED",
|
|
6733
|
+
"ARTIFACT_LIMIT",
|
|
6734
|
+
"PROCESSING_LIMIT"
|
|
6735
|
+
]);
|
|
6802
6736
|
/**
|
|
6803
6737
|
* Track A (ADR 0172, ADR 0177): every session of the Work is submitted to the shared pool at once,
|
|
6804
6738
|
* ahead of Track B, and each completed session's receipt is saved as it arrives. Once every session
|
|
@@ -6862,7 +6796,11 @@ async function publishScanSessions(storage, plan, work, prepared, binding, direc
|
|
|
6862
6796
|
volumes.scanInputFields += resource.input.fieldCount;
|
|
6863
6797
|
volumes.scanFileBytes += resource.input.file.byteLength;
|
|
6864
6798
|
}, (error) => {
|
|
6865
|
-
|
|
6799
|
+
const rootSessionId = families.owners.get(selected.session.id)?.id;
|
|
6800
|
+
failure ??= rootSessionId ? {
|
|
6801
|
+
error,
|
|
6802
|
+
rootSessionId
|
|
6803
|
+
} : { error };
|
|
6866
6804
|
withdraw.abort();
|
|
6867
6805
|
});
|
|
6868
6806
|
if (pooled) {
|
|
@@ -6900,7 +6838,11 @@ async function publishScanSessions(storage, plan, work, prepared, binding, direc
|
|
|
6900
6838
|
...paragraphs ? { scanHostedCopies: paragraphs.hosted } : {}
|
|
6901
6839
|
}
|
|
6902
6840
|
});
|
|
6903
|
-
if (failure)
|
|
6841
|
+
if (failure) {
|
|
6842
|
+
const code = safeCode(failure.error);
|
|
6843
|
+
if (!hooks.signal.aborted && failure.rootSessionId && rootScanRefusals.has(code)) throw new RootPreparationFailure(code, /* @__PURE__ */ new Map([[failure.rootSessionId, code]]), { cause: failure.error });
|
|
6844
|
+
throw failure.error;
|
|
6845
|
+
}
|
|
6904
6846
|
hooks.signal.throwIfAborted();
|
|
6905
6847
|
if (resources.length !== prepared.sessions.length) throw new ApplicationError("SCOPE_NOT_READY");
|
|
6906
6848
|
return {
|
|
@@ -7126,224 +7068,6 @@ async function prepareJourneyWork(storage, plan, work, directory, processing, ho
|
|
|
7126
7068
|
}
|
|
7127
7069
|
}
|
|
7128
7070
|
//#endregion
|
|
7129
|
-
//#region apps/cli/src/journey/static/production.ts
|
|
7130
|
-
/**
|
|
7131
|
-
* The single production both Track B and packaging use for one Work generation (ADR 0172):
|
|
7132
|
-
* frozen captures, built-in static detection only (no custom terms, no Git seeds), the Work's
|
|
7133
|
-
* exceptions and a replacement key bound to this generation. Identical inputs give identical
|
|
7134
|
-
* session render jobs, so packaging can require the stored Track B result by job digest.
|
|
7135
|
-
*/
|
|
7136
|
-
async function staticBaseProduction(storage, state, work, processing, hooks) {
|
|
7137
|
-
const excluded = work.excludedReview?.snapshotId === state.snapshotId && work.excludedReview.inputRevision === work.inputRevision ? new Set(work.excludedReview.rootSessionIds) : /* @__PURE__ */ new Set();
|
|
7138
|
-
const roots = work.rootSessionIds.filter((id) => !excluded.has(id));
|
|
7139
|
-
if (!roots.length) return void 0;
|
|
7140
|
-
const plan = (await storage.resources({
|
|
7141
|
-
journeyId: state.journeyId,
|
|
7142
|
-
kind: "plan"
|
|
7143
|
-
}))[0];
|
|
7144
|
-
if (plan?.kind !== "plan") throw new ApplicationError("CORRUPTED_STATE");
|
|
7145
|
-
const approval = workApproval(plan.approval, roots);
|
|
7146
|
-
if (approval.treatment?.kind !== "configured") throw new ApplicationError("CORRUPTED_STATE");
|
|
7147
|
-
const parts = new Set(approval.inventory.sessions.flatMap((session) => session.parts.map((part) => part.id)));
|
|
7148
|
-
const captures = (await storage.resources({
|
|
7149
|
-
journeyId: state.journeyId,
|
|
7150
|
-
kind: "capture"
|
|
7151
|
-
})).flatMap((item) => item.kind === "capture" && parts.has(item.capture.partId) ? [item.capture] : []);
|
|
7152
|
-
if (captures.length !== parts.size) throw new ApplicationError("SCOPE_NOT_READY");
|
|
7153
|
-
const { sensitiveTerms: _live, customTerms: _terms, staticExceptions: _unscoped, ...local } = processing;
|
|
7154
|
-
const production = await prepareCollectionProduction(approval, state.journeyId, {
|
|
7155
|
-
configured: {
|
|
7156
|
-
...local,
|
|
7157
|
-
gitNames: async () => ({
|
|
7158
|
-
names: [],
|
|
7159
|
-
snapshots: []
|
|
7160
|
-
}),
|
|
7161
|
-
replacementKey: createHash("sha256").update(JSON.stringify([
|
|
7162
|
-
"static-base-v1",
|
|
7163
|
-
plan.replacementKey,
|
|
7164
|
-
work.id,
|
|
7165
|
-
work.inputRevision
|
|
7166
|
-
])).digest("hex"),
|
|
7167
|
-
staticExceptions: {
|
|
7168
|
-
workId: work.id,
|
|
7169
|
-
values: state.exceptions.filter((item) => item.workId === work.id).map(({ kind, value }) => ({
|
|
7170
|
-
kind,
|
|
7171
|
-
value
|
|
7172
|
-
}))
|
|
7173
|
-
}
|
|
7174
|
-
},
|
|
7175
|
-
captures,
|
|
7176
|
-
everyAdmissionRefusal: true
|
|
7177
|
-
}, hooks);
|
|
7178
|
-
if (production.kind !== "configured" || production.workspaceFailures.length) throw new ApplicationError("SCOPE_NOT_READY");
|
|
7179
|
-
const families = reviewFamilies(approval.inventory);
|
|
7180
|
-
const admitted = new Set(production.selection.sessions.map((item) => item.session.id));
|
|
7181
|
-
return {
|
|
7182
|
-
production,
|
|
7183
|
-
families,
|
|
7184
|
-
work: {
|
|
7185
|
-
...work,
|
|
7186
|
-
rootSessionIds: roots.filter((id) => families.members.get(id)?.some((member) => admitted.has(member.id))),
|
|
7187
|
-
access: work.access === "private" ? "private" : "public"
|
|
7188
|
-
}
|
|
7189
|
-
};
|
|
7190
|
-
}
|
|
7191
|
-
//#endregion
|
|
7192
|
-
//#region apps/cli/src/journey/static/store.ts
|
|
7193
|
-
/** Identity, length and complete digest of one owner-only file; a symlink is never followed. */
|
|
7194
|
-
async function describe(path, signal) {
|
|
7195
|
-
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
7196
|
-
try {
|
|
7197
|
-
const before = await handle.stat({ bigint: true });
|
|
7198
|
-
if (!before.isFile()) throw new ApplicationError("CORRUPTED_STATE");
|
|
7199
|
-
const digest = createHash("sha256");
|
|
7200
|
-
let byteLength = 0;
|
|
7201
|
-
const buffer = Buffer.allocUnsafe(1048576);
|
|
7202
|
-
for (;;) {
|
|
7203
|
-
signal.throwIfAborted();
|
|
7204
|
-
const { bytesRead } = await handle.read(buffer, 0, buffer.length, byteLength);
|
|
7205
|
-
if (!bytesRead) break;
|
|
7206
|
-
digest.update(buffer.subarray(0, bytesRead));
|
|
7207
|
-
byteLength += bytesRead;
|
|
7208
|
-
}
|
|
7209
|
-
const after = await handle.stat({ bigint: true });
|
|
7210
|
-
if (after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size || after.mtimeNs !== before.mtimeNs || BigInt(byteLength) !== after.size) throw new ApplicationError("CORRUPTED_STATE");
|
|
7211
|
-
return {
|
|
7212
|
-
path,
|
|
7213
|
-
identity: {
|
|
7214
|
-
dev: String(after.dev),
|
|
7215
|
-
ino: String(after.ino)
|
|
7216
|
-
},
|
|
7217
|
-
sha256: digest.digest("hex"),
|
|
7218
|
-
byteLength
|
|
7219
|
-
};
|
|
7220
|
-
} finally {
|
|
7221
|
-
await handle.close();
|
|
7222
|
-
}
|
|
7223
|
-
}
|
|
7224
|
-
/** A stored file must still be the exact recorded file and bytes. */
|
|
7225
|
-
async function authenticate(file, signal) {
|
|
7226
|
-
const actual = await describe(file.path, signal);
|
|
7227
|
-
if (JSON.stringify(actual) !== JSON.stringify(file)) throw new ApplicationError("CORRUPTED_STATE");
|
|
7228
|
-
}
|
|
7229
|
-
/**
|
|
7230
|
-
* Persist one Track B session as owner-only private state beside the journey's other scan
|
|
7231
|
-
* files: its three spools and a digest-bound result description. Nothing here is uploaded.
|
|
7232
|
-
*/
|
|
7233
|
-
async function saveStaticSession(storage, binding, item, result, signal) {
|
|
7234
|
-
const owned = [
|
|
7235
|
-
result.spoolPath,
|
|
7236
|
-
result.companionSpoolPath,
|
|
7237
|
-
result.maskSpoolPath
|
|
7238
|
-
];
|
|
7239
|
-
const resultPath = join(binding.directory, `${randomUUID()}.static.json`);
|
|
7240
|
-
let saved = false;
|
|
7241
|
-
try {
|
|
7242
|
-
if (result.kind !== "render" || result.maskSpoolPath === void 0 || result.maskEntries === void 0 || result.metadataMasks === void 0 || result.reviewSpoolPath !== void 0) throw new ApplicationError("CORRUPTED_STATE");
|
|
7243
|
-
const stored = {
|
|
7244
|
-
kind: "render",
|
|
7245
|
-
processingMs: result.processingMs,
|
|
7246
|
-
maskEntries: result.maskEntries,
|
|
7247
|
-
metadataMasks: result.metadataMasks,
|
|
7248
|
-
metadata: result.metadata,
|
|
7249
|
-
parts: result.parts,
|
|
7250
|
-
entries: result.entries,
|
|
7251
|
-
agentSetup: result.agentSetup
|
|
7252
|
-
};
|
|
7253
|
-
const file = await open(resultPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 384);
|
|
7254
|
-
owned.push(resultPath);
|
|
7255
|
-
try {
|
|
7256
|
-
await file.writeFile(JSON.stringify(stored));
|
|
7257
|
-
await file.sync();
|
|
7258
|
-
} finally {
|
|
7259
|
-
await file.close();
|
|
7260
|
-
}
|
|
7261
|
-
const resource = staticSessionResourceSchema.parse({
|
|
7262
|
-
kind: "static-session",
|
|
7263
|
-
journeyId: binding.journeyId,
|
|
7264
|
-
workId: binding.work.id,
|
|
7265
|
-
inputRevision: binding.work.inputRevision,
|
|
7266
|
-
snapshotId: binding.snapshotId,
|
|
7267
|
-
sessionId: item.sessionId,
|
|
7268
|
-
jobDigest: item.digest,
|
|
7269
|
-
result: await describe(resultPath, signal),
|
|
7270
|
-
primary: await describe(result.spoolPath, signal),
|
|
7271
|
-
companion: await describe(result.companionSpoolPath, signal),
|
|
7272
|
-
masks: await describe(result.maskSpoolPath, signal),
|
|
7273
|
-
...result.images === void 0 ? {} : { imagesOmitted: result.images }
|
|
7274
|
-
});
|
|
7275
|
-
await storage.saveResource(resource);
|
|
7276
|
-
saved = true;
|
|
7277
|
-
return resource;
|
|
7278
|
-
} finally {
|
|
7279
|
-
if (!saved) {
|
|
7280
|
-
for (const path of owned) if (path !== void 0) await unlink(path).catch(() => {});
|
|
7281
|
-
}
|
|
7282
|
-
}
|
|
7283
|
-
}
|
|
7284
|
-
/** Current-generation Track B receipts of one Work, keyed by application session ID. */
|
|
7285
|
-
async function staticSessions(storage, journeyId, work) {
|
|
7286
|
-
const resources = (await storage.resources({
|
|
7287
|
-
journeyId,
|
|
7288
|
-
kind: "static-session",
|
|
7289
|
-
idPrefix: `${work.id}:${work.inputRevision}:`
|
|
7290
|
-
})).filter((item) => item.kind === "static-session" && item.workId === work.id && item.inputRevision === work.inputRevision);
|
|
7291
|
-
return new Map(resources.map((item) => [item.sessionId, item]));
|
|
7292
|
-
}
|
|
7293
|
-
/**
|
|
7294
|
-
* Shape guard like the worker transport's: the bytes are bound by the recorded digest, and the
|
|
7295
|
-
* archive writer checks parts, entries and masks again when it consumes them.
|
|
7296
|
-
*/
|
|
7297
|
-
function isStoredResult(value) {
|
|
7298
|
-
return typeof value === "object" && value !== null && Reflect.get(value, "kind") === "render" && Array.isArray(Reflect.get(value, "maskEntries")) && Array.isArray(Reflect.get(value, "metadataMasks")) && Array.isArray(Reflect.get(value, "parts")) && Array.isArray(Reflect.get(value, "entries")) && Array.isArray(Reflect.get(value, "agentSetup")) && typeof Reflect.get(value, "metadata") === "string" && Number.isSafeInteger(Reflect.get(value, "processingMs"));
|
|
7299
|
-
}
|
|
7300
|
-
/** Authenticated result description of one stored Track B session. */
|
|
7301
|
-
async function readStaticResult(resource, signal) {
|
|
7302
|
-
await authenticate(resource.result, signal);
|
|
7303
|
-
const handle = await open(resource.result.path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
7304
|
-
try {
|
|
7305
|
-
const text = await handle.readFile("utf8");
|
|
7306
|
-
if (createHash("sha256").update(text).digest("hex") !== resource.result.sha256) throw new ApplicationError("CORRUPTED_STATE");
|
|
7307
|
-
const value = JSON.parse(text);
|
|
7308
|
-
if (!isStoredResult(value)) throw new ApplicationError("CORRUPTED_STATE");
|
|
7309
|
-
return value;
|
|
7310
|
-
} finally {
|
|
7311
|
-
await handle.close();
|
|
7312
|
-
}
|
|
7313
|
-
}
|
|
7314
|
-
/**
|
|
7315
|
-
* Packaging from the Track B base: the stored result of exactly this job, with fresh hard links
|
|
7316
|
-
* to its authenticated spools that the archive writer may consume. Static detection never runs.
|
|
7317
|
-
*/
|
|
7318
|
-
async function linkedStaticResult(resource, job, signal) {
|
|
7319
|
-
if (!resource || resource.jobDigest !== job.digest) throw new ApplicationError("SCOPE_NOT_READY");
|
|
7320
|
-
const result = await readStaticResult(resource, signal);
|
|
7321
|
-
const links = [];
|
|
7322
|
-
try {
|
|
7323
|
-
const linked = async (file) => {
|
|
7324
|
-
const path = join(job.spoolDirectory, `${randomUUID()}.session`);
|
|
7325
|
-
await link(file.path, path);
|
|
7326
|
-
links.push(path);
|
|
7327
|
-
const stat = await lstat(path, { bigint: true });
|
|
7328
|
-
if (String(stat.dev) !== file.identity.dev || String(stat.ino) !== file.identity.ino) throw new ApplicationError("CORRUPTED_STATE");
|
|
7329
|
-
await authenticate({
|
|
7330
|
-
...file,
|
|
7331
|
-
path
|
|
7332
|
-
}, signal);
|
|
7333
|
-
return path;
|
|
7334
|
-
};
|
|
7335
|
-
return {
|
|
7336
|
-
...result,
|
|
7337
|
-
spoolPath: await linked(resource.primary),
|
|
7338
|
-
companionSpoolPath: await linked(resource.companion),
|
|
7339
|
-
maskSpoolPath: await linked(resource.masks)
|
|
7340
|
-
};
|
|
7341
|
-
} catch (error) {
|
|
7342
|
-
for (const path of links) await unlink(path).catch(() => {});
|
|
7343
|
-
throw error;
|
|
7344
|
-
}
|
|
7345
|
-
}
|
|
7346
|
-
//#endregion
|
|
7347
7071
|
//#region apps/cli/src/journey/static/run.ts
|
|
7348
7072
|
/**
|
|
7349
7073
|
* Track B for one Work generation (ADR 0172): render every admitted session with all built-in
|
|
@@ -7924,7 +7648,7 @@ async function runWorkers(workers) {
|
|
|
7924
7648
|
//#endregion
|
|
7925
7649
|
//#region apps/cli/src/journey/package-base.ts
|
|
7926
7650
|
/** Package-only staging from the complete Track B base; scanning never calls this. */
|
|
7927
|
-
async function packageBase(storage, state, work, processing, directory, signal, readState = () => storage.current(state.journeyId)) {
|
|
7651
|
+
async function packageBase(storage, state, work, processing, directory, signal, readState = () => storage.current(state.journeyId), write = writeJourneyBase) {
|
|
7928
7652
|
assertJourneyPrivacyReady(state, true);
|
|
7929
7653
|
const existing = (await storage.resources({
|
|
7930
7654
|
journeyId: state.journeyId,
|
|
@@ -7932,53 +7656,18 @@ async function packageBase(storage, state, work, processing, directory, signal,
|
|
|
7932
7656
|
id: `${work.id}:${work.inputRevision}`
|
|
7933
7657
|
})).find((item) => item.kind === "base" && item.workId === work.id && item.inputRevision === work.inputRevision);
|
|
7934
7658
|
if (existing?.kind === "base") return existing;
|
|
7935
|
-
const
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
const prepared = await staticBaseProduction(storage, state, work, processing, hooks);
|
|
7941
|
-
if (!prepared) throw new ApplicationError("ARTIFACT_EMPTY_SCOPE");
|
|
7942
|
-
const { families } = prepared;
|
|
7943
|
-
const stored = await staticSessions(storage, state.journeyId, work);
|
|
7944
|
-
const written = await writeLogicalJourneyBase({
|
|
7945
|
-
production: prepared.production,
|
|
7659
|
+
const inputs = await staticBaseInputs(storage, state, work);
|
|
7660
|
+
if (!inputs) throw new ApplicationError("ARTIFACT_EMPTY_SCOPE");
|
|
7661
|
+
const base = await write({
|
|
7662
|
+
inputs,
|
|
7663
|
+
stored: [...(await staticSessions(storage, state.journeyId, work)).values()],
|
|
7946
7664
|
directory,
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
prerendered: async (job) => {
|
|
7950
|
-
if (job.spoolDirectory === void 0) throw new ApplicationError("CORRUPTED_STATE");
|
|
7951
|
-
return linkedStaticResult(stored.get(job.session.id), {
|
|
7952
|
-
digest: renderJobDigest(job),
|
|
7953
|
-
spoolDirectory: job.spoolDirectory
|
|
7954
|
-
}, signal);
|
|
7955
|
-
}
|
|
7956
|
-
}, hooks);
|
|
7665
|
+
processing
|
|
7666
|
+
}, signal);
|
|
7957
7667
|
const after = await readState();
|
|
7958
7668
|
const current = after?.works.find((item) => item.id === work.id);
|
|
7959
7669
|
if (!after || after.snapshotId !== state.snapshotId || after.configurationId !== state.configurationId || current?.revision !== work.revision || current.inputRevision !== work.inputRevision || after.pendingHideId) throw new ApplicationError("REVISION_CONFLICT");
|
|
7960
7670
|
assertJourneyPrivacyReady(after, true);
|
|
7961
|
-
const base = {
|
|
7962
|
-
kind: "base",
|
|
7963
|
-
journeyId: state.journeyId,
|
|
7964
|
-
workId: work.id,
|
|
7965
|
-
inputRevision: work.inputRevision,
|
|
7966
|
-
bundleId: written.bundleId,
|
|
7967
|
-
primary: written.primary,
|
|
7968
|
-
companion: written.companion,
|
|
7969
|
-
maskLedger: written.maskLedger,
|
|
7970
|
-
sourceBindings: [...written.sourceBindings],
|
|
7971
|
-
sessions: written.sessionBindings.map((binding) => {
|
|
7972
|
-
const root = families.owners.get(binding.applicationSessionId);
|
|
7973
|
-
if (!root) throw new ApplicationError("CORRUPTED_STATE");
|
|
7974
|
-
return {
|
|
7975
|
-
applicationSessionId: binding.applicationSessionId,
|
|
7976
|
-
archiveSessionId: binding.sessionId,
|
|
7977
|
-
rootSessionId: root.id,
|
|
7978
|
-
role: root.id === binding.applicationSessionId ? "main" : "worker"
|
|
7979
|
-
};
|
|
7980
|
-
})
|
|
7981
|
-
};
|
|
7982
7671
|
await storage.saveResource(base);
|
|
7983
7672
|
return base;
|
|
7984
7673
|
}
|
|
@@ -8373,8 +8062,26 @@ function createRenderWorkers(workers, size) {
|
|
|
8373
8062
|
return pool.serial(() => writeJourneyOutput(work, signal));
|
|
8374
8063
|
}
|
|
8375
8064
|
};
|
|
8065
|
+
const writeBase = async (work, signal) => {
|
|
8066
|
+
const named = /* @__PURE__ */ new Set();
|
|
8067
|
+
try {
|
|
8068
|
+
const result = await pool.acquire().request({
|
|
8069
|
+
kind: "journey-base",
|
|
8070
|
+
job: journeyBaseJob(work)
|
|
8071
|
+
}, signal, { onSpool: (path) => named.add(path) });
|
|
8072
|
+
if (result.kind !== "journey-base") throw new Error("ARTIFACT_PROCESSING_FAILED");
|
|
8073
|
+
return result.base;
|
|
8074
|
+
} catch (error) {
|
|
8075
|
+
await removeNamedTemporarySpools(named).catch((cleanup) => workers.onFallback?.(cleanup));
|
|
8076
|
+
if (error instanceof SessionWorkerError) throw new ApplicationError(error.message, void 0, { cause: error });
|
|
8077
|
+
if (signal.aborted) throw error;
|
|
8078
|
+
workers.onFallback?.(error);
|
|
8079
|
+
return pool.serial(() => writeJourneyBase(work, signal));
|
|
8080
|
+
}
|
|
8081
|
+
};
|
|
8376
8082
|
return {
|
|
8377
8083
|
write,
|
|
8084
|
+
writeBase,
|
|
8378
8085
|
close: () => pool.discard()
|
|
8379
8086
|
};
|
|
8380
8087
|
}
|
|
@@ -8552,7 +8259,7 @@ function createJourneyRenderer(options) {
|
|
|
8552
8259
|
work
|
|
8553
8260
|
};
|
|
8554
8261
|
}
|
|
8555
|
-
async function produce(state, work, signal, write, session, plan) {
|
|
8262
|
+
async function produce(state, work, signal, write, session, plan, writeBase) {
|
|
8556
8263
|
const { journeyId } = state;
|
|
8557
8264
|
const workId = work.id;
|
|
8558
8265
|
assertJourneyPrivacyReady(state, true);
|
|
@@ -8584,11 +8291,11 @@ function createJourneyRenderer(options) {
|
|
|
8584
8291
|
});
|
|
8585
8292
|
const next = moved.works.find((item) => item.id === workId);
|
|
8586
8293
|
if (!next) throw new ApplicationError("REVISION_CONFLICT");
|
|
8587
|
-
return produce(moved, next, signal, write, session, plan);
|
|
8294
|
+
return produce(moved, next, signal, write, session, plan, writeBase);
|
|
8588
8295
|
}
|
|
8589
8296
|
if (!output) {
|
|
8590
8297
|
const directory = await journeyDirectory(options.stateDir, journeyId);
|
|
8591
|
-
await packageBase(storage, state, work, options.processing, directory, signal, () => session.current());
|
|
8298
|
+
await packageBase(storage, state, work, options.processing, directory, signal, () => session.current(), writeBase);
|
|
8592
8299
|
const { base, tokens, confirmed, request } = await journeyOutputRequest(storage, state, work, options.processing, signal, await plan());
|
|
8593
8300
|
const { output: written, tokens: collected } = await write({
|
|
8594
8301
|
request: {
|
|
@@ -8680,10 +8387,10 @@ function createJourneyRenderer(options) {
|
|
|
8680
8387
|
};
|
|
8681
8388
|
});
|
|
8682
8389
|
}
|
|
8683
|
-
async function render(journeyId, workId, signal, write = writeJourneyOutput, session = directRenderSession(storage, journeyId), plan = () => readJourneyPlan(storage, journeyId)) {
|
|
8390
|
+
async function render(journeyId, workId, signal, write = writeJourneyOutput, session = directRenderSession(storage, journeyId), plan = () => readJourneyPlan(storage, journeyId), writeBase = writeJourneyBase) {
|
|
8684
8391
|
const { state, work } = await admit(workId, session);
|
|
8685
8392
|
try {
|
|
8686
|
-
return await produce(state, work, signal, write, session, plan);
|
|
8393
|
+
return await produce(state, work, signal, write, session, plan, writeBase);
|
|
8687
8394
|
} catch (error) {
|
|
8688
8395
|
if (signal.aborted) throw error;
|
|
8689
8396
|
const renderFailure = {
|
|
@@ -8706,14 +8413,16 @@ function createJourneyRenderer(options) {
|
|
|
8706
8413
|
}
|
|
8707
8414
|
}
|
|
8708
8415
|
/**
|
|
8709
|
-
* A several-Work render writes each Work's output in packaging workers; state,
|
|
8710
|
-
* resource writes stay on this thread under the cross-process state lock
|
|
8711
|
-
* may be split into shards, so the pool is not limited to one
|
|
8416
|
+
* A several-Work render writes each Work's base and output in packaging workers; state,
|
|
8417
|
+
* database and resource writes stay on this thread under the cross-process state lock
|
|
8418
|
+
* (ADR 0181, ADR 0228). Any Work may be split into shards, so the pool is not limited to one
|
|
8419
|
+
* worker per Work (ADR 0199). A `workIds` call of one Work uses workers too, so a panel batch
|
|
8420
|
+
* never packages on the serving thread.
|
|
8712
8421
|
*/
|
|
8713
8422
|
async function renderScope(journeyId, works, signal) {
|
|
8714
8423
|
const workers = options.workers;
|
|
8715
8424
|
const size = workers ? workers.parallelism ?? renderWorkerCount() : 0;
|
|
8716
|
-
if (!workers || works <
|
|
8425
|
+
if (!workers || works < 1 || size < 1) return void 0;
|
|
8717
8426
|
const pool = createRenderWorkers(workers, size);
|
|
8718
8427
|
const session = batchedRenderSession(storage, journeyId);
|
|
8719
8428
|
let plan;
|
|
@@ -8723,7 +8432,7 @@ function createJourneyRenderer(options) {
|
|
|
8723
8432
|
});
|
|
8724
8433
|
return {
|
|
8725
8434
|
concurrency: size + 1,
|
|
8726
|
-
render: (workId) => render(journeyId, workId, signal, pool.write, session, readPlan),
|
|
8435
|
+
render: (workId) => render(journeyId, workId, signal, pool.write, session, readPlan, pool.writeBase),
|
|
8727
8436
|
close: pool.close
|
|
8728
8437
|
};
|
|
8729
8438
|
}
|
|
@@ -9479,57 +9188,49 @@ function createClassificationReader(storage, stateDir) {
|
|
|
9479
9188
|
}
|
|
9480
9189
|
//#endregion
|
|
9481
9190
|
//#region apps/cli/src/journey/works/regroup.ts
|
|
9482
|
-
/**
|
|
9483
|
-
|
|
9484
|
-
|
|
9191
|
+
/**
|
|
9192
|
+
* The session of every accepted finding chunk outside the proposal's retained Works, read from
|
|
9193
|
+
* frozen batches before the commit so the store transaction can rebind them without I/O.
|
|
9194
|
+
*/
|
|
9195
|
+
async function resolveChunkSessions(storage, state, works, signal) {
|
|
9485
9196
|
const retained = new Set(works.map((work) => work.id));
|
|
9486
9197
|
const batches = /* @__PURE__ */ new Map();
|
|
9487
|
-
const
|
|
9488
|
-
for (const term of state.privacy.terms) {
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
|
|
9495
|
-
|
|
9496
|
-
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
}
|
|
9500
|
-
let batch = batches.get(source.batchId);
|
|
9501
|
-
if (!batch) {
|
|
9502
|
-
const record = await storage.inspectReview({
|
|
9503
|
-
kind: "attempt",
|
|
9504
|
-
journeyId: state.journeyId,
|
|
9505
|
-
batchId: source.batchId
|
|
9506
|
-
});
|
|
9507
|
-
if (!record || record.batch.snapshotId !== state.snapshotId) throw new ApplicationError("CORRUPTED_STATE");
|
|
9508
|
-
batch = record.batch;
|
|
9509
|
-
if (batches.size >= 16) batches.clear();
|
|
9510
|
-
batches.set(source.batchId, batch);
|
|
9511
|
-
}
|
|
9512
|
-
const chunk = batch.chunks.find((item) => item.id === source.chunkId) ?? batch.duplicates?.find((item) => item.id === source.chunkId);
|
|
9513
|
-
const workId = chunk && owners.get(chunk.sessionId);
|
|
9514
|
-
if (!workId) throw new ApplicationError("CORRUPTED_STATE");
|
|
9515
|
-
sources.push({
|
|
9516
|
-
...source,
|
|
9517
|
-
workId
|
|
9198
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
9199
|
+
for (const term of state.privacy.terms) for (const source of term.sources) {
|
|
9200
|
+
signal.throwIfAborted();
|
|
9201
|
+
if (source.kind === "metadata" || retained.has(source.workId)) continue;
|
|
9202
|
+
const key = `${source.batchId}:${source.chunkId}`;
|
|
9203
|
+
if (resolved.has(key)) continue;
|
|
9204
|
+
let batch = batches.get(source.batchId);
|
|
9205
|
+
if (!batch) {
|
|
9206
|
+
const record = await storage.inspectReview({
|
|
9207
|
+
kind: "attempt",
|
|
9208
|
+
journeyId: state.journeyId,
|
|
9209
|
+
batchId: source.batchId
|
|
9518
9210
|
});
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9211
|
+
if (!record || record.batch.snapshotId !== state.snapshotId) throw new ApplicationError("CORRUPTED_STATE");
|
|
9212
|
+
batch = record.batch;
|
|
9213
|
+
if (batches.size >= 16) batches.clear();
|
|
9214
|
+
batches.set(source.batchId, batch);
|
|
9215
|
+
}
|
|
9216
|
+
const chunk = batch.chunks.find((item) => item.id === source.chunkId) ?? batch.duplicates?.find((item) => item.id === source.chunkId);
|
|
9217
|
+
if (!chunk) throw new ApplicationError("CORRUPTED_STATE");
|
|
9218
|
+
resolved.set(key, {
|
|
9219
|
+
batchId: source.batchId,
|
|
9220
|
+
chunkId: source.chunkId,
|
|
9221
|
+
sessionId: chunk.sessionId
|
|
9523
9222
|
});
|
|
9524
9223
|
}
|
|
9525
|
-
return
|
|
9224
|
+
return [...resolved.values()];
|
|
9526
9225
|
}
|
|
9527
9226
|
//#endregion
|
|
9528
9227
|
//#region apps/cli/src/journey/works/proposal.ts
|
|
9529
9228
|
/**
|
|
9530
9229
|
* One atomic root partition; recommendations have no access authority. Roots still being grouped
|
|
9531
9230
|
* wait in one unread Work while the other Works are confirmed and read; the submission that
|
|
9532
|
-
* groups them may arrive after confirmation and keeps it (E2).
|
|
9231
|
+
* groups them may arrive after confirmation and keeps it (E2). The proposal is prepared from one
|
|
9232
|
+
* read and applied by the store against the current state, so scan writes in between do not
|
|
9233
|
+
* refuse it (BL-216).
|
|
9533
9234
|
*/
|
|
9534
9235
|
function createClassificationProposal(storage) {
|
|
9535
9236
|
return async (input, signal) => {
|
|
@@ -9539,19 +9240,20 @@ function createClassificationProposal(storage) {
|
|
|
9539
9240
|
const classifying = isClassifying(state);
|
|
9540
9241
|
const late = state.scope.status === "confirmed";
|
|
9541
9242
|
const bound = input.expectedClassificationRevision !== void 0 && classifying && input.expectedClassificationRevision === state.classificationRevision;
|
|
9542
|
-
const
|
|
9543
|
-
|
|
9243
|
+
const requestHash = textDigest(JSON.stringify(input));
|
|
9244
|
+
const event = {
|
|
9245
|
+
journeyId: state.journeyId,
|
|
9246
|
+
kind: "response",
|
|
9247
|
+
source: "conversation",
|
|
9248
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9249
|
+
};
|
|
9250
|
+
if (input.expectedClassificationRevision !== void 0 ? !bound : state.revision !== input.expectedRevision) return storage.commit({
|
|
9251
|
+
expectedRevision: input.expectedRevision,
|
|
9544
9252
|
requestKey: input.idempotencyKey,
|
|
9545
|
-
requestHash
|
|
9546
|
-
state
|
|
9547
|
-
event
|
|
9548
|
-
journeyId: state.journeyId,
|
|
9549
|
-
kind: "response",
|
|
9550
|
-
source: "conversation",
|
|
9551
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9552
|
-
}
|
|
9253
|
+
requestHash,
|
|
9254
|
+
state,
|
|
9255
|
+
event
|
|
9553
9256
|
});
|
|
9554
|
-
if (input.expectedClassificationRevision !== void 0 ? !bound : state.revision !== input.expectedRevision) return commit();
|
|
9555
9257
|
if (late && !classifying) throw new ApplicationError("INVALID_TRANSITION");
|
|
9556
9258
|
if (state.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
|
|
9557
9259
|
if (!state.snapshotId || state.pendingHideId) throw new ApplicationError("SCOPE_NOT_READY");
|
|
@@ -9590,96 +9292,43 @@ function createClassificationProposal(storage) {
|
|
|
9590
9292
|
kind: "capture"
|
|
9591
9293
|
});
|
|
9592
9294
|
const captured = new Set(captures.flatMap((resource) => resource.kind === "capture" ? [resource.capture.partId] : []));
|
|
9593
|
-
const
|
|
9295
|
+
const workspacesOf = (rootSessionIds) => {
|
|
9296
|
+
const sessions = rootSessionIds.flatMap((root) => families.members.get(root) ?? []);
|
|
9297
|
+
if (sessions.some((session) => session.parts.some((part) => !captured.has(part.id)))) throw new ApplicationError("SCOPE_NOT_READY");
|
|
9298
|
+
return [...new Set(sessions.map((session) => session.workspaceId))];
|
|
9299
|
+
};
|
|
9300
|
+
const items = [];
|
|
9594
9301
|
for (const item of merged.values()) {
|
|
9595
9302
|
signal.throwIfAborted();
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
if (late && existing) {
|
|
9601
|
-
works.push(existing);
|
|
9602
|
-
continue;
|
|
9603
|
-
}
|
|
9604
|
-
const workspaces = [...new Set(sessions.map((session) => session.workspaceId))];
|
|
9605
|
-
const base = existing ?? (extended ? {
|
|
9606
|
-
...resetWorkScan(extended),
|
|
9607
|
-
rootSessionIds: item.rootSessionIds,
|
|
9608
|
-
sourceWorkspaceIds: [.../* @__PURE__ */ new Set([...extended.sourceWorkspaceIds, ...workspaces])]
|
|
9609
|
-
} : newJourneyWork(randomUUID(), item.projectName, item.rootSessionIds, workspaces, "placed"));
|
|
9610
|
-
const { classificationMetadata: _display, ...original } = base;
|
|
9611
|
-
works.push({
|
|
9612
|
-
...original,
|
|
9613
|
-
projectName: item.projectName,
|
|
9614
|
-
title: base.titleOrigin === "explicit" ? base.title : item.projectName,
|
|
9615
|
-
description: item.description,
|
|
9616
|
-
projectType: {
|
|
9617
|
-
type: item.projectType,
|
|
9618
|
-
reason: item.recommendation.reason,
|
|
9619
|
-
origin: "model"
|
|
9620
|
-
},
|
|
9621
|
-
placement: "placed",
|
|
9622
|
-
...late && !extended ? {
|
|
9623
|
-
access: item.recommendation.access,
|
|
9624
|
-
accessOrigin: "model",
|
|
9625
|
-
accessConfirmed: true
|
|
9626
|
-
} : {},
|
|
9627
|
-
classificationRecommendation: item.recommendation,
|
|
9628
|
-
suggestion: null,
|
|
9629
|
-
artifact: null,
|
|
9630
|
-
revision: base.revision + 1
|
|
9303
|
+
items.push({
|
|
9304
|
+
...item,
|
|
9305
|
+
workId: randomUUID(),
|
|
9306
|
+
workspaceIds: workspacesOf(item.rootSessionIds)
|
|
9631
9307
|
});
|
|
9632
9308
|
}
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
const forced = new Set(preserved.filter((term) => !termTypeInfo[term.type].asked).map((term) => term.value));
|
|
9657
|
-
const overrides = new Map(state.valueOverrides?.map((item) => [item.value, item.choice]) ?? []);
|
|
9658
|
-
const terms = preserved.map((term) => ({
|
|
9659
|
-
...term,
|
|
9660
|
-
disposition: forced.has(term.value) ? "hidden" : overrides.has(term.value) ? overrides.get(term.value) === "hide" ? "hidden" : "keep" : regrouped && cardTerms.has(privacyTermKey(term)) ? "pending" : term.disposition
|
|
9661
|
-
}));
|
|
9662
|
-
return commit({
|
|
9663
|
-
...state,
|
|
9664
|
-
works,
|
|
9665
|
-
classificationRevision: state.revision + 1,
|
|
9666
|
-
scope: late ? state.scope : {
|
|
9667
|
-
status: "pending",
|
|
9668
|
-
actor: null,
|
|
9669
|
-
at: null
|
|
9670
|
-
},
|
|
9671
|
-
privacy: {
|
|
9672
|
-
...state.privacy,
|
|
9673
|
-
terms,
|
|
9674
|
-
packageApproval: null,
|
|
9675
|
-
...regrouped ? {
|
|
9676
|
-
cards: [],
|
|
9677
|
-
answersRevision: state.privacy.answersRevision + 1,
|
|
9678
|
-
confirmedAnswersRevision: null
|
|
9679
|
-
} : {}
|
|
9680
|
-
},
|
|
9681
|
-
readingComplete: !pending.length && works.filter((work) => work.access !== "drop").every((work) => work.totalBatches > 0 && work.completedBatches === work.totalBatches),
|
|
9682
|
-
phase: "processing"
|
|
9309
|
+
const prepared = {
|
|
9310
|
+
journeyId: state.journeyId,
|
|
9311
|
+
requestKey: input.idempotencyKey,
|
|
9312
|
+
requestHash,
|
|
9313
|
+
at: event.at,
|
|
9314
|
+
expectedRevision: input.expectedRevision,
|
|
9315
|
+
expectedClassificationRevision: bound ? input.expectedClassificationRevision ?? null : null,
|
|
9316
|
+
snapshotId: state.snapshotId,
|
|
9317
|
+
partition: classificationPartition(state),
|
|
9318
|
+
late,
|
|
9319
|
+
items,
|
|
9320
|
+
pending: pending.length ? {
|
|
9321
|
+
workId: randomUUID(),
|
|
9322
|
+
rootSessionIds: pending,
|
|
9323
|
+
workspaceIds: [...new Set(pending.flatMap((root) => (families.members.get(root) ?? []).map((session) => session.workspaceId)))]
|
|
9324
|
+
} : null,
|
|
9325
|
+
chunkSessions: []
|
|
9326
|
+
};
|
|
9327
|
+
const { works } = classificationWorks(state, prepared);
|
|
9328
|
+
const chunkSessions = await resolveChunkSessions(storage, state, works, signal);
|
|
9329
|
+
return storage.commitClassification({
|
|
9330
|
+
...prepared,
|
|
9331
|
+
chunkSessions
|
|
9683
9332
|
});
|
|
9684
9333
|
};
|
|
9685
9334
|
}
|
|
@@ -10156,16 +9805,19 @@ function createJourneySendArtifacts(storage) {
|
|
|
10156
9805
|
sha256: output.primary.sha256
|
|
10157
9806
|
});
|
|
10158
9807
|
},
|
|
10159
|
-
async upload(journeyId, artifact,
|
|
9808
|
+
async upload(journeyId, artifact, upload, signal) {
|
|
10160
9809
|
const output = await find(journeyId, artifact.workId, artifact.revision);
|
|
10161
9810
|
if (output.bundleId !== artifact.bundleId || output.primary.sha256 !== artifact.sha256 || output.primary.archiveBytes !== artifact.byteLength || artifact.byteLength > 5368709120) throw new ApplicationError("ARTIFACT_IDENTITY_MISMATCH");
|
|
10162
|
-
await putPrimary(output.primary, artifact.byteLength,
|
|
9811
|
+
await putPrimary(output.primary, artifact.byteLength, upload, signal);
|
|
10163
9812
|
}
|
|
10164
9813
|
};
|
|
10165
9814
|
}
|
|
10166
|
-
/**
|
|
10167
|
-
|
|
10168
|
-
|
|
9815
|
+
/**
|
|
9816
|
+
* Stream a verified primary to a signed HTTPS URL; shared by native Send and Manual upload. A
|
|
9817
|
+
* checksum upload also sends the SHA-256 header the URL signs, so storage refuses other bytes.
|
|
9818
|
+
*/
|
|
9819
|
+
async function putPrimary(primary, byteLength, upload, signal) {
|
|
9820
|
+
const url = new URL(upload.uploadUrl);
|
|
10169
9821
|
if (url.protocol !== "https:" || url.username || url.password || url.hash) throw new ApplicationError("INVALID_ARGUMENTS");
|
|
10170
9822
|
const source = await openConfiguredUpload(primary, signal);
|
|
10171
9823
|
const cleanup = createCleanup();
|
|
@@ -10177,7 +9829,8 @@ async function putPrimary(primary, byteLength, uploadUrl, signal) {
|
|
|
10177
9829
|
redirect: "error",
|
|
10178
9830
|
headers: {
|
|
10179
9831
|
"Content-Type": "application/zip",
|
|
10180
|
-
"Content-Length": String(byteLength)
|
|
9832
|
+
"Content-Length": String(byteLength),
|
|
9833
|
+
...upload.headers
|
|
10181
9834
|
},
|
|
10182
9835
|
duplex: "half"
|
|
10183
9836
|
};
|
|
@@ -10347,7 +10000,7 @@ const completeTimeoutMs = 6e5;
|
|
|
10347
10000
|
function createJourneyOwnerClient(options) {
|
|
10348
10001
|
const base = apiBase(options.apiUrl);
|
|
10349
10002
|
const requestFetch = options.fetch ?? fetch;
|
|
10350
|
-
async function request(path, schema, signal, body, method = "POST", key, timeoutMs = requestTimeoutMs$2) {
|
|
10003
|
+
async function request(path, schema, signal, body, method = "POST", key, timeoutMs = requestTimeoutMs$2, checksumUpload = false) {
|
|
10351
10004
|
const token = await options.authorize();
|
|
10352
10005
|
signal.throwIfAborted();
|
|
10353
10006
|
const response = await requestFetch(new URL(path, base), {
|
|
@@ -10356,7 +10009,8 @@ function createJourneyOwnerClient(options) {
|
|
|
10356
10009
|
accept: "application/json",
|
|
10357
10010
|
authorization: `Bearer ${token}`,
|
|
10358
10011
|
...body === void 0 ? {} : { "content-type": "application/json" },
|
|
10359
|
-
...key === void 0 ? {} : { "idempotency-key": key }
|
|
10012
|
+
...key === void 0 ? {} : { "idempotency-key": key },
|
|
10013
|
+
...checksumUpload ? { "upload-checksum": "sha256" } : {}
|
|
10360
10014
|
},
|
|
10361
10015
|
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
10362
10016
|
credentials: "omit",
|
|
@@ -10411,14 +10065,14 @@ function createJourneyOwnerClient(options) {
|
|
|
10411
10065
|
membersDigest: preview.membersDigest
|
|
10412
10066
|
}, "PUT");
|
|
10413
10067
|
},
|
|
10414
|
-
reserve(key, input, signal) {
|
|
10415
|
-
return request("/me/work-bundles/uploads", sendReceiptSchema.refine((value) => receiptMatches(input, value)), signal, input, "POST", key);
|
|
10068
|
+
reserve(key, input, signal, checksumUpload = true) {
|
|
10069
|
+
return request("/me/work-bundles/uploads", sendReceiptSchema.refine((value) => receiptMatches(input, value)), signal, input, "POST", key, requestTimeoutMs$2, checksumUpload);
|
|
10416
10070
|
},
|
|
10417
10071
|
complete(id, signal) {
|
|
10418
10072
|
return request(`/me/work-bundles/uploads/${encodeURIComponent(id)}/complete`, receipt(id), signal, {}, "POST", void 0, completeTimeoutMs);
|
|
10419
10073
|
},
|
|
10420
|
-
status(id, signal) {
|
|
10421
|
-
return request(`/me/work-bundles/uploads/${encodeURIComponent(id)}`, receipt(id), signal, void 0, "GET");
|
|
10074
|
+
status(id, signal, checksumUpload = true) {
|
|
10075
|
+
return request(`/me/work-bundles/uploads/${encodeURIComponent(id)}`, receipt(id), signal, void 0, "GET", void 0, requestTimeoutMs$2, checksumUpload);
|
|
10422
10076
|
},
|
|
10423
10077
|
async replace(keepWorkIds, signal) {
|
|
10424
10078
|
if (keepWorkIds.length === 0) throw new ApplicationError("INVALID_STATE");
|
|
@@ -10608,9 +10262,24 @@ async function perform(context, item, connection, signal) {
|
|
|
10608
10262
|
const auto = context.snapshot === true;
|
|
10609
10263
|
await guard();
|
|
10610
10264
|
if (item.settings && item.settingsConfirmed) return applySettings(item, connection, signal, guard, auto);
|
|
10611
|
-
if (!item.reservation)
|
|
10612
|
-
|
|
10613
|
-
|
|
10265
|
+
if (!item.reservation) {
|
|
10266
|
+
const next = await resolveItem(item, context.bindingId, connection, signal, auto, context.name);
|
|
10267
|
+
if (next.reservation) context.fresh?.set(item.workId, null);
|
|
10268
|
+
return next;
|
|
10269
|
+
}
|
|
10270
|
+
if (!item.receipt) {
|
|
10271
|
+
const reserved = await connection.reserve(item.operationKey, item.reservation, signal, context.checksumUploads !== false);
|
|
10272
|
+
const next = await observe(item, reserved, connection, signal, auto);
|
|
10273
|
+
if (context.fresh?.has(item.workId) && reserved.upload && next.status === "uploading") context.fresh.set(item.workId, {
|
|
10274
|
+
receiptId: reserved.receiptId,
|
|
10275
|
+
upload: reserved.upload
|
|
10276
|
+
});
|
|
10277
|
+
return next;
|
|
10278
|
+
}
|
|
10279
|
+
const fresh = context.fresh?.get(item.workId);
|
|
10280
|
+
context.fresh?.delete(item.workId);
|
|
10281
|
+
if (fresh?.receiptId === item.receipt.receiptId) return put(context, item, fresh.receiptId, fresh.upload, connection, signal, guard);
|
|
10282
|
+
const receipt = await connection.status(item.receipt.receiptId, signal, context.checksumUploads !== false);
|
|
10614
10283
|
const observed = await observe(item, receipt, connection, signal, auto);
|
|
10615
10284
|
if (receipt.state !== "awaiting_upload") return withoutUpload(observed);
|
|
10616
10285
|
if (!receipt.upload) throw new ApplicationError("INVALID_STATE");
|
|
@@ -10629,15 +10298,23 @@ async function perform(context, item, connection, signal) {
|
|
|
10629
10298
|
}
|
|
10630
10299
|
const current = await connection.resolve(item.reservation.work, signal);
|
|
10631
10300
|
if (current.scopeVersion !== item.reservation.expectedScopeVersion || current.accessVersion !== item.reservation.expectedAccessVersion) return renewed(item, null);
|
|
10301
|
+
return put(context, item, receipt.receiptId, receipt.upload, connection, signal, guard);
|
|
10302
|
+
}
|
|
10303
|
+
/** Uploads the bytes to the receipt's signed URL and completes it. */
|
|
10304
|
+
async function put(context, item, receiptId, upload, connection, signal, guard) {
|
|
10632
10305
|
await guard();
|
|
10633
|
-
await context.upload(item.artifact,
|
|
10306
|
+
await context.upload(item.artifact, upload, signal);
|
|
10634
10307
|
await guard();
|
|
10635
10308
|
try {
|
|
10636
|
-
return observe(item, await connection.complete(
|
|
10309
|
+
return observe(item, await connection.complete(receiptId, signal), connection, signal, context.snapshot === true);
|
|
10637
10310
|
} catch (error) {
|
|
10311
|
+
if (upload.headers !== void 0 && error instanceof JourneyRemoteError && error.code === "bundle_mismatch") return {
|
|
10312
|
+
...renewed(withoutUpload(item), null),
|
|
10313
|
+
checksumUploadRefused: true
|
|
10314
|
+
};
|
|
10638
10315
|
if (error instanceof Object) uploaded.set(error, {
|
|
10639
10316
|
...item,
|
|
10640
|
-
uploadedReceiptId:
|
|
10317
|
+
uploadedReceiptId: receiptId
|
|
10641
10318
|
});
|
|
10642
10319
|
throw error;
|
|
10643
10320
|
}
|
|
@@ -10705,14 +10382,16 @@ const confirmedSettings = (item) => item.settings !== null && item.settingsConfi
|
|
|
10705
10382
|
* time (ADR 0214). Assessment checks change nothing and step up to three together; a confirmed
|
|
10706
10383
|
* linked Settings change alters access versions, so it always steps alone.
|
|
10707
10384
|
*/
|
|
10708
|
-
function pickBatch(items) {
|
|
10385
|
+
function pickBatch(items, now = Date.now()) {
|
|
10709
10386
|
const uploading = items.find((item) => item.status === "uploading") ?? items.filter((item) => item.status === "ready").reduce((smallest, item) => !smallest || item.artifact.byteLength < smallest.artifact.byteLength ? item : smallest, void 0);
|
|
10710
10387
|
if (uploading) return [uploading];
|
|
10711
10388
|
const assessing = items.filter((item) => item.status === "assessing");
|
|
10712
10389
|
const first = assessing[0];
|
|
10713
10390
|
if (!first) return [];
|
|
10714
10391
|
if (confirmedSettings(first)) return [first];
|
|
10715
|
-
|
|
10392
|
+
const checks = assessing.filter((item) => !confirmedSettings(item));
|
|
10393
|
+
const start = Math.floor(now / 1e3) * 3 % checks.length;
|
|
10394
|
+
return [...checks.slice(start), ...checks.slice(0, start)].slice(0, 3);
|
|
10716
10395
|
}
|
|
10717
10396
|
function isCode(error, code) {
|
|
10718
10397
|
return error instanceof ApplicationError && error.code === code;
|
|
@@ -10806,7 +10485,11 @@ async function advanceItems(host, signal) {
|
|
|
10806
10485
|
if (finished !== null) return finished;
|
|
10807
10486
|
}
|
|
10808
10487
|
if (!batch.length) return host.applied();
|
|
10809
|
-
const
|
|
10488
|
+
const checksumUploads = !send.items.some((item) => item.checksumUploadRefused);
|
|
10489
|
+
const results = await Promise.allSettled(batch.map((item) => stepItem({
|
|
10490
|
+
...host.context(item),
|
|
10491
|
+
checksumUploads
|
|
10492
|
+
}, item, connection, signal)));
|
|
10810
10493
|
const failures = results.flatMap((result) => result.status === "rejected" ? [result] : []);
|
|
10811
10494
|
if (failures.some(({ reason }) => isCode(reason, "REVISION_CONFLICT"))) return host.stale();
|
|
10812
10495
|
const first = failures[0];
|
|
@@ -10827,7 +10510,7 @@ async function advanceItems(host, signal) {
|
|
|
10827
10510
|
if (progressed) next.set(item.workId, progressed);
|
|
10828
10511
|
if (cause instanceof JourneyRemoteError && cause.status === 401 || isCode(cause, "ACCOUNT_SIGNED_OUT")) signIn = true;
|
|
10829
10512
|
else if (isCode(cause, "IDENTITY_REJECTED")) changed = true;
|
|
10830
|
-
else if (isInterrupted(cause)) {
|
|
10513
|
+
else if (isInterrupted(cause) || isTransientRefusal(cause)) {
|
|
10831
10514
|
host.failure("SEND_INTERRUPTED", cause);
|
|
10832
10515
|
error = "SEND_INTERRUPTED";
|
|
10833
10516
|
} else {
|
|
@@ -10935,8 +10618,9 @@ function withoutQuestions(send) {
|
|
|
10935
10618
|
};
|
|
10936
10619
|
return send.status === "review" ? aggregate(next) : next;
|
|
10937
10620
|
}
|
|
10938
|
-
async function advanceSend(dependencies, address, signal) {
|
|
10621
|
+
async function advanceSend(dependencies, address, signal, chain) {
|
|
10939
10622
|
const snapshot = requireSend(await dependencies.storage.loadSend(address.journeyId, address.sendId));
|
|
10623
|
+
if (chain) chain.revision = snapshot.send.revision;
|
|
10940
10624
|
if ((await dependencies.storage.loadSend(address.journeyId))?.send?.id !== snapshot.send.id) return {
|
|
10941
10625
|
...snapshot,
|
|
10942
10626
|
status: "stale"
|
|
@@ -10968,22 +10652,23 @@ async function advanceSend(dependencies, address, signal) {
|
|
|
10968
10652
|
return advanceItems({
|
|
10969
10653
|
send,
|
|
10970
10654
|
failure: dependencies.failure,
|
|
10971
|
-
connect: dependencies.auth.connect,
|
|
10655
|
+
connect: chain?.connect ?? dependencies.auth.connect,
|
|
10972
10656
|
profile: (connection, profileSignal) => prepareProfile(dependencies, snapshot, connection, profileSignal),
|
|
10973
10657
|
context: (item) => {
|
|
10974
10658
|
const binding = snapshot.journey.works.find((value) => value.id === item.workId)?.binding;
|
|
10975
10659
|
const retired = !item.receipt && binding?.sentBundleId === item.artifact.bundleId && binding.sentAccess === item.access;
|
|
10976
10660
|
return {
|
|
10977
10661
|
snapshot: true,
|
|
10662
|
+
...chain ? { fresh: chain.fresh } : {},
|
|
10978
10663
|
active: async () => {
|
|
10979
10664
|
const current = requireSend(await dependencies.storage.loadSend(snapshot.journey.journeyId));
|
|
10980
10665
|
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");
|
|
10981
10666
|
assertScope(current.journey, current.send);
|
|
10982
10667
|
},
|
|
10983
10668
|
bindingId: binding?.subject === send.subject && !retired ? binding.bundleRecordId : void 0,
|
|
10984
|
-
upload: async (artifact,
|
|
10669
|
+
upload: async (artifact, upload, uploadSignal) => {
|
|
10985
10670
|
const started = performance.now();
|
|
10986
|
-
await dependencies.artifacts.upload(snapshot.journey.journeyId, artifact,
|
|
10671
|
+
await dependencies.artifacts.upload(snapshot.journey.journeyId, artifact, upload, uploadSignal);
|
|
10987
10672
|
dependencies.uploaded?.(artifact.workId, artifact.byteLength, Math.round(performance.now() - started));
|
|
10988
10673
|
}
|
|
10989
10674
|
};
|
|
@@ -11029,6 +10714,14 @@ async function advanceSend(dependencies, address, signal) {
|
|
|
11029
10714
|
})
|
|
11030
10715
|
}, signal);
|
|
11031
10716
|
}
|
|
10717
|
+
/**
|
|
10718
|
+
* A refusal Passport answers while it or Identity is briefly unavailable. It says nothing about
|
|
10719
|
+
* the Work, so the step is retried instead of blocking it (the 2026-11-01 recorded run blocked a
|
|
10720
|
+
* Work on one `identity_unavailable`).
|
|
10721
|
+
*/
|
|
10722
|
+
function isTransientRefusal(error) {
|
|
10723
|
+
return error instanceof JourneyRemoteError && (error.status >= 500 || error.status === 429 || error.code === "identity_unavailable");
|
|
10724
|
+
}
|
|
11032
10725
|
//#endregion
|
|
11033
10726
|
//#region apps/cli/src/account/journey/decisions.ts
|
|
11034
10727
|
function decide(item, input) {
|
|
@@ -11299,10 +10992,10 @@ function createManualUploader(dependencies) {
|
|
|
11299
10992
|
active: guard(upload, item),
|
|
11300
10993
|
name: item.title,
|
|
11301
10994
|
bindingId: bindings.find((value) => value.subject === upload.subject && value.pathHash === pathHash(item))?.bundleRecordId,
|
|
11302
|
-
upload: (artifact,
|
|
10995
|
+
upload: (artifact, signedUpload, uploadSignal) => {
|
|
11303
10996
|
const slot = upload.slots[item.workId];
|
|
11304
10997
|
if (slot === void 0) throw new ApplicationError("ARTIFACT_MISSING");
|
|
11305
|
-
return dependencies.artifacts.upload(upload.resultId, slot, artifact,
|
|
10998
|
+
return dependencies.artifacts.upload(upload.resultId, slot, artifact, signedUpload, uploadSignal);
|
|
11306
10999
|
}
|
|
11307
11000
|
}),
|
|
11308
11001
|
applied: () => ({
|
|
@@ -11372,10 +11065,10 @@ function createManualUploadArtifacts(application) {
|
|
|
11372
11065
|
})
|
|
11373
11066
|
};
|
|
11374
11067
|
},
|
|
11375
|
-
async upload(resultId, slot, artifact,
|
|
11068
|
+
async upload(resultId, slot, artifact, upload, signal) {
|
|
11376
11069
|
const { row, primary } = await find(resultId, slot);
|
|
11377
11070
|
if (row.bundleId !== artifact.bundleId || primary.sha256 !== artifact.sha256 || primary.archiveBytes !== artifact.byteLength || artifact.byteLength > 5368709120) throw new ApplicationError("ARTIFACT_IDENTITY_MISMATCH");
|
|
11378
|
-
await putPrimary(primary, artifact.byteLength,
|
|
11071
|
+
await putPrimary(primary, artifact.byteLength, upload, signal);
|
|
11379
11072
|
}
|
|
11380
11073
|
};
|
|
11381
11074
|
}
|
|
@@ -12538,13 +12231,42 @@ function createJourneyFiles(storage) {
|
|
|
12538
12231
|
};
|
|
12539
12232
|
};
|
|
12540
12233
|
}
|
|
12234
|
+
/**
|
|
12235
|
+
* One advance carries the Work being uploaded through its phases with one verified connection,
|
|
12236
|
+
* instead of one phase per panel round trip, and uploads a receipt it just resolved and reserved
|
|
12237
|
+
* without reading it again (ADR 0229). Each phase is still fenced and saved on its own, so Pause, a
|
|
12238
|
+
* revision change or a failure stops the chain at the next phase exactly as between advances.
|
|
12239
|
+
* The advance returns once that Work leaves its upload phases, so the next Work starts from the
|
|
12240
|
+
* caller's next advance as before.
|
|
12241
|
+
*/
|
|
12242
|
+
async function advanceWork(dependencies, address, signal, phases = 3) {
|
|
12243
|
+
let connection;
|
|
12244
|
+
const chain = {
|
|
12245
|
+
fresh: /* @__PURE__ */ new Map(),
|
|
12246
|
+
async connect(connectSignal) {
|
|
12247
|
+
if (connection === void 0) connection = await dependencies.auth.connect(connectSignal);
|
|
12248
|
+
return connection;
|
|
12249
|
+
}
|
|
12250
|
+
};
|
|
12251
|
+
let result = await advanceSend(dependencies, address, signal, chain);
|
|
12252
|
+
for (let phase = 1; phase < phases && uploading(result, chain.revision); phase += 1) result = await advanceSend(dependencies, address, signal, chain);
|
|
12253
|
+
return result;
|
|
12254
|
+
}
|
|
12255
|
+
/** The phase saved progress and left its Work mid-upload, with nothing else to report. */
|
|
12256
|
+
function uploading(result, before) {
|
|
12257
|
+
const send = result.send;
|
|
12258
|
+
return result.status === "applied" && send !== null && before !== void 0 && send.revision > before && send.status === "sending" && send.error === null && pickBatch(send.items)[0]?.status === "uploading";
|
|
12259
|
+
}
|
|
12541
12260
|
//#endregion
|
|
12542
12261
|
//#region apps/cli/src/account/journey/send.ts
|
|
12543
12262
|
function sameIds(left, right) {
|
|
12544
12263
|
return left.length === right.length && new Set(left).size === left.length && left.every((id) => right.includes(id));
|
|
12545
12264
|
}
|
|
12546
|
-
/**
|
|
12547
|
-
|
|
12265
|
+
/**
|
|
12266
|
+
* Durable, bounded orchestration; native chat and panel use the same commands. One advance runs
|
|
12267
|
+
* at most `phases` upload phases of one Work (ADR 0229); tests pass 1 to observe each phase.
|
|
12268
|
+
*/
|
|
12269
|
+
function createJourneySender(dependencies, phases = 3) {
|
|
12548
12270
|
const running = /* @__PURE__ */ new Map();
|
|
12549
12271
|
const runKey = (address) => `${address.journeyId}:${address.sendId}`;
|
|
12550
12272
|
const load = async (address) => {
|
|
@@ -12657,7 +12379,7 @@ function createJourneySender(dependencies) {
|
|
|
12657
12379
|
const current = running.get(key);
|
|
12658
12380
|
if (current) return current.result;
|
|
12659
12381
|
const stop = new AbortController();
|
|
12660
|
-
const result =
|
|
12382
|
+
const result = advanceWork(dependencies, address, AbortSignal.any([signal, stop.signal]), phases).catch(async (error) => {
|
|
12661
12383
|
if (!stop.signal.aborted || signal.aborted) throw error;
|
|
12662
12384
|
return {
|
|
12663
12385
|
...requireSend(await dependencies.storage.loadSend(address.journeyId, address.sendId)),
|