Skip to content
Ironflow is pre-1.0: APIs can change between releases. Pin a version. What is ready →

NATS trigger acceptance and retention

EVENTS accepts ironflow.{projectName}.{environment}.events.ingest.> and ironflow.{projectName}.{environment}.events.resume.> only. The environment token is an environment ID; external ingest also accepts an environment name. Other events.* subjects receive no successful JetStream PubAck.

A positive JetStream PubAck is broker acceptance. Async publishers must await the future’s ACK/error. Core NATS publish and flush do not establish acceptance. At capacity, JetStream returns an error and retains the previously accepted payloads. A timeout or lost ACK is an unknown outcome. Retry with the same stable event/operation identity. Broker deduplication lasts two minutes and is not long-term execution deduplication.

EVENTS uses LimitsPolicy with DiscardNew, no age expiry, 50,000 messages, 20 MiB memory or 512 MiB file capacity, and 1 MiB per message. Message TTL and rollup replacement are disabled. ACK does not free capacity under LimitsPolicy. Capacity is reclaimed only after a durable terminal outcome and individual message deletion. ACK alone leaves the message occupying storage.

The ingest and resume consumers each permit 50,000 outstanding deliveries. External NATS account limits must allow that credit. Pending effects remain unacknowledged; their retained messages consume the shared finite capacity. Paused work does not impose a smaller delivery cap on other tenants, but a full stream still rejects new publications. Raising the stream message bound above 50,000 is unsupported.

File storage is required for broker restart survival. Memory mode is ephemeral. For external NATS use spec.nats.fileStorage: true (or NATS_FILE_STORAGE=true on the flags-only path), persistent broker disks, and the documented write durability settings. SQLite development may explicitly opt into memory mode. No disk-loss guarantee is implied. Ordinary broker downtime no longer expires EVENTS by age; ordinary cron downtime retains its existing skip policy.

  1. Stop all external publishers, engine instances, and consumers. Disable automatic restarts of old binaries. Revoke their stream-configuration credentials or remove their access before starting new code. A freeze or DB generation is not a writer fence.
  2. Back up the stopped broker’s persistent storage and record EVENTS config, stream creation identity, sequence range, message count, and bytes.
  3. For a plain legacy LimitsPolicy stream without TTL/rollup or alternate sources, first set only MaxAge=0 and DiscardNew with the NATS administration API while all writers remain stopped. Preserve current limits, subjects, storage and every payload. Read the config back before enumeration. Until this protective edit succeeds, the old broker expiry remains active; neither backup nor startup restores history already lost. Unsafe TTL/rollup histories require the separate reviewed migration below.
  4. First registration in SQL requires an empty EVENTS stream, even when its subjects already match the supported filters. Legacy ACKed entries may never redeliver and have no durable receipt. Startup refuses a nonempty unregistered incarnation without creating a fence or deleting payloads. Narrowing subjects does not make this history safe. The broker emptiness observation and SQL fence insert are not atomic; writers and consumers must remain fenced through first registration.
  5. Reconcile every legacy entry while writers remain stopped and fenced. Retain payloads and execution evidence until each entry has a reviewed terminal disposition and individually authorized deletion. No automatic adoption or reconciliation tool is provided. If any entry cannot be accounted for, retain the broker backup and stopped state; do not upgrade. Only after the stream is empty may its subjects be narrowed and first registration proceed. Existing registered incarnations restart with pending receipts and payloads intact. TTL/rollup, transforms, alternate sources, sealed, deletion-disabled and NoAck histories require a separate reviewed migration, even when empty.
  6. Confirm live usage fits 50,000 messages and the target byte limit. If it does not, keep the existing data and complete a compatible terminal migration before upgrading. Do not lower limits below usage. Storage changes require an explicit migration; replica count is reconciled in place.
  7. Start the new binary with ingress still fenced. Startup updates safe fields and reads them back. On any error, keep ingress fenced. Verify file storage for persistent operation, no TTL/rollup, DiscardNew, MaxAge=0, and both finite limits and successful SQL registration. Resume ingress only after these checks succeed. Never roll back to an eviction-capable binary with pending data.

Previously expired or evicted history is not recovered. Old credentials must stay fenced; startup readback is not continuous protection against old writers. The repository example is examples/nats-ingestion/README.md.

The consuming NATS user must be allowed to request $SYS.REQ.USER.INFO and receive its reply. Startup refuses consumption if the broker cannot report a nonempty account identity. The receipt identity hashes that account, the JetStream domain, EVENTS name and broker creation timestamp. Consumer names, connection sessions and server process IDs are excluded. Every deletion and absence-proof batch rechecks account, domain and creation timestamp. Recreating EVENTS requires a new fence. Identity checks reject an observed recreation, but the broker has no incarnation-conditional DeleteMsg. Administrative credentials must prevent deletion, recreation or configuration mutation while any runtime or old callback can act; a check followed by deletion is not an atomic broker fence.

A committed ingest transaction pins its event before effects or fan-out. A resume delivery carries its broker token into native queue admission. Completion requires the stored incoming-event phase and fan-out proof. Legacy or incomplete proof remains pending. Terminal receipts drive individual message deletion, positive absence confirmation and atomic SQL floor advancement. Each delivery can ACK after its own terminal receipt commits, independently of the floor. ACK alone never releases LimitsPolicy capacity or settles remote execution. The bounded background worker retries deletion and confirmation after restart. The broker FirstSeq can prove an absent prefix after exact account/domain/creation verification. SQL refuses a range crossing a pending receipt or a terminal receipt without individual deletion confirmation. Compaction releases pins and quota atomically; it does not enumerate millions of historical missing sequences.

Definitive resume capacity rejection maps to HTTP 503 or Connect Unavailable. A timeout or other unconfirmed publication retains its unknown-outcome meaning. File storage is required for broker-process restart persistence. Existing disk, replica and backup limits still apply.

Event redaction preserves the accepted receipt fingerprint. Redelivery verifies its exact stream incarnation, sequence, publication time, event and environment binding before reusing that fingerprint. It never hashes the redaction placeholder as a substitute for the accepted identity. Completion still requires native processed and fan-out proof and settlement of both original effect sets. A new broker sequence repeating a completed redacted event binds to that full native proof and the exact event/delivery metadata, independently of earlier receipt compaction. It does not restore erased payloads or rerun effects. A new sequence with unresolved original effects stays pending.

Redaction alone does not retire pending work. An absent original correlation, a committed winner, or acceptance after the captured WAIT deadline can prove local settlement without the erased payload. A timely unresolved target stays pending, including while its run is paused. Cancellation alone can leave the original WAIT correlation behind; that retained correlation does not establish completion. Deleting the terminal run removes the correlation and then proves its absence. These local proofs do not settle remote execution obligations or restore erased data.

SQL retains at most 100,000 active receipts globally and 50,000 per incarnation. At capacity, consumption fails closed and accepted broker payloads stay pending. The database retains at most 1,024 permanent incarnation fences. Restarting the same file-backed stream reuses its fence; recreating a stream, including an explicitly ephemeral memory-mode restart, consumes another fence. The 1,025th new incarnation refuses registration. No automatic fence retirement or supported in-place counter reset exists. Never delete fences, reset counters or age-prune receipts: old callbacks could otherwise execute again.

At the fence limit, preserve the database and broker state. Fence old writers, consumers, callbacks and automatic restarts, then export and reconcile all pending accepted work and execution obligations before considering a separately reviewed migration into a new isolated instance. There is no general safe finite reset procedure in this release. If reconciliation cannot be proved, keep ingress closed and preserve the old state. Disposable development environments may start with entirely fresh isolated database and broker state only when abandoning all old work is intentional and old actors cannot reach the replacement. Memory mode cannot recover payloads lost on restart from receipt metadata.

Each permanent fence retains the latest 64 explicit admission/poison rejection diagnostics in commit order, containing only sequence and reason code; the fence supplies the incarnation. These diagnostics commit atomically with rejection and survive receipt compaction and database reopen. Retrying a terminal rejection does not append another diagnostic. Oldest-diagnostic rotation is independent of active receipts and the permanent reclaimed floor; it never authorizes replay or removes pending proof. Export diagnostics before rotation if longer history is needed.