@happyvertical/smrt-ingestion 0.55.6 → 0.55.8
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/AGENTS.md +13 -3
- package/FEEDBACK.md +156 -0
- package/README.md +15 -3
- package/REVIEW.md +137 -0
- package/dist/chunks/{execution-C6HJvl9e.js → execution-C7sTFXFU.js} +375 -11
- package/dist/chunks/execution-C7sTFXFU.js.map +1 -0
- package/dist/chunks/execution-internal-DWteDHM2.js.map +1 -1
- package/dist/chunks/feedback-C3OHD4sA.js +625 -0
- package/dist/chunks/feedback-C3OHD4sA.js.map +1 -0
- package/dist/chunks/{extraction-bseWZuWN.js → proposal-errors-z24VKWnQ.js} +10 -2
- package/dist/chunks/proposal-errors-z24VKWnQ.js.map +1 -0
- package/dist/dto.d.ts +2 -0
- package/dist/dto.d.ts.map +1 -1
- package/dist/execution-internal.d.ts +3 -1
- package/dist/execution-internal.d.ts.map +1 -1
- package/dist/execution.d.ts +15 -1
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +1 -1
- package/dist/feedback-contracts.d.ts +51 -0
- package/dist/feedback-contracts.d.ts.map +1 -0
- package/dist/feedback-dto.d.ts +106 -0
- package/dist/feedback-dto.d.ts.map +1 -0
- package/dist/feedback-internal.d.ts +19 -0
- package/dist/feedback-internal.d.ts.map +1 -0
- package/dist/feedback.d.ts +54 -0
- package/dist/feedback.d.ts.map +1 -0
- package/dist/manifest.json +34 -1
- package/dist/models.js +1 -1
- package/dist/models.js.map +1 -1
- package/dist/proposal-contracts.d.ts +4 -0
- package/dist/proposal-contracts.d.ts.map +1 -1
- package/dist/proposal-dto.d.ts +4 -0
- package/dist/proposal-dto.d.ts.map +1 -1
- package/dist/proposal-errors.d.ts +5 -0
- package/dist/proposal-errors.d.ts.map +1 -0
- package/dist/proposal-sdk.d.ts.map +1 -1
- package/dist/proposals.d.ts +4 -1
- package/dist/proposals.d.ts.map +1 -1
- package/dist/proposals.js +114 -15
- package/dist/proposals.js.map +1 -1
- package/dist/review-dto.d.ts +115 -0
- package/dist/review-dto.d.ts.map +1 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +146 -54
- package/dist/server.js.map +1 -1
- package/dist/smrt-knowledge.json +97 -14
- package/dist/svelte/components/ActionReview.svelte +232 -0
- package/dist/svelte/components/ActionReview.svelte.d.ts +15 -0
- package/dist/svelte/components/ActionReview.svelte.d.ts.map +1 -0
- package/dist/svelte/components/EvidenceViewer.svelte +31 -0
- package/dist/svelte/components/EvidenceViewer.svelte.d.ts +8 -0
- package/dist/svelte/components/EvidenceViewer.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InboxView.svelte +119 -0
- package/dist/svelte/components/InboxView.svelte.d.ts +9 -0
- package/dist/svelte/components/InboxView.svelte.d.ts.map +1 -0
- package/dist/svelte/components/IntakeInbox.svelte +11 -0
- package/dist/svelte/components/IntakeInbox.svelte.d.ts +10 -0
- package/dist/svelte/components/IntakeInbox.svelte.d.ts.map +1 -0
- package/dist/svelte/components/IntakeReview.svelte +11 -0
- package/dist/svelte/components/IntakeReview.svelte.d.ts +10 -0
- package/dist/svelte/components/IntakeReview.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ReviewView.svelte +243 -0
- package/dist/svelte/components/ReviewView.svelte.d.ts +9 -0
- package/dist/svelte/components/ReviewView.svelte.d.ts.map +1 -0
- package/dist/svelte/i18n.d.ts +72 -0
- package/dist/svelte/i18n.d.ts.map +1 -0
- package/dist/svelte/i18n.js +72 -0
- package/dist/svelte/index.d.ts +5 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +3 -0
- package/dist/svelte/view-utils.d.ts +13 -0
- package/dist/svelte/view-utils.d.ts.map +1 -0
- package/dist/svelte/view-utils.js +96 -0
- package/package.json +49 -22
- package/dist/chunks/execution-C6HJvl9e.js.map +0 -1
- package/dist/chunks/extraction-bseWZuWN.js.map +0 -1
package/dist/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./models.js";
|
|
2
2
|
import { n as generationSnapshotRead } from "./chunks/execution-internal-DWteDHM2.js";
|
|
3
|
-
import { n as
|
|
3
|
+
import { i as proposeDocumentSplits, n as createSDKExtractionAdapter, r as extractAnalysis, t as GenerationSnapshotStaleError } from "./chunks/proposal-errors-z24VKWnQ.js";
|
|
4
4
|
import { detectEngine, isEmbeddedDatabase, isTransientDatabaseError, withEmbeddedWriteTransaction } from "@happyvertical/smrt-core";
|
|
5
5
|
import { withTenant } from "@happyvertical/smrt-tenancy";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -645,6 +645,9 @@ var IngestionService = class {
|
|
|
645
645
|
async reconcileAction(actionId) {
|
|
646
646
|
return (await this.execution()).reconcileAction(actionId);
|
|
647
647
|
}
|
|
648
|
+
async listReviews(itemId, input = {}) {
|
|
649
|
+
return (await this.execution()).listReviews(itemId, input);
|
|
650
|
+
}
|
|
648
651
|
async getAction(actionId) {
|
|
649
652
|
return (await this.execution()).getAction(actionId);
|
|
650
653
|
}
|
|
@@ -677,6 +680,28 @@ var IngestionService = class {
|
|
|
677
680
|
async previewGeneratedProposals(input) {
|
|
678
681
|
return (await this.proposals()).preview(input);
|
|
679
682
|
}
|
|
683
|
+
async feedback() {
|
|
684
|
+
const { IntakeFeedbackService } = await import("./chunks/feedback-C3OHD4sA.js").then((n) => n.n);
|
|
685
|
+
return new IntakeFeedbackService(this.options);
|
|
686
|
+
}
|
|
687
|
+
async recordFeedback(input) {
|
|
688
|
+
return (await this.feedback()).record(input);
|
|
689
|
+
}
|
|
690
|
+
async observeFeedback(input) {
|
|
691
|
+
return (await this.feedback()).observe(input);
|
|
692
|
+
}
|
|
693
|
+
async retrieveFeedback(input) {
|
|
694
|
+
return (await this.proposals()).retrieveFeedback(input);
|
|
695
|
+
}
|
|
696
|
+
async suggestRoutingRule(input) {
|
|
697
|
+
return (await this.feedback()).suggest(input);
|
|
698
|
+
}
|
|
699
|
+
async getRoutingRule(input) {
|
|
700
|
+
return (await this.feedback()).readRule(input);
|
|
701
|
+
}
|
|
702
|
+
async adoptRoutingRule(input) {
|
|
703
|
+
return (await this.feedback()).adopt(input);
|
|
704
|
+
}
|
|
680
705
|
now() {
|
|
681
706
|
return this.options.now?.() ?? /* @__PURE__ */ new Date();
|
|
682
707
|
}
|
|
@@ -1063,62 +1088,129 @@ var IngestionService = class {
|
|
|
1063
1088
|
}
|
|
1064
1089
|
/** Append an immutable input revision. Reprocessing never creates actions. */
|
|
1065
1090
|
async analyze(itemId, configuration, requestKey) {
|
|
1091
|
+
const frozen = structuredClone(configuration);
|
|
1092
|
+
if (Object.hasOwn(frozen, "humanCorrection") && frozen.stage !== "interpret") throw new Error("Human corrections require authenticated split revision");
|
|
1093
|
+
return this.tx((db) => this.appendAnalysis(db, itemId, frozen, requestKey));
|
|
1094
|
+
}
|
|
1095
|
+
async appendAnalysis(db, itemId, configuration, requestKey) {
|
|
1066
1096
|
nonempty(requestKey);
|
|
1067
1097
|
const configDigest = digest(configuration);
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
if (replay)
|
|
1074
|
-
if (object(replay.data).configDigest !== configDigest) throw new Error("Analysis request conflict");
|
|
1075
|
-
return {
|
|
1076
|
-
id: String(replay.id),
|
|
1077
|
-
revision: Number(replay.revision)
|
|
1078
|
-
};
|
|
1079
|
-
}
|
|
1080
|
-
const evidence = await this.rows(db, "intake_evidence", "item_id=? AND state='durable'", [itemId]);
|
|
1081
|
-
await db.query("UPDATE intake_analysis_attempts SET state='superseded' WHERE tenant_id=? AND confidential_scope_id=? AND item_id=? AND state IN ('queued','running')", this.scope.tenantId, this.scope.confidentialScopeId, itemId);
|
|
1082
|
-
await db.query("UPDATE intake_analyses SET state='superseded' WHERE tenant_id=? AND confidential_scope_id=? AND item_id=? AND state IN ('queued','running')", this.scope.tenantId, this.scope.confidentialScopeId, itemId);
|
|
1083
|
-
const revision = Number(item.analysis_revision) + 1;
|
|
1084
|
-
await db.query("UPDATE intake_dispatches SET state='completed' WHERE tenant_id=? AND confidential_scope_id=? AND item_id=? AND revision<? AND state<>'completed'", this.scope.tenantId, this.scope.confidentialScopeId, itemId, revision);
|
|
1085
|
-
const inputs = evidence.map((e) => ({
|
|
1086
|
-
id: e.id,
|
|
1087
|
-
hash: e.content_hash
|
|
1088
|
-
})).sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
1089
|
-
const [analysis] = await this.insert(db, "intake_analyses", {
|
|
1090
|
-
item_id: itemId,
|
|
1091
|
-
revision,
|
|
1092
|
-
fence: 0,
|
|
1093
|
-
attempt_number: 0,
|
|
1094
|
-
current_attempt_id: null,
|
|
1095
|
-
state: "queued",
|
|
1096
|
-
input_digest: digest({
|
|
1097
|
-
inputs,
|
|
1098
|
-
configuration
|
|
1099
|
-
}),
|
|
1100
|
-
data: canonical({
|
|
1101
|
-
inputs,
|
|
1102
|
-
configuration,
|
|
1103
|
-
requestKey,
|
|
1104
|
-
configDigest
|
|
1105
|
-
})
|
|
1106
|
-
});
|
|
1107
|
-
await db.query("UPDATE intake_items SET analysis_revision=?,processing_state='queued' WHERE tenant_id=? AND confidential_scope_id=? AND id=?", revision, this.scope.tenantId, this.scope.confidentialScopeId, itemId);
|
|
1108
|
-
await this.insert(db, "intake_dispatches", {
|
|
1109
|
-
item_id: itemId,
|
|
1110
|
-
stage: "analyze",
|
|
1111
|
-
revision,
|
|
1112
|
-
state: "pending",
|
|
1113
|
-
job_id: "",
|
|
1114
|
-
deliveries: 0,
|
|
1115
|
-
data: "{}"
|
|
1116
|
-
}, "tenant_id,item_id,stage,revision");
|
|
1098
|
+
await this.lock(db, itemId);
|
|
1099
|
+
const item = await this.item(itemId, "process", db);
|
|
1100
|
+
if (item.receipt_state !== "ready" || item.cancelled) throw new Error("Intake not processable");
|
|
1101
|
+
const replay = (await this.rows(db, "intake_analyses", "item_id=?", [itemId])).find((a) => object(a.data).requestKey === requestKey);
|
|
1102
|
+
if (replay) {
|
|
1103
|
+
if (object(replay.data).configDigest !== configDigest) throw new Error("Analysis request conflict");
|
|
1117
1104
|
return {
|
|
1118
|
-
id: String(
|
|
1119
|
-
revision
|
|
1105
|
+
id: String(replay.id),
|
|
1106
|
+
revision: Number(replay.revision)
|
|
1120
1107
|
};
|
|
1108
|
+
}
|
|
1109
|
+
const evidence = await this.rows(db, "intake_evidence", "item_id=? AND state='durable'", [itemId]);
|
|
1110
|
+
await db.query("UPDATE intake_analysis_attempts SET state='superseded' WHERE tenant_id=? AND confidential_scope_id=? AND item_id=? AND state IN ('queued','running')", this.scope.tenantId, this.scope.confidentialScopeId, itemId);
|
|
1111
|
+
await db.query("UPDATE intake_analyses SET state='superseded' WHERE tenant_id=? AND confidential_scope_id=? AND item_id=? AND state IN ('queued','running')", this.scope.tenantId, this.scope.confidentialScopeId, itemId);
|
|
1112
|
+
const revision = Number(item.analysis_revision) + 1;
|
|
1113
|
+
await db.query("UPDATE intake_dispatches SET state='completed' WHERE tenant_id=? AND confidential_scope_id=? AND item_id=? AND revision<? AND state<>'completed'", this.scope.tenantId, this.scope.confidentialScopeId, itemId, revision);
|
|
1114
|
+
const inputs = evidence.map((e) => ({
|
|
1115
|
+
id: e.id,
|
|
1116
|
+
hash: e.content_hash
|
|
1117
|
+
})).sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
1118
|
+
const [analysis] = await this.insert(db, "intake_analyses", {
|
|
1119
|
+
item_id: itemId,
|
|
1120
|
+
revision,
|
|
1121
|
+
fence: 0,
|
|
1122
|
+
attempt_number: 0,
|
|
1123
|
+
current_attempt_id: null,
|
|
1124
|
+
state: "queued",
|
|
1125
|
+
input_digest: digest({
|
|
1126
|
+
inputs,
|
|
1127
|
+
configuration
|
|
1128
|
+
}),
|
|
1129
|
+
data: canonical({
|
|
1130
|
+
inputs,
|
|
1131
|
+
configuration,
|
|
1132
|
+
requestKey,
|
|
1133
|
+
configDigest
|
|
1134
|
+
})
|
|
1121
1135
|
});
|
|
1136
|
+
await db.query("UPDATE intake_items SET analysis_revision=?,processing_state='queued' WHERE tenant_id=? AND confidential_scope_id=? AND id=?", revision, this.scope.tenantId, this.scope.confidentialScopeId, itemId);
|
|
1137
|
+
await this.insert(db, "intake_dispatches", {
|
|
1138
|
+
item_id: itemId,
|
|
1139
|
+
stage: "analyze",
|
|
1140
|
+
revision,
|
|
1141
|
+
state: "pending",
|
|
1142
|
+
job_id: "",
|
|
1143
|
+
deliveries: 0,
|
|
1144
|
+
data: "{}"
|
|
1145
|
+
}, "tenant_id,item_id,stage,revision");
|
|
1146
|
+
return {
|
|
1147
|
+
id: String(analysis.id),
|
|
1148
|
+
revision
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
/** Record an authenticated split correction and queue fresh extraction, never copy model output. */
|
|
1152
|
+
async reviseLogicalSplit(input) {
|
|
1153
|
+
const frozen = structuredClone(input);
|
|
1154
|
+
nonempty(frozen.requestId);
|
|
1155
|
+
integer(frozen.expectedRevision);
|
|
1156
|
+
if (!Array.isArray(frozen.groups) || !frozen.groups.length || frozen.groups.length > 100 || frozen.groups.some((group) => !Array.isArray(group) || !group.length || group.length > 1e3 || group.some((page) => !Number.isSafeInteger(page) || page < 1))) throw new Error("Invalid logical split");
|
|
1157
|
+
const pages = frozen.groups.flat();
|
|
1158
|
+
if (pages.length > 1e3 || new Set(pages).size !== pages.length) throw new Error("Invalid logical split");
|
|
1159
|
+
const requestKey = `split:${frozen.requestId}`;
|
|
1160
|
+
const correction = {
|
|
1161
|
+
...frozen,
|
|
1162
|
+
actorId: this.scope.actorId,
|
|
1163
|
+
kind: "logical_split"
|
|
1164
|
+
};
|
|
1165
|
+
const replay = await this.tx(async (db) => {
|
|
1166
|
+
await this.lock(db, frozen.itemId);
|
|
1167
|
+
await this.item(frozen.itemId, "process", db);
|
|
1168
|
+
const found = (await this.rows(db, "intake_analyses", "item_id=?", [frozen.itemId])).find((row) => object(row.data).requestKey === requestKey);
|
|
1169
|
+
if (!found) return null;
|
|
1170
|
+
if (digest(object(object(found.data).configuration).humanCorrection) !== digest(correction)) throw new Error("Split request conflict");
|
|
1171
|
+
return {
|
|
1172
|
+
id: String(found.id),
|
|
1173
|
+
revision: Number(found.revision)
|
|
1174
|
+
};
|
|
1175
|
+
});
|
|
1176
|
+
if (replay) {
|
|
1177
|
+
await (await this.proposals())[generationSnapshotRead](frozen.itemId, (work) => this.tx(work), (db) => this.completedAnalysisSnapshot(db, frozen.itemId, frozen.attemptId, false), void 0, true);
|
|
1178
|
+
return replay;
|
|
1179
|
+
}
|
|
1180
|
+
let result;
|
|
1181
|
+
await (await this.proposals())[generationSnapshotRead](frozen.itemId, (work) => this.tx(work), (db) => this.completedAnalysisSnapshot(db, frozen.itemId, frozen.attemptId, true), async (db, current) => {
|
|
1182
|
+
if (current.revision !== frozen.expectedRevision) throw new Error("Stale logical split");
|
|
1183
|
+
const pin = object(object(current.configuration.proposals).source);
|
|
1184
|
+
const source = await this.completedAnalysisSnapshot(db, frozen.itemId, String(pin.attemptId), false);
|
|
1185
|
+
for (const key of [
|
|
1186
|
+
"attemptId",
|
|
1187
|
+
"revision",
|
|
1188
|
+
"inputDigest",
|
|
1189
|
+
"outputDigest",
|
|
1190
|
+
"evidenceDigest"
|
|
1191
|
+
]) if (pin[key] !== source[key]) throw new Error("Split source changed");
|
|
1192
|
+
const extraction = source.result.output.results;
|
|
1193
|
+
if (!Array.isArray(extraction)) throw new Error("Split source unavailable");
|
|
1194
|
+
const evidence = extraction.find((value) => object(object(value).evidence).id === frozen.evidenceId);
|
|
1195
|
+
if (!evidence) throw new Error("Split evidence unavailable");
|
|
1196
|
+
const observed = /* @__PURE__ */ new Set();
|
|
1197
|
+
for (const value of object(evidence).segments) {
|
|
1198
|
+
const location = object(object(value).location);
|
|
1199
|
+
if (location.kind === "page") observed.add(Number(location.page));
|
|
1200
|
+
if (location.kind === "pages") {
|
|
1201
|
+
const from = Number(location.startPage), to = Number(location.endPage);
|
|
1202
|
+
if (!Number.isSafeInteger(from) || !Number.isSafeInteger(to) || to - from > 1e3) throw new Error("Invalid source range");
|
|
1203
|
+
for (let page = from; page <= to; page++) observed.add(page);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
if (!observed.size || pages.length !== observed.size || pages.some((page) => !observed.has(page))) throw new Error("Split must cover observed pages exactly");
|
|
1207
|
+
result = await this.appendAnalysis(db, frozen.itemId, {
|
|
1208
|
+
...source.configuration,
|
|
1209
|
+
humanCorrection: correction
|
|
1210
|
+
}, requestKey);
|
|
1211
|
+
}, true);
|
|
1212
|
+
if (!result) throw new Error("Split unavailable");
|
|
1213
|
+
return result;
|
|
1122
1214
|
}
|
|
1123
1215
|
/** Allocate a fenced attempt; active leases cannot be stolen before expiry. */
|
|
1124
1216
|
async claimAnalysis(itemId, revision, workerId) {
|
|
@@ -1259,7 +1351,7 @@ var IngestionService = class {
|
|
|
1259
1351
|
"outputDigest",
|
|
1260
1352
|
"evidenceDigest"
|
|
1261
1353
|
]) if (pin[key] !== source[key]) throw new Error("Generation source changed");
|
|
1262
|
-
if (source.result.provider !== "smrt-ingestion-extraction" || source.configuration.stage === "interpret" || source.revision !== generation.revision - 1 || digest(generation.evidence.map((entry) => ({
|
|
1354
|
+
if (source.result.provider !== "smrt-ingestion-extraction" || source.configuration.stage === "interpret" || source.revision !== generation.revision - 1 || digest(configuration.humanCorrection ?? null) !== digest(source.configuration.humanCorrection ?? null) || digest(generation.evidence.map((entry) => ({
|
|
1263
1355
|
id: entry.id,
|
|
1264
1356
|
hash: entry.contentHash
|
|
1265
1357
|
}))) !== source.evidenceDigest) throw new Error("Generation evidence changed");
|
|
@@ -1583,6 +1675,6 @@ var IngestionService = class {
|
|
|
1583
1675
|
}
|
|
1584
1676
|
};
|
|
1585
1677
|
//#endregion
|
|
1586
|
-
export { EmailSourceAdapter, IngestionService, WatchFolderSourceAdapter, createSDKExtractionAdapter, createSourceDeliveryHandler, createVendorWebhookHandler, extractAnalysis, proposeDocumentSplits };
|
|
1678
|
+
export { EmailSourceAdapter, GenerationSnapshotStaleError, IngestionService, WatchFolderSourceAdapter, createSDKExtractionAdapter, createSourceDeliveryHandler, createVendorWebhookHandler, extractAnalysis, proposeDocumentSplits };
|
|
1587
1679
|
|
|
1588
1680
|
//# sourceMappingURL=server.js.map
|