/** * The updated stamps, held true by the host (#2166). * * An entity that declares `updatedAt`/`updatedBy` (`EntityDef.stamps`) says so in `model.json`, * and that claim is held on every row a call names in `ctx.emit` — not only the rows a derived * write touched. What is read is the emits, not the writes: an emit about a stamped row counts as * a write to it, and a write that emits only about another entity is not seen. A derived * create and a derived update write the pair themselves; an authored handler is trusted to, and * this is what holds it to that. Both adapters run it around every operation and every consumer * delivery, inside the same transaction: * * 1. Before the guards and the handler, `watchUpdatedStamps` takes the outbox's insertion mark — * unless no registered module declares the pair, in which case it reads nothing and returns * `undefined`, and the call pays nothing at all. * 2. After the handler, `assertHeld` reads which rows the call emitted about since that mark, keeps * those whose entity declares the pair, and reads each row's stamped columns by its id. A row * that is gone (a delete) is skipped. Any other row must hold this call's `ctx.now()` and * principal, or the call is refused `internal` and every write it made rolls back with it. * * **Why the outbox, not a record of `ctx.emit`.** An emit inside a `ctx.atomic` that was rolled * back is gone from the outbox with the writes it described, so the outbox is exactly what this * call is about to commit — on both adapters, with nothing to keep in step. * * **A kernel move is not checked.** `ctx.archive`, `ctx.trash` and `ctx.restore` write only the * kernel's own `_substrat_*` columns and emit kernel-authored types; those carry no module write, * so they are left out, and the move does not stamp the module's columns either. */ import { type PrincipalId, type UpdatedStampDeclaration } from '@substrat-run/contracts'; import type { ScopedSql } from './scope-host.js'; /** One entity's updated stamps, and the module that declared them. */ export interface UpdatedStampPlan extends UpdatedStampDeclaration { readonly moduleId: string; } /** Add a module's `updatedStamps` to the host's map — one owner per entity type. */ export declare function addUpdatedStampPlans(byType: Map, moduleId: string, declarations: readonly UpdatedStampDeclaration[] | undefined): void; /** What `assertHeld` compares a stamped row against: the call's own instant and caller. */ export interface StampedBy { now(): string; readonly principal: PrincipalId; } export interface UpdatedStampWatch { /** Refuse the call unless every row it emitted about holds its updated stamps. */ assertHeld(ctx: StampedBy, who: string): void; } /** * Mark the outbox before a call, so `assertHeld` can read what the call emitted. `undefined` — and * no read — when no entity in the scope declares the updated pair. */ export declare function watchUpdatedStamps(sql: Pick, plans: ReadonlyMap): UpdatedStampWatch | undefined; //# sourceMappingURL=entity-stamps.d.ts.map