Skip to main content

faucet_cli/
cli.rs

1//! Argument parser shared by `main.rs` and the integration tests.
2
3use clap::{Parser, Subcommand};
4use std::path::PathBuf;
5
6/// `faucet` — config-driven runner for faucet-stream pipelines.
7#[derive(Debug, Parser)]
8#[command(name = "faucet", version, about, long_about = None)]
9pub struct Cli {
10    /// Override the global log level (also honors `FAUCET_LOG`).
11    #[arg(long, global = true, env = "FAUCET_LOG", default_value = "info")]
12    pub log_level: String,
13
14    #[command(subcommand)]
15    pub command: Command,
16}
17
18/// Top-level subcommands.
19#[derive(Debug, Subcommand)]
20pub enum Command {
21    /// Execute a pipeline config end-to-end.
22    Run(RunArgs),
23    /// Replay a bounded historical window of a pipeline: chunk --from/--to
24    /// into window units, run them with bounded parallelism, and record
25    /// durable, resumable progress. Exits non-zero if any unit fails.
26    Backfill(BackfillArgs),
27    /// Bulk-snapshot a database table, then stream CDC from a position captured
28    /// before the snapshot (a true mirror with `write_mode: upsert`).
29    /// Long-running when `replication.continuous` is true (Ctrl-C / SIGTERM to stop).
30    Replicate(ReplicateArgs),
31    /// Connect to a config's source, enumerate the datasets behind it
32    /// (tables / collections / indices / prefixes), and emit a ready-to-run
33    /// config with one matrix row per dataset.
34    Discover(DiscoverArgs),
35    /// Parse + validate a pipeline config without running it.
36    Validate(ValidateArgs),
37    /// Print the JSON Schema for a specific connector.
38    Schema(SchemaArgs),
39    /// List every compiled-in source, sink, and transform with a one-line
40    /// description (`--available` lists the whole connector registry instead).
41    List(ListArgs),
42    /// Search the connector registry index for connectors by name / keyword.
43    Search(SearchArgs),
44    /// Score each connector's conformance to the faucet SDK contract and print
45    /// its maturity tier (Stable / Experimental / Beta / Draft) + capabilities.
46    Conformance(ConformanceArgs),
47    /// Show how to install or enable a connector from the registry index
48    /// (prints the recipe; never executes anything).
49    Install(InstallArgs),
50    /// Run only the source side and print records to stdout (uses the stdout sink).
51    Preview(PreviewArgs),
52    /// Read-only preview of what a config would do: resolved pipeline, inferred
53    /// output schema, sink schema delta, lineage, and target sinks — zero writes.
54    Plan(PlanArgs),
55    /// Watch a config and re-run a sample offline on every save, printing a
56    /// live diff of the output. Requires the `cli-dev` build feature.
57    #[cfg(feature = "cli-dev")]
58    Dev(DevArgs),
59    /// Scaffold a starter `pipeline.yaml` to disk.
60    Init(InitArgs),
61    /// Scaffold a new artifact — currently a third-party connector crate.
62    New(NewArgs),
63    /// Probe every connector in a config (auth / network / permissions) and
64    /// print a green/red checklist. Exits non-zero if any probe fails.
65    Doctor(DoctorArgs),
66    /// Run fixture-based offline pipeline tests from one or more spec files.
67    /// No real source or sink is touched. Exits non-zero if any case fails.
68    Test(TestArgs),
69    /// Inspect, replay, or discard dead-letter-queue envelopes written by a
70    /// pipeline's `dlq:` sink.
71    Dlq(DlqArgs),
72    /// Validate a config's `contract:` block and print a summary, or export
73    /// it in a machine-readable format (`--export`).
74    #[cfg(feature = "contract")]
75    Contract(ContractArgs),
76    /// Validate a config's `masking:` block and print which rules apply to
77    /// each destination sink.
78    #[cfg(feature = "masking")]
79    Masking(MaskingArgs),
80    /// Run a pipeline on a cron schedule (long-running; Ctrl-C / SIGTERM to stop).
81    #[cfg(feature = "schedule")]
82    Schedule(ScheduleArgs),
83    /// Run a long-running HTTP control plane (submit / poll / cancel pipeline runs).
84    #[cfg(feature = "serve")]
85    Serve(ServeArgs),
86    /// Send a synthetic notification through a config's `notifications:` rules
87    /// to validate channel setup end-to-end (no pipeline runs).
88    #[cfg(feature = "notify")]
89    Notify(NotifyArgs),
90    /// Browse the Data Movement Catalog accumulated by a config's `catalog:`
91    /// store — datasets, schema timelines, volume/freshness, lineage.
92    #[cfg(feature = "catalog")]
93    Catalog(CatalogArgs),
94}
95
96/// `faucet catalog` arguments.
97#[cfg(feature = "catalog")]
98#[derive(Debug, Parser)]
99pub struct CatalogArgs {
100    #[command(subcommand)]
101    pub command: CatalogCommand,
102}
103
104/// `faucet catalog` subcommands.
105#[cfg(feature = "catalog")]
106#[derive(Debug, Subcommand)]
107pub enum CatalogCommand {
108    /// List every catalogued dataset (newest activity first).
109    Datasets(CatalogDatasetsArgs),
110    /// Show one dataset's detail: schema timeline, volume points, edges.
111    Show(CatalogShowArgs),
112    /// Print the dataset lineage graph (optionally rooted at a dataset).
113    Lineage(CatalogLineageArgs),
114}
115
116/// Shared config-loading flags for the `faucet catalog` subcommands.
117#[cfg(feature = "catalog")]
118#[derive(Debug, Parser)]
119pub struct CatalogConfigArgs {
120    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
121    /// `catalog:` block naming the store. If omitted, auto-discover
122    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
123    #[arg(long)]
124    pub config: Option<PathBuf>,
125    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
126    /// Defaults to `.env` in cwd if present.
127    #[arg(long, conflicts_with = "no_env_file")]
128    pub env_file: Option<PathBuf>,
129    /// Skip auto-loading `.env` from cwd.
130    #[arg(long)]
131    pub no_env_file: bool,
132    /// Select a named overlay from the config's `profiles:` block.
133    /// Overrides the `FAUCET_PROFILE` env var.
134    #[arg(long, env = "FAUCET_PROFILE")]
135    pub profile: Option<String>,
136    /// Emit machine-readable JSON instead of the human summary.
137    #[arg(long)]
138    pub json: bool,
139}
140
141/// `faucet catalog datasets` arguments.
142#[cfg(feature = "catalog")]
143#[derive(Debug, Parser)]
144pub struct CatalogDatasetsArgs {
145    #[command(flatten)]
146    pub common: CatalogConfigArgs,
147    /// Only datasets of this connector kind (e.g. `postgres`, `csv`).
148    #[arg(long)]
149    pub kind: Option<String>,
150    /// Case-insensitive substring match on the dataset URI.
151    #[arg(long)]
152    pub q: Option<String>,
153    /// Max datasets to list.
154    #[arg(long, default_value_t = 100)]
155    pub limit: usize,
156}
157
158/// `faucet catalog show <id>` arguments.
159#[cfg(feature = "catalog")]
160#[derive(Debug, Parser)]
161pub struct CatalogShowArgs {
162    /// Dataset id (from `faucet catalog datasets`), or a unique prefix of one.
163    pub id: String,
164    #[command(flatten)]
165    pub common: CatalogConfigArgs,
166}
167
168/// `faucet catalog lineage` arguments.
169#[cfg(feature = "catalog")]
170#[derive(Debug, Parser)]
171pub struct CatalogLineageArgs {
172    #[command(flatten)]
173    pub common: CatalogConfigArgs,
174    /// Dataset id to root the graph at (whole graph when omitted).
175    #[arg(long)]
176    pub root: Option<String>,
177    /// BFS hop bound around --root.
178    #[arg(long, default_value_t = 5)]
179    pub depth: u32,
180}
181
182/// `faucet notify test` arguments.
183#[cfg(feature = "notify")]
184#[derive(Debug, Parser)]
185pub struct NotifyArgs {
186    #[command(subcommand)]
187    pub command: NotifyCommand,
188}
189
190/// `faucet notify` subcommands.
191#[cfg(feature = "notify")]
192#[derive(Debug, Subcommand)]
193pub enum NotifyCommand {
194    /// Fire one synthetic event at every matching rule in the config.
195    Test(NotifyTestArgs),
196}
197
198/// `faucet notify test <config>` arguments.
199#[cfg(feature = "notify")]
200#[derive(Debug, Parser)]
201pub struct NotifyTestArgs {
202    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
203    /// `notifications:` block. If omitted, auto-discover in cwd.
204    pub config: Option<PathBuf>,
205    /// Which event to synthesize (defaults to `run_failure`).
206    #[arg(long, default_value = "run_failure")]
207    pub event: String,
208    /// Path to a `.env` file for `${env:VAR}` interpolation.
209    #[arg(long, conflicts_with = "no_env_file")]
210    pub env_file: Option<PathBuf>,
211    /// Disable `.env` auto-discovery.
212    #[arg(long)]
213    pub no_env_file: bool,
214}
215
216/// `faucet test` arguments.
217#[derive(Debug, Parser)]
218pub struct TestArgs {
219    /// One or more test-spec files (`.yaml`, `.yml`, or `.json`), e.g.
220    /// `faucet test tests/*.yaml`.
221    #[arg(required = true)]
222    pub specs: Vec<PathBuf>,
223    /// Run only cases whose name contains this substring.
224    #[arg(long)]
225    pub filter: Option<String>,
226    /// Emit a machine-readable JSON report instead of the human checklist.
227    #[arg(long)]
228    pub json: bool,
229    /// Default `${now.*}` clock for cases without their own `clock:` field
230    /// (RFC3339 like `2026-01-31T00:00:00Z`, or a date `2026-01-31`).
231    /// Defaults to process start (UTC).
232    #[arg(long)]
233    pub clock: Option<String>,
234    /// Path to a `.env` file to load for `${env:VAR}` interpolation in
235    /// referenced pipeline configs. Defaults to `.env` in cwd if present.
236    #[arg(long, conflicts_with = "no_env_file")]
237    pub env_file: Option<PathBuf>,
238    /// Skip auto-loading `.env` from cwd.
239    #[arg(long)]
240    pub no_env_file: bool,
241    /// Select a named overlay from each referenced config's `profiles:` block.
242    /// Overrides the `FAUCET_PROFILE` env var.
243    #[arg(long, env = "FAUCET_PROFILE")]
244    pub profile: Option<String>,
245    /// Resolve `${vault:…}` / `${aws-sm:…}` / … secret directives in
246    /// referenced configs (requires network + credentials). By default tests
247    /// load configs offline and leave secret directives unresolved — safe
248    /// because the real source/sink configs holding them are never used.
249    #[arg(long)]
250    pub resolve_secrets: bool,
251}
252
253/// `faucet dlq` arguments.
254#[derive(Debug, Parser)]
255pub struct DlqArgs {
256    #[command(subcommand)]
257    pub command: DlqCommand,
258}
259
260/// `faucet dlq` subcommands.
261#[derive(Debug, Subcommand)]
262pub enum DlqCommand {
263    /// Read a DLQ location back and print a per-reason / per-error-kind
264    /// breakdown plus a sample of quarantined records.
265    Inspect(DlqInspectArgs),
266    /// Re-feed quarantined records through a pipeline config (transforms →
267    /// quality → contract → sink). Rows that fail again land in a *fresh* DLQ.
268    Replay(DlqReplayArgs),
269    /// Remove processed envelopes from a DLQ location (archive by default,
270    /// or `--delete`), filtered by reason and/or age.
271    Discard(DlqDiscardArgs),
272}
273
274/// `faucet dlq inspect <location>` arguments.
275#[derive(Debug, Parser)]
276pub struct DlqInspectArgs {
277    /// DLQ location: a `.jsonl` file, a directory of `*.jsonl` files, or a glob.
278    pub location: String,
279    /// Only include envelopes with this DLQ reason (`partial` / `dlq_all` /
280    /// `quality` / `schema_drift` / `contract`).
281    #[arg(long)]
282    pub reason: Option<String>,
283    /// Number of sample records to show. Default: 5.
284    #[arg(long, default_value_t = 5)]
285    pub limit: usize,
286    /// Key for a DLQ sealed at rest by the jsonl sink's `encryption` block.
287    /// Repeat the flag to also try older (rotated) keys. Requires a build
288    /// with the `encryption` feature.
289    #[arg(long = "encryption-key")]
290    pub encryption_key: Vec<String>,
291    /// Emit a machine-readable JSON summary instead of the human report.
292    #[arg(long)]
293    pub json: bool,
294}
295
296/// `faucet dlq replay <config> --from <location>` arguments.
297#[derive(Debug, Parser)]
298pub struct DlqReplayArgs {
299    /// Path to the pipeline config whose sink / transforms / quality / contract
300    /// the replayed records flow through. If omitted, auto-discover in cwd.
301    pub config: Option<PathBuf>,
302    /// DLQ location to replay from: a `.jsonl` file, a directory, or a glob.
303    #[arg(long)]
304    pub from: String,
305    /// Only replay envelopes with this DLQ reason.
306    #[arg(long)]
307    pub reason: Option<String>,
308    /// Where replayed rows that fail *again* are quarantined. Defaults to a
309    /// `replay-failed.jsonl` sibling of the source (never the source itself).
310    #[arg(long)]
311    pub failed_dlq: Option<String>,
312    /// Which root row of the config to replay through. Defaults to the first root.
313    #[arg(long)]
314    pub row: Option<String>,
315    /// Report what would be replayed without writing to the sink.
316    #[arg(long)]
317    pub dry_run: bool,
318    /// Key for a DLQ sealed at rest by the jsonl sink's `encryption` block.
319    /// Repeat the flag to also try older (rotated) keys. Requires a build
320    /// with the `encryption` feature.
321    #[arg(long = "encryption-key")]
322    pub encryption_key: Vec<String>,
323    /// (Replay picks up the config's own dlq `encryption` block automatically
324    /// when no key is passed.)
325    /// Emit a machine-readable JSON result instead of the human summary.
326    #[arg(long)]
327    pub json: bool,
328    /// Path to a `.env` file for `${env:VAR}` interpolation in the config.
329    #[arg(long, conflicts_with = "no_env_file")]
330    pub env_file: Option<PathBuf>,
331    /// Skip auto-loading `.env` from cwd.
332    #[arg(long)]
333    pub no_env_file: bool,
334    /// Select a named overlay from the config's `profiles:` block.
335    #[arg(long, env = "FAUCET_PROFILE")]
336    pub profile: Option<String>,
337}
338
339/// `faucet dlq discard <location>` arguments.
340#[derive(Debug, Parser)]
341pub struct DlqDiscardArgs {
342    /// DLQ location: a `.jsonl` file, a directory of `*.jsonl` files, or a glob.
343    pub location: String,
344    /// Only discard envelopes with this DLQ reason.
345    #[arg(long)]
346    pub reason: Option<String>,
347    /// Only discard envelopes older than this: an RFC3339 timestamp
348    /// (`2026-06-01T00:00:00Z`) or a relative age (`7d`, `24h`, `30m`).
349    #[arg(long)]
350    pub before: Option<String>,
351    /// Permanently delete matching envelopes instead of archiving them to a
352    /// `<file>.archived.jsonl` sibling.
353    #[arg(long)]
354    pub delete: bool,
355    /// Key for a DLQ sealed at rest by the jsonl sink's `encryption` block.
356    /// Repeat the flag to also try older (rotated) keys. Requires a build
357    /// with the `encryption` feature.
358    #[arg(long = "encryption-key")]
359    pub encryption_key: Vec<String>,
360    /// Emit a machine-readable JSON result instead of the human summary.
361    #[arg(long)]
362    pub json: bool,
363}
364
365/// `faucet doctor` arguments.
366#[derive(Debug, Parser)]
367pub struct DoctorArgs {
368    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
369    /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
370    pub config: Option<PathBuf>,
371    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
372    /// Defaults to `.env` in cwd if present.
373    #[arg(long, conflicts_with = "no_env_file")]
374    pub env_file: Option<PathBuf>,
375    /// Skip auto-loading `.env` from cwd.
376    #[arg(long)]
377    pub no_env_file: bool,
378    /// Per-probe timeout in seconds.
379    #[arg(long, default_value_t = 10)]
380    pub timeout_secs: u64,
381    /// Emit machine-readable JSON instead of the human checklist.
382    #[arg(long)]
383    pub json: bool,
384    /// Select a named overlay from the config's `profiles:` block and deep-merge
385    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
386    #[arg(long, env = "FAUCET_PROFILE")]
387    pub profile: Option<String>,
388}
389
390/// `faucet contract` arguments.
391#[cfg(feature = "contract")]
392#[derive(Debug, Parser)]
393pub struct ContractArgs {
394    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
395    /// `pipeline.contract:` block. If omitted, auto-discover
396    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
397    pub config: Option<PathBuf>,
398    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
399    /// Defaults to `.env` in cwd if present.
400    #[arg(long, conflicts_with = "no_env_file")]
401    pub env_file: Option<PathBuf>,
402    /// Skip auto-loading `.env` from cwd.
403    #[arg(long)]
404    pub no_env_file: bool,
405    /// Select a named overlay from the config's `profiles:` block and deep-merge
406    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
407    #[arg(long, env = "FAUCET_PROFILE")]
408    pub profile: Option<String>,
409    /// Export the contract in a machine-readable format instead of the
410    /// human summary: the canonical contract JSON, a standalone JSON Schema,
411    /// or an OpenLineage schema facet.
412    #[arg(long, value_enum)]
413    pub export: Option<ContractExportFormat>,
414}
415
416/// Arguments for `faucet masking`.
417#[cfg(feature = "masking")]
418#[derive(Debug, Parser)]
419pub struct MaskingArgs {
420    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
421    /// `pipeline.masking:` block. If omitted, auto-discover
422    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
423    pub config: Option<PathBuf>,
424    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
425    /// Defaults to `.env` in cwd if present.
426    #[arg(long, conflicts_with = "no_env_file")]
427    pub env_file: Option<PathBuf>,
428    /// Skip auto-loading `.env` from cwd.
429    #[arg(long)]
430    pub no_env_file: bool,
431    /// Select a named overlay from the config's `profiles:` block and deep-merge
432    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
433    #[arg(long, env = "FAUCET_PROFILE")]
434    pub profile: Option<String>,
435}
436
437/// Export format for `faucet contract --export`.
438#[cfg(feature = "contract")]
439#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
440pub enum ContractExportFormat {
441    /// The canonical contract document as JSON.
442    Contract,
443    /// A standalone JSON Schema (draft 2020-12) for the promised records.
444    JsonSchema,
445    /// An OpenLineage `SchemaDatasetFacet` JSON document.
446    Openlineage,
447}
448
449/// `faucet schedule` arguments.
450#[cfg(feature = "schedule")]
451#[derive(Debug, Parser)]
452pub struct ScheduleArgs {
453    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a `schedule:`
454    /// block. If omitted, auto-discover `faucet.yaml` / `.yml` / `.json` in cwd.
455    pub config: Option<PathBuf>,
456    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
457    /// Defaults to `.env` in cwd if present.
458    #[arg(long, conflicts_with = "no_env_file")]
459    pub env_file: Option<PathBuf>,
460    /// Skip auto-loading `.env` from cwd.
461    #[arg(long)]
462    pub no_env_file: bool,
463    /// Run exactly one pipeline run immediately, then exit (ignores cron timing).
464    /// Useful for platform-driven invocation (k8s CronJob / systemd OnCalendar).
465    #[arg(long)]
466    pub once: bool,
467    /// Select a named overlay from the config's `profiles:` block and deep-merge
468    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
469    #[arg(long, env = "FAUCET_PROFILE")]
470    pub profile: Option<String>,
471}
472
473/// `faucet serve` arguments.
474#[cfg(feature = "serve")]
475#[derive(Debug, Clone, Parser)]
476pub struct ServeArgs {
477    /// Bind address. Defaults to loopback; set 0.0.0.0:PORT to expose externally.
478    #[arg(long, env = "FAUCET_SERVE_LISTEN", default_value = "127.0.0.1:8080")]
479    pub listen: String,
480    /// Bearer token required on /v1/* requests. Prefer the env var (avoids `ps` leakage).
481    #[arg(long, env = "FAUCET_SERVE_AUTH_TOKEN", conflicts_with = "no_auth")]
482    pub auth_token: Option<String>,
483    /// Explicitly disable authentication. Required if no token is set, so an
484    /// unauthenticated server is never accidental.
485    #[arg(long)]
486    pub no_auth: bool,
487    /// Path to an RBAC auth config (YAML/JSON) defining principals — each a
488    /// `{ name, token, role }` where role is `viewer` / `operator` / `admin`.
489    /// Enables role-based access control + an audit log. Mutually exclusive with
490    /// `--auth-token` / `--no-auth`.
491    #[arg(long, conflicts_with_all = ["auth_token", "no_auth"])]
492    pub auth_config: Option<std::path::PathBuf>,
493    /// Max pipeline runs executing at once. Default: min(16, cpu count).
494    #[arg(long)]
495    pub max_concurrent_runs: Option<usize>,
496    /// Max queued (not-yet-running) runs before POST /v1/runs returns 429.
497    /// Default: 8 × max-concurrent-runs.
498    #[arg(long)]
499    pub max_queued_runs: Option<usize>,
500    /// Workspace-default config merged under every submitted run.
501    #[arg(long)]
502    pub default_config: Option<std::path::PathBuf>,
503    /// Run-history backend URL: omitted = in-memory; postgres://… ; sqlite:… .
504    #[arg(long)]
505    pub history: Option<String>,
506    /// CORS allow-list origin (repeatable). Omitted = CORS disabled.
507    #[arg(long)]
508    pub cors_origin: Vec<String>,
509    /// Max POST /v1/runs body size in bytes (413 on exceed).
510    #[arg(long, default_value_t = 1_048_576)]
511    pub body_limit_bytes: usize,
512    /// SIGTERM/SIGINT drain window in seconds.
513    #[arg(long, default_value_t = 60)]
514    pub shutdown_grace_secs: u64,
515    /// Retain terminal run records this long (seconds).
516    #[arg(long, default_value_t = 604_800)]
517    pub retain_terminal_runs_secs: u64,
518    /// Idempotency-key replay window (seconds).
519    #[arg(long, default_value_t = 86_400)]
520    pub idempotency_retention_secs: u64,
521    /// Run-ownership lease TTL in seconds (multi-instance orphan fencing). A run
522    /// is owned by the instance executing it and its lease is heartbeated at
523    /// ~⅓ of this interval; only a run whose lease has expired (owner presumed
524    /// dead) is recovered as failed. Make this comfortably larger than expected
525    /// GC/IO stalls so a healthy-but-slow instance is never falsely reclaimed.
526    /// Only relevant with a persistent (postgres/sqlite) history backend.
527    #[arg(long, default_value_t = 30)]
528    pub lease_ttl_secs: u64,
529    /// Per-probe timeout for `doctor_first` preflight (seconds).
530    #[arg(long, default_value_t = 10)]
531    pub probe_timeout_secs: u64,
532    /// Path to a `.env` file loaded for the server's own startup interpolation.
533    #[arg(long, conflicts_with = "no_env_file")]
534    pub env_file: Option<std::path::PathBuf>,
535    /// Skip auto-loading `.env` from cwd at startup.
536    #[arg(long)]
537    pub no_env_file: bool,
538    /// Disable serving the embedded web console (only meaningful in a build that
539    /// includes the `serve-ui` feature; the API is unaffected).
540    #[arg(long)]
541    pub no_ui: bool,
542    /// Enable clustered execution: run a claim loop that pulls Pending runs from
543    /// the shared history DB so N instances pull-balance and fail over. Requires
544    /// a postgres/sqlite --history backend.
545    #[arg(long)]
546    pub cluster: bool,
547    /// Claim-loop poll interval (seconds) in cluster mode. Also the
548    /// cross-instance cancel-propagation lag. Must be > 0.
549    #[arg(long, default_value_t = 2)]
550    pub cluster_poll_secs: u64,
551    /// Max failover re-runs of an orphaned run before it is marked Failed
552    /// (poison). Must be > 0.
553    #[arg(long, default_value_t = 3)]
554    pub cluster_max_attempts: u32,
555    /// Path to a triggers file (YAML/JSON) defining event-driven pipeline
556    /// triggers (object-arrival / webhook / queue-depth). Requires a build with
557    /// the `triggers` feature. See `faucet schema triggers`.
558    #[arg(long)]
559    pub triggers: Option<std::path::PathBuf>,
560}
561
562/// `faucet run` arguments.
563#[derive(Debug, Parser)]
564pub struct RunArgs {
565    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config.
566    /// If omitted (and `--from-env` is not set), auto-discover
567    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in the current directory.
568    /// Mutually exclusive with `--from-env`.
569    #[arg(conflicts_with = "from_env")]
570    pub config: Option<PathBuf>,
571    /// Build the pipeline entirely from `FAUCET_*` environment variables —
572    /// no YAML required. See `cli/README.md` for the variable schema.
573    #[arg(long)]
574    pub from_env: bool,
575    /// Path to a `.env` file to load before reading variables. Works in both
576    /// YAML mode (for `${env:VAR}` interpolation) and `--from-env` mode.
577    /// When omitted, `.env` in the current directory is auto-loaded if present.
578    /// Existing process-env values always win over file-supplied ones.
579    #[arg(long, conflicts_with = "no_env_file")]
580    pub env_file: Option<PathBuf>,
581    /// Skip auto-loading `.env` from the current directory.
582    #[arg(long)]
583    pub no_env_file: bool,
584    /// Stop after fetching from the source — write nothing to the sink.
585    #[arg(long)]
586    pub dry_run: bool,
587    /// Stop after writing this many records to the sink. Default: unlimited.
588    #[arg(long)]
589    pub limit: Option<usize>,
590    /// Override the state-store directory (file backend only).
591    #[arg(long)]
592    pub state_path: Option<PathBuf>,
593    /// Override the `${now.*}` interpolation clock (RFC3339 like
594    /// `2026-01-31T00:00:00Z`, or a date `2026-01-31`). Default: process start (UTC).
595    /// Use for backfills.
596    #[arg(long)]
597    pub clock: Option<String>,
598    /// Select a named overlay from the config's `profiles:` block and deep-merge
599    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
600    /// Not applicable in `--from-env` mode (no config file to compose).
601    #[arg(long, env = "FAUCET_PROFILE")]
602    pub profile: Option<String>,
603    /// Show a live full-screen terminal UI (per-invocation throughput, errors,
604    /// DLQ counts, bookmark age) while the pipeline runs. Requires a binary
605    /// built with the `cli-tui` feature and a real terminal on stdout —
606    /// on a non-TTY (CI, pipes) the run proceeds normally with a notice.
607    /// Press `q` to cancel cooperatively (in-flight work flushes at the next
608    /// page boundary).
609    #[arg(long)]
610    pub tui: bool,
611}
612
613/// `faucet backfill` arguments.
614#[derive(Debug, Parser)]
615pub struct BackfillArgs {
616    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
617    /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
618    pub config: Option<PathBuf>,
619    /// Window start (inclusive): RFC3339 (`2026-06-01T00:00:00Z`) or a date
620    /// (`2026-06-01`, midnight in --timezone). Requires --to.
621    #[arg(long, requires = "to", conflicts_with = "from_bookmark")]
622    pub from: Option<String>,
623    /// Window end (exclusive): RFC3339 or a date.
624    #[arg(long, requires = "from", conflicts_with = "from_bookmark")]
625    pub to: Option<String>,
626    /// Chunk the range into windows of this duration (`45s`, `30m`, `6h`,
627    /// `1d`, `1w`) so each chunk is an independent, resumable unit. Defaults
628    /// to the config's `backfill.window`; omitted = one unit for the whole
629    /// range.
630    #[arg(long)]
631    pub window: Option<String>,
632    /// Replay from this explicit bookmark value instead of a wall-clock
633    /// range (seeded into the backfill's scoped state key; the source's own
634    /// incremental logic reads forward from it). JSON or a bare string.
635    #[arg(long)]
636    pub from_bookmark: Option<String>,
637    /// Upper bookmark bound: records whose --bookmark-field orders after
638    /// this value are dropped before the sink.
639    #[arg(long, requires_all = ["from_bookmark", "bookmark_field"])]
640    pub to_bookmark: Option<String>,
641    /// Record field the --to-bookmark bound applies to.
642    #[arg(long)]
643    pub bookmark_field: Option<String>,
644    /// Max concurrently-running window units. Defaults to the config's
645    /// `backfill.concurrency`, else 1 (sequential).
646    #[arg(long)]
647    pub concurrency: Option<usize>,
648    /// IANA timezone for date boundaries and `${now.*}` rendering. Defaults
649    /// to the config's `backfill.timezone`, else UTC.
650    #[arg(long)]
651    pub timezone: Option<String>,
652    /// Root row of the config to backfill. Defaults to the only root.
653    #[arg(long)]
654    pub row: Option<String>,
655    /// Redirect writes to this named sink template under `pipeline.sinks`
656    /// (backfill into a staging table first).
657    #[arg(long)]
658    pub into: Option<String>,
659    /// Print the planned units without running anything.
660    #[arg(long)]
661    pub dry_run: bool,
662    /// Continue a previously-interrupted backfill of the same range: skip
663    /// units already done, re-run failed and pending ones.
664    #[arg(long, conflicts_with = "restart")]
665    pub resume: bool,
666    /// Discard a previous progress marker for this range and start over.
667    #[arg(long)]
668    pub restart: bool,
669    /// Emit a machine-readable JSON report instead of the human summary.
670    #[arg(long)]
671    pub json: bool,
672    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
673    /// Defaults to `.env` in cwd if present.
674    #[arg(long, conflicts_with = "no_env_file")]
675    pub env_file: Option<PathBuf>,
676    /// Skip auto-loading `.env` from cwd.
677    #[arg(long)]
678    pub no_env_file: bool,
679    /// Select a named overlay from the config's `profiles:` block.
680    /// Overrides the `FAUCET_PROFILE` env var.
681    #[arg(long, env = "FAUCET_PROFILE")]
682    pub profile: Option<String>,
683}
684
685/// `faucet replicate` arguments.
686#[derive(Debug, Parser)]
687pub struct ReplicateArgs {
688    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config with a
689    /// `replication:` block. If omitted, auto-discover
690    /// `faucet.yaml` / `.yml` / `.json` in cwd.
691    pub config: Option<PathBuf>,
692    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
693    /// Defaults to `.env` in cwd if present.
694    #[arg(long, conflicts_with = "no_env_file")]
695    pub env_file: Option<PathBuf>,
696    /// Skip auto-loading `.env` from cwd.
697    #[arg(long)]
698    pub no_env_file: bool,
699    /// Select a named overlay from the config's `profiles:` block and deep-merge
700    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
701    #[arg(long, env = "FAUCET_PROFILE")]
702    pub profile: Option<String>,
703}
704
705/// `faucet discover` arguments.
706#[derive(Debug, Parser)]
707pub struct DiscoverArgs {
708    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config whose source
709    /// points at the system to introspect. If omitted, auto-discover
710    /// `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
711    pub config: Option<PathBuf>,
712    /// Which source template to introspect (an entry under `pipeline.sources`).
713    /// Defaults to `default` (the legacy singular `pipeline.source`).
714    #[arg(long)]
715    pub source: Option<String>,
716    /// Only include datasets whose name matches this `*`-wildcard pattern
717    /// (repeatable; no patterns = include everything).
718    #[arg(long)]
719    pub include: Vec<String>,
720    /// Exclude datasets whose name matches this `*`-wildcard pattern
721    /// (repeatable; applied after --include).
722    #[arg(long)]
723    pub exclude: Vec<String>,
724    /// Write the generated config to this file instead of stdout.
725    #[arg(long, short = 'o')]
726    pub output: Option<PathBuf>,
727    /// Overwrite the --output file if it already exists.
728    #[arg(long)]
729    pub force: bool,
730    /// Emit the discovered datasets as machine-readable JSON instead of a
731    /// generated config.
732    #[arg(long)]
733    pub json: bool,
734    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
735    /// Defaults to `.env` in cwd if present.
736    #[arg(long, conflicts_with = "no_env_file")]
737    pub env_file: Option<PathBuf>,
738    /// Skip auto-loading `.env` from cwd.
739    #[arg(long)]
740    pub no_env_file: bool,
741    /// Select a named overlay from the config's `profiles:` block.
742    /// Overrides the `FAUCET_PROFILE` env var.
743    #[arg(long, env = "FAUCET_PROFILE")]
744    pub profile: Option<String>,
745}
746
747/// `faucet validate` arguments.
748#[derive(Debug, Parser)]
749pub struct ValidateArgs {
750    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
751    /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
752    pub config: Option<PathBuf>,
753    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
754    /// Defaults to `.env` in cwd if present.
755    #[arg(long, conflicts_with = "no_env_file")]
756    pub env_file: Option<PathBuf>,
757    /// Skip auto-loading `.env` from cwd.
758    #[arg(long)]
759    pub no_env_file: bool,
760    /// Validate grammar and structure only — skip fetching from secrets
761    /// managers (no network / credentials needed).
762    #[arg(long)]
763    pub no_secrets: bool,
764    /// Select a named overlay from the config's `profiles:` block and deep-merge
765    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
766    #[arg(long, env = "FAUCET_PROFILE")]
767    pub profile: Option<String>,
768    /// Print the fully-composed config (after extends/!include/profile, before
769    /// `${...}` interpolation) and exit. For debugging composition precedence.
770    /// `--no-secrets` is redundant here (no interpolation or secret fetch occurs).
771    #[arg(long)]
772    pub show_composed: bool,
773}
774
775/// `faucet schema` arguments.
776#[derive(Debug, Parser)]
777pub struct SchemaArgs {
778    #[command(subcommand)]
779    pub target: SchemaTarget,
780}
781
782/// Schema subcommand target — which connector or system component to describe.
783#[derive(Debug, Subcommand)]
784pub enum SchemaTarget {
785    /// Composed JSON Schema for the **entire** `faucet.yaml` / `faucet.json`
786    /// config document (top-level grammar + per-connector `type` discrimination).
787    /// Point an editor at it with a `# yaml-language-server: $schema=…` header.
788    Config,
789    /// JSON Schema for a source connector config.
790    Source {
791        /// Connector name (e.g. `rest`, `graphql`, `postgres`).
792        name: String,
793    },
794    /// JSON Schema for a sink connector config.
795    Sink {
796        /// Connector name (e.g. `jsonl`, `bigquery`, `postgres`).
797        name: String,
798    },
799    /// JSON Schema for a transform's inline config.
800    Transform {
801        /// Transform name (e.g. `flatten`, `keys_case`, `cast`).
802        /// Run `faucet list` to see what is compiled in.
803        name: String,
804    },
805    /// JSON Schema for the DLQ (Dead Letter Queue) specification.
806    Dlq,
807    /// JSON Schema for the `replication:` (snapshot→CDC) block.
808    Replication,
809    /// JSON Schema for the `backfill:` (window replay defaults) block.
810    Backfill,
811    /// JSON Schema for the top-level `execution:` block.
812    Execution,
813    /// JSON Schema for the top-level `resilience:` block.
814    Resilience,
815    /// JSON Schema for the top-level `sla:` (freshness/volume SLA) block.
816    Sla,
817    /// JSON Schema for the `quality:` block.
818    #[cfg(feature = "quality")]
819    Quality,
820    /// JSON Schema for the `contract:` block.
821    #[cfg(feature = "contract")]
822    Contract,
823    /// JSON Schema for the `masking:` (PII masking) block.
824    #[cfg(feature = "masking")]
825    Masking,
826    /// JSON Schema for the `faucet test` spec file.
827    Test,
828    /// Grammar reference for secrets-manager interpolation directives.
829    Secrets,
830    /// JSON Schema for the `schedule:` block.
831    #[cfg(feature = "schedule")]
832    Schedule,
833    /// JSON Schema for the `lineage:` (OpenLineage) block.
834    #[cfg(feature = "lineage")]
835    Lineage,
836    /// JSON Schema for the `--triggers` file (event-driven pipeline triggers).
837    #[cfg(feature = "triggers")]
838    Triggers,
839    /// JSON Schema for the `notifications:` (incident-routing) block.
840    #[cfg(feature = "notify")]
841    Notifications,
842    /// JSON Schema for the `catalog:` (Data Movement Catalog store) block.
843    #[cfg(feature = "catalog")]
844    Catalog,
845}
846
847/// `faucet preview` arguments.
848#[derive(Debug, Parser)]
849pub struct PreviewArgs {
850    /// Path to a `.yaml`, `.yml`, or `.json` pipeline config. If omitted,
851    /// auto-discover `faucet.yaml` / `faucet.yml` / `faucet.json` in cwd.
852    pub config: Option<PathBuf>,
853    /// Stop after this many records. Default: 10.
854    #[arg(long, default_value_t = 10)]
855    pub limit: usize,
856    /// Path to a `.env` file to load for `${env:VAR}` interpolation.
857    /// Defaults to `.env` in cwd if present.
858    #[arg(long, conflicts_with = "no_env_file")]
859    pub env_file: Option<PathBuf>,
860    /// Skip auto-loading `.env` from cwd.
861    #[arg(long)]
862    pub no_env_file: bool,
863    /// Select a named overlay from the config's `profiles:` block and deep-merge
864    /// it over the composed base. Overrides the `FAUCET_PROFILE` env var.
865    #[arg(long, env = "FAUCET_PROFILE")]
866    pub profile: Option<String>,
867}
868
869/// `faucet init` arguments.
870#[derive(Debug, Parser)]
871pub struct InitArgs {
872    /// Name written into the generated file's `name:` field. Defaults to
873    /// `my-pipeline` when omitted.
874    pub name: Option<String>,
875    /// Source connector kind to scaffold (e.g. `rest`, `postgres`, `s3`).
876    /// Defaults to `rest`. Run `faucet list` to see what is compiled in.
877    #[arg(long)]
878    pub source: Option<String>,
879    /// Sink connector kind to scaffold (e.g. `jsonl`, `bigquery`).
880    /// Defaults to `jsonl`. Run `faucet list` to see what is compiled in.
881    #[arg(long)]
882    pub sink: Option<String>,
883    /// Output file path. Defaults to `pipeline.yaml`.
884    #[arg(long, short = 'o', default_value = "pipeline.yaml")]
885    pub output: PathBuf,
886    /// Overwrite the output file if it already exists.
887    #[arg(long)]
888    pub force: bool,
889    /// Prompt for the source and sink kinds interactively instead of using
890    /// `--source` / `--sink`. Requires the `cli-interactive` build feature
891    /// and a TTY on stdin; falls back to the arg-driven path otherwise.
892    #[arg(long)]
893    pub interactive: bool,
894    /// Name of the template under which to register the scaffolded source
895    /// and sink. The generated config uses `pipeline.sources.<TEMPLATE>` and
896    /// `pipeline.sinks.<TEMPLATE>`. Defaults to `default` so a matrix row
897    /// without a `ref:` field still resolves through the new schema.
898    #[arg(long, default_value = "default")]
899    pub template: String,
900    /// (singer only) Run `<executable> --discover` to fetch the tap's catalog,
901    /// write it next to the output, and scaffold the config with the discovered
902    /// streams listed. Requires `--source singer` and `--executable`.
903    #[arg(long)]
904    pub discover: bool,
905    /// (singer only) The Singer tap executable to discover with (used by
906    /// `--discover`), e.g. `tap-github` or `/opt/taps/tap-csv`.
907    #[arg(long)]
908    pub executable: Option<String>,
909    /// (singer only) The target stream to emit. When given with `--discover`,
910    /// the written catalog marks this stream — and any inferable parent
911    /// streams (e.g. a parent-keyed tap's parent) — `selected`, and the
912    /// scaffolded config's `stream:` is set to it. Most DB / SDK taps sync
913    /// nothing unless a stream is selected in the catalog.
914    #[arg(long)]
915    pub stream: Option<String>,
916}
917
918/// `faucet plan` arguments.
919#[derive(Debug, Parser)]
920pub struct PlanArgs {
921    /// Path to a `.yaml`/`.yml`/`.json` config (auto-discovered if omitted).
922    pub config: Option<PathBuf>,
923    /// Which row to plan (default: the first root row).
924    #[arg(long)]
925    pub row: Option<String>,
926    /// Offline sample of input records (`.jsonl` or a `.json` array) to preview
927    /// the output schema, volume, and sink delta through — no source is touched.
928    #[arg(long)]
929    pub sample: Option<PathBuf>,
930    /// Pull a capped, read-only sample from the real source instead of a
931    /// fixture (bounded by `--limit`; no bookmark is advanced).
932    #[arg(long)]
933    pub live: bool,
934    /// Cap for `--live` sampling.
935    #[arg(long, default_value_t = 10)]
936    pub limit: usize,
937    /// Emit the plan as JSON.
938    #[arg(long)]
939    pub json: bool,
940    /// Resolve secrets-manager directives (needs network/credentials). Off by
941    /// default so `plan` works offline like `faucet test`.
942    #[arg(long)]
943    pub resolve_secrets: bool,
944    /// Select a `profiles:` overlay.
945    #[arg(long, env = "FAUCET_PROFILE")]
946    pub profile: Option<String>,
947}
948
949/// `faucet dev` arguments.
950#[derive(Debug, Parser)]
951pub struct DevArgs {
952    /// Path to the `.yaml`/`.yml`/`.json` config to watch.
953    pub config: PathBuf,
954    /// Which row to run (default: the first root row).
955    #[arg(long)]
956    pub row: Option<String>,
957    /// Offline sample of input records (`.jsonl` or `.json` array). Required
958    /// for the offline loop.
959    #[arg(long)]
960    pub sample: Option<PathBuf>,
961    /// (reserved) pull a capped read-only sample from the real source.
962    #[arg(long)]
963    pub live: bool,
964    /// Cap for `--live` sampling.
965    #[arg(long, default_value_t = 10)]
966    pub limit: usize,
967    /// Run once and exit instead of watching (also the non-TTY fallback).
968    #[arg(long)]
969    pub once: bool,
970    /// Debounce window between re-runs, in milliseconds.
971    #[arg(long, default_value_t = 300)]
972    pub debounce_ms: u64,
973    /// Select a `profiles:` overlay.
974    #[arg(long, env = "FAUCET_PROFILE")]
975    pub profile: Option<String>,
976}
977
978/// `faucet list` arguments.
979#[derive(Debug, Parser)]
980pub struct ListArgs {
981    /// List every connector in the registry index (not just the compiled-in
982    /// ones), marking which are already in this binary.
983    #[arg(long)]
984    pub available: bool,
985    /// Read a custom registry index instead of the built-in one.
986    #[arg(long)]
987    pub index: Option<PathBuf>,
988}
989
990/// `faucet conformance` arguments.
991#[derive(Debug, Parser)]
992pub struct ConformanceArgs {
993    /// Only score the connector with this system name (e.g. `postgres`); prints
994    /// a detailed scorecard. Omit to score every compiled-in connector.
995    pub name: Option<String>,
996    /// Restrict to `source` or `sink`.
997    #[arg(long)]
998    pub kind: Option<String>,
999    /// Emit the full scorecards as JSON.
1000    #[arg(long)]
1001    pub json: bool,
1002}
1003
1004/// `faucet search` arguments.
1005#[derive(Debug, Parser)]
1006pub struct SearchArgs {
1007    /// Term to match against connector name / description / keywords / crate.
1008    pub term: String,
1009    /// Read a custom registry index instead of the built-in one.
1010    #[arg(long)]
1011    pub index: Option<PathBuf>,
1012    /// Emit matches as JSON.
1013    #[arg(long)]
1014    pub json: bool,
1015}
1016
1017/// `faucet install` arguments.
1018#[derive(Debug, Parser)]
1019pub struct InstallArgs {
1020    /// Connector system name (e.g. `kafka`).
1021    pub name: String,
1022    /// Disambiguate when a name exists as both a source and a sink.
1023    #[arg(long)]
1024    pub kind: Option<String>,
1025    /// Read a custom registry index instead of the built-in one.
1026    #[arg(long)]
1027    pub index: Option<PathBuf>,
1028}
1029
1030/// `faucet new` arguments.
1031#[derive(Debug, Parser)]
1032pub struct NewArgs {
1033    #[command(subcommand)]
1034    pub target: NewTarget,
1035}
1036
1037/// What `faucet new` scaffolds.
1038#[derive(Debug, Subcommand)]
1039pub enum NewTarget {
1040    /// Scaffold a ready-to-build `faucet-source-<name>` / `faucet-sink-<name>`
1041    /// connector crate following every repo convention.
1042    Connector(NewConnectorArgs),
1043}
1044
1045/// `faucet new connector` arguments.
1046#[derive(Debug, Parser)]
1047pub struct NewConnectorArgs {
1048    /// Connector system name (lowercase, e.g. `acme` or `acme-widgets`). Becomes
1049    /// the crate name `faucet-<kind>-<name>` and the YAML `type:` value.
1050    pub name: String,
1051    /// Whether to scaffold a `source` or a `sink`.
1052    #[arg(long)]
1053    pub kind: String,
1054    /// Also scaffold a `faucet-common-<name>` crate for config shared between a
1055    /// source/sink pair.
1056    #[arg(long)]
1057    pub common: bool,
1058    /// Directory to write the new crate(s) into. Defaults to the current dir.
1059    #[arg(long, short = 'o', default_value = ".")]
1060    pub output: PathBuf,
1061    /// Overwrite any existing files.
1062    #[arg(long)]
1063    pub force: bool,
1064}