@gitroll/tacitt-cli 0.6.9 → 0.6.10

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 (41) hide show
  1. package/dist/{agent-setup-CODlN6zl.mjs → agent-setup-LPd8WEsN.mjs} +2 -2
  2. package/dist/agent-setup-worker.mjs +5 -5
  3. package/dist/cli.mjs +19 -10
  4. package/dist/codex-title-worker.mjs +1 -1
  5. package/dist/configured-preview-worker.mjs +5 -5
  6. package/dist/configured-session-worker.mjs +5 -5
  7. package/dist/{frame-Dj5Zs6UW.mjs → frame-ByT1xayJ.mjs} +1 -1
  8. package/dist/{heap-Dt3-d27v.mjs → heap-CM298Yu0.mjs} +8 -6
  9. package/dist/inspection-worker.mjs +4 -4
  10. package/dist/{job-BQXssW46.mjs → job-BHIwElNF.mjs} +5 -5
  11. package/dist/{locate-BWkblijS.mjs → locate-Cwv6WH8n.mjs} +25 -2
  12. package/dist/{model-CSx2dP_P.mjs → model-zvn5GUJ9.mjs} +1 -1
  13. package/dist/operation-executor.mjs +3 -3
  14. package/dist/preview-worker.mjs +1 -1
  15. package/dist/privacy-validator.mjs +1 -1
  16. package/dist/{query-CfhcS4gd.mjs → query-By41gaKm.mjs} +424 -145
  17. package/dist/{run-xlOaKCk8.mjs → run-ZQx7p4Rw.mjs} +4 -4
  18. package/dist/{src-V9GHLZQs.mjs → src-3jvMvSQn.mjs} +3 -3
  19. package/dist/{src-DzKuzGVH.mjs → src-BqtdFVq7.mjs} +484 -178
  20. package/dist/{src-BgsEnMsy.mjs → src-D_DfY--2.mjs} +4 -4
  21. package/dist/store-database-worker.mjs +1 -1
  22. package/dist/web/assets/index-B14e6_-z.js +174 -0
  23. package/dist/web/index.html +1 -1
  24. package/package.json +1 -1
  25. package/skills/tacitt/INDEX.md +6 -6
  26. package/skills/tacitt/SKILL.md +2 -2
  27. package/skills/tacitt/references/INDEX.md +3 -3
  28. package/skills/tacitt/references/classification.md +12 -2
  29. package/skills/tacitt/references/commands.md +10 -2
  30. package/skills/tacitt/references/journey.md +9 -1
  31. package/skills/tacitt/references/send.md +29 -8
  32. package/skills/tacitt/scripts/INDEX.md +37 -37
  33. package/skills/tacitt/scripts/journey-classification-validation.d.mts +13 -0
  34. package/skills/tacitt/scripts/journey-classification-validation.mjs +38 -0
  35. package/skills/tacitt/scripts/journey-state.d.mts +6 -0
  36. package/skills/tacitt/scripts/journey-state.mjs +24 -1
  37. package/skills/tacitt/scripts/journey.d.mts +5 -0
  38. package/skills/tacitt/scripts/journey.mjs +33 -3
  39. package/skills/tacitt/scripts/privacy-validator.mjs +11 -1
  40. package/skills/tacitt/scripts/review.mjs +14 -2
  41. package/dist/web/assets/index-9UfCT_2C.js +0 -174
@@ -1,10 +1,10 @@
1
- import { $ as createCleanup, B as parallelismLimits, C as readOriginalSearchFields, F as SessionWorkerError, G as processJourneyMetadataFields, H as privateJourneyDirectory, I as sessionWorkerFailure, K as staticMaskCategory, O as captureLogicalJourneySources, P as createSharedSessionPool, Q as removeNamedTemporarySpools, R as defaultParallelism, T as createCollectionArtifacts, U as readLogicalJourneyFinalMasks, W as readConfiguredOutputInfo, Z as createTemporary, _ as processJourneyMetadata, a as scanReviewFields, at as windowsOwnerOnly, c as readSessionScanRecords, g as readCapturedOriginalRows, i as buildWorkScanBatches, it as restrictToOwner, j as runConfiguredSessionJob, k as capturedJourneyScope, l as scanPreparedSession, m as openConfiguredUpload, n as verifyPreparedSessionSources, nt as makePrivateDirectory, o as createWorkPieces, ot as isWindowsAclFailure, p as configuredSessionUploads, q as readWorkspacePair, r as prepareSessionScans, rt as openPrivateFile, s as readSessionScanFields, st as windowsAclCodes, t as preparedScanConfiguration, v as readStaticSessionMasks, w as readConfiguredSearchFields, y as staticSessionJobs, z as gitSeedsProvider } from "./src-V9GHLZQs.mjs";
2
- import { A as createExecutionPort, C as saveRemoteState, D as createPackagingProgressPort, E as exportDelivery, F as loadDeliveryResults, L as inBackgroundStoreLane, M as withStateMutex, N as createDeliveryService, O as createStore, P as preflight, R as isBackgroundStatusPoll, S as readRemoteState, T as collectRemoteWorkers, _ as staticBaseProduction, a as writeJourneyOutput, b as workApproval, c as mergeHideIndexTokens, f as writeJourneyBase, g as staticBaseInputs, h as staticSessions, l as packRenderHideIndex, m as saveStaticSession, o as createHideIndexTokens, p as readStaticResult, s as hideIndexTexts, t as journeyOutputJob, u as journeyBaseJob, v as readSelectedCaptures, w as journeyDirectory, x as isRemoteImport, y as prepareSourcePlan, z as retrySharingViolation } from "./job-BQXssW46.mjs";
3
- import { $t as safeField, Bt as sessionPeriodSchema, Dn as createStaticProcessor, Fn as entityDictionaryVersion, Ft as termTypeInfo, Gt as ownerProfileSchema, Kt as passportAddressSchema, Ot as privacyTermKey, Qt as safeCode, Sn as currentDetectorVersion, T as bLiteralTerms, Un as frameRecords, Wt as mergeOwnerProfile, Xt as ApplicationError, Zt as UnfinishedJourneyError, an as observeSource, ar as admitCustomTerm, c as textDigest, ir as createEntityMatcher, nn as reviewFamilies, or as admitGitName, qt as profileHasChoices, tn as familySelected, ur as ProcessingError, zt as filterInventoryByPeriod } from "./locate-BWkblijS.mjs";
4
- import { a as mainTextChunks, c as previewInWorker, i as nearestSortingRepository, l as createNativeAdapters, n as createProjectEvidenceCollector, o as mainTextFields, r as sortingRepositoryId, s as textChunks, t as createHarnesses, u as createGitNameSeedProvider } from "./src-BgsEnMsy.mjs";
5
- import { $ as settingsBatchSchema, $r as isReadingComplete, A as deviceAuthorizationSchema, B as inspectionStartSchema, Bt as classificationWorks, Ct as sendPrepareSchema, Dt as currentJourneyAdapters, Fn as bTermMatcher, Gn as retireWorkDescription, Gr as sendArtifactSchema, Hn as journeyConfirmedHides, Hr as agentSourceSchema, Ht as newJourneyWork, Jn as workCorrectionBinding, Jr as sendResolutionSchema, Kn as workDecisionDigest, Kr as sendReceiptSchema, Lr as hasCurrentStaticReview, M as profileUrlMatchesIdentity, Mr as privacyDecisionDigest, N as readAccountAvatarUrl, O as createAccount, Or as displayedScopeDigest, Ot as obsoleteJourney, P as storedCredentialSchema, Pt as applyClassification, Qr as isIncludedWork, R as createApplication, Rr as staticReviewDigest, St as sendChoiceSchema, Tt as sendStartSchema, Ur as agentSourcesSchema, V as validateInspectionBatch, Vt as retireScanGeneration, W as inspectionQuerySchema, Wr as journeySendSchema, Xr as hasCompleteWorkDescription, Yr as sendSettingsSchema, Yt as journeySearchResultSchema, Zr as isClassifying, Zt as searchScopeDigest, _ as changeJourney, _r as emptyPreparationFacts, _t as emptyTermsRevision, ai as packageCommandSchema, ar as scanPreparationDigest, bt as replaceSensitiveTermsSchema, cr as scanGitSeedsResource, ei as isSelectedWork, et as settingsQuerySchema, fi as recommendParallelism, ft as manualUploadChoiceSchema, h as hideScopeDigest, ht as manualUploadStartSchema, ii as commandSchema, j as deviceProfileScopeSchema, jr as assertJourneyPrivacyReady, k as accountProfileSchema, kr as requestedScope, li as producerVersion, lr as verifiedScanGitSeeds, lt as validateFailureLocation, m as activeJourneyTerms, mt as manualUploadSchema, oi as selectionBatchSchema, or as scanSessionResourceSchema, pt as manualUploadPrepareSchema, qr as sendReplaceResultSchema, ri as withReadingProgress, rt as appendSensitiveTerms, si as selectionSchema, sr as scanGitSeedsId, st as artifactFailureDetailSchema, ur as scanExclusionResourceSchema, ut as manualUploadActionSchema, wt as sendRequestSchema, xt as sendActionSchema, yt as normalizeSensitiveTerms, z as inspectionCancelSchema, zn as confirmedHideKinds, zt as classificationPartition } from "./src-DzKuzGVH.mjs";
6
- import { _t as isCoordinationBusy } from "./heap-Dt3-d27v.mjs";
7
- import { t as createAgentSetupPort } from "./agent-setup-CODlN6zl.mjs";
1
+ import { $ as createCleanup, B as parallelismLimits, C as readOriginalSearchFields, F as SessionWorkerError, G as processJourneyMetadataFields, H as privateJourneyDirectory, I as sessionWorkerFailure, K as staticMaskCategory, O as captureLogicalJourneySources, P as createSharedSessionPool, Q as removeNamedTemporarySpools, R as defaultParallelism, T as createCollectionArtifacts, U as readLogicalJourneyFinalMasks, W as readConfiguredOutputInfo, Z as createTemporary, _ as processJourneyMetadata, a as scanReviewFields, at as windowsOwnerOnly, c as readSessionScanRecords, g as readCapturedOriginalRows, i as buildWorkScanBatches, it as restrictToOwner, j as runConfiguredSessionJob, k as capturedJourneyScope, l as scanPreparedSession, m as openConfiguredUpload, n as verifyPreparedSessionSources, nt as makePrivateDirectory, o as createWorkPieces, ot as isWindowsAclFailure, p as configuredSessionUploads, q as readWorkspacePair, r as prepareSessionScans, rt as openPrivateFile, s as readSessionScanFields, st as windowsAclCodes, t as preparedScanConfiguration, v as readStaticSessionMasks, w as readConfiguredSearchFields, y as staticSessionJobs, z as gitSeedsProvider } from "./src-3jvMvSQn.mjs";
2
+ import { A as createExecutionPort, C as saveRemoteState, D as createPackagingProgressPort, E as exportDelivery, F as loadDeliveryResults, L as inBackgroundStoreLane, M as withStateMutex, N as createDeliveryService, O as createStore, P as preflight, R as isBackgroundStatusPoll, S as readRemoteState, T as collectRemoteWorkers, _ as staticBaseProduction, a as writeJourneyOutput, b as workApproval, c as mergeHideIndexTokens, f as writeJourneyBase, g as staticBaseInputs, h as staticSessions, l as packRenderHideIndex, m as saveStaticSession, o as createHideIndexTokens, p as readStaticResult, s as hideIndexTexts, t as journeyOutputJob, u as journeyBaseJob, v as readSelectedCaptures, w as journeyDirectory, x as isRemoteImport, y as prepareSourcePlan, z as retrySharingViolation } from "./job-BHIwElNF.mjs";
3
+ import { $t as safeField, Bt as sessionPeriodSchema, Dn as createStaticProcessor, Fn as entityDictionaryVersion, Ft as termTypeInfo, Gt as ownerProfileSchema, Kt as passportAddressSchema, Ot as privacyTermKey, Qt as safeCode, Sn as currentDetectorVersion, T as bLiteralTerms, Un as frameRecords, Wt as mergeOwnerProfile, Xt as ApplicationError, Zt as UnfinishedJourneyError, an as observeSource, ar as admitCustomTerm, c as textDigest, ir as createEntityMatcher, nn as reviewFamilies, or as admitGitName, qt as profileHasChoices, tn as familySelected, ur as ProcessingError, zt as filterInventoryByPeriod } from "./locate-Cwv6WH8n.mjs";
4
+ import { a as mainTextChunks, c as previewInWorker, i as nearestSortingRepository, l as createNativeAdapters, n as createProjectEvidenceCollector, o as mainTextFields, r as sortingRepositoryId, s as textChunks, t as createHarnesses, u as createGitNameSeedProvider } from "./src-D_DfY--2.mjs";
5
+ import { $r as isClassifying, $t as searchScopeDigest, Ar as displayedScopeDigest, At as obsoleteJourney, B as createApplication, Br as staticReviewDigest, Bt as classificationPartition, Ct as sendActionSchema, Dt as sendStartSchema, Et as sendRequestSchema, F as readAccountAvatarUrl, Gr as agentSourcesSchema, H as inspectionStartSchema, Ht as applyClassification, I as storedCredentialSchema, Jn as workDecisionDigest, Jr as sendReceiptSchema, K as inspectionQuerySchema, Kr as journeySendSchema, Ln as bTermMatcher, M as deviceAuthorizationSchema, N as deviceProfileScopeSchema, Nr as assertJourneyPrivacyReady, O as createAccount, P as profileUrlMatchesIdentity, Pr as privacyDecisionDigest, Qr as hasCompleteWorkDescription, St as replaceSensitiveTermsSchema, Tt as sendPrepareSchema, U as validateInspectionBatch, Ut as retireScanGeneration, V as inspectionCancelSchema, Vn as confirmedHideKinds, Vt as classificationWorks, Wn as journeyConfirmedHides, Wr as agentSourceSchema, Wt as newJourneyWork, Xn as workCorrectionBinding, Xr as sendResolutionSchema, Yr as sendReplaceResultSchema, Zr as sendSettingsSchema, Zt as journeySearchResultSchema, _ as changeJourney, _t as manualUploadStartSchema, ai as withReadingProgress, at as appendSensitiveTerms, ci as selectionBatchSchema, cr as scanSessionResourceSchema, di as producerVersion, dr as verifiedScanGitSeeds, dt as validateFailureLocation, ei as isIncludedWork, fr as scanExclusionResourceSchema, ft as manualUploadActionSchema, gt as manualUploadSchema, h as hideScopeDigest, ht as manualUploadPrepareSchema, j as accountProfileSchema, jr as requestedScope, k as renew, kt as currentJourneyAdapters, li as selectionSchema, lr as scanGitSeedsId, lt as artifactFailureDetailSchema, m as activeJourneyTerms, mi as recommendParallelism, mt as manualUploadChoiceSchema, ni as isSelectedWork, nt as settingsQuerySchema, oi as commandSchema, qn as retireWorkDescription, qr as sendArtifactSchema, si as packageCommandSchema, sr as scanPreparationDigest, ti as isReadingComplete, tt as settingsBatchSchema, ur as scanGitSeedsResource, wt as sendChoiceSchema, xt as normalizeSensitiveTerms, yr as emptyPreparationFacts, yt as emptyTermsRevision, zr as hasCurrentStaticReview } from "./src-BqtdFVq7.mjs";
6
+ import { _t as isCoordinationBusy } from "./heap-CM298Yu0.mjs";
7
+ import { t as createAgentSetupPort } from "./agent-setup-LPd8WEsN.mjs";
8
8
  import { t as workerMessageSchema } from "./messages-D3m3bhva.mjs";
9
9
  import { constants, write } from "node:fs";
10
10
  import { z } from "zod";
@@ -3582,7 +3582,7 @@ function assertScope(journey, send) {
3582
3582
  if (journey.pendingHideId) throw new ApplicationError("SCOPE_NOT_READY");
3583
3583
  if (journey.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
3584
3584
  if (scopeDigest(journey) !== send.scopeDigest) throw new ApplicationError("SEND_SCOPE_INVALIDATED");
3585
- assertJourneyPrivacyReady(journey, true);
3585
+ assertJourneyPrivacyReady(journey, true, { publicChoices: send.status !== "completed" });
3586
3586
  for (const item of send.items) {
3587
3587
  const work = journey.works.find((value) => value.id === item.workId);
3588
3588
  if (!work || work.status !== "ready" || !hasCompleteWorkDescription(work) || !work.accessConfirmed || work.access !== item.access || work.artifact?.revision !== work.revision || work.artifact.sha256 !== item.artifact.sha256 || work.artifact.archiveBytes !== item.artifact.byteLength || work.artifact.bundleId !== item.artifact.bundleId) throw new ApplicationError("SCOPE_NOT_READY");
@@ -3867,31 +3867,32 @@ function createJourneyRenderer(options) {
3867
3867
  async function renderScope(journeyId, works, signal) {
3868
3868
  const workers = options.workers;
3869
3869
  const size = workers ? workers.parallelism ?? renderWorkerCount() : 0;
3870
- if (!workers || works < 1 || size < 1) {
3871
- const retry = renderRetry(signal, Math.max(1, works));
3872
- return {
3873
- render: (workId) => render(journeyId, workId, signal, { retry }),
3874
- close: async () => void 0
3875
- };
3876
- }
3877
- const retry = renderRetry(signal, Math.min(works, size + 1));
3878
- const store = retry.store(storage);
3870
+ if (!workers || works < 1 || size < 1) return {
3871
+ render: (workId) => render(journeyId, workId, signal, { retry: renderRetry(signal, Math.max(1, Math.min(16, works))) }),
3872
+ close: async () => void 0
3873
+ };
3879
3874
  const pool = createRenderWorkers(workers, size);
3880
- const session = batchedRenderSession(store, journeyId);
3875
+ const session = batchedRenderSession(storage, journeyId);
3881
3876
  let plan;
3882
- const readPlan = () => plan ??= readJourneyPlan(store, journeyId).catch((error) => {
3877
+ const readPlan = () => plan ??= readJourneyPlan(storage, journeyId).catch((error) => {
3883
3878
  plan = void 0;
3884
3879
  throw error;
3885
3880
  });
3886
3881
  return {
3887
3882
  concurrency: size + 1,
3888
- render: (workId) => render(journeyId, workId, signal, {
3889
- write: pool.write,
3890
- session,
3891
- plan: readPlan,
3892
- writeBase: pool.writeBase,
3893
- retry
3894
- }),
3883
+ render: (workId) => {
3884
+ const retry = renderRetry(signal, Math.min(16, works, size + 1));
3885
+ return render(journeyId, workId, signal, {
3886
+ write: pool.write,
3887
+ session: {
3888
+ current: () => retry.run(() => session.current()),
3889
+ commit: (change) => retry.run(() => session.commit(change))
3890
+ },
3891
+ plan: () => retry.run(readPlan),
3892
+ writeBase: pool.writeBase,
3893
+ retry
3894
+ });
3895
+ },
3895
3896
  close: pool.close
3896
3897
  };
3897
3898
  }
@@ -4691,7 +4692,7 @@ async function resolveChunkSessions(storage, state, works, signal) {
4691
4692
  * read and applied by the store against the current state, so scan writes in between do not
4692
4693
  * refuse it (BL-216).
4693
4694
  */
4694
- function createClassificationProposal(storage) {
4695
+ function createClassificationProposal(storage, accountAccess) {
4695
4696
  return async (input, signal) => {
4696
4697
  signal.throwIfAborted();
4697
4698
  const state = await storage.current(input.journeyId);
@@ -4785,9 +4786,14 @@ function createClassificationProposal(storage) {
4785
4786
  };
4786
4787
  const { works } = classificationWorks(state, prepared);
4787
4788
  const chunkSessions = await resolveChunkSessions(storage, state, works, signal);
4789
+ const access = late ? await accountAccess?.({
4790
+ ...state,
4791
+ works
4792
+ }, signal) : void 0;
4788
4793
  return storage.commitClassification({
4789
4794
  ...prepared,
4790
- chunkSessions
4795
+ chunkSessions,
4796
+ ...access ? { accountAccess: Object.fromEntries(access) } : {}
4791
4797
  });
4792
4798
  };
4793
4799
  }
@@ -5133,11 +5139,11 @@ function createJourneyClassification(storage, _processing) {
5133
5139
  //#endregion
5134
5140
  //#region apps/cli/src/journey/works/index.ts
5135
5141
  /** The journey local port's project catalog and project-type suggestions. */
5136
- function createJourneyWorks(storage, processing, stateDir) {
5142
+ function createJourneyWorks(storage, processing, stateDir, accountAccess) {
5137
5143
  return {
5138
5144
  classificationSessions: createClassificationSessions(storage, processing, stateDir),
5139
5145
  classificationRead: createClassificationReader(storage, stateDir),
5140
- classificationSubmit: createClassificationProposal(storage),
5146
+ classificationSubmit: createClassificationProposal(storage, accountAccess),
5141
5147
  catalog: createJourneyCatalog(storage, processing, stateDir),
5142
5148
  classify: createJourneyClassification(storage, processing)
5143
5149
  };
@@ -5231,6 +5237,46 @@ function protectedSourceText(field, hidden) {
5231
5237
  }
5232
5238
  //#endregion
5233
5239
  //#region apps/cli/src/journey/send-package.ts
5240
+ /** Explicit admission refusals must never become automatic packaging retries. */
5241
+ const packagingRefusalCodes = [
5242
+ "ARTIFACT_INVALID_APPROVAL",
5243
+ "ARTIFACT_IDENTITY_MISMATCH",
5244
+ "CONFIRMATION_REQUIRED",
5245
+ "INCOMPLETE_REVIEW",
5246
+ "PRIVACY_CHANGE_PENDING",
5247
+ "PRIVACY_UNCERTAIN",
5248
+ "REVISION_CONFLICT",
5249
+ "SCOPE_NOT_READY",
5250
+ "JOURNEY_OBSOLETE",
5251
+ "UNKNOWN_ITEM",
5252
+ "UNKNOWN_WORKSPACE",
5253
+ "OPERATION_CANCELED"
5254
+ ];
5255
+ /**
5256
+ * Deterministic failures a later attempt cannot clear: the refusals above, the source refusals
5257
+ * preparation isolates per root, size limits, a missing source and a full disk. Every other code,
5258
+ * including Windows sharing or antivirus locks, retries until the saved request expires.
5259
+ */
5260
+ const finalPackagingCodes = [
5261
+ ...packagingRefusalCodes,
5262
+ ...rootScanRefusals,
5263
+ "ARTIFACT_ARCHIVE_LIMIT",
5264
+ "ARTIFACT_ENTRY_LIMIT",
5265
+ "ARTIFACT_FILESYSTEM_LIMIT",
5266
+ "ARTIFACT_INDEX_LIMIT",
5267
+ "ARTIFACT_METADATA_LIMIT",
5268
+ "ARTIFACT_PART_LIMIT",
5269
+ "ARTIFACT_SOURCE_MISSING",
5270
+ "ARTIFACT_DISK_FULL",
5271
+ "ARTIFACT_INVALID_APPROVAL",
5272
+ "ARTIFACT_INVALID_ARCHIVE",
5273
+ "ARTIFACT_INVALID_MANIFEST",
5274
+ "ARTIFACT_OUTPUT_UNSAFE",
5275
+ "ARTIFACT_PROCESSING_MISMATCH",
5276
+ "ARTIFACT_QUOTA_EXCEEDED",
5277
+ "ARTIFACT_SOURCE_UNSAFE",
5278
+ "ARTIFACT_UNSAFE_REMOTE"
5279
+ ];
5234
5280
  const packaged = (work) => work.status === "ready" && work.artifact?.revision === work.revision;
5235
5281
  /** One host-lifetime packaging batch per advance; persisted artifacts are its progress. */
5236
5282
  function createSendPackager(ports) {
@@ -5249,13 +5295,16 @@ function createSendPackager(ports) {
5249
5295
  if (!current) throw new ApplicationError("UNKNOWN_ITEM");
5250
5296
  }
5251
5297
  const ready = (works, id) => works.some((work) => work.id === id && packaged(work));
5252
- const batch = workIds.filter((id) => !ready(current.works, id)).slice(0, 16);
5298
+ const batch = workIds.filter((id) => !ready(current.works, id));
5253
5299
  if (!batch.length) return true;
5254
5300
  signal.throwIfAborted();
5255
5301
  const result = await ports.render({
5256
5302
  journeyId,
5257
5303
  workIds: batch
5258
5304
  }, signal);
5305
+ const failures = result.renders?.filter((item) => item.status === "failed") ?? [];
5306
+ const failure = failures.find((item) => packagingRefusalCodes.includes(item.code)) ?? failures.find((item) => finalPackagingCodes.includes(item.code)) ?? failures[0];
5307
+ if (failure) throw new ApplicationError(failure.code);
5259
5308
  if (batch.some((id) => !ready(result.works, id))) throw new Error("PACKAGING_FAILED");
5260
5309
  return false;
5261
5310
  };
@@ -5628,6 +5677,19 @@ async function classifyExit(child, startup, exited, busy) {
5628
5677
  }
5629
5678
  //#endregion
5630
5679
  //#region apps/cli/src/send-host.ts
5680
+ /**
5681
+ * The detached focused host (ADR 0227) drives a Send only for the store's current journey, the
5682
+ * check its timer makes, and only while `liveHost` verifies its process identity and URL.
5683
+ * Anything the CLI cannot confirm returns false, so the CLI keeps stepping as before.
5684
+ */
5685
+ async function focusedHostAlive(stateDir, journeyId, currentJourneyId) {
5686
+ try {
5687
+ if (await currentJourneyId() !== journeyId) return false;
5688
+ return (await liveHost(join(stateDir, "serve"), journeyId)).status === "alive";
5689
+ } catch {
5690
+ return false;
5691
+ }
5692
+ }
5631
5693
  const recordSchema = z.object({
5632
5694
  hostId: z.uuid(),
5633
5695
  pid: z.number().int().positive()
@@ -6091,49 +6153,72 @@ var SessionContentConflict = class extends ApplicationError {
6091
6153
  this.contentConflict = contentConflict;
6092
6154
  }
6093
6155
  };
6094
- /** Check the entire pending Send before admitting any session mutation. */
6156
+ /** Collect safe conflicts across the entire pending Send before any session mutation. */
6095
6157
  async function sessionAccessBlocked(dependencies, journeyId, items, inventory, signal) {
6096
6158
  const sessions = dependencies.artifacts.sessionFacts ?? dependencies.artifacts.sessions;
6097
- if (!sessions) return false;
6159
+ if (!sessions) return [];
6160
+ const conflicts = /* @__PURE__ */ new Map();
6161
+ const accountAccesses = /* @__PURE__ */ new Map();
6162
+ function observe(item, access) {
6163
+ const values = accountAccesses.get(item.workId) ?? /* @__PURE__ */ new Set();
6164
+ values.add(access);
6165
+ accountAccesses.set(item.workId, values);
6166
+ }
6167
+ function record(item, sessionKey, rule, account) {
6168
+ if (account) observe(item, account.access);
6169
+ const conflict = {
6170
+ workId: item.workId,
6171
+ sessionKey,
6172
+ requestedAccess: item.access,
6173
+ accountWorkId: account?.logicalWorkId ?? null,
6174
+ accountAccess: account?.access ?? null,
6175
+ rule
6176
+ };
6177
+ conflicts.set(JSON.stringify(conflict), conflict);
6178
+ }
6098
6179
  const requestedAccess = /* @__PURE__ */ new Map();
6099
6180
  const requestedDigests = /* @__PURE__ */ new Map();
6100
6181
  const contentAccess = /* @__PURE__ */ new Map();
6101
- const claimContent = (key, sha256, access) => {
6102
- const identity = JSON.stringify([key, sha256]);
6103
- const previous = contentAccess.get(identity);
6104
- if (previous !== void 0 && previous !== access) return true;
6105
- contentAccess.set(identity, access);
6106
- return false;
6107
- };
6182
+ function claim(claims, identity, key, item, rule) {
6183
+ const previous = claims.get(identity) ?? [];
6184
+ for (const other of previous) {
6185
+ if (other.access === item.access) continue;
6186
+ record(item, key, rule);
6187
+ record(other, key, rule);
6188
+ }
6189
+ if (!previous.some((other) => other.workId === item.workId)) previous.push(item);
6190
+ claims.set(identity, previous);
6191
+ }
6192
+ const claimContent = (key, sha256, item) => claim(contentAccess, JSON.stringify([key, sha256]), key, item, "content-access");
6108
6193
  for (const item of items) {
6109
6194
  if (item.status === "blocked" || item.sessionAssembly) continue;
6110
6195
  const keys = item.sessionMembership?.keys ?? (item.sessionUpload ? [item.sessionUpload.key] : []);
6111
6196
  if (!keys.length) continue;
6112
6197
  const destination = inventory.works.find((work) => work.logicalWorkId === item.workId);
6113
6198
  if (destination?.access === "removed") throw new ApplicationError("WORK_REMOVED");
6199
+ if (destination) observe(item, destination.access);
6114
6200
  for (const key of keys) {
6115
- const access = requestedAccess.get(key);
6116
- if (access !== void 0 && access !== item.access) return true;
6117
- requestedAccess.set(key, item.access);
6118
- const source = inventory.works.find((work) => work.sessions.some((entry) => entry.key === key));
6119
- if (source && source.logicalWorkId !== item.workId && source.access !== item.access) return true;
6120
- const sha256 = item.sessionMembership ? source?.sessions.find((session) => session.key === key)?.sha256 : item.sessionUpload?.sha256;
6121
- if (sha256 && claimContent(key, sha256, item.access)) return true;
6122
- if (destination && destination.access !== item.access && !destination.sessions.some((entry) => entry.key === key && (item.sessionMembership || entry.sha256 === sha256))) return true;
6201
+ claim(requestedAccess, key, key, item, "requested-access");
6202
+ const sources = inventory.works.filter((work) => work.sessions.some((entry) => entry.key === key));
6203
+ for (const source of sources) observe(item, source.access);
6204
+ for (const source of sources) if (source.logicalWorkId !== item.workId && source.access !== item.access) record(item, key, "held-elsewhere", source);
6205
+ const sha256 = item.sessionMembership ? sources[0]?.sessions.find((session) => session.key === key)?.sha256 : item.sessionUpload?.sha256;
6206
+ if (sha256) claimContent(key, sha256, item);
6207
+ if (destination && destination.access !== item.access && !destination.sessions.some((entry) => entry.key === key && (item.sessionMembership || entry.sha256 === sha256))) record(item, key, "destination-access", destination);
6123
6208
  }
6124
6209
  }
6125
6210
  for (const item of items) {
6126
6211
  if (item.sessionAssembly || item.sessionMembership || ["completed", "blocked"].includes(item.status)) continue;
6127
6212
  const destination = inventory.works.find((work) => work.logicalWorkId === item.workId);
6128
6213
  if (destination?.access === "removed") throw new ApplicationError("WORK_REMOVED");
6214
+ if (destination) observe(item, destination.access);
6129
6215
  for await (const session of sessions(journeyId, item.artifact, signal)) {
6130
6216
  const key = await sessionKeyFor(session);
6131
6217
  const { workerIdentity: _, ...original } = session;
6132
6218
  const legacyKey = session.workerIdentity ? await sessionKeyFor(original) : key;
6133
- if (claimContent(key, session.sha256, item.access) || legacyKey !== key && claimContent(legacyKey, session.sha256, item.access)) return true;
6134
- const otherAccess = requestedAccess.get(key);
6135
- if (otherAccess !== void 0 && otherAccess !== item.access) return true;
6136
- requestedAccess.set(key, item.access);
6219
+ claimContent(key, session.sha256, item);
6220
+ if (legacyKey !== key) claimContent(legacyKey, session.sha256, item);
6221
+ claim(requestedAccess, key, key, item, "requested-access");
6137
6222
  const otherDigest = requestedDigests.get(key);
6138
6223
  if (otherDigest !== void 0 && otherDigest.sha256 !== session.sha256) throw new SessionContentConflict({
6139
6224
  workIds: [.../* @__PURE__ */ new Set([...otherDigest.workIds, item.workId])],
@@ -6143,11 +6228,18 @@ async function sessionAccessBlocked(dependencies, journeyId, items, inventory, s
6143
6228
  sha256: session.sha256,
6144
6229
  workIds: [.../* @__PURE__ */ new Set([...otherDigest?.workIds ?? [], item.workId])]
6145
6230
  });
6146
- if (inventory.works.some((work) => work.logicalWorkId !== item.workId && work.access !== item.access && work.sessions.some((entry) => entry.key === key || entry.key === legacyKey && entry.sha256 === session.sha256))) return true;
6147
- if (destination && destination.access !== item.access && !destination.sessions.some((entry) => entry.key === key && entry.sha256 === session.sha256)) return true;
6231
+ for (const work of inventory.works) for (const entry of work.sessions) {
6232
+ if (entry.key !== key && !(entry.key === legacyKey && entry.sha256 === session.sha256)) continue;
6233
+ observe(item, work.access);
6234
+ if (work.logicalWorkId !== item.workId && work.access !== item.access) record(item, entry.key, "held-elsewhere", work);
6235
+ }
6236
+ if (destination && destination.access !== item.access && !destination.sessions.some((entry) => entry.key === key && entry.sha256 === session.sha256)) record(item, key, "destination-access", destination);
6148
6237
  }
6149
6238
  }
6150
- return false;
6239
+ return [...conflicts.values()].map((conflict) => (accountAccesses.get(conflict.workId)?.size ?? 0) > 1 ? {
6240
+ ...conflict,
6241
+ accountAccess: "mixed"
6242
+ } : conflict);
6151
6243
  }
6152
6244
  /** Keep overlapping session ownership serial through membership, including retained members. */
6153
6245
  async function sessionIndependentItems(dependencies, journeyId, items, inventory, signal) {
@@ -6825,8 +6917,8 @@ async function put(context, item, receiptId, upload, connection, signal, guard)
6825
6917
  await guard();
6826
6918
  await context.upload(item.artifact, upload, signal);
6827
6919
  const sent = withoutBusy(item);
6828
- await guard();
6829
6920
  try {
6921
+ await guard();
6830
6922
  return observe(sent, await connection.complete(receiptId, signal), connection, signal, context.snapshot === true);
6831
6923
  } catch (error) {
6832
6924
  if (upload.headers !== void 0 && error instanceof JourneyRemoteError && error.code === "bundle_mismatch") return {
@@ -6956,13 +7048,14 @@ function isCode(error, code) {
6956
7048
  }
6957
7049
  /**
6958
7050
  * A step that ended without a definite answer and is safe to repeat: a request timeout or abort
6959
- * that is not the Send's own cancellation, or a connection failure from `fetch`. The Work keeps
7051
+ * that is not the Send's own cancellation, or a connection failure from `fetch`: `fetch failed`
7052
+ * before the response, `terminated` when the socket resets while the body is read. The Work keeps
6960
7053
  * its state, so the next advance repeats the same idempotent step (ADR 0197).
6961
7054
  */
6962
7055
  function isInterrupted(error) {
6963
7056
  if (!(error instanceof Error)) return false;
6964
7057
  if (error.name === "TimeoutError" || error.name === "AbortError") return true;
6965
- return error instanceof TypeError && error.message === "fetch failed";
7058
+ return error instanceof TypeError && (error.message === "fetch failed" || error.message === "terminated");
6966
7059
  }
6967
7060
  const sessionBackoffMs = (attempt) => 1e3 * 2 ** (attempt - 1);
6968
7061
  const zeroByteBackoffMs = (count) => [
@@ -7280,6 +7373,10 @@ function aggregate(send) {
7280
7373
  function awaitingPassport(send) {
7281
7374
  return send.status === "completed" && send.items.some((item) => !["completed", "blocked"].includes(item.status));
7282
7375
  }
7376
+ /** The host continues confirmed uploads and unfinished Passport assessments without a panel. */
7377
+ function hostDrives(send) {
7378
+ return awaitingPassport(send) || send.confirmedAt != null && ["sending", "waiting"].includes(send.status);
7379
+ }
7283
7380
  /** Explicit resume retries recoverable items and recaptures a pending sign-in or profile. */
7284
7381
  function resumeState(send) {
7285
7382
  if (send.status !== "completed" && send.items.length && send.items.every((item) => item.status === "completed") && send.items.some((item) => !item.sessionAssembly)) return {
@@ -7304,18 +7401,23 @@ function resumeState(send) {
7304
7401
  ...send,
7305
7402
  status: "sending",
7306
7403
  error: null,
7307
- items: send.items.map((item) => item.status === "blocked" && [
7308
- "OPERATION_FAILED",
7309
- "UPLOAD_FAILED",
7310
- "SEND_REMOTE_REFUSED",
7311
- "SESSION_MOVE_ACCESS_REQUIRED",
7312
- "IDENTITY_UNAVAILABLE",
7313
- "ACCOUNT_SIGNED_OUT"
7314
- ].includes(item.error ?? "") ? {
7315
- ...item,
7316
- status: item.receipt ? item.receipt.state === "awaiting_upload" ? "uploading" : "assessing" : "ready",
7317
- error: null
7318
- } : item)
7404
+ items: send.items.map((item) => {
7405
+ const { accessConflict: _previous, accessConflictSessionCount: _count, ...current } = item;
7406
+ return item.status === "blocked" && [
7407
+ "OPERATION_FAILED",
7408
+ "COORDINATION_BUSY",
7409
+ "UPLOAD_FAILED",
7410
+ "SEND_REMOTE_REFUSED",
7411
+ "SESSION_MOVE_ACCESS_REQUIRED",
7412
+ "SESSION_MOVE_ACCESS_HELD",
7413
+ "IDENTITY_UNAVAILABLE",
7414
+ "ACCOUNT_SIGNED_OUT"
7415
+ ].includes(item.error ?? "") ? {
7416
+ ...current,
7417
+ status: item.receipt ? item.receipt.state === "awaiting_upload" ? "uploading" : "assessing" : "ready",
7418
+ error: null
7419
+ } : item;
7420
+ })
7319
7421
  });
7320
7422
  return profileRequired && !["blocked", "completed"].includes(next.status) ? {
7321
7423
  ...next,
@@ -7356,7 +7458,7 @@ async function writeProfile(connection, chosen, guard, signal, failure, captured
7356
7458
  await guard();
7357
7459
  return captured;
7358
7460
  } catch (error) {
7359
- if (signal.aborted) throw error;
7461
+ if (signal.aborted || safeCode(error) === "COORDINATION_BUSY") throw error;
7360
7462
  if (error instanceof JourneyRemoteError && error.status === 409 && addressRefusals[error.code]) return {
7361
7463
  status: "blocked",
7362
7464
  error: addressRefusals[error.code] ?? null
@@ -7382,7 +7484,7 @@ async function captureProfile(connection, guard, signal, failure) {
7382
7484
  parallelCompletes: parallelCompletes ? true : void 0
7383
7485
  };
7384
7486
  } catch (error) {
7385
- if (signal.aborted) throw error;
7487
+ if (signal.aborted || safeCode(error) === "COORDINATION_BUSY") throw error;
7386
7488
  if (error instanceof JourneyRemoteError && error.status === 401 || error instanceof ApplicationError && error.code === "ACCOUNT_SIGNED_OUT") return {
7387
7489
  status: "sign-in",
7388
7490
  error: null
@@ -7503,7 +7605,14 @@ async function advanceItems(host, signal) {
7503
7605
  });
7504
7606
  host.failure("SEND_SCOPE_BUSY", cause);
7505
7607
  error = "SEND_SCOPE_BUSY";
7506
- } else if (isInterrupted(cause) || isTransientRefusal(cause)) {
7608
+ } else if (isInterrupted(cause) || isTransientRefusal(cause) || safeCode(cause) === "COORDINATION_BUSY") {
7609
+ if (safeCode(cause) === "COORDINATION_BUSY") {
7610
+ const current = progressed ?? item;
7611
+ next.set(item.workId, {
7612
+ ...current,
7613
+ busyRefusals: (current.busyRefusals ?? 0) + 1
7614
+ });
7615
+ }
7507
7616
  host.failure("SEND_INTERRUPTED", cause);
7508
7617
  error = "SEND_INTERRUPTED";
7509
7618
  } else {
@@ -7537,7 +7646,7 @@ async function advanceItems(host, signal) {
7537
7646
  items
7538
7647
  }));
7539
7648
  } catch (error) {
7540
- if (!signal.aborted && (isInterrupted(error) || error instanceof Error && isInterrupted(error.cause) || isCode(error, "IDENTITY_UNAVAILABLE"))) {
7649
+ if (!signal.aborted && (isInterrupted(error) || error instanceof Error && isInterrupted(error.cause) || isCode(error, "IDENTITY_UNAVAILABLE") || safeCode(error) === "COORDINATION_BUSY")) {
7541
7650
  host.failure("SEND_INTERRUPTED", error);
7542
7651
  return send.error === "SEND_INTERRUPTED" ? host.applied() : host.save({
7543
7652
  ...send,
@@ -7644,7 +7753,10 @@ async function advanceSend(dependencies, address, signal, chain) {
7644
7753
  })
7645
7754
  });
7646
7755
  return blockSend({
7647
- send: snapshot.send,
7756
+ send: safeCode(error) === "SEND_SCOPE_INVALIDATED" ? {
7757
+ ...snapshot.send,
7758
+ items: snapshot.send.items.filter((item) => !["SESSION_MOVE_ACCESS_REQUIRED", "SESSION_MOVE_ACCESS_HELD"].includes(item.error ?? ""))
7759
+ } : snapshot.send,
7648
7760
  failure: dependencies.failure,
7649
7761
  save
7650
7762
  }, error, signal);
@@ -7670,18 +7782,41 @@ async function advanceSend(dependencies, address, signal, chain) {
7670
7782
  if (!connection.sessions) throw new ApplicationError("INVALID_STATE");
7671
7783
  if (!send.items.some((item) => item.status === "uploading" && !item.sessionAssembly && !item.receipt && !item.reservation)) return null;
7672
7784
  const inventory = await connection.sessions.inventory(preflightSignal);
7673
- if (!await sessionAccessBlocked(preparation.dependencies, snapshot.journey.journeyId, send.items, inventory, preflightSignal)) {
7785
+ const conflicts = await sessionAccessBlocked(preparation.dependencies, snapshot.journey.journeyId, send.items, inventory, preflightSignal);
7786
+ if (conflicts.length === 0) {
7674
7787
  independentItems = await sessionIndependentItems(preparation.dependencies, snapshot.journey.journeyId, send.items, inventory, preflightSignal);
7675
7788
  return null;
7676
7789
  }
7677
7790
  await connection.assertCurrent(preflightSignal);
7791
+ dependencies.failure("SESSION_MOVE_ACCESS_REQUIRED", {
7792
+ code: "SESSION_MOVE_ACCESS_REQUIRED",
7793
+ message: JSON.stringify({
7794
+ conflicts: conflicts.length,
7795
+ works: new Set(conflicts.map((conflict) => conflict.workId)).size,
7796
+ rules: [...new Set(conflicts.map((conflict) => conflict.rule))].sort()
7797
+ })
7798
+ });
7678
7799
  return save({
7679
7800
  ...send,
7680
7801
  status: "blocked",
7681
- items: send.items.map((item) => item.sessionAssembly || item.status === "completed" ? item : {
7682
- ...item,
7683
- status: "blocked",
7684
- error: "SESSION_MOVE_ACCESS_REQUIRED"
7802
+ items: send.items.map((item) => {
7803
+ if (item.sessionAssembly || item.status === "completed") return item;
7804
+ const { accessConflict: _previous, accessConflictSessionCount: _count, ...current } = item;
7805
+ const itemConflicts = conflicts.filter((entry) => entry.workId === item.workId);
7806
+ const conflict = itemConflicts[0];
7807
+ if (!conflict) return {
7808
+ ...current,
7809
+ status: "blocked",
7810
+ error: "SESSION_MOVE_ACCESS_HELD"
7811
+ };
7812
+ const { workId: _, ...accessConflict } = conflict;
7813
+ return {
7814
+ ...current,
7815
+ status: "blocked",
7816
+ error: "SESSION_MOVE_ACCESS_REQUIRED",
7817
+ accessConflict,
7818
+ accessConflictSessionCount: new Set(itemConflicts.map((entry) => entry.sessionKey)).size
7819
+ };
7685
7820
  })
7686
7821
  });
7687
7822
  },
@@ -7702,7 +7837,7 @@ async function advanceSend(dependencies, address, signal, chain) {
7702
7837
  });
7703
7838
  saving = pending.catch(() => void 0);
7704
7839
  return pending;
7705
- }).then((result) => {
7840
+ }).then(({ busyRefusals: _busy, ...result }) => {
7706
7841
  fulfilled.add(item.workId);
7707
7842
  return result;
7708
7843
  })
@@ -8023,20 +8158,15 @@ function createManualUploader(dependencies) {
8023
8158
  upload,
8024
8159
  status: "applied"
8025
8160
  };
8026
- const host = {
8161
+ let bindings = [];
8162
+ return advanceItems({
8027
8163
  send: upload,
8028
8164
  failure: dependencies.failure,
8029
- save: (next) => save(upload, next)
8030
- };
8031
- let bindings;
8032
- try {
8033
- bindings = await storage.bindings();
8034
- } catch (error) {
8035
- return blockSend(host, error, signal);
8036
- }
8037
- return advanceItems({
8038
- ...host,
8039
- connect: dependencies.auth.connect,
8165
+ save: (next) => save(upload, next),
8166
+ connect: async (connectSignal) => {
8167
+ bindings = await storage.bindings();
8168
+ return dependencies.auth.connect(connectSignal);
8169
+ },
8040
8170
  profile: (connection, profileSignal) => captureProfile(connection, guard(upload), profileSignal, dependencies.failure),
8041
8171
  context: (item) => ({
8042
8172
  active: guard(upload, item),
@@ -9330,7 +9460,7 @@ async function requestSend(dependencies, raw, hostId, source) {
9330
9460
  ...current,
9331
9461
  status: "stale"
9332
9462
  };
9333
- assertJourneyPrivacyReady(current.journey);
9463
+ assertJourneyPrivacyReady(current.journey, false, { publicChoices: true });
9334
9464
  if (current.journey.pendingHideId || current.journey.phase === "paused") throw new ApplicationError("SCOPE_NOT_READY");
9335
9465
  if (current.send && ([
9336
9466
  "awaiting-packaging",
@@ -9452,6 +9582,7 @@ function createJourneySender(dependencies, phases = 3, host = {
9452
9582
  /** The serving host owns a pending request; another process leaves it unchanged. */
9453
9583
  const pendingElsewhere = (send) => !host.serving && send?.request !== void 0 && !send.confirmedAt;
9454
9584
  const progress = /* @__PURE__ */ new Map();
9585
+ let packagingRetry;
9455
9586
  const sessionFacts = createSessionFactsCache();
9456
9587
  const running = /* @__PURE__ */ new Map();
9457
9588
  const runKey = (address) => `${address.journeyId}:${address.sendId}`;
@@ -9551,27 +9682,49 @@ function createJourneySender(dependencies, phases = 3, host = {
9551
9682
  const pendingError = pending ? await requestError(dependencies, pending, hostId, signal) : null;
9552
9683
  if (pending && (pendingError || !requestMatches(pending, snapshot.journey))) return block(snapshot, pending, pendingError ?? "REVISION_CONFLICT");
9553
9684
  const draft = await draftSend(snapshot.journey, dependencies, signal);
9685
+ let observed = false;
9554
9686
  const recover = snapshot.send && [
9555
9687
  "blocked",
9556
9688
  "paused",
9557
9689
  "completed"
9558
9690
  ].includes(snapshot.send.status) && snapshot.send.items.some((item) => sendRecoveryCodes.includes(item.error ?? ""));
9559
- if (recover && snapshot.send) {
9691
+ if (recover || dependencies.artifacts.sessions) {
9560
9692
  const connection = await dependencies.auth.connect(signal);
9561
- if (!connection) throw new ApplicationError("ACCOUNT_SIGNED_OUT");
9562
- if (snapshot.send.subject !== null && snapshot.send.subject !== connection.subject) throw new ApplicationError("IDENTITY_REJECTED");
9693
+ if (connection && draft.subject !== connection.subject) return {
9694
+ ...snapshot,
9695
+ status: "stale"
9696
+ };
9697
+ if (recover && !connection) throw new ApplicationError("ACCOUNT_SIGNED_OUT");
9698
+ if (recover && snapshot.send?.subject && snapshot.send.subject !== connection?.subject) throw new ApplicationError("IDENTITY_REJECTED");
9563
9699
  const sessions = dependencies.artifacts.sessions;
9564
- const remote = connection.sessions;
9565
- if (sessions && remote) {
9566
- const inventory = await remote.inventory(signal);
9567
- for (const item of draft.items) {
9568
- const current = inventory.works.find((work) => work.logicalWorkId === item.workId);
9700
+ const remote = connection?.sessions;
9701
+ if (sessions && remote && connection) {
9702
+ const inventory = await remote.inventory(signal).catch((error) => {
9703
+ if (signal.aborted || recover) throw error;
9704
+ dependencies.failure("SEND_INTERRUPTED", error);
9705
+ return null;
9706
+ });
9707
+ observed = inventory !== null;
9708
+ try {
9709
+ const conflicts = inventory ? await sessionAccessBlocked(dependencies, input.journeyId, draft.items, inventory, signal) : [];
9710
+ for (const item of draft.items) {
9711
+ const conflict = conflicts.find((entry) => entry.workId === item.workId && entry.accountAccess !== null) ?? conflicts.find((entry) => entry.workId === item.workId);
9712
+ if (conflict) {
9713
+ const { workId: _, ...accessConflict } = conflict;
9714
+ item.accessConflict = accessConflict;
9715
+ }
9716
+ }
9717
+ } catch (error) {
9718
+ if (!(error instanceof SessionContentConflict) && !(error instanceof ApplicationError && error.code === "WORK_REMOVED")) throw error;
9719
+ }
9720
+ for (const item of inventory && recover && snapshot.send ? draft.items : []) {
9721
+ const current = inventory?.works.find((work) => work.logicalWorkId === item.workId);
9569
9722
  const assembly = await remote.assembly(item.workId, signal);
9570
9723
  const selected = createSessionKeys();
9571
9724
  for await (const session of sessions(input.journeyId, item.artifact, signal)) await selected.add(session);
9572
9725
  const currentKeys = current?.sessions.map((session) => session.key) ?? [];
9573
9726
  item.recoveryReview = {
9574
- previousSendId: snapshot.send.id,
9727
+ previousSendId: snapshot.send?.id ?? draft.id,
9575
9728
  revision: assembly?.revision ?? 0,
9576
9729
  currentAccess: current?.access ?? null,
9577
9730
  currentKeys,
@@ -9581,6 +9734,10 @@ function createJourneySender(dependencies, phases = 3, host = {
9581
9734
  await connection.assertCurrent(signal);
9582
9735
  }
9583
9736
  }
9737
+ for (const item of observed ? [] : draft.items) {
9738
+ const previous = snapshot.send?.items.find((entry) => entry.workId === item.workId && entry.access === item.access)?.accessConflict;
9739
+ if (previous && !item.accessConflict) item.accessConflict = previous;
9740
+ }
9584
9741
  const canReuseExistingSend = !(snapshot.send?.confirmedAt && ["blocked", "paused"].includes(snapshot.send.status)) && !recover && snapshot.send && scopeDigest(snapshot.journey) === snapshot.send.scopeDigest && snapshot.send.subject === draft.subject;
9585
9742
  const next = pending ? {
9586
9743
  ...draft,
@@ -9589,6 +9746,7 @@ function createJourneySender(dependencies, phases = 3, host = {
9589
9746
  request: pending.request
9590
9747
  } : canReuseExistingSend && snapshot.send ? {
9591
9748
  ...snapshot.send,
9749
+ ...snapshot.send.confirmedAt === null ? { items: draft.items } : {},
9592
9750
  revision: snapshot.send.revision + 1
9593
9751
  } : draft;
9594
9752
  const result = await dependencies.storage.commitSend({
@@ -9619,7 +9777,7 @@ function createJourneySender(dependencies, phases = 3, host = {
9619
9777
  const authorizationError = await requestError(dependencies, snapshot.send, hostId, signal);
9620
9778
  if (snapshot.send.request && !snapshot.send.confirmedAt && (authorizationError || !requestMatches(snapshot.send, snapshot.journey))) return block(snapshot, snapshot.send, authorizationError ?? "REVISION_CONFLICT");
9621
9779
  assertScope(snapshot.journey, snapshot.send);
9622
- if (!sameIds$1(input.confirmedWorkIds, snapshot.send.items.map((item) => item.workId)) || !sameIds$1(input.confirmedOmittedWorkIds, snapshot.send.omitted.map((item) => item.workId)) || input.scopeDigest !== snapshot.send.scopeDigest || snapshot.send.items.length === 0) throw new ApplicationError("CONFIRMATION_REQUIRED");
9780
+ if (!sameIds$1(input.confirmedWorkIds, snapshot.send.items.map((item) => item.workId)) || !sameIds$1(input.confirmedOmittedWorkIds, snapshot.send.omitted.map((item) => item.workId)) || input.scopeDigest !== snapshot.send.scopeDigest || snapshot.send.items.length === 0 || snapshot.send.items.some((item) => item.accessConflict !== void 0)) throw new ApplicationError("CONFIRMATION_REQUIRED");
9623
9781
  if (snapshot.send.confirmedAt !== null) return saveStart(snapshot.send);
9624
9782
  const connection = await dependencies.auth.connect(signal);
9625
9783
  if (snapshot.send.request && (connection?.subject ?? null) !== snapshot.send.request.subject) return saveSend(dependencies, snapshot, {
@@ -9728,6 +9886,10 @@ function createJourneySender(dependencies, phases = 3, host = {
9728
9886
  ...snapshot,
9729
9887
  status: "applied"
9730
9888
  };
9889
+ if (!host.serving && hostDrives(snapshot.send) && await host.focused?.(address.journeyId)) return {
9890
+ ...snapshot,
9891
+ status: "applied"
9892
+ };
9731
9893
  const authorizationError = await requestError(dependencies, snapshot.send, hostId, lifetime);
9732
9894
  if (authorizationError) return block(snapshot, snapshot.send, authorizationError);
9733
9895
  if (snapshot.send.request && !snapshot.send.confirmedAt && ["awaiting-packaging", "prepared"].includes(snapshot.send.status)) {
@@ -9742,10 +9904,52 @@ function createJourneySender(dependencies, phases = 3, host = {
9742
9904
  error: "REVISION_CONFLICT"
9743
9905
  });
9744
9906
  if (snapshot.send.status === "awaiting-packaging") {
9745
- if (await dependencies.package?.(address.journeyId, snapshot.send.request.included.map((item) => item.workId), lifetime, snapshot.send.request.source) === false) return {
9746
- ...await load(address),
9907
+ if (packagingRetry?.key !== key) packagingRetry = void 0;
9908
+ if (packagingRetry && Date.parse(dependencies.now()) < packagingRetry.after) return {
9909
+ ...snapshot,
9747
9910
  status: "applied"
9748
9911
  };
9912
+ let packaged;
9913
+ try {
9914
+ packaged = await dependencies.package?.(address.journeyId, snapshot.send.request.included.map((item) => item.workId), lifetime, snapshot.send.request.source);
9915
+ } catch (cause) {
9916
+ lifetime.throwIfAborted();
9917
+ const code = cause instanceof ApplicationError ? cause.code : safeCode(cause);
9918
+ if (packagingRefusalCodes.includes(code)) throw cause;
9919
+ dependencies.failure(code, cause);
9920
+ snapshot = await load(address);
9921
+ if (snapshot.send.status !== "awaiting-packaging") return {
9922
+ ...snapshot,
9923
+ status: "stale"
9924
+ };
9925
+ const error = await requestError(dependencies, snapshot.send, hostId, lifetime);
9926
+ if (error || !requestMatches(snapshot.send, snapshot.journey)) return block(snapshot, snapshot.send, error ?? "REVISION_CONFLICT");
9927
+ if (finalPackagingCodes.includes(code)) {
9928
+ packagingRetry = void 0;
9929
+ return block(snapshot, snapshot.send, code);
9930
+ }
9931
+ const failures = Math.min((packagingRetry?.failures ?? 0) + 1, 5);
9932
+ packagingRetry = {
9933
+ key,
9934
+ failures,
9935
+ after: Date.parse(dependencies.now()) + Math.min(2e3 * 2 ** (failures - 1), 3e4)
9936
+ };
9937
+ return saveSend(dependencies, snapshot, {
9938
+ ...snapshot.send,
9939
+ error: code
9940
+ });
9941
+ }
9942
+ packagingRetry = void 0;
9943
+ if (packaged === false) {
9944
+ snapshot = await load(address);
9945
+ return snapshot.send.error ? saveSend(dependencies, snapshot, {
9946
+ ...snapshot.send,
9947
+ error: null
9948
+ }) : {
9949
+ ...snapshot,
9950
+ status: "applied"
9951
+ };
9952
+ }
9749
9953
  snapshot = await load(address);
9750
9954
  if (snapshot.send.status !== "awaiting-packaging") return {
9751
9955
  ...snapshot,
@@ -9759,6 +9963,10 @@ function createJourneySender(dependencies, phases = 3, host = {
9759
9963
  if (prepared.status === "stale" || prepared.send?.status !== "prepared") return prepared;
9760
9964
  snapshot = requireSend(prepared);
9761
9965
  }
9966
+ if (snapshot.send.items.some((item) => item.accessConflict)) return saveSend(dependencies, snapshot, {
9967
+ ...snapshot.send,
9968
+ request: void 0
9969
+ });
9762
9970
  if (!requestMatches(snapshot.send, snapshot.journey) || !requestedItemsMatch(snapshot.send, snapshot.journey)) return saveSend(dependencies, snapshot, {
9763
9971
  ...snapshot.send,
9764
9972
  status: "blocked",
@@ -9796,10 +10004,6 @@ function createJourneySender(dependencies, phases = 3, host = {
9796
10004
  }
9797
10005
  //#endregion
9798
10006
  //#region apps/cli/src/account/journey/auth.ts
9799
- /** An access token this close to expiry is renewed first, so a step never starts on it. */
9800
- const renewBeforeMs = 12e4;
9801
- const renewTimeoutMs = 3e4;
9802
- const renewals = /* @__PURE__ */ new WeakMap();
9803
10007
  /** Reuses the existing device sign-in credential; a token is never returned to the panel or Agent. */
9804
10008
  function createJourneySendAuth(account, requestFetch) {
9805
10009
  /**
@@ -9826,39 +10030,11 @@ function createJourneySendAuth(account, requestFetch) {
9826
10030
  return value;
9827
10031
  })).credential?.refreshToken) await expire(accessToken);
9828
10032
  }
9829
- function renew(credential) {
9830
- const running = renewals.get(account);
9831
- if (running) return running;
9832
- const renewing = (async () => {
9833
- try {
9834
- const token = await account.identity.refresh(credential.refreshToken, AbortSignal.timeout(renewTimeoutMs));
9835
- if (!token) {
9836
- await expire(credential.accessToken);
9837
- return null;
9838
- }
9839
- return (await account.credentials.update((value) => {
9840
- if (value.credential?.refreshToken === credential.refreshToken) value.credential = {
9841
- ...value.credential,
9842
- accessToken: token.accessToken,
9843
- refreshToken: token.refreshToken ?? credential.refreshToken,
9844
- scope: token.scope,
9845
- issuedAt: account.now(),
9846
- expiresAt: token.expiresAt
9847
- };
9848
- return value;
9849
- })).credential;
9850
- } finally {
9851
- renewals.delete(account);
9852
- }
9853
- })();
9854
- renewals.set(account, renewing);
9855
- return renewing;
9856
- }
9857
10033
  async function current() {
9858
10034
  const credential = (await account.credentials.update((value) => value)).credential;
9859
10035
  if (!credential || credential.identityUrl !== account.configuration.identityUrl || credential.clientId !== account.configuration.clientId) return null;
9860
10036
  const left = Date.parse(credential.expiresAt) - Date.parse(account.now());
9861
- if (Number.isFinite(left) && left > renewBeforeMs) return credential;
10037
+ if (Number.isFinite(left) && left > 12e4) return credential;
9862
10038
  const { refreshToken } = credential;
9863
10039
  if (!refreshToken) {
9864
10040
  if (Number.isFinite(left) && left > 0) return credential;
@@ -9866,7 +10042,7 @@ function createJourneySendAuth(account, requestFetch) {
9866
10042
  return null;
9867
10043
  }
9868
10044
  try {
9869
- return await renew({
10045
+ return await renew(account, {
9870
10046
  ...credential,
9871
10047
  refreshToken
9872
10048
  });
@@ -9916,6 +10092,100 @@ function createJourneySendAuth(account, requestFetch) {
9916
10092
  };
9917
10093
  }
9918
10094
  //#endregion
10095
+ //#region apps/cli/src/account/journey/account-access.ts
10096
+ /** Current and legacy upload keys of every part of one local session. */
10097
+ async function keysOf(session) {
10098
+ const keys = [];
10099
+ for (const part of session.parts.length ? session.parts : [void 0]) {
10100
+ const native = part?.nativeEvidence?.identity;
10101
+ const identity = {
10102
+ harness: session.harness,
10103
+ nativeSessionId: native?.sessionId ?? session.sourceSessionId,
10104
+ ...part?.harnessRelativePath ? { relativePath: part.harnessRelativePath } : {},
10105
+ ...session.harness === "claude" && native?.role === "worker" ? { workerIdentity: { agentId: native.agentId } } : {}
10106
+ };
10107
+ const { workerIdentity: _, ...original } = identity;
10108
+ keys.push([await sessionKeyFor(identity), await sessionKeyFor(original)]);
10109
+ }
10110
+ return keys;
10111
+ }
10112
+ /**
10113
+ * Compare frozen identities with the same hashed keys used by session uploads. A Work sends each
10114
+ * root's whole family (`members`, from `reviewFamilies`), so uniform Public is `public-all` only
10115
+ * when every part of every member is held; any key without history keeps it `public` (ADR 0275).
10116
+ */
10117
+ async function accountAccessForRoots(state, roots, inventory, members) {
10118
+ const sessions = new Map(roots.map((session) => [session.id, session]));
10119
+ const held = /* @__PURE__ */ new Map();
10120
+ for (const work of inventory.works) for (const session of work.sessions) {
10121
+ const access = held.get(session.key) ?? /* @__PURE__ */ new Set();
10122
+ access.add(work.access);
10123
+ held.set(session.key, access);
10124
+ }
10125
+ const result = /* @__PURE__ */ new Map();
10126
+ for (const work of state.works) {
10127
+ const access = /* @__PURE__ */ new Set();
10128
+ let known = 0;
10129
+ let unknown = 0;
10130
+ for (const id of work.rootSessionIds) {
10131
+ const root = sessions.get(id);
10132
+ if (!root) throw new ApplicationError("CORRUPTED_STATE");
10133
+ for (const session of members.get(id) ?? [root]) for (const [key, legacyKey] of await keysOf(session)) {
10134
+ const values = /* @__PURE__ */ new Set([...held.get(key) ?? [], ...held.get(legacyKey) ?? []]);
10135
+ if (!values.size) {
10136
+ unknown++;
10137
+ continue;
10138
+ }
10139
+ known++;
10140
+ for (const value of values) access.add(value);
10141
+ }
10142
+ }
10143
+ if (!known) continue;
10144
+ const uniform = access.size === 1;
10145
+ result.set(work.id, uniform && access.has("private") ? "private" : uniform && access.has("public") ? unknown === 0 ? "public-all" : "public" : "mixed");
10146
+ }
10147
+ return result;
10148
+ }
10149
+ /** Existing authenticated owner ports supply inventory; signed-out scope confirmation stays local. */
10150
+ function createAccountAccess(storage, auth) {
10151
+ return async (state, signal) => {
10152
+ const controller = new AbortController();
10153
+ const boundedSignal = AbortSignal.any([signal, controller.signal]);
10154
+ let timer;
10155
+ let onAbort = () => {};
10156
+ const stopped = new Promise((_resolve, reject) => {
10157
+ onAbort = () => reject(/* @__PURE__ */ new Error("ACCOUNT_ACCESS_CANCELLED"));
10158
+ boundedSignal.addEventListener("abort", onAbort, { once: true });
10159
+ if (boundedSignal.aborted) onAbort();
10160
+ timer = setTimeout(() => controller.abort(), 5e3);
10161
+ });
10162
+ const read = async () => {
10163
+ boundedSignal.throwIfAborted();
10164
+ const connection = await auth.connect(boundedSignal);
10165
+ if (!connection?.sessions) return /* @__PURE__ */ new Map();
10166
+ const plan = (await storage.resources({
10167
+ journeyId: state.journeyId,
10168
+ kind: "plan"
10169
+ }))[0];
10170
+ if (plan?.kind !== "plan") throw new ApplicationError("SCOPE_NOT_READY");
10171
+ const inventory = await connection.sessions.inventory(boundedSignal);
10172
+ const access = await accountAccessForRoots(state, plan.approval.inventory.sessions, inventory, reviewFamilies(plan.approval.inventory).members);
10173
+ await connection.assertCurrent(boundedSignal);
10174
+ return access;
10175
+ };
10176
+ try {
10177
+ return await Promise.race([read(), stopped]);
10178
+ } catch {
10179
+ console.error("ACCOUNT_ACCESS_UNAVAILABLE: using model access");
10180
+ return /* @__PURE__ */ new Map();
10181
+ } finally {
10182
+ clearTimeout(timer);
10183
+ boundedSignal.removeEventListener("abort", onAbort);
10184
+ controller.abort();
10185
+ }
10186
+ };
10187
+ }
10188
+ //#endregion
9919
10189
  //#region apps/cli/src/operation/protocol.ts
9920
10190
  const sourceExclusionReasonSchema = z.enum([
9921
10191
  "guardian-excluded",
@@ -10511,6 +10781,9 @@ async function readJson(response, where) {
10511
10781
  }
10512
10782
  chunks.push(next.value);
10513
10783
  }
10784
+ } catch (error) {
10785
+ if (error instanceof ApplicationError) throw error;
10786
+ throw unavailable(`${where} body read failed`, error);
10514
10787
  } finally {
10515
10788
  reader.releaseLock();
10516
10789
  }
@@ -11545,6 +11818,8 @@ async function compose(options, callbacks, bundles) {
11545
11818
  onFatal: fatalStore.fail
11546
11819
  });
11547
11820
  const account = createAccount(accountPorts);
11821
+ const sendAuth = createJourneySendAuth(accountPorts);
11822
+ const accountAccess = createAccountAccess(journeyStorage, sendAuth);
11548
11823
  const application = createApplication({
11549
11824
  sessionPeriod: options.sessionPeriod,
11550
11825
  journeySender: createJourneySender({
@@ -11561,7 +11836,7 @@ async function compose(options, callbacks, bundles) {
11561
11836
  }
11562
11837
  }),
11563
11838
  artifacts: createJourneySendArtifacts(journeyStorage),
11564
- auth: createJourneySendAuth(accountPorts),
11839
+ auth: sendAuth,
11565
11840
  now: () => (/* @__PURE__ */ new Date()).toISOString(),
11566
11841
  failure: (code, cause) => diagnostics.onFailure(code, cause),
11567
11842
  sessionTransfer: (workId, transfer) => diagnostics.onActivity({
@@ -11584,18 +11859,22 @@ async function compose(options, callbacks, bundles) {
11584
11859
  phases: { upload: elapsedMs },
11585
11860
  volumes: { bytes }
11586
11861
  })
11587
- }, void 0, sendHost),
11862
+ }, void 0, sendHost.serving ? sendHost : {
11863
+ ...sendHost,
11864
+ focused: (journeyId) => focusedHostAlive(stateDir, journeyId, async () => (await journeyStorage.current())?.journeyId)
11865
+ }),
11588
11866
  manualUploader: createManualUploader({
11589
11867
  storage: store.manualUpload,
11590
11868
  artifacts: createManualUploadArtifacts({ getDeliverySources: () => application.getDeliverySources() }),
11591
- auth: createJourneySendAuth(accountPorts),
11869
+ auth: sendAuth,
11592
11870
  now: () => (/* @__PURE__ */ new Date()).toISOString(),
11593
11871
  failure: (code, cause) => diagnostics.onFailure(code, cause)
11594
11872
  }),
11595
11873
  journeyLocal: {
11874
+ accountAccess,
11596
11875
  titles: createJourneyTitles(journeyStorage, processing),
11597
11876
  describe: createJourneyDescription(journeyStorage, processing),
11598
- works: createJourneyWorks(journeyStorage, processing, stateDir),
11877
+ works: createJourneyWorks(journeyStorage, processing, stateDir, accountAccess),
11599
11878
  hide: createJourneyHide({
11600
11879
  storage: journeyStorage,
11601
11880
  execution,
@@ -12064,4 +12343,4 @@ async function cancelOperation(runtime, id) {
12064
12343
  return readOperation(runtime, id);
12065
12344
  }
12066
12345
  //#endregion
12067
- export { permissionDiagnostic as _, readAgentPresence as a, executorRequestSchema as c, guardDetachedStartup as d, createRenderActivity as f, longCommands as g, businessCommands as h, invoke as i, awaitingPassport as l, parseOptions as m, readOperation as n, createRuntime as o, helpText as p, waitOperation as r, executorEventSchema as s, cancelOperation as t, serveDetached as u, startupFailure as v, traceStartup as y };
12346
+ export { longCommands as _, readAgentPresence as a, traceStartup as b, executorRequestSchema as c, serveDetached as d, guardDetachedStartup as f, businessCommands as g, parseOptions as h, invoke as i, awaitingPassport as l, helpText as m, readOperation as n, createRuntime as o, createRenderActivity as p, waitOperation as r, executorEventSchema as s, cancelOperation as t, hostDrives as u, permissionDiagnostic as v, startupFailure as y };