refactor: remove read-time repair from canonical hydrate paths
This commit is contained in:
174
src/db.test.ts
174
src/db.test.ts
@@ -1311,6 +1311,57 @@ describe('paired task state', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fails fast when a paired task row loses canonical agent metadata after init', () => {
|
||||||
|
const tempDir = fs.mkdtempSync('/tmp/ejclaw-paired-task-strict-read-');
|
||||||
|
const dbPath = path.join(tempDir, 'messages.db');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileDb = new Database(dbPath);
|
||||||
|
initializeDatabaseSchema(fileDb);
|
||||||
|
fileDb.close();
|
||||||
|
|
||||||
|
_initTestDatabaseFromFile(dbPath);
|
||||||
|
createPairedTask({
|
||||||
|
id: 'paired-task-strict-read',
|
||||||
|
chat_jid: 'dc:paired-task-strict-read',
|
||||||
|
group_folder: 'paired-task-strict-read',
|
||||||
|
owner_service_id: CODEX_MAIN_SERVICE_ID,
|
||||||
|
reviewer_service_id: CLAUDE_SERVICE_ID,
|
||||||
|
owner_agent_type: 'codex',
|
||||||
|
reviewer_agent_type: 'claude-code',
|
||||||
|
arbiter_agent_type: null,
|
||||||
|
title: 'strict read task',
|
||||||
|
source_ref: null,
|
||||||
|
plan_notes: null,
|
||||||
|
review_requested_at: null,
|
||||||
|
round_trip_count: 0,
|
||||||
|
status: 'active',
|
||||||
|
arbiter_verdict: null,
|
||||||
|
arbiter_requested_at: null,
|
||||||
|
completion_reason: null,
|
||||||
|
created_at: '2026-04-10T00:00:00.000Z',
|
||||||
|
updated_at: '2026-04-10T00:00:00.000Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
const rawDb = new Database(dbPath);
|
||||||
|
rawDb
|
||||||
|
.prepare(
|
||||||
|
`UPDATE paired_tasks
|
||||||
|
SET reviewer_agent_type = NULL
|
||||||
|
WHERE id = ?`,
|
||||||
|
)
|
||||||
|
.run('paired-task-strict-read');
|
||||||
|
rawDb.close();
|
||||||
|
|
||||||
|
expect(() => getPairedTaskById('paired-task-strict-read')).toThrow(
|
||||||
|
/cannot read reviewer_agent_type from stored row metadata/,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
_initTestDatabase();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('preserves stored reviewer service ids during init even when reviewer agent metadata exists', () => {
|
it('preserves stored reviewer service ids during init even when reviewer agent metadata exists', () => {
|
||||||
const tempDir = fs.mkdtempSync(
|
const tempDir = fs.mkdtempSync(
|
||||||
'/tmp/ejclaw-channel-owner-stored-reviewer-',
|
'/tmp/ejclaw-channel-owner-stored-reviewer-',
|
||||||
@@ -1371,6 +1422,44 @@ describe('paired task state', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fails fast when a channel owner lease row loses canonical reviewer metadata after init', () => {
|
||||||
|
const tempDir = fs.mkdtempSync('/tmp/ejclaw-channel-owner-strict-read-');
|
||||||
|
const dbPath = path.join(tempDir, 'messages.db');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileDb = new Database(dbPath);
|
||||||
|
initializeDatabaseSchema(fileDb);
|
||||||
|
fileDb.close();
|
||||||
|
|
||||||
|
_initTestDatabaseFromFile(dbPath);
|
||||||
|
setChannelOwnerLease({
|
||||||
|
chat_jid: 'dc:channel-owner-strict-read',
|
||||||
|
owner_service_id: CODEX_MAIN_SERVICE_ID,
|
||||||
|
reviewer_service_id: CLAUDE_SERVICE_ID,
|
||||||
|
owner_agent_type: 'codex',
|
||||||
|
reviewer_agent_type: 'claude-code',
|
||||||
|
reason: 'strict read setup',
|
||||||
|
});
|
||||||
|
|
||||||
|
const rawDb = new Database(dbPath);
|
||||||
|
rawDb
|
||||||
|
.prepare(
|
||||||
|
`UPDATE channel_owner
|
||||||
|
SET reviewer_agent_type = NULL
|
||||||
|
WHERE chat_jid = ?`,
|
||||||
|
)
|
||||||
|
.run('dc:channel-owner-strict-read');
|
||||||
|
rawDb.close();
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
getChannelOwnerLease('dc:channel-owner-strict-read'),
|
||||||
|
).toThrow(/cannot read reviewer_agent_type from stored row metadata/);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
_initTestDatabase();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('fails init when stored paired task metadata conflicts with stored service ids even if room settings differ', () => {
|
it('fails init when stored paired task metadata conflicts with stored service ids even if room settings differ', () => {
|
||||||
const tempDir = fs.mkdtempSync('/tmp/ejclaw-paired-task-ssot-');
|
const tempDir = fs.mkdtempSync('/tmp/ejclaw-paired-task-ssot-');
|
||||||
const dbPath = path.join(tempDir, 'messages.db');
|
const dbPath = path.join(tempDir, 'messages.db');
|
||||||
@@ -3381,6 +3470,48 @@ describe('service handoff completion', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fails fast when a service handoff row loses canonical target metadata after init', () => {
|
||||||
|
const tempDir = fs.mkdtempSync('/tmp/ejclaw-handoff-strict-read-');
|
||||||
|
const dbPath = path.join(tempDir, 'messages.db');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileDb = new Database(dbPath);
|
||||||
|
initializeDatabaseSchema(fileDb);
|
||||||
|
fileDb.close();
|
||||||
|
|
||||||
|
_initTestDatabaseFromFile(dbPath);
|
||||||
|
const handoff = createServiceHandoff({
|
||||||
|
chat_jid: 'dc:handoff-strict-read',
|
||||||
|
group_folder: 'handoff-strict-read',
|
||||||
|
source_service_id: CLAUDE_SERVICE_ID,
|
||||||
|
target_service_id: CODEX_REVIEW_SERVICE_ID,
|
||||||
|
source_role: 'owner',
|
||||||
|
target_role: 'reviewer',
|
||||||
|
source_agent_type: 'claude-code',
|
||||||
|
target_agent_type: 'codex',
|
||||||
|
prompt: 'strict read handoff',
|
||||||
|
intended_role: 'reviewer',
|
||||||
|
});
|
||||||
|
|
||||||
|
const rawDb = new Database(dbPath);
|
||||||
|
rawDb
|
||||||
|
.prepare(
|
||||||
|
`UPDATE service_handoffs
|
||||||
|
SET target_agent_type = ''
|
||||||
|
WHERE id = ?`,
|
||||||
|
)
|
||||||
|
.run(handoff.id);
|
||||||
|
rawDb.close();
|
||||||
|
|
||||||
|
expect(() => getPendingServiceHandoffs(CODEX_REVIEW_SERVICE_ID)).toThrow(
|
||||||
|
/cannot read target_agent_type from stored row metadata/,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
_initTestDatabase();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('preserves an explicit reviewer target service id when creating a new handoff', () => {
|
it('preserves an explicit reviewer target service id when creating a new handoff', () => {
|
||||||
const handoff = createServiceHandoff({
|
const handoff = createServiceHandoff({
|
||||||
chat_jid: 'dc:handoff-stored-reviewer',
|
chat_jid: 'dc:handoff-stored-reviewer',
|
||||||
@@ -8686,6 +8817,49 @@ describe('work items', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fails fast when a work item row loses canonical agent metadata after init', () => {
|
||||||
|
const tempDir = fs.mkdtempSync('/tmp/ejclaw-work-item-strict-read-');
|
||||||
|
const dbPath = path.join(tempDir, 'messages.db');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileDb = new Database(dbPath);
|
||||||
|
initializeDatabaseSchema(fileDb);
|
||||||
|
fileDb.close();
|
||||||
|
|
||||||
|
_initTestDatabaseFromFile(dbPath);
|
||||||
|
const item = createProducedWorkItem({
|
||||||
|
group_folder: 'discord_test',
|
||||||
|
chat_jid: 'dc:work-item-strict-read',
|
||||||
|
agent_type: 'codex',
|
||||||
|
service_id: CODEX_REVIEW_SERVICE_ID,
|
||||||
|
delivery_role: 'reviewer',
|
||||||
|
start_seq: 1,
|
||||||
|
end_seq: 2,
|
||||||
|
result_payload: 'strict read work item',
|
||||||
|
});
|
||||||
|
|
||||||
|
const rawDb = new Database(dbPath);
|
||||||
|
rawDb
|
||||||
|
.prepare(
|
||||||
|
`UPDATE work_items
|
||||||
|
SET agent_type = ''
|
||||||
|
WHERE id = ?`,
|
||||||
|
)
|
||||||
|
.run(item.id);
|
||||||
|
rawDb.close();
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
getOpenWorkItemForChat(
|
||||||
|
'dc:work-item-strict-read',
|
||||||
|
CODEX_REVIEW_SERVICE_ID,
|
||||||
|
),
|
||||||
|
).toThrow(/cannot read agent_type from stored row metadata/);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
_initTestDatabase();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('backfills work item service ids during init on a canonical work_items schema without service_id columns', () => {
|
it('backfills work item service ids during init on a canonical work_items schema without service_id columns', () => {
|
||||||
const tempDir = fs.mkdtempSync('/tmp/ejclaw-work-items-canonical-');
|
const tempDir = fs.mkdtempSync('/tmp/ejclaw-work-items-canonical-');
|
||||||
const dbPath = path.join(tempDir, 'messages.db');
|
const dbPath = path.join(tempDir, 'messages.db');
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ interface RequiredRoleMetadataInput {
|
|||||||
|
|
||||||
interface OptionalRoleMetadataInput extends RequiredRoleMetadataInput {}
|
interface OptionalRoleMetadataInput extends RequiredRoleMetadataInput {}
|
||||||
|
|
||||||
function resolveRequiredAgentType(input: RequiredRoleMetadataInput): AgentType {
|
function resolveFilledRequiredAgentType(
|
||||||
|
input: RequiredRoleMetadataInput,
|
||||||
|
): AgentType {
|
||||||
const persistedAgentType = normalizeStoredAgentType(input.storedAgentType);
|
const persistedAgentType = normalizeStoredAgentType(input.storedAgentType);
|
||||||
const inferredAgentType = inferAgentTypeFromServiceShadow(
|
const inferredAgentType = inferAgentTypeFromServiceShadow(
|
||||||
input.storedServiceId,
|
input.storedServiceId,
|
||||||
@@ -48,7 +50,33 @@ function resolveRequiredAgentType(input: RequiredRoleMetadataInput): AgentType {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveRequiredServiceId(
|
function readStoredRequiredAgentType(
|
||||||
|
input: RequiredRoleMetadataInput,
|
||||||
|
): AgentType {
|
||||||
|
const persistedAgentType = normalizeStoredAgentType(input.storedAgentType);
|
||||||
|
const inferredAgentType = inferAgentTypeFromServiceShadow(
|
||||||
|
input.storedServiceId,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
persistedAgentType &&
|
||||||
|
inferredAgentType &&
|
||||||
|
persistedAgentType !== inferredAgentType
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`${input.context}: ${input.role}_agent_type conflicts with ${input.role}_service_id`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (persistedAgentType) {
|
||||||
|
return persistedAgentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`${input.context}: cannot read ${input.role}_agent_type from stored row metadata`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveFilledRequiredServiceId(
|
||||||
context: string,
|
context: string,
|
||||||
role: PairedRoomRole,
|
role: PairedRoomRole,
|
||||||
agentType: AgentType,
|
agentType: AgentType,
|
||||||
@@ -65,7 +93,21 @@ function resolveRequiredServiceId(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveOptionalRoleMetadata(input: OptionalRoleMetadataInput): {
|
function readStoredRequiredServiceId(
|
||||||
|
context: string,
|
||||||
|
role: PairedRoomRole,
|
||||||
|
storedServiceId?: string | null,
|
||||||
|
): string {
|
||||||
|
if (storedServiceId) {
|
||||||
|
return storedServiceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`${context}: cannot read ${role}_service_id from stored row metadata`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveFilledOptionalRoleMetadata(input: OptionalRoleMetadataInput): {
|
||||||
agentType: AgentType | null;
|
agentType: AgentType | null;
|
||||||
serviceId: string | null;
|
serviceId: string | null;
|
||||||
} {
|
} {
|
||||||
@@ -73,10 +115,10 @@ function resolveOptionalRoleMetadata(input: OptionalRoleMetadataInput): {
|
|||||||
return { agentType: null, serviceId: null };
|
return { agentType: null, serviceId: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
const agentType = resolveRequiredAgentType(input);
|
const agentType = resolveFilledRequiredAgentType(input);
|
||||||
return {
|
return {
|
||||||
agentType,
|
agentType,
|
||||||
serviceId: resolveRequiredServiceId(
|
serviceId: resolveFilledRequiredServiceId(
|
||||||
input.context,
|
input.context,
|
||||||
input.role,
|
input.role,
|
||||||
agentType,
|
agentType,
|
||||||
@@ -85,6 +127,25 @@ function resolveOptionalRoleMetadata(input: OptionalRoleMetadataInput): {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readStoredOptionalRoleMetadata(input: OptionalRoleMetadataInput): {
|
||||||
|
agentType: AgentType | null;
|
||||||
|
serviceId: string | null;
|
||||||
|
} {
|
||||||
|
if (!input.storedAgentType && !input.storedServiceId) {
|
||||||
|
return { agentType: null, serviceId: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const agentType = readStoredRequiredAgentType(input);
|
||||||
|
return {
|
||||||
|
agentType,
|
||||||
|
serviceId: readStoredRequiredServiceId(
|
||||||
|
input.context,
|
||||||
|
input.role,
|
||||||
|
input.storedServiceId,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface CanonicalPairedTaskMetadata {
|
export interface CanonicalPairedTaskMetadata {
|
||||||
ownerAgentType: AgentType;
|
ownerAgentType: AgentType;
|
||||||
reviewerAgentType: AgentType;
|
reviewerAgentType: AgentType;
|
||||||
@@ -93,7 +154,7 @@ export interface CanonicalPairedTaskMetadata {
|
|||||||
reviewerServiceId: string;
|
reviewerServiceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canonicalizePairedTaskMetadata(input: {
|
export function fillCanonicalPairedTaskMetadata(input: {
|
||||||
id: string;
|
id: string;
|
||||||
owner_service_id?: string | null;
|
owner_service_id?: string | null;
|
||||||
reviewer_service_id?: string | null;
|
reviewer_service_id?: string | null;
|
||||||
@@ -102,14 +163,14 @@ export function canonicalizePairedTaskMetadata(input: {
|
|||||||
arbiter_agent_type?: string | null;
|
arbiter_agent_type?: string | null;
|
||||||
}): CanonicalPairedTaskMetadata {
|
}): CanonicalPairedTaskMetadata {
|
||||||
const context = `paired_tasks(${input.id})`;
|
const context = `paired_tasks(${input.id})`;
|
||||||
const ownerAgentType = resolveRequiredAgentType({
|
const ownerAgentType = resolveFilledRequiredAgentType({
|
||||||
context,
|
context,
|
||||||
role: 'owner',
|
role: 'owner',
|
||||||
storedAgentType: input.owner_agent_type,
|
storedAgentType: input.owner_agent_type,
|
||||||
storedServiceId: input.owner_service_id,
|
storedServiceId: input.owner_service_id,
|
||||||
fallbackAgentType: OWNER_AGENT_TYPE,
|
fallbackAgentType: OWNER_AGENT_TYPE,
|
||||||
});
|
});
|
||||||
const reviewerAgentType = resolveRequiredAgentType({
|
const reviewerAgentType = resolveFilledRequiredAgentType({
|
||||||
context,
|
context,
|
||||||
role: 'reviewer',
|
role: 'reviewer',
|
||||||
storedAgentType: input.reviewer_agent_type,
|
storedAgentType: input.reviewer_agent_type,
|
||||||
@@ -124,13 +185,13 @@ export function canonicalizePairedTaskMetadata(input: {
|
|||||||
normalizeStoredAgentType(input.arbiter_agent_type) ??
|
normalizeStoredAgentType(input.arbiter_agent_type) ??
|
||||||
ARBITER_AGENT_TYPE ??
|
ARBITER_AGENT_TYPE ??
|
||||||
null,
|
null,
|
||||||
ownerServiceId: resolveRequiredServiceId(
|
ownerServiceId: resolveFilledRequiredServiceId(
|
||||||
context,
|
context,
|
||||||
'owner',
|
'owner',
|
||||||
ownerAgentType,
|
ownerAgentType,
|
||||||
input.owner_service_id,
|
input.owner_service_id,
|
||||||
),
|
),
|
||||||
reviewerServiceId: resolveRequiredServiceId(
|
reviewerServiceId: resolveFilledRequiredServiceId(
|
||||||
context,
|
context,
|
||||||
'reviewer',
|
'reviewer',
|
||||||
reviewerAgentType,
|
reviewerAgentType,
|
||||||
@@ -139,6 +200,46 @@ export function canonicalizePairedTaskMetadata(input: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function readCanonicalPairedTaskMetadata(input: {
|
||||||
|
id: string;
|
||||||
|
owner_service_id?: string | null;
|
||||||
|
reviewer_service_id?: string | null;
|
||||||
|
owner_agent_type?: string | null;
|
||||||
|
reviewer_agent_type?: string | null;
|
||||||
|
arbiter_agent_type?: string | null;
|
||||||
|
}): CanonicalPairedTaskMetadata {
|
||||||
|
const context = `paired_tasks(${input.id})`;
|
||||||
|
const ownerAgentType = readStoredRequiredAgentType({
|
||||||
|
context,
|
||||||
|
role: 'owner',
|
||||||
|
storedAgentType: input.owner_agent_type,
|
||||||
|
storedServiceId: input.owner_service_id,
|
||||||
|
});
|
||||||
|
const reviewerAgentType = readStoredRequiredAgentType({
|
||||||
|
context,
|
||||||
|
role: 'reviewer',
|
||||||
|
storedAgentType: input.reviewer_agent_type,
|
||||||
|
storedServiceId: input.reviewer_service_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
ownerAgentType,
|
||||||
|
reviewerAgentType,
|
||||||
|
arbiterAgentType:
|
||||||
|
normalizeStoredAgentType(input.arbiter_agent_type) ?? null,
|
||||||
|
ownerServiceId: readStoredRequiredServiceId(
|
||||||
|
context,
|
||||||
|
'owner',
|
||||||
|
input.owner_service_id,
|
||||||
|
),
|
||||||
|
reviewerServiceId: readStoredRequiredServiceId(
|
||||||
|
context,
|
||||||
|
'reviewer',
|
||||||
|
input.reviewer_service_id,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface CanonicalChannelOwnerLeaseMetadata {
|
export interface CanonicalChannelOwnerLeaseMetadata {
|
||||||
ownerAgentType: AgentType;
|
ownerAgentType: AgentType;
|
||||||
reviewerAgentType: AgentType | null;
|
reviewerAgentType: AgentType | null;
|
||||||
@@ -148,7 +249,7 @@ export interface CanonicalChannelOwnerLeaseMetadata {
|
|||||||
arbiterServiceId: string | null;
|
arbiterServiceId: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canonicalizeChannelOwnerLeaseMetadata(input: {
|
export function fillCanonicalChannelOwnerLeaseMetadata(input: {
|
||||||
chat_jid: string;
|
chat_jid: string;
|
||||||
owner_service_id?: string | null;
|
owner_service_id?: string | null;
|
||||||
reviewer_service_id?: string | null;
|
reviewer_service_id?: string | null;
|
||||||
@@ -158,19 +259,19 @@ export function canonicalizeChannelOwnerLeaseMetadata(input: {
|
|||||||
arbiter_agent_type?: string | null;
|
arbiter_agent_type?: string | null;
|
||||||
}): CanonicalChannelOwnerLeaseMetadata {
|
}): CanonicalChannelOwnerLeaseMetadata {
|
||||||
const context = `channel_owner(${input.chat_jid})`;
|
const context = `channel_owner(${input.chat_jid})`;
|
||||||
const ownerAgentType = resolveRequiredAgentType({
|
const ownerAgentType = resolveFilledRequiredAgentType({
|
||||||
context,
|
context,
|
||||||
role: 'owner',
|
role: 'owner',
|
||||||
storedAgentType: input.owner_agent_type,
|
storedAgentType: input.owner_agent_type,
|
||||||
storedServiceId: input.owner_service_id,
|
storedServiceId: input.owner_service_id,
|
||||||
});
|
});
|
||||||
const reviewer = resolveOptionalRoleMetadata({
|
const reviewer = resolveFilledOptionalRoleMetadata({
|
||||||
context,
|
context,
|
||||||
role: 'reviewer',
|
role: 'reviewer',
|
||||||
storedAgentType: input.reviewer_agent_type,
|
storedAgentType: input.reviewer_agent_type,
|
||||||
storedServiceId: input.reviewer_service_id,
|
storedServiceId: input.reviewer_service_id,
|
||||||
});
|
});
|
||||||
const arbiter = resolveOptionalRoleMetadata({
|
const arbiter = resolveFilledOptionalRoleMetadata({
|
||||||
context,
|
context,
|
||||||
role: 'arbiter',
|
role: 'arbiter',
|
||||||
storedAgentType: input.arbiter_agent_type,
|
storedAgentType: input.arbiter_agent_type,
|
||||||
@@ -181,7 +282,7 @@ export function canonicalizeChannelOwnerLeaseMetadata(input: {
|
|||||||
ownerAgentType,
|
ownerAgentType,
|
||||||
reviewerAgentType: reviewer.agentType,
|
reviewerAgentType: reviewer.agentType,
|
||||||
arbiterAgentType: arbiter.agentType,
|
arbiterAgentType: arbiter.agentType,
|
||||||
ownerServiceId: resolveRequiredServiceId(
|
ownerServiceId: resolveFilledRequiredServiceId(
|
||||||
context,
|
context,
|
||||||
'owner',
|
'owner',
|
||||||
ownerAgentType,
|
ownerAgentType,
|
||||||
@@ -192,6 +293,49 @@ export function canonicalizeChannelOwnerLeaseMetadata(input: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function readCanonicalChannelOwnerLeaseMetadata(input: {
|
||||||
|
chat_jid: string;
|
||||||
|
owner_service_id?: string | null;
|
||||||
|
reviewer_service_id?: string | null;
|
||||||
|
arbiter_service_id?: string | null;
|
||||||
|
owner_agent_type?: string | null;
|
||||||
|
reviewer_agent_type?: string | null;
|
||||||
|
arbiter_agent_type?: string | null;
|
||||||
|
}): CanonicalChannelOwnerLeaseMetadata {
|
||||||
|
const context = `channel_owner(${input.chat_jid})`;
|
||||||
|
const ownerAgentType = readStoredRequiredAgentType({
|
||||||
|
context,
|
||||||
|
role: 'owner',
|
||||||
|
storedAgentType: input.owner_agent_type,
|
||||||
|
storedServiceId: input.owner_service_id,
|
||||||
|
});
|
||||||
|
const reviewer = readStoredOptionalRoleMetadata({
|
||||||
|
context,
|
||||||
|
role: 'reviewer',
|
||||||
|
storedAgentType: input.reviewer_agent_type,
|
||||||
|
storedServiceId: input.reviewer_service_id,
|
||||||
|
});
|
||||||
|
const arbiter = readStoredOptionalRoleMetadata({
|
||||||
|
context,
|
||||||
|
role: 'arbiter',
|
||||||
|
storedAgentType: input.arbiter_agent_type,
|
||||||
|
storedServiceId: input.arbiter_service_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
ownerAgentType,
|
||||||
|
reviewerAgentType: reviewer.agentType,
|
||||||
|
arbiterAgentType: arbiter.agentType,
|
||||||
|
ownerServiceId: readStoredRequiredServiceId(
|
||||||
|
context,
|
||||||
|
'owner',
|
||||||
|
input.owner_service_id,
|
||||||
|
),
|
||||||
|
reviewerServiceId: reviewer.serviceId,
|
||||||
|
arbiterServiceId: arbiter.serviceId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeHandoffRole(
|
function normalizeHandoffRole(
|
||||||
role: string | null | undefined,
|
role: string | null | undefined,
|
||||||
): PairedRoomRole | null {
|
): PairedRoomRole | null {
|
||||||
@@ -232,7 +376,7 @@ export interface CanonicalServiceHandoffMetadata {
|
|||||||
targetServiceId: string;
|
targetServiceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canonicalizeServiceHandoffMetadata(input: {
|
export function fillCanonicalServiceHandoffMetadata(input: {
|
||||||
id: number | string;
|
id: number | string;
|
||||||
chat_jid: string;
|
chat_jid: string;
|
||||||
source_service_id?: string | null;
|
source_service_id?: string | null;
|
||||||
@@ -265,8 +409,6 @@ export function canonicalizeServiceHandoffMetadata(input: {
|
|||||||
`${context}: source_agent_type conflicts with source_service_id`,
|
`${context}: source_agent_type conflicts with source_service_id`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const sourceAgentType =
|
|
||||||
storedSourceAgentType ?? inferredSourceAgentType ?? null;
|
|
||||||
const storedTargetAgentType = normalizeStoredAgentType(
|
const storedTargetAgentType = normalizeStoredAgentType(
|
||||||
input.target_agent_type,
|
input.target_agent_type,
|
||||||
);
|
);
|
||||||
@@ -301,12 +443,133 @@ export function canonicalizeServiceHandoffMetadata(input: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sourceAgentTypeForShadow =
|
||||||
|
storedSourceAgentType ?? inferredSourceAgentType ?? null;
|
||||||
const sourceServiceId =
|
const sourceServiceId =
|
||||||
input.source_service_id ??
|
input.source_service_id ??
|
||||||
(sourceRole != null && sourceAgentType != null
|
(sourceRole != null && sourceAgentTypeForShadow != null
|
||||||
? resolveRoleServiceShadow(sourceRole, sourceAgentType)
|
? resolveRoleServiceShadow(sourceRole, sourceAgentTypeForShadow)
|
||||||
: null) ??
|
: null) ??
|
||||||
SERVICE_SESSION_SCOPE;
|
SERVICE_SESSION_SCOPE;
|
||||||
|
const sourceAgentType =
|
||||||
|
storedSourceAgentType ??
|
||||||
|
inferredSourceAgentType ??
|
||||||
|
inferAgentTypeFromServiceShadow(sourceServiceId) ??
|
||||||
|
null;
|
||||||
|
|
||||||
|
assertStoredRoleMatchesServiceShadow({
|
||||||
|
context,
|
||||||
|
storedRole: sourceRole,
|
||||||
|
agentType: sourceAgentType,
|
||||||
|
serviceId: sourceServiceId,
|
||||||
|
roleField: 'source_role',
|
||||||
|
serviceField: 'source_service_id',
|
||||||
|
});
|
||||||
|
assertStoredRoleMatchesServiceShadow({
|
||||||
|
context,
|
||||||
|
storedRole: targetRole,
|
||||||
|
agentType: targetAgentType,
|
||||||
|
serviceId: targetServiceId,
|
||||||
|
roleField: 'target_role',
|
||||||
|
serviceField: 'target_service_id',
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
sourceRole,
|
||||||
|
targetRole,
|
||||||
|
sourceAgentType,
|
||||||
|
targetAgentType,
|
||||||
|
sourceServiceId,
|
||||||
|
targetServiceId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readStoredHandoffAgentType(args: {
|
||||||
|
context: string;
|
||||||
|
field: 'source_agent_type' | 'target_agent_type';
|
||||||
|
serviceField: 'source_service_id' | 'target_service_id';
|
||||||
|
storedAgentType?: string | null;
|
||||||
|
storedServiceId?: string | null;
|
||||||
|
}): AgentType {
|
||||||
|
const persistedAgentType = normalizeStoredAgentType(args.storedAgentType);
|
||||||
|
const inferredAgentType = inferAgentTypeFromServiceShadow(
|
||||||
|
args.storedServiceId,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
persistedAgentType &&
|
||||||
|
inferredAgentType &&
|
||||||
|
persistedAgentType !== inferredAgentType
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`${args.context}: ${args.field} conflicts with ${args.serviceField}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (persistedAgentType) {
|
||||||
|
return persistedAgentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`${args.context}: cannot read ${args.field} from stored row metadata`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readStoredHandoffServiceId(args: {
|
||||||
|
context: string;
|
||||||
|
field: 'source_service_id' | 'target_service_id';
|
||||||
|
storedServiceId?: string | null;
|
||||||
|
}): string {
|
||||||
|
if (args.storedServiceId) {
|
||||||
|
return args.storedServiceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`${args.context}: cannot read ${args.field} from stored row metadata`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readCanonicalServiceHandoffMetadata(input: {
|
||||||
|
id: number | string;
|
||||||
|
chat_jid: string;
|
||||||
|
source_service_id?: string | null;
|
||||||
|
target_service_id?: string | null;
|
||||||
|
source_role?: string | null;
|
||||||
|
target_role?: string | null;
|
||||||
|
intended_role?: string | null;
|
||||||
|
source_agent_type?: string | null;
|
||||||
|
target_agent_type?: string | null;
|
||||||
|
}): CanonicalServiceHandoffMetadata {
|
||||||
|
const context = `service_handoffs(${input.id})`;
|
||||||
|
const sourceRole =
|
||||||
|
normalizeHandoffRole(input.source_role) ??
|
||||||
|
normalizeHandoffRole(input.intended_role);
|
||||||
|
const targetRole =
|
||||||
|
normalizeHandoffRole(input.target_role) ??
|
||||||
|
normalizeHandoffRole(input.intended_role);
|
||||||
|
const sourceServiceId = readStoredHandoffServiceId({
|
||||||
|
context,
|
||||||
|
field: 'source_service_id',
|
||||||
|
storedServiceId: input.source_service_id,
|
||||||
|
});
|
||||||
|
const targetServiceId = readStoredHandoffServiceId({
|
||||||
|
context,
|
||||||
|
field: 'target_service_id',
|
||||||
|
storedServiceId: input.target_service_id,
|
||||||
|
});
|
||||||
|
const sourceAgentType = readStoredHandoffAgentType({
|
||||||
|
context,
|
||||||
|
field: 'source_agent_type',
|
||||||
|
serviceField: 'source_service_id',
|
||||||
|
storedAgentType: input.source_agent_type,
|
||||||
|
storedServiceId: sourceServiceId,
|
||||||
|
});
|
||||||
|
const targetAgentType = readStoredHandoffAgentType({
|
||||||
|
context,
|
||||||
|
field: 'target_agent_type',
|
||||||
|
serviceField: 'target_service_id',
|
||||||
|
storedAgentType: input.target_agent_type,
|
||||||
|
storedServiceId: targetServiceId,
|
||||||
|
});
|
||||||
|
|
||||||
assertStoredRoleMatchesServiceShadow({
|
assertStoredRoleMatchesServiceShadow({
|
||||||
context,
|
context,
|
||||||
@@ -351,7 +614,7 @@ export function inferExecutionLeaseServiceIdFromCanonicalMetadata(
|
|||||||
): string | null {
|
): string | null {
|
||||||
switch (row.role) {
|
switch (row.role) {
|
||||||
case 'owner': {
|
case 'owner': {
|
||||||
const owner = resolveOptionalRoleMetadata({
|
const owner = resolveFilledOptionalRoleMetadata({
|
||||||
context: `paired_task_execution_leases(${row.rowid})`,
|
context: `paired_task_execution_leases(${row.rowid})`,
|
||||||
role: 'owner',
|
role: 'owner',
|
||||||
storedAgentType: row.owner_agent_type,
|
storedAgentType: row.owner_agent_type,
|
||||||
@@ -360,7 +623,7 @@ export function inferExecutionLeaseServiceIdFromCanonicalMetadata(
|
|||||||
return owner.serviceId;
|
return owner.serviceId;
|
||||||
}
|
}
|
||||||
case 'reviewer': {
|
case 'reviewer': {
|
||||||
const reviewer = resolveOptionalRoleMetadata({
|
const reviewer = resolveFilledOptionalRoleMetadata({
|
||||||
context: `paired_task_execution_leases(${row.rowid})`,
|
context: `paired_task_execution_leases(${row.rowid})`,
|
||||||
role: 'reviewer',
|
role: 'reviewer',
|
||||||
storedAgentType: row.reviewer_agent_type,
|
storedAgentType: row.reviewer_agent_type,
|
||||||
@@ -369,7 +632,7 @@ export function inferExecutionLeaseServiceIdFromCanonicalMetadata(
|
|||||||
return reviewer.serviceId;
|
return reviewer.serviceId;
|
||||||
}
|
}
|
||||||
case 'arbiter': {
|
case 'arbiter': {
|
||||||
const arbiter = resolveOptionalRoleMetadata({
|
const arbiter = resolveFilledOptionalRoleMetadata({
|
||||||
context: `paired_task_execution_leases(${row.rowid})`,
|
context: `paired_task_execution_leases(${row.rowid})`,
|
||||||
role: 'arbiter',
|
role: 'arbiter',
|
||||||
storedAgentType: row.arbiter_agent_type,
|
storedAgentType: row.arbiter_agent_type,
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { Database } from 'bun:sqlite';
|
import { Database } from 'bun:sqlite';
|
||||||
|
|
||||||
import { AgentType } from '../types.js';
|
import { AgentType } from '../types.js';
|
||||||
import { canonicalizeChannelOwnerLeaseMetadata } from './canonical-role-metadata.js';
|
import {
|
||||||
|
fillCanonicalChannelOwnerLeaseMetadata,
|
||||||
|
readCanonicalChannelOwnerLeaseMetadata,
|
||||||
|
} from './canonical-role-metadata.js';
|
||||||
|
|
||||||
export interface ChannelOwnerLeaseRow {
|
export interface ChannelOwnerLeaseRow {
|
||||||
chat_jid: string;
|
chat_jid: string;
|
||||||
@@ -49,7 +52,7 @@ function hydrateChannelOwnerLeaseRow(
|
|||||||
ownerServiceId,
|
ownerServiceId,
|
||||||
reviewerServiceId,
|
reviewerServiceId,
|
||||||
arbiterServiceId,
|
arbiterServiceId,
|
||||||
} = canonicalizeChannelOwnerLeaseMetadata({
|
} = readCanonicalChannelOwnerLeaseMetadata({
|
||||||
chat_jid: row.chat_jid,
|
chat_jid: row.chat_jid,
|
||||||
owner_service_id: row.owner_service_id,
|
owner_service_id: row.owner_service_id,
|
||||||
reviewer_service_id: row.reviewer_service_id,
|
reviewer_service_id: row.reviewer_service_id,
|
||||||
@@ -102,7 +105,7 @@ export function setChannelOwnerLeaseInDatabase(
|
|||||||
ownerServiceId,
|
ownerServiceId,
|
||||||
reviewerServiceId,
|
reviewerServiceId,
|
||||||
arbiterServiceId,
|
arbiterServiceId,
|
||||||
} = canonicalizeChannelOwnerLeaseMetadata({
|
} = fillCanonicalChannelOwnerLeaseMetadata({
|
||||||
chat_jid: input.chat_jid,
|
chat_jid: input.chat_jid,
|
||||||
owner_service_id: input.owner_service_id,
|
owner_service_id: input.owner_service_id,
|
||||||
reviewer_service_id: input.reviewer_service_id,
|
reviewer_service_id: input.reviewer_service_id,
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import {
|
|||||||
buildPairedTurnIdentity,
|
buildPairedTurnIdentity,
|
||||||
resolvePairedTurnRole,
|
resolvePairedTurnRole,
|
||||||
} from '../paired-turn-identity.js';
|
} from '../paired-turn-identity.js';
|
||||||
import { canonicalizePairedTaskMetadata } from './canonical-role-metadata.js';
|
import {
|
||||||
|
fillCanonicalPairedTaskMetadata,
|
||||||
|
readCanonicalPairedTaskMetadata,
|
||||||
|
} from './canonical-role-metadata.js';
|
||||||
import {
|
import {
|
||||||
ensurePairedTurnQueuedInDatabase,
|
ensurePairedTurnQueuedInDatabase,
|
||||||
markPairedTurnRunningInDatabase,
|
markPairedTurnRunningInDatabase,
|
||||||
@@ -74,7 +77,7 @@ function hydratePairedTaskRow(
|
|||||||
arbiterAgentType,
|
arbiterAgentType,
|
||||||
ownerServiceId,
|
ownerServiceId,
|
||||||
reviewerServiceId,
|
reviewerServiceId,
|
||||||
} = canonicalizePairedTaskMetadata({
|
} = readCanonicalPairedTaskMetadata({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
owner_service_id: row.owner_service_id,
|
owner_service_id: row.owner_service_id,
|
||||||
reviewer_service_id: row.reviewer_service_id,
|
reviewer_service_id: row.reviewer_service_id,
|
||||||
@@ -142,7 +145,7 @@ export function createPairedTaskInDatabase(
|
|||||||
arbiterAgentType,
|
arbiterAgentType,
|
||||||
ownerServiceId,
|
ownerServiceId,
|
||||||
reviewerServiceId,
|
reviewerServiceId,
|
||||||
} = canonicalizePairedTaskMetadata({
|
} = fillCanonicalPairedTaskMetadata({
|
||||||
id: task.id,
|
id: task.id,
|
||||||
owner_service_id: task.owner_service_id,
|
owner_service_id: task.owner_service_id,
|
||||||
reviewer_service_id: task.reviewer_service_id,
|
reviewer_service_id: task.reviewer_service_id,
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import {
|
|||||||
resolveRoleServiceShadow,
|
resolveRoleServiceShadow,
|
||||||
} from '../role-service-shadow.js';
|
} from '../role-service-shadow.js';
|
||||||
import {
|
import {
|
||||||
canonicalizeChannelOwnerLeaseMetadata,
|
fillCanonicalChannelOwnerLeaseMetadata,
|
||||||
canonicalizePairedTaskMetadata,
|
fillCanonicalPairedTaskMetadata,
|
||||||
canonicalizeServiceHandoffMetadata,
|
fillCanonicalServiceHandoffMetadata,
|
||||||
inferExecutionLeaseServiceIdFromCanonicalMetadata,
|
inferExecutionLeaseServiceIdFromCanonicalMetadata,
|
||||||
} from './canonical-role-metadata.js';
|
} from './canonical-role-metadata.js';
|
||||||
import {
|
import {
|
||||||
@@ -2320,7 +2320,7 @@ function backfillCanonicalPairedTaskServiceIds(database: Database): void {
|
|||||||
arbiterAgentType,
|
arbiterAgentType,
|
||||||
ownerServiceId,
|
ownerServiceId,
|
||||||
reviewerServiceId,
|
reviewerServiceId,
|
||||||
} = canonicalizePairedTaskMetadata({
|
} = fillCanonicalPairedTaskMetadata({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
owner_service_id: row.owner_service_id,
|
owner_service_id: row.owner_service_id,
|
||||||
reviewer_service_id: row.reviewer_service_id,
|
reviewer_service_id: row.reviewer_service_id,
|
||||||
@@ -2400,7 +2400,7 @@ function backfillCanonicalChannelOwnerServiceIds(database: Database): void {
|
|||||||
ownerServiceId,
|
ownerServiceId,
|
||||||
reviewerServiceId,
|
reviewerServiceId,
|
||||||
arbiterServiceId,
|
arbiterServiceId,
|
||||||
} = canonicalizeChannelOwnerLeaseMetadata({
|
} = fillCanonicalChannelOwnerLeaseMetadata({
|
||||||
chat_jid: row.chat_jid,
|
chat_jid: row.chat_jid,
|
||||||
owner_service_id: row.owner_service_id,
|
owner_service_id: row.owner_service_id,
|
||||||
reviewer_service_id: row.reviewer_service_id,
|
reviewer_service_id: row.reviewer_service_id,
|
||||||
@@ -2486,7 +2486,7 @@ function backfillCanonicalServiceHandoffServiceIds(database: Database): void {
|
|||||||
targetAgentType,
|
targetAgentType,
|
||||||
sourceServiceId,
|
sourceServiceId,
|
||||||
targetServiceId,
|
targetServiceId,
|
||||||
} = canonicalizeServiceHandoffMetadata({
|
} = fillCanonicalServiceHandoffMetadata({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
chat_jid: row.chat_jid,
|
chat_jid: row.chat_jid,
|
||||||
source_service_id: row.source_service_id,
|
source_service_id: row.source_service_id,
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ import {
|
|||||||
getLatestMessageSeqAtOrBeforeFromDatabase,
|
getLatestMessageSeqAtOrBeforeFromDatabase,
|
||||||
normalizeSeqCursor,
|
normalizeSeqCursor,
|
||||||
} from './messages.js';
|
} from './messages.js';
|
||||||
import { canonicalizeServiceHandoffMetadata } from './canonical-role-metadata.js';
|
import {
|
||||||
|
fillCanonicalServiceHandoffMetadata,
|
||||||
|
readCanonicalServiceHandoffMetadata,
|
||||||
|
} from './canonical-role-metadata.js';
|
||||||
import {
|
import {
|
||||||
getRouterStateFromDatabase,
|
getRouterStateFromDatabase,
|
||||||
setRouterStateInDatabase,
|
setRouterStateInDatabase,
|
||||||
@@ -133,7 +136,7 @@ function hydrateServiceHandoffRow(
|
|||||||
targetAgentType,
|
targetAgentType,
|
||||||
sourceServiceId,
|
sourceServiceId,
|
||||||
targetServiceId,
|
targetServiceId,
|
||||||
} = canonicalizeServiceHandoffMetadata({
|
} = readCanonicalServiceHandoffMetadata({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
chat_jid: row.chat_jid,
|
chat_jid: row.chat_jid,
|
||||||
source_service_id: row.source_service_id,
|
source_service_id: row.source_service_id,
|
||||||
@@ -249,7 +252,7 @@ export function createServiceHandoffInDatabase(
|
|||||||
targetAgentType,
|
targetAgentType,
|
||||||
sourceServiceId,
|
sourceServiceId,
|
||||||
targetServiceId,
|
targetServiceId,
|
||||||
} = canonicalizeServiceHandoffMetadata({
|
} = fillCanonicalServiceHandoffMetadata({
|
||||||
id: 'new',
|
id: 'new',
|
||||||
chat_jid: input.chat_jid,
|
chat_jid: input.chat_jid,
|
||||||
source_service_id: input.source_service_id,
|
source_service_id: input.source_service_id,
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ function normalizeStoredAgentType(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveStoredWorkItemServiceId(args: {
|
function fillCanonicalWorkItemServiceId(args: {
|
||||||
agentType: AgentType;
|
agentType: AgentType;
|
||||||
deliveryRole?: PairedRoomRole | null;
|
deliveryRole?: PairedRoomRole | null;
|
||||||
serviceId?: string | null;
|
serviceId?: string | null;
|
||||||
@@ -65,16 +65,37 @@ function resolveStoredWorkItemServiceId(args: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readStoredWorkItemAgentType(
|
||||||
|
row: Pick<StoredWorkItemRow, 'id' | 'agent_type'>,
|
||||||
|
): AgentType {
|
||||||
|
const agentType = normalizeStoredAgentType(row.agent_type);
|
||||||
|
if (agentType) {
|
||||||
|
return agentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`work_items(${row.id}): cannot read agent_type from stored row metadata`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readStoredWorkItemServiceId(
|
||||||
|
row: Pick<StoredWorkItemRow, 'id' | 'service_id'>,
|
||||||
|
): string {
|
||||||
|
if (row.service_id) {
|
||||||
|
return normalizeServiceId(row.service_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`work_items(${row.id}): cannot read service_id from stored row metadata`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function hydrateWorkItemRow(row: StoredWorkItemRow): WorkItem {
|
function hydrateWorkItemRow(row: StoredWorkItemRow): WorkItem {
|
||||||
const agentType = normalizeStoredAgentType(row.agent_type) ?? 'claude-code';
|
const agentType = readStoredWorkItemAgentType(row);
|
||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
agent_type: agentType,
|
agent_type: agentType,
|
||||||
service_id: resolveStoredWorkItemServiceId({
|
service_id: readStoredWorkItemServiceId(row),
|
||||||
agentType,
|
|
||||||
deliveryRole: row.delivery_role,
|
|
||||||
serviceId: row.service_id,
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +203,7 @@ export function createProducedWorkItemInDatabase(
|
|||||||
): WorkItem {
|
): WorkItem {
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const agentType = input.agent_type || 'claude-code';
|
const agentType = input.agent_type || 'claude-code';
|
||||||
const serviceId = resolveStoredWorkItemServiceId({
|
const serviceId = fillCanonicalWorkItemServiceId({
|
||||||
agentType,
|
agentType,
|
||||||
deliveryRole: input.delivery_role,
|
deliveryRole: input.delivery_role,
|
||||||
serviceId: input.service_id,
|
serviceId: input.service_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user