Files
EJClaw/src/db/canonical-role-metadata.ts
2026-04-29 13:31:11 +09:00

647 lines
18 KiB
TypeScript

import {
ARBITER_AGENT_TYPE,
OWNER_AGENT_TYPE,
REVIEWER_AGENT_TYPE,
SERVICE_SESSION_SCOPE,
} from '../config.js';
import {
inferAgentTypeFromServiceShadow,
inferRoleFromServiceShadow,
resolveRoleServiceShadow,
} from '../role-service-shadow.js';
import type { AgentType, PairedRoomRole } from '../types.js';
import { normalizeStoredAgentType } from './room-registration.js';
interface RequiredRoleMetadataInput {
context: string;
role: PairedRoomRole;
storedAgentType?: string | null;
storedServiceId?: string | null;
fallbackAgentType?: AgentType | null;
}
type OptionalRoleMetadataInput = RequiredRoleMetadataInput;
function resolveFilledRequiredAgentType(
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`,
);
}
const agentType =
persistedAgentType ?? inferredAgentType ?? input.fallbackAgentType ?? null;
if (agentType) {
return agentType;
}
throw new Error(
`${input.context}: cannot resolve ${input.role}_agent_type from stored row metadata`,
);
}
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,
role: PairedRoomRole,
agentType: AgentType,
storedServiceId?: string | null,
): string {
return (
storedServiceId ??
resolveRoleServiceShadow(role, agentType) ??
(() => {
throw new Error(
`${context}: cannot resolve ${role}_service_id from stored row metadata`,
);
})()
);
}
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;
serviceId: string | null;
} {
if (!input.storedAgentType && !input.storedServiceId) {
return { agentType: null, serviceId: null };
}
const agentType = resolveFilledRequiredAgentType(input);
return {
agentType,
serviceId: resolveFilledRequiredServiceId(
input.context,
input.role,
agentType,
input.storedServiceId,
),
};
}
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 {
ownerAgentType: AgentType;
reviewerAgentType: AgentType;
arbiterAgentType: AgentType | null;
ownerServiceId: string;
reviewerServiceId: string;
}
export function fillCanonicalPairedTaskMetadata(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 = resolveFilledRequiredAgentType({
context,
role: 'owner',
storedAgentType: input.owner_agent_type,
storedServiceId: input.owner_service_id,
fallbackAgentType: OWNER_AGENT_TYPE,
});
const reviewerAgentType = resolveFilledRequiredAgentType({
context,
role: 'reviewer',
storedAgentType: input.reviewer_agent_type,
storedServiceId: input.reviewer_service_id,
fallbackAgentType: REVIEWER_AGENT_TYPE,
});
return {
ownerAgentType,
reviewerAgentType,
arbiterAgentType:
normalizeStoredAgentType(input.arbiter_agent_type) ??
ARBITER_AGENT_TYPE ??
null,
ownerServiceId: resolveFilledRequiredServiceId(
context,
'owner',
ownerAgentType,
input.owner_service_id,
),
reviewerServiceId: resolveFilledRequiredServiceId(
context,
'reviewer',
reviewerAgentType,
input.reviewer_service_id,
),
};
}
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 {
ownerAgentType: AgentType;
reviewerAgentType: AgentType | null;
arbiterAgentType: AgentType | null;
ownerServiceId: string;
reviewerServiceId: string | null;
arbiterServiceId: string | null;
}
export function fillCanonicalChannelOwnerLeaseMetadata(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 = resolveFilledRequiredAgentType({
context,
role: 'owner',
storedAgentType: input.owner_agent_type,
storedServiceId: input.owner_service_id,
});
const reviewer = resolveFilledOptionalRoleMetadata({
context,
role: 'reviewer',
storedAgentType: input.reviewer_agent_type,
storedServiceId: input.reviewer_service_id,
});
const arbiter = resolveFilledOptionalRoleMetadata({
context,
role: 'arbiter',
storedAgentType: input.arbiter_agent_type,
storedServiceId: input.arbiter_service_id,
});
return {
ownerAgentType,
reviewerAgentType: reviewer.agentType,
arbiterAgentType: arbiter.agentType,
ownerServiceId: resolveFilledRequiredServiceId(
context,
'owner',
ownerAgentType,
input.owner_service_id,
),
reviewerServiceId: reviewer.serviceId,
arbiterServiceId: arbiter.serviceId,
};
}
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(
role: string | null | undefined,
): PairedRoomRole | null {
return role === 'owner' || role === 'reviewer' || role === 'arbiter'
? role
: null;
}
function assertStoredRoleMatchesServiceShadow(args: {
context: string;
storedRole: PairedRoomRole | null;
agentType: AgentType | null;
serviceId: string | null;
roleField: 'source_role' | 'target_role';
serviceField: 'source_service_id' | 'target_service_id';
}): void {
if (!args.storedRole || !args.agentType || !args.serviceId) {
return;
}
const inferredRole = inferRoleFromServiceShadow(
args.agentType,
args.serviceId,
);
if (inferredRole && inferredRole !== args.storedRole) {
throw new Error(
`${args.context}: ${args.roleField} conflicts with ${args.serviceField}`,
);
}
}
export interface CanonicalServiceHandoffMetadata {
sourceRole: PairedRoomRole | null;
targetRole: PairedRoomRole | null;
sourceAgentType: AgentType | null;
targetAgentType: AgentType;
sourceServiceId: string;
targetServiceId: string;
}
export function fillCanonicalServiceHandoffMetadata(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 storedSourceAgentType = normalizeStoredAgentType(
input.source_agent_type,
);
const inferredSourceAgentType = inferAgentTypeFromServiceShadow(
input.source_service_id,
);
if (
storedSourceAgentType &&
inferredSourceAgentType &&
storedSourceAgentType !== inferredSourceAgentType
) {
throw new Error(
`${context}: source_agent_type conflicts with source_service_id`,
);
}
const storedTargetAgentType = normalizeStoredAgentType(
input.target_agent_type,
);
const inferredTargetAgentType = inferAgentTypeFromServiceShadow(
input.target_service_id,
);
if (
storedTargetAgentType &&
inferredTargetAgentType &&
storedTargetAgentType !== inferredTargetAgentType
) {
throw new Error(
`${context}: target_agent_type conflicts with target_service_id`,
);
}
const targetAgentType = storedTargetAgentType ?? inferredTargetAgentType;
if (!targetAgentType) {
throw new Error(
`${context}: cannot resolve target_agent_type from stored row metadata`,
);
}
const targetServiceId =
input.target_service_id ??
(targetRole != null
? resolveRoleServiceShadow(targetRole, targetAgentType)
: null);
if (!targetServiceId) {
throw new Error(
`${context}: cannot resolve target_service_id from stored row metadata`,
);
}
const sourceAgentTypeForShadow =
storedSourceAgentType ?? inferredSourceAgentType ?? null;
const sourceServiceId =
input.source_service_id ??
(sourceRole != null && sourceAgentTypeForShadow != null
? resolveRoleServiceShadow(sourceRole, sourceAgentTypeForShadow)
: null) ??
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({
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,
};
}
interface ExecutionLeaseMetadataRow {
rowid: number;
role: 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;
}
export function inferExecutionLeaseServiceIdFromCanonicalMetadata(
row: ExecutionLeaseMetadataRow,
): string | null {
switch (row.role) {
case 'owner': {
const owner = resolveFilledOptionalRoleMetadata({
context: `paired_task_execution_leases(${row.rowid})`,
role: 'owner',
storedAgentType: row.owner_agent_type,
storedServiceId: row.owner_service_id,
});
return owner.serviceId;
}
case 'reviewer': {
const reviewer = resolveFilledOptionalRoleMetadata({
context: `paired_task_execution_leases(${row.rowid})`,
role: 'reviewer',
storedAgentType: row.reviewer_agent_type,
storedServiceId: row.reviewer_service_id,
});
return reviewer.serviceId;
}
case 'arbiter': {
const arbiter = resolveFilledOptionalRoleMetadata({
context: `paired_task_execution_leases(${row.rowid})`,
role: 'arbiter',
storedAgentType: row.arbiter_agent_type,
storedServiceId: row.arbiter_service_id,
});
return arbiter.serviceId;
}
default:
return null;
}
}