Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 36 additions & 15 deletions cloudflare-gastown/src/dos/Town.do.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3691,28 +3691,49 @@ export class TownDO extends DurableObject<Env> {
}

// DEBUG: dry-run the reconciler against current state, returning actions
// it would emit without applying them. Side-effect-free — reconcile()
// only reads SQLite state; applyAction() is never called.
// it would emit without applying them. Drains pending events first (same
// as the real alarm loop) inside a savepoint that is rolled back, so the
// endpoint remains fully side-effect-free.
async debugDryRun(): Promise<{
actions: Action[];
metrics: Pick<
reconciler.ReconcilerMetrics,
'actionsEmitted' | 'actionsByType' | 'pendingEventCount'
'actionsEmitted' | 'actionsByType' | 'pendingEventCount' | 'eventsDrained'
>;
}> {
const actions = reconciler.reconcile(this.sql);
const actionsByType: Record<string, number> = {};
for (const a of actions) {
actionsByType[a.type] = (actionsByType[a.type] ?? 0) + 1;
// Use a savepoint so we can drain events (which mutates state)
// then roll back without permanent side effects
this.sql.exec('SAVEPOINT debug_dry_run');
try {
// Phase 0: Drain and apply pending events (same as real alarm loop)
const pending = events.drainEvents(this.sql);
for (const event of pending) {
reconciler.applyEvent(this.sql, event);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Dry run no longer matches the real alarm loop when an event is bad

debugDryRun() now applies each drained event without the per-event try/catch that the real alarm loop uses in cloudflare-gastown/src/dos/Town.do.ts:2938. If one pending event throws here, the whole endpoint returns an error and you never see the reconcile actions for the remaining queue, even though the actual alarm tick would log the failure, skip that event, and continue. That makes this preview unreliable հենց when the queue contains the malformed event you're trying to inspect.

events.markProcessed(this.sql, event.event_id);
}

// Phase 1: Reconcile against now-current state
const actions = reconciler.reconcile(this.sql);
const pendingEventCount = events.pendingEventCount(this.sql);
const actionsByType: Record<string, number> = {};
for (const a of actions) {
actionsByType[a.type] = (actionsByType[a.type] ?? 0) + 1;
}

return {
actions,
metrics: {
actionsEmitted: actions.length,
actionsByType,
pendingEventCount,
eventsDrained: pending.length,
},
};
} finally {
// Roll back all state mutations — this is a dry run
this.sql.exec('ROLLBACK TO SAVEPOINT debug_dry_run');
this.sql.exec('RELEASE SAVEPOINT debug_dry_run');
}
return {
actions,
metrics: {
actionsEmitted: actions.length,
actionsByType,
pendingEventCount: events.pendingEventCount(this.sql),
},
};
}

// DEBUG: concise non-terminal bead summary — remove after debugging
Expand Down
5 changes: 4 additions & 1 deletion src/app/(app)/claw/components/CreateInstanceCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { useEffect, useMemo, useRef, useState } from 'react';
import { usePostHog } from 'posthog-js/react';
import { useFeatureFlagVariantKey, usePostHog } from 'posthog-js/react';
import { useQuery } from '@tanstack/react-query';
import { toast } from 'sonner';
import type { useKiloClawMutations } from '@/hooks/useKiloClaw';
Expand All @@ -25,6 +25,9 @@ export function CreateInstanceCard({
mutations: ClawMutations;
onProvisionStart?: () => void;
}) {
// Evaluate the landing-page experiment flag so PostHog attaches
// $feature/button-vs-card to events fired in this component.
useFeatureFlagVariantKey('button-vs-card');
const posthog = usePostHog();
const trpc = useTRPC();
const { data: billingStatus } = useQuery(trpc.kiloclaw.getBillingStatus.queryOptions());
Expand Down
Loading