Analysis Interfaces

The framework couples to external data-analysis pipelines (sliding-window searches, matched filters, alert generators) exclusively through the filesystem. There is no in-memory API to link against: every interface below is a file contract, so consumers may be written in any language and any number of analysis instances may operate concurrently on a single telemetry run.

Design Principles

  1. Consumers are read/copy-only. Analysis processes must never create, modify, move, or delete anything inside a run directory. Data a pipeline needs to own is copied out to consumer-managed storage.
  2. Concurrent consumers are safe by construction. All consumer-facing files are either append-only (event logs, metrics) or appear atomically and are immutable afterwards (batch directories). No locking protocol exists or is needed — provided rule 1 is respected.
  3. The contract is invariant under speed_up. A consumer developed against an accelerated run (speed_up = 3600) works unchanged against a real-time run (speed_up = 1.0); only the wall-clock arrival cadence differs. Development runs accelerated; rehearsal runs at mission cadence.
  4. config_snapshot.toml is the only source of derived quantities. Sample rates, segment/batch geometry, and session parameters are read from the run's own snapshot, never from the live config.toml.

The Run-Directory Contract

data/runs/<RUN_ID>/ contents, from a consumer's perspective:

ArtifactWriterConsumer access
ground/<BATCH>/receiverRead/copy. The delivery surface (see below).
lost/<BATCH>/receiverRead/copy. Retry-exhausted batches, preserved but never ground-available.
events_rx.csvreceiver (single writer)Tail/read. The authoritative arrival feed.
events_tx.csvemitter; the supervisor appends the STREAM gap pair while the emitter is down (one live writer at a time)Tail/read. Generation and transmission milestones.
mission_profile.csvreceiverTail/read. Link and buffer metrics at a change-driven cadence, columns SimTime, WallTime, Mission_Day, Hours_Elapsed, Bandwidth_Pct, Onboard_Buffer, Link_Buffer, Ground_Total, Ground_Live, Ground_Arch, Nominal_Bandwidth_Pct, Lost_Count, Retry_Count, Disruption_Active (a pre-rename Ground_Archive column is normalized to Ground_Total on read).
masks/post-processingRead/copy. Batch-state timeline and point-wise expansions.
config_snapshot.tomlpipeline (at startup)Read. Exact run parameters plus [provenance.platform]package_version, git_commit, hostname, os, cpu_model, logical_cores, total_memory_gb, julia_version, julia_threads, blas_threads — and, for external data, the input identity at [provenance] (external_data_path, external_data_rows, external_data_sha256, declared_sample_rate).
RUN_ACTIVE / RUN_COMPLETE / RUN_ABORTEDpipelineRead. Lifecycle sentinels (see below).
clock_anchor.tomlpipeline (at mission start)Read. Persisted mission-clock anchor and absolute deadline (wall_epoch, start_sim_time, speed_up, deadline_wall); re-attaching components reconstruct the identical clock from it.
component_events.csvsupervisor (single writer)Tail/read. Component lifecycle record, columns SimTime, Component, Event, with Component either emitter or receiver and Event one of down, restart, stalled, recovered.
emitter_alive / receiver_alivecomponents (heartbeats)Read mtime. Liveness signals, refreshed ≈ 1 s while a component runs and deleted when it exits: absence means finished, a stale mtime means stalled.
markers.csvpipeline (at mission start)Read. Event markers of the run (SimTime, Label) — the instants the alert-latency metric is evaluated at (alert_latency_markers.csv).
delivery_delay.csvpost-processingRead. Measurement-to-ground delay of every generated batch, columns Batch, Live, ContentEnd, AvailableAt, Delay_Hours, LowLatency (AvailableAt and Delay_Hours missing for undelivered batches; LowLatency flags deliveries inside a low-latency period); plots/delivery_delay.png renders the distribution against the requirement.
alert_latency.csvpost-processingRead. Alert-latency curves — median and quartiles of the ground availability of look-back data after a live event, realized doctrine against the counterfactual FIFO drain — columns Lookback_Hours, N_Alerts, LIFO_Median_Hours, LIFO_Q25_Hours, LIFO_Q75_Hours, FIFO_Median_Hours, FIFO_Q25_Hours, FIFO_Q75_Hours; plots/alert_latency.png renders the curves.
alert_latency_markers.csvpost-processingRead. The alert latency at every event marker, columns Label, Marker, Batch, Lookback_Hours, LIFO_Hours, FIFO_Hours.
products.h5post-processing (hdf5_export)Read/copy. The tabular products, the mask timeline, the point-wise masks, and the provenance attributes in one HDF5 file (section below) — not the batch payloads, the clock anchor, the sentinels, or the logs; regenerable from the CSV products.
plots/post-processingRead/copy. Figures mission_summary_global, one session_<stem>_detail per contact, alert_latency, delivery_delay, state_raster (the batch-state timeline as a raster), and payload_spectrum (a Welch estimate of the delivered payload against the analytic model; synthetic payloads only), each as .png and .pdf.
publication/post-processing ([post_processing.publication])Read/copy. Journal-width figure export, <stem>__<run_id>.<format>, with a PROVENANCE.toml sidecar ([export]: run_id, run_directory, exported_at, column_width_mm, format, package_version, git_commit, config_snapshot_sha256, figures).
masks/batch_epochs.csvpost-processingRead. Batch → epoch map, columns Batch, GenSimTime, ContentEpoch: the finalization instant from the event log and the first-sample timestamp from the batch metadata (missing when the metadata lacks it); written when the run has gen rows, it re-anchors point-wise mask rows when gap events are present.
masks/pointwise_mask_final.csv, masks/pointwise_mask_t<row>.csvpost-processingRead. Point-wise expansions of one mask-timeline row (the final row and every requested target_event_rows entry), columns Time_Index, Ground_Available.
HALToperatorThe one sanctioned external write: touch HALT stops both components cleanly at their next iteration; the pipeline removes the file once the components have joined, before post-processing.
emitter.log, receiver.logloggerRead. Human diagnostics, rotated to <name>#k.log at retention.log_rotate_mb; not machine-parsed interfaces.
onboard/, link/emitter/receiverOff-limits. Internal staging; the emitter counts in-flight batches from the link/ listing, so a slot frees when the receiver moves a batch out.

A batch directory contains metadata.json and one seg_<id>.csv per segment (single Amplitude column). The metadata keys are batch_id, segment_count, content_epoch — the mission timestamp of the payload's first sample, i.e. the physical epoch the data belong to — and created_at — the mission instant at which the batch was finalized and became transmittable (never earlier than the content end, and within one segment period of it when the host keeps pace with the accelerated clock). Segment files carry no timestamps; sample k of a batch lies at content_epoch + (k − 1) / sample_rate. A batch whose payload holds an event marker carries the marker labels under the optional markers key. Batches are delivered by an atomic same-filesystem mv: a directory visible under ground/ is complete, and it is never modified afterwards except by the retention custodian (below).

Availability Window & Lifecycle Sentinels

With [retention] disabled (the default), nothing is deleted from a run directory while the simulation is active — delivered payloads persist for the run's lifetime, and the only deletion path in the framework is the interactive scripts/maintenance/cleanup.jl.

With retention.enabled = true, the receiver's custodian bounds the delivered-payload footprint: a batch's payload is guaranteed readable for retention.grace_hours of mission time after its ingested event — a pipeline copies what it needs within that window. Beyond it, once the payload tally exceeds retention.high_watermark_gb, the oldest-ingested batches lose their seg_*.csv files; the batch directory remains, keeps metadata.json, gains a zero-byte PRUNED marker, and a pruned row (state-preserving, Attempt = 0) is appended to events_rx.csv. Consumers must tolerate both the marker and the event value. Event logs, metrics, masks, snapshots, and lost/ are never pruned, so post-hoc replay and mask products are unaffected. Time-based retention deliberately requires no consumer registration — that would make consumers writers.

Run lifecycle is signaled by sentinel files in the run directory: RUN_ACTIVE exists while the pipeline may still write; it is replaced by RUN_COMPLETE when the lifecycle ends (including after reported component failures — the sentinel marks "no further writes", not success) or by RUN_ABORTED when the pipeline exits before its lifecycle completes. A consumer may treat either terminal sentinel as the signal to switch from tailing to batch processing.

Component Outages & Generation Gaps

The two components run under a supervisor ([supervision] in the config): on a component failure the policy aborts the run cleanly, continues one-sided, or restarts the component (bounded attempts). Consumers observe outages through component_events.csv and the heartbeat mtimes. A receiver outage needs no special handling — it reproduces ground-station-blackout phenomenology (backlog accumulation, then drain). An emitter outage is a genuine generation gap: the restarted instrument resumes at the current mission time with a fresh noise realization, and the dead window is bounded by gap_start/gap_end rows (Batch = STREAM) in events_tx.csv. The same row pair bounds a scheduled generation gap (a disruption event with affects = "generation"; Batch = SCHEDULED) and a recorder overflow (the on-board buffer at storage.onboard_capacity_days; Batch = RECORDER, closed when room returns). Because batch IDs stay contiguous while mission time is not, point-wise mask rows must be re-anchored via masks/batch_epochs.csv when gap events are present; the mask replay itself treats gap events as state-preserving.

Event Feeds

events_rx.csv — columns SimTime, Batch, Event, Attempt; Attempt is populated on every row:

  • ingested — the batch reached the ground archive; Attempt is the number of failed attempts before the successful transfer (0 for a batch reconciled at receiver start-up). Ordering guarantee: the payload is moved into ground/ before this row is appended, so a consumer that reads an ingested event may open the batch immediately.
  • retry — a transfer attempt was lost; the batch remains on the link and is re-served no earlier than one round-trip light time later, other in-flight batches first. Attempt counts failed attempts so far.
  • lost — retry budget exhausted (Attempt = max_retries + 1); the batch was moved to lost/ (also before the row is appended) and will never become ground-available.
  • pruned — the retention custodian removed the payload of a delivered batch (Attempt = 0); the PRUNED marker and metadata.json remain (Retention above).

events_tx.csv — columns SimTime, Batch, Event, with gen (batch finalized onboard), tx (batch placed on the downlink), marker (SimTime = an event-marker instant, Batch = the batch holding it, appended when that batch is finalized; state-preserving), and the gap_start / gap_end pair bounding a generation gap (Batch = STREAM, SCHEDULED, or RECORDER, see above; state-preserving).

Batch Identity → Sample Interval

Batch names are LIVE_batch_<k> (generated during a contact — a nominal pass or a low-latency period) or ARCH_batch_<k> (generated in a blind spot or blackout); k is the global 1-based batch index. With

points_per_batch = sample_rate × segment_duration_sec × batch_size

(all three from config_snapshot.toml [physics]), batch k covers rows

[(k − 1) · points_per_batch + 1,  k · points_per_batch]

of the underlying time series. This mapping is row-index exact: in external mode the intervals index the input CSV rows one-to-one, and the point-wise masks are generated on the same convention.

Live Consumption (streaming analysis)

The recommended loop for an online sliding-window pipeline:

  1. Tail events_rx.csv (poll or FileWatching-style monitoring).
  2. On ingested: map the batch to its sample interval, add it to a coverage structure (interval set), and copy the payload out if the pipeline needs it beyond the run's lifetime.
  3. Evaluate every analysis window that the updated coverage now fully (or acceptably) spans.
  4. On lost: mark the interval as a permanent hole — windows crossing it must gap-handle or be discarded, never waited on.

Consumers must tolerate out-of-temporal-order arrival: live data streams FIFO with priority, while the archived backlog backfills LIFO (newest first), so coverage grows backwards in time from each live front — contiguously behind it, session by session, with a moving frontier at each blind-spot boundary. This is the intended behavior for sliding-window alert pipelines: the data most tightly coupled to a live event arrives first.

Post-Hoc Replay (offline analysis)

For reproducible offline studies, replay events_rx.csv in SimTime order as a simulated arrival stream and drive the same consumer logic — the event log is the ground truth from which the framework's own mask reconstruction is computed, so replayed availability is bit-identical to the live view. Alternatively, consume the prepared products:

  • masks/telemetry_mask_timeline.csv — rows = time snapshots; the first column is SimTime (the snapshot instant), then Batch_<k> for k = 1 … <largest batch ID> (an ID never generated is an all-zero column), values 0=Future, 1=Onboard, 2=Link, 3=Ground, 4=Lost. A window anchored at snapshot r may use exactly the batches with state 3 in row r.
  • Point-wise 0/1 expansions via scripts/postprocessing/apply_telemetry_mask.jl (config-aware) or the dependency-light standalone_mask_expander.jl (requires only CSV and DataFrames; suitable for Python/MATLAB/C++ collaborators to run alongside their own tooling). Multiply an expanded row against the raw series to blank undelivered data.

HDF5 Product Export

With post_processing.hdf5_export = true (or scripts/postprocessing/export_hdf5.jl [RUN_ID] afterwards) the run's products are written into products.h5, one self-describing file for pipelines that read HDF5 rather than a directory of CSV files. The CSV products stay in place and remain the primary interface; the file is a derived view of them and can be regenerated at any time.

GroupContent
root attributesformat_version, run_id, start_sim_time, speed_up, exported_at, the platform fingerprint of the run snapshot (hostname, package_version, git_commit, julia_version, …), and config_snapshot — the run's configuration as TOML text
events/tx, events/rxthe event logs, one dataset per column
metrics/mission_profilethe metrics profile, one dataset per column
masks/timelinestates — the batch-state matrix laid out as states[snapshot, batch] for C-order readers (h5py, NumPy; Julia reads the transpose), batch_id, the snapshot instants (SimTime, SimTime_iso), and the attributes state_codes and layout
masks/batch_epochsthe batch → epoch map
masks/pointwise/<stem>every point-wise expansion, Ground_Available as Int8 per sample
metrology/alert_latency, metrology/alert_latency_markers, metrology/delivery_delaythe metrology tables
markers, component_eventsthe event markers and the component lifecycle record

Column conventions: a DateTime column is stored as Float64 seconds since start_sim_time (attribute unit) with an ISO-8601 twin <name>_iso; booleans as UInt8 (0xff for missing); integers as Int64 (or Float64 with NaN when a value is missing); other numbers as Float64 with NaN for missing; the rest as strings with "" for missing. Each table group carries the attributes source (the CSV it was read from) and rows; masks/timeline carries source, state_codes, and layout, and the point-wise groups source.

Real-Time Operation

speed_up = 1.0 is a supported configuration (validation only guards against too fast pacing): the mission clock then advances at wall-clock rate and consumers experience genuine mission cadence — one segment per segment_duration_sec of real time. Current limitation for long campaigns: a run executes in a single process with no checkpoint/resume, so multi-year real-time rehearsals should be planned as bounded campaigns (e.g. a session or a disruption window at 1×) until run resumption is implemented.