thevoidforge 21.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scripts/vault-read.d.ts +11 -0
- package/dist/scripts/vault-read.js +89 -0
- package/dist/scripts/voidforge.d.ts +20 -0
- package/dist/scripts/voidforge.js +404 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/wizard/api/auth.d.ts +5 -0
- package/dist/wizard/api/auth.js +133 -0
- package/dist/wizard/api/blueprint.d.ts +45 -0
- package/dist/wizard/api/blueprint.js +184 -0
- package/dist/wizard/api/cloud-providers.d.ts +16 -0
- package/dist/wizard/api/cloud-providers.js +363 -0
- package/dist/wizard/api/credentials.d.ts +1 -0
- package/dist/wizard/api/credentials.js +258 -0
- package/dist/wizard/api/danger-room.d.ts +18 -0
- package/dist/wizard/api/danger-room.js +401 -0
- package/dist/wizard/api/deploy.d.ts +4 -0
- package/dist/wizard/api/deploy.js +164 -0
- package/dist/wizard/api/prd.d.ts +1 -0
- package/dist/wizard/api/prd.js +363 -0
- package/dist/wizard/api/project.d.ts +1 -0
- package/dist/wizard/api/project.js +239 -0
- package/dist/wizard/api/projects.d.ts +6 -0
- package/dist/wizard/api/projects.js +648 -0
- package/dist/wizard/api/provision.d.ts +4 -0
- package/dist/wizard/api/provision.js +535 -0
- package/dist/wizard/api/terminal.d.ts +25 -0
- package/dist/wizard/api/terminal.js +241 -0
- package/dist/wizard/api/users.d.ts +6 -0
- package/dist/wizard/api/users.js +244 -0
- package/dist/wizard/api/war-room.d.ts +14 -0
- package/dist/wizard/api/war-room.js +45 -0
- package/dist/wizard/lib/ad-platform-core.d.ts +6 -0
- package/dist/wizard/lib/ad-platform-core.js +1 -0
- package/dist/wizard/lib/adapters/index.d.ts +52 -0
- package/dist/wizard/lib/adapters/index.js +38 -0
- package/dist/wizard/lib/adapters/sandbox-bank.d.ts +17 -0
- package/dist/wizard/lib/adapters/sandbox-bank.js +77 -0
- package/dist/wizard/lib/adapters/sandbox.d.ts +39 -0
- package/dist/wizard/lib/adapters/sandbox.js +174 -0
- package/dist/wizard/lib/adapters/stripe.d.ts +19 -0
- package/dist/wizard/lib/adapters/stripe.js +143 -0
- package/dist/wizard/lib/adapters/types.d.ts +9 -0
- package/dist/wizard/lib/adapters/types.js +10 -0
- package/dist/wizard/lib/agent-memory.d.ts +36 -0
- package/dist/wizard/lib/agent-memory.js +114 -0
- package/dist/wizard/lib/anomaly-detection.d.ts +59 -0
- package/dist/wizard/lib/anomaly-detection.js +122 -0
- package/dist/wizard/lib/anthropic.d.ts +21 -0
- package/dist/wizard/lib/anthropic.js +105 -0
- package/dist/wizard/lib/asset-scanner.d.ts +23 -0
- package/dist/wizard/lib/asset-scanner.js +107 -0
- package/dist/wizard/lib/audit-log.d.ts +23 -0
- package/dist/wizard/lib/audit-log.js +70 -0
- package/dist/wizard/lib/autonomy-controller.d.ts +76 -0
- package/dist/wizard/lib/autonomy-controller.js +183 -0
- package/dist/wizard/lib/body-parser.d.ts +2 -0
- package/dist/wizard/lib/body-parser.js +36 -0
- package/dist/wizard/lib/build-analytics.d.ts +39 -0
- package/dist/wizard/lib/build-analytics.js +91 -0
- package/dist/wizard/lib/build-step.d.ts +21 -0
- package/dist/wizard/lib/build-step.js +104 -0
- package/dist/wizard/lib/campaign-proposer.d.ts +39 -0
- package/dist/wizard/lib/campaign-proposer.js +180 -0
- package/dist/wizard/lib/campaign-state-machine.d.ts +63 -0
- package/dist/wizard/lib/campaign-state-machine.js +114 -0
- package/dist/wizard/lib/ci-generator.d.ts +14 -0
- package/dist/wizard/lib/ci-generator.js +187 -0
- package/dist/wizard/lib/claude-merge.d.ts +38 -0
- package/dist/wizard/lib/claude-merge.js +115 -0
- package/dist/wizard/lib/codegen/erd-gen.d.ts +16 -0
- package/dist/wizard/lib/codegen/erd-gen.js +98 -0
- package/dist/wizard/lib/codegen/integrations.d.ts +18 -0
- package/dist/wizard/lib/codegen/integrations.js +189 -0
- package/dist/wizard/lib/codegen/openapi-gen.d.ts +15 -0
- package/dist/wizard/lib/codegen/openapi-gen.js +79 -0
- package/dist/wizard/lib/codegen/prisma-types.d.ts +15 -0
- package/dist/wizard/lib/codegen/prisma-types.js +44 -0
- package/dist/wizard/lib/codegen/seed-gen.d.ts +16 -0
- package/dist/wizard/lib/codegen/seed-gen.js +128 -0
- package/dist/wizard/lib/compliance.d.ts +51 -0
- package/dist/wizard/lib/compliance.js +112 -0
- package/dist/wizard/lib/correlation-engine.d.ts +59 -0
- package/dist/wizard/lib/correlation-engine.js +151 -0
- package/dist/wizard/lib/cost-estimator.d.ts +22 -0
- package/dist/wizard/lib/cost-estimator.js +72 -0
- package/dist/wizard/lib/cost-tracker.d.ts +27 -0
- package/dist/wizard/lib/cost-tracker.js +37 -0
- package/dist/wizard/lib/daemon-aggregator.d.ts +71 -0
- package/dist/wizard/lib/daemon-aggregator.js +204 -0
- package/dist/wizard/lib/daemon-core.d.ts +6 -0
- package/dist/wizard/lib/daemon-core.js +5 -0
- package/dist/wizard/lib/dashboard-data.d.ts +132 -0
- package/dist/wizard/lib/dashboard-data.js +336 -0
- package/dist/wizard/lib/dashboard-ws.d.ts +25 -0
- package/dist/wizard/lib/dashboard-ws.js +91 -0
- package/dist/wizard/lib/deep-current.d.ts +77 -0
- package/dist/wizard/lib/deep-current.js +234 -0
- package/dist/wizard/lib/deploy-coordinator.d.ts +40 -0
- package/dist/wizard/lib/deploy-coordinator.js +86 -0
- package/dist/wizard/lib/deploy-log.d.ts +28 -0
- package/dist/wizard/lib/deploy-log.js +52 -0
- package/dist/wizard/lib/desktop-notify.d.ts +27 -0
- package/dist/wizard/lib/desktop-notify.js +98 -0
- package/dist/wizard/lib/dns/cloudflare-dns.d.ts +35 -0
- package/dist/wizard/lib/dns/cloudflare-dns.js +216 -0
- package/dist/wizard/lib/dns/cloudflare-registrar.d.ts +31 -0
- package/dist/wizard/lib/dns/cloudflare-registrar.js +148 -0
- package/dist/wizard/lib/dns/types.d.ts +22 -0
- package/dist/wizard/lib/dns/types.js +4 -0
- package/dist/wizard/lib/document-discovery.d.ts +33 -0
- package/dist/wizard/lib/document-discovery.js +145 -0
- package/dist/wizard/lib/env-validator.d.ts +14 -0
- package/dist/wizard/lib/env-validator.js +205 -0
- package/dist/wizard/lib/env-writer.d.ts +13 -0
- package/dist/wizard/lib/env-writer.js +26 -0
- package/dist/wizard/lib/exec.d.ts +30 -0
- package/dist/wizard/lib/exec.js +52 -0
- package/dist/wizard/lib/experiment.d.ts +70 -0
- package/dist/wizard/lib/experiment.js +169 -0
- package/dist/wizard/lib/extensions.d.ts +20 -0
- package/dist/wizard/lib/extensions.js +183 -0
- package/dist/wizard/lib/financial/adapter-factory.d.ts +47 -0
- package/dist/wizard/lib/financial/adapter-factory.js +225 -0
- package/dist/wizard/lib/financial/billing/base.d.ts +6 -0
- package/dist/wizard/lib/financial/billing/base.js +1 -0
- package/dist/wizard/lib/financial/billing/google-billing.d.ts +56 -0
- package/dist/wizard/lib/financial/billing/google-billing.js +298 -0
- package/dist/wizard/lib/financial/billing/meta-billing.d.ts +54 -0
- package/dist/wizard/lib/financial/billing/meta-billing.js +243 -0
- package/dist/wizard/lib/financial/billing/tiktok-billing.d.ts +54 -0
- package/dist/wizard/lib/financial/billing/tiktok-billing.js +260 -0
- package/dist/wizard/lib/financial/campaign/base.d.ts +13 -0
- package/dist/wizard/lib/financial/campaign/base.js +1 -0
- package/dist/wizard/lib/financial/campaign/google-campaign.d.ts +42 -0
- package/dist/wizard/lib/financial/campaign/google-campaign.js +388 -0
- package/dist/wizard/lib/financial/campaign/meta-campaign.d.ts +41 -0
- package/dist/wizard/lib/financial/campaign/meta-campaign.js +311 -0
- package/dist/wizard/lib/financial/campaign/sandbox-campaign.d.ts +45 -0
- package/dist/wizard/lib/financial/campaign/sandbox-campaign.js +261 -0
- package/dist/wizard/lib/financial/campaign/tiktok-campaign.d.ts +40 -0
- package/dist/wizard/lib/financial/campaign/tiktok-campaign.js +350 -0
- package/dist/wizard/lib/financial/funding-auto.d.ts +44 -0
- package/dist/wizard/lib/financial/funding-auto.js +52 -0
- package/dist/wizard/lib/financial/funding-policy.d.ts +60 -0
- package/dist/wizard/lib/financial/funding-policy.js +179 -0
- package/dist/wizard/lib/financial/platform-planner.d.ts +47 -0
- package/dist/wizard/lib/financial/platform-planner.js +134 -0
- package/dist/wizard/lib/financial/reconciliation-engine.d.ts +78 -0
- package/dist/wizard/lib/financial/reconciliation-engine.js +193 -0
- package/dist/wizard/lib/financial/registry.d.ts +22 -0
- package/dist/wizard/lib/financial/registry.js +26 -0
- package/dist/wizard/lib/financial/reporting.d.ts +96 -0
- package/dist/wizard/lib/financial/reporting.js +198 -0
- package/dist/wizard/lib/financial/stablecoin/base.d.ts +6 -0
- package/dist/wizard/lib/financial/stablecoin/base.js +1 -0
- package/dist/wizard/lib/financial/stablecoin/circle.d.ts +54 -0
- package/dist/wizard/lib/financial/stablecoin/circle.js +367 -0
- package/dist/wizard/lib/financial/stablecoin/mercury.d.ts +24 -0
- package/dist/wizard/lib/financial/stablecoin/mercury.js +171 -0
- package/dist/wizard/lib/financial/stablecoin/sandbox-stablecoin.d.ts +47 -0
- package/dist/wizard/lib/financial/stablecoin/sandbox-stablecoin.js +202 -0
- package/dist/wizard/lib/financial/treasury-planner.d.ts +52 -0
- package/dist/wizard/lib/financial/treasury-planner.js +128 -0
- package/dist/wizard/lib/financial-core.d.ts +6 -0
- package/dist/wizard/lib/financial-core.js +5 -0
- package/dist/wizard/lib/financial-vault.d.ts +34 -0
- package/dist/wizard/lib/financial-vault.js +199 -0
- package/dist/wizard/lib/frontmatter.d.ts +30 -0
- package/dist/wizard/lib/frontmatter.js +96 -0
- package/dist/wizard/lib/gap-analysis.d.ts +37 -0
- package/dist/wizard/lib/gap-analysis.js +218 -0
- package/dist/wizard/lib/github.d.ts +22 -0
- package/dist/wizard/lib/github.js +261 -0
- package/dist/wizard/lib/headless-deploy.d.ts +14 -0
- package/dist/wizard/lib/headless-deploy.js +452 -0
- package/dist/wizard/lib/health-monitor.d.ts +15 -0
- package/dist/wizard/lib/health-monitor.js +91 -0
- package/dist/wizard/lib/health-poller.d.ts +9 -0
- package/dist/wizard/lib/health-poller.js +123 -0
- package/dist/wizard/lib/heartbeat.d.ts +15 -0
- package/dist/wizard/lib/heartbeat.js +827 -0
- package/dist/wizard/lib/http-helpers.d.ts +9 -0
- package/dist/wizard/lib/http-helpers.js +24 -0
- package/dist/wizard/lib/image-gen.d.ts +56 -0
- package/dist/wizard/lib/image-gen.js +159 -0
- package/dist/wizard/lib/instance-sizing.d.ts +26 -0
- package/dist/wizard/lib/instance-sizing.js +51 -0
- package/dist/wizard/lib/kongo/analytics.d.ts +29 -0
- package/dist/wizard/lib/kongo/analytics.js +179 -0
- package/dist/wizard/lib/kongo/campaigns.d.ts +52 -0
- package/dist/wizard/lib/kongo/campaigns.js +91 -0
- package/dist/wizard/lib/kongo/client.d.ts +58 -0
- package/dist/wizard/lib/kongo/client.js +221 -0
- package/dist/wizard/lib/kongo/jobs.d.ts +57 -0
- package/dist/wizard/lib/kongo/jobs.js +122 -0
- package/dist/wizard/lib/kongo/pages.d.ts +60 -0
- package/dist/wizard/lib/kongo/pages.js +150 -0
- package/dist/wizard/lib/kongo/provisioner.d.ts +64 -0
- package/dist/wizard/lib/kongo/provisioner.js +116 -0
- package/dist/wizard/lib/kongo/seed.d.ts +49 -0
- package/dist/wizard/lib/kongo/seed.js +237 -0
- package/dist/wizard/lib/kongo/types.d.ts +323 -0
- package/dist/wizard/lib/kongo/types.js +11 -0
- package/dist/wizard/lib/kongo/variants.d.ts +57 -0
- package/dist/wizard/lib/kongo/variants.js +88 -0
- package/dist/wizard/lib/kongo/webhooks.d.ts +41 -0
- package/dist/wizard/lib/kongo/webhooks.js +112 -0
- package/dist/wizard/lib/marker.d.ts +28 -0
- package/dist/wizard/lib/marker.js +79 -0
- package/dist/wizard/lib/migrator.d.ts +35 -0
- package/dist/wizard/lib/migrator.js +190 -0
- package/dist/wizard/lib/natural-language-deploy.d.ts +30 -0
- package/dist/wizard/lib/natural-language-deploy.js +186 -0
- package/dist/wizard/lib/network.d.ts +22 -0
- package/dist/wizard/lib/network.js +72 -0
- package/dist/wizard/lib/oauth-core.d.ts +6 -0
- package/dist/wizard/lib/oauth-core.js +5 -0
- package/dist/wizard/lib/open-browser.d.ts +1 -0
- package/dist/wizard/lib/open-browser.js +26 -0
- package/dist/wizard/lib/patterns/ad-billing-adapter.d.ts +209 -0
- package/dist/wizard/lib/patterns/ad-billing-adapter.js +269 -0
- package/dist/wizard/lib/patterns/ad-platform-adapter.d.ts +200 -0
- package/dist/wizard/lib/patterns/ad-platform-adapter.js +212 -0
- package/dist/wizard/lib/patterns/daemon-process.d.ts +88 -0
- package/dist/wizard/lib/patterns/daemon-process.js +271 -0
- package/dist/wizard/lib/patterns/financial-transaction.d.ts +161 -0
- package/dist/wizard/lib/patterns/financial-transaction.js +132 -0
- package/dist/wizard/lib/patterns/funding-plan.d.ts +136 -0
- package/dist/wizard/lib/patterns/funding-plan.js +200 -0
- package/dist/wizard/lib/patterns/oauth-token-lifecycle.d.ts +94 -0
- package/dist/wizard/lib/patterns/oauth-token-lifecycle.js +139 -0
- package/dist/wizard/lib/patterns/outbound-rate-limiter.d.ts +67 -0
- package/dist/wizard/lib/patterns/outbound-rate-limiter.js +216 -0
- package/dist/wizard/lib/patterns/revenue-source-adapter.d.ts +96 -0
- package/dist/wizard/lib/patterns/revenue-source-adapter.js +182 -0
- package/dist/wizard/lib/patterns/stablecoin-adapter.d.ts +218 -0
- package/dist/wizard/lib/patterns/stablecoin-adapter.js +264 -0
- package/dist/wizard/lib/prd-validator.d.ts +39 -0
- package/dist/wizard/lib/prd-validator.js +137 -0
- package/dist/wizard/lib/project-init.d.ts +24 -0
- package/dist/wizard/lib/project-init.js +193 -0
- package/dist/wizard/lib/project-registry.d.ts +86 -0
- package/dist/wizard/lib/project-registry.js +359 -0
- package/dist/wizard/lib/provision-manifest.d.ts +44 -0
- package/dist/wizard/lib/provision-manifest.js +164 -0
- package/dist/wizard/lib/provisioner-registry.d.ts +15 -0
- package/dist/wizard/lib/provisioner-registry.js +34 -0
- package/dist/wizard/lib/provisioners/aws-vps.d.ts +6 -0
- package/dist/wizard/lib/provisioners/aws-vps.js +643 -0
- package/dist/wizard/lib/provisioners/cloudflare.d.ts +6 -0
- package/dist/wizard/lib/provisioners/cloudflare.js +300 -0
- package/dist/wizard/lib/provisioners/docker.d.ts +6 -0
- package/dist/wizard/lib/provisioners/docker.js +75 -0
- package/dist/wizard/lib/provisioners/http-client.d.ts +20 -0
- package/dist/wizard/lib/provisioners/http-client.js +79 -0
- package/dist/wizard/lib/provisioners/railway.d.ts +6 -0
- package/dist/wizard/lib/provisioners/railway.js +413 -0
- package/dist/wizard/lib/provisioners/scripts/caddyfile.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/caddyfile.js +54 -0
- package/dist/wizard/lib/provisioners/scripts/deploy-vps.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/deploy-vps.js +112 -0
- package/dist/wizard/lib/provisioners/scripts/docker-compose.d.ts +11 -0
- package/dist/wizard/lib/provisioners/scripts/docker-compose.js +91 -0
- package/dist/wizard/lib/provisioners/scripts/dockerfile.d.ts +5 -0
- package/dist/wizard/lib/provisioners/scripts/dockerfile.js +185 -0
- package/dist/wizard/lib/provisioners/scripts/ecosystem-config.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/ecosystem-config.js +36 -0
- package/dist/wizard/lib/provisioners/scripts/provision-vps.d.ts +14 -0
- package/dist/wizard/lib/provisioners/scripts/provision-vps.js +202 -0
- package/dist/wizard/lib/provisioners/scripts/rollback-vps.d.ts +10 -0
- package/dist/wizard/lib/provisioners/scripts/rollback-vps.js +67 -0
- package/dist/wizard/lib/provisioners/self-deploy.d.ts +41 -0
- package/dist/wizard/lib/provisioners/self-deploy.js +185 -0
- package/dist/wizard/lib/provisioners/static-s3.d.ts +6 -0
- package/dist/wizard/lib/provisioners/static-s3.js +235 -0
- package/dist/wizard/lib/provisioners/types.d.ts +40 -0
- package/dist/wizard/lib/provisioners/types.js +4 -0
- package/dist/wizard/lib/provisioners/vercel.d.ts +6 -0
- package/dist/wizard/lib/provisioners/vercel.js +287 -0
- package/dist/wizard/lib/pty-manager.d.ts +42 -0
- package/dist/wizard/lib/pty-manager.js +231 -0
- package/dist/wizard/lib/rate-limiter-core.d.ts +5 -0
- package/dist/wizard/lib/rate-limiter-core.js +5 -0
- package/dist/wizard/lib/reconciliation.d.ts +43 -0
- package/dist/wizard/lib/reconciliation.js +173 -0
- package/dist/wizard/lib/revenue-types.d.ts +5 -0
- package/dist/wizard/lib/revenue-types.js +1 -0
- package/dist/wizard/lib/route-optimizer.d.ts +28 -0
- package/dist/wizard/lib/route-optimizer.js +93 -0
- package/dist/wizard/lib/s3-deploy.d.ts +19 -0
- package/dist/wizard/lib/s3-deploy.js +156 -0
- package/dist/wizard/lib/safety-tiers.d.ts +76 -0
- package/dist/wizard/lib/safety-tiers.js +134 -0
- package/dist/wizard/lib/sentry-generator.d.ts +15 -0
- package/dist/wizard/lib/sentry-generator.js +116 -0
- package/dist/wizard/lib/server-config.d.ts +13 -0
- package/dist/wizard/lib/server-config.js +23 -0
- package/dist/wizard/lib/service-install.d.ts +18 -0
- package/dist/wizard/lib/service-install.js +182 -0
- package/dist/wizard/lib/site-scanner.d.ts +80 -0
- package/dist/wizard/lib/site-scanner.js +262 -0
- package/dist/wizard/lib/ssh-deploy.d.ts +25 -0
- package/dist/wizard/lib/ssh-deploy.js +225 -0
- package/dist/wizard/lib/templates.d.ts +24 -0
- package/dist/wizard/lib/templates.js +219 -0
- package/dist/wizard/lib/totp.d.ts +35 -0
- package/dist/wizard/lib/totp.js +276 -0
- package/dist/wizard/lib/tower-auth.d.ts +43 -0
- package/dist/wizard/lib/tower-auth.js +352 -0
- package/dist/wizard/lib/tower-rate-limit.d.ts +14 -0
- package/dist/wizard/lib/tower-rate-limit.js +61 -0
- package/dist/wizard/lib/tower-session.d.ts +28 -0
- package/dist/wizard/lib/tower-session.js +119 -0
- package/dist/wizard/lib/treasury-backup.d.ts +23 -0
- package/dist/wizard/lib/treasury-backup.js +126 -0
- package/dist/wizard/lib/treasury-heartbeat.d.ts +82 -0
- package/dist/wizard/lib/treasury-heartbeat.js +1104 -0
- package/dist/wizard/lib/updater.d.ts +29 -0
- package/dist/wizard/lib/updater.js +190 -0
- package/dist/wizard/lib/user-manager.d.ts +39 -0
- package/dist/wizard/lib/user-manager.js +182 -0
- package/dist/wizard/lib/vault.d.ts +26 -0
- package/dist/wizard/lib/vault.js +161 -0
- package/dist/wizard/router.d.ts +5 -0
- package/dist/wizard/router.js +15 -0
- package/dist/wizard/server.d.ts +18 -0
- package/dist/wizard/server.js +436 -0
- package/package.json +59 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern: Financial Transaction (Branded Types + Hash-Chained Append Log)
|
|
3
|
+
*
|
|
4
|
+
* Key principles:
|
|
5
|
+
* - All money in integer cents (Cents branded type) — NEVER floating point
|
|
6
|
+
* - Append-only immutable logs — never rewrite, only append
|
|
7
|
+
* - Hash chain for tamper detection (SHA-256 of previous entry)
|
|
8
|
+
* - Atomic writes (write-to-temp + fsync + rename) per ADR-1
|
|
9
|
+
* - macOS: use F_FULLFSYNC instead of fsync for financial files (§9.18)
|
|
10
|
+
* - Single-writer architecture — only the heartbeat daemon writes financial state
|
|
11
|
+
*
|
|
12
|
+
* Agents: Dockson (treasury), Steris (budget), Vin (analytics)
|
|
13
|
+
*
|
|
14
|
+
* PRD Reference: §9.9, §9.17 (branded types), §9.18 (macOS fsync), ADR-1/ADR-3
|
|
15
|
+
*/
|
|
16
|
+
type Cents = number & {
|
|
17
|
+
readonly __brand: 'Cents';
|
|
18
|
+
};
|
|
19
|
+
type Percentage = number & {
|
|
20
|
+
readonly __brand: 'Percentage';
|
|
21
|
+
};
|
|
22
|
+
type Ratio = number & {
|
|
23
|
+
readonly __brand: 'Ratio';
|
|
24
|
+
};
|
|
25
|
+
type AdPlatform = 'meta' | 'google' | 'tiktok' | 'linkedin' | 'twitter' | 'reddit';
|
|
26
|
+
type RevenueSource = 'stripe' | 'paddle';
|
|
27
|
+
type BankSource = 'mercury' | 'brex';
|
|
28
|
+
type TransactionSource = AdPlatform | RevenueSource | BankSource;
|
|
29
|
+
declare function toCents(dollars: number): Cents;
|
|
30
|
+
declare function toDollars(cents: Cents): number;
|
|
31
|
+
interface Transaction {
|
|
32
|
+
id: string;
|
|
33
|
+
projectId: string;
|
|
34
|
+
type: 'revenue' | 'spend' | 'refund';
|
|
35
|
+
source: TransactionSource;
|
|
36
|
+
externalId: string;
|
|
37
|
+
amount: Cents;
|
|
38
|
+
currency: 'USD';
|
|
39
|
+
description: string;
|
|
40
|
+
metadata: Record<string, string>;
|
|
41
|
+
createdAt: string;
|
|
42
|
+
reconciledAt?: string;
|
|
43
|
+
reconciledStatus?: 'matched' | 'discrepancy' | 'pending';
|
|
44
|
+
}
|
|
45
|
+
interface Budget {
|
|
46
|
+
id: string;
|
|
47
|
+
projectId: string;
|
|
48
|
+
period: 'daily' | 'weekly' | 'monthly';
|
|
49
|
+
totalAmount: Cents;
|
|
50
|
+
currency: 'USD';
|
|
51
|
+
allocations: Array<{
|
|
52
|
+
platform: AdPlatform;
|
|
53
|
+
amount: Cents;
|
|
54
|
+
dailyCap: Cents;
|
|
55
|
+
}>;
|
|
56
|
+
safetyTiers: {
|
|
57
|
+
autoApproveBelow: Cents;
|
|
58
|
+
agentApproveBelow: Cents;
|
|
59
|
+
humanConfirmBelow: Cents;
|
|
60
|
+
hardStopAbove: Cents;
|
|
61
|
+
};
|
|
62
|
+
createdAt: string;
|
|
63
|
+
updatedAt: string;
|
|
64
|
+
}
|
|
65
|
+
type CampaignStatus = 'draft' | 'pending_approval' | 'creating' | 'active' | 'paused' | 'completed' | 'error' | 'suspended' | 'deleting' | 'freeze_pending';
|
|
66
|
+
type CampaignEventSource = 'cli' | 'daemon' | 'platform' | 'agent';
|
|
67
|
+
interface CampaignStateEvent {
|
|
68
|
+
timestamp: string;
|
|
69
|
+
source: CampaignEventSource;
|
|
70
|
+
oldStatus: CampaignStatus;
|
|
71
|
+
newStatus: CampaignStatus;
|
|
72
|
+
reason: string;
|
|
73
|
+
ruleId?: string;
|
|
74
|
+
}
|
|
75
|
+
interface GrowthCampaign {
|
|
76
|
+
id: string;
|
|
77
|
+
projectId: string;
|
|
78
|
+
platform: AdPlatform;
|
|
79
|
+
externalId: string;
|
|
80
|
+
name: string;
|
|
81
|
+
status: CampaignStatus;
|
|
82
|
+
dailyBudget: Cents;
|
|
83
|
+
totalSpend: Cents;
|
|
84
|
+
metrics: {
|
|
85
|
+
impressions: number;
|
|
86
|
+
clicks: number;
|
|
87
|
+
conversions: number;
|
|
88
|
+
ctr: Percentage;
|
|
89
|
+
cpc: Cents;
|
|
90
|
+
roas: Ratio;
|
|
91
|
+
};
|
|
92
|
+
testGroupId?: string;
|
|
93
|
+
testVariant?: string;
|
|
94
|
+
testMetric?: 'ctr' | 'roas' | 'conversions';
|
|
95
|
+
events: CampaignStateEvent[];
|
|
96
|
+
createdAt: string;
|
|
97
|
+
updatedAt: string;
|
|
98
|
+
pausedAt?: string;
|
|
99
|
+
pauseReason?: string;
|
|
100
|
+
}
|
|
101
|
+
interface RevenueEvent {
|
|
102
|
+
id: string;
|
|
103
|
+
projectId: string;
|
|
104
|
+
source: RevenueSource;
|
|
105
|
+
type: 'charge' | 'subscription' | 'refund' | 'dispute';
|
|
106
|
+
amount: Cents;
|
|
107
|
+
currency: 'USD';
|
|
108
|
+
customerId?: string;
|
|
109
|
+
subscriptionId?: string;
|
|
110
|
+
metadata: Record<string, string>;
|
|
111
|
+
createdAt: string;
|
|
112
|
+
}
|
|
113
|
+
interface ReconciliationReport {
|
|
114
|
+
id: string;
|
|
115
|
+
date: string;
|
|
116
|
+
type: 'preliminary' | 'final';
|
|
117
|
+
projectId: string;
|
|
118
|
+
spend: Array<{
|
|
119
|
+
platform: AdPlatform;
|
|
120
|
+
voidforgeRecorded: Cents;
|
|
121
|
+
platformReported: Cents;
|
|
122
|
+
discrepancy: Cents;
|
|
123
|
+
status: 'matched' | 'discrepancy' | 'unavailable';
|
|
124
|
+
}>;
|
|
125
|
+
revenue: Array<{
|
|
126
|
+
source: RevenueSource;
|
|
127
|
+
recorded: Cents;
|
|
128
|
+
reported: Cents;
|
|
129
|
+
discrepancy: Cents;
|
|
130
|
+
status: 'matched' | 'discrepancy' | 'unavailable';
|
|
131
|
+
}>;
|
|
132
|
+
netPosition: Cents;
|
|
133
|
+
blendedRoas: Ratio;
|
|
134
|
+
alerts: string[];
|
|
135
|
+
}
|
|
136
|
+
type CultivationSystemState = 'inactive' | 'active' | 'frozen' | 'partial_freeze' | 'recovering' | 'degraded' | 'recovery_failed';
|
|
137
|
+
interface HashChainedEntry<T> {
|
|
138
|
+
data: T;
|
|
139
|
+
prevHash: string;
|
|
140
|
+
hash: string;
|
|
141
|
+
walIntentId?: string;
|
|
142
|
+
}
|
|
143
|
+
declare function computeHash(data: unknown, prevHash: string): string;
|
|
144
|
+
declare function createChainedEntry<T>(data: T, prevHash: string, walIntentId?: string): HashChainedEntry<T>;
|
|
145
|
+
declare function verifyChain<T>(entries: HashChainedEntry<T>[]): {
|
|
146
|
+
valid: boolean;
|
|
147
|
+
brokenAt?: number;
|
|
148
|
+
};
|
|
149
|
+
declare function atomicWrite(filePath: string, content: string): Promise<void>;
|
|
150
|
+
declare function appendToLog<T>(logPath: string, data: T, prevHash: string, walIntentId?: string): Promise<HashChainedEntry<T>>;
|
|
151
|
+
declare function idempotentAppend<T>(logPath: string, data: T, prevHash: string, walIntentId: string, recentEntries: HashChainedEntry<T>[]): Promise<HashChainedEntry<T> | null>;
|
|
152
|
+
declare const TREASURY_DIR: string;
|
|
153
|
+
declare const SPEND_LOG: string;
|
|
154
|
+
declare const REVENUE_LOG: string;
|
|
155
|
+
declare const PENDING_OPS: string;
|
|
156
|
+
declare const BUDGETS_FILE: string;
|
|
157
|
+
declare function formatCurrency(cents: Cents, detail?: boolean): string;
|
|
158
|
+
declare function formatRoas(roas: Ratio): string;
|
|
159
|
+
declare function formatPercentage(pct: Percentage, showSign?: boolean): string;
|
|
160
|
+
export type { Transaction, Budget, GrowthCampaign, RevenueEvent, ReconciliationReport, CampaignStatus, CampaignEventSource, CampaignStateEvent, CultivationSystemState, HashChainedEntry, Cents, Percentage, Ratio, AdPlatform, RevenueSource, BankSource, TransactionSource, };
|
|
161
|
+
export { toCents, toDollars, computeHash, createChainedEntry, verifyChain, atomicWrite, appendToLog, idempotentAppend, formatCurrency, formatRoas, formatPercentage, TREASURY_DIR, SPEND_LOG, REVENUE_LOG, PENDING_OPS, BUDGETS_FILE, };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern: Financial Transaction (Branded Types + Hash-Chained Append Log)
|
|
3
|
+
*
|
|
4
|
+
* Key principles:
|
|
5
|
+
* - All money in integer cents (Cents branded type) — NEVER floating point
|
|
6
|
+
* - Append-only immutable logs — never rewrite, only append
|
|
7
|
+
* - Hash chain for tamper detection (SHA-256 of previous entry)
|
|
8
|
+
* - Atomic writes (write-to-temp + fsync + rename) per ADR-1
|
|
9
|
+
* - macOS: use F_FULLFSYNC instead of fsync for financial files (§9.18)
|
|
10
|
+
* - Single-writer architecture — only the heartbeat daemon writes financial state
|
|
11
|
+
*
|
|
12
|
+
* Agents: Dockson (treasury), Steris (budget), Vin (analytics)
|
|
13
|
+
*
|
|
14
|
+
* PRD Reference: §9.9, §9.17 (branded types), §9.18 (macOS fsync), ADR-1/ADR-3
|
|
15
|
+
*/
|
|
16
|
+
import { createHash } from 'node:crypto';
|
|
17
|
+
import { appendFile, open, rename } from 'node:fs/promises';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
import { homedir, platform } from 'node:os';
|
|
20
|
+
function toCents(dollars) {
|
|
21
|
+
return Math.round(dollars * 100);
|
|
22
|
+
}
|
|
23
|
+
function toDollars(cents) {
|
|
24
|
+
return cents / 100;
|
|
25
|
+
}
|
|
26
|
+
function computeHash(data, prevHash) {
|
|
27
|
+
const payload = JSON.stringify(data) + prevHash;
|
|
28
|
+
return createHash('sha256').update(payload).digest('hex');
|
|
29
|
+
}
|
|
30
|
+
function createChainedEntry(data, prevHash, walIntentId) {
|
|
31
|
+
const hash = computeHash(data, prevHash);
|
|
32
|
+
return { data, prevHash, hash, walIntentId };
|
|
33
|
+
}
|
|
34
|
+
function verifyChain(entries) {
|
|
35
|
+
for (let i = 0; i < entries.length; i++) {
|
|
36
|
+
const expected = computeHash(entries[i].data, entries[i].prevHash);
|
|
37
|
+
if (expected !== entries[i].hash) {
|
|
38
|
+
return { valid: false, brokenAt: i };
|
|
39
|
+
}
|
|
40
|
+
if (i > 0 && entries[i].prevHash !== entries[i - 1].hash) {
|
|
41
|
+
return { valid: false, brokenAt: i };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { valid: true };
|
|
45
|
+
}
|
|
46
|
+
// ── Atomic File Write ─────────────────────────────────
|
|
47
|
+
// write-to-temp + fsync (F_FULLFSYNC on macOS) + rename
|
|
48
|
+
// Per ADR-1: all mutable financial file writes use this pattern.
|
|
49
|
+
async function atomicWrite(filePath, content) {
|
|
50
|
+
const tempPath = filePath + '.tmp.' + process.pid;
|
|
51
|
+
// Write to temp file
|
|
52
|
+
const fd = await open(tempPath, 'w');
|
|
53
|
+
try {
|
|
54
|
+
await fd.writeFile(content, 'utf-8');
|
|
55
|
+
// Durable sync — F_FULLFSYNC on macOS, fsync on Linux (§9.18)
|
|
56
|
+
if (platform() === 'darwin') {
|
|
57
|
+
// macOS: fsync() does NOT guarantee physical durability
|
|
58
|
+
// Must use fcntl(fd, F_FULLFSYNC) — 51 is the macOS constant
|
|
59
|
+
// Node.js datasync maps to fdatasync, not F_FULLFSYNC
|
|
60
|
+
// In production: use native addon or child_process to call fcntl(fd, 51)
|
|
61
|
+
// For now: document the gap — datasync is sufficient for crash safety, not power loss
|
|
62
|
+
await fd.datasync();
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
await fd.sync();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
await fd.close();
|
|
70
|
+
}
|
|
71
|
+
// Atomic rename — on POSIX, rename is atomic
|
|
72
|
+
await rename(tempPath, filePath);
|
|
73
|
+
}
|
|
74
|
+
// ── Append-Only Log ───────────────────────────────────
|
|
75
|
+
// For spend-log.jsonl and revenue-log.jsonl
|
|
76
|
+
async function appendToLog(logPath, data, prevHash, walIntentId) {
|
|
77
|
+
const entry = createChainedEntry(data, prevHash, walIntentId);
|
|
78
|
+
const line = JSON.stringify(entry) + '\n';
|
|
79
|
+
await appendFile(logPath, line, 'utf-8');
|
|
80
|
+
return entry;
|
|
81
|
+
}
|
|
82
|
+
// ── Idempotent Append (ADR-3 WAL replay) ──────────────
|
|
83
|
+
// Before appending during WAL replay, check if the intent ID exists in recent entries.
|
|
84
|
+
async function idempotentAppend(logPath, data, prevHash, walIntentId, recentEntries) {
|
|
85
|
+
// Check if this WAL intent was already applied
|
|
86
|
+
const existing = recentEntries.find(e => e.walIntentId === walIntentId);
|
|
87
|
+
if (existing) {
|
|
88
|
+
return null; // Already applied — skip
|
|
89
|
+
}
|
|
90
|
+
return appendToLog(logPath, data, prevHash, walIntentId);
|
|
91
|
+
}
|
|
92
|
+
// ── Storage Layout ────────────────────────────────────
|
|
93
|
+
// ~/.voidforge/treasury/
|
|
94
|
+
// ├── vault.enc # financial vault (AES-256-GCM, Argon2id)
|
|
95
|
+
// ├── budgets.json # active budget allocations per project
|
|
96
|
+
// ├── spend-log.jsonl # append-only spend log (immutable, hash-chained)
|
|
97
|
+
// ├── revenue-log.jsonl # append-only revenue log (immutable, hash-chained)
|
|
98
|
+
// ├── pending-ops.jsonl # WAL for platform API operations (ADR-3)
|
|
99
|
+
// ├── campaigns/
|
|
100
|
+
// │ └── {projectId}/
|
|
101
|
+
// │ ├── meta-{id}.json
|
|
102
|
+
// │ └── google-{id}.json
|
|
103
|
+
// ├── reconciliation/
|
|
104
|
+
// │ ├── 2026-03-17.json
|
|
105
|
+
// │ └── 2026-03-16.json
|
|
106
|
+
// └── reports/
|
|
107
|
+
// └── 2026-03.json # monthly summary
|
|
108
|
+
const TREASURY_DIR = join(homedir(), '.voidforge', 'treasury');
|
|
109
|
+
const SPEND_LOG = join(TREASURY_DIR, 'spend-log.jsonl');
|
|
110
|
+
const REVENUE_LOG = join(TREASURY_DIR, 'revenue-log.jsonl');
|
|
111
|
+
const PENDING_OPS = join(TREASURY_DIR, 'pending-ops.jsonl');
|
|
112
|
+
const BUDGETS_FILE = join(TREASURY_DIR, 'budgets.json');
|
|
113
|
+
// ── Number Formatting (§9.15.4) ───────────────────────
|
|
114
|
+
function formatCurrency(cents, detail = false) {
|
|
115
|
+
const dollars = toDollars(cents);
|
|
116
|
+
if (dollars >= 100_000) {
|
|
117
|
+
return dollars >= 1_000_000 ? `$${(dollars / 1_000_000).toFixed(1)}M` : `$${Math.round(dollars / 1000)}K`;
|
|
118
|
+
}
|
|
119
|
+
if (detail)
|
|
120
|
+
return `$${dollars.toFixed(2)}`;
|
|
121
|
+
if (cents < 0)
|
|
122
|
+
return `-$${Math.abs(dollars).toLocaleString('en-US', { maximumFractionDigits: 0 })}`;
|
|
123
|
+
return `$${dollars.toLocaleString('en-US', { maximumFractionDigits: 0 })}`;
|
|
124
|
+
}
|
|
125
|
+
function formatRoas(roas) {
|
|
126
|
+
return `${roas.toFixed(1)}x`;
|
|
127
|
+
}
|
|
128
|
+
function formatPercentage(pct, showSign = false) {
|
|
129
|
+
const sign = showSign && pct > 0 ? '+' : '';
|
|
130
|
+
return `${sign}${pct.toFixed(1)}%`;
|
|
131
|
+
}
|
|
132
|
+
export { toCents, toDollars, computeHash, createChainedEntry, verifyChain, atomicWrite, appendToLog, idempotentAppend, formatCurrency, formatRoas, formatPercentage, TREASURY_DIR, SPEND_LOG, REVENUE_LOG, PENDING_OPS, BUDGETS_FILE, };
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
type Cents = number & {
|
|
18
|
+
readonly __brand: 'Cents';
|
|
19
|
+
};
|
|
20
|
+
declare function toCents(dollars: number): Cents;
|
|
21
|
+
declare function toDollars(cents: Cents): number;
|
|
22
|
+
type StablecoinProviderType = 'circle' | 'bridge' | 'manual';
|
|
23
|
+
interface StablecoinFundingSource {
|
|
24
|
+
id: string;
|
|
25
|
+
provider: StablecoinProviderType;
|
|
26
|
+
asset: string;
|
|
27
|
+
network: string;
|
|
28
|
+
sourceAccountId: string;
|
|
29
|
+
whitelistedDestinationBankId: string;
|
|
30
|
+
status: 'active' | 'suspended' | 'unconfigured';
|
|
31
|
+
}
|
|
32
|
+
type BankProvider = 'mercury' | 'external';
|
|
33
|
+
interface OperatingBankAccount {
|
|
34
|
+
id: string;
|
|
35
|
+
provider: BankProvider;
|
|
36
|
+
accountId: string;
|
|
37
|
+
currency: 'USD';
|
|
38
|
+
availableBalanceCents: Cents;
|
|
39
|
+
reservedBalanceCents: Cents;
|
|
40
|
+
minimumBufferCents: Cents;
|
|
41
|
+
}
|
|
42
|
+
type CapabilityState = 'FULLY_FUNDABLE' | 'MONITORED_ONLY' | 'UNSUPPORTED';
|
|
43
|
+
type BillingMode = 'monthly_invoicing' | 'direct_debit' | 'extended_credit' | 'manual_bank_transfer' | 'card_only' | 'unknown';
|
|
44
|
+
type AdPlatform = 'google' | 'meta';
|
|
45
|
+
interface PlatformBillingProfile {
|
|
46
|
+
platform: AdPlatform;
|
|
47
|
+
capabilityState: CapabilityState;
|
|
48
|
+
billingMode: BillingMode;
|
|
49
|
+
externalAccountId: string;
|
|
50
|
+
billingSetupId?: string;
|
|
51
|
+
invoiceGroupId?: string;
|
|
52
|
+
paymentProfileId?: string;
|
|
53
|
+
fundingSourceId?: string;
|
|
54
|
+
currency: 'USD';
|
|
55
|
+
nextDueDate?: string;
|
|
56
|
+
status: 'active' | 'degraded' | 'suspended' | 'unconfigured';
|
|
57
|
+
}
|
|
58
|
+
type FundingPlanStatus = 'DRAFT' | 'APPROVED' | 'PENDING_SETTLEMENT' | 'SETTLED' | 'FAILED' | 'FROZEN';
|
|
59
|
+
type FundingPlanReason = 'LOW_BUFFER' | 'INVOICE_DUE' | 'RUNWAY_SHORTFALL' | 'MANUAL_REQUEST';
|
|
60
|
+
type ApprovalMode = 'policy_auto' | 'vault_manual' | 'totp_required';
|
|
61
|
+
type TargetPlatform = AdPlatform | 'shared_buffer';
|
|
62
|
+
interface FundingPlan {
|
|
63
|
+
id: string;
|
|
64
|
+
createdAt: string;
|
|
65
|
+
updatedAt: string;
|
|
66
|
+
reason: FundingPlanReason;
|
|
67
|
+
sourceFundingId: string;
|
|
68
|
+
destinationBankId: string;
|
|
69
|
+
targetPlatform: TargetPlatform;
|
|
70
|
+
requiredCents: Cents;
|
|
71
|
+
reservedCents: Cents;
|
|
72
|
+
status: FundingPlanStatus;
|
|
73
|
+
approvalMode: ApprovalMode;
|
|
74
|
+
approvedAt?: string;
|
|
75
|
+
approvedBy?: string;
|
|
76
|
+
settledAt?: string;
|
|
77
|
+
failureReason?: string;
|
|
78
|
+
idempotencyKey: string;
|
|
79
|
+
previousHash: string;
|
|
80
|
+
hash: string;
|
|
81
|
+
}
|
|
82
|
+
type TransferDirection = 'crypto_to_fiat' | 'bank_to_platform' | 'platform_debit';
|
|
83
|
+
type TransferStatus = 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled';
|
|
84
|
+
interface TransferRecord {
|
|
85
|
+
id: string;
|
|
86
|
+
fundingPlanId: string;
|
|
87
|
+
providerTransferId: string;
|
|
88
|
+
bankTransactionId?: string;
|
|
89
|
+
direction: TransferDirection;
|
|
90
|
+
amountCents: Cents;
|
|
91
|
+
feesCents: Cents;
|
|
92
|
+
netAmountCents: Cents;
|
|
93
|
+
currency: 'USD';
|
|
94
|
+
reference: string;
|
|
95
|
+
status: TransferStatus;
|
|
96
|
+
initiatedAt: string;
|
|
97
|
+
completedAt?: string;
|
|
98
|
+
previousHash: string;
|
|
99
|
+
hash: string;
|
|
100
|
+
}
|
|
101
|
+
type ReconciliationResult = 'MATCHED' | 'WITHIN_THRESHOLD' | 'MISMATCH';
|
|
102
|
+
interface ReconciliationRecord {
|
|
103
|
+
id: string;
|
|
104
|
+
platform: AdPlatform;
|
|
105
|
+
date: string;
|
|
106
|
+
spendCents: Cents;
|
|
107
|
+
bankSettledCents: Cents;
|
|
108
|
+
invoiceCents: Cents;
|
|
109
|
+
varianceCents: Cents;
|
|
110
|
+
result: ReconciliationResult;
|
|
111
|
+
notes: string;
|
|
112
|
+
createdAt: string;
|
|
113
|
+
previousHash: string;
|
|
114
|
+
hash: string;
|
|
115
|
+
}
|
|
116
|
+
declare function computePlanHash(plan: Omit<FundingPlan, 'hash'>, previousHash: string): string;
|
|
117
|
+
declare function computeTransferHash(record: Omit<TransferRecord, 'hash'>, previousHash: string): string;
|
|
118
|
+
declare function computeReconciliationHash(record: Omit<ReconciliationRecord, 'hash'>, previousHash: string): string;
|
|
119
|
+
declare function createFundingPlan(reason: FundingPlanReason, sourceFundingId: string, destinationBankId: string, targetPlatform: TargetPlatform, requiredCents: Cents, previousHash: string): FundingPlan;
|
|
120
|
+
declare function approvePlan(plan: FundingPlan, approvalMode: ApprovalMode, approvedBy: string): FundingPlan;
|
|
121
|
+
declare function transitionPlan(plan: FundingPlan, newStatus: FundingPlanStatus, reason?: string): FundingPlan;
|
|
122
|
+
declare function calculateRunway(bankBalanceCents: Cents, dailySpendRateCents: Cents): number;
|
|
123
|
+
declare function shouldTriggerOfframp(bankBalanceCents: Cents, bufferThresholdCents: Cents, pendingSpendCents: Cents, pendingOfframpCents: Cents): boolean;
|
|
124
|
+
declare function calculateRequiredOfframp(bankBalanceCents: Cents, bufferThresholdCents: Cents, pendingSpendCents: Cents, pendingOfframpCents: Cents, minimumOfframpCents: Cents): Cents;
|
|
125
|
+
declare function reconcileTransfer(transferAmountCents: Cents, bankTransactionCents: Cents, platformSpendCents: Cents, thresholdBps: number, // basis points tolerance (e.g., 50 = 0.5%)
|
|
126
|
+
platform: AdPlatform, date: string, previousHash: string): ReconciliationRecord;
|
|
127
|
+
interface PendingObligation {
|
|
128
|
+
id: string;
|
|
129
|
+
platform: AdPlatform;
|
|
130
|
+
amountCents: Cents;
|
|
131
|
+
dueDate: string;
|
|
132
|
+
overdue: boolean;
|
|
133
|
+
}
|
|
134
|
+
declare function prioritizeObligations(obligations: PendingObligation[]): PendingObligation[];
|
|
135
|
+
export type { Cents, StablecoinProviderType, StablecoinFundingSource, BankProvider, OperatingBankAccount, CapabilityState, BillingMode, AdPlatform, PlatformBillingProfile, FundingPlanStatus, FundingPlanReason, ApprovalMode, TargetPlatform, FundingPlan, TransferDirection, TransferStatus, TransferRecord, ReconciliationResult, ReconciliationRecord, PendingObligation, };
|
|
136
|
+
export { toCents, toDollars, computePlanHash, computeTransferHash, computeReconciliationHash, createFundingPlan, approvePlan, transitionPlan, calculateRunway, shouldTriggerOfframp, calculateRequiredOfframp, reconcileTransfer, prioritizeObligations, };
|
|
@@ -0,0 +1,200 @@
|
|
|
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
|
+
import { createHash } from 'node:crypto';
|
|
18
|
+
function toCents(dollars) {
|
|
19
|
+
return Math.round(dollars * 100);
|
|
20
|
+
}
|
|
21
|
+
function toDollars(cents) {
|
|
22
|
+
return cents / 100;
|
|
23
|
+
}
|
|
24
|
+
// ── Hash Chain Helpers ──────────────────────────────
|
|
25
|
+
function computePlanHash(plan, previousHash) {
|
|
26
|
+
const payload = JSON.stringify({
|
|
27
|
+
id: plan.id,
|
|
28
|
+
reason: plan.reason,
|
|
29
|
+
sourceFundingId: plan.sourceFundingId,
|
|
30
|
+
destinationBankId: plan.destinationBankId,
|
|
31
|
+
requiredCents: plan.requiredCents,
|
|
32
|
+
status: plan.status,
|
|
33
|
+
createdAt: plan.createdAt,
|
|
34
|
+
idempotencyKey: plan.idempotencyKey,
|
|
35
|
+
}) + previousHash;
|
|
36
|
+
return createHash('sha256').update(payload).digest('hex');
|
|
37
|
+
}
|
|
38
|
+
function computeTransferHash(record, previousHash) {
|
|
39
|
+
const payload = JSON.stringify({
|
|
40
|
+
id: record.id,
|
|
41
|
+
fundingPlanId: record.fundingPlanId,
|
|
42
|
+
providerTransferId: record.providerTransferId,
|
|
43
|
+
amountCents: record.amountCents,
|
|
44
|
+
feesCents: record.feesCents,
|
|
45
|
+
status: record.status,
|
|
46
|
+
initiatedAt: record.initiatedAt,
|
|
47
|
+
}) + previousHash;
|
|
48
|
+
return createHash('sha256').update(payload).digest('hex');
|
|
49
|
+
}
|
|
50
|
+
function computeReconciliationHash(record, previousHash) {
|
|
51
|
+
const payload = JSON.stringify({
|
|
52
|
+
id: record.id,
|
|
53
|
+
platform: record.platform,
|
|
54
|
+
date: record.date,
|
|
55
|
+
spendCents: record.spendCents,
|
|
56
|
+
bankSettledCents: record.bankSettledCents,
|
|
57
|
+
invoiceCents: record.invoiceCents,
|
|
58
|
+
varianceCents: record.varianceCents,
|
|
59
|
+
result: record.result,
|
|
60
|
+
}) + previousHash;
|
|
61
|
+
return createHash('sha256').update(payload).digest('hex');
|
|
62
|
+
}
|
|
63
|
+
// ── Pure Logic: Funding Plan Creation ───────────────
|
|
64
|
+
function createFundingPlan(reason, sourceFundingId, destinationBankId, targetPlatform, requiredCents, previousHash) {
|
|
65
|
+
const now = new Date().toISOString();
|
|
66
|
+
const id = crypto.randomUUID();
|
|
67
|
+
const idempotencyKey = crypto.randomUUID();
|
|
68
|
+
const draft = {
|
|
69
|
+
id,
|
|
70
|
+
createdAt: now,
|
|
71
|
+
updatedAt: now,
|
|
72
|
+
reason,
|
|
73
|
+
sourceFundingId,
|
|
74
|
+
destinationBankId,
|
|
75
|
+
targetPlatform,
|
|
76
|
+
requiredCents,
|
|
77
|
+
reservedCents: 0,
|
|
78
|
+
status: 'DRAFT',
|
|
79
|
+
approvalMode: 'policy_auto', // default; upgraded by approval logic
|
|
80
|
+
idempotencyKey,
|
|
81
|
+
previousHash,
|
|
82
|
+
};
|
|
83
|
+
const hash = computePlanHash(draft, previousHash);
|
|
84
|
+
return { ...draft, hash };
|
|
85
|
+
}
|
|
86
|
+
// ── Pure Logic: Plan Approval ───────────────────────
|
|
87
|
+
function approvePlan(plan, approvalMode, approvedBy) {
|
|
88
|
+
if (plan.status !== 'DRAFT') {
|
|
89
|
+
throw new Error(`Cannot approve plan in status ${plan.status} — must be DRAFT`);
|
|
90
|
+
}
|
|
91
|
+
const now = new Date().toISOString();
|
|
92
|
+
const approved = {
|
|
93
|
+
...plan,
|
|
94
|
+
status: 'APPROVED',
|
|
95
|
+
approvalMode,
|
|
96
|
+
approvedBy,
|
|
97
|
+
approvedAt: now,
|
|
98
|
+
updatedAt: now,
|
|
99
|
+
previousHash: plan.hash,
|
|
100
|
+
};
|
|
101
|
+
const hash = computePlanHash(approved, plan.hash);
|
|
102
|
+
return { ...approved, hash };
|
|
103
|
+
}
|
|
104
|
+
// ── Pure Logic: Plan State Transitions ──────────────
|
|
105
|
+
function transitionPlan(plan, newStatus, reason) {
|
|
106
|
+
// Validate allowed transitions
|
|
107
|
+
const allowedTransitions = {
|
|
108
|
+
'DRAFT': ['APPROVED', 'FROZEN'],
|
|
109
|
+
'APPROVED': ['PENDING_SETTLEMENT', 'FROZEN', 'FAILED'],
|
|
110
|
+
'PENDING_SETTLEMENT': ['SETTLED', 'FAILED', 'FROZEN'],
|
|
111
|
+
'SETTLED': [], // terminal state
|
|
112
|
+
'FAILED': ['DRAFT'], // can retry by creating new draft
|
|
113
|
+
'FROZEN': ['DRAFT'], // can unfreeze to draft for re-approval
|
|
114
|
+
};
|
|
115
|
+
const allowed = allowedTransitions[plan.status];
|
|
116
|
+
if (!allowed.includes(newStatus)) {
|
|
117
|
+
throw new Error(`Invalid transition: ${plan.status} → ${newStatus}. Allowed: ${allowed.join(', ') || 'none (terminal)'}`);
|
|
118
|
+
}
|
|
119
|
+
const now = new Date().toISOString();
|
|
120
|
+
const transitioned = {
|
|
121
|
+
...plan,
|
|
122
|
+
status: newStatus,
|
|
123
|
+
updatedAt: now,
|
|
124
|
+
settledAt: newStatus === 'SETTLED' ? now : plan.settledAt,
|
|
125
|
+
failureReason: newStatus === 'FAILED' ? reason : plan.failureReason,
|
|
126
|
+
previousHash: plan.hash,
|
|
127
|
+
};
|
|
128
|
+
const hash = computePlanHash(transitioned, plan.hash);
|
|
129
|
+
return { ...transitioned, hash };
|
|
130
|
+
}
|
|
131
|
+
// ── Pure Logic: Runway Calculation ──────────────────
|
|
132
|
+
function calculateRunway(bankBalanceCents, dailySpendRateCents) {
|
|
133
|
+
if (dailySpendRateCents <= 0)
|
|
134
|
+
return Infinity;
|
|
135
|
+
return Math.floor(bankBalanceCents / dailySpendRateCents);
|
|
136
|
+
}
|
|
137
|
+
// ── Pure Logic: Off-ramp Trigger Decision ───────────
|
|
138
|
+
function shouldTriggerOfframp(bankBalanceCents, bufferThresholdCents, pendingSpendCents, pendingOfframpCents) {
|
|
139
|
+
// Available balance after accounting for pending obligations
|
|
140
|
+
const effectiveBalance = (bankBalanceCents - pendingSpendCents + pendingOfframpCents);
|
|
141
|
+
return effectiveBalance < bufferThresholdCents;
|
|
142
|
+
}
|
|
143
|
+
// ── Pure Logic: Required Off-ramp Amount ────────────
|
|
144
|
+
function calculateRequiredOfframp(bankBalanceCents, bufferThresholdCents, pendingSpendCents, pendingOfframpCents, minimumOfframpCents) {
|
|
145
|
+
const deficit = (bufferThresholdCents + pendingSpendCents - bankBalanceCents - pendingOfframpCents);
|
|
146
|
+
if (deficit <= 0)
|
|
147
|
+
return 0;
|
|
148
|
+
// Round up to minimum off-ramp amount if below provider minimum
|
|
149
|
+
return Math.max(deficit, minimumOfframpCents);
|
|
150
|
+
}
|
|
151
|
+
// ── Pure Logic: Reconciliation ──────────────────────
|
|
152
|
+
function reconcileTransfer(transferAmountCents, bankTransactionCents, platformSpendCents, thresholdBps, // basis points tolerance (e.g., 50 = 0.5%)
|
|
153
|
+
platform, date, previousHash) {
|
|
154
|
+
const invoiceCents = transferAmountCents; // what was transferred
|
|
155
|
+
const varianceCents = Math.abs(invoiceCents - bankTransactionCents);
|
|
156
|
+
// Determine result based on variance threshold
|
|
157
|
+
let result;
|
|
158
|
+
if (varianceCents === 0) {
|
|
159
|
+
result = 'MATCHED';
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const varianceBps = invoiceCents > 0
|
|
163
|
+
? (varianceCents / invoiceCents) * 10_000
|
|
164
|
+
: 0;
|
|
165
|
+
result = varianceBps <= thresholdBps ? 'WITHIN_THRESHOLD' : 'MISMATCH';
|
|
166
|
+
}
|
|
167
|
+
const id = crypto.randomUUID();
|
|
168
|
+
const now = new Date().toISOString();
|
|
169
|
+
const record = {
|
|
170
|
+
id,
|
|
171
|
+
platform,
|
|
172
|
+
date,
|
|
173
|
+
spendCents: platformSpendCents,
|
|
174
|
+
bankSettledCents: bankTransactionCents,
|
|
175
|
+
invoiceCents,
|
|
176
|
+
varianceCents,
|
|
177
|
+
result,
|
|
178
|
+
notes: result === 'MISMATCH'
|
|
179
|
+
? `Variance ${varianceCents} cents exceeds ${thresholdBps}bps threshold`
|
|
180
|
+
: result === 'WITHIN_THRESHOLD'
|
|
181
|
+
? `Variance ${varianceCents} cents within ${thresholdBps}bps threshold`
|
|
182
|
+
: 'Exact match',
|
|
183
|
+
createdAt: now,
|
|
184
|
+
previousHash,
|
|
185
|
+
};
|
|
186
|
+
const hash = computeReconciliationHash(record, previousHash);
|
|
187
|
+
return { ...record, hash };
|
|
188
|
+
}
|
|
189
|
+
function prioritizeObligations(obligations) {
|
|
190
|
+
return [...obligations].sort((a, b) => {
|
|
191
|
+
// Overdue items first
|
|
192
|
+
if (a.overdue && !b.overdue)
|
|
193
|
+
return -1;
|
|
194
|
+
if (!a.overdue && b.overdue)
|
|
195
|
+
return 1;
|
|
196
|
+
// Then by due date (earliest first)
|
|
197
|
+
return new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime();
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
export { toCents, toDollars, computePlanHash, computeTransferHash, computeReconciliationHash, createFundingPlan, approvePlan, transitionPlan, calculateRunway, shouldTriggerOfframp, calculateRequiredOfframp, reconcileTransfer, prioritizeObligations, };
|