@wirecat/cli-core 0.556.0 → 0.558.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 (62) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/calendar/mcp/index.d.ts +12 -12
  3. package/dist/cli/index.d.ts +0 -1
  4. package/dist/cli/index.js +0 -1
  5. package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
  6. package/dist/domain/finance.d.ts +40 -0
  7. package/dist/domain/finance.js +39 -0
  8. package/dist/domain/money.d.ts +29 -0
  9. package/dist/domain/money.js +98 -0
  10. package/dist/domain/ref-summaries.js +2 -0
  11. package/dist/domain/references.d.ts +1 -1
  12. package/dist/domain/references.js +2 -0
  13. package/dist/entities/fact.d.ts +1 -12
  14. package/dist/entities/fact.js +0 -13
  15. package/dist/entities/index.d.ts +2 -12
  16. package/dist/entities/meeting-findings.d.ts +1 -1
  17. package/dist/errors/reasons.js +16 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +2 -0
  20. package/dist/mail/cli/review.js +0 -1
  21. package/dist/meetings/findings-rules.d.ts +7 -4
  22. package/dist/meetings/findings-rules.js +11 -4
  23. package/dist/meetings/mcp/index.d.ts +15 -15
  24. package/dist/meetings/payment.d.ts +10 -6
  25. package/dist/meetings/payment.js +21 -6
  26. package/dist/services/project-status/index.d.ts +1 -1
  27. package/dist/services/project-status/index.js +7 -3
  28. package/dist/services/project-status/signals/payments.d.ts +3 -3
  29. package/dist/services/project-status/signals/payments.js +43 -17
  30. package/dist/services/proposals/kinds/finance.d.ts +4 -0
  31. package/dist/services/proposals/kinds/finance.js +40 -0
  32. package/dist/services/proposals/registry.js +3 -0
  33. package/dist/store/evidence-findings.js +16 -8
  34. package/dist/store/index.d.ts +2 -1
  35. package/dist/store/project-transfer/request.d.ts +1 -1
  36. package/dist/store/project-transfer/schema.d.ts +2 -2
  37. package/dist/store/project-transfer/snapshot-model.d.ts +2 -2
  38. package/dist/store/sqlite/entity-types.d.ts +2 -0
  39. package/dist/store/sqlite/entity-types.js +2 -0
  40. package/dist/store/sqlite/facts.d.ts +0 -13
  41. package/dist/store/sqlite/facts.js +5 -58
  42. package/dist/store/sqlite/finance.d.ts +203 -0
  43. package/dist/store/sqlite/finance.js +529 -0
  44. package/dist/store/sqlite/findings-core.js +5 -14
  45. package/dist/store/sqlite/knowledge.js +3 -1
  46. package/dist/store/sqlite/manifest.js +3 -0
  47. package/dist/store/sqlite/migrations.generated.js +43 -0
  48. package/dist/store/sqlite/open-store.js +2 -0
  49. package/dist/store/sqlite/payment-entries.d.ts +26 -0
  50. package/dist/store/sqlite/payment-entries.js +91 -0
  51. package/dist/store/sqlite/payment-facts.d.ts +12 -0
  52. package/dist/store/sqlite/payment-facts.js +92 -0
  53. package/dist/store/sqlite/project-transfer/snapshot.d.ts +1 -1
  54. package/dist/store/sqlite/ref-summaries.js +33 -0
  55. package/dist/store/sqlite/schema.d.ts +1043 -0
  56. package/dist/store/sqlite/schema.js +98 -0
  57. package/dist/store/sqlite/things.js +2 -0
  58. package/dist/store/store.d.ts +3 -1
  59. package/dist/store/store.js +5 -0
  60. package/package.json +1 -1
  61. package/dist/cli/payment-query.d.ts +0 -8
  62. package/dist/cli/payment-query.js +0 -27
@@ -20,6 +20,7 @@ export declare const ENTITY_TABLES: {
20
20
  readonly file: "files";
21
21
  readonly identity: "identities";
22
22
  readonly image: "attachments";
23
+ readonly invoice: "invoices";
23
24
  readonly meeting: "meetings";
24
25
  readonly meeting_transcript: "meeting_transcripts";
25
26
  readonly meeting_transcript_row: "meeting_transcript_rows";
@@ -27,6 +28,7 @@ export declare const ENTITY_TABLES: {
27
28
  readonly message: "messages";
28
29
  readonly note: "notes";
29
30
  readonly organization: "organizations";
31
+ readonly payment: "payments";
30
32
  readonly person: "persons";
31
33
  readonly place: "places";
32
34
  readonly project: "projects";
@@ -19,6 +19,7 @@ export const ENTITY_TABLES = {
19
19
  file: "files",
20
20
  identity: "identities",
21
21
  image: "attachments",
22
+ invoice: "invoices",
22
23
  meeting: "meetings",
23
24
  meeting_transcript: "meeting_transcripts",
24
25
  meeting_transcript_row: "meeting_transcript_rows",
@@ -26,6 +27,7 @@ export const ENTITY_TABLES = {
26
27
  message: "messages",
27
28
  note: "notes",
28
29
  organization: "organizations",
30
+ payment: "payments",
29
31
  person: "persons",
30
32
  place: "places",
31
33
  project: "projects",
@@ -1,5 +1,4 @@
1
1
  import { type FACT_STATUSES, FACT_TYPES, MEMORY_SCOPES } from "../../domain/knowledge.js";
2
- import { type PAYMENT_DIRECTIONS, type PAYMENT_STATES, type Payment } from "../../meetings/index.js";
3
2
  import { type Decider } from "../decisions.js";
4
3
  import { type Actor, type ProposedByFilter } from "./actors.js";
5
4
  import { type EvidenceSource } from "./evidence-sources.js";
@@ -35,8 +34,6 @@ export interface Fact {
35
34
  scope: (typeof MEMORY_SCOPES)[number] | null;
36
35
  source: string;
37
36
  evidence: string[];
38
- /** Set on a `payment` fact only. */
39
- payment: Payment | null;
40
37
  createdAt: string;
41
38
  updatedAt: string;
42
39
  }
@@ -55,14 +52,6 @@ export interface FactInput {
55
52
  supersedes?: string;
56
53
  /** The memory an agent drew it from: a `created-from` link. */
57
54
  createdFrom?: string;
58
- /** Required on a `payment` fact, refused on any other; checked as `validatePayment` does. */
59
- payment?: Payment;
60
- }
61
- /** `open` is an expected payment that still holds: not superseded, rejected or cancelled. */
62
- export interface PaymentFilter {
63
- direction?: (typeof PAYMENT_DIRECTIONS)[number];
64
- state?: (typeof PAYMENT_STATES)[number];
65
- open?: boolean;
66
55
  }
67
56
  export interface FactsStore {
68
57
  add(input: FactInput): Promise<Fact>;
@@ -73,8 +62,6 @@ export interface FactsStore {
73
62
  subject?: string;
74
63
  status?: Fact["status"];
75
64
  proposedBy?: ProposedByFilter;
76
- /** Narrows to payment facts by their fields. */
77
- payment?: PaymentFilter;
78
65
  /** `created` lists the newest written first; by default the newest by when it happened. */
79
66
  order?: "occurred" | "created";
80
67
  limit?: number;
@@ -1,7 +1,6 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { FACT_TYPES, MEMORY_SCOPES } from "../../domain/knowledge.js";
3
3
  import { calendarMoment } from "../../domain/moment.js";
4
- import { validatePayment } from "../../meetings/index.js";
5
4
  import { assertMayDecide, assertMayWithdraw, deciderAuthor } from "../decisions.js";
6
5
  import { proposedBySql, proposerOf } from "./actors.js";
7
6
  import { evidenceSourcesOf } from "./evidence-sources.js";
@@ -36,7 +35,6 @@ export const factOperationsOver = (context) => {
36
35
  scope: row.scope == null ? null : row.scope,
37
36
  source: String(row.source),
38
37
  evidence: evidenceOf(context, { type: "fact", id: Number(row.id) }),
39
- payment: row.type === "payment" ? paymentOf(row.metadata) : null,
40
38
  createdAt: iso(row.created_at),
41
39
  updatedAt: iso(row.updated_at),
42
40
  });
@@ -54,7 +52,6 @@ export const factOperationsOver = (context) => {
54
52
  const decided = (row, by) => assertMayDecide({
55
53
  id: `fact:${row.id}`,
56
54
  proposedBy: proposerOf(database, row.author_type, row.author_id),
57
- ownerOnly: row.type === "payment",
58
55
  }, by, context.decisions);
59
56
  /** Keeps who decided for the owner and why in `metadata.decision`; the decider's actor is in `confirmed_by`. */
60
57
  const keepDecision = (id, by, reason) => {
@@ -83,6 +80,8 @@ export const factOperationsOver = (context) => {
83
80
  };
84
81
  return {
85
82
  add: (input) => {
83
+ if (input.type === "payment")
84
+ throw new CliError("validation_error", "a payment is not a fact: record it with store.finance.addPayment, a payment: row, or an invoice: for one expected", { reason: "fact.payment-retired" });
86
85
  const type = oneOf(input.type, FACT_TYPES, "a fact's type");
87
86
  const statement = input.statement?.trim() ?? "";
88
87
  if (!statement || statement.length > 2000)
@@ -91,7 +90,6 @@ export const factOperationsOver = (context) => {
91
90
  const occurredAt = input.occurredAt === undefined ? null : calendarMoment(input.occurredAt, "occurredAt");
92
91
  if (occurredAt !== null && !Number.isFinite(occurredAt))
93
92
  throw new CliError("validation_error", "occurredAt takes an ISO timestamp");
94
- const payment = paymentInput(type, input);
95
93
  const id = write(database, () => {
96
94
  const project = input.project === undefined ? undefined : requiredThing(database, input.project, "the project");
97
95
  if (project && project.type !== "project")
@@ -100,10 +98,6 @@ export const factOperationsOver = (context) => {
100
98
  const replaced = input.supersedes === undefined ? undefined : rowOf(input.supersedes);
101
99
  if (replaced && replaced.status !== "proposed" && replaced.status !== "accepted")
102
100
  throw new CliError("validation_error", `${input.supersedes} is ${String(replaced.status)}; only a proposed or accepted fact can be corrected`);
103
- if (replaced && (replaced.type === "payment") !== (type === "payment"))
104
- throw new CliError("validation_error", "a payment fact corrects a payment fact, and only one");
105
- if (payment?.counterparty)
106
- requiredThing(database, payment.counterparty, "the counterparty");
107
101
  const owner = input.by === "owner";
108
102
  const { actor, model } = authorActor(context, input.by);
109
103
  const at = now();
@@ -111,7 +105,7 @@ export const factOperationsOver = (context) => {
111
105
  .prepare("INSERT INTO facts (id, type, statement, status, project_id, subject_type, subject_id, occurred_at, supersedes_id, " +
112
106
  "author_type, author_id, model, scope, source, metadata, created_at, updated_at) " +
113
107
  "VALUES (?, ?, ?, 'proposed', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id")
114
- .get(nextId(database, "facts"), type, statement, project?.id ?? null, subject?.type ?? null, subject?.id ?? null, occurredAt, replaced === undefined ? null : Number(replaced.id), actor.type, actor.id, model, scope, owner ? "owner" : "agent", payment ? JSON.stringify({ payment }) : null, at, at)?.id);
108
+ .get(nextId(database, "facts"), type, statement, project?.id ?? null, subject?.type ?? null, subject?.id ?? null, occurredAt, replaced === undefined ? null : Number(replaced.id), actor.type, actor.id, model, scope, owner ? "owner" : "agent", null, at, at)?.id);
115
109
  linkEvidence(context, { type: "fact", id: made }, input.evidence ?? [], owner ? "owner" : "agent");
116
110
  if (input.createdFrom !== undefined) {
117
111
  const from = requiredThing(database, input.createdFrom, "the source");
@@ -132,12 +126,11 @@ export const factOperationsOver = (context) => {
132
126
  const subject = options.subject === undefined ? undefined : requiredThing(database, options.subject);
133
127
  const by = proposedBySql("f.author_type", "f.author_id", options.proposedBy);
134
128
  const order = options.order === "created" ? "f.created_at" : "coalesce(f.occurred_at, f.created_at)";
135
- const paid = paymentSql(options.payment);
136
129
  return database
137
130
  .prepare(`${NAMED} WHERE f.deleted_at IS NULL AND (? IS NULL OR f.type = ?) AND (? IS NULL OR f.project_id = ?) ` +
138
131
  "AND (? IS NULL OR (f.subject_type = ? AND f.subject_id = ?)) AND (? IS NULL OR f.status = ?) " +
139
- `AND ${by.sql}${paid.sql} ORDER BY ${order} DESC, f.id DESC LIMIT ?`)
140
- .all(options.type ?? null, options.type ?? null, project?.id ?? null, project?.id ?? null, subject?.type ?? null, subject?.type ?? null, subject?.id ?? null, options.status ?? null, options.status ?? null, ...by.params, ...paid.params, Math.min(Math.max(options.limit ?? 100, 1), 500))
132
+ `AND ${by.sql} ORDER BY ${order} DESC, f.id DESC LIMIT ?`)
133
+ .all(options.type ?? null, options.type ?? null, project?.id ?? null, project?.id ?? null, subject?.type ?? null, subject?.type ?? null, subject?.id ?? null, options.status ?? null, options.status ?? null, ...by.params, Math.min(Math.max(options.limit ?? 100, 1), 500))
141
134
  .map(factOf);
142
135
  },
143
136
  accept: (reference, by, reason) => {
@@ -195,52 +188,6 @@ export const factOperationsOver = (context) => {
195
188
  sources: (reference) => evidenceSourcesOf(database, { type: "fact", id: Number(rowOf(reference).id) }),
196
189
  };
197
190
  };
198
- const paymentInput = (type, input) => {
199
- if (type !== "payment") {
200
- if (input.payment !== undefined)
201
- throw new CliError("validation_error", "only a payment fact takes a payment");
202
- return undefined;
203
- }
204
- if (input.payment === undefined)
205
- throw new CliError("validation_error", "a payment fact needs its payment: amount, currency, direction and state");
206
- const evidence = input.evidence?.length ?? 0;
207
- if (evidence < 1 || evidence > 8)
208
- throw new CliError("validation_error", "a payment fact cites 1–8 references: the ask, the transfer or a sheet row");
209
- return validatePayment(input.payment);
210
- };
211
- const paymentOf = (metadata) => {
212
- if (metadata == null)
213
- return null;
214
- try {
215
- const { payment } = JSON.parse(String(metadata));
216
- return payment ?? null;
217
- }
218
- catch {
219
- return null;
220
- }
221
- };
222
- const paymentSql = (filter) => {
223
- if (filter === undefined)
224
- return { sql: "", params: [] };
225
- const parts = ["f.type = 'payment'"];
226
- const params = [];
227
- const state = filter.open ? "expected" : filter.state;
228
- if (filter.open && filter.state !== undefined && filter.state !== "expected")
229
- parts.push("0");
230
- if (filter.direction !== undefined) {
231
- parts.push("json_extract(f.metadata, '$.payment.direction') = ?");
232
- params.push(filter.direction);
233
- }
234
- if (state !== undefined) {
235
- parts.push("json_extract(f.metadata, '$.payment.state') = ?");
236
- params.push(state);
237
- }
238
- if (filter.open)
239
- parts.push("f.status IN ('proposed', 'accepted')");
240
- if (filter.open === false)
241
- parts.push("NOT (json_extract(f.metadata, '$.payment.state') = 'expected' AND f.status IN ('proposed', 'accepted'))");
242
- return { sql: ` AND ${parts.join(" AND ")}`, params };
243
- };
244
191
  const migratedFrom = (metadata) => {
245
192
  try {
246
193
  const { memory } = JSON.parse(metadata);
@@ -0,0 +1,203 @@
1
+ import { type FinanceReview, type InvoiceDirection, type InvoiceStatus, type MoneyDirection } from "../../domain/finance.js";
2
+ import { type Money } from "../../domain/money.js";
3
+ import { type Decider } from "../decisions.js";
4
+ import { type Actor, type ProposedByFilter } from "./actors.js";
5
+ import { type Author } from "./memories.js";
6
+ import type { StoreContext } from "./open.js";
7
+ /** A `person:` or `organization:` on an invoice or a payment, with its name so a list needs no lookup. */
8
+ export interface Party {
9
+ ref: string;
10
+ name: string | null;
11
+ /** The owner's own person row. */
12
+ owner: boolean;
13
+ }
14
+ interface Reviewed {
15
+ review: FinanceReview;
16
+ /** `name` is a bot's handle and `kind` whether it is an agent or a rule; both `null` for a person. */
17
+ author: {
18
+ type: Actor["type"];
19
+ id: string;
20
+ name: string | null;
21
+ kind: "agent" | "rule" | null;
22
+ };
23
+ model: string | null;
24
+ confirmedBy: {
25
+ type: Actor["type"];
26
+ id: string;
27
+ } | null;
28
+ source: string;
29
+ /** Why an agent or an import proposed it. */
30
+ reason: string | null;
31
+ /** What it was read from: the messages, documents or notes an agent or the owner cited. */
32
+ evidence: string[];
33
+ project: string | null;
34
+ projectName: string | null;
35
+ createdAt: string;
36
+ updatedAt: string;
37
+ }
38
+ export interface Invoice extends Reviewed {
39
+ id: string;
40
+ ref: string;
41
+ /** One line, as a list or the review inbox prints it. */
42
+ title: string;
43
+ direction: InvoiceDirection;
44
+ number: string | null;
45
+ issuer: Party | null;
46
+ counterparty: Party;
47
+ currency: string;
48
+ total: string;
49
+ /** Settled by confirmed payments. */
50
+ paid: string;
51
+ /** Still owed: `total − paid`. */
52
+ open: string;
53
+ issueDate: string | null;
54
+ dueDate: string | null;
55
+ status: InvoiceStatus;
56
+ }
57
+ export interface InvoiceLine {
58
+ position: number;
59
+ description: string;
60
+ quantity: string;
61
+ unitPrice: string | null;
62
+ amount: string;
63
+ }
64
+ export interface Allocation {
65
+ payment: string;
66
+ invoice: string;
67
+ amount: string;
68
+ currency: string;
69
+ createdAt: string;
70
+ }
71
+ export interface InvoiceDetail extends Invoice {
72
+ lines: InvoiceLine[];
73
+ allocations: Allocation[];
74
+ }
75
+ export interface Payment extends Reviewed {
76
+ id: string;
77
+ ref: string;
78
+ title: string;
79
+ direction: MoneyDirection;
80
+ payer: Party | null;
81
+ payee: Party | null;
82
+ amount: string;
83
+ currency: string;
84
+ /** `YYYY-MM-DD`. */
85
+ date: string;
86
+ reference: string | null;
87
+ /** Allocated to invoices, confirmed or not. */
88
+ allocated: string;
89
+ unallocated: string;
90
+ }
91
+ export interface PaymentDetail extends Payment {
92
+ allocations: Allocation[];
93
+ }
94
+ interface Written {
95
+ /** The owner's entry is confirmed at once; a bot's waits as proposed for the owner. */
96
+ by: Author;
97
+ project?: string;
98
+ /** Where a bot's row came from, such as `agent` or an importer's name; the owner's is always `owner`. */
99
+ source?: string;
100
+ /** Why a bot proposes it, kept for the reviewer. */
101
+ reason?: string;
102
+ /** References it was read from, linked as `evidence`. */
103
+ evidence?: string[];
104
+ }
105
+ export interface InvoiceLineInput {
106
+ description: string;
107
+ /** A decimal string; `1` when not given. */
108
+ quantity?: string;
109
+ /** Decimal strings in the invoice's currency. Give either or both: both must agree, quantity × unit price. */
110
+ unitPrice?: string;
111
+ amount?: string;
112
+ }
113
+ export interface InvoiceInput extends Written {
114
+ direction: InvoiceDirection;
115
+ number?: string;
116
+ /** `person:` or `organization:`. */
117
+ issuer?: string;
118
+ counterparty: string;
119
+ currency: string;
120
+ /** `YYYY-MM-DD`. */
121
+ issueDate?: string;
122
+ dueDate?: string;
123
+ /** Kept as a draft until `issueInvoice`; otherwise issued at once. */
124
+ draft?: boolean;
125
+ lines: InvoiceLineInput[];
126
+ /** The total printed on the invoice: refused when it is not the sum of the lines. */
127
+ total?: string;
128
+ }
129
+ export interface PaymentInput extends Written {
130
+ direction: MoneyDirection;
131
+ payer?: string;
132
+ payee?: string;
133
+ amount: string;
134
+ currency: string;
135
+ date: string;
136
+ reference?: string;
137
+ /** Invoices it settles, in the payment's currency; a bot's count only once the owner confirms the payment. */
138
+ allocations?: {
139
+ invoice: string;
140
+ amount: string;
141
+ }[];
142
+ }
143
+ interface ListFilter {
144
+ project?: string;
145
+ review?: FinanceReview;
146
+ proposedBy?: ProposedByFilter;
147
+ /** 1–500, 100 by default; newest written first. */
148
+ limit?: number;
149
+ }
150
+ export interface OpenTotal extends Money {
151
+ invoices: number;
152
+ }
153
+ export interface FinanceStore {
154
+ addInvoice(input: InvoiceInput): Promise<InvoiceDetail>;
155
+ invoice(reference: string): Promise<InvoiceDetail>;
156
+ invoices(filter?: ListFilter & {
157
+ direction?: InvoiceDirection;
158
+ status?: InvoiceStatus;
159
+ /** Issued or partly paid. */
160
+ open?: boolean;
161
+ counterparty?: string;
162
+ }): Promise<Invoice[]>;
163
+ /** The owner sends a draft: its status then follows its allocations. */
164
+ issueInvoice(reference: string, by: Author): Promise<InvoiceDetail>;
165
+ /** The owner cancels an invoice no confirmed payment is allocated to; proposed allocations to it go. */
166
+ voidInvoice(reference: string, by: Author): Promise<InvoiceDetail>;
167
+ addPayment(input: PaymentInput): Promise<PaymentDetail>;
168
+ payment(reference: string): Promise<PaymentDetail>;
169
+ payments(filter?: ListFilter & {
170
+ direction?: MoneyDirection;
171
+ party?: string;
172
+ }): Promise<Payment[]>;
173
+ /**
174
+ * Settles part of an invoice with part of a payment, in one currency, never beyond either. The owner allocates any
175
+ * payment; a bot only one it proposed that is still proposed.
176
+ */
177
+ allocate(input: {
178
+ payment: string;
179
+ invoice: string;
180
+ amount: string;
181
+ by: Author;
182
+ }): Promise<Allocation>;
183
+ /**
184
+ * The owner confirms a proposed `invoice:` or `payment:`; only the owner decides money. A payment whose allocations
185
+ * would settle more than an invoice's total, given the confirmed payments by then, is refused and stays proposed.
186
+ */
187
+ confirm(reference: string, by: Decider, reason?: string): Promise<InvoiceDetail | PaymentDetail>;
188
+ /** The owner turns a proposal down; it stays, `rejected`, and its allocations go. */
189
+ reject(reference: string, by: Decider, reason?: string): Promise<InvoiceDetail | PaymentDetail>;
190
+ /** What is still owed on confirmed open invoices, per direction and per currency: nothing is converted. */
191
+ openTotals(filter?: {
192
+ project?: string;
193
+ }): Promise<{
194
+ receivable: OpenTotal[];
195
+ payable: OpenTotal[];
196
+ }>;
197
+ }
198
+ type FinanceOperations = {
199
+ [K in keyof FinanceStore]: FinanceStore[K] extends (...args: infer A) => Promise<infer R> ? (...args: A) => R : never;
200
+ };
201
+ export declare const financeOperationsOver: (context: StoreContext) => FinanceOperations;
202
+ export declare const financeStoreOver: (context: StoreContext) => FinanceStore;
203
+ export {};