Files
GrandPlan/docs/superpowers/specs/2026-07-28-flowfile-packaging-signing-design.md
T
Ubuntu 0bae727a26 Add attribute governance design: controlled attribute lists, egress regex selection, ingest signature validation
Defines the SA-V3-Ext/Int placeholder attribute scheme (external list
owned by the S3 handover spec, internal list for SA routing + internal
S3 usage), the per-egress Regex property for packing/signing selection,
the inverse-regex delete step at true external egress, optional
ingest-time signing with mandatory validation when already signed, and
the S3 JSON-fallback size attribute for suffix-range trailer reads.
2026-07-28 07:49:21 +00:00

35 KiB

FlowFile Packaging & Signing — Design Spec

Date: 2026-07-28 Status: Draft — pending user review

Purpose

NiFi is used to manage complex workflows with payloads ranging from tiny files to TB scale. Two related problems need solving:

  1. Performance. The current pack/unpack path (MergeContent + UnpackContent) is disk-I/O bound under high throughput. Content lives on vSAN storage, which is shared, virtualized IOPS across the vSphere cluster — so the pain isn't any single slow disk, it's contention across every VM sharing that storage. The current flow makes multiple redundant passes over content on disk: an ingest write, a separate read to compute a content hash, a read+write to merge/pack, and a read+write to unpack. Each pass competes for the same shared IOPS pool. NiFi runs as independent single-node instances (no clustering), each with its own logical function, scaled horizontally by adding more instances — so there's no cluster-wide load balancing to fall back on.

  2. Integrity & non-repudiation. When a FlowFile (content + attributes) is packed and exported outside NiFi's space, its attributes — which already include a hash of the content, so content hashing is not part of packing — need to be signed to prove they haven't been tampered with and to prove who produced them, in a way that holds up even to a party who didn't produce the artifact. Uses for the packed form: storage/ retrieval to/from S3, delivery to other applications via HTTP or filesystem, and selective (date/category) replay from a catalog database.

A future requirement extends this to TB-scale files that must be broken into pieces, tracked, transited through NiFi (for routing and content-assurance controls — all content must pass through NiFi), and reconstructed at the destination — including across a one-way network boundary — referred to throughout this doc as MR, the named relay bridging the two NiFi host chains within the overall system referred to as SA (see Deployment Topology) — with no return channel for retransmission requests. Forward error correction for that hop is assumed to be available via an existing HTTP service, with ~1GB as the target chunk size fed to it.

Non-goals

  • Redesigning NiFi's clustering model. Instances remain independent, single-node, horizontally scaled by function.
  • Automated recovery signaling across the true one-way diode boundary — by construction there is no return channel; recovery there is necessarily operator-mediated (see Error Handling).
  • Building or operating the forward-error-correction transport itself — assumed to already exist as an HTTP service outside this design's scope.
  • A general-purpose NiFi improvement library. This design covers exactly the packaging, chunking, signing, delivery, and replay-indexing concerns described above.

Relationship to the "Phat Files" design

A separate, already-approved spec (2026-07-23-phat-files-design.md) covers low-side-S3-to-high-side-S3 transfer of TB-scale files across a one-way boundary: fixed-size blocks, resend-vs-duplicate handling, staging by block index, server-side assembly via UploadPartCopy, stall monitoring, and cleanup. That spec is treated here as a source of proven concepts, not a fixed dependency — this design is not an extension of it and does not modify it. The following ideas are adopted (generalized beyond S3-to-S3):

  • Streaming byte-range reads; never buffer a whole file.
  • Chunk descriptor attributes (index, total, size, checksum).
  • Server-side assembly via UploadPartCopy from independently-staged per-chunk objects, rather than a live multipart-upload session spanning the transport.
  • Explicit resend-vs-duplicate distinction via a flag, not inferred from bytes.
  • Stall detection + missing-chunk report, for hops with no return channel.
  • Testing convention: pure logic extracted to statically-testable methods.

What's changed: packaging moves from stock FlowFile Package V3 to the custom manifest-trailer format below (needed for signing and attribute-independent reads); staging/monitoring/cleanup responsibilities move to an external service rather than NiFi timer-driven processors; completion tracking is dual-written to a durable marker and a catalog database instead of an S3-only marker object.

Architecture

Deployment Topology

The whole system described in this doc is referred to as SA: a single system containing two chains of independent, single-function NiFi hosts linked by Site-to-Site (per the existing horizontal-scaling model — no clustering, just chained instances), bridged in the middle by MR — the named one-way relay this design has been built around (FEC via an HTTP service, ~1GB chunks, per Purpose). Content flows in one direction through SA: external S3/S2S in, through the first chain, across MR, through the second chain, external S3/S2S out. The Replay DB branches off before MR, from node 1 of the first chain — i.e. at the true ingest point into SA, not after delivery.

   +------------------------------------ SA -------------------------------------+
   |                                                                                |
   | S3 --\                                                                /-- S3     |
   | S2S --+-->[1]--S2S-->[2]--S2S-->[3]--HTTP-->[MR]--HTTP-->[1]--S2S-->[2]--S2S-->[3]--+-- S2S |
   |           |                                                                        |
   |         "copy"                                                                     |
   |           v                                                                        |
   |     +-----------+                                                                  |
   |     | Replay DB  |                                                                  |
   |     +-----------+                                                                  |
   |      /          \                                                                  |
   |     v            v                                                                  |
   | S3 (large files) XFS (small files)                                                  |
   +------------------------------------------------------------------------------------+

Chunking is applied uniformly at ingest (below) specifically so the same chunked representation transits both the intra-chain S2S hops and the MR relay — there's no separate chunking scheme per transport. Hashing happens at this same ingest point — node 1 of the first chain, the true entry into SA — consistent with the single-pass design below.

Per-FlowFile Pipeline

                              INGEST (custom NiFi processor;
                                       replaces ListenHTTP for HTTP,
                                       or first-in-flow processor
                                       right after the S2S Input Port)
                                         |
                                         v
                     Single streaming pass: write content to NiFi's
                     content repo (required — all content must transit
                     NiFi for routing/content-assurance) WHILE computing
                     the content hash and emitting a chunk boundary every
                     ~1GB. No pre-ingest size check or path branching —
                     chunk count falls out of content length; a small file
                     is just the one-chunk case.
                                         |
                                         v
                     Optionally (ingest-time signing is a configurable
                     property, see Attribute Governance): ordered
                     attribute list (incl. content hash, chunk
                     descriptors) is clear-signed in-process (Bouncy
                     Castle OpenPGP, key via Controller Service — see
                     Signing) and stored as a new attribute.
                                         |
                                         v
                     FlowFile(s) proceed through normal NiFi routing /
                     content-assurance flow logic.
                                         |
                                         v
                     DELIVERY: per-chunk content streamed once (single
                     content-repo read) to destination staging.
                                         |
                                         v
                 +-----------------------------------------------+
                 |                  DESTINATIONS                    |
                 |  S3: stage per-chunk objects, assemble via        |
                 |       UploadPartCopy (no re-transfer)             |
                 |  filesystem: pwrite chunks at offset, preallocated|
                 |  HTTP: deliver assembled object to receiving app  |
                 |  diode: chunks -> FEC HTTP service (~1GB each)    |
                 +-----------------------------------------------+
                             |            |
                     write marker    upsert row
                     (durable,        (fast index,
                      per-dest)        rebuildable
                             |          from markers)
                             v            v
                 +-----------------------------------------------+
                 |     EXTERNAL SERVICE (post-NiFi only):            |
                 |     assembly (UploadPartCopy), stall monitoring,  |
                 |     cleanup, catalog DB maintenance                |
                 +-----------------------------------------------+
                             |
                             v
                 +-----------------------------------------------+
                 |     CATALOG / REPLAY DATABASE                    |
                 +-----------------------------------------------+

A prior iteration of this design considered a sidecar process fronting ingestion (to bypass NiFi's content repository entirely for large payloads, via a claim-check pattern). That's rejected: all content must go through NiFi for routing and content-assurance controls, which removes the main justification for a front door outside the JVM. The external service's role is narrowed to post-NiFi work that never needs to see raw content: assembly, monitoring, cleanup, and catalog indexing.

Attribute Governance

Naming convention (placeholder): until the concrete attribute list is enumerated (see Open Items), controlled attributes are referred to by placeholder names — external attributes as SA-V3-Ext-01SA-V3-Ext-15, internal attributes as SA-V3-Int-01SA-V3-Int-NN. These stand in for the real names throughout this doc and in code until finalized.

Placeholder regexes, used for matching against these placeholder names until the real attribute list replaces them:

  • External allowlist: ^SA-V3-Ext-(0[1-9]|1[0-5])$ (matches SA-V3-Ext-01 through SA-V3-Ext-15).
  • Internal: ^SA-V3-Int-\d{2}$ (count not yet fixed — placeholder covers any two-digit index until NN is finalized).

The content hash algorithm is not fixed by this design. It is exposed as a dynamic property on the custom ingest processor (and mirrored on the corresponding egress/delivery processor), so it can be changed per deployment without a code change. Whichever algorithm is configured is recorded as one of SA's controlled attributes and carried with the FlowFile, so the algorithm used is self-describing at verification time rather than assumed out-of-band.

SA maintains a fixed set of roughly 15 controlled external attributes (SA-V3-Ext-##) — attributes with an externally-defined meaning/contract, as opposed to SA-internal bookkeeping — that are carried through unchanged from ingest to final destination. This list is owned by the existing S3 handover interface spec (see Storage Backend Handover), not authored by this design — this design consumes it. Only a subset of the ~15 are mandatory; the rest are optional, so their absence on a given FlowFile is not itself an error. The permissible input/read attribute set is the same as the permissible write/output attribute set — one list governs both directions of external S3 interaction, not two independently-defined ones (this resolves the write-side/read-side reconciliation concern raised earlier — see Open Items for what's still needed to finalize it). Which of the permissible attributes actually get packed and signed at a given egress point is a further, configurable selection — see Egress Packing & Signing Selection below — so "the attribute list" referenced elsewhere in this doc means this controlled set (or a configured subset of it), not the full/arbitrary FlowFile attribute map. The concrete list is not yet provided — only the placeholder names/regex above exist today.

SA also uses a separate set of controlled internal attributes (SA-V3-Int-##) for its own routing and processing needs, and — new — for internal S3 usage: the reassembly/staging space (per-chunk staging/<generation-id>/<chunk-index> objects) and the replay buffer (the Replay DB's S3 tier for large files) are internal S3, distinct from the external S3 interaction governed by the handover spec above. Internal S3 objects may carry this additional permissible-internal- attribute set — itself also not yet provided, count TBD (NN). Internal attributes are never part of the externally-defined contract and are never packed or signed for delivery — but they are allowed to cross the MR hop: MR bridges the two NiFi chains within SA, not SA's external boundary, so internal attributes travel with the FlowFile through MR like any other attribute. They're stripped only at SA's true external egress (final delivery to S3/HTTP/filesystem, outside SA) — internal S3 (staging, replay buffer) is itself inside that boundary, not the egress point.

Two boundary filters enforce this split, both implemented as a delete-attributes-by-regex step (a standard NiFi capability, e.g. an UpdateAttribute "Delete Attributes Expression"):

  • On import (ingest): attributes not matching the external allowlist regex (^SA-V3-Ext-(0[1-9]|1[0-5])$) are deleted. Uncontrolled/unexpected external input never enters SA's attribute space.
  • On external egress (final delivery — not the MR hop): the inverse of the external allowlist regex is applied to delete everything that does not match ^SA-V3-Ext-(0[1-9]|1[0-5])$. This is a single step that removes internal (SA-V3-Int-##) attributes and any other stray/uncontrolled attributes at once — it doesn't depend on separately enumerating what "internal" means, only on what counts as a valid external attribute. Only controlled external attributes reach S3/ HTTP/filesystem destinations.

Egress Packing & Signing Selection

Each egress processor exposes a Regex property naming which of the controlled external (SA-V3-Ext-##) attributes get packed and signed at that point in the flow. This is deliberately per-processor rather than one fixed set for every egress point, so a flow author can configure a different subset at each position in the flow depending on what that position needs to expose.

For S3 destinations specifically:

  • Attributes matched by the regex whose combined key=value size is under 2KB when URL-encoded are written as S3 user metadata (x-amz-meta-*) — also selected via the same Regex property.
  • Attributes that don't fit under that 2KB threshold are instead appended in JSON form per Packaged Artifact Format, matching the existing >2KB fallback path described in Storage Backend Handover.

This regex-driven selection governs the write side — which attributes this design packs or promotes to metadata. It's a distinct concern from the read-side regex in the existing S3 handover interface spec (which maps x-amz-meta-* keys back to NiFi attributes on read) — the two need to be reconciled so a value written via this design's regex is correctly recognized on read by that existing spec. Not yet confirmed; see Open Items.

Signing at ingest is optional, not mandatory — a configurable property on the ingest processor (see also Signing). Egress-time packing/signing via the Regex property above is what actually determines the contents of the final signed, packed artifact; ingest-time signing, when enabled, is an earlier integrity checkpoint within SA, not a substitute for it.

Packaged Artifact Format

This is the format used for filesystem/XFS delivery always, and for S3 delivery when the attribute set is too large for S3 user object metadata (see Storage Backend Handover). Content and its manifest travel as one object, not two, for end-to-end loss protection: two separate objects (content + manifest) have independent failure/lifecycle/replication paths that can diverge — e.g. content lands but the manifest write fails, or a lifecycle rule reaps one and not the other. A single completed write (one PutObject, or one completed multipart upload) can't end up in that split state.

   PACKAGED OBJECT (single PutObject, or single completed multipart upload)
   +---------------------------------------------------------+
   |   CONTENT (1 object, or N ~1GB parts via multipart)       |
   +---------------------------------------------------------+
   |   MANIFEST TRAILER (final part -- direct UploadPart, not  |
   |   UploadPartCopy, so it can be arbitrarily small)          |
   |   = JSON document, e.g.:                                   |
   |    { "format_version": ...,                                |
   |      "clearsign": "-----BEGIN PGP SIGNED MESSAGE-----      |
   |                     ...ordered attribute list...           |
   |                     -----BEGIN PGP SIGNATURE-----...      |
   |                     -----END PGP SIGNATURE-----" }         |
   |   The clearsign field is self-contained and human-         |
   |   verifiable via `gpg --verify` independent of the JSON     |
   |   wrapper around it.                                       |
   +---------------------------------------------------------+
   |   FOOTER (fixed size, e.g. last 12 bytes: magic + 8-byte   |
   |   big-endian trailer length) -- self-describing, storage-  |
   |   agnostic; works identically on S3, filesystem, and HTTP  |
   +---------------------------------------------------------+

   S3 object tag/attribute (attr-size = trailer byte length), written
   whenever the JSON-fallback flag is set (see Storage Backend Handover ->
   S3 path): lets a reader jump straight to the trailer's byte range via a
   suffix-range GET without reading the footer first. On S3 this is the
   primary mechanism; the footer remains the storage-agnostic fallback for
   filesystem/HTTP, which have no equivalent tag/metadata mechanism.

Manifest is written after content by construction — the ingest pass is single-streaming, so the final content hash isn't known until content is fully read, ruling out a header-first format (which would require either buffering to know its size upfront, or a second pass to fix it up).

Attribute-only reads (replay filtering by date/category, signature verification, routing) use a suffix-range read — Range: bytes=-N where N is the trailer size (from the catalog DB, the S3 tag, or the footer) — which never touches the (possibly TB-scale) content bytes and completes in one round trip when the DB already knows the offset.

For chunked files, S3 multipart uploads mix UploadPartCopy parts (the staged content chunks) with one ordinary UploadPart (the manifest trailer — no 5MB minimum applies to the last part), so CompleteMultipartUpload produces one final object with the same content-then-trailer-then-footer shape regardless of size.

Signing

  • Where signing is triggered: ingest-time signing is optional (configurable per ingest processor); egress-time signing, of the attribute subset selected by the egress Regex property, is what actually produces the signed content shipped in the final artifact — see Egress Packing & Signing Selection.
  • Ingest-time validation: incoming data at ingest may or may not already carry a signature (e.g. from an earlier stage, or on replay). Absence is fine — ingest signing is optional, per above. But if a signature is already present on ingest, it must be validated at that point rather than passed through unchecked; see Error Handling for the failure case.
  • Format: OpenPGP detached-style trailer via gpg --clearsign semantics — clear-sign the canonical ordered attribute list (sorted by attribute key, including the content hash and any chunk descriptors), producing a self-contained ASCII-armored block (plaintext + embedded signature). The result is stored as a new FlowFile attribute, then becomes the clearsign field of the JSON manifest trailer at pack time (see Packaged Artifact Format) — the JSON wrapper carries packaging metadata (format version, chunk descriptors) alongside the signed block, without altering the signed bytes themselves.
  • Why clear-sign / OpenPGP over a bespoke signature scheme: an operator on the far side of the one-way diode boundary can verify an artifact by hand with stock GnuPG and a public key — no custom SDK needed. This matters because that boundary already has a manual-operator recovery model (missing-chunk reports carried by hand, per Phat Files); OpenPGP fits that operational reality directly.
  • Where signing runs: in-process, via Bouncy Castle's OpenPGP API (not shelling out to the gpg CLI per FlowFile — too slow at throughput). No external signing service — this was evaluated and explicitly rejected in favor of keeping the architecture self-contained within NiFi. The private key is loaded via a Controller Service (same pattern as the existing AwsCredentialsProviderService), plain in-JVM key material protected by NiFi's sensitive-property encryption and OS-level file permissions.
  • Non-repudiation trade-off, stated explicitly: non-repudiation depends on the private key being exclusively controlled by one identity. Moving signing in-process (vs. a network-isolated external service) means the key now lives on the same host as ingestion, so custody depends on host-level protections rather than network isolation. Mitigations: only the NiFi instances actually responsible for packing/export have the signing Controller Service configured (not every instance in the deployment); NiFi's provenance repository gives an audit trail of which flow/processor invoked signing and when, partially substituting for the audit log a dedicated signing service would give for free. This is not a one-way door — the Controller Service abstraction means a PKCS11/HSM token could be swapped in later as a stronger key-custody option without changing processor logic, if ever required.
  • Algorithm: Ed25519 preferred for speed and small signature size at throughput, unless a compliance requirement (e.g. FIPS mode) forces RSA/ECDSA instead — not yet confirmed as a constraint.
  • Tamper-detection vs. non-repudiation: a symmetric HMAC alternative was considered and rejected. HMAC would detect unauthorized modification (any party without the shared secret can't forge a valid tag) but cannot prove authorship to a third party, since every verifier necessarily holds the same secret and could in principle have produced it themselves. The stated requirement is non-repudiation, which needs an asymmetric scheme.

Storage Backend Handover

Final storage backend is chosen by size, separate from the ingest-time chunking decision (which is uniform, no threshold — see Architecture): large files land in S3, small files land on a local/shared XFS filesystem. This split exists because S3's per-request overhead and cost make it a poor fit for very high volumes of tiny objects; XFS is cheaper and faster for that case. Open question, not yet resolved: if S3 can be shown to serve small files efficiently enough (e.g. via batching, a different bucket/storage class, or acceptable cost at the actual small-file volume expected), the XFS tier could be dropped and S3 used uniformly — this needs validating against real small-file throughput numbers before implementation, see Open Items. The JSON-trailer packaged artifact format is shared by both backends, but S3 additionally supports an existing metadata-based mechanism for small attribute sets that avoids the trailer entirely — see below.

This section (S3 path) covers external S3 — SA's true delivery boundary. It's distinct from internal S3 — the reassembly/staging space and replay buffer used within SA — which is governed by its own additional permissible-internal-attribute set instead of the handover spec below; see Attribute Governance.

S3 path

Attribute handover on external S3 follows an existing, established handover interface specification already used elsewhere in this environment — this design integrates with it rather than replacing it. That spec defines the ~15 controlled external attributes (SA-V3-Ext-## placeholder), and the same list governs both directions — what's permissible to read is permissible to write, so there's one shared attribute contract for external S3, not independently-evolved read and write lists. Under that spec:

  • Attributes are written as S3 user object metadata (x-amz-meta-* headers) when small enough to fit. A regular expression (defined by that existing spec, not this one) identifies which user metadata keys get turned into NiFi attributes on read — so consuming the attributes is a single HeadObject call, no content touch, not even a suffix-range read. Which attributes from that permissible list actually get written as metadata at a given egress point is a separate, this-design-owned selection (a configured subset, not necessarily all permissible ones) — see Egress Packing & Signing Selection.
  • A flag (also part of that existing spec) records whether the attribute set exceeded S3's ~2KB user-metadata limit. When it does, the object falls back to this design's JSON manifest trailer (Packaged Artifact Format) instead of metadata.
  • Whenever that fallback flag is set, this design also writes a second attribute giving the byte size of the appended JSON manifest trailer. Together, the two let a reader do a single suffix-range GETRange: bytes=-N with N from that size attribute — to fetch exactly the appended JSON and parse it directly into attributes, with no need to read the fixed footer first (see Packaged Artifact Format).
  • The exact regex pattern and flag key/tag name live in the existing handover interface spec document, not reproduced here — see Open Items.

So for S3 specifically, the JSON trailer is the fallback path (oversized attribute sets), not the only mechanism — small-attribute objects never get a trailer at all. Filesystem/XFS delivery, which has no equivalent native metadata mechanism, always uses the JSON trailer.

   NiFi (per chunk FlowFile, content already in repo from ingest)
        |
        |  PutObject  (single read of the content-repo claim --
        v   the only disk touch on the delivery side)
   S3 staging:  staging/<generation-id>/<chunk-index>
        |
        |  (all N chunks staged; external service detects
        v   completeness via catalog DB or S3 listing)
   UploadPartCopy x N  -- server-side, zero bytes touch NiFi or the
        |                external service
        v
   Attributes fit in 2KB?
     yes -> PutObjectMetadata / CompleteMultipartUpload with attributes
            as x-amz-meta-* headers (no trailer)
     no  -> + UploadPart (JSON manifest trailer), oversized flag set
        v
   Final object at <key>
        |
        v
   write durable completion marker + upsert catalog DB row
        |
        v
   delete staging/<generation-id>/*  (cleanup)

XFS path (small files, below the size threshold)

No staging/assembly needed — small files are single-chunk by construction (Architecture), so NiFi writes the packaged object (content + JSON trailer

  • footer) directly to its final path in one write() call. The external service still writes the durable completion marker (a sibling file, or an xattr, depending on implementation preference) and upserts the catalog DB row, same as the S3 path, so replay and reconciliation behave identically regardless of backend.

Chunk upload failures for plain S3/filesystem/HTTP delivery are ordinary NiFi FlowFile retry — Phat Files' resend/stall-report machinery is reserved for the actual one-way diode hop, where there's no return channel; applying it to destinations that do have bidirectional connectivity would be unnecessary complexity.

Catalog / Replay Database

One row per packed artifact, written alongside the durable completion marker (same write-marker-then-upsert-row ordering as Section "Architecture"). Indexed for date/category replay queries: which backend (S3 or XFS), object location (S3 key or filesystem path), the clear-signed attribute list (so filtering never touches the object), completion status, and generation/chunk-set id for large files.

The database is explicitly a rebuildable index, not a source of truth — the durable marker plus the trailer/footer on disk or S3 is authoritative, and a reconciliation job can rebuild the catalog by scanning objects and reading trailers if the database is ever lost, corrupted, or migrated. Replay is: query by date/category → get object locations → re-inject as FlowFiles via the same ingest path used for first-time delivery.

Error Handling

Condition Handling
Checksum mismatch on chunk arrival Route to failure, never staged
Resend vs. unrequested duplicate Explicit flag on the FlowFile, checked against the durable completion marker (adopted from Phat Files)
Diode hop: chunks stop arriving No return channel by design; stall detection produces a missing-chunk report an operator carries back manually (adopted from Phat Files). Loss tolerance in transit is handled by the external FEC HTTP service, out of scope here.
Non-diode destination delivery failure (S3/filesystem/HTTP) Ordinary NiFi FlowFile retry — no resend/duplicate machinery needed, a return channel exists
Signature or trailer corruption on read Rejected outright — never treated as a valid artifact for replay or delivery, same handling as a checksum mismatch
Data arrives at ingest with no existing signature Not an error — ingest-time signing is optional (see Signing); proceeds normally
Data arrives at ingest already signed, signature fails validation Route to failure — an already-signed FlowFile at ingest is validated on arrival, not passed through unchecked
Catalog DB unavailable or inconsistent Non-blocking for delivery (DB is an index, not authoritative); reconciliation job rebuilds it from durable markers/trailers

Testing

Following the convention established in Phat Files: pure logic extracted to package-private static methods, unit-testable without a live NiFi/S3 connection —

  • Canonical attribute-list ordering and serialization (must be deterministic for clear-sign to produce consistent output for the same attribute set).
  • Chunk-boundary computation from a content stream (byte offset math, ~1GB target size).
  • Footer encode/decode (magic + length) and suffix-range calculation.
  • Missing-chunk diffing (expected range vs. staged contents) — adopted from Phat Files.
  • Resend-vs-duplicate decision logic — adopted from Phat Files.

End-to-end behavior (ingest → sign → chunk → stage → assemble → catalog row → attribute-only read via suffix range → replay) verified against the lab's MinIO instance, consistent with prior processors in this environment.

Open Items for Implementation Planning

  • Exact NAR/module layout for the new ingest processor(s), Controller Service (signing key), and the external post-NiFi service.
  • Concrete canonical attribute-list serialization format (sorted key: value lines vs. canonical JSON) — functionally equivalent, pick one for implementation.
  • Confirm whether Ed25519 is acceptable or a compliance constraint (e.g. FIPS) forces RSA/ECDSA.
  • Concrete default values: chunk size (default ~1GB, confirm whether it should differ between diode-bound and non-diode-bound chunked delivery), stall timeout, cleanup retention window.
  • Catalog database technology selection (not yet specified).
  • Whether HTTP delivery to receiving applications needs the same suffix- range attribute-only read support, or always transfers the full object.
  • S3-vs-XFS size threshold. Needs validating against real small-file volume/throughput numbers — if S3 can serve small files efficiently enough at the actual expected volume, the XFS tier could be dropped entirely and S3 used as the sole backend, simplifying Storage Backend Handover to one path instead of two.
  • Exact JSON trailer schema beyond the clearsign field (format version, chunk descriptor list — whether chunk descriptors are duplicated at the JSON top level for easy programmatic access, or only present inside the signed attribute list and require parsing the clearsign block to read).
  • Get the existing S3 handover interface spec document referenced in Storage Backend Handover: the exact regex used to map x-amz-meta-* keys to NiFi attributes, and the exact flag/tag name and value convention used to signal the >2KB fallback to the JSON trailer. This design assumes that spec's behavior but doesn't reproduce its details.
  • Enumerate the controlled attribute lists, to be provided later per Attribute Governance:
    • The ~15 controlled external attributes (SA-V3-Ext-## placeholders), owned by the external S3 handover interface spec — names, types, and which subset of the ~15 is mandatory vs. optional.
    • The separate controlled internal attribute list (SA-V3-Int-## placeholders, count NN not yet fixed) — used for SA routing/ processing generally, and specifically for internal S3 (staging/ replay buffer) objects.
  • Confirm the write-side/read-side regex reconciliation is fully closed in practice, not just in principle. It's now established that the permissible external attribute set is the same for reads and writes (one list, not two independently-defined ones) — but this still needs the actual document to confirm: exact key-casing convention (S3 lowercases x-amz-meta-* header names on read), and that the flag/size attributes this design writes for the >2KB fallback don't collide with any of the ~15 external attribute keys.
  • Define how internal attributes are represented on internal S3 objects (staging, replay buffer) — S3 user metadata vs. tags vs. the JSON-trailer mechanism, and whether the same 2KB threshold applies there.