Files
stack/docs/native-kanban-sot/contracts/mechanical-coordinator.v1.ts
jason.woltje 49e8a54105
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
docs(#751): Publish native Kanban/SOT canon (#752)
2026-07-14 17:08:09 +00:00

420 lines
12 KiB
TypeScript

/**
* Mosaic Native Kanban — frozen Mechanical Coordinator contracts v1.
*
* The pure decision engine and persistence/orchestration service are separate.
* Neither surface can create scope, edit acceptance, waive gates, certify,
* merge, release a deployment, or close a provider issue.
*/
import type {
DeliberateWriteDenialV1,
InternalKanbanMutationContextV1,
KanbanEvaluationContextV1,
KanbanMutationFailureV1,
RetryableTransportErrorV1,
VersionConflictV1,
} from './health-state.v1.js';
export const COORDINATOR_CONTRACT_VERSION = '1.0.0' as const;
export type Uuid = string;
export type IsoTimestamp = string;
/** PostgreSQL bigint-safe decimal string; never a JavaScript number. */
export type FencingTokenV1 = string;
export const specialistRoles = [
'planning',
'enhance',
'coder',
'review',
'security-review',
'pr-monitor',
'certifier',
] as const;
export type SpecialistRole = (typeof specialistRoles)[number];
/** One vocabulary shared with task_assignment_state_v1 in the Drizzle schema. */
export const assignmentStates = [
'awaiting_approval',
'policy_pre_authorized',
'approved',
'rejected',
'leased',
'released',
'expired',
'superseded',
] as const;
export type AssignmentStateV1 = (typeof assignmentStates)[number];
export const readinessStates = [
'dependency-gated',
'schedule-gated',
'policy-gated',
'lease-available',
'leased',
'retry-delayed',
'exhausted',
'quarantined',
] as const;
export type ReadinessState = (typeof readinessStates)[number];
export interface RetryStateSnapshotV1 {
disposition: 'available' | 'retry_delayed' | 'quarantined' | 'exhausted';
attemptCount: number;
maxAttempts: number;
nextEligibleAt: IsoTimestamp | null;
idempotent: boolean;
terminalReason: string | null;
version: number;
}
export interface TaskEligibilitySnapshotV1 {
workspaceId: Uuid;
taskId: Uuid;
taskVersion: number;
projectId: Uuid;
projectActive: boolean;
missionId: Uuid | null;
missionActive: boolean;
status: 'ready';
priority: 'critical' | 'high' | 'medium' | 'low';
boardRank: string;
dueAt: IsoTimestamp | null;
notBeforeAt: IsoTimestamp | null;
createdAt: IsoTimestamp;
requiredRole: SpecialistRole;
requiredCapabilities: readonly string[];
blockingDependencies: readonly {
taskId: Uuid;
done: boolean;
completionConditionSatisfied: boolean;
}[];
releaseApproval: {
decisionId: Uuid;
approved: boolean;
policyRevision: string;
} | null;
activeLeaseId: Uuid | null;
retry: RetryStateSnapshotV1;
}
export interface AgentSessionSnapshotV1 {
workspaceId: Uuid;
agentId: Uuid;
sessionId: Uuid;
state: 'available' | 'busy';
roles: readonly SpecialistRole[];
capabilities: readonly string[];
capacity: number;
activeLeaseCount: number;
heartbeatAt: IsoTimestamp;
}
export interface EligibilityExplanationV1 {
taskId: Uuid;
eligible: boolean;
readiness: ReadinessState;
reasons: readonly {
gate:
| 'status'
| 'project'
| 'mission'
| 'dependency'
| 'schedule'
| 'retry'
| 'approval'
| 'lease'
| 'capability'
| 'capacity'
| 'health';
satisfied: boolean;
code: string;
detail: string;
}[];
policyRevision: string;
evaluatedAt: IsoTimestamp;
}
export interface AssignmentProposalDecisionV1 {
workspaceId: Uuid;
taskId: Uuid;
taskVersion: number;
targetAgentId: Uuid;
targetSessionId: Uuid;
specialistRole: SpecialistRole;
initialState: 'awaiting_approval' | 'policy_pre_authorized';
policyRevision: string;
explanation: EligibilityExplanationV1;
expiresAt: IsoTimestamp;
}
export interface AssignmentCycleSnapshotV1 {
context: KanbanEvaluationContextV1;
tasks: readonly TaskEligibilitySnapshotV1[];
sessions: readonly AgentSessionSnapshotV1[];
workspaceFairness: Readonly<Record<Uuid, number>>;
limit: number;
}
export interface AssignmentCycleDecisionV1 {
evaluatedTaskCount: number;
proposals: readonly AssignmentProposalDecisionV1[];
explanations: readonly EligibilityExplanationV1[];
}
export interface LeaseExpirySnapshotV1 {
workspaceId: Uuid;
taskId: Uuid;
taskVersion: number;
leaseId: Uuid;
assignmentId: Uuid;
sessionId: Uuid;
fencingToken: FencingTokenV1;
state: 'pending_ack' | 'active';
acknowledgeBy: IsoTimestamp;
expiresAt: IsoTimestamp;
lastHeartbeatAt: IsoTimestamp | null;
retry: RetryStateSnapshotV1;
}
export interface LeaseExpiryDecisionV1 {
leaseId: Uuid;
action: 'retain' | 'release' | 'retry' | 'quarantine' | 'exhaust';
reason: string;
nextEligibleAt: IsoTimestamp | null;
}
/** Pure package owned by KBN-200. It receives complete immutable snapshots. */
export interface MechanicalCoordinatorDecisionEngineV1 {
evaluateAssignmentCycle(snapshot: AssignmentCycleSnapshotV1): AssignmentCycleDecisionV1;
explainEligibility(
context: KanbanEvaluationContextV1,
task: TaskEligibilitySnapshotV1,
sessions: readonly AgentSessionSnapshotV1[],
): EligibilityExplanationV1;
decideLeaseExpiry(
context: KanbanEvaluationContextV1,
lease: LeaseExpirySnapshotV1,
): LeaseExpiryDecisionV1;
}
export interface PersistedAssignmentV1 {
assignmentId: Uuid;
workspaceId: Uuid;
taskId: Uuid;
taskVersion: number;
targetAgentId: Uuid;
targetSessionId: Uuid;
specialistRole: SpecialistRole;
state: AssignmentStateV1;
policyRevision: string;
proposedBy: { kind: 'user' | 'agent'; id: Uuid };
reason: string;
createdAt: IsoTimestamp;
expiresAt: IsoTimestamp;
}
export interface TaskLeaseV1 {
leaseId: Uuid;
workspaceId: Uuid;
taskId: Uuid;
taskVersion: number;
assignmentId: Uuid;
agentId: Uuid;
sessionId: Uuid;
state: 'pending_ack' | 'active';
fencingToken: FencingTokenV1;
attempt: number;
acquiredAt: IsoTimestamp;
acknowledgeBy: IsoTimestamp;
lastHeartbeatAt: IsoTimestamp | null;
expiresAt: IsoTimestamp;
}
interface ServiceCommandBaseV1 {
context: InternalKanbanMutationContextV1;
taskId: Uuid;
expectedTaskVersion: number;
}
export interface AcquireApprovedLeaseCommandV1 extends ServiceCommandBaseV1 {
assignmentId: Uuid;
approvalDecisionId: Uuid;
targetSessionId: Uuid;
leaseTtlSeconds: number;
}
export interface LeaseCommandV1 extends ServiceCommandBaseV1 {
leaseId: Uuid;
sessionId: Uuid;
fencingToken: FencingTokenV1;
}
export interface HeartbeatLeaseCommandV1 extends LeaseCommandV1 {
extendSeconds: number;
}
export interface CheckpointCommandV1 extends LeaseCommandV1 {
sequence: number;
resumableSummary: string;
artifactIds: readonly Uuid[];
contextUsagePercent: number;
}
export interface SubmitForReviewCommandV1 extends LeaseCommandV1 {
artifactIds: readonly Uuid[];
summary: string;
}
export interface ReleaseLeaseCommandV1 extends LeaseCommandV1 {
reason:
| 'worker_requested'
| 'ack_timeout'
| 'heartbeat_timeout'
| 'task_submitted'
| 'policy_revoked'
| 'shutdown';
}
export interface AssignmentCycleCommandV1 {
context: InternalKanbanMutationContextV1;
limit: number;
}
export interface ExpirySweepCommandV1 {
context: InternalKanbanMutationContextV1;
limit: number;
}
export interface RecoverCoordinatorCommandV1 {
context: InternalKanbanMutationContextV1;
}
interface CoordinatorRejectionBaseV1 {
kind: 'coordinator_rejection';
retryable: false;
requestOutcome: 'not_applied';
correlationId: Uuid;
idempotencyKey: string;
message: string;
}
export type CoordinatorPolicyRejectionV1 =
| (CoordinatorRejectionBaseV1 & { code: 'WORKSPACE_MISMATCH' })
| (CoordinatorRejectionBaseV1 & {
code: 'TASK_NOT_ELIGIBLE';
explanation: EligibilityExplanationV1;
})
| (CoordinatorRejectionBaseV1 & { code: 'APPROVAL_REQUIRED' })
| (CoordinatorRejectionBaseV1 & { code: 'APPROVAL_STALE' })
| (CoordinatorRejectionBaseV1 & { code: 'ASSIGNMENT_STALE' })
| (CoordinatorRejectionBaseV1 & { code: 'ASSIGNMENT_TARGET_MISMATCH' })
| (CoordinatorRejectionBaseV1 & { code: 'POLICY_REVISION_MISMATCH' })
| (CoordinatorRejectionBaseV1 & { code: 'ARTIFACT_WORKSPACE_MISMATCH' })
| (CoordinatorRejectionBaseV1 & { code: 'LEASE_ALREADY_ACTIVE' })
| (CoordinatorRejectionBaseV1 & { code: 'LEASE_NOT_FOUND' })
| (CoordinatorRejectionBaseV1 & { code: 'LEASE_NOT_ACTIVE' })
| (CoordinatorRejectionBaseV1 & {
code: 'ACK_DEADLINE_EXPIRED';
expiredAt: IsoTimestamp;
})
| (CoordinatorRejectionBaseV1 & {
code: 'FENCING_TOKEN_STALE';
currentFencingToken: FencingTokenV1;
})
| (CoordinatorRejectionBaseV1 & { code: 'SESSION_MISMATCH' })
| (CoordinatorRejectionBaseV1 & {
code: 'HEARTBEAT_EXPIRED';
expiredAt: IsoTimestamp;
})
| (CoordinatorRejectionBaseV1 & {
code: 'CHECKPOINT_SEQUENCE_CONFLICT';
currentSequence: number;
})
| (CoordinatorRejectionBaseV1 & { code: 'RETRY_EXHAUSTED' })
| (CoordinatorRejectionBaseV1 & {
code: 'NON_IDEMPOTENT_RETRY_REQUIRES_ORCHESTRATOR';
});
/** Explicit mapping to the Gateway mutation failure union; no arbitrary booleans. */
export type CoordinatorFailureV1 =
| DeliberateWriteDenialV1
| VersionConflictV1
| RetryableTransportErrorV1
| CoordinatorPolicyRejectionV1;
export interface CoordinatorSuccessV1<T> {
ok: true;
value: T;
correlationId: Uuid;
}
export interface CoordinatorFailureResultV1 {
ok: false;
failure: CoordinatorFailureV1;
}
export type CoordinatorResultV1<T> = CoordinatorSuccessV1<T> | CoordinatorFailureResultV1;
export interface ExpirySweepResultV1 {
examined: number;
released: readonly Uuid[];
retryScheduled: readonly Uuid[];
quarantined: readonly Uuid[];
exhausted: readonly Uuid[];
}
export interface RestartRecoveryResultV1 {
activeLeaseIds: readonly Uuid[];
expiredLeaseIds: readonly Uuid[];
pendingAssignmentIds: readonly Uuid[];
pendingOutboxEventIds: readonly Uuid[];
}
/** Persistence/Gateway adapter owned by KBN-210. */
export interface MechanicalCoordinatorServicePortV1 {
/** Loads immutable snapshots, invokes pure engine, and persists proposals atomically. */
runAssignmentCycle(
command: AssignmentCycleCommandV1,
): Promise<CoordinatorResultV1<{ assignments: readonly PersistedAssignmentV1[] }>>;
/** Query path loads by ID; public health observation cannot authorize mutation. */
getEligibilityExplanation(
context: KanbanEvaluationContextV1,
taskId: Uuid,
): Promise<CoordinatorResultV1<EligibilityExplanationV1>>;
/**
* Accepts IDs only. Implementation reloads and locks assignment + approval +
* task + target session in PostgreSQL, then verifies workspace, task version,
* target agent/session, state, expiry, policy revision, and current approval.
*/
acquireApprovedLease(
command: AcquireApprovedLeaseCommandV1,
): Promise<CoordinatorResultV1<TaskLeaseV1>>;
acknowledgeLease(command: LeaseCommandV1): Promise<CoordinatorResultV1<TaskLeaseV1>>;
heartbeatLease(command: HeartbeatLeaseCommandV1): Promise<CoordinatorResultV1<TaskLeaseV1>>;
appendCheckpoint(
command: CheckpointCommandV1,
): Promise<CoordinatorResultV1<{ checkpointId: Uuid }>>;
submitForReview(
command: SubmitForReviewCommandV1,
): Promise<CoordinatorResultV1<{ taskVersion: number; status: 'in_review' }>>;
releaseLease(command: ReleaseLeaseCommandV1): Promise<CoordinatorResultV1<{ released: true }>>;
expireAndRecover(
command: ExpirySweepCommandV1,
): Promise<CoordinatorResultV1<ExpirySweepResultV1>>;
recoverFromPostgres(
command: RecoverCoordinatorCommandV1,
): Promise<CoordinatorResultV1<RestartRecoveryResultV1>>;
}
/** Compile-time mapping guarantee: Coordinator Gateway failures are Kanban failures or exact policy rejections. */
export function isKanbanMutationFailureV1(
failure: CoordinatorFailureV1,
): failure is KanbanMutationFailureV1 {
return (
failure.kind === 'deliberate_fail_closed_denial' ||
failure.kind === 'retryable_transport_error' ||
failure.kind === 'version_conflict'
);
}