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
284 changes: 57 additions & 227 deletions .specs/kiloclaw-composio.md

Large diffs are not rendered by default.

57 changes: 55 additions & 2 deletions .specs/kiloclaw-datamodel.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ background jobs). All consumers MUST comply with the rules below.

Draft -- created 2026-04-15.
Updated 2026-05-12 -- required KiloClaw price-version lineage invariants.
Updated 2026-05-27 -- required durable fresh-provision admission reservations.
Updated 2026-05-28 -- fraud-enforcement subscription mutation invariants.

## Conventions
Expand Down Expand Up @@ -68,6 +69,11 @@ capitals, as shown here.
Fraud Warning under `.specs/stripe-early-fraud-warnings.md`.
- **Active instance**: An instance record that has not been marked
as destroyed.
- **Provision reservation**: Durable coordination state for one fresh
provisioning attempt in a user context. A reservation assigns the
candidate instance identifier that the attempt MUST use if it succeeds,
but it is not an instance record, does not assert that infrastructure
exists, and does not grant access.
- **Mutation**: Any database write (INSERT or UPDATE) to a
`kiloclaw_subscription` row that changes one or more of its
business-relevant fields (status, plan, billing period, payment
Expand Down Expand Up @@ -187,6 +193,40 @@ KiloClaw billing.
9. When the single-instance limit is relaxed in the future, no
schema migration SHALL be required.

### Fresh Provision Admission

1. Before a fresh personal or organization-context provision invokes an
instance Durable Object or any infra provider operation, the KiloClaw
Worker MUST persist a provision reservation for the requesting user and
context.
2. A provision reservation MUST remain coordination metadata only. It MUST
NOT be stored as an instance record, routed as an active instance, treated
as billing access, or reported as completed onboarding.
3. A provision reservation MUST assign one candidate instance identifier.
An admitted attempt MUST carry that identifier through Durable Object
routing, instance record insertion, subscription bootstrap, and routing
registry publication; runtime MUST NOT silently choose a replacement
identifier during that attempt.
4. While an admitted fresh attempt is in progress or its provider-side
outcome requires reconciliation, another fresh attempt for the same user
and context MUST NOT execute provider creation work. The system MUST fail
closed or report a retryable conflict rather than risk duplicate
infrastructure.
5. Before performing provider creation under an admitted reservation, the
Worker MUST reconcile authoritative active-instance state for the same
user/context. Existing active state MUST prevent another fresh provision
even if a routing index entry is absent or stale.
6. If a provision attempt fails after provider resources may have been
created, its reservation MUST remain blocked or marked for reconciliation
until cleanup or canonical recovery has been confirmed. An expired request
or lease alone MUST NOT authorize another fresh attempt.
7. A completed instance that is intentionally destroyed MAY later be
reprovisioned when no active instance remains in the context, subject to
subscription successor-transfer and entitlement rules.
8. Reservation storage and admission enforcement MUST remain application/
Worker-layer behavior; they MUST NOT introduce a schema-level constraint
that prevents future multi-instance product behavior.

### Operational Instance Markers

Instance records MAY store operational lifecycle markers that do not
Expand Down Expand Up @@ -287,10 +327,12 @@ MUST be enforced only after the existing data model has been brought
into the desired state (rules 1–6 satisfied, early-bird backfill
complete).

19. A Cloudflare Worker Durable Object and a infra provider base resource MUST both exist
19. A Cloudflare Worker Durable Object and an infra provider base resource MUST both exist
before an instance record is created in `kiloclaw_instance`.
Infrastructure MUST be provisioned first; the record is a
reflection of existing infrastructure, not a reservation.
reflection of existing infrastructure, not a reservation. A
provision reservation created under Fresh Provision Admission is
coordination metadata and does not violate this creation order.
20. If either infrastructure component fails to provision, the system
MUST NOT create an instance record. Cleanup of any partially
provisioned infrastructure is the responsibility of the
Expand Down Expand Up @@ -350,6 +392,11 @@ not yet enforced in the current codebase:
across all services that mutate subscription records. Some
subscription-creation paths may already write change-log entries;
complete cross-service coverage remains the intended invariant.
4. Fresh Provision Admission SHOULD be implemented in the Registry-backed
Worker admission flow before the existing web advisory lock is removed.
(Currently, web requests use transitional PostgreSQL advisory-lock
coordination that is being replaced because it is unsafe through
transaction-pooled production connections.)

## Changelog

Expand All @@ -358,6 +405,12 @@ not yet enforced in the current codebase:
- Defined enforced personal Stripe Early Fraud Warnings as exceptional immediate cancellation/suspension mutations that retain instance history, write system-attributed change logs, and preserve the seven-day destruction grace.
- Excluded organization-owned warnings from automatic organization-managed instance or subscription mutation.

### 2026-05-27 -- Required durable fresh-provision admission reservations

- Defined provision reservations as non-routable, non-entitling coordination state.
- Required Worker-side admission before any fresh provider creation and fail-closed handling for concurrent or ambiguous failed attempts.
- Preserved Worker-only instance insertion and infrastructure-before-row ordering.

### 2026-05-12 -- Required KiloClaw price-version lineage invariants

- Added required `kiloclaw_price_version` row semantics.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { ProvisioningStepView } from './ProvisioningStep';

const FAKE_STEP_LABELS: Record<ClawOnboardingRenderStep, string> = {
identity: 'Identity',
tools: 'Tools',
calendar: 'Calendar',
email: 'Inbound Email',
interests: 'Interests',
Expand Down Expand Up @@ -172,13 +171,12 @@ type RenderFakeStepInput = {
};

function getFakeStepProgress(step: ClawOnboardingRenderStep): StepProgress {
return getClawOnboardingStepProgress(getFakeOnboardingStep(step), true, true, false);
return getClawOnboardingStepProgress(getFakeOnboardingStep(step), true, true);
}

function getFakeOnboardingStep(step: ClawOnboardingRenderStep): OnboardingStep {
switch (step) {
case 'identity':
case 'tools':
case 'calendar':
case 'email':
case 'interests':
Expand All @@ -195,7 +193,6 @@ function renderFakeStep({ step, setStep, stepProgress, basePath }: RenderFakeSte
case 'identity': {
return <BotIdentityStep {...stepProgress} onContinue={() => setStep('calendar')} />;
}
case 'tools':
case 'calendar': {
return (
<CalendarConnectStepView
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,6 @@ describe('ClawOnboardingFlow state machine', () => {
expect(state.instanceStatus).toBeNull();
});

test('allows managed tools before initial provisioning starts', () => {
const state = getClawOnboardingFlowState(
createInput({
onboardingStep: 'tools',
hasBotIdentity: true,
hasToolsStep: true,
})
);

expect(state.renderStep).toBe('tools');
expect(state.createSetupActive).toBe(false);
expect(state.instanceStatus).toBeNull();
});

test('keeps create setup active once an instance status exists', () => {
const state = getClawOnboardingFlowState(
createInput({
Expand All @@ -130,16 +116,6 @@ describe('ClawOnboardingFlow state machine', () => {
expect(getClawOnboardingFlowState(createInput({ createSetupStarted: true })).renderStep).toBe(
'identity'
);
expect(
getClawOnboardingFlowState(
createInput({
createSetupStarted: true,
onboardingStep: 'tools',
hasBotIdentity: true,
hasToolsStep: true,
})
).renderStep
).toBe('tools');
expect(
getClawOnboardingFlowState(
createInput({
Expand Down Expand Up @@ -210,28 +186,6 @@ describe('ClawOnboardingFlow state machine', () => {
expect(getClawOnboardingStepProgress('done')).toEqual({ currentStep: 5, totalSteps: 5 });
});

test('managed tools step replaces calendar in the active wizard', () => {
const state = getClawOnboardingFlowState(
createInput({
createSetupStarted: true,
onboardingStep: 'calendar',
hasBotIdentity: true,
hasToolsStep: true,
})
);

expect(state.renderStep).toBe('tools');
expect(state.totalSteps).toBe(5);
expect(getClawOnboardingStepProgress('tools', true, true, true)).toEqual({
currentStep: 2,
totalSteps: 5,
});
expect(getClawOnboardingStepProgress('calendar', true, true, true)).toEqual({
currentStep: 2,
totalSteps: 5,
});
});

test.each(CLAW_ONBOARDING_PROVISIONING_STATUSES)(
'renders the post-provisioning spinner while machine status is %s',
status => {
Expand Down Expand Up @@ -443,21 +397,6 @@ describe('ClawOnboardingFlow state machine', () => {
expect(state.renderStep).toBe('calendar');
});

test('renders tools in post-provisioning mode when explicit Composio resume is requested', () => {
const state = getClawOnboardingFlowState(
createInput({
mode: 'post-provisioning',
status: createStatus('running'),
onboardingStep: 'tools',
hasBotIdentity: true,
hasToolsStep: true,
gatewayState: 'running',
})
);

expect(state.renderStep).toBe('tools');
});

test('renders calendar in post-provisioning mode even before the gateway is ready', () => {
// The OAuth round-trip can complete before the gateway boots; respect
// the calendar resume regardless of postProvisioningReady.
Expand Down
Loading
Loading