@gitroll/tacitt-cli 0.6.11 → 0.6.13

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 (57) hide show
  1. package/README.md +4 -0
  2. package/bin/INDEX.md +6 -4
  3. package/bin/tacitt.cjs +1 -0
  4. package/dist/{agent-setup-DdfF3zch.mjs → agent-setup-Doy_AnPY.mjs} +2 -2
  5. package/dist/agent-setup-worker.mjs +5 -5
  6. package/dist/cli.mjs +32 -32
  7. package/dist/codex-title-worker.mjs +2 -2
  8. package/dist/configured-preview-worker.mjs +5 -5
  9. package/dist/configured-session-worker.mjs +23 -25
  10. package/dist/{frame-Bq2zRh-i.mjs → frame-BPbypzmt.mjs} +1 -1
  11. package/dist/{heap-CV8Eca8V.mjs → heap-DCS_WXIA.mjs} +264 -23
  12. package/dist/inspection-worker.mjs +4 -4
  13. package/dist/{job-BKsifhyv.mjs → job--zxwgaPy.mjs} +155 -41
  14. package/dist/{locate-Cx52Zqxv.mjs → locate-PLEyLYW-.mjs} +292 -236
  15. package/dist/{model-C1UXkHh9.mjs → model-CyXvsWLz.mjs} +1 -1
  16. package/dist/operation-executor.mjs +3 -3
  17. package/dist/preview-worker.mjs +1 -1
  18. package/dist/privacy-validator.mjs +28 -8
  19. package/dist/{query-AMZerUh8.mjs → query-BC7MIaWc.mjs} +1914 -759
  20. package/dist/{run-CaPktPhH.mjs → run-Dod7Vykp.mjs} +6 -6
  21. package/dist/session-process.mjs +20 -0
  22. package/dist/{src-DlVbGB32.mjs → src-CP-K2PBv.mjs} +2100 -1649
  23. package/dist/{src-D1iONla3.mjs → src-CttNuxOn.mjs} +925 -630
  24. package/dist/{src-wZx_v0Ih.mjs → src-dL6y2mcu.mjs} +180 -30
  25. package/dist/store-database-worker.mjs +1 -1
  26. package/dist/web/assets/index-BgDdPL1c.js +172 -0
  27. package/dist/web/index.html +2 -1
  28. package/package.json +2 -1
  29. package/skills/INDEX.md +6 -4
  30. package/skills/tacitt/INDEX.md +7 -6
  31. package/skills/tacitt/SKILL.md +7 -7
  32. package/skills/tacitt/references/INDEX.md +14 -11
  33. package/skills/tacitt/references/b-method.md +6 -4
  34. package/skills/tacitt/references/bootstrap.md +18 -4
  35. package/skills/tacitt/references/classification.md +1 -1
  36. package/skills/tacitt/references/commands.md +34 -10
  37. package/skills/tacitt/references/journey.md +7 -7
  38. package/skills/tacitt/references/parallel-review.md +31 -12
  39. package/skills/tacitt/references/remote-workers.md +6 -2
  40. package/skills/tacitt/references/send.md +7 -3
  41. package/skills/tacitt/scripts/INDEX.md +40 -37
  42. package/skills/tacitt/scripts/answer.mjs +31 -2
  43. package/skills/tacitt/scripts/describe.mjs +3 -2
  44. package/skills/tacitt/scripts/journey-card-consolidation.mjs +1 -1
  45. package/skills/tacitt/scripts/journey-classification-tasks.mjs +28 -8
  46. package/skills/tacitt/scripts/journey-classification.mjs +1 -0
  47. package/skills/tacitt/scripts/journey-panel-host.d.mts +2 -0
  48. package/skills/tacitt/scripts/journey-panel-host.mjs +25 -2
  49. package/skills/tacitt/scripts/journey-scan-timing.d.mts +4 -0
  50. package/skills/tacitt/scripts/journey-scan-timing.mjs +15 -2
  51. package/skills/tacitt/scripts/journey-state.mjs +11 -0
  52. package/skills/tacitt/scripts/journey-tasks.mjs +12 -5
  53. package/skills/tacitt/scripts/journey.mjs +11 -3
  54. package/skills/tacitt/scripts/privacy-validator.mjs +112 -40
  55. package/skills/tacitt/scripts/review.d.mts +1 -0
  56. package/skills/tacitt/scripts/review.mjs +680 -119
  57. package/dist/web/assets/index-CYl7eDUu.js +0 -172
@@ -1,4 +1,4 @@
1
- import { $ as bTermSchema, A as indexBRanges, At as packageConfirmActionSchema, B as unownedBObligations, Bn as cleanupImplementationVersion, Bt as termOverrideValueSchema, C as createBAnalysisUnits, Ct as hidePlanResourceSchema, D as bLiteralTerms, Dt as cardsSubmitSchema, E as bLiteralAdmitted, Et as cardsConfirmActionSchema, F as bUnitFragments, Fn as staticOptions, Ft as emptyReviewCorrectionSchema, G as codePointSlice, H as bEvidenceReader, Ht as termTypeSchema, I as bAdoptedObligations, Jt as journeyProfileAddressActionSchema, K as createCodePointIndex, Kt as sessionPeriodSchema, Lt as reviewTermsInputSchema, M as bDuplicateMirror, Mt as privacyTermKey, Nn as createStaticProcessor, O as bLiterals, On as currentDetectorVersion, P as bLegacyBindings, Pn as staticDefaults, Q as bSessionDecisionSchema, Qn as orderProfile, R as bIssue, Rt as reviewTermsResultSchema, S as validateBReview, St as hidePlanInputSchema, T as validateBUnit, Tt as cardAnswerActionSchema, U as bDigest, Ut as termTypes, V as bAnchorKey, Vn as entityDictionaryVersion, Vt as termTypeInfo, W as codePointLength, Wt as deselectOutsidePeriod, X as bRetainedGroupSchema, Y as bRepresentationSchema, Yt as journeyProfileLinksActionSchema, Z as bReviewSubmissionSchema, Zn as validateProfile, _ as reviewFindingsDigest, _t as journeyInstructionInputSchema, a as reviewBatchChunks, ar as detectStatic, at as journeyAccessSchema, c as reviewDigest, cn as reviewFamilies, ct as journeyExceptionSchema, d as validateReview, dn as observeSource, dt as journeyPreferenceScopeSchema, en as respondProfileLinks, er as validatePlan, et as bAnchorSchema, f as findingReferenceKey, fn as sourceSnapshotId, ft as journeyProjectTypeSchema, g as reviewAlignmentSchema, gn as sourcePartSchema, gt as journeyGuidanceSchema, h as reviewAlignmentInputSchema, hr as defaultDetectionLimits, ht as reviewInterruptionSchema, i as compileBFindings, in as safeCode, it as consentedWithoutAccount, jn as gapReasons, jt as privacyResolveSchema, k as uncoveredBObligations, kn as inspectionSemanticVersions, kt as emptyJourneyPrivacy, l as reviewSubmitSchema, ln as sourceGraph, lt as journeyIdSchema, m as reviewAlignmentAuditSchema, mn as inventorySchema, mr as scalarBefore, mt as nativeJourneySchema, nn as ApplicationError, nt as bIdSchema, o as reviewBatchSchema, on as familyNeedsReview, ot as journeyChoiceSchema, pn as fileIdentitySchema, pr as scalarAfter, pt as journeyWorkTypeSchema, q as subtractBRanges, qt as emptyJourneyProfile, rn as UnfinishedJourneyError, rt as bMethod, s as reviewChunkSchema, sn as familySelected, sr as staticCategories, st as journeyDigestSchema, tn as classificationTitleMaskSchema, tr as copyData, tt as bCountSchema, u as textDigest, un as sourceKey, ur as createEntityMatcher, ut as journeyKindSchema, v as validateReviewAlignment, vn as nativeSourceIdentitySchema, vt as delegatedDecisionSchema, w as validateBFields, wt as journeyConfigurationSchema, x as validateBClassification, xt as hideConfirmSchema, y as semanticFindingSchema, yt as journeyDescribeSchema, z as bOverlaps, zt as termAssessmentSchema } from "./locate-Cx52Zqxv.mjs";
1
+ import { $ as createCodePointIndex, $n as validateProfile, A as bLegacyBindings, An as currentDetectorVersion, B as bIssue, Bt as reviewTermsResultSchema, C as validateBReview, Ct as hideConfirmSchema, Dt as cardAnswerActionSchema, E as validateBUnit, Et as journeyConfigurationSchema, F as bLiterals, Fn as createStaticProcessor, G as bReviewSubmissionSchema, Gt as termTypes, H as unownedBObligations, Hn as cleanupImplementationVersion, Ht as termOverrideValueSchema, I as uncoveredBObligations, In as staticDefaults, J as bAnchorKey, Jt as sessionPeriodSchema, K as bSessionDecisionSchema, Kt as deselectOutsidePeriod, L as indexBRanges, Ln as staticOptions, Lt as emptyReviewCorrectionSchema, M as semanticFindingSchema, Mt as packageConfirmActionSchema, N as bLiteralAdmitted, Nn as gapReasons, Nt as privacyResolveSchema, O as bDuplicateMirror, Ot as cardsConfirmActionSchema, P as bLiteralTerms, Pt as privacyTermKey, Q as codePointSlice, R as bAdoptedObligations, S as validateBClassification, T as validateBFields, Tt as hidePlanResourceSchema, U as bRepresentationSchema, Un as entityDictionaryVersion, Ut as termTypeInfo, V as bOverlaps, Vt as termAssessmentSchema, W as bRetainedGroupSchema, Wt as termTypeSchema, X as bDigest, Xt as journeyProfileAddressActionSchema, Y as bEvidenceReader, Yt as emptyJourneyProfile, Z as codePointLength, Zt as journeyProfileLinksActionSchema, _ as reviewAlignmentInputSchema, _r as defaultDetectionLimits, _t as reviewInterruptionSchema, an as UnfinishedJourneyError, at as bMethod, b as validateReviewAlignment, bn as nativeSourceIdentitySchema, bt as delegatedDecisionSchema, c as reviewBatchSchema, cn as familyNeedsReview, ct as journeyChoiceSchema, d as reviewSubmitSchema, dn as sourceGraph, dt as journeyIdSchema, er as orderProfile, et as subtractBRanges, f as textDigest, fn as sourceKey, fr as createEntityMatcher, ft as journeyKindSchema, g as reviewAlignmentAuditSchema, gn as inventorySchema, gr as scalarBefore, gt as nativeJourneySchema, hn as fileIdentitySchema, hr as scalarAfter, ht as journeyWorkTypeSchema, in as ApplicationError, it as bIdSchema, j as bUnitFragments, jn as inspectionSemanticVersions, jt as emptyJourneyPrivacy, kt as cardsSubmitSchema, l as reviewChunkSchema, ln as familySelected, lr as staticCategories, lt as journeyDigestSchema, m as findingReferenceKey, mn as sourceSnapshotId, mt as journeyProjectTypeSchema, nn as respondProfileLinks, nr as validatePlan, nt as bAnchorSchema, o as compileBFindings, on as safeCode, ot as consentedWithoutAccount, p as validateReview, pn as observeSource, pt as journeyPreferenceScopeSchema, q as bTermSchema, rn as classificationTitleMaskSchema, rr as copyData, rt as bCountSchema, s as reviewBatchChunks, sr as detectStatic, st as journeyAccessSchema, u as reviewDigest, un as reviewFamilies, ut as journeyExceptionSchema, v as reviewAlignmentSchema, vn as sourcePartSchema, vt as journeyGuidanceSchema, w as createBAnalysisUnits, wt as hidePlanInputSchema, xt as journeyDescribeSchema, y as reviewFindingsDigest, yt as journeyInstructionInputSchema, zt as reviewTermsInputSchema } from "./locate-PLEyLYW-.mjs";
2
2
  import { z } from "zod";
3
3
  import { createHash, createHmac, createSecretKey, randomUUID } from "node:crypto";
4
4
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
@@ -1199,100 +1199,439 @@ function requireSelecting(run) {
1199
1199
  if (run.phase !== "selecting") throw new ApplicationError("INVALID_TRANSITION");
1200
1200
  }
1201
1201
  //#endregion
1202
- //#region packages/application/src/journey/placement/readiness.ts
1203
- function hasCompleteWorkDescription(work) {
1204
- return work.placement === "placed" && !!work.description.trim() && work.classificationRecommendation !== null;
1205
- }
1202
+ //#region packages/processing/src/session/marker.ts
1206
1203
  /**
1207
- * Dropped Works are not AI-read; a Work emptied by repartition has nothing left to read, and
1208
- * roots classification has not grouped yet are read once they join a Work (E2).
1204
+ * One complete replacement marker as bindings and semantic markers write it. Group 1 names the
1205
+ * static method, or `semantic` for an AI-review span. Dependency-free so the browser can use it.
1209
1206
  */
1210
- function isSelectedWork(work) {
1211
- return work.access !== "drop" && work.rootSessionIds.length > 0 && work.placement !== "classifying";
1207
+ const replacementMarkerPattern = /\[sp_[a-z0-9]{8,48}_([a-z][a-z0-9_]{0,47})_[a-f0-9]{24,64}\]/gu;
1208
+ //#endregion
1209
+ //#region packages/application/src/journey/b/matches.ts
1210
+ function bDictionaryHits(fields, literals, signal = new AbortController().signal) {
1211
+ const matcher = createEntityMatcher([...new Set(literals.map((part) => part.term))].map((value) => ({
1212
+ kind: "custom-term",
1213
+ value
1214
+ })));
1215
+ const hits = [];
1216
+ for (const field of fields) {
1217
+ const index = createCodePointIndex(field.text);
1218
+ const base = field.start ?? 0;
1219
+ for (const match of matcher.find(field.text, signal, 1e5)) {
1220
+ if (hits.length >= 1e6) throw new ApplicationError("PROCESSING_LIMIT");
1221
+ const body = {
1222
+ fieldId: field.id,
1223
+ fieldHash: field.fieldHash,
1224
+ recordId: field.recordId,
1225
+ start: base + index.codePointOffset(match.range.start),
1226
+ end: base + index.codePointOffset(match.range.end),
1227
+ exactQuote: field.text.slice(match.range.start, match.range.end),
1228
+ term: match.entity.value
1229
+ };
1230
+ hits.push({
1231
+ ...body,
1232
+ digest: bDigest(JSON.stringify(body))
1233
+ });
1234
+ }
1235
+ }
1236
+ return [...new Map(hits.map((hit) => [hit.digest, hit])).values()];
1212
1237
  }
1213
- /** Classification published some Works and is still grouping the remaining roots (E2). */
1214
- function isClassifying(state) {
1215
- return state.works.some((work) => work.placement === "classifying");
1238
+ function unclassifiedBHits(hits, literals, negative) {
1239
+ const issues = [];
1240
+ const positiveFields = new Map([...Map.groupBy(literals, (part) => part.fieldId)].map(([id, parts]) => [id, indexBRanges(parts)]));
1241
+ const negativeFields = new Map([...Map.groupBy(negative, (part) => part.fieldId)].map(([id, parts]) => [id, indexBRanges(parts)]));
1242
+ for (const hit of hits) {
1243
+ const positive = positiveFields.get(hit.fieldId)?.relation(hit).covers ?? false;
1244
+ const negative = negativeFields.get(hit.fieldId)?.relation(hit);
1245
+ if (!positive && !negative?.covers || positive && negative?.overlaps) issues.push(bIssue(positive ? "CONFLICTING_PRIVACY_DECISIONS" : "UNCLASSIFIED_MATCH", hit));
1246
+ }
1247
+ return issues;
1216
1248
  }
1217
- /**
1218
- * A Work whose read failed: it is skipped for this Send without a decision (ADR 0236). It holds no
1219
- * reading, card, packaging or Send gate and is never sent; Retry or a move out of Drop reads it
1220
- * again. A pending Hide also blocks the Works it changes, but those wait for its recovery and are
1221
- * not skipped. The Skill helpers mirror this rule in `journey-state.mjs`.
1222
- */
1223
- function isSkippedWork(work) {
1224
- return work.status === "blocked" && !work.blockedCodes.includes("PRIVACY_CHANGE_PENDING") && currentReviewInterruptions(work).length === 0;
1249
+ const bSpacedWord = (value) => /[\p{L}\p{N}_]/u.test(value) && !/[\p{sc=Han}\p{sc=Hiragana}\p{sc=Katakana}\p{sc=Thai}\p{sc=Lao}\p{sc=Khmer}\p{sc=Myanmar}]/u.test(value);
1250
+ /** In decoded tool text an odd backslash run escapes its following n, r or t. */
1251
+ function afterEscapedSpace(text, start) {
1252
+ const letter = text[start - 1];
1253
+ if (letter !== "n" && letter !== "r" && letter !== "t") return false;
1254
+ let index = start - 2;
1255
+ while (index >= 0 && text[index] === "\\") index--;
1256
+ return (start - 2 - index) % 2 === 1;
1225
1257
  }
1226
- /** A selected Work that is not skipped (ADR 0236). */
1227
- function isIncludedWork(work) {
1228
- return isSelectedWork(work) && !isSkippedWork(work);
1258
+ /** A confirmed Hide value matches only where it does not run into a neighbouring word (ADR 0069). */
1259
+ function bWordBounded(text, start, end, toolResultEscapes = false) {
1260
+ return !(bSpacedWord(scalarBefore(text, start)) && bSpacedWord(scalarAfter(text, start)) && !(toolResultEscapes && afterEscapedSpace(text, start)) || bSpacedWord(scalarBefore(text, end)) && bSpacedWord(scalarAfter(text, end)));
1229
1261
  }
1230
1262
  /**
1231
- * ADR 0274: Public needs a recorded choice: the person's panel click or named conversation
1232
- * answer (origin `explicit`), or, under ADR 0275, the account proof that every root was already
1233
- * Public (origin and channel `account`). A Public Work stored before `accessChoice` existed is
1234
- * still pending. Mirrored by `phaseOf` in the Skill's `journey-state.mjs` and by the panel's
1235
- * `WorkAccess`.
1263
+ * Occurrences of a Work's explicit exceptions, matched like Hide everywhere: exact NFC values
1264
+ * over complete graphemes. A confirmed Hide value inside one stays visible there.
1236
1265
  */
1237
- function hasPublicChoice(work) {
1238
- if (work.access !== "public") return true;
1239
- if (!work.accessConfirmed || !work.accessChoice) return false;
1240
- return work.accessChoice.channel === "account" ? work.accessOrigin === "account" : work.accessOrigin === "explicit";
1241
- }
1242
- /** ADR 0277 slice 2 display projection. Send consent remains a separate gate. */
1243
- function publicSource(work) {
1244
- if (!isIncludedWork(work) || work.access !== "public" || work.accountAccessConflict) return null;
1245
- if (!hasPublicChoice(work)) return "suggestion";
1246
- return work.accessChoice?.channel === "account" ? "account" : "choice";
1266
+ function bExceptionRanges(values) {
1267
+ const matcher = createEntityMatcher([...new Set([...values].map((value) => value.normalize("NFC")))].map((value) => ({
1268
+ kind: "custom-term",
1269
+ value
1270
+ })));
1271
+ return (text, signal = new AbortController().signal) => matcher.find(text, signal, 1e5).filter(({ range, entity }) => text.slice(range.start, range.end).normalize("NFC") === entity.value).map(({ range }) => range);
1247
1272
  }
1248
- function selectedWorks(state) {
1249
- return state.works.filter(isSelectedWork);
1273
+ /** Whether a range lies inside one of `ranges`: a prefix maximum of ends, O(log n) per query. */
1274
+ function bContainment(ranges) {
1275
+ const sorted = ranges.toSorted((a, b) => a.start - b.start);
1276
+ const reach = [];
1277
+ for (const [index, range] of sorted.entries()) reach.push(Math.max(range.end, reach[index - 1] ?? range.end));
1278
+ return (range) => {
1279
+ let low = 0, high = sorted.length;
1280
+ while (low < high) {
1281
+ const middle = low + high >> 1;
1282
+ if ((sorted[middle]?.start ?? Infinity) <= range.start) low = middle + 1;
1283
+ else high = middle;
1284
+ }
1285
+ return low > 0 && (reach[low - 1] ?? -1) >= range.end;
1286
+ };
1250
1287
  }
1251
- /** Categories found only in Dropped Works are neither asked nor counted. */
1252
- function unselectedGroupIds(state) {
1253
- const selected = new Set(selectedWorks(state).flatMap((work) => work.groupIds));
1254
- return new Set(state.works.filter((work) => !isSelectedWork(work)).flatMap((work) => work.groupIds).filter((id) => !selected.has(id)));
1288
+ /**
1289
+ * Word-bounded, exact, case-sensitive NFC occurrences of the terms, in UTF-16 coordinates,
1290
+ * except where an explicit exception occurrence contains one.
1291
+ */
1292
+ function bTermMatcher(terms, exceptions = []) {
1293
+ const matcher = createEntityMatcher([...new Set(terms)].map((value) => ({
1294
+ kind: "custom-term",
1295
+ value
1296
+ })));
1297
+ const excepted = bExceptionRanges(exceptions);
1298
+ return (text, signal = new AbortController().signal) => {
1299
+ const hits = matcher.find(text, signal, 1e5).filter(({ range }) => bWordBounded(text, range.start, range.end));
1300
+ const kept = hits.length ? bContainment(excepted(text, signal)) : () => false;
1301
+ return hits.filter(({ range }) => !kept(range)).map(({ range, entity }) => ({
1302
+ ...range,
1303
+ term: entity.value
1304
+ }));
1305
+ };
1255
1306
  }
1256
- /** Every root without batches is covered by an empty-input or explicit exclusion proof. */
1257
- function hasCertifiedNoBatchReview(work, snapshotId) {
1258
- const proofs = [work.emptyReview, work.excludedReview].filter((proof) => proof !== void 0);
1259
- if (!proofs.length || proofs.some((proof) => proof.snapshotId !== snapshotId || proof.inputRevision !== work.inputRevision)) return false;
1260
- const roots = proofs.flatMap((proof) => proof.rootSessionIds);
1261
- return roots.length === work.rootSessionIds.length && new Set(roots).size === roots.length && work.rootSessionIds.every((id) => roots.includes(id));
1307
+ /** Dictionary hits that a confirmed Hide value masks at render, so they need no classification. */
1308
+ function bMaskedHits(fields, terms) {
1309
+ const byId = new Map(fields.map((field) => [field.id, {
1310
+ text: field.text,
1311
+ index: createCodePointIndex(field.text),
1312
+ base: field.start ?? 0
1313
+ }]));
1314
+ return (hit) => {
1315
+ const field = byId.get(hit.fieldId);
1316
+ return !!field && terms.has(hit.term) && bWordBounded(field.text, field.index.offset(hit.start - field.base), field.index.offset(hit.end - field.base));
1317
+ };
1262
1318
  }
1263
- /** A reused Work ID cannot carry an old input generation's failed review forward. */
1264
- function currentReviewInterruptions(work) {
1265
- return (work.reviewInterruptions ?? []).filter((item) => item.inputRevision === work.inputRevision);
1319
+ //#endregion
1320
+ //#region packages/application/src/journey/terms/groups.ts
1321
+ const keyPrefix = "type:";
1322
+ const termGroupKey = (type) => `${keyPrefix}${type}`;
1323
+ /** RFC 9562 UUID version 5 of `name` in the `namespace` UUID. */
1324
+ function uuidV5(namespace, name) {
1325
+ const bytes = createHash("sha1").update(Buffer.from(namespace.replaceAll("-", ""), "hex")).update(name, "utf8").digest().subarray(0, 16);
1326
+ bytes.writeUInt8(bytes.readUInt8(6) & 15 | 80, 6);
1327
+ bytes.writeUInt8(bytes.readUInt8(8) & 63 | 128, 8);
1328
+ const hex = bytes.toString("hex");
1329
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
1266
1330
  }
1267
- /** Reading completes over included Works once the person has confirmed the scan scope. */
1268
- function isReadingComplete(state) {
1269
- return state.scope.status === "confirmed" && state.works.length > 0 && !isClassifying(state) && state.works.filter(isIncludedWork).every((work) => currentReviewInterruptions(work).length === 0 && work.status !== "pending" && work.status !== "blocked" && (work.totalBatches > 0 || hasCertifiedNoBatchReview(work, state.snapshotId)) && work.completedBatches === work.totalBatches);
1331
+ /** Deterministic: every reader and writer of a journey names a type's group alike. */
1332
+ const termGroupId = (journeyId, type) => uuidV5(journeyId, type);
1333
+ /** The term type a group answers, or null for any other category. */
1334
+ function termGroupType(group) {
1335
+ if (!group.key.startsWith(keyPrefix)) return null;
1336
+ const parsed = termTypeSchema.safeParse(group.key.slice(5));
1337
+ return parsed.success ? parsed.data : null;
1270
1338
  }
1271
- /** Recompute completion after a selection or reading change without leaving a paused/send phase. */
1272
- function withReadingProgress(state) {
1273
- const readingComplete = isReadingComplete(state);
1274
- const phase = state.phase === "paused" || !state.snapshotId ? state.phase : readingComplete ? state.phase === "processing" ? "review" : state.phase : state.phase === "review" || state.phase === "ready" ? "processing" : state.phase;
1339
+ const typeGroup = (state, type) => state.groups.find((group) => termGroupType(group) === type);
1340
+ function newTypeGroup(state, type, preference) {
1341
+ const info = termTypeInfo[type];
1342
+ const base = {
1343
+ id: termGroupId(state.journeyId, type),
1344
+ kind: info.kind,
1345
+ key: termGroupKey(type),
1346
+ label: info.label,
1347
+ condition: info.condition,
1348
+ count: 0,
1349
+ preferenceScope: null,
1350
+ representative: null
1351
+ };
1352
+ if (!info.asked) return {
1353
+ ...base,
1354
+ belief: .97,
1355
+ choice: "hide",
1356
+ confirmed: true,
1357
+ origin: "policy"
1358
+ };
1359
+ if (state.questionsPaused) return {
1360
+ ...base,
1361
+ belief: .97,
1362
+ choice: "hide",
1363
+ confirmed: false,
1364
+ origin: "paused"
1365
+ };
1275
1366
  return {
1276
- ...state,
1277
- readingComplete,
1278
- phase
1367
+ ...base,
1368
+ belief: preference === null ? .5 : preference === "hide" ? .97 : .03,
1369
+ choice: preference,
1370
+ confirmed: false,
1371
+ origin: preference === null ? "model" : "preference"
1279
1372
  };
1280
1373
  }
1374
+ /**
1375
+ * One group per type with at least one dictionary value, `count` its distinct values. Existing
1376
+ * groups keep their identity and answer; other groups are unchanged.
1377
+ */
1378
+ function syncTermGroups(state, counts, preference) {
1379
+ const groups = state.groups.map((group) => {
1380
+ const type = termGroupType(group);
1381
+ return type === null ? group : {
1382
+ ...group,
1383
+ count: counts.get(type) ?? group.count
1384
+ };
1385
+ });
1386
+ for (const type of termTypes) {
1387
+ const count = counts.get(type) ?? 0;
1388
+ if (count === 0 || groups.some((group) => termGroupType(group) === type)) continue;
1389
+ groups.push({
1390
+ ...newTypeGroup(state, type, preference(termGroupKey(type))),
1391
+ count
1392
+ });
1393
+ }
1394
+ return groups;
1395
+ }
1281
1396
  //#endregion
1282
- //#region packages/application/src/journey/send/model.ts
1283
- const count$13 = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
1284
- const access = z.enum(["public", "private"]);
1285
- const sendSourceSchema = z.object({
1286
- id: z.string().regex(/^w[0-9]{6,}$/u),
1287
- pathHash: journeyDigestSchema.nullable(),
1288
- remoteHashes: z.array(journeyDigestSchema),
1289
- primaryRemoteHash: journeyDigestSchema.nullable(),
1290
- challengeId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/u).optional()
1291
- }).strict();
1292
- function agentSourcePathSegment(value) {
1293
- return /^[A-Za-z0-9.][A-Za-z0-9._-]*$/u.test(value) && value !== "." && value !== "..";
1397
+ //#region packages/application/src/journey/review/confirmed-hides.ts
1398
+ /** Kinds from lowest to highest mask priority. */
1399
+ const confirmedHideKinds = [
1400
+ "internal",
1401
+ "identity",
1402
+ "private",
1403
+ "credentials"
1404
+ ];
1405
+ const kinds = confirmedHideKinds;
1406
+ const rank$1 = (kind) => kinds.indexOf(kind);
1407
+ /**
1408
+ * ADR 0086: a type's values are hidden unless its group is answered Keep; before an answer the
1409
+ * default is Hide. Credentials and contact details are always hidden (D06).
1410
+ */
1411
+ function typeHides(state, type) {
1412
+ const group = typeGroup(state, type);
1413
+ return !termTypeInfo[type].asked || !group?.confirmed || group.choice !== "keep";
1294
1414
  }
1295
- /** Local candidates become public only after anonymous upload-time evidence checks. */
1415
+ /** B review (superseded by ADR 0086): IDs of the categories confirmed as Hide. */
1416
+ const confirmedHideGroupIds = (state) => state.groups.filter((group) => group.confirmed && group.choice === "hide").map((group) => group.id);
1417
+ const overrideKey = (state) => (state.valueOverrides ?? []).map((item) => JSON.stringify([
1418
+ item.type,
1419
+ item.value,
1420
+ item.choice
1421
+ ])).toSorted();
1422
+ /**
1423
+ * Identifies the hidden set (ADR 0079, 0086). The dictionary only grows and a type's group
1424
+ * counts its distinct values, so each type's count and Hide answer, with the value overrides,
1425
+ * name exactly one hidden set.
1426
+ */
1427
+ function confirmedHideKey(state) {
1428
+ return createHash("sha256").update(JSON.stringify({
1429
+ types: termTypes.map((type) => [
1430
+ type,
1431
+ typeGroup(state, type)?.count ?? 0,
1432
+ typeHides(state, type)
1433
+ ]),
1434
+ overrides: overrideKey(state),
1435
+ terms: state.privacy.terms.filter((term) => term.disposition === "hidden").map((term) => [term.type, term.value]).toSorted((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)))
1436
+ })).digest("hex");
1437
+ }
1438
+ /** Whether two states hold the same value overrides. */
1439
+ const sameValueOverrides = (left, right) => JSON.stringify(overrideKey(left)) === JSON.stringify(overrideKey(right));
1440
+ /**
1441
+ * A preparation's waived hits stay waived only while every category recorded for them is still a
1442
+ * confirmed Hide; otherwise render may no longer mask them, so the session needs reconciliation.
1443
+ */
1444
+ function maskedHitsStale(maskedHits, hiding) {
1445
+ const current = new Set(hiding);
1446
+ return !!maskedHits?.some((hit) => hit.groupIds.some((id) => !current.has(id)));
1447
+ }
1448
+ /**
1449
+ * ADR 0086 hidden set: values of types not answered Keep, minus Keep overrides, plus Hide
1450
+ * overrides, plus every value of an always-hidden type. A value of several types takes the
1451
+ * highest mask kind among the types hiding it.
1452
+ */
1453
+ function dictionaryHides(state, terms) {
1454
+ const overrides = state.valueOverrides ?? [];
1455
+ const kept = new Set(overrides.flatMap((item) => item.choice === "keep" ? [item.value] : []));
1456
+ const hidden = /* @__PURE__ */ new Map();
1457
+ const add = (value, type) => {
1458
+ const prior = hidden.get(value);
1459
+ const kind = termTypeInfo[type].kind;
1460
+ hidden.set(value, {
1461
+ kind: prior && rank$1(prior.kind) > rank$1(kind) ? prior.kind : kind,
1462
+ groupIds: [.../* @__PURE__ */ new Set([...prior?.groupIds ?? [], termGroupId(state.journeyId, type)])].sort()
1463
+ });
1464
+ };
1465
+ const all = [...terms];
1466
+ const hiddenValues = new Set(state.privacy.terms.filter((item) => item.disposition === "hidden").map((item) => item.value));
1467
+ for (const term of all) if (hiddenValues.has(term.value) && !kept.has(term.value)) add(term.value, term.type);
1468
+ for (const item of overrides) if (item.choice === "hide") add(item.value, item.type);
1469
+ for (const term of all) if (!termTypeInfo[term.type].asked) add(term.value, term.type);
1470
+ for (const value of state.rememberedHides ?? []) if (!hidden.has(value) && !kept.has(value)) add(value, "projects");
1471
+ return hidden;
1472
+ }
1473
+ /** The journey's current hidden set, applied to every selected Work (ADR 0086, D03). */
1474
+ async function journeyConfirmedHides(reader, state) {
1475
+ return dictionaryHides(state, [...await reader.journeyTerms({ journeyId: state.journeyId }), ...state.privacy.terms]);
1476
+ }
1477
+ function confirmedHideTerms(groups, records) {
1478
+ const confirmed = new Map(groups.filter((group) => group.confirmed && group.choice === "hide").map((group) => [group.id, group]));
1479
+ const fields = /* @__PURE__ */ new Map();
1480
+ for (const record of records) {
1481
+ const assigned = new Map(record.assignments.map((item) => [item.findingId, item.groupId]));
1482
+ const chunks = new Map(record.chunks.map((chunk) => [chunk.id, chunk]));
1483
+ for (const finding of record.findings) {
1484
+ const group = confirmed.get(assigned.get(finding.id) ?? "");
1485
+ const chunk = chunks.get(finding.chunkId);
1486
+ if (!group || !chunk || finding.class !== "gray") continue;
1487
+ const key = JSON.stringify([chunk.fieldId, chunk.fieldHash]);
1488
+ fields.set(key, [...fields.get(key) ?? [], {
1489
+ start: chunk.offset + finding.start,
1490
+ end: chunk.offset + finding.end,
1491
+ text: chunk.text.slice(finding.start, finding.end),
1492
+ split: [finding.start === 0, finding.end === chunk.text.length],
1493
+ group
1494
+ }]);
1495
+ }
1496
+ }
1497
+ const terms = /* @__PURE__ */ new Map();
1498
+ const add = (text, groupIds, kind) => {
1499
+ for (const term of bLiteralTerms(text)) {
1500
+ const prior = terms.get(term);
1501
+ terms.set(term, {
1502
+ kind: prior && rank$1(prior.kind) > rank$1(kind) ? prior.kind : kind,
1503
+ groupIds: [.../* @__PURE__ */ new Set([...prior?.groupIds ?? [], ...groupIds])].sort()
1504
+ });
1505
+ }
1506
+ };
1507
+ for (const spans of fields.values()) {
1508
+ let open;
1509
+ for (const span of spans.toSorted((a, b) => a.start - b.start || b.end - a.end)) {
1510
+ if (open && span.split[0] && span.start === open.end) {
1511
+ open.text += span.text;
1512
+ open.end = span.end;
1513
+ open.groupIds.push(span.group.id);
1514
+ } else {
1515
+ if (open) add(open.text, open.groupIds, open.kind);
1516
+ open = {
1517
+ end: span.end,
1518
+ text: span.text,
1519
+ groupIds: [span.group.id],
1520
+ kind: span.group.kind
1521
+ };
1522
+ }
1523
+ if (!span.split[1]) {
1524
+ add(open.text, open.groupIds, open.kind);
1525
+ open = void 0;
1526
+ }
1527
+ }
1528
+ if (open) add(open.text, open.groupIds, open.kind);
1529
+ }
1530
+ return terms;
1531
+ }
1532
+ //#endregion
1533
+ //#region packages/application/src/journey/placement/readiness.ts
1534
+ function hasCompleteWorkDescription(work) {
1535
+ return work.placement === "placed" && !!work.description.trim() && work.classificationRecommendation !== null;
1536
+ }
1537
+ /** Explain why a requested Work still has no usable output after rendering. */
1538
+ function unfinishedPackagingCode(work) {
1539
+ if (!work) return "REVISION_CONFLICT";
1540
+ if (work.renderFailure?.revision === work.revision) return work.renderFailure.code;
1541
+ if (!hasCompleteWorkDescription(work)) return "WORK_METADATA_INCOMPLETE";
1542
+ if (!work.accessConfirmed) return "CONFIRMATION_REQUIRED";
1543
+ return "PACKAGING_FAILED";
1544
+ }
1545
+ /**
1546
+ * Dropped Works are not AI-read; a Work emptied by repartition has nothing left to read, and
1547
+ * roots classification has not grouped yet are read once they join a Work (E2).
1548
+ */
1549
+ function isSelectedWork(work) {
1550
+ return work.access !== "drop" && work.rootSessionIds.length > 0 && work.placement !== "classifying";
1551
+ }
1552
+ /** Classification published some Works and is still grouping the remaining roots (E2). */
1553
+ function isClassifying(state) {
1554
+ return state.works.some((work) => work.placement === "classifying");
1555
+ }
1556
+ /**
1557
+ * A Work whose read failed: it is skipped for this Send without a decision (ADR 0236). It holds no
1558
+ * reading, card, packaging or Send gate and is never sent; Retry or a move out of Drop reads it
1559
+ * again. A pending Hide also blocks the Works it changes, but those wait for its recovery and are
1560
+ * not skipped. The Skill helpers mirror this rule in `journey-state.mjs`.
1561
+ */
1562
+ function isSkippedWork(work) {
1563
+ return work.status === "blocked" && !work.blockedCodes.includes("PRIVACY_CHANGE_PENDING") && currentReviewInterruptions(work).length === 0;
1564
+ }
1565
+ /** A selected Work that is not skipped (ADR 0236). */
1566
+ function isIncludedWork(work) {
1567
+ return isSelectedWork(work) && !isSkippedWork(work);
1568
+ }
1569
+ /**
1570
+ * ADR 0274: Public needs a recorded choice: the person's panel click or named conversation
1571
+ * answer (origin `explicit`), or, under ADR 0275, the account proof that every root was already
1572
+ * Public (origin and channel `account`). A Public Work stored before `accessChoice` existed is
1573
+ * still pending. Mirrored by `phaseOf` in the Skill's `journey-state.mjs` and by the panel's
1574
+ * `WorkAccess`.
1575
+ */
1576
+ function hasPublicChoice(work) {
1577
+ if (work.access !== "public") return true;
1578
+ if (!work.accessConfirmed || !work.accessChoice) return false;
1579
+ return work.accessChoice.channel === "account" ? work.accessOrigin === "account" : work.accessOrigin === "explicit";
1580
+ }
1581
+ /** ADR 0277 slice 2 display projection. Send consent remains a separate gate. */
1582
+ function publicSource(work) {
1583
+ if (!isIncludedWork(work) || work.access !== "public" || work.accountAccessConflict) return null;
1584
+ if (!hasPublicChoice(work)) return "suggestion";
1585
+ return work.accessChoice?.channel === "account" ? "account" : "choice";
1586
+ }
1587
+ function selectedWorks(state) {
1588
+ return state.works.filter(isSelectedWork);
1589
+ }
1590
+ /** Categories found only in Dropped Works are neither asked nor counted. */
1591
+ function unselectedGroupIds(state) {
1592
+ const selected = new Set(selectedWorks(state).flatMap((work) => work.groupIds));
1593
+ return new Set(state.works.filter((work) => !isSelectedWork(work)).flatMap((work) => work.groupIds).filter((id) => !selected.has(id)));
1594
+ }
1595
+ /** Every root without batches is covered by an empty-input or explicit exclusion proof. */
1596
+ function hasCertifiedNoBatchReview(work, snapshotId) {
1597
+ const proofs = [work.emptyReview, work.excludedReview].filter((proof) => proof !== void 0);
1598
+ if (!proofs.length || proofs.some((proof) => proof.snapshotId !== snapshotId || proof.inputRevision !== work.inputRevision)) return false;
1599
+ const roots = proofs.flatMap((proof) => proof.rootSessionIds);
1600
+ return roots.length === work.rootSessionIds.length && new Set(roots).size === roots.length && work.rootSessionIds.every((id) => roots.includes(id));
1601
+ }
1602
+ /** A reused Work ID cannot carry an old input generation's failed review forward. */
1603
+ function currentReviewInterruptions(work) {
1604
+ return (work.reviewInterruptions ?? []).filter((item) => item.inputRevision === work.inputRevision);
1605
+ }
1606
+ /** Reading completes over included Works once the person has confirmed the scan scope. */
1607
+ function isReadingComplete(state) {
1608
+ return state.scope.status === "confirmed" && state.works.length > 0 && !isClassifying(state) && state.works.filter(isIncludedWork).every((work) => currentReviewInterruptions(work).length === 0 && work.status !== "pending" && work.status !== "blocked" && (work.totalBatches > 0 || hasCertifiedNoBatchReview(work, state.snapshotId)) && work.completedBatches === work.totalBatches);
1609
+ }
1610
+ /** Recompute completion after a selection or reading change without leaving a paused/send phase. */
1611
+ function withReadingProgress(state) {
1612
+ const readingComplete = isReadingComplete(state);
1613
+ const phase = state.phase === "paused" || !state.snapshotId ? state.phase : readingComplete ? state.phase === "processing" ? "review" : state.phase : state.phase === "review" || state.phase === "ready" ? "processing" : state.phase;
1614
+ return {
1615
+ ...state,
1616
+ readingComplete,
1617
+ phase
1618
+ };
1619
+ }
1620
+ //#endregion
1621
+ //#region packages/application/src/journey/send/model.ts
1622
+ const count$13 = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
1623
+ const access = z.enum(["public", "private"]);
1624
+ const sendSourceSchema = z.object({
1625
+ id: z.string().regex(/^w[0-9]{6,}$/u),
1626
+ pathHash: journeyDigestSchema.nullable(),
1627
+ remoteHashes: z.array(journeyDigestSchema),
1628
+ primaryRemoteHash: journeyDigestSchema.nullable(),
1629
+ challengeId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/u).optional()
1630
+ }).strict();
1631
+ function agentSourcePathSegment(value) {
1632
+ return /^[A-Za-z0-9.][A-Za-z0-9._-]*$/u.test(value) && value !== "." && value !== "..";
1633
+ }
1634
+ /** Local candidates become public only after anonymous upload-time evidence checks. */
1296
1635
  const agentSourceSchema = z.object({
1297
1636
  kind: z.enum(["skill", "mcp"]),
1298
1637
  name: z.string().regex(/^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,199}$/u),
@@ -1443,6 +1782,19 @@ const sendUploadInputSchema = z.object({
1443
1782
  expectedScopeVersion: count$13
1444
1783
  }).strict();
1445
1784
  const savedSendReceiptSchema = sendReceiptSchema.omit({ upload: true });
1785
+ const sendSessionUploadSchema = z.object({
1786
+ key: journeyDigestSchema,
1787
+ sha256: journeyDigestSchema,
1788
+ /** Byte-started transport/server errors charged to this Send. */
1789
+ attempt: count$13.optional(),
1790
+ /** Transient failures used only to schedule capped zero-byte retries. */
1791
+ retryCount: count$13.optional(),
1792
+ bytesAttempted: count$13.optional(),
1793
+ bytesAcknowledged: count$13.optional(),
1794
+ contentLength: count$13.optional(),
1795
+ bytesSent: count$13.optional(),
1796
+ nextAttemptAt: z.iso.datetime().optional()
1797
+ }).strict();
1446
1798
  const sendItemSchema = z.object({
1447
1799
  workId: journeyIdSchema,
1448
1800
  title: z.string(),
@@ -1471,22 +1823,13 @@ const sendItemSchema = z.object({
1471
1823
  * uploads through staging (ADR 0231).
1472
1824
  */
1473
1825
  checksumUploadRefused: z.literal(true).optional(),
1826
+ /** Selected session ZIPs confirmed by inventory or completion during this traversal. */
1827
+ storedSessions: count$13.optional(),
1474
1828
  /** Write-ahead upload intent; completion probes recover an unacknowledged blob PUT. */
1475
- sessionUpload: z.object({
1476
- key: journeyDigestSchema,
1477
- sha256: journeyDigestSchema,
1478
- /** Byte-started transport/server failures charged to this Send. */
1479
- attempt: count$13.optional(),
1480
- /** Transient failures used only to schedule capped zero-byte retries. */
1481
- retryCount: count$13.optional(),
1482
- bytesAttempted: count$13.optional(),
1483
- bytesAcknowledged: count$13.optional(),
1484
- contentLength: count$13.optional(),
1485
- bytesSent: count$13.optional(),
1486
- nextAttemptAt: z.iso.datetime().optional()
1487
- }).strict().nullable().optional(),
1829
+ sessionUpload: sendSessionUploadSchema.nullable().optional(),
1488
1830
  /** Local key-rule marker; current membership replays need no artifact migration inspection. */
1489
1831
  sessionMembershipKeyVersion: z.literal(2).optional(),
1832
+ sessionMembershipMetadataVersion: z.literal(1).optional(),
1490
1833
  /** Exact write-ahead membership request; never rebase it after an uncertain PUT. */
1491
1834
  sessionMembership: z.object({
1492
1835
  workId: journeyIdSchema,
@@ -1658,19 +2001,34 @@ function hasCurrentStaticReview(state, work) {
1658
2001
  }
1659
2002
  //#endregion
1660
2003
  //#region packages/application/src/journey/privacy/record.ts
1661
- /** Merge independently reviewed occurrences into one journey-wide decision. */
1662
- function mergePrivacyTerms(state, incoming) {
1663
- const terms = new Map(state.privacy.terms.map((term) => [privacyTermKey(term), term]));
2004
+ /** Only a stored human rule or an explicitly delegated category choice can settle a reader proposal. */
2005
+ function currentTermRules(state) {
1664
2006
  const overrides = /* @__PURE__ */ new Map();
1665
2007
  for (const item of state.valueOverrides ?? []) {
1666
2008
  const value = item.value.normalize("NFC");
1667
2009
  if (!overrides.has(value)) overrides.set(value, item);
1668
2010
  }
2011
+ const categories = new Map(state.groups.filter((group) => group.origin === "preference" || group.confirmed && (group.origin === "explicit" || group.origin === "delegate")).map((group) => [group.key, group.choice]));
2012
+ const remembered = new Set(state.rememberedHides ?? []);
2013
+ function choice(term) {
2014
+ const value = term.value.normalize("NFC");
2015
+ return overrides.get(value)?.choice ?? (remembered.has(value) ? "hide" : categories.get(`type:${term.type}`));
2016
+ }
2017
+ return {
2018
+ overrides,
2019
+ choice
2020
+ };
2021
+ }
2022
+ /** Merge independently reviewed occurrences into one journey-wide decision. */
2023
+ function mergePrivacyTerms(state, incoming) {
2024
+ const terms = new Map(state.privacy.terms.map((term) => [privacyTermKey(term), term]));
2025
+ const rules = currentTermRules(state);
2026
+ const overrides = rules.overrides;
1669
2027
  for (const term of incoming) {
1670
- const key = privacyTermKey(term), prior = terms.get(key);
1671
- const override = overrides.get(term.value.normalize("NFC"));
1672
- const preference = state.groups.find((group) => group.key === `type:${term.type}` && group.origin === "preference");
1673
- const disposition = !termTypeInfo[term.type].asked ? "hidden" : override ? override.choice === "hide" ? "hidden" : "keep" : preference?.choice ? preference.choice === "hide" ? "hidden" : "keep" : prior?.disposition === "hidden" || term.disposition === "hidden" ? "hidden" : prior && prior.disposition !== term.disposition ? "pending" : term.disposition;
2028
+ const key = privacyTermKey(term);
2029
+ const prior = terms.get(key);
2030
+ const choice = rules.choice(term);
2031
+ const disposition = !termTypeInfo[term.type].asked || choice === "hide" ? "hidden" : choice === "keep" ? "keep" : prior?.disposition === "hidden" || term.disposition === "hidden" ? "hidden" : "pending";
1674
2032
  const sources = [...new Map([...prior?.sources ?? [], ...term.sources].map((source) => [JSON.stringify(source.kind === "session" ? [
1675
2033
  source.workId,
1676
2034
  source.batchId,
@@ -1680,7 +2038,7 @@ function mergePrivacyTerms(state, incoming) {
1680
2038
  ...term,
1681
2039
  disposition,
1682
2040
  sources,
1683
- reason: prior && prior.disposition !== term.disposition ? disposition === "pending" ? "Conflicting scan judgments require a contextual decision." : disposition === "hidden" ? "A Hidden judgment for this keyword takes precedence." : term.reason : term.reason
2041
+ reason: term.disposition === "keep" && disposition === "pending" ? "A current decision is required to keep this value." : choice && term.disposition !== disposition ? "The current value or category decision applies." : prior && prior.disposition !== term.disposition ? disposition === "pending" ? "Conflicting scan judgments require a contextual decision." : disposition === "hidden" ? "A Hidden judgment for this keyword takes precedence." : term.reason : term.reason
1684
2042
  });
1685
2043
  }
1686
2044
  const values = Map.groupBy([...terms.values()], (term) => term.value.normalize("NFC"));
@@ -1715,34 +2073,125 @@ function activePrivacyTerms(state) {
1715
2073
  return state.works.some((work) => work.access !== "drop" && work.rootSessionIds.length > 0) ? state.privacy.terms : [];
1716
2074
  }
1717
2075
  //#endregion
1718
- //#region packages/application/src/journey/policy/commands.ts
1719
- const journeyFindingReferenceSchema = z.object({
1720
- batchId: journeyIdSchema,
1721
- findingId: journeyIdSchema
1722
- }).strict();
1723
- /** Exact original values as `journey.groupItems` lists them; never trimmed or normalized. */
1724
- const journeyExactValuesSchema = z.array(z.string().min(1).max(240)).min(1).max(256);
1725
- const partFields = {
1726
- groupId: journeyIdSchema,
1727
- groupKey: z.string().trim().min(1).max(120),
1728
- label: z.string().trim().min(1).max(120),
1729
- condition: z.string().trim().min(1).max(1e3),
1730
- choice: journeyChoiceSchema.nullable()
1731
- };
1732
- const journeyGroupPartSchema = z.object({
1733
- ...partFields,
1734
- members: z.array(journeyFindingReferenceSchema).min(1).max(1e4)
1735
- }).strict();
1736
- /** A split part names its members exactly, by exact value, or as the rest of the group. */
1737
- const journeySplitPartSchema = z.union([
1738
- journeyGroupPartSchema,
1739
- z.object({
1740
- ...partFields,
1741
- values: journeyExactValuesSchema
1742
- }).strict(),
1743
- z.object({
1744
- ...partFields,
1745
- rest: z.literal(true)
2076
+ //#region packages/application/src/export/versions.ts
2077
+ /** Current built-in admission, annotation and retention contracts; implementations share these. */
2078
+ const nativeAdapterVersions = Object.freeze({
2079
+ claude: "claude-native-export-v19",
2080
+ codex: "codex-native-export-v16"
2081
+ });
2082
+ /** Shared metadata annotation/preparation semantics, independent of serialization formats. */
2083
+ const metadataProcessingVersion = "metadata-annotations-v1";
2084
+ //#endregion
2085
+ //#region packages/application/src/journey/adapters.ts
2086
+ /** The adapter contracts a Journey's snapshot is prepared under. */
2087
+ const currentJourneyAdapters = () => ({
2088
+ claude: `${nativeAdapterVersions.claude}/main-prose-source-scan-v3`,
2089
+ codex: `${nativeAdapterVersions.codex}/main-prose-source-scan-v3`
2090
+ });
2091
+ /**
2092
+ * An unfinished snapshot frozen under other adapter contracts (absent: an earlier release), or
2093
+ * read by B review instead of dictionary review (ADR 0086), cannot render its Works (ADR 0076).
2094
+ * A new scan replaces it unless a Work was sent or a Send may have reached Passport, which the
2095
+ * store keeps for the person to finish (ADR 0284).
2096
+ */
2097
+ function obsoleteJourney(state) {
2098
+ return state.phase !== "completed" && state.snapshotId !== null && (state.adapters?.claude !== currentJourneyAdapters().claude || state.adapters.codex !== currentJourneyAdapters().codex || state.reviewMethod !== "dictionary");
2099
+ }
2100
+ /** A receipt or persisted session mutation intent means a Send may have reached Passport. */
2101
+ function sendReachedPassport(send) {
2102
+ return send.items.some((item) => item.receipt || item.previousReceipts.length || item.sessionUpload !== void 0 || item.sessionMembership || item.sessionAssembly);
2103
+ }
2104
+ /**
2105
+ * ADR 0284: Passport acknowledged this Send's Work: this Send's receipt for it is past
2106
+ * `awaiting_upload`, or its session membership is acknowledged. An `awaiting_upload` receipt is
2107
+ * an empty reservation, and a reservation, a session upload intent or a membership intent is saved
2108
+ * before the call it announces, so none of them shows that anything left.
2109
+ */
2110
+ function itemAtPassport(item) {
2111
+ return Boolean(item.receipt && item.receipt.state !== "awaiting_upload" || item.sessionAssembly);
2112
+ }
2113
+ /**
2114
+ * The Works the next advance steps without the person to send something: an upload, a
2115
+ * completion or a confirmed access change. `pickBatch` selects upload work by this; a Work being
2116
+ * assessed is only read, and the other states wait on the person or have ended.
2117
+ */
2118
+ function sendsOnNextAdvance(item) {
2119
+ return item.status === "uploading" || item.status === "ready";
2120
+ }
2121
+ /**
2122
+ * A completed Send that Passport has not finished with: a Work is still assessed or reapplied
2123
+ * (ADR 0233). A blocked Work waits on the person's Retry, not on Passport.
2124
+ */
2125
+ function awaitingPassport(send) {
2126
+ return send.status === "completed" && send.items.some((item) => !["completed", "blocked"].includes(item.status));
2127
+ }
2128
+ /**
2129
+ * Whether an advance steps this Send's Works at all. `advanceSend` returns early exactly when this
2130
+ * is false: the person paused the journey, the Send is unconfirmed, paused or prepared, or it is
2131
+ * completed with every Work settled. A blocked Send is stepped only to re-check its scope.
2132
+ */
2133
+ function advanceStepsSend(journey, send) {
2134
+ return journey.phase !== "paused" && send.confirmedAt !== null && send.status !== "paused" && send.status !== "prepared" && !(send.status === "completed" && send.items.every((item) => item.status === "completed" || item.status === "blocked"));
2135
+ }
2136
+ /**
2137
+ * Whether a Send in each state is stepped without the person once `advanceStepsSend` holds.
2138
+ * Every status is listed, so a new one fails the type check until it is classified. Unconfirmed
2139
+ * requests and the person's pause, refusal, sign-in and review decisions stay explicit.
2140
+ */
2141
+ const drivenWithoutPerson = {
2142
+ "awaiting-packaging": false,
2143
+ prepared: false,
2144
+ "sign-in": false,
2145
+ review: false,
2146
+ paused: false,
2147
+ blocked: false,
2148
+ sending: true,
2149
+ waiting: true,
2150
+ completed: true
2151
+ };
2152
+ /**
2153
+ * The one decision whether the advance steps a Send without the person: the detached host's
2154
+ * timer drives exactly these Sends, and a CLI step defers to a live host for them (ADR 0271).
2155
+ */
2156
+ function hostDrives(journey, send) {
2157
+ return advanceStepsSend(journey, send) && drivenWithoutPerson[send.status];
2158
+ }
2159
+ /**
2160
+ * ADR 0284: a Work at Passport still owes Passport something that this Send's next advance sends
2161
+ * without the person. An obsolete journey gives way unless its current Send owes.
2162
+ */
2163
+ function sendOwesPassport(journey, send) {
2164
+ return hostDrives(journey, send) && send.items.some((item) => itemAtPassport(item) && sendsOnNextAdvance(item));
2165
+ }
2166
+ //#endregion
2167
+ //#region packages/application/src/journey/policy/commands.ts
2168
+ const journeyFindingReferenceSchema = z.object({
2169
+ batchId: journeyIdSchema,
2170
+ findingId: journeyIdSchema
2171
+ }).strict();
2172
+ /** Exact original values as `journey.groupItems` lists them; never trimmed or normalized. */
2173
+ const journeyExactValuesSchema = z.array(z.string().min(1).max(240)).min(1).max(256);
2174
+ const partFields = {
2175
+ groupId: journeyIdSchema,
2176
+ groupKey: z.string().trim().min(1).max(120),
2177
+ label: z.string().trim().min(1).max(120),
2178
+ condition: z.string().trim().min(1).max(1e3),
2179
+ choice: journeyChoiceSchema.nullable()
2180
+ };
2181
+ const journeyGroupPartSchema = z.object({
2182
+ ...partFields,
2183
+ members: z.array(journeyFindingReferenceSchema).min(1).max(1e4)
2184
+ }).strict();
2185
+ /** A split part names its members exactly, by exact value, or as the rest of the group. */
2186
+ const journeySplitPartSchema = z.union([
2187
+ journeyGroupPartSchema,
2188
+ z.object({
2189
+ ...partFields,
2190
+ values: journeyExactValuesSchema
2191
+ }).strict(),
2192
+ z.object({
2193
+ ...partFields,
2194
+ rest: z.literal(true)
1746
2195
  }).strict()
1747
2196
  ]);
1748
2197
  const journeySplitActionSchema = z.object({
@@ -2067,6 +2516,7 @@ const nativeRespondSchema = z.object({
2067
2516
  const nativeEventsSchema = z.object({
2068
2517
  journeyId: journeyIdSchema,
2069
2518
  after: z.number().int().nonnegative(),
2519
+ changesOnly: z.boolean().optional(),
2070
2520
  waitMs: z.number().int().min(0).max(3e4).default(0)
2071
2521
  }).strict();
2072
2522
  //#endregion
@@ -2141,6 +2591,58 @@ function assertPublicChoices(state) {
2141
2591
  function assertJourneyStaticReady(state) {
2142
2592
  if (state.works.some((work) => work.access !== "drop" && work.rootSessionIds.length > 0 && !isSkippedWork(work) && !hasCurrentStaticReview(state, work))) throw new ApplicationError("SCOPE_NOT_READY");
2143
2593
  }
2594
+ /** Check a proposed or stored Keep against the current structured decisions. An answered
2595
+ * card covers its actual members, including the opposite choice for an unticked member. */
2596
+ function keepAuthority(state) {
2597
+ const rules = currentTermRules(state);
2598
+ const { keep, hide } = answeredValues(state);
2599
+ const mandatory = new Set(state.privacy.terms.filter((term) => !termTypeInfo[term.type].asked).map((term) => term.value.normalize("NFC")));
2600
+ const own = workProjectNames(state);
2601
+ return (term) => {
2602
+ const value = term.value.normalize("NFC");
2603
+ if (mandatory.has(value)) return "hidden";
2604
+ const override = rules.overrides.get(value);
2605
+ if (override) return override.choice === "keep" ? "keep" : "hidden";
2606
+ if (keep.has(value) && own.has(value.toLowerCase())) return "keep";
2607
+ if (hide.has(value)) return "hidden";
2608
+ if (keep.has(value)) return "keep";
2609
+ const choice = rules.choice(term);
2610
+ return choice === "keep" ? "keep" : choice === "hide" ? "hidden" : "pending";
2611
+ };
2612
+ }
2613
+ const reconciledKeeps = /* @__PURE__ */ new WeakMap();
2614
+ /** Recover unresolved visibility in a stored unfinished journey without repeating source reads. */
2615
+ function reconcileReaderKeeps(state) {
2616
+ if (state.phase === "completed") return state;
2617
+ const cached = reconciledKeeps.get(state);
2618
+ if (cached) return cached;
2619
+ let result = state;
2620
+ if (state.privacy.terms.some((term) => term.disposition === "keep")) {
2621
+ const authority = keepAuthority(state);
2622
+ let changed = false;
2623
+ const terms = state.privacy.terms.map((term) => {
2624
+ if (term.disposition !== "keep") return term;
2625
+ const disposition = authority(term);
2626
+ if (disposition === "keep") return term;
2627
+ changed = true;
2628
+ return {
2629
+ ...term,
2630
+ disposition,
2631
+ reason: "Current privacy decisions require review of this value."
2632
+ };
2633
+ });
2634
+ if (changed) result = {
2635
+ ...retirePrivacyArtifacts(state),
2636
+ privacy: {
2637
+ ...state.privacy,
2638
+ terms,
2639
+ packageApproval: null
2640
+ }
2641
+ };
2642
+ }
2643
+ reconciledKeeps.set(state, result);
2644
+ return result;
2645
+ }
2144
2646
  /**
2145
2647
  * Cards need only Track A, AI review and metadata; this final gate also needs Track B.
2146
2648
  * `publicChoices` defaults to `packaging`: the Send click checks it before packaging, and the
@@ -2149,7 +2651,7 @@ function assertJourneyStaticReady(state) {
2149
2651
  function assertJourneyPrivacyReady(state, packaging = false, { publicChoices = packaging } = {}) {
2150
2652
  assertJourneyScanned(state);
2151
2653
  if (publicChoices) assertPublicChoices(state);
2152
- if (activePrivacyTerms(state).some((term) => term.disposition === "pending") || state.privacy.cards.length > 0 && (state.privacy.cards.some((card) => !card.answer) || state.privacy.confirmedAnswersRevision !== state.privacy.answersRevision)) throw new ApplicationError("CONFIRMATION_REQUIRED");
2654
+ if (reconcileReaderKeeps(state) !== state || activePrivacyTerms(state).some((term) => term.disposition === "pending") || state.privacy.cards.length > 0 && (state.privacy.cards.some((card) => !card.answer) || state.privacy.confirmedAnswersRevision !== state.privacy.answersRevision)) throw new ApplicationError("CONFIRMATION_REQUIRED");
2153
2655
  assertJourneyStaticReady(state);
2154
2656
  if (packaging && state.privacy.packageApproval !== privacyDecisionDigest(state)) throw new ApplicationError("CONFIRMATION_REQUIRED");
2155
2657
  }
@@ -2169,7 +2671,7 @@ function ownIdentityValues(state) {
2169
2671
  }
2170
2672
  /**
2171
2673
  * D3: a batch covers only Pending values no card covers yet. Values added to an existing card
2172
- * take its answer at once when the person already answered it.
2674
+ * take its answer, except new literals on an answered Keep card arrive Hidden off-card.
2173
2675
  */
2174
2676
  function appendDecisionCards(state, input) {
2175
2677
  assertCardsAllowed(state);
@@ -2196,12 +2698,27 @@ function appendDecisionCards(state, input) {
2196
2698
  if (term?.type === "people" && ownCard && !ownValues.has(identityValue(term.value))) throw new ApplicationError("INVALID_SELECTION", "cards.append.other-person");
2197
2699
  }
2198
2700
  }
2199
- const additions = new Map(extend.map((item) => [item.cardId, item.termKeys]));
2200
- const inherited = new Map(extend.flatMap((item) => item.termKeys.map((key) => [key, item.cardId])));
2701
+ const keepCards = state.privacy.cards.filter((card) => card.answer?.optionId === "keep");
2702
+ const keptValues = new Set(keepCards.flatMap((card) => card.termKeys.flatMap((key) => {
2703
+ const term = terms.get(key);
2704
+ return term?.disposition === "keep" && !card.answer?.excludedTermKeys?.includes(key) ? [term.value.normalize("NFC")] : [];
2705
+ })));
2706
+ const diverted = new Set(extend.flatMap((item) => keepCards.some((card) => card.id === item.cardId) ? item.termKeys.filter((key) => !keptValues.has(terms.get(key)?.value.normalize("NFC") ?? "")) : []));
2707
+ const joined = extend.map((item) => ({
2708
+ ...item,
2709
+ termKeys: item.termKeys.filter((key) => !diverted.has(key))
2710
+ }));
2711
+ const additions = new Map(joined.map((item) => [item.cardId, item.termKeys]));
2712
+ const inherited = new Map(joined.flatMap((item) => item.termKeys.map((key) => [key, item.cardId])));
2201
2713
  return applyCardAnswers({
2202
2714
  ...retirePrivacyArtifacts(state),
2203
2715
  privacy: {
2204
2716
  ...state.privacy,
2717
+ terms: state.privacy.terms.map((term) => diverted.has(privacyTermKey(term)) ? {
2718
+ ...term,
2719
+ disposition: "hidden",
2720
+ reason: "Found after you answered Keep on its card; hidden."
2721
+ } : term),
2205
2722
  cards: [...state.privacy.cards.map((card) => {
2206
2723
  const added = additions.get(card.id);
2207
2724
  if (!added) return card;
@@ -2264,11 +2781,14 @@ function resolvePrivacyTerms(state, input) {
2264
2781
  terms
2265
2782
  }
2266
2783
  }), (term) => term.value.normalize("NFC")).values()) if (same.some((term) => term.disposition === "pending") && input.resolutions.some((item) => item.value.normalize("NFC") === same[0]?.value.normalize("NFC"))) throw new ApplicationError("INVALID_SELECTION");
2784
+ const normalized = normalizeValues(state, terms);
2785
+ const canKeep = keepAuthority(state);
2786
+ if (normalized.some((term) => resolutions.has(privacyTermKey(term)) && term.disposition === "keep" && canKeep(term) !== "keep")) throw new ApplicationError("CONFIRMATION_REQUIRED");
2267
2787
  return {
2268
2788
  ...retirePrivacyArtifacts(state),
2269
2789
  privacy: {
2270
2790
  ...state.privacy,
2271
- terms: normalizeValues(state, terms),
2791
+ terms: normalized,
2272
2792
  packageApproval: null
2273
2793
  }
2274
2794
  };
@@ -2281,6 +2801,34 @@ function answeredKeys(state, optionId) {
2281
2801
  return card.answer.optionId === optionId ? card.termKeys.filter((key) => !excluded.has(key)) : [...excluded];
2282
2802
  }));
2283
2803
  }
2804
+ /**
2805
+ * Values the person hid with their own panel answer, still on that card and Hidden now; the next
2806
+ * journey hides them on arrival (issue #126). Never sent anywhere.
2807
+ */
2808
+ function rememberedHideValues(state) {
2809
+ const keys = new Set(state.privacy.cards.flatMap((card) => {
2810
+ if (card.answer?.optionId !== "hide" || card.answer.source !== "panel") return [];
2811
+ const excluded = new Set(card.answer.excludedTermKeys ?? []);
2812
+ return card.termKeys.filter((key) => !excluded.has(key));
2813
+ }));
2814
+ return [...new Set(state.privacy.terms.filter((term) => term.disposition === "hidden" && keys.has(privacyTermKey(term))).map((term) => term.value.normalize("NFC")))];
2815
+ }
2816
+ function answeredValues(state) {
2817
+ const keepKeys = answeredKeys(state, "keep");
2818
+ const hideKeys = answeredKeys(state, "hide");
2819
+ const keep = /* @__PURE__ */ new Set();
2820
+ const hide = /* @__PURE__ */ new Set();
2821
+ for (const term of state.privacy.terms) {
2822
+ const key = privacyTermKey(term);
2823
+ const value = term.value.normalize("NFC");
2824
+ if (keepKeys.has(key)) keep.add(value);
2825
+ if (hideKeys.has(key)) hide.add(value);
2826
+ }
2827
+ return {
2828
+ keep,
2829
+ hide
2830
+ };
2831
+ }
2284
2832
  /** One NFC value has one outcome across term types, cards and per-value rules. */
2285
2833
  function normalizeValues(state, input) {
2286
2834
  const active = activePrivacyTerms({
@@ -2290,17 +2838,8 @@ function normalizeValues(state, input) {
2290
2838
  terms: input
2291
2839
  }
2292
2840
  });
2293
- const overrides = /* @__PURE__ */ new Map();
2294
- for (const item of state.valueOverrides ?? []) {
2295
- const value = item.value.normalize("NFC");
2296
- if (!overrides.has(value)) overrides.set(value, item);
2297
- }
2298
- const cardValues = (optionId) => {
2299
- const keys = answeredKeys(state, optionId);
2300
- return new Set(state.privacy.terms.filter((term) => keys.has(privacyTermKey(term))).map((term) => term.value.normalize("NFC")));
2301
- };
2302
- const hideCardValues = cardValues("hide");
2303
- const keepCardValues = cardValues("keep");
2841
+ const { overrides } = currentTermRules(state);
2842
+ const { keep: keepCardValues, hide: hideCardValues } = answeredValues(state);
2304
2843
  const ownNames = workProjectNames(state);
2305
2844
  const normalized = /* @__PURE__ */ new Map();
2306
2845
  for (const same of Map.groupBy(active, (term) => term.value.normalize("NFC")).values()) {
@@ -2372,6 +2911,7 @@ function respondPrivacy(state, action, source) {
2372
2911
  rule: [option.rule, action.rule?.trim()].filter(Boolean).join("\n"),
2373
2912
  additionalRule: action.rule?.trim() ?? "",
2374
2913
  remember: action.remember,
2914
+ source,
2375
2915
  ...excluded.length ? { excludedTermKeys: excluded } : {}
2376
2916
  }
2377
2917
  });
@@ -2404,6 +2944,7 @@ function respondPrivacy(state, action, source) {
2404
2944
  };
2405
2945
  }
2406
2946
  if (action.kind === "package-confirm") {
2947
+ if (obsoleteJourney(state)) throw new ApplicationError("JOURNEY_OBSOLETE");
2407
2948
  assertPublicChoices(state);
2408
2949
  assertJourneyPrivacyReady(state);
2409
2950
  return {
@@ -2552,7 +3093,9 @@ const journeyClassificationReadSchema = z.object({
2552
3093
  journeyId: journeyIdSchema,
2553
3094
  workId: journeyIdSchema,
2554
3095
  rootSessionId: z.string().min(1).max(128).optional(),
2555
- cursor: classificationCursorSchema.optional()
3096
+ cursor: classificationCursorSchema.optional(),
3097
+ /** Classification-only prefix; omitted callers retain full capture verification. */
3098
+ limit: z.number().int().min(1).max(8).optional()
2556
3099
  }).strict();
2557
3100
  const journeyClassificationSubmitSchema = z.object({
2558
3101
  journeyId: journeyIdSchema,
@@ -2579,6 +3122,12 @@ const journeyClassificationSubmitSchema = z.object({
2579
3122
  */
2580
3123
  expectedClassificationRevision: count$10.optional()
2581
3124
  }).strict();
3125
+ /** Restore only the split families that can be proven against the accepted original proposal. */
3126
+ const isolatedMetadataRecoverySchema = z.object({
3127
+ proposal: journeyClassificationSubmitSchema,
3128
+ workIds: z.array(journeyIdSchema).min(1).max(1e3),
3129
+ idempotencyKey: journeyIdSchema
3130
+ }).strict();
2582
3131
  const journeyClassificationSessionsSchema = z.object({
2583
3132
  journeyId: journeyIdSchema,
2584
3133
  workId: journeyIdSchema,
@@ -2716,401 +3265,86 @@ const correctionReportSchema = z.object({
2716
3265
  groupId: journeyIdSchema,
2717
3266
  kind: journeyKindSchema,
2718
3267
  reason: z.string().trim().min(1).max(1e3),
2719
- anchors: z.array(correctionAnchorSchema).min(1).max(256)
2720
- }).strict();
2721
- const reviewCorrectSchema = z.object({
2722
- journeyId: journeyIdSchema,
2723
- expectedRevision: z.number().int().nonnegative(),
2724
- idempotencyKey: journeyIdSchema,
2725
- report: correctionReportSchema
2726
- }).strict();
2727
- /** Immutable private report receipt; the only place where correction quotes are retained. */
2728
- const correctionReportResourceSchema = z.object({
2729
- journeyId: journeyIdSchema,
2730
- kind: z.literal("correction-report"),
2731
- id: journeyIdSchema,
2732
- requestHash: journeyDigestSchema,
2733
- at: z.iso.datetime(),
2734
- snapshotId: journeyDigestSchema,
2735
- batchDigest: journeyDigestSchema,
2736
- decisionDigest: journeyDigestSchema,
2737
- report: correctionReportSchema
2738
- }).strict();
2739
- const correctionAuditSchema = z.object({
2740
- reportId: journeyIdSchema,
2741
- action: z.enum([
2742
- "report",
2743
- "hide",
2744
- "dismiss"
2745
- ]),
2746
- actor: z.enum([
2747
- "observer",
2748
- "human",
2749
- "delegate"
2750
- ]),
2751
- authorityId: journeyIdSchema.nullable(),
2752
- decisionDigest: journeyDigestSchema
2753
- }).strict();
2754
- //#endregion
2755
- //#region packages/application/src/journey/correction/binding.ts
2756
- function workCorrections(state, workId) {
2757
- return (state.corrections ?? []).filter((item) => item.workId === workId);
2758
- }
2759
- /** Binds rendered output and generated metadata to every correction disposition of the Work. */
2760
- function workCorrectionBinding(state, workId) {
2761
- const corrections = workCorrections(state, workId);
2762
- return corrections.length ? corrections.map(({ id, groupId, inputRevision, batchId, status, disposition }) => ({
2763
- id,
2764
- groupId,
2765
- inputRevision,
2766
- batchId,
2767
- status,
2768
- choice: disposition?.choice ?? null,
2769
- decisionDigest: disposition?.decisionDigest ?? null
2770
- })) : null;
2771
- }
2772
- //#endregion
2773
- //#region packages/application/src/journey/placement/decisions.ts
2774
- function workPrivacyDecisions(state, work) {
2775
- return state.groups.filter((group) => work.groupIds.includes(group.id)).map(({ id, key, kind, label, condition, choice, confirmed }) => ({
2776
- id,
2777
- key,
2778
- kind,
2779
- label,
2780
- condition,
2781
- choice,
2782
- confirmed
2783
- })).sort((a, b) => a.id.localeCompare(b.id));
2784
- }
2785
- /** Corrections are additive under an unchanged Hide group, so they bind metadata review too. */
2786
- function workDecisionDigest(state, work) {
2787
- const decisions = workPrivacyDecisions(state, work);
2788
- const corrections = workCorrectionBinding(state, work.id);
2789
- return textDigest(JSON.stringify(corrections ? {
2790
- decisions,
2791
- corrections
2792
- } : decisions));
2793
- }
2794
- /** Retire generated text until it is reviewed; the processed project name keeps rows distinct. */
2795
- function retireWorkDescription(work) {
2796
- return {
2797
- ...work,
2798
- suggestion: null
2799
- };
2800
- }
2801
- //#endregion
2802
- //#region packages/application/src/journey/terms/groups.ts
2803
- const keyPrefix = "type:";
2804
- const termGroupKey = (type) => `${keyPrefix}${type}`;
2805
- /** RFC 9562 UUID version 5 of `name` in the `namespace` UUID. */
2806
- function uuidV5(namespace, name) {
2807
- const bytes = createHash("sha1").update(Buffer.from(namespace.replaceAll("-", ""), "hex")).update(name, "utf8").digest().subarray(0, 16);
2808
- bytes.writeUInt8(bytes.readUInt8(6) & 15 | 80, 6);
2809
- bytes.writeUInt8(bytes.readUInt8(8) & 63 | 128, 8);
2810
- const hex = bytes.toString("hex");
2811
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
2812
- }
2813
- /** Deterministic: every reader and writer of a journey names a type's group alike. */
2814
- const termGroupId = (journeyId, type) => uuidV5(journeyId, type);
2815
- /** The term type a group answers, or null for any other category. */
2816
- function termGroupType(group) {
2817
- if (!group.key.startsWith(keyPrefix)) return null;
2818
- const parsed = termTypeSchema.safeParse(group.key.slice(5));
2819
- return parsed.success ? parsed.data : null;
2820
- }
2821
- const typeGroup = (state, type) => state.groups.find((group) => termGroupType(group) === type);
2822
- function newTypeGroup(state, type, preference) {
2823
- const info = termTypeInfo[type];
2824
- const base = {
2825
- id: termGroupId(state.journeyId, type),
2826
- kind: info.kind,
2827
- key: termGroupKey(type),
2828
- label: info.label,
2829
- condition: info.condition,
2830
- count: 0,
2831
- preferenceScope: null,
2832
- representative: null
2833
- };
2834
- if (!info.asked) return {
2835
- ...base,
2836
- belief: .97,
2837
- choice: "hide",
2838
- confirmed: true,
2839
- origin: "policy"
2840
- };
2841
- if (state.questionsPaused) return {
2842
- ...base,
2843
- belief: .97,
2844
- choice: "hide",
2845
- confirmed: false,
2846
- origin: "paused"
2847
- };
2848
- return {
2849
- ...base,
2850
- belief: preference === null ? .5 : preference === "hide" ? .97 : .03,
2851
- choice: preference,
2852
- confirmed: false,
2853
- origin: preference === null ? "model" : "preference"
2854
- };
2855
- }
2856
- /**
2857
- * One group per type with at least one dictionary value, `count` its distinct values. Existing
2858
- * groups keep their identity and answer; other groups are unchanged.
2859
- */
2860
- function syncTermGroups(state, counts, preference) {
2861
- const groups = state.groups.map((group) => {
2862
- const type = termGroupType(group);
2863
- return type === null ? group : {
2864
- ...group,
2865
- count: counts.get(type) ?? group.count
2866
- };
2867
- });
2868
- for (const type of termTypes) {
2869
- const count = counts.get(type) ?? 0;
2870
- if (count === 0 || groups.some((group) => termGroupType(group) === type)) continue;
2871
- groups.push({
2872
- ...newTypeGroup(state, type, preference(termGroupKey(type))),
2873
- count
2874
- });
2875
- }
2876
- return groups;
2877
- }
2878
- //#endregion
2879
- //#region packages/application/src/journey/review/confirmed-hides.ts
2880
- /** Kinds from lowest to highest mask priority. */
2881
- const confirmedHideKinds = [
2882
- "internal",
2883
- "identity",
2884
- "private",
2885
- "credentials"
2886
- ];
2887
- const kinds = confirmedHideKinds;
2888
- const rank$1 = (kind) => kinds.indexOf(kind);
2889
- /**
2890
- * ADR 0086: a type's values are hidden unless its group is answered Keep; before an answer the
2891
- * default is Hide. Credentials and contact details are always hidden (D06).
2892
- */
2893
- function typeHides(state, type) {
2894
- const group = typeGroup(state, type);
2895
- return !termTypeInfo[type].asked || !group?.confirmed || group.choice !== "keep";
2896
- }
2897
- /** B review (superseded by ADR 0086): IDs of the categories confirmed as Hide. */
2898
- const confirmedHideGroupIds = (state) => state.groups.filter((group) => group.confirmed && group.choice === "hide").map((group) => group.id);
2899
- const overrideKey = (state) => (state.valueOverrides ?? []).map((item) => JSON.stringify([
2900
- item.type,
2901
- item.value,
2902
- item.choice
2903
- ])).toSorted();
2904
- /**
2905
- * Identifies the hidden set (ADR 0079, 0086). The dictionary only grows and a type's group
2906
- * counts its distinct values, so each type's count and Hide answer, with the value overrides,
2907
- * name exactly one hidden set.
2908
- */
2909
- function confirmedHideKey(state) {
2910
- return createHash("sha256").update(JSON.stringify({
2911
- types: termTypes.map((type) => [
2912
- type,
2913
- typeGroup(state, type)?.count ?? 0,
2914
- typeHides(state, type)
2915
- ]),
2916
- overrides: overrideKey(state),
2917
- terms: state.privacy.terms.filter((term) => term.disposition === "hidden").map((term) => [term.type, term.value]).toSorted((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)))
2918
- })).digest("hex");
2919
- }
2920
- /** Whether two states hold the same value overrides. */
2921
- const sameValueOverrides = (left, right) => JSON.stringify(overrideKey(left)) === JSON.stringify(overrideKey(right));
2922
- /**
2923
- * A preparation's waived hits stay waived only while every category recorded for them is still a
2924
- * confirmed Hide; otherwise render may no longer mask them, so the session needs reconciliation.
2925
- */
2926
- function maskedHitsStale(maskedHits, hiding) {
2927
- const current = new Set(hiding);
2928
- return !!maskedHits?.some((hit) => hit.groupIds.some((id) => !current.has(id)));
2929
- }
2930
- /**
2931
- * ADR 0086 hidden set: values of types not answered Keep, minus Keep overrides, plus Hide
2932
- * overrides, plus every value of an always-hidden type. A value of several types takes the
2933
- * highest mask kind among the types hiding it.
2934
- */
2935
- function dictionaryHides(state, terms) {
2936
- const overrides = state.valueOverrides ?? [];
2937
- const kept = new Set(overrides.flatMap((item) => item.choice === "keep" ? [item.value] : []));
2938
- const hidden = /* @__PURE__ */ new Map();
2939
- const add = (value, type) => {
2940
- const prior = hidden.get(value);
2941
- const kind = termTypeInfo[type].kind;
2942
- hidden.set(value, {
2943
- kind: prior && rank$1(prior.kind) > rank$1(kind) ? prior.kind : kind,
2944
- groupIds: [.../* @__PURE__ */ new Set([...prior?.groupIds ?? [], termGroupId(state.journeyId, type)])].sort()
2945
- });
2946
- };
2947
- const all = [...terms];
2948
- const hiddenValues = new Set(state.privacy.terms.filter((item) => item.disposition === "hidden").map((item) => item.value));
2949
- for (const term of all) if (hiddenValues.has(term.value) && !kept.has(term.value)) add(term.value, term.type);
2950
- for (const item of overrides) if (item.choice === "hide") add(item.value, item.type);
2951
- for (const term of all) if (!termTypeInfo[term.type].asked) add(term.value, term.type);
2952
- return hidden;
2953
- }
2954
- /** The journey's current hidden set, applied to every selected Work (ADR 0086, D03). */
2955
- async function journeyConfirmedHides(reader, state) {
2956
- return dictionaryHides(state, [...await reader.journeyTerms({ journeyId: state.journeyId }), ...state.privacy.terms]);
2957
- }
2958
- function confirmedHideTerms(groups, records) {
2959
- const confirmed = new Map(groups.filter((group) => group.confirmed && group.choice === "hide").map((group) => [group.id, group]));
2960
- const fields = /* @__PURE__ */ new Map();
2961
- for (const record of records) {
2962
- const assigned = new Map(record.assignments.map((item) => [item.findingId, item.groupId]));
2963
- const chunks = new Map(record.chunks.map((chunk) => [chunk.id, chunk]));
2964
- for (const finding of record.findings) {
2965
- const group = confirmed.get(assigned.get(finding.id) ?? "");
2966
- const chunk = chunks.get(finding.chunkId);
2967
- if (!group || !chunk || finding.class !== "gray") continue;
2968
- const key = JSON.stringify([chunk.fieldId, chunk.fieldHash]);
2969
- fields.set(key, [...fields.get(key) ?? [], {
2970
- start: chunk.offset + finding.start,
2971
- end: chunk.offset + finding.end,
2972
- text: chunk.text.slice(finding.start, finding.end),
2973
- split: [finding.start === 0, finding.end === chunk.text.length],
2974
- group
2975
- }]);
2976
- }
2977
- }
2978
- const terms = /* @__PURE__ */ new Map();
2979
- const add = (text, groupIds, kind) => {
2980
- for (const term of bLiteralTerms(text)) {
2981
- const prior = terms.get(term);
2982
- terms.set(term, {
2983
- kind: prior && rank$1(prior.kind) > rank$1(kind) ? prior.kind : kind,
2984
- groupIds: [.../* @__PURE__ */ new Set([...prior?.groupIds ?? [], ...groupIds])].sort()
2985
- });
2986
- }
2987
- };
2988
- for (const spans of fields.values()) {
2989
- let open;
2990
- for (const span of spans.toSorted((a, b) => a.start - b.start || b.end - a.end)) {
2991
- if (open && span.split[0] && span.start === open.end) {
2992
- open.text += span.text;
2993
- open.end = span.end;
2994
- open.groupIds.push(span.group.id);
2995
- } else {
2996
- if (open) add(open.text, open.groupIds, open.kind);
2997
- open = {
2998
- end: span.end,
2999
- text: span.text,
3000
- groupIds: [span.group.id],
3001
- kind: span.group.kind
3002
- };
3003
- }
3004
- if (!span.split[1]) {
3005
- add(open.text, open.groupIds, open.kind);
3006
- open = void 0;
3007
- }
3008
- }
3009
- if (open) add(open.text, open.groupIds, open.kind);
3010
- }
3011
- return terms;
3012
- }
3013
- //#endregion
3014
- //#region packages/application/src/journey/b/matches.ts
3015
- function bDictionaryHits(fields, literals, signal = new AbortController().signal) {
3016
- const matcher = createEntityMatcher([...new Set(literals.map((part) => part.term))].map((value) => ({
3017
- kind: "custom-term",
3018
- value
3019
- })));
3020
- const hits = [];
3021
- for (const field of fields) {
3022
- const index = createCodePointIndex(field.text);
3023
- const base = field.start ?? 0;
3024
- for (const match of matcher.find(field.text, signal, 1e5)) {
3025
- if (hits.length >= 1e6) throw new ApplicationError("PROCESSING_LIMIT");
3026
- const body = {
3027
- fieldId: field.id,
3028
- fieldHash: field.fieldHash,
3029
- recordId: field.recordId,
3030
- start: base + index.codePointOffset(match.range.start),
3031
- end: base + index.codePointOffset(match.range.end),
3032
- exactQuote: field.text.slice(match.range.start, match.range.end),
3033
- term: match.entity.value
3034
- };
3035
- hits.push({
3036
- ...body,
3037
- digest: bDigest(JSON.stringify(body))
3038
- });
3039
- }
3040
- }
3041
- return [...new Map(hits.map((hit) => [hit.digest, hit])).values()];
3042
- }
3043
- function unclassifiedBHits(hits, literals, negative) {
3044
- const issues = [];
3045
- const positiveFields = new Map([...Map.groupBy(literals, (part) => part.fieldId)].map(([id, parts]) => [id, indexBRanges(parts)]));
3046
- const negativeFields = new Map([...Map.groupBy(negative, (part) => part.fieldId)].map(([id, parts]) => [id, indexBRanges(parts)]));
3047
- for (const hit of hits) {
3048
- const positive = positiveFields.get(hit.fieldId)?.relation(hit).covers ?? false;
3049
- const negative = negativeFields.get(hit.fieldId)?.relation(hit);
3050
- if (!positive && !negative?.covers || positive && negative?.overlaps) issues.push(bIssue(positive ? "CONFLICTING_PRIVACY_DECISIONS" : "UNCLASSIFIED_MATCH", hit));
3051
- }
3052
- return issues;
3053
- }
3054
- const bSpacedWord = (value) => /[\p{L}\p{N}_]/u.test(value) && !/[\p{sc=Han}\p{sc=Hiragana}\p{sc=Katakana}\p{sc=Thai}\p{sc=Lao}\p{sc=Khmer}\p{sc=Myanmar}]/u.test(value);
3055
- /** A confirmed Hide value matches only where it does not run into a neighbouring word (ADR 0069). */
3056
- function bWordBounded(text, start, end) {
3057
- return !(bSpacedWord(scalarBefore(text, start)) && bSpacedWord(scalarAfter(text, start)) || bSpacedWord(scalarBefore(text, end)) && bSpacedWord(scalarAfter(text, end)));
3268
+ anchors: z.array(correctionAnchorSchema).min(1).max(256)
3269
+ }).strict();
3270
+ const reviewCorrectSchema = z.object({
3271
+ journeyId: journeyIdSchema,
3272
+ expectedRevision: z.number().int().nonnegative(),
3273
+ idempotencyKey: journeyIdSchema,
3274
+ report: correctionReportSchema
3275
+ }).strict();
3276
+ /** Immutable private report receipt; the only place where correction quotes are retained. */
3277
+ const correctionReportResourceSchema = z.object({
3278
+ journeyId: journeyIdSchema,
3279
+ kind: z.literal("correction-report"),
3280
+ id: journeyIdSchema,
3281
+ requestHash: journeyDigestSchema,
3282
+ at: z.iso.datetime(),
3283
+ snapshotId: journeyDigestSchema,
3284
+ batchDigest: journeyDigestSchema,
3285
+ decisionDigest: journeyDigestSchema,
3286
+ report: correctionReportSchema
3287
+ }).strict();
3288
+ const correctionAuditSchema = z.object({
3289
+ reportId: journeyIdSchema,
3290
+ action: z.enum([
3291
+ "report",
3292
+ "hide",
3293
+ "dismiss"
3294
+ ]),
3295
+ actor: z.enum([
3296
+ "observer",
3297
+ "human",
3298
+ "delegate"
3299
+ ]),
3300
+ authorityId: journeyIdSchema.nullable(),
3301
+ decisionDigest: journeyDigestSchema
3302
+ }).strict();
3303
+ //#endregion
3304
+ //#region packages/application/src/journey/correction/binding.ts
3305
+ function workCorrections(state, workId) {
3306
+ return (state.corrections ?? []).filter((item) => item.workId === workId);
3058
3307
  }
3059
- /**
3060
- * Occurrences of a Work's explicit exceptions, matched like Hide everywhere: exact NFC values
3061
- * over complete graphemes. A confirmed Hide value inside one stays visible there.
3062
- */
3063
- function bExceptionRanges(values) {
3064
- const matcher = createEntityMatcher([...new Set([...values].map((value) => value.normalize("NFC")))].map((value) => ({
3065
- kind: "custom-term",
3066
- value
3067
- })));
3068
- return (text, signal = new AbortController().signal) => matcher.find(text, signal, 1e5).filter(({ range, entity }) => text.slice(range.start, range.end).normalize("NFC") === entity.value).map(({ range }) => range);
3308
+ /** Binds rendered output and generated metadata to every correction disposition of the Work. */
3309
+ function workCorrectionBinding(state, workId) {
3310
+ const corrections = workCorrections(state, workId);
3311
+ return corrections.length ? corrections.map(({ id, groupId, inputRevision, batchId, status, disposition }) => ({
3312
+ id,
3313
+ groupId,
3314
+ inputRevision,
3315
+ batchId,
3316
+ status,
3317
+ choice: disposition?.choice ?? null,
3318
+ decisionDigest: disposition?.decisionDigest ?? null
3319
+ })) : null;
3069
3320
  }
3070
- /** Whether a range lies inside one of `ranges`: a prefix maximum of ends, O(log n) per query. */
3071
- function bContainment(ranges) {
3072
- const sorted = ranges.toSorted((a, b) => a.start - b.start);
3073
- const reach = [];
3074
- for (const [index, range] of sorted.entries()) reach.push(Math.max(range.end, reach[index - 1] ?? range.end));
3075
- return (range) => {
3076
- let low = 0, high = sorted.length;
3077
- while (low < high) {
3078
- const middle = low + high >> 1;
3079
- if ((sorted[middle]?.start ?? Infinity) <= range.start) low = middle + 1;
3080
- else high = middle;
3081
- }
3082
- return low > 0 && (reach[low - 1] ?? -1) >= range.end;
3083
- };
3321
+ //#endregion
3322
+ //#region packages/application/src/journey/placement/decisions.ts
3323
+ function workPrivacyDecisions(state, work) {
3324
+ return state.groups.filter((group) => work.groupIds.includes(group.id)).map(({ id, key, kind, label, condition, choice, confirmed }) => ({
3325
+ id,
3326
+ key,
3327
+ kind,
3328
+ label,
3329
+ condition,
3330
+ choice,
3331
+ confirmed
3332
+ })).sort((a, b) => a.id.localeCompare(b.id));
3084
3333
  }
3085
- /**
3086
- * Word-bounded, exact, case-sensitive NFC occurrences of the terms, in UTF-16 coordinates,
3087
- * except where an explicit exception occurrence contains one.
3088
- */
3089
- function bTermMatcher(terms, exceptions = []) {
3090
- const matcher = createEntityMatcher([...new Set(terms)].map((value) => ({
3091
- kind: "custom-term",
3092
- value
3093
- })));
3094
- const excepted = bExceptionRanges(exceptions);
3095
- return (text, signal = new AbortController().signal) => {
3096
- const hits = matcher.find(text, signal, 1e5).filter(({ range }) => bWordBounded(text, range.start, range.end));
3097
- const kept = hits.length ? bContainment(excepted(text, signal)) : () => false;
3098
- return hits.filter(({ range }) => !kept(range)).map(({ range, entity }) => ({
3099
- ...range,
3100
- term: entity.value
3101
- }));
3102
- };
3334
+ /** Corrections are additive under an unchanged Hide group, so they bind metadata review too. */
3335
+ function workDecisionDigest(state, work) {
3336
+ const decisions = workPrivacyDecisions(state, work);
3337
+ const corrections = workCorrectionBinding(state, work.id);
3338
+ return textDigest(JSON.stringify(corrections ? {
3339
+ decisions,
3340
+ corrections
3341
+ } : decisions));
3103
3342
  }
3104
- /** Dictionary hits that a confirmed Hide value masks at render, so they need no classification. */
3105
- function bMaskedHits(fields, terms) {
3106
- const byId = new Map(fields.map((field) => [field.id, {
3107
- text: field.text,
3108
- index: createCodePointIndex(field.text),
3109
- base: field.start ?? 0
3110
- }]));
3111
- return (hit) => {
3112
- const field = byId.get(hit.fieldId);
3113
- return !!field && terms.has(hit.term) && bWordBounded(field.text, field.index.offset(hit.start - field.base), field.index.offset(hit.end - field.base));
3343
+ /** Retire generated text until it is reviewed; the processed project name keeps rows distinct. */
3344
+ function retireWorkDescription(work) {
3345
+ return {
3346
+ ...work,
3347
+ suggestion: null
3114
3348
  };
3115
3349
  }
3116
3350
  const segmenter = new Intl.Segmenter("und", { granularity: "grapheme" });
@@ -3126,6 +3360,14 @@ const maximumHideIndexTokens = 2e6;
3126
3360
  * has too many such positions to list.
3127
3361
  */
3128
3362
  function addHideIndexTokens(text, add) {
3363
+ let complete = ordinaryTokens(text, add);
3364
+ if (text.includes("\\")) {
3365
+ if (/^\s*[[{]/u.test(text) && text.includes("\\u")) return false;
3366
+ for (const [, word] of text.matchAll(/\\[nrt]([\p{L}\p{N}\p{M}_]+)/gu)) if (word) complete = ordinaryTokens(word, add) && complete;
3367
+ }
3368
+ return complete;
3369
+ }
3370
+ function ordinaryTokens(text, add) {
3129
3371
  let complete = true;
3130
3372
  let from = 0;
3131
3373
  for (const { index } of text.matchAll(/[^\p{ASCII}]+/gu)) {
@@ -3340,8 +3582,9 @@ function memberChange(previous, next) {
3340
3582
  return counts.every((count) => count >= 0) ? "additions" : counts.every((count) => count <= 0) ? "removals" : "other";
3341
3583
  }
3342
3584
  /**
3343
- * Whether a Work's output may change with the delta (Work metadata is never masked, so a
3344
- * generated description never retires on its own). Without a delta that covers this exact change every Work is affected, as ADR 0069 first decided, except when
3585
+ * Whether a Work's output may change with the delta (generated descriptions remain unmasked
3586
+ * and never retire output on their own; titles are covered by the output index). Without a
3587
+ * delta that covers this exact change every Work is affected, as ADR 0069 first decided, except when
3345
3588
  * members were only removed: no value is then added, so nothing can be under-hidden, and an
3346
3589
  * output may keep masking a value that left its category until it is rendered again.
3347
3590
  */
@@ -3944,6 +4187,40 @@ const journeyPreparationProgressSchema = z.object({
3944
4187
  capturedBytes: preparationCountSchema
3945
4188
  }).strict();
3946
4189
  //#endregion
4190
+ //#region packages/application/src/journey/inventory/progress.ts
4191
+ /** State-only gate: the scope is confirmed and a selected Work still waits for preparation. */
4192
+ function preparationInProgress(state) {
4193
+ return state.consent !== null && state.snapshotId !== null && state.scope.status === "confirmed" && state.works.some((work) => work.status === "pending" && isSelectedWork(work));
4194
+ }
4195
+ /**
4196
+ * Capture counts cover only the selected Works' frozen parts, so Dropped Works never hold the
4197
+ * total open. Captured bytes can end below the frozen size at a complete record boundary.
4198
+ * Current Works are the pending selected Works whose newest attempt has no settlement, in
4199
+ * Work order; several prepare at once.
4200
+ */
4201
+ function projectPreparationProgress(state, families, captures, starts, settlements) {
4202
+ const selected = state.works.filter(isSelectedWork);
4203
+ const sizes = /* @__PURE__ */ new Map();
4204
+ for (const id of selected.flatMap((work) => work.rootSessionIds)) {
4205
+ const members = families.members.get(id);
4206
+ if (!members) throw new ApplicationError("CORRUPTED_STATE");
4207
+ for (const part of members.flatMap((session) => session.parts)) sizes.set(part.id, part.snapshot.size);
4208
+ }
4209
+ const captured = captures.filter((resource) => sizes.has(resource.partId));
4210
+ const settled = new Set(settlements.map((item) => item.id));
4211
+ return {
4212
+ currentWorkIds: selected.filter((work) => {
4213
+ if (work.status !== "pending") return false;
4214
+ const newest = starts.filter((start) => start.workId === work.id && start.inputRevision === work.inputRevision).reduce((latest, start) => !latest || start.sequence > latest.sequence ? start : latest, void 0);
4215
+ return newest !== void 0 && !settled.has(newest.id);
4216
+ }).map((work) => work.id),
4217
+ targetParts: sizes.size,
4218
+ capturedParts: captured.length,
4219
+ targetBytes: [...sizes.values()].reduce((sum, size) => sum + size, 0),
4220
+ capturedBytes: captured.reduce((sum, resource) => sum + resource.bytes, 0)
4221
+ };
4222
+ }
4223
+ //#endregion
3947
4224
  //#region packages/application/src/source/resolve.ts
3948
4225
  /** Queue-based ancestry resolution cannot leak deep descendants through a recursion cutoff. */
3949
4226
  function resolveParts(inventory, route) {
@@ -4646,7 +4923,7 @@ const sessionReconcileResultSchema = z.object({
4646
4923
  //#endregion
4647
4924
  //#region packages/application/src/journey/review/attempt-model.ts
4648
4925
  /**
4649
- * At most twenty small-model analysis agents run at once (D12, raised on 2026-10-20). One agent receives at most
4926
+ * At most twenty-four small-model analysis agents run at once (raised on 2026-11-14). One agent receives at most
4650
4927
  * 240,000 code points and 48 units; attempts dispatched with the same `packId` share one agent.
4651
4928
  */
4652
4929
  const reviewAgentCodePoints = 24e4;
@@ -4857,6 +5134,31 @@ const journeyCaptureSchema = z.object({
4857
5134
  source: sourcePartSchema
4858
5135
  }).strict();
4859
5136
  const base = { journeyId: journeyIdSchema };
5137
+ const journeyOutputSchema = z.object({
5138
+ ...base,
5139
+ kind: z.literal("output"),
5140
+ workId: journeyIdSchema,
5141
+ revision: z.number().int().positive(),
5142
+ bundleId: journeyIdSchema,
5143
+ decisionDigest: journeyDigestSchema,
5144
+ primary: journeyFileSchema,
5145
+ companion: journeyFileSchema,
5146
+ finalMaskLedger: journeyFileSchema.extend({
5147
+ primarySha256: journeyDigestSchema,
5148
+ basePrimarySha256: journeyDigestSchema,
5149
+ workId: journeyIdSchema,
5150
+ inputRevision: z.number().int().positive(),
5151
+ decisionDigest: journeyDigestSchema,
5152
+ occurrences: z.number().int().nonnegative(),
5153
+ contributors: z.number().int().nonnegative(),
5154
+ categories: z.object({
5155
+ identity: z.number().int().nonnegative(),
5156
+ credentials: z.number().int().nonnegative(),
5157
+ internal: z.number().int().nonnegative(),
5158
+ private: z.number().int().nonnegative()
5159
+ }).strict()
5160
+ }).strict()
5161
+ }).strict();
4860
5162
  const journeyResourceSchema = z.discriminatedUnion("kind", [
4861
5163
  z.object({
4862
5164
  ...base,
@@ -4976,31 +5278,7 @@ const journeyResourceSchema = z.discriminatedUnion("kind", [
4976
5278
  bundleSourceId: z.string().min(1)
4977
5279
  }).strict())
4978
5280
  }).strict(),
4979
- z.object({
4980
- ...base,
4981
- kind: z.literal("output"),
4982
- workId: journeyIdSchema,
4983
- revision: z.number().int().positive(),
4984
- bundleId: journeyIdSchema,
4985
- decisionDigest: journeyDigestSchema,
4986
- primary: journeyFileSchema,
4987
- companion: journeyFileSchema,
4988
- finalMaskLedger: journeyFileSchema.extend({
4989
- primarySha256: journeyDigestSchema,
4990
- basePrimarySha256: journeyDigestSchema,
4991
- workId: journeyIdSchema,
4992
- inputRevision: z.number().int().positive(),
4993
- decisionDigest: journeyDigestSchema,
4994
- occurrences: z.number().int().nonnegative(),
4995
- contributors: z.number().int().nonnegative(),
4996
- categories: z.object({
4997
- identity: z.number().int().nonnegative(),
4998
- credentials: z.number().int().nonnegative(),
4999
- internal: z.number().int().nonnegative(),
5000
- private: z.number().int().nonnegative()
5001
- }).strict()
5002
- }).strict()
5003
- }).strict()
5281
+ journeyOutputSchema
5004
5282
  ]);
5005
5283
  const journeyResourceQuerySchema = z.union([
5006
5284
  z.object({
@@ -5057,40 +5335,37 @@ const journeyResourceQuerySchema = z.union([
5057
5335
  }).strict()
5058
5336
  ]);
5059
5337
  //#endregion
5060
- //#region packages/application/src/journey/inventory/progress.ts
5061
- /** State-only gate: the scope is confirmed and a selected Work still waits for preparation. */
5062
- function preparationInProgress(state) {
5063
- return state.consent !== null && state.snapshotId !== null && state.scope.status === "confirmed" && state.works.some((work) => work.status === "pending" && isSelectedWork(work));
5064
- }
5065
- /**
5066
- * Capture counts cover only the selected Works' frozen parts, so Dropped Works never hold the
5067
- * total open. Captured bytes can end below the frozen size at a complete record boundary.
5068
- * Current Works are the pending selected Works whose newest attempt has no settlement, in
5069
- * Work order; several prepare at once.
5070
- */
5071
- function projectPreparationProgress(state, plan, captures, starts, settlements) {
5072
- const families = reviewFamilies(plan.approval.inventory);
5073
- const selected = state.works.filter(isSelectedWork);
5074
- const sizes = /* @__PURE__ */ new Map();
5075
- for (const id of selected.flatMap((work) => work.rootSessionIds)) {
5076
- const members = families.members.get(id);
5077
- if (!members) throw new ApplicationError("CORRUPTED_STATE");
5078
- for (const part of members.flatMap((session) => session.parts)) sizes.set(part.id, part.snapshot.size);
5079
- }
5080
- const captured = captures.filter((resource) => sizes.has(resource.capture.partId));
5081
- const settled = new Set(settlements.map((item) => item.id));
5082
- return {
5083
- currentWorkIds: selected.filter((work) => {
5084
- if (work.status !== "pending") return false;
5085
- const newest = starts.filter((start) => start.workId === work.id && start.inputRevision === work.inputRevision).reduce((latest, start) => !latest || start.sequence > latest.sequence ? start : latest, void 0);
5086
- return newest !== void 0 && !settled.has(newest.id);
5087
- }).map((work) => work.id),
5088
- targetParts: sizes.size,
5089
- capturedParts: captured.length,
5090
- targetBytes: [...sizes.values()].reduce((sum, size) => sum + size, 0),
5091
- capturedBytes: captured.reduce((sum, resource) => sum + resource.capture.facts.byteLength, 0)
5092
- };
5093
- }
5338
+ //#region packages/application/src/journey/send/port.ts
5339
+ /** Storage validates current package approval before releasing one Work's frozen descriptor. */
5340
+ const sendOutputQuerySchema = z.object({
5341
+ journeyId: z.uuid(),
5342
+ workId: z.uuid(),
5343
+ revision: z.number().int().positive(),
5344
+ sessions: z.boolean()
5345
+ }).strict();
5346
+ const sendOutputSchema = z.object({
5347
+ output: journeyOutputSchema,
5348
+ extension: z.object({
5349
+ approval: z.string(),
5350
+ groups: z.array(z.array(z.string())),
5351
+ sources: z.array(z.object({
5352
+ session: z.string(),
5353
+ source: z.string(),
5354
+ part: sourcePartSchema
5355
+ })),
5356
+ outputs: z.array(z.object({
5357
+ workId: z.uuid(),
5358
+ revision: z.number().int(),
5359
+ sha256: z.string(),
5360
+ bundleId: z.string()
5361
+ }))
5362
+ }).strict().optional(),
5363
+ identities: z.array(z.object({
5364
+ archiveSessionId: z.string(),
5365
+ relativePath: z.string(),
5366
+ lastAt: z.iso.datetime()
5367
+ }).strict())
5368
+ }).strict();
5094
5369
  //#endregion
5095
5370
  //#region packages/application/src/journey/policy/port.ts
5096
5371
  const journeyPolicyRespondSchema = z.object({
@@ -5224,6 +5499,14 @@ function newJourneyWork(id, projectName, rootSessionIds, sourceWorkspaceIds, pla
5224
5499
  binding: null
5225
5500
  };
5226
5501
  }
5502
+ /** Stable blocked Work IDs per parent, so later failing roots join one instead of multiplying. */
5503
+ function isolatedWorkId(parentId, ordinal) {
5504
+ const bytes = createHash("sha256").update(`isolated-roots:${parentId}:${ordinal}`).digest().subarray(0, 16);
5505
+ bytes[6] = (bytes[6] ?? 0) & 15 | 64;
5506
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
5507
+ const hex = bytes.toString("hex");
5508
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
5509
+ }
5227
5510
  //#endregion
5228
5511
  //#region packages/application/src/journey/scan-generation.ts
5229
5512
  /** Retire one scan generation while preserving the person's confirmed classification. */
@@ -5890,6 +6173,7 @@ function respondToJourney(state, action, source, now, decision, preferenceScope,
5890
6173
  reason: action.reason
5891
6174
  }];
5892
6175
  } else {
6176
+ if (source !== "panel") throw new ApplicationError("CONFIRMATION_REQUIRED");
5893
6177
  if (previous?.reason !== "retry-exhausted") throw new ApplicationError("INVALID_TRANSITION");
5894
6178
  nextHolds = holds.filter((item) => item.packId !== action.packId);
5895
6179
  }
@@ -6001,7 +6285,7 @@ function answeredRule(card) {
6001
6285
  /** A dispatch without a pack ID is its own analysis agent. */
6002
6286
  const agentKey = (attemptId, packId) => packId === void 0 ? `attempt:${attemptId}` : `pack:${packId}`;
6003
6287
  /**
6004
- * Refuses a dispatch that would run more than twenty agents at once, or give one agent more than
6288
+ * Refuses a dispatch that would run more than twenty-four agents at once, or give one agent more than
6005
6289
  * 240,000 code points or 48 units. A full reservation (repair or reconciliation) fills an agent,
6006
6290
  * so it always runs alone.
6007
6291
  */
@@ -6020,7 +6304,7 @@ function assertAgentCapacity(open, dispatches, attemptId, packId, budgetCodePoin
6020
6304
  }
6021
6305
  const key = agentKey(attemptId, packId);
6022
6306
  const agent = agents.get(key);
6023
- if (agent === void 0 && agents.size >= 16 || (agent?.codePoints ?? 0) + budgetCodePoints > 24e4 || (agent?.units ?? 0) + 1 > 48) throw new ApplicationError("OPERATION_CONFLICT");
6307
+ if (agent === void 0 && agents.size >= 24 || (agent?.codePoints ?? 0) + budgetCodePoints > 24e4 || (agent?.units ?? 0) + 1 > 48) throw new ApplicationError("OPERATION_CONFLICT");
6024
6308
  }
6025
6309
  //#endregion
6026
6310
  //#region packages/application/src/journey/review/attempt.ts
@@ -6282,30 +6566,6 @@ async function readGroupItems(storage, value) {
6282
6566
  };
6283
6567
  }
6284
6568
  //#endregion
6285
- //#region packages/application/src/export/versions.ts
6286
- /** Current built-in admission, annotation and retention contracts; implementations share these. */
6287
- const nativeAdapterVersions = Object.freeze({
6288
- claude: "claude-native-export-v19",
6289
- codex: "codex-native-export-v16"
6290
- });
6291
- /** Shared metadata annotation/preparation semantics, independent of serialization formats. */
6292
- const metadataProcessingVersion = "metadata-annotations-v1";
6293
- //#endregion
6294
- //#region packages/application/src/journey/adapters.ts
6295
- /** The adapter contracts a Journey's snapshot is prepared under. */
6296
- const currentJourneyAdapters = () => ({
6297
- claude: `${nativeAdapterVersions.claude}/main-prose-source-scan-v2`,
6298
- codex: `${nativeAdapterVersions.codex}/main-prose-source-scan-v2`
6299
- });
6300
- /**
6301
- * An unfinished snapshot frozen under other adapter contracts (absent: an earlier release), or
6302
- * read by B review instead of dictionary review (ADR 0086), cannot render its Works (ADR 0076).
6303
- * A new scan replaces it unless it holds a Send, which the store keeps for the person to finish.
6304
- */
6305
- function obsoleteJourney(state) {
6306
- return state.phase !== "completed" && state.snapshotId !== null && (state.adapters?.claude !== currentJourneyAdapters().claude || state.adapters.codex !== currentJourneyAdapters().codex || state.reviewMethod !== "dictionary");
6307
- }
6308
- //#endregion
6309
6569
  //#region packages/application/src/journey/correction/anchors.ts
6310
6570
  /** Exact frozen-field validation only: no fuzzy relocation, context ownership or other unit. */
6311
6571
  function locateCorrectionAnchors(batch, anchors) {
@@ -6650,6 +6910,25 @@ async function sessionReviewStatus(storage, value) {
6650
6910
  }
6651
6911
  //#endregion
6652
6912
  //#region packages/application/src/journey/service.ts
6913
+ /** Display uses the same bounded literal/exception matcher without depending on artifact I/O. */
6914
+ function presentedTitle(text, terms, exceptions) {
6915
+ const markers = [...text.matchAll(replacementMarkerPattern)].map((match) => ({
6916
+ start: match.index,
6917
+ end: match.index + match[0].length
6918
+ }));
6919
+ const spans = [];
6920
+ for (const hit of bTermMatcher(terms, exceptions)(text).sort((left, right) => left.start - right.start || right.end - left.end)) {
6921
+ if (markers.some((marker) => marker.start < hit.end && marker.end > hit.start)) continue;
6922
+ const previous = spans.at(-1);
6923
+ if (previous && hit.start < previous.end) previous.end = Math.max(previous.end, hit.end);
6924
+ else spans.push({
6925
+ start: hit.start,
6926
+ end: hit.end
6927
+ });
6928
+ }
6929
+ for (const span of spans.toReversed()) text = text.slice(0, span.start) + "[hidden]" + text.slice(span.end);
6930
+ return text;
6931
+ }
6653
6932
  function digest$1(value) {
6654
6933
  return createHash("sha256").update(JSON.stringify(value)).digest("hex");
6655
6934
  }
@@ -6658,8 +6937,9 @@ function digest$1(value) {
6658
6937
  * quotes stay out. The agent reads override values from `journey.get` and the guidance.
6659
6938
  */
6660
6939
  function journeyView(state, now) {
6661
- const { exceptions, instructions, corrections, valueOverrides, consent, accountRead, ...safe } = state;
6940
+ const { exceptions, instructions, corrections, valueOverrides, consent, accountRead, rememberedHides: _remembered, ...safe } = state;
6662
6941
  const terms = activePrivacyTerms(state);
6942
+ const hidden = dictionaryHides(state, state.privacy.terms);
6663
6943
  return {
6664
6944
  ...safe,
6665
6945
  consent: consent && {
@@ -6680,15 +6960,17 @@ function journeyView(state, now) {
6680
6960
  const localTitle = work.suggestion || work.explicitTitleId ? work.title : work.classificationMetadata?.title ?? work.title;
6681
6961
  const imagesOmitted = hasCurrentStaticReview(state, work) ? work.staticReview?.imagesOmitted ?? 0 : 0;
6682
6962
  const source = publicSource(work);
6963
+ const title = work.titleOrigin === "explicit" ? localTitle : presentedTitle(localTitle, hidden.keys(), state.exceptions.filter((item) => item.workId === work.id).map((item) => item.value));
6683
6964
  if (!work.classificationMetadata) return {
6684
6965
  ...work,
6966
+ title,
6685
6967
  localTitle,
6686
6968
  imagesOmitted,
6687
6969
  publicSource: source
6688
6970
  };
6689
6971
  const metadata = work.classificationMetadata;
6690
6972
  const displayed = {
6691
- title: work.suggestion || work.explicitTitleId ? work.title : metadata.title,
6973
+ title,
6692
6974
  projectName: metadata.projectName,
6693
6975
  description: work.suggestion ? work.description : metadata.description,
6694
6976
  reason: work.suggestion ? work.reason : metadata.reason
@@ -6786,7 +7068,7 @@ async function joinRender(port, journeyId, workId, run, signal, retryConflict =
6786
7068
  }
6787
7069
  function createNativeJourney(storage, now = () => (/* @__PURE__ */ new Date()).toISOString(), local) {
6788
7070
  /** Callers present after their write is durable, so a concurrent commit must not turn it into an error. */
6789
- async function present(committed) {
7071
+ async function present(committed, includePreparationProgress = true) {
6790
7072
  let state = committed;
6791
7073
  for (let attempt = 1;; attempt++) {
6792
7074
  const view = journeyView(state, now());
@@ -6806,7 +7088,7 @@ function createNativeJourney(storage, now = () => (/* @__PURE__ */ new Date()).t
6806
7088
  review: null
6807
7089
  };
6808
7090
  }
6809
- const preparationProgress = preparationInProgress(state) ? await storage.readPreparationProgress({ journeyId: state.journeyId }) : null;
7091
+ const preparationProgress = includePreparationProgress && preparationInProgress(state) ? await storage.readPreparationProgress({ journeyId: state.journeyId }) : null;
6810
7092
  const classificationProgress = state.consent && state.snapshotId ? (await storage.resources({
6811
7093
  journeyId: state.journeyId,
6812
7094
  kind: "classification-progress",
@@ -6865,8 +7147,8 @@ function createNativeJourney(storage, now = () => (/* @__PURE__ */ new Date()).t
6865
7147
  const query = nativeGetSchema.parse(input);
6866
7148
  const state = await storage.current(query.journeyId);
6867
7149
  if (!state) return null;
6868
- const view = await present(state);
6869
- return audience === "agent" && state.valueOverrides ? {
7150
+ const view = await present(state, audience !== "review-helper");
7151
+ return audience !== "panel" && state.valueOverrides ? {
6870
7152
  ...view,
6871
7153
  valueOverrides: state.valueOverrides
6872
7154
  } : view;
@@ -6933,6 +7215,7 @@ function createNativeJourney(storage, now = () => (/* @__PURE__ */ new Date()).t
6933
7215
  };
6934
7216
  };
6935
7217
  if (current && current.phase !== "completed" && !current.abandonedAt && !obsoleteJourney(current)) return resume(current);
7218
+ const carried = current && !current.abandonedAt && obsoleteJourney(current) ? (current.instructions ?? []).filter((item) => item.actor === "human") : [];
6936
7219
  const state = {
6937
7220
  formatVersion: 10,
6938
7221
  classificationRevision: null,
@@ -6961,6 +7244,7 @@ function createNativeJourney(storage, now = () => (/* @__PURE__ */ new Date()).t
6961
7244
  exceptions: [],
6962
7245
  adapters: currentJourneyAdapters(),
6963
7246
  lastEvent: 0,
7247
+ ...carried.length ? { instructions: carried } : {},
6964
7248
  ...input.period?.since || input.period?.until ? { period: input.period } : {}
6965
7249
  };
6966
7250
  const result = await storage.commit({
@@ -7142,7 +7426,7 @@ function createNativeJourney(storage, now = () => (/* @__PURE__ */ new Date()).t
7142
7426
  const rows = await storage.events(input.journeyId, input.after);
7143
7427
  if (rows.length || Date.now() >= deadline) return {
7144
7428
  events: rows,
7145
- state: await get({ journeyId: input.journeyId })
7429
+ state: rows.length || !input.changesOnly ? await get({ journeyId: input.journeyId }) : null
7146
7430
  };
7147
7431
  await setTimeout$1(Math.min(1e3, Math.max(0, deadline - Date.now())), void 0, { signal });
7148
7432
  }
@@ -7445,6 +7729,14 @@ function createNativeJourney(storage, now = () => (/* @__PURE__ */ new Date()).t
7445
7729
  if (!local) throw new ApplicationError("INVALID_TRANSITION");
7446
7730
  return local.works.classificationRead(journeyClassificationReadSchema.parse(value), signal);
7447
7731
  },
7732
+ recoverIsolatedMetadata: async (value, signal = new AbortController().signal) => {
7733
+ if (!local) throw new ApplicationError("INVALID_TRANSITION");
7734
+ const result = await local.works.recoverIsolatedMetadata(isolatedMetadataRecoverySchema.parse(value), signal);
7735
+ return {
7736
+ ...result,
7737
+ state: await present(result.state)
7738
+ };
7739
+ },
7448
7740
  classificationSubmit: async (value, signal = new AbortController().signal) => {
7449
7741
  if (!local) throw new ApplicationError("INVALID_TRANSITION");
7450
7742
  const result = await local.works.classificationSubmit(journeyClassificationSubmitSchema.parse(value), signal);
@@ -7577,7 +7869,8 @@ function safeJourneySender(sender, now) {
7577
7869
  async get(input) {
7578
7870
  const result = await sender.get({
7579
7871
  journeyId: input.journeyId,
7580
- ...input.sendId ? { sendId: input.sendId } : {}
7872
+ ...input.sendId ? { sendId: input.sendId } : {},
7873
+ ...input.observedRevision !== void 0 ? { observedRevision: input.observedRevision } : {}
7581
7874
  });
7582
7875
  return result ? safe(result) : null;
7583
7876
  },
@@ -7605,6 +7898,8 @@ function safeJourneySender(sender, now) {
7605
7898
  async advance(input, signal = new AbortController().signal) {
7606
7899
  return safe(await sender.advance(input, signal));
7607
7900
  },
7901
+ uploadAssessments: (input) => sender.uploadAssessments(input),
7902
+ ownedPassport: (input, signal = new AbortController().signal) => sender.ownedPassport(input, signal),
7608
7903
  ownerProfile: (signal = new AbortController().signal) => sender.ownerProfile(signal),
7609
7904
  addressTaken: (input, signal = new AbortController().signal) => sender.addressTaken(input, signal),
7610
7905
  saveSentProfile: (input, signal = new AbortController().signal) => sender.saveSentProfile(input, signal)
@@ -14050,4 +14345,4 @@ function equivalenceDuplicates(batch) {
14050
14345
  return bindings.sort();
14051
14346
  }
14052
14347
  //#endregion
14053
- export { validateDetectionSummary as $, hideKeyId as $n, sendReplaceResultSchema as $r, journeyResourceSchema as $t, renewBeforeMs as A, scanGitSeedsResource as An, journeyPreviewSchema as Ar, obsoleteJourney as At, createApplication as B, journeySearchResultSchema as Bn, nativeRespondSchema as Br, classificationPartition as Bt, rebindReviewOverlay as C, selectFrozenConfiguration as Ci, mergeBRepair as Cn, preparationStartSchema as Cr, sendActionSchema as Ct, staticallyHiddenFindings as D, scanPreparationDigest as Dn, journeyClassificationSessionsSchema as Dr, sendStartSchema as Dt, guardedFindingText as E, scanCanonicalResourceSchema as En, validatePreparationFacts as Er, sendRequestSchema as Et, readAccountAvatarUrl as F, journeyInventoryQuerySchema as Fn, applyCardAnswers as Fr, journeyGuidance as Ft, validateInspectionRequest as G, journeyEventSchema as Gn, staticSessionId as Gr, journeyContextQuerySchema as Gt, inspectionStartSchema as H, searchScopeDigest as Hn, mergePrivacyTerms as Hr, retireScanGeneration as Ht, storedCredentialSchema as I, journeyInventoryReadSchema as In, assertJourneyPrivacyReady as Ir, journeyBatchReadSchema as It, validateInspectionFence as J, readConfirmedHideChange as Jn, agentSourceSchema as Jr, hideFinishSchema as Jt, inspectionQuerySchema as K, confirmedHideAdditions as Kn, staticSessionResourceSchema as Kr, journeyContextSchema as Kt, sourceIdentityEvidence as L, journeyInventorySchema as Ln, privacyDecisionDigest as Lr, journeyReviewStorageQuerySchema as Lt, deviceAuthorizationSchema as M, scanExclusionResourceSchema as Mn, journeyRenderSchema as Mr, nativeAdapterVersions as Mt, deviceProfileScopeSchema as N, validateScanExclusion as Nn, displayedScopeDigest as Nr, attemptBudgetCodePoints as Nt, createAccount as O, scanSessionResourceSchema as On, journeyFindingImpactSchema as Or, settleCorrections as Ot, profileUrlMatchesIdentity as P, resolveSourceSelection as Pn, requestedScope as Pr, validateAttemptAppend as Pt, validateInspectionContribution as Q, addHideIndexTokens as Qn, sendReceiptSchema as Qr, journeyResourceQuerySchema as Qt, withSourceEvidence as R, journeyPreparationProgressSchema as Rn, nativeEventsSchema as Rr, applyClassificationProposal as Rt, rebindReviewAlignment as S, freezeConfiguration as Si, bReviewDigest as Sn, preparationSettlementSchema as Sr, replaceSensitiveTermsSchema as St, reviewBatchReuse as T, scanAliasResourceSchema as Tn, preparationUpdateSchema as Tr, sendPrepareSchema as Tt, validateInspectionBatch as U, journeyCommitResultSchema as Un, hasCurrentStaticReview as Ur, newJourneyWork as Ut, inspectionCancelSchema as V, journeySearchSaveSchema as Vn, retryReviewActionSchema as Vr, classificationWorks as Vt, validateInspectionBatches as W, journeyCommitSchema as Wn, staticReviewDigest as Wr, reviewCompileSchema as Wt, validateInspectionInputs as X, settleConfirmedHides as Xn, journeySendSchema as Xr, journeyPolicyRespondSchema as Xt, validateInspectionHead as Y, renderedHideValue as Yn, agentSourcesSchema as Yr, hidePublishSchema as Yt, validateInspectionQuery as Z, termHideAdditions as Zn, sendArtifactSchema as Zr, projectPreparationProgress as Zt, changeJourney as _, sourceChangePolicy as _i, reviewCandidateInputSchema as _n, workPrivacyDecisions as _r, manualUploadStartSchema as _t, bCrossSegmentReviewSchema as a, isReadingComplete as ai, reviewAttemptsResultSchema as an, bTermMatcher as ar, appendSensitiveTerms as at, isGenericPublicTerm as b, effectiveTreatmentSchema as bi, reviewRepairResourceSchema as bn, preparationBeginSchema as br, maximumTermsFileBytes as bt, reconstructBFields as c, selectedWorks as ci, sessionReconciliationResourceSchema as cn, confirmedHideKey as cr, maximumAgentSetupSessions as ct, resolveGroupSplit as d, packageCommandSchema as di, sessionReviewInputSchema as dn, dictionaryHides as dr, validateFailureLocation as dt, sendResolutionSchema as ei, journeyIsolateSchema as en, maximumHideIndexTokens as er, inspectionInputs as et, selectedValueMembers as f, selectionBatchSchema as fi, sessionReviewQuerySchema as fn, journeyConfirmedHides as fr, manualUploadActionSchema as ft, semanticGroupKey as g, stateSchema as gi, sessionUsefulnessResourceSchema as gn, workDecisionDigest as gr, manualUploadSchema as gt, hideScopeDigest as h, producerVersion as hi, sessionUsefulnessInputSchema as hn, retireWorkDescription as hr, manualUploadPrepareSchema as ht, validateBCrossSegmentReview as i, isIncludedWork as ii, reviewAttemptsQuerySchema as in, bMaskedHits as ir, reviewInventory as it, accountProfileSchema as j, verifiedScanGitSeeds as jn, journeyRenderInputSchema as jr, metadataProcessingVersion as jt, renew as k, scanGitSeedsId as kn, journeyFindingsSchema as kr, currentJourneyAdapters as kt, chooseCheapPlacement as l, withReadingProgress as li, sessionRereadInputSchema as ln, confirmedHideKinds as lr, artifactFailureDetailSchema as lt, activeJourneyTerms as m, gitMetadataBudgetBytes as mi, sessionReviewStatusSchema as mn, syncTermGroups as mr, manualUploadChoiceSchema as mt, scanBatchEquivalenceDigest as n, hasCompleteWorkDescription as ni, sortingApplySchema as nn, bContainment as nr, settingsQuerySchema as nt, bRereadDigest as o, isSelectedWork as oi, sessionReconcileInputSchema as on, bWordBounded as or, classifyAgentSetup as ot, splitSelectorValues as p, selectionSchema as pi, sessionReviewResourceSchema as pn, maskedHitsStale as pr, manualUploadBindingSchema as pt, validateInspectionClaim as q, confirmedHideDeltaSchema as qn, agentSourcePathSegment as qr, hideBeginSchema as qt, locateTermEvidence as r, isClassifying as ri, reviewAttemptResourceSchema as rn, bExceptionRanges as rr, readWorkspaceOutcomes as rt, reconcileBSession as s, isSkippedWork as si, sessionReconcileResultSchema as sn, confirmedHideGroupIds as sr, defaultAgentSetupInclusion as st, scanBatchContextDigest as t, sendSettingsSchema as ti, journeyPlaceSchema as tn, packHideIndex as tr, settingsBatchSchema as tt, reviseGroupMembership as u, commandSchema as ui, sessionRereadResourceSchema as un, confirmedHideTerms as ur, resolveFailureLocation as ut, projectJourneyInventory as v, recommendParallelism as vi, reviewCandidateResourceSchema as vn, workCorrectionBinding as vr, candidateSortAvailable as vt, rebindReviewSubmit as w, staticCatalogRef as wi, planBRepair as wn, preparationStateSchema as wr, sendChoiceSchema as wt, bReviewScope as x, defaultScopedSettings as xi, reviewTransportBytes as xn, preparationPermissionSchema as xr, normalizeSensitiveTerms as xt, preparationScope as y, approvedTreatmentSchema as yi, reviewRepairInputSchema as yn, emptyPreparationFacts as yr, emptyTermsRevision as yt, agentSetupKey as z, applyClassification as zn, nativeGetSchema as zr, classificationCommitSchema as zt };
14348
+ export { validateDetectionSummary as $, maximumHideIndexTokens as $n, sendReplaceResultSchema as $r, journeyPlaceSchema as $t, renewBeforeMs as A, gitMetadataBudgetBytes as Ai, scanExclusionResourceSchema as An, retryReviewActionSchema as Ar, validateAttemptAppend as At, createApplication as B, staticCatalogRef as Bi, journeySearchSaveSchema as Bn, metadataProcessingVersion as Br, newJourneyWork as Bt, rebindReviewOverlay as C, bTermMatcher as Ci, scanAliasResourceSchema as Cn, privacyDecisionDigest as Cr, sendActionSchema as Ct, staticallyHiddenFindings as D, packageCommandSchema as Di, scanGitSeedsId as Dn, nativeGetSchema as Dr, sendStartSchema as Dt, guardedFindingText as E, commandSchema as Ei, scanSessionResourceSchema as En, nativeEventsSchema as Er, sendRequestSchema as Et, readAccountAvatarUrl as F, approvedTreatmentSchema as Fi, journeyInventoryReadSchema as Fn, itemAtPassport as Fr, classificationCommitSchema as Ft, validateInspectionRequest as G, confirmedHideAdditions as Gn, staticSessionId as Gr, hideFinishSchema as Gt, inspectionStartSchema as H, journeyCommitResultSchema as Hn, mergePrivacyTerms as Hr, journeyContextQuerySchema as Ht, storedCredentialSchema as I, effectiveTreatmentSchema as Ii, journeyInventorySchema as In, obsoleteJourney as Ir, classificationPartition as It, validateInspectionFence as J, renderedHideValue as Jn, agentSourceSchema as Jr, sendOutputQuerySchema as Jt, inspectionQuerySchema as K, confirmedHideDeltaSchema as Kn, staticSessionResourceSchema as Kr, hidePublishSchema as Kt, sourceIdentityEvidence as L, defaultScopedSettings as Li, journeyPreparationProgressSchema as Ln, sendOwesPassport as Lr, classificationWorks as Lt, deviceAuthorizationSchema as M, stateSchema as Mi, resolveSourceSelection as Mn, awaitingPassport as Mr, journeyBatchReadSchema as Mt, deviceProfileScopeSchema as N, sourceChangePolicy as Ni, projectPreparationProgress as Nn, currentJourneyAdapters as Nr, journeyReviewStorageQuerySchema as Nt, createAccount as O, selectionBatchSchema as Oi, scanGitSeedsResource as On, nativeRespondSchema as Or, settleCorrections as Ot, profileUrlMatchesIdentity as P, recommendParallelism as Pi, journeyInventoryQuerySchema as Pn, hostDrives as Pr, applyClassificationProposal as Pt, validateInspectionContribution as Q, hideKeyId as Qn, sendReceiptSchema as Qr, journeyIsolateSchema as Qt, withSourceEvidence as R, freezeConfiguration as Ri, applyClassification as Rn, sendReachedPassport as Rr, retireScanGeneration as Rt, rebindReviewAlignment as S, bMaskedHits as Si, planBRepair as Sn, assertJourneyPrivacyReady as Sr, replaceSensitiveTermsSchema as St, reviewBatchReuse as T, replacementMarkerPattern as Ti, scanPreparationDigest as Tn, rememberedHideValues as Tr, sendPrepareSchema as Tt, validateInspectionBatch as U, journeyCommitSchema as Un, hasCurrentStaticReview as Ur, journeyContextSchema as Ut, inspectionCancelSchema as V, searchScopeDigest as Vn, nativeAdapterVersions as Vr, reviewCompileSchema as Vt, validateInspectionBatches as W, journeyEventSchema as Wn, staticReviewDigest as Wr, hideBeginSchema as Wt, validateInspectionInputs as X, termHideAdditions as Xn, journeySendSchema as Xr, journeyResourceQuerySchema as Xt, validateInspectionHead as Y, settleConfirmedHides as Yn, agentSourcesSchema as Yr, sendOutputSchema as Yt, validateInspectionQuery as Z, addHideIndexTokens as Zn, sendArtifactSchema as Zr, journeyResourceSchema as Zt, changeJourney as _, journeyConfirmedHides as _i, reviewRepairInputSchema as _n, journeyRenderInputSchema as _r, manualUploadStartSchema as _t, bCrossSegmentReviewSchema as a, isIncludedWork as ai, sessionReconcileResultSchema as an, emptyPreparationFacts as ar, appendSensitiveTerms as at, isGenericPublicTerm as b, bContainment as bi, bReviewDigest as bn, requestedScope as br, maximumTermsFileBytes as bt, reconstructBFields as c, isSkippedWork as ci, sessionRereadResourceSchema as cn, preparationSettlementSchema as cr, maximumAgentSetupSessions as ct, resolveGroupSplit as d, withReadingProgress as di, sessionReviewResourceSchema as dn, preparationUpdateSchema as dr, validateFailureLocation as dt, sendResolutionSchema as ei, sortingApplySchema as en, packHideIndex as er, inspectionInputs as et, selectedValueMembers as f, confirmedHideGroupIds as fi, sessionReviewStatusSchema as fn, validatePreparationFacts as fr, manualUploadActionSchema as ft, semanticGroupKey as g, dictionaryHides as gi, reviewCandidateResourceSchema as gn, journeyPreviewSchema as gr, manualUploadSchema as gt, hideScopeDigest as h, confirmedHideTerms as hi, reviewCandidateInputSchema as hn, journeyFindingsSchema as hr, manualUploadPrepareSchema as ht, validateBCrossSegmentReview as i, isClassifying as ii, sessionReconcileInputSchema as in, workCorrectionBinding as ir, reviewInventory as it, accountProfileSchema as j, producerVersion as ji, validateScanExclusion as jn, advanceStepsSend as jr, journeyGuidance as jt, renew as k, selectionSchema as ki, verifiedScanGitSeeds as kn, nativeStartSchema as kr, attemptBudgetCodePoints as kt, chooseCheapPlacement as l, selectedWorks as li, sessionReviewInputSchema as ln, preparationStartSchema as lr, artifactFailureDetailSchema as lt, activeJourneyTerms as m, confirmedHideKinds as mi, sessionUsefulnessResourceSchema as mn, journeyFindingImpactSchema as mr, manualUploadChoiceSchema as mt, scanBatchEquivalenceDigest as n, sendSettingsSchema as ni, reviewAttemptsQuerySchema as nn, workDecisionDigest as nr, settingsQuerySchema as nt, bRereadDigest as o, isReadingComplete as oi, sessionReconciliationResourceSchema as on, preparationBeginSchema as or, classifyAgentSetup as ot, splitSelectorValues as p, confirmedHideKey as pi, sessionUsefulnessInputSchema as pn, journeyClassificationSessionsSchema as pr, manualUploadBindingSchema as pt, validateInspectionClaim as q, readConfirmedHideChange as qn, agentSourcePathSegment as qr, journeyPolicyRespondSchema as qt, locateTermEvidence as r, hasCompleteWorkDescription as ri, reviewAttemptsResultSchema as rn, workPrivacyDecisions as rr, readWorkspaceOutcomes as rt, reconcileBSession as s, isSelectedWork as si, sessionRereadInputSchema as sn, preparationPermissionSchema as sr, defaultAgentSetupInclusion as st, scanBatchContextDigest as t, sendSessionUploadSchema as ti, reviewAttemptResourceSchema as tn, retireWorkDescription as tr, settingsBatchSchema as tt, reviseGroupMembership as u, unfinishedPackagingCode as ui, sessionReviewQuerySchema as un, preparationStateSchema as ur, resolveFailureLocation as ut, projectJourneyInventory as v, maskedHitsStale as vi, reviewRepairResourceSchema as vn, journeyRenderSchema as vr, candidateSortAvailable as vt, rebindReviewSubmit as w, bWordBounded as wi, scanCanonicalResourceSchema as wn, reconcileReaderKeeps as wr, sendChoiceSchema as wt, bReviewScope as x, bExceptionRanges as xi, mergeBRepair as xn, applyCardAnswers as xr, normalizeSensitiveTerms as xt, preparationScope as y, syncTermGroups as yi, reviewTransportBytes as yn, displayedScopeDigest as yr, emptyTermsRevision as yt, agentSetupKey as z, selectFrozenConfiguration as zi, journeySearchResultSchema as zn, sendsOnNextAdvance as zr, isolatedWorkId as zt };