@dsh-enhanced/assistant-delivery 0.1.65 → 0.1.67

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 (101) hide show
  1. package/README.md +128 -4
  2. package/lib/agent-runtime.d.ts +6 -2
  3. package/lib/agent-runtime.d.ts.map +1 -1
  4. package/lib/agent-runtime.js +60 -14
  5. package/lib/agent-runtime.js.map +1 -1
  6. package/lib/attention-audit-store.d.ts +54 -0
  7. package/lib/attention-audit-store.d.ts.map +1 -0
  8. package/lib/attention-audit-store.js +213 -0
  9. package/lib/attention-audit-store.js.map +1 -0
  10. package/lib/attention-enforcement.d.ts +75 -0
  11. package/lib/attention-enforcement.d.ts.map +1 -0
  12. package/lib/attention-enforcement.js +84 -0
  13. package/lib/attention-enforcement.js.map +1 -0
  14. package/lib/attention.d.ts +203 -0
  15. package/lib/attention.d.ts.map +1 -0
  16. package/lib/attention.js +288 -0
  17. package/lib/attention.js.map +1 -0
  18. package/lib/coordinator.d.ts.map +1 -1
  19. package/lib/coordinator.js +6 -0
  20. package/lib/coordinator.js.map +1 -1
  21. package/lib/feedback-command.d.ts +23 -0
  22. package/lib/feedback-command.d.ts.map +1 -1
  23. package/lib/feedback-command.js +176 -0
  24. package/lib/feedback-command.js.map +1 -1
  25. package/lib/foreground-observation.d.ts +21 -0
  26. package/lib/foreground-observation.d.ts.map +1 -1
  27. package/lib/foreground-observation.js.map +1 -1
  28. package/lib/foreground-termination.d.ts +31 -0
  29. package/lib/foreground-termination.d.ts.map +1 -0
  30. package/lib/foreground-termination.js +42 -0
  31. package/lib/foreground-termination.js.map +1 -0
  32. package/lib/in-process-reload-runtime.d.ts +87 -0
  33. package/lib/in-process-reload-runtime.d.ts.map +1 -0
  34. package/lib/in-process-reload-runtime.js +319 -0
  35. package/lib/in-process-reload-runtime.js.map +1 -0
  36. package/lib/index.d.ts +20 -6
  37. package/lib/index.d.ts.map +1 -1
  38. package/lib/index.js +9 -3
  39. package/lib/index.js.map +1 -1
  40. package/lib/native-termination.d.ts +12 -0
  41. package/lib/native-termination.d.ts.map +1 -0
  42. package/lib/native-termination.js +57 -0
  43. package/lib/native-termination.js.map +1 -0
  44. package/lib/native-web-owner.d.ts +7 -0
  45. package/lib/native-web-owner.d.ts.map +1 -1
  46. package/lib/native-web-owner.js +40 -10
  47. package/lib/native-web-owner.js.map +1 -1
  48. package/lib/operator-snapshot.d.ts +31 -3
  49. package/lib/operator-snapshot.d.ts.map +1 -1
  50. package/lib/operator-snapshot.js +120 -47
  51. package/lib/operator-snapshot.js.map +1 -1
  52. package/lib/progress-attention.d.ts +55 -0
  53. package/lib/progress-attention.d.ts.map +1 -0
  54. package/lib/progress-attention.js +128 -0
  55. package/lib/progress-attention.js.map +1 -0
  56. package/lib/service.d.ts +515 -4
  57. package/lib/service.d.ts.map +1 -1
  58. package/lib/service.js +2183 -130
  59. package/lib/service.js.map +1 -1
  60. package/lib/sqlite.d.ts +1 -1
  61. package/lib/sqlite.d.ts.map +1 -1
  62. package/lib/sqlite.js +102 -2
  63. package/lib/sqlite.js.map +1 -1
  64. package/lib/store.d.ts +113 -2
  65. package/lib/store.d.ts.map +1 -1
  66. package/lib/store.js +450 -39
  67. package/lib/store.js.map +1 -1
  68. package/lib/types.d.ts +175 -0
  69. package/lib/types.d.ts.map +1 -1
  70. package/lib/types.js.map +1 -1
  71. package/lib/version.d.ts +1 -1
  72. package/lib/version.js +1 -1
  73. package/lib/work-item-engineering-context.d.ts +127 -0
  74. package/lib/work-item-engineering-context.d.ts.map +1 -0
  75. package/lib/work-item-engineering-context.js +151 -0
  76. package/lib/work-item-engineering-context.js.map +1 -0
  77. package/lib/work-item-feedback-schema.d.ts +2 -0
  78. package/lib/work-item-feedback-schema.d.ts.map +1 -0
  79. package/lib/work-item-feedback-schema.js +29 -0
  80. package/lib/work-item-feedback-schema.js.map +1 -0
  81. package/lib/work-item-feedback-store.d.ts +29 -0
  82. package/lib/work-item-feedback-store.d.ts.map +1 -0
  83. package/lib/work-item-feedback-store.js +111 -0
  84. package/lib/work-item-feedback-store.js.map +1 -0
  85. package/lib/work-item-feedback.d.ts +157 -0
  86. package/lib/work-item-feedback.d.ts.map +1 -0
  87. package/lib/work-item-feedback.js +2 -0
  88. package/lib/work-item-feedback.js.map +1 -0
  89. package/lib/work-item-replies-schema.d.ts +3 -0
  90. package/lib/work-item-replies-schema.d.ts.map +1 -0
  91. package/lib/work-item-replies-schema.js +45 -0
  92. package/lib/work-item-replies-schema.js.map +1 -0
  93. package/lib/work-item-replies-store.d.ts +43 -0
  94. package/lib/work-item-replies-store.d.ts.map +1 -0
  95. package/lib/work-item-replies-store.js +203 -0
  96. package/lib/work-item-replies-store.js.map +1 -0
  97. package/lib/work-item-replies.d.ts +93 -0
  98. package/lib/work-item-replies.d.ts.map +1 -0
  99. package/lib/work-item-replies.js +15 -0
  100. package/lib/work-item-replies.js.map +1 -0
  101. package/package.json +24 -24
@@ -0,0 +1,54 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import { type AttentionAssessment, type AttentionAuditEntry, type AttentionLane, type AttentionLedgerView, type NotificationOutcomeFact } from './attention.js';
3
+ /** Append-only WP19 stage-1 audit. One row per Outbox record; replays keep the first verdict. */
4
+ export declare const attentionAuditSchema = "\n CREATE TABLE delivery_attention_audit (\n outbox_id TEXT PRIMARY KEY REFERENCES outbox_messages(id),\n audit_sequence INTEGER NOT NULL UNIQUE CHECK(audit_sequence>=1),\n binding_id TEXT NOT NULL,\n lane TEXT NOT NULL CHECK(lane IN ('background','background-route','owner-notification','automation-result','work-item-report','scheduled-goal-result')),\n source_id TEXT NOT NULL,\n purpose TEXT NOT NULL CHECK(purpose IN ('result','attention','answer')),\n purpose_source TEXT NOT NULL CHECK(purpose_source IN ('declared','lane','default')),\n work_item_id TEXT,\n question_id TEXT,\n answers_question_id TEXT,\n word_count INTEGER NOT NULL CHECK(word_count>=0),\n word_cap INTEGER NOT NULL CHECK(word_cap>=1),\n verdict TEXT NOT NULL CHECK(verdict IN ('would-accept','would-attach','would-reject')),\n reasons_json TEXT NOT NULL CHECK(json_valid(reasons_json)),\n recorded_at INTEGER NOT NULL CHECK(recorded_at>=0)\n ) STRICT;\n CREATE INDEX delivery_attention_audit_question ON delivery_attention_audit(binding_id,question_id);\n CREATE INDEX delivery_attention_audit_answer ON delivery_attention_audit(binding_id,answers_question_id);\n CREATE INDEX delivery_attention_audit_work_item ON delivery_attention_audit(binding_id,work_item_id);\n CREATE TRIGGER delivery_attention_audit_immutable BEFORE UPDATE ON delivery_attention_audit\n BEGIN SELECT RAISE(ABORT,'attention audit is append-only'); END;\n CREATE TRIGGER delivery_attention_audit_no_delete BEFORE DELETE ON delivery_attention_audit\n BEGIN SELECT RAISE(ABORT,'attention audit is durable'); END;\n";
5
+ export declare class AttentionAuditStore {
6
+ private readonly database;
7
+ private readonly transaction;
8
+ private readonly now;
9
+ constructor(database: DatabaseSync, transaction: <T>(fn: () => T) => T, now: () => number);
10
+ get(outboxId: string): AttentionAuditEntry | undefined;
11
+ /** Only non-rejected rows establish question state; a would-reject notice never opens or settles a question. */
12
+ ledger(bindingId: string): AttentionLedgerView;
13
+ /**
14
+ * Durable gap detector, derived by query (no extra state): Outbox rows that
15
+ * carry a durable marker of an audited lane, were created at or after the
16
+ * first audit row, and have no audit row. Plain `enqueueBackground` rows
17
+ * carry no lane marker and are not counted here. Gaps before the first
18
+ * successful audit write are not detectable.
19
+ */
20
+ unauditedOutboxCount(): number;
21
+ /**
22
+ * WP19 stage 2b split-evasion input: inline bodies (Outbox text, never the
23
+ * attachment) of audited notices from one source on one binding and lane,
24
+ * recorded within the last `windowMs` of this store's clock (the same clock
25
+ * that stamps `recorded_at`, so the window survives restarts), in audit
26
+ * order. Notices that were never audited (best-effort audit write failed)
27
+ * are not visible here.
28
+ */
29
+ recentInlineTexts(input: Readonly<{
30
+ bindingId: string;
31
+ lane: AttentionLane;
32
+ sourceId: string;
33
+ windowMs: number;
34
+ }>): readonly string[];
35
+ /** Assess against the committed ledger and append in one writer transaction; an existing row wins. */
36
+ record(input: Readonly<{
37
+ outboxId: string;
38
+ bindingId: string;
39
+ lane: AttentionLane;
40
+ sourceId: string;
41
+ }>, assess: (ledger: AttentionLedgerView) => AttentionAssessment): AttentionAuditEntry;
42
+ list(afterSequence: number, limit: number): {
43
+ entries: readonly AttentionAuditEntry[];
44
+ hasMore: boolean;
45
+ };
46
+ /**
47
+ * Current facts for exact notifications; unknown or unaudited ids are omitted.
48
+ * The unindexed JSON lookups (reply reactions, work-item feedback) run once
49
+ * per call over the whole batch rather than once per notification.
50
+ */
51
+ outcomeFactsFor(outboxIds: readonly string[]): readonly NotificationOutcomeFact[];
52
+ private fact;
53
+ }
54
+ //# sourceMappingURL=attention-audit-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention-audit-store.d.ts","sourceRoot":"","sources":["../src/attention-audit-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAyB,KAAK,mBAAmB,EAAE,KAAK,mBAAmB,EAA6B,KAAK,aAAa,EAAE,KAAK,mBAAmB,EAAE,KAAK,uBAAuB,EAAqD,MAAM,gBAAgB,CAAA;AAEpQ,iGAAiG;AACjG,eAAO,MAAM,oBAAoB,ylDAyBhC,CAAA;AAyBD,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAAgB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAAyB,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAjG,QAAQ,EAAE,YAAY,EAAmB,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,EAAmB,GAAG,EAAE,MAAM,MAAM;IAE5I,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAKtD,gHAAgH;IAChH,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB;IAiB9C;;;;;;OAMG;IACH,oBAAoB,IAAI,MAAM;IAW9B;;;;;;;OAOG;IACH,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,aAAa,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,SAAS,MAAM,EAAE;IAQrI,sGAAsG;IACtG,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,aAAa,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,EACpG,MAAM,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,mBAAmB,GAAG,mBAAmB;IAepF,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE;IAMzG;;;;OAIG;IACH,eAAe,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,uBAAuB,EAAE;IA+BjF,OAAO,CAAC,IAAI;CAmDb"}
@@ -0,0 +1,213 @@
1
+ import { attentionAuditReasons, linkedReportKeyPrefix, linkedReportMetadataPrefix } from './attention.js';
2
+ /** Append-only WP19 stage-1 audit. One row per Outbox record; replays keep the first verdict. */
3
+ export const attentionAuditSchema = `
4
+ CREATE TABLE delivery_attention_audit (
5
+ outbox_id TEXT PRIMARY KEY REFERENCES outbox_messages(id),
6
+ audit_sequence INTEGER NOT NULL UNIQUE CHECK(audit_sequence>=1),
7
+ binding_id TEXT NOT NULL,
8
+ lane TEXT NOT NULL CHECK(lane IN ('background','background-route','owner-notification','automation-result','work-item-report','scheduled-goal-result')),
9
+ source_id TEXT NOT NULL,
10
+ purpose TEXT NOT NULL CHECK(purpose IN ('result','attention','answer')),
11
+ purpose_source TEXT NOT NULL CHECK(purpose_source IN ('declared','lane','default')),
12
+ work_item_id TEXT,
13
+ question_id TEXT,
14
+ answers_question_id TEXT,
15
+ word_count INTEGER NOT NULL CHECK(word_count>=0),
16
+ word_cap INTEGER NOT NULL CHECK(word_cap>=1),
17
+ verdict TEXT NOT NULL CHECK(verdict IN ('would-accept','would-attach','would-reject')),
18
+ reasons_json TEXT NOT NULL CHECK(json_valid(reasons_json)),
19
+ recorded_at INTEGER NOT NULL CHECK(recorded_at>=0)
20
+ ) STRICT;
21
+ CREATE INDEX delivery_attention_audit_question ON delivery_attention_audit(binding_id,question_id);
22
+ CREATE INDEX delivery_attention_audit_answer ON delivery_attention_audit(binding_id,answers_question_id);
23
+ CREATE INDEX delivery_attention_audit_work_item ON delivery_attention_audit(binding_id,work_item_id);
24
+ CREATE TRIGGER delivery_attention_audit_immutable BEFORE UPDATE ON delivery_attention_audit
25
+ BEGIN SELECT RAISE(ABORT,'attention audit is append-only'); END;
26
+ CREATE TRIGGER delivery_attention_audit_no_delete BEFORE DELETE ON delivery_attention_audit
27
+ BEGIN SELECT RAISE(ABORT,'attention audit is durable'); END;
28
+ `;
29
+ function entry(row) {
30
+ const reasons = JSON.parse(row.reasons_json);
31
+ if (!Array.isArray(reasons) || !reasons.every(reason => attentionAuditReasons.includes(reason))) {
32
+ throw new Error('invalid attention audit reasons');
33
+ }
34
+ return Object.freeze({
35
+ sequence: row.audit_sequence, outboxId: row.outbox_id, bindingId: row.binding_id, lane: row.lane, sourceId: row.source_id,
36
+ purpose: row.purpose, purposeSource: row.purpose_source,
37
+ ...(row.work_item_id === null ? {} : { workItemId: row.work_item_id }),
38
+ ...(row.question_id === null ? {} : { questionId: row.question_id }),
39
+ ...(row.answers_question_id === null ? {} : { answersQuestionId: row.answers_question_id }),
40
+ wordCount: row.word_count, wordCap: row.word_cap, verdict: row.verdict,
41
+ reasons: Object.freeze(reasons), recordedAt: row.recorded_at,
42
+ });
43
+ }
44
+ export class AttentionAuditStore {
45
+ database;
46
+ transaction;
47
+ now;
48
+ constructor(database, transaction, now) {
49
+ this.database = database;
50
+ this.transaction = transaction;
51
+ this.now = now;
52
+ }
53
+ get(outboxId) {
54
+ const row = this.database.prepare('SELECT * FROM delivery_attention_audit WHERE outbox_id=?').get(outboxId);
55
+ return row === undefined ? undefined : entry(row);
56
+ }
57
+ /** Only non-rejected rows establish question state; a would-reject notice never opens or settles a question. */
58
+ ledger(bindingId) {
59
+ return {
60
+ questionAsked: questionId => this.database.prepare(`SELECT 1 FROM delivery_attention_audit WHERE binding_id=? AND question_id=?
61
+ AND purpose='attention' AND verdict!='would-reject' LIMIT 1`).get(bindingId, questionId) !== undefined,
62
+ questionAnswered: questionId => this.database.prepare(`SELECT 1 FROM delivery_attention_audit WHERE binding_id=? AND answers_question_id=?
63
+ AND purpose='answer' AND verdict!='would-reject' LIMIT 1`).get(bindingId, questionId) !== undefined,
64
+ openQuestions: workItemId => this.database.prepare(`SELECT DISTINCT q.question_id AS id FROM delivery_attention_audit q
65
+ WHERE q.binding_id=? AND q.work_item_id=? AND q.purpose='attention' AND q.verdict!='would-reject' AND q.question_id IS NOT NULL
66
+ AND NOT EXISTS(SELECT 1 FROM delivery_attention_audit a WHERE a.binding_id=q.binding_id AND a.answers_question_id=q.question_id
67
+ AND a.purpose='answer' AND a.verdict!='would-reject') ORDER BY q.question_id`).all(bindingId, workItemId)
68
+ .map(row => row.id),
69
+ questionWorkItem: questionId => this.database.prepare(`SELECT work_item_id FROM delivery_attention_audit WHERE binding_id=? AND question_id=?
70
+ AND purpose='attention' AND verdict!='would-reject' AND work_item_id IS NOT NULL ORDER BY audit_sequence LIMIT 1`)
71
+ .get(bindingId, questionId)?.work_item_id,
72
+ };
73
+ }
74
+ /**
75
+ * Durable gap detector, derived by query (no extra state): Outbox rows that
76
+ * carry a durable marker of an audited lane, were created at or after the
77
+ * first audit row, and have no audit row. Plain `enqueueBackground` rows
78
+ * carry no lane marker and are not counted here. Gaps before the first
79
+ * successful audit write are not detectable.
80
+ */
81
+ unauditedOutboxCount() {
82
+ return this.database.prepare(`SELECT count(*) AS count FROM outbox_messages o
83
+ WHERE o.created_at >= (SELECT min(recorded_at) FROM delivery_attention_audit)
84
+ AND NOT EXISTS(SELECT 1 FROM delivery_attention_audit a WHERE a.outbox_id=o.id)
85
+ AND (json_extract(o.intent_json,'$.metadata."dsh.native-notice"')='v1'
86
+ OR json_extract(o.intent_json,'$.metadata."dsh.route.authority"') IS NOT NULL
87
+ OR json_extract(o.intent_json,'$.metadata."dsh.learning.kind"') IN ('automation-run','goal-outcome')
88
+ OR EXISTS(SELECT 1 FROM delivery_goal_outcome_targets g WHERE g.outbox_id=o.id)
89
+ OR EXISTS(SELECT 1 FROM delivery_work_item_reports r WHERE r.outbox_id=o.id))`).get().count;
90
+ }
91
+ /**
92
+ * WP19 stage 2b split-evasion input: inline bodies (Outbox text, never the
93
+ * attachment) of audited notices from one source on one binding and lane,
94
+ * recorded within the last `windowMs` of this store's clock (the same clock
95
+ * that stamps `recorded_at`, so the window survives restarts), in audit
96
+ * order. Notices that were never audited (best-effort audit write failed)
97
+ * are not visible here.
98
+ */
99
+ recentInlineTexts(input) {
100
+ return this.database.prepare(`SELECT json_extract(o.intent_json,'$.text') AS text FROM delivery_attention_audit a
101
+ JOIN outbox_messages o ON o.id=a.outbox_id
102
+ WHERE a.binding_id=? AND a.lane=? AND a.source_id=? AND a.recorded_at>=? ORDER BY a.audit_sequence`)
103
+ .all(input.bindingId, input.lane, input.sourceId, Math.max(0, this.now() - input.windowMs))
104
+ .map(row => typeof row.text === 'string' ? row.text : '');
105
+ }
106
+ /** Assess against the committed ledger and append in one writer transaction; an existing row wins. */
107
+ record(input, assess) {
108
+ return this.transaction(() => {
109
+ const existing = this.get(input.outboxId);
110
+ if (existing !== undefined)
111
+ return existing;
112
+ const assessment = assess(this.ledger(input.bindingId));
113
+ const next = this.database.prepare('SELECT coalesce(max(audit_sequence),0)+1 AS next FROM delivery_attention_audit').get().next;
114
+ this.database.prepare(`INSERT INTO delivery_attention_audit(outbox_id,audit_sequence,binding_id,lane,source_id,purpose,purpose_source,
115
+ work_item_id,question_id,answers_question_id,word_count,word_cap,verdict,reasons_json,recorded_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`)
116
+ .run(input.outboxId, next, input.bindingId, input.lane, input.sourceId, assessment.purpose, assessment.purposeSource, assessment.workItemId ?? null, assessment.questionId ?? null, assessment.answersQuestionId ?? null, assessment.wordCount, assessment.wordCap, assessment.verdict, JSON.stringify(assessment.reasons), this.now());
117
+ return this.get(input.outboxId);
118
+ });
119
+ }
120
+ list(afterSequence, limit) {
121
+ const rows = this.database.prepare('SELECT * FROM delivery_attention_audit WHERE audit_sequence>? ORDER BY audit_sequence LIMIT ?')
122
+ .all(afterSequence, limit + 1);
123
+ return { entries: Object.freeze(rows.slice(0, limit).map(entry)), hasMore: rows.length > limit };
124
+ }
125
+ /**
126
+ * Current facts for exact notifications; unknown or unaudited ids are omitted.
127
+ * The unindexed JSON lookups (reply reactions, work-item feedback) run once
128
+ * per call over the whole batch rather than once per notification.
129
+ */
130
+ outcomeFactsFor(outboxIds) {
131
+ const audits = outboxIds.map(id => this.get(id)).filter(item => item !== undefined);
132
+ if (audits.length === 0)
133
+ return Object.freeze([]);
134
+ const ids = JSON.stringify(audits.map(audit => audit.outboxId));
135
+ // A reply counts when it answers the provider message in the same
136
+ // conversation from the same principal, including after a new-session
137
+ // command moved the conversation to a newer binding generation.
138
+ const reactions = new Map(this.database.prepare(`WITH target AS (
139
+ SELECT o.id AS outbox_id, o.channel, o.account, o.provider_message_id, b.conversation_hash, b.principal_id
140
+ FROM json_each(?) j JOIN outbox_messages o ON o.id=j.value JOIN conversation_bindings b ON b.id=o.binding_id
141
+ WHERE o.provider_message_id IS NOT NULL)
142
+ SELECT t.outbox_id, min(i.received_at) AS first_at, count(*) AS count
143
+ FROM inbox_messages i
144
+ JOIN conversation_bindings ib ON ib.id=i.binding_id
145
+ JOIN target t ON t.channel=i.channel AND t.account=i.account
146
+ AND t.provider_message_id=json_extract(i.envelope_json,'$.metadata.replyToProviderMessageId')
147
+ AND ib.conversation_hash=t.conversation_hash AND ib.principal_id=t.principal_id
148
+ GROUP BY t.outbox_id`).all(ids)
149
+ .map(row => [row.outbox_id, row]));
150
+ const feedback = new Map();
151
+ for (const row of this.database.prepare(`SELECT json_extract(source_json,'$.target.outboxId') AS outbox_id,
152
+ json_extract(canonical_json,'$.version') AS version, json_extract(canonical_json,'$.objectiveStatus') AS status
153
+ FROM delivery_work_item_feedback WHERE state='recorded'
154
+ AND json_extract(source_json,'$.target.outboxId') IN (SELECT value FROM json_each(?))
155
+ ORDER BY version DESC, admission_sequence DESC`).all(ids)) {
156
+ if (!feedback.has(row.outbox_id))
157
+ feedback.set(row.outbox_id, row);
158
+ }
159
+ return Object.freeze(audits.map(audit => this.fact(audit, reactions.get(audit.outboxId), feedback.get(audit.outboxId)))
160
+ .filter(item => item !== undefined));
161
+ }
162
+ fact(audit, reaction, feedback) {
163
+ const outbox = this.database.prepare(`SELECT o.status, o.channel, o.account, o.provider_message_id, o.updated_at,
164
+ b.workspace, b.agent_preset, o.idempotency_key,
165
+ json_extract(o.intent_json,?) AS linked_report, json_extract(o.intent_json,?) AS linked_origin
166
+ FROM outbox_messages o JOIN conversation_bindings b ON b.id=o.binding_id WHERE o.id=?`)
167
+ .get(`$.metadata."${linkedReportMetadataPrefix}"`, `$.metadata."${linkedReportMetadataPrefix}.originBindingId"`, audit.outboxId);
168
+ if (outbox === undefined)
169
+ return undefined;
170
+ const accepted = this.database.prepare(`SELECT min(finished_at) AS at FROM outbox_attempts
171
+ WHERE outbox_id=? AND status IN ('accepted','reconciled') AND finished_at IS NOT NULL`).get(audit.outboxId).at;
172
+ const receipts = outbox.provider_message_id === null ? [] : this.database.prepare(`SELECT status, occurred_at FROM delivery_receipts
173
+ WHERE channel=? AND account=? AND provider_message_id=?`).all(outbox.channel, outbox.account, outbox.provider_message_id);
174
+ const receipt = (status) => receipts.find(row => row.status === status)?.occurred_at;
175
+ const sent = ['accepted', 'delivered', 'read'].includes(outbox.status);
176
+ const acceptedAt = accepted ?? receipt('accepted') ?? (sent ? outbox.updated_at : undefined);
177
+ const deliveredAt = receipt('delivered') ?? (outbox.status === 'delivered' || outbox.status === 'read' ? outbox.updated_at : undefined);
178
+ const readAt = receipt('read') ?? (outbox.status === 'read' ? outbox.updated_at : undefined);
179
+ // Same precedence as Delivery's own objective state: the newest accepted
180
+ // owner command, else the legacy first verified-task receipt as version 1.
181
+ const owner = feedback !== undefined ? undefined : this.database.prepare(`SELECT version, objective_status AS status
182
+ FROM delivery_owner_objective_commands WHERE source_outbox_id=? AND json_extract(result_json,'$.error') IS NULL
183
+ ORDER BY version DESC LIMIT 1`).get(audit.outboxId)
184
+ ?? this.database.prepare(`SELECT 1 AS version, objective_status AS status FROM workflow_verified_task_feedback
185
+ WHERE source_outbox_id=?`).get(audit.outboxId);
186
+ // Only `routeWorkItemReport` writes this reserved marker + key prefix;
187
+ // every public enqueue rejects both.
188
+ const linkedOrigin = outbox.linked_report === 'v1' && outbox.idempotency_key.startsWith(linkedReportKeyPrefix)
189
+ && typeof outbox.linked_origin === 'string' && outbox.linked_origin.length > 0 ? outbox.linked_origin : undefined;
190
+ const judged = feedback ?? owner;
191
+ const objectiveStatus = (status) => status === 'achieved' || status === 'partial' || status === 'not-achieved' ? status : 'unknown';
192
+ return Object.freeze({
193
+ auditSequence: audit.sequence, outboxId: audit.outboxId, bindingId: audit.bindingId,
194
+ scope: Object.freeze({ workspace: outbox.workspace, preset: outbox.agent_preset }),
195
+ lane: audit.lane, sourceId: audit.sourceId, purpose: audit.purpose, purposeSource: audit.purposeSource,
196
+ ...(audit.workItemId === undefined ? {} : { workItemId: audit.workItemId }),
197
+ ...(audit.questionId === undefined ? {} : { questionId: audit.questionId }),
198
+ ...(audit.answersQuestionId === undefined ? {} : { answersQuestionId: audit.answersQuestionId }),
199
+ verdict: audit.verdict, reasons: audit.reasons, auditedAt: audit.recordedAt,
200
+ transport: Object.freeze({ status: outbox.status,
201
+ ...(acceptedAt === undefined ? {} : { acceptedAt }),
202
+ ...(deliveredAt === undefined ? {} : { deliveredAt }),
203
+ ...(readAt === undefined ? {} : { readAt }) }),
204
+ ...(reaction === undefined || outbox.provider_message_id === null ? {} : { reaction: Object.freeze({ firstAt: reaction.first_at, count: reaction.count }) }),
205
+ ...(judged === undefined || !Number.isSafeInteger(judged.version) ? {} : { judgement: Object.freeze({
206
+ source: feedback !== undefined ? 'work-item-feedback' : 'owner-objective',
207
+ version: judged.version, objectiveStatus: objectiveStatus(judged.status)
208
+ }) }),
209
+ ...(linkedOrigin === undefined ? {} : { route: Object.freeze({ kind: 'linked', originBindingId: linkedOrigin }) }),
210
+ });
211
+ }
212
+ }
213
+ //# sourceMappingURL=attention-audit-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention-audit-store.js","sourceRoot":"","sources":["../src/attention-audit-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * WP19 stage 3: purpose enforcement (opt-in Delivery config `attentionEnforce`,
3
+ * default off). With it on, a background/owner notice whose purpose is missing
4
+ * or fails the Host checks, or that exceeds its caps while
5
+ * `attentionAttachOverCap` is off, is rejected before it is queued.
6
+ *
7
+ * A rejected notice never becomes an Outbox row, and the v31
8
+ * `delivery_attention_audit` table is keyed by `outbox_id` with an Outbox
9
+ * foreign key and a closed `verdict` CHECK, so it cannot hold a `rejected`
10
+ * verdict without a schema bump. Rejections are therefore recorded in this
11
+ * process-local, bounded, non-durable log, like the stage 2c progress audit.
12
+ * A rejection never opens or settles a question, so the durable ledger that
13
+ * decides it is unchanged by it and a retry after restart is decided again
14
+ * the same way.
15
+ */
16
+ import { type AttentionAssessment, type AttentionAuditReason, type AttentionLane } from './attention.js';
17
+ /** Enforcement-only reasons, appended after the audit reasons. */
18
+ export declare const attentionEnforcementOnlyReasons: readonly ["split-evasion", "attachment-unsupported"];
19
+ export type AttentionRejectionReason = AttentionAuditReason | typeof attentionEnforcementOnlyReasons[number];
20
+ /** Purpose reasons that reject under enforcement: the stage 1 would-reject set plus an undeclared purpose. */
21
+ export declare const attentionEnforcedPurposeReasons: ReadonlySet<AttentionAuditReason>;
22
+ /**
23
+ * Pure: reasons one assessment is rejected for, in fixed order. Over-cap text
24
+ * is rejected only when it cannot take the stage 2b attachment path.
25
+ * `text-exposes-*` stay audit-only.
26
+ */
27
+ export declare function attentionEnforcementReasons(assessment: Readonly<Pick<AttentionAssessment, 'reasons'>>, attachOverCap: boolean): readonly AttentionAuditReason[];
28
+ export declare function orderAttentionRejectionReasons(reasons: Iterable<AttentionRejectionReason>): readonly AttentionRejectionReason[];
29
+ export interface AttentionRejectionEntry {
30
+ /** Process-local read cursor, strictly increasing from 1. */
31
+ sequence: number;
32
+ bindingId: string;
33
+ lane: AttentionLane;
34
+ sourceId: string;
35
+ idempotencyKey: string;
36
+ purpose: AttentionAssessment['purpose'];
37
+ purposeSource: AttentionAssessment['purposeSource'];
38
+ workItemId?: string;
39
+ questionId?: string;
40
+ answersQuestionId?: string;
41
+ wordCount: number;
42
+ wordCap: number;
43
+ verdict: 'rejected';
44
+ reasons: readonly AttentionRejectionReason[];
45
+ recordedAt: number;
46
+ }
47
+ export interface AttentionRejectionPage {
48
+ entries: readonly AttentionRejectionEntry[];
49
+ hasMore: boolean;
50
+ nextAfterSequence: number | null;
51
+ /** Always false: entries live in this Host process only. */
52
+ durable: false;
53
+ /** Entries evicted from the bounded ring since this Host started. */
54
+ evicted: number;
55
+ }
56
+ export declare class AttentionRejectionLog {
57
+ private readonly maxEntries;
58
+ private readonly now;
59
+ private readonly entries;
60
+ private sequence;
61
+ private evictedEntries;
62
+ constructor(maxEntries?: number, now?: () => number);
63
+ /** A retry of the same key rejected for the same reasons returns the retained entry instead of appending. */
64
+ record(input: Readonly<{
65
+ bindingId: string;
66
+ lane: AttentionLane;
67
+ sourceId: string;
68
+ idempotencyKey: string;
69
+ assessment: Readonly<AttentionAssessment>;
70
+ reasons: readonly AttentionRejectionReason[];
71
+ }>): AttentionRejectionEntry;
72
+ /** `afterSequence`/`limit` are validated by the caller. */
73
+ list(afterSequence: number, limit: number): AttentionRejectionPage;
74
+ }
75
+ //# sourceMappingURL=attention-enforcement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention-enforcement.d.ts","sourceRoot":"","sources":["../src/attention-enforcement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAyB,KAAK,mBAAmB,EAAE,KAAK,oBAAoB,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE/H,kEAAkE;AAClE,eAAO,MAAM,+BAA+B,sDAAuD,CAAA;AACnG,MAAM,MAAM,wBAAwB,GAAG,oBAAoB,GAAG,OAAO,+BAA+B,CAAC,MAAM,CAAC,CAAA;AAG5G,8GAA8G;AAC9G,eAAO,MAAM,+BAA+B,EAAE,WAAW,CAAC,oBAAoB,CAI5E,CAAA;AAIF;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,GAAG,SAAS,oBAAoB,EAAE,CAG/J;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,QAAQ,CAAC,wBAAwB,CAAC,GAAG,SAAS,wBAAwB,EAAE,CAG/H;AAED,MAAM,WAAW,uBAAuB;IACtC,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,aAAa,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAA;IACvC,aAAa,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAA;IACnD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,UAAU,CAAA;IACnB,OAAO,EAAE,SAAS,wBAAwB,EAAE,CAAA;IAC5C,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAA;IAC3C,OAAO,EAAE,OAAO,CAAA;IAChB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,4DAA4D;IAC5D,OAAO,EAAE,KAAK,CAAA;IACd,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,qBAAa,qBAAqB;IAKpB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAAS,OAAO,CAAC,QAAQ,CAAC,GAAG;IAJpE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,cAAc,CAAI;gBAEG,UAAU,SAAO,EAAmB,GAAG,GAAE,MAAM,MAAiB;IAE7F,6GAA6G;IAC7G,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,aAAa,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;QACvG,UAAU,EAAE,QAAQ,CAAC,mBAAmB,CAAC,CAAC;QAAC,OAAO,EAAE,SAAS,wBAAwB,EAAE,CAAA;KAAE,CAAC,GAAG,uBAAuB;IAoBtH,2DAA2D;IAC3D,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,sBAAsB;CAQnE"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * WP19 stage 3: purpose enforcement (opt-in Delivery config `attentionEnforce`,
3
+ * default off). With it on, a background/owner notice whose purpose is missing
4
+ * or fails the Host checks, or that exceeds its caps while
5
+ * `attentionAttachOverCap` is off, is rejected before it is queued.
6
+ *
7
+ * A rejected notice never becomes an Outbox row, and the v31
8
+ * `delivery_attention_audit` table is keyed by `outbox_id` with an Outbox
9
+ * foreign key and a closed `verdict` CHECK, so it cannot hold a `rejected`
10
+ * verdict without a schema bump. Rejections are therefore recorded in this
11
+ * process-local, bounded, non-durable log, like the stage 2c progress audit.
12
+ * A rejection never opens or settles a question, so the durable ledger that
13
+ * decides it is unchanged by it and a retry after restart is decided again
14
+ * the same way.
15
+ */
16
+ import { attentionAuditReasons } from './attention.js';
17
+ /** Enforcement-only reasons, appended after the audit reasons. */
18
+ export const attentionEnforcementOnlyReasons = ['split-evasion', 'attachment-unsupported'];
19
+ const rejectionReasonOrder = [...attentionAuditReasons, ...attentionEnforcementOnlyReasons];
20
+ /** Purpose reasons that reject under enforcement: the stage 1 would-reject set plus an undeclared purpose. */
21
+ export const attentionEnforcedPurposeReasons = new Set([
22
+ 'missing-purpose', 'invalid-envelope', 'attention-missing-work-item', 'attention-question-invalid', 'attention-question-not-current',
23
+ 'attention-question-already-answered', 'answer-missing-question', 'answer-unknown-question', 'answer-already-used',
24
+ 'answer-work-item-mismatch', 'attention-question-reused',
25
+ ]);
26
+ const capReasons = new Set(['over-word-cap', 'over-grapheme-cap']);
27
+ /**
28
+ * Pure: reasons one assessment is rejected for, in fixed order. Over-cap text
29
+ * is rejected only when it cannot take the stage 2b attachment path.
30
+ * `text-exposes-*` stay audit-only.
31
+ */
32
+ export function attentionEnforcementReasons(assessment, attachOverCap) {
33
+ return Object.freeze(assessment.reasons.filter(reason => attentionEnforcedPurposeReasons.has(reason)
34
+ || (!attachOverCap && capReasons.has(reason))));
35
+ }
36
+ export function orderAttentionRejectionReasons(reasons) {
37
+ const set = new Set(reasons);
38
+ return Object.freeze(rejectionReasonOrder.filter(reason => set.has(reason)));
39
+ }
40
+ export class AttentionRejectionLog {
41
+ maxEntries;
42
+ now;
43
+ entries = [];
44
+ sequence = 0;
45
+ evictedEntries = 0;
46
+ constructor(maxEntries = 1000, now = Date.now) {
47
+ this.maxEntries = maxEntries;
48
+ this.now = now;
49
+ }
50
+ /** A retry of the same key rejected for the same reasons returns the retained entry instead of appending. */
51
+ record(input) {
52
+ const reasons = orderAttentionRejectionReasons(input.reasons);
53
+ const retained = this.entries.findLast(entry => entry.bindingId === input.bindingId && entry.lane === input.lane
54
+ && entry.idempotencyKey === input.idempotencyKey);
55
+ if (retained !== undefined && retained.sourceId === input.sourceId && retained.reasons.join() === reasons.join())
56
+ return retained;
57
+ const { assessment } = input;
58
+ this.sequence += 1;
59
+ const entry = Object.freeze({
60
+ sequence: this.sequence, bindingId: input.bindingId, lane: input.lane, sourceId: input.sourceId, idempotencyKey: input.idempotencyKey,
61
+ purpose: assessment.purpose, purposeSource: assessment.purposeSource,
62
+ ...(assessment.workItemId === undefined ? {} : { workItemId: assessment.workItemId }),
63
+ ...(assessment.questionId === undefined ? {} : { questionId: assessment.questionId }),
64
+ ...(assessment.answersQuestionId === undefined ? {} : { answersQuestionId: assessment.answersQuestionId }),
65
+ wordCount: assessment.wordCount, wordCap: assessment.wordCap, verdict: 'rejected', reasons, recordedAt: this.now(),
66
+ });
67
+ this.entries.push(entry);
68
+ while (this.entries.length > this.maxEntries) {
69
+ this.entries.shift();
70
+ this.evictedEntries += 1;
71
+ }
72
+ return entry;
73
+ }
74
+ /** `afterSequence`/`limit` are validated by the caller. */
75
+ list(afterSequence, limit) {
76
+ const start = this.entries.findIndex(entry => entry.sequence > afterSequence);
77
+ const remaining = start < 0 ? [] : this.entries.slice(start);
78
+ const entries = Object.freeze(remaining.slice(0, limit));
79
+ const hasMore = remaining.length > limit;
80
+ return Object.freeze({ entries, hasMore, nextAfterSequence: hasMore ? entries.at(-1).sequence : null, durable: false,
81
+ evicted: this.evictedEntries });
82
+ }
83
+ }
84
+ //# sourceMappingURL=attention-enforcement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention-enforcement.js","sourceRoot":"","sources":["../src/attention-enforcement.ts"],"names":[],"mappings":"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"}