@happyvertical/smrt-expenses 0.55.13 → 0.55.14

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.
@@ -2,7 +2,7 @@
2
2
  "version": "1.0.0",
3
3
  "timestamp": 0,
4
4
  "packageName": "@happyvertical/smrt-expenses",
5
- "packageVersion": "0.55.13",
5
+ "packageVersion": "0.55.14",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-expenses:ExpenseCollection": {
8
8
  "name": "expensecollection",
@@ -86,7 +86,7 @@
86
86
  "collectionExportName": "ExpenseCollectionCollection",
87
87
  "schema": {
88
88
  "tableName": "expenses",
89
- "ddl": "CREATE TABLE IF NOT EXISTS \"expenses\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"tenant_id\" UUID,\n \"cost_object_type\" TEXT,\n \"cost_object_id\" TEXT,\n \"category\" TEXT,\n \"description\" TEXT,\n \"amount\" INTEGER,\n \"currency\" TEXT,\n \"incurred_on\" TEXT,\n \"recorded_at\" TIMESTAMP,\n \"recorded_by_profile_id\" UUID,\n \"paid_by\" TEXT,\n \"paid_by_profile_id\" UUID,\n \"vendor_id\" UUID,\n \"commitment_id\" UUID,\n \"commitment_line_id\" UUID,\n \"review_status\" TEXT,\n \"reviewed_by_profile_id\" UUID,\n \"reviewed_at\" TIMESTAMP,\n \"review_note\" TEXT,\n \"duplicate_of_id\" UUID,\n \"reimbursable\" BOOLEAN,\n \"reimbursed_at\" TIMESTAMP,\n \"reimbursement_reference\" TEXT\n);",
89
+ "ddl": "CREATE TABLE IF NOT EXISTS \"expenses\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"tenant_id\" UUID,\n \"cost_object_type\" TEXT,\n \"cost_object_id\" TEXT,\n \"category\" TEXT,\n \"description\" TEXT,\n \"amount\" INTEGER,\n \"currency\" TEXT,\n \"financial_breakdown\" TEXT,\n \"paid_by_name\" TEXT,\n \"refund_of_id\" UUID,\n \"incurred_on\" TEXT,\n \"recorded_at\" TIMESTAMP,\n \"recorded_by_profile_id\" UUID,\n \"paid_by\" TEXT,\n \"paid_by_profile_id\" UUID,\n \"vendor_id\" UUID,\n \"commitment_id\" UUID,\n \"commitment_line_id\" UUID,\n \"review_status\" TEXT,\n \"reviewed_by_profile_id\" UUID,\n \"reviewed_at\" TIMESTAMP,\n \"review_note\" TEXT,\n \"duplicate_of_id\" UUID,\n \"reimbursable\" BOOLEAN,\n \"reimbursed_at\" TIMESTAMP,\n \"reimbursement_reference\" TEXT\n);",
90
90
  "columns": {
91
91
  "id": {
92
92
  "type": "UUID",
@@ -149,6 +149,28 @@
149
149
  "notNull": false,
150
150
  "unique": false
151
151
  },
152
+ "financial_breakdown": {
153
+ "type": "TEXT",
154
+ "notNull": false,
155
+ "unique": false
156
+ },
157
+ "paid_by_name": {
158
+ "type": "TEXT",
159
+ "notNull": false,
160
+ "unique": false
161
+ },
162
+ "refund_of_id": {
163
+ "type": "UUID",
164
+ "referenceKind": "foreignKey",
165
+ "notNull": false,
166
+ "unique": false,
167
+ "foreignKey": {
168
+ "table": "expenses",
169
+ "column": "id",
170
+ "onDelete": "RESTRICT",
171
+ "onUpdate": "CASCADE"
172
+ }
173
+ },
152
174
  "incurred_on": {
153
175
  "type": "TEXT",
154
176
  "notNull": false,
@@ -286,6 +308,12 @@
286
308
  "created_at"
287
309
  ]
288
310
  },
311
+ {
312
+ "name": "expenses_refund_of_id_idx",
313
+ "columns": [
314
+ "refund_of_id"
315
+ ]
316
+ },
289
317
  {
290
318
  "name": "expenses_recorded_by_profile_id_idx",
291
319
  "columns": [
@@ -329,7 +357,7 @@
329
357
  ]
330
358
  }
331
359
  ],
332
- "version": "af101807"
360
+ "version": "287668fa"
333
361
  },
334
362
  "importPath": "@happyvertical/smrt-expenses"
335
363
  },
@@ -697,6 +725,24 @@
697
725
  "required": false,
698
726
  "_meta": {}
699
727
  },
728
+ "financialBreakdown": {
729
+ "type": "text",
730
+ "required": false,
731
+ "_meta": {}
732
+ },
733
+ "paidByName": {
734
+ "type": "text",
735
+ "required": false,
736
+ "_meta": {}
737
+ },
738
+ "refundOfId": {
739
+ "type": "foreignKey",
740
+ "required": false,
741
+ "related": "Expense",
742
+ "_meta": {
743
+ "onDelete": "RESTRICT"
744
+ }
745
+ },
700
746
  "incurredOn": {
701
747
  "type": "text",
702
748
  "required": false,
@@ -717,7 +763,8 @@
717
763
  "_meta": {},
718
764
  "enum": [
719
765
  "company",
720
- "person"
766
+ "person",
767
+ "third-party"
721
768
  ]
722
769
  },
723
770
  "paidByProfileId": {
@@ -798,6 +845,44 @@
798
845
  }
799
846
  },
800
847
  "methods": {
848
+ "getFinancialBreakdown": {
849
+ "name": "getFinancialBreakdown",
850
+ "async": false,
851
+ "parameters": [],
852
+ "returnType": "ExpenseFinancialBreakdown | null",
853
+ "isStatic": false,
854
+ "isPublic": true
855
+ },
856
+ "setFinancialBreakdown": {
857
+ "name": "setFinancialBreakdown",
858
+ "async": false,
859
+ "parameters": [
860
+ {
861
+ "name": "value",
862
+ "type": "ExpenseFinancialBreakdown | null",
863
+ "optional": false,
864
+ "unionBranches": [
865
+ {
866
+ "type": "ExpenseFinancialBreakdown"
867
+ },
868
+ {
869
+ "type": "null"
870
+ }
871
+ ]
872
+ }
873
+ ],
874
+ "returnType": "void",
875
+ "isStatic": false,
876
+ "isPublic": true
877
+ },
878
+ "getCostBasis": {
879
+ "name": "getCostBasis",
880
+ "async": false,
881
+ "parameters": [],
882
+ "returnType": "ExpenseCostBasis | null",
883
+ "isStatic": false,
884
+ "isPublic": true
885
+ },
801
886
  "isReimbursed": {
802
887
  "name": "isReimbursed",
803
888
  "async": false,
@@ -951,7 +1036,7 @@
951
1036
  "collectionExportName": "ExpenseCollection",
952
1037
  "schema": {
953
1038
  "tableName": "expenses",
954
- "ddl": "CREATE TABLE IF NOT EXISTS \"expenses\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"tenant_id\" UUID,\n \"cost_object_type\" TEXT,\n \"cost_object_id\" TEXT,\n \"category\" TEXT,\n \"description\" TEXT,\n \"amount\" INTEGER,\n \"currency\" TEXT,\n \"incurred_on\" TEXT,\n \"recorded_at\" TIMESTAMP,\n \"recorded_by_profile_id\" UUID,\n \"paid_by\" TEXT,\n \"paid_by_profile_id\" UUID,\n \"vendor_id\" UUID,\n \"commitment_id\" UUID,\n \"commitment_line_id\" UUID,\n \"review_status\" TEXT,\n \"reviewed_by_profile_id\" UUID,\n \"reviewed_at\" TIMESTAMP,\n \"review_note\" TEXT,\n \"duplicate_of_id\" UUID,\n \"reimbursable\" BOOLEAN,\n \"reimbursed_at\" TIMESTAMP,\n \"reimbursement_reference\" TEXT\n);",
1039
+ "ddl": "CREATE TABLE IF NOT EXISTS \"expenses\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"tenant_id\" UUID,\n \"cost_object_type\" TEXT,\n \"cost_object_id\" TEXT,\n \"category\" TEXT,\n \"description\" TEXT,\n \"amount\" INTEGER,\n \"currency\" TEXT,\n \"financial_breakdown\" TEXT,\n \"paid_by_name\" TEXT,\n \"refund_of_id\" UUID,\n \"incurred_on\" TEXT,\n \"recorded_at\" TIMESTAMP,\n \"recorded_by_profile_id\" UUID,\n \"paid_by\" TEXT,\n \"paid_by_profile_id\" UUID,\n \"vendor_id\" UUID,\n \"commitment_id\" UUID,\n \"commitment_line_id\" UUID,\n \"review_status\" TEXT,\n \"reviewed_by_profile_id\" UUID,\n \"reviewed_at\" TIMESTAMP,\n \"review_note\" TEXT,\n \"duplicate_of_id\" UUID,\n \"reimbursable\" BOOLEAN,\n \"reimbursed_at\" TIMESTAMP,\n \"reimbursement_reference\" TEXT\n);",
955
1040
  "columns": {
956
1041
  "id": {
957
1042
  "type": "UUID",
@@ -1014,6 +1099,28 @@
1014
1099
  "notNull": false,
1015
1100
  "unique": false
1016
1101
  },
1102
+ "financial_breakdown": {
1103
+ "type": "TEXT",
1104
+ "notNull": false,
1105
+ "unique": false
1106
+ },
1107
+ "paid_by_name": {
1108
+ "type": "TEXT",
1109
+ "notNull": false,
1110
+ "unique": false
1111
+ },
1112
+ "refund_of_id": {
1113
+ "type": "UUID",
1114
+ "referenceKind": "foreignKey",
1115
+ "notNull": false,
1116
+ "unique": false,
1117
+ "foreignKey": {
1118
+ "table": "expenses",
1119
+ "column": "id",
1120
+ "onDelete": "RESTRICT",
1121
+ "onUpdate": "CASCADE"
1122
+ }
1123
+ },
1017
1124
  "incurred_on": {
1018
1125
  "type": "TEXT",
1019
1126
  "notNull": false,
@@ -1151,6 +1258,12 @@
1151
1258
  "created_at"
1152
1259
  ]
1153
1260
  },
1261
+ {
1262
+ "name": "expenses_refund_of_id_idx",
1263
+ "columns": [
1264
+ "refund_of_id"
1265
+ ]
1266
+ },
1154
1267
  {
1155
1268
  "name": "expenses_recorded_by_profile_id_idx",
1156
1269
  "columns": [
@@ -1194,7 +1307,7 @@
1194
1307
  ]
1195
1308
  }
1196
1309
  ],
1197
- "version": "af101807"
1310
+ "version": "287668fa"
1198
1311
  },
1199
1312
  "importPath": "@happyvertical/smrt-expenses"
1200
1313
  },
@@ -5,7 +5,7 @@
5
5
  * @packageDocumentation
6
6
  */
7
7
  import { SmrtObject } from '@happyvertical/smrt-core';
8
- import { type ExpenseOptions, type ExpensePayer, type ExpenseReviewStatus, type MarkDuplicateExpenseInput, type MarkReimbursedInput, type RejectExpenseInput, type ReopenExpenseInput, type ReviewExpenseInput } from '../types.js';
8
+ import { type ExpenseCostBasis, type ExpenseFinancialBreakdown, type ExpenseOptions, type ExpensePayer, type ExpenseReviewStatus, type MarkDuplicateExpenseInput, type MarkReimbursedInput, type RejectExpenseInput, type ReopenExpenseInput, type ReviewExpenseInput } from '../types.js';
9
9
  /**
10
10
  * A cost someone incurred.
11
11
  *
@@ -46,17 +46,23 @@ export declare class Expense extends SmrtObject {
46
46
  category: string;
47
47
  /** What was bought or paid for. */
48
48
  description: string;
49
- /** Amount in integer minor units (cents). `= 0` makes the column INTEGER. */
49
+ /** Integer minor units; negative only for a refund linked by refundOfId. */
50
50
  amount: number;
51
51
  /** ISO 4217 currency code, upper case. Required. */
52
52
  currency: string;
53
+ /** Optional supplier facts as guarded JSON text; null or empty means unknown. */
54
+ financialBreakdown: string | null;
55
+ /** Name of the third party who paid; no employee reimbursement is implied. */
56
+ paidByName: string | null;
57
+ /** Positive source expense reversed by this negative refund; no settlement. */
58
+ refundOfId: string | null;
53
59
  /** Calendar date the cost was incurred, `YYYY-MM-DD`. Required. */
54
60
  incurredOn: string;
55
61
  /** When the expense was recorded; set on first save when empty. */
56
62
  recordedAt: Date | null;
57
63
  /** Profile that recorded the expense. */
58
64
  recordedByProfileId: string | null;
59
- /** `company`, or `person` (then {@link Expense.paidByProfileId} is set). */
65
+ /** Company, named person (profile), or named third party (paidByName). */
60
66
  paidBy: ExpensePayer;
61
67
  /** Profile that paid out of pocket; required when `paidBy` is `person`. */
62
68
  paidByProfileId: string | null;
@@ -89,6 +95,12 @@ export declare class Expense extends SmrtObject {
89
95
  /** Payroll line, payables reference, or transfer id of the payback. */
90
96
  reimbursementReference: string;
91
97
  constructor(options?: ExpenseOptions);
98
+ /** Read validated supplier facts, or null when none were recorded. */
99
+ getFinancialBreakdown(): ExpenseFinancialBreakdown | null;
100
+ /** Set complete supplier facts; null explicitly clears facts on an unreviewed row. */
101
+ setFinancialBreakdown(value: ExpenseFinancialBreakdown | null): void;
102
+ /** Return exact retained purchase cost; legacy unknown tax facts return null. */
103
+ getCostBasis(): ExpenseCostBasis | null;
92
104
  /** True when the person who paid has been paid back. */
93
105
  isReimbursed(): boolean;
94
106
  /** True when this expense counts toward reviewed totals and drawdown. */
@@ -148,6 +160,8 @@ export declare class Expense extends SmrtObject {
148
160
  * commitment, then persist.
149
161
  */
150
162
  save(): Promise<this>;
163
+ /** Keep both directions of refund validation and the write on one executor. */
164
+ private saveValidated;
151
165
  private normalize;
152
166
  private validateShape;
153
167
  /**
@@ -181,6 +195,15 @@ export declare class Expense extends SmrtObject {
181
195
  * tenant's vendor. Checked when the vendor is set or changed.
182
196
  */
183
197
  private assertVendor;
198
+ /**
199
+ * Source edits and refund writes contend on the same source row. Acquire
200
+ * rows in deterministic order before either relationship check, and hold
201
+ * them through commit. SQLite's write transaction supplies serialization.
202
+ */
203
+ private lockRefundRows;
204
+ /** Refuse source corrections that invalidate any same-owner refund. */
205
+ private assertInboundRefunds;
206
+ private assertRefund;
184
207
  private transition;
185
208
  }
186
209
  //# sourceMappingURL=Expense.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Expense.d.ts","sourceRoot":"","sources":["../../src/models/Expense.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Expense.d.ts","sourceRoot":"","sources":["../../src/models/Expense.ts"],"names":[],"mappings":"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"}
@@ -3,13 +3,14 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-expenses",
6
- "packageVersion": "0.55.13",
6
+ "packageVersion": "0.55.14",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "b554ed5c408015ad83d60b3aa0d896d5c74b8ea726cba016c94d5f532073efd8",
11
- "packageJson": "794134591a26f601dc3832bb6571d06a727ca34bad1604b45d3a74b468e3376e",
12
- "agents": "874f50fb4947edfa4547fa4355767f94693481c548f6b087174b7ed4286c47f6",
10
+ "manifest": "888139b55083bf31ad437961d2efce070b7a4e3d11b541debd301c6435b26769",
11
+ "packageJson": "47fe2b1430433dd77e944389ef61aad22a6986c0f45865e8323f90780698146a",
12
+ "agents": "5eceb196cc83aae058b9289b23bc05a8a313b7f2f5975852cdf2924e73dcd772",
13
+ "moduleDoc:agents/financial-facts-test-matrix.md": "798c9f6c7c5694057ec438cf17252832a38164dd80a47f77c9cd4642b3e5cbd6",
13
14
  "moduleDoc:agents/expense-ui.md": "0bdea9e6c627f8cbab2a1f21ef4da3ab7f00409764a1ef48572dd40694fb6785"
14
15
  },
15
16
  "exports": [
@@ -344,6 +345,25 @@
344
345
  "required": false,
345
346
  "columnType": "TEXT"
346
347
  },
348
+ {
349
+ "name": "financialBreakdown",
350
+ "type": "text",
351
+ "required": false,
352
+ "columnType": "TEXT"
353
+ },
354
+ {
355
+ "name": "paidByName",
356
+ "type": "text",
357
+ "required": false,
358
+ "columnType": "TEXT"
359
+ },
360
+ {
361
+ "name": "refundOfId",
362
+ "type": "foreignKey",
363
+ "required": false,
364
+ "related": "Expense",
365
+ "columnType": "UUID"
366
+ },
347
367
  {
348
368
  "name": "incurredOn",
349
369
  "type": "text",
@@ -452,6 +472,13 @@
452
472
  }
453
473
  ],
454
474
  "relationships": [
475
+ {
476
+ "name": "refundOfId",
477
+ "type": "foreignKey",
478
+ "required": false,
479
+ "related": "Expense",
480
+ "columnType": "UUID"
481
+ },
455
482
  {
456
483
  "name": "recordedByProfileId",
457
484
  "type": "crossPackageRef",
@@ -503,7 +530,9 @@
503
530
  }
504
531
  ],
505
532
  "methods": [
533
+ "getCostBasis",
506
534
  "getCostObject",
535
+ "getFinancialBreakdown",
507
536
  "isCountable",
508
537
  "isReimbursed",
509
538
  "markDuplicate",
@@ -511,14 +540,23 @@
511
540
  "reject",
512
541
  "reopen",
513
542
  "review",
514
- "save"
543
+ "save",
544
+ "setFinancialBreakdown"
515
545
  ],
516
546
  "methodSignatures": [
547
+ {
548
+ "name": "getCostBasis",
549
+ "returns": "ExpenseCostBasis | null"
550
+ },
517
551
  {
518
552
  "name": "getCostObject",
519
553
  "async": true,
520
554
  "returns": "Promise<T | null>"
521
555
  },
556
+ {
557
+ "name": "getFinancialBreakdown",
558
+ "returns": "ExpenseFinancialBreakdown | null"
559
+ },
522
560
  {
523
561
  "name": "isCountable",
524
562
  "returns": "boolean"
@@ -571,6 +609,13 @@
571
609
  "name": "save",
572
610
  "async": true,
573
611
  "returns": "Promise<this>"
612
+ },
613
+ {
614
+ "name": "setFinancialBreakdown",
615
+ "params": [
616
+ "value: ExpenseFinancialBreakdown | null"
617
+ ],
618
+ "returns": "void"
574
619
  }
575
620
  ],
576
621
  "tenant": {
@@ -585,6 +630,13 @@
585
630
  ],
586
631
  "surfaces": [],
587
632
  "withheldSurfaces": [
633
+ {
634
+ "kind": "api",
635
+ "operation": "getCostBasis",
636
+ "code": "not-included",
637
+ "reason": "not listed in api.include",
638
+ "objectName": "@happyvertical/smrt-expenses:Expense"
639
+ },
588
640
  {
589
641
  "kind": "api",
590
642
  "operation": "getCostObject",
@@ -592,6 +644,13 @@
592
644
  "reason": "not listed in api.include",
593
645
  "objectName": "@happyvertical/smrt-expenses:Expense"
594
646
  },
647
+ {
648
+ "kind": "api",
649
+ "operation": "getFinancialBreakdown",
650
+ "code": "not-included",
651
+ "reason": "not listed in api.include",
652
+ "objectName": "@happyvertical/smrt-expenses:Expense"
653
+ },
595
654
  {
596
655
  "kind": "api",
597
656
  "operation": "isCountable",
@@ -647,6 +706,13 @@
647
706
  "code": "lifecycle-method",
648
707
  "reason": "`save` is a framework lifecycle method, not a distinct operation",
649
708
  "objectName": "@happyvertical/smrt-expenses:Expense"
709
+ },
710
+ {
711
+ "kind": "api",
712
+ "operation": "setFinancialBreakdown",
713
+ "code": "not-included",
714
+ "reason": "not listed in api.include",
715
+ "objectName": "@happyvertical/smrt-expenses:Expense"
650
716
  }
651
717
  ],
652
718
  "relationshipFeatures": [
@@ -783,15 +849,20 @@
783
849
  "surfaces": [],
784
850
  "prompts": [],
785
851
  "relationshipsV2": {
786
- "foreignKeyFields": 2,
852
+ "foreignKeyFields": 3,
787
853
  "crossPackageRefFields": 8,
788
854
  "junctionCollections": 1,
789
855
  "hierarchicalObjects": 0,
790
856
  "polymorphicAssociations": 0,
791
- "uuidColumns": 28
857
+ "uuidColumns": 30
792
858
  },
793
- "agentDoc": "# @happyvertical/smrt-expenses\n\nIncurred costs against a cost object, with deduplicated receipts, an explicit\nreview, reimbursement recording, and smrt-commerce commitment drawdown (#3289).\n\n## Models\n\n- **Expense** (`expenses`): `costObjectType` (qualified class name) +\n `costObjectId` (bare string), `category` (lowercase kebab-case; suggestions\n in `SUGGESTED_EXPENSE_CATEGORIES`), `amount` integer minor units + ISO 4217\n `currency`, `incurredOn` (`YYYY-MM-DD` text: a calendar date, not an\n instant), `recordedAt`, `recordedByProfileId`, `paidBy` (`company` |\n `person`) + `paidByProfileId`, `vendorId`, `commitmentId` /\n `commitmentLineId`, review fields, `reimbursable` / `reimbursedAt` /\n `reimbursementReference`.\n- **ExpenseReceipt** (`expense_receipts`): noun-specific asset join\n (`expenseId` → Expense, `assetId` → smrt-assets Asset) with uploader,\n `filename`, `mimeType`, `byteCount`, `contentSha256`. Collection extends\n `SmrtJunction` (left expense, right asset, ordered by `created_at`).\n\n## Invariants\n\n- **Review is method-only.** `review()`, `reject()`, `markDuplicate()`,\n `reopen()` read the persisted status, check the move, and authorize exactly\n one save through a module `WeakMap`. Every transition assigns every review\n field, and the token carries that complete resulting state, so a field set\n directly beside `review()`/`reject()` (e.g. an injected `duplicateOfId`)\n is refused. A `reviewed` expense never carries `duplicateOfId`. `save()` compares every review field\n (status, reviewer, time, note, `duplicateOfId`) with the stored row and\n refuses an unauthorized change (`EXPENSE_REVIEW_FIELDS_LOCKED`), including\n on a brand-new row. Review fields are also `readonly` for generated writes.\n- **Writes are pinned to the guarded row.** The guards read the row stored\n under `id`, but core's natural-key save adopts any same-owner row on\n `(tenant_id, slug, context)` and upserts it with no revision predicate;\n these models' slugs come from the id. So `pinNaturalKey()` lets a fresh\n (not `isPersisted`) instance only INSERT (`requireInsertOnSave()`): naming\n an existing row's id is `EXPENSE_IDENTITY_CONFLICT`, and a row appearing\n mid-save fails the INSERT instead of being adopted. An existing row changes\n only through a loaded instance (revision compare-and-swap) that keeps its\n slug/context. Never `create({ id: <existing> })` or `getOrUpsert()` these\n models; load and save instead.\n- **Reviewed money is frozen.** While the stored status is `reviewed`,\n `LOCKED_WHEN_REVIEWED` fields (money, currency, both dates, cost object,\n vendor, commitment, payer) cannot change; `reopen()` first. `recordedAt` is\n compared as an instant, since drivers return it as a `Date` or a string.\n- **Drawdown counts once.** `ExpenseCollection.commitmentPosition()` sums only\n `reviewed` rows with no `duplicateOfId`, in the commitment's currency, deduped\n by id. Committed is the contract's `totalAmount` (or the line's `amount`).\n- **Currencies never mix.** A commitment match must share the currency and\n tenant (`EXPENSE_COMMITMENT_MISMATCH`), checked when the match, line or\n currency is set or changed — not on every save, so a later commitment edit\n never blocks e.g. `markReimbursed()`. `totalsByCurrency()` keys by currency.\n Rows left in another currency by a later commitment change land in\n `otherCurrencies`, never in `drawn`.\n- **Duplicate receipts.** Same `(expense, sha256)` is refused by a pre-insert\n read plus the unique index `expense_receipts_expense_sha256_key`\n `(expense_id, content_sha256)`; a unique violation is rethrown as\n `DuplicateReceiptError`, classified from the error itself (violated key\n name, or `details.fieldName` columns on core's typed error) because on\n PostgreSQL the violation aborts a caller's transaction and nothing can be\n re-read; only an error naming no key falls back to a re-read. The index omits `tenant_id` deliberately: the\n expense already fixes the tenant, and a nullable leading `tenant_id` would\n leave NULL-tenant (global) receipts unenforced. Same hash on different\n expenses is allowed; `findDuplicateReceipts()` reports it (facet on\n `contentSha256`, count > 1). Receipt `tenantId` (derived from the\n expense on insert), `expenseId`, `assetId` and `contentSha256` are\n immutable. The inherited `SmrtJunction.attach()` and\n `setLinks()` are overridden to throw (`setLinks()` would delete every\n receipt, then fail to re-attach without hashes); `attachReceipt()` adds and\n `detach()` removes a link, keeping the asset. Because `ExpenseReceipt`\n overrides `save()`, `hasBaseJunctionLifecycle()` is false and no batch\n junction path can write a hashless row around it.\n- **Vendor ownership.** `vendorId` is a `crossPackageRef` (no FK, no\n validation), so `assertVendor()` checks the vendor is visible in and owned\n by the expense's tenant whenever it is set or changed\n (`EXPENSE_VENDOR_MISMATCH`).\n- **Tenant never moves.** Expense and receipt tenants are fixed once\n stored, compared as `effectiveTenant()` (the field, or the active context\n the `beforeSave` interceptor would fill an unset one from; unset is falsy,\n so `''` counts). `normalizeTenantId()` stores `''` as NULL (global), never\n as a pseudo-tenant, matching core/tenancy's falsy \"unset\". Otherwise a\n global (NULL-tenant) row saved inside `withTenant()` would be silently\n populated and split from its global expense or receipts.\n- **Tenancy.** Optional (`@TenantScoped({ mode: 'optional' })`, nullable\n `tenantId`), like smrt-commerce. A receipt takes its expense's tenant; its\n expense and asset must be visible in that tenant. A commitment must belong\n to the expense's tenant; before `super.save()` that is\n `tenantId ?? getTenantId()`, because the interceptor has not populated it.\n Consumers make it required with a qualified\n `registerTenantScopedClass(…, { mode: 'required' })`.\n- **Closed generated surface.** Models AND collections declare\n `api: { include: [] }`, `mcp: { include: [] }`, `cli: false`. Collection\n classes are registered too: an undecorated collection publishes its CRUD and\n public methods as MCP tools. Core has no consumer-side switch to reopen a\n package model's surface; consumers write their own routes.\n- **Cost object is not dereferenced on save.** `getCostObject()` resolves it\n through the registry and returns `null` for a foreign-tenant row. One cost\n object per expense (columns, not a `SmrtPolymorphicAssociation` join) so a\n cost can never roll up into two jobs.\n\n## Cross-package references\n\n- `vendorId` → `@crossPackageRef('@happyvertical/smrt-commerce:Vendor')`\n- `commitmentId` → `…smrt-commerce:Contract` (usually a `PurchaseOrder`)\n- `commitmentLineId` → `…smrt-commerce:ContractLineItem`\n- `assetId` → `…smrt-assets:Asset`\n- profile ids → `…smrt-profiles:Profile` (no dependency; references only)\n- `expenseId`, `duplicateOfId` → `@foreignKey` within the package\n\n## Scope\n\nAllowance (a planned amount) is not an expense kind; it belongs with\nbudget/commitment records. Labour belongs to `@happyvertical/smrt-timesheets`\n(#3288). No OCR, no accounting-provider posting.\n\n## Validation\n\n```bash\npnpm --filter @happyvertical/smrt-expenses test\npnpm --filter @happyvertical/smrt-expenses typecheck\npnpm --filter @happyvertical/smrt-expenses test:postgres\n```\n\n`src/__tests__/helpers/expense-suite.ts` is the shared behaviour suite: the\nSQLite file and the PostgreSQL lane (`*.optional.test.ts`, schema from\n`migrateSmrtSchemas` plus live parity) both run it. Use UUID tenant and\nprofile ids in tests: PostgreSQL stores them as `uuid`.\n\n## UI\n\n`src/svelte/` owns native expense entry and review presentation. See\n[the UI contract](agents/expense-ui.md) for DTOs, native payloads and recovery.\nDraft amounts are lossless currency-unit text; model/review amounts remain integer\nminor units. Receipt upload is composed from Assets, never reimplemented here.\n`test` includes backend and UI SSR; `test:e2e` proves native browser submissions.\n",
859
+ "agentDoc": "# @happyvertical/smrt-expenses\n\nIncurred costs against a cost object, with deduplicated receipts, an explicit\nreview, reimbursement recording, and smrt-commerce commitment drawdown (#3289).\n\n## Models\n\n- **Expense** (`expenses`): `costObjectType` (qualified class name) +\n `costObjectId` (bare string), `category` (lowercase kebab-case; suggestions\n in `SUGGESTED_EXPENSE_CATEGORIES`), `amount` integer minor units + ISO 4217\n `currency`, `incurredOn` (`YYYY-MM-DD` text: a calendar date, not an\n instant), `recordedAt`, `recordedByProfileId`, `paidBy` (`company` |\n `person` | `third-party`) + `paidByProfileId` / `paidByName`, `vendorId`, `commitmentId` /\n `commitmentLineId`, review fields, `reimbursable` / `reimbursedAt` /\n `reimbursementReference`.\n- **ExpenseReceipt** (`expense_receipts`): noun-specific asset join\n (`expenseId` → Expense, `assetId` → smrt-assets Asset) with uploader,\n `filename`, `mimeType`, `byteCount`, `contentSha256`. Collection extends\n `SmrtJunction` (left expense, right asset, ordered by `created_at`).\n\n## Invariants\n\n- **Review is method-only.** `review()`, `reject()`, `markDuplicate()`,\n `reopen()` read the persisted status, check the move, and authorize exactly\n one save through a module `WeakMap`. Every transition assigns every review\n field, and the token carries that complete resulting state, so a field set\n directly beside `review()`/`reject()` (e.g. an injected `duplicateOfId`)\n is refused. A `reviewed` expense never carries `duplicateOfId`. `save()` compares every review field\n (status, reviewer, time, note, `duplicateOfId`) with the stored row and\n refuses an unauthorized change (`EXPENSE_REVIEW_FIELDS_LOCKED`), including\n on a brand-new row. Review fields are also `readonly` for generated writes.\n- **Writes are pinned to the guarded row.** The guards read the row stored\n under `id`, but core's natural-key save adopts any same-owner row on\n `(tenant_id, slug, context)` and upserts it with no revision predicate;\n these models' slugs come from the id. So `pinNaturalKey()` lets a fresh\n (not `isPersisted`) instance only INSERT (`requireInsertOnSave()`): naming\n an existing row's id is `EXPENSE_IDENTITY_CONFLICT`, and a row appearing\n mid-save fails the INSERT instead of being adopted. An existing row changes\n only through a loaded instance (revision compare-and-swap) that keeps its\n slug/context. Never `create({ id: <existing> })` or `getOrUpsert()` these\n models; load and save instead.\n- **Reviewed money is frozen.** While the stored status is `reviewed`,\n `LOCKED_WHEN_REVIEWED` fields (money, currency, both dates, cost object,\n vendor, commitment, payer) cannot change; `reopen()` first. `recordedAt` is\n compared as an instant, since drivers return it as a `Date` or a string.\n- **Drawdown counts once.** `ExpenseCollection.commitmentPosition()` sums only\n `reviewed` rows with no `duplicateOfId`, in the commitment's currency, deduped\n by id. Committed is the contract's `totalAmount` (or the line's `amount`).\n- **Currencies never mix.** A commitment match must share the currency and\n tenant (`EXPENSE_COMMITMENT_MISMATCH`), checked when the match, line or\n currency is set or changed — not on every save, so a later commitment edit\n never blocks e.g. `markReimbursed()`. `totalsByCurrency()` keys by currency.\n Rows left in another currency by a later commitment change land in\n `otherCurrencies`, never in `drawn`.\n- **Duplicate receipts.** Same `(expense, sha256)` is refused by a pre-insert\n read plus the unique index `expense_receipts_expense_sha256_key`\n `(expense_id, content_sha256)`; a unique violation is rethrown as\n `DuplicateReceiptError`, classified from the error itself (violated key\n name, or `details.fieldName` columns on core's typed error) because on\n PostgreSQL the violation aborts a caller's transaction and nothing can be\n re-read; only an error naming no key falls back to a re-read. The index omits `tenant_id` deliberately: the\n expense already fixes the tenant, and a nullable leading `tenant_id` would\n leave NULL-tenant (global) receipts unenforced. Same hash on different\n expenses is allowed; `findDuplicateReceipts()` reports it (facet on\n `contentSha256`, count > 1). Receipt `tenantId` (derived from the\n expense on insert), `expenseId`, `assetId` and `contentSha256` are\n immutable. The inherited `SmrtJunction.attach()` and\n `setLinks()` are overridden to throw (`setLinks()` would delete every\n receipt, then fail to re-attach without hashes); `attachReceipt()` adds and\n `detach()` removes a link, keeping the asset. Because `ExpenseReceipt`\n overrides `save()`, `hasBaseJunctionLifecycle()` is false and no batch\n junction path can write a hashless row around it.\n- **Vendor ownership.** `vendorId` is a `crossPackageRef` (no FK, no\n validation), so `assertVendor()` checks the vendor is visible in and owned\n by the expense's tenant whenever it is set or changed\n (`EXPENSE_VENDOR_MISMATCH`).\n- **Tenant never moves.** Expense and receipt tenants are fixed once\n stored, compared as `effectiveTenant()` (the field, or the active context\n the `beforeSave` interceptor would fill an unset one from; unset is falsy,\n so `''` counts). `normalizeTenantId()` stores `''` as NULL (global), never\n as a pseudo-tenant, matching core/tenancy's falsy \"unset\". Otherwise a\n global (NULL-tenant) row saved inside `withTenant()` would be silently\n populated and split from its global expense or receipts.\n- **Tenancy.** Optional (`@TenantScoped({ mode: 'optional' })`, nullable\n `tenantId`), like smrt-commerce. A receipt takes its expense's tenant; its\n expense and asset must be visible in that tenant. A commitment must belong\n to the expense's tenant; before `super.save()` that is\n `tenantId ?? getTenantId()`, because the interceptor has not populated it.\n Consumers make it required with a qualified\n `registerTenantScopedClass(…, { mode: 'required' })`.\n- **Closed generated surface.** Models AND collections declare\n `api: { include: [] }`, `mcp: { include: [] }`, `cli: false`. Collection\n classes are registered too: an undecorated collection publishes its CRUD and\n public methods as MCP tools. Core has no consumer-side switch to reopen a\n package model's surface; consumers write their own routes.\n- **Cost object is not dereferenced on save.** `getCostObject()` resolves it\n through the registry and returns `null` for a foreign-tenant row. One cost\n object per expense (columns, not a `SmrtPolymorphicAssociation` join) so a\n cost can never roll up into two jobs.\n\n## Optional financial facts\n\n`financialBreakdown` is guarded JSON text (`get/setFinancialBreakdown`), absent\nfor legacy rows. Exact `getCostBasis()` returns null without facts. Complete facts\nreconcile subtotal + signed tax components to amount/currency; recoverable amounts\nare explicit. No tax law or customer pricing is inferred. `third-party` payer\nrequires `paidByName` and is not reimbursable. Negative amounts require `refundOfId`\n(same-owner/currency positive source, checked through the bound executor).\nSource edits also check inbound same-owner refunds: a referenced source stays\npositive, non-refund, and in their currency. `save()` encloses guards and write\nin the public `withTransaction()` boundary; PostgreSQL locks its row and proposed\nsource in id order before either refund check, while SQLite serializes writes.\nThe inbound projected lookup uses the existing `refund_of_id` index and an\nexplicit owner filter. Keep caller transaction affinity and per-row revision\nchecks; neither an unlocked lookup nor revision CAS alone protects this pair.\nAll three fields participate in the reviewed-money freeze. See the README and\n[financial facts test matrix](agents/financial-facts-test-matrix.md).\n\nLinking a new refund (or changing its source) also claims the source revision in\nthat same transaction through `claimRevision(source.updated_at)`. This makes a\nsource edit under an older PostgreSQL `REPEATABLE READ` snapshot fail with a\nserialization error instead of missing the new refund. Reload a source after\nlinking a refund before editing/reviewing it; on a serialization failure, retry\nthe entire caller transaction with fresh instances. Failed outer transactions\nroll back both the refund and the source revision claim. Existing-link saves do\nnot advance the source revision.\n\n## Cross-package references\n\n- `vendorId` → `@crossPackageRef('@happyvertical/smrt-commerce:Vendor')`\n- `commitmentId` → `…smrt-commerce:Contract` (usually a `PurchaseOrder`)\n- `commitmentLineId` → `…smrt-commerce:ContractLineItem`\n- `assetId` → `…smrt-assets:Asset`\n- profile ids → `…smrt-profiles:Profile` (no dependency; references only)\n- `expenseId`, `duplicateOfId` → `@foreignKey` within the package\n\n## Scope\n\nAllowance (a planned amount) is not an expense kind; it belongs with\nbudget/commitment records. Labour belongs to `@happyvertical/smrt-timesheets`\n(#3288). No OCR, no accounting-provider posting.\n\n## Validation\n\n```bash\npnpm --filter @happyvertical/smrt-expenses test\npnpm --filter @happyvertical/smrt-expenses typecheck\npnpm --filter @happyvertical/smrt-expenses test:postgres\n```\n\n`src/__tests__/helpers/expense-suite.ts` is the shared behaviour suite: the\nSQLite file and the PostgreSQL lane (`*.optional.test.ts`, schema from\n`migrateSmrtSchemas` plus live parity) both run it. Use UUID tenant and\nprofile ids in tests: PostgreSQL stores them as `uuid`.\n\n## UI\n\n`src/svelte/` owns native expense entry and review presentation. See\n[the UI contract](agents/expense-ui.md) for DTOs, native payloads and recovery.\nDraft amounts are lossless currency-unit text; model/review amounts remain integer\nminor units. Receipt upload is composed from Assets, never reimplemented here.\n`test` includes backend and UI SSR; `test:e2e` proves native browser submissions.\n",
794
860
  "moduleDocs": [
861
+ {
862
+ "path": "agents/financial-facts-test-matrix.md",
863
+ "module": "financial-facts-test-matrix",
864
+ "content": "# Expense financial facts test design (#3792)\n\nRisk: high — persisted financial mutation, tenancy, reviewed immutability, and\nschema migration. Synthetic data only. Scope is source facts, not tax law or\nsettlement. Existing generated mutation surfaces remain closed.\n\nAll integration rows run in the shared expense behavior suite on SQLite and\nPostgreSQL, using the caller-bound database executor and active tenant. Commands:\n`pnpm --filter @happyvertical/smrt-expenses test` (S),\n`pnpm --filter @happyvertical/smrt-expenses test:postgres` (P).\n\n| Behavior / reachable trigger | Positive | Negative / failure | Actor / context | Executor / transaction | Runtime / dialect | External edge | Level / command |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n| Optional source facts through create/save/load | Legacy amount roundtrip has no breakdown or inferred basis; explicit facts survive reload | Unknown keys, malformed JSON/types/currency, mismatched total/sign, duplicate component, overflow rejected | Consumer with same active tenant | Bound collection database, no multirow mutation | Node SQLite/PostgreSQL | Optional missing facts; unknown values fail closed | Integration S/P |\n| Exact basis through getCostBasis | Partial recovery and negative refund exact minor units | Unsafe numbers and inconsistent recovery rejected | Consumer reading source facts | Pure synchronous calculation; N/A transaction (no writes) | Node plus persisted roundtrip both dialects | Structural typed result usable without dependency | Unit/integration S/P |\n| Refund zero-tax recovery boundary | Zero charged and zero recovered accepted | Negative recovery against zero tax rejected by setter and save; persisted facts unchanged | Same tenant author | Bound collection database; retry after correction | SQLite/PostgreSQL | Exact magnitude bound includes zero | Regression integration S/P |\n| Named third-party payer save | Name persists without employee reimbursement | Missing name, incompatible profile/name, reimbursable rejected; company/person unchanged | Same tenant expense author | Bound database | SQLite/PostgreSQL | New enum explicit, unknown rejected | Integration S/P |\n| Refund source save | Negative refund references positive same-owner/currency source | Self, missing, cross-tenant/global, currency mismatch, refund-of-refund rejected | Active tenant × same/foreign/global resource | Lookup uses same database executor; transaction rollback and retry | SQLite/PostgreSQL | No settlement or cumulative refund entitlement inferred | Integration S/P |\n| Reviewed facts save/reopen/review | Reopen, correct, review permitted | Direct financial/payer/refund mutation and mutation alongside reopen rejected; failed save preserves persisted row | Consumer review method; existing authorization contract unchanged | Revision-guarded single-row save; transaction rollback | SQLite/PostgreSQL | Existing closed generated surface retained | Integration S/P |\n| Reviewed refund roll-up | Signed gross totals and commitment drawdown count source/refund once, retained basis does not replace legacy amount | Existing cross-currency/tenant commitment denials remain active | Same tenant reviewer | Bound collections and revision-guarded saves | SQLite/PostgreSQL | Existing aggregate semantics preserved | Integration S/P |\n| Additive schema migration | Existing row retains amount and absent financial facts; repeat migration no-op | Live parity and column types asserted | Schema operator, no end-user authorization | Public dbMigrate migration path | SQLite/PostgreSQL | Existing deployed schema upgraded | Migration S/P |\n\nAdditional gates: package typecheck, build, browser harness; root combined release\nvalidation and exact-head review belong to the coordinator. Original additive\nfeature rows do not require a bug-fix comparison; the zero-tax correction has\nexplicit failing-base evidence below. No network provider is\nintroduced: upstream provider failure is N/A. Refund lookup failure is tested.\n\n## Executed release evidence\n\nExecuted on Node 26.10.0 / pnpm 11.25.0, with SQLite and PostgreSQL. All\nbehavior rows above run in the shared suite; PostgreSQL additionally checks\nnative column types and live parity. Both migration fixtures start from the\nprevious expense schema, preserve a seeded legacy row, exercise the migrated\nrefund FK's RESTRICT behavior, and prove repeat migration is a no-op.\n\n- `test`: 61 backend tests and 15 UI tests passed. The optional PostgreSQL file\n is deliberately skipped here and executed by its dedicated command.\n- `test:postgres`: 55 passed; no skipped tests.\n- `test:e2e`: 11 passed using the supported system Chromium, including native\n submissions without JavaScript.\n- Package build, typecheck (including Svelte), and scoped Biome checks passed.\n- SQLite migration requires the generic new nullable FK column fix in #3795;\n the initial executable failure and passing dependency-head run are retained.\n\nFull output is retained outside the checkout under\n`/home/will/.local/state/happyvertical/tradeschief-277-release1/expenses/`:\n`test-final.log`, `postgres-final-with-core.log`, `e2e.log`,\n`package-build.log`, `typecheck-delivery.log`, `lint-final.log`, and\n`core-build.log`. Initial diagnostic runs remain there for failure provenance.\nThe redundant whole-workspace build was stopped to reduce memory contention;\nthe coordinator owns the final combined root gates and exact-head review.\n\n\n## Zero-tax refund correction\n\nThe added regression fails against feature commit `08a791a93`: a refund with\nsubtotal/total -100, tax 0, and recoverable tax -1 was accepted. Comparing exact\nBigInt magnitudes fixes the bound without changing the sign requirements.\nThe shared regression also rejects raw-save bypass, verifies stored facts are\nunchanged after failure, and permits retry with zero recovery. Evidence is in\n`zero-tax-base.log` (expected failure), `zero-tax-test.log`,\n`zero-tax-postgres.log`, `zero-tax-typecheck.log`, and `zero-tax-build.log`\nunder the evidence directory above.\nFinal correction validation: 62 backend + 15 UI tests and 56 PostgreSQL tests\npassed, including both migration lanes. The unchanged browser contract retains\nthe 11-pass evidence above.\n\n\n## Inbound refund source invariant — review thread 4243223180\n\nRisk remains high (financial relationship mutation). Baseline is release\n`d372e2df68f1000bbfccb9dbe88afcd7d78ece9b`. Execute before/after evidence for\nthe new regressions; the existing per-row revision guard alone cannot protect a\nsource/refund pair from concurrent edits.\n\n| Behavior / reachable trigger | Positive | Negative / failure | Actor / context | Executor / transaction | Runtime / dialect | External edge | Level / command |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n| Source save with inbound refunds | Unrelated edits and positive same-currency corrections remain valid; unreferenced sources remain editable | Zero amount, changed currency, or conversion to refund refused; source and reviewed refund unchanged | Same owner, source unreviewed or reopened, refund reviewed | Bound transaction; existing revision CAS retained | SQLite/PostgreSQL | Refund source eligibility preserved in both directions | Shared integration S/P |\n| Inbound lookup ownership | NULL owner and tenant-owned references enforced outside context too | Malformed foreign-owner inbound fixture must not block another tenant; ordinary cross-tenant creation remains refused | Active tenant / no context × own / foreign / global | Public projected list with owner predicate, existing refund FK index | SQLite/PostgreSQL | No cross-tenant lookup bypass or existence disclosure | Shared integration S/P |\n| Caller transaction affinity | Guard sees uncommitted inbound refund; valid retry after rollback | Invalid source edit rolls back sibling refund mutation; original rows remain valid | Same tenant author | Existing public nested transaction and withDatabase APIs | SQLite/PostgreSQL | No independent root transaction or partial commit | Shared integration S/P |\n| Source/refund concurrent save | Winner preserves eligible persisted relationship | Refund-first blocks source edit; source-first makes later refund fail on current source | Same tenant, distinct real connections | Ordered PostgreSQL row locks held from validation through commit; SQLite write transaction serialization | PostgreSQL interleaving plus shared SQLite atomicity | No blanket source freeze or settlement limits | Real-PG integration P |\n| Stale repeatable-read source snapshot | Fresh same-currency positive edit succeeds | Snapshot pinned before independent refund commit; stale currency edit aborts serialization; fresh invalid edit rejects | Same tenant | Public source revision claim in refund transaction | PostgreSQL REPEATABLE READ | No missed committed refund under stale snapshot | Real-PG integration P |\n\nThe source revision advances atomically on a new/changed refund link through the\npublic revision API, including rollback with a caller transaction. Source\ninstances loaded before linking must be reloaded for subsequent valid edits.\n\nReproduction evidence in the external `inbound-refunds/` directory: baseline\nSQLite fails all four new shared regressions, baseline PostgreSQL fails those\nfour plus both writer-order cases. Row locks without a source revision claim\nalso fail the repeatable-read regression (`base-rr.log`). The corrected focused\nruns pass four SQLite and seven PostgreSQL cases (`fixed-sqlite.log`,\n`fixed-postgres.log`). Final committed-head package build, full test, PostgreSQL,\ntypecheck, and browser commands are recorded with HEAD and exit status in\n`validation-records.json` and their named logs in that same evidence directory.\n"
865
+ },
795
866
  {
796
867
  "path": "agents/expense-ui.md",
797
868
  "module": "expense-ui",
package/dist/types.d.ts CHANGED
@@ -18,9 +18,10 @@ export type ExpenseReviewStatus = 'unreviewed' | 'reviewed' | 'rejected';
18
18
  export declare const EXPENSE_REVIEW_STATUSES: readonly ExpenseReviewStatus[];
19
19
  /**
20
20
  * Who paid for an expense: the `company` (card, account, vendor terms) or a
21
- * named `person` out of pocket, who may then be owed a reimbursement.
21
+ * named `person` out of pocket, or a named `third-party`. Only a person may
22
+ * be owed an employee reimbursement.
22
23
  */
23
- export type ExpensePayer = 'company' | 'person';
24
+ export type ExpensePayer = 'company' | 'person' | 'third-party';
24
25
  /** Every {@link ExpensePayer}. */
25
26
  export declare const EXPENSE_PAYERS: readonly ExpensePayer[];
26
27
  /**
@@ -30,6 +31,43 @@ export declare const EXPENSE_PAYERS: readonly ExpensePayer[];
30
31
  * common additions `equipment` and `travel`.
31
32
  */
32
33
  export declare const SUGGESTED_EXPENSE_CATEGORIES: readonly string[];
34
+ /** Explicit supplier tax in signed integer minor units; no jurisdiction defaults. */
35
+ export interface ExpenseTaxComponent {
36
+ /** Unique caller-defined lower-case kebab-case identifier. */
37
+ code: string;
38
+ /** Tax charged by the supplier, negative for a refund. */
39
+ amount: number;
40
+ /** Explicit recoverable portion, same sign and no larger than amount. */
41
+ recoverableAmount: number;
42
+ }
43
+ /** Complete optional purchase facts; subtotal plus taxes must equal total. */
44
+ export interface ExpenseFinancialBreakdown {
45
+ /** Exact ISO currency of the expense. */
46
+ currency: string;
47
+ /** Supplier amount before the explicit taxes. */
48
+ subtotal: number;
49
+ /** Explicit components; an empty array asserts no tax. */
50
+ taxes: ExpenseTaxComponent[];
51
+ /** Supplier total, exactly equal to Expense.amount. */
52
+ total: number;
53
+ }
54
+ /** Exact retained basis; never implies customer markup or sales tax treatment. */
55
+ export interface ExpenseCostBasis {
56
+ /** Currency shared by every value. */
57
+ currency: string;
58
+ /** Supplier subtotal. */
59
+ subtotal: number;
60
+ /** Sum of supplier tax components. */
61
+ taxTotal: number;
62
+ /** Explicit recoverable supplier tax. */
63
+ recoverableTax: number;
64
+ /** Supplier tax retained as cost. */
65
+ nonRecoverableTax: number;
66
+ /** Supplier gross total. */
67
+ total: number;
68
+ /** Subtotal plus non-recoverable tax. */
69
+ costBasis: number;
70
+ }
33
71
  /** Constructor options for {@link Expense}. */
34
72
  export interface ExpenseOptions extends SmrtObjectOptions {
35
73
  tenantId?: string | null;
@@ -39,6 +77,12 @@ export interface ExpenseOptions extends SmrtObjectOptions {
39
77
  description?: string;
40
78
  amount?: number;
41
79
  currency?: string;
80
+ /** Complete supplier facts as JSON text; absent means unknown. */
81
+ financialBreakdown?: string | null;
82
+ /** Required only for third-party payers. */
83
+ paidByName?: string | null;
84
+ /** Same-tenant/currency positive source required for a negative refund. */
85
+ refundOfId?: string | null;
42
86
  incurredOn?: string;
43
87
  recordedAt?: Date | null;
44
88
  recordedByProfileId?: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,UAAU,GAAG,UAAU,CAAC;AAEzE,6DAA6D;AAC7D,eAAO,MAAM,uBAAuB,EAAE,SAAS,mBAAmB,EACX,CAAC;AAExD;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEhD,kCAAkC;AAClC,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAGhD,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,EAAE,SAAS,MAAM,EAQxD,CAAC;AAEH,+CAA+C;AAC/C,MAAM,WAAW,cAAe,SAAQ,iBAAiB;IACvD,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3B,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,sDAAsD;AACtD,MAAM,WAAW,qBAAsB,SAAQ,iBAAiB;IAC9D,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wCAAwC;AACxC,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,iBAAiB,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,EAAE,CAAC,EAAE,IAAI,CAAC;CACX;AAED,wCAAwC;AACxC,MAAM,WAAW,kBAAkB;IACjC,kCAAkC;IAClC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,EAAE,CAAC,EAAE,IAAI,CAAC;CACX;AAED,+CAA+C;AAC/C,MAAM,WAAW,yBAAyB;IACxC,kCAAkC;IAClC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uEAAuE;IACvE,aAAa,EAAE,MAAM,CAAC;IACtB,oCAAoC;IACpC,EAAE,CAAC,EAAE,IAAI,CAAC;CACX;AAED,wCAAwC;AACxC,MAAM,WAAW,kBAAkB;IACjC,qDAAqD;IACrD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,gDAAgD;AAChD,MAAM,WAAW,mBAAmB;IAClC,sDAAsD;IACtD,EAAE,CAAC,EAAE,IAAI,CAAC;IACV,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IACjC,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAC;IACtB,6BAA6B;IAC7B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAE3D,0DAA0D;AAC1D,MAAM,WAAW,oBAAoB;IACnC,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC;;;OAGG;IACH,cAAc,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACjD;AAED,2DAA2D;AAC3D,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,YAAY,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,+CAA+C;IAC/C,QAAQ,EAAE;QACR,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF;;;;OAIG;IACH,eAAe,EAAE,cAAc,CAAC;CACjC;AAED,wDAAwD;AACxD,MAAM,WAAW,qBAAqB;IACpC,0BAA0B;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,2CAA2C;IAC3C,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,sEAAsE;AACtE,MAAM,WAAW,4BAA4B;IAC3C,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,iDAAiD;AACjD,MAAM,MAAM,gBAAgB,GACxB,iBAAiB,GACjB,2BAA2B,GAC3B,8BAA8B,GAC9B,6BAA6B,GAC7B,yBAAyB,GACzB,2BAA2B,GAC3B,2BAA2B,GAC3B,yBAAyB,CAAC;AAE9B;;GAEG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;gBAEpB,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAK5E;AAED;;;;GAIG;AACH,qBAAa,qBAAsB,SAAQ,YAAY;IACrD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;gBAG7B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,YAAY;CAYzB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -4,7 +4,11 @@ var EXPENSE_REVIEW_STATUSES = Object.freeze([
4
4
  "reviewed",
5
5
  "rejected"
6
6
  ]);
7
- var EXPENSE_PAYERS = Object.freeze(["company", "person"]);
7
+ var EXPENSE_PAYERS = Object.freeze([
8
+ "company",
9
+ "person",
10
+ "third-party"
11
+ ]);
8
12
  var SUGGESTED_EXPENSE_CATEGORIES = Object.freeze([
9
13
  "material",
10
14
  "consumable",