@wirecat/cli-core 0.573.0 → 0.575.0

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 (45) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/botcheck/registries.js +10 -7
  3. package/dist/calendar/feeds.js +7 -3
  4. package/dist/calendar/mcp/index.d.ts +12 -12
  5. package/dist/cli/findings-input.js +6 -0
  6. package/dist/cli/messenger/serve-catch-up.js +3 -2
  7. package/dist/domain/orders.d.ts +19 -0
  8. package/dist/domain/orders.js +12 -0
  9. package/dist/domain/ref-summaries.js +1 -0
  10. package/dist/domain/references.d.ts +1 -1
  11. package/dist/domain/references.js +1 -0
  12. package/dist/entities/index.d.ts +75 -0
  13. package/dist/entities/mail-findings.d.ts +75 -0
  14. package/dist/entities/mail-findings.js +62 -0
  15. package/dist/errors/reasons.js +5 -0
  16. package/dist/http/time-limit.d.ts +11 -0
  17. package/dist/http/time-limit.js +20 -0
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.js +1 -0
  20. package/dist/mail/findings.d.ts +73 -1
  21. package/dist/mail/findings.js +164 -1
  22. package/dist/models/openai.js +4 -3
  23. package/dist/oauth/requests.js +10 -6
  24. package/dist/services/proposals/kinds/mail-findings.js +9 -0
  25. package/dist/services/proposals/kinds/orders.d.ts +4 -0
  26. package/dist/services/proposals/kinds/orders.js +32 -0
  27. package/dist/services/proposals/registry.js +2 -0
  28. package/dist/store/index.d.ts +1 -0
  29. package/dist/store/sqlite/entity-types.d.ts +1 -0
  30. package/dist/store/sqlite/entity-types.js +1 -0
  31. package/dist/store/sqlite/finance.d.ts +40 -2
  32. package/dist/store/sqlite/finance.js +48 -42
  33. package/dist/store/sqlite/knowledge.js +2 -1
  34. package/dist/store/sqlite/mail-findings.js +81 -1
  35. package/dist/store/sqlite/manifest.js +2 -0
  36. package/dist/store/sqlite/migrations.generated.js +20 -0
  37. package/dist/store/sqlite/orders.d.ts +84 -0
  38. package/dist/store/sqlite/orders.js +186 -0
  39. package/dist/store/sqlite/ref-summaries.js +10 -0
  40. package/dist/store/sqlite/schema.d.ts +440 -0
  41. package/dist/store/sqlite/schema.js +38 -0
  42. package/dist/store/sqlite/things.js +1 -0
  43. package/dist/store/store.d.ts +3 -0
  44. package/dist/store/store.js +5 -0
  45. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ export { formatLocator, isLocator, parseLocator } from "./domain/locator.js";
6
6
  export { parseMarkdown } from "./domain/markdown.js";
7
7
  export { parseMarkdownSpans } from "./domain/markdown-spans.js";
8
8
  export { CURRENCY_SCALES, currencyOf, minorUnits, moneyLabel, moneyOf, } from "./domain/money.js";
9
+ export { ORDER_PURPOSES, ORDER_STATUSES, } from "./domain/orders.js";
9
10
  export { countedPoll } from "./domain/polls.js";
10
11
  export { FOLDER_PROVIDERS, isFolderProvider, isKind, KINDS, kindIn, kindOf, PROVIDERS, providerNamed, providers, UNKNOWN_PROVIDER, useProviders, } from "./domain/providers.js";
11
12
  export { rankingOptions } from "./domain/rankings-options.js";
@@ -1,4 +1,6 @@
1
+ import { INVOICE_DIRECTIONS, MONEY_DIRECTIONS } from "../domain/finance.js";
1
2
  import { MEMORY_KINDS, MEMORY_SCOPES } from "../domain/knowledge.js";
3
+ import { ORDER_PURPOSES, ORDER_STATUSES } from "../domain/orders.js";
2
4
  import { type FactFinding, type FindingEvidence, type TaskFinding } from "../meetings/index.js";
3
5
  export { MAIL_FINDINGS_KIND } from "../store/mail-findings.js";
4
6
  /** At most this many findings in one run's batch, and per sender in it. */
@@ -39,7 +41,77 @@ export interface MailNewContactFinding extends Cited {
39
41
  name: string;
40
42
  reason?: string;
41
43
  }
42
- export type MailFinding = MailTaskFinding | MailFactFinding | MailTaskUpdateFinding | MailMemoryFinding | MailLinkFinding | MailNewContactFinding;
44
+ /** An invoice the mail carries; accepting it records the invoice through `store.finance`, confirmed. */
45
+ export interface MailInvoiceFinding extends Cited {
46
+ kind: "invoice";
47
+ direction: (typeof INVOICE_DIRECTIONS)[number];
48
+ /** `person:` or `organization:`. */
49
+ counterparty: string;
50
+ currency: string;
51
+ lines: {
52
+ description: string;
53
+ quantity?: string;
54
+ unitPrice?: string;
55
+ amount?: string;
56
+ }[];
57
+ /** The total printed on it: the store refuses it unless it is the lines' sum. */
58
+ total?: string;
59
+ number?: string;
60
+ issueDate?: string;
61
+ dueDate?: string;
62
+ project?: string;
63
+ reason: string;
64
+ }
65
+ /** A payment the mail confirms, such as a receipt; accepting it records the payment through `store.finance`. */
66
+ export interface MailPaymentFinding extends Cited {
67
+ kind: "payment";
68
+ direction: (typeof MONEY_DIRECTIONS)[number];
69
+ payer?: string;
70
+ payee?: string;
71
+ amount: string;
72
+ currency: string;
73
+ /** `YYYY-MM-DD`. */
74
+ date: string;
75
+ /** The payment's own reference text, such as the receipt number. */
76
+ reference?: string;
77
+ /** Stored invoices it settles, in the payment's currency. */
78
+ allocations?: {
79
+ invoice: string;
80
+ amount: string;
81
+ }[];
82
+ project?: string;
83
+ reason: string;
84
+ }
85
+ /** An order the mail confirms; accepting it records the order through `store.orders`. */
86
+ export interface MailOrderFinding extends Cited {
87
+ kind: "order";
88
+ purpose: (typeof ORDER_PURPOSES)[number];
89
+ /** Whom it is for; the owner when left out. */
90
+ customer?: string;
91
+ supplier?: string;
92
+ supplierName: string;
93
+ number?: string;
94
+ total?: string;
95
+ currency?: string;
96
+ status?: (typeof ORDER_STATUSES)[number];
97
+ orderDate?: string;
98
+ fulfilledDate?: string;
99
+ invoice?: string;
100
+ payment?: string;
101
+ project?: string;
102
+ reason: string;
103
+ }
104
+ /** A later mail about a stored order, found by its shop and number: shipped, delivered, cancelled, returned. */
105
+ export interface MailOrderUpdateFinding extends Cited {
106
+ kind: "order_update";
107
+ supplierName: string;
108
+ number: string;
109
+ status: (typeof ORDER_STATUSES)[number];
110
+ /** `YYYY-MM-DD`, the delivery day of a delivered order. */
111
+ date?: string;
112
+ reason: string;
113
+ }
114
+ export type MailFinding = MailTaskFinding | MailFactFinding | MailTaskUpdateFinding | MailMemoryFinding | MailLinkFinding | MailNewContactFinding | MailInvoiceFinding | MailPaymentFinding | MailOrderFinding | MailOrderUpdateFinding;
43
115
  export interface MailFindingsInput {
44
116
  schemaVersion: 1;
45
117
  /** `<holder>:<through_id>`: one batch per run of a holder over the mail it was given. */
@@ -1,6 +1,8 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
+ import { INVOICE_DIRECTIONS, MONEY_DIRECTIONS } from "../domain/finance.js";
2
3
  import { MEMORY_KINDS, MEMORY_SCOPES } from "../domain/knowledge.js";
3
4
  import { formatLocator, parseLocator } from "../domain/locator.js";
5
+ import { ORDER_PURPOSES, ORDER_STATUSES } from "../domain/orders.js";
4
6
  import { parseReference } from "../domain/references.js";
5
7
  import { findingFields, findingHash, findingId, findingText, meetingTimestamp, validateBatch, validateFinding, validEvidence, } from "../meetings/index.js";
6
8
  export { MAIL_FINDINGS_KIND } from "../store/mail-findings.js";
@@ -42,6 +44,18 @@ const LINKS = {
42
44
  "chat:email": "project",
43
45
  };
44
46
  const ADDRESS = /^[^\s@<>]+@[^\s@<>]+$/;
47
+ const PARTY = /^(person|organization):[1-9]\d*$/;
48
+ const INVOICE = /^invoice:[1-9]\d*$/;
49
+ const PAYMENT = /^payment:[1-9]\d*$/;
50
+ const PROJECT = /^project:[1-9]\d*$/;
51
+ const oneOf = (value, allowed, what) => {
52
+ if (typeof value !== "string" || !allowed.includes(value))
53
+ return invalid(`${what} is ${allowed.join(", ")}`);
54
+ return value;
55
+ };
56
+ /** Money and dates stay text here: the store's finance and order services check them as they check the owner's. */
57
+ const word = (value, limit) => findingText(value, limit);
58
+ const optional = (key, value, read) => (value === undefined ? {} : { [key]: read(value) });
45
59
  const one = (raw, ids) => {
46
60
  if (!raw || typeof raw !== "object")
47
61
  return invalid("a finding is an object");
@@ -123,8 +137,157 @@ const one = (raw, ids) => {
123
137
  evidence,
124
138
  };
125
139
  }
140
+ case "invoice": {
141
+ findingFields(raw, [
142
+ "id",
143
+ "kind",
144
+ "direction",
145
+ "counterparty",
146
+ "currency",
147
+ "lines",
148
+ "total",
149
+ "number",
150
+ "issueDate",
151
+ "dueDate",
152
+ "project",
153
+ "reason",
154
+ "evidence",
155
+ ], RULES.singular);
156
+ if (!Array.isArray(raw.lines) || raw.lines.length < 1 || raw.lines.length > 50)
157
+ return invalid("an invoice finding has 1–50 lines");
158
+ const lines = raw.lines.map((line) => {
159
+ if (!line || typeof line !== "object")
160
+ return invalid("an invoice line is an object");
161
+ const fields = line;
162
+ findingFields(fields, ["description", "quantity", "unitPrice", "amount"], "invoice line");
163
+ return {
164
+ description: word(fields.description, 500),
165
+ ...optional("quantity", fields.quantity, (value) => word(value, 20)),
166
+ ...optional("unitPrice", fields.unitPrice, (value) => word(value, 30)),
167
+ ...optional("amount", fields.amount, (value) => word(value, 30)),
168
+ };
169
+ });
170
+ return {
171
+ id,
172
+ kind: "invoice",
173
+ direction: oneOf(raw.direction, INVOICE_DIRECTIONS, "an invoice's direction"),
174
+ counterparty: reference(raw.counterparty, PARTY, "an invoice's counterparty"),
175
+ currency: word(raw.currency, 3),
176
+ lines,
177
+ ...optional("total", raw.total, (value) => word(value, 30)),
178
+ ...optional("number", raw.number, (value) => word(value, 80)),
179
+ ...optional("issueDate", raw.issueDate, (value) => word(value, 10)),
180
+ ...optional("dueDate", raw.dueDate, (value) => word(value, 10)),
181
+ ...optional("project", raw.project, (value) => reference(value, PROJECT, "a project")),
182
+ reason: findingText(raw.reason, 2000),
183
+ evidence,
184
+ };
185
+ }
186
+ case "payment": {
187
+ findingFields(raw, [
188
+ "id",
189
+ "kind",
190
+ "direction",
191
+ "payer",
192
+ "payee",
193
+ "amount",
194
+ "currency",
195
+ "date",
196
+ "reference",
197
+ "allocations",
198
+ "project",
199
+ "reason",
200
+ "evidence",
201
+ ], RULES.singular);
202
+ let allocations;
203
+ if (raw.allocations !== undefined) {
204
+ if (!Array.isArray(raw.allocations) || raw.allocations.length < 1 || raw.allocations.length > 8)
205
+ return invalid("a payment finding's allocations are 1–8 invoices it settles");
206
+ allocations = raw.allocations.map((share) => {
207
+ if (!share || typeof share !== "object")
208
+ return invalid("an allocation is an invoice and an amount");
209
+ const fields = share;
210
+ findingFields(fields, ["invoice", "amount"], "allocation");
211
+ return {
212
+ invoice: reference(fields.invoice, INVOICE, "an allocation's invoice"),
213
+ amount: word(fields.amount, 30),
214
+ };
215
+ });
216
+ }
217
+ return {
218
+ id,
219
+ kind: "payment",
220
+ direction: oneOf(raw.direction, MONEY_DIRECTIONS, "a payment's direction"),
221
+ ...optional("payer", raw.payer, (value) => reference(value, PARTY, "a payment's payer")),
222
+ ...optional("payee", raw.payee, (value) => reference(value, PARTY, "a payment's payee")),
223
+ amount: word(raw.amount, 30),
224
+ currency: word(raw.currency, 3),
225
+ date: word(raw.date, 10),
226
+ ...optional("reference", raw.reference, (value) => word(value, 500)),
227
+ ...(allocations === undefined ? {} : { allocations }),
228
+ ...optional("project", raw.project, (value) => reference(value, PROJECT, "a project")),
229
+ reason: findingText(raw.reason, 2000),
230
+ evidence,
231
+ };
232
+ }
233
+ case "order": {
234
+ findingFields(raw, [
235
+ "id",
236
+ "kind",
237
+ "purpose",
238
+ "customer",
239
+ "supplier",
240
+ "supplierName",
241
+ "number",
242
+ "total",
243
+ "currency",
244
+ "status",
245
+ "orderDate",
246
+ "fulfilledDate",
247
+ "invoice",
248
+ "payment",
249
+ "project",
250
+ "reason",
251
+ "evidence",
252
+ ], RULES.singular);
253
+ return {
254
+ id,
255
+ kind: "order",
256
+ purpose: oneOf(raw.purpose, ORDER_PURPOSES, "an order's purpose"),
257
+ ...optional("customer", raw.customer, (value) => reference(value, PARTY, "an order's customer")),
258
+ ...optional("supplier", raw.supplier, (value) => reference(value, PARTY, "an order's supplier")),
259
+ supplierName: word(raw.supplierName, 200),
260
+ ...optional("number", raw.number, (value) => word(value, 80)),
261
+ ...optional("total", raw.total, (value) => word(value, 30)),
262
+ ...optional("currency", raw.currency, (value) => word(value, 3)),
263
+ ...optional("status", raw.status, (value) => oneOf(value, ORDER_STATUSES, "an order's status")),
264
+ ...optional("orderDate", raw.orderDate, (value) => word(value, 10)),
265
+ ...optional("fulfilledDate", raw.fulfilledDate, (value) => word(value, 10)),
266
+ ...optional("invoice", raw.invoice, (value) => reference(value, INVOICE, "an order's invoice")),
267
+ ...optional("payment", raw.payment, (value) => reference(value, PAYMENT, "an order's payment")),
268
+ ...optional("project", raw.project, (value) => reference(value, PROJECT, "a project")),
269
+ reason: findingText(raw.reason, 2000),
270
+ evidence,
271
+ };
272
+ }
273
+ case "order_update": {
274
+ findingFields(raw, ["id", "kind", "supplierName", "number", "status", "date", "reason", "evidence"], RULES.singular);
275
+ const status = oneOf(raw.status, ORDER_STATUSES, "an order update's status");
276
+ if (raw.date !== undefined && status !== "delivered")
277
+ return invalid("only a delivered order update gives date");
278
+ return {
279
+ id,
280
+ kind: "order_update",
281
+ supplierName: word(raw.supplierName, 200),
282
+ number: word(raw.number, 80),
283
+ status,
284
+ ...optional("date", raw.date, (value) => word(value, 10)),
285
+ reason: findingText(raw.reason, 2000),
286
+ evidence,
287
+ };
288
+ }
126
289
  default:
127
- return invalid("a mail finding is a task, task_update, fact, memory, link or new_contact");
290
+ return invalid("a mail finding is a task, task_update, fact, memory, link, new_contact, invoice, payment, order or order_update");
128
291
  }
129
292
  };
130
293
  /**
@@ -1,4 +1,5 @@
1
1
  import * as v from "valibot";
2
+ import { withTimeLimit } from "../http/time-limit.js";
2
3
  import { checkedOptions, range } from "./options.js";
3
4
  import { messagesFor, systemFor } from "./types.js";
4
5
  const options = v.strictObject({
@@ -14,7 +15,7 @@ export const openai = {
14
15
  images: true,
15
16
  baseUrl: "https://api.openai.com/v1",
16
17
  validate: (given) => checkedOptions("openai", options, given),
17
- complete: async (target, request, checked, apiKey, fetcher) => {
18
+ complete: (target, request, checked, apiKey, fetcher) => withTimeLimit(120_000, async (deadline) => {
18
19
  const response = await fetcher(`${target.baseUrl.replace(/\/+$/, "")}/chat/completions`, {
19
20
  method: "POST",
20
21
  headers: { "Content-Type": "application/json", ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}) },
@@ -40,7 +41,7 @@ export const openai = {
40
41
  ? { max_completion_tokens: request.maxTokens }
41
42
  : { max_tokens: request.maxTokens }),
42
43
  }),
43
- signal: AbortSignal.timeout(120_000),
44
+ signal: deadline,
44
45
  redirect: "error",
45
46
  });
46
47
  if (!response.ok)
@@ -53,5 +54,5 @@ export const openai = {
53
54
  (body.usage?.total_tokens ?? 0) < 1)
54
55
  throw new Error("incomplete response");
55
56
  return { text: choice.message.content, tokens: body.usage?.total_tokens };
56
- },
57
+ }),
57
58
  };
@@ -1,4 +1,5 @@
1
1
  import { CliError, isCliError } from "@wirecat/cli-base";
2
+ import { withTimeLimit } from "../http/time-limit.js";
2
3
  const REQUEST_LIMIT_MS = 30_000;
3
4
  export const TOKEN_LOG_EVENT = "oauth.token";
4
5
  /** The keys the module's log records carry, for a run log's `allow`; names only, never a token. */
@@ -8,20 +9,20 @@ const object = (value) => {
8
9
  throw new Error("not an object");
9
10
  return value;
10
11
  };
11
- const requestSignal = (signal) => signal ? AbortSignal.any([signal, AbortSignal.timeout(REQUEST_LIMIT_MS)]) : AbortSignal.timeout(REQUEST_LIMIT_MS);
12
+ const requestSignal = (deadline, signal) => signal ? AbortSignal.any([signal, deadline]) : deadline;
12
13
  const cancelledBy = (signal) => {
13
14
  if (!signal?.aborted)
14
15
  return;
15
16
  throw isCliError(signal.reason) ? signal.reason : new CliError("cancelled", "sign-in was cancelled");
16
17
  };
17
- const post = async (url, body, context) => {
18
+ const post = async (url, body, context, deadline) => {
18
19
  cancelledBy(context.signal);
19
20
  try {
20
21
  return await context.fetch(url, {
21
22
  method: "POST",
22
23
  headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
23
24
  body,
24
- signal: requestSignal(context.signal),
25
+ signal: requestSignal(deadline, context.signal),
25
26
  redirect: "error",
26
27
  });
27
28
  }
@@ -38,11 +39,12 @@ const clientFields = ({ client }) => ({
38
39
  * The token endpoint, form-encoded, for both grants. Nothing from the request or the response reaches a message or
39
40
  * a log record: both carry codes and tokens.
40
41
  */
41
- export const requestTokens = async (provider, params, context) => {
42
+ export const requestTokens = (provider, params, context) => withTimeLimit(REQUEST_LIMIT_MS, (deadline) => exchangeTokens(provider, params, context, deadline));
43
+ const exchangeTokens = async (provider, params, context, deadline) => {
42
44
  const operation = params.grant_type ?? "unknown";
43
45
  const record = (level, fields) => context.log?.[level]({ event: TOKEN_LOG_EVENT, provider: provider.id, operation, pid: process.pid, ...fields });
44
46
  const again = `run ${context.signInCommand} again`;
45
- const response = await post(provider.tokenUrl, new URLSearchParams({ ...params, ...clientFields(context) }), context);
47
+ const response = await post(provider.tokenUrl, new URLSearchParams({ ...params, ...clientFields(context) }), context, deadline);
46
48
  if (!response) {
47
49
  record("warn", { outcome: "no_response" });
48
50
  throw new CliError("outcome_unknown", `${provider.name} token request did not finish; ${again} if it keeps failing`, {
@@ -112,7 +114,9 @@ export const revokeToken = async (provider, tokens, context) => {
112
114
  return provider.revoke(tokens, context);
113
115
  if (!provider.revokeUrl)
114
116
  throw new CliError("validation_error", `${provider.name} declares no way to revoke a sign-in`);
115
- const response = await post(provider.revokeUrl, new URLSearchParams({ token: tokens.refreshToken, ...clientFields(context) }), context);
117
+ const { revokeUrl } = provider;
118
+ const form = new URLSearchParams({ token: tokens.refreshToken, ...clientFields(context) });
119
+ const response = await withTimeLimit(REQUEST_LIMIT_MS, (deadline) => post(revokeUrl, form, context, deadline));
116
120
  context.log?.[response?.ok ? "info" : "warn"]({
117
121
  event: TOKEN_LOG_EVENT,
118
122
  provider: provider.id,
@@ -1,5 +1,6 @@
1
1
  import { MAIL_FINDINGS_KIND } from "../../../store/index.js";
2
2
  import { kept, proposerOfAuthor } from "./shared.js";
3
+ const words = (...parts) => parts.filter(Boolean).join(" ");
3
4
  /** A finding's one line in the inbox; never the email's text, only what the agent wrote. */
4
5
  const summaryOf = (finding) => {
5
6
  switch (finding.kind) {
@@ -15,6 +16,14 @@ const summaryOf = (finding) => {
15
16
  return `${finding.from} → ${finding.to}`;
16
17
  case "new_contact":
17
18
  return `${finding.name} <${finding.address}>`;
19
+ case "invoice":
20
+ return words("invoice", finding.number, finding.total, finding.currency, `· ${finding.reason}`);
21
+ case "payment":
22
+ return words("payment", finding.amount, finding.currency, finding.date, `· ${finding.reason}`);
23
+ case "order":
24
+ return words("order", finding.number, finding.supplierName, finding.status ?? "placed", `· ${finding.reason}`);
25
+ case "order_update":
26
+ return words("order", finding.number, finding.supplierName, finding.status, `· ${finding.reason}`);
18
27
  }
19
28
  };
20
29
  const rowOf = (batch, createdAt) => {
@@ -0,0 +1,4 @@
1
+ import type { MessageStore } from "../../../store/index.js";
2
+ import type { ProposalKind } from "../types.js";
3
+ /** An agent's order, by its own ref (`order:12`) as invoices are; only the owner decides it. */
4
+ export declare const orderKind: (store: Pick<MessageStore, "orders">) => ProposalKind;
@@ -0,0 +1,32 @@
1
+ import { kept, proposerOfAuthor, pushed } from "./shared.js";
2
+ const rowOf = (order) => ({
3
+ id: order.ref,
4
+ kind: "order",
5
+ status: order.review,
6
+ project: order.project,
7
+ subject: order.ref,
8
+ object: order.supplier?.ref ?? null,
9
+ summary: order.title,
10
+ proposedBy: proposerOfAuthor(order.author),
11
+ reason: order.reason,
12
+ evidence: [],
13
+ score: null,
14
+ signals: null,
15
+ parts: null,
16
+ createdAt: order.createdAt,
17
+ });
18
+ /** An agent's order, by its own ref (`order:12`) as invoices are; only the owner decides it. */
19
+ export const orderKind = (store) => ({
20
+ kind: "order",
21
+ prefix: "order",
22
+ list: async (filter) => {
23
+ const status = filter.status ?? "proposed";
24
+ const orders = await store.orders.list({ ...pushed(filter), ...(status === "all" ? {} : { review: status }) });
25
+ return kept(orders.map(rowOf), filter);
26
+ },
27
+ get: async (id) => rowOf(await store.orders.get(id)),
28
+ decide: async (id, { confirm, by, reason }) => {
29
+ const order = confirm ? await store.orders.confirm(id, by, reason) : await store.orders.reject(id, by, reason);
30
+ return { id, kind: "order", status: confirm ? "confirmed" : "rejected", made: confirm ? [order.ref] : [] };
31
+ },
32
+ });
@@ -8,6 +8,7 @@ import { keyDocumentKind } from "./kinds/key-document.js";
8
8
  import { mailFindingsKind } from "./kinds/mail-findings.js";
9
9
  import { meetingFindingsKind } from "./kinds/meeting-findings.js";
10
10
  import { memoryKind } from "./kinds/memory.js";
11
+ import { orderKind } from "./kinds/orders.js";
11
12
  import { relationshipKind } from "./kinds/relationship.js";
12
13
  import { relationshipUpdateKind } from "./kinds/relationship-update.js";
13
14
  import { taskCloseKind } from "./kinds/task-close.js";
@@ -132,6 +133,7 @@ export const proposalRegistry = (store) => {
132
133
  factKind(store),
133
134
  invoiceKind(store),
134
135
  paymentKind(store),
136
+ orderKind(store),
135
137
  memoryKind(store),
136
138
  findingsKind(store),
137
139
  mailFindingsKind(store),
@@ -53,6 +53,7 @@ export type { Author, MemoriesStore, Memory, MemoryInput } from "./sqlite/memori
53
53
  export type { NoteIndexState } from "./sqlite/note-index.js";
54
54
  export { type NearestNote, NOTE_FIELDS, type NoteChunkToEmbed, type NoteHit, type NoteQuery, type NoteSearch, } from "./sqlite/note-search.js";
55
55
  export type { FileNoteInput, Link, LinkInput, LinkListFilter, Note, NoteFolder, NotesStore, NoteTag, } from "./sqlite/notes.js";
56
+ export type { Order, OrderFilter, OrderInput, OrdersStore } from "./sqlite/orders.js";
56
57
  export type { PrivateContact, PrivateContactNote } from "./sqlite/private-people.js";
57
58
  export { assertGenericAction, PROPOSAL_STATUSES, type ProposalInput, type ProposedAction, type ProposedActionsStore, proposerOfAction, } from "./sqlite/proposed-actions.js";
58
59
  export type { RecordKey, RecordOutcome, RecordStore, RecordsWritten, SourceRecord, } from "./sqlite/records.js";
@@ -27,6 +27,7 @@ export declare const ENTITY_TABLES: {
27
27
  readonly memory: "memories";
28
28
  readonly message: "messages";
29
29
  readonly note: "notes";
30
+ readonly order: "orders";
30
31
  readonly organization: "organizations";
31
32
  readonly payment: "payments";
32
33
  readonly person: "persons";
@@ -26,6 +26,7 @@ export const ENTITY_TABLES = {
26
26
  memory: "memories",
27
27
  message: "messages",
28
28
  note: "notes",
29
+ order: "orders",
29
30
  organization: "organizations",
30
31
  payment: "payments",
31
32
  person: "persons",
@@ -11,7 +11,7 @@ export interface Party {
11
11
  /** The owner's own person row. */
12
12
  owner: boolean;
13
13
  }
14
- interface Reviewed {
14
+ export interface Reviewed {
15
15
  review: FinanceReview;
16
16
  /** `name` is a bot's handle and `kind` whether it is an agent or a rule; both `null` for a person. */
17
17
  author: {
@@ -91,7 +91,7 @@ export interface Payment extends Reviewed {
91
91
  export interface PaymentDetail extends Payment {
92
92
  allocations: Allocation[];
93
93
  }
94
- interface Written {
94
+ export interface Written {
95
95
  /** The owner's entry is confirmed at once; a bot's waits as proposed for the owner. */
96
96
  by: Author;
97
97
  project?: string;
@@ -204,6 +204,44 @@ export interface FinanceStore {
204
204
  payable: OpenTotal[];
205
205
  }>;
206
206
  }
207
+ type Row = Record<string, unknown>;
208
+ export declare const refuse: (message: string, reason: string, code?: "validation_error" | "permission_error") => never;
209
+ export declare const oneOf: <T extends string>(value: unknown, allowed: readonly T[], what: string) => T;
210
+ export declare const text: (value: unknown, what: string, limit: number) => string | null;
211
+ export declare const isoDate: (value: unknown, what: string) => string | null;
212
+ export declare const iso: (value: unknown) => string;
213
+ /** What invoices, payments and orders share: their parties, who wrote them, and how the owner decides them. */
214
+ export declare const reviewedRowsOver: (context: StoreContext) => {
215
+ partyOf: (type: unknown, id: unknown) => Party | null;
216
+ partyThing: (reference: string | undefined, what: string) => import("./things.js").Thing | undefined;
217
+ reviewedOf: (row: Row, type: "invoice" | "payment" | "order") => Reviewed;
218
+ written: (input: Written) => {
219
+ owner: boolean;
220
+ actor: Actor;
221
+ model: string | null;
222
+ source: string;
223
+ review: string;
224
+ confirmedType: "bot" | "person" | null;
225
+ confirmedId: number | null;
226
+ metadata: string | null;
227
+ project: number | null;
228
+ evidence: string[];
229
+ };
230
+ keepEvidence: (type: "invoice" | "payment" | "order", id: number, by: ReturnType<(input: Written) => {
231
+ owner: boolean;
232
+ actor: Actor;
233
+ model: string | null;
234
+ source: string;
235
+ review: string;
236
+ confirmedType: "bot" | "person" | null;
237
+ confirmedId: number | null;
238
+ metadata: string | null;
239
+ project: number | null;
240
+ evidence: string[];
241
+ }>) => void;
242
+ ownerOnly: (by: Author, what: string) => void;
243
+ keepDecision: (table: "invoices" | "payments" | "orders", id: number, by: Decider, reason?: string) => void;
244
+ };
207
245
  type FinanceOperations = {
208
246
  [K in keyof FinanceStore]: FinanceStore[K] extends (...args: infer A) => Promise<infer R> ? (...args: A) => R : never;
209
247
  };