Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 0 additions & 5 deletions .specs/kiloclaw-datamodel.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,11 +392,6 @@ 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 Down
15 changes: 15 additions & 0 deletions apps/web/src/lib/kiloclaw/kiloclaw-internal-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,21 @@ export class KiloClawInternalClient {
);
}

async repairProvisionReservation(
userId: string,
instanceId: string,
orgId?: string
): Promise<{ ok: true }> {
return this.request(
'/api/platform/provision/repair-reservation',
{
method: 'POST',
body: JSON.stringify({ userId, instanceId, orgId }),
},
{ userId }
);
}

async start(
userId: string,
instanceId?: string,
Expand Down
200 changes: 0 additions & 200 deletions apps/web/src/lib/kiloclaw/provision-lock.test.ts

This file was deleted.

90 changes: 0 additions & 90 deletions apps/web/src/lib/kiloclaw/provision-lock.ts

This file was deleted.

10 changes: 10 additions & 0 deletions apps/web/src/lib/kiloclaw/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,16 @@ export type RegistryResult = {
createdAt: string;
destroyedAt: string | null;
}>;
reservations: Array<{
instanceId: string;
doKey: string;
assignedUserId: string;
status: 'in_progress' | 'completed' | 'failed_requires_reconciliation' | 'released';
startedAt: string;
updatedAt: string;
completedAt: string | null;
failureCode: string | null;
}>;
migrated: boolean;
};

Expand Down
25 changes: 25 additions & 0 deletions apps/web/src/routers/kiloclaw-billing-router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ const stripePriceIdsMock = jest.requireMock<{
const kiloclawInternalClientMock = jest.requireMock<KiloclawInternalClientMockShape>(
'@/lib/kiloclaw/kiloclaw-internal-client'
);
const { KiloClawApiError: MockKiloClawApiError } = jest.requireMock<{
KiloClawApiError: new (statusCode: number, responseBody: string) => Error;
}>('@/lib/kiloclaw/kiloclaw-internal-client');

beforeAll(async () => {
const mod = await import('@/routers/test-utils');
Expand Down Expand Up @@ -816,6 +819,28 @@ describe('provision detached personal billing recovery', () => {

expect(kiloclawInternalClientMock.__provisionMock).not.toHaveBeenCalled();
});

it.each(['provision', 'updateConfig'] as const)(
'maps Worker admission conflicts from kiloclaw.%s',
async procedure => {
kiloclawInternalClientMock.__provisionMock.mockRejectedValueOnce(
new MockKiloClawApiError(
409,
JSON.stringify({
error:
'An instance is already being created. Wait for setup to finish, then try again.',
code: 'provision_in_progress',
})
)
);

const caller = await createCallerForUser(user.id);
await expect(caller.kiloclaw[procedure]({})).rejects.toMatchObject({
code: 'CONFLICT',
message: 'An instance is already being created. Wait for setup to finish, then try again.',
});
}
);
});

describe('requireKiloClawAccess', () => {
Expand Down
Loading
Loading