File Sink
The local file sink writes JetStream messages to JSON files on a local or
mounted filesystem. It is the second production sink in nats-sinks, alongside
Oracle Database.
If you need disconnected local custody followed by later replay into another
sink, use the Edge Spool Sink instead. FileSink is a final
filesystem destination; SpoolSink is a local custody queue with encrypted
replay records and explicit forwarding semantics.
The file sink is useful when you need a durable handoff directory, a simple audit trail, a development destination without a database, or an integration point for another process that watches files. It follows the same core rule as every sink in this project:
It can also be useful in controlled mission environments where a system needs a simple local handoff, disconnected transfer, or inspection point before a more specialized backend is introduced. The file sink is not a replacement for a records-management system, but it gives a clear durable boundary and preserves the same metadata contract as the database sink.
For reviewable handoff bundles, see the
Cross-Domain Handoff Package
blueprint. The blueprint shows how a file-oriented workflow can group a
manifest, metadata, payload reference, and evidence files without claiming that
nats-sinks is a cross-domain guard or certification boundary.
Commit first. ACK last. Design for redelivery.
For FileSink, a batch is successful only after every required output file has
been written to a temporary file, flushed, optionally fsynced, and atomically
placed at its deterministic final path. The core runtime acknowledges
JetStream messages only after FileSink.write_batch(...) returns success.
Processing Model
sequenceDiagram
participant JS as JetStream
participant R as JetStreamSinkRunner
participant F as FileSink
participant FS as Filesystem
JS->>R: Deliver message batch
R->>F: write_batch(envelopes)
F->>F: Normalize payload and metadata
F->>FS: Write temporary file
F->>FS: Flush and optional fsync
F->>FS: Atomically place final file
F->>FS: Optional parent directory fsync
FS-->>F: Durable file success
F-->>R: Return success
R->>JS: ACK messages
The sink does not call ACK, NAK, TERM, or any other JetStream acknowledgement method. Delivery behavior remains owned by the core runtime.
For operational evidence handling, this matters: the final file path is the durable artifact, and the JetStream ACK is sent only after that artifact has been placed according to the configured durability settings.
Certification Status
FileSink is part of the first-party production sink surface and is covered by
the shared Sink Certification process. Its
certification evidence includes:
- lifecycle and write certification using the reusable helper module,
- duplicate-redelivery certification with deterministic file names and
skip_existing, - atomic temporary-file-to-final-file placement tests,
- gzip and uncompressed output tests,
- encrypted payload envelope storage and decrypt verification,
- JSON, text, bytes, and empty payload handling,
- path traversal and unsafe filename sanitization tests,
- local file e2e tests proving ACK happens after file success.
Live external services are not required for the file sink certification path.
All deterministic tests write under pytest-managed temporary directories or
ignored .local/ paths.
Installation
The file sink has no extra runtime dependencies. It is available with the base package:
pip install nats-sinks
Oracle still requires the Oracle extra:
pip install "nats-sinks[oracle]"
Configuration
Minimal file sink configuration:
{
"nats": {
"url": "nats://localhost:4222",
"stream": "ORDERS",
"consumer": "file-orders-sink",
"subject": "orders.*",
"durable": true
},
"sink": {
"type": "file",
"directory": ".local/file-sink/events",
"filename_strategy": "stream_sequence",
"duplicate_policy": "skip_existing"
}
}
Complete example:
{
"sink": {
"type": "file",
"directory": ".local/file-sink/events",
"mode": "one_file_per_message",
"filename_strategy": "stream_sequence",
"duplicate_policy": "skip_existing",
"payload_mode": "json_or_envelope",
"extension": ".json",
"compression": "none",
"compression_level": 6,
"include_metadata": true,
"partition_by_subject": true,
"create_directory": true,
"fsync": true,
"pretty": false
}
}
Configuration Reference
This section lists every file sink field. The core sections such as nats,
delivery, dead_letter, logging, and metrics are documented in
Configuration.
| Field | Required | Default | Valid values | Description |
|---|---|---|---|---|
type |
yes | none | file |
Selects the local filesystem sink. |
directory |
yes | none | Local or mounted directory path. | Root directory where output files are written. Relative paths are resolved from the process working directory. Production services should normally use an absolute path. |
mode |
no | one_file_per_message |
one_file_per_message |
Writes one durable file per message. This is the only production file mode today because it gives deterministic idempotency and simple replay behavior. |
filename_strategy |
no | stream_sequence |
stream_sequence, message_id, payload_sha256 |
Controls the deterministic filename and therefore the idempotency key. See Filename Strategies. |
duplicate_policy |
no | skip_existing |
skip_existing, overwrite, fail |
Controls what happens when a redelivered message maps to a final file that already exists. See Duplicate Policies. |
payload_mode |
no | json_or_envelope |
json_or_envelope, json_only, text_envelope, bytes_envelope |
Controls how NATS message bytes become JSON storage content. See Payload Mode Values. |
extension |
no | .json, or .json.gz when gzip is enabled and no extension is set |
String beginning with . and containing no path separators. |
File suffix appended to generated filenames. Use .json for plain JSON and .json.gz for gzip-compressed JSON. |
compression |
no | none |
none, gzip |
Compression mode for each output file. gzip uses Python's standard-library gzip module, not an operating-system command. |
compression_level |
no | 6 |
Integer 1 to 9. |
Gzip compression level. Lower values generally use less CPU; higher values may reduce file size for compressible data. Ignored when compression is none. |
include_metadata |
no | true |
true or false. |
Includes the standard nats-sinks metadata document in each output file. Keep enabled for audit, replay, and troubleshooting. |
partition_by_subject |
no | true |
true or false. |
Places files under sanitized subject directories, for example orders.created/ORDERS-0001.json. Disable only when all files should share one directory. |
create_directory |
no | true |
true or false. |
Creates the root output directory if it does not exist. Set false when deployment tooling must create directories explicitly. |
fsync |
no | true |
true or false. |
Flushes file contents and parent directory metadata for stronger crash durability before returning success. Disabling can improve speed but weakens the durable boundary. |
pretty |
no | false |
true or false. |
Pretty-prints JSON with indentation. Useful for manual inspection; compact JSON is faster and smaller. |
Payload Mode Values
payload_mode is a shared nats-sinks concept used by JSON-capable sinks. It is
important for file output because each file is a JSON record even when the
original NATS message body is not JSON.
| Value | Behavior | When to use it |
|---|---|---|
json_or_envelope |
Attempts to parse the body as standards-compliant JSON. Valid JSON is stored as the original JSON value. UTF-8 text that is not JSON, including Python-only constants such as NaN, is wrapped in a text envelope. Non-text bytes are base64-wrapped in a bytes envelope. |
Recommended default for mixed streams. |
json_only |
Requires every body to be standards-compliant JSON. Invalid JSON, text, bytes, malformed JSON, and Python-only constants such as NaN become permanent serialization failures. |
Strict data contracts where non-JSON messages should be sent to DLQ. |
text_envelope |
Treats every body as UTF-8 text and wraps it in the standard text envelope without attempting JSON parsing. Non-UTF-8 bytes fail serialization. | Encrypted text or log streams where parsing as JSON would waste CPU or create ambiguity. |
bytes_envelope |
Treats every body as opaque bytes and stores base64 text inside the standard bytes envelope. | Binary, compressed, encrypted, or otherwise opaque payloads. |
For payloads that arrive from NATS already encrypted by another system, choose
text_envelope when the ciphertext is guaranteed to be UTF-8 text and
bytes_envelope when the ciphertext may contain arbitrary bytes. For
framework-level payload encryption configured with the top-level encryption
section, keep the default json_or_envelope; the core stores ciphertext inside
a valid nats-sinks encrypted JSON payload envelope.
Filename Strategies
filename_strategy decides how a message maps to a deterministic final path.
The strategy should be stable across redelivery so duplicate processing is safe.
| Value | Required message data | Filename shape | Production guidance |
|---|---|---|---|
stream_sequence |
JetStream stream name and stream sequence. | ORDERS-00000000000000000042.json |
Recommended for normal JetStream sinks. |
message_id |
Nats-Msg-Id or equivalent message ID metadata. |
Sanitized message ID plus extension. | Use when publishers reliably set unique message IDs. |
payload_sha256 |
Message subject and payload bytes. | Subject plus payload digest. | Useful for controlled archival flows where identical payloads should collapse. |
Compression Values
| Value | File content | Typical suffix | Notes |
|---|---|---|---|
none |
Plain UTF-8 JSON followed by a newline. | .json |
Fastest and easiest to inspect manually. |
gzip |
Gzip-compressed JSON bytes. | .json.gz |
Saves space for compressible data and uses Python's standard-library gzip module. |
Compression happens after payload normalization and before atomic placement. Both modes keep the same commit-then-ACK rule: the core ACKs only after the file sink returns success.
Compression can reduce storage volume for log-like or report-like operational streams, but it does not change handling classification. A compressed file that contains restricted data should be protected exactly like the uncompressed JSON record.
Idempotency
The file sink is designed for at-least-once delivery. A message may be redelivered after the file has already been committed, especially if the process crashes before the JetStream ACK reaches the server.
The default strategy is stream_sequence, which creates file names from the
JetStream stream name and stream sequence:
ORDERS-00000000000000000042.json
This is the recommended production strategy when the sink consumes from a JetStream stream because stream sequence values are stable and unique inside a stream.
Supported filename strategies:
| Strategy | Behavior | Recommended use |
|---|---|---|
stream_sequence |
Uses stream name plus stream sequence. | Production JetStream sinks. |
message_id |
Uses Nats-Msg-Id or equivalent message ID metadata. |
Streams where publishers reliably set unique message IDs. |
payload_sha256 |
Uses subject plus payload digest. | Controlled archival workflows where identical payloads should collapse. |
If the selected strategy requires metadata that is missing, the sink raises a
framework PermanentSinkError. With DLQ enabled, the core publishes the
message to DLQ and ACKs the original only after DLQ publication succeeds.
Duplicate Policies
| Policy | Behavior | Production guidance |
|---|---|---|
skip_existing |
Existing final file is treated as prior durable success. | Recommended default. |
overwrite |
Existing final file is replaced. | Use only when later metadata replacement is acceptable. |
fail |
Existing final file raises PermanentSinkError. |
Useful for strict diagnostics, not for normal redelivery. |
skip_existing is the safest default because it makes duplicate redelivery
boring: if the file already exists, the durable side effect has already
happened, so the sink can return success and allow the core to ACK.
Output Shape
Each uncompressed file contains a single JSON document:
{
"schema": "nats_sinks.file.message.v1",
"schema_version": 1,
"subject": "mission.reports.created",
"stream": "MISSION",
"stream_sequence": 42,
"consumer": "file-mission-sink",
"consumer_sequence": 12,
"message_id": "R-1001",
"priority": "immediate",
"classification": "NATO SECRET",
"labels": "mission-report;coalition;watch-floor",
"labels_list": ["mission-report", "coalition", "watch-floor"],
"mission_metadata": {
"profile": "mission-event-v1",
"profile_version": 1,
"mission_id": "SYN-MISSION-001",
"f2t2ea_phase": "track"
},
"security_labels": {
"profile": "nats-sinks.security-label.v1",
"priority": "immediate",
"classification": "NATO SECRET",
"labels": ["mission-report", "coalition", "watch-floor"],
"releasability": ["NATO", "FVEY"],
"handling_caveats": ["MISSION"],
"owner": "example-owner",
"originator": "example-originator",
"policy_id": "example-policy",
"retention_category": "mission-log-1y"
},
"custody": null,
"payload": {
"report_id": "R-1001",
"status": "received"
},
"payload_info": {
"original_format": "json",
"wrapped": false,
"sha256": "sha256-of-stored-encrypted-envelope",
"size_bytes": 512
},
"metadata": {
"metadata_version": 1,
"subject": "mission.reports.created",
"headers": {},
"jetstream": {},
"timestamps": {}
}
}
The actual metadata document contains the standard framework metadata
snapshot: headers, known and future Nats-* reserved headers, JetStream stream
and sequence values, epoch nanosecond timing fields, and the normalized
priority, classification, labels, and payload-presence values. When
priority or classification is missing, the file sink writes JSON null rather
than the literal string "null". When labels are missing, the scalar labels
field is JSON null and labels_list is an empty JSON array. When a
headers-only consumer omitted the message body, metadata.payload records
present=false, omitted=true, the omission reason, the delivered byte count,
and the parsed Nats-Msg-Size value when available.
When the core mission_metadata feature is enabled, the file sink writes the
validated object as top-level mission_metadata and also includes it in
metadata.mission_metadata. If mission metadata is absent, both locations use
JSON null. This is the recommended pattern for optional F2T2EA phase tagging
and other mission-support context that should not become fixed generic
framework fields. See Mission Metadata and
F2T2EA Event Phase Tagging.
When top-level security_labels.enabled is true, the file sink writes the
validated data-centric security label profile as top-level security_labels
and also includes it in metadata.security_labels. If the profile is absent,
both locations use JSON null. The profile can carry releasability, handling
caveats, owner, originator, policy ID, and retention category without turning
every policy concept into a fixed file-sink field. See
Data-Centric Security Label Profile.
The generic routing policy can name a file destination as a logical target,
for example file_secret_audit. When the active sink is fanout, that
logical target binds to a named file sink instance. The file child sink keeps
the normal file durability contract: it returns success only after the output
file has been placed according to its configured duplicate, compression,
atomic-write, and fsync settings. Required file targets must complete before
ACK; optional file targets receive a bounded wait window for side-copy use
cases.
For broader file-based handoff, edge operation, classification, labels, and audit examples, see Defence And Mission Support.
If a file workflow is used to stage review packages, generate package directory names and file names from validated internal identifiers. Do not allow publisher-controlled values to become raw path components, and keep package size, file count, manifest size, metadata size, and payload size bounded.
When top-level custody.enabled is true, the file sink writes the custody
object as a top-level custody field. This object contains hashes for the
normalized payload and stable metadata before the file was written. It gives
auditors a deterministic value to recompute later, while the normal
commit-then-ACK rule still depends on the file being atomically placed.
Example custody object:
{
"custody": {
"schema": "nats_sinks.custody.v1",
"schema_version": 1,
"algorithm": "sha256",
"hash_input_format": "canonical-json",
"key_id": "custody-policy-v1",
"payload_hash": "hex-encoded-payload-hash",
"metadata_hash": "hex-encoded-metadata-hash",
"record_hash": "hex-encoded-record-hash",
"previous_record_hash": null,
"hash_payload": true,
"hash_metadata": true,
"privacy": "hashes_are_not_encryption"
}
}
See Tamper-Evident Custody Metadata for the full configuration model and privacy notes.
Output Shape With Payload Encryption
When top-level payload encryption is enabled, the top-level operational fields remain readable and the body moves into the encrypted payload envelope. This is the visible effect of enabling encryption for the same message:
{
"schema": "nats_sinks.file.message.v1",
"schema_version": 1,
"subject": "mission.reports.created",
"stream": "MISSION",
"stream_sequence": 42,
"consumer": "file-mission-sink",
"consumer_sequence": 12,
"message_id": "R-1001",
"priority": "immediate",
"classification": "NATO SECRET",
"labels": "mission-report;coalition;watch-floor",
"labels_list": ["mission-report", "coalition", "watch-floor"],
"mission_metadata": {
"profile": "mission-event-v1",
"mission_id": "SYN-MISSION-001",
"f2t2ea_phase": "track"
},
"security_labels": {
"profile": "nats-sinks.security-label.v1",
"priority": "immediate",
"classification": "NATO SECRET",
"labels": ["mission-report", "coalition", "watch-floor"],
"releasability": ["NATO", "FVEY"],
"handling_caveats": ["MISSION"],
"owner": "example-owner",
"originator": "example-originator",
"policy_id": "example-policy",
"retention_category": "mission-log-1y"
},
"payload": {
"_nats_sinks_encryption": {
"schema": "nats_sinks.encrypted_payload.v1",
"version": 1,
"algorithm": "aes-256-gcm",
"key_id": "mission-prod-2026-05",
"nonce": "base64-nonce",
"nonce_size_bytes": 12,
"ciphertext": "base64-ciphertext-and-tag",
"ciphertext_encoding": "base64",
"tag_length": 16,
"plaintext_sha256": "hex-encoded-sha256",
"plaintext_size_bytes": 43
}
},
"payload_info": {
"original_format": "json",
"wrapped": false,
"sha256": "redacted-example",
"size_bytes": 43
},
"metadata": {
"message_metadata": {
"priority": "immediate",
"classification": "NATO SECRET",
"labels": ["mission-report", "coalition", "watch-floor"]
}
}
}
The file name and idempotency behavior should still use stable metadata such as
stream sequence or message ID. Do not use payload_sha256 with core payload
encryption unless you explicitly accept that fresh nonces can produce different
ciphertext for the same redelivered message.
Compression
The file sink can gzip-compress each output file by setting
compression: "gzip":
{
"sink": {
"type": "file",
"directory": ".local/file-sink/events",
"compression": "gzip",
"compression_level": 6
}
}
When gzip is enabled and extension is not explicitly configured, the sink
uses .json.gz as the default extension:
ORDERS-00000000000000000042.json.gz
Compression happens after the message has been normalized into the standard
JSON file record and before the temporary file is flushed, optionally fsynced,
and atomically placed. This means compressed and uncompressed files have the
same durable boundary: write_batch(...) returns success only after the final
file has been placed according to the configured durability settings.
Gzip is implemented with Python's standard-library gzip module. The file sink
does not call an operating-system gzip command, does not require a gzip binary
to be installed on the host, and does not invoke a shell for compression. This
keeps behavior portable across Oracle Linux, Debian, macOS, containers, and CI.
Compression is useful for JSON, text, logs, and repeated structured messages.
It may provide little benefit for encrypted or already-compressed payloads and
can add CPU cost. For encrypted text streams, test both compression: "none"
and compression: "gzip" with realistic payloads before enabling gzip in
production.
Compressed output uses a deterministic gzip timestamp so two otherwise identical records do not differ only because they were compressed at different times. The JSON record itself may still contain processing timestamps when metadata is enabled.
If you need a custom extension, configure it explicitly:
{
"sink": {
"type": "file",
"directory": ".local/file-sink/events",
"compression": "gzip",
"extension": ".event.json.gz"
}
}
Payload Handling
The file sink uses the same payload normalization contract as Oracle and future JSON-capable sinks:
- valid JSON is stored as JSON,
- non-JSON UTF-8 text is wrapped in a JSON envelope,
- non-text bytes are base64-wrapped in a JSON envelope,
- empty payloads are represented safely and do not crash the sink.
For encrypted text streams where the ciphertext may or may not decrypt to JSON later, use:
{
"sink": {
"type": "file",
"payload_mode": "text_envelope"
}
}
That mode avoids repeated JSON parse attempts and stores every body in the standard text envelope.
Core Payload Encryption
When top-level payload encryption is enabled, the core runner encrypts the
message body before FileSink.write_batch(...) is called. The file sink stores
the encrypted JSON payload envelope in the payload field and still writes
normal subject, stream, header, and timing metadata in the rest of the file
record.
sequenceDiagram
participant R as JetStreamSinkRunner
participant E as PayloadEncryptor
participant F as FileSink
participant FS as Filesystem
R->>E: Encrypt NatsEnvelope.data
E-->>R: Encrypted JSON payload envelope
R->>F: write_batch(encrypted envelope)
F->>FS: Write JSON file record
FS-->>F: Durable file placement
F-->>R: Return success
Example:
{
"encryption": {
"enabled": true,
"algorithm": "aes-256-gcm",
"key_id": "file-prod-2026-05",
"key_b64_env": "NATS_SINKS_PAYLOAD_KEY_B64"
},
"sink": {
"type": "file",
"directory": "/var/lib/nats-sinks/events",
"filename_strategy": "stream_sequence",
"duplicate_policy": "skip_existing",
"payload_mode": "json_or_envelope"
}
}
The file output remains a JSON document. The encrypted body appears under
payload._nats_sinks_encryption, and metadata remains clear:
{
"schema": "nats_sinks.file.message.v1",
"subject": "mission.reports.created",
"priority": "immediate",
"classification": "NATO SECRET",
"labels": "mission-report;coalition;watch-floor",
"labels_list": ["mission-report", "coalition", "watch-floor"],
"payload": {
"_nats_sinks_encryption": {
"schema": "nats_sinks.encrypted_payload.v1",
"algorithm": "aes-256-gcm",
"key_id": "mission-prod-2026-05"
}
}
}
Use filename_strategy: "stream_sequence" or message_id when encryption is
enabled. The payload_sha256 filename strategy hashes the encrypted payload
bytes it receives; because the core uses a fresh nonce per encryption, the same
redelivered message can produce different ciphertext. Stable JetStream metadata
is safer for duplicate handling.
Filesystem Safety
Subjects, streams, and message IDs are external input. The file sink never uses them as raw path names. It sanitizes path components with an allow-list and verifies that the resolved destination remains under the configured root directory.
The default output layout partitions by subject:
.local/file-sink/events/
orders.created/
ORDERS-00000000000000000001.json
ORDERS-00000000000000000002.json
With gzip compression enabled, the same layout uses compressed file suffixes:
.local/file-sink/events/
orders.created/
ORDERS-00000000000000000001.json.gz
ORDERS-00000000000000000002.json.gz
Characters that are unsafe for path components are replaced with _. Very long
components are truncated with a digest suffix so they remain deterministic
without creating oversized filenames.
Failure Behavior
flowchart TD
Start[write_batch starts] --> Map[Map envelope to safe path and JSON record]
Map --> Temp[Write temporary file]
Temp --> Flush[Flush and optional fsync]
Flush --> Place[Atomic final placement]
Place --> Done[Return success]
Done --> Ack[Core ACKs JetStream message]
Map -->|Missing key metadata| Permanent[PermanentSinkError]
Temp -->|Permission or disk error| Temporary[DestinationUnavailableError]
Place -->|Duplicate and fail policy| Permanent
Temporary --> Redeliver[No ACK; message can redeliver]
Permanent --> DLQ[DLQ when configured]
Failure examples:
| Scenario | Sink behavior | Core behavior |
|---|---|---|
| Output directory cannot be created | Raises DestinationUnavailableError. |
No ACK; message remains eligible for redelivery. |
| Disk is full during temporary file write | Raises DestinationUnavailableError. |
No ACK; message remains eligible for redelivery. |
Stream sequence is missing with stream_sequence strategy |
Raises PermanentSinkError. |
DLQ then ACK if DLQ succeeds. |
Duplicate file exists with skip_existing |
Treats as success. | ACK after sink returns success. |
Duplicate file exists with fail |
Raises PermanentSinkError. |
DLQ then ACK if DLQ succeeds. |
| Subject contains path traversal text | Sanitizes path component. | File remains under configured root. |
| Payload is not JSON | Wraps text or bytes in JSON envelope. | ACK after durable write succeeds. |
| Empty payload | Wraps empty text in JSON envelope. | ACK after durable write succeeds. |
| Gzip compression is enabled | Writes gzip-compressed JSON records. | ACK after compressed file placement succeeds. |
| Parent directory is a symlink outside root | Rejects escaped resolved path. | No ACK; message can redeliver after operator fixes path. |
| Fsync fails | Raises DestinationUnavailableError. |
No ACK; message can redeliver. |
Throughput Notes
The file sink is optimized for correctness first. It writes compact JSON by default and moves filesystem work into a worker thread so the NATS event loop does not block on local disk I/O.
For higher throughput:
- keep
prettyset tofalse, - keep
compressionset tononewhen CPU is the bottleneck or payloads are already encrypted/compressed, - use
compression: "gzip"when disk capacity or file transfer size is the bottleneck and payloads compress well, - use a fast local disk or low-latency mounted volume,
- tune
delivery.batch_size, - keep
partition_by_subjectenabled for large subject sets, - consider
fsync: falseonly when the surrounding storage layer provides an acceptable durability boundary.
Disabling fsync can improve speed, but it weakens crash durability. If the
process returns success and the core ACKs, the file should already be durable
according to the configured policy. Keep fsync: true when local file loss
after a host crash is unacceptable.
Python API
from nats_sinks import JetStreamSinkRunner
from nats_sinks.file import FileSink
sink = FileSink(
directory="/var/lib/nats-sinks/events",
filename_strategy="stream_sequence",
duplicate_policy="skip_existing",
)
runner = JetStreamSinkRunner(
nats_url="nats://localhost:4222",
stream="ORDERS",
consumer="file-orders-sink",
subject="orders.*",
sink=sink,
)
await runner.run()
Local Example
The repository includes a tracked example at examples/file-basic/config.json.
It writes generated files under .local/file-sink/events, which is ignored by
git.
nats-sink validate examples/file-basic/config.json
nats-sink test-sink examples/file-basic/config.json
To run against local NATS:
nats-server -js -m 8222
nats stream add ORDERS --subjects "orders.*"
nats-sink run examples/file-basic/config.json
nats pub orders.created '{"order_id":"O-1001","amount":42.50}'
For a complete mission-support file handoff pattern, including compression, duplicate handling, failure behavior, and test guidance, see Disconnected File Handoff.
Production Recommendations
- Run the service as a dedicated operating system user.
- Use an absolute output directory in production service configs.
- Make the output directory writable only by the sink service and trusted operators.
- Keep generated files out of the source repository.
- Monitor disk usage and inode usage.
- Back up or rotate files according to your retention policy.
- Prefer
stream_sequenceplusskip_existingfor JetStream consumers. - Keep payload logging disabled unless a deployment has explicit approval to log message bodies.