AppSurface Search
Guide

ForgeTrust.AppSurface.Durable.Provider

Source of truth

View source Edit this page

Last updated

Public preview: the PostgreSQL provider supplies the current conformance path. This package contains SPI contracts, not a runtime.

ForgeTrust.AppSurface.Durable.Provider is the runtime-provider and operator SPI for ForgeTrust.AppSurface.Durable. It depends on that adopter package; the adopter package never depends on Provider. Production providers implement this public SPI without friend access.

Choose this package when

  • implementing a storage/runtime provider;
  • hosting a bounded provider pump explicitly;
  • exposing application-authorized health, drain, recovery, or operator operations; or
  • adapting a provider claim to an adopter-registered Work executor.

Ordinary applications and reusable modules should reference only ForgeTrust.AppSurface.Durable. This package does not provide PostgreSQL storage, migrations, polling, schedule execution, hosted services, endpoints, metrics, or tracing.

Slice 7 discovery boundary

This SPI remains a public preview. Provider contracts describe activation and operator boundaries, but storage registration is passive and does not imply worker hosting. The PostgreSQL provider requires an explicit AddWorkerHost() opt-in for continuous processing.

An opted-in host validates schema compatibility and the active runtime epoch during startup, then fails closed when they are incompatible. Startup never applies DDL or rewrites migration history. The ordered schema and reconciliation flow is documented in the Slice 7 Durable guide.

Activation and broker evolution

IDurableRuntimePump is the common bounded activation primitive for a continuously hosted loop, scheduled job, function, HTTP wake-up, or broker notification. A wake-up is advisory: implementations must recover eligible work from their authoritative state even when notifications are lost, duplicated, delayed, or reordered.

Do not translate broker receipt into a claim, effect permit, or terminal fact. A wake-only adapter must call the pump without carrying application payloads. A future targeted-dispatch or broker-native provider must revalidate its opaque reference against authoritative scope, revision, lease, runtime-epoch, and provider-effect state before invoking work. Slice 2 leaves that adapter shape open until a concrete broker topology proves the required routing and acknowledgement contract.

Operational assessment and admission

The operational-assessment adoption guide is the copyable consumer path for this SPI. Use DurableRuntimeHealthSnapshot.WasStoreObserved, CanEnableActivation, CanAttemptPump, and IsReady for their distinct meanings; do not reconstruct readiness from component fields or timestamps. CanAttemptPump is advisory and must not be a check-then-act gate.

var health = await healthClient.GetAsync(cancellationToken);
if (!health.WasStoreObserved)
{
    logger.LogWarning("The Durable store could not be observed; code={ProblemCode}.", health.ProblemCode);
}

activationEnabled = health.CanEnableActivation;
runtimeControlPlaneReady = health.IsReady;
// health.CanAttemptPump may inform display/backoff, but admission remains the authority.

External activators should resolve IDurableRuntimePumpAdmission and call TryRunOnceAsync directly. Handle every DurableRuntimePumpAttemptKind in an exhaustive switch:

Kind Result Execution certainty
Completed Non-null DurableRuntimePumpResult, including an empty result This invocation entered execution and terminal bookkeeping completed; inspect counts for item success
Refused Null This invocation did not enter RunPassAsync
Unavailable Null and ASDUR103 This invocation did not enter RunPassAsync; the store could not be observed
Incompatible Null and ASDUR108 or ASDUR400ASDUR403 This invocation did not enter RunPassAsync; the observed store rejected this runtime

Caller cancellation, application execution failures, malformed provider state, and finalization failures propagate; Try does not mean “never throws.” A new retry is host policy and certifies only a new invocation. See the PostgreSQL diagnostics matrix for cause-specific remedies without provider-specific public exception types.

Public API by audience

Every public type in this package belongs to one of these provider-facing families. The member-level API snapshot is the canonical inventory.

Audience Public types Contract role
Runtime implementers DurableRuntimeSurface, DurableRuntimePumpRequest, DurableRuntimePumpResult, DurableRuntimePumpAttemptKind, DurableRuntimePumpAttempt, IDurableRuntimePump, IDurableRuntimePumpAdmission Run one bounded pass through the legacy exception/result projection or the admission-aware operational assessment
Health and host implementers DurableRuntimeHealthState, DurableRuntimeHealthSnapshot, IDurableRuntimeHealth, IDurableRuntimeDrainControl Report low-cardinality health and coordinate graceful drain
Work-store implementers DurableClaimedWork, DurablePreparedWorkInvocation, DurableProviderWorkAdapter Validate a claim, derive immutable execution identity, and invoke the adopter registry
Application-authorized control implementers Work get/cancel/list/snapshot types and IDurableWorkControlClient; scope disable types and IDurableScopeControlClient Expose bounded, scoped, payload-free operational control
Application-authorized operator implementers Operator outcome/resolution/result/request types and IDurableWorkOperatorClient Reconcile, resolve, safely retry, or recovery-release suspended Work
Application-authorized retention implementers IDurableFlowRetentionClient, bounded assessment/manifest/package/receipt/hold/purge types Prove one exact terminal Flow source set before a separately authorized purge
Flow-repair operator implementers IFlowRepairOperatorClient, repair request/evidence/assessment/result/receipt types Inspect and repair only the two evidence-backed child-effect assertions

The SPI accepts and returns public Durable identifiers and command fingerprints. Collection results defensively copy inputs, default identifiers are rejected, timestamps normalize to UTC, page sizes are bounded, and every mutation uses revision/generation fencing. Provider worker ids, terminal/problem codes, and registered Work names and versions use the Durable package's canonical identifier alphabet and bounds.

Provider work adaptation

A provider constructs DurableClaimedWork only after it owns a validated claim. Prepare maps that claim to the adopter-facing DurableWorkExecutionContext and resolves the registered executor. The resulting DurablePreparedWorkInvocation owns encoded input and exposes the legacy InvokeAsync boundary plus InvokeExitAsync for an opt-in typed Work exit. A provider that implements exits must call InvokeExitAsync after its effect permit commits. The default DurablePreparedWork.InvokeExitAsync implementation wraps a legacy InvokeAsync success result, so existing registrations remain compatible.

Exit-aware registrations are supported only when the provider explicitly calls InvokeExitAsync. Calling the legacy success-only method for a non-success exit throws DurableWorkExitCompatibilityException with no raw application code or payload. After a permit, treat that exception exactly like every other invocation failure: preserve ambiguity rather than treating the exit as success or blindly retrying it. The portable typed-exit contract does not grant a provider an arbitrary exit factory, direct Work-state access, or an exception classifier.

Registration Provider invocation Required behavior
Legacy IDurableWorkerExecutor<TWork,TResult> InvokeAsync or InvokeExitAsync Existing success/exception behavior; InvokeExitAsync wraps success.
Exit-aware IDurableWorkExitExecutor<TWork,TResult> InvokeExitAsync Forward the exact encoded fact to the provider's one authoritative completion translator.
Exit-aware IDurableWorkExitExecutor<TWork,TResult> Legacy InvokeAsync Success is returned; a non-success exit throws the safe compatibility exception.

The execution identity transition is enforceable: create the first identity from an activity id and current fences, then call Advance for a later attempt/lease/scope/runtime epoch. The provider key remains exactly the activity id so lease turnover cannot create a new external idempotency identity.

Command fingerprints

Work reconcile, manual resolution, safe retry, and recovery release each use a distinct v1 fingerprint schema. A provider persists the schema id and digest with command outcome truth. A repeated command id with UnsupportedSchema or Conflict fails closed; it must never repeat reconciliation merely because the command id matches.

Flow repair operator preview

IFlowRepairOperatorClient is a separate application-authorized boundary for an ASDUR211 child-effect suspension. It intentionally exposes only AssertChildEffectCompleted and AssertChildEffectNotApplied. The first binds a retained terminal Work result; the second binds a named completed manual-resolution command whose persisted resolution_kind is proven_not_applied. Neither action can release an arbitrary suspension, mutate the child Work, invoke an executor, or force terminate an ambiguous effect.

Call GetAssessmentAsync with a trusted scope and Flow id to obtain an advisory, payload-free candidate. The assessment can go stale; submit the selected action through a fresh revision- and descriptor-digest-fenced request. RepairAsync returns an immutable receipt only for Applied and Duplicate; Refused, RaceLost, and Conflict carry no receipt. The receipt digest binds the request schema/digest, descriptor, action-specific Work history fact, audit actor/reason, transition states, and accepted timestamp. It is integrity evidence, not a signature or a claim that ActorId authorized the caller.

Do not call IDurableFlowClient.ReleaseSuspensionAsync as a fallback for this descriptor. The PostgreSQL provider rejects that broad lifecycle command with ASDUR211; follow the normative Flow repair protocol instead.

Verified Flow retention

IDurableFlowRetentionClient is an evidence boundary, not a cleanup scheduler. An application first assesses exactly one Flow and receives Safe, Blocked, or Indeterminate with a typed reason. Only a still-matching safe assessment can create an immutable manifest. The provider then builds a reproducible DFA1 package, records an adopter-supplied archive receipt, verifies source correspondence, permits an application-owned hold, and accepts a separate compare-and-swap purge command.

Canonical retention API reference

Operation Request type Result payload Required state Next sequence
AssessAsync DurableRetentionAssessmentRequest DurableRetentionAssessment Terminal Flow N/A
CreateManifestAsync DurableRetentionManifestCreateRequest DurableRetentionManifestCreateResult Safe assessment 1 (Frozen)
BuildArchivePackageAsync (scopeId, manifestId) DurableArchivePackageV1 Any active manifest Unchanged
RecordArchiveReceiptAsync DurableRetentionRecordArchiveReceiptRequest DurableRetentionMutationResult Frozen, sequence 1 2 (ArchiveReceiptRecorded)
VerifyArchiveAsync DurableRetentionVerifyArchiveRequest DurableRetentionMutationResult ArchiveReceiptRecorded, sequence 2 3 (Verified)
SetHoldAsync DurableRetentionHoldRequest DurableRetentionMutationResult Verified or Held Next monotonic sequence
PurgeAsync DurableRetentionPurgeRequest DurableRetentionMutationResult Verified, no active hold Next monotonic sequence (Purged)

Lifecycle state sequence and boundaries

The retention lifecycle uses monotonic sequence checks to prevent out-of-order or duplicate execution:

  1. Assessment & Manifest: AssessAsync evaluates one Flow against boundary limits (maximum 10,000 closure items and 64 MiB package bytes). Non-safe outcomes (Blocked due to active child Work, nonterminal state, or repair required; Indeterminate due to unknown state) forbid manifest creation. CreateManifestAsync freezes source hashes and initializes LifecycleSequence = 1 in Frozen state.
  2. Archive & Receipt: BuildArchivePackageAsync constructs the canonical DFA1 archive byte array. The adopter writes the archive package to external storage and calls RecordArchiveReceiptAsync with sequence 1.
  3. Verification: VerifyArchiveAsync validates source SHA-256 correspondence against the frozen manifest, advances state to Verified, and increments LifecycleSequence to 3.
  4. Hold & Purge: SetHoldAsync can place or release a legal hold (PlaceHold = true/false) and increments the sequence for every applied transition. PurgeAsync requires state Verified, no active hold, and the current sequence. It transitions state to Purged, clears terminal payloads, and deletes manifest-covered history rows.

Common failures and pitfalls

  • ASDUR102 (Command Conflict): Reusing a command identity with different request parameters fails closed.
  • ASDUR214 (Manifest Not Found): Specified manifest ID does not exist in the authorized scope.
  • ASDUR215 (Source Changed): Live Flow source items changed after assessment or manifest creation. Caller must create a new assessment and manifest.
  • ASDUR216 (Sequence Conflict): Expected lifecycle sequence is stale. Reload manifest state before retrying.
  • ASDUR217 (Lifecycle Rejected): Operation attempted out of order (e.g. purging before verification or while a legal hold is active).

The application must authorize every call and owns archive transport, encryption, retention duration, availability, and legal/compliance requirements. Receipt verification proves the package corresponds to the frozen PostgreSQL source; it does not prove external bytes are present or adequate. No API accepts an archive URI, raw SQL, age-range deletion, continuation token, or multi-Flow manifest. See the PostgreSQL retention deployment guidance.

Operational prerequisites

Before a provider is included in a coordinated prerelease, release review must verify storage and migration ownership, polling/schedule execution, restore fencing, graceful drain, privacy-bounded diagnostics and telemetry, packed-consumer proof, verified-retention evidence when the retention SPI is implemented, and conformance tests against this SPI. The PostgreSQL provider supplies Work, Flow, Schedule, hosted activation, drain/recovery, retention, and repair conformance; the review checklist remains the prerelease publication gate.

See the ASDURxxx diagnostics catalog for currently available contract, PostgreSQL Work, and hosted-runtime codes.

From the repository root, ./Durable/verify-packed-consumers.sh packs the three public-preview packages and their local dependencies, then compiles and runs isolated adopter and provider consumers against only those packages.

Release Guidance

AppSurface ships as a coordinated package family. Before installing this package from a prerelease feed, check the package chooser and release hub for current release risk, migration guidance, and readiness.