@volter/twin-polar 0.1.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.
@@ -0,0 +1,413 @@
1
+ // Polar capability manifest — the EXPECTED REAL-PRODUCT SURFACE (the target), authored
2
+ // top-down from what Polar (polar.sh) actually does — NOT from what this twin has built. The
3
+ // honest denominator: most entries start as `todo`. `verify()` (required to count as done) is
4
+ // ground truth and drives the KERNEL-BACKED `handlePolarTwinRequest` against a FRESH temp root
5
+ // (no spawned mock; no in-memory side-store) — create → read → assert VALUES + a negative 4xx
6
+ // in Polar's error shape, so every claim is failable.
7
+ import { mkdtempSync, rmSync } from 'node:fs';
8
+ import { tmpdir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ import { checkCapabilities, type CapabilityReport, type CapabilitySpec, verifyBoundary } from '@volter/twin-tooling';
11
+ import { checkPolarConformance } from './polar-conformance.ts';
12
+ import { syncPolarFromReal, pushPendingPolarActions, type PolarLikeClient } from './polar-connector.ts';
13
+ import { handlePolarTwinRequest, type PolarResponse } from './polar-twin.ts';
14
+
15
+ const done = (id: string, area: string, title: string, dimension: CapabilitySpec['dimension'], tier: CapabilitySpec['tier'], verify: CapabilitySpec['verify']): CapabilitySpec =>
16
+ ({ id, area, title, dimension, tier, expected: 'done', verify });
17
+ const todo = (id: string, area: string, title: string, dimension: CapabilitySpec['dimension'], tier: CapabilitySpec['tier']): CapabilitySpec =>
18
+ ({ id, area, title, dimension, tier, expected: 'todo' });
19
+ const outOfScope = (id: string, area: string, title: string, dimension: CapabilitySpec['dimension'], tier: CapabilitySpec['tier'], reason: string): CapabilitySpec =>
20
+ ({ id, area, title, dimension, tier, expected: 'todo', outOfScope: reason });
21
+
22
+ // ── API verify: drive REAL requests against a fresh isolated root, then assert status/shape ──
23
+ type Step = { m: string; p: string; b?: unknown };
24
+ type Body = Record<string, unknown>;
25
+
26
+ /** Run a sequence of real Polar requests against an isolated kernel root; return all responses. */
27
+ async function withRoot(steps: (h: (s: Step) => Promise<PolarResponse>) => Promise<boolean>): Promise<boolean> {
28
+ const root = mkdtempSync(join(tmpdir(), 'polar-cap-'));
29
+ const h = (s: Step) => handlePolarTwinRequest({ method: s.m, path: s.p, body: s.b === undefined ? undefined : JSON.stringify(s.b), root });
30
+ try {
31
+ return await verifyBoundary('polar.withRoot', () => steps(h));
32
+ } finally {
33
+ rmSync(root, { recursive: true, force: true });
34
+ }
35
+ }
36
+
37
+ const ok = (r: PolarResponse) => r.status >= 200 && r.status < 300;
38
+ const id = (r: PolarResponse) => (r.body as Body)?.id as string;
39
+ const field = (r: PolarResponse, k: string) => (r.body as Body)?.[k];
40
+ const items = (r: PolarResponse) => (r.body as Body)?.items as Body[];
41
+ /** A Polar ResourceNotFound 404 (the real error envelope). */
42
+ const isNotFound = (r: PolarResponse) => r.status === 404 && (r.body as Body)?.type === 'ResourceNotFound';
43
+ /** A FastAPI/Polar 422 validation envelope. */
44
+ const isValidation = (r: PolarResponse) => r.status === 422 && Array.isArray((r.body as Body)?.detail);
45
+
46
+ export const POLAR_CAPABILITIES: CapabilitySpec[] = [
47
+ // ── Customers ─────────────────────────────────────────────────────────────────────
48
+ done('polar.customers.create', 'customers', 'Create customer with email/name/external_id', 'api', 'core', () =>
49
+ withRoot(async (h) => {
50
+ const c = await h({ m: 'POST', p: '/v1/customers/', b: { email: 'a@example.test', name: 'A', external_id: 'ext-1' } });
51
+ return c.status === 201 && typeof id(c) === 'string' && field(c, 'email') === 'a@example.test' && field(c, 'name') === 'A' && field(c, 'external_id') === 'ext-1' && field(c, 'organization_id') != null;
52
+ })),
53
+ done('polar.customers.create_requires_email', 'customers', 'Creating an individual customer without email → 422', 'api', 'core', () =>
54
+ withRoot(async (h) => {
55
+ const c = await h({ m: 'POST', p: '/v1/customers/', b: { name: 'No Email' } });
56
+ return isValidation(c);
57
+ })),
58
+ done('polar.customers.duplicate_email', 'customers', 'Duplicate email → 422 (not a fabricated success)', 'api', 'common', () =>
59
+ withRoot(async (h) => {
60
+ await h({ m: 'POST', p: '/v1/customers/', b: { email: 'dup@example.test' } });
61
+ const c = await h({ m: 'POST', p: '/v1/customers/', b: { email: 'dup@example.test' } });
62
+ return c.status === 422;
63
+ })),
64
+ done('polar.customers.get', 'customers', 'Retrieve a customer by id; unknown id → 404', 'api', 'core', () =>
65
+ withRoot(async (h) => {
66
+ const c = await h({ m: 'POST', p: '/v1/customers/', b: { email: 'get@example.test' } });
67
+ const g = await h({ m: 'GET', p: `/v1/customers/${id(c)}` });
68
+ const miss = await h({ m: 'GET', p: '/v1/customers/00000000-0000-4000-8000-000000000999' });
69
+ return ok(g) && id(g) === id(c) && field(g, 'email') === 'get@example.test' && isNotFound(miss);
70
+ })),
71
+ done('polar.customers.list_filter_email', 'customers', 'List customers and filter by email', 'api', 'core', () =>
72
+ withRoot(async (h) => {
73
+ await h({ m: 'POST', p: '/v1/customers/', b: { email: 'list@example.test' } });
74
+ await h({ m: 'POST', p: '/v1/customers/', b: { email: 'other@example.test' } });
75
+ const r = await h({ m: 'GET', p: '/v1/customers/?email=list@example.test' });
76
+ return ok(r) && items(r).length === 1 && items(r)[0]!.email === 'list@example.test' && (field(r, 'pagination') as Body).total_count === 1;
77
+ })),
78
+ done('polar.customers.update', 'customers', 'Update (PATCH) customer name/metadata; unknown → 404', 'api', 'common', () =>
79
+ withRoot(async (h) => {
80
+ const c = await h({ m: 'POST', p: '/v1/customers/', b: { email: 'patch@example.test', name: 'Old' } });
81
+ const u = await h({ m: 'PATCH', p: `/v1/customers/${id(c)}`, b: { name: 'New', metadata: { tier: 'pro' } } });
82
+ const miss = await h({ m: 'PATCH', p: '/v1/customers/nope', b: { name: 'x' } });
83
+ return ok(u) && field(u, 'name') === 'New' && (field(u, 'metadata') as Body).tier === 'pro' && isNotFound(miss);
84
+ })),
85
+ done('polar.customers.delete', 'customers', 'Delete (soft) a customer → 204, then 404 on read', 'api', 'common', () =>
86
+ withRoot(async (h) => {
87
+ const c = await h({ m: 'POST', p: '/v1/customers/', b: { email: 'del@example.test' } });
88
+ const d = await h({ m: 'DELETE', p: `/v1/customers/${id(c)}` });
89
+ const g = await h({ m: 'GET', p: `/v1/customers/${id(c)}` });
90
+ return d.status === 204 && isNotFound(g);
91
+ })),
92
+ done('polar.customers.state_meter_balance', 'customers', 'Customer state returns active meter balance shape', 'api', 'core', () =>
93
+ withRoot(async (h) => {
94
+ const c = await h({ m: 'POST', p: '/v1/customers/', b: { email: 'state@example.test' } });
95
+ const s = await h({ m: 'GET', p: `/v1/customers/${id(c)}/state` });
96
+ const meters = field(s, 'active_meters') as Body[];
97
+ return ok(s) && Array.isArray(meters) && typeof meters[0]?.balance === 'number' && Array.isArray(field(s, 'active_subscriptions'));
98
+ })),
99
+
100
+ // ── Products ──────────────────────────────────────────────────────────────────────
101
+ done('polar.products.create', 'products', 'Create a recurring product; retrieve round-trips', 'api', 'core', () =>
102
+ withRoot(async (h) => {
103
+ const p = await h({ m: 'POST', p: '/v1/products/', b: { name: 'Pro', recurring_interval: 'month', prices: [{ price_amount: 1500, price_currency: 'usd' }] } });
104
+ const g = await h({ m: 'GET', p: `/v1/products/${id(p)}` });
105
+ return p.status === 201 && field(p, 'name') === 'Pro' && field(p, 'is_recurring') === true && ok(g) && (field(g, 'prices') as unknown[]).length === 1;
106
+ })),
107
+ done('polar.products.create_requires_name', 'products', 'Create product without name → 422', 'api', 'common', () =>
108
+ withRoot(async (h) => isValidation(await h({ m: 'POST', p: '/v1/products/', b: {} })))),
109
+ done('polar.products.list_and_archive', 'products', 'List products + archive via PATCH is_archived', 'api', 'common', () =>
110
+ withRoot(async (h) => {
111
+ const p = await h({ m: 'POST', p: '/v1/products/', b: { name: 'Arch' } });
112
+ const u = await h({ m: 'PATCH', p: `/v1/products/${id(p)}`, b: { is_archived: true } });
113
+ const l = await h({ m: 'GET', p: '/v1/products/?is_archived=true' });
114
+ return field(u, 'is_archived') === true && items(l).some((x) => x.id === id(p));
115
+ })),
116
+
117
+ // ── Checkouts + confirm → subscription/order ────────────────────────────────────────
118
+ done('polar.checkouts.create', 'checkouts', 'Create checkout with client secret + hosted url', 'api', 'core', () =>
119
+ withRoot(async (h) => {
120
+ const prod = await h({ m: 'POST', p: '/v1/products/', b: { name: 'CO', recurring_interval: 'month', prices: [{ price_amount: 2000 }] } });
121
+ const c = await h({ m: 'POST', p: '/v1/checkouts/', b: { products: [id(prod)] } });
122
+ return c.status === 201 && String(field(c, 'client_secret')).startsWith('polar_checkout_') && String(field(c, 'url')).includes('/checkout/') && field(c, 'status') === 'open' && field(c, 'amount') === 2000;
123
+ })),
124
+ done('polar.checkouts.requires_product', 'checkouts', 'Checkout without products → 422', 'api', 'common', () =>
125
+ withRoot(async (h) => isValidation(await h({ m: 'POST', p: '/v1/checkouts/', b: {} })))),
126
+ done('polar.checkouts.confirm_creates_subscription', 'checkouts', 'Confirm a recurring checkout → active subscription + paid order + customer', 'api', 'core', () =>
127
+ withRoot(async (h) => {
128
+ const prod = await h({ m: 'POST', p: '/v1/products/', b: { name: 'Sub', recurring_interval: 'month', prices: [{ price_amount: 1000 }] } });
129
+ const c = await h({ m: 'POST', p: '/v1/checkouts/', b: { products: [id(prod)], customer_email: 'buyer@example.test' } });
130
+ const conf = await h({ m: 'POST', p: `/v1/checkouts/${id(c)}/confirm`, b: { customer_email: 'buyer@example.test' } });
131
+ if (!ok(conf) || field(conf, 'status') !== 'confirmed' || !field(conf, 'subscription_id')) return false;
132
+ const subs = await h({ m: 'GET', p: '/v1/subscriptions/' });
133
+ const orders = await h({ m: 'GET', p: '/v1/orders/' });
134
+ return items(subs).length === 1 && items(subs)[0]!.status === 'active' && items(orders).length === 1 && items(orders)[0]!.paid === true;
135
+ })),
136
+ done('polar.checkouts.confirm_twice_rejected', 'checkouts', 'Confirming an already-confirmed checkout → 403', 'api', 'common', () =>
137
+ withRoot(async (h) => {
138
+ const prod = await h({ m: 'POST', p: '/v1/products/', b: { name: 'Once', recurring_interval: 'month', prices: [{ price_amount: 500 }] } });
139
+ const c = await h({ m: 'POST', p: '/v1/checkouts/', b: { products: [id(prod)], customer_email: 'x@example.test' } });
140
+ await h({ m: 'POST', p: `/v1/checkouts/${id(c)}/confirm`, b: { customer_email: 'x@example.test' } });
141
+ const again = await h({ m: 'POST', p: `/v1/checkouts/${id(c)}/confirm`, b: {} });
142
+ return again.status === 403;
143
+ })),
144
+
145
+ // ── Subscriptions ───────────────────────────────────────────────────────────────────
146
+ done('polar.subscriptions.list_get', 'subscriptions', 'List + retrieve subscriptions; unknown → 404', 'api', 'core', () =>
147
+ withRoot(async (h) => {
148
+ const prod = await h({ m: 'POST', p: '/v1/products/', b: { name: 'S', recurring_interval: 'month', prices: [{ price_amount: 100 }] } });
149
+ const c = await h({ m: 'POST', p: '/v1/checkouts/', b: { products: [id(prod)], customer_email: 's@example.test' } });
150
+ const conf = await h({ m: 'POST', p: `/v1/checkouts/${id(c)}/confirm`, b: { customer_email: 's@example.test' } });
151
+ const subId = field(conf, 'subscription_id') as string;
152
+ const g = await h({ m: 'GET', p: `/v1/subscriptions/${subId}` });
153
+ const miss = await h({ m: 'GET', p: '/v1/subscriptions/nope' });
154
+ return ok(g) && id(g) === subId && field(g, 'customer') != null && isNotFound(miss);
155
+ })),
156
+ done('polar.subscriptions.patch_cancel_at_period_end', 'subscriptions', 'PATCH cancel_at_period_end flips flag + records canceled_at', 'api', 'common', () =>
157
+ withRoot(async (h) => {
158
+ const prod = await h({ m: 'POST', p: '/v1/products/', b: { name: 'P', recurring_interval: 'month', prices: [{ price_amount: 100 }] } });
159
+ const c = await h({ m: 'POST', p: '/v1/checkouts/', b: { products: [id(prod)], customer_email: 'p@example.test' } });
160
+ const conf = await h({ m: 'POST', p: `/v1/checkouts/${id(c)}/confirm`, b: { customer_email: 'p@example.test' } });
161
+ const subId = field(conf, 'subscription_id') as string;
162
+ const u = await h({ m: 'PATCH', p: `/v1/subscriptions/${subId}`, b: { cancel_at_period_end: true } });
163
+ return ok(u) && field(u, 'cancel_at_period_end') === true && field(u, 'canceled_at') != null && field(u, 'status') === 'active';
164
+ })),
165
+ done('polar.subscriptions.cancel', 'subscriptions', 'DELETE subscription → status canceled + ended_at', 'api', 'common', () =>
166
+ withRoot(async (h) => {
167
+ const prod = await h({ m: 'POST', p: '/v1/products/', b: { name: 'C', recurring_interval: 'month', prices: [{ price_amount: 100 }] } });
168
+ const c = await h({ m: 'POST', p: '/v1/checkouts/', b: { products: [id(prod)], customer_email: 'c@example.test' } });
169
+ const conf = await h({ m: 'POST', p: `/v1/checkouts/${id(c)}/confirm`, b: { customer_email: 'c@example.test' } });
170
+ const subId = field(conf, 'subscription_id') as string;
171
+ const d = await h({ m: 'DELETE', p: `/v1/subscriptions/${subId}` });
172
+ return ok(d) && field(d, 'status') === 'canceled' && field(d, 'ended_at') != null;
173
+ })),
174
+
175
+ // ── Orders ──────────────────────────────────────────────────────────────────────────
176
+ done('polar.orders.list_filter', 'orders', 'List orders + filter by customer_id', 'api', 'common', () =>
177
+ withRoot(async (h) => {
178
+ const prod = await h({ m: 'POST', p: '/v1/products/', b: { name: 'O', prices: [{ price_amount: 700 }] } });
179
+ const c = await h({ m: 'POST', p: '/v1/checkouts/', b: { products: [id(prod)], customer_email: 'o@example.test' } });
180
+ const conf = await h({ m: 'POST', p: `/v1/checkouts/${id(c)}/confirm`, b: { customer_email: 'o@example.test' } });
181
+ const custId = field(conf, 'customer_id') as string;
182
+ const l = await h({ m: 'GET', p: `/v1/orders/?customer_id=${custId}` });
183
+ return items(l).length === 1 && items(l)[0]!.customer_id === custId;
184
+ })),
185
+ done('polar.orders.refund', 'orders', 'Refund an order → status refunded + refunded_amount', 'api', 'common', () =>
186
+ withRoot(async (h) => {
187
+ const prod = await h({ m: 'POST', p: '/v1/products/', b: { name: 'R', prices: [{ price_amount: 900 }] } });
188
+ const c = await h({ m: 'POST', p: '/v1/checkouts/', b: { products: [id(prod)], customer_email: 'r@example.test' } });
189
+ const conf = await h({ m: 'POST', p: `/v1/checkouts/${id(c)}/confirm`, b: { customer_email: 'r@example.test' } });
190
+ const ordList = await h({ m: 'GET', p: '/v1/orders/' });
191
+ const orderId = items(ordList)[0]!.id as string;
192
+ const ref = await h({ m: 'POST', p: `/v1/orders/${orderId}/refund`, b: { amount: 900 } });
193
+ const miss = await h({ m: 'POST', p: '/v1/orders/nope/refund', b: {} });
194
+ return ok(ref) && field(ref, 'status') === 'refunded' && field(ref, 'refunded_amount') === 900 && field(ref, 'paid') === false && isNotFound(miss) && Boolean(conf);
195
+ })),
196
+
197
+ // ── Benefits / Discounts / Meters ────────────────────────────────────────────────────
198
+ done('polar.benefits.crud', 'benefits', 'Create/list/delete a benefit', 'api', 'common', () =>
199
+ withRoot(async (h) => {
200
+ const b = await h({ m: 'POST', p: '/v1/benefits/', b: { type: 'custom', description: 'VIP' } });
201
+ const g = await h({ m: 'GET', p: `/v1/benefits/${id(b)}` });
202
+ const d = await h({ m: 'DELETE', p: `/v1/benefits/${id(b)}` });
203
+ const after = await h({ m: 'GET', p: `/v1/benefits/${id(b)}` });
204
+ return b.status === 201 && field(g, 'description') === 'VIP' && d.status === 204 && isNotFound(after);
205
+ })),
206
+ done('polar.discounts.crud', 'discounts', 'Create/update/list discounts; requires name+type', 'api', 'common', () =>
207
+ withRoot(async (h) => {
208
+ const bad = await h({ m: 'POST', p: '/v1/discounts/', b: { name: 'NoType' } });
209
+ const d = await h({ m: 'POST', p: '/v1/discounts/', b: { name: 'Launch', type: 'percentage', basis_points: 2000 } });
210
+ const u = await h({ m: 'PATCH', p: `/v1/discounts/${id(d)}`, b: { basis_points: 5000 } });
211
+ return isValidation(bad) && d.status === 201 && field(d, 'type') === 'percentage' && field(u, 'basis_points') === 5000;
212
+ })),
213
+ done('polar.meters.crud', 'meters', 'Create + retrieve a usage meter', 'api', 'common', () =>
214
+ withRoot(async (h) => {
215
+ const m = await h({ m: 'POST', p: '/v1/meters/', b: { name: 'tokens', aggregation: { func: 'sum', property: 'amount' } } });
216
+ const g = await h({ m: 'GET', p: `/v1/meters/${id(m)}` });
217
+ return m.status === 201 && field(g, 'name') === 'tokens' && (field(g, 'aggregation') as Body).func === 'sum';
218
+ })),
219
+
220
+ // ── Events ────────────────────────────────────────────────────────────────────────
221
+ done('polar.events.ingest', 'events', 'Ingest usage events → inserted count', 'api', 'core', () =>
222
+ withRoot(async (h) => {
223
+ const r = await h({ m: 'POST', p: '/v1/events/ingest', b: { events: [{ name: 'tokens', metadata: { cost_usd: 1 } }, { name: 'tokens' }] } });
224
+ return ok(r) && field(r, 'inserted') === 2 && field(r, 'duplicates') === 0;
225
+ })),
226
+ done('polar.events.ingest_validates_name', 'events', 'Ingest with a nameless event → 422', 'api', 'common', () =>
227
+ withRoot(async (h) => isValidation(await h({ m: 'POST', p: '/v1/events/ingest', b: { events: [{ metadata: {} }] } })))),
228
+ done('polar.events.list_filter', 'events', 'List events + filter by customer_id', 'api', 'common', () =>
229
+ withRoot(async (h) => {
230
+ const c = await h({ m: 'POST', p: '/v1/customers/', b: { email: 'ev@example.test' } });
231
+ await h({ m: 'POST', p: '/v1/events/ingest', b: { events: [{ name: 'used', customer_id: id(c) }] } });
232
+ await h({ m: 'POST', p: '/v1/events/ingest', b: { events: [{ name: 'other' }] } });
233
+ const l = await h({ m: 'GET', p: `/v1/events/?customer_id=${id(c)}` });
234
+ return items(l).length === 1 && items(l)[0]!.name === 'used';
235
+ })),
236
+ done('polar.events.balance_reflects_usage', 'events', 'Customer state meter balance nets credits − consumption', 'api', 'common', () =>
237
+ withRoot(async (h) => {
238
+ const c = await h({ m: 'POST', p: '/v1/customers/', b: { email: 'bal2@example.test' } });
239
+ await h({ m: 'POST', p: '/v1/events/ingest', b: { events: [{ name: 'credit', customer_id: id(c), metadata: { cost_usd: -5 } }, { name: 'use', customer_id: id(c), metadata: { cost_usd: 2 } }] } });
240
+ const s = await h({ m: 'GET', p: `/v1/customers/${id(c)}/state` });
241
+ return (field(s, 'active_meters') as Body[])[0]!.balance === 3;
242
+ })),
243
+
244
+ // ── Customer sessions ───────────────────────────────────────────────────────────────
245
+ done('polar.customer_sessions.create', 'customer-sessions', 'Create a customer portal session; unknown customer → 404', 'api', 'common', () =>
246
+ withRoot(async (h) => {
247
+ const c = await h({ m: 'POST', p: '/v1/customers/', b: { email: 'cs@example.test' } });
248
+ const s = await h({ m: 'POST', p: '/v1/customer-sessions/', b: { customer_id: id(c) } });
249
+ const miss = await h({ m: 'POST', p: '/v1/customer-sessions/', b: { customer_id: 'nope' } });
250
+ return s.status === 201 && String(field(s, 'token')).startsWith('polar_cst_') && String(field(s, 'customer_portal_url')).includes('customer_session_token') && isNotFound(miss);
251
+ })),
252
+
253
+ // ── Webhook endpoints ───────────────────────────────────────────────────────────────
254
+ done('polar.webhooks.endpoints_crud', 'webhooks', 'Create/list/delete webhook endpoints; create requires url', 'api', 'core', () =>
255
+ withRoot(async (h) => {
256
+ const bad = await h({ m: 'POST', p: '/v1/webhooks/endpoints/', b: {} });
257
+ const w = await h({ m: 'POST', p: '/v1/webhooks/endpoints/', b: { url: 'https://app.test/hook', events: ['order.created'] } });
258
+ const l = await h({ m: 'GET', p: '/v1/webhooks/endpoints/' });
259
+ const d = await h({ m: 'DELETE', p: `/v1/webhooks/endpoints/${id(w)}` });
260
+ return isValidation(bad) && w.status === 201 && items(l).length === 1 && d.status === 204;
261
+ })),
262
+
263
+ // ── Safety / honesty ────────────────────────────────────────────────────────────────
264
+ done('polar.read_only', 'safety', 'Read-only mode rejects writes with 405', 'api', 'core', () =>
265
+ withRoot(async () => {
266
+ const root = mkdtempSync(join(tmpdir(), 'polar-ro-'));
267
+ try {
268
+ const r = await handlePolarTwinRequest({ method: 'POST', path: '/v1/customers/', body: JSON.stringify({ email: 'ro@example.test' }), root, readOnly: true });
269
+ const g = await handlePolarTwinRequest({ method: 'GET', path: '/v1/customers/', root, readOnly: true });
270
+ return r.status === 405 && g.status === 200;
271
+ } finally {
272
+ rmSync(root, { recursive: true, force: true });
273
+ }
274
+ })),
275
+ done('polar.errors.unmodeled_route', 'errors', 'Unmodeled route → ResourceNotFound (never fabricated success)', 'api', 'core', () =>
276
+ withRoot(async (h) => {
277
+ const r = await h({ m: 'POST', p: '/v1/totally-made-up/', b: {} });
278
+ return isNotFound(r);
279
+ })),
280
+
281
+ // ── Connector (D6/D7) ────────────────────────────────────────────────────────────────
282
+ done('polar.connector.sync.entrypoint', 'connector', 'syncPolarFromReal folds pulled customers/products into the twin', 'connector', 'core', () =>
283
+ withRoot(async (h) => {
284
+ const root = mkdtempSync(join(tmpdir(), 'polar-sync-'));
285
+ try {
286
+ const client: PolarLikeClient = {
287
+ customers: { list: async () => ({ items: [{ id: 'cus_real_1', email: 'real@example.test', name: 'Real' }] }) },
288
+ products: { list: async () => ({ items: [{ id: 'prod_real_1', name: 'Real Plan', recurring_interval: 'month' }] }) },
289
+ subscriptions: { list: async () => ({ items: [] }) },
290
+ orders: { list: async () => ({ items: [] }) },
291
+ };
292
+ const first = await syncPolarFromReal(client, { root, occurredAt: '2026-01-01T00:00:00.000Z', budgetOptions: { root } });
293
+ const again = await syncPolarFromReal(client, { root, occurredAt: '2026-01-01T00:00:00.000Z', budgetOptions: { root } });
294
+ // pulled customer is now served by the twin's read path over the SAME root
295
+ const c = await handlePolarTwinRequest({ method: 'GET', path: '/v1/customers/cus_real_1', root });
296
+ return first.observed === 2 && first.deltasAppended === 2 && again.deltasAppended === 0 && c.status === 200 && field(c, 'email') === 'real@example.test' && Boolean(h);
297
+ } finally {
298
+ rmSync(root, { recursive: true, force: true });
299
+ }
300
+ })),
301
+ done('polar.connector.push.entrypoint', 'connector', 'pushPendingPolarActions pushes a local customer.create to the injected client', 'connector', 'core', () =>
302
+ withRoot(async () => {
303
+ const root = mkdtempSync(join(tmpdir(), 'polar-push-'));
304
+ try {
305
+ await handlePolarTwinRequest({ method: 'POST', path: '/v1/customers/', body: JSON.stringify({ email: 'push@example.test', name: 'P' }), root });
306
+ let pushedEmail: string | null = null;
307
+ const client: PolarLikeClient = {
308
+ customers: { list: async () => ({ items: [] }), create: async (params) => { pushedEmail = String((params as Body).email); return { id: 'cus_pushed' }; } },
309
+ };
310
+ const res = await pushPendingPolarActions(client, { root, occurredAt: '2026-01-01T00:00:00.000Z', budgetOptions: { root } });
311
+ const again = await pushPendingPolarActions(client, { root, occurredAt: '2026-01-01T00:00:00.000Z', budgetOptions: { root } });
312
+ return res.pushed === 1 && pushedEmail === 'push@example.test' && again.pushed === 0;
313
+ } finally {
314
+ rmSync(root, { recursive: true, force: true });
315
+ }
316
+ })),
317
+
318
+ done('polar.conformance.snapshot', 'conformance', 'Conformance snapshot covers implemented resource types and endpoints', 'api', 'common', () => {
319
+ const report = checkPolarConformance();
320
+ return report.ok && report.endpointsChecked >= 30 && report.resourceTypesChecked >= 10;
321
+ }),
322
+
323
+ // ── out of scope (a local twin cannot honestly do these) ──────────────────────────────
324
+ outOfScope('polar.payments.real_processing', 'payments', 'Real payment processor authorization, capture, disputes, and settlement', 'api', 'core', 'A local twin must not contact or emulate real card networks; it returns deterministic checkout/order shapes and "confirms" payment offline.'),
325
+ outOfScope('polar.tax.real_calculation', 'tax', 'Hosted tax/VAT calculation, nexus, remittance, and jurisdiction updates', 'api', 'common', 'Tax authority integrations and live jurisdiction rules are outside a deterministic local twin.'),
326
+
327
+ // ── the remaining real Polar surface (the honest TODO denominator) ─────────────────────
328
+ ...[
329
+ ['products.prices_full', 'products', 'Full price modeling (fixed/recurring/metered/custom/tiered)'],
330
+ ['products.benefits_attach', 'products', 'Attach/detach benefits to a product'],
331
+ ['products.medias', 'products', 'Product media uploads and ordering'],
332
+ ['products.custom_fields', 'products', 'Custom field attachment + validation'],
333
+ ['prices.archive', 'products', 'Archive/replace a price'],
334
+ ['benefits.grants', 'benefits', 'Grant/revoke customer benefits + benefit grants list'],
335
+ ['benefits.types', 'benefits', 'Built-in benefit types (downloadables, license keys, GitHub, Discord)'],
336
+ ['discounts.redemption', 'discounts', 'Discount redemption + max_redemptions enforcement'],
337
+ ['meters.quantities', 'meters', 'Meter quantities aggregation + time buckets'],
338
+ ['meters.update', 'meters', 'Update/archive a meter'],
339
+ ['events.dedupe', 'events', 'Event idempotency / external_id duplicate detection'],
340
+ ['events.external_customer', 'events', 'Ingest by external_customer_id resolution'],
341
+ ['customer_sessions.scopes', 'customer-sessions', 'Customer portal session scopes + expiry enforcement'],
342
+ ['customer_portal.api', 'customer-portal', 'Customer portal API (subscriptions/orders/benefit grants by session token)'],
343
+ ['checkouts.update', 'checkouts', 'Update an open checkout (customer fields, discount, amount)'],
344
+ ['checkouts.embed', 'checkouts', 'Embedded checkout embed_origin enforcement'],
345
+ ['checkouts.expiry', 'checkouts', 'Checkout expiry transitions'],
346
+ ['checkout_links.crud', 'checkouts', 'Reusable checkout links CRUD'],
347
+ ['subscriptions.proration', 'subscriptions', 'Plan changes with proration'],
348
+ ['subscriptions.trials', 'subscriptions', 'Trials and trial-end transitions'],
349
+ ['subscriptions.revoke', 'subscriptions', 'Revoke (cancel immediately) vs cancel-at-period-end'],
350
+ ['subscriptions.uncancel', 'subscriptions', 'Uncancel a subscription scheduled to cancel'],
351
+ ['subscriptions.seats', 'subscriptions', 'Seat-based subscription quantity'],
352
+ ['orders.invoice', 'orders', 'Generate/download an order invoice PDF'],
353
+ ['orders.list_filters', 'orders', 'Order filters (product/status/date) + sorting'],
354
+ ['refunds.crud', 'refunds', 'Refunds resource CRUD + partial refund accounting'],
355
+ ['webhooks.delivery', 'webhooks', 'Webhook delivery, signing (standard-webhooks), and retries'],
356
+ ['webhooks.deliveries_list', 'webhooks', 'Webhook delivery logs + redelivery'],
357
+ ['webhooks.endpoint_update', 'webhooks', 'Update a webhook endpoint (url/events/secret)'],
358
+ ['license_keys.crud', 'license-keys', 'License key create/activate/validate'],
359
+ ['license_keys.limits', 'license-keys', 'Activation limits and revocation'],
360
+ ['files.upload', 'files', 'File upload (S3 multipart) for downloadable benefits'],
361
+ ['organizations.crud', 'organizations', 'Organization settings, branding, and account'],
362
+ ['custom_fields.crud', 'custom-fields', 'Custom field definitions CRUD'],
363
+ ['oauth.apps', 'oauth', 'OAuth2 app + token flows'],
364
+ ['oauth.userinfo', 'oauth', 'OAuth userinfo + token introspection'],
365
+ ['pagination.cursors', 'pagination', 'Page/limit pagination + sorting across list endpoints'],
366
+ ['metadata.filter', 'metadata', 'Metadata filtering on list endpoints'],
367
+ ['auth.token', 'auth', 'Organization access token auth (401 on bad token)'],
368
+ ['rate_limits', 'limits', 'Rate-limit headers and retry semantics'],
369
+ ['idempotency', 'idempotency', 'Idempotency-Key dedupe on mutating requests'],
370
+ ['mirror.ui', 'ui', 'Operator mirror UI for customers/subscriptions/orders/events'],
371
+ ['connector.subscriptions_orders', 'connector', 'Pull subscriptions/orders incrementally with cursors'],
372
+ ['fixtures.seed', 'fixtures', 'Seed products, prices, and subscriptions from fixtures'],
373
+ ].map(([id, area, title]) => todo(`polar.${id}`, area, title, id.startsWith('connector') ? 'connector' : id === 'mirror.ui' ? 'ui' : 'api', id.includes('webhooks') || id.includes('connector') || id.includes('auth') ? 'core' : 'common')),
374
+ // ── Pull-surface coverage audit gaps (TWIN-46 / G2) — filed as manifest todos, which is
375
+ // what the demand-ordered build list is drawn from. See pull-audit.json (repo root) for the
376
+ // per-pack pull-vs-read-surface evidence.
377
+ todo('polar.connector.pull_webhook_endpoints', 'connector', 'Connector: pull registered webhook endpoints from the real account', 'connector', 'core'),
378
+ todo('polar.connector.pull_benefits', 'connector', 'Connector: pull benefits from the real account', 'connector', 'common'),
379
+ todo('polar.connector.pull_discounts', 'connector', 'Connector: pull discounts from the real account', 'connector', 'common'),
380
+
381
+ // ── Missing-area sweep (TWIN-87 / F1) ────────────────────────────────────────────────────
382
+ // /v1/metrics and /v1/customer-meters had NO manifest entry of any status, and the
383
+ // `payments` area held only the real-processing carve-out (list/read was unrepresented).
384
+ // Filed as honest todos (all genuinely buildable — reading back recorded payment metadata
385
+ // is not the same as real card processing, which stays out of scope above). See
386
+ // POLAR_AREAS below + polar-capabilities.test.ts's area-census meta-test, gate-wired so a
387
+ // future whole-area omission fails here instead of waiting for another review pass.
388
+ todo('polar.metrics.query', 'metrics', 'Metrics: aggregated revenue/orders/subscribers time-series (GET /v1/metrics/)', 'api', 'niche'),
389
+ todo('polar.metrics.limits', 'metrics', 'Metrics: query granularity/date-range validation limits', 'api', 'niche'),
390
+ todo('polar.customer_meters.list', 'customer-meters', 'Customer meters: list per-customer meter balances (GET /v1/customer-meters/)', 'api', 'niche'),
391
+ todo('polar.customer_meters.get', 'customer-meters', 'Customer meters: retrieve a single customer-meter by id', 'api', 'niche'),
392
+ todo('polar.payments.list', 'payments', 'Payments: list recorded payment metadata (GET /v1/payments/)', 'api', 'niche'),
393
+ todo('polar.payments.get', 'payments', 'Payments: retrieve a single recorded payment by id', 'api', 'niche'),
394
+
395
+ ];
396
+
397
+ // TWIN-87 committed area census — the vendor's top-level API product areas (docs nav /
398
+ // OpenAPI tags), authored top-down independent of what a manifest entry happens to already
399
+ // exist for. polar-capabilities.test.ts's area-census meta-test (assertAreaCensus) fails the
400
+ // gate if a declared area has zero manifest entries and no named exclusion, OR if a manifest
401
+ // entry's `area` drifts outside this list — so a whole missing area (metrics, customer-
402
+ // meters were the concrete F1 leak) can never again hide invisibly.
403
+ export const POLAR_AREAS = [
404
+ 'auth', 'benefits', 'checkouts', 'conformance', 'connector', 'custom-fields', 'customer-meters',
405
+ 'customer-portal', 'customer-sessions', 'customers', 'discounts', 'errors', 'events', 'files',
406
+ 'fixtures', 'idempotency', 'license-keys', 'limits', 'metadata', 'meters', 'metrics', 'oauth',
407
+ 'orders', 'organizations', 'pagination', 'payments', 'products', 'refunds', 'safety',
408
+ 'subscriptions', 'tax', 'ui', 'webhooks',
409
+ ] as const;
410
+
411
+ export async function polarCapabilities(): Promise<CapabilityReport> {
412
+ return checkCapabilities('polar', POLAR_CAPABILITIES);
413
+ }
@@ -0,0 +1,33 @@
1
+ // Polar conformance (dev-only; lazy-imported by the CLI, NEVER from index.ts/runtime — E2).
2
+ // Checks the twin's endpoint/resource inventory against the modeled Polar surface.
3
+ import { polarTwinSnapshot, POLAR_RESOURCE_TYPES } from './polar-twin.ts';
4
+
5
+ export type PolarConformanceReport = {
6
+ ok: boolean;
7
+ endpointsChecked: number;
8
+ resourceTypesChecked: number;
9
+ violations: string[];
10
+ };
11
+
12
+ export function checkPolarConformance(): PolarConformanceReport {
13
+ const snapshot = polarTwinSnapshot();
14
+ const violations: string[] = [];
15
+ // Every declared resource type must have at least one implemented endpoint touching it.
16
+ for (const type of POLAR_RESOURCE_TYPES) {
17
+ const stem = type === 'customer_session' ? 'customer-sessions'
18
+ : type === 'webhook_endpoint' ? 'webhooks/endpoints'
19
+ : `${type}s`;
20
+ if (!snapshot.implementedEndpoints.some((e) => e.includes(`/v1/${stem}`))) {
21
+ violations.push(`resource type '${type}' has no implemented endpoint`);
22
+ }
23
+ }
24
+ if (!snapshot.implementedEndpoints.some((e) => e.startsWith('POST /v1/customers/'))) {
25
+ violations.push('missing customer create endpoint');
26
+ }
27
+ return {
28
+ ok: violations.length === 0,
29
+ endpointsChecked: snapshot.implementedEndpoints.length,
30
+ resourceTypesChecked: snapshot.resourceTypes.length,
31
+ violations,
32
+ };
33
+ }
@@ -0,0 +1,155 @@
1
+ // Polar CONNECTOR — the live-vendor pull/push path that gives the Polar twin the full
2
+ // "git for SaaS" lifecycle over an INJECTED client (the auth boundary).
3
+ //
4
+ // PULL (real → twin): fetch real Polar objects via the injected client (customers, products,
5
+ // subscriptions, orders), map them to SyncResource[], and fold them into
6
+ // the event log via syncPull (shadow-diff dedup, so a re-pull of identical
7
+ // state is a no-op). Kernel subject ids are TYPE-PREFIXED to stay
8
+ // collision-safe (mirrors the twin handler).
9
+ // PUSH (twin → real): for every PENDING local action, call the injected client and
10
+ // confirmAction on success — recording the confirmed fields as observed
11
+ // and suppressing the local projection.
12
+ //
13
+ // The vendor I/O is an INJECTED client interface (`PolarLikeClient`): a fake in tests, a real
14
+ // `new Polar({ accessToken })` in prod. The pack imports NO SDK and holds NO key.
15
+ import { confirmAction, pendingActions, syncPull } from '@volter/twin';
16
+ import type { SyncResource, TwinAction } from '@volter/twin';
17
+ //
18
+ // ── The client-side RATE BUDGET is not optional here ────────────────────────────────────────
19
+ // Polar publishes 500 requests/minute in production and 100/minute in sandbox per organization, and
20
+ // is the one vendor in this batch that documents a Retry-After header — so the guard's cooldown is
21
+ // armed by the vendor itself rather than inferred.
22
+ // Every entrypoint below GUARDS the injected client before touching it (`guardPolarClient`, which is
23
+ // idempotent — a caller who already wrapped is not double-charged, a caller who forgot is protected
24
+ // anyway); there is deliberately no option that turns the budget off. See polar-budget.ts.
25
+ import { polarBudgetOf, guardPolarClient, type PolarBudgetedOptions } from './polar-budget.ts';
26
+
27
+ const SERVICE = 'polar';
28
+
29
+ export type { PolarBudgetedOptions };
30
+
31
+ export type PolarCustomer = { id: string; email?: string | null; name?: string | null; external_id?: string | null; created_at?: string; metadata?: Record<string, unknown> };
32
+ export type PolarProduct = { id: string; name?: string | null; description?: string | null; recurring_interval?: string | null; is_archived?: boolean; created_at?: string };
33
+ export type PolarSubscription = { id: string; status?: string; amount?: number; currency?: string; recurring_interval?: string; customer_id?: string | null; product_id?: string | null; created_at?: string; current_period_end?: string | null; cancel_at_period_end?: boolean };
34
+ export type PolarOrder = { id: string; status?: string; amount?: number; total_amount?: number; currency?: string; customer_id?: string | null; product_id?: string | null; subscription_id?: string | null; created_at?: string };
35
+
36
+ // Polar SDK list responses page through `{ result: { items, pagination } }`. The injected
37
+ // client exposes the minimal subset the connector calls; a real `Polar` instance is structurally
38
+ // assignable. Each `list` returns the items array directly (the caller paginates upstream).
39
+ export interface PolarLikeClient {
40
+ customers?: { list: (params?: Record<string, unknown>) => Promise<{ items: PolarCustomer[] }>; create?: (params: Record<string, unknown>) => Promise<{ id: string }> };
41
+ products?: { list: (params?: Record<string, unknown>) => Promise<{ items: PolarProduct[] }>; create?: (params: Record<string, unknown>) => Promise<{ id: string }> };
42
+ subscriptions?: { list: (params?: Record<string, unknown>) => Promise<{ items: PolarSubscription[] }> };
43
+ orders?: { list: (params?: Record<string, unknown>) => Promise<{ items: PolarOrder[] }> };
44
+ }
45
+
46
+ // Kernel subject id (type-prefixed → collision-safe across types; mirrors polar-twin.ts).
47
+ function kid(type: string, id: string): string {
48
+ return `${type}:${id}`;
49
+ }
50
+
51
+ // ── PULL mappers (real Polar object → SyncResource) ──────────────────────────────────
52
+ export function mapCustomer(c: PolarCustomer): SyncResource {
53
+ return { type: 'customer', id: kid('customer', String(c.id)), fields: { created_at: c.created_at ?? null, modified_at: null, email: c.email ?? null, name: c.name ?? null, external_id: c.external_id ?? null, customer_type: 'individual', email_verified: false, locale: 'en', metadata: c.metadata ?? {} } };
54
+ }
55
+ export function mapProduct(p: PolarProduct): SyncResource {
56
+ return { type: 'product', id: kid('product', String(p.id)), fields: { created_at: p.created_at ?? null, modified_at: null, name: p.name ?? null, description: p.description ?? null, recurring_interval: p.recurring_interval ?? null, is_archived: p.is_archived ?? false, prices: [], benefits: [], metadata: {} } };
57
+ }
58
+ export function mapSubscription(s: PolarSubscription): SyncResource {
59
+ return { type: 'subscription', id: kid('subscription', String(s.id)), fields: { created_at: s.created_at ?? null, modified_at: null, status: s.status ?? 'active', amount: s.amount ?? 0, currency: s.currency ?? 'usd', recurring_interval: s.recurring_interval ?? 'month', customer_id: s.customer_id ?? null, product_id: s.product_id ?? null, current_period_end: s.current_period_end ?? null, cancel_at_period_end: s.cancel_at_period_end ?? false, metadata: {} } };
60
+ }
61
+ export function mapOrder(o: PolarOrder): SyncResource {
62
+ return { type: 'order', id: kid('order', String(o.id)), fields: { created_at: o.created_at ?? null, modified_at: null, status: o.status ?? 'paid', paid: o.status !== 'refunded', amount: o.amount ?? o.total_amount ?? 0, total_amount: o.total_amount ?? o.amount ?? 0, currency: o.currency ?? 'usd', customer_id: o.customer_id ?? null, product_id: o.product_id ?? null, subscription_id: o.subscription_id ?? null, metadata: {} } };
63
+ }
64
+
65
+ export async function pullPolarCustomers(rawClient: PolarLikeClient, opts: PolarBudgetedOptions = {}): Promise<SyncResource[]> {
66
+ const client = guardPolarClient(rawClient, opts);
67
+ if (!client.customers) return [];
68
+ return (await client.customers.list()).items.map(mapCustomer);
69
+ }
70
+ export async function pullPolarProducts(rawClient: PolarLikeClient, opts: PolarBudgetedOptions = {}): Promise<SyncResource[]> {
71
+ const client = guardPolarClient(rawClient, opts);
72
+ if (!client.products) return [];
73
+ return (await client.products.list()).items.map(mapProduct);
74
+ }
75
+ export async function pullPolarSubscriptions(rawClient: PolarLikeClient, opts: PolarBudgetedOptions = {}): Promise<SyncResource[]> {
76
+ const client = guardPolarClient(rawClient, opts);
77
+ if (!client.subscriptions) return [];
78
+ return (await client.subscriptions.list()).items.map(mapSubscription);
79
+ }
80
+ export async function pullPolarOrders(rawClient: PolarLikeClient, opts: PolarBudgetedOptions = {}): Promise<SyncResource[]> {
81
+ const client = guardPolarClient(rawClient, opts);
82
+ if (!client.orders) return [];
83
+ return (await client.orders.list()).items.map(mapOrder);
84
+ }
85
+
86
+ /**
87
+ * D7 entry point: pull from real Polar (customers + products + subscriptions + orders) and fold
88
+ * EVERY domain into the twin via ONE syncPull (shadow-diff dedup). Returns { observed,
89
+ * deltasAppended } — callers need not know vendor-specific pull mechanics.
90
+ */
91
+ export async function syncPolarFromReal(
92
+ rawClient: PolarLikeClient,
93
+ opts: { root?: string; occurredAt?: string } & PolarBudgetedOptions = {},
94
+ ): Promise<{ observed: number; deltasAppended: number }> {
95
+ // Guard ONCE here and hand the guarded client down, so no domain pull can run unbudgeted.
96
+ const client = guardPolarClient(rawClient, polarBudgetOf(opts));
97
+ const occurredAt = opts.occurredAt ?? new Date().toISOString();
98
+ const resources: SyncResource[] = [
99
+ ...(await pullPolarCustomers(client)),
100
+ ...(await pullPolarProducts(client)),
101
+ ...(await pullPolarSubscriptions(client)),
102
+ ...(await pullPolarOrders(client)),
103
+ ];
104
+ const result = syncPull({ service: SERVICE, resources, occurredAt, ...(opts.root !== undefined ? { root: opts.root } : {}) });
105
+ return { observed: result.observed, deltasAppended: result.deltasAppended };
106
+ }
107
+
108
+ // ── PUSH ──────────────────────────────────────────────────────────────────────────
109
+ // The twin operations this connector knows how to push to real Polar. Anything else FAILS
110
+ // LOUDLY rather than being silently dropped.
111
+ const PUSHABLE = new Set(['customer.create', 'product.create']);
112
+
113
+ export async function pushPolarAction(
114
+ rawClient: PolarLikeClient,
115
+ action: Pick<TwinAction, 'operation' | 'subject' | 'fields'>,
116
+ opts: PolarBudgetedOptions = {},
117
+ ): Promise<{ externalId: string }> {
118
+ // Writes count against the SAME per-minute budget reads do, so the push path is budgeted too.
119
+ const client = guardPolarClient(rawClient, opts);
120
+ const op = action.operation ?? `${action.subject.type}.update`;
121
+ if (!PUSHABLE.has(op)) throw new Error(`polar push: unsupported operation '${op}' — refusing to silently drop a local write`);
122
+ const fields = action.fields ?? {};
123
+ let externalId = action.subject.id;
124
+ if (op === 'customer.create' && client.customers?.create) {
125
+ externalId = (await client.customers.create({ email: fields.email, name: fields.name, external_id: fields.external_id })).id;
126
+ } else if (op === 'product.create' && client.products?.create) {
127
+ externalId = (await client.products.create({ name: fields.name, recurring_interval: fields.recurring_interval })).id;
128
+ }
129
+ return { externalId: externalId || action.subject.id };
130
+ }
131
+
132
+ /**
133
+ * Push the twin's PENDING local actions to real Polar and CONFIRM each. A confirmed action is
134
+ * no longer pending, so a re-push enacts NOTHING. Unpushable ops are skipped.
135
+ */
136
+ export async function pushPendingPolarActions(
137
+ rawClient: PolarLikeClient,
138
+ opts: { root?: string; occurredAt?: string } & PolarBudgetedOptions = {},
139
+ ): Promise<{ pushed: number; confirmed: string[]; externalIds: Record<string, string> }> {
140
+ // Guard here as well as in pushPolarAction: guarding is idempotent, so the inner call is a no-op
141
+ // and the ledger/clock the caller chose is what the whole loop accounts against.
142
+ const client = guardPolarClient(rawClient, polarBudgetOf(opts));
143
+ const occurredAt = opts.occurredAt ?? new Date().toISOString();
144
+ const confirmed: string[] = [];
145
+ const externalIds: Record<string, string> = {};
146
+ for (const action of pendingActions(SERVICE, opts.root)) {
147
+ const op = action.operation ?? `${action.subject.type}.update`;
148
+ if (!PUSHABLE.has(op)) continue;
149
+ const { externalId } = await pushPolarAction(client, action, polarBudgetOf(opts));
150
+ confirmAction({ service: SERVICE, actionId: action.id, subject: action.subject, fields: action.fields ?? {}, occurredAt, ...(opts.root !== undefined ? { root: opts.root } : {}) });
151
+ confirmed.push(action.id);
152
+ externalIds[action.id] = externalId;
153
+ }
154
+ return { pushed: confirmed.length, confirmed, externalIds };
155
+ }