@gitroll/tacitt-cli 0.6.10 → 0.6.12
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 +47 -4
- package/bin/INDEX.md +6 -4
- package/bin/tacitt.cjs +1 -0
- package/dist/{agent-setup-LPd8WEsN.mjs → agent-setup-B12VbvN_.mjs} +2 -2
- package/dist/agent-setup-worker.mjs +5 -5
- package/dist/cli.mjs +39 -25
- package/dist/codex-title-worker.mjs +2 -2
- package/dist/configured-preview-worker.mjs +5 -5
- package/dist/configured-session-worker.mjs +27 -27
- package/dist/{frame-ByT1xayJ.mjs → frame-B3He21G8.mjs} +1 -1
- package/dist/{heap-CM298Yu0.mjs → heap-Blrat--4.mjs} +469 -30
- package/dist/inspection-worker.mjs +4 -4
- package/dist/{job-BHIwElNF.mjs → job-CBuIMTQ0.mjs} +57 -22
- package/dist/{locate-Cwv6WH8n.mjs → locate-D-ZaHpI0.mjs} +184 -18
- package/dist/{model-zvn5GUJ9.mjs → model-DR9odhWP.mjs} +1 -1
- package/dist/operation-executor.mjs +3 -3
- package/dist/preview-worker.mjs +1 -1
- package/dist/privacy-validator.mjs +49 -2
- package/dist/{query-By41gaKm.mjs → query-BYDcNKy2.mjs} +1869 -716
- package/dist/{run-ZQx7p4Rw.mjs → run-DQ9xyKSt.mjs} +4 -4
- package/dist/session-process.mjs +20 -0
- package/dist/{src-D_DfY--2.mjs → src-CKQlTA2Z.mjs} +297 -207
- package/dist/{src-3jvMvSQn.mjs → src-DeNJ8vOD.mjs} +535 -140
- package/dist/{src-BqtdFVq7.mjs → src-Do-qzuMn.mjs} +2329 -1786
- package/dist/store-database-worker.mjs +1 -1
- package/dist/web/assets/index-BgDdPL1c.js +172 -0
- package/dist/web/assets/index-DzOozqms.css +1 -0
- package/dist/web/index.html +3 -2
- package/package.json +2 -1
- package/skills/INDEX.md +6 -4
- package/skills/tacitt/INDEX.md +7 -6
- package/skills/tacitt/SKILL.md +11 -11
- package/skills/tacitt/references/INDEX.md +14 -11
- package/skills/tacitt/references/b-method.md +4 -4
- package/skills/tacitt/references/bootstrap.md +19 -5
- package/skills/tacitt/references/classification.md +4 -4
- package/skills/tacitt/references/commands.md +51 -34
- package/skills/tacitt/references/journey.md +7 -7
- package/skills/tacitt/references/parallel-review.md +35 -35
- package/skills/tacitt/references/send.md +15 -6
- package/skills/tacitt/scripts/INDEX.md +40 -37
- package/skills/tacitt/scripts/answer.mjs +91 -15
- package/skills/tacitt/scripts/journey-card-consolidation.mjs +1 -1
- 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-tasks.mjs +28 -8
- package/skills/tacitt/scripts/journey-classification.mjs +6 -1
- package/skills/tacitt/scripts/journey-consolidation.mjs +1 -0
- package/skills/tacitt/scripts/journey-panel-host.d.mts +2 -0
- package/skills/tacitt/scripts/journey-panel-host.mjs +25 -2
- package/skills/tacitt/scripts/journey-scan-timing.d.mts +5 -0
- package/skills/tacitt/scripts/journey-scan-timing.mjs +31 -4
- package/skills/tacitt/scripts/journey-state.d.mts +9 -1
- package/skills/tacitt/scripts/journey-state.mjs +95 -8
- package/skills/tacitt/scripts/journey-tasks.d.mts +1 -0
- package/skills/tacitt/scripts/journey-tasks.mjs +24 -9
- package/skills/tacitt/scripts/journey.d.mts +44 -31
- package/skills/tacitt/scripts/journey.mjs +34 -7
- package/skills/tacitt/scripts/privacy-validator.mjs +188 -11
- package/skills/tacitt/scripts/review.d.mts +7 -6
- package/skills/tacitt/scripts/review.mjs +922 -205
- package/dist/web/assets/index-B14e6_-z.js +0 -174
- package/dist/web/assets/index-CYJBq1px.css +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { $ as validateDetectionSummary, $t as
|
|
1
|
+
import { C as writePrivateReviewPayload, ct as windowsOwnerOnly, it as exclusiveLock, ot as openPrivateFile, st as restrictToOwner, w as readPrivateReviewPayload } from "./src-DeNJ8vOD.mjs";
|
|
2
|
+
import { An as createDetectionSummary, Ft as emptyReviewCorrectionSchema, Ht as termTypeSchema, It as journeyTermSchema, Lt as reviewTermsInputSchema, Nt as privacyTermSchema, Pt as emptyReviewCorrectionResultSchema, Rt as reviewTermsResultSchema, S as validateBReview, a as reviewBatchChunks, b as bContextEvidence, bt as journeyWorkSuggestionSchema, c as reviewDigest, cn as reviewFamilies, d as validateReview, f as findingReferenceKey, g as reviewAlignmentSchema, gt as journeyGuidanceSchema, l as reviewSubmitSchema, lt as journeyIdSchema, mt as nativeJourneySchema, nn as ApplicationError, o as reviewBatchSchema, or as isStaticCategory, p as reviewAlignmentAudit, pn as fileIdentitySchema, sn as familySelected, sr as staticCategories, st as journeyDigestSchema, u as textDigest, v as validateReviewAlignment, y as semanticFindingSchema, yt as journeyDescribeSchema } from "./locate-D-ZaHpI0.mjs";
|
|
3
|
+
import { $ as validateDetectionSummary, $t as journeyPlaceSchema, Ai as stateSchema, At as validateAttemptAppend, Bn as journeySearchSaveSchema, Bt as newJourneyWork, Ci as withReadingProgress, Cn as scanAliasResourceSchema, Cr as preparationStateSchema, Dn as scanGitSeedsId, En as scanSessionResourceSchema, Fn as journeyInventoryReadSchema, Fr as assertJourneyPrivacyReady, Ft as classificationCommitSchema, Gt as hideFinishSchema, Hn as journeyCommitResultSchema, Ht as journeyContextQuerySchema, In as journeyInventorySchema, J as validateInspectionFence, Jr as obsoleteJourney, Jt as sendOutputQuerySchema, Kn as confirmedHideDeltaSchema, Kt as hidePublishSchema, Ln as journeyPreparationProgressSchema, Lr as reconcileReaderKeeps, Mt as journeyBatchReadSchema, Nn as projectPreparationProgress, Nt as journeyReviewStorageQuerySchema, Ot as settleCorrections, Pr as applyCardAnswers, Pt as applyClassificationProposal, Q as validateInspectionContribution, Qt as journeyIsolateSchema, Sr as preparationStartSchema, Tr as validatePreparationFacts, U as validateInspectionBatch, Un as journeyCommitSchema, Ut as journeyContextSchema, Vn as searchScopeDigest, W as validateInspectionBatches, Wn as journeyEventSchema, Wt as hideBeginSchema, X as validateInspectionInputs, Xr as sendReachedPassport, Xt as journeyResourceQuerySchema, Y as validateInspectionHead, Yn as settleConfirmedHides, Yr as sendOwesPassport, Yt as sendOutputSchema, Z as validateInspectionQuery, Zt as journeyResourceSchema, an as sessionReconcileResultSchema, bi as isSkippedWork, bn as bReviewDigest, br as preparationPermissionSchema, c as reconstructBFields, ci as journeySendSchema, cn as sessionRereadResourceSchema, d as resolveGroupSplit, dn as sessionReviewResourceSchema, ei as mergePrivacyTerms, en as sortingApplySchema, et as inspectionInputs, fn as sessionReviewStatusSchema, fr as maskedHitsStale, g as semanticGroupKey, gn as reviewCandidateResourceSchema, gr as workPrivacyDecisions, gt as manualUploadSchema, h as hideScopeDigest, hi as hasCompleteWorkDescription, hr as workDecisionDigest, jn as validateScanExclusion, jt as journeyGuidance, kt as attemptBudgetCodePoints, l as chooseCheapPlacement, lr as confirmedHideTerms, mn as sessionUsefulnessResourceSchema, mr as retireWorkDescription, nn as reviewAttemptsQuerySchema, o as bRereadDigest, on as sessionReconciliationResourceSchema, or as confirmedHideGroupIds, pi as sendSessionUploadSchema, pn as sessionUsefulnessInputSchema, pr as syncTermGroups, pt as manualUploadBindingSchema, q as validateInspectionClaim, qt as journeyPolicyRespondSchema, ri as staticSessionId, rn as reviewAttemptsResultSchema, rr as bMaskedHits, s as reconcileBSession, sr as confirmedHideKey, t as scanBatchContextDigest, tn as reviewAttemptResourceSchema, u as reviseGroupMembership, un as sessionReviewQuerySchema, ur as dictionaryHides, v as projectJourneyInventory, vi as isReadingComplete, vn as reviewRepairResourceSchema, vt as candidateSortAvailable, wn as scanCanonicalResourceSchema, wr as preparationUpdateSchema, x as bReviewScope, xi as selectedWorks, xn as mergeBRepair, xr as preparationSettlementSchema, y as preparationScope, yi as isSelectedWork, yr as preparationBeginSchema, zn as journeySearchResultSchema, zt as isolatedWorkId } from "./src-Do-qzuMn.mjs";
|
|
4
4
|
import { constants } from "node:fs";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { copyFile, link, lstat, mkdir, mkdtemp, open, readFile, realpath, rm, unlink } from "node:fs/promises";
|
|
@@ -346,9 +346,26 @@ function readJourney(db, journeyId) {
|
|
|
346
346
|
if (typeof text !== "string") throw new ApplicationError("CORRUPTED_STATE");
|
|
347
347
|
const state = JSON.parse(text);
|
|
348
348
|
if (typeof state === "object" && state !== null && "formatVersion" in state && state.formatVersion !== 10) throw new StoreError("OBSOLETE_SCHEMA", "Stored native Journey state uses an obsolete candidate format and has been preserved.");
|
|
349
|
+
let value = reconcileReaderKeeps(nativeJourneySchema.parse(state));
|
|
350
|
+
if (value.outputVersion !== void 0 && value.outputVersion > 1) throw new StoreError("OBSOLETE_SCHEMA", "This journey requires a newer final writer.");
|
|
351
|
+
if (value.outputVersion !== 1 && value.phase !== "completed" && !obsoleteJourney(value)) value = {
|
|
352
|
+
...value,
|
|
353
|
+
outputVersion: 1,
|
|
354
|
+
phase: value.phase === "ready" ? "review" : value.phase,
|
|
355
|
+
privacy: {
|
|
356
|
+
...value.privacy,
|
|
357
|
+
packageApproval: null
|
|
358
|
+
},
|
|
359
|
+
works: value.works.map((work) => ({
|
|
360
|
+
...work,
|
|
361
|
+
revision: work.revision + 1,
|
|
362
|
+
status: work.status === "ready" ? "review" : work.status,
|
|
363
|
+
artifact: null
|
|
364
|
+
}))
|
|
365
|
+
};
|
|
349
366
|
return {
|
|
350
367
|
text,
|
|
351
|
-
value
|
|
368
|
+
value
|
|
352
369
|
};
|
|
353
370
|
});
|
|
354
371
|
}
|
|
@@ -362,23 +379,29 @@ function insertCorrectionReport(db, resource) {
|
|
|
362
379
|
}
|
|
363
380
|
db.prepare("INSERT INTO journey_resources VALUES(?,?,?,?)").run(resource.journeyId, resource.kind, resource.id, json);
|
|
364
381
|
}
|
|
365
|
-
/**
|
|
366
|
-
* A snapshot of an earlier release cannot render, so a new Journey may replace it, but never
|
|
367
|
-
* one with a Send record or a sent Work: that Send stays for the person to finish.
|
|
368
|
-
*/
|
|
369
|
-
/** A receipt or persisted session mutation intent means a Send may have reached Passport. */
|
|
382
|
+
/** Whether any Send of this Journey may have reached Passport (`sendReachedPassport`). */
|
|
370
383
|
function reachedPassport(db, journeyId) {
|
|
371
384
|
return db.prepare("SELECT json FROM journey_sends WHERE journey_id=?").all(journeyId).some((row) => {
|
|
372
385
|
if (typeof row["json"] !== "string") throw new ApplicationError("CORRUPTED_STATE");
|
|
373
|
-
return journeySendSchema.parse(JSON.parse(row["json"]))
|
|
386
|
+
return sendReachedPassport(journeySendSchema.parse(JSON.parse(row["json"])));
|
|
374
387
|
});
|
|
375
388
|
}
|
|
376
389
|
/** An abandoned Journey gives way to a new one unless one of its Sends reached Passport. */
|
|
377
390
|
function abandoned(db, current) {
|
|
378
391
|
return current.abandonedAt !== void 0 && !reachedPassport(db, current.journeyId);
|
|
379
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* A snapshot of an earlier release cannot render, so a new Journey replaces it unless its current
|
|
395
|
+
* Send still owes Passport something its next advance sends without the person
|
|
396
|
+
* (`sendOwesPassport`, ADR 0284, owner answer A): sent Works stay on Passport, and the new Journey
|
|
397
|
+
* reads every Work again. Replacement cancels its unfinished Sends.
|
|
398
|
+
*/
|
|
380
399
|
function replaceable(db, current) {
|
|
381
|
-
|
|
400
|
+
if (!obsoleteJourney(current)) return false;
|
|
401
|
+
const row = db.prepare("SELECT json FROM journey_sends JOIN journey_current_send ON journey_sends.id=journey_current_send.send_id WHERE journey_current_send.journey_id=?").get(current.journeyId);
|
|
402
|
+
if (!row) return true;
|
|
403
|
+
if (typeof row["json"] !== "string") throw new ApplicationError("CORRUPTED_STATE");
|
|
404
|
+
return !sendOwesPassport(current, journeySendSchema.parse(JSON.parse(row["json"])));
|
|
382
405
|
}
|
|
383
406
|
/** An addition counts only when one of its findings was assigned to a confirmed Hide category. */
|
|
384
407
|
function joinedHideValues(db, state, delta) {
|
|
@@ -439,6 +462,7 @@ function commitJourney(db, input) {
|
|
|
439
462
|
...input,
|
|
440
463
|
state: {
|
|
441
464
|
...input.state,
|
|
465
|
+
outputVersion: 1,
|
|
442
466
|
privacy: {
|
|
443
467
|
...input.state.privacy,
|
|
444
468
|
rememberedRules
|
|
@@ -453,7 +477,25 @@ function commitJourney(db, input) {
|
|
|
453
477
|
state: current
|
|
454
478
|
};
|
|
455
479
|
}
|
|
480
|
+
if (input.expectedRevision === null && current && replaceable(db, current)) db.prepare("DELETE FROM journey_current_send WHERE journey_id=?").run(current.journeyId);
|
|
456
481
|
if (input.state.journeyId !== input.event.journeyId) throw new ApplicationError("INVALID_ARGUMENTS");
|
|
482
|
+
if (input.closedReader) {
|
|
483
|
+
const hold = input.closedReader;
|
|
484
|
+
const unread = db.prepare(`SELECT 1 FROM journey_batches
|
|
485
|
+
WHERE id=? AND journey_id=? AND work_id=? AND input_revision=? AND review_ref_json IS NULL`);
|
|
486
|
+
if (hold.batchIds.some((id) => !unread.get(id, input.state.journeyId, hold.workId, hold.inputRevision))) throw new ApplicationError("REVISION_CONFLICT");
|
|
487
|
+
const save = db.prepare("INSERT OR IGNORE INTO journey_resources VALUES(?,?,?,?)");
|
|
488
|
+
for (const batchId of hold.batchIds) {
|
|
489
|
+
const fence = {
|
|
490
|
+
kind: "closed-reader",
|
|
491
|
+
journeyId: input.state.journeyId,
|
|
492
|
+
id: batchId + ":" + hold.packId,
|
|
493
|
+
packId: hold.packId,
|
|
494
|
+
batchId
|
|
495
|
+
};
|
|
496
|
+
save.run(fence.journeyId, fence.kind, fence.id, JSON.stringify(fence));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
457
499
|
if (input.state.abandonedAt && current?.journeyId === input.state.journeyId && !current.abandonedAt) {
|
|
458
500
|
if (reachedPassport(db, current.journeyId)) throw new ApplicationError("INVALID_TRANSITION");
|
|
459
501
|
db.prepare("DELETE FROM journey_current_send WHERE journey_id=?").run(current.journeyId);
|
|
@@ -2265,6 +2307,14 @@ function putSessionUsefulness(db, value) {
|
|
|
2265
2307
|
const privateReviewHash = (value) => textDigest(JSON.stringify(value));
|
|
2266
2308
|
//#endregion
|
|
2267
2309
|
//#region packages/local-host/src/store/journey/terms/aliases.ts
|
|
2310
|
+
function scanAliasRequestKey(alias, requestKey) {
|
|
2311
|
+
const digest = textDigest(JSON.stringify([
|
|
2312
|
+
"scan-alias-v1",
|
|
2313
|
+
alias,
|
|
2314
|
+
requestKey
|
|
2315
|
+
]));
|
|
2316
|
+
return `${digest.slice(0, 8)}-${digest.slice(8, 12)}-4${digest.slice(13, 16)}-8${digest.slice(17, 20)}-${digest.slice(20, 32)}`;
|
|
2317
|
+
}
|
|
2268
2318
|
const scanAliasQuerySchema = z.object({ journeyId: z.uuid() }).strict();
|
|
2269
2319
|
const scanAliasSelectionSchema = z.array(z.object({
|
|
2270
2320
|
alias: scanAliasResourceSchema,
|
|
@@ -2288,7 +2338,8 @@ function selectScanAliases(db, input) {
|
|
|
2288
2338
|
requireActiveBatch(state, canonical.header);
|
|
2289
2339
|
if (canonical.header.digest !== alias.canonicalBatchDigest || target.header.digest !== alias.targetBatchDigest || canonical.header.workId !== target.header.workId || target.header.id !== alias.targetBatchId) throw new ApplicationError("CORRUPTED_STATE");
|
|
2290
2340
|
if (!canonical.termPack) return [];
|
|
2291
|
-
const packs = db.prepare(`SELECT p.payload_ref_json,p.request_hash,r.request_hash AS receipt_hash FROM journey_term_pack_batches m JOIN journey_term_packs p ON p.request_key=m.request_key LEFT JOIN journey_receipts r ON r.request_key=p.request_key AND r.journey_id=p.journey_id WHERE m.batch_id=? AND p.journey_id=?
|
|
2341
|
+
const packs = db.prepare(`SELECT p.payload_ref_json,p.request_hash,r.request_hash AS receipt_hash FROM journey_term_pack_batches m JOIN journey_term_packs p ON p.request_key=m.request_key LEFT JOIN journey_receipts r ON r.request_key=p.request_key AND r.journey_id=p.journey_id WHERE m.batch_id=? AND p.journey_id=?
|
|
2342
|
+
AND NOT EXISTS (SELECT 1 FROM journey_resources w WHERE w.journey_id=p.journey_id AND w.kind='withdrawn-term-pack' AND w.id=m.batch_id||':'||json_extract(p.payload_ref_json,'$.packId')) ORDER BY p.rowid`).all(canonical.header.id, input.journeyId).map((pack) => {
|
|
2292
2343
|
if (typeof pack["payload_ref_json"] !== "string" || typeof pack["receipt_hash"] !== "string") throw new ApplicationError("CORRUPTED_STATE");
|
|
2293
2344
|
const reference = termPackReferenceSchema.parse(JSON.parse(pack["payload_ref_json"]));
|
|
2294
2345
|
if (reference.requestHash !== pack["request_hash"] || reference.requestHash !== pack["receipt_hash"]) throw new ApplicationError("CORRUPTED_STATE");
|
|
@@ -2307,6 +2358,107 @@ function selectScanAliases(db, input) {
|
|
|
2307
2358
|
});
|
|
2308
2359
|
}
|
|
2309
2360
|
//#endregion
|
|
2361
|
+
//#region packages/local-host/src/store/journey/terms/reopen.ts
|
|
2362
|
+
/** A permanent fence for an accepted pack whose empty coverage was withdrawn. */
|
|
2363
|
+
function withdrawnTermPack(db, journeyId, batchId, packId) {
|
|
2364
|
+
return Boolean(db.prepare("SELECT 1 FROM journey_resources WHERE journey_id=? AND kind='withdrawn-term-pack' AND id=?").get(journeyId, batchId + ":" + packId));
|
|
2365
|
+
}
|
|
2366
|
+
function reopenEmptyReview(db, input) {
|
|
2367
|
+
const state = requireReviewState(db, input.journeyId);
|
|
2368
|
+
if (state.scope.status !== "confirmed" || ![
|
|
2369
|
+
"processing",
|
|
2370
|
+
"review",
|
|
2371
|
+
"ready"
|
|
2372
|
+
].includes(state.phase) || db.prepare("SELECT 1 FROM journey_current_send WHERE journey_id=?").get(input.journeyId)) throw new ApplicationError("INVALID_TRANSITION");
|
|
2373
|
+
const row = db.prepare("SELECT * FROM journey_batches WHERE journey_id=? AND id=?").get(input.journeyId, input.batchId);
|
|
2374
|
+
if (!row) throw new ApplicationError("UNKNOWN_ITEM");
|
|
2375
|
+
const batch = parseStoredBatch(row);
|
|
2376
|
+
const work = requireActiveBatch(state, batch.header);
|
|
2377
|
+
if (work.access === "drop" || batch.header.digest !== input.batchDigest) throw new ApplicationError("REVISION_CONFLICT");
|
|
2378
|
+
if (input.kind === "apply" && withdrawnTermPack(db, input.journeyId, input.batchId, input.expectedPackId)) return {
|
|
2379
|
+
status: "replayed",
|
|
2380
|
+
packId: input.expectedPackId
|
|
2381
|
+
};
|
|
2382
|
+
const ref = batch.termPack;
|
|
2383
|
+
if (!ref || input.kind === "apply" && ref.packId !== input.expectedPackId) throw new ApplicationError("REVISION_CONFLICT");
|
|
2384
|
+
if (db.prepare("SELECT 1 FROM journey_resources WHERE journey_id=? AND kind='scan-alias' AND id=?").get(input.journeyId, input.batchId)) throw new ApplicationError("INVALID_SELECTION");
|
|
2385
|
+
const aliases = db.prepare(`SELECT b.*,a.json AS alias_json FROM journey_batches b ${activeBatchJoin}
|
|
2386
|
+
JOIN journey_resources a ON a.journey_id=b.journey_id AND a.kind='scan-alias' AND a.id=b.id
|
|
2387
|
+
WHERE b.journey_id=? AND json_extract(a.json,'$.canonicalBatchId')=?`).all(input.journeyId, input.batchId).map((row) => {
|
|
2388
|
+
if (typeof row["alias_json"] !== "string") throw new ApplicationError("CORRUPTED_STATE");
|
|
2389
|
+
const alias = scanAliasResourceSchema.parse(JSON.parse(row["alias_json"]));
|
|
2390
|
+
const stored = parseStoredBatch(row);
|
|
2391
|
+
const packId = scanAliasRequestKey(alias, ref.idempotencyKey);
|
|
2392
|
+
if (stored.termPack && stored.termPack.packId !== packId) throw new ApplicationError("REVISION_CONFLICT");
|
|
2393
|
+
return {
|
|
2394
|
+
batch: stored,
|
|
2395
|
+
packId
|
|
2396
|
+
};
|
|
2397
|
+
});
|
|
2398
|
+
const attempts = [{
|
|
2399
|
+
batch,
|
|
2400
|
+
packId: ref.packId
|
|
2401
|
+
}, ...aliases];
|
|
2402
|
+
const targets = attempts.map((a) => a.batch).filter((b) => b.termPack);
|
|
2403
|
+
for (const target of targets) {
|
|
2404
|
+
const pointer = target.termPack;
|
|
2405
|
+
if (!pointer || target.header.workId !== work.id) throw new ApplicationError("CORRUPTED_STATE");
|
|
2406
|
+
const receipt = db.prepare(`SELECT p.request_hash, r.request_hash AS confirmed_hash, json_extract(p.json,'$.terms.kept') AS kept
|
|
2407
|
+
FROM journey_term_packs p JOIN journey_receipts r ON r.journey_id=p.journey_id AND r.request_key=p.request_key
|
|
2408
|
+
WHERE p.journey_id=? AND p.request_key=?`).get(input.journeyId, pointer.idempotencyKey);
|
|
2409
|
+
if (!receipt || receipt["request_hash"] !== pointer.requestHash || receipt["confirmed_hash"] !== pointer.requestHash) throw new ApplicationError("CORRUPTED_STATE");
|
|
2410
|
+
if (receipt["kept"] !== 0 || db.prepare("SELECT 1 FROM journey_term_occurrences WHERE journey_id=? AND batch_id=? LIMIT 1").get(input.journeyId, target.header.id)) throw new ApplicationError("INVALID_SELECTION");
|
|
2411
|
+
}
|
|
2412
|
+
if (input.kind === "inspect") return {
|
|
2413
|
+
status: "eligible",
|
|
2414
|
+
packId: ref.packId
|
|
2415
|
+
};
|
|
2416
|
+
if (work.completedBatches < targets.length) throw new ApplicationError("CORRUPTED_STATE");
|
|
2417
|
+
for (const { batch: target, packId } of attempts) {
|
|
2418
|
+
const id = target.header.id + ":" + packId;
|
|
2419
|
+
db.prepare("INSERT INTO journey_resources VALUES(?,?,?,?)").run(input.journeyId, "withdrawn-term-pack", id, JSON.stringify({
|
|
2420
|
+
kind: "withdrawn-term-pack",
|
|
2421
|
+
id,
|
|
2422
|
+
journeyId: input.journeyId,
|
|
2423
|
+
batchId: target.header.id,
|
|
2424
|
+
packId
|
|
2425
|
+
}));
|
|
2426
|
+
db.prepare("UPDATE journey_batches SET review_ref_json=NULL,review_sequence=NULL WHERE id=?").run(target.header.id);
|
|
2427
|
+
}
|
|
2428
|
+
if (commitJourney(db, {
|
|
2429
|
+
state: {
|
|
2430
|
+
...state,
|
|
2431
|
+
readingComplete: false,
|
|
2432
|
+
phase: "processing",
|
|
2433
|
+
privacy: {
|
|
2434
|
+
...state.privacy,
|
|
2435
|
+
packageApproval: null,
|
|
2436
|
+
confirmedAnswersRevision: null
|
|
2437
|
+
},
|
|
2438
|
+
works: state.works.map((item) => item.id === work.id ? {
|
|
2439
|
+
...item,
|
|
2440
|
+
status: "reading",
|
|
2441
|
+
revision: item.revision + 1,
|
|
2442
|
+
completedBatches: item.completedBatches - targets.length,
|
|
2443
|
+
artifact: null
|
|
2444
|
+
} : item)
|
|
2445
|
+
},
|
|
2446
|
+
expectedRevision: state.revision,
|
|
2447
|
+
requestKey: input.idempotencyKey,
|
|
2448
|
+
requestHash: textDigest(JSON.stringify(input)),
|
|
2449
|
+
event: {
|
|
2450
|
+
journeyId: input.journeyId,
|
|
2451
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2452
|
+
kind: "batch",
|
|
2453
|
+
source: "conversation"
|
|
2454
|
+
}
|
|
2455
|
+
}).status !== "applied") throw new ApplicationError("REVISION_CONFLICT");
|
|
2456
|
+
return {
|
|
2457
|
+
status: "reopened",
|
|
2458
|
+
packId: ref.packId
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
//#endregion
|
|
2310
2462
|
//#region packages/local-host/src/store/journey/finding-groups.ts
|
|
2311
2463
|
function findingPreference(db, key) {
|
|
2312
2464
|
const choice = db.prepare("SELECT choice FROM journey_preferences WHERE group_key=?").get(key)?.["choice"];
|
|
@@ -2442,6 +2594,7 @@ function selectTerms(db, query) {
|
|
|
2442
2594
|
const batches = input.units.map((unit) => {
|
|
2443
2595
|
const row = db.prepare("SELECT * FROM journey_batches WHERE journey_id=? AND id=?").get(input.journeyId, unit.batchId);
|
|
2444
2596
|
if (!row) throw new ApplicationError("UNKNOWN_ITEM");
|
|
2597
|
+
if (withdrawnTermPack(db, input.journeyId, unit.batchId, input.packId)) throw new ApplicationError("REVISION_CONFLICT");
|
|
2445
2598
|
const batch = parseStoredBatch(row);
|
|
2446
2599
|
const alias = db.prepare("SELECT json FROM journey_resources WHERE journey_id=? AND kind='scan-alias' AND id=?").get(input.journeyId, unit.batchId);
|
|
2447
2600
|
if (alias) {
|
|
@@ -2449,7 +2602,9 @@ function selectTerms(db, query) {
|
|
|
2449
2602
|
const source = db.prepare("SELECT b.review_ref_json FROM journey_batches b WHERE b.journey_id=? AND b.id=json_extract(?,'$.canonicalBatchId')").get(input.journeyId, alias["json"]);
|
|
2450
2603
|
if (!source || source["review_ref_json"] === null) throw new ApplicationError("SCOPE_NOT_READY");
|
|
2451
2604
|
}
|
|
2452
|
-
requireActiveBatch(state, batch.header);
|
|
2605
|
+
const work = requireActiveBatch(state, batch.header);
|
|
2606
|
+
if (work.access === "drop") throw new ApplicationError("REVISION_CONFLICT");
|
|
2607
|
+
if (work.reviewInterruptions?.some((hold) => hold.inputRevision === work.inputRevision && hold.batchIds.includes(unit.batchId)) || db.prepare("SELECT 1 FROM journey_resources WHERE journey_id=? AND kind='closed-reader' AND id=?").get(state.journeyId, unit.batchId + ":" + input.packId)) throw new ApplicationError("REVISION_CONFLICT");
|
|
2453
2608
|
if (batch.header.digest !== unit.batchDigest) throw new ApplicationError("REVISION_CONFLICT");
|
|
2454
2609
|
return batch;
|
|
2455
2610
|
});
|
|
@@ -2613,7 +2768,8 @@ const termReuseSelectionSchema = z.object({
|
|
|
2613
2768
|
*/
|
|
2614
2769
|
function selectTermReuse(db, input) {
|
|
2615
2770
|
const state = requireReviewState(db, input.journeyId);
|
|
2616
|
-
const targets = db.prepare(`SELECT b.* FROM journey_batches b ${activeBatchJoin} WHERE b.journey_id=? AND b.work_id=? AND b.review_ref_json IS NULL ${notScanAlias}
|
|
2771
|
+
const targets = db.prepare(`SELECT b.* FROM journey_batches b ${activeBatchJoin} WHERE b.journey_id=? AND b.work_id=? AND b.review_ref_json IS NULL ${notScanAlias}
|
|
2772
|
+
AND NOT EXISTS (SELECT 1 FROM journey_resources wr WHERE wr.journey_id=b.journey_id AND wr.kind='withdrawn-term-pack' AND json_extract(wr.json,'$.batchId')=b.id) AND json_extract(b.header_json,'\$.unit.sessionId')=? ORDER BY b.rowid`).all(input.journeyId, input.workId, input.sessionId).map(parseStoredBatch).filter((batch) => isLegacyBatch(db, batch.header));
|
|
2617
2773
|
if (!targets.length) return {
|
|
2618
2774
|
targets,
|
|
2619
2775
|
origins: []
|
|
@@ -3557,7 +3713,7 @@ function readInventoryFamilies(db, state) {
|
|
|
3557
3713
|
//#endregion
|
|
3558
3714
|
//#region packages/local-host/src/store/journey/resources.ts
|
|
3559
3715
|
function saveJourneyResource(db, resource) {
|
|
3560
|
-
if (resource.kind === "scan-canonical" || resource.kind === "scan-alias" || resource.kind === "b-attempt" || resource.kind === "preparation-start" || resource.kind === "preparation-settlement" || resource.kind === "b-usefulness" || resource.kind === "b-candidate" || resource.kind === "b-repair" || resource.kind === "b-session" || resource.kind === "b-reread" || resource.kind === "b-reconciliation" || resource.kind === "correction-report") throw new ApplicationError("INVALID_TRANSITION");
|
|
3716
|
+
if (resource.kind === "closed-reader" || resource.kind === "withdrawn-term-pack" || resource.kind === "scan-canonical" || resource.kind === "scan-alias" || resource.kind === "b-attempt" || resource.kind === "preparation-start" || resource.kind === "preparation-settlement" || resource.kind === "b-usefulness" || resource.kind === "b-candidate" || resource.kind === "b-repair" || resource.kind === "b-session" || resource.kind === "b-reread" || resource.kind === "b-reconciliation" || resource.kind === "correction-report") throw new ApplicationError("INVALID_TRANSITION");
|
|
3561
3717
|
const state = readJourney(db, resource.journeyId);
|
|
3562
3718
|
if (!state?.consent) throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
3563
3719
|
if (state.pendingHideId && (resource.kind === "base" || resource.kind === "scan-session" || resource.kind === "static-session" || resource.kind === "output")) throw new ApplicationError("SCOPE_NOT_READY");
|
|
@@ -3644,6 +3800,64 @@ function readJourneyResources(db, input) {
|
|
|
3644
3800
|
}
|
|
3645
3801
|
//#endregion
|
|
3646
3802
|
//#region packages/local-host/src/store/journey/preparation.ts
|
|
3803
|
+
function projection(fields) {
|
|
3804
|
+
const sql = `SELECT state_json ->> '$.formatVersion' AS format, ${fields.map((key) => `state_json -> '$.${key}' AS "${key}"`).join(",")} FROM journey_runs WHERE id=?`;
|
|
3805
|
+
return (db, journeyId) => {
|
|
3806
|
+
const row = db.prepare(sql).get(journeyId);
|
|
3807
|
+
if (!row) return null;
|
|
3808
|
+
if (row["format"] !== 10) throw new StoreError("OBSOLETE_SCHEMA", "Stored Journey preparation format is obsolete.");
|
|
3809
|
+
const result = {};
|
|
3810
|
+
for (const key of fields) {
|
|
3811
|
+
const value = row[key];
|
|
3812
|
+
if (typeof value === "string") result[key] = JSON.parse(value);
|
|
3813
|
+
}
|
|
3814
|
+
return result;
|
|
3815
|
+
};
|
|
3816
|
+
}
|
|
3817
|
+
const selectPermission = projection(preparationPermissionSchema.keyof().options);
|
|
3818
|
+
const selectState = projection(preparationStateSchema.keyof().options);
|
|
3819
|
+
function readPreparationPermission(db, journeyId) {
|
|
3820
|
+
return preparationPermissionSchema.nullable().parse(selectPermission(db, journeyId));
|
|
3821
|
+
}
|
|
3822
|
+
function readPreparationState(db, journeyId) {
|
|
3823
|
+
return preparationStateSchema.nullable().parse(selectState(db, journeyId));
|
|
3824
|
+
}
|
|
3825
|
+
/** Merge the small set of changed Work rows into the current state under its revision fence.
|
|
3826
|
+
* The normal commit owns its receipts, input generations, hide and correction invalidation. */
|
|
3827
|
+
function updatePreparation(db, input) {
|
|
3828
|
+
const current = readJourney(db);
|
|
3829
|
+
if (!current || current.journeyId !== input.journeyId) throw new ApplicationError("REVISION_CONFLICT");
|
|
3830
|
+
const replacements = new Map(input.works.map((work) => [work.id, work]));
|
|
3831
|
+
if (current.revision === input.expectedRevision && (replacements.size !== input.works.length || input.works.some((work) => !current.works.some((item) => item.id === work.id)))) throw new ApplicationError("INVALID_ARGUMENTS");
|
|
3832
|
+
const result = commitJourney(db, {
|
|
3833
|
+
expectedRevision: input.expectedRevision,
|
|
3834
|
+
requestKey: input.requestKey,
|
|
3835
|
+
requestHash: textDigest(JSON.stringify(input)),
|
|
3836
|
+
event: {
|
|
3837
|
+
journeyId: input.journeyId,
|
|
3838
|
+
kind: input.kind,
|
|
3839
|
+
source: "system",
|
|
3840
|
+
at: input.at
|
|
3841
|
+
},
|
|
3842
|
+
state: {
|
|
3843
|
+
...current,
|
|
3844
|
+
works: current.works.map((work) => replacements.get(work.id) ?? work),
|
|
3845
|
+
phase: input.phase,
|
|
3846
|
+
readingComplete: input.readingComplete,
|
|
3847
|
+
readingStartedAt: input.readingStartedAt,
|
|
3848
|
+
privacy: input.clearPackageApproval ? {
|
|
3849
|
+
...current.privacy,
|
|
3850
|
+
packageApproval: null
|
|
3851
|
+
} : current.privacy
|
|
3852
|
+
}
|
|
3853
|
+
});
|
|
3854
|
+
const state = readPreparationState(db, result.state.journeyId);
|
|
3855
|
+
if (!state) throw new ApplicationError("CORRUPTED_STATE");
|
|
3856
|
+
return {
|
|
3857
|
+
status: result.status,
|
|
3858
|
+
state
|
|
3859
|
+
};
|
|
3860
|
+
}
|
|
3647
3861
|
function readPreparationReceipts(db, journeyId) {
|
|
3648
3862
|
const starts = [], settlements = [];
|
|
3649
3863
|
for (const row of db.prepare("SELECT kind,id,json FROM journey_resources WHERE journey_id=? AND kind IN ('preparation-start','preparation-settlement')").all(journeyId)) {
|
|
@@ -3665,9 +3879,9 @@ function activeScope(db, input, starting) {
|
|
|
3665
3879
|
if (state.pendingHideId) throw new ApplicationError("SCOPE_NOT_READY");
|
|
3666
3880
|
const work = state.works.find((item) => item.id === input.workId);
|
|
3667
3881
|
if (!work || input.inputRevision !== work.inputRevision || input.snapshotId !== state.snapshotId) throw new ApplicationError("REVISION_CONFLICT");
|
|
3668
|
-
const plan =
|
|
3882
|
+
const { plan, families } = readInventoryFamilies(db, state);
|
|
3669
3883
|
if (!plan.approval.inventory.complete) throw new ApplicationError("CORRUPTED_STATE");
|
|
3670
|
-
return preparationScope(plan.approval.inventory, work);
|
|
3884
|
+
return preparationScope(plan.approval.inventory, work, families);
|
|
3671
3885
|
}
|
|
3672
3886
|
function insert(db, record) {
|
|
3673
3887
|
db.prepare("INSERT INTO journey_resources VALUES(?,?,?,?)").run(record.journeyId, record.kind, record.id, JSON.stringify(record));
|
|
@@ -3738,7 +3952,16 @@ function readJourneyPreparationProgress(db, input) {
|
|
|
3738
3952
|
const state = readFencedJourney(db, input);
|
|
3739
3953
|
if (!state.consent || !state.snapshotId) return null;
|
|
3740
3954
|
const { starts, settlements } = readPreparationReceipts(db, state.journeyId);
|
|
3741
|
-
|
|
3955
|
+
const captures = db.prepare(`SELECT id, json_extract(json,'$.capture.partId') AS partId,
|
|
3956
|
+
json_extract(json,'$.capture.facts.byteLength') AS bytes FROM journey_resources
|
|
3957
|
+
WHERE journey_id=? AND kind='capture'`).all(state.journeyId).map(({ id, partId, bytes }) => {
|
|
3958
|
+
if (id !== partId || typeof partId !== "string" || typeof bytes !== "number" || !Number.isSafeInteger(bytes) || bytes < 0) throw new ApplicationError("CORRUPTED_STATE");
|
|
3959
|
+
return {
|
|
3960
|
+
partId,
|
|
3961
|
+
bytes
|
|
3962
|
+
};
|
|
3963
|
+
});
|
|
3964
|
+
return projectPreparationProgress(state, readInventoryFamilies(db, state).families, captures, starts, settlements);
|
|
3742
3965
|
}
|
|
3743
3966
|
//#endregion
|
|
3744
3967
|
//#region packages/local-host/src/store/journey/hide.ts
|
|
@@ -4544,14 +4767,6 @@ function finishJourneyPlacement(db, packet) {
|
|
|
4544
4767
|
//#endregion
|
|
4545
4768
|
//#region packages/local-host/src/store/journey/isolation.ts
|
|
4546
4769
|
const reason = " – couldn't prepare";
|
|
4547
|
-
/** Stable blocked Work IDs per parent, so later failing roots join one instead of multiplying. */
|
|
4548
|
-
function isolatedWorkId(parentId, ordinal) {
|
|
4549
|
-
const bytes = createHash("sha256").update(`isolated-roots:${parentId}:${ordinal}`).digest().subarray(0, 16);
|
|
4550
|
-
bytes[6] = (bytes[6] ?? 0) & 15 | 64;
|
|
4551
|
-
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
4552
|
-
const hex = bytes.toString("hex");
|
|
4553
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
4554
|
-
}
|
|
4555
4770
|
/**
|
|
4556
4771
|
* Moves identified failing roots out of a pending multi-root Work into its visible blocked
|
|
4557
4772
|
* Work, carrying their public-safe codes; the parent returns to pending for the rest.
|
|
@@ -4595,8 +4810,15 @@ function isolateJourneyRoots(db, input) {
|
|
|
4595
4810
|
})),
|
|
4596
4811
|
blocks: /* @__PURE__ */ new Map([[targetId, [...codes]]])
|
|
4597
4812
|
});
|
|
4813
|
+
const classified = !existing && parent.classificationRecommendation && hasCompleteWorkDescription(parent) && !parent.suggestion ? {
|
|
4814
|
+
description: parent.description,
|
|
4815
|
+
classificationRecommendation: parent.classificationRecommendation,
|
|
4816
|
+
reason: parent.classificationRecommendation.reason,
|
|
4817
|
+
projectType: parent.projectType?.origin === "model" ? parent.projectType : null
|
|
4818
|
+
} : {};
|
|
4598
4819
|
const works = next.works.map((work) => work.id === targetId ? {
|
|
4599
4820
|
...work,
|
|
4821
|
+
...classified,
|
|
4600
4822
|
...access
|
|
4601
4823
|
} : work);
|
|
4602
4824
|
return commitJourney(db, commit(input, {
|
|
@@ -4724,9 +4946,197 @@ function finishJourneyDescription(db, packet) {
|
|
|
4724
4946
|
}
|
|
4725
4947
|
//#endregion
|
|
4726
4948
|
//#region packages/local-host/src/store/journey/send.ts
|
|
4949
|
+
/** Run the privacy fence against the store's current immutable row on every request.
|
|
4950
|
+
* Only the approved output and this Work's frozen upload identities cross the worker boundary. */
|
|
4951
|
+
function readSendOutput(db, input) {
|
|
4952
|
+
const { journeyId, workId, revision } = input;
|
|
4953
|
+
const state = readJourney(db, journeyId);
|
|
4954
|
+
if (!state?.consent) throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
4955
|
+
if (state.phase === "completed" && state.outputVersion !== 1) throw new ApplicationError("SCOPE_NOT_READY");
|
|
4956
|
+
assertJourneyPrivacyReady(state, true);
|
|
4957
|
+
if (state.phase === "paused") throw new ApplicationError("OPERATION_CANCELED");
|
|
4958
|
+
const work = state.works.find((item) => item.id === workId);
|
|
4959
|
+
if (!work?.artifact || work.revision !== revision || work.artifact.revision !== revision) throw new ApplicationError("REVISION_CONFLICT");
|
|
4960
|
+
const [output] = readJourneyResources(db, {
|
|
4961
|
+
journeyId,
|
|
4962
|
+
kind: "output",
|
|
4963
|
+
id: `${workId}:${revision}`
|
|
4964
|
+
});
|
|
4965
|
+
if (output?.kind !== "output" || output.workId !== workId || output.revision !== revision || output.primary.sha256 !== work.artifact.sha256) throw new ApplicationError("ARTIFACT_IDENTITY_MISMATCH");
|
|
4966
|
+
if (!input.sessions) return {
|
|
4967
|
+
output,
|
|
4968
|
+
identities: []
|
|
4969
|
+
};
|
|
4970
|
+
const [base] = readJourneyResources(db, {
|
|
4971
|
+
journeyId,
|
|
4972
|
+
kind: "base",
|
|
4973
|
+
id: `${workId}:${output.finalMaskLedger.inputRevision}`
|
|
4974
|
+
});
|
|
4975
|
+
if (base?.kind !== "base" || base.workId !== workId || base.inputRevision !== output.finalMaskLedger.inputRevision || base.primary.sha256 !== output.finalMaskLedger.basePrimarySha256) throw new ApplicationError("ARTIFACT_IDENTITY_MISMATCH");
|
|
4976
|
+
const plan = readInventoryPlan(db, state);
|
|
4977
|
+
const sessions = new Map(plan.approval.inventory.sessions.map((session) => [session.id, session]));
|
|
4978
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4979
|
+
const identities = base.sessions.map((binding) => {
|
|
4980
|
+
const session = sessions.get(binding.applicationSessionId);
|
|
4981
|
+
const part = session?.parts[0];
|
|
4982
|
+
const lastAt = session?.lastInteractionAt ?? session?.createdAt;
|
|
4983
|
+
if (!session || !part || !lastAt || !Number.isFinite(Date.parse(lastAt))) throw new ApplicationError("ARTIFACT_IDENTITY_MISMATCH");
|
|
4984
|
+
const native = part.nativeEvidence?.identity;
|
|
4985
|
+
if (native?.sessionId?.trim() && native.role !== "unknown" && (native.role !== "worker" || native.agentId?.trim() && session.harness === "claude")) {
|
|
4986
|
+
const key = JSON.stringify([
|
|
4987
|
+
session.harness,
|
|
4988
|
+
native.sessionId,
|
|
4989
|
+
native.role === "worker" ? native.agentId : null
|
|
4990
|
+
]);
|
|
4991
|
+
const group = groups.get(key) ?? [];
|
|
4992
|
+
group.push(binding.archiveSessionId);
|
|
4993
|
+
groups.set(key, group);
|
|
4994
|
+
}
|
|
4995
|
+
const nativeId = part.nativeEvidence?.identity?.sessionId ?? session.sourceSessionId;
|
|
4996
|
+
const path = part.harnessRelativePath ?? "";
|
|
4997
|
+
if (!path && !nativeId?.trim() || isAbsolute(path) || path.includes("\\") || path.split("/").some((segment) => segment === ".." || segment === ".")) throw new ApplicationError("ARTIFACT_IDENTITY_MISMATCH");
|
|
4998
|
+
return {
|
|
4999
|
+
archiveSessionId: binding.archiveSessionId,
|
|
5000
|
+
relativePath: path,
|
|
5001
|
+
lastAt: new Date(lastAt).toISOString()
|
|
5002
|
+
};
|
|
5003
|
+
});
|
|
5004
|
+
const duplicates = [...groups.values()].filter((group) => group.length > 1);
|
|
5005
|
+
if (!duplicates.length) return {
|
|
5006
|
+
output,
|
|
5007
|
+
identities
|
|
5008
|
+
};
|
|
5009
|
+
const duplicateIds = new Set(duplicates.flat());
|
|
5010
|
+
const owners = new Map(base.sessions.map((s) => [s.archiveSessionId, s.applicationSessionId]));
|
|
5011
|
+
const sources = base.sourceBindings.flatMap((binding) => {
|
|
5012
|
+
if (!duplicateIds.has(binding.sessionId) || binding.bundleId !== base.bundleId || owners.get(binding.sessionId) !== binding.applicationSessionId) return [];
|
|
5013
|
+
const part = sessions.get(binding.applicationSessionId)?.parts.find((p) => p.id === binding.sourcePartId);
|
|
5014
|
+
return part ? [{
|
|
5015
|
+
session: binding.sessionId,
|
|
5016
|
+
source: binding.bundleSourceId,
|
|
5017
|
+
part
|
|
5018
|
+
}] : [];
|
|
5019
|
+
});
|
|
5020
|
+
const outputs = state.works.filter(isSelectedWork).flatMap((w) => w.artifact ? [{
|
|
5021
|
+
workId: w.id,
|
|
5022
|
+
revision: w.revision,
|
|
5023
|
+
sha256: w.artifact.sha256,
|
|
5024
|
+
bundleId: w.artifact.bundleId
|
|
5025
|
+
}] : []);
|
|
5026
|
+
const approval = state.privacy.packageApproval;
|
|
5027
|
+
if (!approval) throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
5028
|
+
return {
|
|
5029
|
+
output,
|
|
5030
|
+
identities,
|
|
5031
|
+
extension: {
|
|
5032
|
+
groups: duplicates,
|
|
5033
|
+
sources,
|
|
5034
|
+
outputs,
|
|
5035
|
+
approval
|
|
5036
|
+
}
|
|
5037
|
+
};
|
|
5038
|
+
}
|
|
5039
|
+
const sendCurrentSchema = z.object({
|
|
5040
|
+
journeyId: journeyIdSchema,
|
|
5041
|
+
sendId: journeyIdSchema,
|
|
5042
|
+
revision: z.number().int().nonnegative()
|
|
5043
|
+
}).strict();
|
|
5044
|
+
function isCurrentSend(db, input) {
|
|
5045
|
+
return !!db.prepare(`SELECT 1 FROM journey_runs r JOIN journey_current_send s ON s.journey_id=r.id
|
|
5046
|
+
JOIN journey_current c ON c.journey_id=r.id AND c.singleton=1
|
|
5047
|
+
WHERE r.id=? AND r.revision=? AND s.send_id=?`).get(input.journeyId, input.revision, input.sendId);
|
|
5048
|
+
}
|
|
5049
|
+
const transferCountSchema = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
5050
|
+
const sendTransferSchema = z.object({
|
|
5051
|
+
journeyId: journeyIdSchema,
|
|
5052
|
+
sendId: journeyIdSchema,
|
|
5053
|
+
workId: journeyIdSchema,
|
|
5054
|
+
expectedJourneyRevision: transferCountSchema,
|
|
5055
|
+
expectedSendRevision: transferCountSchema,
|
|
5056
|
+
storedSessions: transferCountSchema.optional(),
|
|
5057
|
+
sessionUpload: sendSessionUploadSchema,
|
|
5058
|
+
requestKey: journeyIdSchema,
|
|
5059
|
+
at: z.iso.datetime()
|
|
5060
|
+
}).strict();
|
|
5061
|
+
const sendTransferResultSchema = z.discriminatedUnion("status", [z.object({
|
|
5062
|
+
status: z.literal("applied"),
|
|
5063
|
+
revision: transferCountSchema,
|
|
5064
|
+
lastEvent: transferCountSchema,
|
|
5065
|
+
sendRevision: transferCountSchema
|
|
5066
|
+
}).strict(), z.object({ status: z.enum(["stale", "replayed"]) }).strict()]);
|
|
5067
|
+
/** Update only transfer fields in the Send document and stamp its Journey revision/event. */
|
|
5068
|
+
function checkpointJourneySendTransfer(db, input) {
|
|
5069
|
+
const requestHash = createHash("sha256").update(JSON.stringify(input)).digest("hex");
|
|
5070
|
+
const receipt = db.prepare("SELECT request_hash,journey_id FROM journey_receipts WHERE request_key=?").get(input.requestKey);
|
|
5071
|
+
if (receipt) {
|
|
5072
|
+
if (receipt["request_hash"] !== requestHash || receipt["journey_id"] !== input.journeyId) throw new ApplicationError("OPERATION_CONFLICT");
|
|
5073
|
+
return { status: "replayed" };
|
|
5074
|
+
}
|
|
5075
|
+
const row = db.prepare(`SELECT r.revision,r.state_json ->> '$.formatVersion' AS formatVersion,
|
|
5076
|
+
r.state_json ->> '$.phase' AS phase,
|
|
5077
|
+
json_type(r.state_json,'$.consent') AS consentType,
|
|
5078
|
+
r.state_json ->> '$.pendingHideId' AS pendingHideId,
|
|
5079
|
+
json_type(r.state_json,'$.abandonedAt') AS abandonedType,s.json
|
|
5080
|
+
FROM journey_runs r
|
|
5081
|
+
JOIN journey_current c ON c.singleton=1 AND c.journey_id=r.id
|
|
5082
|
+
JOIN journey_current_send cs ON cs.journey_id=r.id AND cs.send_id=?
|
|
5083
|
+
JOIN journey_sends s ON s.journey_id=r.id AND s.id=cs.send_id
|
|
5084
|
+
WHERE r.id=?`).get(input.sendId, input.journeyId);
|
|
5085
|
+
if (!row) return { status: "stale" };
|
|
5086
|
+
const revisionValue = row["revision"];
|
|
5087
|
+
if (typeof revisionValue !== "number" || !Number.isSafeInteger(revisionValue) || revisionValue < 0) throw new ApplicationError("CORRUPTED_STATE");
|
|
5088
|
+
if (revisionValue !== input.expectedJourneyRevision) return { status: "stale" };
|
|
5089
|
+
if (row["formatVersion"] !== 10) throw new StoreError("OBSOLETE_SCHEMA", "Stored native Journey state uses an obsolete candidate format and has been preserved.");
|
|
5090
|
+
if (row["consentType"] !== "object") throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
5091
|
+
if (row["phase"] !== "sending") throw new ApplicationError(row["phase"] === "paused" ? "OPERATION_CANCELED" : "INVALID_TRANSITION");
|
|
5092
|
+
if (row["pendingHideId"] !== null && row["pendingHideId"] !== void 0) throw new ApplicationError("REVISION_CONFLICT");
|
|
5093
|
+
if (row["abandonedType"] !== null && row["abandonedType"] !== void 0) throw new ApplicationError("INVALID_TRANSITION");
|
|
5094
|
+
if (typeof row["json"] !== "string") throw new ApplicationError("CORRUPTED_STATE");
|
|
5095
|
+
const send = journeySendSchema.parse(JSON.parse(row["json"]));
|
|
5096
|
+
if (send.revision !== input.expectedSendRevision) return { status: "stale" };
|
|
5097
|
+
if (!send.subject || !send.confirmedAt) throw new ApplicationError("CONFIRMATION_REQUIRED");
|
|
5098
|
+
if (send.status === "paused") throw new ApplicationError("OPERATION_CANCELED");
|
|
5099
|
+
if (!["sending", "waiting"].includes(send.status)) throw new ApplicationError("INVALID_TRANSITION");
|
|
5100
|
+
const index = send.items.findIndex((item) => item.workId === input.workId);
|
|
5101
|
+
if (index < 0) throw new ApplicationError("INVALID_SELECTION");
|
|
5102
|
+
const item = send.items[index];
|
|
5103
|
+
if (!item) throw new ApplicationError("CORRUPTED_STATE");
|
|
5104
|
+
if (item.status !== "uploading") throw new ApplicationError("INVALID_TRANSITION");
|
|
5105
|
+
const revision = revisionValue + 1;
|
|
5106
|
+
const sendRevision = send.revision + 1;
|
|
5107
|
+
const next = journeySendSchema.parse({
|
|
5108
|
+
...send,
|
|
5109
|
+
revision: sendRevision,
|
|
5110
|
+
items: send.items.map((candidate, candidateIndex) => candidateIndex === index ? {
|
|
5111
|
+
...candidate,
|
|
5112
|
+
sessionUpload: input.sessionUpload,
|
|
5113
|
+
...input.storedSessions === void 0 ? {} : { storedSessions: input.storedSessions }
|
|
5114
|
+
} : candidate)
|
|
5115
|
+
});
|
|
5116
|
+
if (db.prepare("UPDATE journey_sends SET json=? WHERE journey_id=? AND id=?").run(JSON.stringify(next), input.journeyId, input.sendId).changes !== 1) throw new ApplicationError("REVISION_CONFLICT");
|
|
5117
|
+
const inserted = db.prepare("INSERT INTO journey_events(journey_id,event_json) VALUES(?,?)").run(input.journeyId, JSON.stringify({
|
|
5118
|
+
journeyId: input.journeyId,
|
|
5119
|
+
revision,
|
|
5120
|
+
kind: "send",
|
|
5121
|
+
source: "system",
|
|
5122
|
+
at: input.at
|
|
5123
|
+
}));
|
|
5124
|
+
const lastEvent = Number(inserted.lastInsertRowid);
|
|
5125
|
+
if (!Number.isSafeInteger(lastEvent) || lastEvent < 1) throw new ApplicationError("CORRUPTED_STATE");
|
|
5126
|
+
if (db.prepare(`UPDATE journey_runs SET revision=?,state_json=json_set(
|
|
5127
|
+
state_json,'$.revision',?,'$.lastEvent',?) WHERE id=? AND revision=?`).run(revision, revision, lastEvent, input.journeyId, input.expectedJourneyRevision).changes !== 1) throw new ApplicationError("REVISION_CONFLICT");
|
|
5128
|
+
db.prepare("INSERT INTO journey_receipts VALUES(?,?,?,?)").run(input.requestKey, requestHash, input.journeyId, revision);
|
|
5129
|
+
return {
|
|
5130
|
+
status: "applied",
|
|
5131
|
+
revision,
|
|
5132
|
+
lastEvent,
|
|
5133
|
+
sendRevision
|
|
5134
|
+
};
|
|
5135
|
+
}
|
|
4727
5136
|
const sendLoadSchema = z.object({
|
|
4728
5137
|
journeyId: journeyIdSchema,
|
|
4729
|
-
sendId: journeyIdSchema.optional()
|
|
5138
|
+
sendId: journeyIdSchema.optional(),
|
|
5139
|
+
observedRevision: z.number().int().nonnegative().optional()
|
|
4730
5140
|
}).strict();
|
|
4731
5141
|
const sendSnapshotSchema = z.object({
|
|
4732
5142
|
journey: nativeJourneySchema,
|
|
@@ -4747,6 +5157,7 @@ const sendCommitResultSchema = sendSnapshotSchema.extend({ status: z.enum([
|
|
|
4747
5157
|
"stale"
|
|
4748
5158
|
]) });
|
|
4749
5159
|
function loadJourneySend(db, input) {
|
|
5160
|
+
if (input.observedRevision !== void 0 && !input.sendId && db.prepare("SELECT 1 AS same FROM journey_runs WHERE id=? AND revision=?").get(input.journeyId, input.observedRevision)) return null;
|
|
4750
5161
|
const journey = readJourney(db, input.journeyId);
|
|
4751
5162
|
if (!journey) return null;
|
|
4752
5163
|
const row = input.sendId ? db.prepare("SELECT json FROM journey_sends WHERE journey_id=? AND id=?").get(input.journeyId, input.sendId) : db.prepare("SELECT json FROM journey_sends JOIN journey_current_send ON journey_sends.id=journey_current_send.send_id WHERE journey_current_send.journey_id=?").get(input.journeyId);
|
|
@@ -4784,6 +5195,7 @@ function commitJourneySend(db, input) {
|
|
|
4784
5195
|
...input.state,
|
|
4785
5196
|
revision
|
|
4786
5197
|
});
|
|
5198
|
+
if (!prior && obsoleteJourney(current.journey)) throw new ApplicationError("JOURNEY_OBSOLETE");
|
|
4787
5199
|
const skipped = (workId) => current.journey.works.some((work) => work.id === workId && isSkippedWork(work));
|
|
4788
5200
|
const finished = send.status === "completed" && send.omitted.every((work) => [
|
|
4789
5201
|
"drop",
|
|
@@ -4858,7 +5270,10 @@ function admitScanSession(db, input) {
|
|
|
4858
5270
|
const state = readJourney(db, input.resource.journeyId);
|
|
4859
5271
|
if (!state) throw new ApplicationError("UNKNOWN_ITEM");
|
|
4860
5272
|
return {
|
|
4861
|
-
state
|
|
5273
|
+
state: preparationStateSchema.strip().parse({
|
|
5274
|
+
...state,
|
|
5275
|
+
works: state.works.filter((work) => work.id === input.resource.workId)
|
|
5276
|
+
}),
|
|
4862
5277
|
priors: input.headers.map((header) => admitReviewBatch(db, header))
|
|
4863
5278
|
};
|
|
4864
5279
|
}
|
|
@@ -6867,6 +7282,9 @@ const operations = {
|
|
|
6867
7282
|
"journey.policy.respond": define(true, policyRespondSchema, respondJourneyPolicy, (value) => journeyCommitResultSchema.parse(value)),
|
|
6868
7283
|
"journey.policy.batches": define(false, policyBatchesSchema, selectPolicyBatches, (value) => z.array(z.uuid()).parse(value)),
|
|
6869
7284
|
"journey.send.load": define(false, sendLoadSchema, loadJourneySend, (value) => sendSnapshotSchema.nullable().parse(value)),
|
|
7285
|
+
"journey.send.current": define(false, sendCurrentSchema, isCurrentSend, (value) => z.boolean().parse(value)),
|
|
7286
|
+
"journey.send.transfer": define(true, sendTransferSchema, checkpointJourneySendTransfer, (value) => sendTransferResultSchema.parse(value)),
|
|
7287
|
+
"journey.send.output": define(false, sendOutputQuerySchema, readSendOutput, (value) => sendOutputSchema.parse(value)),
|
|
6870
7288
|
"journey.send.commit": define(true, sendCommitSchema, commitJourneySend, (value) => sendCommitResultSchema.parse(value)),
|
|
6871
7289
|
"manual.upload.load": define(false, manualUploadLoadSchema, loadManualUpload, (value) => manualUploadSchema.nullable().parse(value)),
|
|
6872
7290
|
"manual.upload.commit": define(true, manualUploadCommitSchema, commitManualUpload, (value) => manualUploadResultSchema.parse(value)),
|
|
@@ -6884,13 +7302,34 @@ const operations = {
|
|
|
6884
7302
|
"journey.batch.put": define(true, batchPublicationSchema, putStoredBatch, nothing),
|
|
6885
7303
|
"journey.batch.admit": define(false, batchHeaderSchema, admitReviewBatch, (value) => storedBatchSchema.nullable().parse(value)),
|
|
6886
7304
|
"journey.scan.admit": define(true, scanAdmissionSchema, admitScanSession, (value) => z.object({
|
|
6887
|
-
state:
|
|
7305
|
+
state: preparationStateSchema,
|
|
6888
7306
|
priors: z.array(storedBatchSchema.nullable())
|
|
6889
7307
|
}).strict().parse(value)),
|
|
6890
7308
|
"journey.scan.put": define(true, scanBatchesSchema, putScanBatches, nothing),
|
|
6891
7309
|
"journey.batch.read": define(false, journeyBatchReadSchema, selectReviewBatch, (value) => batchSelectionSchema.parse(value)),
|
|
6892
7310
|
"journey.review.attempts": define(false, reviewAttemptsQuerySchema, readReviewAttempts, (value) => reviewAttemptsResultSchema.parse(value)),
|
|
7311
|
+
"journey.preparation.state": define(false, z.object({ journeyId: z.uuid() }), (db, { journeyId }) => readPreparationState(db, journeyId), (value) => preparationStateSchema.nullable().parse(value)),
|
|
7312
|
+
"journey.preparation.permission": define(false, z.object({ journeyId: z.uuid() }), (db, { journeyId }) => readPreparationPermission(db, journeyId), (value) => preparationPermissionSchema.nullable().parse(value)),
|
|
7313
|
+
"journey.preparation.update": define(true, preparationUpdateSchema, updatePreparation, (value) => z.object({
|
|
7314
|
+
status: z.enum([
|
|
7315
|
+
"applied",
|
|
7316
|
+
"stale",
|
|
7317
|
+
"replayed"
|
|
7318
|
+
]),
|
|
7319
|
+
state: preparationStateSchema
|
|
7320
|
+
}).strict().parse(value)),
|
|
7321
|
+
"journey.receipt.revision": define(false, z.object({
|
|
7322
|
+
journeyId: z.uuid(),
|
|
7323
|
+
key: z.uuid(),
|
|
7324
|
+
hash: z.string().regex(/^[0-9a-f]{64}$/u)
|
|
7325
|
+
}).strict(), (db, { journeyId, key, hash }) => {
|
|
7326
|
+
const row = db.prepare("SELECT journey_id,request_hash,revision FROM journey_receipts WHERE request_key=?").get(key);
|
|
7327
|
+
if (!row) return null;
|
|
7328
|
+
if (row["journey_id"] !== journeyId || row["request_hash"] !== hash) throw new ApplicationError("OPERATION_CONFLICT");
|
|
7329
|
+
return z.number().int().nonnegative().parse(row["revision"]);
|
|
7330
|
+
}, (value) => z.number().int().nonnegative().nullable().parse(value)),
|
|
6893
7331
|
"journey.current": define(false, z.object({ journeyId: z.uuid().optional() }), (db, input) => readJourney(db, input.journeyId), (value) => nativeJourneySchema.nullable().parse(value)),
|
|
7332
|
+
"journey.terms.reopen-empty": define(true, emptyReviewCorrectionSchema, reopenEmptyReview, (value) => emptyReviewCorrectionResultSchema.parse(value)),
|
|
6894
7333
|
"journey.terms.select": define(false, termsQuerySchema, selectTerms, (value) => termsSelectionSchema.parse(value)),
|
|
6895
7334
|
"journey.terms.finish": define(true, termsFinishSchema, finishTerms, (value) => reviewTermsResultSchema.parse(value)),
|
|
6896
7335
|
"journey.terms.read": define(false, journeyTermsQuerySchema, readJourneyTerms, (value) => z.array(journeyTermSchema).parse(value)),
|
|
@@ -7412,4 +7851,4 @@ function assertStoreHeap(limitBytes = getHeapStatistics().heap_size_limit) {
|
|
|
7412
7851
|
return limitBytes;
|
|
7413
7852
|
}
|
|
7414
7853
|
//#endregion
|
|
7415
|
-
export {
|
|
7854
|
+
export { validateState as $, resourceSelectionQuerySchema as A, termReuseQuerySchema as B, loadAcceptedReview as C, aggregateUsefulness as D, loadPrivateResource as E, validateUnitCandidate as F, validateBatchBody as G, privateReviewHash as H, reuseQuerySchema as I, ownership as J, requireActiveBatch as K, batchQuery as L, evaluateUnitEvidence as M, sameCandidateRequest as N, sessionUsefulnessRecommendation as O, validateCandidateAttempt as P, readStoredState as Q, isPrivateResourceKind as R, validateSessionBindings as S, loadBatch as T, batchHeader as U, scanAliasRequestKey as V, createJourneyPayloadFiles as W, withDatabase as X, validateDatabase as Y, readBundleDirectory as Z, loadReconciliation as _, errorCode as _t, decodeError as a, finishReset as at, validatePreparationSelection as b, dropsAnchor as c, present as ct, parseDelivery as d, recheckPrivatePath as dt, checkVersion as et, validateCandidate as f, stateDirectory as ft, loadPreparedBodies as g, StoreError as gt, loadPreparation as h, withFileCheckTimings as ht, createExecutor as i, databaseName as it, inspectionQuerySchema as j, sessionQuery as k, replySchema as l, privatePath as lt, loadStatus as m, withStateSnapshot as mt, releaseInheritedHeapFlags as n, beginReset as nt, ProtocolError as o, hasResetMarker as ot, classificationJourneyCommit as p, syncDirectory as pt, initializeAndClaim as q, storeHeapLimits as r, createDatabaseFile as rt, allowedReplies as s, inspectStateFiles as st, assertStoreHeap as t, schemaVersion as tt, operations as u, readResetMarker as ut, loadRereadBodies as v, isCoordinationBusy as vt, loadAuthenticatedReview as w, validateSessionReconciliation as x, loadSessionBodies as y, isSqliteCorruption as yt, privateResourceHeader as z };
|