Primitive semantics
Forge keeps method shapes aligned across backends, but it does not pretend the backends have identical durability or process scope. This page is the behavioral contract; backend caveats returned by backendCapabilities are the runtime view of the same facts.
Contract by primitive
Section titled “Contract by primitive”| Primitive | Consistency and atomicity | Ordering and duplication | Expiry and retry | Outage and multi-process behavior |
|---|---|---|---|---|
| KV | A single get observes committed state. Set-if-absent, set-if-present, increment, expiry changes, and compare-and-swap are atomic per key. Prefix scans are ordered pages, not snapshots. | No event ordering is implied. A repeated unconditional write is last-commit-wins; CAS is the write fence. | TTL is checked on access and reclaimed by maintenance; an expired key is absent even before physical cleanup. Retry only retryable errors and use CAS or an idempotent value when duplicate writes matter. | PostgreSQL is durable and shared. Memory is process-local and lost on exit. An outage returns a retryable unavailable/backend error; Forge never silently falls back to memory. |
| Queue | Enqueue commits before returning. Dequeue grants a time-bounded fenced lease; only its current receipt may heartbeat or settle. | Visible jobs are FIFO where priorities are equal, but concurrency, retries, and delayed jobs prevent a global completion order. Delivery is at-least-once. A caller job ID makes enqueue idempotent; a deduplication ID is a separate bounded reservation. | Delay controls first visibility. Nack or lease expiry schedules retry until maxAttempts, then the job becomes dead. Payload and terminal retention are separate settings. |
PostgreSQL workers share claims across replicas. Memory workers coordinate only inside one process and lose jobs on exit. Backend outages do not turn a handled job into an acknowledgement; stale workers are fenced. |
| Pub/sub | Publish reports provider acceptance, not subscriber processing. A subscriber sees only messages delivered while connected. | At-most-once, no replay, and no durable ordering promise across reconnects or publishers. | No retention, retry, cursor, or catch-up exists. Use a queue or application table when loss matters. | PostgreSQL uses cross-process LISTEN/NOTIFY; memory is in-process only. Reconnects may miss messages, so notifications must trigger a read of authoritative state. |
| Blob | Put replaces one logical key subject to its precondition. Head returns full metadata; list returns summaries. PostgreSQL bytes are transactional with Forge metadata, while filesystem and S3 bytes are separate provider writes. | List is key-ordered pagination but not a snapshot under concurrent writes. Delete is idempotent and makes no prior-existence claim. ETags are opaque version tokens, not portable MD5 hashes. | Native provider retries are bounded. Buffered bodies are size-limited; range and streaming paths avoid whole-object buffering. Presigns expire at their declared instant and are bearer credentials. | PostgreSQL and S3 are shared and durable within their provider guarantees. Filesystem requires a shared mount across replicas. Memory is local and ephemeral. Provider outages return retryable errors and do not fall back to another byte store. |
| Auth | Password verification is local CPU work. Session, API-key, and one-time-token records are stored as hashes; token consumption is atomic and single-winner. | Tokens have no ordering. Repeating create operations mints new credentials; repeating one-time-token consumption returns absent. | Idle and absolute session expiry, API-key expiry, and token expiry are checked on access and reclaimed later. Credential creation is not safe to blindly retry unless the application can discard all returned secrets. | PostgreSQL credentials work across replicas. Memory credentials exist in one process and vanish on exit. Backend unavailability is an authentication dependency failure; the application decides whether to reject or degrade. |
| Rate limit | Check-and-consume is atomic per policy and subject. A denial is a decision, not an error. | Concurrent checks serialize at the bucket and cannot oversubscribe capacity. No ordering is exposed to callers. | Capacity refills from elapsed time. Retry after the returned interval; do not retry an allowed decision as though it were idempotent. | PostgreSQL enforces one shared budget. Memory creates one independent budget per process. Fail-open converts a backend outage into an allowed decision and must be chosen explicitly for the route’s risk. |
| Schedule | Each selected occurrence becomes one deterministic queue job. Concurrent ticks claim rows, while the occurrence ID makes retries idempotent. | Cron and absolute instants are stored in UTC. skip, run_once, and capped catch_up define outage ordering; queue handling may still reorder completion. |
One-offs fire at most once. Failed enqueue remains due. Catch-up is capped at 100 per schedule and drops older excess occurrences before advancing, preventing restart storms. | PostgreSQL schedules and scheduler diagnostics are durable and shared. Memory state is process-local. Pause removes a schedule from due lag/count without deleting it. |
| Config and flags | Writes commit before cache invalidation is announced. Reads may use a bounded local cache; TTL refresh is the correctness fallback after missed notifications. | There is no event log. Concurrent writes are last-commit-wins. Flag evaluation is deterministic for the same key, subject, value, and captured environment override. | Cache entries expire by TTL. Notification reconnect uses bounded backoff. Environment overrides are captured at process initialization and do not change afterward. | PostgreSQL values are shared and invalidated across processes. Memory values and notifications are local. During outage a cached value may remain available until its documented stale bound; uncached reads fail. |
Invalidation events use pub/sub only as a lossy transport hint. Their optional revision is application-owned metadata, not a Forge replay cursor. After connecting or reconnecting, a consumer reads authoritative application state or resumes from an application-owned durable cursor.
Backend capabilities and limitations
Section titled “Backend capabilities and limitations”| Backend role | Durable | Shared across processes | Notifications | Streaming/range | Native presign | Primary limitations |
|---|---|---|---|---|---|---|
| PostgreSQL runtime | Yes after commit | Yes | Yes, connected-only | Blob range/streaming supported | No provider-native URL | Pool and row contention must be measured; pub/sub remains non-durable |
| Memory profile | No | No | In-process only | Bounded in-memory bodies | No | Tests/local development only; restart loses all state; rate limits multiply per process |
| Filesystem blob bytes | Byte files survive process restart | Only with a shared mount | Metadata notifications use PostgreSQL | Yes | No | Byte write and PostgreSQL metadata write are not one transaction |
| S3-compatible blob bytes | Provider-defined after success | Yes | Metadata coordination uses PostgreSQL | Yes, including multipart | Yes | Provider policies, lifecycle, replication, legal hold, CDN, and native PUT size enforcement are outside Forge |
Crash boundaries
Section titled “Crash boundaries”A successful queue acknowledgement means Forge committed settlement, not that an external side effect happened exactly once. Handlers must be idempotent. When an application-owned SQL transaction and enqueue must agree, insert the versioned outbox row in that transaction and let the relay enqueue by deterministic event ID. A crash before enqueue leaves the row pending; a crash after enqueue repeats the same effective job; a crash after marking is complete.
Expiry is logical before it is physical. Reads and claims exclude expired state immediately according to backend time, while maintain later removes storage. Production replicas therefore need reasonably synchronized clocks; Forge diagnostics report meaningful skew where the provider exposes it.