Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

98 linesCodeBlameRaw
1// One-time demo cutover. Preview by default; --apply detaches test billing.
2// Keep usage history and flagon-io. Stop on data requiring the general service
3// workflow (enterprise, purchased grants, debt, or historical month closes).
4import assert from 'node:assert/strict';
5import { readFileSync, mkdirSync, writeFileSync } from 'node:fs';
6import { randomUUID } from 'node:crypto';
7import { getPlatformProxy } from 'wrangler';
8
9const apply = process.argv.includes('--apply');
10const source = name => readFileSync(`services/billing/src/${name}.rs`, 'utf8');
11const constant = (file, name) => {
12 const match = source(file).match(new RegExp(`const ${name}: &str = "([\\s\\S]*?)";`));
13 assert.ok(match, `${file}::${name}`);
14 return match[1];
15};
16const found = constant('detach', 'FOUND').replace('{internal}', constant('sales', 'INTERNAL_SQL'));
17const fixed = [...source('detach').split('const STATEMENTS: &[&str] = &[')[1].split('\n];')[0].matchAll(/"([^"]*)"/g)].map(m => m[1]);
18// 16 since migration 0064: test-mode complimentary enrollments end too.
19assert.equal(fixed.length, 16, 'Review changed detach statements before running');
20const at = new Date().toISOString();
21const month = at.slice(0, 7);
22const by = 'codex:user-authorized-demo-cutover';
23const dir = 'target/billing-downgrade';
24mkdirSync(dir, { recursive: true });
25const configPath = `${dir}/maintenance.json`;
26writeFileSync(configPath, JSON.stringify({ name: 'g1t-demo-cutover', account_id: '1e6f2cffa3f445920836e8ebe446bb58', compatibility_date: '2026-09-26',
27 services: [{ binding: 'BILLING', service: 'g1t-billing', remote: true }],
28 d1_databases: [{ binding: 'DB', database_name: 'g1t-billing', database_id: '695a6979-fd07-4850-bc97-904a6b4b7a04', remote: true }],
29}));
30const proxy = await getPlatformProxy({ configPath, persist: false, remoteBindings: true, envFiles: [] });
31try {
32 const { DB, BILLING } = proxy.env;
33 const status = await (await BILLING.fetch('https://billing/rpc/status', { method: 'POST', body: '{}' })).json();
34 assert.equal(status.live, false, 'Refusing to detach live billing');
35 assert.equal(status.enabled, true);
36 const query = async (sql, args = []) => (await DB.prepare(sql).bind(...args).all()).results;
37 const snapshot = await query("SELECT * FROM billing_accounts WHERE id='ws_flagon-io'");
38 assert.equal(snapshot.length, 1);
39 const tables = new Set((await query("SELECT name FROM sqlite_master WHERE type='table'")).map(r => r.name));
40 const targets = (await query(found, [null, month])).filter(r => r.workspace !== 'flagon-io');
41 for (const row of targets) {
42 assert.equal(row.comped + row.enterprise + row.ai_grants + row.pending_past, 0, `Manual review required: ${row.workspace}`);
43 assert.equal(row.balance - Math.max(0, row.paid), 0, `Unexpected balance: ${row.workspace}`);
44 }
45 const overrides = await query("SELECT id FROM billing_accounts WHERE id <> 'ws_flagon-io' AND (team_granted <> 0 OR terms_kind <> 'standard' OR discount_percent <> 0)");
46 assert.equal(overrides.length, 0, 'Additional plan grants need review');
47 writeFileSync(`${dir}/demo-${apply ? 'apply' : 'preview'}.json`, JSON.stringify({ at, status, targets, preserved: snapshot }, null, 2));
48 console.log(JSON.stringify({ apply, preserved: 'flagon-io', targets }));
49 if (apply) {
50 const key = readFileSync('.secrets/stripe-test-key.txt', 'utf8').trim();
51 assert.match(key, /^(sk|rk)_test_/);
52 const version = source('stripe').match(/STRIPE_VERSION: &str = "([^"]+)"/)[1];
53 const stripe = async (path, method = 'GET') => {
54 const response = await fetch(`https://api.stripe.com/v1${path}`, { method, headers: { Authorization: `Bearer ${key}`, 'Stripe-Version': version } });
55 const value = await response.json();
56 assert.ok(response.ok, `Stripe ${method}: ${response.status} ${value.error?.code ?? ''}`);
57 assert.equal(value.livemode, false, 'Expected a test object');
58 return value;
59 };
60 for (const row of targets) {
61 const workspace = row.workspace;
62 const subscriptions = await query('SELECT subscription_id FROM subscriptions WHERE workspace=? AND status <> \'canceled\'', [workspace]);
63 const customer = (await query('SELECT customer_id FROM accounts WHERE workspace=?', [workspace]))[0]?.customer_id;
64 if (customer) await stripe(`/customers/${customer}`);
65 for (const { subscription_id: id } of subscriptions) {
66 const subscription = await stripe(`/subscriptions/${id}`);
67 assert.equal(subscription.customer, customer, 'Subscription/customer mismatch');
68 if (subscription.status !== 'canceled') {
69 assert.equal((await stripe(`/subscriptions/${id}`, 'DELETE')).status, 'canceled');
70 }
71 }
72 // Refresh after Stripe calls. Each workspace's local changes commit
73 // together; interruption is retryable, including canceled subscriptions.
74 const current = (await query(found, [workspace, month]))[0];
75 assert.equal(current.balance - Math.max(0, current.paid), 0);
76 assert.equal(current.ai_grants + current.pending_past + current.enterprise + current.comped, 0);
77 const values = [workspace, at, by, month];
78 const statements = fixed.filter(sql => tables.has('invoice_attempts') || !sql.includes('invoice_attempts')).map(sql => {
79 const count = Math.max(...[...sql.matchAll(/\?(\d+)/g)].map(m => Number(m[1])));
80 return DB.prepare(sql).bind(...values.slice(0, count));
81 });
82 if (current.paid > 0) {
83 statements.push(DB.prepare(constant('detach', 'LEDGER')).bind(`led_${randomUUID()}`, workspace, -current.paid, 'Reverse demo payments before live billing', `test_detach/${workspace}/${at}`, by, at, null));
84 statements.push(DB.prepare(constant('detach', 'BALANCE')).bind(workspace, -current.paid, at));
85 }
86 statements.push(DB.prepare('INSERT INTO admin_actions (id, account, action, detail, by, created_at) VALUES (?, ?, ?, ?, ?, ?)').bind(`adm_${randomUUID()}`, `ws_${workspace}`, 'test_detach', 'User requested Free plan before live launch; Stripe test subscriptions canceled; demo payments reversed; history retained.', by, at));
87 await DB.batch(statements);
88 console.log(JSON.stringify({ detached: workspace }));
89 }
90 assert.deepEqual(await query("SELECT * FROM billing_accounts WHERE id='ws_flagon-io'"), snapshot);
91 const remaining = (await query(found, [null, month])).filter(r => r.workspace !== 'flagon-io');
92 for (const row of remaining) {
93 for (const field of ['subscriptions', 'customer', 'card_cached', 'balance', 'paid', 'plan_unreversed', 'plan_credit', 'pending_now', 'open_disputes']) assert.equal(row[field], 0, `${row.workspace}: ${field}`);
94 }
95 writeFileSync(`${dir}/demo-verified.json`, JSON.stringify({ at: new Date().toISOString(), rows: remaining, flagonPreserved: true }, null, 2));
96 console.log('Verified: non-flagon workspaces detached; flagon-io terms preserved.');
97 }
98} finally { await proxy.dispose(); }