@happyvertical/smrt-ingestion 0.55.6 → 0.55.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/AGENTS.md +13 -3
  2. package/FEEDBACK.md +156 -0
  3. package/README.md +15 -3
  4. package/REVIEW.md +137 -0
  5. package/dist/chunks/{execution-C6HJvl9e.js → execution-C7sTFXFU.js} +375 -11
  6. package/dist/chunks/execution-C7sTFXFU.js.map +1 -0
  7. package/dist/chunks/execution-internal-DWteDHM2.js.map +1 -1
  8. package/dist/chunks/feedback-C3OHD4sA.js +625 -0
  9. package/dist/chunks/feedback-C3OHD4sA.js.map +1 -0
  10. package/dist/chunks/{extraction-bseWZuWN.js → proposal-errors-z24VKWnQ.js} +10 -2
  11. package/dist/chunks/proposal-errors-z24VKWnQ.js.map +1 -0
  12. package/dist/dto.d.ts +2 -0
  13. package/dist/dto.d.ts.map +1 -1
  14. package/dist/execution-internal.d.ts +3 -1
  15. package/dist/execution-internal.d.ts.map +1 -1
  16. package/dist/execution.d.ts +15 -1
  17. package/dist/execution.d.ts.map +1 -1
  18. package/dist/execution.js +1 -1
  19. package/dist/feedback-contracts.d.ts +51 -0
  20. package/dist/feedback-contracts.d.ts.map +1 -0
  21. package/dist/feedback-dto.d.ts +106 -0
  22. package/dist/feedback-dto.d.ts.map +1 -0
  23. package/dist/feedback-internal.d.ts +19 -0
  24. package/dist/feedback-internal.d.ts.map +1 -0
  25. package/dist/feedback.d.ts +54 -0
  26. package/dist/feedback.d.ts.map +1 -0
  27. package/dist/manifest.json +34 -1
  28. package/dist/models.js +1 -1
  29. package/dist/models.js.map +1 -1
  30. package/dist/proposal-contracts.d.ts +4 -0
  31. package/dist/proposal-contracts.d.ts.map +1 -1
  32. package/dist/proposal-dto.d.ts +4 -0
  33. package/dist/proposal-dto.d.ts.map +1 -1
  34. package/dist/proposal-errors.d.ts +5 -0
  35. package/dist/proposal-errors.d.ts.map +1 -0
  36. package/dist/proposal-sdk.d.ts.map +1 -1
  37. package/dist/proposals.d.ts +4 -1
  38. package/dist/proposals.d.ts.map +1 -1
  39. package/dist/proposals.js +114 -15
  40. package/dist/proposals.js.map +1 -1
  41. package/dist/review-dto.d.ts +115 -0
  42. package/dist/review-dto.d.ts.map +1 -0
  43. package/dist/server.d.ts +24 -0
  44. package/dist/server.d.ts.map +1 -1
  45. package/dist/server.js +146 -54
  46. package/dist/server.js.map +1 -1
  47. package/dist/smrt-knowledge.json +97 -14
  48. package/dist/svelte/components/ActionReview.svelte +232 -0
  49. package/dist/svelte/components/ActionReview.svelte.d.ts +15 -0
  50. package/dist/svelte/components/ActionReview.svelte.d.ts.map +1 -0
  51. package/dist/svelte/components/EvidenceViewer.svelte +31 -0
  52. package/dist/svelte/components/EvidenceViewer.svelte.d.ts +8 -0
  53. package/dist/svelte/components/EvidenceViewer.svelte.d.ts.map +1 -0
  54. package/dist/svelte/components/InboxView.svelte +119 -0
  55. package/dist/svelte/components/InboxView.svelte.d.ts +9 -0
  56. package/dist/svelte/components/InboxView.svelte.d.ts.map +1 -0
  57. package/dist/svelte/components/IntakeInbox.svelte +11 -0
  58. package/dist/svelte/components/IntakeInbox.svelte.d.ts +10 -0
  59. package/dist/svelte/components/IntakeInbox.svelte.d.ts.map +1 -0
  60. package/dist/svelte/components/IntakeReview.svelte +11 -0
  61. package/dist/svelte/components/IntakeReview.svelte.d.ts +10 -0
  62. package/dist/svelte/components/IntakeReview.svelte.d.ts.map +1 -0
  63. package/dist/svelte/components/ReviewView.svelte +243 -0
  64. package/dist/svelte/components/ReviewView.svelte.d.ts +9 -0
  65. package/dist/svelte/components/ReviewView.svelte.d.ts.map +1 -0
  66. package/dist/svelte/i18n.d.ts +72 -0
  67. package/dist/svelte/i18n.d.ts.map +1 -0
  68. package/dist/svelte/i18n.js +72 -0
  69. package/dist/svelte/index.d.ts +5 -0
  70. package/dist/svelte/index.d.ts.map +1 -0
  71. package/dist/svelte/index.js +3 -0
  72. package/dist/svelte/view-utils.d.ts +13 -0
  73. package/dist/svelte/view-utils.d.ts.map +1 -0
  74. package/dist/svelte/view-utils.js +96 -0
  75. package/package.json +49 -22
  76. package/dist/chunks/execution-C6HJvl9e.js.map +0 -1
  77. package/dist/chunks/extraction-bseWZuWN.js.map +0 -1
@@ -6341,8 +6341,12 @@ var require_ajv = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6341
6341
  });
6342
6342
  }));
6343
6343
  //#endregion
6344
- //#region src/policy.ts
6344
+ //#region src/feedback-internal.ts
6345
6345
  var import_ajv = /* @__PURE__ */ __toESM(require_ajv(), 1);
6346
+ var feedbackActionTransaction = /* @__PURE__ */ Symbol("ingestion.feedbackActionTransaction");
6347
+ var feedbackDiscoveryContext = /* @__PURE__ */ Symbol("ingestion.feedbackDiscoveryContext");
6348
+ //#endregion
6349
+ //#region src/policy.ts
6346
6350
  var lists = [
6347
6351
  "handlers",
6348
6352
  "operations",
@@ -6665,9 +6669,15 @@ var IntakeExecutionService = class {
6665
6669
  return this.tx((db) => this.discoveryContext(db, itemId, handlerId, handlerVersion, work));
6666
6670
  }
6667
6671
  /** Keep retention restoration outside the supplied foundation transaction. */
6668
- [discoveryTransaction](run, itemId, work) {
6672
+ [discoveryTransaction](run, itemId, work, requireReview = false) {
6669
6673
  return this.withRetention(() => run(async (db) => {
6670
- const authorize = (handlerId, handlerVersion, callback) => this.discoveryContext(db, itemId, handlerId, handlerVersion, callback);
6674
+ const authorize = (handlerId, handlerVersion, callback) => this.discoveryContext(db, itemId, handlerId, handlerVersion, (context) => callback(context, (model, id2) => this.config.assertTarget({
6675
+ db,
6676
+ scope: this.scope,
6677
+ itemId,
6678
+ model,
6679
+ id: id2
6680
+ })), requireReview);
6671
6681
  const result = await work(db, authorize);
6672
6682
  for (const [retainedItemId, deadline] of this.retentionIntents.getStore() ?? []) {
6673
6683
  const item = await this.restoreRetention(db, retainedItemId, deadline);
@@ -6676,7 +6686,11 @@ var IntakeExecutionService = class {
6676
6686
  return result;
6677
6687
  }));
6678
6688
  }
6679
- async discoveryContext(db, itemId, handlerId, handlerVersion, work) {
6689
+ async discoveryContext(db, itemId, handlerId, handlerVersion, work, requireReview = false, revalidate) {
6690
+ const review = async () => {
6691
+ const item = await this.item(db, itemId, true);
6692
+ await this.access(db, item, this.handler(handlerId, handlerVersion), "review");
6693
+ };
6680
6694
  const authorize = async (callback) => {
6681
6695
  const item = await this.item(db, itemId, true);
6682
6696
  const handler = this.handler(handlerId, handlerVersion);
@@ -6701,16 +6715,18 @@ var IntakeExecutionService = class {
6701
6715
  }
6702
6716
  return callback(context);
6703
6717
  };
6718
+ if (requireReview) await review();
6704
6719
  const result = await authorize(work);
6705
- await authorize(async () => {});
6720
+ await authorize(revalidate ?? (async () => {}));
6721
+ if (requireReview) await review();
6706
6722
  return result;
6707
6723
  }
6708
- async bound(db, actionId, lock = false, expired = false) {
6724
+ async bound(db, actionId, lock = false, expired = false, proposalId) {
6709
6725
  const [found] = await this.rows(db, "intake_actions", "id=?", [actionId]);
6710
6726
  if (!found) throw new Error("Intake unavailable");
6711
6727
  const item = await this.item(db, String(found.item_id), lock, expired);
6712
6728
  const [action] = await this.rows(db, "intake_actions", "id=?", [actionId]);
6713
- const [proposal] = await this.rows(db, "intake_proposals", "action_id=? AND revision=?", [actionId, action.proposal_revision]);
6729
+ const [proposal] = await this.rows(db, "intake_proposals", proposalId ? "action_id=? AND id=?" : "action_id=? AND revision=?", [actionId, proposalId ?? action.proposal_revision]);
6714
6730
  if (!proposal) throw new Error("Proposal unavailable");
6715
6731
  const binding = object(object(proposal.data).binding);
6716
6732
  const handler = this.operation(this.handler(binding.handlerId, binding.handlerVersion));
@@ -6737,6 +6753,104 @@ var IntakeExecutionService = class {
6737
6753
  state: String(bound.action.state)
6738
6754
  };
6739
6755
  }
6756
+ /** Same-executor current review/evidence/target gate for the owning feedback ledger. */
6757
+ /** Join an existing owning transaction; its outer boundary restores retention after rollback. */
6758
+ [feedbackDiscoveryContext](db, itemId, handlerId, handlerVersion, work, revalidate) {
6759
+ const run = (executor) => this.discoveryContext(executor, itemId, handlerId, handlerVersion, work, false, revalidate);
6760
+ return db ? run(db) : this.tx(run);
6761
+ }
6762
+ [feedbackActionTransaction](actionId, reviewer, work, executor, requiredProviders, proposalId) {
6763
+ const operation = async (db) => {
6764
+ const load = async () => {
6765
+ const bound = await this.bound(db, actionId, true, false, proposalId);
6766
+ const { context, permissions } = await this.access(db, bound.item, bound.handler, reviewer ? "review" : "read");
6767
+ if (requiredProviders?.some((provider) => !context.policy.providers.includes(provider))) throw new Error("Feedback source provider unavailable");
6768
+ await this.verifyEvidence(db, bound);
6769
+ for (const target of bound.binding.targetPreconditions) await context.assertTarget(target.model, target.id, bound.action.state === "succeeded" ? void 0 : target.revision);
6770
+ if (bound.binding.plan) {
6771
+ 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];
6772
+ if (!plan || plan.digest !== bound.binding.plan.digest) throw new Error("Plan superseded");
6773
+ const data2 = object(plan.data);
6774
+ await this.discoveryContext(db, String(bound.item.id), String(data2.handlerId), String(data2.handlerVersion), async (parent) => {
6775
+ for (const target of object(data2.preview).targetPreconditions) await parent.assertTarget(target.model, target.id, bound.action.state === "succeeded" ? void 0 : target.revision);
6776
+ }, reviewer);
6777
+ }
6778
+ const [analysis] = await this.rows(db, "intake_analyses", "id=?", [bound.proposal.analysis_id]);
6779
+ const [attempt] = await this.rows(db, "intake_analysis_attempts", "id=?", [bound.proposal.analysis_attempt_id]);
6780
+ if (!analysis || !attempt || analysis.current_attempt_id !== attempt.id) throw new Error("Stale feedback source");
6781
+ const data = object(attempt.data);
6782
+ const result2 = {
6783
+ status: data.status,
6784
+ provider: data.provider,
6785
+ model: data.model,
6786
+ version: data.version,
6787
+ output: data.output,
6788
+ usage: data.usage,
6789
+ ...Object.hasOwn(data, "error") ? { error: data.error } : {},
6790
+ ...Object.hasOwn(data, "confidence") ? { confidence: data.confidence } : {}
6791
+ };
6792
+ if (intakeBindingDigest(result2) !== attempt.output_digest) throw new Error("Analysis output integrity");
6793
+ const input = object(analysis.data);
6794
+ if (intakeBindingDigest({
6795
+ inputs: input.inputs,
6796
+ configuration: input.configuration
6797
+ }) !== analysis.input_digest) throw new Error("Analysis input integrity");
6798
+ if (!this.retained(bound.item)) throw new Error("Feedback unavailable");
6799
+ return {
6800
+ db,
6801
+ context,
6802
+ permissions,
6803
+ review: this.review(bound),
6804
+ itemId: String(bound.item.id),
6805
+ attemptId: String(attempt.id),
6806
+ args: structuredClone(bound.binding.args),
6807
+ analysis: input,
6808
+ result: result2,
6809
+ handlerId: bound.handler.id,
6810
+ handlerVersion: bound.handler.version
6811
+ };
6812
+ };
6813
+ const before = await load();
6814
+ const result = await work(before);
6815
+ const after = await load();
6816
+ if (intakeBindingDigest(before.review) !== intakeBindingDigest(after.review)) throw new Error("Feedback binding changed");
6817
+ if (after.permissions.some((permission) => !before.permissions.includes(permission))) throw new Error("Feedback cannot widen permissions");
6818
+ const prior = before.context.policy, next = after.context.policy;
6819
+ for (const key of [
6820
+ "handlers",
6821
+ "operations",
6822
+ "reviewers",
6823
+ "providers",
6824
+ "access",
6825
+ "automaticHandlers"
6826
+ ]) if (next[key].some((value) => !prior[key].includes(value))) throw new Error("Feedback cannot widen policy");
6827
+ for (const key of [
6828
+ "maxAttempts",
6829
+ "maxSteps",
6830
+ "maxBytes",
6831
+ "approvalMs",
6832
+ "leaseMs",
6833
+ "retentionMs"
6834
+ ]) if (next[key] > prior[key]) throw new Error("Feedback cannot widen policy");
6835
+ if (prior.requireReview && !next.requireReview || next.minimumCertainty < prior.minimumCertainty || !prior.consequentialAutomation && next.consequentialAutomation) throw new Error("Feedback cannot widen policy");
6836
+ return result;
6837
+ };
6838
+ return executor ? operation(executor) : this.tx(operation);
6839
+ }
6840
+ async feedbackLineage(db, bound) {
6841
+ const [attempt] = await this.rows(db, "intake_analysis_attempts", "id=?", [bound.proposal.analysis_attempt_id]);
6842
+ if (!attempt) throw new Error("Analysis unavailable");
6843
+ const output = object(object(attempt.data).output);
6844
+ if (!output.proposals) return;
6845
+ const feedback = object(object(output.proposals).provenance).feedback;
6846
+ if (!feedback) return;
6847
+ const references = object(object(feedback).selection).examples;
6848
+ if (!Array.isArray(references)) throw new Error("Feedback lineage unavailable");
6849
+ if (references.length === 0) return;
6850
+ if (!this.options.feedback) throw new Error("Feedback configuration unavailable");
6851
+ const { IntakeFeedbackService } = await import("./feedback-C3OHD4sA.js").then((n) => n.n);
6852
+ await new IntakeFeedbackService(this.options, this).assertActionLineage(String(bound.action.id), db, String(bound.proposal.id));
6853
+ }
6740
6854
  async getAction(actionId) {
6741
6855
  return this.tx(async (db) => {
6742
6856
  const [action] = await this.rows(db, "intake_actions", "id=?", [actionId]);
@@ -6760,9 +6874,175 @@ var IntakeExecutionService = class {
6760
6874
  const bound = await this.bound(db, actionId);
6761
6875
  await this.access(db, bound.item, bound.handler, "read");
6762
6876
  if (!this.retained(bound.item)) return expiredReview();
6877
+ try {
6878
+ await this.feedbackLineage(db, bound);
6879
+ } catch (error) {
6880
+ if (bound.action.state !== "succeeded") throw error;
6881
+ await this.successful(db, bound.action, bound.item);
6882
+ return {
6883
+ ...this.review(bound),
6884
+ display: {}
6885
+ };
6886
+ }
6763
6887
  return this.review(bound);
6764
6888
  });
6765
6889
  }
6890
+ /** Bounded reload discovery. The cursor is an opaque action ID, never authority. */
6891
+ async listReviews(itemId, input = {}) {
6892
+ const limit = input.limit ?? 20;
6893
+ 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");
6894
+ return this.tx(async (db) => {
6895
+ const item = await this.item(db, itemId, true, true);
6896
+ 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;
6897
+ const actions = [];
6898
+ let generationStale = false;
6899
+ for (const action of rows.slice(0, limit)) {
6900
+ const [proposal] = await this.rows(db, "intake_proposals", "action_id=? AND revision=?", [action.id, action.proposal_revision]);
6901
+ if (!proposal) {
6902
+ actions.push({ review: {
6903
+ actionId: String(action.id),
6904
+ proposalId: "",
6905
+ revision: Number(action.proposal_revision),
6906
+ reviewVersion: Number(action.review_version),
6907
+ bindingHash: "",
6908
+ display: {},
6909
+ state: this.retained(item) ? "proposed" : "expired"
6910
+ } });
6911
+ continue;
6912
+ }
6913
+ const tombstone = (state) => ({ review: {
6914
+ actionId: String(action.id),
6915
+ proposalId: String(proposal.id),
6916
+ revision: Number(proposal.revision),
6917
+ reviewVersion: Number(action.review_version),
6918
+ bindingHash: String(proposal.binding_hash),
6919
+ display: {},
6920
+ state
6921
+ } });
6922
+ if (!this.retained(item)) {
6923
+ actions.push(tombstone("expired"));
6924
+ continue;
6925
+ }
6926
+ const bound = await this.bound(db, String(action.id));
6927
+ await this.access(db, bound.item, bound.handler, "read");
6928
+ item.expires_at = bound.item.expires_at;
6929
+ if (!this.retained(bound.item)) {
6930
+ actions.push(tombstone("expired"));
6931
+ continue;
6932
+ }
6933
+ let stale = false;
6934
+ let recoveryAttemptId;
6935
+ const authorizeTargets = async (targets) => {
6936
+ for (const target of targets) if ((await this.config.assertTarget({
6937
+ db,
6938
+ scope: this.scope,
6939
+ itemId,
6940
+ model: target.model,
6941
+ id: target.id
6942
+ })).revision !== target.revision) {
6943
+ generationStale = true;
6944
+ if (bound.action.state !== "succeeded") stale = true;
6945
+ }
6946
+ };
6947
+ await authorizeTargets(bound.binding.targetPreconditions);
6948
+ if (!this.retained(bound.item)) {
6949
+ actions.push(tombstone("expired"));
6950
+ continue;
6951
+ }
6952
+ if (bound.action.state !== "succeeded") {
6953
+ const [analysis] = await this.rows(db, "intake_analyses", "id=?", [proposal.analysis_id]);
6954
+ if (!analysis || Number(analysis.revision) !== Number(item.analysis_revision)) stale = true;
6955
+ else {
6956
+ await this.verifyEvidence(db, bound);
6957
+ const [attempt] = await this.rows(db, "intake_analysis_attempts", "id=? AND analysis_id=?", [proposal.analysis_attempt_id, analysis.id]);
6958
+ if (attempt && ["completed", "partial"].includes(String(attempt.state)) && String(analysis.current_attempt_id) === String(attempt.id)) recoveryAttemptId = String(attempt.id);
6959
+ }
6960
+ }
6961
+ const entry = {
6962
+ review: this.review(bound),
6963
+ args: structuredClone(bound.binding.args),
6964
+ handlerId: bound.handler.id,
6965
+ handlerVersion: bound.handler.version,
6966
+ attemptId: String(proposal.analysis_attempt_id),
6967
+ dependencies: structuredClone(bound.binding.dependencies)
6968
+ };
6969
+ if (action.state === "succeeded") entry.result = await this.successful(db, action, item);
6970
+ if (bound.binding.plan) {
6971
+ const [plan] = await this.rows(db, "intake_plans", bound.action.state === "succeeded" ? "item_id=? AND plan_key=? AND revision=?" : "item_id=? AND plan_key=? ORDER BY revision DESC LIMIT 1", bound.action.state === "succeeded" ? [
6972
+ itemId,
6973
+ bound.binding.plan.key,
6974
+ bound.binding.plan.revision
6975
+ ] : [itemId, bound.binding.plan.key]);
6976
+ if (!plan) throw new Error("Plan unavailable");
6977
+ if (bound.action.state !== "succeeded" && Number(plan.revision) !== bound.binding.plan.revision) stale = true;
6978
+ const planData = object(plan.data);
6979
+ const parent = this.handler(String(planData.handlerId), String(planData.handlerVersion));
6980
+ const parentPreview = object(planData.preview);
6981
+ await this.discoveryContext(db, itemId, parent.id, parent.version, async () => {
6982
+ await authorizeTargets(parentPreview.targetPreconditions);
6983
+ });
6984
+ const steps = planData.steps;
6985
+ entry.plan = {
6986
+ id: String(plan.id),
6987
+ key: String(plan.plan_key),
6988
+ revision: Number(plan.revision),
6989
+ stepIndex: steps.findIndex((step) => step.actionId === action.id),
6990
+ args: object(planData.args),
6991
+ handlerId: parent.id,
6992
+ handlerVersion: parent.version,
6993
+ attemptId: String(proposal.analysis_attempt_id)
6994
+ };
6995
+ }
6996
+ await this.access(db, bound.item, bound.handler, "read");
6997
+ actions.push(!this.retained(bound.item) ? tombstone("expired") : stale ? {
6998
+ ...tombstone("stale"),
6999
+ ...recoveryAttemptId ? {
7000
+ attemptId: recoveryAttemptId,
7001
+ handlerId: bound.handler.id,
7002
+ handlerVersion: bound.handler.version
7003
+ } : {},
7004
+ ...entry.plan ? { stalePlan: {
7005
+ id: entry.plan.id,
7006
+ key: entry.plan.key,
7007
+ revision: entry.plan.revision,
7008
+ handlerId: entry.plan.handlerId,
7009
+ handlerVersion: entry.plan.handlerVersion
7010
+ } } : {}
7011
+ } : entry);
7012
+ }
7013
+ const current = await this.item(db, itemId, false, true);
7014
+ if (!this.retained(current)) {
7015
+ await this.redactExecution(db, current);
7016
+ for (let index = 0; index < actions.length; index++) actions[index] = { review: {
7017
+ ...actions[index].review,
7018
+ display: {},
7019
+ state: "expired"
7020
+ } };
7021
+ }
7022
+ for (let index = 0; index < actions.length; index++) {
7023
+ const entry = actions[index];
7024
+ if (!entry.review.proposalId || entry.review.state === "expired" || entry.review.state === "stale") continue;
7025
+ const bound = await this.bound(db, entry.review.actionId);
7026
+ try {
7027
+ await this.feedbackLineage(db, bound);
7028
+ } catch (error) {
7029
+ if (bound.action.state !== "succeeded") throw error;
7030
+ actions[index] = {
7031
+ review: {
7032
+ ...entry.review,
7033
+ display: {}
7034
+ },
7035
+ result: await this.successful(db, bound.action, bound.item)
7036
+ };
7037
+ }
7038
+ }
7039
+ return {
7040
+ actions,
7041
+ ...generationStale ? { generationStale: true } : {},
7042
+ ...rows.length > limit ? { nextCursor: String(rows[limit - 1].id) } : {}
7043
+ };
7044
+ });
7045
+ }
6766
7046
  async buildBinding(db, item, input) {
6767
7047
  const handler = this.operation(this.handler(input.handlerId, input.handlerVersion));
6768
7048
  const { policy, context } = await this.access(db, item, handler, "preview");
@@ -6850,6 +7130,7 @@ var IntakeExecutionService = class {
6850
7130
  const replay = prior.find((row) => object(row.data).requestId === input.requestId);
6851
7131
  if (replay) {
6852
7132
  if (object(replay.data).requestHash !== requestHash) throw new Error("Proposal request conflict");
7133
+ await this.feedbackLineage(db, await this.bound(db, input.actionId, false, false, String(replay.id)));
6853
7134
  return {
6854
7135
  actionId: input.actionId,
6855
7136
  proposalId: String(replay.id),
@@ -6891,6 +7172,7 @@ var IntakeExecutionService = class {
6891
7172
  })
6892
7173
  });
6893
7174
  await db.query("UPDATE intake_actions SET proposal_revision=?,review_version=?,state='waiting_review' WHERE id=? AND tenant_id=? AND confidential_scope_id=?", revision, reviewVersion, action.id, this.scope.tenantId, this.scope.confidentialScopeId);
7175
+ await this.feedbackLineage(db, await this.bound(db, input.actionId, false, false, String(proposal.id)));
6894
7176
  return {
6895
7177
  actionId: String(action.id),
6896
7178
  proposalId: String(proposal.id),
@@ -6909,6 +7191,62 @@ var IntakeExecutionService = class {
6909
7191
  return this.publish(db, item, frozen, binding);
6910
7192
  });
6911
7193
  }
7194
+ /** Capture owner facts in the same transaction; these are never correctness labels. */
7195
+ async feedbackEvent(db, bound, eventId, kind) {
7196
+ if (!this.options.feedback || !this.retained(bound.item)) return;
7197
+ if (!await this.options.feedback.authorize({
7198
+ db,
7199
+ scope: this.scope,
7200
+ itemId: String(bound.item.id),
7201
+ operation: "capture"
7202
+ })) return;
7203
+ const table = kind === "action_decision" ? "intake_review_decisions" : "intake_executions";
7204
+ const [event] = await this.rows(db, table, "id=? AND action_id=?", [eventId, bound.action.id]);
7205
+ if (!event) throw new Error("Feedback event unavailable");
7206
+ const eventData = object(event.data), signal = kind === "action_decision" ? event.decision : event.state;
7207
+ const key = `event:${kind}:${eventId}:${String(signal)}`;
7208
+ const [replay] = await this.rows(db, "intake_feedback", "item_id=? AND request_key=?", [bound.item.id, key]);
7209
+ if (replay) return;
7210
+ const [attempt] = await this.rows(db, "intake_analysis_attempts", "id=?", [bound.proposal.analysis_attempt_id]);
7211
+ const provenance = object(attempt.data), generation = object(object(provenance.output).proposals ?? {});
7212
+ const body = {
7213
+ version: 1,
7214
+ eventId,
7215
+ actionId: bound.action.id,
7216
+ proposalId: bound.proposal.id,
7217
+ revision: Number(bound.proposal.revision),
7218
+ bindingHash: bound.proposal.binding_hash,
7219
+ attemptId: attempt.id,
7220
+ attemptDigest: attempt.output_digest,
7221
+ evidence: bound.binding.evidence,
7222
+ handlerId: bound.handler.id,
7223
+ handlerVersion: bound.handler.version,
7224
+ signal,
7225
+ reviewer: kind === "action_decision" ? eventData.reviewer : null,
7226
+ observedBy: this.scope.actorId,
7227
+ principalId: eventData.principalId ?? null,
7228
+ reason: kind === "action_decision" ? eventData.reason ?? "" : "",
7229
+ resultDigest: event.result_digest ?? null,
7230
+ model: {
7231
+ provider: provenance.provider,
7232
+ model: provenance.model,
7233
+ version: provenance.version
7234
+ },
7235
+ generationProvenance: generation.provenance ?? null
7236
+ };
7237
+ const data = {
7238
+ ...body,
7239
+ digest: intakeBindingDigest(body)
7240
+ };
7241
+ if (Buffer.byteLength(canonical(data)) > this.options.feedback.maxBytes) return;
7242
+ await this.insert(db, "intake_feedback", {
7243
+ item_id: bound.item.id,
7244
+ request_key: key,
7245
+ supersedes_id: null,
7246
+ kind,
7247
+ data: canonical(data)
7248
+ });
7249
+ }
6912
7250
  async submitDecision(input) {
6913
7251
  const frozen = JSON.parse(canonical(input));
6914
7252
  id(frozen.requestId);
@@ -6921,6 +7259,7 @@ var IntakeExecutionService = class {
6921
7259
  return this.tx(async (db) => {
6922
7260
  const bound = await this.bound(db, frozen.actionId, true);
6923
7261
  const { policy } = await this.access(db, bound.item, bound.handler, "review");
7262
+ await this.feedbackLineage(db, bound);
6924
7263
  const decisions = await this.rows(db, "intake_review_decisions", "action_id=?", [frozen.actionId]);
6925
7264
  const requestHash = intakeBindingDigest(frozen);
6926
7265
  const replay = decisions.find((row) => object(row.data).requestId === frozen.requestId);
@@ -6932,7 +7271,7 @@ var IntakeExecutionService = class {
6932
7271
  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");
6933
7272
  await this.verifyEvidence(db, bound);
6934
7273
  const expiresAt = new Date(Math.min(time(bound.item.expires_at), this.now().getTime() + policy.approvalMs)).toISOString();
6935
- await this.insert(db, "intake_review_decisions", {
7274
+ const decisionEvent = await this.insert(db, "intake_review_decisions", {
6936
7275
  item_id: bound.item.id,
6937
7276
  action_id: bound.action.id,
6938
7277
  proposal_id: bound.proposal.id,
@@ -6950,6 +7289,7 @@ var IntakeExecutionService = class {
6950
7289
  kind: "human"
6951
7290
  })
6952
7291
  });
7292
+ await this.feedbackEvent(db, bound, String(decisionEvent.id), "action_decision");
6953
7293
  if (frozen.decision === "correct") {
6954
7294
  if (bound.binding.plan) throw new Error("Plan correction requires re-expansion");
6955
7295
  if (!frozen.correctedArgs) throw new Error("Correction arguments required");
@@ -6965,8 +7305,10 @@ var IntakeExecutionService = class {
6965
7305
  dependencies: bound.binding.dependencies
6966
7306
  };
6967
7307
  const binding = await this.buildBinding(db, bound.item, corrected);
7308
+ await this.feedbackLineage(db, bound);
6968
7309
  return this.publish(db, bound.item, corrected, binding);
6969
7310
  }
7311
+ await this.feedbackLineage(db, bound);
6970
7312
  const state = {
6971
7313
  approve: "authorized",
6972
7314
  reject: "rejected",
@@ -6995,6 +7337,7 @@ var IntakeExecutionService = class {
6995
7337
  }
6996
7338
  }
6997
7339
  async approved(db, bound) {
7340
+ await this.feedbackLineage(db, bound);
6998
7341
  await this.verifyEvidence(db, bound);
6999
7342
  if (bound.binding.plan) {
7000
7343
  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];
@@ -7053,6 +7396,7 @@ var IntakeExecutionService = class {
7053
7396
  const access = await this.access(db, bound.item, bound.handler, "execute");
7054
7397
  const consequential = bound.handler.execution.kind === "external" || bound.handler.capability.effect === "destructive" || bound.handler.capability.openWorld;
7055
7398
  if (!access.policy.automaticHandlers.includes(bound.handler.id) || consequential && !access.policy.consequentialAutomation || !this.config.evaluateAutomatic) throw new Error("Automatic eligibility revoked");
7399
+ await this.feedbackLineage(db, bound);
7056
7400
  const evaluated = await this.config.evaluateAutomatic({
7057
7401
  bindingHash: String(bound.proposal.binding_hash),
7058
7402
  handlerId: bound.handler.id,
@@ -7062,6 +7406,7 @@ var IntakeExecutionService = class {
7062
7406
  }
7063
7407
  if (time(object(decision.data).expiresAt) <= this.now().getTime()) throw new Error("Approval required");
7064
7408
  await this.verifyEvidence(db, bound);
7409
+ await this.feedbackLineage(db, bound);
7065
7410
  return Math.min(time(decisionData.expiresAt), time(bound.item.expires_at));
7066
7411
  }
7067
7412
  async arguments(db, bound, context) {
@@ -7169,6 +7514,7 @@ var IntakeExecutionService = class {
7169
7514
  };
7170
7515
  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");
7171
7516
  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);
7517
+ await this.feedbackEvent(db, bound, String(execution.id), "downstream_outcome");
7172
7518
  return {
7173
7519
  state: "succeeded",
7174
7520
  actionId: String(bound.action.id),
@@ -7212,6 +7558,7 @@ var IntakeExecutionService = class {
7212
7558
  };
7213
7559
  await db.query("UPDATE intake_actions SET state='outcome_unknown' WHERE id=? AND tenant_id=? AND confidential_scope_id=?", actionId, this.scope.tenantId, this.scope.confidentialScopeId);
7214
7560
  await db.query("UPDATE intake_executions SET state='outcome_unknown' WHERE id=? AND tenant_id=? AND confidential_scope_id=?", execution2.id, this.scope.tenantId, this.scope.confidentialScopeId);
7561
+ await this.feedbackEvent(db, bound2, String(execution2.id), "downstream_outcome");
7215
7562
  return {
7216
7563
  state: "outcome_unknown",
7217
7564
  actionId
@@ -7222,6 +7569,7 @@ var IntakeExecutionService = class {
7222
7569
  return this.principal(db, bound2.item, bound2.handler, "execute", async (context) => {
7223
7570
  if (Number(bound2.action.execution_attempt) >= context.policy.maxAttempts) throw new Error("Execution budget exceeded");
7224
7571
  const args = await this.arguments(db, bound2, context);
7572
+ await this.feedbackLineage(db, bound2);
7225
7573
  const attemptNumber = Number(bound2.action.execution_attempt) + 1;
7226
7574
  const fence = Number(bound2.action.fence) + 1;
7227
7575
  const execution2 = await this.insert(db, "intake_executions", {
@@ -7277,6 +7625,7 @@ var IntakeExecutionService = class {
7277
7625
  error: "domain_failure",
7278
7626
  rolledBack: true
7279
7627
  } : {}), execution2.id, this.scope.tenantId, this.scope.confidentialScopeId);
7628
+ await this.feedbackEvent(db, bound2, String(execution2.id), "downstream_outcome");
7280
7629
  await db.query("UPDATE intake_actions SET state='failed' WHERE id=? AND tenant_id=? AND confidential_scope_id=?", actionId, this.scope.tenantId, this.scope.confidentialScopeId);
7281
7630
  return {
7282
7631
  state: "failed",
@@ -7340,6 +7689,10 @@ var IntakeExecutionService = class {
7340
7689
  if (!this.retained(item)) await this.redactExecution(db, item);
7341
7690
  await db.query("UPDATE intake_actions SET state='outcome_unknown' WHERE id=? AND tenant_id=? AND confidential_scope_id=? AND fence=? AND state='executing'", actionId, this.scope.tenantId, this.scope.confidentialScopeId, execution.fence);
7342
7691
  await db.query("UPDATE intake_executions SET state='outcome_unknown' WHERE id=? AND tenant_id=? AND confidential_scope_id=? AND state='claimed'", execution.id, this.scope.tenantId, this.scope.confidentialScopeId);
7692
+ await this.feedbackEvent(db, {
7693
+ ...bound,
7694
+ item
7695
+ }, String(execution.id), "downstream_outcome");
7343
7696
  return {
7344
7697
  state: "outcome_unknown",
7345
7698
  actionId,
@@ -7393,6 +7746,10 @@ var IntakeExecutionService = class {
7393
7746
  ...object(execution.data),
7394
7747
  reconciliation: observed.kind
7395
7748
  }), execution.id, this.scope.tenantId, this.scope.confidentialScopeId);
7749
+ await this.feedbackEvent(db, {
7750
+ ...bound,
7751
+ item
7752
+ }, String(execution.id), "downstream_outcome");
7396
7753
  return {
7397
7754
  state,
7398
7755
  actionId,
@@ -7407,12 +7764,14 @@ var IntakeExecutionService = class {
7407
7764
  const { current, policy, context } = await this.access(db, bound.item, bound.handler, "execute");
7408
7765
  const consequential = bound.handler.execution.kind === "external" || bound.handler.capability.effect === "destructive" || bound.handler.capability.openWorld;
7409
7766
  if (bound.action.state !== "waiting_review" || !policy.automaticHandlers.includes(bound.handler.id) || consequential && !policy.consequentialAutomation || !this.config.evaluateAutomatic) throw new Error("Automatic execution unavailable");
7767
+ await this.feedbackLineage(db, bound);
7410
7768
  const evaluated = await this.config.evaluateAutomatic({
7411
7769
  bindingHash: String(bound.proposal.binding_hash),
7412
7770
  handlerId: bound.handler.id,
7413
7771
  context
7414
7772
  });
7415
7773
  if (!evaluated.eligible || !Number.isFinite(evaluated.certainty) || evaluated.certainty < policy.minimumCertainty || evaluated.certainty > 1 || current.policy.filter((layer, index) => index < 3 || layer.automation !== void 0).some((layer) => layer.automation?.evaluationVersion !== evaluated.evaluationVersion)) throw new Error("Automatic execution unavailable");
7774
+ await this.feedbackLineage(db, bound);
7416
7775
  await this.verifyEvidence(db, bound);
7417
7776
  if (!this.retained(bound.item)) throw new RetentionExpired(String(bound.item.id), time(bound.item.expires_at));
7418
7777
  await this.insert(db, "intake_review_decisions", {
@@ -7471,7 +7830,11 @@ var IntakeExecutionService = class {
7471
7830
  key: frozen.planKey,
7472
7831
  revision: Number(replay.revision),
7473
7832
  digest: String(replay.digest),
7474
- steps: await Promise.all(steps.map(async (step) => this.review(await this.bound(db, step.actionId))))
7833
+ steps: await Promise.all(steps.map(async (step) => {
7834
+ const bound = await this.bound(db, step.actionId);
7835
+ await this.feedbackLineage(db, bound);
7836
+ return this.review(bound);
7837
+ }))
7475
7838
  };
7476
7839
  }
7477
7840
  if (Math.max(0, ...existing.map((plan2) => Number(plan2.revision))) !== frozen.expectedRevision) throw new Error("Plan conflict");
@@ -7600,6 +7963,7 @@ var IntakeExecutionService = class {
7600
7963
  }))
7601
7964
  })
7602
7965
  });
7966
+ for (const review of reviews) await this.feedbackLineage(db, await this.bound(db, review.actionId, false, false, review.proposalId));
7603
7967
  return {
7604
7968
  id: String(plan.id),
7605
7969
  key: frozen.planKey,
@@ -7662,6 +8026,6 @@ async function continueIntakeReview(service, actionId, task) {
7662
8026
  return service.applyAction(actionId);
7663
8027
  }
7664
8028
  //#endregion
7665
- export { require_ajv as a, resolveIntakePolicy as i, continueIntakeReview as n, __toESM as o, intakeBindingDigest as r, IntakeExecutionService as t };
8029
+ export { feedbackActionTransaction as a, __exportAll as c, resolveIntakePolicy as i, __toESM as l, continueIntakeReview as n, feedbackDiscoveryContext as o, intakeBindingDigest as r, require_ajv as s, IntakeExecutionService as t };
7666
8030
 
7667
- //# sourceMappingURL=execution-C6HJvl9e.js.map
8031
+ //# sourceMappingURL=execution-C7sTFXFU.js.map