@gitroll/tacitt-cli 0.6.10 → 0.6.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -4
- package/dist/{agent-setup-LPd8WEsN.mjs → agent-setup-DdfF3zch.mjs} +2 -2
- package/dist/agent-setup-worker.mjs +5 -5
- package/dist/cli.mjs +22 -8
- package/dist/codex-title-worker.mjs +1 -1
- package/dist/configured-preview-worker.mjs +5 -5
- package/dist/configured-session-worker.mjs +9 -7
- package/dist/{frame-ByT1xayJ.mjs → frame-Bq2zRh-i.mjs} +1 -1
- package/dist/{heap-CM298Yu0.mjs → heap-CV8Eca8V.mjs} +212 -10
- package/dist/inspection-worker.mjs +4 -4
- package/dist/{job-BHIwElNF.mjs → job-BKsifhyv.mjs} +37 -17
- package/dist/{locate-Cwv6WH8n.mjs → locate-Cx52Zqxv.mjs} +179 -18
- package/dist/{model-zvn5GUJ9.mjs → model-C1UXkHh9.mjs} +1 -1
- package/dist/operation-executor.mjs +3 -3
- package/dist/preview-worker.mjs +1 -1
- package/dist/privacy-validator.mjs +29 -2
- package/dist/{query-By41gaKm.mjs → query-AMZerUh8.mjs} +312 -94
- package/dist/{run-ZQx7p4Rw.mjs → run-CaPktPhH.mjs} +4 -4
- package/dist/{src-BqtdFVq7.mjs → src-D1iONla3.mjs} +1902 -1606
- package/dist/{src-3jvMvSQn.mjs → src-DlVbGB32.mjs} +74 -37
- package/dist/{src-D_DfY--2.mjs → src-wZx_v0Ih.mjs} +297 -207
- package/dist/store-database-worker.mjs +1 -1
- package/dist/web/assets/index-CYl7eDUu.js +172 -0
- package/dist/web/assets/index-DzOozqms.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/skills/tacitt/SKILL.md +11 -11
- package/skills/tacitt/references/b-method.md +2 -2
- package/skills/tacitt/references/bootstrap.md +2 -2
- package/skills/tacitt/references/classification.md +3 -3
- package/skills/tacitt/references/commands.md +29 -30
- package/skills/tacitt/references/journey.md +6 -6
- package/skills/tacitt/references/parallel-review.md +27 -33
- package/skills/tacitt/references/send.md +9 -4
- package/skills/tacitt/scripts/INDEX.md +37 -37
- package/skills/tacitt/scripts/answer.mjs +73 -15
- package/skills/tacitt/scripts/journey-classification-metadata.mjs +75 -26
- package/skills/tacitt/scripts/journey-classification-progress.mjs +1 -2
- package/skills/tacitt/scripts/journey-classification.mjs +6 -1
- package/skills/tacitt/scripts/journey-consolidation.mjs +1 -0
- package/skills/tacitt/scripts/journey-scan-timing.d.mts +1 -0
- package/skills/tacitt/scripts/journey-scan-timing.mjs +19 -4
- package/skills/tacitt/scripts/journey-state.d.mts +9 -1
- package/skills/tacitt/scripts/journey-state.mjs +84 -8
- package/skills/tacitt/scripts/journey-tasks.d.mts +1 -0
- package/skills/tacitt/scripts/journey-tasks.mjs +13 -5
- package/skills/tacitt/scripts/journey.d.mts +44 -31
- package/skills/tacitt/scripts/journey.mjs +24 -5
- package/skills/tacitt/scripts/privacy-validator.mjs +167 -11
- package/skills/tacitt/scripts/review.d.mts +7 -6
- package/skills/tacitt/scripts/review.mjs +562 -145
- package/dist/web/assets/index-B14e6_-z.js +0 -174
- package/dist/web/assets/index-CYJBq1px.css +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
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";
|
|
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";
|
|
@@ -440,7 +440,7 @@ const staticPresets = Object.freeze([
|
|
|
440
440
|
/** Browser-safe fixed metadata and definitions; no recognizers, native sources or session state. */
|
|
441
441
|
const staticCatalogRef = Object.freeze({
|
|
442
442
|
id: "static-catalog",
|
|
443
|
-
version: "
|
|
443
|
+
version: "12"
|
|
444
444
|
});
|
|
445
445
|
//#endregion
|
|
446
446
|
//#region packages/application/src/processing/settings/catalog.ts
|
|
@@ -1221,7 +1221,7 @@ function isClassifying(state) {
|
|
|
1221
1221
|
* not skipped. The Skill helpers mirror this rule in `journey-state.mjs`.
|
|
1222
1222
|
*/
|
|
1223
1223
|
function isSkippedWork(work) {
|
|
1224
|
-
return work.status === "blocked" && !work.blockedCodes.includes("PRIVACY_CHANGE_PENDING");
|
|
1224
|
+
return work.status === "blocked" && !work.blockedCodes.includes("PRIVACY_CHANGE_PENDING") && currentReviewInterruptions(work).length === 0;
|
|
1225
1225
|
}
|
|
1226
1226
|
/** A selected Work that is not skipped (ADR 0236). */
|
|
1227
1227
|
function isIncludedWork(work) {
|
|
@@ -1239,6 +1239,12 @@ function hasPublicChoice(work) {
|
|
|
1239
1239
|
if (!work.accessConfirmed || !work.accessChoice) return false;
|
|
1240
1240
|
return work.accessChoice.channel === "account" ? work.accessOrigin === "account" : work.accessOrigin === "explicit";
|
|
1241
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";
|
|
1247
|
+
}
|
|
1242
1248
|
function selectedWorks(state) {
|
|
1243
1249
|
return state.works.filter(isSelectedWork);
|
|
1244
1250
|
}
|
|
@@ -1254,9 +1260,13 @@ function hasCertifiedNoBatchReview(work, snapshotId) {
|
|
|
1254
1260
|
const roots = proofs.flatMap((proof) => proof.rootSessionIds);
|
|
1255
1261
|
return roots.length === work.rootSessionIds.length && new Set(roots).size === roots.length && work.rootSessionIds.every((id) => roots.includes(id));
|
|
1256
1262
|
}
|
|
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);
|
|
1266
|
+
}
|
|
1257
1267
|
/** Reading completes over included Works once the person has confirmed the scan scope. */
|
|
1258
1268
|
function isReadingComplete(state) {
|
|
1259
|
-
return state.scope.status === "confirmed" && state.works.length > 0 && !isClassifying(state) && state.works.filter(isIncludedWork).every((work) => work.status !== "pending" && work.status !== "blocked" && (work.totalBatches > 0 || hasCertifiedNoBatchReview(work, state.snapshotId)) && work.completedBatches === work.totalBatches);
|
|
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);
|
|
1260
1270
|
}
|
|
1261
1271
|
/** Recompute completion after a selection or reading change without leaving a paused/send phase. */
|
|
1262
1272
|
function withReadingProgress(state) {
|
|
@@ -1910,7 +1920,23 @@ const nativeStartSchema = z.object({
|
|
|
1910
1920
|
period: sessionPeriodSchema.optional()
|
|
1911
1921
|
}).strict();
|
|
1912
1922
|
const nativeGetSchema = z.object({ journeyId: journeyIdSchema.optional() }).strict();
|
|
1923
|
+
const retryReviewActionSchema = reviewInterruptionSchema.pick({
|
|
1924
|
+
packId: true,
|
|
1925
|
+
inputRevision: true
|
|
1926
|
+
}).extend({
|
|
1927
|
+
kind: z.literal("retry-review"),
|
|
1928
|
+
workId: journeyIdSchema
|
|
1929
|
+
});
|
|
1913
1930
|
const journeyActionSchema = z.discriminatedUnion("kind", [
|
|
1931
|
+
reviewInterruptionSchema.extend({
|
|
1932
|
+
kind: z.literal("review-interrupted"),
|
|
1933
|
+
workId: journeyIdSchema
|
|
1934
|
+
}),
|
|
1935
|
+
reviewInterruptionSchema.omit({ reason: true }).extend({
|
|
1936
|
+
kind: z.literal("review-reader-closed"),
|
|
1937
|
+
workId: journeyIdSchema
|
|
1938
|
+
}),
|
|
1939
|
+
retryReviewActionSchema,
|
|
1914
1940
|
journeySplitActionSchema,
|
|
1915
1941
|
journeyMergeActionSchema,
|
|
1916
1942
|
journeyPropagateActionSchema,
|
|
@@ -1923,6 +1949,7 @@ const journeyActionSchema = z.discriminatedUnion("kind", [
|
|
|
1923
1949
|
kind: z.literal("consent"),
|
|
1924
1950
|
allow: z.boolean()
|
|
1925
1951
|
}).strict(),
|
|
1952
|
+
z.object({ kind: z.literal("account-read") }).strict(),
|
|
1926
1953
|
z.object({ kind: z.literal("abandon") }).strict(),
|
|
1927
1954
|
z.object({
|
|
1928
1955
|
kind: z.literal("delegate"),
|
|
@@ -2104,7 +2131,7 @@ function privacyDecisionDigest(state) {
|
|
|
2104
2131
|
return digest;
|
|
2105
2132
|
}
|
|
2106
2133
|
function assertJourneyScanned(state) {
|
|
2107
|
-
if (!state.consent || state.scope.status !== "confirmed" || !state.readingComplete || state.works.some((work) => isIncludedWork(work) && (!work.classificationMetadata || work.totalBatches === 0 && !hasCertifiedNoBatchReview(work, state.snapshotId) || work.completedBatches !== work.totalBatches || work.status === "blocked"))) throw new ApplicationError("SCOPE_NOT_READY");
|
|
2134
|
+
if (!state.consent || state.scope.status !== "confirmed" || !state.readingComplete || state.works.some((work) => isIncludedWork(work) && (currentReviewInterruptions(work).length > 0 || !work.classificationMetadata || work.totalBatches === 0 && !hasCertifiedNoBatchReview(work, state.snapshotId) || work.completedBatches !== work.totalBatches || work.status === "blocked"))) throw new ApplicationError("SCOPE_NOT_READY");
|
|
2108
2135
|
}
|
|
2109
2136
|
/** Legacy model, delegate or unrecorded confirmation is not the person's Public choice. */
|
|
2110
2137
|
function assertPublicChoices(state) {
|
|
@@ -2606,6 +2633,45 @@ const preparationSettlementSchema = z.object({
|
|
|
2606
2633
|
...binding,
|
|
2607
2634
|
facts: preparationFactsSchema
|
|
2608
2635
|
}).strict();
|
|
2636
|
+
const preparationStateSchema = nativeJourneySchema.pick({
|
|
2637
|
+
journeyId: true,
|
|
2638
|
+
revision: true,
|
|
2639
|
+
classificationRevision: true,
|
|
2640
|
+
phase: true,
|
|
2641
|
+
snapshotId: true,
|
|
2642
|
+
scope: true,
|
|
2643
|
+
consent: true,
|
|
2644
|
+
pendingHideId: true,
|
|
2645
|
+
configurationId: true,
|
|
2646
|
+
works: true,
|
|
2647
|
+
exceptions: true,
|
|
2648
|
+
instructions: true,
|
|
2649
|
+
instructionsRevision: true,
|
|
2650
|
+
readingComplete: true,
|
|
2651
|
+
readingStartedAt: true,
|
|
2652
|
+
period: true,
|
|
2653
|
+
adapters: true,
|
|
2654
|
+
reviewMethod: true
|
|
2655
|
+
});
|
|
2656
|
+
const preparationPermissionSchema = nativeJourneySchema.pick({
|
|
2657
|
+
destination: true,
|
|
2658
|
+
consent: true,
|
|
2659
|
+
phase: true,
|
|
2660
|
+
scope: true,
|
|
2661
|
+
pendingHideId: true
|
|
2662
|
+
});
|
|
2663
|
+
const preparationUpdateSchema = z.object({
|
|
2664
|
+
journeyId: journeyIdSchema,
|
|
2665
|
+
expectedRevision: preparationCountSchema,
|
|
2666
|
+
requestKey: journeyIdSchema,
|
|
2667
|
+
at: z.iso.datetime(),
|
|
2668
|
+
kind: z.enum(["snapshot", "batch"]),
|
|
2669
|
+
works: z.array(nativeJourneySchema.shape.works.element),
|
|
2670
|
+
phase: nativeJourneySchema.shape.phase,
|
|
2671
|
+
readingComplete: z.boolean(),
|
|
2672
|
+
readingStartedAt: nativeJourneySchema.shape.readingStartedAt,
|
|
2673
|
+
clearPackageApproval: z.boolean()
|
|
2674
|
+
}).strict();
|
|
2609
2675
|
function emptyPreparationFacts() {
|
|
2610
2676
|
return {
|
|
2611
2677
|
stage: "capture",
|
|
@@ -2632,1335 +2698,1727 @@ function validatePreparationFacts(start, facts) {
|
|
|
2632
2698
|
if (facts.unsupported.evaluated > start.sourceParts || facts.emptyReviewRoots.evaluated > start.reviewRoots || facts.unsupported.known + facts.resourceLimited.known + facts.excluded.known > facts.unsupported.evaluated) throw new ApplicationError("CORRUPTED_STATE");
|
|
2633
2699
|
}
|
|
2634
2700
|
//#endregion
|
|
2635
|
-
//#region packages/application/src/journey/
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
"INCOMPLETE_DISCOVERY",
|
|
2648
|
-
"DISCOVERY_ENTRY_LIMIT",
|
|
2649
|
-
"DISCOVERY_DEPTH_LIMIT",
|
|
2650
|
-
"BROKEN_SOURCE_LINK"
|
|
2651
|
-
]);
|
|
2652
|
-
const evidence = z.object({
|
|
2653
|
-
known: preparationCountSchema,
|
|
2654
|
-
total: preparationCountSchema.nullable()
|
|
2655
|
-
}).strict().refine((value) => value.total === null || value.total === value.known);
|
|
2656
|
-
const workPreparationObservationSchema = z.object({
|
|
2701
|
+
//#region packages/application/src/journey/correction/model.ts
|
|
2702
|
+
/** Original field-relative code-point coordinates, end-exclusive, exactly as B anchors. */
|
|
2703
|
+
const correctionAnchorSchema = z.object({
|
|
2704
|
+
fieldId: bIdSchema,
|
|
2705
|
+
recordId: bIdSchema,
|
|
2706
|
+
fieldHash: journeyDigestSchema,
|
|
2707
|
+
start: bCountSchema,
|
|
2708
|
+
end: bCountSchema,
|
|
2709
|
+
quote: z.string().min(1).max(4096)
|
|
2710
|
+
}).strict();
|
|
2711
|
+
const correctionReportSchema = z.object({
|
|
2712
|
+
id: journeyIdSchema,
|
|
2657
2713
|
workId: journeyIdSchema,
|
|
2658
|
-
inputRevision:
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
stage: preparationStageSchema.nullable(),
|
|
2665
|
-
sourceParts: z.object({
|
|
2666
|
-
unsupported: evidence,
|
|
2667
|
-
resourceLimited: evidence,
|
|
2668
|
-
excluded: evidence,
|
|
2669
|
-
unclassified: preparationCountSchema
|
|
2670
|
-
}).strict(),
|
|
2671
|
-
emptyReviewRoots: evidence,
|
|
2672
|
-
unevaluatedReviewRoots: preparationCountSchema
|
|
2714
|
+
inputRevision: z.number().int().min(1),
|
|
2715
|
+
batchId: journeyIdSchema,
|
|
2716
|
+
groupId: journeyIdSchema,
|
|
2717
|
+
kind: journeyKindSchema,
|
|
2718
|
+
reason: z.string().trim().min(1).max(1e3),
|
|
2719
|
+
anchors: z.array(correctionAnchorSchema).min(1).max(256)
|
|
2673
2720
|
}).strict();
|
|
2674
|
-
const
|
|
2721
|
+
const reviewCorrectSchema = z.object({
|
|
2675
2722
|
journeyId: journeyIdSchema,
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
retainedSessions: preparationCountSchema,
|
|
2680
|
-
reviewRoots: preparationCountSchema,
|
|
2681
|
-
identifiedMainRoots: preparationCountSchema,
|
|
2682
|
-
unknownRoleRoots: preparationCountSchema,
|
|
2683
|
-
attachedSubagents: preparationCountSchema,
|
|
2684
|
-
unresolvedWorkerSessions: preparationCountSchema,
|
|
2685
|
-
sourceParts: preparationCountSchema,
|
|
2686
|
-
frozenSourceBytes: preparationCountSchema,
|
|
2687
|
-
discoveryUnsupportedInputs: z.null(),
|
|
2688
|
-
discoverySkippedInputs: z.null(),
|
|
2689
|
-
discoverySkippedSourceBytes: z.null(),
|
|
2690
|
-
warningOccurrences: z.array(z.object({
|
|
2691
|
-
code: discoveryWarningCodeSchema,
|
|
2692
|
-
occurrences: preparationCountSchema
|
|
2693
|
-
}).strict())
|
|
2694
|
-
}).strict().nullable(),
|
|
2695
|
-
captures: z.object({
|
|
2696
|
-
targetParts: preparationCountSchema,
|
|
2697
|
-
capturedParts: preparationCountSchema,
|
|
2698
|
-
capturedBytes: preparationCountSchema,
|
|
2699
|
-
completeCapturedBytes: preparationCountSchema.nullable()
|
|
2700
|
-
}).strict().nullable(),
|
|
2701
|
-
workScope: z.object({
|
|
2702
|
-
total: preparationCountSchema,
|
|
2703
|
-
byStatus: z.object({
|
|
2704
|
-
pending: preparationCountSchema,
|
|
2705
|
-
reading: preparationCountSchema,
|
|
2706
|
-
review: preparationCountSchema,
|
|
2707
|
-
ready: preparationCountSchema,
|
|
2708
|
-
blocked: preparationCountSchema
|
|
2709
|
-
}).strict(),
|
|
2710
|
-
byAccess: z.object({
|
|
2711
|
-
public: preparationCountSchema,
|
|
2712
|
-
private: preparationCountSchema,
|
|
2713
|
-
drop: preparationCountSchema
|
|
2714
|
-
}).strict(),
|
|
2715
|
-
assignedReviewRoots: preparationCountSchema,
|
|
2716
|
-
unassignedReviewRoots: preparationCountSchema.nullable(),
|
|
2717
|
-
excludedAutomationRoots: preparationCountSchema.nullable(),
|
|
2718
|
-
unassignedOtherRoots: preparationCountSchema.nullable()
|
|
2719
|
-
}).strict(),
|
|
2720
|
-
preparation: z.array(workPreparationObservationSchema)
|
|
2723
|
+
expectedRevision: z.number().int().nonnegative(),
|
|
2724
|
+
idempotencyKey: journeyIdSchema,
|
|
2725
|
+
report: correctionReportSchema
|
|
2721
2726
|
}).strict();
|
|
2722
|
-
/**
|
|
2723
|
-
const
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
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
|
|
2729
2753
|
}).strict();
|
|
2730
2754
|
//#endregion
|
|
2731
|
-
//#region packages/application/src/
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
const
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
reason
|
|
2748
|
-
});
|
|
2749
|
-
depths.set(node.key, depth);
|
|
2750
|
-
queue.push(node);
|
|
2751
|
-
};
|
|
2752
|
-
const pending = (node, reason) => finish(node, route === "original", true, reason);
|
|
2753
|
-
for (const node of nodes) {
|
|
2754
|
-
const { identity, reason } = node.observation;
|
|
2755
|
-
if (families.unresolved.has(node.session.id)) finish(node, false, node.ancestry !== "missing", node.ancestry === "missing" ? "missing-parent" : "ancestry-unresolved");
|
|
2756
|
-
else if (!familySelected(inventory, families, node.session)) finish(node, false, false, families.owners.get(node.session.id)?.id !== node.session.id ? "deselected-parent" : node.session.selected ? "workspace-not-selected" : "not-selected");
|
|
2757
|
-
else if (reason !== null) pending(node, reason);
|
|
2758
|
-
else if (identity?.role === "guardian") finish(node, false, false, "guardian-excluded");
|
|
2759
|
-
else if (identity?.role !== "worker") finish(node, true, false, null);
|
|
2760
|
-
else if (node.ancestry === "missing") finish(node, false, false, "missing-parent");
|
|
2761
|
-
else if (node.ancestry !== "parents") finish(node, false, true, "ancestry-unresolved");
|
|
2762
|
-
else {
|
|
2763
|
-
remaining.set(node.key, node.parents.length);
|
|
2764
|
-
for (const parent of node.parents) {
|
|
2765
|
-
const children = dependents.get(parent.key) ?? [];
|
|
2766
|
-
children.push(node);
|
|
2767
|
-
dependents.set(parent.key, children);
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
}
|
|
2771
|
-
for (let cursor = 0; cursor < queue.length; cursor++) {
|
|
2772
|
-
const parent = queue[cursor];
|
|
2773
|
-
if (!parent) continue;
|
|
2774
|
-
const value = resolved.get(parent.key);
|
|
2775
|
-
for (const node of dependents.get(parent.key) ?? []) {
|
|
2776
|
-
if (resolved.has(node.key)) continue;
|
|
2777
|
-
const outstanding = (remaining.get(node.key) ?? 1) - 1;
|
|
2778
|
-
remaining.set(node.key, outstanding);
|
|
2779
|
-
const depth = (depths.get(parent.key) ?? 0) + 1;
|
|
2780
|
-
if (value?.included && !value.unresolved && (route === "original" || depth <= 256)) {
|
|
2781
|
-
finish(node, true, false, null, depth);
|
|
2782
|
-
continue;
|
|
2783
|
-
}
|
|
2784
|
-
if (outstanding > 0) continue;
|
|
2785
|
-
const results = node.parents.map((entry) => resolved.get(entry.key));
|
|
2786
|
-
if (results.every((entry) => entry && [
|
|
2787
|
-
"not-selected",
|
|
2788
|
-
"workspace-not-selected",
|
|
2789
|
-
"deselected-parent"
|
|
2790
|
-
].includes(entry.reason ?? ""))) finish(node, false, false, "deselected-parent");
|
|
2791
|
-
else if (results.every((entry) => entry?.reason === "missing-parent" || entry?.reason === "deselected-parent")) finish(node, false, false, "missing-parent");
|
|
2792
|
-
else finish(node, false, true, "ancestry-unresolved");
|
|
2793
|
-
}
|
|
2794
|
-
}
|
|
2795
|
-
for (const node of nodes) if (!resolved.has(node.key)) finish(node, false, true, "ancestry-unresolved");
|
|
2796
|
-
return resolved;
|
|
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;
|
|
2797
2771
|
}
|
|
2798
2772
|
//#endregion
|
|
2799
|
-
//#region packages/application/src/
|
|
2800
|
-
function
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
};
|
|
2811
|
-
const coverage = {
|
|
2812
|
-
includedSessions: 0,
|
|
2813
|
-
excludedSessions: 0,
|
|
2814
|
-
unresolvedSessions: 0,
|
|
2815
|
-
unresolvedParts: 0
|
|
2816
|
-
};
|
|
2817
|
-
for (const session of inventory.sessions) {
|
|
2818
|
-
const decisions = session.parts.flatMap((part) => {
|
|
2819
|
-
const value = parts.get(sourceKey(session.id, part.id));
|
|
2820
|
-
return value ? [{
|
|
2821
|
-
part,
|
|
2822
|
-
value
|
|
2823
|
-
}] : [];
|
|
2824
|
-
});
|
|
2825
|
-
const included = decisions.filter(({ value }) => value.included);
|
|
2826
|
-
const unresolved = decisions.filter(({ value }) => value.unresolved);
|
|
2827
|
-
const excluded = decisions.filter(({ value }) => !value.included && !value.unresolved);
|
|
2828
|
-
const selected = familySelected(inventory, families, session);
|
|
2829
|
-
const missing = selected && session.parts.length === 0;
|
|
2830
|
-
const reasons = [...new Set(decisions.flatMap(({ value }) => value.reason ? [value.reason] : []))].sort();
|
|
2831
|
-
if (missing) reasons.push("unreadable-source");
|
|
2832
|
-
const summary = Object.freeze({
|
|
2833
|
-
status: unresolved.length || missing ? "unresolved" : included.length ? excluded.length ? "partial" : "included" : "excluded",
|
|
2834
|
-
includedParts: included.length,
|
|
2835
|
-
excludedParts: excluded.length,
|
|
2836
|
-
unresolvedParts: unresolved.length,
|
|
2837
|
-
sourceBytes: included.reduce((sum, { part }) => sum + part.snapshot.size, 0),
|
|
2838
|
-
reasons: Object.freeze(reasons)
|
|
2839
|
-
});
|
|
2840
|
-
sessions.set(session.id, summary);
|
|
2841
|
-
if (!selected) continue;
|
|
2842
|
-
if (summary.includedParts) {
|
|
2843
|
-
counts.sessions++;
|
|
2844
|
-
counts.parts += summary.includedParts;
|
|
2845
|
-
counts.sourceBytes += summary.sourceBytes;
|
|
2846
|
-
includedWorkspaces.add(session.workspaceId);
|
|
2847
|
-
coverage.includedSessions++;
|
|
2848
|
-
}
|
|
2849
|
-
if (summary.status === "excluded") coverage.excludedSessions++;
|
|
2850
|
-
if (summary.status === "unresolved") coverage.unresolvedSessions++;
|
|
2851
|
-
coverage.unresolvedParts += summary.unresolvedParts;
|
|
2852
|
-
}
|
|
2853
|
-
counts.workspaces = includedWorkspaces.size;
|
|
2854
|
-
return Object.freeze({
|
|
2855
|
-
counts: Object.freeze(counts),
|
|
2856
|
-
coverage: Object.freeze(coverage),
|
|
2857
|
-
sessions,
|
|
2858
|
-
session: (sessionId) => {
|
|
2859
|
-
const value = sessions.get(sessionId);
|
|
2860
|
-
if (!value) throw new ApplicationError("UNKNOWN_ITEM");
|
|
2861
|
-
return value;
|
|
2862
|
-
},
|
|
2863
|
-
part: (sessionId, partId) => parts.get(sourceKey(sessionId, partId))
|
|
2864
|
-
});
|
|
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));
|
|
2865
2784
|
}
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
const
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
native: nativeSourceIdentitySchema,
|
|
2882
|
-
adapterVersion: z.string().min(1),
|
|
2883
|
-
reason: z.enum([
|
|
2884
|
-
"guardian-excluded",
|
|
2885
|
-
"missing-parent",
|
|
2886
|
-
"deselected-parent",
|
|
2887
|
-
"ancestry-unresolved",
|
|
2888
|
-
"non-interactive-launch"
|
|
2889
|
-
]),
|
|
2890
|
-
facts: z.object({
|
|
2891
|
-
representation: z.enum(["jsonl", "jsonl.zst"]),
|
|
2892
|
-
byteLength: z.number().int().nonnegative(),
|
|
2893
|
-
sha256: journeyDigestSchema
|
|
2894
|
-
}).strict()
|
|
2895
|
-
}).strict();
|
|
2896
|
-
/** Validate policy against frozen identity evidence, never a caller-asserted source role. */
|
|
2897
|
-
function validateScanExclusion(resource, state, inventory, capture) {
|
|
2898
|
-
const work = state.works.find((item) => item.id === resource.workId);
|
|
2899
|
-
if (state.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
|
|
2900
|
-
if (!work || state.scope.status !== "confirmed" || work.access === "drop" || state.pendingHideId) throw new ApplicationError("SCOPE_NOT_READY");
|
|
2901
|
-
if (state.journeyId !== resource.journeyId || work.inputRevision !== resource.inputRevision || state.snapshotId !== resource.snapshotId || !work.rootSessionIds.includes(resource.rootSessionId)) throw new ApplicationError("REVISION_CONFLICT");
|
|
2902
|
-
const families = reviewFamilies(inventory);
|
|
2903
|
-
const part = (families.members.get(resource.rootSessionId)?.find((item) => item.id === resource.sessionId))?.parts.find((item) => item.id === resource.sourcePartId);
|
|
2904
|
-
const evidence = capture.source.nativeEvidence;
|
|
2905
|
-
const launch = resource.reason === "non-interactive-launch";
|
|
2906
|
-
const ancestry = resource.reason === "ancestry-unresolved";
|
|
2907
|
-
if (ancestry && resource.native.role !== "worker") throw new ApplicationError("CORRUPTED_STATE");
|
|
2908
|
-
const prefixRoot = launch && resource.rootSessionId === resource.sessionId && resource.native.role === "main" && evidence?.recognition === "unknown" && evidence.completeness === "prefix" && JSON.stringify(evidence.identity) === JSON.stringify({
|
|
2909
|
-
...resource.native,
|
|
2910
|
-
role: "unknown"
|
|
2911
|
-
});
|
|
2912
|
-
if (!part || capture.partId !== resource.sourcePartId || capture.facts.sha256 !== resource.facts.sha256 || capture.facts.byteLength !== resource.facts.byteLength || capture.facts.representation !== resource.facts.representation || !evidence || !prefixRoot && (evidence.recognition !== "recognized" || JSON.stringify(evidence.identity) !== JSON.stringify(resource.native)) || evidence.integrity !== "valid" || evidence.adapterVersion !== resource.adapterVersion || evidence.snapshotId !== sourceSnapshotId(capture.source) || JSON.stringify(part.nativeEvidence) !== JSON.stringify(evidence)) throw new ApplicationError("CORRUPTED_STATE");
|
|
2913
|
-
const ids = new Set(work.rootSessionIds.flatMap((id) => (families.members.get(id) ?? []).map((item) => item.id)));
|
|
2914
|
-
const sessions = inventory.sessions.filter((item) => ids.has(item.id));
|
|
2915
|
-
const workspaceIds = new Set(sessions.map((item) => item.workspaceId));
|
|
2916
|
-
const decision = resolveSourceSelection({
|
|
2917
|
-
...inventory,
|
|
2918
|
-
sessions,
|
|
2919
|
-
workspaces: inventory.workspaces.filter((item) => workspaceIds.has(item.id))
|
|
2920
|
-
}, "configured").part(resource.sessionId, resource.sourcePartId);
|
|
2921
|
-
if (!decision || (prefixRoot ? decision.included || decision.reason !== "unknown-source" : ancestry ? decision.included ? decision.reason !== null : decision.reason !== resource.reason : decision.included !== launch || (launch ? decision.reason !== null : decision.reason !== resource.reason))) throw new ApplicationError("CORRUPTED_STATE");
|
|
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
|
+
};
|
|
2922
2800
|
}
|
|
2923
2801
|
//#endregion
|
|
2924
|
-
//#region packages/application/src/journey/
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
const
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
}
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
const
|
|
2947
|
-
|
|
2948
|
-
|
|
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"
|
|
2949
2847
|
};
|
|
2950
2848
|
return {
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
sha256: textDigest(JSON.stringify(value))
|
|
2849
|
+
...base,
|
|
2850
|
+
belief: preference === null ? .5 : preference === "hide" ? .97 : .03,
|
|
2851
|
+
choice: preference,
|
|
2852
|
+
confirmed: false,
|
|
2853
|
+
origin: preference === null ? "model" : "preference"
|
|
2957
2854
|
};
|
|
2958
2855
|
}
|
|
2959
|
-
/**
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
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;
|
|
2963
2877
|
}
|
|
2964
2878
|
//#endregion
|
|
2965
|
-
//#region packages/application/src/journey/
|
|
2966
|
-
|
|
2967
|
-
const
|
|
2968
|
-
|
|
2969
|
-
identity
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
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);
|
|
2973
2889
|
/**
|
|
2974
|
-
*
|
|
2975
|
-
*
|
|
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).
|
|
2976
2892
|
*/
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
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;
|
|
3016
3012
|
}
|
|
3017
|
-
const scanBatchBinding = {
|
|
3018
|
-
journeyId: journeyIdSchema,
|
|
3019
|
-
workId: journeyIdSchema,
|
|
3020
|
-
inputRevision: count$9.min(1),
|
|
3021
|
-
snapshotId: journeyDigestSchema,
|
|
3022
|
-
configurationDigest: journeyDigestSchema
|
|
3023
|
-
};
|
|
3024
|
-
const scanCanonicalResourceSchema = z.object({
|
|
3025
|
-
...scanBatchBinding,
|
|
3026
|
-
kind: z.literal("scan-canonical"),
|
|
3027
|
-
id: journeyDigestSchema,
|
|
3028
|
-
batchId: journeyIdSchema,
|
|
3029
|
-
batchDigest: journeyDigestSchema
|
|
3030
|
-
}).strict();
|
|
3031
|
-
const scanAliasResourceSchema = z.object({
|
|
3032
|
-
...scanBatchBinding,
|
|
3033
|
-
kind: z.literal("scan-alias"),
|
|
3034
|
-
id: journeyIdSchema,
|
|
3035
|
-
canonicalBatchId: journeyIdSchema,
|
|
3036
|
-
canonicalBatchDigest: journeyDigestSchema,
|
|
3037
|
-
targetBatchId: journeyIdSchema,
|
|
3038
|
-
targetBatchDigest: journeyDigestSchema,
|
|
3039
|
-
equivalenceDigest: journeyDigestSchema
|
|
3040
|
-
}).strict();
|
|
3041
3013
|
//#endregion
|
|
3042
|
-
//#region packages/application/src/journey/b/
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
const
|
|
3052
|
-
const
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
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()];
|
|
3067
3042
|
}
|
|
3068
|
-
function
|
|
3069
|
-
const
|
|
3070
|
-
const
|
|
3071
|
-
|
|
3072
|
-
const
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
...correction.retainedSourceGroups,
|
|
3077
|
-
...correction.representationLimits,
|
|
3078
|
-
...correction.uncertainOccurrences
|
|
3079
|
-
].some((part) => !targeted(part.fieldId))) throw new ApplicationError("INVALID_REVIEW_SPAN");
|
|
3080
|
-
const repaired = validateBReview(unit, correction, plan.targetFields);
|
|
3081
|
-
const prior = validateBReview(unit, previous.submission);
|
|
3082
|
-
const correctedMeanings = /* @__PURE__ */ new Map();
|
|
3083
|
-
for (const part of repaired.sensitive) {
|
|
3084
|
-
const key = bAnchorKey(part);
|
|
3085
|
-
const meanings = correctedMeanings.get(key) ?? /* @__PURE__ */ new Set();
|
|
3086
|
-
meanings.add(repairMeaning(part));
|
|
3087
|
-
correctedMeanings.set(key, meanings);
|
|
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));
|
|
3088
3051
|
}
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
}
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
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)));
|
|
3058
|
+
}
|
|
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);
|
|
3069
|
+
}
|
|
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
|
+
};
|
|
3084
|
+
}
|
|
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
|
+
}));
|
|
3128
3102
|
};
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3103
|
+
}
|
|
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));
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
const segmenter = new Intl.Segmenter("und", { granularity: "grapheme" });
|
|
3117
|
+
/** Beyond this many inner match edges in one word the index is not kept (retire instead). */
|
|
3118
|
+
const maximumEdges = 16;
|
|
3119
|
+
/** Beyond this many distinct pieces the index is not kept (retire instead). */
|
|
3120
|
+
const maximumHideIndexTokens = 2e6;
|
|
3121
|
+
/**
|
|
3122
|
+
* Adds the word pieces of `text` that an occurrence of a confirmed Hide value can consist of.
|
|
3123
|
+
* The matcher compares per-grapheme NFC text and checks word edges on the original characters
|
|
3124
|
+
* at grapheme edges only, so a match may start or end inside a run of word characters there,
|
|
3125
|
+
* or inside a grapheme. Every piece between such positions is added. Returns false when a run
|
|
3126
|
+
* has too many such positions to list.
|
|
3127
|
+
*/
|
|
3128
|
+
function addHideIndexTokens(text, add) {
|
|
3129
|
+
let complete = true;
|
|
3130
|
+
let from = 0;
|
|
3131
|
+
for (const { index } of text.matchAll(/[^\p{ASCII}]+/gu)) {
|
|
3132
|
+
if (index < from) continue;
|
|
3133
|
+
let start = index;
|
|
3134
|
+
while (start > from && asciiWord(text.charCodeAt(start - 1))) start--;
|
|
3135
|
+
let end = index + 1;
|
|
3136
|
+
while (end < text.length && (text.charCodeAt(end) > 127 || asciiWord(text.charCodeAt(end)))) end++;
|
|
3137
|
+
asciiTokens(text.slice(from, start), add);
|
|
3138
|
+
complete = graphemeTokens(text.slice(start, end), add) && complete;
|
|
3139
|
+
from = end;
|
|
3140
|
+
}
|
|
3141
|
+
asciiTokens(text.slice(from), add);
|
|
3142
|
+
return complete;
|
|
3143
|
+
}
|
|
3144
|
+
const asciiWord = (code) => code >= 48 && code <= 57 || code >= 65 && code <= 90 || code === 95 || code >= 97 && code <= 122;
|
|
3145
|
+
function asciiTokens(text, add) {
|
|
3146
|
+
for (const [token] of text.matchAll(/[A-Za-z0-9_]+/gu)) add(token);
|
|
3147
|
+
}
|
|
3148
|
+
function graphemeTokens(text, add) {
|
|
3149
|
+
let complete = true, run = [], edges = [];
|
|
3150
|
+
const flush = () => {
|
|
3151
|
+
if (edges.length > maximumEdges) complete = false;
|
|
3152
|
+
else {
|
|
3153
|
+
const cuts = [
|
|
3154
|
+
0,
|
|
3155
|
+
...edges,
|
|
3156
|
+
run.length
|
|
3157
|
+
];
|
|
3158
|
+
for (let from = 0; from < cuts.length; from++) for (let to = from + 1; to < cuts.length; to++) add(run.slice(cuts[from], cuts[to]).join(""));
|
|
3138
3159
|
}
|
|
3160
|
+
run = [];
|
|
3161
|
+
edges = [];
|
|
3139
3162
|
};
|
|
3163
|
+
let previous = "";
|
|
3164
|
+
for (const { segment } of segmenter.segment(text)) {
|
|
3165
|
+
const original = Array.from(segment);
|
|
3166
|
+
Array.from(segment.normalize("NFC")).forEach((scalar, index) => {
|
|
3167
|
+
if (!bSpacedWord(scalar)) {
|
|
3168
|
+
if (run.length) flush();
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
if (run.length && (index > 0 || !(bSpacedWord(previous) && bSpacedWord(original[0] ?? "")))) edges.push(run.length);
|
|
3172
|
+
run.push(scalar);
|
|
3173
|
+
});
|
|
3174
|
+
previous = original.at(-1) ?? "";
|
|
3175
|
+
}
|
|
3176
|
+
if (run.length) flush();
|
|
3177
|
+
return complete;
|
|
3140
3178
|
}
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
start: z.number().int().nonnegative().nullable(),
|
|
3153
|
-
end: z.number().int().nonnegative().nullable()
|
|
3154
|
-
}).strict();
|
|
3155
|
-
const reviewCandidateInputSchema = z.object({
|
|
3156
|
-
journeyId: journeyIdSchema,
|
|
3157
|
-
batchId: journeyIdSchema,
|
|
3158
|
-
batchDigest: journeyDigestSchema,
|
|
3159
|
-
idempotencyKey: journeyIdSchema,
|
|
3160
|
-
attemptId: journeyIdSchema,
|
|
3161
|
-
response: z.string().min(1).refine((value) => Buffer.byteLength(value, "utf8") <= reviewResponseBytes),
|
|
3162
|
-
repairId: journeyIdSchema.nullable(),
|
|
3163
|
-
sessionReviewId: journeyIdSchema.nullable()
|
|
3164
|
-
}).strict();
|
|
3165
|
-
const reviewCandidateResourceSchema = reviewCandidateInputSchema.omit({ idempotencyKey: true }).extend({
|
|
3166
|
-
kind: z.literal("b-candidate"),
|
|
3167
|
-
id: journeyIdSchema,
|
|
3168
|
-
requestHash: journeyDigestSchema,
|
|
3169
|
-
at: z.iso.datetime(),
|
|
3170
|
-
workId: journeyIdSchema,
|
|
3171
|
-
inputRevision: z.number().int().positive(),
|
|
3172
|
-
snapshotId: journeyDigestSchema,
|
|
3173
|
-
responseDigest: journeyDigestSchema,
|
|
3174
|
-
status: z.enum([
|
|
3175
|
-
"valid",
|
|
3176
|
-
"pending",
|
|
3177
|
-
"invalid"
|
|
3178
|
-
]),
|
|
3179
|
-
issues: z.array(reviewIssueSchema),
|
|
3180
|
-
evidence: bReviewSubmissionSchema.nullable()
|
|
3181
|
-
}).strict();
|
|
3182
|
-
const reviewCandidateResultSchema = reviewCandidateResourceSchema.pick({
|
|
3183
|
-
responseDigest: true,
|
|
3184
|
-
status: true,
|
|
3185
|
-
issues: true,
|
|
3186
|
-
evidence: true
|
|
3187
|
-
}).extend({ candidateId: journeyIdSchema }).strict();
|
|
3188
|
-
const reviewRepairInputSchema = z.object({
|
|
3189
|
-
journeyId: journeyIdSchema,
|
|
3190
|
-
candidateId: journeyIdSchema,
|
|
3191
|
-
idempotencyKey: journeyIdSchema
|
|
3192
|
-
}).strict();
|
|
3193
|
-
const reviewRepairResourceSchema = z.object({
|
|
3194
|
-
kind: z.literal("b-repair"),
|
|
3195
|
-
id: journeyIdSchema,
|
|
3196
|
-
journeyId: journeyIdSchema,
|
|
3197
|
-
workId: journeyIdSchema,
|
|
3198
|
-
batchId: journeyIdSchema,
|
|
3199
|
-
batchDigest: journeyDigestSchema,
|
|
3200
|
-
inputRevision: z.number().int().positive(),
|
|
3201
|
-
snapshotId: journeyDigestSchema,
|
|
3202
|
-
candidateId: journeyIdSchema,
|
|
3203
|
-
requestHash: journeyDigestSchema,
|
|
3204
|
-
at: z.iso.datetime(),
|
|
3205
|
-
plan: bRepairPlanSchema
|
|
3206
|
-
}).strict();
|
|
3207
|
-
//#endregion
|
|
3208
|
-
//#region packages/application/src/journey/correction/model.ts
|
|
3209
|
-
/** Original field-relative code-point coordinates, end-exclusive, exactly as B anchors. */
|
|
3210
|
-
const correctionAnchorSchema = z.object({
|
|
3211
|
-
fieldId: bIdSchema,
|
|
3212
|
-
recordId: bIdSchema,
|
|
3213
|
-
fieldHash: journeyDigestSchema,
|
|
3214
|
-
start: bCountSchema,
|
|
3215
|
-
end: bCountSchema,
|
|
3216
|
-
quote: z.string().min(1).max(4096)
|
|
3217
|
-
}).strict();
|
|
3218
|
-
const correctionReportSchema = z.object({
|
|
3219
|
-
id: journeyIdSchema,
|
|
3220
|
-
workId: journeyIdSchema,
|
|
3221
|
-
inputRevision: z.number().int().min(1),
|
|
3222
|
-
batchId: journeyIdSchema,
|
|
3223
|
-
groupId: journeyIdSchema,
|
|
3224
|
-
kind: journeyKindSchema,
|
|
3225
|
-
reason: z.string().trim().min(1).max(1e3),
|
|
3226
|
-
anchors: z.array(correctionAnchorSchema).min(1).max(256)
|
|
3227
|
-
}).strict();
|
|
3228
|
-
const reviewCorrectSchema = z.object({
|
|
3229
|
-
journeyId: journeyIdSchema,
|
|
3230
|
-
expectedRevision: z.number().int().nonnegative(),
|
|
3231
|
-
idempotencyKey: journeyIdSchema,
|
|
3232
|
-
report: correctionReportSchema
|
|
3233
|
-
}).strict();
|
|
3234
|
-
/** Immutable private report receipt; the only place where correction quotes are retained. */
|
|
3235
|
-
const correctionReportResourceSchema = z.object({
|
|
3236
|
-
journeyId: journeyIdSchema,
|
|
3237
|
-
kind: z.literal("correction-report"),
|
|
3238
|
-
id: journeyIdSchema,
|
|
3239
|
-
requestHash: journeyDigestSchema,
|
|
3240
|
-
at: z.iso.datetime(),
|
|
3241
|
-
snapshotId: journeyDigestSchema,
|
|
3242
|
-
batchDigest: journeyDigestSchema,
|
|
3243
|
-
decisionDigest: journeyDigestSchema,
|
|
3244
|
-
report: correctionReportSchema
|
|
3245
|
-
}).strict();
|
|
3246
|
-
const correctionAuditSchema = z.object({
|
|
3247
|
-
reportId: journeyIdSchema,
|
|
3248
|
-
action: z.enum([
|
|
3249
|
-
"report",
|
|
3250
|
-
"hide",
|
|
3251
|
-
"dismiss"
|
|
3252
|
-
]),
|
|
3253
|
-
actor: z.enum([
|
|
3254
|
-
"observer",
|
|
3255
|
-
"human",
|
|
3256
|
-
"delegate"
|
|
3257
|
-
]),
|
|
3258
|
-
authorityId: journeyIdSchema.nullable(),
|
|
3259
|
-
decisionDigest: journeyDigestSchema
|
|
3260
|
-
}).strict();
|
|
3261
|
-
//#endregion
|
|
3262
|
-
//#region packages/application/src/journey/correction/binding.ts
|
|
3263
|
-
function workCorrections(state, workId) {
|
|
3264
|
-
return (state.corrections ?? []).filter((item) => item.workId === workId);
|
|
3179
|
+
/** The words of an NFC value, or null when it has none and cannot be checked by words. */
|
|
3180
|
+
function hideValueTokens(term) {
|
|
3181
|
+
const tokens = [];
|
|
3182
|
+
let run = "";
|
|
3183
|
+
for (const scalar of term) if (bSpacedWord(scalar)) run += scalar;
|
|
3184
|
+
else if (run) {
|
|
3185
|
+
tokens.push(run);
|
|
3186
|
+
run = "";
|
|
3187
|
+
}
|
|
3188
|
+
if (run) tokens.push(run);
|
|
3189
|
+
return tokens.length ? [...new Set(tokens)] : null;
|
|
3265
3190
|
}
|
|
3266
|
-
/**
|
|
3267
|
-
function
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3191
|
+
/** A keyed, truncated digest; a collision can only retire an unaffected Work. */
|
|
3192
|
+
function hideTokenDigest(key, token) {
|
|
3193
|
+
return createHmac("sha256", key).update(token, "utf8").digest("base64url").slice(0, 11);
|
|
3194
|
+
}
|
|
3195
|
+
/** Identifies the key an index or delta was made with, without revealing it. */
|
|
3196
|
+
const hideKeyId = (key) => hideTokenDigest(key, "\0key");
|
|
3197
|
+
/** Sorted, distinct, fixed-width digests in one string. */
|
|
3198
|
+
function packHideIndex(key, tokens) {
|
|
3199
|
+
const secret = createSecretKey(key);
|
|
3200
|
+
return [...new Set([...tokens].map((token) => hideTokenDigest(secret, token)))].sort().join("");
|
|
3201
|
+
}
|
|
3202
|
+
/** One digest; an index is a run of them, checked by length and alphabet. */
|
|
3203
|
+
const hideDigestPattern = /^[A-Za-z0-9_-]{11}$/u;
|
|
3204
|
+
const isHideIndex = (value) => value.length % 11 === 0 && /^[A-Za-z0-9_-]*$/u.test(value);
|
|
3205
|
+
function hideIndexHas(index, digest) {
|
|
3206
|
+
let low = 0, high = index.length / 11 - 1;
|
|
3207
|
+
while (low <= high) {
|
|
3208
|
+
const middle = low + high >> 1;
|
|
3209
|
+
const value = index.slice(middle * 11, (middle + 1) * 11);
|
|
3210
|
+
if (value === digest) return true;
|
|
3211
|
+
if (value < digest) low = middle + 1;
|
|
3212
|
+
else high = middle - 1;
|
|
3213
|
+
}
|
|
3214
|
+
return false;
|
|
3278
3215
|
}
|
|
3279
3216
|
//#endregion
|
|
3280
|
-
//#region packages/application/src/journey/
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3217
|
+
//#region packages/application/src/journey/review/retirement.ts
|
|
3218
|
+
/**
|
|
3219
|
+
* ADR 0079: the confirmed Hide values a commit adds, removes or recategorizes, with the kind
|
|
3220
|
+
* each then has (null once removed; for additions, the lowest it can have). Terms exist only in
|
|
3221
|
+
* memory for this commit. `rendered` holds the value's keyed digest for each kind, to compare
|
|
3222
|
+
* with the values a Work was rendered with; `tokens` holds its words' keyed digests, or null
|
|
3223
|
+
* when it has none. A partial value is a literal that may join a neighbour across a chunk edge,
|
|
3224
|
+
* so its whole text, and every Work it may reach, is unknown.
|
|
3225
|
+
*/
|
|
3226
|
+
const deltaValueSchema = z.object({
|
|
3227
|
+
term: z.string().min(1),
|
|
3228
|
+
kind: journeyKindSchema.nullable(),
|
|
3229
|
+
partial: z.boolean(),
|
|
3230
|
+
rendered: z.array(z.string().regex(hideDigestPattern)).length(confirmedHideKinds.length),
|
|
3231
|
+
tokens: z.array(z.string().regex(hideDigestPattern)).min(1).nullable(),
|
|
3232
|
+
/** The new findings an addition comes from; the store keeps it only if one joins a Hide. */
|
|
3233
|
+
sources: z.array(z.object({
|
|
3234
|
+
batchId: z.uuid(),
|
|
3235
|
+
findingId: z.uuid()
|
|
3236
|
+
}).strict()).min(1).optional()
|
|
3237
|
+
}).strict();
|
|
3238
|
+
/** `keyId` names the journey key the digests were made with; an index under another key is unusable. */
|
|
3239
|
+
const keyId = z.string().regex(hideDigestPattern);
|
|
3240
|
+
const confirmedHideDeltaSchema = z.discriminatedUnion("scope", [z.object({
|
|
3241
|
+
scope: z.literal("complete"),
|
|
3242
|
+
keyId,
|
|
3243
|
+
from: z.string(),
|
|
3244
|
+
to: z.string(),
|
|
3245
|
+
values: z.array(deltaValueSchema)
|
|
3246
|
+
}).strict(), z.object({
|
|
3247
|
+
scope: z.literal("additions"),
|
|
3248
|
+
keyId,
|
|
3249
|
+
values: z.array(deltaValueSchema)
|
|
3250
|
+
}).strict()]);
|
|
3251
|
+
/** What a value was rendered as: its kind, in the digests a render keeps (ADR 0079). */
|
|
3252
|
+
const renderedHideValue = (kind, term) => `${kind}\u0000${term}`;
|
|
3253
|
+
function hideDeltaValue(key, term, kind, partial = false) {
|
|
3254
|
+
const tokens = partial ? null : hideValueTokens(term);
|
|
3255
|
+
return {
|
|
3256
|
+
term,
|
|
3285
3257
|
kind,
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
3291
|
-
}
|
|
3292
|
-
/** Corrections are additive under an unchanged Hide group, so they bind metadata review too. */
|
|
3293
|
-
function workDecisionDigest(state, work) {
|
|
3294
|
-
const decisions = workPrivacyDecisions(state, work);
|
|
3295
|
-
const corrections = workCorrectionBinding(state, work.id);
|
|
3296
|
-
return textDigest(JSON.stringify(corrections ? {
|
|
3297
|
-
decisions,
|
|
3298
|
-
corrections
|
|
3299
|
-
} : decisions));
|
|
3258
|
+
partial,
|
|
3259
|
+
rendered: confirmedHideKinds.map((item) => hideTokenDigest(key, renderedHideValue(item, term))),
|
|
3260
|
+
tokens: tokens && tokens.map((token) => hideTokenDigest(key, token))
|
|
3261
|
+
};
|
|
3300
3262
|
}
|
|
3301
|
-
|
|
3302
|
-
|
|
3263
|
+
const kindOf = (kind) => journeyKindSchema.nullable().parse(kind ?? null);
|
|
3264
|
+
const rank = (kind) => kind === null ? -1 : confirmedHideKinds.indexOf(kind);
|
|
3265
|
+
/**
|
|
3266
|
+
* Every value whose hidden kind differs between two states (ADR 0086): both are read against the
|
|
3267
|
+
* same dictionary, which only grows and is unchanged by the commit that asks.
|
|
3268
|
+
*/
|
|
3269
|
+
async function readConfirmedHideChange(reader, key, previous, next) {
|
|
3270
|
+
const terms = await reader.journeyTerms({ journeyId: previous.journeyId });
|
|
3271
|
+
const before = dictionaryHides(previous, terms), after = dictionaryHides(next, terms);
|
|
3303
3272
|
return {
|
|
3304
|
-
|
|
3305
|
-
|
|
3273
|
+
scope: "complete",
|
|
3274
|
+
keyId: hideKeyId(key),
|
|
3275
|
+
from: confirmedHideKey(previous),
|
|
3276
|
+
to: confirmedHideKey(next),
|
|
3277
|
+
values: [.../* @__PURE__ */ new Set([...before.keys(), ...after.keys()])].filter((term) => before.get(term)?.kind !== after.get(term)?.kind).map((term) => hideDeltaValue(key, term, kindOf(after.get(term)?.kind)))
|
|
3306
3278
|
};
|
|
3307
3279
|
}
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
const
|
|
3311
|
-
const
|
|
3312
|
-
/** RFC 9562 UUID version 5 of `name` in the `namespace` UUID. */
|
|
3313
|
-
function uuidV5(namespace, name) {
|
|
3314
|
-
const bytes = createHash("sha1").update(Buffer.from(namespace.replaceAll("-", ""), "hex")).update(name, "utf8").digest().subarray(0, 16);
|
|
3315
|
-
bytes.writeUInt8(bytes.readUInt8(6) & 15 | 80, 6);
|
|
3316
|
-
bytes.writeUInt8(bytes.readUInt8(8) & 63 | 128, 8);
|
|
3317
|
-
const hex = bytes.toString("hex");
|
|
3318
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
3319
|
-
}
|
|
3320
|
-
/** Deterministic: every reader and writer of a journey names a type's group alike. */
|
|
3321
|
-
const termGroupId = (journeyId, type) => uuidV5(journeyId, type);
|
|
3322
|
-
/** The term type a group answers, or null for any other category. */
|
|
3323
|
-
function termGroupType(group) {
|
|
3324
|
-
if (!group.key.startsWith(keyPrefix)) return null;
|
|
3325
|
-
const parsed = termTypeSchema.safeParse(group.key.slice(5));
|
|
3326
|
-
return parsed.success ? parsed.data : null;
|
|
3327
|
-
}
|
|
3328
|
-
const typeGroup = (state, type) => state.groups.find((group) => termGroupType(group) === type);
|
|
3329
|
-
function newTypeGroup(state, type, preference) {
|
|
3330
|
-
const info = termTypeInfo[type];
|
|
3331
|
-
const base = {
|
|
3332
|
-
id: termGroupId(state.journeyId, type),
|
|
3333
|
-
kind: info.kind,
|
|
3334
|
-
key: termGroupKey(type),
|
|
3335
|
-
label: info.label,
|
|
3336
|
-
condition: info.condition,
|
|
3337
|
-
count: 0,
|
|
3338
|
-
preferenceScope: null,
|
|
3339
|
-
representative: null
|
|
3340
|
-
};
|
|
3341
|
-
if (!info.asked) return {
|
|
3342
|
-
...base,
|
|
3343
|
-
belief: .97,
|
|
3344
|
-
choice: "hide",
|
|
3345
|
-
confirmed: true,
|
|
3346
|
-
origin: "policy"
|
|
3347
|
-
};
|
|
3348
|
-
if (state.questionsPaused) return {
|
|
3349
|
-
...base,
|
|
3350
|
-
belief: .97,
|
|
3351
|
-
choice: "hide",
|
|
3352
|
-
confirmed: false,
|
|
3353
|
-
origin: "paused"
|
|
3354
|
-
};
|
|
3280
|
+
/** Dictionary values a commit may add to the hidden set, each with its type's kind (ADR 0086). */
|
|
3281
|
+
function termHideAdditions(key, values) {
|
|
3282
|
+
const unique = /* @__PURE__ */ new Map();
|
|
3283
|
+
for (const { term, kind } of values) unique.set(JSON.stringify([term, kind]), hideDeltaValue(key, term, kindOf(kind)));
|
|
3355
3284
|
return {
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
confirmed: false,
|
|
3360
|
-
origin: preference === null ? "model" : "preference"
|
|
3285
|
+
scope: "additions",
|
|
3286
|
+
keyId: hideKeyId(key),
|
|
3287
|
+
values: [...unique.values()]
|
|
3361
3288
|
};
|
|
3362
3289
|
}
|
|
3363
3290
|
/**
|
|
3364
|
-
*
|
|
3365
|
-
*
|
|
3291
|
+
* The values new Gray findings can add. A finding at a chunk edge may join a neighbouring
|
|
3292
|
+
* literal of the same field, so its words are not checked.
|
|
3366
3293
|
*/
|
|
3367
|
-
function
|
|
3368
|
-
const
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3294
|
+
function confirmedHideAdditions(key, records) {
|
|
3295
|
+
const values = /* @__PURE__ */ new Map();
|
|
3296
|
+
for (const record of records) {
|
|
3297
|
+
const chunks = new Map(record.chunks.map((chunk) => [chunk.id, chunk]));
|
|
3298
|
+
for (const finding of record.findings) {
|
|
3299
|
+
const chunk = chunks.get(finding.chunkId);
|
|
3300
|
+
if (!chunk || finding.class !== "gray") continue;
|
|
3301
|
+
const whole = chunk.offset === 0 && chunk.textHash === chunk.fieldHash;
|
|
3302
|
+
const joins = finding.start === 0 && chunk.offset > 0 || finding.end === chunk.text.length && !whole;
|
|
3303
|
+
const source = {
|
|
3304
|
+
batchId: record.batchId,
|
|
3305
|
+
findingId: finding.id
|
|
3306
|
+
};
|
|
3307
|
+
for (const term of bLiteralTerms(chunk.text.slice(finding.start, finding.end))) {
|
|
3308
|
+
const id = JSON.stringify([
|
|
3309
|
+
term,
|
|
3310
|
+
finding.kind,
|
|
3311
|
+
joins
|
|
3312
|
+
]);
|
|
3313
|
+
const value = values.get(id);
|
|
3314
|
+
if (value) value.sources.push(source);
|
|
3315
|
+
else values.set(id, {
|
|
3316
|
+
...hideDeltaValue(key, term, finding.kind, joins),
|
|
3317
|
+
sources: [source]
|
|
3318
|
+
});
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3382
3321
|
}
|
|
3383
|
-
return
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
const confirmedHideKinds = [
|
|
3389
|
-
"internal",
|
|
3390
|
-
"identity",
|
|
3391
|
-
"private",
|
|
3392
|
-
"credentials"
|
|
3393
|
-
];
|
|
3394
|
-
const kinds = confirmedHideKinds;
|
|
3395
|
-
const rank$1 = (kind) => kinds.indexOf(kind);
|
|
3396
|
-
/**
|
|
3397
|
-
* ADR 0086: a type's values are hidden unless its group is answered Keep; before an answer the
|
|
3398
|
-
* default is Hide. Credentials and contact details are always hidden (D06).
|
|
3399
|
-
*/
|
|
3400
|
-
function typeHides(state, type) {
|
|
3401
|
-
const group = typeGroup(state, type);
|
|
3402
|
-
return !termTypeInfo[type].asked || !group?.confirmed || group.choice !== "keep";
|
|
3322
|
+
return {
|
|
3323
|
+
scope: "additions",
|
|
3324
|
+
keyId: hideKeyId(key),
|
|
3325
|
+
values: [...values.values()]
|
|
3326
|
+
};
|
|
3403
3327
|
}
|
|
3404
|
-
/** B review (superseded by ADR 0086): IDs of the categories confirmed as Hide. */
|
|
3405
|
-
const confirmedHideGroupIds = (state) => state.groups.filter((group) => group.confirmed && group.choice === "hide").map((group) => group.id);
|
|
3406
|
-
const overrideKey = (state) => (state.valueOverrides ?? []).map((item) => JSON.stringify([
|
|
3407
|
-
item.type,
|
|
3408
|
-
item.value,
|
|
3409
|
-
item.choice
|
|
3410
|
-
])).toSorted();
|
|
3411
3328
|
/**
|
|
3412
|
-
*
|
|
3413
|
-
*
|
|
3414
|
-
*
|
|
3329
|
+
* How hidden values moved when the value overrides and type answers stay the same: not at all,
|
|
3330
|
+
* only added (new dictionary values of hiding types), only removed, or otherwise. A type's
|
|
3331
|
+
* hidden values are its distinct values while it hides; an answer that changes whether a type
|
|
3332
|
+
* hides is otherwise, so only a complete delta covers it.
|
|
3415
3333
|
*/
|
|
3416
|
-
function
|
|
3417
|
-
return
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
overrides: overrideKey(state),
|
|
3424
|
-
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)))
|
|
3425
|
-
})).digest("hex");
|
|
3334
|
+
function memberChange(previous, next) {
|
|
3335
|
+
if (!sameValueOverrides(previous, next)) return "other";
|
|
3336
|
+
if (termTypes.some((type) => typeGroup(previous, type) !== void 0 && typeHides(previous, type) !== typeHides(next, type))) return "other";
|
|
3337
|
+
const hidden = (state, type) => typeHides(state, type) ? typeGroup(state, type)?.count ?? 0 : 0;
|
|
3338
|
+
const counts = termTypes.map((type) => hidden(next, type) - hidden(previous, type));
|
|
3339
|
+
if (counts.every((count) => count === 0)) return "same";
|
|
3340
|
+
return counts.every((count) => count >= 0) ? "additions" : counts.every((count) => count <= 0) ? "removals" : "other";
|
|
3426
3341
|
}
|
|
3427
|
-
/** Whether two states hold the same value overrides. */
|
|
3428
|
-
const sameValueOverrides = (left, right) => JSON.stringify(overrideKey(left)) === JSON.stringify(overrideKey(right));
|
|
3429
3342
|
/**
|
|
3430
|
-
*
|
|
3431
|
-
*
|
|
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
|
|
3345
|
+
* members were only removed: no value is then added, so nothing can be under-hidden, and an
|
|
3346
|
+
* output may keep masking a value that left its category until it is rendered again.
|
|
3432
3347
|
*/
|
|
3433
|
-
function
|
|
3434
|
-
const
|
|
3435
|
-
|
|
3348
|
+
function impact(previous, next, delta, outputIndex) {
|
|
3349
|
+
const change = memberChange(previous, next);
|
|
3350
|
+
const covered = delta && (delta.scope === "complete" ? delta.from === confirmedHideKey(previous) && delta.to === confirmedHideKey(next) : change === "additions" || change === "same");
|
|
3351
|
+
if (!delta || !covered) return change === "removals" || change === "same" ? () => false : () => true;
|
|
3352
|
+
const unchanged = (index, value) => {
|
|
3353
|
+
const at = value.rendered.findIndex((digest) => hideIndexHas(index.values, digest));
|
|
3354
|
+
const was = at < 0 ? null : confirmedHideKinds[at] ?? null;
|
|
3355
|
+
return delta.scope === "additions" ? rank(was) >= rank(value.kind) : was === value.kind;
|
|
3356
|
+
};
|
|
3357
|
+
const reaches = (index, value) => {
|
|
3358
|
+
const digests = index.digests;
|
|
3359
|
+
return !value.tokens || !digests || value.tokens.every((token) => hideIndexHas(digests, token));
|
|
3360
|
+
};
|
|
3361
|
+
return (work) => {
|
|
3362
|
+
if (!work.artifact) return false;
|
|
3363
|
+
const found = outputIndex(work);
|
|
3364
|
+
const index = found?.keyId === delta.keyId ? found : null;
|
|
3365
|
+
return !index || delta.values.some((value) => value.partial || !unchanged(index, value) && reaches(index, value));
|
|
3366
|
+
};
|
|
3436
3367
|
}
|
|
3437
3368
|
/**
|
|
3438
|
-
*
|
|
3439
|
-
*
|
|
3440
|
-
*
|
|
3369
|
+
* Applied with every Journey commit. When confirmed Hide categories or their members change,
|
|
3370
|
+
* a Work whose output or generated description may change is retired until both are produced
|
|
3371
|
+
* again (ADR 0069); ADR 0079 limits this to Works the changed values can reach.
|
|
3441
3372
|
*/
|
|
3442
|
-
function
|
|
3443
|
-
|
|
3444
|
-
const
|
|
3445
|
-
const
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3373
|
+
function settleConfirmedHides(previous, next, delta, outputIndex = () => null) {
|
|
3374
|
+
if (!previous || confirmedHideKey(previous) === confirmedHideKey(next)) return next;
|
|
3375
|
+
const before = new Map(previous.works.map((work) => [work.id, work.revision]));
|
|
3376
|
+
const affected = impact(previous, next, delta, outputIndex);
|
|
3377
|
+
return {
|
|
3378
|
+
...next,
|
|
3379
|
+
works: next.works.map((work) => work.artifact === null && work.revision > (before.get(work.id) ?? work.revision) || !affected(work) ? work : {
|
|
3380
|
+
...retireWorkDescription(work),
|
|
3381
|
+
artifact: null,
|
|
3382
|
+
revision: work.revision + 1,
|
|
3383
|
+
status: work.status === "ready" ? "review" : work.status
|
|
3384
|
+
})
|
|
3453
3385
|
};
|
|
3454
|
-
const all = [...terms];
|
|
3455
|
-
const hiddenValues = new Set(state.privacy.terms.filter((item) => item.disposition === "hidden").map((item) => item.value));
|
|
3456
|
-
for (const term of all) if (hiddenValues.has(term.value) && !kept.has(term.value)) add(term.value, term.type);
|
|
3457
|
-
for (const item of overrides) if (item.choice === "hide") add(item.value, item.type);
|
|
3458
|
-
for (const term of all) if (!termTypeInfo[term.type].asked) add(term.value, term.type);
|
|
3459
|
-
return hidden;
|
|
3460
|
-
}
|
|
3461
|
-
/** The journey's current hidden set, applied to every selected Work (ADR 0086, D03). */
|
|
3462
|
-
async function journeyConfirmedHides(reader, state) {
|
|
3463
|
-
return dictionaryHides(state, [...await reader.journeyTerms({ journeyId: state.journeyId }), ...state.privacy.terms]);
|
|
3464
3386
|
}
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3387
|
+
//#endregion
|
|
3388
|
+
//#region packages/application/src/journey/storage.ts
|
|
3389
|
+
const journeyEventSchema = z.object({
|
|
3390
|
+
sequence: z.number().int().nonnegative(),
|
|
3391
|
+
journeyId: journeyIdSchema,
|
|
3392
|
+
revision: z.number().int().nonnegative(),
|
|
3393
|
+
kind: z.enum([
|
|
3394
|
+
"started",
|
|
3395
|
+
"response",
|
|
3396
|
+
"snapshot",
|
|
3397
|
+
"batch",
|
|
3398
|
+
"output",
|
|
3399
|
+
"send",
|
|
3400
|
+
"search"
|
|
3401
|
+
]),
|
|
3402
|
+
source: z.enum([
|
|
3403
|
+
"conversation",
|
|
3404
|
+
"panel",
|
|
3405
|
+
"system"
|
|
3406
|
+
]),
|
|
3407
|
+
at: z.iso.datetime(),
|
|
3408
|
+
alignment: reviewAlignmentAuditSchema.optional(),
|
|
3409
|
+
preferenceScopes: z.array(z.object({
|
|
3410
|
+
groupId: journeyIdSchema,
|
|
3411
|
+
preferenceScope: journeyPreferenceScopeSchema
|
|
3412
|
+
}).strict()).min(1).max(1e4).optional(),
|
|
3413
|
+
correction: correctionAuditSchema.optional(),
|
|
3414
|
+
decision: delegatedDecisionSchema.extend({
|
|
3415
|
+
action: z.enum([
|
|
3416
|
+
"group",
|
|
3417
|
+
"groups-hide",
|
|
3418
|
+
"work-access",
|
|
3419
|
+
"instruction",
|
|
3420
|
+
"correction",
|
|
3421
|
+
"works-access",
|
|
3422
|
+
"work-type",
|
|
3423
|
+
"scope-confirm",
|
|
3424
|
+
"account-read",
|
|
3425
|
+
"card-answer",
|
|
3426
|
+
"cards-confirm"
|
|
3427
|
+
]),
|
|
3428
|
+
targetIds: z.array(journeyIdSchema).min(1),
|
|
3429
|
+
choice: z.string().min(1).max(16)
|
|
3430
|
+
}).optional()
|
|
3431
|
+
}).strict();
|
|
3432
|
+
const journeyCommitSchema = z.object({
|
|
3433
|
+
expectedRevision: z.number().int().nonnegative().nullable(),
|
|
3434
|
+
requestKey: journeyIdSchema,
|
|
3435
|
+
requestHash: journeyDigestSchema,
|
|
3436
|
+
state: nativeJourneySchema,
|
|
3437
|
+
event: journeyEventSchema.omit({
|
|
3438
|
+
sequence: true,
|
|
3439
|
+
revision: true
|
|
3440
|
+
}),
|
|
3441
|
+
/** Inserted in the same transaction as this state; never through the generic resource save. */
|
|
3442
|
+
correctionReport: correctionReportResourceSchema.optional(),
|
|
3443
|
+
/** Verify unread batch ownership in the same transaction as a reader interruption. */
|
|
3444
|
+
closedReader: reviewInterruptionSchema.pick({
|
|
3445
|
+
inputRevision: true,
|
|
3446
|
+
packId: true,
|
|
3447
|
+
batchIds: true
|
|
3448
|
+
}).extend({ workId: journeyIdSchema }).optional(),
|
|
3449
|
+
/** Set by the store for this commit only and never persisted (ADR 0079). */
|
|
3450
|
+
confirmedHideDelta: confirmedHideDeltaSchema.optional(),
|
|
3451
|
+
/** Dictionary values a Hide override adds (ADR 0086), inserted with this state. */
|
|
3452
|
+
terms: z.array(z.object({
|
|
3453
|
+
type: termTypeSchema,
|
|
3454
|
+
value: z.string().min(1)
|
|
3455
|
+
}).strict()).max(2e4).optional()
|
|
3456
|
+
}).strict();
|
|
3457
|
+
const journeyCommitResultSchema = z.object({
|
|
3458
|
+
status: z.enum([
|
|
3459
|
+
"applied",
|
|
3460
|
+
"replayed",
|
|
3461
|
+
"stale"
|
|
3462
|
+
]),
|
|
3463
|
+
state: nativeJourneySchema
|
|
3464
|
+
}).strict();
|
|
3465
|
+
//#endregion
|
|
3466
|
+
//#region packages/application/src/journey/search.ts
|
|
3467
|
+
const count$9 = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
3468
|
+
const term = z.string().min(1).max(1024).refine((value) => Buffer.byteLength(value) <= 1024 && !/[\0\r\n]/u.test(value));
|
|
3469
|
+
const journeySearchSchema = z.object({
|
|
3470
|
+
journeyId: journeyIdSchema,
|
|
3471
|
+
terms: z.array(z.object({
|
|
3472
|
+
id: z.string().min(1).max(64),
|
|
3473
|
+
text: term,
|
|
3474
|
+
variants: z.array(term).max(7).default([])
|
|
3475
|
+
}).strict()).min(1).max(16)
|
|
3476
|
+
}).strict();
|
|
3477
|
+
const journeySearchResultSchema = z.object({
|
|
3478
|
+
binding: z.object({
|
|
3479
|
+
journeyId: journeyIdSchema,
|
|
3480
|
+
snapshotId: journeyDigestSchema,
|
|
3481
|
+
revision: count$9,
|
|
3482
|
+
scopeDigest: journeyDigestSchema,
|
|
3483
|
+
outputs: z.array(z.object({
|
|
3484
|
+
workId: journeyIdSchema,
|
|
3485
|
+
outputRevision: count$9,
|
|
3486
|
+
sha256: journeyDigestSchema
|
|
3487
|
+
}).strict())
|
|
3488
|
+
}).strict(),
|
|
3489
|
+
status: z.enum(["complete", "partial"]),
|
|
3490
|
+
scope: z.object({
|
|
3491
|
+
retainedText: z.literal(true),
|
|
3492
|
+
metadata: z.literal(true),
|
|
3493
|
+
setupText: z.literal(true),
|
|
3494
|
+
attachments: z.literal(false),
|
|
3495
|
+
images: z.literal(false),
|
|
3496
|
+
originals: z.literal("frozen-native-records-and-discovery-metadata"),
|
|
3497
|
+
matching: z.literal("case-sensitive-literal-leftmost-longest")
|
|
3498
|
+
}).strict(),
|
|
3499
|
+
terms: z.array(z.object({
|
|
3500
|
+
id: z.string(),
|
|
3501
|
+
tried: z.array(z.string()),
|
|
3502
|
+
sentMatches: count$9.nullable(),
|
|
3503
|
+
originalMatches: count$9.nullable(),
|
|
3504
|
+
sentSessions: count$9.nullable(),
|
|
3505
|
+
originalSessions: count$9.nullable()
|
|
3506
|
+
}).strict()),
|
|
3507
|
+
hits: z.array(z.object({
|
|
3508
|
+
termId: z.string(),
|
|
3509
|
+
basis: z.enum(["sent", "original"]),
|
|
3510
|
+
workId: journeyIdSchema,
|
|
3511
|
+
locator: z.object({
|
|
3512
|
+
kind: z.enum([
|
|
3513
|
+
"retained-text",
|
|
3514
|
+
"metadata",
|
|
3515
|
+
"setup-text"
|
|
3516
|
+
]),
|
|
3517
|
+
entryPath: z.string().optional(),
|
|
3518
|
+
archiveSessionId: z.string().optional(),
|
|
3519
|
+
sessionId: z.string().optional(),
|
|
3520
|
+
sourcePartId: z.string().optional(),
|
|
3521
|
+
recordIndex: count$9,
|
|
3522
|
+
fieldId: z.string(),
|
|
3523
|
+
line: count$9
|
|
3524
|
+
}).strict(),
|
|
3525
|
+
occurrences: count$9,
|
|
3526
|
+
modelContext: z.string().max(4096).optional(),
|
|
3527
|
+
match: z.object({
|
|
3528
|
+
variantIndex: count$9.max(7),
|
|
3529
|
+
start: count$9,
|
|
3530
|
+
end: count$9
|
|
3531
|
+
}).strict().optional()
|
|
3532
|
+
}).strict()),
|
|
3533
|
+
failures: z.array(z.object({
|
|
3534
|
+
workId: journeyIdSchema,
|
|
3535
|
+
basis: z.enum(["sent", "original"]),
|
|
3536
|
+
code: z.enum(["SEARCH_INPUT_UNAVAILABLE", "SEARCH_ENGINE_FAILED"])
|
|
3537
|
+
}).strict()),
|
|
3538
|
+
truncated: z.boolean()
|
|
3539
|
+
}).strict().refine((result) => result.hits.every((hit) => {
|
|
3540
|
+
if (hit.basis === "original") return hit.modelContext === void 0 && hit.match === void 0;
|
|
3541
|
+
if (!hit.match || hit.modelContext === void 0) return false;
|
|
3542
|
+
const { start, end, variantIndex } = hit.match;
|
|
3543
|
+
const variant = result.terms.find((term) => term.id === hit.termId)?.tried[variantIndex];
|
|
3544
|
+
if (!variant || end <= start || end > hit.modelContext.length || hit.modelContext.slice(start, end) !== variant) return false;
|
|
3545
|
+
return [start, end].every((offset) => {
|
|
3546
|
+
const before = hit.modelContext?.charCodeAt(offset - 1) ?? 0;
|
|
3547
|
+
const after = hit.modelContext?.charCodeAt(offset) ?? 0;
|
|
3548
|
+
return !(before >= 55296 && before <= 56319 && after >= 56320 && after <= 57343);
|
|
3549
|
+
});
|
|
3550
|
+
}));
|
|
3551
|
+
const journeySearchSaveSchema = z.object({
|
|
3552
|
+
journeyId: journeyIdSchema,
|
|
3553
|
+
expectedRevision: count$9,
|
|
3554
|
+
idempotencyKey: journeyIdSchema,
|
|
3555
|
+
result: journeySearchResultSchema
|
|
3556
|
+
}).strict();
|
|
3557
|
+
function searchScopeDigest(state) {
|
|
3558
|
+
return textDigest(JSON.stringify({
|
|
3559
|
+
snapshot: state.snapshotId,
|
|
3560
|
+
configurationId: state.configurationId,
|
|
3561
|
+
pendingHideId: state.pendingHideId,
|
|
3562
|
+
works: state.works.map((work) => ({
|
|
3563
|
+
id: work.id,
|
|
3564
|
+
revision: work.revision,
|
|
3565
|
+
inputRevision: work.inputRevision,
|
|
3566
|
+
access: work.access,
|
|
3567
|
+
artifact: work.artifact?.sha256 ?? null
|
|
3568
|
+
}))
|
|
3569
|
+
}));
|
|
3519
3570
|
}
|
|
3571
|
+
z.object({
|
|
3572
|
+
journeyId: journeyIdSchema.optional(),
|
|
3573
|
+
workIds: z.array(journeyIdSchema).min(1).max(1e3).optional()
|
|
3574
|
+
}).strict();
|
|
3575
|
+
const categories = z.object({
|
|
3576
|
+
identity: z.number().int().nonnegative(),
|
|
3577
|
+
credentials: z.number().int().nonnegative(),
|
|
3578
|
+
internal: z.number().int().nonnegative(),
|
|
3579
|
+
private: z.number().int().nonnegative()
|
|
3580
|
+
}).strict();
|
|
3581
|
+
z.object({
|
|
3582
|
+
journeyId: journeyIdSchema,
|
|
3583
|
+
nonAdditiveMechanismCounts: z.literal(true),
|
|
3584
|
+
total: z.number().int().nonnegative(),
|
|
3585
|
+
categories,
|
|
3586
|
+
byMechanism: z.object({
|
|
3587
|
+
staticNodes: z.record(z.string(), z.number().int().nonnegative()),
|
|
3588
|
+
semantic: z.number().int().nonnegative(),
|
|
3589
|
+
correction: z.number().int().nonnegative()
|
|
3590
|
+
}).strict(),
|
|
3591
|
+
works: z.array(z.object({
|
|
3592
|
+
workId: journeyIdSchema,
|
|
3593
|
+
revision: z.number().int().positive(),
|
|
3594
|
+
inputRevision: z.number().int().positive(),
|
|
3595
|
+
primarySha256: z.string().length(64),
|
|
3596
|
+
companionSha256: z.string().length(64),
|
|
3597
|
+
ledgerSha256: z.string().length(64),
|
|
3598
|
+
decisionDigest: z.string().length(64),
|
|
3599
|
+
reviewInputDigest: z.string().length(64),
|
|
3600
|
+
total: z.number().int().nonnegative(),
|
|
3601
|
+
categories,
|
|
3602
|
+
byMechanism: z.object({
|
|
3603
|
+
staticNodes: z.record(z.string(), z.number().int().nonnegative()),
|
|
3604
|
+
semantic: z.number().int().nonnegative(),
|
|
3605
|
+
correction: z.number().int().nonnegative()
|
|
3606
|
+
}).strict()
|
|
3607
|
+
}).strict())
|
|
3608
|
+
}).strict();
|
|
3520
3609
|
//#endregion
|
|
3521
|
-
//#region packages/application/src/journey/
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
exactQuote: field.text.slice(match.range.start, match.range.end),
|
|
3540
|
-
term: match.entity.value
|
|
3541
|
-
};
|
|
3542
|
-
hits.push({
|
|
3543
|
-
...body,
|
|
3544
|
-
digest: bDigest(JSON.stringify(body))
|
|
3545
|
-
});
|
|
3546
|
-
}
|
|
3547
|
-
}
|
|
3548
|
-
return [...new Map(hits.map((hit) => [hit.digest, hit])).values()];
|
|
3549
|
-
}
|
|
3550
|
-
function unclassifiedBHits(hits, literals, negative) {
|
|
3551
|
-
const issues = [];
|
|
3552
|
-
const positiveFields = new Map([...Map.groupBy(literals, (part) => part.fieldId)].map(([id, parts]) => [id, indexBRanges(parts)]));
|
|
3553
|
-
const negativeFields = new Map([...Map.groupBy(negative, (part) => part.fieldId)].map(([id, parts]) => [id, indexBRanges(parts)]));
|
|
3554
|
-
for (const hit of hits) {
|
|
3555
|
-
const positive = positiveFields.get(hit.fieldId)?.relation(hit).covers ?? false;
|
|
3556
|
-
const negative = negativeFields.get(hit.fieldId)?.relation(hit);
|
|
3557
|
-
if (!positive && !negative?.covers || positive && negative?.overlaps) issues.push(bIssue(positive ? "CONFLICTING_PRIVACY_DECISIONS" : "UNCLASSIFIED_MATCH", hit));
|
|
3558
|
-
}
|
|
3559
|
-
return issues;
|
|
3560
|
-
}
|
|
3561
|
-
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);
|
|
3562
|
-
/** A confirmed Hide value matches only where it does not run into a neighbouring word (ADR 0069). */
|
|
3563
|
-
function bWordBounded(text, start, end) {
|
|
3564
|
-
return !(bSpacedWord(scalarBefore(text, start)) && bSpacedWord(scalarAfter(text, start)) || bSpacedWord(scalarBefore(text, end)) && bSpacedWord(scalarAfter(text, end)));
|
|
3610
|
+
//#region packages/application/src/journey/scope/respond.ts
|
|
3611
|
+
/**
|
|
3612
|
+
* Account-seeded access (ADR 0274 D3(b)). With `held`, the time the account inventory was read,
|
|
3613
|
+
* a Work whose every root is held Public is confirmed Public with the account as its recorded
|
|
3614
|
+
* choice (ADR 0275); without it, Public stays a suggestion.
|
|
3615
|
+
*/
|
|
3616
|
+
function accountSeededAccess(account, held) {
|
|
3617
|
+
const accessChoice = account === "public-all" && held !== void 0 ? {
|
|
3618
|
+
channel: "account",
|
|
3619
|
+
at: held
|
|
3620
|
+
} : void 0;
|
|
3621
|
+
return {
|
|
3622
|
+
access: account === "public" || account === "public-all" ? "public" : "private",
|
|
3623
|
+
accessOrigin: "account",
|
|
3624
|
+
accessConfirmed: account === "private" || accessChoice !== void 0,
|
|
3625
|
+
accountAccessConflict: account === "mixed",
|
|
3626
|
+
accessChoice
|
|
3627
|
+
};
|
|
3565
3628
|
}
|
|
3566
3629
|
/**
|
|
3567
|
-
*
|
|
3568
|
-
*
|
|
3630
|
+
* The scope-confirmation stop (ADR 0277 section 5), or null when the read lets it confirm. A
|
|
3631
|
+
* failed read always stops, with Retry only. A signed-out read stops too, except when the
|
|
3632
|
+
* journey's consent has no subject (Q11 a and Q13 a), regardless of caller configuration.
|
|
3569
3633
|
*/
|
|
3570
|
-
function
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
})));
|
|
3575
|
-
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);
|
|
3634
|
+
function accountReadStop(state, read) {
|
|
3635
|
+
if (!read || read.status === "read") return null;
|
|
3636
|
+
if (read.status === "unavailable") return "ACCOUNT_ACCESS_UNAVAILABLE";
|
|
3637
|
+
return consentedWithoutAccount(state) ? null : "ACCOUNT_SIGNED_OUT";
|
|
3576
3638
|
}
|
|
3577
|
-
/**
|
|
3578
|
-
function
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3639
|
+
/** The journey with this read recorded; the subject is kept only for a `read`. */
|
|
3640
|
+
function withAccountRead(state, read, now) {
|
|
3641
|
+
return {
|
|
3642
|
+
...state,
|
|
3643
|
+
accountRead: read.status === "read" ? {
|
|
3644
|
+
status: "read",
|
|
3645
|
+
subject: read.subject,
|
|
3646
|
+
at: now
|
|
3647
|
+
} : {
|
|
3648
|
+
status: read.status,
|
|
3649
|
+
at: now
|
|
3588
3650
|
}
|
|
3589
|
-
return low > 0 && (reach[low - 1] ?? -1) >= range.end;
|
|
3590
3651
|
};
|
|
3591
3652
|
}
|
|
3592
3653
|
/**
|
|
3593
|
-
*
|
|
3594
|
-
*
|
|
3654
|
+
* Retry at the Send screen (ADR 0277 section 5): record the read and, on `read`, re-apply the
|
|
3655
|
+
* account while keeping confirmed person choices and every Private or dropped Work. An earlier choice
|
|
3656
|
+
* that cannot carry falls back to Private. Access changes get a new revision and clear the
|
|
3657
|
+
* output and package approval.
|
|
3595
3658
|
*/
|
|
3596
|
-
function
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
const
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3659
|
+
function retryAccountRead(state, read, now) {
|
|
3660
|
+
if (state.scope.status !== "confirmed" || state.phase === "sending" || state.phase === "completed") throw new ApplicationError("INVALID_TRANSITION");
|
|
3661
|
+
const recorded = withAccountRead(state, read, now);
|
|
3662
|
+
if (read.status !== "read") return recorded;
|
|
3663
|
+
let changed = false;
|
|
3664
|
+
const works = state.works.map((work) => {
|
|
3665
|
+
if (work.placement === "classifying") return work;
|
|
3666
|
+
if (work.accessOrigin === "explicit" && work.accessConfirmed) return work;
|
|
3667
|
+
if (work.access !== "public") return work;
|
|
3668
|
+
const account = read.access.get(work.id);
|
|
3669
|
+
if (!account && !work.accessChoice && work.accessOrigin !== "account") return work;
|
|
3670
|
+
const seeded = accountSeededAccess(account ?? "private", now);
|
|
3671
|
+
if (work.accessChoice && (work.accessChoice.channel !== "account" || !seeded.accessChoice)) {
|
|
3672
|
+
seeded.access = "private";
|
|
3673
|
+
seeded.accessConfirmed = account !== "mixed";
|
|
3674
|
+
seeded.accessChoice = void 0;
|
|
3675
|
+
}
|
|
3676
|
+
if (seeded.access === work.access) return {
|
|
3677
|
+
...work,
|
|
3678
|
+
...seeded
|
|
3679
|
+
};
|
|
3680
|
+
changed = true;
|
|
3681
|
+
return {
|
|
3682
|
+
...work,
|
|
3683
|
+
...seeded,
|
|
3684
|
+
revision: work.revision + 1,
|
|
3685
|
+
artifact: null,
|
|
3686
|
+
status: work.status === "ready" ? "review" : work.status
|
|
3687
|
+
};
|
|
3688
|
+
});
|
|
3689
|
+
return changed ? withReadingProgress({
|
|
3690
|
+
...recorded,
|
|
3691
|
+
privacy: {
|
|
3692
|
+
...state.privacy,
|
|
3693
|
+
packageApproval: null
|
|
3694
|
+
},
|
|
3695
|
+
works
|
|
3696
|
+
}) : {
|
|
3697
|
+
...recorded,
|
|
3698
|
+
works
|
|
3609
3699
|
};
|
|
3610
3700
|
}
|
|
3611
|
-
/**
|
|
3612
|
-
function
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3701
|
+
/** Classification confirmation initializes visibility and scan membership for this journey. */
|
|
3702
|
+
function confirmScope(state, now, decision, accountAccess) {
|
|
3703
|
+
if (state.scope.status === "confirmed") return state;
|
|
3704
|
+
if (state.classificationRevision === null || !state.works.length || state.works.some((work) => work.placement !== "placed" && work.placement !== "classifying" || work.placement === "placed" && !(work.status === "blocked" && work.access === "drop" && work.accessConfirmed) && (!work.description.trim() || !work.projectType))) throw new ApplicationError("SCOPE_NOT_READY");
|
|
3705
|
+
return withReadingProgress({
|
|
3706
|
+
...state,
|
|
3707
|
+
scope: {
|
|
3708
|
+
status: "confirmed",
|
|
3709
|
+
actor: decision ? "delegate" : "human",
|
|
3710
|
+
at: now
|
|
3711
|
+
},
|
|
3712
|
+
works: state.works.map((work) => {
|
|
3713
|
+
if (work.placement === "classifying") return work;
|
|
3714
|
+
if (work.accessOrigin === "explicit" && work.accessConfirmed) return work;
|
|
3715
|
+
if (work.access === "private" && work.accessOrigin === "delegate") return work;
|
|
3716
|
+
const account = accountAccess?.get(work.id);
|
|
3717
|
+
if (account) return {
|
|
3718
|
+
...work,
|
|
3719
|
+
...accountSeededAccess(account, now)
|
|
3720
|
+
};
|
|
3721
|
+
if (work.accessOrigin === "account") return work;
|
|
3722
|
+
const access = work.accessConfirmed ? work.access : work.classificationRecommendation?.access ?? work.access;
|
|
3723
|
+
return {
|
|
3724
|
+
...work,
|
|
3725
|
+
access,
|
|
3726
|
+
accessOrigin: work.accessConfirmed || !work.classificationRecommendation ? work.accessOrigin : "model",
|
|
3727
|
+
accessConfirmed: access !== "public"
|
|
3728
|
+
};
|
|
3729
|
+
})
|
|
3730
|
+
});
|
|
3731
|
+
}
|
|
3732
|
+
function setWorkType(state, workId, type, decision) {
|
|
3733
|
+
if (state.scope.status === "confirmed") throw new ApplicationError("INVALID_TRANSITION");
|
|
3734
|
+
const projectType = decision ? {
|
|
3735
|
+
type,
|
|
3736
|
+
reason: "Chosen by your delegate for this journey.",
|
|
3737
|
+
origin: "delegate"
|
|
3738
|
+
} : {
|
|
3739
|
+
type,
|
|
3740
|
+
reason: "Chosen by you.",
|
|
3741
|
+
origin: "explicit"
|
|
3621
3742
|
};
|
|
3743
|
+
return {
|
|
3744
|
+
...state,
|
|
3745
|
+
scope: {
|
|
3746
|
+
status: "pending",
|
|
3747
|
+
actor: null,
|
|
3748
|
+
at: null
|
|
3749
|
+
},
|
|
3750
|
+
privacy: {
|
|
3751
|
+
...state.privacy,
|
|
3752
|
+
cards: [],
|
|
3753
|
+
confirmedAnswersRevision: null,
|
|
3754
|
+
packageApproval: null
|
|
3755
|
+
},
|
|
3756
|
+
works: state.works.map((work) => work.id !== workId || decision && work.projectType?.origin === "explicit" ? work : {
|
|
3757
|
+
...work,
|
|
3758
|
+
projectType
|
|
3759
|
+
})
|
|
3760
|
+
};
|
|
3761
|
+
}
|
|
3762
|
+
/** One rule for single and bulk choices; selection changes recompute reading completion. */
|
|
3763
|
+
function setWorksAccess(state, workIds, access, decision, accessChoice) {
|
|
3764
|
+
if (decision && access === "public") throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
3765
|
+
if (new Set(workIds).size !== workIds.length) throw new ApplicationError("INVALID_SELECTION");
|
|
3766
|
+
if (workIds.some((id) => !state.works.some((work) => work.id === id))) throw new ApplicationError("UNKNOWN_WORKSPACE");
|
|
3767
|
+
if (state.scope.status === "confirmed") return changeConfirmedAccess(state, workIds, access, decision, accessChoice);
|
|
3768
|
+
return withReadingProgress({
|
|
3769
|
+
...state,
|
|
3770
|
+
scope: {
|
|
3771
|
+
status: "pending",
|
|
3772
|
+
actor: null,
|
|
3773
|
+
at: null
|
|
3774
|
+
},
|
|
3775
|
+
privacy: {
|
|
3776
|
+
...state.privacy,
|
|
3777
|
+
cards: [],
|
|
3778
|
+
confirmedAnswersRevision: null,
|
|
3779
|
+
packageApproval: null
|
|
3780
|
+
},
|
|
3781
|
+
works: state.works.map((work) => !workIds.includes(work.id) ? work : {
|
|
3782
|
+
...work,
|
|
3783
|
+
revision: work.revision + 1,
|
|
3784
|
+
artifact: null,
|
|
3785
|
+
status: work.status === "ready" ? "review" : work.status,
|
|
3786
|
+
access,
|
|
3787
|
+
accessConfirmed: true,
|
|
3788
|
+
accountAccessConflict: false,
|
|
3789
|
+
accessChoice: access === "public" ? accessChoice : void 0,
|
|
3790
|
+
accessOrigin: decision && work.accessOrigin !== "explicit" ? "delegate" : "explicit"
|
|
3791
|
+
})
|
|
3792
|
+
});
|
|
3622
3793
|
}
|
|
3623
|
-
const segmenter = new Intl.Segmenter("und", { granularity: "grapheme" });
|
|
3624
|
-
/** Beyond this many inner match edges in one word the index is not kept (retire instead). */
|
|
3625
|
-
const maximumEdges = 16;
|
|
3626
|
-
/** Beyond this many distinct pieces the index is not kept (retire instead). */
|
|
3627
|
-
const maximumHideIndexTokens = 2e6;
|
|
3628
3794
|
/**
|
|
3629
|
-
*
|
|
3630
|
-
*
|
|
3631
|
-
*
|
|
3632
|
-
* or inside a grapheme. Every piece between such positions is added. Returns false when a run
|
|
3633
|
-
* has too many such positions to list.
|
|
3795
|
+
* After confirmation the person, never a delegate, may still change any Work's visibility
|
|
3796
|
+
* (D2, superseding ADR 0162's lock and ADR 0188's blocked-only Drop). Drop stops that Work's
|
|
3797
|
+
* scan; a Work moved out of Drop is read again; reading progress is recomputed.
|
|
3634
3798
|
*/
|
|
3635
|
-
function
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
}
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3799
|
+
function changeConfirmedAccess(state, workIds, access, decision, accessChoice) {
|
|
3800
|
+
if (decision || state.phase === "sending" || state.phase === "completed" || state.works.some((work) => workIds.includes(work.id) && work.access === access && work.accessConfirmed && hasPublicChoice(work))) throw new ApplicationError("INVALID_TRANSITION");
|
|
3801
|
+
if (access === "public" && state.works.every((work) => !workIds.includes(work.id) || work.access === "public" && work.accessConfirmed)) return {
|
|
3802
|
+
...state,
|
|
3803
|
+
works: state.works.map((work) => workIds.includes(work.id) && accessChoice ? {
|
|
3804
|
+
...work,
|
|
3805
|
+
accessOrigin: "explicit",
|
|
3806
|
+
accessChoice
|
|
3807
|
+
} : work)
|
|
3808
|
+
};
|
|
3809
|
+
return withReadingProgress({
|
|
3810
|
+
...state,
|
|
3811
|
+
privacy: {
|
|
3812
|
+
...state.privacy,
|
|
3813
|
+
packageApproval: null
|
|
3814
|
+
},
|
|
3815
|
+
works: state.works.map((work) => workIds.includes(work.id) ? {
|
|
3816
|
+
...work,
|
|
3817
|
+
revision: work.revision + 1,
|
|
3818
|
+
artifact: null,
|
|
3819
|
+
status: work.status === "ready" ? "review" : work.status,
|
|
3820
|
+
access,
|
|
3821
|
+
accessConfirmed: true,
|
|
3822
|
+
accountAccessConflict: false,
|
|
3823
|
+
accessChoice: access === "public" ? accessChoice : void 0,
|
|
3824
|
+
accessOrigin: "explicit"
|
|
3825
|
+
} : work)
|
|
3826
|
+
});
|
|
3654
3827
|
}
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3828
|
+
/** Model suggestions fill or refresh model types only; a person's or delegate's type stays. */
|
|
3829
|
+
function applyClassification(state, items) {
|
|
3830
|
+
if (state.scope.status === "confirmed") throw new ApplicationError("INVALID_TRANSITION");
|
|
3831
|
+
const byWork = new Map(items.map((item) => [item.workId, item]));
|
|
3832
|
+
if (byWork.size !== items.length) throw new ApplicationError("INVALID_SELECTION");
|
|
3833
|
+
if (items.some((item) => !state.works.some((work) => work.id === item.workId))) throw new ApplicationError("UNKNOWN_WORKSPACE");
|
|
3834
|
+
return {
|
|
3835
|
+
...state,
|
|
3836
|
+
works: state.works.map((work) => {
|
|
3837
|
+
const item = byWork.get(work.id);
|
|
3838
|
+
if (!item || work.projectType && work.projectType.origin !== "model") return work;
|
|
3839
|
+
return {
|
|
3840
|
+
...work,
|
|
3841
|
+
projectType: {
|
|
3842
|
+
type: item.type,
|
|
3843
|
+
reason: item.reason,
|
|
3844
|
+
origin: "model"
|
|
3845
|
+
}
|
|
3846
|
+
};
|
|
3847
|
+
})
|
|
3669
3848
|
};
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3849
|
+
}
|
|
3850
|
+
//#endregion
|
|
3851
|
+
//#region packages/application/src/journey/inventory/schema.ts
|
|
3852
|
+
const journeyInventoryQuerySchema = z.object({ journeyId: journeyIdSchema }).strict();
|
|
3853
|
+
/** Trusted host binding, never accepted by the public command schema. */
|
|
3854
|
+
const journeyInventoryReadSchema = journeyInventoryQuerySchema.extend({ expectedDestination: z.string().min(1).optional() }).strict();
|
|
3855
|
+
const discoveryWarningCodeSchema = z.enum([
|
|
3856
|
+
"UNSUPPORTED_SOURCE_ENCODING",
|
|
3857
|
+
"SKIPPED_NONREGULAR_SOURCE",
|
|
3858
|
+
"WORKFLOW_JOURNAL_EXCLUDED",
|
|
3859
|
+
"AUTOMATIC_HELPER_EXCLUDED",
|
|
3860
|
+
"UNRESOLVED_WORKERS_EXCLUDED",
|
|
3861
|
+
"METADATA_LIMIT",
|
|
3862
|
+
"METADATA_DEPTH_LIMIT",
|
|
3863
|
+
"INCOMPLETE_DISCOVERY",
|
|
3864
|
+
"DISCOVERY_ENTRY_LIMIT",
|
|
3865
|
+
"DISCOVERY_DEPTH_LIMIT",
|
|
3866
|
+
"BROKEN_SOURCE_LINK"
|
|
3867
|
+
]);
|
|
3868
|
+
const evidence = z.object({
|
|
3869
|
+
known: preparationCountSchema,
|
|
3870
|
+
total: preparationCountSchema.nullable()
|
|
3871
|
+
}).strict().refine((value) => value.total === null || value.total === value.known);
|
|
3872
|
+
const workPreparationObservationSchema = z.object({
|
|
3873
|
+
workId: journeyIdSchema,
|
|
3874
|
+
inputRevision: preparationCountSchema.min(1),
|
|
3875
|
+
observation: z.enum([
|
|
3876
|
+
"unavailable",
|
|
3877
|
+
"incomplete",
|
|
3878
|
+
"settled"
|
|
3879
|
+
]),
|
|
3880
|
+
stage: preparationStageSchema.nullable(),
|
|
3881
|
+
sourceParts: z.object({
|
|
3882
|
+
unsupported: evidence,
|
|
3883
|
+
resourceLimited: evidence,
|
|
3884
|
+
excluded: evidence,
|
|
3885
|
+
unclassified: preparationCountSchema
|
|
3886
|
+
}).strict(),
|
|
3887
|
+
emptyReviewRoots: evidence,
|
|
3888
|
+
unevaluatedReviewRoots: preparationCountSchema
|
|
3889
|
+
}).strict();
|
|
3890
|
+
const journeyInventorySchema = z.object({
|
|
3891
|
+
journeyId: journeyIdSchema,
|
|
3892
|
+
revision: preparationCountSchema,
|
|
3893
|
+
snapshotId: journeyDigestSchema.nullable(),
|
|
3894
|
+
frozen: z.object({
|
|
3895
|
+
retainedSessions: preparationCountSchema,
|
|
3896
|
+
reviewRoots: preparationCountSchema,
|
|
3897
|
+
identifiedMainRoots: preparationCountSchema,
|
|
3898
|
+
unknownRoleRoots: preparationCountSchema,
|
|
3899
|
+
attachedSubagents: preparationCountSchema,
|
|
3900
|
+
unresolvedWorkerSessions: preparationCountSchema,
|
|
3901
|
+
sourceParts: preparationCountSchema,
|
|
3902
|
+
frozenSourceBytes: preparationCountSchema,
|
|
3903
|
+
discoveryUnsupportedInputs: z.null(),
|
|
3904
|
+
discoverySkippedInputs: z.null(),
|
|
3905
|
+
discoverySkippedSourceBytes: z.null(),
|
|
3906
|
+
warningOccurrences: z.array(z.object({
|
|
3907
|
+
code: discoveryWarningCodeSchema,
|
|
3908
|
+
occurrences: preparationCountSchema
|
|
3909
|
+
}).strict())
|
|
3910
|
+
}).strict().nullable(),
|
|
3911
|
+
captures: z.object({
|
|
3912
|
+
targetParts: preparationCountSchema,
|
|
3913
|
+
capturedParts: preparationCountSchema,
|
|
3914
|
+
capturedBytes: preparationCountSchema,
|
|
3915
|
+
completeCapturedBytes: preparationCountSchema.nullable()
|
|
3916
|
+
}).strict().nullable(),
|
|
3917
|
+
workScope: z.object({
|
|
3918
|
+
total: preparationCountSchema,
|
|
3919
|
+
byStatus: z.object({
|
|
3920
|
+
pending: preparationCountSchema,
|
|
3921
|
+
reading: preparationCountSchema,
|
|
3922
|
+
review: preparationCountSchema,
|
|
3923
|
+
ready: preparationCountSchema,
|
|
3924
|
+
blocked: preparationCountSchema
|
|
3925
|
+
}).strict(),
|
|
3926
|
+
byAccess: z.object({
|
|
3927
|
+
public: preparationCountSchema,
|
|
3928
|
+
private: preparationCountSchema,
|
|
3929
|
+
drop: preparationCountSchema
|
|
3930
|
+
}).strict(),
|
|
3931
|
+
assignedReviewRoots: preparationCountSchema,
|
|
3932
|
+
unassignedReviewRoots: preparationCountSchema.nullable(),
|
|
3933
|
+
excludedAutomationRoots: preparationCountSchema.nullable(),
|
|
3934
|
+
unassignedOtherRoots: preparationCountSchema.nullable()
|
|
3935
|
+
}).strict(),
|
|
3936
|
+
preparation: z.array(workPreparationObservationSchema)
|
|
3937
|
+
}).strict();
|
|
3938
|
+
/** Panel preparation counts over the selected Works: opaque Work IDs, parts and bytes only. */
|
|
3939
|
+
const journeyPreparationProgressSchema = z.object({
|
|
3940
|
+
currentWorkIds: z.array(journeyIdSchema),
|
|
3941
|
+
targetParts: preparationCountSchema,
|
|
3942
|
+
capturedParts: preparationCountSchema,
|
|
3943
|
+
targetBytes: preparationCountSchema,
|
|
3944
|
+
capturedBytes: preparationCountSchema
|
|
3945
|
+
}).strict();
|
|
3946
|
+
//#endregion
|
|
3947
|
+
//#region packages/application/src/source/resolve.ts
|
|
3948
|
+
/** Queue-based ancestry resolution cannot leak deep descendants through a recursion cutoff. */
|
|
3949
|
+
function resolveParts(inventory, route) {
|
|
3950
|
+
const nodes = sourceGraph(inventory);
|
|
3951
|
+
const families = reviewFamilies(inventory, nodes);
|
|
3952
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
3953
|
+
const depths = /* @__PURE__ */ new Map();
|
|
3954
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
3955
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
3956
|
+
const queue = [];
|
|
3957
|
+
const finish = (node, included, unresolved, reason, depth = 0) => {
|
|
3958
|
+
resolved.set(node.key, {
|
|
3959
|
+
sessionId: node.session.id,
|
|
3960
|
+
partId: node.part?.id ?? "",
|
|
3961
|
+
included,
|
|
3962
|
+
unresolved,
|
|
3963
|
+
reason
|
|
3680
3964
|
});
|
|
3681
|
-
|
|
3965
|
+
depths.set(node.key, depth);
|
|
3966
|
+
queue.push(node);
|
|
3967
|
+
};
|
|
3968
|
+
const pending = (node, reason) => finish(node, route === "original", true, reason);
|
|
3969
|
+
for (const node of nodes) {
|
|
3970
|
+
const { identity, reason } = node.observation;
|
|
3971
|
+
if (families.unresolved.has(node.session.id)) finish(node, false, node.ancestry !== "missing", node.ancestry === "missing" ? "missing-parent" : "ancestry-unresolved");
|
|
3972
|
+
else if (!familySelected(inventory, families, node.session)) finish(node, false, false, families.owners.get(node.session.id)?.id !== node.session.id ? "deselected-parent" : node.session.selected ? "workspace-not-selected" : "not-selected");
|
|
3973
|
+
else if (reason !== null) pending(node, reason);
|
|
3974
|
+
else if (identity?.role === "guardian") finish(node, false, false, "guardian-excluded");
|
|
3975
|
+
else if (identity?.role !== "worker") finish(node, true, false, null);
|
|
3976
|
+
else if (node.ancestry === "missing") finish(node, false, false, "missing-parent");
|
|
3977
|
+
else if (node.ancestry !== "parents") finish(node, false, true, "ancestry-unresolved");
|
|
3978
|
+
else {
|
|
3979
|
+
remaining.set(node.key, node.parents.length);
|
|
3980
|
+
for (const parent of node.parents) {
|
|
3981
|
+
const children = dependents.get(parent.key) ?? [];
|
|
3982
|
+
children.push(node);
|
|
3983
|
+
dependents.set(parent.key, children);
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3682
3986
|
}
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3987
|
+
for (let cursor = 0; cursor < queue.length; cursor++) {
|
|
3988
|
+
const parent = queue[cursor];
|
|
3989
|
+
if (!parent) continue;
|
|
3990
|
+
const value = resolved.get(parent.key);
|
|
3991
|
+
for (const node of dependents.get(parent.key) ?? []) {
|
|
3992
|
+
if (resolved.has(node.key)) continue;
|
|
3993
|
+
const outstanding = (remaining.get(node.key) ?? 1) - 1;
|
|
3994
|
+
remaining.set(node.key, outstanding);
|
|
3995
|
+
const depth = (depths.get(parent.key) ?? 0) + 1;
|
|
3996
|
+
if (value?.included && !value.unresolved && (route === "original" || depth <= 256)) {
|
|
3997
|
+
finish(node, true, false, null, depth);
|
|
3998
|
+
continue;
|
|
3999
|
+
}
|
|
4000
|
+
if (outstanding > 0) continue;
|
|
4001
|
+
const results = node.parents.map((entry) => resolved.get(entry.key));
|
|
4002
|
+
if (results.every((entry) => entry && [
|
|
4003
|
+
"not-selected",
|
|
4004
|
+
"workspace-not-selected",
|
|
4005
|
+
"deselected-parent"
|
|
4006
|
+
].includes(entry.reason ?? ""))) finish(node, false, false, "deselected-parent");
|
|
4007
|
+
else if (results.every((entry) => entry?.reason === "missing-parent" || entry?.reason === "deselected-parent")) finish(node, false, false, "missing-parent");
|
|
4008
|
+
else finish(node, false, true, "ancestry-unresolved");
|
|
4009
|
+
}
|
|
3694
4010
|
}
|
|
3695
|
-
|
|
3696
|
-
return
|
|
3697
|
-
}
|
|
3698
|
-
/** A keyed, truncated digest; a collision can only retire an unaffected Work. */
|
|
3699
|
-
function hideTokenDigest(key, token) {
|
|
3700
|
-
return createHmac("sha256", key).update(token, "utf8").digest("base64url").slice(0, 11);
|
|
3701
|
-
}
|
|
3702
|
-
/** Identifies the key an index or delta was made with, without revealing it. */
|
|
3703
|
-
const hideKeyId = (key) => hideTokenDigest(key, "\0key");
|
|
3704
|
-
/** Sorted, distinct, fixed-width digests in one string. */
|
|
3705
|
-
function packHideIndex(key, tokens) {
|
|
3706
|
-
const secret = createSecretKey(key);
|
|
3707
|
-
return [...new Set([...tokens].map((token) => hideTokenDigest(secret, token)))].sort().join("");
|
|
4011
|
+
for (const node of nodes) if (!resolved.has(node.key)) finish(node, false, true, "ancestry-unresolved");
|
|
4012
|
+
return resolved;
|
|
3708
4013
|
}
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
4014
|
+
//#endregion
|
|
4015
|
+
//#region packages/application/src/source/index.ts
|
|
4016
|
+
function resolveSourceSelection(inventory, route = "original") {
|
|
4017
|
+
const parts = resolveParts(inventory, route);
|
|
4018
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
4019
|
+
const families = reviewFamilies(inventory);
|
|
4020
|
+
const includedWorkspaces = /* @__PURE__ */ new Set();
|
|
4021
|
+
const counts = {
|
|
4022
|
+
workspaces: 0,
|
|
4023
|
+
sessions: 0,
|
|
4024
|
+
parts: 0,
|
|
4025
|
+
sourceBytes: 0
|
|
4026
|
+
};
|
|
4027
|
+
const coverage = {
|
|
4028
|
+
includedSessions: 0,
|
|
4029
|
+
excludedSessions: 0,
|
|
4030
|
+
unresolvedSessions: 0,
|
|
4031
|
+
unresolvedParts: 0
|
|
4032
|
+
};
|
|
4033
|
+
for (const session of inventory.sessions) {
|
|
4034
|
+
const decisions = session.parts.flatMap((part) => {
|
|
4035
|
+
const value = parts.get(sourceKey(session.id, part.id));
|
|
4036
|
+
return value ? [{
|
|
4037
|
+
part,
|
|
4038
|
+
value
|
|
4039
|
+
}] : [];
|
|
4040
|
+
});
|
|
4041
|
+
const included = decisions.filter(({ value }) => value.included);
|
|
4042
|
+
const unresolved = decisions.filter(({ value }) => value.unresolved);
|
|
4043
|
+
const excluded = decisions.filter(({ value }) => !value.included && !value.unresolved);
|
|
4044
|
+
const selected = familySelected(inventory, families, session);
|
|
4045
|
+
const missing = selected && session.parts.length === 0;
|
|
4046
|
+
const reasons = [...new Set(decisions.flatMap(({ value }) => value.reason ? [value.reason] : []))].sort();
|
|
4047
|
+
if (missing) reasons.push("unreadable-source");
|
|
4048
|
+
const summary = Object.freeze({
|
|
4049
|
+
status: unresolved.length || missing ? "unresolved" : included.length ? excluded.length ? "partial" : "included" : "excluded",
|
|
4050
|
+
includedParts: included.length,
|
|
4051
|
+
excludedParts: excluded.length,
|
|
4052
|
+
unresolvedParts: unresolved.length,
|
|
4053
|
+
sourceBytes: included.reduce((sum, { part }) => sum + part.snapshot.size, 0),
|
|
4054
|
+
reasons: Object.freeze(reasons)
|
|
4055
|
+
});
|
|
4056
|
+
sessions.set(session.id, summary);
|
|
4057
|
+
if (!selected) continue;
|
|
4058
|
+
if (summary.includedParts) {
|
|
4059
|
+
counts.sessions++;
|
|
4060
|
+
counts.parts += summary.includedParts;
|
|
4061
|
+
counts.sourceBytes += summary.sourceBytes;
|
|
4062
|
+
includedWorkspaces.add(session.workspaceId);
|
|
4063
|
+
coverage.includedSessions++;
|
|
4064
|
+
}
|
|
4065
|
+
if (summary.status === "excluded") coverage.excludedSessions++;
|
|
4066
|
+
if (summary.status === "unresolved") coverage.unresolvedSessions++;
|
|
4067
|
+
coverage.unresolvedParts += summary.unresolvedParts;
|
|
3720
4068
|
}
|
|
3721
|
-
|
|
4069
|
+
counts.workspaces = includedWorkspaces.size;
|
|
4070
|
+
return Object.freeze({
|
|
4071
|
+
counts: Object.freeze(counts),
|
|
4072
|
+
coverage: Object.freeze(coverage),
|
|
4073
|
+
sessions,
|
|
4074
|
+
session: (sessionId) => {
|
|
4075
|
+
const value = sessions.get(sessionId);
|
|
4076
|
+
if (!value) throw new ApplicationError("UNKNOWN_ITEM");
|
|
4077
|
+
return value;
|
|
4078
|
+
},
|
|
4079
|
+
part: (sessionId, partId) => parts.get(sourceKey(sessionId, partId))
|
|
4080
|
+
});
|
|
3722
4081
|
}
|
|
3723
4082
|
//#endregion
|
|
3724
|
-
//#region packages/application/src/journey/
|
|
3725
|
-
/**
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
4083
|
+
//#region packages/application/src/journey/scan-exclusion.ts
|
|
4084
|
+
/** Manual source-policy proof; never an assertion that unknown content was scanned. */
|
|
4085
|
+
const scanExclusionResourceSchema = z.object({
|
|
4086
|
+
kind: z.literal("scan-exclusion"),
|
|
4087
|
+
proofVersion: z.literal(1),
|
|
4088
|
+
journeyId: journeyIdSchema,
|
|
4089
|
+
workId: journeyIdSchema,
|
|
4090
|
+
inputRevision: z.number().int().positive(),
|
|
4091
|
+
snapshotId: journeyDigestSchema,
|
|
4092
|
+
configurationDigest: journeyDigestSchema,
|
|
4093
|
+
rootSessionId: z.string().min(1),
|
|
4094
|
+
sessionId: z.string().min(1),
|
|
4095
|
+
sourceId: z.string().min(1),
|
|
4096
|
+
sourcePartId: z.string().min(1),
|
|
4097
|
+
native: nativeSourceIdentitySchema,
|
|
4098
|
+
adapterVersion: z.string().min(1),
|
|
4099
|
+
reason: z.enum([
|
|
4100
|
+
"guardian-excluded",
|
|
4101
|
+
"missing-parent",
|
|
4102
|
+
"deselected-parent",
|
|
4103
|
+
"ancestry-unresolved",
|
|
4104
|
+
"non-interactive-launch"
|
|
4105
|
+
]),
|
|
4106
|
+
facts: z.object({
|
|
4107
|
+
representation: z.enum(["jsonl", "jsonl.zst"]),
|
|
4108
|
+
byteLength: z.number().int().nonnegative(),
|
|
4109
|
+
sha256: journeyDigestSchema
|
|
4110
|
+
}).strict()
|
|
3744
4111
|
}).strict();
|
|
3745
|
-
/**
|
|
3746
|
-
|
|
3747
|
-
const
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
4112
|
+
/** Validate policy against frozen identity evidence, never a caller-asserted source role. */
|
|
4113
|
+
function validateScanExclusion(resource, state, inventory, capture) {
|
|
4114
|
+
const work = state.works.find((item) => item.id === resource.workId);
|
|
4115
|
+
if (state.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
|
|
4116
|
+
if (!work || state.scope.status !== "confirmed" || work.access === "drop" || state.pendingHideId) throw new ApplicationError("SCOPE_NOT_READY");
|
|
4117
|
+
if (state.journeyId !== resource.journeyId || work.inputRevision !== resource.inputRevision || state.snapshotId !== resource.snapshotId || !work.rootSessionIds.includes(resource.rootSessionId)) throw new ApplicationError("REVISION_CONFLICT");
|
|
4118
|
+
const families = reviewFamilies(inventory);
|
|
4119
|
+
const part = (families.members.get(resource.rootSessionId)?.find((item) => item.id === resource.sessionId))?.parts.find((item) => item.id === resource.sourcePartId);
|
|
4120
|
+
const evidence = capture.source.nativeEvidence;
|
|
4121
|
+
const launch = resource.reason === "non-interactive-launch";
|
|
4122
|
+
const ancestry = resource.reason === "ancestry-unresolved";
|
|
4123
|
+
if (ancestry && resource.native.role !== "worker") throw new ApplicationError("CORRUPTED_STATE");
|
|
4124
|
+
const prefixRoot = launch && resource.rootSessionId === resource.sessionId && resource.native.role === "main" && evidence?.recognition === "unknown" && evidence.completeness === "prefix" && JSON.stringify(evidence.identity) === JSON.stringify({
|
|
4125
|
+
...resource.native,
|
|
4126
|
+
role: "unknown"
|
|
4127
|
+
});
|
|
4128
|
+
if (!part || capture.partId !== resource.sourcePartId || capture.facts.sha256 !== resource.facts.sha256 || capture.facts.byteLength !== resource.facts.byteLength || capture.facts.representation !== resource.facts.representation || !evidence || !prefixRoot && (evidence.recognition !== "recognized" || JSON.stringify(evidence.identity) !== JSON.stringify(resource.native)) || evidence.integrity !== "valid" || evidence.adapterVersion !== resource.adapterVersion || evidence.snapshotId !== sourceSnapshotId(capture.source) || JSON.stringify(part.nativeEvidence) !== JSON.stringify(evidence)) throw new ApplicationError("CORRUPTED_STATE");
|
|
4129
|
+
const ids = new Set(work.rootSessionIds.flatMap((id) => (families.members.get(id) ?? []).map((item) => item.id)));
|
|
4130
|
+
const sessions = inventory.sessions.filter((item) => ids.has(item.id));
|
|
4131
|
+
const workspaceIds = new Set(sessions.map((item) => item.workspaceId));
|
|
4132
|
+
const decision = resolveSourceSelection({
|
|
4133
|
+
...inventory,
|
|
4134
|
+
sessions,
|
|
4135
|
+
workspaces: inventory.workspaces.filter((item) => workspaceIds.has(item.id))
|
|
4136
|
+
}, "configured").part(resource.sessionId, resource.sourcePartId);
|
|
4137
|
+
if (!decision || (prefixRoot ? decision.included || decision.reason !== "unknown-source" : ancestry ? decision.included ? decision.reason !== null : decision.reason !== resource.reason : decision.included !== launch || (launch ? decision.reason !== null : decision.reason !== resource.reason))) throw new ApplicationError("CORRUPTED_STATE");
|
|
3769
4138
|
}
|
|
3770
|
-
|
|
3771
|
-
|
|
4139
|
+
//#endregion
|
|
4140
|
+
//#region packages/application/src/journey/scan-git-seeds.ts
|
|
3772
4141
|
/**
|
|
3773
|
-
*
|
|
3774
|
-
*
|
|
4142
|
+
* Owner-only Git detector input fixed once per frozen snapshot and canonical root set.
|
|
4143
|
+
* Later Git activity cannot change or invalidate a scan of the same frozen sources.
|
|
3775
4144
|
*/
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
4145
|
+
const scanGitSeedsResourceSchema = z.object({
|
|
4146
|
+
kind: z.literal("git-seeds"),
|
|
4147
|
+
journeyId: journeyIdSchema,
|
|
4148
|
+
snapshotId: journeyDigestSchema,
|
|
4149
|
+
roots: z.array(z.string()),
|
|
4150
|
+
seeds: z.object({
|
|
4151
|
+
names: z.array(z.string().min(1)),
|
|
4152
|
+
snapshots: z.array(z.object({
|
|
4153
|
+
id: z.string().min(1),
|
|
4154
|
+
sha256: z.string()
|
|
4155
|
+
}).strict())
|
|
4156
|
+
}).strict(),
|
|
4157
|
+
sha256: journeyDigestSchema
|
|
4158
|
+
}).strict();
|
|
4159
|
+
/** Storage key; equal root sets in any order share one receipt. */
|
|
4160
|
+
const scanGitSeedsId = (snapshotId, roots) => `${snapshotId}:${textDigest(JSON.stringify([...new Set(roots)].sort()))}`;
|
|
4161
|
+
function scanGitSeedsResource(journeyId, snapshotId, roots, seeds) {
|
|
4162
|
+
const value = {
|
|
4163
|
+
names: [...seeds.names],
|
|
4164
|
+
snapshots: seeds.snapshots.map((row) => ({ ...row }))
|
|
3785
4165
|
};
|
|
3786
|
-
}
|
|
3787
|
-
/** Dictionary values a commit may add to the hidden set, each with its type's kind (ADR 0086). */
|
|
3788
|
-
function termHideAdditions(key, values) {
|
|
3789
|
-
const unique = /* @__PURE__ */ new Map();
|
|
3790
|
-
for (const { term, kind } of values) unique.set(JSON.stringify([term, kind]), hideDeltaValue(key, term, kindOf(kind)));
|
|
3791
4166
|
return {
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
4167
|
+
kind: "git-seeds",
|
|
4168
|
+
journeyId,
|
|
4169
|
+
snapshotId,
|
|
4170
|
+
roots: [...new Set(roots)].sort(),
|
|
4171
|
+
seeds: value,
|
|
4172
|
+
sha256: textDigest(JSON.stringify(value))
|
|
3795
4173
|
};
|
|
3796
4174
|
}
|
|
3797
|
-
/**
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
function confirmedHideAdditions(key, records) {
|
|
3802
|
-
const values = /* @__PURE__ */ new Map();
|
|
3803
|
-
for (const record of records) {
|
|
3804
|
-
const chunks = new Map(record.chunks.map((chunk) => [chunk.id, chunk]));
|
|
3805
|
-
for (const finding of record.findings) {
|
|
3806
|
-
const chunk = chunks.get(finding.chunkId);
|
|
3807
|
-
if (!chunk || finding.class !== "gray") continue;
|
|
3808
|
-
const whole = chunk.offset === 0 && chunk.textHash === chunk.fieldHash;
|
|
3809
|
-
const joins = finding.start === 0 && chunk.offset > 0 || finding.end === chunk.text.length && !whole;
|
|
3810
|
-
const source = {
|
|
3811
|
-
batchId: record.batchId,
|
|
3812
|
-
findingId: finding.id
|
|
3813
|
-
};
|
|
3814
|
-
for (const term of bLiteralTerms(chunk.text.slice(finding.start, finding.end))) {
|
|
3815
|
-
const id = JSON.stringify([
|
|
3816
|
-
term,
|
|
3817
|
-
finding.kind,
|
|
3818
|
-
joins
|
|
3819
|
-
]);
|
|
3820
|
-
const value = values.get(id);
|
|
3821
|
-
if (value) value.sources.push(source);
|
|
3822
|
-
else values.set(id, {
|
|
3823
|
-
...hideDeltaValue(key, term, finding.kind, joins),
|
|
3824
|
-
sources: [source]
|
|
3825
|
-
});
|
|
3826
|
-
}
|
|
3827
|
-
}
|
|
3828
|
-
}
|
|
3829
|
-
return {
|
|
3830
|
-
scope: "additions",
|
|
3831
|
-
keyId: hideKeyId(key),
|
|
3832
|
-
values: [...values.values()]
|
|
3833
|
-
};
|
|
4175
|
+
/** A missing, tampered or foreign receipt is corrupted state, never a cue to reread live Git. */
|
|
4176
|
+
function verifiedScanGitSeeds(resource, snapshotId, roots) {
|
|
4177
|
+
if (resource.snapshotId !== snapshotId || scanGitSeedsId(resource.snapshotId, resource.roots) !== scanGitSeedsId(snapshotId, roots) || textDigest(JSON.stringify(resource.seeds)) !== resource.sha256) throw new ApplicationError("CORRUPTED_STATE");
|
|
4178
|
+
return resource.seeds;
|
|
3834
4179
|
}
|
|
4180
|
+
//#endregion
|
|
4181
|
+
//#region packages/application/src/journey/scan-session.ts
|
|
4182
|
+
const count$8 = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
4183
|
+
const scanFileSchema = z.object({
|
|
4184
|
+
path: z.string().min(1),
|
|
4185
|
+
identity: fileIdentitySchema,
|
|
4186
|
+
sha256: journeyDigestSchema,
|
|
4187
|
+
byteLength: count$8
|
|
4188
|
+
}).strict();
|
|
3835
4189
|
/**
|
|
3836
|
-
*
|
|
3837
|
-
*
|
|
3838
|
-
* hidden values are its distinct values while it hides; an answer that changes whether a type
|
|
3839
|
-
* hides is otherwise, so only a complete delta covers it.
|
|
4190
|
+
* Track A source-bound prose (`file`) with built-in static protection spans; no delivery archive
|
|
4191
|
+
* exists during scanning (ADR 0172). Obsolete version 3 inputs with detector evidence are refused.
|
|
3840
4192
|
*/
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
4193
|
+
const sessionScanInputSchema = z.object({
|
|
4194
|
+
version: z.literal(4),
|
|
4195
|
+
sessionId: z.string().min(1),
|
|
4196
|
+
file: scanFileSchema,
|
|
4197
|
+
sources: z.array(z.object({
|
|
4198
|
+
sourceId: z.string().min(1),
|
|
4199
|
+
sourcePartId: z.string().min(1),
|
|
4200
|
+
native: nativeSourceIdentitySchema,
|
|
4201
|
+
facts: z.object({
|
|
4202
|
+
representation: z.enum(["jsonl", "jsonl.zst"]),
|
|
4203
|
+
byteLength: count$8,
|
|
4204
|
+
sha256: journeyDigestSchema
|
|
4205
|
+
}).strict()
|
|
4206
|
+
}).strict()),
|
|
4207
|
+
recordCount: count$8,
|
|
4208
|
+
fieldCount: count$8,
|
|
4209
|
+
inputUtf16: count$8,
|
|
4210
|
+
mainReviewVerified: z.literal(true),
|
|
4211
|
+
emptyReviewVerified: z.boolean()
|
|
4212
|
+
}).strict().refine((input) => new Set(input.sources.map((source) => source.sourceId)).size === input.sources.length);
|
|
4213
|
+
const scanSessionResourceSchema = z.object({
|
|
4214
|
+
kind: z.literal("scan-session"),
|
|
4215
|
+
journeyId: journeyIdSchema,
|
|
4216
|
+
workId: journeyIdSchema,
|
|
4217
|
+
inputRevision: count$8.min(1),
|
|
4218
|
+
snapshotId: journeyDigestSchema,
|
|
4219
|
+
configurationDigest: journeyDigestSchema,
|
|
4220
|
+
rootSessionId: z.string().min(1),
|
|
4221
|
+
input: sessionScanInputSchema
|
|
4222
|
+
}).strict();
|
|
4223
|
+
/** Complete scan-set evidence for certified empty main input; never an output digest. */
|
|
4224
|
+
function scanPreparationDigest(resources) {
|
|
4225
|
+
return textDigest(JSON.stringify(resources.map((resource) => resource.kind === "scan-exclusion" ? resource : [
|
|
4226
|
+
resource.input.sessionId,
|
|
4227
|
+
resource.rootSessionId,
|
|
4228
|
+
resource.configurationDigest,
|
|
4229
|
+
resource.input.file.sha256,
|
|
4230
|
+
resource.input.sources
|
|
4231
|
+
]).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)))));
|
|
3848
4232
|
}
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
4233
|
+
const scanBatchBinding = {
|
|
4234
|
+
journeyId: journeyIdSchema,
|
|
4235
|
+
workId: journeyIdSchema,
|
|
4236
|
+
inputRevision: count$8.min(1),
|
|
4237
|
+
snapshotId: journeyDigestSchema,
|
|
4238
|
+
configurationDigest: journeyDigestSchema
|
|
4239
|
+
};
|
|
4240
|
+
const scanCanonicalResourceSchema = z.object({
|
|
4241
|
+
...scanBatchBinding,
|
|
4242
|
+
kind: z.literal("scan-canonical"),
|
|
4243
|
+
id: journeyDigestSchema,
|
|
4244
|
+
batchId: journeyIdSchema,
|
|
4245
|
+
batchDigest: journeyDigestSchema
|
|
4246
|
+
}).strict();
|
|
4247
|
+
const scanAliasResourceSchema = z.object({
|
|
4248
|
+
...scanBatchBinding,
|
|
4249
|
+
kind: z.literal("scan-alias"),
|
|
4250
|
+
id: journeyIdSchema,
|
|
4251
|
+
canonicalBatchId: journeyIdSchema,
|
|
4252
|
+
canonicalBatchDigest: journeyDigestSchema,
|
|
4253
|
+
targetBatchId: journeyIdSchema,
|
|
4254
|
+
targetBatchDigest: journeyDigestSchema,
|
|
4255
|
+
equivalenceDigest: journeyDigestSchema
|
|
4256
|
+
}).strict();
|
|
4257
|
+
//#endregion
|
|
4258
|
+
//#region packages/application/src/journey/b/repair.ts
|
|
4259
|
+
const bRepairPlanSchema = z.object({
|
|
4260
|
+
unitId: journeyDigestSchema,
|
|
4261
|
+
unitDigest: journeyDigestSchema,
|
|
4262
|
+
priorDigest: journeyDigestSchema,
|
|
4263
|
+
round: z.literal(1),
|
|
4264
|
+
targetFields: z.array(bIdSchema).min(1).max(24),
|
|
4265
|
+
remainingFields: z.array(bIdSchema)
|
|
4266
|
+
}).strict();
|
|
4267
|
+
const bReviewDigest = (submission) => bDigest(JSON.stringify(bReviewSubmissionSchema.parse(submission)));
|
|
4268
|
+
const repairMeaning = (part) => JSON.stringify([part.category, part.classification]);
|
|
4269
|
+
function planBRepair(unit, review, completedRepairs = 0) {
|
|
4270
|
+
if (completedRepairs !== 0) throw new ApplicationError("INVALID_OPERATION");
|
|
4271
|
+
const validated = validateBReview(unit, review.submission);
|
|
4272
|
+
const needed = new Set(validated.issues.flatMap((issue) => issue.fieldId === null ? [] : [issue.fieldId]));
|
|
4273
|
+
const targets = [...new Set(unit.fragments.filter((part) => part.core).map((part) => part.fieldId))].filter((fieldId) => needed.has(fieldId));
|
|
4274
|
+
if (!targets.length) return null;
|
|
4275
|
+
return bRepairPlanSchema.parse({
|
|
4276
|
+
unitId: unit.id,
|
|
4277
|
+
unitDigest: unit.digest,
|
|
4278
|
+
priorDigest: bReviewDigest(review.submission),
|
|
4279
|
+
round: 1,
|
|
4280
|
+
targetFields: targets.slice(0, 24),
|
|
4281
|
+
remainingFields: [...needed].filter((fieldId) => !targets.slice(0, 24).includes(fieldId))
|
|
4282
|
+
});
|
|
3874
4283
|
}
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
if (
|
|
3882
|
-
|
|
3883
|
-
|
|
4284
|
+
function mergeBRepair(unit, previous, inputPlan, raw) {
|
|
4285
|
+
const plan = bRepairPlanSchema.parse(inputPlan);
|
|
4286
|
+
const expected = planBRepair(unit, previous);
|
|
4287
|
+
if (JSON.stringify(plan) !== JSON.stringify(expected)) throw new ApplicationError("REVISION_CONFLICT");
|
|
4288
|
+
const correction = bReviewSubmissionSchema.parse(raw);
|
|
4289
|
+
const targeted = (fieldId) => plan.targetFields.includes(fieldId);
|
|
4290
|
+
if ([
|
|
4291
|
+
...correction.terms.flatMap((term) => term.occurrences),
|
|
4292
|
+
...correction.retainedSourceGroups,
|
|
4293
|
+
...correction.representationLimits,
|
|
4294
|
+
...correction.uncertainOccurrences
|
|
4295
|
+
].some((part) => !targeted(part.fieldId))) throw new ApplicationError("INVALID_REVIEW_SPAN");
|
|
4296
|
+
const repaired = validateBReview(unit, correction, plan.targetFields);
|
|
4297
|
+
const prior = validateBReview(unit, previous.submission);
|
|
4298
|
+
const correctedMeanings = /* @__PURE__ */ new Map();
|
|
4299
|
+
for (const part of repaired.sensitive) {
|
|
4300
|
+
const key = bAnchorKey(part);
|
|
4301
|
+
const meanings = correctedMeanings.get(key) ?? /* @__PURE__ */ new Set();
|
|
4302
|
+
meanings.add(repairMeaning(part));
|
|
4303
|
+
correctedMeanings.set(key, meanings);
|
|
4304
|
+
}
|
|
4305
|
+
const preservedKeys = /* @__PURE__ */ new Set();
|
|
4306
|
+
const conflicts = [];
|
|
4307
|
+
const preserved = prior.sensitive.filter((part) => targeted(part.fieldId)).flatMap((part) => {
|
|
4308
|
+
const retentions = part.classification.class === "certain" ? [] : repaired.retained.filter((retained) => retained.fieldId === part.fieldId);
|
|
4309
|
+
return subtractBRanges([part], retentions).map((range) => ({
|
|
4310
|
+
fieldId: part.fieldId,
|
|
4311
|
+
recordId: part.recordId,
|
|
4312
|
+
fieldHash: part.fieldHash,
|
|
4313
|
+
...range,
|
|
4314
|
+
exactQuote: codePointSlice(part.exactQuote, range.start - part.start, range.end - part.start),
|
|
4315
|
+
category: part.category,
|
|
4316
|
+
classification: part.classification,
|
|
4317
|
+
reason: "Prior sensitive obligation preserved during bounded repair."
|
|
4318
|
+
}));
|
|
4319
|
+
}).filter((part) => {
|
|
4320
|
+
const anchor = bAnchorKey(part), meaning = repairMeaning(part), corrected = correctedMeanings.get(anchor);
|
|
4321
|
+
if (corrected && (corrected.size > 1 || !corrected.has(meaning))) conflicts.push(bIssue("CONFLICTING_PRIVACY_DECISIONS", part));
|
|
4322
|
+
if (corrected?.has(meaning)) return false;
|
|
4323
|
+
const key = JSON.stringify([anchor, meaning]);
|
|
4324
|
+
if (preservedKeys.has(key)) return false;
|
|
4325
|
+
preservedKeys.add(key);
|
|
4326
|
+
return true;
|
|
4327
|
+
});
|
|
4328
|
+
const original = previous.submission;
|
|
4329
|
+
const merged = {
|
|
4330
|
+
...original,
|
|
4331
|
+
terms: [...original.terms.map((term) => ({
|
|
4332
|
+
...term,
|
|
4333
|
+
occurrences: term.occurrences.filter((part) => !targeted(part.fieldId))
|
|
4334
|
+
})).filter((term) => term.occurrences.length > 0), ...correction.terms],
|
|
4335
|
+
retainedSourceGroups: [...original.retainedSourceGroups.filter((part) => !targeted(part.fieldId)), ...correction.retainedSourceGroups],
|
|
4336
|
+
representationLimits: [
|
|
4337
|
+
...original.representationLimits.filter((part) => !targeted(part.fieldId)),
|
|
4338
|
+
...correction.representationLimits,
|
|
4339
|
+
...preserved
|
|
4340
|
+
],
|
|
4341
|
+
uncertainOccurrences: [...original.uncertainOccurrences.filter((part) => !targeted(part.fieldId)), ...correction.uncertainOccurrences],
|
|
4342
|
+
coverage: [...original.coverage.filter((part) => !targeted(part.fieldId)), ...correction.coverage],
|
|
4343
|
+
sessionDecision: correction.sessionDecision
|
|
4344
|
+
};
|
|
4345
|
+
const review = validateBReview(unit, merged);
|
|
4346
|
+
review.issues = [...new Map([...review.issues, ...conflicts].map((issue) => [JSON.stringify(issue), issue])).values()];
|
|
3884
4347
|
return {
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
...
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
}
|
|
4348
|
+
review,
|
|
4349
|
+
receipt: {
|
|
4350
|
+
...plan,
|
|
4351
|
+
resultDigest: bReviewDigest(merged),
|
|
4352
|
+
priorObligationsPreserved: true,
|
|
4353
|
+
unresolvedIssues: review.issues
|
|
4354
|
+
}
|
|
3892
4355
|
};
|
|
3893
4356
|
}
|
|
3894
4357
|
//#endregion
|
|
3895
|
-
//#region packages/application/src/journey/
|
|
3896
|
-
|
|
3897
|
-
|
|
4358
|
+
//#region packages/application/src/journey/review/limits.ts
|
|
4359
|
+
/** Raw provider bytes stay bounded independently of JSON escaping and compiled findings. */
|
|
4360
|
+
const reviewResponseBytes = 4194304;
|
|
4361
|
+
/** Fits the escaped raw-response envelope and evidence plus compiled occurrence findings. */
|
|
4362
|
+
const reviewTransportBytes = 33554432;
|
|
4363
|
+
//#endregion
|
|
4364
|
+
//#region packages/application/src/journey/review/candidate-schema.ts
|
|
4365
|
+
const reviewIssueSchema = z.object({
|
|
4366
|
+
code: z.string().regex(/^[A-Z_]{3,80}$/u),
|
|
4367
|
+
fieldId: z.string().min(1).nullable(),
|
|
4368
|
+
start: z.number().int().nonnegative().nullable(),
|
|
4369
|
+
end: z.number().int().nonnegative().nullable()
|
|
4370
|
+
}).strict();
|
|
4371
|
+
const reviewCandidateInputSchema = z.object({
|
|
3898
4372
|
journeyId: journeyIdSchema,
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
"send",
|
|
3907
|
-
"search"
|
|
3908
|
-
]),
|
|
3909
|
-
source: z.enum([
|
|
3910
|
-
"conversation",
|
|
3911
|
-
"panel",
|
|
3912
|
-
"system"
|
|
3913
|
-
]),
|
|
3914
|
-
at: z.iso.datetime(),
|
|
3915
|
-
alignment: reviewAlignmentAuditSchema.optional(),
|
|
3916
|
-
preferenceScopes: z.array(z.object({
|
|
3917
|
-
groupId: journeyIdSchema,
|
|
3918
|
-
preferenceScope: journeyPreferenceScopeSchema
|
|
3919
|
-
}).strict()).min(1).max(1e4).optional(),
|
|
3920
|
-
correction: correctionAuditSchema.optional(),
|
|
3921
|
-
decision: delegatedDecisionSchema.extend({
|
|
3922
|
-
action: z.enum([
|
|
3923
|
-
"group",
|
|
3924
|
-
"groups-hide",
|
|
3925
|
-
"work-access",
|
|
3926
|
-
"instruction",
|
|
3927
|
-
"correction",
|
|
3928
|
-
"works-access",
|
|
3929
|
-
"work-type",
|
|
3930
|
-
"scope-confirm",
|
|
3931
|
-
"card-answer",
|
|
3932
|
-
"cards-confirm"
|
|
3933
|
-
]),
|
|
3934
|
-
targetIds: z.array(journeyIdSchema).min(1),
|
|
3935
|
-
choice: z.string().min(1).max(16)
|
|
3936
|
-
}).optional()
|
|
4373
|
+
batchId: journeyIdSchema,
|
|
4374
|
+
batchDigest: journeyDigestSchema,
|
|
4375
|
+
idempotencyKey: journeyIdSchema,
|
|
4376
|
+
attemptId: journeyIdSchema,
|
|
4377
|
+
response: z.string().min(1).refine((value) => Buffer.byteLength(value, "utf8") <= reviewResponseBytes),
|
|
4378
|
+
repairId: journeyIdSchema.nullable(),
|
|
4379
|
+
sessionReviewId: journeyIdSchema.nullable()
|
|
3937
4380
|
}).strict();
|
|
3938
|
-
const
|
|
3939
|
-
|
|
3940
|
-
|
|
4381
|
+
const reviewCandidateResourceSchema = reviewCandidateInputSchema.omit({ idempotencyKey: true }).extend({
|
|
4382
|
+
kind: z.literal("b-candidate"),
|
|
4383
|
+
id: journeyIdSchema,
|
|
3941
4384
|
requestHash: journeyDigestSchema,
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
/** Inserted in the same transaction as this state; never through the generic resource save. */
|
|
3948
|
-
correctionReport: correctionReportResourceSchema.optional(),
|
|
3949
|
-
/** Set by the store for this commit only and never persisted (ADR 0079). */
|
|
3950
|
-
confirmedHideDelta: confirmedHideDeltaSchema.optional(),
|
|
3951
|
-
/** Dictionary values a Hide override adds (ADR 0086), inserted with this state. */
|
|
3952
|
-
terms: z.array(z.object({
|
|
3953
|
-
type: termTypeSchema,
|
|
3954
|
-
value: z.string().min(1)
|
|
3955
|
-
}).strict()).max(2e4).optional()
|
|
3956
|
-
}).strict();
|
|
3957
|
-
const journeyCommitResultSchema = z.object({
|
|
4385
|
+
at: z.iso.datetime(),
|
|
4386
|
+
workId: journeyIdSchema,
|
|
4387
|
+
inputRevision: z.number().int().positive(),
|
|
4388
|
+
snapshotId: journeyDigestSchema,
|
|
4389
|
+
responseDigest: journeyDigestSchema,
|
|
3958
4390
|
status: z.enum([
|
|
3959
|
-
"
|
|
3960
|
-
"
|
|
3961
|
-
"
|
|
4391
|
+
"valid",
|
|
4392
|
+
"pending",
|
|
4393
|
+
"invalid"
|
|
3962
4394
|
]),
|
|
3963
|
-
|
|
4395
|
+
issues: z.array(reviewIssueSchema),
|
|
4396
|
+
evidence: bReviewSubmissionSchema.nullable()
|
|
4397
|
+
}).strict();
|
|
4398
|
+
const reviewCandidateResultSchema = reviewCandidateResourceSchema.pick({
|
|
4399
|
+
responseDigest: true,
|
|
4400
|
+
status: true,
|
|
4401
|
+
issues: true,
|
|
4402
|
+
evidence: true
|
|
4403
|
+
}).extend({ candidateId: journeyIdSchema }).strict();
|
|
4404
|
+
const reviewRepairInputSchema = z.object({
|
|
4405
|
+
journeyId: journeyIdSchema,
|
|
4406
|
+
candidateId: journeyIdSchema,
|
|
4407
|
+
idempotencyKey: journeyIdSchema
|
|
4408
|
+
}).strict();
|
|
4409
|
+
const reviewRepairResourceSchema = z.object({
|
|
4410
|
+
kind: z.literal("b-repair"),
|
|
4411
|
+
id: journeyIdSchema,
|
|
4412
|
+
journeyId: journeyIdSchema,
|
|
4413
|
+
workId: journeyIdSchema,
|
|
4414
|
+
batchId: journeyIdSchema,
|
|
4415
|
+
batchDigest: journeyDigestSchema,
|
|
4416
|
+
inputRevision: z.number().int().positive(),
|
|
4417
|
+
snapshotId: journeyDigestSchema,
|
|
4418
|
+
candidateId: journeyIdSchema,
|
|
4419
|
+
requestHash: journeyDigestSchema,
|
|
4420
|
+
at: z.iso.datetime(),
|
|
4421
|
+
plan: bRepairPlanSchema
|
|
3964
4422
|
}).strict();
|
|
3965
4423
|
//#endregion
|
|
3966
4424
|
//#region packages/application/src/journey/review/usefulness-schema.ts
|
|
@@ -4032,7 +4490,7 @@ const reviewProgressSchema = z.array(z.object({
|
|
|
4032
4490
|
}).strict());
|
|
4033
4491
|
//#endregion
|
|
4034
4492
|
//#region packages/application/src/journey/review/session-schema.ts
|
|
4035
|
-
const count$
|
|
4493
|
+
const count$7 = z.number().int().nonnegative();
|
|
4036
4494
|
const sessionReviewInputSchema = z.object({
|
|
4037
4495
|
journeyId: journeyIdSchema,
|
|
4038
4496
|
workId: journeyIdSchema,
|
|
@@ -4053,7 +4511,7 @@ const sessionReviewResourceSchema = z.object({
|
|
|
4053
4511
|
workId: journeyIdSchema,
|
|
4054
4512
|
sessionId: z.string().min(1).max(128),
|
|
4055
4513
|
snapshotId: journeyDigestSchema,
|
|
4056
|
-
inputRevision: count$
|
|
4514
|
+
inputRevision: count$7.positive(),
|
|
4057
4515
|
requestHash: journeyDigestSchema,
|
|
4058
4516
|
bindingDigest: journeyDigestSchema,
|
|
4059
4517
|
at: z.iso.datetime(),
|
|
@@ -4067,8 +4525,8 @@ const sessionReviewResourceSchema = z.object({
|
|
|
4067
4525
|
issues: z.array(reviewIssueSchema),
|
|
4068
4526
|
requiresSemantic: z.boolean(),
|
|
4069
4527
|
usefulness: sessionUsefulnessSchema,
|
|
4070
|
-
totalCodePoints: count$
|
|
4071
|
-
reviewedCodePoints: count$
|
|
4528
|
+
totalCodePoints: count$7,
|
|
4529
|
+
reviewedCodePoints: count$7
|
|
4072
4530
|
}).strict();
|
|
4073
4531
|
const sessionRereadInputSchema = z.object({
|
|
4074
4532
|
journeyId: journeyIdSchema,
|
|
@@ -4077,8 +4535,8 @@ const sessionRereadInputSchema = z.object({
|
|
|
4077
4535
|
anchors: z.array(z.object({
|
|
4078
4536
|
fieldId: z.string().min(1),
|
|
4079
4537
|
fieldHash: journeyDigestSchema,
|
|
4080
|
-
start: count$
|
|
4081
|
-
end: count$
|
|
4538
|
+
start: count$7,
|
|
4539
|
+
end: count$7
|
|
4082
4540
|
}).strict()).min(1).max(128)
|
|
4083
4541
|
}).strict();
|
|
4084
4542
|
const sessionRereadResourceSchema = z.object({
|
|
@@ -4087,7 +4545,7 @@ const sessionRereadResourceSchema = z.object({
|
|
|
4087
4545
|
journeyId: journeyIdSchema,
|
|
4088
4546
|
workId: journeyIdSchema,
|
|
4089
4547
|
sessionId: z.string().min(1).max(128),
|
|
4090
|
-
inputRevision: count$
|
|
4548
|
+
inputRevision: count$7.positive(),
|
|
4091
4549
|
snapshotId: journeyDigestSchema,
|
|
4092
4550
|
sessionReviewId: journeyIdSchema,
|
|
4093
4551
|
bindingDigest: journeyDigestSchema,
|
|
@@ -4105,8 +4563,8 @@ const sessionReconcileInputSchema = z.object({
|
|
|
4105
4563
|
alignment: reviewAlignmentSchema
|
|
4106
4564
|
}).strict();
|
|
4107
4565
|
const retainedReviewAnchorSchema = bAnchorSchema.extend({
|
|
4108
|
-
utf16Start: count$
|
|
4109
|
-
utf16End: count$
|
|
4566
|
+
utf16Start: count$7,
|
|
4567
|
+
utf16End: count$7,
|
|
4110
4568
|
owned: z.boolean()
|
|
4111
4569
|
}).strict();
|
|
4112
4570
|
const sessionOverlaySchema = z.object({
|
|
@@ -4120,7 +4578,7 @@ const sessionReconciliationResourceSchema = z.object({
|
|
|
4120
4578
|
journeyId: journeyIdSchema,
|
|
4121
4579
|
workId: journeyIdSchema,
|
|
4122
4580
|
sessionId: z.string().min(1).max(128),
|
|
4123
|
-
inputRevision: count$
|
|
4581
|
+
inputRevision: count$7.positive(),
|
|
4124
4582
|
snapshotId: journeyDigestSchema,
|
|
4125
4583
|
sessionReviewId: journeyIdSchema,
|
|
4126
4584
|
bindingDigest: journeyDigestSchema,
|
|
@@ -4148,8 +4606,8 @@ const sessionReviewQuerySchema = z.object({
|
|
|
4148
4606
|
workId: journeyIdSchema.optional(),
|
|
4149
4607
|
sessionId: z.string().min(1).max(128).optional(),
|
|
4150
4608
|
sessionReviewId: journeyIdSchema.optional(),
|
|
4151
|
-
afterHit: count$
|
|
4152
|
-
limit: count$
|
|
4609
|
+
afterHit: count$7.default(0),
|
|
4610
|
+
limit: count$7.positive().max(500).default(100)
|
|
4153
4611
|
}).strict();
|
|
4154
4612
|
const sessionReviewStatusSchema = z.object({
|
|
4155
4613
|
complete: z.boolean(),
|
|
@@ -4165,8 +4623,8 @@ const sessionReviewStatusSchema = z.object({
|
|
|
4165
4623
|
requiresHumanDecision: z.boolean(),
|
|
4166
4624
|
requiresSemantic: z.boolean(),
|
|
4167
4625
|
issues: z.array(reviewIssueSchema),
|
|
4168
|
-
totalCodePoints: count$
|
|
4169
|
-
reviewedCodePoints: count$
|
|
4626
|
+
totalCodePoints: count$7,
|
|
4627
|
+
reviewedCodePoints: count$7,
|
|
4170
4628
|
reconciliationId: journeyIdSchema.nullable(),
|
|
4171
4629
|
/** ADR 0086: a read unit of the session suggests excluding it; advice, never a gate. */
|
|
4172
4630
|
suggestExclude: z.boolean(),
|
|
@@ -4174,8 +4632,8 @@ const sessionReviewStatusSchema = z.object({
|
|
|
4174
4632
|
reason: z.string().min(1).max(2e3).nullable()
|
|
4175
4633
|
}).strict()),
|
|
4176
4634
|
hits: z.array(sessionReviewHitSchema),
|
|
4177
|
-
nextHit: count$
|
|
4178
|
-
totalHits: count$
|
|
4635
|
+
nextHit: count$7.nullable(),
|
|
4636
|
+
totalHits: count$7
|
|
4179
4637
|
}).strict();
|
|
4180
4638
|
const sessionReviewResultSchema = z.object({
|
|
4181
4639
|
preparation: sessionReviewResourceSchema,
|
|
@@ -4259,13 +4717,13 @@ const metadataFieldsSchema = z.object({
|
|
|
4259
4717
|
}).strict();
|
|
4260
4718
|
//#endregion
|
|
4261
4719
|
//#region packages/application/src/journey/placement/routing-schema.ts
|
|
4262
|
-
const count$
|
|
4720
|
+
const count$6 = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
4263
4721
|
const bucket = z.object({
|
|
4264
4722
|
complete: z.boolean(),
|
|
4265
|
-
total: count$
|
|
4723
|
+
total: count$6,
|
|
4266
4724
|
repositories: z.array(z.object({
|
|
4267
4725
|
id: z.string().min(1),
|
|
4268
|
-
count: count$
|
|
4726
|
+
count: count$6.min(1)
|
|
4269
4727
|
}).strict())
|
|
4270
4728
|
}).strict();
|
|
4271
4729
|
const placementSignalsSchema = z.object({
|
|
@@ -4283,8 +4741,8 @@ const cheapPlacementSchema = z.object({
|
|
|
4283
4741
|
"name"
|
|
4284
4742
|
]),
|
|
4285
4743
|
confidence: z.enum(["high", "medium"]),
|
|
4286
|
-
matched: count$
|
|
4287
|
-
total: count$
|
|
4744
|
+
matched: count$6.min(1),
|
|
4745
|
+
total: count$6.min(1),
|
|
4288
4746
|
otherRepositoryIds: z.array(z.string())
|
|
4289
4747
|
}).strict();
|
|
4290
4748
|
const sortingResourceSchema = z.object({
|
|
@@ -4305,26 +4763,26 @@ const sortingResourceSchema = z.object({
|
|
|
4305
4763
|
const sortingApplySchema = z.object({
|
|
4306
4764
|
journeyId: z.uuid(),
|
|
4307
4765
|
sessionId: z.string().min(1).max(128),
|
|
4308
|
-
expectedRevision: count$
|
|
4766
|
+
expectedRevision: count$6,
|
|
4309
4767
|
idempotencyKey: z.uuid()
|
|
4310
4768
|
}).strict();
|
|
4311
4769
|
/** Preparation moves identified failing roots of one pending Work into its blocked Work. */
|
|
4312
4770
|
const journeyIsolateSchema = z.object({
|
|
4313
4771
|
journeyId: z.uuid(),
|
|
4314
4772
|
workId: z.uuid(),
|
|
4315
|
-
inputRevision: count$
|
|
4773
|
+
inputRevision: count$6.min(1),
|
|
4316
4774
|
roots: z.array(z.object({
|
|
4317
4775
|
sessionId: z.string().min(1).max(128),
|
|
4318
4776
|
code: z.string().regex(/^[A-Z_]{3,80}$/u)
|
|
4319
4777
|
}).strict()).min(1).max(1e5),
|
|
4320
|
-
expectedRevision: count$
|
|
4778
|
+
expectedRevision: count$6,
|
|
4321
4779
|
idempotencyKey: z.uuid()
|
|
4322
4780
|
}).strict();
|
|
4323
4781
|
const placement = z.object({
|
|
4324
4782
|
workId: z.uuid(),
|
|
4325
4783
|
sessionId: z.string().min(1).max(128),
|
|
4326
|
-
inputRevision: count$
|
|
4327
|
-
throughEvent: count$
|
|
4784
|
+
inputRevision: count$6.min(1),
|
|
4785
|
+
throughEvent: count$6,
|
|
4328
4786
|
contextDigest: z.string().regex(/^[a-f0-9]{64}$/u),
|
|
4329
4787
|
decision: z.enum([
|
|
4330
4788
|
"join",
|
|
@@ -4340,13 +4798,13 @@ const placement = z.object({
|
|
|
4340
4798
|
}).strict();
|
|
4341
4799
|
const journeyPlaceSchema = z.object({
|
|
4342
4800
|
journeyId: z.uuid(),
|
|
4343
|
-
expectedRevision: count$
|
|
4801
|
+
expectedRevision: count$6,
|
|
4344
4802
|
idempotencyKey: z.uuid(),
|
|
4345
4803
|
placements: z.array(placement).min(1).max(1e3)
|
|
4346
4804
|
}).strict();
|
|
4347
4805
|
//#endregion
|
|
4348
4806
|
//#region packages/application/src/journey/scope/progress.ts
|
|
4349
|
-
const count$
|
|
4807
|
+
const count$5 = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
4350
4808
|
const ids = z.array(z.string().min(1).max(128)).max(1e5).refine((values) => new Set(values).size === values.length);
|
|
4351
4809
|
const journeyClassificationProgressSchema = z.object({
|
|
4352
4810
|
journeyId: journeyIdSchema,
|
|
@@ -4354,8 +4812,8 @@ const journeyClassificationProgressSchema = z.object({
|
|
|
4354
4812
|
id: z.literal("current"),
|
|
4355
4813
|
snapshotId: journeyDigestSchema,
|
|
4356
4814
|
inputFingerprint: journeyDigestSchema,
|
|
4357
|
-
baselineRevision: count$
|
|
4358
|
-
proposalRevision: count$
|
|
4815
|
+
baselineRevision: count$5,
|
|
4816
|
+
proposalRevision: count$5.nullable(),
|
|
4359
4817
|
stage: z.enum(["classification", "proposal"]),
|
|
4360
4818
|
status: z.enum([
|
|
4361
4819
|
"running",
|
|
@@ -4365,7 +4823,7 @@ const journeyClassificationProgressSchema = z.object({
|
|
|
4365
4823
|
]),
|
|
4366
4824
|
startedAt: z.iso.datetime(),
|
|
4367
4825
|
updatedAt: z.iso.datetime(),
|
|
4368
|
-
elapsedMs: count$
|
|
4826
|
+
elapsedMs: count$5,
|
|
4369
4827
|
rootIds: ids,
|
|
4370
4828
|
completedRootIds: ids,
|
|
4371
4829
|
groupedRootIds: ids,
|
|
@@ -4400,9 +4858,23 @@ const journeyCaptureSchema = z.object({
|
|
|
4400
4858
|
}).strict();
|
|
4401
4859
|
const base = { journeyId: journeyIdSchema };
|
|
4402
4860
|
const journeyResourceSchema = z.discriminatedUnion("kind", [
|
|
4861
|
+
z.object({
|
|
4862
|
+
...base,
|
|
4863
|
+
kind: z.literal("closed-reader"),
|
|
4864
|
+
id: z.string(),
|
|
4865
|
+
batchId: journeyIdSchema,
|
|
4866
|
+
packId: journeyIdSchema
|
|
4867
|
+
}).strict(),
|
|
4403
4868
|
scanSessionResourceSchema,
|
|
4404
4869
|
scanCanonicalResourceSchema,
|
|
4405
4870
|
scanAliasResourceSchema,
|
|
4871
|
+
z.object({
|
|
4872
|
+
...base,
|
|
4873
|
+
kind: z.literal("withdrawn-term-pack"),
|
|
4874
|
+
id: z.string(),
|
|
4875
|
+
batchId: journeyIdSchema,
|
|
4876
|
+
packId: journeyIdSchema
|
|
4877
|
+
}).strict(),
|
|
4406
4878
|
scanExclusionResourceSchema,
|
|
4407
4879
|
scanGitSeedsResourceSchema,
|
|
4408
4880
|
staticSessionResourceSchema,
|
|
@@ -4620,119 +5092,13 @@ function projectPreparationProgress(state, plan, captures, starts, settlements)
|
|
|
4620
5092
|
};
|
|
4621
5093
|
}
|
|
4622
5094
|
//#endregion
|
|
4623
|
-
//#region packages/application/src/journey/policy/port.ts
|
|
4624
|
-
const journeyPolicyRespondSchema = z.object({
|
|
4625
|
-
request: nativeRespondSchema,
|
|
4626
|
-
source: z.enum(["conversation", "panel"]),
|
|
4627
|
-
now: z.iso.datetime()
|
|
4628
|
-
}).strict();
|
|
4629
|
-
//#endregion
|
|
4630
|
-
//#region packages/application/src/journey/search.ts
|
|
4631
|
-
const count$5 = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
4632
|
-
const term = z.string().min(1).max(1024).refine((value) => Buffer.byteLength(value) <= 1024 && !/[\0\r\n]/u.test(value));
|
|
4633
|
-
const journeySearchSchema = z.object({
|
|
4634
|
-
journeyId: journeyIdSchema,
|
|
4635
|
-
terms: z.array(z.object({
|
|
4636
|
-
id: z.string().min(1).max(64),
|
|
4637
|
-
text: term,
|
|
4638
|
-
variants: z.array(term).max(7).default([])
|
|
4639
|
-
}).strict()).min(1).max(16)
|
|
4640
|
-
}).strict();
|
|
4641
|
-
const journeySearchResultSchema = z.object({
|
|
4642
|
-
binding: z.object({
|
|
4643
|
-
journeyId: journeyIdSchema,
|
|
4644
|
-
snapshotId: journeyDigestSchema,
|
|
4645
|
-
revision: count$5,
|
|
4646
|
-
scopeDigest: journeyDigestSchema,
|
|
4647
|
-
outputs: z.array(z.object({
|
|
4648
|
-
workId: journeyIdSchema,
|
|
4649
|
-
outputRevision: count$5,
|
|
4650
|
-
sha256: journeyDigestSchema
|
|
4651
|
-
}).strict())
|
|
4652
|
-
}).strict(),
|
|
4653
|
-
status: z.enum(["complete", "partial"]),
|
|
4654
|
-
scope: z.object({
|
|
4655
|
-
retainedText: z.literal(true),
|
|
4656
|
-
metadata: z.literal(true),
|
|
4657
|
-
setupText: z.literal(true),
|
|
4658
|
-
attachments: z.literal(false),
|
|
4659
|
-
images: z.literal(false),
|
|
4660
|
-
originals: z.literal("frozen-native-records-and-discovery-metadata"),
|
|
4661
|
-
matching: z.literal("case-sensitive-literal-leftmost-longest")
|
|
4662
|
-
}).strict(),
|
|
4663
|
-
terms: z.array(z.object({
|
|
4664
|
-
id: z.string(),
|
|
4665
|
-
tried: z.array(z.string()),
|
|
4666
|
-
sentMatches: count$5.nullable(),
|
|
4667
|
-
originalMatches: count$5.nullable(),
|
|
4668
|
-
sentSessions: count$5.nullable(),
|
|
4669
|
-
originalSessions: count$5.nullable()
|
|
4670
|
-
}).strict()),
|
|
4671
|
-
hits: z.array(z.object({
|
|
4672
|
-
termId: z.string(),
|
|
4673
|
-
basis: z.enum(["sent", "original"]),
|
|
4674
|
-
workId: journeyIdSchema,
|
|
4675
|
-
locator: z.object({
|
|
4676
|
-
kind: z.enum([
|
|
4677
|
-
"retained-text",
|
|
4678
|
-
"metadata",
|
|
4679
|
-
"setup-text"
|
|
4680
|
-
]),
|
|
4681
|
-
entryPath: z.string().optional(),
|
|
4682
|
-
archiveSessionId: z.string().optional(),
|
|
4683
|
-
sessionId: z.string().optional(),
|
|
4684
|
-
sourcePartId: z.string().optional(),
|
|
4685
|
-
recordIndex: count$5,
|
|
4686
|
-
fieldId: z.string(),
|
|
4687
|
-
line: count$5
|
|
4688
|
-
}).strict(),
|
|
4689
|
-
occurrences: count$5,
|
|
4690
|
-
modelContext: z.string().max(4096).optional(),
|
|
4691
|
-
match: z.object({
|
|
4692
|
-
variantIndex: count$5.max(7),
|
|
4693
|
-
start: count$5,
|
|
4694
|
-
end: count$5
|
|
4695
|
-
}).strict().optional()
|
|
4696
|
-
}).strict()),
|
|
4697
|
-
failures: z.array(z.object({
|
|
4698
|
-
workId: journeyIdSchema,
|
|
4699
|
-
basis: z.enum(["sent", "original"]),
|
|
4700
|
-
code: z.enum(["SEARCH_INPUT_UNAVAILABLE", "SEARCH_ENGINE_FAILED"])
|
|
4701
|
-
}).strict()),
|
|
4702
|
-
truncated: z.boolean()
|
|
4703
|
-
}).strict().refine((result) => result.hits.every((hit) => {
|
|
4704
|
-
if (hit.basis === "original") return hit.modelContext === void 0 && hit.match === void 0;
|
|
4705
|
-
if (!hit.match || hit.modelContext === void 0) return false;
|
|
4706
|
-
const { start, end, variantIndex } = hit.match;
|
|
4707
|
-
const variant = result.terms.find((term) => term.id === hit.termId)?.tried[variantIndex];
|
|
4708
|
-
if (!variant || end <= start || end > hit.modelContext.length || hit.modelContext.slice(start, end) !== variant) return false;
|
|
4709
|
-
return [start, end].every((offset) => {
|
|
4710
|
-
const before = hit.modelContext?.charCodeAt(offset - 1) ?? 0;
|
|
4711
|
-
const after = hit.modelContext?.charCodeAt(offset) ?? 0;
|
|
4712
|
-
return !(before >= 55296 && before <= 56319 && after >= 56320 && after <= 57343);
|
|
4713
|
-
});
|
|
4714
|
-
}));
|
|
4715
|
-
const journeySearchSaveSchema = z.object({
|
|
4716
|
-
journeyId: journeyIdSchema,
|
|
4717
|
-
expectedRevision: count$5,
|
|
4718
|
-
idempotencyKey: journeyIdSchema,
|
|
4719
|
-
result: journeySearchResultSchema
|
|
4720
|
-
}).strict();
|
|
4721
|
-
function searchScopeDigest(state) {
|
|
4722
|
-
return textDigest(JSON.stringify({
|
|
4723
|
-
snapshot: state.snapshotId,
|
|
4724
|
-
configurationId: state.configurationId,
|
|
4725
|
-
pendingHideId: state.pendingHideId,
|
|
4726
|
-
works: state.works.map((work) => ({
|
|
4727
|
-
id: work.id,
|
|
4728
|
-
revision: work.revision,
|
|
4729
|
-
inputRevision: work.inputRevision,
|
|
4730
|
-
access: work.access,
|
|
4731
|
-
artifact: work.artifact?.sha256 ?? null
|
|
4732
|
-
}))
|
|
4733
|
-
}));
|
|
4734
|
-
}
|
|
4735
|
-
//#endregion
|
|
5095
|
+
//#region packages/application/src/journey/policy/port.ts
|
|
5096
|
+
const journeyPolicyRespondSchema = z.object({
|
|
5097
|
+
request: nativeRespondSchema,
|
|
5098
|
+
source: z.enum(["conversation", "panel"]),
|
|
5099
|
+
now: z.iso.datetime()
|
|
5100
|
+
}).strict();
|
|
5101
|
+
//#endregion
|
|
4736
5102
|
//#region packages/application/src/journey/hide/port.ts
|
|
4737
5103
|
const hidePublishSchema = z.object({
|
|
4738
5104
|
expectedRevision: z.number().int().nonnegative(),
|
|
@@ -4890,7 +5256,7 @@ function retireScanGeneration(state, workId) {
|
|
|
4890
5256
|
}
|
|
4891
5257
|
/** The same Work with a new scan generation: it is read again from its first session. */
|
|
4892
5258
|
function resetWorkScan(work) {
|
|
4893
|
-
const { emptyReview: _empty, excludedReview: _excluded, staticReview: _static, metadataReviewedDigest: _metadata, reviewReuseFrom: _reuse, renderFailure: _failure, ...retained } = work;
|
|
5259
|
+
const { reviewInterruptions: _reviewInterruptions, emptyReview: _empty, excludedReview: _excluded, staticReview: _static, metadataReviewedDigest: _metadata, reviewReuseFrom: _reuse, renderFailure: _failure, ...retained } = work;
|
|
4894
5260
|
return {
|
|
4895
5261
|
...retained,
|
|
4896
5262
|
revision: work.revision + 1,
|
|
@@ -4905,174 +5271,6 @@ function resetWorkScan(work) {
|
|
|
4905
5271
|
};
|
|
4906
5272
|
}
|
|
4907
5273
|
//#endregion
|
|
4908
|
-
//#region packages/application/src/journey/scope/respond.ts
|
|
4909
|
-
/**
|
|
4910
|
-
* Account-seeded access (ADR 0274 D3(b)). With `held`, the time the account inventory was read,
|
|
4911
|
-
* a Work whose every root is held Public is confirmed Public with the account as its recorded
|
|
4912
|
-
* choice (ADR 0275); without it, Public stays a suggestion.
|
|
4913
|
-
*/
|
|
4914
|
-
function accountSeededAccess(account, held) {
|
|
4915
|
-
const accessChoice = account === "public-all" && held !== void 0 ? {
|
|
4916
|
-
channel: "account",
|
|
4917
|
-
at: held
|
|
4918
|
-
} : void 0;
|
|
4919
|
-
return {
|
|
4920
|
-
access: account === "public" || account === "public-all" ? "public" : "private",
|
|
4921
|
-
accessOrigin: "account",
|
|
4922
|
-
accessConfirmed: account === "private" || accessChoice !== void 0,
|
|
4923
|
-
accountAccessConflict: account === "mixed",
|
|
4924
|
-
accessChoice
|
|
4925
|
-
};
|
|
4926
|
-
}
|
|
4927
|
-
/** Classification confirmation initializes visibility and scan membership for this journey. */
|
|
4928
|
-
function confirmScope(state, now, decision, accountAccess) {
|
|
4929
|
-
if (state.scope.status === "confirmed") return state;
|
|
4930
|
-
if (state.classificationRevision === null || !state.works.length || state.works.some((work) => work.placement !== "placed" && work.placement !== "classifying" || work.placement === "placed" && !(work.status === "blocked" && work.access === "drop" && work.accessConfirmed) && (!work.description.trim() || !work.projectType))) throw new ApplicationError("SCOPE_NOT_READY");
|
|
4931
|
-
return withReadingProgress({
|
|
4932
|
-
...state,
|
|
4933
|
-
scope: {
|
|
4934
|
-
status: "confirmed",
|
|
4935
|
-
actor: decision ? "delegate" : "human",
|
|
4936
|
-
at: now
|
|
4937
|
-
},
|
|
4938
|
-
works: state.works.map((work) => {
|
|
4939
|
-
if (work.placement === "classifying") return work;
|
|
4940
|
-
if (work.accessOrigin === "explicit" && work.accessConfirmed) return work;
|
|
4941
|
-
const account = accountAccess?.get(work.id);
|
|
4942
|
-
if (account) return {
|
|
4943
|
-
...work,
|
|
4944
|
-
...accountSeededAccess(account, now)
|
|
4945
|
-
};
|
|
4946
|
-
if (work.accessOrigin === "account") return work;
|
|
4947
|
-
const access = work.accessConfirmed ? work.access : work.classificationRecommendation?.access ?? work.access;
|
|
4948
|
-
return {
|
|
4949
|
-
...work,
|
|
4950
|
-
access,
|
|
4951
|
-
accessOrigin: work.accessConfirmed || !work.classificationRecommendation ? work.accessOrigin : "model",
|
|
4952
|
-
accessConfirmed: access !== "public"
|
|
4953
|
-
};
|
|
4954
|
-
})
|
|
4955
|
-
});
|
|
4956
|
-
}
|
|
4957
|
-
function setWorkType(state, workId, type, decision) {
|
|
4958
|
-
if (state.scope.status === "confirmed") throw new ApplicationError("INVALID_TRANSITION");
|
|
4959
|
-
const projectType = decision ? {
|
|
4960
|
-
type,
|
|
4961
|
-
reason: "Chosen by your delegate for this journey.",
|
|
4962
|
-
origin: "delegate"
|
|
4963
|
-
} : {
|
|
4964
|
-
type,
|
|
4965
|
-
reason: "Chosen by you.",
|
|
4966
|
-
origin: "explicit"
|
|
4967
|
-
};
|
|
4968
|
-
return {
|
|
4969
|
-
...state,
|
|
4970
|
-
scope: {
|
|
4971
|
-
status: "pending",
|
|
4972
|
-
actor: null,
|
|
4973
|
-
at: null
|
|
4974
|
-
},
|
|
4975
|
-
privacy: {
|
|
4976
|
-
...state.privacy,
|
|
4977
|
-
cards: [],
|
|
4978
|
-
confirmedAnswersRevision: null,
|
|
4979
|
-
packageApproval: null
|
|
4980
|
-
},
|
|
4981
|
-
works: state.works.map((work) => work.id !== workId || decision && work.projectType?.origin === "explicit" ? work : {
|
|
4982
|
-
...work,
|
|
4983
|
-
projectType
|
|
4984
|
-
})
|
|
4985
|
-
};
|
|
4986
|
-
}
|
|
4987
|
-
/** One rule for single and bulk choices; selection changes recompute reading completion. */
|
|
4988
|
-
function setWorksAccess(state, workIds, access, decision, accessChoice) {
|
|
4989
|
-
if (decision && access === "public") throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
4990
|
-
if (new Set(workIds).size !== workIds.length) throw new ApplicationError("INVALID_SELECTION");
|
|
4991
|
-
if (workIds.some((id) => !state.works.some((work) => work.id === id))) throw new ApplicationError("UNKNOWN_WORKSPACE");
|
|
4992
|
-
if (state.scope.status === "confirmed") return changeConfirmedAccess(state, workIds, access, decision, accessChoice);
|
|
4993
|
-
return withReadingProgress({
|
|
4994
|
-
...state,
|
|
4995
|
-
scope: {
|
|
4996
|
-
status: "pending",
|
|
4997
|
-
actor: null,
|
|
4998
|
-
at: null
|
|
4999
|
-
},
|
|
5000
|
-
privacy: {
|
|
5001
|
-
...state.privacy,
|
|
5002
|
-
cards: [],
|
|
5003
|
-
confirmedAnswersRevision: null,
|
|
5004
|
-
packageApproval: null
|
|
5005
|
-
},
|
|
5006
|
-
works: state.works.map((work) => !workIds.includes(work.id) ? work : {
|
|
5007
|
-
...work,
|
|
5008
|
-
revision: work.revision + 1,
|
|
5009
|
-
artifact: null,
|
|
5010
|
-
status: work.status === "ready" ? "review" : work.status,
|
|
5011
|
-
access,
|
|
5012
|
-
accessConfirmed: true,
|
|
5013
|
-
accountAccessConflict: false,
|
|
5014
|
-
accessChoice: access === "public" ? accessChoice : void 0,
|
|
5015
|
-
accessOrigin: decision && work.accessOrigin !== "explicit" ? "delegate" : "explicit"
|
|
5016
|
-
})
|
|
5017
|
-
});
|
|
5018
|
-
}
|
|
5019
|
-
/**
|
|
5020
|
-
* After confirmation the person, never a delegate, may still change any Work's visibility
|
|
5021
|
-
* (D2, superseding ADR 0162's lock and ADR 0188's blocked-only Drop). Drop stops that Work's
|
|
5022
|
-
* scan; a Work moved out of Drop is read again; reading progress is recomputed.
|
|
5023
|
-
*/
|
|
5024
|
-
function changeConfirmedAccess(state, workIds, access, decision, accessChoice) {
|
|
5025
|
-
if (decision || state.phase === "sending" || state.phase === "completed" || state.works.some((work) => workIds.includes(work.id) && work.access === access && work.accessConfirmed && hasPublicChoice(work))) throw new ApplicationError("INVALID_TRANSITION");
|
|
5026
|
-
if (access === "public" && state.works.every((work) => !workIds.includes(work.id) || work.access === "public" && work.accessConfirmed)) return {
|
|
5027
|
-
...state,
|
|
5028
|
-
works: state.works.map((work) => workIds.includes(work.id) && accessChoice ? {
|
|
5029
|
-
...work,
|
|
5030
|
-
accessOrigin: "explicit",
|
|
5031
|
-
accessChoice
|
|
5032
|
-
} : work)
|
|
5033
|
-
};
|
|
5034
|
-
return withReadingProgress({
|
|
5035
|
-
...state,
|
|
5036
|
-
privacy: {
|
|
5037
|
-
...state.privacy,
|
|
5038
|
-
packageApproval: null
|
|
5039
|
-
},
|
|
5040
|
-
works: state.works.map((work) => workIds.includes(work.id) ? {
|
|
5041
|
-
...work,
|
|
5042
|
-
revision: work.revision + 1,
|
|
5043
|
-
artifact: null,
|
|
5044
|
-
status: work.status === "ready" ? "review" : work.status,
|
|
5045
|
-
access,
|
|
5046
|
-
accessConfirmed: true,
|
|
5047
|
-
accountAccessConflict: false,
|
|
5048
|
-
accessChoice: access === "public" ? accessChoice : void 0,
|
|
5049
|
-
accessOrigin: "explicit"
|
|
5050
|
-
} : work)
|
|
5051
|
-
});
|
|
5052
|
-
}
|
|
5053
|
-
/** Model suggestions fill or refresh model types only; a person's or delegate's type stays. */
|
|
5054
|
-
function applyClassification(state, items) {
|
|
5055
|
-
if (state.scope.status === "confirmed") throw new ApplicationError("INVALID_TRANSITION");
|
|
5056
|
-
const byWork = new Map(items.map((item) => [item.workId, item]));
|
|
5057
|
-
if (byWork.size !== items.length) throw new ApplicationError("INVALID_SELECTION");
|
|
5058
|
-
if (items.some((item) => !state.works.some((work) => work.id === item.workId))) throw new ApplicationError("UNKNOWN_WORKSPACE");
|
|
5059
|
-
return {
|
|
5060
|
-
...state,
|
|
5061
|
-
works: state.works.map((work) => {
|
|
5062
|
-
const item = byWork.get(work.id);
|
|
5063
|
-
if (!item || work.projectType && work.projectType.origin !== "model") return work;
|
|
5064
|
-
return {
|
|
5065
|
-
...work,
|
|
5066
|
-
projectType: {
|
|
5067
|
-
type: item.type,
|
|
5068
|
-
reason: item.reason,
|
|
5069
|
-
origin: "model"
|
|
5070
|
-
}
|
|
5071
|
-
};
|
|
5072
|
-
})
|
|
5073
|
-
};
|
|
5074
|
-
}
|
|
5075
|
-
//#endregion
|
|
5076
5274
|
//#region packages/application/src/journey/scope/proposal.ts
|
|
5077
5275
|
const count$3 = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
5078
5276
|
const workspaces = z.array(z.string().min(1).max(128));
|
|
@@ -5095,6 +5293,10 @@ const classificationCommitSchema = z.object({
|
|
|
5095
5293
|
partition: journeyDigestSchema,
|
|
5096
5294
|
late: z.boolean(),
|
|
5097
5295
|
accountAccess: z.record(z.string(), accountWorkAccessSchema).optional(),
|
|
5296
|
+
/** Binds late account facts to the full read that remains current at commit. */
|
|
5297
|
+
accountSubject: z.string().min(1).max(128).optional(),
|
|
5298
|
+
/** A failed or mismatched late read (ADR 0277 section 5). */
|
|
5299
|
+
accountRead: z.enum(["signed-out", "unavailable"]).optional(),
|
|
5098
5300
|
items: z.array(journeyClassificationSubmitSchema.shape.items.element.extend({
|
|
5099
5301
|
workId: journeyIdSchema,
|
|
5100
5302
|
workspaceIds: workspaces
|
|
@@ -5238,7 +5440,12 @@ function regroupTerms(terms, works, changedMetadata, chunkSessions) {
|
|
|
5238
5440
|
function applyClassificationProposal(state, plan) {
|
|
5239
5441
|
const classifying = isClassifying(state);
|
|
5240
5442
|
if (!state.consent || state.journeyId !== plan.journeyId || (plan.expectedClassificationRevision === null ? state.revision !== plan.expectedRevision : !classifying || state.classificationRevision !== plan.expectedClassificationRevision) || state.snapshotId !== plan.snapshotId || state.scope.status === "confirmed" !== plan.late || plan.late && !classifying || state.phase === "paused" || state.pendingHideId || classificationPartition(state) !== plan.partition) return null;
|
|
5241
|
-
const
|
|
5443
|
+
const currentPlan = plan.accountAccess !== void 0 && (!plan.accountSubject || state.accountRead?.status !== "read" || state.accountRead.subject !== plan.accountSubject) ? {
|
|
5444
|
+
...plan,
|
|
5445
|
+
accountAccess: void 0,
|
|
5446
|
+
accountRead: "unavailable"
|
|
5447
|
+
} : plan;
|
|
5448
|
+
const { works, changedMetadata } = classificationWorks(state, currentPlan);
|
|
5242
5449
|
const retained = new Set(works.map((work) => work.id));
|
|
5243
5450
|
const regrouped = !plan.late && (state.works.some((work) => !retained.has(work.id)) || changedMetadata.size > 0);
|
|
5244
5451
|
const cardTerms = new Set(state.privacy.cards.flatMap((card) => card.termKeys));
|
|
@@ -5269,6 +5476,10 @@ function applyClassificationProposal(state, plan) {
|
|
|
5269
5476
|
confirmedAnswersRevision: null
|
|
5270
5477
|
} : {}
|
|
5271
5478
|
},
|
|
5479
|
+
...currentPlan.accountRead ? { accountRead: {
|
|
5480
|
+
status: currentPlan.accountRead,
|
|
5481
|
+
at: plan.at
|
|
5482
|
+
} } : {},
|
|
5272
5483
|
readingComplete: !plan.pending && works.filter((work) => work.access !== "drop").every((work) => work.totalBatches > 0 && work.completedBatches === work.totalBatches),
|
|
5273
5484
|
phase: "processing"
|
|
5274
5485
|
};
|
|
@@ -5334,44 +5545,6 @@ z.object({
|
|
|
5334
5545
|
groupId: journeyIdSchema
|
|
5335
5546
|
}).strict())
|
|
5336
5547
|
}).strict().nullable();
|
|
5337
|
-
z.object({
|
|
5338
|
-
journeyId: journeyIdSchema.optional(),
|
|
5339
|
-
workIds: z.array(journeyIdSchema).min(1).max(1e3).optional()
|
|
5340
|
-
}).strict();
|
|
5341
|
-
const categories = z.object({
|
|
5342
|
-
identity: z.number().int().nonnegative(),
|
|
5343
|
-
credentials: z.number().int().nonnegative(),
|
|
5344
|
-
internal: z.number().int().nonnegative(),
|
|
5345
|
-
private: z.number().int().nonnegative()
|
|
5346
|
-
}).strict();
|
|
5347
|
-
z.object({
|
|
5348
|
-
journeyId: journeyIdSchema,
|
|
5349
|
-
nonAdditiveMechanismCounts: z.literal(true),
|
|
5350
|
-
total: z.number().int().nonnegative(),
|
|
5351
|
-
categories,
|
|
5352
|
-
byMechanism: z.object({
|
|
5353
|
-
staticNodes: z.record(z.string(), z.number().int().nonnegative()),
|
|
5354
|
-
semantic: z.number().int().nonnegative(),
|
|
5355
|
-
correction: z.number().int().nonnegative()
|
|
5356
|
-
}).strict(),
|
|
5357
|
-
works: z.array(z.object({
|
|
5358
|
-
workId: journeyIdSchema,
|
|
5359
|
-
revision: z.number().int().positive(),
|
|
5360
|
-
inputRevision: z.number().int().positive(),
|
|
5361
|
-
primarySha256: z.string().length(64),
|
|
5362
|
-
companionSha256: z.string().length(64),
|
|
5363
|
-
ledgerSha256: z.string().length(64),
|
|
5364
|
-
decisionDigest: z.string().length(64),
|
|
5365
|
-
reviewInputDigest: z.string().length(64),
|
|
5366
|
-
total: z.number().int().nonnegative(),
|
|
5367
|
-
categories,
|
|
5368
|
-
byMechanism: z.object({
|
|
5369
|
-
staticNodes: z.record(z.string(), z.number().int().nonnegative()),
|
|
5370
|
-
semantic: z.number().int().nonnegative(),
|
|
5371
|
-
correction: z.number().int().nonnegative()
|
|
5372
|
-
}).strict()
|
|
5373
|
-
}).strict())
|
|
5374
|
-
}).strict();
|
|
5375
5548
|
//#endregion
|
|
5376
5549
|
//#region packages/application/src/journey/delegation.ts
|
|
5377
5550
|
function delegatedDecisionScope(action) {
|
|
@@ -5424,6 +5597,11 @@ function delegatedDecisionTargets(action, journeyId) {
|
|
|
5424
5597
|
targetIds: [journeyId],
|
|
5425
5598
|
choice: String(action.answersRevision)
|
|
5426
5599
|
};
|
|
5600
|
+
if (action.kind === "account-read") return {
|
|
5601
|
+
action: action.kind,
|
|
5602
|
+
targetIds: [journeyId],
|
|
5603
|
+
choice: "retry"
|
|
5604
|
+
};
|
|
5427
5605
|
return action.kind === "scope-confirm" ? {
|
|
5428
5606
|
action: action.kind,
|
|
5429
5607
|
targetIds: [journeyId],
|
|
@@ -5455,7 +5633,7 @@ function assertDelegatedDecision(state, action, decision, source) {
|
|
|
5455
5633
|
if (work?.projectType?.origin === "explicit" && work.projectType.type !== action.type) throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
5456
5634
|
return;
|
|
5457
5635
|
}
|
|
5458
|
-
if (action.kind === "scope-confirm") return;
|
|
5636
|
+
if (action.kind === "scope-confirm" || action.kind === "account-read") return;
|
|
5459
5637
|
if (action.kind !== "group" && action.kind !== "groups-hide") throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
5460
5638
|
const ids = action.kind === "group" ? [action.groupId] : action.groupIds;
|
|
5461
5639
|
const choice = action.kind === "group" ? action.choice : "hide";
|
|
@@ -5528,7 +5706,7 @@ function invalidateAnalysisIntent(state) {
|
|
|
5528
5706
|
...state,
|
|
5529
5707
|
works: state.works.map((work) => {
|
|
5530
5708
|
if (!work.rootSessionIds.length) return work;
|
|
5531
|
-
const { emptyReview: _empty, excludedReview: _excluded, ...retired } = retireWorkDescription(work);
|
|
5709
|
+
const { reviewInterruptions: _reviewInterruptions, emptyReview: _empty, excludedReview: _excluded, ...retired } = retireWorkDescription(work);
|
|
5532
5710
|
return {
|
|
5533
5711
|
...retired,
|
|
5534
5712
|
title: work.explicitTitleId ? work.title : retired.title,
|
|
@@ -5543,7 +5721,7 @@ function invalidateAnalysisIntent(state) {
|
|
|
5543
5721
|
})
|
|
5544
5722
|
});
|
|
5545
5723
|
}
|
|
5546
|
-
function respondToJourney(state, action, source, now, decision, preferenceScope,
|
|
5724
|
+
function respondToJourney(state, action, source, now, decision, preferenceScope, accountRead, account) {
|
|
5547
5725
|
if (preferenceScope && (decision || !["group", "groups-hide"].includes(action.kind))) throw new ApplicationError("INVALID_ARGUMENTS");
|
|
5548
5726
|
if (state.abandonedAt) throw new ApplicationError("INVALID_TRANSITION");
|
|
5549
5727
|
if (action.kind === "abandon") {
|
|
@@ -5597,12 +5775,14 @@ function respondToJourney(state, action, source, now, decision, preferenceScope,
|
|
|
5597
5775
|
...state,
|
|
5598
5776
|
phase: "welcome"
|
|
5599
5777
|
};
|
|
5778
|
+
if (account?.configured && !account.subject) throw new ApplicationError("JOURNEY_SIGN_IN_REQUIRED");
|
|
5600
5779
|
return {
|
|
5601
5780
|
...state,
|
|
5602
5781
|
consent: {
|
|
5603
5782
|
provider: state.provider,
|
|
5604
5783
|
source,
|
|
5605
|
-
allowedAt: now
|
|
5784
|
+
allowedAt: now,
|
|
5785
|
+
...account?.subject ? { subject: account.subject } : {}
|
|
5606
5786
|
},
|
|
5607
5787
|
phase: "discovering"
|
|
5608
5788
|
};
|
|
@@ -5657,7 +5837,14 @@ function respondToJourney(state, action, source, now, decision, preferenceScope,
|
|
|
5657
5837
|
};
|
|
5658
5838
|
if (action.kind === "scope-confirm") {
|
|
5659
5839
|
if (source !== "conversation" || decision) throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
5660
|
-
|
|
5840
|
+
const stop = accountReadStop(state, accountRead);
|
|
5841
|
+
if (stop) throw new ApplicationError(stop);
|
|
5842
|
+
const confirmed = confirmScope(state, now, void 0, accountRead?.status === "read" ? accountRead.access : void 0);
|
|
5843
|
+
return accountRead && confirmed !== state ? withAccountRead(confirmed, accountRead, now) : confirmed;
|
|
5844
|
+
}
|
|
5845
|
+
if (action.kind === "account-read") {
|
|
5846
|
+
if (!accountRead) throw new ApplicationError("INVALID_TRANSITION");
|
|
5847
|
+
return retryAccountRead(state, accountRead, now);
|
|
5661
5848
|
}
|
|
5662
5849
|
if (action.kind === "card-answer" || action.kind === "cards-confirm" || action.kind === "package-confirm") throw new ApplicationError("INVALID_TRANSITION");
|
|
5663
5850
|
if ((action.kind === "work-access" || action.kind === "works-access") && action.access === "public" && source === "conversation" && (!action.accessChoice || action.accessChoice.channel !== "conversation" || !Number.isFinite(Date.parse(action.accessChoice.at)))) throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
@@ -5678,6 +5865,48 @@ function respondToJourney(state, action, source, now, decision, preferenceScope,
|
|
|
5678
5865
|
const workId = action.workId;
|
|
5679
5866
|
if (!state.works.some((work) => work.id === workId)) throw new ApplicationError("UNKNOWN_WORKSPACE");
|
|
5680
5867
|
if (action.kind === "work-type") return setWorkType(state, workId, action.type, decision);
|
|
5868
|
+
if (action.kind === "review-interrupted" || action.kind === "review-reader-closed" || action.kind === "retry-review") {
|
|
5869
|
+
const work = state.works.find((item) => item.id === workId);
|
|
5870
|
+
if (!work || work.inputRevision !== action.inputRevision) throw new ApplicationError("REVISION_CONFLICT");
|
|
5871
|
+
if (!isSelectedWork(work) || work.status === "blocked" || state.scope.status !== "confirmed" || state.phase === "sending" || state.phase === "completed") throw new ApplicationError("INVALID_TRANSITION");
|
|
5872
|
+
if (action.kind === "review-reader-closed") {
|
|
5873
|
+
if (source !== "conversation" || decision) throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
5874
|
+
return state;
|
|
5875
|
+
}
|
|
5876
|
+
const holds = currentReviewInterruptions(work);
|
|
5877
|
+
const previous = holds.find((item) => item.packId === action.packId);
|
|
5878
|
+
let nextHolds;
|
|
5879
|
+
if (action.kind === "review-interrupted") {
|
|
5880
|
+
if (source !== "conversation" || decision) throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
5881
|
+
if (previous) {
|
|
5882
|
+
if (previous.reason !== action.reason || previous.batchIds.length !== action.batchIds.length || !action.batchIds.every((id) => previous.batchIds.includes(id))) throw new ApplicationError("OPERATION_CONFLICT");
|
|
5883
|
+
return state;
|
|
5884
|
+
}
|
|
5885
|
+
if (work.totalBatches - work.completedBatches < action.batchIds.length || holds.some((hold) => hold.batchIds.some((id) => action.batchIds.includes(id)))) throw new ApplicationError("INVALID_TRANSITION");
|
|
5886
|
+
nextHolds = [...holds, {
|
|
5887
|
+
packId: action.packId,
|
|
5888
|
+
inputRevision: action.inputRevision,
|
|
5889
|
+
batchIds: action.batchIds,
|
|
5890
|
+
reason: action.reason
|
|
5891
|
+
}];
|
|
5892
|
+
} else {
|
|
5893
|
+
if (previous?.reason !== "retry-exhausted") throw new ApplicationError("INVALID_TRANSITION");
|
|
5894
|
+
nextHolds = holds.filter((item) => item.packId !== action.packId);
|
|
5895
|
+
}
|
|
5896
|
+
return withReadingProgress({
|
|
5897
|
+
...state,
|
|
5898
|
+
privacy: {
|
|
5899
|
+
...state.privacy,
|
|
5900
|
+
packageApproval: null
|
|
5901
|
+
},
|
|
5902
|
+
works: state.works.map((item) => item.id === workId ? {
|
|
5903
|
+
...item,
|
|
5904
|
+
reviewInterruptions: nextHolds,
|
|
5905
|
+
revision: item.revision + 1,
|
|
5906
|
+
artifact: null
|
|
5907
|
+
} : item)
|
|
5908
|
+
});
|
|
5909
|
+
}
|
|
5681
5910
|
if (action.kind === "retry-work") {
|
|
5682
5911
|
if (state.works.find((item) => item.id === workId)?.status !== "blocked" || state.phase === "sending" || state.phase === "completed") throw new ApplicationError("INVALID_TRANSITION");
|
|
5683
5912
|
return withReadingProgress({
|
|
@@ -6429,10 +6658,19 @@ function digest$1(value) {
|
|
|
6429
6658
|
* quotes stay out. The agent reads override values from `journey.get` and the guidance.
|
|
6430
6659
|
*/
|
|
6431
6660
|
function journeyView(state, now) {
|
|
6432
|
-
const { exceptions, instructions, corrections, valueOverrides, ...safe } = state;
|
|
6661
|
+
const { exceptions, instructions, corrections, valueOverrides, consent, accountRead, ...safe } = state;
|
|
6433
6662
|
const terms = activePrivacyTerms(state);
|
|
6434
6663
|
return {
|
|
6435
6664
|
...safe,
|
|
6665
|
+
consent: consent && {
|
|
6666
|
+
provider: consent.provider,
|
|
6667
|
+
allowedAt: consent.allowedAt,
|
|
6668
|
+
source: consent.source
|
|
6669
|
+
},
|
|
6670
|
+
...accountRead ? { accountRead: {
|
|
6671
|
+
status: accountRead.status,
|
|
6672
|
+
at: accountRead.at
|
|
6673
|
+
} } : {},
|
|
6436
6674
|
displayedScopeDigest: displayedScopeDigest(state),
|
|
6437
6675
|
privacy: {
|
|
6438
6676
|
...state.privacy,
|
|
@@ -6441,10 +6679,12 @@ function journeyView(state, now) {
|
|
|
6441
6679
|
works: state.works.map((work) => {
|
|
6442
6680
|
const localTitle = work.suggestion || work.explicitTitleId ? work.title : work.classificationMetadata?.title ?? work.title;
|
|
6443
6681
|
const imagesOmitted = hasCurrentStaticReview(state, work) ? work.staticReview?.imagesOmitted ?? 0 : 0;
|
|
6682
|
+
const source = publicSource(work);
|
|
6444
6683
|
if (!work.classificationMetadata) return {
|
|
6445
6684
|
...work,
|
|
6446
6685
|
localTitle,
|
|
6447
|
-
imagesOmitted
|
|
6686
|
+
imagesOmitted,
|
|
6687
|
+
publicSource: source
|
|
6448
6688
|
};
|
|
6449
6689
|
const metadata = work.classificationMetadata;
|
|
6450
6690
|
const displayed = {
|
|
@@ -6458,6 +6698,7 @@ function journeyView(state, now) {
|
|
|
6458
6698
|
...displayed,
|
|
6459
6699
|
localTitle,
|
|
6460
6700
|
imagesOmitted,
|
|
6701
|
+
publicSource: source,
|
|
6461
6702
|
projectType: work.projectType ? {
|
|
6462
6703
|
...work.projectType,
|
|
6463
6704
|
reason: work.projectType.origin === "model" ? displayed.reason : work.projectType.reason
|
|
@@ -6576,12 +6817,14 @@ function createNativeJourney(storage, now = () => (/* @__PURE__ */ new Date()).t
|
|
|
6576
6817
|
status: state.phase === "paused" && classificationProgress.status === "running" ? "paused" : classificationProgress.status
|
|
6577
6818
|
} : null;
|
|
6578
6819
|
const packaging = await local?.renderActivity?.read(state.journeyId) ?? null;
|
|
6820
|
+
const subject = state.accountRead?.status === "read" ? await local?.accountState?.().then((account) => account.subject).catch(() => null) : null;
|
|
6579
6821
|
return {
|
|
6580
6822
|
...view,
|
|
6581
6823
|
...reads,
|
|
6582
6824
|
packaging,
|
|
6583
6825
|
preparationProgress,
|
|
6584
|
-
classificationProgress: liveClassificationProgress
|
|
6826
|
+
classificationProgress: liveClassificationProgress,
|
|
6827
|
+
accountReadCurrent: state.accountRead?.status === "read" && typeof subject === "string" && state.accountRead.subject === subject
|
|
6585
6828
|
};
|
|
6586
6829
|
}
|
|
6587
6830
|
}
|
|
@@ -6815,19 +7058,50 @@ function createNativeJourney(storage, now = () => (/* @__PURE__ */ new Date()).t
|
|
|
6815
7058
|
"works-access",
|
|
6816
7059
|
"card-answer",
|
|
6817
7060
|
"profile-address",
|
|
6818
|
-
"profile-links"
|
|
7061
|
+
"profile-links",
|
|
7062
|
+
"account-read"
|
|
6819
7063
|
].includes(input.action.kind);
|
|
7064
|
+
let accountRead;
|
|
7065
|
+
const readAccount = async (state) => {
|
|
7066
|
+
const scope = digest$1(state.works.map((work) => ({
|
|
7067
|
+
id: work.id,
|
|
7068
|
+
roots: work.rootSessionIds.toSorted()
|
|
7069
|
+
})).sort((left, right) => left.id.localeCompare(right.id)));
|
|
7070
|
+
if (!accountRead || accountRead.scope !== scope) accountRead = {
|
|
7071
|
+
scope,
|
|
7072
|
+
result: await local?.accountAccess?.(state, new AbortController().signal)
|
|
7073
|
+
};
|
|
7074
|
+
return accountRead.result;
|
|
7075
|
+
};
|
|
6820
7076
|
for (let attempt = 0;; attempt++) {
|
|
6821
|
-
const result = await commitResponse(input, source, rebase);
|
|
7077
|
+
const result = await commitResponse(input, source, rebase, readAccount);
|
|
6822
7078
|
if (result.status !== "stale" || !rebase || attempt >= 4) return result;
|
|
6823
7079
|
}
|
|
6824
7080
|
}
|
|
6825
|
-
async function commitResponse(input, source, rebase) {
|
|
7081
|
+
async function commitResponse(input, source, rebase, readAccount) {
|
|
6826
7082
|
const current = await storage.current(input.journeyId);
|
|
6827
7083
|
if (!current) throw new ApplicationError("UNKNOWN_ITEM");
|
|
6828
7084
|
const expectedRevision = rebase ? current.revision : input.expectedRevision;
|
|
6829
|
-
const
|
|
6830
|
-
const
|
|
7085
|
+
const scopeRead = input.action.kind === "scope-confirm" && current.scope.status !== "confirmed" && source === "conversation" && !input.decision;
|
|
7086
|
+
const accountRead = current.revision === expectedRevision && (scopeRead || input.action.kind === "account-read") ? await readAccount(current) : void 0;
|
|
7087
|
+
const account = current.revision === expectedRevision && (input.action.kind === "consent" && input.action.allow && !current.consent || scopeRead) ? await local?.accountState?.() : void 0;
|
|
7088
|
+
const stop = scopeRead && accountReadStop(current, accountRead);
|
|
7089
|
+
if (stop && accountRead) {
|
|
7090
|
+
await storage.commit({
|
|
7091
|
+
expectedRevision,
|
|
7092
|
+
requestKey: randomUUID(),
|
|
7093
|
+
requestHash: digest$1(input),
|
|
7094
|
+
state: withAccountRead(current, accountRead, now()),
|
|
7095
|
+
event: {
|
|
7096
|
+
journeyId: current.journeyId,
|
|
7097
|
+
kind: "response",
|
|
7098
|
+
source,
|
|
7099
|
+
at: now()
|
|
7100
|
+
}
|
|
7101
|
+
});
|
|
7102
|
+
throw new ApplicationError(stop);
|
|
7103
|
+
}
|
|
7104
|
+
const state = current.revision === expectedRevision ? respondToJourney(current, input.action, source, now(), input.decision, input.preferenceScope, accountRead, account) : current;
|
|
6831
7105
|
const groupIds = input.action.kind === "group" ? [input.action.groupId] : input.action.kind === "groups-hide" ? input.action.groupIds : [];
|
|
6832
7106
|
const preferenceScopes = input.decision ? [] : state.groups.flatMap((group) => groupIds.includes(group.id) && group.preferenceScope ? [{
|
|
6833
7107
|
groupId: group.id,
|
|
@@ -6838,6 +7112,12 @@ function createNativeJourney(storage, now = () => (/* @__PURE__ */ new Date()).t
|
|
|
6838
7112
|
requestKey: input.idempotencyKey,
|
|
6839
7113
|
requestHash: digest$1(input),
|
|
6840
7114
|
state,
|
|
7115
|
+
...input.action.kind === "review-interrupted" || input.action.kind === "review-reader-closed" ? { closedReader: {
|
|
7116
|
+
packId: input.action.packId,
|
|
7117
|
+
workId: input.action.workId,
|
|
7118
|
+
inputRevision: input.action.inputRevision,
|
|
7119
|
+
batchIds: input.action.batchIds
|
|
7120
|
+
} } : {},
|
|
6841
7121
|
event: {
|
|
6842
7122
|
journeyId: state.journeyId,
|
|
6843
7123
|
kind: "response",
|
|
@@ -6867,12 +7147,27 @@ function createNativeJourney(storage, now = () => (/* @__PURE__ */ new Date()).t
|
|
|
6867
7147
|
await setTimeout$1(Math.min(1e3, Math.max(0, deadline - Date.now())), void 0, { signal });
|
|
6868
7148
|
}
|
|
6869
7149
|
}
|
|
6870
|
-
async function
|
|
7150
|
+
async function runPreparation(value, signal) {
|
|
6871
7151
|
const input = nativeGetSchema.required().parse(value);
|
|
6872
7152
|
if (!local) throw new ApplicationError("INVALID_TRANSITION");
|
|
6873
|
-
await
|
|
6874
|
-
|
|
6875
|
-
|
|
7153
|
+
const permission = await storage.preparationPermission(input.journeyId);
|
|
7154
|
+
if (!permission) throw new ApplicationError("UNKNOWN_ITEM");
|
|
7155
|
+
if (permission.pendingHideId) await local.hide.recover(input.journeyId, signal ?? new AbortController().signal);
|
|
7156
|
+
return local.prepare(input.journeyId, signal);
|
|
7157
|
+
}
|
|
7158
|
+
async function prepare(value, signal) {
|
|
7159
|
+
const state = await runPreparation(value, signal);
|
|
7160
|
+
return state ? get({ journeyId: state.journeyId }) : null;
|
|
7161
|
+
}
|
|
7162
|
+
/** The owned CLI scan needs an exit receipt; the panel owns the full presentation read. */
|
|
7163
|
+
async function prepareReceipt(value, signal) {
|
|
7164
|
+
const state = await runPreparation(value, signal);
|
|
7165
|
+
return state && {
|
|
7166
|
+
journeyId: state.journeyId,
|
|
7167
|
+
revision: state.revision,
|
|
7168
|
+
phase: state.phase,
|
|
7169
|
+
readingComplete: state.readingComplete
|
|
7170
|
+
};
|
|
6876
7171
|
}
|
|
6877
7172
|
async function read(value) {
|
|
6878
7173
|
return storage.readBatch(journeyBatchReadSchema.parse(value));
|
|
@@ -7168,8 +7463,10 @@ function createNativeJourney(storage, now = () => (/* @__PURE__ */ new Date()).t
|
|
|
7168
7463
|
events,
|
|
7169
7464
|
groupItems: (value) => readGroupItems(storage, value),
|
|
7170
7465
|
/** ADR 0086: one pack of read units and the values found in them. */
|
|
7466
|
+
reopenEmpty: (value) => storage.reopenEmpty(emptyReviewCorrectionSchema.parse(value)),
|
|
7171
7467
|
terms: async (value) => reviewTermsResultSchema.parse(await storage.recordTerms(reviewTermsInputSchema.parse(value))),
|
|
7172
7468
|
prepare,
|
|
7469
|
+
prepareReceipt,
|
|
7173
7470
|
read,
|
|
7174
7471
|
guidance,
|
|
7175
7472
|
inspect,
|
|
@@ -10813,7 +11110,7 @@ const bindingFields = {
|
|
|
10813
11110
|
};
|
|
10814
11111
|
const inspectionBindingSchema = z.union([z.object({
|
|
10815
11112
|
...bindingFields,
|
|
10816
|
-
detectorVersion: z.
|
|
11113
|
+
detectorVersion: z.enum([currentDetectorVersion, "static-catalog-11"]),
|
|
10817
11114
|
dictionaryVersion: z.literal(entityDictionaryVersion)
|
|
10818
11115
|
}).strict(), z.object({
|
|
10819
11116
|
...bindingFields,
|
|
@@ -13651,7 +13948,6 @@ z.object({
|
|
|
13651
13948
|
}).strict();
|
|
13652
13949
|
//#endregion
|
|
13653
13950
|
//#region packages/application/src/journey/scan-equivalence.ts
|
|
13654
|
-
/** State authority relevant to AI meaning; unrelated Work progress does not invalidate publication. */
|
|
13655
13951
|
function scanBatchContextDigest(state, workId) {
|
|
13656
13952
|
const work = state.works.find((work) => work.id === workId);
|
|
13657
13953
|
if (!work) throw new ApplicationError("REVISION_CONFLICT");
|
|
@@ -13754,4 +14050,4 @@ function equivalenceDuplicates(batch) {
|
|
|
13754
14050
|
return bindings.sort();
|
|
13755
14051
|
}
|
|
13756
14052
|
//#endregion
|
|
13757
|
-
export { validateDetectionSummary as $,
|
|
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 };
|