@happyvertical/smrt-ingestion 0.55.5 → 0.55.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/AGENTS.md +13 -3
  2. package/FEEDBACK.md +117 -0
  3. package/PROPOSALS.md +173 -0
  4. package/README.md +14 -0
  5. package/REVIEW.md +136 -0
  6. package/agents/execution.md +132 -0
  7. package/dist/chunks/execution-CYjtj9_l.js +8031 -0
  8. package/dist/chunks/execution-CYjtj9_l.js.map +1 -0
  9. package/dist/chunks/execution-internal-DWteDHM2.js +7 -0
  10. package/dist/chunks/execution-internal-DWteDHM2.js.map +1 -0
  11. package/dist/chunks/feedback-B_NILilH.js +577 -0
  12. package/dist/chunks/feedback-B_NILilH.js.map +1 -0
  13. package/dist/chunks/proposal-errors-z24VKWnQ.js +301 -0
  14. package/dist/chunks/proposal-errors-z24VKWnQ.js.map +1 -0
  15. package/dist/dto.d.ts +3 -0
  16. package/dist/dto.d.ts.map +1 -1
  17. package/dist/execution-contracts.d.ts +3 -0
  18. package/dist/execution-contracts.d.ts.map +1 -1
  19. package/dist/execution-internal.d.ts +9 -0
  20. package/dist/execution-internal.d.ts.map +1 -0
  21. package/dist/execution.d.ts +23 -1
  22. package/dist/execution.d.ts.map +1 -1
  23. package/dist/execution.js +1 -7614
  24. package/dist/extraction.d.ts.map +1 -1
  25. package/dist/feedback-contracts.d.ts +40 -0
  26. package/dist/feedback-contracts.d.ts.map +1 -0
  27. package/dist/feedback-dto.d.ts +100 -0
  28. package/dist/feedback-dto.d.ts.map +1 -0
  29. package/dist/feedback-internal.d.ts +19 -0
  30. package/dist/feedback-internal.d.ts.map +1 -0
  31. package/dist/feedback.d.ts +53 -0
  32. package/dist/feedback.d.ts.map +1 -0
  33. package/dist/manifest.json +13 -1
  34. package/dist/models.js +1 -1
  35. package/dist/models.js.map +1 -1
  36. package/dist/proposal-contracts.d.ts +89 -0
  37. package/dist/proposal-contracts.d.ts.map +1 -0
  38. package/dist/proposal-dto.d.ts +124 -0
  39. package/dist/proposal-dto.d.ts.map +1 -0
  40. package/dist/proposal-errors.d.ts +5 -0
  41. package/dist/proposal-errors.d.ts.map +1 -0
  42. package/dist/proposal-sdk.d.ts +12 -0
  43. package/dist/proposal-sdk.d.ts.map +1 -0
  44. package/dist/proposal-validation.d.ts +188 -0
  45. package/dist/proposal-validation.d.ts.map +1 -0
  46. package/dist/proposals.d.ts +44 -0
  47. package/dist/proposals.d.ts.map +1 -0
  48. package/dist/proposals.js +964 -0
  49. package/dist/proposals.js.map +1 -0
  50. package/dist/review-dto.d.ts +115 -0
  51. package/dist/review-dto.d.ts.map +1 -0
  52. package/dist/server.d.ts +56 -0
  53. package/dist/server.d.ts.map +1 -1
  54. package/dist/server.js +263 -341
  55. package/dist/server.js.map +1 -1
  56. package/dist/smrt-knowledge.json +87 -12
  57. package/dist/svelte/components/ActionReview.svelte +232 -0
  58. package/dist/svelte/components/ActionReview.svelte.d.ts +15 -0
  59. package/dist/svelte/components/ActionReview.svelte.d.ts.map +1 -0
  60. package/dist/svelte/components/EvidenceViewer.svelte +31 -0
  61. package/dist/svelte/components/EvidenceViewer.svelte.d.ts +8 -0
  62. package/dist/svelte/components/EvidenceViewer.svelte.d.ts.map +1 -0
  63. package/dist/svelte/components/InboxView.svelte +119 -0
  64. package/dist/svelte/components/InboxView.svelte.d.ts +9 -0
  65. package/dist/svelte/components/InboxView.svelte.d.ts.map +1 -0
  66. package/dist/svelte/components/IntakeInbox.svelte +11 -0
  67. package/dist/svelte/components/IntakeInbox.svelte.d.ts +10 -0
  68. package/dist/svelte/components/IntakeInbox.svelte.d.ts.map +1 -0
  69. package/dist/svelte/components/IntakeReview.svelte +11 -0
  70. package/dist/svelte/components/IntakeReview.svelte.d.ts +10 -0
  71. package/dist/svelte/components/IntakeReview.svelte.d.ts.map +1 -0
  72. package/dist/svelte/components/ReviewView.svelte +243 -0
  73. package/dist/svelte/components/ReviewView.svelte.d.ts +9 -0
  74. package/dist/svelte/components/ReviewView.svelte.d.ts.map +1 -0
  75. package/dist/svelte/i18n.d.ts +72 -0
  76. package/dist/svelte/i18n.d.ts.map +1 -0
  77. package/dist/svelte/i18n.js +72 -0
  78. package/dist/svelte/index.d.ts +5 -0
  79. package/dist/svelte/index.d.ts.map +1 -0
  80. package/dist/svelte/index.js +3 -0
  81. package/dist/svelte/view-utils.d.ts +13 -0
  82. package/dist/svelte/view-utils.d.ts.map +1 -0
  83. package/dist/svelte/view-utils.js +96 -0
  84. package/package.json +53 -22
  85. package/dist/execution.js.map +0 -1
@@ -0,0 +1,7 @@
1
+ //#region src/execution-internal.ts
2
+ var discoveryTransaction = /* @__PURE__ */ Symbol("ingestion.discoveryTransaction");
3
+ var generationSnapshotRead = /* @__PURE__ */ Symbol("ingestion.generationSnapshotRead");
4
+ //#endregion
5
+ export { generationSnapshotRead as n, discoveryTransaction as t };
6
+
7
+ //# sourceMappingURL=execution-internal-DWteDHM2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-internal-DWteDHM2.js","names":[],"sources":["../../src/execution-internal.ts"],"sourcesContent":["import type { DatabaseInterface } from '@happyvertical/sql';\nimport type { HandlerContext } from './execution-contracts.js';\n\n// Package-internal transaction bridge. Neither symbol is a public package export.\nexport const discoveryTransaction = Symbol('ingestion.discoveryTransaction');\nexport const generationSnapshotRead = Symbol(\n 'ingestion.generationSnapshotRead',\n);\nexport type TransactionRunner<T> = (\n work: (db: DatabaseInterface) => Promise<T>,\n) => Promise<T>;\nexport type DiscoveryGate = <T>(\n handlerId: string,\n handlerVersion: string,\n work: (\n context: HandlerContext,\n currentTarget: (model: string, id: string) => Promise<{ revision: string }>,\n ) => Promise<T>,\n) => Promise<T>;\n"],"mappings":";AAIO,IAAM,uBAAuB,uBAAO,gCAAgC;AACpE,IAAM,yBAAyB,uBACpC,kCACF"}
@@ -0,0 +1,577 @@
1
+ import { a as feedbackActionTransaction, c as __exportAll, o as feedbackDiscoveryContext, r as intakeBindingDigest, t as IntakeExecutionService } from "./execution-CYjtj9_l.js";
2
+ import { randomUUID } from "node:crypto";
3
+ //#region src/feedback.ts
4
+ var feedback_exports = /* @__PURE__ */ __exportAll({
5
+ IntakeFeedbackService: () => IntakeFeedbackService,
6
+ feedbackSimilarity: () => feedbackSimilarity,
7
+ feedbackTerms: () => feedbackTerms
8
+ });
9
+ function object(value) {
10
+ const result = typeof value === "string" ? JSON.parse(value) : value;
11
+ if (!result || typeof result !== "object" || Array.isArray(result)) throw new Error("Feedback unavailable");
12
+ return result;
13
+ }
14
+ function text(value, max = 512) {
15
+ if (typeof value !== "string" || !value.trim() || value.length > max) throw new Error("Invalid feedback");
16
+ }
17
+ function resultData(data) {
18
+ return {
19
+ status: data.status,
20
+ provider: data.provider,
21
+ model: data.model,
22
+ version: data.version,
23
+ output: data.output,
24
+ usage: data.usage,
25
+ ...Object.hasOwn(data, "error") ? { error: data.error } : {},
26
+ ...Object.hasOwn(data, "confidence") ? { confidence: data.confidence } : {}
27
+ };
28
+ }
29
+ function feedbackTerms(value) {
30
+ return [...new Set(value.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [])].sort();
31
+ }
32
+ function feedbackSimilarity(left, right) {
33
+ const a = new Set(feedbackTerms(left)), b = new Set(feedbackTerms(right));
34
+ const union = /* @__PURE__ */ new Set([...a, ...b]);
35
+ return union.size ? [...a].filter((value) => b.has(value)).length / union.size : 0;
36
+ }
37
+ var IntakeFeedbackService = class {
38
+ constructor(options, execution) {
39
+ this.options = options;
40
+ if (!options.execution || !options.feedback) throw new Error("Feedback not configured");
41
+ this.config = { ...options.feedback };
42
+ this.execution = execution ?? new IntakeExecutionService(options, options.execution);
43
+ text(this.config.version);
44
+ for (const [value, max] of [
45
+ [this.config.maxExamples, 20],
46
+ [this.config.maxScan, 1e3],
47
+ [this.config.maxBytes, 1e6]
48
+ ]) if (!Number.isSafeInteger(value) || value < 1 || value > max) throw new Error("Invalid feedback limits");
49
+ if (!Number.isFinite(this.config.minimumSimilarity) || this.config.minimumSimilarity < .2 || this.config.minimumSimilarity > 1) throw new Error("Invalid feedback similarity");
50
+ }
51
+ options;
52
+ execution;
53
+ config;
54
+ lineage = /* @__PURE__ */ new Set();
55
+ exampleChecks = 0;
56
+ configurationDigest() {
57
+ const { version, maxExamples, maxScan, maxBytes, minimumSimilarity } = this.config;
58
+ return intakeBindingDigest({
59
+ version,
60
+ maxExamples,
61
+ maxScan,
62
+ maxBytes,
63
+ minimumSimilarity
64
+ });
65
+ }
66
+ scope() {
67
+ return this.options.scope;
68
+ }
69
+ async rows(db, where, params = []) {
70
+ const scope = this.scope();
71
+ return (await db.query(`SELECT * FROM intake_feedback WHERE tenant_id=? AND confidential_scope_id=? AND ${where}`, scope.tenantId, scope.confidentialScopeId, ...params)).rows;
72
+ }
73
+ async authorize(ctx, operation) {
74
+ if (!await this.config.authorize({
75
+ db: ctx.db,
76
+ scope: this.scope(),
77
+ itemId: ctx.itemId,
78
+ operation
79
+ })) throw new Error("Feedback unavailable");
80
+ }
81
+ checkBinding(input, ctx) {
82
+ if (input.itemId !== ctx.itemId || input.actionId !== ctx.review.actionId || input.expectedRevision !== ctx.review.revision || input.expectedReviewVersion !== ctx.review.reviewVersion || input.bindingHash !== ctx.review.bindingHash) throw new Error("Stale feedback binding");
83
+ }
84
+ async insert(ctx, key, kind, data, supersedesId = null) {
85
+ const serialized = JSON.stringify(data);
86
+ if (Buffer.byteLength(serialized) > Math.min(this.config.maxBytes, ctx.context.policy.maxBytes)) throw new Error("Feedback limit");
87
+ const scope = this.scope(), now = (this.options.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
88
+ const row = {
89
+ id: randomUUID(),
90
+ slug: randomUUID(),
91
+ context: "",
92
+ created_at: now,
93
+ updated_at: now,
94
+ tenant_id: scope.tenantId,
95
+ confidential_scope_id: scope.confidentialScopeId,
96
+ item_id: ctx.itemId,
97
+ request_key: key,
98
+ supersedes_id: supersedesId,
99
+ kind,
100
+ data: serialized
101
+ };
102
+ return (await ctx.db.query(`INSERT INTO intake_feedback (${Object.keys(row).join(",")}) VALUES (${Object.keys(row).map(() => "?").join(",")}) RETURNING *`, ...Object.values(row))).rows[0];
103
+ }
104
+ receipt(row) {
105
+ return {
106
+ id: String(row.id),
107
+ digest: String(object(row.data).digest),
108
+ kind: row.kind
109
+ };
110
+ }
111
+ async provenance(ctx) {
112
+ const generation = object(object(ctx.result.output).proposals), pin = object(generation.source), provenance = object(generation.provenance);
113
+ if (provenance.feedback) {
114
+ const inherited = object(provenance.feedback);
115
+ const key = ctx.attemptId;
116
+ if (this.lineage.has(key) || this.lineage.size >= 8) throw new Error("Feedback lineage unavailable");
117
+ this.lineage.add(key);
118
+ try {
119
+ await this.assertSelection({
120
+ itemId: ctx.itemId,
121
+ query: String(inherited.query),
122
+ offered: generation.offered,
123
+ promptVersion: String(provenance.promptVersion),
124
+ configurationVersion: String(provenance.configurationVersion),
125
+ model: provenance.generative,
126
+ providers: [String(object(provenance.generative).provider), ...object(provenance.decision).configured ? [String(object(provenance.decision).provider)] : []]
127
+ }, inherited.selection, ctx.db);
128
+ } finally {
129
+ this.lineage.delete(key);
130
+ }
131
+ }
132
+ const scope = this.scope();
133
+ const [source] = (await ctx.db.query("SELECT data,output_digest FROM intake_analysis_attempts WHERE tenant_id=? AND confidential_scope_id=? AND item_id=? AND id=?", scope.tenantId, scope.confidentialScopeId, ctx.itemId, pin.attemptId)).rows;
134
+ if (!source || source.output_digest !== pin.outputDigest) throw new Error("Feedback source unavailable");
135
+ const sourceResult = resultData(object(source.data));
136
+ if (intakeBindingDigest(sourceResult) !== pin.outputDigest) throw new Error("Feedback source integrity");
137
+ const results = object(sourceResult.output).results;
138
+ if (!Array.isArray(results)) throw new Error("Feedback extraction unavailable");
139
+ const query = results.flatMap((entry) => {
140
+ const segments = object(entry).segments;
141
+ return Array.isArray(segments) ? segments.map((segment) => String(object(segment).text ?? "")) : [];
142
+ }).join("\n").slice(0, 4096);
143
+ const model = object(provenance.generative);
144
+ for (const field of [
145
+ "provider",
146
+ "model",
147
+ "version"
148
+ ]) text(model[field]);
149
+ text(provenance.promptVersion);
150
+ text(provenance.configurationVersion);
151
+ return {
152
+ query,
153
+ model: {
154
+ provider: String(model.provider),
155
+ model: String(model.model),
156
+ version: String(model.version)
157
+ },
158
+ promptVersion: String(provenance.promptVersion),
159
+ configurationVersion: String(provenance.configurationVersion),
160
+ attemptId: ctx.attemptId,
161
+ evidence: object(ctx.analysis).inputs,
162
+ source: pin
163
+ };
164
+ }
165
+ /** Internal saved-action gate; the execution owner supplies its transaction. */
166
+ async assertActionLineage(actionId, db, proposalId) {
167
+ await this.execution[feedbackActionTransaction](actionId, false, async (ctx) => {
168
+ await this.provenance(ctx);
169
+ }, db, void 0, proposalId);
170
+ }
171
+ async record(input) {
172
+ intakeBindingDigest(input);
173
+ text(input.requestId);
174
+ if (!["correct", "incorrect"].includes(input.judgment) || input.comment !== void 0 && (typeof input.comment !== "string" || input.comment.length > 4e3)) throw new Error("Invalid feedback");
175
+ const frozen = structuredClone(input);
176
+ return this.execution[feedbackActionTransaction](input.actionId, true, async (ctx) => {
177
+ await this.authorize(ctx, "capture");
178
+ this.checkBinding(frozen, ctx);
179
+ const requestHash = intakeBindingDigest(frozen), key = `judgment:${frozen.requestId}`;
180
+ const [replay] = await this.rows(ctx.db, "item_id=? AND request_key=?", [ctx.itemId, key]);
181
+ if (replay) {
182
+ const data = object(replay.data);
183
+ if (data.requestHash !== requestHash || data.reviewer !== this.scope().actorId) throw new Error("Feedback request conflict");
184
+ return this.receipt(replay);
185
+ }
186
+ if (frozen.supersedesId) {
187
+ const [old] = await this.rows(ctx.db, "item_id=? AND id=? AND kind=?", [
188
+ ctx.itemId,
189
+ frozen.supersedesId,
190
+ "interpretation"
191
+ ]);
192
+ if (!old || object(old.data).actionId !== frozen.actionId || (await this.rows(ctx.db, "supersedes_id=?", [old.id])).length) throw new Error("Feedback supersession conflict");
193
+ }
194
+ const provenance = await this.provenance(ctx);
195
+ const body = {
196
+ version: 1,
197
+ requestHash,
198
+ reviewer: this.scope().actorId,
199
+ actionId: ctx.review.actionId,
200
+ revision: ctx.review.revision,
201
+ reviewVersion: ctx.review.reviewVersion,
202
+ bindingHash: ctx.review.bindingHash,
203
+ handlerId: ctx.handlerId,
204
+ handlerVersion: ctx.handlerVersion,
205
+ args: ctx.args,
206
+ judgment: frozen.judgment,
207
+ comment: frozen.comment ?? "",
208
+ ...provenance
209
+ };
210
+ const row = await this.insert(ctx, key, "interpretation", {
211
+ ...body,
212
+ digest: intakeBindingDigest(body)
213
+ }, frozen.supersedesId ?? null);
214
+ await this.authorize(ctx, "capture");
215
+ return this.receipt(row);
216
+ });
217
+ }
218
+ /** Consume a durable owner event idempotently. Never derive correctness from these signals. */
219
+ async observe(input) {
220
+ intakeBindingDigest(input);
221
+ text(input.eventId);
222
+ if (!["action_decision", "downstream_outcome"].includes(input.kind)) throw new Error("Invalid feedback kind");
223
+ return this.execution[feedbackActionTransaction](input.actionId, true, async (ctx) => {
224
+ await this.authorize(ctx, "capture");
225
+ this.checkBinding(input, ctx);
226
+ const scope = this.scope();
227
+ const table = input.kind === "action_decision" ? "intake_review_decisions" : "intake_executions";
228
+ const [event] = (await ctx.db.query(`SELECT * FROM ${table} WHERE tenant_id=? AND confidential_scope_id=? AND item_id=? AND action_id=? AND id=?`, scope.tenantId, scope.confidentialScopeId, ctx.itemId, input.actionId, input.eventId)).rows;
229
+ if (!event || event.binding_hash !== ctx.review.bindingHash) throw new Error("Feedback event unavailable");
230
+ const state = input.kind === "action_decision" ? event.decision : event.state;
231
+ if (input.kind === "downstream_outcome" && ![
232
+ "succeeded",
233
+ "failed",
234
+ "outcome_unknown"
235
+ ].includes(String(state))) throw new Error("Outcome is not terminal");
236
+ const body = {
237
+ version: 1,
238
+ eventId: input.eventId,
239
+ actionId: input.actionId,
240
+ revision: ctx.review.revision,
241
+ reviewer: input.kind === "action_decision" ? object(event.data).reviewer : null,
242
+ observedBy: this.scope().actorId,
243
+ principalId: object(event.data).principalId ?? null,
244
+ signal: state,
245
+ provenance: await this.provenance(ctx)
246
+ };
247
+ const key = `event:${input.kind}:${input.eventId}:${String(state)}`;
248
+ const [existing] = await this.rows(ctx.db, "item_id=? AND request_key=?", [ctx.itemId, key]);
249
+ if (existing) return this.receipt(existing);
250
+ const row = await this.insert(ctx, key, input.kind, {
251
+ ...body,
252
+ digest: intakeBindingDigest(body)
253
+ });
254
+ await this.authorize(ctx, "capture");
255
+ return this.receipt(row);
256
+ });
257
+ }
258
+ async example(row, input, executor) {
259
+ if (++this.exampleChecks > this.config.maxScan) return void 0;
260
+ try {
261
+ const candidate = object(row.data);
262
+ if (typeof candidate.actionId !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(candidate.actionId)) return void 0;
263
+ return await this.execution[feedbackActionTransaction](candidate.actionId, false, async (ctx) => {
264
+ await this.authorize(ctx, "retrieve");
265
+ await this.provenance(ctx);
266
+ const [current] = await this.rows(ctx.db, "id=? AND kind=? AND NOT EXISTS (SELECT 1 FROM intake_feedback successor WHERE successor.tenant_id=intake_feedback.tenant_id AND successor.confidential_scope_id=intake_feedback.confidential_scope_id AND successor.supersedes_id=intake_feedback.id)", [row.id, "interpretation"]);
267
+ if (!current) return void 0;
268
+ const data = object(current.data);
269
+ const { digest: stored, ...body } = data;
270
+ if (intakeBindingDigest(body) !== stored) throw new Error("Feedback integrity");
271
+ if (data.revision !== ctx.review.revision || data.bindingHash !== ctx.review.bindingHash || data.attemptId !== ctx.attemptId || data.promptVersion !== input.promptVersion || data.configurationVersion !== input.configurationVersion || intakeBindingDigest(data.model) !== intakeBindingDigest(input.model)) return void 0;
272
+ const offered = input.offered.find((entry) => entry.handler.id === data.handlerId && entry.handler.version === data.handlerVersion);
273
+ if (!offered) return void 0;
274
+ const args = object(data.args);
275
+ for (const [field, reference] of Object.entries(offered.handler.references)) if (reference.kind === "candidate" && Object.hasOwn(args, field) && !offered.candidates.some((candidate2) => candidate2.id === args[field] && candidate2.model === reference.model)) return void 0;
276
+ return {
277
+ feedbackId: String(current.id),
278
+ digest: String(stored),
279
+ itemId: ctx.itemId,
280
+ actionId: ctx.review.actionId,
281
+ revision: ctx.review.revision,
282
+ handlerId: ctx.handlerId,
283
+ handlerVersion: ctx.handlerVersion,
284
+ judgment: data.judgment,
285
+ query: String(data.query),
286
+ args,
287
+ model: data.model,
288
+ promptVersion: String(data.promptVersion),
289
+ configurationVersion: String(data.configurationVersion)
290
+ };
291
+ }, executor, input.providers ?? [input.model.provider]);
292
+ } catch {
293
+ return;
294
+ }
295
+ }
296
+ receivingContext(input, executor, work) {
297
+ let limit = this.config.maxBytes;
298
+ let bytes;
299
+ const validate = async (context) => {
300
+ if ((input.providers ?? [input.model.provider]).some((provider) => !context.policy.providers.includes(provider))) throw new Error("Feedback recipient unavailable");
301
+ limit = Math.min(limit, context.policy.maxBytes);
302
+ if (bytes !== void 0 && bytes > limit) throw new Error("Feedback limit");
303
+ };
304
+ const visit = (index, db) => {
305
+ const entry = input.offered[index];
306
+ return this.execution[feedbackDiscoveryContext](db, input.itemId, entry.handler.id, entry.handler.version, async (context) => {
307
+ await validate(context);
308
+ const selection = index + 1 < input.offered.length ? await visit(index + 1, context.db) : await work(context);
309
+ bytes = Buffer.byteLength(JSON.stringify(selection));
310
+ if (bytes > limit) throw new Error("Feedback limit");
311
+ return selection;
312
+ }, validate);
313
+ };
314
+ return visit(0, executor);
315
+ }
316
+ async retrieve(input, executor) {
317
+ text(input.query, 4096);
318
+ if (!input.offered[0]) return {
319
+ version: this.config.version,
320
+ configurationDigest: this.configurationDigest(),
321
+ examples: []
322
+ };
323
+ const work = async (context) => {
324
+ const authorize = async () => {
325
+ if (!await this.config.authorize({
326
+ db: context.db,
327
+ scope: this.scope(),
328
+ itemId: input.itemId,
329
+ operation: "retrieve"
330
+ })) throw new Error("Feedback unavailable");
331
+ };
332
+ await authorize();
333
+ const scope = this.scope();
334
+ const rows = (await context.db.query("SELECT * FROM intake_feedback WHERE tenant_id=? AND confidential_scope_id=? AND kind='interpretation' ORDER BY id LIMIT ?", scope.tenantId, scope.confidentialScopeId, this.config.maxScan)).rows;
335
+ const ranked = [];
336
+ for (const row of rows) {
337
+ const example = await this.example(row, input, context.db);
338
+ if (!example) continue;
339
+ const score = feedbackSimilarity(input.query, example.query);
340
+ if (score >= this.config.minimumSimilarity) ranked.push({
341
+ example,
342
+ score
343
+ });
344
+ }
345
+ ranked.sort((a, b) => b.score - a.score || a.example.feedbackId.localeCompare(b.example.feedbackId));
346
+ const selection = {
347
+ version: this.config.version,
348
+ configurationDigest: this.configurationDigest(),
349
+ examples: ranked.slice(0, this.config.maxExamples).map((entry) => entry.example)
350
+ };
351
+ await authorize();
352
+ return selection;
353
+ };
354
+ return this.receivingContext(input, executor, work);
355
+ }
356
+ async assertSelection(input, selection, executor) {
357
+ if (selection.version !== this.config.version || selection.configurationDigest !== this.configurationDigest() || !Array.isArray(selection.examples) || selection.examples.length > this.config.maxExamples || new Set(selection.examples.map((example) => example.feedbackId)).size !== selection.examples.length || Buffer.byteLength(JSON.stringify(selection)) > this.config.maxBytes) throw new Error("Feedback eligibility changed");
358
+ if (!input.offered[0]) {
359
+ if (selection.examples.length) throw new Error("Feedback eligibility changed");
360
+ return;
361
+ }
362
+ const work = async (context) => {
363
+ if (!await this.config.authorize({
364
+ db: context.db,
365
+ scope: this.scope(),
366
+ itemId: input.itemId,
367
+ operation: "retrieve"
368
+ })) throw new Error("Feedback unavailable");
369
+ const examples = [];
370
+ for (const expected of selection.examples) {
371
+ const [row] = await this.rows(context.db, "id=? AND kind=?", [expected.feedbackId, "interpretation"]);
372
+ const current = row ? await this.example(row, input, context.db) : void 0;
373
+ if (!current || current.digest !== expected.digest || feedbackSimilarity(input.query, current.query) < this.config.minimumSimilarity) throw new Error("Feedback eligibility changed");
374
+ examples.push(current);
375
+ }
376
+ if (!await this.config.authorize({
377
+ db: context.db,
378
+ scope: this.scope(),
379
+ itemId: input.itemId,
380
+ operation: "retrieve"
381
+ })) throw new Error("Feedback unavailable");
382
+ return {
383
+ ...selection,
384
+ examples
385
+ };
386
+ };
387
+ await this.receivingContext(input, executor, work);
388
+ }
389
+ async support(ctx, ids) {
390
+ if (!Array.isArray(ids) || ids.length < 2 || ids.length > this.config.maxExamples || new Set(ids).size !== ids.length) throw new Error("Insufficient rule evidence");
391
+ const provenance = await this.provenance(ctx), origins = /* @__PURE__ */ new Set(), result = [];
392
+ for (const id of [...ids].sort()) {
393
+ text(id);
394
+ const [row] = await this.rows(ctx.db, "id=? AND kind=?", [id, "interpretation"]);
395
+ if (!row) throw new Error("Rule evidence unavailable");
396
+ const data = object(row.data);
397
+ await this.execution[feedbackActionTransaction](String(data.actionId), false, async (source) => {
398
+ await this.authorize(source, "retrieve");
399
+ await this.provenance(source);
400
+ if (origins.has(source.itemId)) throw new Error("Independent rule evidence required");
401
+ origins.add(source.itemId);
402
+ const { digest: stored, ...body } = data;
403
+ if (intakeBindingDigest(body) !== stored || data.judgment !== "correct" || data.revision !== source.review.revision || data.bindingHash !== source.review.bindingHash || data.handlerId !== ctx.handlerId || data.handlerVersion !== ctx.handlerVersion || intakeBindingDigest(data.args) !== intakeBindingDigest(ctx.args) || data.promptVersion !== provenance.promptVersion || data.configurationVersion !== provenance.configurationVersion || intakeBindingDigest(data.model) !== intakeBindingDigest(provenance.model) || (await this.rows(ctx.db, "supersedes_id=?", [id])).length) throw new Error("Rule evidence unavailable");
404
+ result.push({
405
+ id,
406
+ digest: String(stored),
407
+ query: String(data.query)
408
+ });
409
+ }, ctx.db);
410
+ }
411
+ return result;
412
+ }
413
+ async suggest(input) {
414
+ intakeBindingDigest(input);
415
+ text(input.requestId);
416
+ if (!this.config.policy) throw new Error("Rule policy owner unavailable");
417
+ return this.execution[feedbackActionTransaction](input.actionId, true, async (ctx) => {
418
+ this.checkBinding(input, ctx);
419
+ await this.authorize(ctx, "suggest");
420
+ const support = await this.support(ctx, input.supportingFeedbackIds);
421
+ const terms = feedbackTerms(support[0].query).filter((term) => support.every((example) => feedbackTerms(example.query).includes(term)));
422
+ if (!terms.length) throw new Error("No shared routing evidence");
423
+ const rule = {
424
+ handlerId: ctx.handlerId,
425
+ handlerVersion: ctx.handlerVersion,
426
+ matchTerms: terms,
427
+ args: ctx.args
428
+ };
429
+ const preview = await this.config.policy.preview({
430
+ db: ctx.db,
431
+ scope: this.scope(),
432
+ rule: structuredClone(rule)
433
+ });
434
+ text(preview.version);
435
+ text(preview.preview, 4e3);
436
+ const body = {
437
+ version: 1,
438
+ actionId: input.actionId,
439
+ revision: ctx.review.revision,
440
+ bindingHash: ctx.review.bindingHash,
441
+ rule,
442
+ supportingFeedback: support.map(({ id, digest: digest2 }) => ({
443
+ id,
444
+ digest: digest2
445
+ })),
446
+ expectedPolicyVersion: preview.version,
447
+ preview: preview.preview,
448
+ reviewer: this.scope().actorId,
449
+ requestHash: intakeBindingDigest(input)
450
+ };
451
+ const key = `rule:${input.requestId}`;
452
+ let [row] = await this.rows(ctx.db, "item_id=? AND request_key=?", [ctx.itemId, key]);
453
+ if (row) {
454
+ if (object(row.data).requestHash !== body.requestHash) throw new Error("Rule request conflict");
455
+ } else row = await this.insert(ctx, key, "rule_suggestion", {
456
+ ...body,
457
+ digest: intakeBindingDigest(body)
458
+ });
459
+ await this.authorize(ctx, "suggest");
460
+ return this.ruleDTO(row);
461
+ });
462
+ }
463
+ ruleDTO(row) {
464
+ const data = object(row.data);
465
+ return {
466
+ id: String(row.id),
467
+ digest: String(data.digest),
468
+ itemId: String(row.item_id),
469
+ rule: data.rule,
470
+ supportingFeedback: data.supportingFeedback,
471
+ expectedPolicyVersion: String(data.expectedPolicyVersion),
472
+ preview: String(data.preview),
473
+ automaticActionEligible: false
474
+ };
475
+ }
476
+ async rule(ctx, id) {
477
+ const [row] = await this.rows(ctx.db, "item_id=? AND id=? AND kind=?", [
478
+ ctx.itemId,
479
+ id,
480
+ "rule_suggestion"
481
+ ]);
482
+ if (!row) throw new Error("Rule unavailable");
483
+ const data = object(row.data), { digest: stored, ...body } = data;
484
+ if (intakeBindingDigest(body) !== stored || data.actionId !== ctx.review.actionId || data.revision !== ctx.review.revision || data.bindingHash !== ctx.review.bindingHash) throw new Error("Stale rule");
485
+ const support = data.supportingFeedback;
486
+ if (intakeBindingDigest((await this.support(ctx, support.map((entry) => entry.id))).map(({ id: id2, digest: digest2 }) => ({
487
+ id: id2,
488
+ digest: digest2
489
+ }))) !== intakeBindingDigest(support)) throw new Error("Rule evidence changed");
490
+ return row;
491
+ }
492
+ async readRule(input) {
493
+ return this.execution[feedbackActionTransaction](input.actionId, true, async (ctx) => {
494
+ this.checkBinding(input, ctx);
495
+ await this.authorize(ctx, "suggest");
496
+ const suggestion = this.ruleDTO(await this.rule(ctx, input.suggestionId));
497
+ if (!this.config.policy) throw new Error("Rule policy owner unavailable");
498
+ const current = await this.config.policy.preview({
499
+ db: ctx.db,
500
+ scope: this.scope(),
501
+ rule: structuredClone(suggestion.rule)
502
+ });
503
+ if (current.version !== suggestion.expectedPolicyVersion || current.preview !== suggestion.preview) throw new Error("Rule policy changed");
504
+ await this.authorize(ctx, "suggest");
505
+ return suggestion;
506
+ });
507
+ }
508
+ async adopt(input) {
509
+ intakeBindingDigest(input);
510
+ text(input.requestId);
511
+ if (!this.config.policy) throw new Error("Rule policy owner unavailable");
512
+ return this.execution[feedbackActionTransaction](input.actionId, true, async (ctx) => {
513
+ this.checkBinding(input, ctx);
514
+ await this.authorize(ctx, "adopt");
515
+ const row = await this.rule(ctx, input.suggestionId), suggestion = this.ruleDTO(row);
516
+ if (input.expectedDigest !== suggestion.digest || input.expectedPolicyVersion !== suggestion.expectedPolicyVersion) throw new Error("Stale rule");
517
+ const requestHash = intakeBindingDigest(input), key = `adopt:${input.requestId}`;
518
+ const [replay] = await this.rows(ctx.db, "item_id=? AND request_key=?", [ctx.itemId, key]);
519
+ if (replay) {
520
+ const data = object(replay.data);
521
+ if (data.requestHash !== requestHash || data.reviewer !== this.scope().actorId) throw new Error("Rule request conflict");
522
+ return {
523
+ id: String(replay.id),
524
+ policyVersion: String(data.policyVersion),
525
+ auditId: String(data.auditId),
526
+ automaticActionEligible: false
527
+ };
528
+ }
529
+ if ((await this.config.policy.preview({
530
+ db: ctx.db,
531
+ scope: this.scope(),
532
+ rule: structuredClone(suggestion.rule)
533
+ })).version !== input.expectedPolicyVersion) throw new Error("Policy version changed");
534
+ const applied = await this.config.policy.adopt({
535
+ db: ctx.db,
536
+ scope: this.scope(),
537
+ rule: structuredClone(suggestion.rule),
538
+ expectedVersion: input.expectedPolicyVersion,
539
+ requestId: input.requestId
540
+ });
541
+ text(applied.version);
542
+ text(applied.auditId);
543
+ if (applied.version === input.expectedPolicyVersion) throw new Error("Policy version did not advance");
544
+ if ((await this.config.policy.preview({
545
+ db: ctx.db,
546
+ scope: this.scope(),
547
+ rule: structuredClone(suggestion.rule)
548
+ })).version !== applied.version) throw new Error("Policy adoption mismatch");
549
+ const body = {
550
+ version: 1,
551
+ requestHash,
552
+ suggestionId: suggestion.id,
553
+ suggestionDigest: suggestion.digest,
554
+ previousPolicyVersion: input.expectedPolicyVersion,
555
+ policyVersion: applied.version,
556
+ auditId: applied.auditId,
557
+ reviewer: this.scope().actorId,
558
+ automaticActionEligible: false
559
+ };
560
+ const saved = await this.insert(ctx, key, "rule_adoption", {
561
+ ...body,
562
+ digest: intakeBindingDigest(body)
563
+ });
564
+ await this.authorize(ctx, "adopt");
565
+ return {
566
+ id: String(saved.id),
567
+ policyVersion: applied.version,
568
+ auditId: applied.auditId,
569
+ automaticActionEligible: false
570
+ };
571
+ });
572
+ }
573
+ };
574
+ //#endregion
575
+ export { feedback_exports as n, IntakeFeedbackService as t };
576
+
577
+ //# sourceMappingURL=feedback-B_NILilH.js.map