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,462 @@
1
+ /**
2
+ * Pattern: Funding Plan (Core Data Structure + Pure Logic)
3
+ *
4
+ * Key principles:
5
+ * - The FundingPlan is the central data structure connecting:
6
+ * stablecoin source → off-ramp → bank settlement → platform billing
7
+ * - Plans are IMMUTABLE once approved — state transitions only, no field edits
8
+ * - Hash-chained for tamper-evident audit trail (per financial-transaction.ts)
9
+ * - All monetary values use branded integer cents (Cents type)
10
+ * - Pure logic functions: no I/O, no side effects, fully testable
11
+ * - Single-writer: only Heartbeat daemon creates and transitions FundingPlans
12
+ *
13
+ * Agents: Dockson (treasury), Heartbeat daemon
14
+ *
15
+ * PRD Reference: §12.1, §12.2, §12.4, §12.5, §12.6, §13, §15
16
+ */
17
+
18
+ import { createHash } from 'node:crypto';
19
+
20
+ // ── Branded Financial Types (from financial-transaction.ts) ──
21
+
22
+ type Cents = number & { readonly __brand: 'Cents' };
23
+
24
+ function toCents(dollars: number): Cents {
25
+ return Math.round(dollars * 100) as Cents;
26
+ }
27
+
28
+ function toDollars(cents: Cents): number {
29
+ return cents / 100;
30
+ }
31
+
32
+ // ── Stablecoin Funding Source (PRD §12.1) ────────────
33
+
34
+ type StablecoinProviderType = 'circle' | 'bridge' | 'manual';
35
+
36
+ interface StablecoinFundingSource {
37
+ id: string; // UUID v4
38
+ provider: StablecoinProviderType;
39
+ asset: string; // 'USDC' in V1
40
+ network: string; // 'ETH', 'SOL', 'MATIC', etc.
41
+ sourceAccountId: string; // provider-specific wallet/account ID
42
+ whitelistedDestinationBankId: string; // only this bank can receive off-ramps
43
+ status: 'active' | 'suspended' | 'unconfigured';
44
+ }
45
+
46
+ // ── Operating Bank Account (PRD §12.2) ───────────────
47
+
48
+ type BankProvider = 'mercury' | 'external';
49
+
50
+ interface OperatingBankAccount {
51
+ id: string; // UUID v4
52
+ provider: BankProvider;
53
+ accountId: string; // bank-specific account identifier
54
+ currency: 'USD'; // USD-only in V1
55
+ availableBalanceCents: Cents;
56
+ reservedBalanceCents: Cents; // earmarked for pending settlements
57
+ minimumBufferCents: Cents; // floor below which off-ramp triggers
58
+ }
59
+
60
+ // ── Platform Billing Profile (PRD §12.3) ────────────
61
+ // Re-exported from ad-billing-adapter.ts for convenience.
62
+
63
+ type CapabilityState = 'FULLY_FUNDABLE' | 'MONITORED_ONLY' | 'UNSUPPORTED';
64
+ type BillingMode =
65
+ | 'monthly_invoicing'
66
+ | 'direct_debit'
67
+ | 'extended_credit'
68
+ | 'manual_bank_transfer'
69
+ | 'card_only'
70
+ | 'unknown';
71
+ type AdPlatform = 'google' | 'meta';
72
+
73
+ interface PlatformBillingProfile {
74
+ platform: AdPlatform;
75
+ capabilityState: CapabilityState;
76
+ billingMode: BillingMode;
77
+ externalAccountId: string;
78
+ billingSetupId?: string;
79
+ invoiceGroupId?: string;
80
+ paymentProfileId?: string;
81
+ fundingSourceId?: string;
82
+ currency: 'USD';
83
+ nextDueDate?: string; // ISO 8601
84
+ status: 'active' | 'degraded' | 'suspended' | 'unconfigured';
85
+ }
86
+
87
+ // ── Funding Plan (PRD §12.4) ────────────────────────
88
+
89
+ type FundingPlanStatus =
90
+ | 'DRAFT' // created, not yet approved
91
+ | 'APPROVED' // approved by policy or human, awaiting execution
92
+ | 'PENDING_SETTLEMENT' // off-ramp initiated, waiting for bank settlement
93
+ | 'SETTLED' // fiat arrived at bank, plan complete
94
+ | 'FAILED' // off-ramp or settlement failed
95
+ | 'FROZEN'; // frozen by circuit breaker or manual freeze
96
+
97
+ type FundingPlanReason =
98
+ | 'LOW_BUFFER' // bank balance below minimum buffer
99
+ | 'INVOICE_DUE' // Google invoice approaching due date
100
+ | 'RUNWAY_SHORTFALL' // projected spend exceeds available fiat runway
101
+ | 'MANUAL_REQUEST'; // user-initiated funding request
102
+
103
+ type ApprovalMode =
104
+ | 'policy_auto' // rules engine approved automatically
105
+ | 'vault_manual' // user approved via vault password
106
+ | 'totp_required'; // user approved via vault + TOTP (high-risk actions)
107
+
108
+ type TargetPlatform = AdPlatform | 'shared_buffer';
109
+
110
+ interface FundingPlan {
111
+ id: string; // UUID v4
112
+ createdAt: string; // ISO 8601
113
+ updatedAt: string; // ISO 8601
114
+ reason: FundingPlanReason;
115
+ sourceFundingId: string; // → StablecoinFundingSource.id
116
+ destinationBankId: string; // → OperatingBankAccount.id
117
+ targetPlatform: TargetPlatform; // which platform this funding supports
118
+ requiredCents: Cents; // how much fiat is needed
119
+ reservedCents: Cents; // how much has been earmarked at the bank
120
+ status: FundingPlanStatus;
121
+ approvalMode: ApprovalMode;
122
+ approvedAt?: string; // ISO 8601
123
+ approvedBy?: string; // 'policy_engine' | 'user:{hashedId}'
124
+ settledAt?: string; // ISO 8601
125
+ failureReason?: string;
126
+ idempotencyKey: string; // UUID, prevents duplicate off-ramps
127
+ // Hash chain fields
128
+ previousHash: string;
129
+ hash: string;
130
+ }
131
+
132
+ // ── Transfer Record (PRD §12.5) ─────────────────────
133
+
134
+ type TransferDirection = 'crypto_to_fiat' | 'bank_to_platform' | 'platform_debit';
135
+ type TransferStatus = 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';
136
+
137
+ interface TransferRecord {
138
+ id: string; // UUID v4
139
+ fundingPlanId: string; // → FundingPlan.id
140
+ providerTransferId: string; // stablecoin provider's transfer ID
141
+ bankTransactionId?: string; // bank-side reference once detected
142
+ direction: TransferDirection;
143
+ amountCents: Cents;
144
+ feesCents: Cents;
145
+ netAmountCents: Cents; // amountCents - feesCents
146
+ currency: 'USD';
147
+ reference: string; // human-readable reference for matching
148
+ status: TransferStatus;
149
+ initiatedAt: string; // ISO 8601
150
+ completedAt?: string; // ISO 8601
151
+ // Hash chain fields (per financial-transaction.ts pattern)
152
+ previousHash: string;
153
+ hash: string;
154
+ }
155
+
156
+ // ── Reconciliation Record (PRD §12.6) ───────────────
157
+
158
+ type ReconciliationResult = 'MATCHED' | 'WITHIN_THRESHOLD' | 'MISMATCH';
159
+
160
+ interface ReconciliationRecord {
161
+ id: string; // UUID v4
162
+ platform: AdPlatform;
163
+ date: string; // YYYY-MM-DD
164
+ spendCents: Cents; // platform-reported ad spend
165
+ bankSettledCents: Cents; // bank transactions matched to this platform
166
+ invoiceCents: Cents; // invoiced amount (Google) or debit amount (Meta)
167
+ varianceCents: Cents; // absolute difference: |invoiceCents - bankSettledCents|
168
+ result: ReconciliationResult;
169
+ notes: string;
170
+ createdAt: string; // ISO 8601
171
+ // Hash chain fields
172
+ previousHash: string;
173
+ hash: string;
174
+ }
175
+
176
+ // ── Hash Chain Helpers ──────────────────────────────
177
+
178
+ function computePlanHash(plan: Omit<FundingPlan, 'hash'>, previousHash: string): string {
179
+ const payload = JSON.stringify({
180
+ id: plan.id,
181
+ reason: plan.reason,
182
+ sourceFundingId: plan.sourceFundingId,
183
+ destinationBankId: plan.destinationBankId,
184
+ requiredCents: plan.requiredCents,
185
+ status: plan.status,
186
+ createdAt: plan.createdAt,
187
+ idempotencyKey: plan.idempotencyKey,
188
+ }) + previousHash;
189
+ return createHash('sha256').update(payload).digest('hex');
190
+ }
191
+
192
+ function computeTransferHash(record: Omit<TransferRecord, 'hash'>, previousHash: string): string {
193
+ const payload = JSON.stringify({
194
+ id: record.id,
195
+ fundingPlanId: record.fundingPlanId,
196
+ providerTransferId: record.providerTransferId,
197
+ amountCents: record.amountCents,
198
+ feesCents: record.feesCents,
199
+ status: record.status,
200
+ initiatedAt: record.initiatedAt,
201
+ }) + previousHash;
202
+ return createHash('sha256').update(payload).digest('hex');
203
+ }
204
+
205
+ function computeReconciliationHash(
206
+ record: Omit<ReconciliationRecord, 'hash'>,
207
+ previousHash: string
208
+ ): string {
209
+ const payload = JSON.stringify({
210
+ id: record.id,
211
+ platform: record.platform,
212
+ date: record.date,
213
+ spendCents: record.spendCents,
214
+ bankSettledCents: record.bankSettledCents,
215
+ invoiceCents: record.invoiceCents,
216
+ varianceCents: record.varianceCents,
217
+ result: record.result,
218
+ }) + previousHash;
219
+ return createHash('sha256').update(payload).digest('hex');
220
+ }
221
+
222
+ // ── Pure Logic: Funding Plan Creation ───────────────
223
+
224
+ function createFundingPlan(
225
+ reason: FundingPlanReason,
226
+ sourceFundingId: string,
227
+ destinationBankId: string,
228
+ targetPlatform: TargetPlatform,
229
+ requiredCents: Cents,
230
+ previousHash: string
231
+ ): FundingPlan {
232
+ const now = new Date().toISOString();
233
+ const id = crypto.randomUUID();
234
+ const idempotencyKey = crypto.randomUUID();
235
+
236
+ const draft: Omit<FundingPlan, 'hash'> = {
237
+ id,
238
+ createdAt: now,
239
+ updatedAt: now,
240
+ reason,
241
+ sourceFundingId,
242
+ destinationBankId,
243
+ targetPlatform,
244
+ requiredCents,
245
+ reservedCents: 0 as Cents,
246
+ status: 'DRAFT',
247
+ approvalMode: 'policy_auto', // default; upgraded by approval logic
248
+ idempotencyKey,
249
+ previousHash,
250
+ };
251
+
252
+ const hash = computePlanHash(draft, previousHash);
253
+ return { ...draft, hash };
254
+ }
255
+
256
+ // ── Pure Logic: Plan Approval ───────────────────────
257
+
258
+ function approvePlan(
259
+ plan: FundingPlan,
260
+ approvalMode: ApprovalMode,
261
+ approvedBy: string
262
+ ): FundingPlan {
263
+ if (plan.status !== 'DRAFT') {
264
+ throw new Error(`Cannot approve plan in status ${plan.status} — must be DRAFT`);
265
+ }
266
+
267
+ const now = new Date().toISOString();
268
+ const approved: Omit<FundingPlan, 'hash'> = {
269
+ ...plan,
270
+ status: 'APPROVED',
271
+ approvalMode,
272
+ approvedBy,
273
+ approvedAt: now,
274
+ updatedAt: now,
275
+ previousHash: plan.hash,
276
+ };
277
+
278
+ const hash = computePlanHash(approved, plan.hash);
279
+ return { ...approved, hash };
280
+ }
281
+
282
+ // ── Pure Logic: Plan State Transitions ──────────────
283
+
284
+ function transitionPlan(
285
+ plan: FundingPlan,
286
+ newStatus: FundingPlanStatus,
287
+ reason?: string
288
+ ): FundingPlan {
289
+ // Validate allowed transitions
290
+ const allowedTransitions: Record<FundingPlanStatus, FundingPlanStatus[]> = {
291
+ 'DRAFT': ['APPROVED', 'FROZEN'],
292
+ 'APPROVED': ['PENDING_SETTLEMENT', 'FROZEN', 'FAILED'],
293
+ 'PENDING_SETTLEMENT': ['SETTLED', 'FAILED', 'FROZEN'],
294
+ 'SETTLED': [], // terminal state
295
+ 'FAILED': ['DRAFT'], // can retry by creating new draft
296
+ 'FROZEN': ['DRAFT'], // can unfreeze to draft for re-approval
297
+ };
298
+
299
+ const allowed = allowedTransitions[plan.status];
300
+ if (!allowed.includes(newStatus)) {
301
+ throw new Error(
302
+ `Invalid transition: ${plan.status} → ${newStatus}. Allowed: ${allowed.join(', ') || 'none (terminal)'}`
303
+ );
304
+ }
305
+
306
+ const now = new Date().toISOString();
307
+ const transitioned: Omit<FundingPlan, 'hash'> = {
308
+ ...plan,
309
+ status: newStatus,
310
+ updatedAt: now,
311
+ settledAt: newStatus === 'SETTLED' ? now : plan.settledAt,
312
+ failureReason: newStatus === 'FAILED' ? reason : plan.failureReason,
313
+ previousHash: plan.hash,
314
+ };
315
+
316
+ const hash = computePlanHash(transitioned, plan.hash);
317
+ return { ...transitioned, hash };
318
+ }
319
+
320
+ // ── Pure Logic: Runway Calculation ──────────────────
321
+
322
+ function calculateRunway(bankBalanceCents: Cents, dailySpendRateCents: Cents): number {
323
+ if (dailySpendRateCents <= 0) return Infinity;
324
+ return Math.floor(bankBalanceCents / dailySpendRateCents);
325
+ }
326
+
327
+ // ── Pure Logic: Off-ramp Trigger Decision ───────────
328
+
329
+ function shouldTriggerOfframp(
330
+ bankBalanceCents: Cents,
331
+ bufferThresholdCents: Cents,
332
+ pendingSpendCents: Cents,
333
+ pendingOfframpCents: Cents
334
+ ): boolean {
335
+ // Available balance after accounting for pending obligations
336
+ const effectiveBalance = (bankBalanceCents - pendingSpendCents + pendingOfframpCents) as Cents;
337
+ return effectiveBalance < bufferThresholdCents;
338
+ }
339
+
340
+ // ── Pure Logic: Required Off-ramp Amount ────────────
341
+
342
+ function calculateRequiredOfframp(
343
+ bankBalanceCents: Cents,
344
+ bufferThresholdCents: Cents,
345
+ pendingSpendCents: Cents,
346
+ pendingOfframpCents: Cents,
347
+ minimumOfframpCents: Cents
348
+ ): Cents {
349
+ const deficit = (bufferThresholdCents + pendingSpendCents - bankBalanceCents - pendingOfframpCents);
350
+ if (deficit <= 0) return 0 as Cents;
351
+ // Round up to minimum off-ramp amount if below provider minimum
352
+ return Math.max(deficit, minimumOfframpCents) as Cents;
353
+ }
354
+
355
+ // ── Pure Logic: Reconciliation ──────────────────────
356
+
357
+ function reconcileTransfer(
358
+ transferAmountCents: Cents,
359
+ bankTransactionCents: Cents,
360
+ platformSpendCents: Cents,
361
+ thresholdBps: number, // basis points tolerance (e.g., 50 = 0.5%)
362
+ platform: AdPlatform,
363
+ date: string,
364
+ previousHash: string
365
+ ): ReconciliationRecord {
366
+ const invoiceCents = transferAmountCents; // what was transferred
367
+ const varianceCents = Math.abs(invoiceCents - bankTransactionCents) as Cents;
368
+
369
+ // Determine result based on variance threshold
370
+ let result: ReconciliationResult;
371
+ if (varianceCents === 0) {
372
+ result = 'MATCHED';
373
+ } else {
374
+ const varianceBps = invoiceCents > 0
375
+ ? (varianceCents / invoiceCents) * 10_000
376
+ : 0;
377
+ result = varianceBps <= thresholdBps ? 'WITHIN_THRESHOLD' : 'MISMATCH';
378
+ }
379
+
380
+ const id = crypto.randomUUID();
381
+ const now = new Date().toISOString();
382
+
383
+ const record: Omit<ReconciliationRecord, 'hash'> = {
384
+ id,
385
+ platform,
386
+ date,
387
+ spendCents: platformSpendCents,
388
+ bankSettledCents: bankTransactionCents,
389
+ invoiceCents,
390
+ varianceCents,
391
+ result,
392
+ notes: result === 'MISMATCH'
393
+ ? `Variance ${varianceCents} cents exceeds ${thresholdBps}bps threshold`
394
+ : result === 'WITHIN_THRESHOLD'
395
+ ? `Variance ${varianceCents} cents within ${thresholdBps}bps threshold`
396
+ : 'Exact match',
397
+ createdAt: now,
398
+ previousHash,
399
+ };
400
+
401
+ const hash = computeReconciliationHash(record, previousHash);
402
+ return { ...record, hash };
403
+ }
404
+
405
+ // ── Pure Logic: Invoice Priority ────────────────────
406
+ // When multiple invoices are pending, prioritize by due date and overdue status.
407
+
408
+ interface PendingObligation {
409
+ id: string;
410
+ platform: AdPlatform;
411
+ amountCents: Cents;
412
+ dueDate: string; // ISO 8601
413
+ overdue: boolean;
414
+ }
415
+
416
+ function prioritizeObligations(obligations: PendingObligation[]): PendingObligation[] {
417
+ return [...obligations].sort((a, b) => {
418
+ // Overdue items first
419
+ if (a.overdue && !b.overdue) return -1;
420
+ if (!a.overdue && b.overdue) return 1;
421
+ // Then by due date (earliest first)
422
+ return new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime();
423
+ });
424
+ }
425
+
426
+ // ── Framework Adaptation Notes ──────────────────────
427
+ //
428
+ // Express/Node.js:
429
+ // - FundingPlan stored as JSONL append log (funding-plans.jsonl)
430
+ // - TransferRecord stored as JSONL append log (transfers.jsonl)
431
+ // - ReconciliationRecord stored as JSONL append log (reconciliation.jsonl)
432
+ // - All writes go through atomicWrite() from financial-transaction.ts
433
+ // - Only Heartbeat daemon writes — API reads from logs
434
+ //
435
+ // Django/FastAPI:
436
+ // - FundingPlan as Django model with status FSM (django-fsm)
437
+ // - TransferRecord as separate model with FK to FundingPlan
438
+ // - ReconciliationRecord as daily snapshot model
439
+ // - Single Celery worker for all write operations
440
+ //
441
+ // Next.js:
442
+ // - Read API routes serve from JSONL logs
443
+ // - Write operations proxied to Heartbeat daemon via Unix socket
444
+ // - Dashboard components consume normalizeFundingState() output
445
+
446
+ export type {
447
+ Cents,
448
+ StablecoinProviderType, StablecoinFundingSource,
449
+ BankProvider, OperatingBankAccount,
450
+ CapabilityState, BillingMode, AdPlatform, PlatformBillingProfile,
451
+ FundingPlanStatus, FundingPlanReason, ApprovalMode, TargetPlatform, FundingPlan,
452
+ TransferDirection, TransferStatus, TransferRecord,
453
+ ReconciliationResult, ReconciliationRecord,
454
+ PendingObligation,
455
+ };
456
+ export {
457
+ toCents, toDollars,
458
+ computePlanHash, computeTransferHash, computeReconciliationHash,
459
+ createFundingPlan, approvePlan, transitionPlan,
460
+ calculateRunway, shouldTriggerOfframp, calculateRequiredOfframp,
461
+ reconcileTransfer, prioritizeObligations,
462
+ };
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Pattern: Entity Component System (ECS)
3
+ *
4
+ * Key principles:
5
+ * - Entities are IDs, not objects. No inheritance hierarchy.
6
+ * - Components are pure data. No methods.
7
+ * - Systems operate on components. All logic lives in systems.
8
+ * - Composition over inheritance: an entity with Position + Velocity + Sprite is a moving sprite.
9
+ * - Object pooling: reuse entity IDs and component storage instead of GC pressure.
10
+ *
11
+ * Agents: Spike-GameDev (architecture), L-Profiler (memory/GC), Gimli (performance)
12
+ *
13
+ * Engine adaptations:
14
+ * Phaser: Arcade Physics bodies are pseudo-ECS. For full ECS, use bitecs or miniplex.
15
+ * Godot: Scene tree is composition-based (nodes = components). Not pure ECS but same spirit.
16
+ * Unity: Unity DOTS/ECS for performance-critical systems, MonoBehaviour for the rest.
17
+ * Three.js: This file (manual ECS, or use bitecs/miniplex library)
18
+ */
19
+
20
+ // --- Entity ---
21
+ type EntityId = number
22
+ let nextEntityId: EntityId = 0
23
+
24
+ function createEntity(): EntityId {
25
+ return nextEntityId++
26
+ }
27
+
28
+ function destroyEntity(id: EntityId): void {
29
+ // Remove all components for this entity
30
+ for (const store of componentStores.values()) {
31
+ store.delete(id)
32
+ }
33
+ }
34
+
35
+ // --- Components (pure data, no methods) ---
36
+ interface Position { x: number; y: number }
37
+ interface Velocity { vx: number; vy: number }
38
+ interface Sprite { texture: string; width: number; height: number }
39
+ interface Health { current: number; max: number }
40
+ interface Collider { radius: number }
41
+ interface PlayerControlled { speed: number }
42
+ interface Enemy { aiType: 'chase' | 'patrol' | 'idle'; target?: EntityId }
43
+
44
+ // --- Component Storage ---
45
+ const componentStores = new Map<string, Map<EntityId, unknown>>()
46
+
47
+ function registerComponent<T>(name: string): {
48
+ set: (id: EntityId, data: T) => void
49
+ get: (id: EntityId) => T | undefined
50
+ has: (id: EntityId) => boolean
51
+ delete: (id: EntityId) => void
52
+ all: () => [EntityId, T][]
53
+ } {
54
+ const store = new Map<EntityId, T>()
55
+ componentStores.set(name, store as Map<EntityId, unknown>)
56
+ return {
57
+ set: (id, data) => store.set(id, data),
58
+ get: (id) => store.get(id),
59
+ has: (id) => store.has(id),
60
+ delete: (id) => store.delete(id),
61
+ all: () => [...store.entries()],
62
+ }
63
+ }
64
+
65
+ // Register component stores
66
+ const positions = registerComponent<Position>('position')
67
+ const velocities = registerComponent<Velocity>('velocity')
68
+ const sprites = registerComponent<Sprite>('sprite')
69
+ const healths = registerComponent<Health>('health')
70
+ const colliders = registerComponent<Collider>('collider')
71
+ const playerControlled = registerComponent<PlayerControlled>('playerControlled')
72
+ const enemies = registerComponent<Enemy>('enemy')
73
+
74
+ // --- Systems (all logic here, not in components) ---
75
+
76
+ function movementSystem(dt: number): void {
77
+ for (const [id, vel] of velocities.all()) {
78
+ const pos = positions.get(id)
79
+ if (pos) {
80
+ pos.x += vel.vx * dt
81
+ pos.y += vel.vy * dt
82
+ }
83
+ }
84
+ }
85
+
86
+ function collisionSystem(): [EntityId, EntityId][] {
87
+ const collisions: [EntityId, EntityId][] = []
88
+ const entities = colliders.all()
89
+ for (let i = 0; i < entities.length; i++) {
90
+ for (let j = i + 1; j < entities.length; j++) {
91
+ const [idA, colA] = entities[i]
92
+ const [idB, colB] = entities[j]
93
+ const posA = positions.get(idA)
94
+ const posB = positions.get(idB)
95
+ if (posA && posB) {
96
+ const dx = posA.x - posB.x
97
+ const dy = posA.y - posB.y
98
+ const dist = Math.sqrt(dx * dx + dy * dy)
99
+ if (dist < colA.radius + colB.radius) {
100
+ collisions.push([idA, idB])
101
+ }
102
+ }
103
+ }
104
+ }
105
+ return collisions
106
+ }
107
+
108
+ // --- Entity Factory (composition) ---
109
+
110
+ function createPlayer(x: number, y: number): EntityId {
111
+ const id = createEntity()
112
+ positions.set(id, { x, y })
113
+ velocities.set(id, { vx: 0, vy: 0 })
114
+ sprites.set(id, { texture: 'player.png', width: 32, height: 32 })
115
+ healths.set(id, { current: 100, max: 100 })
116
+ colliders.set(id, { radius: 16 })
117
+ playerControlled.set(id, { speed: 200 })
118
+ return id
119
+ }
120
+
121
+ function createEnemy(x: number, y: number, aiType: 'chase' | 'patrol'): EntityId {
122
+ const id = createEntity()
123
+ positions.set(id, { x, y })
124
+ velocities.set(id, { vx: 0, vy: 0 })
125
+ sprites.set(id, { texture: 'enemy.png', width: 32, height: 32 })
126
+ healths.set(id, { current: 50, max: 50 })
127
+ colliders.set(id, { radius: 16 })
128
+ enemies.set(id, { aiType })
129
+ return id
130
+ }
131
+
132
+ export {
133
+ createEntity, destroyEntity, createPlayer, createEnemy,
134
+ movementSystem, collisionSystem,
135
+ positions, velocities, sprites, healths, colliders, playerControlled, enemies
136
+ }
137
+ export type { EntityId, Position, Velocity, Sprite, Health }