/** * Correlation-id generation for the entity families a replay can create. * * Correlation ids are minted by the workflow VM and are the server's identity * gate: a conditional create on the id is what makes a duplicate write from a * second live replay idempotent instead of additive. That only works if two * replays of the same run mint the same id for the same entity. * * Historically every id was the Nth draw of *one* monotonic ULID sequence per * run, shared by steps, waits, hooks, attribute writes, abort controllers and * stream ids alike. Every id was therefore an ordinal over the whole run, and a * single extra draw of any kind renumbered every entity of every kind after it. * Two replays that agreed about every step but disagreed about one `sleep()` * would mint different ids for all subsequent steps, so their writes appended * side by side instead of colliding, and the settled log ended up holding two * names for one logical step. Only one of them can be consumed on the next * replay; the other is fatal (`onUnconsumedEvent`). * * Per-kind sources narrow that coupling to one kind at a time: each family * draws from its own independent incrementing sequence, so a disagreement about * how many hooks or sleeps were created no longer renames steps. * * Ids stay syntactically valid ULIDs (10 Crockford characters of * `fixedTimestamp` plus 16 of body), because correlation ids are validated as * prefixed 26-char ULIDs by the backend, and they stay monotonic *within* a * kind, because `hooks.list` is ordered by hook id. * * Monotonicity is per kind, and two kinds mint `hook_` ids (`hook` and * `abortHook`), so listing order is only creation order *within* each of them. * No world filters system hooks out of a listing, so a run that constructs an * abort controller and also creates its own hooks lists that system hook at a * position decided by its kind's hash rather than at its creation position. * Order among the user's own hooks is unaffected. * * This does not make ids independent of *ordinal position within their own * kind*: two replays that disagree about how many steps ran still mint * different ids for the next step. That is a narrower failure than the shared * sequence's, not an eliminated one. */ /** Entity families that draw correlation ids, each from its own sequence. */ export type CorrelationIdKind = /** `step_` ids, one per step invocation. */ 'step' /** `wait_` ids, one per `sleep()`. */ | 'wait' /** `hook_` ids for hooks created by workflow code. */ | 'hook' /** `attr_` ids, one per attribute write. */ | 'attr' /** * The abort controller's own id, which becomes its stream name and hook * token. Separate from `hook` so constructing an abort controller does not * renumber later user hooks. */ | 'abort' /** `hook_` ids for the internal system hook backing an abort controller. */ | 'abortHook' /** * Ids minted during serialization (`STABLE_ULID`): stream names, and an abort * holder's stream name and `abrt_` hook token when it reaches serialization * without an identity yet (`reduceAbortWithListener`), which is why `abort` * above is not the only mint path for an abort identity. Not correlation ids, * but they drew from the same shared sequence, so a workflow that serialized * a stream renumbered every entity created after it. */ | 'stream'; /** Mints the ULID body of a correlation id for one entity family. */ export type CorrelationIdGenerator = (kind: CorrelationIdKind) => string; /** * Builds a replay's correlation-id generator. * * `perKind: false` returns the run's single shared monotonic sequence and * ignores the kind entirely, so both schemes go through one call path and the * flag is the only difference between them. */ export declare function createCorrelationIdGenerator(options: { /** * The run's replay-stable seed. Must not vary between replays of one run, and * must differ between runs, or two runs would mint identical ids. */ seed: string; fixedTimestamp: number; /** The run's shared monotonic sequence, used as-is when `perKind` is false. */ positional: () => string; perKind: boolean; }): CorrelationIdGenerator; /** Length of a ULID, exported so tests need not restate it. */ export declare const CORRELATION_ID_LENGTH: number; /** * Whether each entity family draws correlation ids from its own sequence rather * than from one sequence shared by the whole run. Off unless opted in, so an SDK * upgrade alone never moves a run between schemes. * * The invariant either way: a run must replay under the scheme that minted its * ids. A replay under the other scheme mints ids its own earlier events do not * carry, so it can consume none of them and fails the run. Two things can break * it, and both are about turning the flag on rather than about upgrading: * * - Enabling it while runs are in flight. On Vercel, skew protection keeps a run * on the deployment that started it, so a run only ever sees the value baked * into its own deployment. Elsewhere (world-postgres, world-local, a * self-hosted process) nothing pins a run to the code that started it, so * enable it during a quiet window. * - A rolling deploy that leaves both values live, which puts two schemes on one * run concurrently — the side-by-side append this whole mechanism exists to * avoid. Roll the value out to the whole fleet at once. */ export declare function isPerKindCorrelationIdsEnabled(): boolean; //# sourceMappingURL=correlation-id.d.ts.map