Skip to content

Commit fd2fc48

Browse files
authored
refactor(gateway): remove quota schema compatibility (#65)
## Summary - delete QuotaTracker schema migration, retired-row pruning, and rebuild compatibility code - initialize new quota objects directly with the exact current schema - require every existing quota object and maintenance check to match the current schema exactly - document the current-only QuotaTracker storage contract ## Production reconciliation - the sole active production user quota object was migrated by the preceding release - `/usage`, quota peek/record, idempotency, and a live prompt all succeeded against the reconciled object - this change intentionally fails closed for any non-current quota schema ## Validation - `pnpm --filter @cheatcode/gateway-worker typecheck` - `pnpm --filter @cheatcode/gateway-worker build` - `pnpm knip` - `pnpm lint` - `pnpm typecheck` - `pnpm build`
1 parent c2b8dd4 commit fd2fc48

3 files changed

Lines changed: 20 additions & 194 deletions

File tree

apps/gateway-worker/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ reconciles dormant objects to that shape when they are next activated. Run
106106
creation is also durably idempotent in Postgres, so request-cache evolution
107107
cannot create a duplicate run.
108108

109+
`QuotaTracker` has no compatibility migration path. New objects initialize
110+
directly into the current exact schema, while existing objects must already
111+
match that schema before any quota operation is admitted.
112+
109113
`/v1/tools` and `/v1/agents` read the shared framework-free capability catalog
110114
from `@cheatcode/types`. The Mastra registries are statically constrained to the
111115
same exact names; workflows are exposed through tools and are not reported as
Lines changed: 15 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import {
22
assertExactSqliteSchema,
3-
assertSqliteRowCountPreserved,
43
type ExpectedSqliteObject,
5-
reconcileExactSqliteStorage,
64
setCurrentSqliteStorageVersion,
75
} from "@cheatcode/durable-storage";
8-
import { QUOTA_FEATURES } from "@cheatcode/types/quota";
96

107
const MAX_FINITE_REAL = "1.7976931348623157e308";
118
const FEATURE_CHECK = "feature IN ('composio_calls', 'sandbox_hours')";
@@ -46,8 +43,6 @@ const USAGE_EVENT_INDEX_SQL =
4643
"CREATE INDEX usage_event_feature_time_idx ON usage_event(feature, recorded_at)";
4744
const QUOTA_OPERATION_INDEX_SQL =
4845
"CREATE INDEX quota_operation_feature_time_idx ON quota_operation(feature, recorded_at)";
49-
const QUOTA_TABLES = ["counter", "limit_override", "usage_event", "quota_operation"] as const;
50-
const CURRENT_QUOTA_FEATURES = [QUOTA_FEATURES.composioCalls, QUOTA_FEATURES.sandboxHours] as const;
5146

5247
const QUOTA_STORAGE_SCHEMA: readonly ExpectedSqliteObject[] = [
5348
{ name: "counter", sql: COUNTER_SQL, tableName: "counter", type: "table" },
@@ -78,63 +73,33 @@ const QUOTA_STORAGE_SCHEMA: readonly ExpectedSqliteObject[] = [
7873
},
7974
];
8075

81-
/**
82-
* Opens quota storage and transactionally upgrades an older supported schema.
83-
*
84-
* Durable Object input gates serialize this synchronous admission path. The
85-
* guarded maintenance route still calls the same reconciler for planned bulk
86-
* verification, but ordinary forward-compatible releases cannot strand a
87-
* dormant user object on its previous schema.
88-
*/
76+
/** Opens quota storage only when it matches the exact current contract. */
8977
export function ensureQuotaTrackerStorage(ctx: DurableObjectState): void {
9078
if (!hasQuotaTrackerStorage(ctx)) {
9179
initializeQuotaTrackerStorage(ctx);
9280
return;
9381
}
94-
reconcileExactSqliteStorage(
95-
"reconcile",
96-
() => assertQuotaTrackerStorage(ctx),
97-
() => reconcileQuotaTrackerStorage(ctx),
98-
);
99-
}
100-
101-
/**
102-
* Force-normalizes quota storage while preserving every current product row.
103-
*
104-
* Quotas removed from the product are intentionally discarded before the
105-
* exact-schema rebuild. Feature values must still be valid text so corrupt
106-
* rows cannot be mistaken for safely retired data.
107-
*/
108-
export function reconcileQuotaTrackerStorage(ctx: DurableObjectState): void {
109-
ctx.storage.transactionSync(() => {
110-
ensureSourceTables(ctx);
111-
const limitColumns = ctx.storage.sql.exec("PRAGMA table_info(limit_override)").toArray();
112-
if (!hasColumn(limitColumns, "feature") || !hasColumn(limitColumns, "limit_val")) {
113-
throw new Error("Unsupported quota limit schema; refusing lossy evolution.");
114-
}
115-
const hasEntitlementVersion = hasColumn(limitColumns, "entitlement_version");
116-
assertQuotaFeatureTypes(ctx);
117-
deleteRetiredQuotaRows(ctx);
118-
assertQuotaSourceRows(ctx, hasEntitlementVersion);
119-
const entitlementVersion = hasEntitlementVersion ? "entitlement_version" : "0";
120-
rebuildQuotaTables(ctx, entitlementVersion);
121-
setCurrentSqliteStorageVersion(ctx);
122-
assertQuotaTrackerStorage(ctx);
123-
});
82+
assertQuotaTrackerStorage(ctx);
12483
}
12584

12685
export function assertQuotaTrackerStorage(ctx: DurableObjectState): void {
12786
assertExactSqliteSchema(ctx, QUOTA_STORAGE_SCHEMA);
12887
}
12988

13089
function initializeQuotaTrackerStorage(ctx: DurableObjectState): void {
131-
ensureSourceTables(ctx);
132-
ctx.storage.sql.exec(USAGE_EVENT_INDEX_SQL.replace("CREATE INDEX", "CREATE INDEX IF NOT EXISTS"));
133-
ctx.storage.sql.exec(
134-
QUOTA_OPERATION_INDEX_SQL.replace("CREATE INDEX", "CREATE INDEX IF NOT EXISTS"),
135-
);
136-
setCurrentSqliteStorageVersion(ctx);
137-
assertQuotaTrackerStorage(ctx);
90+
ctx.storage.transactionSync(() => {
91+
for (const sql of [COUNTER_SQL, LIMIT_OVERRIDE_SQL, USAGE_EVENT_SQL, QUOTA_OPERATION_SQL]) {
92+
ctx.storage.sql.exec(sql.replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS"));
93+
}
94+
ctx.storage.sql.exec(
95+
USAGE_EVENT_INDEX_SQL.replace("CREATE INDEX", "CREATE INDEX IF NOT EXISTS"),
96+
);
97+
ctx.storage.sql.exec(
98+
QUOTA_OPERATION_INDEX_SQL.replace("CREATE INDEX", "CREATE INDEX IF NOT EXISTS"),
99+
);
100+
setCurrentSqliteStorageVersion(ctx);
101+
assertQuotaTrackerStorage(ctx);
102+
});
138103
}
139104

140105
export function hasQuotaTrackerStorage(ctx: DurableObjectState): boolean {
@@ -146,140 +111,3 @@ export function hasQuotaTrackerStorage(ctx: DurableObjectState): boolean {
146111
.toArray().length > 0
147112
);
148113
}
149-
150-
function ensureSourceTables(ctx: DurableObjectState): void {
151-
for (const sql of [COUNTER_SQL, LIMIT_OVERRIDE_SQL, USAGE_EVENT_SQL, QUOTA_OPERATION_SQL]) {
152-
ctx.storage.sql.exec(sql.replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS"));
153-
}
154-
}
155-
156-
function rebuildQuotaTables(ctx: DurableObjectState, entitlementVersion: string): void {
157-
ctx.storage.sql.exec("DROP INDEX IF EXISTS usage_event_feature_time_idx");
158-
ctx.storage.sql.exec("DROP INDEX IF EXISTS quota_operation_feature_time_idx");
159-
for (const table of QUOTA_TABLES) {
160-
ctx.storage.sql.exec(`ALTER TABLE ${table} RENAME TO ${table}_reconcile_source`);
161-
}
162-
for (const sql of [COUNTER_SQL, LIMIT_OVERRIDE_SQL, USAGE_EVENT_SQL, QUOTA_OPERATION_SQL]) {
163-
ctx.storage.sql.exec(sql);
164-
}
165-
copyQuotaRows(ctx, entitlementVersion);
166-
for (const table of QUOTA_TABLES) {
167-
assertSqliteRowCountPreserved(ctx, `${table}_reconcile_source`, table);
168-
ctx.storage.sql.exec(`DROP TABLE ${table}_reconcile_source`);
169-
}
170-
ctx.storage.sql.exec(USAGE_EVENT_INDEX_SQL);
171-
ctx.storage.sql.exec(QUOTA_OPERATION_INDEX_SQL);
172-
}
173-
174-
function copyQuotaRows(ctx: DurableObjectState, entitlementVersion: string): void {
175-
ctx.storage.sql.exec(
176-
`INSERT INTO counter (feature, period_key, used, updated_at)
177-
SELECT feature, period_key, used, updated_at FROM counter_reconcile_source`,
178-
);
179-
ctx.storage.sql.exec(
180-
`INSERT INTO limit_override (feature, limit_val, entitlement_version)
181-
SELECT feature, limit_val, ${entitlementVersion} FROM limit_override_reconcile_source`,
182-
);
183-
ctx.storage.sql.exec(
184-
`INSERT INTO usage_event (id, feature, amount, recorded_at)
185-
SELECT id, feature, amount, recorded_at FROM usage_event_reconcile_source`,
186-
);
187-
ctx.storage.sql.exec(
188-
`INSERT INTO quota_operation
189-
(event_id, operation, feature, period_key, amount, allowed,
190-
limit_val, remaining, used, recorded_at)
191-
SELECT event_id, operation, feature, period_key, amount, allowed,
192-
limit_val, remaining, used, recorded_at
193-
FROM quota_operation_reconcile_source`,
194-
);
195-
}
196-
197-
function assertQuotaFeatureTypes(ctx: DurableObjectState): void {
198-
for (const table of QUOTA_TABLES) {
199-
if (
200-
ctx.storage.sql
201-
.exec(`SELECT 1 FROM ${table} WHERE typeof(feature) <> 'text' LIMIT 1`)
202-
.toArray().length > 0
203-
) {
204-
throw new Error(`Quota ${table} contains a malformed feature; refusing lossy evolution.`);
205-
}
206-
}
207-
}
208-
209-
function deleteRetiredQuotaRows(ctx: DurableObjectState): void {
210-
for (const table of QUOTA_TABLES) {
211-
ctx.storage.sql.exec(
212-
`DELETE FROM ${table} WHERE feature NOT IN (?, ?)`,
213-
...CURRENT_QUOTA_FEATURES,
214-
);
215-
}
216-
}
217-
218-
function assertQuotaSourceRows(ctx: DurableObjectState, hasEntitlementVersion: boolean): void {
219-
assertNoInvalidRows(
220-
ctx,
221-
"counter",
222-
`SELECT 1 FROM counter WHERE
223-
typeof(feature) <> 'text' OR feature NOT IN (?, ?) OR
224-
typeof(period_key) <> 'text' OR length(period_key) <> 7 OR
225-
period_key NOT GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]' OR
226-
typeof(used) NOT IN ('integer', 'real') OR used < 0 OR abs(used) > ${MAX_FINITE_REAL} OR
227-
typeof(updated_at) <> 'integer' OR updated_at < 0 LIMIT 1`,
228-
);
229-
const entitlementPredicate = hasEntitlementVersion
230-
? " OR typeof(entitlement_version) <> 'integer' OR entitlement_version < 0"
231-
: "";
232-
assertNoInvalidRows(
233-
ctx,
234-
"limit_override",
235-
`SELECT 1 FROM limit_override WHERE
236-
typeof(feature) <> 'text' OR feature NOT IN (?, ?) OR
237-
typeof(limit_val) NOT IN ('integer', 'real') OR limit_val < 0 OR
238-
abs(limit_val) > ${MAX_FINITE_REAL}${entitlementPredicate} LIMIT 1`,
239-
);
240-
assertNoInvalidRows(
241-
ctx,
242-
"usage_event",
243-
`SELECT 1 FROM usage_event WHERE
244-
typeof(id) <> 'integer' OR typeof(feature) <> 'text' OR feature NOT IN (?, ?) OR
245-
typeof(amount) NOT IN ('integer', 'real') OR amount <= 0 OR
246-
abs(amount) > ${MAX_FINITE_REAL} OR typeof(recorded_at) <> 'integer' OR
247-
recorded_at < 0 LIMIT 1`,
248-
);
249-
assertNoInvalidRows(
250-
ctx,
251-
"quota_operation",
252-
`SELECT 1 FROM quota_operation WHERE
253-
typeof(event_id) <> 'text' OR length(event_id) NOT BETWEEN 1 AND 200 OR
254-
typeof(operation) <> 'text' OR operation NOT IN ('record', 'try-consume') OR
255-
typeof(feature) <> 'text' OR feature NOT IN (?, ?) OR
256-
typeof(period_key) <> 'text' OR length(period_key) <> 7 OR
257-
period_key NOT GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]' OR
258-
typeof(amount) NOT IN ('integer', 'real') OR amount <= 0 OR
259-
abs(amount) > ${MAX_FINITE_REAL} OR typeof(allowed) <> 'integer' OR
260-
allowed NOT IN (0, 1) OR typeof(limit_val) NOT IN ('integer', 'real') OR
261-
limit_val < 0 OR abs(limit_val) > ${MAX_FINITE_REAL} OR
262-
typeof(remaining) NOT IN ('integer', 'real') OR remaining < 0 OR
263-
abs(remaining) > ${MAX_FINITE_REAL} OR typeof(used) NOT IN ('integer', 'real') OR
264-
used < 0 OR abs(used) > ${MAX_FINITE_REAL} OR
265-
typeof(recorded_at) <> 'integer' OR recorded_at < 0 LIMIT 1`,
266-
);
267-
}
268-
269-
function assertNoInvalidRows(
270-
ctx: DurableObjectState,
271-
table: (typeof QUOTA_TABLES)[number],
272-
sql: string,
273-
): void {
274-
if (ctx.storage.sql.exec(sql, ...CURRENT_QUOTA_FEATURES).toArray().length > 0) {
275-
throw new Error(`Quota ${table} contains invalid current data; refusing lossy evolution.`);
276-
}
277-
}
278-
279-
function hasColumn(rows: unknown[], name: string): boolean {
280-
return rows.some((row) => isRecord(row) && row["name"] === name);
281-
}
282-
283-
function isRecord(value: unknown): value is Record<string, unknown> {
284-
return typeof value === "object" && value !== null;
285-
}

apps/gateway-worker/src/durable-objects/quota-tracker.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { DurableObject } from "cloudflare:workers";
22
import {
33
assertStorageReconciliationRequest,
4-
reconcileExactSqliteStorage,
54
storageSchemaEvidence,
65
} from "@cheatcode/durable-storage";
76
import { readJsonRequest } from "@cheatcode/observability";
@@ -35,7 +34,6 @@ import {
3534
assertQuotaTrackerStorage,
3635
ensureQuotaTrackerStorage,
3736
hasQuotaTrackerStorage,
38-
reconcileQuotaTrackerStorage,
3937
} from "./quota-tracker-storage";
4038
import {
4139
assertGatewayDurableObjectOpen,
@@ -131,11 +129,7 @@ export class QuotaTracker extends DurableObject<QuotaTrackerEnv> {
131129
value: InternalDurableObjectStorageRequest,
132130
): InternalDurableObjectStorageResponse {
133131
const input = assertStorageReconciliationRequest(this.ctx, this.env, value, "QuotaTracker");
134-
reconcileExactSqliteStorage(
135-
input.mode,
136-
() => assertQuotaTrackerStorage(this.ctx),
137-
() => reconcileQuotaTrackerStorage(this.ctx),
138-
);
132+
assertQuotaTrackerStorage(this.ctx);
139133
this.isStorageInitialized = true;
140134
return storageSchemaEvidence(input);
141135
}

0 commit comments

Comments
 (0)