@happyvertical/smrt-ingestion 0.57.21 → 0.57.22

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 (83) hide show
  1. package/FEEDBACK.md +104 -0
  2. package/PROPOSALS.md +19 -0
  3. package/README.md +126 -0
  4. package/REVIEW.md +272 -4
  5. package/agents/execution.md +72 -0
  6. package/dist/chunks/{execution-9CfjxX8I.js → execution-BzVQax0n.js} +421 -23
  7. package/dist/chunks/execution-BzVQax0n.js.map +1 -0
  8. package/dist/chunks/{feedback-CuUzWKzP.js → feedback-D3UTF8fm.js} +119 -21
  9. package/dist/chunks/feedback-D3UTF8fm.js.map +1 -0
  10. package/dist/chunks/{extraction-CJ-zrbg0.js → proposal-contracts-77Uo1B1j.js} +39 -39
  11. package/dist/chunks/proposal-contracts-77Uo1B1j.js.map +1 -0
  12. package/dist/chunks/{reviewer-instruction-Db3jDN9a.js → reviewer-instruction-CEc3AOuU.js} +2 -2
  13. package/dist/chunks/{reviewer-instruction-Db3jDN9a.js.map → reviewer-instruction-CEc3AOuU.js.map} +1 -1
  14. package/dist/dto.d.ts +69 -0
  15. package/dist/dto.d.ts.map +1 -1
  16. package/dist/execution-contracts.d.ts +4 -1
  17. package/dist/execution-contracts.d.ts.map +1 -1
  18. package/dist/execution-dto.d.ts +16 -0
  19. package/dist/execution-dto.d.ts.map +1 -1
  20. package/dist/execution.d.ts +58 -3
  21. package/dist/execution.d.ts.map +1 -1
  22. package/dist/execution.js +2 -2
  23. package/dist/feedback-dto.d.ts +12 -4
  24. package/dist/feedback-dto.d.ts.map +1 -1
  25. package/dist/feedback-internal.d.ts +9 -0
  26. package/dist/feedback-internal.d.ts.map +1 -1
  27. package/dist/feedback.d.ts +3 -0
  28. package/dist/feedback.d.ts.map +1 -1
  29. package/dist/index.js +2 -2
  30. package/dist/manifest.json +321 -3
  31. package/dist/models.d.ts +15 -0
  32. package/dist/models.d.ts.map +1 -1
  33. package/dist/models.js +73 -2
  34. package/dist/models.js.map +1 -1
  35. package/dist/policy.d.ts +3 -0
  36. package/dist/policy.d.ts.map +1 -1
  37. package/dist/proposal-errors.d.ts +4 -0
  38. package/dist/proposal-errors.d.ts.map +1 -1
  39. package/dist/proposals.d.ts +1 -0
  40. package/dist/proposals.d.ts.map +1 -1
  41. package/dist/proposals.js +11 -6
  42. package/dist/proposals.js.map +1 -1
  43. package/dist/redaction.d.ts +9 -0
  44. package/dist/redaction.d.ts.map +1 -0
  45. package/dist/review-dto.d.ts +14 -4
  46. package/dist/review-dto.d.ts.map +1 -1
  47. package/dist/review-host.d.ts.map +1 -1
  48. package/dist/server.d.ts +82 -10
  49. package/dist/server.d.ts.map +1 -1
  50. package/dist/server.js +366 -47
  51. package/dist/server.js.map +1 -1
  52. package/dist/smrt-knowledge.json +124 -18
  53. package/dist/svelte/components/ActionReview.svelte +39 -9
  54. package/dist/svelte/components/ActionReview.svelte.d.ts +8 -1
  55. package/dist/svelte/components/ActionReview.svelte.d.ts.map +1 -1
  56. package/dist/svelte/components/EvidenceViewer.svelte +60 -4
  57. package/dist/svelte/components/EvidenceViewer.svelte.d.ts +12 -1
  58. package/dist/svelte/components/EvidenceViewer.svelte.d.ts.map +1 -1
  59. package/dist/svelte/components/InboxView.svelte +160 -18
  60. package/dist/svelte/components/InboxView.svelte.d.ts +28 -1
  61. package/dist/svelte/components/InboxView.svelte.d.ts.map +1 -1
  62. package/dist/svelte/components/IntakeWorkspace.svelte +27 -0
  63. package/dist/svelte/components/IntakeWorkspace.svelte.d.ts +19 -0
  64. package/dist/svelte/components/IntakeWorkspace.svelte.d.ts.map +1 -0
  65. package/dist/svelte/components/ReviewView.svelte +264 -23
  66. package/dist/svelte/components/ReviewView.svelte.d.ts +18 -1
  67. package/dist/svelte/components/ReviewView.svelte.d.ts.map +1 -1
  68. package/dist/svelte/components/TriageView.svelte +669 -0
  69. package/dist/svelte/components/TriageView.svelte.d.ts +16 -0
  70. package/dist/svelte/components/TriageView.svelte.d.ts.map +1 -0
  71. package/dist/svelte/i18n.d.ts +39 -0
  72. package/dist/svelte/i18n.d.ts.map +1 -1
  73. package/dist/svelte/i18n.js +39 -0
  74. package/dist/svelte/index.d.ts +1 -0
  75. package/dist/svelte/index.d.ts.map +1 -1
  76. package/dist/svelte/index.js +1 -0
  77. package/dist/svelte/view-utils.d.ts +65 -0
  78. package/dist/svelte/view-utils.d.ts.map +1 -1
  79. package/dist/svelte/view-utils.js +145 -0
  80. package/package.json +13 -13
  81. package/dist/chunks/execution-9CfjxX8I.js.map +0 -1
  82. package/dist/chunks/extraction-CJ-zrbg0.js.map +0 -1
  83. 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 Error("Intake unavailable");
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 Error("Intake unavailable");
6576
- if (!expired && (item.visibility !== "active" || time(item.expires_at) <= this.now().getTime() || item.cancelled)) throw new Error("Intake unavailable");
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 Error("Intake unavailable");
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 Error("Intake unavailable");
6646
- if (operation === "review" && (!current.reviewer || !policy.reviewers.includes(this.scope.actorId))) throw new Error("Intake unavailable");
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 Error("Intake unavailable");
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-CuUzWKzP.js").then((n) => n.r);
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 Error("Intake unavailable");
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 && !/^[0-9a-f-]{36}$/i.test(input.cursor)) throw new Error("Invalid review page");
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 Error("Intake unavailable");
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, "review");
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 Error("Intake unavailable");
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 { feedbackActionTransaction as a, GenerationSnapshotStaleError as c, discoveryTransaction as d, generationSnapshotRead as f, __toESM as h, resolveIntakePolicy as i, GenerationSourceStaleError as l, __exportAll as m, continueIntakeReview as n, feedbackDiscoveryContext as o, listReadGate as p, intakeBindingDigest as r, require_ajv as s, IntakeExecutionService as t, HandlerArgumentsInvalidError as u };
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-9CfjxX8I.js.map
8515
+ //# sourceMappingURL=execution-BzVQax0n.js.map