thevoidforge 21.0.11 → 21.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dist/.claude/commands/ai.md +69 -0
  2. package/dist/.claude/commands/architect.md +121 -0
  3. package/dist/.claude/commands/assemble.md +201 -0
  4. package/dist/.claude/commands/assess.md +75 -0
  5. package/dist/.claude/commands/blueprint.md +135 -0
  6. package/dist/.claude/commands/build.md +116 -0
  7. package/dist/.claude/commands/campaign.md +201 -0
  8. package/dist/.claude/commands/cultivation.md +166 -0
  9. package/dist/.claude/commands/current.md +128 -0
  10. package/dist/.claude/commands/dangerroom.md +74 -0
  11. package/dist/.claude/commands/debrief.md +178 -0
  12. package/dist/.claude/commands/deploy.md +99 -0
  13. package/dist/.claude/commands/devops.md +143 -0
  14. package/dist/.claude/commands/gauntlet.md +140 -0
  15. package/dist/.claude/commands/git.md +104 -0
  16. package/dist/.claude/commands/grow.md +146 -0
  17. package/dist/.claude/commands/imagine.md +126 -0
  18. package/dist/.claude/commands/portfolio.md +50 -0
  19. package/dist/.claude/commands/prd.md +113 -0
  20. package/dist/.claude/commands/qa.md +107 -0
  21. package/dist/.claude/commands/review.md +151 -0
  22. package/dist/.claude/commands/security.md +100 -0
  23. package/dist/.claude/commands/test.md +96 -0
  24. package/dist/.claude/commands/thumper.md +116 -0
  25. package/dist/.claude/commands/treasury.md +100 -0
  26. package/dist/.claude/commands/ux.md +118 -0
  27. package/dist/.claude/commands/vault.md +189 -0
  28. package/dist/.claude/commands/void.md +108 -0
  29. package/dist/CHANGELOG.md +1918 -0
  30. package/dist/CLAUDE.md +250 -0
  31. package/dist/HOLOCRON.md +856 -0
  32. package/dist/VERSION.md +123 -0
  33. package/dist/docs/NAMING_REGISTRY.md +478 -0
  34. package/dist/docs/methods/AI_INTELLIGENCE.md +276 -0
  35. package/dist/docs/methods/ASSEMBLER.md +142 -0
  36. package/dist/docs/methods/BACKEND_ENGINEER.md +165 -0
  37. package/dist/docs/methods/BUILD_JOURNAL.md +185 -0
  38. package/dist/docs/methods/BUILD_PROTOCOL.md +426 -0
  39. package/dist/docs/methods/CAMPAIGN.md +568 -0
  40. package/dist/docs/methods/CONTEXT_MANAGEMENT.md +189 -0
  41. package/dist/docs/methods/DEEP_CURRENT.md +184 -0
  42. package/dist/docs/methods/DEVOPS_ENGINEER.md +295 -0
  43. package/dist/docs/methods/FIELD_MEDIC.md +261 -0
  44. package/dist/docs/methods/FORGE_ARTIST.md +108 -0
  45. package/dist/docs/methods/FORGE_KEEPER.md +268 -0
  46. package/dist/docs/methods/GAUNTLET.md +344 -0
  47. package/dist/docs/methods/GROWTH_STRATEGIST.md +466 -0
  48. package/dist/docs/methods/HEARTBEAT.md +168 -0
  49. package/dist/docs/methods/MCP_INTEGRATION.md +139 -0
  50. package/dist/docs/methods/MUSTER.md +148 -0
  51. package/dist/docs/methods/PRD_GENERATOR.md +186 -0
  52. package/dist/docs/methods/PRODUCT_DESIGN_FRONTEND.md +250 -0
  53. package/dist/docs/methods/QA_ENGINEER.md +337 -0
  54. package/dist/docs/methods/RELEASE_MANAGER.md +145 -0
  55. package/dist/docs/methods/SECURITY_AUDITOR.md +320 -0
  56. package/dist/docs/methods/SUB_AGENTS.md +335 -0
  57. package/dist/docs/methods/SYSTEMS_ARCHITECT.md +171 -0
  58. package/dist/docs/methods/TESTING.md +359 -0
  59. package/dist/docs/methods/THUMPER.md +175 -0
  60. package/dist/docs/methods/TIME_VAULT.md +120 -0
  61. package/dist/docs/methods/TREASURY.md +184 -0
  62. package/dist/docs/methods/TROUBLESHOOTING.md +265 -0
  63. package/dist/docs/patterns/README.md +52 -0
  64. package/dist/docs/patterns/ad-billing-adapter.ts +537 -0
  65. package/dist/docs/patterns/ad-platform-adapter.ts +421 -0
  66. package/dist/docs/patterns/ai-classifier.ts +195 -0
  67. package/dist/docs/patterns/ai-eval.ts +272 -0
  68. package/dist/docs/patterns/ai-orchestrator.ts +341 -0
  69. package/dist/docs/patterns/ai-router.ts +194 -0
  70. package/dist/docs/patterns/ai-tool-schema.ts +237 -0
  71. package/dist/docs/patterns/api-route.ts +241 -0
  72. package/dist/docs/patterns/backtest-engine.ts +499 -0
  73. package/dist/docs/patterns/browser-review.ts +292 -0
  74. package/dist/docs/patterns/combobox.tsx +300 -0
  75. package/dist/docs/patterns/component.tsx +262 -0
  76. package/dist/docs/patterns/daemon-process.ts +338 -0
  77. package/dist/docs/patterns/data-pipeline.ts +297 -0
  78. package/dist/docs/patterns/database-migration.ts +466 -0
  79. package/dist/docs/patterns/e2e-test.ts +629 -0
  80. package/dist/docs/patterns/error-handling.ts +312 -0
  81. package/dist/docs/patterns/execution-safety.ts +601 -0
  82. package/dist/docs/patterns/financial-transaction.ts +342 -0
  83. package/dist/docs/patterns/funding-plan.ts +462 -0
  84. package/dist/docs/patterns/game-entity.ts +137 -0
  85. package/dist/docs/patterns/game-loop.ts +113 -0
  86. package/dist/docs/patterns/game-state.ts +143 -0
  87. package/dist/docs/patterns/job-queue.ts +225 -0
  88. package/dist/docs/patterns/kongo-integration.ts +164 -0
  89. package/dist/docs/patterns/middleware.ts +363 -0
  90. package/dist/docs/patterns/mobile-screen.tsx +139 -0
  91. package/dist/docs/patterns/mobile-service.ts +167 -0
  92. package/dist/docs/patterns/multi-tenant.ts +382 -0
  93. package/dist/docs/patterns/oauth-token-lifecycle.ts +223 -0
  94. package/dist/docs/patterns/outbound-rate-limiter.ts +260 -0
  95. package/dist/docs/patterns/prompt-template.ts +195 -0
  96. package/dist/docs/patterns/revenue-source-adapter.ts +311 -0
  97. package/dist/docs/patterns/service.ts +224 -0
  98. package/dist/docs/patterns/sse-endpoint.ts +118 -0
  99. package/dist/docs/patterns/stablecoin-adapter.ts +511 -0
  100. package/dist/docs/patterns/third-party-script.ts +68 -0
  101. package/dist/scripts/thumper/gom-jabbar.sh +241 -0
  102. package/dist/scripts/thumper/relay.sh +610 -0
  103. package/dist/scripts/thumper/scan.sh +359 -0
  104. package/dist/scripts/thumper/thumper.sh +190 -0
  105. package/dist/scripts/thumper/water-rings.sh +76 -0
  106. package/package.json +1 -1
  107. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,537 @@
1
+ /**
2
+ * Pattern: Ad Billing Adapter (Split Interface)
3
+ *
4
+ * Key principles:
5
+ * - Billing is SEPARATE from campaign CRUD. This adapter handles how ad platforms
6
+ * get paid (invoices, direct debits, settlements), not how campaigns are managed.
7
+ * Campaign operations live in ad-platform-adapter.ts.
8
+ * - Three capability states determine what Treasury can automate:
9
+ * FULLY_FUNDABLE — Treasury manages settlement lifecycle end-to-end
10
+ * MONITORED_ONLY — spend and billing are observed, but funding is not automated
11
+ * UNSUPPORTED — billing configuration blocks any automation
12
+ * - Split interface: AdBillingSetup (interactive) + AdBillingAdapter (runtime)
13
+ * - Single-writer: only Heartbeat daemon calls the runtime adapter
14
+ * - All amounts in branded integer cents (Cents type from financial-transaction.ts)
15
+ *
16
+ * Agents: Dockson (treasury), Wax (paid ads), Heartbeat daemon
17
+ *
18
+ * PRD Reference: §10.2, §11.1B, §12.3
19
+ *
20
+ * Google Ads Billing API reference:
21
+ * Billing setup: managed via Google Ads UI — no API for enabling monthly invoicing.
22
+ * Read-only API paths (Ads API v16+):
23
+ * BillingSetup resource → billingSetup.status, paymentsAccount, paymentsProfile
24
+ * Invoice resource → GET /customers/{id}/invoices (monthly invoicing accounts only)
25
+ * Fields: invoice.id, invoice.type, invoice.due_date, invoice.total_amount_micros
26
+ * Note: amounts in micros (1/1,000,000 of currency unit). Divide by 10,000 for cents.
27
+ * payments_account_id identifies the payments profile linked to billing.
28
+ * monthly_invoicing must be enabled by Google — not programmatically toggleable.
29
+ *
30
+ * Meta Ads Billing reference:
31
+ * No first-party invoice API. Billing is managed via:
32
+ * GET /act_{id}?fields=funding_source,funding_source_details → funding method classification
33
+ * funding_source_details.type: 1=credit_card, 2=debit_card, 4=direct_debit,
34
+ * 5=paypal, 8=extended_credit, 11=invoice
35
+ * Direct debit: Meta pulls from linked bank. No settlement instruction needed.
36
+ * Extended credit / invoicing: available to qualifying business accounts.
37
+ * For direct debit accounts, Treasury maintains a buffer; for invoiced accounts,
38
+ * Treasury tracks due dates and settlement instructions.
39
+ */
40
+
41
+ // ── Branded Financial Types (from financial-transaction.ts) ──
42
+
43
+ type Cents = number & { readonly __brand: 'Cents' };
44
+
45
+ function toCents(dollars: number): Cents {
46
+ return Math.round(dollars * 100) as Cents;
47
+ }
48
+
49
+ // ── Capability and Billing Types ─────────────────────
50
+
51
+ type CapabilityState = 'FULLY_FUNDABLE' | 'MONITORED_ONLY' | 'UNSUPPORTED';
52
+
53
+ type BillingMode =
54
+ | 'monthly_invoicing' // Google: approved monthly invoicing
55
+ | 'direct_debit' // Meta: bank-backed autopay
56
+ | 'extended_credit' // Meta: extended credit / invoice path
57
+ | 'manual_bank_transfer' // manual wire/ACH with reference numbers
58
+ | 'card_only' // credit/debit card — not automatable
59
+ | 'unknown'; // could not determine billing mode
60
+
61
+ type AdPlatform = 'google' | 'meta' | 'tiktok' | 'linkedin' | 'twitter' | 'reddit' | 'snap';
62
+
63
+ // ── Invoice and Debit Types ─────────────────────────
64
+
65
+ interface Invoice {
66
+ id: string; // platform-specific invoice ID
67
+ platform: AdPlatform;
68
+ externalAccountId: string;
69
+ amountCents: Cents;
70
+ currency: 'USD';
71
+ issueDate: string; // ISO 8601
72
+ dueDate: string; // ISO 8601
73
+ status: 'pending' | 'paid' | 'overdue' | 'cancelled';
74
+ lineItems?: InvoiceLineItem[];
75
+ paymentReference?: string; // bank reference for settlement matching
76
+ }
77
+
78
+ interface InvoiceLineItem {
79
+ description: string;
80
+ amountCents: Cents;
81
+ campaignId?: string;
82
+ }
83
+
84
+ interface ExpectedDebit {
85
+ id: string; // internal tracking ID
86
+ platform: AdPlatform;
87
+ externalAccountId: string;
88
+ estimatedAmountCents: Cents; // estimated debit based on recent spend
89
+ currency: 'USD';
90
+ expectedDate: string; // ISO 8601 — estimated debit date
91
+ status: 'expected' | 'detected' | 'confirmed' | 'failed';
92
+ bankTransactionId?: string; // set once debit is detected in bank
93
+ }
94
+
95
+ // ── Settlement Types ────────────────────────────────
96
+
97
+ interface SettlementInstruction {
98
+ invoiceId: string;
99
+ platform: AdPlatform;
100
+ payeeName: string; // e.g., "Google Ads" or "Meta Platforms Inc"
101
+ paymentMethod: 'wire' | 'ach' | 'direct_debit';
102
+ amountCents: Cents;
103
+ currency: 'USD';
104
+ dueDate: string; // ISO 8601
105
+ bankReference?: string; // reference number for wire/ACH
106
+ routingNumber?: string; // destination routing (for wire/ACH to platform)
107
+ accountNumber?: string; // destination account (for wire/ACH to platform)
108
+ notes: string; // human-readable settlement summary
109
+ }
110
+
111
+ // ── Platform Billing Profile (PRD §12.3) ────────────
112
+
113
+ interface PlatformBillingProfile {
114
+ platform: AdPlatform;
115
+ capabilityState: CapabilityState;
116
+ billingMode: BillingMode;
117
+ externalAccountId: string; // Google: customer ID, Meta: ad account ID
118
+ billingSetupId?: string; // Google: billing setup resource name
119
+ invoiceGroupId?: string; // Google: invoice group for consolidated billing
120
+ paymentProfileId?: string; // Google: payments profile ID
121
+ fundingSourceId?: string; // Meta: funding source ID
122
+ currency: 'USD';
123
+ nextDueDate?: string; // ISO 8601
124
+ status: 'active' | 'degraded' | 'suspended' | 'unconfigured';
125
+ lastVerifiedAt: string; // ISO 8601
126
+ }
127
+
128
+ // ── Billing Configuration ───────────────────────────
129
+
130
+ interface BillingConfiguration {
131
+ billingMode: BillingMode;
132
+ accountIds: {
133
+ externalAccountId: string;
134
+ billingSetupId?: string;
135
+ invoiceGroupId?: string;
136
+ paymentProfileId?: string;
137
+ fundingSourceId?: string;
138
+ };
139
+ nextDueDate?: string;
140
+ estimatedMonthlySpendCents?: Cents;
141
+ }
142
+
143
+ // ── Normalized Funding State ────────────────────────
144
+
145
+ interface NormalizedFundingState {
146
+ platform: AdPlatform;
147
+ capabilityState: CapabilityState;
148
+ billingMode: BillingMode;
149
+ outstandingCents: Cents; // unpaid invoices or expected debits
150
+ nextPaymentDueDate?: string;
151
+ daysUntilNextPayment?: number;
152
+ fundingHealthy: boolean; // true if no overdue invoices and capability is not degraded
153
+ warnings: string[];
154
+ }
155
+
156
+ // ── Date Range ──────────────────────────────────────
157
+
158
+ interface DateRange {
159
+ start: string; // ISO 8601
160
+ end: string; // ISO 8601
161
+ }
162
+
163
+ // ── Interactive Setup Interface ─────────────────────
164
+ // Runs in CLI or Danger Room during `/grow --setup`.
165
+ // Classifies each platform's billing capability.
166
+
167
+ interface AdBillingSetup {
168
+ /** Determine what level of funding automation this platform supports */
169
+ verifyBillingCapability(
170
+ platform: AdPlatform,
171
+ externalAccountId: string,
172
+ tokens: { accessToken: string }
173
+ ): Promise<CapabilityState>;
174
+
175
+ /** Read the billing configuration details for storage in vault */
176
+ readBillingConfiguration(
177
+ platform: AdPlatform,
178
+ externalAccountId: string,
179
+ tokens: { accessToken: string }
180
+ ): Promise<BillingConfiguration>;
181
+
182
+ /** Auto-detect the billing mode from platform API responses */
183
+ detectBillingMode(
184
+ platform: AdPlatform,
185
+ externalAccountId: string,
186
+ tokens: { accessToken: string }
187
+ ): Promise<BillingMode>;
188
+ }
189
+
190
+ // ── Runtime Adapter Interface ───────────────────────
191
+ // Runs in heartbeat daemon ONLY — non-interactive, autonomous.
192
+ // Single-writer: no other process may call these methods.
193
+
194
+ interface AdBillingAdapter {
195
+ /** Current capability state — may degrade at runtime if billing config changes */
196
+ getCapabilityState(platform: AdPlatform): Promise<CapabilityState>;
197
+
198
+ /** List pending and paid invoices in date range (Google monthly invoicing) */
199
+ readInvoices(platform: AdPlatform, dateRange: DateRange): Promise<Invoice[]>;
200
+
201
+ /** List expected bank debits in date range (Meta direct debit) */
202
+ readExpectedDebits(platform: AdPlatform, dateRange: DateRange): Promise<ExpectedDebit[]>;
203
+
204
+ /** Generate a settlement instruction for a specific invoice */
205
+ generateSettlementInstructions(invoice: Invoice): Promise<SettlementInstruction>;
206
+
207
+ /** Confirm that an invoice was settled by linking to a bank transaction */
208
+ confirmSettlement(invoiceId: string, bankTransactionId: string): Promise<{
209
+ confirmed: boolean;
210
+ reconciledAmountCents: Cents;
211
+ varianceCents: Cents;
212
+ }>;
213
+
214
+ /** Unified funding view across all connected platforms */
215
+ normalizeFundingState(): Promise<NormalizedFundingState[]>;
216
+ }
217
+
218
+ // ── Reference Implementation: Google Billing ─────────
219
+ // Production implementation would live in wizard/lib/financial/billing/google-billing.ts
220
+
221
+ class GoogleBillingAdapter implements AdBillingSetup, AdBillingAdapter {
222
+ private readonly adsApiUrl = 'https://googleads.googleapis.com/v16';
223
+ private profiles: Map<string, PlatformBillingProfile> = new Map();
224
+
225
+ // ── Setup (interactive) ──────────
226
+
227
+ async verifyBillingCapability(
228
+ _platform: AdPlatform,
229
+ externalAccountId: string,
230
+ tokens: { accessToken: string }
231
+ ): Promise<CapabilityState> {
232
+ const mode = await this.detectBillingMode('google', externalAccountId, tokens);
233
+ if (mode === 'monthly_invoicing') return 'FULLY_FUNDABLE';
234
+ if (mode === 'manual_bank_transfer') return 'MONITORED_ONLY';
235
+ if (mode === 'unknown' || mode === 'card_only') return 'UNSUPPORTED';
236
+ return 'MONITORED_ONLY';
237
+ }
238
+
239
+ async readBillingConfiguration(
240
+ _platform: AdPlatform,
241
+ externalAccountId: string,
242
+ tokens: { accessToken: string }
243
+ ): Promise<BillingConfiguration> {
244
+ // Google Ads API: query billing_setup resource
245
+ // SELECT billing_setup.id, billing_setup.status, billing_setup.payments_account,
246
+ // billing_setup.payments_profile
247
+ // FROM billing_setup
248
+ const billingSetup = await this.queryBillingSetup(externalAccountId, tokens);
249
+ const mode = this.classifyGoogleBillingMode(billingSetup);
250
+
251
+ return {
252
+ billingMode: mode,
253
+ accountIds: {
254
+ externalAccountId,
255
+ billingSetupId: billingSetup.id as string | undefined,
256
+ paymentProfileId: billingSetup.paymentsProfile as string | undefined,
257
+ },
258
+ };
259
+ }
260
+
261
+ async detectBillingMode(
262
+ _platform: AdPlatform,
263
+ externalAccountId: string,
264
+ tokens: { accessToken: string }
265
+ ): Promise<BillingMode> {
266
+ const billingSetup = await this.queryBillingSetup(externalAccountId, tokens);
267
+ return this.classifyGoogleBillingMode(billingSetup);
268
+ }
269
+
270
+ // ── Runtime (daemon) ──────────
271
+
272
+ async getCapabilityState(_platform: AdPlatform): Promise<CapabilityState> {
273
+ // Re-verify from cached profile — full re-check happens on scheduled interval
274
+ const profile = this.profiles.get('google');
275
+ return profile?.capabilityState ?? 'UNSUPPORTED';
276
+ }
277
+
278
+ async readInvoices(_platform: AdPlatform, dateRange: DateRange): Promise<Invoice[]> {
279
+ // Google Ads API: query invoice resource
280
+ // SELECT invoice.id, invoice.type, invoice.due_date,
281
+ // invoice.service_date_range, invoice.total_amount_micros,
282
+ // invoice.payments_account_id
283
+ // FROM invoice
284
+ // WHERE invoice.issue_date >= '{dateRange.start}'
285
+ // AND invoice.issue_date <= '{dateRange.end}'
286
+ //
287
+ // Amounts are in micros: divide by 10,000 to get cents
288
+ // e.g., 1,500,000,000 micros = $1,500.00 = 150,000 cents
289
+ throw new Error('HTTP implementation — use node:https with Google Ads REST API');
290
+ }
291
+
292
+ async readExpectedDebits(_platform: AdPlatform, _dateRange: DateRange): Promise<ExpectedDebit[]> {
293
+ // Google does not use direct debit in the monthly invoicing flow.
294
+ // This method returns empty for Google — debits are a Meta concept.
295
+ return [];
296
+ }
297
+
298
+ async generateSettlementInstructions(invoice: Invoice): Promise<SettlementInstruction> {
299
+ // Google monthly invoicing: payment via wire/ACH to Google's bank account
300
+ // Payment instructions are on the invoice itself.
301
+ // Reference number must be included for matching.
302
+ return {
303
+ invoiceId: invoice.id,
304
+ platform: 'google',
305
+ payeeName: 'Google Ads',
306
+ paymentMethod: 'wire',
307
+ amountCents: invoice.amountCents,
308
+ currency: 'USD',
309
+ dueDate: invoice.dueDate,
310
+ bankReference: invoice.paymentReference,
311
+ notes: `Google monthly invoice ${invoice.id} — wire payment with reference ${invoice.paymentReference ?? 'see invoice'}`,
312
+ };
313
+ }
314
+
315
+ async confirmSettlement(invoiceId: string, bankTransactionId: string): Promise<{
316
+ confirmed: boolean; reconciledAmountCents: Cents; varianceCents: Cents;
317
+ }> {
318
+ // Match bank transaction amount against invoice amount.
319
+ // In production: read from bank adapter + invoice store.
320
+ // Variance threshold: configurable, default 0 cents for exact match.
321
+ throw new Error('Implementation requires invoice store + bank adapter integration');
322
+ }
323
+
324
+ async normalizeFundingState(): Promise<NormalizedFundingState[]> {
325
+ const profile = this.profiles.get('google');
326
+ if (!profile) return [];
327
+
328
+ const invoices = await this.readInvoices('google', {
329
+ start: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString(),
330
+ end: new Date().toISOString(),
331
+ });
332
+ const pending = invoices.filter(i => i.status === 'pending' || i.status === 'overdue');
333
+ const outstandingCents = pending.reduce(
334
+ (sum, i) => (sum + i.amountCents) as Cents, 0 as Cents
335
+ );
336
+ const overdue = pending.some(i => i.status === 'overdue');
337
+
338
+ return [{
339
+ platform: 'google',
340
+ capabilityState: profile.capabilityState,
341
+ billingMode: profile.billingMode,
342
+ outstandingCents,
343
+ nextPaymentDueDate: profile.nextDueDate,
344
+ daysUntilNextPayment: profile.nextDueDate
345
+ ? Math.ceil((new Date(profile.nextDueDate).getTime() - Date.now()) / (24 * 60 * 60 * 1000))
346
+ : undefined,
347
+ fundingHealthy: !overdue && profile.status === 'active',
348
+ warnings: overdue ? ['Overdue Google Ads invoice — settlement required'] : [],
349
+ }];
350
+ }
351
+
352
+ // ── Private helpers ──────────
353
+
354
+ private async queryBillingSetup(
355
+ _externalAccountId: string, _tokens: { accessToken: string }
356
+ ): Promise<Record<string, unknown>> {
357
+ // POST {adsApiUrl}/customers/{id}/googleAds:searchStream
358
+ // Body: { query: "SELECT billing_setup.id, ..." }
359
+ // Headers: Authorization: Bearer {accessToken}, developer-token: {devToken}
360
+ throw new Error('HTTP implementation — use node:https with Google Ads REST API');
361
+ }
362
+
363
+ private classifyGoogleBillingMode(billingSetup: Record<string, unknown>): BillingMode {
364
+ const status = billingSetup.status as string | undefined;
365
+ if (status === 'APPROVED') return 'monthly_invoicing';
366
+ if (status === 'PENDING') return 'unknown';
367
+ if (status === 'CANCELLED') return 'unknown';
368
+ return 'unknown';
369
+ }
370
+ }
371
+
372
+ // ── Reference Implementation: Meta Billing ───────────
373
+ // Production implementation would live in wizard/lib/financial/billing/meta-billing.ts
374
+
375
+ class MetaBillingAdapter implements AdBillingSetup, AdBillingAdapter {
376
+ private readonly baseUrl = 'https://graph.facebook.com/v19.0';
377
+ private profiles: Map<string, PlatformBillingProfile> = new Map();
378
+
379
+ // ── Setup (interactive) ──────────
380
+
381
+ async verifyBillingCapability(
382
+ _platform: AdPlatform,
383
+ externalAccountId: string,
384
+ tokens: { accessToken: string }
385
+ ): Promise<CapabilityState> {
386
+ const mode = await this.detectBillingMode('meta', externalAccountId, tokens);
387
+ if (mode === 'direct_debit' || mode === 'extended_credit') return 'FULLY_FUNDABLE';
388
+ if (mode === 'card_only') return 'UNSUPPORTED';
389
+ return 'MONITORED_ONLY';
390
+ }
391
+
392
+ async readBillingConfiguration(
393
+ _platform: AdPlatform,
394
+ externalAccountId: string,
395
+ tokens: { accessToken: string }
396
+ ): Promise<BillingConfiguration> {
397
+ const mode = await this.detectBillingMode('meta', externalAccountId, tokens);
398
+ return {
399
+ billingMode: mode,
400
+ accountIds: {
401
+ externalAccountId,
402
+ },
403
+ };
404
+ }
405
+
406
+ async detectBillingMode(
407
+ _platform: AdPlatform,
408
+ externalAccountId: string,
409
+ tokens: { accessToken: string }
410
+ ): Promise<BillingMode> {
411
+ // GET /act_{id}?fields=funding_source_details&access_token={token}
412
+ // Response: { funding_source_details: { id, type, display_string } }
413
+ // type values: 1=credit_card, 2=debit_card, 4=direct_debit,
414
+ // 5=paypal, 8=extended_credit, 11=invoice
415
+ const res = await this.apiCall(
416
+ 'GET',
417
+ `/act_${externalAccountId}`,
418
+ tokens.accessToken,
419
+ { fields: 'funding_source_details' }
420
+ );
421
+ const details = res.funding_source_details as Record<string, unknown> | undefined;
422
+ const fundingType = details?.type as number | undefined;
423
+
424
+ switch (fundingType) {
425
+ case 4: return 'direct_debit';
426
+ case 8: return 'extended_credit';
427
+ case 11: return 'monthly_invoicing';
428
+ case 1:
429
+ case 2: return 'card_only';
430
+ default: return 'unknown';
431
+ }
432
+ }
433
+
434
+ // ── Runtime (daemon) ──────────
435
+
436
+ async getCapabilityState(_platform: AdPlatform): Promise<CapabilityState> {
437
+ const profile = this.profiles.get('meta');
438
+ return profile?.capabilityState ?? 'UNSUPPORTED';
439
+ }
440
+
441
+ async readInvoices(_platform: AdPlatform, _dateRange: DateRange): Promise<Invoice[]> {
442
+ // Meta does not expose a first-party invoice API for most account types.
443
+ // For extended_credit accounts: invoices may be available via Business Manager.
444
+ // In V1: return empty — Meta billing is tracked via expected debits.
445
+ return [];
446
+ }
447
+
448
+ async readExpectedDebits(_platform: AdPlatform, dateRange: DateRange): Promise<ExpectedDebit[]> {
449
+ // For direct debit accounts: estimate upcoming debits from recent spend.
450
+ // Meta typically debits when spend threshold is reached or on billing date.
451
+ // This is a forecast based on spend velocity, not a platform API call.
452
+ // The actual debit is detected when it appears in the bank account.
453
+ throw new Error('Implementation requires spend history + bank transaction detection');
454
+ }
455
+
456
+ async generateSettlementInstructions(invoice: Invoice): Promise<SettlementInstruction> {
457
+ // Meta direct debit: no manual settlement needed — Meta pulls from bank.
458
+ // Meta extended credit / invoicing: payment instructions on invoice.
459
+ return {
460
+ invoiceId: invoice.id,
461
+ platform: 'meta',
462
+ payeeName: 'Meta Platforms Inc',
463
+ paymentMethod: 'direct_debit',
464
+ amountCents: invoice.amountCents,
465
+ currency: 'USD',
466
+ dueDate: invoice.dueDate,
467
+ notes: 'Meta direct debit — ensure sufficient bank balance before debit date',
468
+ };
469
+ }
470
+
471
+ async confirmSettlement(invoiceId: string, bankTransactionId: string): Promise<{
472
+ confirmed: boolean; reconciledAmountCents: Cents; varianceCents: Cents;
473
+ }> {
474
+ throw new Error('Implementation requires invoice store + bank adapter integration');
475
+ }
476
+
477
+ async normalizeFundingState(): Promise<NormalizedFundingState[]> {
478
+ const profile = this.profiles.get('meta');
479
+ if (!profile) return [];
480
+
481
+ return [{
482
+ platform: 'meta',
483
+ capabilityState: profile.capabilityState,
484
+ billingMode: profile.billingMode,
485
+ outstandingCents: 0 as Cents, // Meta direct debit is pulled, not pushed
486
+ nextPaymentDueDate: profile.nextDueDate,
487
+ daysUntilNextPayment: profile.nextDueDate
488
+ ? Math.ceil((new Date(profile.nextDueDate).getTime() - Date.now()) / (24 * 60 * 60 * 1000))
489
+ : undefined,
490
+ fundingHealthy: profile.status === 'active',
491
+ warnings: profile.status === 'degraded'
492
+ ? ['Meta billing degraded — check funding source status']
493
+ : [],
494
+ }];
495
+ }
496
+
497
+ // ── Private helpers ──────────
498
+
499
+ private async apiCall(
500
+ method: string,
501
+ path: string,
502
+ accessToken: string,
503
+ params: Record<string, unknown>
504
+ ): Promise<Record<string, unknown>> {
505
+ // Implementation: raw HTTPS (no SDK — zero dependency principle)
506
+ // Headers: Authorization: Bearer {accessToken}
507
+ // Sanitize response strings per §9.19.16 before returning
508
+ throw new Error('HTTP implementation — use node:https, no SDK dependencies');
509
+ }
510
+ }
511
+
512
+ // ── Framework Adaptation Notes ──────────────────────
513
+ //
514
+ // Express/Node.js:
515
+ // - Setup routes in /api/grow/billing/* (interactive, session-authed)
516
+ // - Runtime methods called by Heartbeat scheduler only (daemon-process.ts)
517
+ // - Billing adapter is a SEPARATE service from campaign adapter
518
+ //
519
+ // Django/FastAPI:
520
+ // - AdBillingSetup maps to a ViewSet with session auth
521
+ // - AdBillingAdapter methods become Celery/ARQ tasks (single-worker queue)
522
+ // - Store PlatformBillingProfile in encrypted JSONField
523
+ //
524
+ // Next.js:
525
+ // - Setup API routes under /api/grow/billing/*
526
+ // - Server actions for interactive billing verification
527
+ // - Runtime adapter runs in separate worker, NOT in Next.js server
528
+
529
+ export type {
530
+ AdBillingSetup, AdBillingAdapter,
531
+ CapabilityState, BillingMode, AdPlatform,
532
+ Invoice, InvoiceLineItem, ExpectedDebit,
533
+ SettlementInstruction, PlatformBillingProfile,
534
+ BillingConfiguration, NormalizedFundingState, DateRange,
535
+ Cents,
536
+ };
537
+ export { toCents, GoogleBillingAdapter, MetaBillingAdapter };