Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, it } from "vitest";

import { __AdmissionLockKey } from "../admission-lock-key";

/** The byte PostgreSQL text cannot hold, written this way so no source file carries a literal NUL. */
const _NUL = String.fromCharCode(0);

describe("__AdmissionLockKey", function _AdmissionLockKeySuite()
{
// PostgreSQL rejects a NUL byte in text with SQLSTATE 22021, which fails the whole statement.
// Separating the two parts with NUL is what made every message submit return 503.
it("introduces no byte PostgreSQL text cannot hold", function _AddsNoControlByte()
{
expect(__AdmissionLockKey("testv4", "idempotency-key")).not.toContain(_NUL);
expect(__AdmissionLockKey("silo-1", "conversation:7753e9bb/message")).not.toContain(_NUL);
});

it("keeps distinct pairs distinct where a plain join would collide", function _StaysInjective()
{
// A separator either part may itself contain cannot separate them: "a:b" + "c" and "a" + "b:c"
// both read as "a:b:c". The length prefix removes the ambiguity without reserving a character.
expect(__AdmissionLockKey("a:b", "c")).not.toEqual(__AdmissionLockKey("a", "b:c"));
expect(__AdmissionLockKey("ab", "c")).not.toEqual(__AdmissionLockKey("a", "bc"));
});

it("gives one silo and key the same lock on every attempt", function _StaysStable()
{
expect(__AdmissionLockKey("testv4", "key-1")).toEqual(__AdmissionLockKey("testv4", "key-1"));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ describe("PrismaChildRunReservationRepository", function _describeReservationRep

expect(result).toEqual({ outcome: "idempotent", snapshot: childSnapshot });
expect(transaction.$queryRaw).toHaveBeenCalledTimes(1);
// The advisory-lock key is a text parameter, and PostgreSQL rejects a NUL byte in text with
// SQLSTATE 22021, failing the whole reservation.
for (const [statement] of transaction.$queryRaw.mock.calls)
{
expect((statement as { values: unknown[] }).values.filter(function _IsText(value): value is string { return typeof value === "string"; }).join("")).not.toContain(String.fromCharCode(0));
}
expect(transaction.agentRun.create).not.toHaveBeenCalled();
expect(transaction.runInputSnapshot.create).not.toHaveBeenCalled();
expect(transaction.childRunReservation.create).not.toHaveBeenCalled();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
const prisma = { $transaction: vi.fn(async function _transaction(callback: (client: typeof transaction) => Promise<unknown>) { return callback(transaction); }) } as unknown as PrismaClient;
const repository = new PrismaRunAdmissionRepository(prisma, { now: function _now() { return new Date("2026-07-20T00:00:00.000Z"); } });

await expect(repository.admit(command, async function _build() { return { outcome: "ready", value: { authority, snapshot } } as const; })).resolves.toMatchObject({ outcome: "accepted" });

Check failure on line 48 in libs/backend/agents/execution/runs/main/src/__tests__/prisma-run-admission-repository.test.ts

View workflow job for this annotation

GitHub Actions / Build, test, and lint affected projects

src/__tests__/prisma-run-admission-repository.test.ts > PrismaRunAdmissionRepository > admits a scheduled managed run without accepting a user-shaped execution subject

AssertionError: promise rejected "TypeError: Right-hand side of 'instanceof…" instead of resolving ❯ _admitsManagedService src/__tests__/prisma-run-admission-repository.test.ts:48:140 Caused by: Caused by: TypeError: Right-hand side of 'instanceof' is not an object ❯ PrismaRunAdmissionRepository.admit src/prisma-run-admission-repository.ts:167:8 ❯ _admitsManagedService src/__tests__/prisma-run-admission-repository.test.ts:48:27
expect(transaction.agentRun.create).toHaveBeenCalledWith({ data: expect.objectContaining({ trigger: "Schedule", delegatedUserId: null }) });
});

Expand All @@ -55,8 +55,14 @@
const prisma = { $transaction: vi.fn(async function _transaction(callback: (client: typeof transaction) => Promise<unknown>) { return callback(transaction); }) } as unknown as PrismaClient;
const repository = new PrismaRunAdmissionRepository(prisma, { now: function _now() { return new Date("2026-07-20T00:00:00.000Z"); } });

await expect(repository.admit(_command(), async function _build() { return { outcome: "ready", value: { authority: _authority(), snapshot: _snapshot() } } as const; })).resolves.toEqual({ outcome: "accepted", snapshot: _snapshot() });

Check failure on line 58 in libs/backend/agents/execution/runs/main/src/__tests__/prisma-run-admission-repository.test.ts

View workflow job for this annotation

GitHub Actions / Build, test, and lint affected projects

src/__tests__/prisma-run-admission-repository.test.ts > PrismaRunAdmissionRepository > creates the logical run, immutable snapshot, and ordered acceptance/dispatch events in one transaction

AssertionError: promise rejected "TypeError: Right-hand side of 'instanceof…" instead of resolving ❯ _persistsAdmission src/__tests__/prisma-run-admission-repository.test.ts:58:170 Caused by: Caused by: TypeError: Right-hand side of 'instanceof' is not an object ❯ PrismaRunAdmissionRepository.admit src/prisma-run-admission-repository.ts:167:8 ❯ _persistsAdmission src/__tests__/prisma-run-admission-repository.test.ts:58:27
expect(transaction.$queryRaw).toHaveBeenCalledTimes(2);
// PostgreSQL rejects a NUL byte in text with SQLSTATE 22021 and fails the whole statement, so
// an advisory-lock key carrying one made every admission return 503 against a real database.
for (const [statement] of transaction.$queryRaw.mock.calls)
{
expect((statement as { values: unknown[] }).values.filter(function _IsText(value): value is string { return typeof value === "string"; }).join("")).not.toContain(String.fromCharCode(0));
}
expect(transaction.agentRun.create).toHaveBeenCalledWith({ data: expect.objectContaining({ inputSnapshotDigest: `sha256:${"c".repeat(64)}`, acceptedAt: new Date("2026-07-20T00:00:00.000Z") }) });
expect(transaction.runInputSnapshot.create).toHaveBeenCalledWith({ data: expect.objectContaining({ runId: "run-1", digest: `sha256:${"c".repeat(64)}`, messageIds: ["message-1"] }) });
expect(transaction.outboxEvent.createMany).toHaveBeenCalledWith({ data: [expect.objectContaining({ sequence: 1, kind: "RunAccepted", idempotencyKey: "run-1:accepted" }), expect.objectContaining({ sequence: 2, kind: "RunAttemptRequested", idempotencyKey: "run-1:attempt:1" })] });
Expand All @@ -79,7 +85,7 @@
expect(transactionContext.prisma).toBe(transaction);
expect(value.snapshot.runId).toBe("run-1");
order.push("message");
})).resolves.toMatchObject({ outcome: "accepted" });

Check failure on line 88 in libs/backend/agents/execution/runs/main/src/__tests__/prisma-run-admission-repository.test.ts

View workflow job for this annotation

GitHub Actions / Build, test, and lint affected projects

src/__tests__/prisma-run-admission-repository.test.ts > PrismaRunAdmissionRepository > runs caller-owned persistence after run rows inside the same serializable transaction

AssertionError: promise rejected "TypeError: Right-hand side of 'instanceof…" instead of resolving ❯ _RunsCommitParticipant src/__tests__/prisma-run-admission-repository.test.ts:88:5 Caused by: Caused by: TypeError: Right-hand side of 'instanceof' is not an object ❯ PrismaRunAdmissionRepository.admit src/prisma-run-admission-repository.ts:167:8 ❯ _RunsCommitParticipant src/__tests__/prisma-run-admission-repository.test.ts:83:27
expect(order).toEqual(["run", "snapshot", "outbox", "message"]);
});

Expand All @@ -94,7 +100,7 @@
{
order.push("build");
return { outcome: "ready", value: { authority: _authority(), snapshot: _snapshot() } } as const;
}, async function _Commit() { order.push("commit"); }, async function _Prepare() { order.push("prepare"); })).resolves.toMatchObject({ outcome: "accepted" });

Check failure on line 103 in libs/backend/agents/execution/runs/main/src/__tests__/prisma-run-admission-repository.test.ts

View workflow job for this annotation

GitHub Actions / Build, test, and lint affected projects

src/__tests__/prisma-run-admission-repository.test.ts > PrismaRunAdmissionRepository > prepares child authority before compilation and commits remaining writes last

AssertionError: promise rejected "TypeError: Right-hand side of 'instanceof…" instead of resolving ❯ _PreparesChildAuthority src/__tests__/prisma-run-admission-repository.test.ts:103:111 Caused by: Caused by: TypeError: Right-hand side of 'instanceof' is not an object ❯ PrismaRunAdmissionRepository.admit src/prisma-run-admission-repository.ts:167:8 ❯ _PreparesChildAuthority src/__tests__/prisma-run-admission-repository.test.ts:99:27
expect(order).toEqual(["prepare", "build", "run", "snapshot", "outbox", "commit"]);
});

Expand All @@ -118,7 +124,7 @@
await expect(repository.admit(_command(), async function _Deny() { return { outcome: "denied", reason: "persona_unavailable" } as const; }, undefined, async function _Prepare(context)
{
(context.prisma as unknown as { pendingChildren: string[] }).pendingChildren.push("child-1");
})).resolves.toEqual({ outcome: "denied", reason: "persona_unavailable" });

Check failure on line 127 in libs/backend/agents/execution/runs/main/src/__tests__/prisma-run-admission-repository.test.ts

View workflow job for this annotation

GitHub Actions / Build, test, and lint affected projects

src/__tests__/prisma-run-admission-repository.test.ts > PrismaRunAdmissionRepository > rolls back prepared child authority when snapshot compilation denies

AssertionError: promise rejected "TypeError: Right-hand side of 'instanceof…" instead of resolving ❯ _RollsBackPreparedAuthority src/__tests__/prisma-run-admission-repository.test.ts:127:5 Caused by: Caused by: TypeError: Right-hand side of 'instanceof' is not an object ❯ PrismaRunAdmissionRepository.admit src/prisma-run-admission-repository.ts:167:8 ❯ _RollsBackPreparedAuthority src/__tests__/prisma-run-admission-repository.test.ts:124:27
expect(committedChildren).toEqual([]);
});

Expand All @@ -138,7 +144,7 @@
await expect(repository.admit(_command(), async function _BuildConflict() { return { outcome: "ready", value: { authority: { ..._authority(), delegatedUserId: "user-2" }, snapshot: _snapshot() } } as const; }, undefined, async function _Prepare(context)
{
(context.prisma as unknown as { pendingChildren: string[] }).pendingChildren.push("child-1");
})).resolves.toEqual({ outcome: "denied", reason: RunAdmissionDenialReasons.AuthorityConflict });

Check failure on line 147 in libs/backend/agents/execution/runs/main/src/__tests__/prisma-run-admission-repository.test.ts

View workflow job for this annotation

GitHub Actions / Build, test, and lint affected projects

src/__tests__/prisma-run-admission-repository.test.ts > PrismaRunAdmissionRepository > rolls back prepared child authority when compiled coordinates conflict

AssertionError: promise rejected "TypeError: Right-hand side of 'instanceof…" instead of resolving ❯ _RollsBackPreparedAuthorityConflict src/__tests__/prisma-run-admission-repository.test.ts:147:5 Caused by: Caused by: TypeError: Right-hand side of 'instanceof' is not an object ❯ PrismaRunAdmissionRepository.admit src/prisma-run-admission-repository.ts:167:8 ❯ _RollsBackPreparedAuthorityConflict src/__tests__/prisma-run-admission-repository.test.ts:144:27
expect(committedChildren).toEqual([]);
});

Expand All @@ -150,7 +156,7 @@
const repository = new PrismaRunAdmissionRepository(prisma);
const prepare = vi.fn();

await expect(repository.admit(_command(), async function _UnexpectedBuild() { throw new Error("unexpected build"); }, undefined, prepare)).resolves.toEqual({ outcome: "idempotent", snapshot });

Check failure on line 159 in libs/backend/agents/execution/runs/main/src/__tests__/prisma-run-admission-repository.test.ts

View workflow job for this annotation

GitHub Actions / Build, test, and lint affected projects

src/__tests__/prisma-run-admission-repository.test.ts > PrismaRunAdmissionRepository > does not replay preparation for an existing exact run

AssertionError: promise rejected "TypeError: Right-hand side of 'instanceof…" instead of resolving ❯ _SkipsPrepareForIdempotentRun src/__tests__/prisma-run-admission-repository.test.ts:159:140 Caused by: Caused by: TypeError: Right-hand side of 'instanceof' is not an object ❯ PrismaRunAdmissionRepository.admit src/prisma-run-admission-repository.ts:167:8 ❯ _SkipsPrepareForIdempotentRun src/__tests__/prisma-run-admission-repository.test.ts:159:27
expect(prepare).not.toHaveBeenCalled();
});

Expand All @@ -162,7 +168,7 @@
const repository = new PrismaRunAdmissionRepository(prisma, { now: function _now() { return new Date("2026-07-20T00:05:00.000Z"); } });
let compiled = false;

await expect(repository.admit({ ..._command(), conversationId: null }, async function _build() { compiled = true; return { outcome: "ready", value: { authority: _authority(), snapshot } } as const; })).resolves.toEqual({ outcome: "idempotent", snapshot });

Check failure on line 171 in libs/backend/agents/execution/runs/main/src/__tests__/prisma-run-admission-repository.test.ts

View workflow job for this annotation

GitHub Actions / Build, test, and lint affected projects

src/__tests__/prisma-run-admission-repository.test.ts > PrismaRunAdmissionRepository > returns a null-conversation snapshot before a later retry can load or compile a new request instant

AssertionError: promise rejected "TypeError: Right-hand side of 'instanceof…" instead of resolving ❯ _returnsIdempotent src/__tests__/prisma-run-admission-repository.test.ts:171:203 Caused by: Caused by: TypeError: Right-hand side of 'instanceof' is not an object ❯ PrismaRunAdmissionRepository.admit src/prisma-run-admission-repository.ts:167:8 ❯ _returnsIdempotent src/__tests__/prisma-run-admission-repository.test.ts:171:27
expect(compiled).toBe(false);
expect(transaction.agentRun.create).not.toHaveBeenCalled();
expect(transaction.runInputSnapshot.create).not.toHaveBeenCalled();
Expand All @@ -180,7 +186,7 @@
const repository = new PrismaRunAdmissionRepository(prisma);
const build = vi.fn();

await expect(repository.admit(_command(), build)).resolves.toEqual({ outcome: "denied", reason: "authority_conflict" });

Check failure on line 189 in libs/backend/agents/execution/runs/main/src/__tests__/prisma-run-admission-repository.test.ts

View workflow job for this annotation

GitHub Actions / Build, test, and lint affected projects

src/__tests__/prisma-run-admission-repository.test.ts > PrismaRunAdmissionRepository > denies a same-silo delivery key already used by another agent service

AssertionError: promise rejected "TypeError: Right-hand side of 'instanceof…" instead of resolving ❯ _deniesCrossScopeDuplicate src/__tests__/prisma-run-admission-repository.test.ts:189:51 Caused by: Caused by: TypeError: Right-hand side of 'instanceof' is not an object ❯ PrismaRunAdmissionRepository.admit src/prisma-run-admission-repository.ts:167:8 ❯ _deniesCrossScopeDuplicate src/__tests__/prisma-run-admission-repository.test.ts:189:27
expect(build).not.toHaveBeenCalled();
expect(transaction.runInputSnapshot.findUnique).not.toHaveBeenCalled();
expect(transaction.agentRun.create).not.toHaveBeenCalled();
Expand Down
14 changes: 14 additions & 0 deletions libs/backend/agents/execution/runs/main/src/admission-lock-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Builds the advisory-lock key that serialises one silo's admission of one idempotency key.
*
* The key is a text parameter to `hashtextextended`, and PostgreSQL text cannot hold a NUL byte —
* a value carrying one fails the whole statement with SQLSTATE 22021, so joining the two parts with
* NUL made every admission fail. The silo's length prefixes the pair instead: distinct pairs stay
* distinct keys without reserving a character either part might legitimately contain.
*
* Called by: PrismaRunAdmissionRepository.admit, PrismaChildRunReservationRepository (reservation).
*/
export function __AdmissionLockKey(siloId: string, requestIdempotencyKey: string): string
{
return `${siloId.length}:${siloId}${requestIdempotencyKey}`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AgentRunState, AgentRunTrigger, Prisma, type AgentRun as PrismaAgentRun
import type { RunInputSnapshot } from "@opencrane/contracts";
import { ___CreateLogger, type Logger } from "@opencrane/backend/observability";

import { __AdmissionLockKey } from "./admission-lock-key";
import { __PrepareChildRunAdmission } from "./child-run-admission";
import type { ChildRunParentAuthority, PrepareChildRunAdmissionCommand, PreparedChildRunAdmission } from "./child-run-admission.types";
import { _InitialRunOutboxData, _RunInputSnapshot, _RunInputSnapshotData } from "./prisma-run-admission-repository";
Expand Down Expand Up @@ -33,7 +34,7 @@ export class PrismaChildRunReservationRepository implements ChildRunReservationR
return await this.prisma.$transaction(async function _reserve(transaction): Promise<ChildRunReservationResult>
{
// 1. Serialize one inherited-silo key before observing or creating a child.
await transaction.$queryRaw(Prisma.sql`SELECT pg_advisory_xact_lock(hashtextextended(${`${command.prepared.siloId}\u0000${command.requestIdempotencyKey}`}, 0))`);
await transaction.$queryRaw(Prisma.sql`SELECT pg_advisory_xact_lock(hashtextextended(${__AdmissionLockKey(command.prepared.siloId, command.requestIdempotencyKey)}, 0))`);
const existing = await transaction.agentRun.findUnique({ where: { siloId_requestIdempotencyKey: { siloId: command.prepared.siloId, requestIdempotencyKey: command.requestIdempotencyKey } } });
if (existing !== null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ___CreateLogger, type Logger } from "@opencrane/backend/observability";
import { ___CloneCanonicalJson } from "@opencrane/util";
import type { JsonValue } from "@opencrane/util";

import { __AdmissionLockKey } from "./admission-lock-key";
import { RunAdmissionDenialReasons } from "./run-admission.types";
import type { InitialRunAuthority, RunAdmissionBuild, RunAdmissionBuildResult, RunAdmissionClock, RunAdmissionCommand, RunAdmissionCommit, RunAdmissionPrepare, RunAdmissionRepository, RunAdmissionResult, RunAdmissionTransaction } from "./run-admission.types";

Expand Down Expand Up @@ -116,7 +117,7 @@ export class PrismaRunAdmissionRepository implements RunAdmissionRepository
return await this.prisma.$transaction(async function _admit(transaction: Prisma.TransactionClient): Promise<RunAdmissionResult<TDenial>>
{
// 1. Serialize the user-visible key before loading inputs so a duplicate never recompiles at a later instant.
await transaction.$queryRaw(Prisma.sql`SELECT pg_advisory_xact_lock(hashtextextended(${`${command.siloId}\u0000${command.requestIdempotencyKey}`}, 0))`);
await transaction.$queryRaw(Prisma.sql`SELECT pg_advisory_xact_lock(hashtextextended(${__AdmissionLockKey(command.siloId, command.requestIdempotencyKey)}, 0))`);
const existing = await transaction.agentRun.findUnique({ where: { siloId_requestIdempotencyKey: { siloId: command.siloId, requestIdempotencyKey: command.requestIdempotencyKey } } });
if (existing !== null)
{
Expand Down
Loading