@happyvertical/smrt-ingestion 0.57.21 → 0.57.23
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/FEEDBACK.md +104 -0
- package/PROPOSALS.md +77 -0
- package/README.md +126 -0
- package/REVIEW.md +272 -4
- package/agents/execution.md +72 -0
- package/dist/chunks/{execution-9CfjxX8I.js → execution-BzVQax0n.js} +421 -23
- package/dist/chunks/execution-BzVQax0n.js.map +1 -0
- package/dist/chunks/{feedback-CuUzWKzP.js → feedback-D3UTF8fm.js} +119 -21
- package/dist/chunks/feedback-D3UTF8fm.js.map +1 -0
- package/dist/chunks/{extraction-CJ-zrbg0.js → proposal-contracts-77Uo1B1j.js} +39 -39
- package/dist/chunks/proposal-contracts-77Uo1B1j.js.map +1 -0
- package/dist/chunks/{reviewer-instruction-Db3jDN9a.js → reviewer-instruction-CEc3AOuU.js} +2 -2
- package/dist/chunks/{reviewer-instruction-Db3jDN9a.js.map → reviewer-instruction-CEc3AOuU.js.map} +1 -1
- package/dist/dto.d.ts +69 -0
- package/dist/dto.d.ts.map +1 -1
- package/dist/execution-contracts.d.ts +4 -1
- package/dist/execution-contracts.d.ts.map +1 -1
- package/dist/execution-dto.d.ts +16 -0
- package/dist/execution-dto.d.ts.map +1 -1
- package/dist/execution.d.ts +58 -3
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +2 -2
- package/dist/feedback-dto.d.ts +12 -4
- package/dist/feedback-dto.d.ts.map +1 -1
- package/dist/feedback-internal.d.ts +9 -0
- package/dist/feedback-internal.d.ts.map +1 -1
- package/dist/feedback.d.ts +3 -0
- package/dist/feedback.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/manifest.json +321 -3
- package/dist/models.d.ts +15 -0
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +73 -2
- package/dist/models.js.map +1 -1
- package/dist/policy.d.ts +3 -0
- package/dist/policy.d.ts.map +1 -1
- package/dist/proposal-errors.d.ts +4 -0
- package/dist/proposal-errors.d.ts.map +1 -1
- package/dist/proposal-sdk.d.ts +54 -1
- package/dist/proposal-sdk.d.ts.map +1 -1
- package/dist/proposals.d.ts +2 -1
- package/dist/proposals.d.ts.map +1 -1
- package/dist/proposals.js +215 -10
- package/dist/proposals.js.map +1 -1
- package/dist/provider-failure.d.ts +3 -0
- package/dist/provider-failure.d.ts.map +1 -0
- package/dist/redaction.d.ts +9 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/review-dto.d.ts +14 -4
- package/dist/review-dto.d.ts.map +1 -1
- package/dist/review-host.d.ts.map +1 -1
- package/dist/server.d.ts +82 -10
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +366 -47
- package/dist/server.js.map +1 -1
- package/dist/smrt-knowledge.json +128 -22
- package/dist/svelte/components/ActionReview.svelte +39 -9
- package/dist/svelte/components/ActionReview.svelte.d.ts +8 -1
- package/dist/svelte/components/ActionReview.svelte.d.ts.map +1 -1
- package/dist/svelte/components/EvidenceViewer.svelte +60 -4
- package/dist/svelte/components/EvidenceViewer.svelte.d.ts +12 -1
- package/dist/svelte/components/EvidenceViewer.svelte.d.ts.map +1 -1
- package/dist/svelte/components/InboxView.svelte +160 -18
- package/dist/svelte/components/InboxView.svelte.d.ts +28 -1
- package/dist/svelte/components/InboxView.svelte.d.ts.map +1 -1
- package/dist/svelte/components/IntakeWorkspace.svelte +27 -0
- package/dist/svelte/components/IntakeWorkspace.svelte.d.ts +19 -0
- package/dist/svelte/components/IntakeWorkspace.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ReviewView.svelte +264 -23
- package/dist/svelte/components/ReviewView.svelte.d.ts +18 -1
- package/dist/svelte/components/ReviewView.svelte.d.ts.map +1 -1
- package/dist/svelte/components/TriageView.svelte +669 -0
- package/dist/svelte/components/TriageView.svelte.d.ts +16 -0
- package/dist/svelte/components/TriageView.svelte.d.ts.map +1 -0
- package/dist/svelte/i18n.d.ts +39 -0
- package/dist/svelte/i18n.d.ts.map +1 -1
- package/dist/svelte/i18n.js +39 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +1 -0
- package/dist/svelte/view-utils.d.ts +65 -0
- package/dist/svelte/view-utils.d.ts.map +1 -1
- package/dist/svelte/view-utils.js +145 -0
- package/package.json +13 -13
- package/dist/chunks/execution-9CfjxX8I.js.map +0 -1
- package/dist/chunks/extraction-CJ-zrbg0.js.map +0 -1
- package/dist/chunks/feedback-CuUzWKzP.js.map +0 -1
|
@@ -56,6 +56,12 @@ var HandlerArgumentsInvalidError = class extends Error {
|
|
|
56
56
|
this.name = "HandlerArgumentsInvalidError";
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
|
+
var ProposalsNotConfiguredError = class extends Error {
|
|
60
|
+
constructor() {
|
|
61
|
+
super("Intake proposals are not configured");
|
|
62
|
+
this.name = "ProposalsNotConfiguredError";
|
|
63
|
+
}
|
|
64
|
+
};
|
|
59
65
|
var GenerationSourceStaleError = class extends Error {
|
|
60
66
|
constructor() {
|
|
61
67
|
super("Generation source superseded");
|
|
@@ -69,6 +75,18 @@ var GenerationSnapshotStaleError = class extends Error {
|
|
|
69
75
|
}
|
|
70
76
|
};
|
|
71
77
|
//#endregion
|
|
78
|
+
//#region src/redaction.ts
|
|
79
|
+
var UNFINISHED_ACTION_STATES = ["executing", "outcome_unknown"];
|
|
80
|
+
function redactedCeiling(ceiling, unfinishedActions) {
|
|
81
|
+
const execution = ceiling.execution;
|
|
82
|
+
if (unfinishedActions || !execution || typeof execution !== "object" || Array.isArray(execution) || !Object.hasOwn(execution, "principalId")) return ceiling;
|
|
83
|
+
const { principalId: _principal, ...rest } = execution;
|
|
84
|
+
return {
|
|
85
|
+
...ceiling,
|
|
86
|
+
execution: rest
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
72
90
|
//#region ../../node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/compile/codegen/code.js
|
|
73
91
|
var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
74
92
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -6401,6 +6419,7 @@ function resolveIntakePolicy(layers) {
|
|
|
6401
6419
|
providers: [],
|
|
6402
6420
|
access: [],
|
|
6403
6421
|
requireReview: false,
|
|
6422
|
+
submitterReview: false,
|
|
6404
6423
|
maxAttempts: 1,
|
|
6405
6424
|
maxSteps: 10,
|
|
6406
6425
|
maxBytes: 65536,
|
|
@@ -6414,6 +6433,7 @@ function resolveIntakePolicy(layers) {
|
|
|
6414
6433
|
for (const key of lists) result[key] = [...app[key] ?? []];
|
|
6415
6434
|
for (const key of limits) result[key] = app[key] ?? result[key];
|
|
6416
6435
|
result.minimumCertainty = app.minimumCertainty ?? 1;
|
|
6436
|
+
result.submitterReview = app.submitterReview === true;
|
|
6417
6437
|
result.automaticHandlers = [...app.automation?.handlers ?? []];
|
|
6418
6438
|
result.consequentialAutomation = app.automation?.consequential === true;
|
|
6419
6439
|
for (const [index, layer] of layers.entries()) {
|
|
@@ -6421,6 +6441,7 @@ function resolveIntakePolicy(layers) {
|
|
|
6421
6441
|
result.versions.push(layer.version);
|
|
6422
6442
|
result.enabled &&= layer.enabled !== false;
|
|
6423
6443
|
result.requireReview ||= layer.requireReview === true;
|
|
6444
|
+
result.submitterReview &&= layer.submitterReview !== false;
|
|
6424
6445
|
for (const key of lists) {
|
|
6425
6446
|
const values = layer[key];
|
|
6426
6447
|
if (values !== void 0) {
|
|
@@ -6450,6 +6471,12 @@ function resolveIntakePolicy(layers) {
|
|
|
6450
6471
|
}
|
|
6451
6472
|
//#endregion
|
|
6452
6473
|
//#region src/execution.ts
|
|
6474
|
+
var IntakeAccessDeniedError = class extends Error {
|
|
6475
|
+
constructor() {
|
|
6476
|
+
super("Intake unavailable");
|
|
6477
|
+
this.name = "IntakeAccessDeniedError";
|
|
6478
|
+
}
|
|
6479
|
+
};
|
|
6453
6480
|
var RetentionExpired = class extends Error {
|
|
6454
6481
|
constructor(itemId, deadline) {
|
|
6455
6482
|
super("Intake retention expired");
|
|
@@ -6459,6 +6486,9 @@ var RetentionExpired = class extends Error {
|
|
|
6459
6486
|
itemId;
|
|
6460
6487
|
deadline;
|
|
6461
6488
|
};
|
|
6489
|
+
var OWN_SCAN_BATCH = 50;
|
|
6490
|
+
var OWN_SCAN_CEILING = 1e3;
|
|
6491
|
+
var UUID_CURSOR = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6462
6492
|
function object(value) {
|
|
6463
6493
|
const parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
6464
6494
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Invalid intake binding");
|
|
@@ -6484,7 +6514,7 @@ function strings(value) {
|
|
|
6484
6514
|
function time(value) {
|
|
6485
6515
|
return new Date(value).getTime();
|
|
6486
6516
|
}
|
|
6487
|
-
var IntakeExecutionService = class {
|
|
6517
|
+
var IntakeExecutionService = class IntakeExecutionService {
|
|
6488
6518
|
constructor(options, config) {
|
|
6489
6519
|
this.options = options;
|
|
6490
6520
|
this.db = options.db;
|
|
@@ -6525,7 +6555,7 @@ var IntakeExecutionService = class {
|
|
|
6525
6555
|
async restoreRetention(db, itemId, deadline) {
|
|
6526
6556
|
const expiry = new Date(deadline).toISOString();
|
|
6527
6557
|
const item = (await db.query("UPDATE intake_items SET expires_at=CASE WHEN expires_at>? THEN ? ELSE expires_at END WHERE id=? AND tenant_id=? AND confidential_scope_id=? RETURNING id,expires_at,visibility", expiry, expiry, itemId, this.scope.tenantId, this.scope.confidentialScopeId)).rows[0];
|
|
6528
|
-
if (!item) throw new
|
|
6558
|
+
if (!item) throw new IntakeAccessDeniedError();
|
|
6529
6559
|
if (!this.retained(item)) await this.redactExecution(db, item);
|
|
6530
6560
|
return item;
|
|
6531
6561
|
}
|
|
@@ -6536,7 +6566,8 @@ var IntakeExecutionService = class {
|
|
|
6536
6566
|
"intake_review_decisions",
|
|
6537
6567
|
"intake_executions",
|
|
6538
6568
|
"intake_plans",
|
|
6539
|
-
"intake_feedback"
|
|
6569
|
+
"intake_feedback",
|
|
6570
|
+
"intake_dispositions"
|
|
6540
6571
|
]) await db.query(`UPDATE ${table} SET data='{}' WHERE item_id=? AND tenant_id=? AND confidential_scope_id=?`, item.id, this.scope.tenantId, this.scope.confidentialScopeId);
|
|
6541
6572
|
}
|
|
6542
6573
|
async narrowRetention(db, item, deadline) {
|
|
@@ -6572,8 +6603,8 @@ var IntakeExecutionService = class {
|
|
|
6572
6603
|
operation: "read",
|
|
6573
6604
|
capturedCeiling: object(object(item.data).capturedCeiling),
|
|
6574
6605
|
db
|
|
6575
|
-
})) throw new
|
|
6576
|
-
if (!expired && (item.visibility !== "active" || time(item.expires_at) <= this.now().getTime() || item.cancelled)) throw new
|
|
6606
|
+
})) throw new IntakeAccessDeniedError();
|
|
6607
|
+
if (!expired && (item.visibility !== "active" || time(item.expires_at) <= this.now().getTime() || item.cancelled)) throw new IntakeAccessDeniedError();
|
|
6577
6608
|
return item;
|
|
6578
6609
|
}
|
|
6579
6610
|
ceiling(item) {
|
|
@@ -6639,11 +6670,12 @@ var IntakeExecutionService = class {
|
|
|
6639
6670
|
onHostError?.(error);
|
|
6640
6671
|
throw error;
|
|
6641
6672
|
}
|
|
6642
|
-
if (!current.allowed || current.mutationBoundary !== "serialized" || current.principalId !== ceiling.principalId) throw new
|
|
6673
|
+
if (!current.allowed || current.mutationBoundary !== "serialized" || current.principalId !== ceiling.principalId) throw new IntakeAccessDeniedError();
|
|
6643
6674
|
const policy = resolveIntakePolicy(current.policy);
|
|
6644
6675
|
const operationKey = "model" in handler.operation ? `${handler.operation.model}:${handler.operation.action}@${handler.operation.version}` : handler.operation.playbookKey;
|
|
6645
|
-
if (!policy.enabled || !policy.handlers.includes(handler.id) || !ceiling.handlers.includes(handler.id) || !policy.operations.includes(operationKey) || !ceiling.operations.includes(operationKey) || !policy.access.includes(this.scope.confidentialScopeId)) throw new
|
|
6646
|
-
if (operation === "review" && (!current.reviewer || !policy.reviewers.includes(this.scope.actorId))) throw new
|
|
6676
|
+
if (!policy.enabled || !policy.handlers.includes(handler.id) || !ceiling.handlers.includes(handler.id) || !policy.operations.includes(operationKey) || !ceiling.operations.includes(operationKey) || !policy.access.includes(this.scope.confidentialScopeId)) throw new IntakeAccessDeniedError();
|
|
6677
|
+
if (operation === "review" && (!current.reviewer || !policy.reviewers.includes(this.scope.actorId))) throw new IntakeAccessDeniedError();
|
|
6678
|
+
if (operation === "review_own" && (current.submitter !== true || !policy.submitterReview || item.submitter_id !== this.scope.actorId || ceiling.principalId !== this.scope.actorId)) throw new IntakeAccessDeniedError();
|
|
6647
6679
|
const deadline = Math.min(time(item.expires_at), time(item.created_at) + policy.retentionMs);
|
|
6648
6680
|
return {
|
|
6649
6681
|
current,
|
|
@@ -6774,7 +6806,7 @@ var IntakeExecutionService = class {
|
|
|
6774
6806
|
}
|
|
6775
6807
|
async bound(db, actionId, lock = false, expired = false, proposalId) {
|
|
6776
6808
|
const [found] = await this.rows(db, "intake_actions", "id=?", [actionId]);
|
|
6777
|
-
if (!found) throw new
|
|
6809
|
+
if (!found) throw new IntakeAccessDeniedError();
|
|
6778
6810
|
const item = await this.item(db, String(found.item_id), lock, expired);
|
|
6779
6811
|
const [action] = await this.rows(db, "intake_actions", "id=?", [actionId]);
|
|
6780
6812
|
const [proposal] = await this.rows(db, "intake_proposals", proposalId ? "action_id=? AND id=?" : "action_id=? AND revision=?", [actionId, proposalId ?? action.proposal_revision]);
|
|
@@ -6842,14 +6874,14 @@ var IntakeExecutionService = class {
|
|
|
6842
6874
|
const run = (executor) => this.discoveryContext(executor, itemId, handlerId, handlerVersion, work, false, revalidate);
|
|
6843
6875
|
return db ? run(db) : this.tx(run);
|
|
6844
6876
|
}
|
|
6845
|
-
[feedbackActionTransaction](actionId, reviewer, work, executor, requiredProviders, proposalId) {
|
|
6877
|
+
[feedbackActionTransaction](actionId, reviewer, work, executor, requiredProviders, proposalId, targetRevisions = true) {
|
|
6846
6878
|
const operation = async (db) => {
|
|
6847
6879
|
const load = async () => {
|
|
6848
6880
|
const bound = await this.bound(db, actionId, true, false, proposalId);
|
|
6849
6881
|
const { context, permissions } = await this.access(db, bound.item, bound.handler, reviewer ? "review" : "read");
|
|
6850
6882
|
if (requiredProviders?.some((provider) => !context.policy.providers.includes(provider))) throw new Error("Feedback source provider unavailable");
|
|
6851
6883
|
await this.verifyEvidence(db, bound);
|
|
6852
|
-
for (const target of bound.binding.targetPreconditions) await context.assertTarget(target.model, target.id, bound.action.state === "succeeded" ? void 0 : target.revision);
|
|
6884
|
+
for (const target of bound.binding.targetPreconditions) await context.assertTarget(target.model, target.id, bound.action.state === "succeeded" || !targetRevisions ? void 0 : target.revision);
|
|
6853
6885
|
if (bound.binding.plan) {
|
|
6854
6886
|
const plan = (await this.rows(db, "intake_plans", "item_id=? AND plan_key=?", [bound.item.id, bound.binding.plan.key])).sort((a, b) => Number(b.revision) - Number(a.revision))[0];
|
|
6855
6887
|
if (!plan || plan.digest !== bound.binding.plan.digest) throw new Error("Plan superseded");
|
|
@@ -6879,10 +6911,16 @@ var IntakeExecutionService = class {
|
|
|
6879
6911
|
configuration: input.configuration
|
|
6880
6912
|
}) !== analysis.input_digest) throw new Error("Analysis input integrity");
|
|
6881
6913
|
if (!this.retained(bound.item)) throw new Error("Feedback unavailable");
|
|
6914
|
+
const resolved = !!await this.disposition(db, String(bound.item.id));
|
|
6915
|
+
const state = String(bound.action.state);
|
|
6916
|
+
const settled = !resolved && (["executing", "succeeded"].includes(state) || state === "authorized" && await this.approvalLive(db, bound.action));
|
|
6917
|
+
const origin = object(bound.action.data).origin;
|
|
6882
6918
|
return {
|
|
6883
6919
|
db,
|
|
6884
6920
|
context,
|
|
6885
6921
|
permissions,
|
|
6922
|
+
origin: typeof origin === "string" ? origin : "",
|
|
6923
|
+
settled,
|
|
6886
6924
|
review: this.review(bound),
|
|
6887
6925
|
itemId: String(bound.item.id),
|
|
6888
6926
|
attemptId: String(attempt.id),
|
|
@@ -6915,7 +6953,7 @@ var IntakeExecutionService = class {
|
|
|
6915
6953
|
"leaseMs",
|
|
6916
6954
|
"retentionMs"
|
|
6917
6955
|
]) if (next[key] > prior[key]) throw new Error("Feedback cannot widen policy");
|
|
6918
|
-
if (prior.requireReview && !next.requireReview || next.minimumCertainty < prior.minimumCertainty || !prior.consequentialAutomation && next.consequentialAutomation) throw new Error("Feedback cannot widen policy");
|
|
6956
|
+
if (prior.requireReview && !next.requireReview || next.minimumCertainty < prior.minimumCertainty || !prior.consequentialAutomation && next.consequentialAutomation || !prior.submitterReview && next.submitterReview) throw new Error("Feedback cannot widen policy");
|
|
6919
6957
|
return result;
|
|
6920
6958
|
};
|
|
6921
6959
|
return executor ? operation(executor) : this.tx(operation);
|
|
@@ -6931,13 +6969,13 @@ var IntakeExecutionService = class {
|
|
|
6931
6969
|
if (!Array.isArray(references)) throw new Error("Feedback lineage unavailable");
|
|
6932
6970
|
if (references.length === 0) return;
|
|
6933
6971
|
if (!this.options.feedback) throw new Error("Feedback configuration unavailable");
|
|
6934
|
-
const { IntakeFeedbackService } = await import("./feedback-
|
|
6972
|
+
const { IntakeFeedbackService } = await import("./feedback-D3UTF8fm.js").then((n) => n.r);
|
|
6935
6973
|
await new IntakeFeedbackService(this.options, this).assertActionLineage(String(bound.action.id), db, String(bound.proposal.id));
|
|
6936
6974
|
}
|
|
6937
6975
|
async getAction(actionId) {
|
|
6938
6976
|
return this.tx(async (db) => {
|
|
6939
6977
|
const [action] = await this.rows(db, "intake_actions", "id=?", [actionId]);
|
|
6940
|
-
if (!action) throw new
|
|
6978
|
+
if (!action) throw new IntakeAccessDeniedError();
|
|
6941
6979
|
const item = await this.item(db, String(action.item_id), true, true);
|
|
6942
6980
|
const expiredReview = async () => {
|
|
6943
6981
|
await this.redactExecution(db, item);
|
|
@@ -6973,7 +7011,7 @@ var IntakeExecutionService = class {
|
|
|
6973
7011
|
/** Bounded reload discovery. The cursor is an opaque action ID, never authority. */
|
|
6974
7012
|
async listReviews(itemId, input = {}) {
|
|
6975
7013
|
const limit = input.limit ?? 20;
|
|
6976
|
-
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 50 || input.cursor !== void 0 &&
|
|
7014
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 50 || input.cursor !== void 0 && !UUID_CURSOR.test(input.cursor)) throw new Error("Invalid review page");
|
|
6977
7015
|
return this.tx(async (db) => {
|
|
6978
7016
|
const item = await this.item(db, itemId, true, true);
|
|
6979
7017
|
const rows = (await db.query(`SELECT * FROM intake_actions WHERE tenant_id=? AND confidential_scope_id=? AND item_id=?${input.cursor ? " AND id>?" : ""} ORDER BY id LIMIT ?`, this.scope.tenantId, this.scope.confidentialScopeId, itemId, ...input.cursor ? [input.cursor] : [], limit + 1)).rows;
|
|
@@ -7041,12 +7079,14 @@ var IntakeExecutionService = class {
|
|
|
7041
7079
|
if (attempt && ["completed", "partial"].includes(String(attempt.state)) && String(analysis.current_attempt_id) === String(attempt.id)) recoveryAttemptId = String(attempt.id);
|
|
7042
7080
|
}
|
|
7043
7081
|
}
|
|
7082
|
+
const origin = object(action.data).origin;
|
|
7044
7083
|
const entry = {
|
|
7045
7084
|
review: this.review(bound),
|
|
7046
7085
|
args: structuredClone(bound.binding.args),
|
|
7047
7086
|
handlerId: bound.handler.id,
|
|
7048
7087
|
handlerVersion: bound.handler.version,
|
|
7049
7088
|
attemptId: String(proposal.analysis_attempt_id),
|
|
7089
|
+
...typeof origin === "string" && origin ? { origin } : {},
|
|
7050
7090
|
dependencies: structuredClone(bound.binding.dependencies)
|
|
7051
7091
|
};
|
|
7052
7092
|
if (action.state === "succeeded") entry.result = await this.successful(db, action, item);
|
|
@@ -7212,7 +7252,7 @@ var IntakeExecutionService = class {
|
|
|
7212
7252
|
if (!this.retained(item)) throw new RetentionExpired(String(item.id), time(item.expires_at));
|
|
7213
7253
|
id(input.requestId);
|
|
7214
7254
|
const [action] = await this.rows(db, "intake_actions", "id=? AND item_id=?", [input.actionId, input.itemId]);
|
|
7215
|
-
if (!action) throw new
|
|
7255
|
+
if (!action) throw new IntakeAccessDeniedError();
|
|
7216
7256
|
const prior = await this.rows(db, "intake_proposals", "action_id=?", [input.actionId]);
|
|
7217
7257
|
const requestHash = intakeBindingDigest(input);
|
|
7218
7258
|
const replay = prior.find((row) => object(row.data).requestId === input.requestId);
|
|
@@ -7234,6 +7274,7 @@ var IntakeExecutionService = class {
|
|
|
7234
7274
|
"executing",
|
|
7235
7275
|
"outcome_unknown"
|
|
7236
7276
|
].includes(String(action.state))) throw new Error("Proposal conflict");
|
|
7277
|
+
if (await this.disposition(db, String(item.id))) throw new Error("Item resolved");
|
|
7237
7278
|
const [attempt] = await this.rows(db, "intake_analysis_attempts", "id=? AND item_id=?", [input.attemptId, input.itemId]);
|
|
7238
7279
|
const [analysis] = await this.rows(db, "intake_analyses", "id=?", [attempt.analysis_id]);
|
|
7239
7280
|
const bindingHash = intakeBindingDigest({
|
|
@@ -7275,10 +7316,24 @@ var IntakeExecutionService = class {
|
|
|
7275
7316
|
const frozen = JSON.parse(canonical(input));
|
|
7276
7317
|
return this.tx(async (db) => {
|
|
7277
7318
|
const item = await this.item(db, frozen.itemId, true);
|
|
7319
|
+
await this.assertReviewerRevision(db, item, frozen);
|
|
7278
7320
|
const binding = await this.buildBinding(db, item, frozen, true);
|
|
7279
7321
|
return this.publish(db, item, frozen, binding);
|
|
7280
7322
|
});
|
|
7281
7323
|
}
|
|
7324
|
+
/**
|
|
7325
|
+
* A reviewer-selected action records a human routing choice: re-previewing it
|
|
7326
|
+
* needs the reviewer grant (not just `preview`) and keeps its handler fixed,
|
|
7327
|
+
* so only the arguments can be corrected.
|
|
7328
|
+
*/
|
|
7329
|
+
async assertReviewerRevision(db, item, input) {
|
|
7330
|
+
const [action] = await this.rows(db, "intake_actions", "id=? AND item_id=?", [input.actionId, item.id]);
|
|
7331
|
+
if (!action || object(action.data).origin !== "reviewer-selected") return;
|
|
7332
|
+
const [current] = await this.rows(db, "intake_proposals", "action_id=? AND revision=?", [action.id, action.proposal_revision]);
|
|
7333
|
+
const binding = current ? object(object(current.data).binding) : void 0;
|
|
7334
|
+
if (!binding || binding.handlerId !== input.handlerId || binding.handlerVersion !== input.handlerVersion) throw new Error("Reviewer-selected handler is fixed");
|
|
7335
|
+
await this.access(db, item, this.operation(this.handler(input.handlerId, input.handlerVersion)), "review");
|
|
7336
|
+
}
|
|
7282
7337
|
/** Capture owner facts in the same transaction; these are never correctness labels. */
|
|
7283
7338
|
async feedbackEvent(db, bound, eventId, kind) {
|
|
7284
7339
|
if (!this.options.feedback || !this.retained(bound.item)) return;
|
|
@@ -7311,6 +7366,8 @@ var IntakeExecutionService = class {
|
|
|
7311
7366
|
handlerVersion: bound.handler.version,
|
|
7312
7367
|
signal,
|
|
7313
7368
|
reviewer: kind === "action_decision" ? eventData.reviewer : null,
|
|
7369
|
+
decisionMode: kind === "action_decision" ? eventData.mode ?? "review" : null,
|
|
7370
|
+
selfDecision: kind === "action_decision" && eventData.selfDecision === true,
|
|
7314
7371
|
observedBy: this.scope.actorId,
|
|
7315
7372
|
principalId: eventData.principalId ?? null,
|
|
7316
7373
|
reason: kind === "action_decision" ? eventData.reason ?? "" : "",
|
|
@@ -7335,7 +7392,7 @@ var IntakeExecutionService = class {
|
|
|
7335
7392
|
data: canonical(data)
|
|
7336
7393
|
});
|
|
7337
7394
|
}
|
|
7338
|
-
async submitDecision(input) {
|
|
7395
|
+
async submitDecision(input, mode = "review") {
|
|
7339
7396
|
const frozen = JSON.parse(canonical(input));
|
|
7340
7397
|
id(frozen.requestId);
|
|
7341
7398
|
if (![
|
|
@@ -7344,9 +7401,10 @@ var IntakeExecutionService = class {
|
|
|
7344
7401
|
"defer",
|
|
7345
7402
|
"correct"
|
|
7346
7403
|
].includes(frozen.decision) || (frozen.reason?.length ?? 0) > 2048) throw new Error("Invalid decision");
|
|
7404
|
+
if (mode === "review_own" && frozen.decision === "correct") throw new Error("Invalid decision");
|
|
7347
7405
|
return this.tx(async (db) => {
|
|
7348
7406
|
const bound = await this.bound(db, frozen.actionId, true);
|
|
7349
|
-
const { policy } = await this.access(db, bound.item, bound.handler,
|
|
7407
|
+
const { policy } = await this.access(db, bound.item, bound.handler, mode);
|
|
7350
7408
|
await this.feedbackLineage(db, bound);
|
|
7351
7409
|
const decisions = await this.rows(db, "intake_review_decisions", "action_id=?", [frozen.actionId]);
|
|
7352
7410
|
const requestHash = intakeBindingDigest(frozen);
|
|
@@ -7358,6 +7416,7 @@ var IntakeExecutionService = class {
|
|
|
7358
7416
|
}
|
|
7359
7417
|
if (bound.action.state !== "waiting_review" || Number(bound.proposal.revision) !== frozen.expectedRevision || Number(bound.action.review_version) !== frozen.expectedReviewVersion || bound.proposal.binding_hash !== frozen.bindingHash) throw new Error("Stale review");
|
|
7360
7418
|
await this.verifyEvidence(db, bound);
|
|
7419
|
+
if (frozen.decision !== "reject" && await this.disposition(db, String(bound.item.id))) throw new Error("Item resolved");
|
|
7361
7420
|
const expiresAt = new Date(Math.min(time(bound.item.expires_at), this.now().getTime() + policy.approvalMs)).toISOString();
|
|
7362
7421
|
const decisionEvent = await this.insert(db, "intake_review_decisions", {
|
|
7363
7422
|
item_id: bound.item.id,
|
|
@@ -7374,7 +7433,9 @@ var IntakeExecutionService = class {
|
|
|
7374
7433
|
expiresAt,
|
|
7375
7434
|
policyVersions: policy.versions,
|
|
7376
7435
|
decidedAt: this.now().toISOString(),
|
|
7377
|
-
kind: "human"
|
|
7436
|
+
kind: "human",
|
|
7437
|
+
mode,
|
|
7438
|
+
selfDecision: bound.item.submitter_id === this.scope.actorId
|
|
7378
7439
|
})
|
|
7379
7440
|
});
|
|
7380
7441
|
await this.feedbackEvent(db, bound, String(decisionEvent.id), "action_decision");
|
|
@@ -7403,6 +7464,21 @@ var IntakeExecutionService = class {
|
|
|
7403
7464
|
defer: "deferred"
|
|
7404
7465
|
}[frozen.decision];
|
|
7405
7466
|
await db.query("UPDATE intake_actions SET state=? WHERE id=? AND tenant_id=? AND confidential_scope_id=?", state, frozen.actionId, this.scope.tenantId, this.scope.confidentialScopeId);
|
|
7467
|
+
const provenance = object(bound.action.data);
|
|
7468
|
+
if (frozen.decision === "approve" && mode === "review" && provenance.origin === "reviewer-selected") {
|
|
7469
|
+
const [attempt] = await this.rows(db, "intake_analysis_attempts", "id=? AND item_id=?", [bound.proposal.analysis_attempt_id, bound.item.id]);
|
|
7470
|
+
if (attempt) await this.routingFeedback(db, bound.item, attempt, {
|
|
7471
|
+
requestId: frozen.requestId,
|
|
7472
|
+
reason: typeof provenance.reason === "string" ? provenance.reason : "",
|
|
7473
|
+
...typeof provenance.reviewer === "string" ? { startedBy: provenance.reviewer } : {},
|
|
7474
|
+
handler: {
|
|
7475
|
+
handlerId: bound.handler.id,
|
|
7476
|
+
handlerVersion: bound.handler.version,
|
|
7477
|
+
args: structuredClone(bound.binding.args),
|
|
7478
|
+
review: this.review(bound)
|
|
7479
|
+
}
|
|
7480
|
+
});
|
|
7481
|
+
}
|
|
7406
7482
|
return {
|
|
7407
7483
|
...this.review(bound),
|
|
7408
7484
|
state
|
|
@@ -7427,6 +7503,7 @@ var IntakeExecutionService = class {
|
|
|
7427
7503
|
async approved(db, bound) {
|
|
7428
7504
|
await this.feedbackLineage(db, bound);
|
|
7429
7505
|
await this.verifyEvidence(db, bound);
|
|
7506
|
+
if (await this.disposition(db, String(bound.item.id))) throw new Error("Item resolved");
|
|
7430
7507
|
if (bound.binding.plan) {
|
|
7431
7508
|
const latest = (await this.rows(db, "intake_plans", "item_id=? AND plan_key=?", [bound.item.id, bound.binding.plan.key])).sort((a, b) => Number(b.revision) - Number(a.revision))[0];
|
|
7432
7509
|
if (!latest || latest.digest !== bound.binding.plan.digest || Number(latest.revision) !== bound.binding.plan.revision) throw new Error("Plan superseded");
|
|
@@ -7480,6 +7557,7 @@ var IntakeExecutionService = class {
|
|
|
7480
7557
|
]);
|
|
7481
7558
|
if (!decision || decision.decision !== "approve" || decision.binding_hash !== bound.proposal.binding_hash || time(object(decision.data).expiresAt) <= this.now().getTime()) throw new Error("Approval required");
|
|
7482
7559
|
const decisionData = object(decision.data);
|
|
7560
|
+
if (decisionData.mode === "review_own" && (decisionData.reviewer !== bound.item.submitter_id || bound.item.submitter_id !== this.ceiling(bound.item).principalId)) throw new Error("Approval required");
|
|
7483
7561
|
if (decisionData.kind === "machine") {
|
|
7484
7562
|
const access = await this.access(db, bound.item, bound.handler, "execute");
|
|
7485
7563
|
const consequential = bound.handler.execution.kind === "external" || bound.handler.capability.effect === "destructive" || bound.handler.capability.openWorld;
|
|
@@ -7602,6 +7680,7 @@ var IntakeExecutionService = class {
|
|
|
7602
7680
|
};
|
|
7603
7681
|
if (!(await db.query("UPDATE intake_actions SET state='succeeded',result_digest=? WHERE id=? AND tenant_id=? AND confidential_scope_id=? AND fence=? AND state IN ('executing','outcome_unknown') RETURNING id", resultDigest, bound.action.id, this.scope.tenantId, this.scope.confidentialScopeId, execution.fence)).rows.length) throw new Error("Execution fence lost");
|
|
7604
7682
|
await db.query("UPDATE intake_executions SET state='succeeded',result_digest=?,data=? WHERE id=? AND tenant_id=? AND confidential_scope_id=?", resultDigest, canonical(data), execution.id, this.scope.tenantId, this.scope.confidentialScopeId);
|
|
7683
|
+
await this.releaseRedactedPrincipal(db, String(bound.item.id));
|
|
7605
7684
|
await this.feedbackEvent(db, bound, String(execution.id), "downstream_outcome");
|
|
7606
7685
|
return {
|
|
7607
7686
|
state: "succeeded",
|
|
@@ -7615,7 +7694,7 @@ var IntakeExecutionService = class {
|
|
|
7615
7694
|
let reserved;
|
|
7616
7695
|
const result = await this.tx(async (db) => {
|
|
7617
7696
|
const [action] = await this.rows(db, "intake_actions", "id=?", [actionId]);
|
|
7618
|
-
if (!action) throw new
|
|
7697
|
+
if (!action) throw new IntakeAccessDeniedError();
|
|
7619
7698
|
const item = await this.item(db, String(action.item_id), true, true);
|
|
7620
7699
|
const [current] = await this.rows(db, "intake_actions", "id=?", [actionId]);
|
|
7621
7700
|
if (current.state === "succeeded") return this.successful(db, current, item);
|
|
@@ -7832,6 +7911,7 @@ var IntakeExecutionService = class {
|
|
|
7832
7911
|
...object(execution.data),
|
|
7833
7912
|
reconciliation: observed.kind
|
|
7834
7913
|
}), execution.id, this.scope.tenantId, this.scope.confidentialScopeId);
|
|
7914
|
+
await this.releaseRedactedPrincipal(db, String(item.id));
|
|
7835
7915
|
await this.feedbackEvent(db, {
|
|
7836
7916
|
...bound,
|
|
7837
7917
|
item
|
|
@@ -7849,7 +7929,7 @@ var IntakeExecutionService = class {
|
|
|
7849
7929
|
const bound = await this.bound(db, actionId, true);
|
|
7850
7930
|
const { current, policy, context } = await this.access(db, bound.item, bound.handler, "execute");
|
|
7851
7931
|
const consequential = bound.handler.execution.kind === "external" || bound.handler.capability.effect === "destructive" || bound.handler.capability.openWorld;
|
|
7852
|
-
if (bound.action.state !== "waiting_review" || !policy.automaticHandlers.includes(bound.handler.id) || consequential && !policy.consequentialAutomation || !this.config.evaluateAutomatic) throw new Error("Automatic execution unavailable");
|
|
7932
|
+
if (bound.action.state !== "waiting_review" || !policy.automaticHandlers.includes(bound.handler.id) || consequential && !policy.consequentialAutomation || !this.config.evaluateAutomatic || object(bound.action.data).origin === "reviewer-selected" || await this.disposition(db, String(bound.item.id))) throw new Error("Automatic execution unavailable");
|
|
7853
7933
|
await this.feedbackLineage(db, bound);
|
|
7854
7934
|
const evaluated = await this.config.evaluateAutomatic({
|
|
7855
7935
|
bindingHash: String(bound.proposal.binding_hash),
|
|
@@ -7882,6 +7962,226 @@ var IntakeExecutionService = class {
|
|
|
7882
7962
|
};
|
|
7883
7963
|
});
|
|
7884
7964
|
}
|
|
7965
|
+
/**
|
|
7966
|
+
* Whether an `authorized` action still holds an unexpired approval for its
|
|
7967
|
+
* current review version. An expired one can be neither applied nor rejected,
|
|
7968
|
+
* so it must not keep the item open; `approved()` refuses resolved items.
|
|
7969
|
+
*/
|
|
7970
|
+
async approvalLive(db, action) {
|
|
7971
|
+
return (await this.rows(db, "intake_review_decisions", "action_id=? AND review_version=? AND decision='approve'", [action.id, action.review_version])).some((row) => time(object(row.data).expiresAt) > this.now().getTime());
|
|
7972
|
+
}
|
|
7973
|
+
/** Current terminal reviewer disposition, if any. */
|
|
7974
|
+
async disposition(db, itemId) {
|
|
7975
|
+
const [row] = await this.rows(db, "intake_dispositions", "item_id=? ORDER BY id LIMIT 1", [itemId]);
|
|
7976
|
+
return row;
|
|
7977
|
+
}
|
|
7978
|
+
dispositionDTO(row) {
|
|
7979
|
+
const data = object(row.data);
|
|
7980
|
+
return {
|
|
7981
|
+
kind: "resolved_no_action",
|
|
7982
|
+
reviewer: typeof data.reviewer === "string" ? data.reviewer : "",
|
|
7983
|
+
reason: typeof data.reason === "string" ? data.reason : "",
|
|
7984
|
+
decidedAt: typeof data.decidedAt === "string" ? data.decidedAt : ""
|
|
7985
|
+
};
|
|
7986
|
+
}
|
|
7987
|
+
/** The current revision's completed/partial attempt; failed revisions need reprocessing. */
|
|
7988
|
+
async currentAttempt(db, item) {
|
|
7989
|
+
const [analysis] = await this.rows(db, "intake_analyses", "item_id=? AND revision=?", [item.id, item.analysis_revision]);
|
|
7990
|
+
if (!analysis?.current_attempt_id) throw new Error("Analysis unavailable");
|
|
7991
|
+
const [attempt] = await this.rows(db, "intake_analysis_attempts", "id=? AND item_id=?", [analysis.current_attempt_id, item.id]);
|
|
7992
|
+
if (!attempt || !["completed", "partial"].includes(String(attempt.state)) || !attempt.output_digest) throw new Error("Analysis unavailable");
|
|
7993
|
+
return attempt;
|
|
7994
|
+
}
|
|
7995
|
+
/** Reviewer authority under a handler this item's captured ceiling offers; no new grant. */
|
|
7996
|
+
async reviewerAccess(db, item) {
|
|
7997
|
+
const handlers = [...this.config.handlers].sort((a, b) => `${a.id}@${a.version}`.localeCompare(`${b.id}@${b.version}`));
|
|
7998
|
+
for (const handler of handlers) try {
|
|
7999
|
+
return await this.access(db, item, handler, "review");
|
|
8000
|
+
} catch (error) {
|
|
8001
|
+
if (error instanceof RetentionExpired) throw error;
|
|
8002
|
+
}
|
|
8003
|
+
throw new Error("Intake unavailable");
|
|
8004
|
+
}
|
|
8005
|
+
/** Extracted text for routing examples: the attempt's own results or its pinned extraction. */
|
|
8006
|
+
async extractedQuery(db, item, attempt) {
|
|
8007
|
+
let source = attempt;
|
|
8008
|
+
const output = object(object(attempt.data).output);
|
|
8009
|
+
if (output.proposals) {
|
|
8010
|
+
const pin = object(object(output.proposals).source);
|
|
8011
|
+
const [row] = await this.rows(db, "intake_analysis_attempts", "id=? AND item_id=?", [pin.attemptId, item.id]);
|
|
8012
|
+
if (!row || row.output_digest !== pin.outputDigest) throw new Error("Feedback source unavailable");
|
|
8013
|
+
source = row;
|
|
8014
|
+
}
|
|
8015
|
+
const results = object(object(source.data).output).results;
|
|
8016
|
+
if (!Array.isArray(results)) return "";
|
|
8017
|
+
return results.flatMap((entry) => {
|
|
8018
|
+
const segments = object(entry).segments;
|
|
8019
|
+
return Array.isArray(segments) ? segments.map((segment) => String(object(segment).text ?? "")) : [];
|
|
8020
|
+
}).join("\n").slice(0, 4096);
|
|
8021
|
+
}
|
|
8022
|
+
/** Optional explicit routing projection; the reviewer's action or disposition stays authoritative. */
|
|
8023
|
+
async routingFeedback(db, item, attempt, choice) {
|
|
8024
|
+
if (!this.options.feedback || !this.retained(item)) return;
|
|
8025
|
+
if (!await this.options.feedback.authorize({
|
|
8026
|
+
db,
|
|
8027
|
+
scope: this.scope,
|
|
8028
|
+
itemId: String(item.id),
|
|
8029
|
+
operation: "capture"
|
|
8030
|
+
})) return;
|
|
8031
|
+
const key = `routing:${choice.requestId}`;
|
|
8032
|
+
const [replay] = await this.rows(db, "intake_feedback", "item_id=? AND request_key=?", [item.id, key]);
|
|
8033
|
+
if (replay) return;
|
|
8034
|
+
let data;
|
|
8035
|
+
try {
|
|
8036
|
+
const [analysis] = await this.rows(db, "intake_analyses", "id=?", [attempt.analysis_id]);
|
|
8037
|
+
const inputs = object(analysis.data).inputs;
|
|
8038
|
+
if (!Array.isArray(inputs)) throw new Error("Invalid evidence");
|
|
8039
|
+
const body = {
|
|
8040
|
+
version: 1,
|
|
8041
|
+
choice: choice.handler ? "handler" : "none",
|
|
8042
|
+
handlerId: choice.handler?.handlerId ?? null,
|
|
8043
|
+
handlerVersion: choice.handler?.handlerVersion ?? null,
|
|
8044
|
+
args: choice.handler?.args ?? null,
|
|
8045
|
+
actionId: choice.handler?.review.actionId ?? null,
|
|
8046
|
+
proposalId: choice.handler?.review.proposalId ?? null,
|
|
8047
|
+
revision: choice.handler?.review.revision ?? null,
|
|
8048
|
+
bindingHash: choice.handler?.review.bindingHash ?? null,
|
|
8049
|
+
attemptId: String(attempt.id),
|
|
8050
|
+
attemptDigest: String(attempt.output_digest),
|
|
8051
|
+
analysisRevision: Number(analysis.revision),
|
|
8052
|
+
evidence: inputs,
|
|
8053
|
+
query: await this.extractedQuery(db, item, attempt),
|
|
8054
|
+
reviewer: this.scope.actorId,
|
|
8055
|
+
startedBy: choice.startedBy ?? null,
|
|
8056
|
+
approvedBy: choice.handler ? this.scope.actorId : null,
|
|
8057
|
+
reason: choice.reason,
|
|
8058
|
+
requestId: choice.requestId,
|
|
8059
|
+
decidedAt: this.now().toISOString()
|
|
8060
|
+
};
|
|
8061
|
+
data = {
|
|
8062
|
+
...body,
|
|
8063
|
+
digest: intakeBindingDigest(body)
|
|
8064
|
+
};
|
|
8065
|
+
} catch {
|
|
8066
|
+
return;
|
|
8067
|
+
}
|
|
8068
|
+
if (Buffer.byteLength(canonical(data)) > this.options.feedback.maxBytes) return;
|
|
8069
|
+
await this.insert(db, "intake_feedback", {
|
|
8070
|
+
item_id: item.id,
|
|
8071
|
+
request_key: key,
|
|
8072
|
+
supersedes_id: null,
|
|
8073
|
+
kind: "reviewer_routing",
|
|
8074
|
+
data: canonical(data)
|
|
8075
|
+
});
|
|
8076
|
+
}
|
|
8077
|
+
/** Human-chosen handler for any item. Same preview/approval path; never automation-eligible. */
|
|
8078
|
+
async startAction(input) {
|
|
8079
|
+
const frozen = JSON.parse(canonical(JSON.parse(JSON.stringify(input))));
|
|
8080
|
+
id(frozen.requestId);
|
|
8081
|
+
if (frozen.reason !== void 0 && (typeof frozen.reason !== "string" || frozen.reason.length > 2048) || !frozen.args || typeof frozen.args !== "object" || Array.isArray(frozen.args)) throw new Error("Invalid reviewer action");
|
|
8082
|
+
return this.tx(async (db) => {
|
|
8083
|
+
const item = await this.item(db, frozen.itemId, true);
|
|
8084
|
+
const handler = this.handler(frozen.handlerId, frozen.handlerVersion);
|
|
8085
|
+
if (!("execution" in handler)) throw new Error("Handler unavailable");
|
|
8086
|
+
await this.access(db, item, handler, "review");
|
|
8087
|
+
const evidence = await this.rows(db, "intake_evidence", "item_id=? AND state='durable'", [item.id]);
|
|
8088
|
+
if (!handler.discovery?.mediaTypes.some((type) => evidence.some((entry) => entry.media_type === type))) throw new Error("Handler unavailable");
|
|
8089
|
+
if (await this.disposition(db, String(item.id))) throw new Error("Item resolved");
|
|
8090
|
+
const attempt = await this.currentAttempt(db, item);
|
|
8091
|
+
const actionKey = `reviewer:${frozen.requestId}`;
|
|
8092
|
+
const provenance = canonical({
|
|
8093
|
+
origin: "reviewer-selected",
|
|
8094
|
+
intentionKey: actionKey,
|
|
8095
|
+
reviewer: this.scope.actorId,
|
|
8096
|
+
reason: frozen.reason ?? ""
|
|
8097
|
+
});
|
|
8098
|
+
let [action] = await this.rows(db, "intake_actions", "item_id=? AND action_key=?", [item.id, actionKey]);
|
|
8099
|
+
if (action && canonical(object(action.data)) !== provenance) throw new Error("Action identity conflict");
|
|
8100
|
+
action ??= await this.insert(db, "intake_actions", {
|
|
8101
|
+
item_id: item.id,
|
|
8102
|
+
action_key: actionKey,
|
|
8103
|
+
state: "proposed",
|
|
8104
|
+
proposal_revision: 0,
|
|
8105
|
+
review_version: 0,
|
|
8106
|
+
execution_attempt: 0,
|
|
8107
|
+
fence: 0,
|
|
8108
|
+
result_digest: "",
|
|
8109
|
+
data: provenance
|
|
8110
|
+
});
|
|
8111
|
+
const replayed = (await this.rows(db, "intake_proposals", "action_id=?", [action.id])).find((row) => object(row.data).requestId === frozen.requestId);
|
|
8112
|
+
const preview = {
|
|
8113
|
+
itemId: String(item.id),
|
|
8114
|
+
actionId: String(action.id),
|
|
8115
|
+
attemptId: String(attempt.id),
|
|
8116
|
+
expectedRevision: replayed ? Number(replayed.revision) - 1 : Number(action.proposal_revision),
|
|
8117
|
+
requestId: frozen.requestId,
|
|
8118
|
+
handlerId: handler.id,
|
|
8119
|
+
handlerVersion: handler.version,
|
|
8120
|
+
args: frozen.args
|
|
8121
|
+
};
|
|
8122
|
+
const binding = await this.buildBinding(db, item, preview);
|
|
8123
|
+
return this.publish(db, item, preview, binding);
|
|
8124
|
+
});
|
|
8125
|
+
}
|
|
8126
|
+
/** The full disposition (reviewer, reason) under the current reviewer authority. */
|
|
8127
|
+
async getItemDisposition(itemId) {
|
|
8128
|
+
return this.tx(async (db) => {
|
|
8129
|
+
const item = await this.item(db, itemId);
|
|
8130
|
+
await this.reviewerAccess(db, item);
|
|
8131
|
+
const row = await this.disposition(db, String(item.id));
|
|
8132
|
+
return row ? this.dispositionDTO(row) : void 0;
|
|
8133
|
+
});
|
|
8134
|
+
}
|
|
8135
|
+
/** Close an item as handled without an action. Terminal; replay returns the same disposition. */
|
|
8136
|
+
async resolveItem(input) {
|
|
8137
|
+
const frozen = JSON.parse(canonical(JSON.parse(JSON.stringify(input))));
|
|
8138
|
+
id(frozen.requestId);
|
|
8139
|
+
if (frozen.disposition !== "resolve_no_action" || frozen.reason !== void 0 && (typeof frozen.reason !== "string" || frozen.reason.length > 2048)) throw new Error("Invalid disposition");
|
|
8140
|
+
return this.tx(async (db) => {
|
|
8141
|
+
const item = await this.item(db, frozen.itemId, true);
|
|
8142
|
+
const { policy } = await this.reviewerAccess(db, item);
|
|
8143
|
+
const requestHash = intakeBindingDigest(frozen);
|
|
8144
|
+
const key = `disposition:${frozen.requestId}`;
|
|
8145
|
+
const existing = await this.disposition(db, String(item.id));
|
|
8146
|
+
if (existing) {
|
|
8147
|
+
const data = object(existing.data);
|
|
8148
|
+
if (existing.request_key === key && data.requestHash === requestHash && data.reviewer === this.scope.actorId) return this.dispositionDTO(existing);
|
|
8149
|
+
throw new Error("Item resolved");
|
|
8150
|
+
}
|
|
8151
|
+
const pending = [];
|
|
8152
|
+
for (const action of await this.rows(db, "intake_actions", "item_id=? AND state IN ('authorized','executing','outcome_unknown','succeeded')", [item.id])) if (action.state !== "authorized" || await this.approvalLive(db, action)) pending.push(action);
|
|
8153
|
+
if (pending.length) throw new Error("Item has pending or applied actions");
|
|
8154
|
+
if (!this.retained(item)) throw new RetentionExpired(String(item.id), time(item.expires_at));
|
|
8155
|
+
let attempt;
|
|
8156
|
+
try {
|
|
8157
|
+
attempt = await this.currentAttempt(db, item);
|
|
8158
|
+
} catch {
|
|
8159
|
+
attempt = void 0;
|
|
8160
|
+
}
|
|
8161
|
+
for (const table of ["intake_analysis_attempts", "intake_analyses"]) await db.query(`UPDATE ${table} SET state='superseded' WHERE tenant_id=? AND confidential_scope_id=? AND item_id=? AND state IN ('queued','running')`, this.scope.tenantId, this.scope.confidentialScopeId, item.id);
|
|
8162
|
+
await db.query("UPDATE intake_dispatches SET state='completed' WHERE tenant_id=? AND confidential_scope_id=? AND item_id=? AND state<>'completed'", this.scope.tenantId, this.scope.confidentialScopeId, item.id);
|
|
8163
|
+
const row = await this.insert(db, "intake_dispositions", {
|
|
8164
|
+
item_id: item.id,
|
|
8165
|
+
request_key: key,
|
|
8166
|
+
disposition: "resolved_no_action",
|
|
8167
|
+
data: canonical({
|
|
8168
|
+
reviewer: this.scope.actorId,
|
|
8169
|
+
reason: frozen.reason ?? "",
|
|
8170
|
+
requestId: frozen.requestId,
|
|
8171
|
+
requestHash,
|
|
8172
|
+
decidedAt: this.now().toISOString(),
|
|
8173
|
+
attemptId: attempt ? String(attempt.id) : null,
|
|
8174
|
+
attemptDigest: attempt ? String(attempt.output_digest) : null,
|
|
8175
|
+
policyVersions: policy.versions
|
|
8176
|
+
})
|
|
8177
|
+
});
|
|
8178
|
+
if (attempt) await this.routingFeedback(db, item, attempt, {
|
|
8179
|
+
requestId: frozen.requestId,
|
|
8180
|
+
reason: frozen.reason ?? ""
|
|
8181
|
+
});
|
|
8182
|
+
return this.dispositionDTO(row);
|
|
8183
|
+
});
|
|
8184
|
+
}
|
|
7885
8185
|
async previewPlan(input) {
|
|
7886
8186
|
const frozen = JSON.parse(canonical(input));
|
|
7887
8187
|
id(frozen.planKey);
|
|
@@ -8085,6 +8385,104 @@ var IntakeExecutionService = class {
|
|
|
8085
8385
|
}
|
|
8086
8386
|
return results;
|
|
8087
8387
|
}
|
|
8388
|
+
/** The recorded submitter decides their own proposal under `review_own`
|
|
8389
|
+
* authority. Apply remains the ordinary once-only path under live grants. */
|
|
8390
|
+
async submitOwnDecision(input) {
|
|
8391
|
+
return this.submitDecision(input, "review_own");
|
|
8392
|
+
}
|
|
8393
|
+
/** Bounded listing of this actor's own active submissions with their saved
|
|
8394
|
+
* reviews. The submitter column is the authenticated receipt actor recorded by
|
|
8395
|
+
* the server; every page reloads current item/handler/target authority. */
|
|
8396
|
+
async listOwnProposals(input = {}) {
|
|
8397
|
+
const limit = input.limit ?? 20;
|
|
8398
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 50 || input.cursor !== void 0 && !UUID_CURSOR.test(input.cursor)) throw new Error("Invalid review page");
|
|
8399
|
+
const readable = [];
|
|
8400
|
+
let more = false;
|
|
8401
|
+
let after = input.cursor;
|
|
8402
|
+
let hidden = 0;
|
|
8403
|
+
scan: for (;;) {
|
|
8404
|
+
const batch = (await this.db.query(`SELECT * FROM intake_items WHERE tenant_id=? AND confidential_scope_id=? AND submitter_id=? AND visibility='active' AND expires_at>?${after ? " AND id>?" : ""} ORDER BY id LIMIT ?`, this.scope.tenantId, this.scope.confidentialScopeId, this.scope.actorId, this.now().toISOString(), ...after ? [after] : [], OWN_SCAN_BATCH)).rows;
|
|
8405
|
+
for (const row of batch) if (await this.readableOwn(row)) {
|
|
8406
|
+
if (readable.length === limit) {
|
|
8407
|
+
more = true;
|
|
8408
|
+
break scan;
|
|
8409
|
+
}
|
|
8410
|
+
readable.push(row);
|
|
8411
|
+
} else if (++hidden >= OWN_SCAN_CEILING) throw new Error("Own proposal scan limit");
|
|
8412
|
+
if (batch.length < OWN_SCAN_BATCH) break;
|
|
8413
|
+
after = String(batch[batch.length - 1].id);
|
|
8414
|
+
}
|
|
8415
|
+
const reviews = new IntakeExecutionService(this.options, {
|
|
8416
|
+
...this.config,
|
|
8417
|
+
assertTarget: async (target) => {
|
|
8418
|
+
try {
|
|
8419
|
+
return await this.config.assertTarget(target);
|
|
8420
|
+
} catch {
|
|
8421
|
+
throw new IntakeAccessDeniedError();
|
|
8422
|
+
}
|
|
8423
|
+
}
|
|
8424
|
+
});
|
|
8425
|
+
const items = [];
|
|
8426
|
+
for (const row of readable) {
|
|
8427
|
+
const item = {
|
|
8428
|
+
id: String(row.id),
|
|
8429
|
+
receiptState: String(row.receipt_state),
|
|
8430
|
+
processingState: String(row.processing_state),
|
|
8431
|
+
analysisRevision: Number(row.analysis_revision),
|
|
8432
|
+
cancelled: !!row.cancelled,
|
|
8433
|
+
traceId: String(row.trace_id)
|
|
8434
|
+
};
|
|
8435
|
+
try {
|
|
8436
|
+
items.push({
|
|
8437
|
+
item,
|
|
8438
|
+
reviews: await reviews.listReviews(item.id, { limit: 50 })
|
|
8439
|
+
});
|
|
8440
|
+
} catch (error) {
|
|
8441
|
+
if (!(error instanceof IntakeAccessDeniedError)) throw error;
|
|
8442
|
+
items.push({
|
|
8443
|
+
item,
|
|
8444
|
+
reviews: null,
|
|
8445
|
+
unavailable: true
|
|
8446
|
+
});
|
|
8447
|
+
}
|
|
8448
|
+
}
|
|
8449
|
+
return {
|
|
8450
|
+
items,
|
|
8451
|
+
...more && items.length ? { nextCursor: items[items.length - 1].item.id } : {}
|
|
8452
|
+
};
|
|
8453
|
+
}
|
|
8454
|
+
/** Once a privacy-redacted item has no external action left to reconcile,
|
|
8455
|
+
* drop the principal that redaction had to retain for it. Caller holds the
|
|
8456
|
+
* item lock. */
|
|
8457
|
+
async releaseRedactedPrincipal(db, itemId) {
|
|
8458
|
+
const [item] = await this.rows(db, "intake_items", "id=?", [itemId]);
|
|
8459
|
+
if (!item || item.visibility === "active") return;
|
|
8460
|
+
if ((await this.rows(db, "intake_actions", `item_id=? AND state IN (${UNFINISHED_ACTION_STATES.map(() => "?").join(",")})`, [itemId, ...UNFINISHED_ACTION_STATES])).length) return;
|
|
8461
|
+
const data = object(item.data);
|
|
8462
|
+
const ceiling = object(data.capturedCeiling ?? {});
|
|
8463
|
+
const released = redactedCeiling(ceiling, false);
|
|
8464
|
+
if (canonical(released) === canonical(ceiling)) return;
|
|
8465
|
+
await db.query("UPDATE intake_items SET data=? WHERE tenant_id=? AND confidential_scope_id=? AND id=?", canonical({
|
|
8466
|
+
...data,
|
|
8467
|
+
capturedCeiling: released
|
|
8468
|
+
}), this.scope.tenantId, this.scope.confidentialScopeId, itemId);
|
|
8469
|
+
}
|
|
8470
|
+
/** Only an explicit denial hides a row; a failing grant store propagates
|
|
8471
|
+
* instead of silently shrinking the list. */
|
|
8472
|
+
async readableOwn(row) {
|
|
8473
|
+
const capturedCeiling = object(object(row.data).capturedCeiling ?? {});
|
|
8474
|
+
try {
|
|
8475
|
+
return await this.options.authorize({
|
|
8476
|
+
scope: this.scope,
|
|
8477
|
+
operation: "read",
|
|
8478
|
+
capturedCeiling,
|
|
8479
|
+
db: this.db
|
|
8480
|
+
}) === true;
|
|
8481
|
+
} catch (error) {
|
|
8482
|
+
if (error instanceof IntakeAccessDeniedError) return false;
|
|
8483
|
+
throw error;
|
|
8484
|
+
}
|
|
8485
|
+
}
|
|
8088
8486
|
};
|
|
8089
8487
|
async function continueIntakeReview(service, actionId, task) {
|
|
8090
8488
|
await task.assertAuthorized();
|
|
@@ -8112,6 +8510,6 @@ async function continueIntakeReview(service, actionId, task) {
|
|
|
8112
8510
|
return service.applyAction(actionId);
|
|
8113
8511
|
}
|
|
8114
8512
|
//#endregion
|
|
8115
|
-
export {
|
|
8513
|
+
export { listReadGate as _, resolveIntakePolicy as a, require_ajv as c, GenerationSnapshotStaleError as d, GenerationSourceStaleError as f, generationSnapshotRead as g, discoveryTransaction as h, intakeBindingDigest as i, UNFINISHED_ACTION_STATES as l, ProposalsNotConfiguredError as m, IntakeExecutionService as n, feedbackActionTransaction as o, HandlerArgumentsInvalidError as p, continueIntakeReview as r, feedbackDiscoveryContext as s, IntakeAccessDeniedError as t, redactedCeiling as u, __exportAll as v, __toESM as y };
|
|
8116
8514
|
|
|
8117
|
-
//# sourceMappingURL=execution-
|
|
8515
|
+
//# sourceMappingURL=execution-BzVQax0n.js.map
|