@volter/twin-polar 0.1.2 → 2.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/twin-polar",
3
- "version": "0.1.2",
3
+ "version": "2.0.1",
4
4
  "description": "Local Polar twin for customers, checkouts, subscriptions, meters, and usage events.",
5
5
  "author": "Volter (https://github.com/volter-ai)",
6
6
  "license": "Apache-2.0",
@@ -8,7 +8,8 @@
8
8
  "src",
9
9
  "README.md",
10
10
  "LICENSE",
11
- "!**/*.test.ts"
11
+ "!**/*.test.ts",
12
+ "dist"
12
13
  ],
13
14
  "repository": {
14
15
  "type": "git",
@@ -18,26 +19,32 @@
18
19
  "homepage": "https://github.com/volter-ai/twin/tree/main/packages/twin/polar#readme",
19
20
  "type": "module",
20
21
  "exports": {
21
- ".": "./src/index.ts"
22
+ ".": {
23
+ "types": "./dist/src/index.d.ts",
24
+ "default": "./dist/src/index.js"
25
+ }
22
26
  },
23
27
  "bin": {
24
- "world-polar": "src/cli.ts"
28
+ "world-polar": "dist/src/cli.js"
25
29
  },
26
30
  "scripts": {
27
31
  "test": "bun test src/*.test.ts",
28
- "typecheck": "tsc --noEmit"
32
+ "typecheck": "tsc --noEmit",
33
+ "build": "node ../../../scripts/publish/build.mjs",
34
+ "prepack": "node ../../../scripts/publish/prepare-publish.mjs prepack",
35
+ "postpack": "node ../../../scripts/publish/prepare-publish.mjs postpack"
29
36
  },
30
37
  "peerDependencies": {
31
- "@volter/twin": "^0.1.2"
38
+ "@volter/world-core": "2.0.1"
32
39
  },
33
40
  "devDependencies": {
34
41
  "@types/bun": "^1.2.20",
35
42
  "@types/node": "^24.0.0",
36
- "@volter/twin": "workspace:*",
37
- "@volter/twin-tooling": "workspace:*",
43
+ "@volter/world-core": "2.0.1",
44
+ "@volter/world-tooling": "0.1.0",
38
45
  "typescript": "^5.9.0"
39
46
  },
40
47
  "engines": {
41
- "bun": ">=1.2.0"
48
+ "node": ">=22.3"
42
49
  }
43
50
  }
package/src/cli.ts CHANGED
@@ -1,9 +1,9 @@
1
- #!/usr/bin/env bun
2
- import { keepProcessAlive } from '@volter/twin/lifecycle';
1
+ #!/usr/bin/env node
2
+ import { keepProcessAlive } from '@volter/world-core/lifecycle';
3
3
  // world-polar CLI: serve the KERNEL-BACKED Polar API twin, or run conformance. State lives in
4
- // the @volter/twin action log (no in-memory side-store). Conformance is dev-only + lazy-imported
5
- // so the bin runs without @volter/twin-tooling (E2).
6
- import { hasFlag, optionValue } from '@volter/twin/args';
4
+ // the @volter/world-core action log (no in-memory side-store). Conformance is dev-only + lazy-imported
5
+ // so the bin runs without @volter/world-tooling (E2).
6
+ import { hasFlag, optionValue } from '@volter/world-core/args';
7
7
  import { createPolarTwinServer } from './polar-server.ts';
8
8
 
9
9
  const [cmd, ...rest] = process.argv.slice(2);
@@ -12,7 +12,7 @@ const root = optionValue(rest, '--root') || undefined;
12
12
  const readOnly = hasFlag(rest, '--read-only'); // a twin accepts writes unless started read-only
13
13
 
14
14
  if (cmd === 'serve' || cmd === undefined) {
15
- const s = createPolarTwinServer({ readOnly, ...(root ? { root } : {}), ...(port ? { port } : {}) });
15
+ const s = await createPolarTwinServer({ readOnly, ...(root ? { root } : {}), ...(port ? { port } : {}) });
16
16
  process.stdout.write(`polar twin (merchant-of-record billing API)${readOnly ? ' [read-only]' : ''} at http://127.0.0.1:${s.port}\n`);
17
17
  await keepProcessAlive();
18
18
  } else if (cmd === 'conformance') {
package/src/index.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  // @volter/twin-polar — the Polar (polar.sh) merchant-of-record billing API twin, built on the
2
- // shared @volter/twin kernel. REST transport over api.polar.sh shapes; state lives entirely in
2
+ // shared @volter/world-core kernel. REST transport over api.polar.sh shapes; state lives entirely in
3
3
  // the kernel action log (no side-store). Customers, products, subscriptions, orders, checkouts,
4
4
  // benefits, discounts, meters, usage events, customer sessions, and webhook endpoints.
5
- // (Conformance/capability tooling lives in @volter/twin-tooling, a dev dependency — NOT shipped.)
5
+ // (Conformance/capability tooling lives in @volter/world-tooling, a dev dependency — NOT shipped.)
6
6
  export { handlePolarTwinRequest, polarTwinSnapshot, POLAR_RESOURCE_TYPES } from './polar-twin.ts';
7
7
  export type { PolarRequest, PolarResponse, PolarResourceType, PolarTwinSnapshot } from './polar-twin.ts';
8
8
  export { createPolarTwinFetch, createPolarTwinServer, type PolarTwinFetchOptions } from './polar-server.ts';
@@ -15,9 +15,11 @@ export {
15
15
  pullPolarOrders,
16
16
  pullPolarProducts,
17
17
  pullPolarSubscriptions,
18
- pushPendingPolarActions,
19
18
  pushPolarAction,
20
19
  syncPolarFromReal,
20
+ polarClientOver,
21
+ syncPolarFromRemote,
22
+ performPolarAction,
21
23
  } from './polar-connector.ts';
22
24
  export type { PolarCustomer, PolarLikeClient, PolarOrder, PolarProduct, PolarSubscription, PolarBudgetedOptions } from './polar-connector.ts';
23
25
  // The client-side rate budget — the fail-closed backstop every live call goes through. The
@@ -43,9 +45,21 @@ export {
43
45
  export type { PolarBudgetErrorKind, PolarBudgetOptions, PolarBudgetReservation, PolarBudgetSnapshot } from './polar-budget.ts';
44
46
 
45
47
  // Registry descriptor: the pack self-describes so tooling can discover it.
46
- import type { TwinPack } from '@volter/twin';
48
+ import { registerPack, type TwinPack } from '@volter/world-core';
47
49
  import { POLAR_RATE_BUDGET as RATE_BUDGET } from './polar-budget.ts';
50
+ import { performPolarAction as perform, syncPolarFromRemote as refresh } from './polar-connector.ts';
48
51
  export const pack: TwinPack = {
52
+ // PROTOCOL 2 (docs/contributing/architecture.md#protocol-2-the-pack-is-a-plugin): the pack is a plugin — its wire, its tree, and its half of the real
53
+ // state system: perform one entry against Polar, refresh the root from it. Moved 2026-09-08.
54
+ protocol: '2',
55
+ refresh: { every: '5m', webhook: true, onDemand: { atMost: '30s' } },
56
+ stateSystem: { perform, refresh },
57
+ roundTrip: { method: 'POST', path: '/v1/products', body: { name: 'round trip', recurring_interval: 'month' }, headers: { authorization: 'Bearer polar_oat_round_trip' } },
58
+ // rule 5: a checkout names its product; when the head adopts Polar's id for the product, the kernel resolves it first
59
+ referenceTrip: { method: 'POST', path: '/v1/checkouts', body: { products: ['{{id}}'] }, headers: { authorization: 'Bearer polar_oat_round_trip' } },
60
+ references: [{ type: 'checkout', to: 'product', key: (f: Record<string, unknown>) => (typeof f.product_id === 'string' ? `product:${f.product_id}` : undefined), adopt: (_f: Record<string, unknown>, vendorId: string) => ({ product_id: vendorId.replace(/^product:/, '') }) }],
61
+ parityOrigin: 'http://twin',
62
+ shapeParity: 'held',
49
63
  // The SAME object polar-budget.ts declares at module load — one source of truth, so registering
50
64
  // the pack and importing the connector can never arm two different ceilings.
51
65
  rateBudget: RATE_BUDGET,
@@ -56,8 +70,8 @@ export const pack: TwinPack = {
56
70
  resources: ['customer', 'product', 'subscription', 'order', 'checkout', 'benefit', 'discount', 'meter', 'event', 'customer_session', 'webhook_endpoint'],
57
71
  specSource: 'polar-conformance.ts (endpoint/resource inventory from the Polar API docs at docs.polar.sh)',
58
72
  description: 'Polar merchant-of-record billing twin — customers, products, subscriptions, orders, checkouts (with confirm → subscription/order), benefits, discounts, meters, usage events, customer sessions, and webhook endpoints. Kernel-backed.',
59
- // Adoption + interception, moved off the central maps unchanged (TWIN-PACK-CONTRACT
60
- // back-migration 2026-08-31). Both the production API host and Polar's own sandbox host are
73
+ // Adoption + interception, moved off the central maps unchanged (descriptor-first back-migration, adding-a-twin.md §3,
74
+ // 2026-08-31). Both the production API host and Polar's own sandbox host are
61
75
  // claimed — an integration picks one by configuration, and claiming only production would let
62
76
  // sandbox traffic escape the world.
63
77
  adoption: {
@@ -68,3 +82,5 @@ export const pack: TwinPack = {
68
82
  hosts: [{ host: 'api.polar.sh' }, { host: 'sandbox-api.polar.sh' }],
69
83
  browserRouting: { apiPathPrefix: '/v1', loaderHost: 'https://api.polar.sh' },
70
84
  };
85
+ // registered at import: the kernel learns the pack's state system and its references (protocol 2)
86
+ registerPack(pack);
@@ -1,7 +1,7 @@
1
1
  // Polar's CLIENT-SIDE RATE BUDGET — the pack's DECLARATION (the numbers) plus `guardPolarClient`,
2
2
  // the choke point every live Polar call goes through. The MECHANISM — the durable token-keyed
3
3
  // ledger, the rolling window, reserve-under-lock, the `Retry-After`/429 cooldown, fail-CLOSED on a
4
- // corrupt ledger — lives ONCE in the vendor-agnostic kernel (`@volter/twin` → `rateBudget.ts`).
4
+ // corrupt ledger — lives ONCE in the vendor-agnostic kernel (`@volter/world-core` → `rateBudget.ts`).
5
5
  // Read that module's header for the full rationale AND for the honest list of what the guard does
6
6
  // NOT guarantee (an injected clock or ledger path still defeats it — it guards carelessness, not
7
7
  // malice). This module is modeled on notion-budget.ts, the reference injected-client decorator.
@@ -47,11 +47,12 @@ import {
47
47
  rateBudgetPath,
48
48
  rateBudgetWeight,
49
49
  RateBudget,
50
+ RateBudgetError,
50
51
  type RateBudgetDeclaration,
51
52
  type RateBudgetOptions,
52
53
  type RateBudgetReservation,
53
54
  type RateBudgetSnapshot,
54
- } from '@volter/twin';
55
+ } from '@volter/world-core';
55
56
  import type { PolarLikeClient } from './polar-connector.ts';
56
57
 
57
58
  const VENDOR = 'polar';
@@ -153,8 +154,8 @@ export class PolarBudget extends RateBudget {
153
154
  }
154
155
  }
155
156
 
156
- export type { RateBudgetErrorKind as PolarBudgetErrorKind } from '@volter/twin';
157
- export { RateBudgetError as PolarBudgetError } from '@volter/twin';
157
+ export type { RateBudgetErrorKind as PolarBudgetErrorKind } from '@volter/world-core';
158
+ export { RateBudgetError as PolarBudgetError } from '@volter/world-core';
158
159
  export type PolarBudgetReservation = RateBudgetReservation;
159
160
  export type PolarBudgetSnapshot = RateBudgetSnapshot;
160
161
 
@@ -253,6 +254,21 @@ export function guardPolarClient(client: PolarLikeClient, opts: PolarBudgetedOpt
253
254
  else budget.recordCall(weight, undefined, { reservation });
254
255
  };
255
256
 
257
+ /**
258
+ * Settle once the call RESOLVED: the vendor has answered. recordCall arms any cooldown before
259
+ * it throws (a back-off beyond the cap), so that refusal is swallowed and the answer kept — a
260
+ * write the vendor accepted is never reported failed and performed again on retry. A resolved
261
+ * answer that carries a non-2xx status still lets the louder refusal win.
262
+ */
263
+ const settleAnswered = (weight: number, reservation: RateBudgetReservation, v: unknown): void => {
264
+ try {
265
+ settle(weight, reservation, v);
266
+ } catch (e) {
267
+ const status = responseStatus(v);
268
+ if (!(e instanceof RateBudgetError) || (status !== undefined && (status < 200 || status >= 300))) throw e;
269
+ }
270
+ };
271
+
256
272
  /**
257
273
  * Charge, call, settle — for a MODELED method, whose interface declares it `async`. Refusing
258
274
  * REJECTS rather than throwing synchronously, so `client.x().catch(…)` behaves exactly as it does
@@ -265,7 +281,7 @@ export function guardPolarClient(client: PolarLikeClient, opts: PolarBudgetedOpt
265
281
  const reservation = budget.checkBudget(weight);
266
282
  try {
267
283
  const res = await invoke(...args);
268
- settle(weight, reservation, res);
284
+ settleAnswered(weight, reservation, res);
269
285
  return res;
270
286
  } catch (e) {
271
287
  settle(weight, reservation, e); // may throw its own louder refusal, which wins
@@ -304,7 +320,7 @@ export function guardPolarClient(client: PolarLikeClient, opts: PolarBudgetedOpt
304
320
  : out;
305
321
  }
306
322
  return out.then(
307
- (res) => { settle(weight, reservation, res); return res; },
323
+ (res) => { settleAnswered(weight, reservation, res); return res; },
308
324
  (e: unknown) => { settle(weight, reservation, e); throw e; },
309
325
  );
310
326
  };
@@ -7,17 +7,15 @@
7
7
  import { mkdtempSync, rmSync } from 'node:fs';
8
8
  import { tmpdir } from 'node:os';
9
9
  import { join } from 'node:path';
10
- import { checkCapabilities, type CapabilityReport, type CapabilitySpec, verifyBoundary } from '@volter/twin-tooling';
10
+ import { checkCapabilities, type CapabilityReport, type CapabilitySpec, verifyBoundary } from '@volter/world-tooling';
11
11
  import { checkPolarConformance } from './polar-conformance.ts';
12
- import { syncPolarFromReal, pushPendingPolarActions, type PolarLikeClient } from './polar-connector.ts';
12
+ import { syncPolarFromReal, performPolarAction, type PolarLikeClient } from './polar-connector.ts';
13
13
  import { handlePolarTwinRequest, type PolarResponse } from './polar-twin.ts';
14
14
 
15
15
  const done = (id: string, area: string, title: string, dimension: CapabilitySpec['dimension'], tier: CapabilitySpec['tier'], verify: CapabilitySpec['verify']): CapabilitySpec =>
16
16
  ({ id, area, title, dimension, tier, expected: 'done', verify });
17
17
  const todo = (id: string, area: string, title: string, dimension: CapabilitySpec['dimension'], tier: CapabilitySpec['tier']): CapabilitySpec =>
18
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
19
 
22
20
  // ── API verify: drive REAL requests against a fresh isolated root, then assert status/shape ──
23
21
  type Step = { m: string; p: string; b?: unknown };
@@ -39,7 +37,7 @@ const id = (r: PolarResponse) => (r.body as Body)?.id as string;
39
37
  const field = (r: PolarResponse, k: string) => (r.body as Body)?.[k];
40
38
  const items = (r: PolarResponse) => (r.body as Body)?.items as Body[];
41
39
  /** A Polar ResourceNotFound 404 (the real error envelope). */
42
- const isNotFound = (r: PolarResponse) => r.status === 404 && (r.body as Body)?.type === 'ResourceNotFound';
40
+ const isNotFound = (r: PolarResponse) => r.status === 404 && (r.body as Body)?.error === 'ResourceNotFound'; // Polar's envelope: { error, detail }
43
41
  /** A FastAPI/Polar 422 validation envelope. */
44
42
  const isValidation = (r: PolarResponse) => r.status === 422 && Array.isArray((r.body as Body)?.detail);
45
43
 
@@ -241,6 +239,47 @@ export const POLAR_CAPABILITIES: CapabilitySpec[] = [
241
239
  return (field(s, 'active_meters') as Body[])[0]!.balance === 3;
242
240
  })),
243
241
 
242
+ // ── The customer by YOUR id, meters as Polar keeps them (2026-09-07, the platform's billing) ──
243
+ done('polar.customers.get_external_state', 'customers', 'GET /customers/external/{id} and its /state: the customer by external id; unknown → 404', 'api', 'core', () =>
244
+ withRoot(async (h) => {
245
+ await h({ m: 'POST', p: '/v1/customers/', b: { email: 'ext@example.test', external_id: 'org_1' } });
246
+ const g = await h({ m: 'GET', p: '/v1/customers/external/org_1' });
247
+ const s = await h({ m: 'GET', p: '/v1/customers/external/org_1/state' });
248
+ const miss = await h({ m: 'GET', p: '/v1/customers/external/nobody/state' });
249
+ return ok(g) && field(g, 'external_id') === 'org_1' && ok(s) && Array.isArray(field(s, 'active_subscriptions')) && isNotFound(miss);
250
+ })),
251
+ done('polar.checkouts.external_customer_id', 'checkouts', 'A checkout created with external_customer_id confirms onto the customer that id names (created if new)', 'api', 'core', () =>
252
+ withRoot(async (h) => {
253
+ const p = await h({ m: 'POST', p: '/v1/products/', b: { name: 'Team', recurring_interval: 'month', prices: [{ price_amount: 2000, price_currency: 'usd' }] } });
254
+ const c = await h({ m: 'POST', p: '/v1/checkouts/', b: { products: [id(p)], external_customer_id: 'org_42', customer_email: 'pay@example.test' } });
255
+ const done = await h({ m: 'POST', p: `/v1/checkouts/${id(c)}/confirm`, b: {} });
256
+ const s = await h({ m: 'GET', p: '/v1/customers/external/org_42/state' });
257
+ const subs = field(s, 'active_subscriptions') as Body[];
258
+ return c.status === 201 && ok(done) && ok(s) && subs.length === 1 && subs[0]!.status === 'active';
259
+ })),
260
+ done('polar.events.ingest_dedupes_external_id', 'events', 'Ingest counts a repeated external_id as a duplicate and resolves external_customer_id to the customer', 'api', 'core', () =>
261
+ withRoot(async (h) => {
262
+ const first = await h({ m: 'POST', p: '/v1/events/ingest', b: { events: [{ name: 'world.hour', external_customer_id: 'org_7', external_id: 'org_7:h:1', metadata: { units: 1 } }] } });
263
+ const again = await h({ m: 'POST', p: '/v1/events/ingest', b: { events: [{ name: 'world.hour', external_customer_id: 'org_7', external_id: 'org_7:h:1', metadata: { units: 1 } }] } });
264
+ const list = await h({ m: 'GET', p: '/v1/events/?external_customer_id=org_7' });
265
+ const cust = await h({ m: 'GET', p: '/v1/customers/external/org_7' });
266
+ return field(first, 'inserted') === 1 && field(again, 'duplicates') === 1 && field(again, 'inserted') === 0 && (field(list, 'items') as unknown[]).length === 1 && ok(cust);
267
+ })),
268
+ done('polar.customers.state_per_meter_aggregation', 'customers', 'With meters defined, state carries one entry per meter aggregated by its filter and function', 'api', 'core', () =>
269
+ withRoot(async (h) => {
270
+ const m = await h({ m: 'POST', p: '/v1/meters/', b: { name: 'world_hours', filter: { conjunction: 'and', clauses: [{ property: 'name', operator: 'eq', value: 'world.hour' }] }, aggregation: { func: 'sum', property: 'units' } } });
271
+ await h({ m: 'POST', p: '/v1/meters/', b: { name: 'pushes', filter: { conjunction: 'and', clauses: [{ property: 'name', operator: 'eq', value: 'world.push' }] }, aggregation: { func: 'count' } } });
272
+ await h({ m: 'POST', p: '/v1/events/ingest', b: { events: [
273
+ { name: 'world.hour', external_customer_id: 'org_9', external_id: 'a', metadata: { units: 2 } },
274
+ { name: 'world.hour', external_customer_id: 'org_9', external_id: 'b', metadata: { units: 3 } },
275
+ { name: 'world.push', external_customer_id: 'org_9', external_id: 'c' },
276
+ ] } });
277
+ const s = await h({ m: 'GET', p: '/v1/customers/external/org_9/state' });
278
+ const meters = field(s, 'active_meters') as Body[];
279
+ const hours = meters.find((x) => x.meter_id === id(m)); const pushes = meters.find((x) => x.meter_id !== id(m));
280
+ return meters.length === 2 && hours?.consumed_units === 5 && pushes?.consumed_units === 1;
281
+ })),
282
+
244
283
  // ── Customer sessions ───────────────────────────────────────────────────────────────
245
284
  done('polar.customer_sessions.create', 'customer-sessions', 'Create a customer portal session; unknown customer → 404', 'api', 'common', () =>
246
285
  withRoot(async (h) => {
@@ -298,18 +337,17 @@ export const POLAR_CAPABILITIES: CapabilitySpec[] = [
298
337
  rmSync(root, { recursive: true, force: true });
299
338
  }
300
339
  })),
301
- done('polar.connector.push.entrypoint', 'connector', 'pushPendingPolarActions pushes a local customer.create to the injected client', 'connector', 'core', () =>
340
+ done('polar.connector.push.entrypoint', 'connector', 'performPolarAction crosses a local customer.create to Polar through the executor (protocol 2)', 'connector', 'core', () =>
302
341
  withRoot(async () => {
303
342
  const root = mkdtempSync(join(tmpdir(), 'polar-push-'));
304
343
  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;
344
+ const made = await handlePolarTwinRequest({ method: 'POST', path: '/v1/customers/', body: JSON.stringify({ email: 'push@example.test', name: 'P' }), root });
345
+ const wire: Array<{ method: string; path: string; body?: string | Uint8Array }> = [];
346
+ const execute = async (r: { method: string; path: string; body?: string | Uint8Array }) => { wire.push(r); return { status: 201, headers: {}, body: JSON.stringify({ id: 'cus_pushed' }) }; };
347
+ const out = await performPolarAction(execute, { operation: 'customer.create', subject: { type: 'customer', id: `customer:${String(field(made, 'id'))}` }, fields: { email: 'push@example.test', name: 'P' } } as never, { resolve: (_t, l) => l, service: 'polar', root });
348
+ const sent = JSON.parse(typeof wire[0]?.body === 'string' ? wire[0].body : Buffer.from(wire[0]?.body ?? '{}').toString()) as Body;
349
+ // one entry, one wire write, the vendor's id back; re-performing is the kernel's to refuse
350
+ return out.externalId === 'cus_pushed' && wire.length === 1 && wire[0]!.path === '/v1/customers' && sent.email === 'push@example.test';
313
351
  } finally {
314
352
  rmSync(root, { recursive: true, force: true });
315
353
  }
@@ -320,9 +358,6 @@ export const POLAR_CAPABILITIES: CapabilitySpec[] = [
320
358
  return report.ok && report.endpointsChecked >= 30 && report.resourceTypesChecked >= 10;
321
359
  }),
322
360
 
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
361
 
327
362
  // ── the remaining real Polar surface (the honest TODO denominator) ─────────────────────
328
363
  ...[
@@ -380,9 +415,9 @@ export const POLAR_CAPABILITIES: CapabilitySpec[] = [
380
415
 
381
416
  // ── Missing-area sweep (TWIN-87 / F1) ────────────────────────────────────────────────────
382
417
  // /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).
418
+ // `payments` area held only a real-processing phantom (list/read was unrepresented).
384
419
  // 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
420
+ // is not the same as real card processing, which is what the real root does). See
386
421
  // POLAR_AREAS below + polar-capabilities.test.ts's area-census meta-test, gate-wired so a
387
422
  // future whole-area omission fails here instead of waiting for another review pass.
388
423
  todo('polar.metrics.query', 'metrics', 'Metrics: aggregated revenue/orders/subscribers time-series (GET /v1/metrics/)', 'api', 'niche'),
@@ -12,8 +12,8 @@
12
12
  //
13
13
  // The vendor I/O is an INJECTED client interface (`PolarLikeClient`): a fake in tests, a real
14
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';
15
+ import { observeResources } from '@volter/world-core';
16
+ import type { PerformContext, PushOutcome, RemoteExecute, SyncResource, TwinAction } from '@volter/world-core';
17
17
  //
18
18
  // ── The client-side RATE BUDGET is not optional here ────────────────────────────────────────
19
19
  // Polar publishes 500 requests/minute in production and 100/minute in sandbox per organization, and
@@ -29,7 +29,7 @@ const SERVICE = 'polar';
29
29
  export type { PolarBudgetedOptions };
30
30
 
31
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 };
32
+ export type PolarProduct = { id: string; name?: string | null; description?: string | null; recurring_interval?: string | null; is_archived?: boolean; created_at?: string; prices?: unknown[]; trial_interval?: string | null; trial_interval_count?: number | null };
33
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
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
35
 
@@ -53,7 +53,7 @@ export function mapCustomer(c: PolarCustomer): SyncResource {
53
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
54
  }
55
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: {} } };
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, trial_interval: p.trial_interval ?? null, trial_interval_count: p.trial_interval_count ?? null, prices: p.prices ?? [], benefits: [], metadata: {} } };
57
57
  }
58
58
  export function mapSubscription(s: PolarSubscription): SyncResource {
59
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: {} } };
@@ -101,8 +101,43 @@ export async function syncPolarFromReal(
101
101
  ...(await pullPolarSubscriptions(client)),
102
102
  ...(await pullPolarOrders(client)),
103
103
  ];
104
- const result = syncPull({ service: SERVICE, resources, occurredAt, ...(opts.root !== undefined ? { root: opts.root } : {}) });
105
- return { observed: result.observed, deltasAppended: result.deltasAppended };
104
+ // protocol 2: an observation lands on the head through the kernel's fold — one batch, one instant
105
+ const report = observeResources(SERVICE, resources.map((r) => ({ type: r.type, id: r.id, fields: r.fields })), { ...(opts.root !== undefined ? { root: opts.root } : {}), at: occurredAt, batch: `obs:${SERVICE}:${occurredAt}` });
106
+ return { observed: report.observed, deltasAppended: report.appended };
107
+ }
108
+
109
+ // ── PROTOCOL 2: the state system's two adapters, over the kernel's executor ──────────
110
+ /** A PolarLikeClient over a RemoteExecute: the calls the SDK makes, as wire requests the executor carries. */
111
+ export function polarClientOver(execute: RemoteExecute): PolarLikeClient {
112
+ const call = async <T>(method: string, path: string, body?: Record<string, unknown>): Promise<T> => {
113
+ const res = await execute({ method, path, headers: { accept: 'application/json', ...(body ? { 'content-type': 'application/json' } : {}) }, ...(body ? { body: JSON.stringify(body) } : {}) });
114
+ if (res.status >= 300) throw new Error(`polar: ${method} ${path} answered ${res.status}: ${res.body.slice(0, 200)}`);
115
+ return (res.body === '' ? {} : JSON.parse(res.body)) as T;
116
+ };
117
+ const list = <T>(path: string) => async (): Promise<{ items: T[] }> => { const page = await call<{ items?: T[] }>('GET', `${path}?limit=100`); return { items: page.items ?? [] }; };
118
+ return {
119
+ customers: { list: list<PolarCustomer>('/v1/customers'), create: (params) => call<{ id: string }>('POST', '/v1/customers', params) },
120
+ products: { list: list<PolarProduct>('/v1/products'), create: (params) => call<{ id: string }>('POST', '/v1/products', params) },
121
+ subscriptions: { list: list<PolarSubscription>('/v1/subscriptions') },
122
+ orders: { list: list<PolarOrder>('/v1/orders') },
123
+ };
124
+ }
125
+ /** The refresh adapter: pull Polar's customers, products, subscriptions and orders through the executor and fold them into the root. */
126
+ export async function syncPolarFromRemote(execute: RemoteExecute, opts: { root?: string; origin?: string; occurredAt?: string } & PolarBudgetedOptions = {}): Promise<{ observed: number; deltasAppended: number }> {
127
+ // guarded HERE too (idempotent), so the source tooth sees every entrypoint hold the budget
128
+ const budget = { ...(opts.budgetOptions ?? {}), ...(opts.root !== undefined && !opts.budgetOptions?.root ? { root: opts.root } : {}) };
129
+ const client = guardPolarClient(polarClientOver(execute), polarBudgetOf({ ...opts, budgetOptions: budget }));
130
+ return syncPolarFromReal(client, { ...opts, budgetOptions: budget });
131
+ }
132
+ /** The perform adapter: one entry crosses to Polar through the executor; a customer or product it names by a local id resolves first. */
133
+ export async function performPolarAction(execute: RemoteExecute, action: TwinAction, ctx: PerformContext): Promise<PushOutcome> {
134
+ const fields = { ...(action.fields ?? {}) };
135
+ for (const [field, type] of [['customer_id', 'customer'], ['product_id', 'product']] as const) {
136
+ if (typeof fields[field] === 'string') fields[field] = ctx.resolve(type, kid(type, String(fields[field]).replace(new RegExp(`^${type}:`), ''))).replace(new RegExp(`^${type}:`), '');
137
+ }
138
+ const budget = { budgetOptions: { ...(ctx.root !== undefined ? { root: ctx.root } : {}) } };
139
+ const { externalId } = await pushPolarAction(guardPolarClient(polarClientOver(execute), polarBudgetOf(budget)), { operation: action.operation, subject: action.subject, fields }, budget);
140
+ return { externalId };
106
141
  }
107
142
 
108
143
  // ── PUSH ──────────────────────────────────────────────────────────────────────────
@@ -133,23 +168,5 @@ export async function pushPolarAction(
133
168
  * Push the twin's PENDING local actions to real Polar and CONFIRM each. A confirmed action is
134
169
  * no longer pending, so a re-push enacts NOTHING. Unpushable ops are skipped.
135
170
  */
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
- }
171
+ // protocol 2: the pending-actions loop is the kernel's (the head performs each entry through `performPolarAction`);
172
+ // the v1 `pushPendingPolarActions` is gone with the log it read.
@@ -7,8 +7,9 @@
7
7
  // kernel's ONE adaptation (`createTwinFetchFromHandler`); this file contributes only VALUES. The
8
8
  // vendor's 204 deletes return `{ status: 204, body: null }`, which the adapter's null-body rule
9
9
  // serves as a genuinely empty response. The server is one line of Bun.serve around that closure.
10
+ import { serveHttp } from '@volter/world-core';
10
11
  import { handlePolarTwinRequest } from './polar-twin.ts';
11
- import { createTwinFetchFromHandler, statefulTwinManifest } from '@volter/twin';
12
+ import { createTwinFetchFromHandler, statefulTwinManifest, twinPublicBase } from '@volter/world-core';
12
13
 
13
14
  /** Options every Polar-twin HTTP surface needs, independent of who owns the socket. */
14
15
  export interface PolarTwinFetchOptions {
@@ -17,14 +18,37 @@ export interface PolarTwinFetchOptions {
17
18
  }
18
19
 
19
20
  export function createPolarTwinFetch(options: PolarTwinFetchOptions = {}): (request: Request) => Promise<Response> {
20
- return createTwinFetchFromHandler(handlePolarTwinRequest, {
21
+ const api = createTwinFetchFromHandler(handlePolarTwinRequest, {
21
22
  ...options,
22
23
  manifest: statefulTwinManifest({ vendor: 'polar', twinOf: 'the Polar billing API', stores: 'customers, checkouts, subscriptions, meters and usage events' }),
24
+ // where this twin is reached (origin plus any served-World mount path): the checkout's hosted page is minted there
25
+ extras: (request) => ({ publicBase: twinPublicBase(request) }),
23
26
  });
27
+ // the hosted checkout page, at the twin's own address: what a person sees after "continue to checkout at
28
+ // Polar" in a walk — the product, the amount, a Pay button that confirms the checkout and returns to the
29
+ // app's success_url (in production this page is Polar's; the twin stands in for it, plainly labelled)
30
+ return async function polarFetch(request: Request): Promise<Response> {
31
+ const url = new URL(request.url);
32
+ const page = /^\/twin\/checkout\/([a-zA-Z0-9-]+)(\/pay)?$/.exec(url.pathname);
33
+ if (!page) return api(request);
34
+ const id = page[1]!;
35
+ const read = await api(new Request(`${url.origin}/v1/checkouts/${id}`, { headers: { authorization: request.headers.get('authorization') ?? 'Bearer polar_oat_page' } }));
36
+ if (!read.ok) return new Response('no such checkout', { status: 404 });
37
+ const c = (await read.json()) as { status: string; amount: number; currency: string; success_url: string | null; product?: { name?: string } | null; customer_email?: string | null };
38
+ if (page[2] && request.method === 'POST') {
39
+ const done = await api(new Request(`${url.origin}/v1/checkouts/${id}/confirm`, { method: 'POST', headers: { 'content-type': 'application/json', authorization: 'Bearer polar_oat_page' }, body: '{}' }));
40
+ if (!done.ok) return new Response(`the checkout could not be confirmed (${done.status})`, { status: 502 });
41
+ return new Response(null, { status: 303, headers: { location: c.success_url ?? `${twinPublicBase(request)}/twin/checkout/${id}` } });
42
+ }
43
+ const money = `$${(Number(c.amount ?? 0) / 100).toFixed(2)} ${String(c.currency ?? 'usd').toUpperCase()}`;
44
+ const esc = (v: unknown): string => String(v ?? '').replace(/[&<>"]/g, (ch) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[ch]!);
45
+ const html = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Checkout — Polar (twin)</title><style>body{font:16px/1.5 -apple-system,system-ui,sans-serif;margin:0;background:#f6f6f7;color:#111}main{max-width:480px;margin:48px auto;background:#fff;border:1px solid #e3e3e6;border-radius:12px;padding:28px 32px}h1{font-size:20px;margin:0 0 4px}.mode{color:#666;font-size:13px}.price{font-size:32px;font-weight:650;margin:12px 0}button{font:inherit;padding:10px 18px;border-radius:8px;border:0;background:#111;color:#fff;cursor:pointer}label{display:block;margin:12px 0 4px;font-size:13px;color:#555}input{width:100%;padding:8px 10px;border:1px solid #ccc;border-radius:6px;font:inherit}</style></head><body><main><p class="mode">Polar checkout · this page is the polar twin's stand-in for Polar's hosted checkout</p><h1>${esc(c.product?.name ?? 'Subscription')}</h1><p class="price">${esc(money)} <span class="mode">/ month</span></p>${c.status !== 'open' ? `<p>This checkout is ${esc(c.status)}.</p>` : `<form method="post" action="${esc(twinPublicBase(request))}/twin/checkout/${esc(id)}/pay"><label>Email</label><input name="email" value="${esc(c.customer_email ?? '')}" readonly /><label>Card</label><input name="card" value="4242 4242 4242 4242 · any date · any code" readonly /><p class="mode">A test card: nothing is charged here.</p><button type="submit">Pay ${esc(money)}</button></form>`}</main></body></html>`;
46
+ return new Response(html, { headers: { 'content-type': 'text/html; charset=utf-8' } });
47
+ };
24
48
  }
25
49
 
26
- export function createPolarTwinServer(options: { root?: string; port?: number; readOnly?: boolean } = {}): { port: number; stop: () => void } {
27
- const server = Bun.serve({
50
+ export async function createPolarTwinServer(options: { root?: string; port?: number; readOnly?: boolean } = {}): Promise<{ port: number; stop: () => void }> {
51
+ const server = await serveHttp({
28
52
  port: options.port ?? 0,
29
53
  idleTimeout: 60,
30
54
  fetch: createPolarTwinFetch(options),