mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import type {
|
||||
ApprovalActionBinding,
|
||||
ApprovalDecision,
|
||||
ApprovalRequestRecord,
|
||||
ApprovedActionDispatchRecord,
|
||||
ApprovalRisk,
|
||||
} from '../domain/approvalRequest';
|
||||
import type {
|
||||
PolicySubject,
|
||||
ProjectPolicyFence,
|
||||
} from '../domain/projectPolicy';
|
||||
|
||||
export interface CreateApprovalRequestCommand {
|
||||
request: ApprovalRequestRecord;
|
||||
authorizationFence: ProjectPolicyFence;
|
||||
}
|
||||
|
||||
export interface CreateApprovalRequestResult {
|
||||
status: 'created' | 'existing';
|
||||
request: Readonly<ApprovalRequestRecord>;
|
||||
}
|
||||
|
||||
export interface DecideApprovalRequestCommand {
|
||||
requestId: string;
|
||||
expectedVersion: number;
|
||||
decisionId: string;
|
||||
decision: ApprovalDecision;
|
||||
reasonCode: string;
|
||||
decidedBy: PolicySubject;
|
||||
decidedAtMs: number;
|
||||
authorizationFence: ProjectPolicyFence;
|
||||
}
|
||||
|
||||
export interface DecideApprovalRequestResult {
|
||||
status: 'decided' | 'existing';
|
||||
request: Readonly<ApprovalRequestRecord>;
|
||||
}
|
||||
|
||||
export interface ConsumeApprovalRequestCommand {
|
||||
requestId: string;
|
||||
expectedVersion: number;
|
||||
consumptionId: string;
|
||||
dispatchId: string;
|
||||
action: ApprovalActionBinding;
|
||||
requestedBy: PolicySubject;
|
||||
consumedBy: PolicySubject;
|
||||
consumedAtMs: number;
|
||||
authorizationFence: ProjectPolicyFence;
|
||||
}
|
||||
|
||||
export interface ConsumeApprovalRequestResult {
|
||||
status: 'consumed' | 'existing';
|
||||
request: Readonly<ApprovalRequestRecord>;
|
||||
dispatch: Readonly<ApprovedActionDispatchRecord>;
|
||||
}
|
||||
|
||||
export interface ListPendingApprovalRequestsQuery {
|
||||
projectId: string;
|
||||
nowMs: number;
|
||||
limit: number;
|
||||
afterExpiresAtMs?: number;
|
||||
afterId?: string;
|
||||
risks?: readonly ApprovalRisk[];
|
||||
}
|
||||
|
||||
export interface ApprovalRequestRepository {
|
||||
findById(id: string): Promise<Readonly<ApprovalRequestRecord> | null>;
|
||||
|
||||
create(
|
||||
command: CreateApprovalRequestCommand,
|
||||
): Promise<CreateApprovalRequestResult>;
|
||||
|
||||
decide(
|
||||
command: DecideApprovalRequestCommand,
|
||||
): Promise<DecideApprovalRequestResult>;
|
||||
|
||||
consume(
|
||||
command: ConsumeApprovalRequestCommand,
|
||||
): Promise<ConsumeApprovalRequestResult>;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import type {
|
||||
ApprovedActionDispatchCursor,
|
||||
ApprovedActionDispatchExecutionSnapshot,
|
||||
} from '../domain/approvedActionDispatchExecution';
|
||||
|
||||
export interface ListDueApprovedActionDispatchesQuery {
|
||||
nowMs: number;
|
||||
limit: number;
|
||||
cursor?: ApprovedActionDispatchCursor;
|
||||
}
|
||||
|
||||
export interface ListDueApprovedActionDispatchesResult {
|
||||
dispatches: readonly ApprovedActionDispatchExecutionSnapshot[];
|
||||
truncated: boolean;
|
||||
nextCursor?: Readonly<ApprovedActionDispatchCursor>;
|
||||
}
|
||||
|
||||
export interface ClaimApprovedActionDispatchCommand {
|
||||
dispatchId: string;
|
||||
owner: string;
|
||||
leaseToken: string;
|
||||
nowMs: number;
|
||||
leaseDurationMs: number;
|
||||
}
|
||||
|
||||
export type ClaimApprovedActionDispatchResult =
|
||||
| {
|
||||
status: 'claimed';
|
||||
snapshot: Readonly<ApprovedActionDispatchExecutionSnapshot>;
|
||||
}
|
||||
| { status: 'not_found' }
|
||||
| {
|
||||
status:
|
||||
| 'not_due'
|
||||
| 'leased'
|
||||
| 'executing'
|
||||
| 'recovery_required'
|
||||
| 'succeeded'
|
||||
| 'failed'
|
||||
| 'blocked';
|
||||
snapshot: Readonly<ApprovedActionDispatchExecutionSnapshot>;
|
||||
};
|
||||
|
||||
export interface StartApprovedActionDispatchCommand {
|
||||
dispatchId: string;
|
||||
approvalRequestId: string;
|
||||
actionDigest: string;
|
||||
owner: string;
|
||||
leaseToken: string;
|
||||
expectedVersion: number;
|
||||
startedAtMs: number;
|
||||
}
|
||||
|
||||
export interface RenewApprovedActionDispatchLeaseCommand {
|
||||
dispatchId: string;
|
||||
owner: string;
|
||||
leaseToken: string;
|
||||
expectedVersion: number;
|
||||
nowMs: number;
|
||||
leaseDurationMs: number;
|
||||
}
|
||||
|
||||
export interface ReleaseApprovedActionDispatchBeforeStartCommand {
|
||||
dispatchId: string;
|
||||
owner: string;
|
||||
leaseToken: string;
|
||||
expectedVersion: number;
|
||||
resultMutationId: string;
|
||||
resultCode: string;
|
||||
atMs: number;
|
||||
retryAtMs?: number;
|
||||
}
|
||||
|
||||
export interface CompleteApprovedActionDispatchCommand {
|
||||
dispatchId: string;
|
||||
owner: string;
|
||||
leaseToken: string;
|
||||
expectedVersion: number;
|
||||
resultMutationId: string;
|
||||
outcome: 'succeeded' | 'failed' | 'indeterminate';
|
||||
resultCode: string;
|
||||
completedAtMs: number;
|
||||
}
|
||||
|
||||
export interface ApprovedActionDispatchRepository {
|
||||
findById(
|
||||
dispatchId: string,
|
||||
): Promise<Readonly<ApprovedActionDispatchExecutionSnapshot> | null>;
|
||||
|
||||
listDue(
|
||||
query: ListDueApprovedActionDispatchesQuery,
|
||||
): Promise<ListDueApprovedActionDispatchesResult>;
|
||||
|
||||
claim(
|
||||
command: ClaimApprovedActionDispatchCommand,
|
||||
): Promise<ClaimApprovedActionDispatchResult>;
|
||||
|
||||
start(
|
||||
command: StartApprovedActionDispatchCommand,
|
||||
): Promise<Readonly<ApprovedActionDispatchExecutionSnapshot>>;
|
||||
|
||||
renew(
|
||||
command: RenewApprovedActionDispatchLeaseCommand,
|
||||
): Promise<Readonly<ApprovedActionDispatchExecutionSnapshot>>;
|
||||
|
||||
releaseBeforeStart(
|
||||
command: ReleaseApprovedActionDispatchBeforeStartCommand,
|
||||
): Promise<Readonly<ApprovedActionDispatchExecutionSnapshot>>;
|
||||
|
||||
complete(
|
||||
command: CompleteApprovedActionDispatchCommand,
|
||||
): Promise<Readonly<ApprovedActionDispatchExecutionSnapshot>>;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { ApprovedActionDispatchExecutionSnapshot } from '../domain/approvedActionDispatchExecution';
|
||||
import type { ApprovedActionDispatchRecord } from '../domain/approvalRequest';
|
||||
|
||||
export type ApprovedActionInspectionResult =
|
||||
| { status: 'ready'; actionDigest: string }
|
||||
| { status: 'retry'; resultCode: string }
|
||||
| { status: 'blocked'; resultCode: string };
|
||||
|
||||
export interface ApprovedActionExecutionContext {
|
||||
dispatch: Readonly<ApprovedActionDispatchRecord>;
|
||||
execution: Readonly<ApprovedActionDispatchExecutionSnapshot['execution']>;
|
||||
idempotencyKey: string;
|
||||
fence: {
|
||||
owner: string;
|
||||
leaseToken: string;
|
||||
version: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ApprovedActionExecutionResult {
|
||||
outcome: 'succeeded' | 'failed' | 'indeterminate';
|
||||
resultCode: string;
|
||||
}
|
||||
|
||||
export interface ApprovedActionHandler {
|
||||
readonly actionType: string;
|
||||
|
||||
/** Must not perform the approved external side effect. */
|
||||
inspect(
|
||||
dispatch: Readonly<ApprovedActionDispatchRecord>,
|
||||
): Promise<ApprovedActionInspectionResult>;
|
||||
|
||||
execute(
|
||||
context: Readonly<ApprovedActionExecutionContext>,
|
||||
): Promise<ApprovedActionExecutionResult>;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type {
|
||||
ApprovedActionRecoveryFinding,
|
||||
ApprovedActionRecoverySnapshot,
|
||||
} from '../domain/approvedActionRecovery';
|
||||
|
||||
export type ApprovedActionRecoveryCapability = 'automatic' | 'manual_only';
|
||||
|
||||
export type ApprovedActionRecoveryEvidence =
|
||||
| {
|
||||
finding: 'verified_succeeded' | 'verified_failed';
|
||||
resultCode: string;
|
||||
evidenceDigest: string;
|
||||
}
|
||||
| {
|
||||
finding: Exclude<
|
||||
ApprovedActionRecoveryFinding,
|
||||
'verified_succeeded' | 'verified_failed'
|
||||
>;
|
||||
resultCode: string;
|
||||
evidenceDigest?: string;
|
||||
};
|
||||
|
||||
export interface ApprovedActionRecoveryEvidenceContext {
|
||||
snapshot: Readonly<ApprovedActionRecoverySnapshot>;
|
||||
idempotencyKey: string;
|
||||
observedAtMs: number;
|
||||
}
|
||||
|
||||
export interface ApprovedActionRecoveryEvidenceProvider {
|
||||
readonly actionType: string;
|
||||
readonly capability: ApprovedActionRecoveryCapability;
|
||||
|
||||
/** Must only observe evidence; it must not repeat the approved side effect. */
|
||||
inspect(
|
||||
context: Readonly<ApprovedActionRecoveryEvidenceContext>,
|
||||
): Promise<ApprovedActionRecoveryEvidence>;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import type {
|
||||
ApprovedActionRecoveryCursor,
|
||||
ApprovedActionRecoveryDecision,
|
||||
ApprovedActionRecoveryFinding,
|
||||
ApprovedActionRecoverySnapshot,
|
||||
} from '../domain/approvedActionRecovery';
|
||||
import type { PolicySubject } from '../domain/projectPolicy';
|
||||
import type { ApprovedActionRecoveryAuthorizationFact } from '../domain/approvedActionRecoveryAuthorization';
|
||||
|
||||
export interface ListDueApprovedActionRecoveriesQuery {
|
||||
nowMs: number;
|
||||
limit: number;
|
||||
cursor?: ApprovedActionRecoveryCursor;
|
||||
}
|
||||
|
||||
export interface ListDueApprovedActionRecoveriesResult {
|
||||
recoveries: readonly ApprovedActionRecoverySnapshot[];
|
||||
truncated: boolean;
|
||||
nextCursor?: Readonly<ApprovedActionRecoveryCursor>;
|
||||
}
|
||||
|
||||
export interface ClaimApprovedActionRecoveryCommand {
|
||||
dispatchId: string;
|
||||
owner: string;
|
||||
leaseToken: string;
|
||||
nowMs: number;
|
||||
leaseDurationMs: number;
|
||||
}
|
||||
|
||||
export type ClaimApprovedActionRecoveryResult =
|
||||
| {
|
||||
status: 'claimed';
|
||||
snapshot: Readonly<ApprovedActionRecoverySnapshot>;
|
||||
}
|
||||
| { status: 'not_found' }
|
||||
| {
|
||||
status:
|
||||
| 'not_due'
|
||||
| 'leased'
|
||||
| 'execution_active'
|
||||
| 'manual_required'
|
||||
| 'resolved';
|
||||
snapshot: Readonly<ApprovedActionRecoverySnapshot>;
|
||||
};
|
||||
|
||||
export interface RecordApprovedActionRecoveryFindingCommand {
|
||||
dispatchId: string;
|
||||
expectedExecutionVersion: number;
|
||||
expectedRecoveryVersion: number;
|
||||
owner: string;
|
||||
leaseToken: string;
|
||||
findingMutationId: string;
|
||||
finding: Exclude<
|
||||
ApprovedActionRecoveryFinding,
|
||||
'verified_succeeded' | 'verified_failed'
|
||||
>;
|
||||
resultCode: string;
|
||||
evidenceDigest?: string;
|
||||
observedAtMs: number;
|
||||
retryAtMs?: number;
|
||||
}
|
||||
|
||||
export interface ResolveApprovedActionRecoveryAutomaticallyCommand {
|
||||
dispatchId: string;
|
||||
expectedExecutionVersion: number;
|
||||
expectedRecoveryVersion: number;
|
||||
owner: string;
|
||||
leaseToken: string;
|
||||
mutationId: string;
|
||||
source: 'automatic_evidence';
|
||||
decision: Exclude<ApprovedActionRecoveryDecision, 'abandon_unknown'>;
|
||||
evidenceDigest: string;
|
||||
reasonCode: string;
|
||||
resolvedAtMs: number;
|
||||
}
|
||||
|
||||
export interface ResolveApprovedActionRecoveryManuallyCommand {
|
||||
dispatchId: string;
|
||||
expectedExecutionVersion: number;
|
||||
expectedRecoveryVersion: number;
|
||||
mutationId: string;
|
||||
source: 'human';
|
||||
decision: ApprovedActionRecoveryDecision;
|
||||
evidenceDigest?: string;
|
||||
reasonCode: string;
|
||||
resolvedBy: PolicySubject;
|
||||
resolvedAtMs: number;
|
||||
authorizationFact: ApprovedActionRecoveryAuthorizationFact;
|
||||
}
|
||||
|
||||
export type ResolveApprovedActionRecoveryCommand =
|
||||
| ResolveApprovedActionRecoveryAutomaticallyCommand
|
||||
| ResolveApprovedActionRecoveryManuallyCommand;
|
||||
|
||||
export type ResolveApprovedActionRecoveryResult =
|
||||
| {
|
||||
status: 'resolved';
|
||||
snapshot: Readonly<ApprovedActionRecoverySnapshot>;
|
||||
}
|
||||
| {
|
||||
status: 'already_terminal';
|
||||
snapshot: Readonly<ApprovedActionRecoverySnapshot>;
|
||||
}
|
||||
| { status: 'not_found' };
|
||||
|
||||
export interface ApprovedActionRecoveryRepository {
|
||||
findById(
|
||||
dispatchId: string,
|
||||
): Promise<Readonly<ApprovedActionRecoverySnapshot> | null>;
|
||||
|
||||
listDue(
|
||||
query: ListDueApprovedActionRecoveriesQuery,
|
||||
): Promise<ListDueApprovedActionRecoveriesResult>;
|
||||
|
||||
claim(
|
||||
command: ClaimApprovedActionRecoveryCommand,
|
||||
): Promise<ClaimApprovedActionRecoveryResult>;
|
||||
|
||||
recordFinding(
|
||||
command: RecordApprovedActionRecoveryFindingCommand,
|
||||
): Promise<Readonly<ApprovedActionRecoverySnapshot>>;
|
||||
|
||||
resolve(
|
||||
command: ResolveApprovedActionRecoveryCommand,
|
||||
): Promise<ResolveApprovedActionRecoveryResult>;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { ApprovedRunCreationPlan } from '../domain/approvedRunAction';
|
||||
|
||||
export interface ApprovedRunActionPlanResolver {
|
||||
/** Resolves an immutable, versioned plan without performing its side effect. */
|
||||
resolve(actionRef: string): Promise<Readonly<ApprovedRunCreationPlan> | null>;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { ApprovedRunCreationPlan } from '../domain/approvedRunAction';
|
||||
import type { ApprovedActionDispatchExecutionSnapshot } from '../domain/approvedActionDispatchExecution';
|
||||
import type { RunAttemptRecord, RunRecord } from '../domain/run';
|
||||
|
||||
export interface ApprovedRunReference {
|
||||
run: Readonly<RunRecord>;
|
||||
attempt: Readonly<RunAttemptRecord>;
|
||||
}
|
||||
|
||||
export interface CreateApprovedRunCommand {
|
||||
snapshot: Readonly<ApprovedActionDispatchExecutionSnapshot>;
|
||||
plan: Readonly<ApprovedRunCreationPlan>;
|
||||
}
|
||||
|
||||
export interface ApprovedRunActionRepository {
|
||||
/** Creates the Run aggregate and its bound success receipt atomically. */
|
||||
create(
|
||||
command: Readonly<CreateApprovedRunCommand>,
|
||||
): Promise<Readonly<ApprovedRunReference>>;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { ArtifactReadSubject } from '../domain/artifactRead';
|
||||
|
||||
export type ArtifactReadAuthorizationEffect =
|
||||
| 'allow'
|
||||
| 'deny'
|
||||
| 'require_approval';
|
||||
|
||||
export interface ArtifactReadAuthorizationRequest {
|
||||
action: 'artifact.read';
|
||||
subject: Readonly<ArtifactReadSubject>;
|
||||
projectId: string;
|
||||
runId: string;
|
||||
logArtifactId: string;
|
||||
}
|
||||
|
||||
export interface ArtifactReadAuthorizer {
|
||||
authorize(
|
||||
request: Readonly<ArtifactReadAuthorizationRequest>,
|
||||
): Promise<ArtifactReadAuthorizationEffect>;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import type {
|
||||
CancellationDispatchRecord,
|
||||
CancellationDispatchResult,
|
||||
} from '../domain/cancellationDispatch';
|
||||
import type { RunEventRecord } from '../domain/run';
|
||||
|
||||
export interface ClaimCancellationDispatchCommand {
|
||||
runId: string;
|
||||
attemptId: string;
|
||||
requestedAtMs: number;
|
||||
owner: string;
|
||||
leaseToken: string;
|
||||
nowMs: number;
|
||||
leaseDurationMs: number;
|
||||
}
|
||||
|
||||
export type ClaimCancellationDispatchResult =
|
||||
| { status: 'claimed'; dispatch: CancellationDispatchRecord }
|
||||
| { status: 'not_eligible' }
|
||||
| {
|
||||
status: 'not_due' | 'leased' | 'dispatched' | 'blocked';
|
||||
dispatch: CancellationDispatchRecord;
|
||||
};
|
||||
|
||||
export interface RecordCancellationDispatchResultCommand {
|
||||
runId: string;
|
||||
attemptId: string;
|
||||
owner: string;
|
||||
leaseToken: string;
|
||||
expectedVersion: number;
|
||||
result: CancellationDispatchResult;
|
||||
atMs: number;
|
||||
nextAttemptAtMs?: number;
|
||||
eventId: string;
|
||||
}
|
||||
|
||||
export interface RecordCancellationDispatchResult {
|
||||
dispatch: CancellationDispatchRecord;
|
||||
event: RunEventRecord;
|
||||
}
|
||||
|
||||
export interface CancellationDispatchRepository {
|
||||
findByRunId(runId: string): Promise<CancellationDispatchRecord | null>;
|
||||
claim(
|
||||
command: ClaimCancellationDispatchCommand,
|
||||
): Promise<ClaimCancellationDispatchResult>;
|
||||
recordResult(
|
||||
command: RecordCancellationDispatchResultCommand,
|
||||
): Promise<RecordCancellationDispatchResult>;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import type {
|
||||
CompletionReceiptJournalCursor,
|
||||
CompletionReceiptJournalPage,
|
||||
} from '../domain/completionReceiptJournal';
|
||||
|
||||
export const MAX_COMPLETION_RECEIPT_JOURNAL_BATCH_SIZE = 64;
|
||||
|
||||
export interface RegisterCompletionReceiptCommand {
|
||||
attemptId: string;
|
||||
runId: string;
|
||||
registeredAtMs: number;
|
||||
}
|
||||
|
||||
export interface QuarantineCompletionReceiptCommand {
|
||||
attemptId: string;
|
||||
quarantineRef: string;
|
||||
purgeAfterMs: number;
|
||||
updatedAtMs: number;
|
||||
}
|
||||
|
||||
export interface CompletionReceiptJournal {
|
||||
register(command: RegisterCompletionReceiptCommand): Promise<void>;
|
||||
markQuarantined(command: QuarantineCompletionReceiptCommand): Promise<void>;
|
||||
resolve(attemptId: string): Promise<boolean>;
|
||||
listCandidates(options: {
|
||||
observedAtMs: number;
|
||||
cursor?: CompletionReceiptJournalCursor;
|
||||
limit?: number;
|
||||
}): Promise<CompletionReceiptJournalPage>;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { RunAttemptStatus } from '../domain/run';
|
||||
|
||||
export type CompletionReceiptDirectoryEntryKind =
|
||||
| 'receipt'
|
||||
| 'temporary'
|
||||
| 'unknown'
|
||||
| 'unsafe';
|
||||
|
||||
export interface CompletionReceiptDirectoryEntry {
|
||||
shard: string;
|
||||
name: string;
|
||||
kind: CompletionReceiptDirectoryEntryKind;
|
||||
attemptId?: string;
|
||||
modifiedAtMs: number;
|
||||
sizeBytes: number;
|
||||
filesystemIdentity: string;
|
||||
}
|
||||
|
||||
export interface CompletionReceiptShardSnapshot {
|
||||
shard: string;
|
||||
entries: readonly CompletionReceiptDirectoryEntry[];
|
||||
overflow: boolean;
|
||||
}
|
||||
|
||||
export type CompletionReceiptOrphanQuarantineResult =
|
||||
| { status: 'quarantined'; reference: string }
|
||||
| { status: 'changed' };
|
||||
|
||||
export interface CompletionReceiptOrphanDirectory {
|
||||
inspectShard(
|
||||
shard: string,
|
||||
maxEntries: number,
|
||||
): Promise<CompletionReceiptShardSnapshot>;
|
||||
quarantine(
|
||||
entry: CompletionReceiptDirectoryEntry,
|
||||
): Promise<CompletionReceiptOrphanQuarantineResult>;
|
||||
}
|
||||
|
||||
export interface CompletionReceiptOwnership {
|
||||
attemptId: string;
|
||||
attemptStatus?: RunAttemptStatus;
|
||||
journalState?: 'pending' | 'quarantined';
|
||||
}
|
||||
|
||||
export interface CompletionReceiptOwnershipSource {
|
||||
lookup(
|
||||
attemptIds: readonly string[],
|
||||
): Promise<ReadonlyMap<string, CompletionReceiptOwnership>>;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { CompletionReceipt } from '../domain/completionReceipt';
|
||||
|
||||
export interface CompletionReceiptStore {
|
||||
publish(receipt: CompletionReceipt): Promise<void>;
|
||||
read(attemptId: string): Promise<CompletionReceipt | undefined>;
|
||||
remove(attemptId: string): Promise<boolean>;
|
||||
quarantineReference(attemptId: string): string;
|
||||
/** Moves an untrusted receipt out of the replay path for later inspection. */
|
||||
quarantine(attemptId: string): Promise<string | undefined>;
|
||||
purgeQuarantine(attemptId: string): Promise<boolean>;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type {
|
||||
ExecutionContext,
|
||||
ExecutionHandle,
|
||||
ExecutionInspection,
|
||||
ExecutionSpec,
|
||||
ExecutionStopReason,
|
||||
ExecutionStopResult,
|
||||
ExecutorCapabilities,
|
||||
ExecutorType,
|
||||
} from '../domain/execution';
|
||||
|
||||
export interface Executor {
|
||||
readonly type: ExecutorType;
|
||||
capabilities(): ExecutorCapabilities;
|
||||
start(
|
||||
spec: ExecutionSpec,
|
||||
context: ExecutionContext,
|
||||
): Promise<ExecutionHandle>;
|
||||
stop(
|
||||
handle: ExecutionHandle,
|
||||
reason: ExecutionStopReason,
|
||||
): Promise<ExecutionStopResult>;
|
||||
inspect(handle: ExecutionHandle): Promise<ExecutionInspection>;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { PolicySubject } from '../domain/projectPolicy';
|
||||
|
||||
export interface IdentityDirectoryRepository {
|
||||
resolveAuthenticationSubject(
|
||||
provider: string,
|
||||
providerSubject: string,
|
||||
): Promise<Readonly<PolicySubject> | null>;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export interface LegacyExecutionSelector {
|
||||
legacyCronId: number;
|
||||
pid?: number;
|
||||
logArtifactId?: string;
|
||||
}
|
||||
|
||||
export interface LegacyExecutionCancellationFact
|
||||
extends LegacyExecutionSelector {
|
||||
atMs: number;
|
||||
scope: 'all' | 'one';
|
||||
reason: 'user' | 'policy' | 'shutdown' | 'reconcile';
|
||||
}
|
||||
|
||||
export interface LegacyExecutionCallbackFact extends LegacyExecutionSelector {
|
||||
atMs: number;
|
||||
phase: 'running' | 'finished';
|
||||
exitCode?: number;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { ExecutionOrigin } from '../domain/run';
|
||||
|
||||
export interface LegacyExecutionAcceptedFact {
|
||||
origin: ExecutionOrigin;
|
||||
projectId: string;
|
||||
taskId: string;
|
||||
taskRevision: string;
|
||||
taskName?: string;
|
||||
legacyCronId?: number;
|
||||
triggerType: string;
|
||||
triggeredBy?: string;
|
||||
requestId?: string;
|
||||
scheduledForMs?: number;
|
||||
acceptedAtMs: number;
|
||||
}
|
||||
|
||||
export interface LegacyExecutionSpawnedFact {
|
||||
atMs: number;
|
||||
pid?: number;
|
||||
executorHandle?: string;
|
||||
logArtifactId?: string;
|
||||
}
|
||||
|
||||
export interface LegacyExecutionRunningFact {
|
||||
atMs: number;
|
||||
}
|
||||
|
||||
export interface LegacyExecutionStartFailedFact {
|
||||
atMs: number;
|
||||
errorCode: string;
|
||||
}
|
||||
|
||||
export interface LegacyExecutionExitedFact {
|
||||
atMs: number;
|
||||
exitCode: number | null;
|
||||
signal?: NodeJS.Signals;
|
||||
}
|
||||
|
||||
export interface LegacyExecutionCancelledFact {
|
||||
atMs: number;
|
||||
reason: 'user' | 'policy' | 'shutdown' | 'reconcile';
|
||||
}
|
||||
|
||||
export interface LegacyExecutionObservation {
|
||||
spawned(fact: LegacyExecutionSpawnedFact): void;
|
||||
running(fact: LegacyExecutionRunningFact): void;
|
||||
startFailed(fact: LegacyExecutionStartFailedFact): void;
|
||||
exited(fact: LegacyExecutionExitedFact): void;
|
||||
cancelled(fact: LegacyExecutionCancelledFact): void;
|
||||
}
|
||||
|
||||
export interface LegacyExecutionObserver {
|
||||
begin(fact: LegacyExecutionAcceptedFact): LegacyExecutionObservation;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
export type LegacyPanelPlatform = 'desktop' | 'mobile';
|
||||
|
||||
export interface LegacyPanelSessionSource {
|
||||
isActive(token: string, platform: LegacyPanelPlatform): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface LegacyPanelTokenSnapshot {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface LegacyPanelAuthSnapshot {
|
||||
token?: string;
|
||||
tokens?: Readonly<
|
||||
Record<
|
||||
string,
|
||||
string | readonly LegacyPanelTokenSnapshot[] | null | undefined
|
||||
>
|
||||
>;
|
||||
}
|
||||
|
||||
export type LegacyPanelAuthSnapshotReader =
|
||||
() => Promise<Readonly<LegacyPanelAuthSnapshot> | null>;
|
||||
@@ -0,0 +1,35 @@
|
||||
import type {
|
||||
ExecutionOrigin,
|
||||
RunAttemptStatus,
|
||||
RunStatus,
|
||||
} from '../domain/run';
|
||||
|
||||
export const MAX_LEGACY_SHADOW_LOOKUP_CANDIDATES = 64;
|
||||
|
||||
export interface ActiveLegacyShadowRun {
|
||||
runId: string;
|
||||
attemptId: string;
|
||||
origin: ExecutionOrigin;
|
||||
runStatus: RunStatus;
|
||||
attemptStatus: RunAttemptStatus;
|
||||
pid?: number;
|
||||
logArtifactId?: string;
|
||||
createdAtMs: number;
|
||||
}
|
||||
|
||||
export interface ActiveLegacyShadowRunQuery {
|
||||
legacyCronId: number;
|
||||
origins: readonly ExecutionOrigin[];
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface ActiveLegacyShadowRunResult {
|
||||
candidates: readonly ActiveLegacyShadowRun[];
|
||||
truncated: boolean;
|
||||
}
|
||||
|
||||
export interface LegacyShadowRunLocator {
|
||||
listActiveByLegacyCron(
|
||||
query: ActiveLegacyShadowRunQuery,
|
||||
): Promise<ActiveLegacyShadowRunResult>;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { LocalArtifactReadRange } from '../domain/artifactRead';
|
||||
|
||||
export interface AvailableLocalArtifactByteRange {
|
||||
status: 'available';
|
||||
content: Buffer;
|
||||
start: number;
|
||||
endExclusive: number;
|
||||
totalBytes: number;
|
||||
nextOffset?: number;
|
||||
}
|
||||
|
||||
export type LocalArtifactByteRangeReadResult =
|
||||
| AvailableLocalArtifactByteRange
|
||||
| { status: 'missing' };
|
||||
|
||||
export interface LocalArtifactByteRangeReader {
|
||||
read(
|
||||
logArtifactId: string,
|
||||
range: Readonly<LocalArtifactReadRange>,
|
||||
): Promise<LocalArtifactByteRangeReadResult>;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface LocalArtifactCapacitySnapshot {
|
||||
availableBytes: bigint;
|
||||
totalBytes: bigint;
|
||||
}
|
||||
|
||||
export interface LocalArtifactCapacityProbe {
|
||||
inspect(root: string): Promise<LocalArtifactCapacitySnapshot>;
|
||||
}
|
||||
|
||||
export interface LocalArtifactCapacitySource {
|
||||
inspect(): Promise<LocalArtifactCapacitySnapshot>;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface LocalArtifactFileRetirementResult {
|
||||
disposition: 'deleted' | 'already_absent';
|
||||
bytesReclaimed: number;
|
||||
}
|
||||
|
||||
export interface LocalArtifactFileRetirementStore {
|
||||
retire(logArtifactId: string): Promise<LocalArtifactFileRetirementResult>;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { LocalArtifactReadMetadata } from '../domain/artifactRead';
|
||||
|
||||
export interface LocalArtifactReadMetadataRepository {
|
||||
find(input: {
|
||||
projectId: string;
|
||||
runId: string;
|
||||
logArtifactId: string;
|
||||
}): Promise<Readonly<LocalArtifactReadMetadata> | null>;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { LocalArtifactRetentionCursor } from '../domain/localArtifactRetention';
|
||||
import type { LocalArtifactRetentionCheckpoint } from '../domain/localArtifactRetentionCheckpoint';
|
||||
|
||||
export interface LocalArtifactRetentionCheckpointStore {
|
||||
load(): Promise<Readonly<LocalArtifactRetentionCheckpoint>>;
|
||||
compareAndSet(value: {
|
||||
expectedVersion: number;
|
||||
cursor?: LocalArtifactRetentionCursor;
|
||||
updatedAtMs: number;
|
||||
}): Promise<boolean>;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type {
|
||||
LocalArtifactRetentionCandidate,
|
||||
LocalArtifactRetentionCursor,
|
||||
LocalArtifactRetentionRecord,
|
||||
} from '../domain/localArtifactRetention';
|
||||
|
||||
export const MAX_LOCAL_ARTIFACT_RETENTION_PAGE_SIZE = 64;
|
||||
|
||||
export interface LocalArtifactRetentionPage {
|
||||
candidates: readonly LocalArtifactRetentionCandidate[];
|
||||
truncated: boolean;
|
||||
nextCursor?: LocalArtifactRetentionCursor;
|
||||
}
|
||||
|
||||
export interface LocalArtifactRetentionRepository {
|
||||
list(options: {
|
||||
cutoffMs: number;
|
||||
cursor?: LocalArtifactRetentionCursor;
|
||||
limit: number;
|
||||
}): Promise<LocalArtifactRetentionPage>;
|
||||
record(
|
||||
record: LocalArtifactRetentionRecord,
|
||||
): Promise<'inserted' | 'existing'>;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { LocalArtifactTruncationFact } from '../domain/localArtifactTruncation';
|
||||
|
||||
export interface LocalArtifactTruncationFactStore {
|
||||
read(
|
||||
logArtifactId: string,
|
||||
): Promise<Readonly<LocalArtifactTruncationFact> | null>;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { ExecutionOutputSink } from '../domain/execution';
|
||||
import type { RunDispatchCandidate } from '../domain/runDispatchCandidate';
|
||||
|
||||
export interface PreparedLocalExecutionArtifact {
|
||||
logArtifactId: string;
|
||||
output: ExecutionOutputSink;
|
||||
dispose(): void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface LocalExecutionArtifactAllocator {
|
||||
prepare(
|
||||
candidate: Readonly<RunDispatchCandidate>,
|
||||
): Promise<PreparedLocalExecutionArtifact>;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { ExecutionContext } from '../domain/execution';
|
||||
import type { RunDispatchCandidate } from '../domain/runDispatchCandidate';
|
||||
|
||||
export interface LocalExecutionContextRequest {
|
||||
candidate: Readonly<RunDispatchCandidate>;
|
||||
contextRef: string;
|
||||
}
|
||||
|
||||
export interface MaterializedLocalExecutionContext {
|
||||
context: ExecutionContext;
|
||||
/** Opaque bounded reference persisted on the Attempt before spawn. */
|
||||
logArtifactId?: string;
|
||||
/** Optional non-blocking cleanup; plaintext capability values must not leak. */
|
||||
dispose?: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
export interface LocalExecutionContextMaterializer {
|
||||
prepare(
|
||||
request: Readonly<LocalExecutionContextRequest>,
|
||||
): Promise<MaterializedLocalExecutionContext | null>;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { LocalExecutionContextRecipe } from '../domain/localExecutionContextRecipe';
|
||||
import type { LocalExecutionContextRecipeSource } from './localExecutionContextRecipeSource';
|
||||
|
||||
export type InsertLocalExecutionContextRecipeResult = 'inserted' | 'idempotent';
|
||||
|
||||
export interface LocalExecutionContextRecipeRepository
|
||||
extends LocalExecutionContextRecipeSource {
|
||||
insert(
|
||||
recipe: LocalExecutionContextRecipe,
|
||||
createdAtMs: number,
|
||||
): Promise<InsertLocalExecutionContextRecipeResult>;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { LocalExecutionContextRecipe } from '../domain/localExecutionContextRecipe';
|
||||
|
||||
export interface LocalExecutionContextRecipeSource {
|
||||
/** Resolves one exact opaque reference and never falls back to latest. */
|
||||
resolve(contextRef: string): Promise<LocalExecutionContextRecipe | null>;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { ExecutionContext, ExecutionSpec } from '../domain/execution';
|
||||
import type { RunDispatchCandidate } from '../domain/runDispatchCandidate';
|
||||
|
||||
export interface LocalRunDispatchPlan {
|
||||
executionSpec: ExecutionSpec;
|
||||
context: ExecutionContext;
|
||||
logArtifactId?: string;
|
||||
/** Optional non-blocking cleanup for attempt-scoped local resources. */
|
||||
dispose?: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trusted local materializer. Implementations must resolve the pinned Task
|
||||
* revision and create fresh Attempt-scoped output and Secret capabilities.
|
||||
*/
|
||||
export interface LocalRunDispatchPlanSource {
|
||||
prepare(
|
||||
candidate: Readonly<RunDispatchCandidate>,
|
||||
): Promise<LocalRunDispatchPlan | null>;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type {
|
||||
LocalSecretEnvelope,
|
||||
LocalSecretReference,
|
||||
} from '../domain/localSecret';
|
||||
|
||||
export interface AppendLocalSecretEnvelopeCommand {
|
||||
envelope: LocalSecretEnvelope;
|
||||
expectedCurrentVersion: number;
|
||||
}
|
||||
|
||||
export type AppendLocalSecretEnvelopeResult =
|
||||
| { status: 'inserted'; envelope: LocalSecretEnvelope }
|
||||
| { status: 'existing'; envelope: LocalSecretEnvelope };
|
||||
|
||||
export interface LocalSecretEnvelopeRepository {
|
||||
append(
|
||||
command: AppendLocalSecretEnvelopeCommand,
|
||||
): Promise<AppendLocalSecretEnvelopeResult>;
|
||||
findByMutation(
|
||||
projectId: string,
|
||||
name: string,
|
||||
mutationId: string,
|
||||
): Promise<LocalSecretEnvelope | null>;
|
||||
resolveMany(
|
||||
references: readonly LocalSecretReference[],
|
||||
): Promise<readonly (LocalSecretEnvelope | null)[]>;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { RunDispatchCandidate } from '../domain/runDispatchCandidate';
|
||||
|
||||
export interface LocalSecretEnvironmentRequest {
|
||||
candidate: Readonly<RunDispatchCandidate>;
|
||||
secretRefs: readonly string[];
|
||||
}
|
||||
|
||||
/** Returns plaintext only in memory, positionally aligned to secretRefs. */
|
||||
export interface LocalSecretEnvironmentProvider {
|
||||
resolve(
|
||||
request: Readonly<LocalSecretEnvironmentRequest>,
|
||||
): Promise<readonly string[] | null>;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export interface LocalSecretKeyMaterial {
|
||||
keyId: string;
|
||||
/** Exactly 32 bytes; the consumer owns and must wipe this copy. */
|
||||
key: Uint8Array;
|
||||
}
|
||||
|
||||
export interface LocalSecretKeyProvider {
|
||||
active(): Promise<LocalSecretKeyMaterial>;
|
||||
resolve(keyId: string): Promise<LocalSecretKeyMaterial | null>;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { ExecutionStopReason, ExecutorType } from '../domain/execution';
|
||||
|
||||
export type PersistedExecutionStopStatus =
|
||||
| 'termination_requested'
|
||||
| 'already_exited'
|
||||
| 'identity_mismatch'
|
||||
| 'pid_mismatch'
|
||||
| 'unsupported'
|
||||
| 'invalid';
|
||||
|
||||
export interface PersistedExecutionStopResult {
|
||||
status: PersistedExecutionStopStatus;
|
||||
termSignalSent: boolean;
|
||||
killSignalSent: boolean;
|
||||
}
|
||||
|
||||
export interface PersistedExecutionController {
|
||||
readonly executorType: ExecutorType;
|
||||
stop(input: {
|
||||
durableHandle: string;
|
||||
expectedPid?: number;
|
||||
reason: ExecutionStopReason;
|
||||
}): Promise<PersistedExecutionStopResult>;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { ExecutorType } from '../domain/execution';
|
||||
|
||||
export type PersistedExecutionInspectionStatus =
|
||||
| 'running'
|
||||
| 'exited'
|
||||
| 'identity_mismatch'
|
||||
| 'unsupported'
|
||||
| 'invalid';
|
||||
|
||||
export interface PersistedExecutionInspection {
|
||||
status: PersistedExecutionInspectionStatus;
|
||||
identityPid?: number;
|
||||
}
|
||||
|
||||
export interface PersistedExecutionInspector {
|
||||
readonly executorType: ExecutorType;
|
||||
inspect(durableHandle: string): Promise<PersistedExecutionInspection>;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { ExecutionStopKind, ExecutorType } from '../domain/execution';
|
||||
|
||||
export const MAX_PRIMARY_CANCELLATION_BATCH_SIZE = 64;
|
||||
|
||||
export interface PrimaryCancellationCursor {
|
||||
requestedAtMs: number;
|
||||
runId: string;
|
||||
}
|
||||
|
||||
export interface PrimaryCancellationAttemptReference {
|
||||
attemptId: string;
|
||||
executorType: ExecutorType;
|
||||
executorHandle?: string;
|
||||
pid?: number;
|
||||
}
|
||||
|
||||
export interface PrimaryCancellationCandidate {
|
||||
runId: string;
|
||||
requestedAtMs: number;
|
||||
reason: ExecutionStopKind;
|
||||
attempts: readonly PrimaryCancellationAttemptReference[];
|
||||
}
|
||||
|
||||
export interface PrimaryCancellationPage {
|
||||
candidates: readonly PrimaryCancellationCandidate[];
|
||||
truncated: boolean;
|
||||
unsafeAttemptOverflow: boolean;
|
||||
nextCursor?: PrimaryCancellationCursor;
|
||||
}
|
||||
|
||||
export interface PrimaryCancellationSource {
|
||||
listCandidates(options?: {
|
||||
cursor?: PrimaryCancellationCursor;
|
||||
limit?: number;
|
||||
}): Promise<PrimaryCancellationPage>;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface PrimaryRunIdempotencyLookup {
|
||||
findRunId(projectId: string, idempotencyKey: string): Promise<string | null>;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { ExecutorType } from '../domain/execution';
|
||||
|
||||
export const MAX_PRIMARY_RECOVERY_BATCH_SIZE = 64;
|
||||
|
||||
export interface PrimaryRunRecoveryCursor {
|
||||
createdAtMs: number;
|
||||
runId: string;
|
||||
}
|
||||
|
||||
export interface PrimaryRunRecoveryAttemptReference {
|
||||
attemptId: string;
|
||||
executorType: ExecutorType;
|
||||
}
|
||||
|
||||
export interface PrimaryRunRecoveryCandidate {
|
||||
runId: string;
|
||||
attempts: readonly PrimaryRunRecoveryAttemptReference[];
|
||||
}
|
||||
|
||||
export interface PrimaryRunRecoveryPage {
|
||||
candidates: readonly PrimaryRunRecoveryCandidate[];
|
||||
truncated: boolean;
|
||||
unsafeAttemptOverflow: boolean;
|
||||
nextCursor?: PrimaryRunRecoveryCursor;
|
||||
}
|
||||
|
||||
export interface PrimaryRunRecoverySource {
|
||||
listCandidates(options?: {
|
||||
cursor?: PrimaryRunRecoveryCursor;
|
||||
limit?: number;
|
||||
}): Promise<PrimaryRunRecoveryPage>;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
export const MAX_PRIMARY_TIMEOUT_BATCH_SIZE = 64;
|
||||
|
||||
export interface PrimaryTimeoutCursor {
|
||||
deadlineAtMs: number;
|
||||
attemptId: string;
|
||||
}
|
||||
|
||||
export interface PrimaryTimeoutCandidate {
|
||||
runId: string;
|
||||
attemptId: string;
|
||||
deadlineAtMs: number;
|
||||
}
|
||||
|
||||
export interface PrimaryTimeoutPage {
|
||||
candidates: readonly PrimaryTimeoutCandidate[];
|
||||
truncated: boolean;
|
||||
nextCursor?: PrimaryTimeoutCursor;
|
||||
}
|
||||
|
||||
export interface PrimaryTimeoutSource {
|
||||
listOverdue(options: {
|
||||
nowMs: number;
|
||||
cursor?: PrimaryTimeoutCursor;
|
||||
limit?: number;
|
||||
}): Promise<PrimaryTimeoutPage>;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import type {
|
||||
PolicySubject,
|
||||
ProjectRoleBindingRecord,
|
||||
} from '../domain/projectPolicy';
|
||||
import type { ProjectOwnerBootstrapChallengeRecord } from '../domain/projectOwnerBootstrap';
|
||||
|
||||
export interface IssueProjectOwnerBootstrapChallengeCommand {
|
||||
projectId: string;
|
||||
challengeId: string;
|
||||
tokenDigest: string;
|
||||
issuedAtMs: number;
|
||||
expiresAtMs: number;
|
||||
}
|
||||
|
||||
export interface ClaimProjectOwnerBootstrapChallengeCommand {
|
||||
projectId: string;
|
||||
challengeId: string;
|
||||
tokenDigest: string;
|
||||
subject: PolicySubject;
|
||||
claimedAtMs: number;
|
||||
}
|
||||
|
||||
export interface ClaimProjectOwnerBootstrapChallengeResult {
|
||||
status: 'claimed' | 'existing';
|
||||
binding: Readonly<ProjectRoleBindingRecord>;
|
||||
}
|
||||
|
||||
export interface ProjectOwnerBootstrapRepository {
|
||||
issue(
|
||||
command: IssueProjectOwnerBootstrapChallengeCommand,
|
||||
): Promise<Readonly<ProjectOwnerBootstrapChallengeRecord>>;
|
||||
|
||||
claim(
|
||||
command: ClaimProjectOwnerBootstrapChallengeCommand,
|
||||
): Promise<ClaimProjectOwnerBootstrapChallengeResult>;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import type {
|
||||
PolicySubject,
|
||||
ProjectPolicySnapshot,
|
||||
ProjectRoleBindingRecord,
|
||||
} from '../domain/projectPolicy';
|
||||
|
||||
export interface AppendProjectRoleBindingCommand {
|
||||
expectedCurrentVersion: number;
|
||||
binding: ProjectRoleBindingRecord;
|
||||
}
|
||||
|
||||
export interface AppendProjectRoleBindingResult {
|
||||
status: 'inserted' | 'existing';
|
||||
binding: Readonly<ProjectRoleBindingRecord>;
|
||||
}
|
||||
|
||||
export interface ProjectPolicyRepository {
|
||||
resolve(
|
||||
projectId: string,
|
||||
subject: Readonly<PolicySubject>,
|
||||
): Promise<Readonly<ProjectPolicySnapshot> | null>;
|
||||
|
||||
append(
|
||||
command: AppendProjectRoleBindingCommand,
|
||||
): Promise<AppendProjectRoleBindingResult>;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type {
|
||||
RunDispatchCandidate,
|
||||
RunDispatchCandidateCursor,
|
||||
} from '../domain/runDispatchCandidate';
|
||||
|
||||
export interface ListRunDispatchCandidatesOptions {
|
||||
observedAtMs: number;
|
||||
after?: RunDispatchCandidateCursor;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface RunDispatchCandidateSource {
|
||||
listCandidates(
|
||||
options: ListRunDispatchCandidatesOptions,
|
||||
): Promise<RunDispatchCandidate[]>;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export const MAX_RUN_DISPATCH_LEASE_EXPIRY_PAGE_SIZE = 64;
|
||||
|
||||
export interface RunDispatchLeaseExpiryCursor {
|
||||
expiresAtMs: number;
|
||||
attemptId: string;
|
||||
}
|
||||
|
||||
export interface ExpiredRunDispatchLeaseCandidate
|
||||
extends RunDispatchLeaseExpiryCursor {
|
||||
runId: string;
|
||||
}
|
||||
|
||||
export interface ListExpiredRunDispatchLeasesOptions {
|
||||
observedAtMs: number;
|
||||
after?: RunDispatchLeaseExpiryCursor;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface RunDispatchLeaseExpirySource {
|
||||
listExpired(
|
||||
options: ListExpiredRunDispatchLeasesOptions,
|
||||
): Promise<readonly ExpiredRunDispatchLeaseCandidate[]>;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import type { RunDispatchLeaseRecord } from '../domain/runDispatchLease';
|
||||
import type { RunEventRecord } from '../domain/run';
|
||||
import type { RunRepositoryTransaction } from './runRepository';
|
||||
|
||||
export interface ClaimRunDispatchLeaseCommand {
|
||||
runId: string;
|
||||
attemptId: string;
|
||||
workerId: string;
|
||||
workerSessionId: string;
|
||||
workerGeneration: number;
|
||||
leaseToken: string;
|
||||
nowMs: number;
|
||||
leaseDurationMs: number;
|
||||
eventId: string;
|
||||
}
|
||||
|
||||
export type ClaimRunDispatchLeaseResult =
|
||||
| {
|
||||
status: 'claimed';
|
||||
lease: RunDispatchLeaseRecord;
|
||||
event: RunEventRecord;
|
||||
}
|
||||
| { status: 'idempotent' | 'leased'; lease: RunDispatchLeaseRecord }
|
||||
| {
|
||||
status: 'not_eligible' | 'worker_unavailable' | 'capacity_exhausted';
|
||||
};
|
||||
|
||||
export interface RenewRunDispatchLeaseCommand {
|
||||
attemptId: string;
|
||||
workerId: string;
|
||||
workerSessionId: string;
|
||||
workerGeneration: number;
|
||||
leaseGeneration: number;
|
||||
leaseToken: string;
|
||||
expectedVersion: number;
|
||||
nowMs: number;
|
||||
leaseDurationMs: number;
|
||||
}
|
||||
|
||||
export interface ReleaseRunDispatchLeaseCommand {
|
||||
runId: string;
|
||||
attemptId: string;
|
||||
workerId: string;
|
||||
workerSessionId: string;
|
||||
workerGeneration: number;
|
||||
leaseGeneration: number;
|
||||
leaseToken: string;
|
||||
expectedVersion: number;
|
||||
reason: 'declined' | 'shutdown' | 'start_failed' | 'capacity_changed';
|
||||
nowMs: number;
|
||||
eventId: string;
|
||||
}
|
||||
|
||||
export interface ReleaseRunDispatchLeaseResult {
|
||||
lease: RunDispatchLeaseRecord;
|
||||
event?: RunEventRecord;
|
||||
}
|
||||
|
||||
export interface CompleteWithRunDispatchLeaseCommand {
|
||||
runId: string;
|
||||
attemptId: string;
|
||||
workerId: string;
|
||||
workerSessionId: string;
|
||||
workerGeneration: number;
|
||||
leaseGeneration: number;
|
||||
leaseToken: string;
|
||||
expectedVersion: number;
|
||||
completedAtMs: number;
|
||||
}
|
||||
|
||||
export interface ExpireRunDispatchLeaseCommand {
|
||||
runId: string;
|
||||
attemptId: string;
|
||||
observedAtMs: number;
|
||||
}
|
||||
|
||||
export interface UseRunDispatchLeaseCommand {
|
||||
runId: string;
|
||||
attemptId: string;
|
||||
workerId: string;
|
||||
workerSessionId: string;
|
||||
workerGeneration: number;
|
||||
leaseGeneration: number;
|
||||
leaseToken: string;
|
||||
expectedVersion: number;
|
||||
observedAtMs: number;
|
||||
}
|
||||
|
||||
export interface UseRunDispatchLeaseResult<T> {
|
||||
value: T;
|
||||
lease: RunDispatchLeaseRecord;
|
||||
}
|
||||
|
||||
export interface CompleteWithRunDispatchLeaseResult<T> {
|
||||
value: T;
|
||||
lease: RunDispatchLeaseRecord;
|
||||
}
|
||||
|
||||
export type ExpireRunDispatchLeaseResult<T> =
|
||||
| { status: 'expired'; value: T; lease: RunDispatchLeaseRecord }
|
||||
| { status: 'already_expired'; lease: RunDispatchLeaseRecord }
|
||||
| {
|
||||
status: 'not_found' | 'not_due' | 'not_eligible';
|
||||
lease?: RunDispatchLeaseRecord;
|
||||
};
|
||||
|
||||
export interface RunDispatchLeaseRepository {
|
||||
findByAttemptId(attemptId: string): Promise<RunDispatchLeaseRecord | null>;
|
||||
claim(
|
||||
command: ClaimRunDispatchLeaseCommand,
|
||||
): Promise<ClaimRunDispatchLeaseResult>;
|
||||
renew(command: RenewRunDispatchLeaseCommand): Promise<RunDispatchLeaseRecord>;
|
||||
release(
|
||||
command: ReleaseRunDispatchLeaseCommand,
|
||||
): Promise<ReleaseRunDispatchLeaseResult>;
|
||||
withLease<T>(
|
||||
command: UseRunDispatchLeaseCommand,
|
||||
work: (
|
||||
transaction: RunRepositoryTransaction,
|
||||
lease: RunDispatchLeaseRecord,
|
||||
) => Promise<T>,
|
||||
): Promise<UseRunDispatchLeaseResult<T>>;
|
||||
completeWithLease<T>(
|
||||
command: CompleteWithRunDispatchLeaseCommand,
|
||||
work: (
|
||||
transaction: RunRepositoryTransaction,
|
||||
lease: RunDispatchLeaseRecord,
|
||||
) => Promise<T>,
|
||||
): Promise<CompleteWithRunDispatchLeaseResult<T>>;
|
||||
expireWithLease<T>(
|
||||
command: ExpireRunDispatchLeaseCommand,
|
||||
work: (
|
||||
transaction: RunRepositoryTransaction,
|
||||
lease: RunDispatchLeaseRecord,
|
||||
) => Promise<T>,
|
||||
): Promise<ExpireRunDispatchLeaseResult<T>>;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { RunDispatchCandidate } from '../domain/runDispatchCandidate';
|
||||
import type { RunDispatchPlan } from '../domain/runDispatchOffer';
|
||||
|
||||
/**
|
||||
* Prepares one bounded plan in trusted control-plane memory. The Dispatcher must
|
||||
* not expose it as an execution offer until the corresponding lease is claimed.
|
||||
*/
|
||||
export interface RunDispatchPlanSource {
|
||||
prepare(candidate: RunDispatchCandidate): Promise<RunDispatchPlan | null>;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type {
|
||||
RecoverableRunDispatch,
|
||||
RunDispatchRecoveryCursor,
|
||||
} from '../domain/runDispatchRecovery';
|
||||
|
||||
export interface ListRecoverableRunDispatchesOptions {
|
||||
observedAtMs: number;
|
||||
after?: RunDispatchRecoveryCursor;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface RunDispatchRecoverySource {
|
||||
listRecoverable(
|
||||
options: ListRecoverableRunDispatchesOptions,
|
||||
): Promise<RecoverableRunDispatch[]>;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export const MAX_RUN_LOST_RETRY_PAGE_SIZE = 64;
|
||||
|
||||
export interface RunLostRetryCandidate {
|
||||
runId: string;
|
||||
phase: 'lost' | 'retry_wait';
|
||||
availableAtMs: number;
|
||||
}
|
||||
|
||||
export interface ListRunLostRetryCandidatesOptions {
|
||||
observedAtMs: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface RunLostRetrySource {
|
||||
listCandidates(
|
||||
options: ListRunLostRetryCandidatesOptions,
|
||||
): Promise<readonly RunLostRetryCandidate[]>;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import type {
|
||||
RunAttemptRecord,
|
||||
RunAttemptStatus,
|
||||
RunEventRecord,
|
||||
RunRecord,
|
||||
} from '../domain/run';
|
||||
import type { RunRetryPolicyRecord } from '../domain/runRetryPolicy';
|
||||
|
||||
export const MAX_RUN_EVENT_PAYLOAD_BYTES = 16 * 1024;
|
||||
export const MAX_RUN_EVENT_PAGE_SIZE = 500;
|
||||
export const MAX_CANCELLATION_RECOVERY_PAGE_SIZE = 500;
|
||||
|
||||
export interface RunRepositoryReader {
|
||||
findRunById(runId: string): Promise<RunRecord | null>;
|
||||
findAttemptById(attemptId: string): Promise<RunAttemptRecord | null>;
|
||||
findLatestAttemptByRunId(runId: string): Promise<RunAttemptRecord | null>;
|
||||
findRetryPolicyByRunId(runId: string): Promise<RunRetryPolicyRecord | null>;
|
||||
listEvents(
|
||||
runId: string,
|
||||
options?: { afterSequence?: number; limit?: number },
|
||||
): Promise<RunEventRecord[]>;
|
||||
listCancellationRequested(options?: {
|
||||
beforeMs?: number;
|
||||
limit?: number;
|
||||
}): Promise<RunRecord[]>;
|
||||
}
|
||||
|
||||
export interface RunRepositoryTransaction extends RunRepositoryReader {
|
||||
insertRun(run: RunRecord): Promise<void>;
|
||||
insertAttempt(attempt: RunAttemptRecord): Promise<void>;
|
||||
insertRetryPolicy(policy: RunRetryPolicyRecord): Promise<void>;
|
||||
/**
|
||||
* Replaces a Run only when its persisted version still equals
|
||||
* `expectedVersion`. The supplied Run must carry `expectedVersion + 1`.
|
||||
*/
|
||||
compareAndSetRun(run: RunRecord, expectedVersion: number): Promise<boolean>;
|
||||
/**
|
||||
* Replaces an Attempt only when both state and callback sequence still match.
|
||||
* The Run aggregate version remains the primary serialization boundary.
|
||||
*/
|
||||
compareAndSetAttempt(
|
||||
attempt: RunAttemptRecord,
|
||||
expected: {
|
||||
status: RunAttemptStatus;
|
||||
callbackSequence: number;
|
||||
},
|
||||
): Promise<boolean>;
|
||||
compareAndSetRetryPolicy(
|
||||
policy: RunRetryPolicyRecord,
|
||||
expectedVersion: number,
|
||||
): Promise<boolean>;
|
||||
appendEvent(event: RunEventRecord): Promise<void>;
|
||||
}
|
||||
|
||||
export interface RunRepository extends RunRepositoryReader {
|
||||
transaction<T>(
|
||||
work: (transaction: RunRepositoryTransaction) => Promise<T>,
|
||||
): Promise<T>;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { ExecutionOrigin } from '../domain/run';
|
||||
import type { RunRetryPolicyDefinition } from '../domain/runRetryPolicy';
|
||||
|
||||
export interface RunRetryPolicyAdmissionRequest {
|
||||
projectId: string;
|
||||
taskId: string;
|
||||
taskRevision: string;
|
||||
triggerType: string;
|
||||
executionOrigin: ExecutionOrigin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trusted server-side admission. Implementations must resolve policy from the
|
||||
* pinned Task revision or another administrator-controlled immutable source;
|
||||
* an individual Run request is never allowed to self-assert retry safety.
|
||||
* `deduplicated` is valid only when that revision binds an enforced business
|
||||
* deduplication contract, not merely a descriptive user flag.
|
||||
*/
|
||||
export interface RunRetryPolicyAdmission {
|
||||
resolve(
|
||||
request: Readonly<RunRetryPolicyAdmissionRequest>,
|
||||
): Promise<RunRetryPolicyDefinition | undefined>;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { RuntimeRolloutPolicy } from '../domain/runtimeRollout';
|
||||
import type { RuntimeRolloutManifest } from '../domain/runtimeRolloutManifest';
|
||||
|
||||
export type RuntimeRolloutLoadStatus =
|
||||
| 'missing'
|
||||
| 'disabled'
|
||||
| 'accepted'
|
||||
| 'rejected';
|
||||
|
||||
export interface RuntimeRolloutLoadAudit {
|
||||
event: 'runtime.rollout_config_evaluated';
|
||||
evaluatedAtMs: number;
|
||||
sourcePath: string;
|
||||
status: RuntimeRolloutLoadStatus;
|
||||
sourceSha256?: string;
|
||||
revision?: string;
|
||||
reasonCode?:
|
||||
| 'FILE_MISSING'
|
||||
| 'FILE_READ_FAILED'
|
||||
| 'FILE_TOO_LARGE'
|
||||
| 'INVALID_JSON'
|
||||
| 'INVALID_MANIFEST';
|
||||
}
|
||||
|
||||
export interface RuntimeRolloutLoadResult {
|
||||
status: RuntimeRolloutLoadStatus;
|
||||
policy: RuntimeRolloutPolicy;
|
||||
audit: RuntimeRolloutLoadAudit;
|
||||
manifest?: RuntimeRolloutManifest;
|
||||
}
|
||||
|
||||
export interface RuntimeRolloutLoader {
|
||||
load(): Promise<RuntimeRolloutLoadResult>;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { PinnedTaskExecutionRevision } from '../domain/taskExecutionRevision';
|
||||
import type { TaskExecutionRevisionSource } from './taskExecutionRevisionSource';
|
||||
|
||||
export type InsertTaskExecutionRevisionResult = 'inserted' | 'idempotent';
|
||||
|
||||
/** Append-only revision store. Existing identities can never be overwritten. */
|
||||
export interface TaskExecutionRevisionRepository
|
||||
extends TaskExecutionRevisionSource {
|
||||
insert(
|
||||
revision: PinnedTaskExecutionRevision,
|
||||
createdAtMs: number,
|
||||
): Promise<InsertTaskExecutionRevisionResult>;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { PinnedTaskExecutionRevision } from '../domain/taskExecutionRevision';
|
||||
|
||||
export interface TaskExecutionRevisionRequest {
|
||||
projectId: string;
|
||||
taskId: string;
|
||||
taskRevision: string;
|
||||
}
|
||||
|
||||
/** Reads an immutable revision; implementations must not fall back to latest. */
|
||||
export interface TaskExecutionRevisionSource {
|
||||
resolve(
|
||||
request: Readonly<TaskExecutionRevisionRequest>,
|
||||
): Promise<PinnedTaskExecutionRevision | null>;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { WorkerCapabilities, WorkerRecord } from '../domain/worker';
|
||||
|
||||
export interface RegisterWorkerRequest {
|
||||
workerId: string;
|
||||
sessionId: string;
|
||||
capabilities: WorkerCapabilities;
|
||||
maxConcurrentRuns: number;
|
||||
availableSlots: number;
|
||||
}
|
||||
|
||||
export interface HeartbeatWorkerRequest {
|
||||
workerId: string;
|
||||
sessionId: string;
|
||||
generation: number;
|
||||
expectedVersion: number;
|
||||
availableSlots: number;
|
||||
}
|
||||
|
||||
export interface TransitionWorkerRequest {
|
||||
workerId: string;
|
||||
sessionId: string;
|
||||
generation: number;
|
||||
expectedVersion: number;
|
||||
}
|
||||
|
||||
export interface WorkerControlPlaneClient {
|
||||
register(request: RegisterWorkerRequest): Promise<WorkerRecord>;
|
||||
heartbeat(request: HeartbeatWorkerRequest): Promise<WorkerRecord>;
|
||||
drain(request: TransitionWorkerRequest): Promise<WorkerRecord>;
|
||||
disconnect(request: TransitionWorkerRequest): Promise<WorkerRecord>;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { CompletionReceipt } from '../domain/completionReceipt';
|
||||
import type { WorkerExecutionOfferJournalRecord } from '../domain/workerExecutionOffer';
|
||||
|
||||
/**
|
||||
* Verifies the Worker-local completion capability without exposing it in a
|
||||
* recovery result. Implementations may compare a persisted digest, consult a
|
||||
* secure local store, or validate a rotated capability.
|
||||
*/
|
||||
export interface WorkerExecutionCompletionReceiptAuthenticator {
|
||||
authenticate(
|
||||
receipt: CompletionReceipt,
|
||||
record: WorkerExecutionOfferJournalRecord,
|
||||
): boolean | Promise<boolean>;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { ExecutionContext } from '../domain/execution';
|
||||
import type { ClaimedExecutionOffer } from '../domain/runDispatchOffer';
|
||||
|
||||
export interface PreparedWorkerExecutionContext {
|
||||
context: ExecutionContext;
|
||||
logArtifactId?: string;
|
||||
}
|
||||
|
||||
export interface WorkerExecutionContextFactory {
|
||||
prepare(
|
||||
offer: ClaimedExecutionOffer,
|
||||
): Promise<PreparedWorkerExecutionContext>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export type WorkerExecutionDrainResult = 'drained' | 'timed_out';
|
||||
|
||||
export interface WorkerExecutionDrainer {
|
||||
drain(): Promise<WorkerExecutionDrainResult>;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { WorkerExecutionOfferJournalRecord } from '../domain/workerExecutionOffer';
|
||||
|
||||
export type WorkerExecutionOfferJournalCreateResult = 'created' | 'exists';
|
||||
|
||||
export interface WorkerExecutionOfferJournalPage {
|
||||
records: readonly WorkerExecutionOfferJournalRecord[];
|
||||
nextAfterOfferId?: string;
|
||||
}
|
||||
|
||||
export interface WorkerExecutionOfferJournal {
|
||||
create(
|
||||
record: WorkerExecutionOfferJournalRecord,
|
||||
): Promise<WorkerExecutionOfferJournalCreateResult>;
|
||||
read(offerId: string): Promise<WorkerExecutionOfferJournalRecord | undefined>;
|
||||
replace(
|
||||
record: WorkerExecutionOfferJournalRecord,
|
||||
expectedRevision: number,
|
||||
): Promise<void>;
|
||||
remove(offerId: string, expectedRevision?: number): Promise<boolean>;
|
||||
list(options?: {
|
||||
afterOfferId?: string;
|
||||
limit?: number;
|
||||
}): Promise<WorkerExecutionOfferJournalPage>;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export type WorkerExecutionOfferJournalOwnershipState =
|
||||
| 'unowned'
|
||||
| 'owned'
|
||||
| 'releasing'
|
||||
| 'compromised';
|
||||
|
||||
export interface WorkerExecutionOfferJournalOwnership {
|
||||
ownershipState(): WorkerExecutionOfferJournalOwnershipState;
|
||||
acquireOwnership(): Promise<'acquired' | 'already_owned'>;
|
||||
releaseOwnership(): Promise<'released' | 'not_owned' | 'compromised'>;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { WorkerRecord, WorkerStatus } from '../domain/worker';
|
||||
|
||||
export const MAX_AVAILABLE_WORKER_PAGE_SIZE = 64;
|
||||
|
||||
export interface RegisterWorkerSessionCommand {
|
||||
workerId: string;
|
||||
sessionId: string;
|
||||
capabilitiesJson: string;
|
||||
capabilitiesHash: string;
|
||||
maxConcurrentRuns: number;
|
||||
availableSlots: number;
|
||||
registeredAtMs: number;
|
||||
leaseExpiresAtMs: number;
|
||||
}
|
||||
|
||||
export interface RegisterWorkerSessionResult {
|
||||
worker: WorkerRecord;
|
||||
replacedSession: boolean;
|
||||
}
|
||||
|
||||
export interface HeartbeatWorkerSessionCommand {
|
||||
workerId: string;
|
||||
sessionId: string;
|
||||
generation: number;
|
||||
expectedVersion: number;
|
||||
availableSlots: number;
|
||||
heartbeatAtMs: number;
|
||||
leaseExpiresAtMs: number;
|
||||
}
|
||||
|
||||
export interface TransitionWorkerSessionCommand {
|
||||
workerId: string;
|
||||
sessionId: string;
|
||||
generation: number;
|
||||
expectedVersion: number;
|
||||
status: Extract<WorkerStatus, 'draining' | 'offline'>;
|
||||
transitionedAtMs: number;
|
||||
}
|
||||
|
||||
export interface AvailableWorkerPage {
|
||||
workers: readonly WorkerRecord[];
|
||||
truncated: boolean;
|
||||
nextCursor?: string;
|
||||
}
|
||||
|
||||
export interface WorkerRegistryRepository {
|
||||
findById(workerId: string): Promise<WorkerRecord | null>;
|
||||
register(
|
||||
command: RegisterWorkerSessionCommand,
|
||||
): Promise<RegisterWorkerSessionResult>;
|
||||
heartbeat(command: HeartbeatWorkerSessionCommand): Promise<WorkerRecord>;
|
||||
transition(command: TransitionWorkerSessionCommand): Promise<WorkerRecord>;
|
||||
listAvailable(options: {
|
||||
observedAtMs: number;
|
||||
afterWorkerId?: string;
|
||||
limit?: number;
|
||||
}): Promise<AvailableWorkerPage>;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type {
|
||||
AcknowledgeRemoteRunRunningCommand,
|
||||
AcknowledgeRemoteRunStartingCommand,
|
||||
FailRemoteRunStartCommand,
|
||||
RemoteRunActivationResult,
|
||||
} from '../application/remoteRunActivationService';
|
||||
|
||||
export interface WorkerRemoteRunActivationClient {
|
||||
acknowledgeStarting(
|
||||
command: AcknowledgeRemoteRunStartingCommand,
|
||||
): Promise<RemoteRunActivationResult>;
|
||||
acknowledgeRunning(
|
||||
command: AcknowledgeRemoteRunRunningCommand,
|
||||
): Promise<RemoteRunActivationResult>;
|
||||
failStart(
|
||||
command: FailRemoteRunStartCommand,
|
||||
): Promise<RemoteRunActivationResult>;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { RemoteRunCompletionCommand } from '../application/remoteRunCompletionService';
|
||||
import type { PrimaryRunCompletionResult } from '../application/primaryRunCompletionService';
|
||||
|
||||
export interface WorkerRemoteRunCompletionClient {
|
||||
complete(
|
||||
command: RemoteRunCompletionCommand,
|
||||
): Promise<PrimaryRunCompletionResult>;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { RunDispatchLeaseRecord } from '../domain/runDispatchLease';
|
||||
import type {
|
||||
FencedRunDispatchLeaseRequest,
|
||||
ReleaseRunDispatchLeaseRequest,
|
||||
} from '../application/runDispatchLeaseService';
|
||||
import type { ReleaseRunDispatchLeaseResult } from './runDispatchLeaseRepository';
|
||||
|
||||
export interface WorkerRunLeaseClient {
|
||||
renew(
|
||||
request: FencedRunDispatchLeaseRequest,
|
||||
): Promise<RunDispatchLeaseRecord>;
|
||||
release(
|
||||
request: ReleaseRunDispatchLeaseRequest,
|
||||
): Promise<ReleaseRunDispatchLeaseResult>;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { RunDispatchLeaseRecord } from '../domain/runDispatchLease';
|
||||
|
||||
/** In-process lease renewal view used by the offer receiver. */
|
||||
export interface WorkerRunLeaseTracker {
|
||||
track(lease: RunDispatchLeaseRecord): void;
|
||||
untrack(attemptId: string): RunDispatchLeaseRecord | undefined;
|
||||
leases(): RunDispatchLeaseRecord[];
|
||||
}
|
||||
Reference in New Issue
Block a user