import type { JobId, NodeId, Role } from "@norskvideo/norsk-manager-sdk"; import type { MigrationId, PlacementDecisionInfo } from "./automanager"; /** * The claim an in-flight migration holds for its job: which migration, and * — once placed — the node its TARGET is bound to and the target's own * placement decision, parked until `done` makes it the job's placement of * record. Deliberately separate from the job's own `nodeId`, which keeps * naming the source for the whole migration (that is where the job lives * until `done`): recovery, `nodesByJob` and `wantsPlacementNow` must never * mistake the target's existence for the job having moved, while the * derived reservation counts the job on BOTH nodes for as long as the * overlap lasts. * * @public */ export interface MigrationClaim { migrationId: MigrationId; targetNodeId?: NodeId; targetPlacement?: PlacementDecisionInfo; } /** * Everything the AutoManager knows about where one job is. * * @public */ export interface JobBinding { jobId: JobId; /** * The node the job's placement of record is on. Set at placement time * (an existing node via StartJob, or the provisional node of a fresh * provision), by hydration from job history, and by a migration's * `done`; released on rejection, dispatch failure, runtime failure, node * loss and job deletion. A node that has since terminated may still be * named here until the job is re-placed — resolve through the node map. */ nodeId?: NodeId; /** * The instance KEY of the job's current instance, where it differs from * the slot (`roleFromReplicaIndex`): set at a migration's `done` to the * target's key, rebuilt from history at hydration, and dropped with the * binding — a fresh instance is under the slot key again. Every RPC that * addresses the RUNNING instance goes through it: after one migration * that is `primary@xxxxxxxx`, not `primary`. Never set without `nodeId`. */ role?: Role; /** * The placement decision of record. Kept across an unbind — a node that * bounced does not change where the job was placed — and replaced by * the next successful placement; failures never bind one. */ placement?: PlacementDecisionInfo; /** * When the last initial placement was dispatched: the in-flight gate * that stops `handleJobChanged` re-firing placement while the daemon's * provisionJob is still mid-dispatch. Cleared whenever the attempt is * known to have concluded — a failure, an unbind — so the next capacity * event is free to retry. */ lastPlacementAttemptAt?: Date; /** The in-flight migration's claim, if one is in flight. */ migration?: MigrationClaim; } /** * The map of job bindings, with the operations the AutoManager needs and * nothing that can leave two fields disagreeing. * * @public */ export declare class JobBindings { private readonly byJob; get(jobId: JobId): JobBinding | undefined; nodeOf(jobId: JobId): NodeId | undefined; roleOf(jobId: JobId): Role | undefined; placementOf(jobId: JobId): PlacementDecisionInfo | undefined; lastAttemptOf(jobId: JobId): Date | undefined; migrationOf(jobId: JobId): MigrationClaim | undefined; /** Is this job's placement of record on `nodeId`? */ isBoundTo(jobId: JobId, nodeId: NodeId): boolean; /** Is this job's in-flight migration TARGET bound to `nodeId`? */ isTargetOn(jobId: JobId, nodeId: NodeId): boolean; /** Jobs whose placement of record is `nodeId`. A migration target bound * there is not yet on the node's books. */ jobsOn(nodeId: NodeId): JobId[]; /** Jobs whose in-flight migration target is bound to `nodeId`. */ targetsOn(nodeId: NodeId): JobId[]; /** Does any job's placement of record name `nodeId`? */ anyOn(nodeId: NodeId): boolean; /** Every node some job's placement of record names. */ boundNodes(): Set; /** Every (job, node) placement of record. */ bound(): Array<{ jobId: JobId; nodeId: NodeId; }>; /** Every (job, node) in-flight migration target binding. */ targets(): Array<{ jobId: JobId; nodeId: NodeId; migrationId: MigrationId; }>; /** Every placement of record. */ placements(): PlacementDecisionInfo[]; /** The id of every in-flight migration. */ inFlightMigrationIds(): MigrationId[]; /** * A fresh placement of `jobId` onto `nodeId`: a new instance under the * slot key, so any key a past migration left the job under is forgotten * with it. `placement` (when the decision is known) becomes the placement * of record; the in-flight gate is left as the attempt set it. */ place(jobId: JobId, nodeId: NodeId, placement?: PlacementDecisionInfo): void; /** * The provisioned node a job was waiting for has arrived: bind to it, * keeping whatever instance key hydration established — the instance * is the one the history describes, not a fresh one. */ adopt(jobId: JobId, nodeId: NodeId): void; /** * Rebuilt from job history at hydration: the node the current instance * is on, the key it wears where that differs from the slot, and a * synthesised placement of record when one could be derived. */ hydrate(jobId: JobId, nodeId: NodeId, role: Role | undefined, placement: PlacementDecisionInfo | undefined): void; /** * The job no longer runs where it was bound (or the attempt to put it * there conclusively failed): drop the node, the instance key and the * in-flight gate. The placement of record stays — it is what was * decided, and the next successful placement replaces it. A migration * claim is the saga's to release. */ unbind(jobId: JobId): void; /** `unbind`, but only when the placement of record is `nodeId` — the * guard every rejection / dispatch-failure path applies, in case * something re-placed the job meanwhile. Returns whether it did. */ releaseFrom(jobId: JobId, nodeId: NodeId): boolean; /** The job is gone: forget everything about it. */ forget(jobId: JobId): void; /** An initial placement is being dispatched now. */ noteAttempt(jobId: JobId, at: Date): void; /** The attempt concluded (a failure, a superseding event): the next * capacity event may retry at once. */ clearAttempt(jobId: JobId): void; /** Every in-flight gate, at once (close: nothing is in flight any more). */ clearAttempts(): void; /** A migration of `jobId` is now in flight. One at a time per job: the * caller checks `migrationOf` first. */ claimMigration(jobId: JobId, migrationId: MigrationId): void; /** The migration's target has been placed onto `nodeId`. */ bindTarget(jobId: JobId, migrationId: MigrationId, nodeId: NodeId): void; /** The target's placement decision, parked until `done`. */ parkTargetPlacement(jobId: JobId, migrationId: MigrationId, placement: PlacementDecisionInfo): void; /** The target lost its node (a provision abandoned, a rejection) while * the migration goes on: drop the target binding only. */ unbindTarget(jobId: JobId, migrationId: MigrationId): void; /** * The migration is over without moving the job (aborted, or forgotten): * release its claim — target binding and parked placement included. * The job's own binding still names the source. A claim that is no * longer this migration's (a later one took over) is left alone. */ releaseMigration(jobId: JobId, migrationId: MigrationId): void; /** * `done`: the job now lives on the target. The binding rebinds to it, * the target's separate binding folds away, the instance key becomes * the target's, and the parked target decision becomes the placement of * record. Returns false when this migration held no claim. */ completeMigration(jobId: JobId, migrationId: MigrationId, targetRole: Role): boolean; clear(): void; /** * What must hold between the fields of every record, checked by tests * after every step of every scenario. Throws naming the first violation. */ assertConsistent(): void; private ensure; private claimFor; /** A record with nothing left in it is not kept: `get` answering a * bare `{ jobId }` for a job nobody has placed would read as state. */ private prune; } //# sourceMappingURL=jobBinding.d.ts.map