@dainamite/billing 0.18.0 → 0.19.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @dainamite/billing
2
2
 
3
+ ## 0.19.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8834b8b: Require Open Mercato `0.6.7`. Peer ranges move from `^0.6.0` to `^0.6.7` for
8
+ `@open-mercato/core`, `shared`, `ui`, `events` and `queue`, and the MikroORM
9
+ peers from `^7.0.0` to `^7.1.5` (7.1 split the SQL layer into
10
+ `@mikro-orm/sql`, so a shared 7.1 instance is required).
11
+
12
+ No package source changes — this only narrows the supported framework range.
13
+ Consumers on Open Mercato `0.6.0`–`0.6.6` must upgrade before taking these
14
+ versions.
15
+
16
+ Note for consumers running commands with a system identity (`ctx.auth: null`):
17
+ `0.6.7` hardened `ensureTenantScope` / `ensureOrganizationScope`. Those calls
18
+ are still no-ops with `auth: null`, but an unscoped organization command now
19
+ logs a warning and will throw `403` if `OM_ENFORCE_ORG_SCOPE_STRICT=true`.
20
+
3
21
  ## 0.18.0
4
22
 
5
23
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dainamite/billing",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Dainamite Billing — lightweight recurring-billing engine for Open Mercato (Bill Runs, one-time / recurring / usage items, draft invoices in core/sales with human approval).",
5
5
  "type": "module",
6
6
  "main": "./dist/modules/billing/index.js",
@@ -117,14 +117,14 @@
117
117
  }
118
118
  },
119
119
  "peerDependencies": {
120
- "@mikro-orm/core": "^7.0.0",
121
- "@mikro-orm/decorators": "^7.0.0",
122
- "@mikro-orm/postgresql": "^7.0.0",
123
- "@open-mercato/core": "^0.6.0",
124
- "@open-mercato/events": "^0.6.0",
125
- "@open-mercato/queue": "^0.6.0",
126
- "@open-mercato/shared": "^0.6.0",
127
- "@open-mercato/ui": "^0.6.0",
120
+ "@mikro-orm/core": "^7.1.5",
121
+ "@mikro-orm/decorators": "^7.1.5",
122
+ "@mikro-orm/postgresql": "^7.1.5",
123
+ "@open-mercato/core": "^0.6.7",
124
+ "@open-mercato/events": "^0.6.7",
125
+ "@open-mercato/queue": "^0.6.7",
126
+ "@open-mercato/shared": "^0.6.7",
127
+ "@open-mercato/ui": "^0.6.7",
128
128
  "@tanstack/react-table": "^8.0.0 || ^9.0.0",
129
129
  "awilix": "^12.0.0",
130
130
  "lucide-react": "^0.400.0",
@@ -106,7 +106,7 @@ function makeLine(invoice: FakeInvoice, overrides: Partial<FakeLine> = {}): Fake
106
106
  }
107
107
 
108
108
  type Env = {
109
- ctx: { container: { resolve: jest.MockedFunction<(name: string) => unknown> }; auth: { sub?: string } | null }
109
+ ctx: { container: { resolve: jest.MockedFunction<(name: string) => unknown> }; auth: { sub?: string; tenantId?: string; orgId?: string } | null }
110
110
  em: {
111
111
  findOne: jest.MockedFunction<(entity: unknown, where: unknown) => Promise<unknown>>
112
112
  find: jest.MockedFunction<(entity: unknown, where: unknown) => Promise<unknown[]>>
@@ -131,7 +131,7 @@ function createEnv(
131
131
  statusEntryPosted?: string | null
132
132
  initialLines?: FakeLine[]
133
133
  invoiceFinalLines?: FakeLine[]
134
- auth?: { sub?: string } | null
134
+ auth?: { sub?: string; tenantId?: string; orgId?: string } | null
135
135
  testInvoiceRows?: Array<{ id: string }>
136
136
  } = {},
137
137
  ): Env {
@@ -199,7 +199,7 @@ function createEnv(
199
199
  return {
200
200
  ctx: {
201
201
  container,
202
- auth: options.auth ?? { sub: '99999999-9999-4999-8999-999999999999' },
202
+ auth: options.auth ?? { sub: '99999999-9999-4999-8999-999999999999', tenantId: TENANT, orgId: ORG },
203
203
  },
204
204
  em,
205
205
  persistedEntities,
@@ -23,7 +23,7 @@ function makeCtx(em: MockEm, gateway: unknown) {
23
23
  flushOrmEntityChanges: jest.fn(async () => undefined),
24
24
  }
25
25
  return {
26
- auth: { sub: '99999999-9999-4999-8999-999999999999' },
26
+ auth: { sub: '99999999-9999-4999-8999-999999999999', tenantId: TENANT, orgId: ORG },
27
27
  container: {
28
28
  resolve: jest.fn((name: string) => {
29
29
  if (name === 'em') return em
@@ -17,13 +17,15 @@ function prepaidAccount(overrides: Record<string, unknown> = {}) {
17
17
  }
18
18
  }
19
19
 
20
- function makeCtx(em: MockEm) {
20
+ type CtxAuth = { sub?: string; tenantId?: string; orgId?: string } | null
21
+
22
+ function makeCtx(em: MockEm, auth: CtxAuth = { sub: '99999999-9999-4999-8999-999999999999', tenantId: TENANT, orgId: ORG }) {
21
23
  const dataEngine = {
22
24
  markOrmEntityChange: jest.fn(),
23
25
  flushOrmEntityChanges: jest.fn(async () => undefined),
24
26
  }
25
27
  return {
26
- auth: { sub: '99999999-9999-4999-8999-999999999999' },
28
+ auth,
27
29
  container: {
28
30
  resolve: jest.fn((name: string) => {
29
31
  if (name === 'em') return em
@@ -54,6 +56,27 @@ describe('adjustTransactionCommand', () => {
54
56
  expect(em.transactions[0].userId).toBe('99999999-9999-4999-8999-999999999999')
55
57
  })
56
58
 
59
+ // The CPQ↔billing connector runs as a system identity, not a user: it calls
60
+ // the command bus with `auth: null` and carries tenant/organization in the
61
+ // payload instead. Open Mercato 0.6.7 hardened ensureTenantScope from
62
+ // fail-open to fail-closed, but kept `auth: null` a no-op — this test pins
63
+ // that, because the connector has no other guarantee than prose. If a future
64
+ // release closes this branch too, the connector breaks and this fails first.
65
+ it('accepts a system-identity call (auth: null) without a scope error', async () => {
66
+ const em = createPrepaidMockEm({
67
+ account: prepaidAccount(),
68
+ balance: { tenantId: TENANT, billAccountId: ACCT, balance: '100.0000' },
69
+ })
70
+ const ctx = makeCtx(em, null)
71
+ const result = await adjustTransactionCommand.execute(
72
+ { tenantId: TENANT, organizationId: ORG, billAccountId: ACCT, amount: -10, description: 'Connector debit' } as never,
73
+ ctx as never,
74
+ )
75
+ expect(result.balance).toBe('90.0000')
76
+ expect(em.transactions).toHaveLength(1)
77
+ expect(em.transactions[0].userId).toBeFalsy()
78
+ })
79
+
57
80
  it('credits the balance', async () => {
58
81
  const em = createPrepaidMockEm({
59
82
  account: prepaidAccount(),