/** * Conditional-execution surface (reporting only — this package never writes). * * ACTIVE + FRESH only proves content matched at measurement time. A host that resolves * then writes unconditionally still races; the conditional write itself is the host's job. * We define the surface, demand it by policy, and report whether the host said it happened. * * Host contract (executeIfUnchanged): * The host SHOULD perform writes via executeIfUnchanged(write, expected_token) (or * equivalent: only apply the mutation if the resource still holds expected_token). * After the attempt, the host reports on the call context: * - conditional_write: true — write was conditioned on a version token * - conditional_write: false — write was unconditional (host admits it) * - conditional_write: 'not_reported' — host did not report (DEFAULT; absence ≠ false) * When true, conditioned_on_token is the opaque token the host conditioned on. * Tokens are host-defined (git blob sha, etag, mtime-hash, …). This package never * interprets them — only carries them and compares for equality (===). */ /** Host-defined opaque version token. Compared only by string equality. */ export type VersionToken = string; /** * Host resolution surface: content plus an opaque version token. * Returned by host resolvers that support conditional execution. */ export type VersionedContent = { content: string; version_token: VersionToken; }; /** * Host report of whether the write was conditioned on a version token. * `not_reported` is the default — absence of a report is not false * (absent ≠ 0 ≠ null ≠ unknown). */ export type ConditionalWriteReport = true | false | 'not_reported'; /** * HOW STRONG the atomicity guarantee was — claim language on the mutation side, worded the way the * four claim levels are: each class says what it ASSERTS and what it DOES NOT. * * SAME_TRANSACTION * ASSERTS: the check and the mutation committed inside ONE transaction of the same system, so no * interleaving is possible — the provider, not us, enforces it. * DOES NOT ASSERT: that the transaction was correct, that it was the right row, or anything about * any resource outside that transaction. * * CONDITIONAL_EXTERNAL * ASSERTS: a SINGLE conditional claim was made against a provider that supports CAS or * idempotency (if-match, expected-version, expected-old-sha), and the result was read back. * DOES NOT ASSERT: atomicity across the check and the mutation. Two systems were involved and the * provider's own compare-and-set is what closed the window — a Redis SETNX followed by a separate * HTTP call is NOT this class and is NOT a transaction, because the claim and the mutation are two * claims against two systems. * * NON_ATOMIC * ASSERTS: the mutation was applied. * DOES NOT ASSERT: that anything guarded it. The provider lacked the capability, or no * authorization-time token was available, so the write was unconditional. This is the honest * class for "we wrote it and cannot say more", and it must never be reported as either of the * two above. */ export type ConditionalWriteGuarantee = 'SAME_TRANSACTION' | 'CONDITIONAL_EXTERNAL' | 'NON_ATOMIC'; /** * Why an outcome is INDETERMINATE. The claim is strictly "we do not know", never "it failed". */ export type IndeterminateReason = 'response_lost' | 'ambiguous_provider_reply' | 'observation_failed'; /** * Per-call forensic basis for conditional write (on every GuardOutcome). * Survives replay unchanged when recorded. */ export type ConditionalWriteBasis = { conditional_write: ConditionalWriteReport; /** * When conditional_write === true, the token the host says it conditioned on. * Omitted when not_reported / false / not supplied. */ conditioned_on_token?: VersionToken | null; /** Config requireConditionalWrite for this call (policy). */ require_conditional_write: boolean; /** Whether this call was treated as write-style (freshness classification; reporting only). */ write_style: boolean; /** * Whether this call was treated as a MUTATION — the fact the policy actually gates on. * Independent of artifacts[]: carrying both sides of a change does not make the commit atomic. */ mutating: boolean; /** * HOW STRONG the guarantee was, when the host reported one. Absent when conditional_write is * not_reported — "we were not told" is not a guarantee class. `conditional_write: true` with * guarantee NON_ATOMIC is a coherent and important combination: the host conditioned nothing. */ guarantee?: ConditionalWriteGuarantee; }; /** * Values the RUNNER collected / host reported before the pure guard path. * Default when omitted: conditional_write 'not_reported'. */ export type ConditionalWriteCallContext = { conditional_write?: ConditionalWriteReport; conditioned_on_token?: VersionToken | null; /** Optional versioned content for token carry (equality only). */ versioned_content?: VersionedContent | null; /** Host-reported strength of the guarantee. Carried, never inferred. */ guarantee?: ConditionalWriteGuarantee; }; /** Equality compare only — never semantic interpretation of the token. */ export declare function tokensEqual(a: VersionToken | null | undefined, b: VersionToken | null | undefined): boolean; /** * Build per-call conditional-write basis + optional enforce block. * Pure once the host report is a value (no I/O). * * Policy: when requireConditionalWrite is true and the call MUTATES, conditional_write must be * true for enforced execution to be eligible. false and not_reported both refuse (distinct from * "host said false" vs "host silent" only on the basis field — both block under policy). * * The gate is `mutating`, NOT `writeStyle`. It once mirrored requireFreshness's write-style * predicate; that predicate means "the caller supplied no both-sides snapshot", which is the right * question for freshness and the wrong one for atomicity. Under the old gate an ordinary Edit * carrying artifacts[] with non-empty before AND after classified as not-write-style and the * policy never fired — the demand for an atomic commit was suppressed by evidence about content. * * `mutating` is optional and defaults to `writeStyle` so a direct caller of this pure export keeps * its previous behaviour; guardToolCall always supplies it. */ export declare function buildConditionalWriteBasis(input: { writeStyle: boolean; /** Artifact-independent mutation classification (isMutatingCall). Defaults to writeStyle. */ mutating?: boolean; requireConditionalWrite: boolean; ctx?: ConditionalWriteCallContext | null; }): { basis: ConditionalWriteBasis; /** If set, enforced execution must not proceed. */ blockCause?: 'CONDITIONAL_WRITE_REQUIRED'; }; /** Residual name when policy requires conditional write but host did not report true. */ export declare const RESIDUAL_UNCONDITIONAL_WRITE = "composition_unconditional_write_under_policy"; /** * Coverage residual when requireConditionalWrite is on and the host report is not true. * inescapable_runtime must stay false — we report what the host told us, never claim compliance. */ export declare function conditionalWriteResidual(input: { /** Artifact-independent mutation classification. Defaults to writeStyle when omitted. */ mutating?: boolean; requireConditionalWrite: boolean; writeStyle: boolean; conditionalWrite: ConditionalWriteReport; }): string | null; /** * Outcome of a conditional write attempt. * - committed: write ran under the expected token; post-check (if any) agreed * - refused / stale_version_token: resource token moved BEFORE write; write did NOT run * - committed_stale_detected / stale_during_commit: write DID run, but a post-commit * re-check found the resource no longer matches the write's expected post-state * (detection only — not a rollback) */ /** Whether post-commit detection compared anything. See ExecuteIfUnchangedOutcome.detection. */ export type DetectionState = 'ran' | 'no_op_no_intended_token'; export type ExecuteIfUnchangedOutcome = { status: 'committed'; result: T; version_token: VersionToken; observed_token?: VersionToken | null; /** * 1577/1587 — DID POST-COMMIT DETECTION ACTUALLY RUN? * * `detect_stale_during_commit` can be true and still do nothing: an adapter whose host did * not return an intended post-state has no token to compare against, so its * expected_after_commit falls back to a live re-read and tokensEqual is tautologically * true. That is documented in cas-adapters/api.ts, in a comment — and a comment is not in * the outcome. A caller reading `committed` could not tell a detection that ran from one * that was a no-op. * * 'ran' an intended post-state was compared * 'no_op_no_intended_token' requested, but the host supplied no intended post-state * absent detection was not requested * * ABSENT WHEN NOT REQUESTED, deliberately. cas-fs.test.js pins that the successful path * WITHOUT the detect flag keeps a byte-identical committed shape; a field that appeared * there anyway would break a guarantee someone named on purpose. A caller who did not ask * for detection already knows it did not run — the states worth reporting are the two that * only arise when it was asked for. * * Additive and verdict-neutral: no comparison, status or reason changes with it. */ detection?: DetectionState; } | { status: 'refused'; reason: 'stale_version_token'; expected_token: VersionToken; current_token: VersionToken | null; } | { /** * FIRST-CLASS UNKNOWN. The mutation may or may not have been applied — we sent it and could * not observe the result. Distinct from 'refused' (we know it did not land) and from * 'committed' (we know it did). * * The rule is strict and is the whole point of the class: * - NEVER blindly retry. A retry of a write that already landed is a second mutation. * - Reconciliation is REQUIRED: read the resource and establish which state holds. * - Downstream MUST block until it is resolved. An indeterminate write is not a pass. */ status: 'indeterminate'; reason: IndeterminateReason; expected_token: VersionToken; /** What we managed to observe, if anything. null when observation itself failed. */ observed_token?: VersionToken | null; detail?: string; } | { status: 'committed_stale_detected'; reason: 'stale_during_commit'; result: T; expected_token: VersionToken; post_commit_token: VersionToken | null; observed_token?: VersionToken | null; }; /** * Thrown from write() when the adapter aborts BEFORE applying the mutation * (e.g. pre-rename re-check). executeIfUnchanged maps this to status:'refused'. * Not a public host API — adapters use it to stay on the single helper path. */ export declare class StaleVersionTokenAbort extends Error { readonly current_token: VersionToken | null; constructor(current_token: VersionToken | null); } export type ExecuteIfUnchangedArgs = { /** Token the host measured before deciding to write (opaque string). */ expected_token: VersionToken; /** * Re-read the resource's token immediately before commit. * Return null when the resource is gone / unreadable (treated as mismatch). */ current_token: () => VersionToken | null | Promise; /** Mutation applied ONLY when current_token equals expected_token. */ write: () => T | Promise; /** * Opt-in post-commit detection (default false — byte-identical old paths). * When true AND expected_after_commit is provided: after write(), re-read * current_token and compare to expected_after_commit(result). Mismatch → * committed_stale_detected (write already happened; honest report only). * * NOTE: do NOT compare post-commit to expected_token for content-changing * writes — the token is supposed to change. Supply the token of the written state. */ detect_stale_during_commit?: boolean; /** * Optional: did the host give an intended post-state for THIS write? * * Only the adapter knows. cas-adapters/api.ts answers it with the same condition it already * uses to decide whether to build a real token or fall back to a live re-read, so the reported * state and the behaviour come from one expression rather than two that can drift. * * Absent → detection is reported as 'ran' when requested, which is what it was before this * field existed. */ intended_post_state_known?: (written: T) => boolean; /** * Token that should hold after a successful write (the written state's token). * Required when detect_stale_during_commit is true; ignored otherwise. */ expected_after_commit?: (result: T) => VersionToken | null | Promise; }; /** * Host contract helper: re-check version token, then write or refuse. * Equality via tokensEqual only — never interprets token format. * Never throws for stale tokens (returns refused / committed_stale_detected); * write/current_token errors (other than StaleVersionTokenAbort) propagate. */ export declare function executeIfUnchanged(args: ExecuteIfUnchangedArgs): Promise>; //# sourceMappingURL=conditional-write.d.ts.map