@gitroll/tacitt-cli 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/{agent-setup-B8DAiQ6Z.mjs → agent-setup-CQRWVUWR.mjs} +1 -1
  2. package/dist/agent-setup-worker.mjs +4 -4
  3. package/dist/cli.mjs +66 -9
  4. package/dist/codex-title-worker.mjs +1 -1
  5. package/dist/configured-preview-worker.mjs +4 -4
  6. package/dist/configured-session-worker.mjs +25 -4
  7. package/dist/{heap-BHcvxWCw.mjs → heap-BaTNC9kf.mjs} +45 -3
  8. package/dist/inspection-worker.mjs +3 -3
  9. package/dist/job-DrcpSL_9.mjs +615 -0
  10. package/dist/{model-BJdTLNjT.mjs → model-D7UnGeag.mjs} +1 -1
  11. package/dist/operation-executor.mjs +2 -2
  12. package/dist/preview-worker.mjs +1 -1
  13. package/dist/{query-BA0XCwnR.mjs → query-ByD0Rp_o.mjs} +391 -551
  14. package/dist/{run-BeLHsZWb.mjs → run-3UIkr7GZ.mjs} +2 -2
  15. package/dist/session-process.mjs +6 -1
  16. package/dist/{src-k0Ya1Bri.mjs → src-CmP73EcD.mjs} +510 -161
  17. package/dist/{src-B5SArpVY.mjs → src-DES7hBAP.mjs} +17 -5
  18. package/dist/{src-CC95LYYp.mjs → src-DNn7SifS.mjs} +763 -528
  19. package/dist/store-database-worker.mjs +1 -1
  20. package/dist/web/assets/index-BkuUI3Eu.js +67 -0
  21. package/dist/web/assets/index-Clg6Pfv_.css +1 -0
  22. package/dist/web/index.html +2 -2
  23. package/package.json +1 -1
  24. package/skills/tacitt/SKILL.md +8 -6
  25. package/skills/tacitt/references/commands.md +14 -9
  26. package/skills/tacitt/references/journey.md +10 -6
  27. package/skills/tacitt/references/send.md +1 -1
  28. package/skills/tacitt/scripts/INDEX.md +6 -2
  29. package/skills/tacitt/scripts/journey-card-consolidation.mjs +90 -4
  30. package/skills/tacitt/scripts/journey-card-values.d.mts +17 -0
  31. package/skills/tacitt/scripts/journey-card-values.mjs +116 -0
  32. package/skills/tacitt/scripts/journey-classification.mjs +6 -2
  33. package/skills/tacitt/scripts/journey-panel-host.d.mts +9 -0
  34. package/skills/tacitt/scripts/journey-panel-host.mjs +56 -0
  35. package/skills/tacitt/scripts/journey-state.mjs +10 -4
  36. package/skills/tacitt/scripts/journey-tasks.mjs +1 -1
  37. package/skills/tacitt/scripts/journey.d.mts +2 -0
  38. package/skills/tacitt/scripts/journey.mjs +120 -53
  39. package/skills/tacitt/scripts/review-stream-client.mjs +1 -1
  40. package/dist/job-J3NFw3MH.mjs +0 -194
  41. package/dist/web/assets/index-Bjos73wK.js +0 -67
  42. package/dist/web/assets/index-Bl4C2b2_.css +0 -1
@@ -1,10 +1,10 @@
1
- import { A as runConfiguredSessionJob, C as readConfiguredSearchFields, D as captureLogicalJourneySources, E as prepareCollectionProduction, 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, R as gitSeedsProvider, S as readOriginalSearchFields, T as checkReusableBundle, U as readConfiguredOutputInfo, V as privateJourneyDirectory, W as processJourneyMetadataFields, X as createTemporary, Z as createCleanup, _ as readStaticSessionMasks, a as scanReviewFields, at as windowsAclCodes, c as readSessionScanRecords, et as makePrivateDirectory, g as processJourneyMetadata, h as readCapturedOriginalRows, i as buildWorkScanBatches, it as isWindowsAclFailure, j as readCollectionIndex, k as renderJobDigest, l as scanPreparedSession, n as verifyPreparedSessionSources, nt as restrictToOwner, o as createWorkPieces, p as openConfiguredUpload, r as prepareSessionScans, rt as windowsOwnerOnly, s as readSessionScanFields, t as preparedScanConfiguration, tt as openPrivateFile, v as staticSessionJobs, w as createCollectionArtifacts, y as writeLogicalJourneyBase, z as parallelismLimits } from "./src-k0Ya1Bri.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, zt as filterInventoryByPeriod } 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-B5SArpVY.mjs";
4
- import { $n as reviewRepairResourceSchema, $t as journeyPolicyRespondSchema, A as createAccount, Ar as hasCurrentStaticReview, At as sendReplaceResultSchema, B as createApplication, Bn as confirmedHideKinds, C as bReviewScope, Cn as confirmedHideAdditions, Cr as journeyClassificationSessionsSchema, Ct as sendActionSchema, D as reviewBatchReuse, Dr as nativeEventsSchema, Dt as journeySendSchema, E as rebindReviewSubmit, Er as privacyDecisionDigest, Et as sendStartSchema, F as readAccountAvatarUrl, Fr as isClassifying, Ft as obsoleteJourney, Gr as journeyRenderSchema, Gt as journeyContextQuerySchema, H as inspectionStartSchema, Hr as journeyFindingsSchema, I as storedCredentialSchema, In as bTermMatcher, Ir as isReadingComplete, Jr as selectionBatchSchema, K as inspectionQuerySchema, Kn as retireWorkDescription, Kr as commandSchema, Lr as isSelectedWork, M as deviceAuthorizationSchema, Mt as sendSettingsSchema, N as deviceProfileScopeSchema, Nr as staticSessionResourceSchema, O as guardedFindingText, On as termHideAdditions, Or as nativeGetSchema, Ot as sendArtifactSchema, P as profileUrlMatchesIdentity, Pr as hasCompleteWorkDescription, Pt as currentJourneyAdapters, Qn as reviewRepairInputSchema, Qr as stateSchema, Qt as searchScopeDigest, Rn as confirmedHideGroupIds, S as isGenericPublicTerm, St as replaceSensitiveTermsSchema, T as rebindReviewOverlay, Tn as readConfirmedHideChange, Tr as assertJourneyPrivacyReady, Tt as sendPrepareSchema, U as validateInspectionBatch, Un as journeyConfirmedHides, Ur as journeyPreviewSchema, Ut as journeyReviewStorageQuerySchema, V as inspectionCancelSchema, Vr as journeyFindingImpactSchema, Vt as applyClassification, Wn as maskedHitsStale, Wr as journeyRenderInputSchema, Wt as reviewCompileSchema, Xn as reviewCandidateInputSchema, Xt as journeySearchResultSchema, Yn as workCorrectionBinding, Yr as selectionSchema, Zn as reviewCandidateResourceSchema, Zr as producerVersion, _ as hideScopeDigest, _t as manualUploadStartSchema, a as locateTermEvidence, ai as selectFrozenConfiguration, at as appendSensitiveTerms, bt as maximumTermsFileBytes, c as bRereadDigest, cr as scanGitSeedsId, d as newJourneyWork, dr as scanExclusionResourceSchema, dt as validateFailureLocation, ei as recommendParallelism, fn as sessionRereadInputSchema, ft as manualUploadActionSchema, g as activeJourneyTerms, gt as manualUploadSchema, h as splitSelectorValues, hn as sessionReviewQuerySchema, ht as manualUploadPrepareSchema, i as scanBatchEquivalenceDigest, ii as freezeConfiguration, in as journeyPlaceSchema, j as accountProfileSchema, jr as staticReviewDigest, jt as sendResolutionSchema, k as staticallyHiddenFindings, kr as nativeRespondSchema, kt as sendReceiptSchema, l as reconcileBSession, ln as sessionReconcileInputSchema, lr as scanGitSeedsResource, lt as artifactFailureDetailSchema, m as selectedValueMembers, mn as sessionReviewInputSchema, mr as journeyInventoryQuerySchema, mt as manualUploadChoiceSchema, n as retireScanGeneration, nt as settingsQuerySchema, o as validateBCrossSegmentReview, on as reviewAttemptResourceSchema, or as scanPreparationDigest, qn as workDecisionDigest, qr as packageCommandSchema, r as scanBatchContextDigest, ri as defaultScopedSettings, rr as planBRepair, rt as readWorkspaceOutcomes, s as bCrossSegmentReviewSchema, sr as scanSessionResourceSchema, t as resetWorkScan, tn as journeyResourceQuerySchema, tr as bReviewDigest, tt as settingsBatchSchema, u as reconstructBFields, ur as verifiedScanGitSeeds, ut as resolveFailureLocation, vn as sessionUsefulnessInputSchema, vr as emptyPreparationFacts, w as rebindReviewAlignment, wt as sendChoiceSchema, xt as normalizeSensitiveTerms, y as changeJourney, yt as emptyTermsRevision, zr as withReadingProgress } from "./src-CC95LYYp.mjs";
5
- import { $ as schemaVersion, A as inspectionQuerySchema$1, B as privateReviewHash, C as loadAuthenticatedReview, D as sessionUsefulnessRecommendation, E as aggregateUsefulness, F as reuseQuerySchema, G as initializeAndClaim, H as createJourneyPayloadFiles, I as batchQuery, J as withDatabase, K as ownership, L as isPrivateResourceKind, M as sameCandidateRequest, N as validateCandidateAttempt, O as sessionQuery, P as validateUnitCandidate, Q as checkVersion, R as privateResourceHeader, S as loadAcceptedReview, T as loadPrivateResource, U as validateBatchBody, V as batchHeader, W as requireActiveBatch, X as readStoredState, Y as readBundleDirectory, Z as validateState, _ as loadRereadBodies, _t as isSqliteCorruption, a as decodeError, at as inspectStateFiles, b as validateSessionReconciliation, c as dropsAnchor, ct as readResetMarker, d as parseDelivery, dt as syncDirectory, et as beginReset, f as validateCandidate, ft as withStateSnapshot, g as loadReconciliation, gt as isCoordinationBusy, h as loadPreparedBodies, ht as errorCode$1, i as createExecutor, it as hasResetMarker, j as evaluateUnitEvidence, k as resourceSelectionQuerySchema, l as replySchema, lt as recheckPrivatePath, m as loadPreparation, mt as StoreError, n as releaseInheritedHeapFlags, nt as databaseName, o as ProtocolError, ot as present, p as loadStatus, pt as withFileCheckTimings, q as validateDatabase, r as storeHeapLimits, rt as finishReset, s as allowedReplies, st as privatePath, tt as createDatabaseFile, u as operations, ut as stateDirectory, v as loadSessionBodies, w as loadBatch, x as validateSessionBindings, y as validatePreparationSelection, z as termReuseQuerySchema } from "./heap-BHcvxWCw.mjs";
6
- import { a as writeJourneyOutput, c as mergeHideIndexTokens, l as packRenderHideIndex, o as createHideIndexTokens, s as hideIndexTexts, t as journeyOutputJob } from "./job-J3NFw3MH.mjs";
7
- import { t as createAgentSetupPort } from "./agent-setup-B8DAiQ6Z.mjs";
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-CmP73EcD.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-DES7hBAP.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-DNn7SifS.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-BaTNC9kf.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-DrcpSL_9.mjs";
7
+ import { t as createAgentSetupPort } from "./agent-setup-CQRWVUWR.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
- failure ??= { error };
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) throw failure.error;
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 hooks = {
7936
- signal,
7937
- onTemporary: async () => {},
7938
- onPhase: async () => {}
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
- captureReview: false,
7948
- work: prepared.work,
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, database and
8710
- * resource writes stay on this thread under the cross-process state lock (ADR 0181). Any Work
8711
- * may be split into shards, so the pool is not limited to one worker per Work (ADR 0199).
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 < 2 || size < 1) return void 0;
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
- /** Rebind accepted frozen evidence; original batch/chunk IDs remain its audit provenance. */
9483
- async function regroupPrivacySources(storage, state, works, changedMetadata, signal) {
9484
- const owners = new Map(works.flatMap((work) => work.rootSessionIds.map((id) => [id, work.id])));
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 terms = [];
9488
- for (const term of state.privacy.terms) {
9489
- const sources = [];
9490
- for (const source of term.sources) {
9491
- signal.throwIfAborted();
9492
- if (source.kind === "metadata") {
9493
- if (retained.has(source.workId) && !changedMetadata.has(source.workId)) sources.push(source);
9494
- continue;
9495
- }
9496
- if (retained.has(source.workId)) {
9497
- sources.push(source);
9498
- continue;
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
- terms.push({
9521
- ...term,
9522
- sources
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 terms;
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 commit = (next = state) => storage.commit({
9543
- expectedRevision: bound ? state.revision : input.expectedRevision,
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: textDigest(JSON.stringify(input)),
9546
- state: next,
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 works = [];
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
- const sessions = item.rootSessionIds.flatMap((root) => families.members.get(root) ?? []);
9597
- if (sessions.some((session) => session.parts.some((part) => !captured.has(part.id)))) throw new ApplicationError("SCOPE_NOT_READY");
9598
- const existing = state.works.find((work) => work.placement !== "classifying" && work.rootSessionIds.length === item.rootSessionIds.length && work.rootSessionIds.every((root) => item.rootSessionIds.includes(root)));
9599
- const extended = late ? state.works.find((work) => work.placement === "placed" && work.rootSessionIds.every((root) => item.rootSessionIds.includes(root))) : void 0;
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
- if (pending.length) {
9634
- const prior = state.works.find((work) => work.placement === "classifying" && work.rootSessionIds.length === pending.length && work.rootSessionIds.every((root) => pending.includes(root)));
9635
- const sessions = pending.flatMap((root) => families.members.get(root) ?? []);
9636
- works.push(prior ?? newJourneyWork(randomUUID(), "Still being grouped", pending, [...new Set(sessions.map((session) => session.workspaceId))], "classifying"));
9637
- }
9638
- const retained = new Set(works.map((work) => work.id));
9639
- const changedMetadata = new Set(works.filter((work) => {
9640
- const old = state.works.find((item) => item.id === work.id);
9641
- return !old || JSON.stringify([
9642
- work.title,
9643
- work.projectName,
9644
- work.description,
9645
- work.classificationRecommendation?.reason
9646
- ]) !== JSON.stringify([
9647
- old.title,
9648
- old.projectName,
9649
- old.description,
9650
- old.classificationRecommendation?.reason
9651
- ]);
9652
- }).map((work) => work.id));
9653
- const regrouped = !late && (state.works.some((work) => !retained.has(work.id)) || changedMetadata.size > 0);
9654
- const cardTerms = new Set(state.privacy.cards.flatMap((card) => card.termKeys));
9655
- const preserved = await regroupPrivacySources(storage, state, works, changedMetadata, signal);
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
  }
@@ -10143,7 +9792,33 @@ function createJourneySendArtifacts(storage) {
10143
9792
  const output = await find(journey.journeyId, work.id, work.revision);
10144
9793
  const info = await readConfiguredOutputInfo(output.primary, signal);
10145
9794
  if (info.work.id !== work.id || info.work.access !== work.access) throw new ApplicationError("ARTIFACT_IDENTITY_MISMATCH");
9795
+ let agentSources = [];
9796
+ try {
9797
+ const base = (await storage.resources({
9798
+ journeyId: journey.journeyId,
9799
+ kind: "base"
9800
+ })).find((item) => item.kind === "base" && item.workId === work.id && item.inputRevision === output.finalMaskLedger.inputRevision && item.primary.sha256 === output.finalMaskLedger.basePrimarySha256);
9801
+ if (base?.kind === "base") {
9802
+ const included = new Set(base.sessions.map((item) => item.applicationSessionId));
9803
+ const resources = await storage.resources({
9804
+ journeyId: journey.journeyId,
9805
+ kind: "static-session"
9806
+ });
9807
+ const unique = /* @__PURE__ */ new Map();
9808
+ for (const resource of resources) {
9809
+ if (resource.kind !== "static-session" || resource.workId !== work.id || resource.inputRevision !== base.inputRevision || !included.has(resource.sessionId)) continue;
9810
+ for (const candidate of resource.agentSources ?? []) {
9811
+ const parsed = agentSourceSchema.safeParse(candidate);
9812
+ if (parsed.success && unique.size < 500) unique.set(JSON.stringify(parsed.data), parsed.data);
9813
+ }
9814
+ }
9815
+ agentSources = [...unique.values()];
9816
+ }
9817
+ } catch {
9818
+ console.error("AGENT_SOURCES_METADATA_UNAVAILABLE");
9819
+ }
10146
9820
  return sendArtifactSchema.parse({
9821
+ ...agentSources.length === 0 ? {} : { agentSources },
10147
9822
  workId: work.id,
10148
9823
  revision: work.revision,
10149
9824
  bundleId: info.manifest.bundleId,
@@ -10156,16 +9831,19 @@ function createJourneySendArtifacts(storage) {
10156
9831
  sha256: output.primary.sha256
10157
9832
  });
10158
9833
  },
10159
- async upload(journeyId, artifact, uploadUrl, signal) {
9834
+ async upload(journeyId, artifact, upload, signal) {
10160
9835
  const output = await find(journeyId, artifact.workId, artifact.revision);
10161
9836
  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, uploadUrl, signal);
9837
+ await putPrimary(output.primary, artifact.byteLength, upload, signal);
10163
9838
  }
10164
9839
  };
10165
9840
  }
10166
- /** Stream a verified primary to a signed HTTPS URL; shared by native Send and Manual upload. */
10167
- async function putPrimary(primary, byteLength, uploadUrl, signal) {
10168
- const url = new URL(uploadUrl);
9841
+ /**
9842
+ * Stream a verified primary to a signed HTTPS URL; shared by native Send and Manual upload. A
9843
+ * checksum upload also sends the SHA-256 header the URL signs, so storage refuses other bytes.
9844
+ */
9845
+ async function putPrimary(primary, byteLength, upload, signal) {
9846
+ const url = new URL(upload.uploadUrl);
10169
9847
  if (url.protocol !== "https:" || url.username || url.password || url.hash) throw new ApplicationError("INVALID_ARGUMENTS");
10170
9848
  const source = await openConfiguredUpload(primary, signal);
10171
9849
  const cleanup = createCleanup();
@@ -10177,7 +9855,8 @@ async function putPrimary(primary, byteLength, uploadUrl, signal) {
10177
9855
  redirect: "error",
10178
9856
  headers: {
10179
9857
  "Content-Type": "application/zip",
10180
- "Content-Length": String(byteLength)
9858
+ "Content-Length": String(byteLength),
9859
+ ...upload.headers
10181
9860
  },
10182
9861
  duplex: "half"
10183
9862
  };
@@ -10299,6 +9978,85 @@ async function readOwnerJson(response) {
10299
9978
  return JSON.parse(Buffer.concat(chunks, bytes).toString("utf8"));
10300
9979
  }
10301
9980
  //#endregion
9981
+ //#region apps/cli/src/account/journey/public-sources.ts
9982
+ /** Anonymous evidence only. Expected absence, refusal and lookup failure all omit enrichment. */
9983
+ async function publicSource(source, requestFetch, signal) {
9984
+ const controller = new AbortController();
9985
+ let onAbort = () => {};
9986
+ const cancelled = new Promise((resolve) => {
9987
+ onAbort = () => {
9988
+ controller.abort();
9989
+ resolve(false);
9990
+ };
9991
+ signal?.addEventListener("abort", onAbort, { once: true });
9992
+ if (signal?.aborted) onAbort();
9993
+ });
9994
+ let timer;
9995
+ const expired = new Promise((resolve) => {
9996
+ timer = setTimeout(() => {
9997
+ controller.abort();
9998
+ resolve(false);
9999
+ }, 3e3);
10000
+ });
10001
+ const check = async () => {
10002
+ const response = await requestFetch(source.type === "github" ? `https://api.github.com/repos/${source.repo}` : `https://registry.npmjs.org/${encodeURIComponent(source.package)}`, {
10003
+ method: "GET",
10004
+ credentials: "omit",
10005
+ redirect: "error",
10006
+ referrerPolicy: "no-referrer",
10007
+ cache: "no-store",
10008
+ signal: controller.signal
10009
+ });
10010
+ if (response.status !== 200 || source.type === "npm") {
10011
+ await response.body?.cancel();
10012
+ return response.status === 200;
10013
+ }
10014
+ const value = await readOwnerJson(response);
10015
+ return typeof value === "object" && value !== null && "private" in value && value.private === false;
10016
+ };
10017
+ try {
10018
+ return await Promise.race([
10019
+ check(),
10020
+ expired,
10021
+ cancelled
10022
+ ]);
10023
+ } catch {
10024
+ return false;
10025
+ } finally {
10026
+ signal?.removeEventListener("abort", onAbort);
10027
+ clearTimeout(timer);
10028
+ controller.abort();
10029
+ }
10030
+ }
10031
+ /** One upload, at most twenty distinct checks in parallel and a three-second I/O deadline. */
10032
+ async function checkPublicAgentSources(candidates, requestFetch = fetch, signal) {
10033
+ signal?.throwIfAborted();
10034
+ if (!Array.isArray(candidates) || candidates.length > 500) return [];
10035
+ const admittedShape = candidates.flatMap((item) => {
10036
+ const parsed = agentSourcesSchema.element.safeParse(item);
10037
+ return parsed.success ? [parsed.data] : [];
10038
+ });
10039
+ const cache = /* @__PURE__ */ new Map();
10040
+ const selected = [];
10041
+ for (const item of admittedShape) {
10042
+ const source = item.source;
10043
+ const key = source.type === "github" ? `github:${source.repo.toLowerCase()}` : `npm:${source.package}`;
10044
+ let result = cache.get(key);
10045
+ if (!result) {
10046
+ if (cache.size === 20) continue;
10047
+ result = publicSource(source, requestFetch, signal);
10048
+ cache.set(key, result);
10049
+ }
10050
+ selected.push({
10051
+ item,
10052
+ result
10053
+ });
10054
+ }
10055
+ const results = await Promise.all(selected.map(async ({ item, result }) => await result ? item : null));
10056
+ signal?.throwIfAborted();
10057
+ return results.filter((item) => item !== null);
10058
+ }
10059
+ //#endregion
10302
10060
  //#region apps/cli/src/account/journey/client.ts
10303
10061
  const refusalSchema = z.object({
10304
10062
  error: z.string(),
@@ -10347,7 +10105,7 @@ const completeTimeoutMs = 6e5;
10347
10105
  function createJourneyOwnerClient(options) {
10348
10106
  const base = apiBase(options.apiUrl);
10349
10107
  const requestFetch = options.fetch ?? fetch;
10350
- async function request(path, schema, signal, body, method = "POST", key, timeoutMs = requestTimeoutMs$2) {
10108
+ async function request(path, schema, signal, body, method = "POST", key, timeoutMs = requestTimeoutMs$2, checksumUpload = false) {
10351
10109
  const token = await options.authorize();
10352
10110
  signal.throwIfAborted();
10353
10111
  const response = await requestFetch(new URL(path, base), {
@@ -10356,7 +10114,8 @@ function createJourneyOwnerClient(options) {
10356
10114
  accept: "application/json",
10357
10115
  authorization: `Bearer ${token}`,
10358
10116
  ...body === void 0 ? {} : { "content-type": "application/json" },
10359
- ...key === void 0 ? {} : { "idempotency-key": key }
10117
+ ...key === void 0 ? {} : { "idempotency-key": key },
10118
+ ...checksumUpload ? { "upload-checksum": "sha256" } : {}
10360
10119
  },
10361
10120
  ...body === void 0 ? {} : { body: JSON.stringify(body) },
10362
10121
  credentials: "omit",
@@ -10387,6 +10146,9 @@ function createJourneyOwnerClient(options) {
10387
10146
  const receipt = (id) => sendReceiptSchema.refine((value) => value.receiptId === id);
10388
10147
  return {
10389
10148
  subject: options.subject,
10149
+ checkAgentSources(candidates, signal) {
10150
+ return checkPublicAgentSources(candidates, requestFetch, signal);
10151
+ },
10390
10152
  async assertCurrent(signal) {
10391
10153
  await options.authorize();
10392
10154
  signal.throwIfAborted();
@@ -10411,14 +10173,21 @@ function createJourneyOwnerClient(options) {
10411
10173
  membersDigest: preview.membersDigest
10412
10174
  }, "PUT");
10413
10175
  },
10414
- reserve(key, input, signal) {
10415
- return request("/me/work-bundles/uploads", sendReceiptSchema.refine((value) => receiptMatches(input, value)), signal, input, "POST", key);
10176
+ async reserve(key, input, signal, checksumUpload = true) {
10177
+ const reserve = (body) => request("/me/work-bundles/uploads", sendReceiptSchema.refine((value) => receiptMatches(input, value)), signal, body, "POST", key, requestTimeoutMs$2, checksumUpload);
10178
+ try {
10179
+ return await reserve(input);
10180
+ } catch (error) {
10181
+ if (!(error instanceof JourneyRemoteError) || error.status !== 400 || error.code !== "invalid_request" || input.agentSources === void 0) throw error;
10182
+ const { agentSources: _, ...withoutSources } = input;
10183
+ return reserve(withoutSources);
10184
+ }
10416
10185
  },
10417
10186
  complete(id, signal) {
10418
10187
  return request(`/me/work-bundles/uploads/${encodeURIComponent(id)}/complete`, receipt(id), signal, {}, "POST", void 0, completeTimeoutMs);
10419
10188
  },
10420
- status(id, signal) {
10421
- return request(`/me/work-bundles/uploads/${encodeURIComponent(id)}`, receipt(id), signal, void 0, "GET");
10189
+ status(id, signal, checksumUpload = true) {
10190
+ return request(`/me/work-bundles/uploads/${encodeURIComponent(id)}`, receipt(id), signal, void 0, "GET", void 0, requestTimeoutMs$2, checksumUpload);
10422
10191
  },
10423
10192
  async replace(keepWorkIds, signal) {
10424
10193
  if (keepWorkIds.length === 0) throw new ApplicationError("INVALID_STATE");
@@ -10565,6 +10334,8 @@ async function resolveItem(item, bindingId, connection, signal, snapshot = false
10565
10334
  resolution
10566
10335
  }, settings, null, snapshot);
10567
10336
  }
10337
+ const agentSources = await connection.checkAgentSources?.(item.artifact.agentSources, signal);
10338
+ signal.throwIfAborted();
10568
10339
  return {
10569
10340
  ...item,
10570
10341
  target,
@@ -10572,6 +10343,7 @@ async function resolveItem(item, bindingId, connection, signal, snapshot = false
10572
10343
  status: "uploading",
10573
10344
  error: null,
10574
10345
  reservation: {
10346
+ ...agentSources?.length ? { agentSources } : {},
10575
10347
  work,
10576
10348
  target,
10577
10349
  bundleId: item.artifact.bundleId,
@@ -10608,9 +10380,24 @@ async function perform(context, item, connection, signal) {
10608
10380
  const auto = context.snapshot === true;
10609
10381
  await guard();
10610
10382
  if (item.settings && item.settingsConfirmed) return applySettings(item, connection, signal, guard, auto);
10611
- if (!item.reservation) return resolveItem(item, context.bindingId, connection, signal, auto, context.name);
10612
- if (!item.receipt) return observe(item, await connection.reserve(item.operationKey, item.reservation, signal), connection, signal, auto);
10613
- const receipt = await connection.status(item.receipt.receiptId, signal);
10383
+ if (!item.reservation) {
10384
+ const next = await resolveItem(item, context.bindingId, connection, signal, auto, context.name);
10385
+ if (next.reservation) context.fresh?.set(item.workId, null);
10386
+ return next;
10387
+ }
10388
+ if (!item.receipt) {
10389
+ const reserved = await connection.reserve(item.operationKey, item.reservation, signal, context.checksumUploads !== false);
10390
+ const next = await observe(item, reserved, connection, signal, auto);
10391
+ if (context.fresh?.has(item.workId) && reserved.upload && next.status === "uploading") context.fresh.set(item.workId, {
10392
+ receiptId: reserved.receiptId,
10393
+ upload: reserved.upload
10394
+ });
10395
+ return next;
10396
+ }
10397
+ const fresh = context.fresh?.get(item.workId);
10398
+ context.fresh?.delete(item.workId);
10399
+ if (fresh?.receiptId === item.receipt.receiptId) return put(context, item, fresh.receiptId, fresh.upload, connection, signal, guard);
10400
+ const receipt = await connection.status(item.receipt.receiptId, signal, context.checksumUploads !== false);
10614
10401
  const observed = await observe(item, receipt, connection, signal, auto);
10615
10402
  if (receipt.state !== "awaiting_upload") return withoutUpload(observed);
10616
10403
  if (!receipt.upload) throw new ApplicationError("INVALID_STATE");
@@ -10629,15 +10416,23 @@ async function perform(context, item, connection, signal) {
10629
10416
  }
10630
10417
  const current = await connection.resolve(item.reservation.work, signal);
10631
10418
  if (current.scopeVersion !== item.reservation.expectedScopeVersion || current.accessVersion !== item.reservation.expectedAccessVersion) return renewed(item, null);
10419
+ return put(context, item, receipt.receiptId, receipt.upload, connection, signal, guard);
10420
+ }
10421
+ /** Uploads the bytes to the receipt's signed URL and completes it. */
10422
+ async function put(context, item, receiptId, upload, connection, signal, guard) {
10632
10423
  await guard();
10633
- await context.upload(item.artifact, receipt.upload.uploadUrl, signal);
10424
+ await context.upload(item.artifact, upload, signal);
10634
10425
  await guard();
10635
10426
  try {
10636
- return observe(item, await connection.complete(receipt.receiptId, signal), connection, signal, auto);
10427
+ return observe(item, await connection.complete(receiptId, signal), connection, signal, context.snapshot === true);
10637
10428
  } catch (error) {
10429
+ if (upload.headers !== void 0 && error instanceof JourneyRemoteError && error.code === "bundle_mismatch") return {
10430
+ ...renewed(withoutUpload(item), null),
10431
+ checksumUploadRefused: true
10432
+ };
10638
10433
  if (error instanceof Object) uploaded.set(error, {
10639
10434
  ...item,
10640
- uploadedReceiptId: receipt.receiptId
10435
+ uploadedReceiptId: receiptId
10641
10436
  });
10642
10437
  throw error;
10643
10438
  }
@@ -10705,14 +10500,16 @@ const confirmedSettings = (item) => item.settings !== null && item.settingsConfi
10705
10500
  * time (ADR 0214). Assessment checks change nothing and step up to three together; a confirmed
10706
10501
  * linked Settings change alters access versions, so it always steps alone.
10707
10502
  */
10708
- function pickBatch(items) {
10503
+ function pickBatch(items, now = Date.now()) {
10709
10504
  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
10505
  if (uploading) return [uploading];
10711
10506
  const assessing = items.filter((item) => item.status === "assessing");
10712
10507
  const first = assessing[0];
10713
10508
  if (!first) return [];
10714
10509
  if (confirmedSettings(first)) return [first];
10715
- return assessing.filter((item) => !confirmedSettings(item)).slice(0, 3);
10510
+ const checks = assessing.filter((item) => !confirmedSettings(item));
10511
+ const start = Math.floor(now / 1e3) * 3 % checks.length;
10512
+ return [...checks.slice(start), ...checks.slice(0, start)].slice(0, 3);
10716
10513
  }
10717
10514
  function isCode(error, code) {
10718
10515
  return error instanceof ApplicationError && error.code === code;
@@ -10806,7 +10603,11 @@ async function advanceItems(host, signal) {
10806
10603
  if (finished !== null) return finished;
10807
10604
  }
10808
10605
  if (!batch.length) return host.applied();
10809
- const results = await Promise.allSettled(batch.map((item) => stepItem(host.context(item), item, connection, signal)));
10606
+ const checksumUploads = !send.items.some((item) => item.checksumUploadRefused);
10607
+ const results = await Promise.allSettled(batch.map((item) => stepItem({
10608
+ ...host.context(item),
10609
+ checksumUploads
10610
+ }, item, connection, signal)));
10810
10611
  const failures = results.flatMap((result) => result.status === "rejected" ? [result] : []);
10811
10612
  if (failures.some(({ reason }) => isCode(reason, "REVISION_CONFLICT"))) return host.stale();
10812
10613
  const first = failures[0];
@@ -10827,7 +10628,7 @@ async function advanceItems(host, signal) {
10827
10628
  if (progressed) next.set(item.workId, progressed);
10828
10629
  if (cause instanceof JourneyRemoteError && cause.status === 401 || isCode(cause, "ACCOUNT_SIGNED_OUT")) signIn = true;
10829
10630
  else if (isCode(cause, "IDENTITY_REJECTED")) changed = true;
10830
- else if (isInterrupted(cause)) {
10631
+ else if (isInterrupted(cause) || isTransientRefusal(cause)) {
10831
10632
  host.failure("SEND_INTERRUPTED", cause);
10832
10633
  error = "SEND_INTERRUPTED";
10833
10634
  } else {
@@ -10935,8 +10736,9 @@ function withoutQuestions(send) {
10935
10736
  };
10936
10737
  return send.status === "review" ? aggregate(next) : next;
10937
10738
  }
10938
- async function advanceSend(dependencies, address, signal) {
10739
+ async function advanceSend(dependencies, address, signal, chain) {
10939
10740
  const snapshot = requireSend(await dependencies.storage.loadSend(address.journeyId, address.sendId));
10741
+ if (chain) chain.revision = snapshot.send.revision;
10940
10742
  if ((await dependencies.storage.loadSend(address.journeyId))?.send?.id !== snapshot.send.id) return {
10941
10743
  ...snapshot,
10942
10744
  status: "stale"
@@ -10968,22 +10770,23 @@ async function advanceSend(dependencies, address, signal) {
10968
10770
  return advanceItems({
10969
10771
  send,
10970
10772
  failure: dependencies.failure,
10971
- connect: dependencies.auth.connect,
10773
+ connect: chain?.connect ?? dependencies.auth.connect,
10972
10774
  profile: (connection, profileSignal) => prepareProfile(dependencies, snapshot, connection, profileSignal),
10973
10775
  context: (item) => {
10974
10776
  const binding = snapshot.journey.works.find((value) => value.id === item.workId)?.binding;
10975
10777
  const retired = !item.receipt && binding?.sentBundleId === item.artifact.bundleId && binding.sentAccess === item.access;
10976
10778
  return {
10977
10779
  snapshot: true,
10780
+ ...chain ? { fresh: chain.fresh } : {},
10978
10781
  active: async () => {
10979
10782
  const current = requireSend(await dependencies.storage.loadSend(snapshot.journey.journeyId));
10980
10783
  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
10784
  assertScope(current.journey, current.send);
10982
10785
  },
10983
10786
  bindingId: binding?.subject === send.subject && !retired ? binding.bundleRecordId : void 0,
10984
- upload: async (artifact, uploadUrl, uploadSignal) => {
10787
+ upload: async (artifact, upload, uploadSignal) => {
10985
10788
  const started = performance.now();
10986
- await dependencies.artifacts.upload(snapshot.journey.journeyId, artifact, uploadUrl, uploadSignal);
10789
+ await dependencies.artifacts.upload(snapshot.journey.journeyId, artifact, upload, uploadSignal);
10987
10790
  dependencies.uploaded?.(artifact.workId, artifact.byteLength, Math.round(performance.now() - started));
10988
10791
  }
10989
10792
  };
@@ -11029,6 +10832,14 @@ async function advanceSend(dependencies, address, signal) {
11029
10832
  })
11030
10833
  }, signal);
11031
10834
  }
10835
+ /**
10836
+ * A refusal Passport answers while it or Identity is briefly unavailable. It says nothing about
10837
+ * the Work, so the step is retried instead of blocking it (the 2026-11-01 recorded run blocked a
10838
+ * Work on one `identity_unavailable`).
10839
+ */
10840
+ function isTransientRefusal(error) {
10841
+ return error instanceof JourneyRemoteError && (error.status >= 500 || error.status === 429 || error.code === "identity_unavailable");
10842
+ }
11032
10843
  //#endregion
11033
10844
  //#region apps/cli/src/account/journey/decisions.ts
11034
10845
  function decide(item, input) {
@@ -11299,10 +11110,10 @@ function createManualUploader(dependencies) {
11299
11110
  active: guard(upload, item),
11300
11111
  name: item.title,
11301
11112
  bindingId: bindings.find((value) => value.subject === upload.subject && value.pathHash === pathHash(item))?.bundleRecordId,
11302
- upload: (artifact, uploadUrl, uploadSignal) => {
11113
+ upload: (artifact, signedUpload, uploadSignal) => {
11303
11114
  const slot = upload.slots[item.workId];
11304
11115
  if (slot === void 0) throw new ApplicationError("ARTIFACT_MISSING");
11305
- return dependencies.artifacts.upload(upload.resultId, slot, artifact, uploadUrl, uploadSignal);
11116
+ return dependencies.artifacts.upload(upload.resultId, slot, artifact, signedUpload, uploadSignal);
11306
11117
  }
11307
11118
  }),
11308
11119
  applied: () => ({
@@ -11372,10 +11183,10 @@ function createManualUploadArtifacts(application) {
11372
11183
  })
11373
11184
  };
11374
11185
  },
11375
- async upload(resultId, slot, artifact, uploadUrl, signal) {
11186
+ async upload(resultId, slot, artifact, upload, signal) {
11376
11187
  const { row, primary } = await find(resultId, slot);
11377
11188
  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, uploadUrl, signal);
11189
+ await putPrimary(primary, artifact.byteLength, upload, signal);
11379
11190
  }
11380
11191
  };
11381
11192
  }
@@ -12538,13 +12349,42 @@ function createJourneyFiles(storage) {
12538
12349
  };
12539
12350
  };
12540
12351
  }
12352
+ /**
12353
+ * One advance carries the Work being uploaded through its phases with one verified connection,
12354
+ * instead of one phase per panel round trip, and uploads a receipt it just resolved and reserved
12355
+ * without reading it again (ADR 0229). Each phase is still fenced and saved on its own, so Pause, a
12356
+ * revision change or a failure stops the chain at the next phase exactly as between advances.
12357
+ * The advance returns once that Work leaves its upload phases, so the next Work starts from the
12358
+ * caller's next advance as before.
12359
+ */
12360
+ async function advanceWork(dependencies, address, signal, phases = 3) {
12361
+ let connection;
12362
+ const chain = {
12363
+ fresh: /* @__PURE__ */ new Map(),
12364
+ async connect(connectSignal) {
12365
+ if (connection === void 0) connection = await dependencies.auth.connect(connectSignal);
12366
+ return connection;
12367
+ }
12368
+ };
12369
+ let result = await advanceSend(dependencies, address, signal, chain);
12370
+ for (let phase = 1; phase < phases && uploading(result, chain.revision); phase += 1) result = await advanceSend(dependencies, address, signal, chain);
12371
+ return result;
12372
+ }
12373
+ /** The phase saved progress and left its Work mid-upload, with nothing else to report. */
12374
+ function uploading(result, before) {
12375
+ const send = result.send;
12376
+ return result.status === "applied" && send !== null && before !== void 0 && send.revision > before && send.status === "sending" && send.error === null && pickBatch(send.items)[0]?.status === "uploading";
12377
+ }
12541
12378
  //#endregion
12542
12379
  //#region apps/cli/src/account/journey/send.ts
12543
12380
  function sameIds(left, right) {
12544
12381
  return left.length === right.length && new Set(left).size === left.length && left.every((id) => right.includes(id));
12545
12382
  }
12546
- /** Durable, bounded orchestration; native chat and panel use the same commands. */
12547
- function createJourneySender(dependencies) {
12383
+ /**
12384
+ * Durable, bounded orchestration; native chat and panel use the same commands. One advance runs
12385
+ * at most `phases` upload phases of one Work (ADR 0229); tests pass 1 to observe each phase.
12386
+ */
12387
+ function createJourneySender(dependencies, phases = 3) {
12548
12388
  const running = /* @__PURE__ */ new Map();
12549
12389
  const runKey = (address) => `${address.journeyId}:${address.sendId}`;
12550
12390
  const load = async (address) => {
@@ -12657,7 +12497,7 @@ function createJourneySender(dependencies) {
12657
12497
  const current = running.get(key);
12658
12498
  if (current) return current.result;
12659
12499
  const stop = new AbortController();
12660
- const result = advanceSend(dependencies, address, AbortSignal.any([signal, stop.signal])).catch(async (error) => {
12500
+ const result = advanceWork(dependencies, address, AbortSignal.any([signal, stop.signal]), phases).catch(async (error) => {
12661
12501
  if (!stop.signal.aborted || signal.aborted) throw error;
12662
12502
  return {
12663
12503
  ...requireSend(await dependencies.storage.loadSend(address.journeyId, address.sendId)),