ForgeTrust.AppSurface.Durable
Source of truth
Source-only public preview: the PostgreSQL source provider supplies the current source-preview conformance path; publication remains held pending coordinated release evidence. This package installs no runtime and starts no hosted service.
ForgeTrust.AppSurface.Durable is the adopter-facing contract package for durable Work, resumable
AppSurface Flow, schedules, serialization, registration, and clients.
Runtime-provider and operator APIs live in
ForgeTrust.AppSurface.Durable.Provider.
Choose this package when
- a reusable module needs to describe durable work without choosing storage;
- a typed Flow must resume at explicit, persisted transition boundaries;
- a host needs
At,After,Every, or Cron schedule intent; or - an application needs stable command fingerprints for retry/conflict comparison.
Do not choose it for arbitrary replayable code, exactly-once external effects, child workflows, unbounded fan-out, a message bus, storage, or worker hosting.
Passive registration proof
AppSurfaceDurableModule registers only payload, Work, and Flow registries. This complete source consumer verifies that
registration resolves those registries and leaves IHostedService empty:
using ForgeTrust.AppSurface.Core;
using ForgeTrust.AppSurface.Durable;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace ForgeTrust.AppSurface.Durable.Examples;
internal static class PassiveRegistrationProof
{
internal static void Run()
{
var services = new ServiceCollection();
new AppSurfaceDurableModule().ConfigureServices(
new StartupContext([], new PassiveHostModule()),
services);
using var provider = services.BuildServiceProvider();
_ = provider.GetRequiredService<IDurablePayloadCodecRegistry>();
_ = provider.GetRequiredService<IDurableWorkRegistry>();
_ = provider.GetRequiredService<IDurableFlowRegistry>();
if (provider.GetService<IDurableWorkClient>() is not null
|| provider.GetService<IDurableFlowClient>() is not null
|| provider.GetService<IDurableScheduleClient>() is not null
|| provider.GetServices<IHostedService>().Any())
{
throw new InvalidOperationException("Durable contract registration must remain passive.");
}
Console.WriteLine("contracts registered; no runtime installed");
}
private sealed class PassiveHostModule : IAppSurfaceHostModule
{
public void ConfigureHostBeforeServices(StartupContext context, IHostBuilder builder)
{
}
public void ConfigureHostAfterServices(StartupContext context, IHostBuilder builder)
{
}
public void ConfigureServices(StartupContext context, IServiceCollection services)
{
}
public void RegisterDependentModules(ModuleDependencyBuilder builder)
{
}
}
}
From the repository root, the compile-and-run proof is one command:
dotnet test Durable/ForgeTrust.AppSurface.Durable.Tests/ForgeTrust.AppSurface.Durable.Tests.csproj \
--filter PassiveRegistrationProof --artifacts-path /tmp/appsurface-durable-passive
Expected result: the named proof passes; no runtime, network call, DDL, poller, or hosted service starts.
The proof emits contracts registered; no runtime installed.
Slice 7 discovery boundary
This package is a source-only public preview, and publication remains held pending coordinated release evidence.
Registration is intentionally passive: it installs contract registries, not storage operations or worker hosting.
PostgreSQL storage registration remains passive as well; continuous processing requires the explicit
AddWorkerHost() opt-in.
When a host starts the opted-in worker, startup validates schema compatibility and the active runtime epoch. It fails
closed when those values are incompatible and never applies DDL or advances migration history. See the
Slice 7 discovery and reconciliation guide for the ordered
0001–0006 migration flow, canonical role recipe, preferred preflight sequence, recovery posture, and the
implemented durable schema CLI commands.
Public API by audience
Every public type in this package belongs to one of these adopter-facing families. The member-level API snapshot is the canonical inventory; public types added to the corresponding source families inherit the audience and compatibility policy shown here.
| Audience | Public types | Contract role |
|---|---|---|
| All adopters | DurableScopeId, DurableWorkId, DurableCommandId, DurableProblem, DurableOperationResult<T>, DurableProblemCodes |
Opaque identity and safe diagnostics |
| Serialization authors | DurableDataClassification, DurableEncodedPayload, IDurablePayloadCodec, IDurablePayloadCodec<T>, SystemTextJsonDurablePayloadCodec<T>, registry types |
Explicit, versioned, policy-approved payload bytes |
| Work authors | DurableProviderSafety, retry/state/request/acceptance types, IDurableWorkClient, execution/prepared-work/registration/registry types, DurableServiceCollectionExtensions |
Declare, enqueue, and execute typed Work through a provider adapter |
| Flow authors | Flow identifiers, state/request/result/snapshot/client types; evaluation, activity, event, registration, registry, and determinism-verifier types | Persist one explicit Flow transition at a time |
| Schedule authors | Schedule shapes/policies/targets, schedule request/result/snapshot/list/explain types, IDurableScheduleClient, DurableScheduleProblemCodes |
Author and inspect versioned schedule intent |
| Retry-aware clients and providers | DurableCommandFingerprint, DurableCommandFingerprintMatch |
Compare canonical semantic command bytes without treating unknown schemas as equal |
| Effect-reconciliation authors | DurableEffectReconciliationKind, reconciliation result types, IDurableEffectReconciler<TWork,TResult> |
Declare side-effect-free reconciliation for ambiguous provider outcomes |
| Composition roots | AppSurfaceDurableModule |
Register passive contract registries only |
The application surface intentionally excludes runtime pump, claim, health, drain, scope-control, and Work operator types. Those are Provider SPI.
Command fingerprints
Every command-bearing mutation exposes a computed DurableCommandFingerprint with a versioned schema id and SHA-256
digest. Fingerprints cover Work enqueue; Flow start, event, cancel, and recovery release; schedule create, update, pause,
resume, delete, and recovery release. Provider operator commands have their own schemas in the Provider package.
Use Compare before treating a repeated command identity as equivalent. Exact means the schema and digest agree;
Conflict means the known schema agrees but semantic bytes differ; UnsupportedSchema means the caller must not guess.
Never persist a caller-supplied digest as authoritative.
Schedule targets are encoded by their registered codec when the target is constructed. Mutating the caller's input object afterward cannot change the fingerprint or the bytes a provider receives.
Identifiers, results, and limits
All request and result constructors reject default opaque identifiers at the public boundary. Collection-bearing
results defensively copy caller collections. DurableWorkerExecutionIdentity can only be created through validated
factories and advanced through its monotonic transition API; its provider key is the immutable activity id.
Durable identifier alphabet and bounds
Fields documented as durable identifiers accept only ASCII letters (A-Z, a-z), digits (0-9), hyphens (-),
underscores (_), periods (.), and colons (:). They reject null, empty, or whitespace-only values, control
characters, and every other character. The shared rule keeps persisted identity values ordinal, privacy-safe, and
portable across providers.
Work and Flow names and other registered names are limited to 200 characters; immutable Work and Flow versions and other registered versions are limited to 100 characters. Provider health and inventory contracts apply the same alphabet with a 200-character worker-id limit and 120-character terminal/problem-code limit. These rules apply only to fields documented as durable identifiers. Human-readable labels, Cron expressions, time-zone ids, and encoded payloads have their own validation rules and must not be inferred from this alphabet.
Cron expression text is limited to 512 characters and the IANA time-zone id to 128 characters. These are contract limits, not proof that a string is valid Cronos grammar or a known time zone; a provider performs grammar and zone validation before persistence.
Preview compatibility
| Change | Preview policy |
|---|---|
| Additive request/result member | Allowed only with a documented default and fingerprint review |
| Mutation semantics or canonical bytes | Before the first supported publication or persisted deployment, v1 may be corrected in place with updated test vectors; afterward, requires a new fingerprint schema id |
| Payload bytes | Requires a new application contract version |
| Flow executable behavior | Requires a new implementation version or explicit migration |
| Schedule dialect semantics | Requires a new dialect/version, never reinterpret persisted occurrences |
| Provider SPI behavior | Requires provider conformance evidence before adoption |
Diagnostics available now cover contract validation, semantic conflicts, and PostgreSQL Work storage, schema,
activation, and restore failures. Heartbeat, drain, and hosted-runtime diagnostics are provider-owned. See the
ASDURxxx catalog.
Release Guidance
This package has an explicitly experimental or publication-held contract. Do not treat it as a normal prerelease install; use the package chooser and release hub for the current publication decision, proof requirements, and migration guidance.