Skip to main content

heddle_cli_args/cli/cli_args/
commands_redact.rs

1// SPDX-License-Identifier: Apache-2.0
2//! `heddle redact` and `heddle redact purge` — the redaction primitive.
3//!
4//! See `docs/PRINCIPLES.md` and the build brief at
5//! `.agents/redaction-primitive.md` for the design rationale. Briefly:
6//!
7//! - `redact` declares a blob redacted; readers see a stub on
8//!   materialize, but the bytes remain on disk.
9//! - `purge` removes the bytes from local storage. The `Redaction`
10//!   tombstone stays in the DAG forever.
11//!
12//! Both verbs write `OpRecord` entries (`Redact`, `Purge`) so the
13//! oplog audit trail records who did what when.
14
15use clap::{Args, Subcommand};
16
17#[derive(Clone, Debug, Subcommand)]
18pub enum RedactCommands {
19    /// Declare a redaction on a blob in a state. The blob bytes stay
20    /// on disk; reads return the stub. Use `heddle redact purge` afterward
21    /// to physically remove the bytes.
22    Apply(RedactApplyArgs),
23    /// List every active redaction in the repo.
24    List(RedactListArgs),
25    /// Show a single redaction by its content-addressed id.
26    Show(RedactShowArgs),
27    /// Manage the list of operator public keys whose signed
28    /// redactions this repo accepts over the wire.
29    ///
30    /// `accept_wire_redactions` is fail-closed: an empty trust list
31    /// rejects every signed redaction. Operators run `heddle redact
32    /// trust add` to authorize a key for cross-replica propagation.
33    /// Signing alone proves *who* declared the redaction; the trust
34    /// list proves the receiver has authorized that operator to act
35    /// on this workspace.
36    #[command(subcommand)]
37    Trust(RedactTrustCommands),
38    /// Physically remove bytes for an existing redaction.
39    #[command(subcommand)]
40    Purge(PurgeCommands),
41}
42
43#[derive(Clone, Debug, Subcommand)]
44pub enum RedactTrustCommands {
45    /// Add an operator public key to `[redact] trusted_keys` in
46    /// `.heddle/config.toml`. Subsequent `heddle pull` or `heddle clone`
47    /// invocations will accept signed redactions from that key.
48    Add(RedactTrustAddArgs),
49    /// List the currently-trusted operator keys.
50    List(RedactTrustListArgs),
51    /// Remove an operator public key from the trust list. Future
52    /// signed redactions from that key will be refused.
53    Remove(RedactTrustRemoveArgs),
54}
55
56#[derive(Clone, Debug, Args)]
57pub struct RedactTrustAddArgs {
58    /// Path to a PEM file (public or private key — the public half
59    /// is extracted either way). Algorithm autodetected from the PEM
60    /// header. This is the operator-friendly path: same PEM you
61    /// passed to `heddle redact apply --sign-with`.
62    #[arg(long, value_name = "PATH", group = "key_source")]
63    pub from_pem: Option<std::path::PathBuf>,
64    /// Algorithm identifier (`ed25519`, `p256`) when supplying
65    /// the raw hex-encoded public key directly via `--public-key`.
66    #[arg(long, value_name = "ALGO", requires = "public_key")]
67    pub algorithm: Option<String>,
68    /// Hex-encoded raw public key bytes. Use alongside `--algorithm`
69    /// when the operator already has the key in hex form (e.g. from
70    /// a signed-redaction's `signature.public_key` field).
71    #[arg(long, value_name = "HEX", requires = "algorithm", group = "key_source")]
72    pub public_key: Option<String>,
73    /// Optional free-text label for the trust entry (`"luke-laptop"`,
74    /// `"ci-signing"`). Doesn't affect matching semantics.
75    #[arg(long)]
76    pub label: Option<String>,
77}
78
79#[derive(Clone, Debug, Args)]
80pub struct RedactTrustListArgs {}
81
82#[derive(Clone, Debug, Args)]
83pub struct RedactTrustRemoveArgs {
84    /// Hex-encoded raw public key to remove. Exact match (case-
85    /// insensitive).
86    pub public_key: String,
87}
88
89#[derive(Clone, Debug, Args)]
90pub struct RedactApplyArgs {
91    /// State that surfaces the file. Accepts short or full state IDs,
92    /// marker names, `HEAD`, `@`, or `HEAD~N`.
93    pub state: String,
94    /// Path within the state's tree.
95    #[arg(long)]
96    pub path: String,
97    /// Operator-supplied reason. Lands in the materialized stub so
98    /// reviewers know why content disappeared.
99    #[arg(long)]
100    pub reason: String,
101    /// Walk every reachable state and redact every occurrence of the
102    /// same blob hash. Default: just the named state.
103    #[arg(long)]
104    pub all_states: bool,
105    /// Path to a private key (PEM) used to sign the redaction. The
106    /// signature binds operator → declaration; auditors can verify
107    /// who hid what when with `heddle redact show`.
108    #[arg(long, value_name = "PATH")]
109    pub sign_with: Option<std::path::PathBuf>,
110    /// Override the signing algorithm. Defaults to autodetect from the
111    /// key file's PEM header. Accepts `ed25519`, `p256`.
112    #[arg(long, value_name = "ALGO", requires = "sign_with")]
113    pub sign_algo: Option<String>,
114}
115
116#[derive(Clone, Debug, Args)]
117pub struct RedactListArgs {}
118
119#[derive(Clone, Debug, Args)]
120pub struct RedactShowArgs {
121    /// Redaction id (full or short prefix).
122    pub redaction_id: String,
123}
124
125#[derive(Clone, Debug, Subcommand)]
126pub enum PurgeCommands {
127    /// Physically remove the blob bytes referenced by an existing
128    /// redaction. Refuses if no redaction declared the blob first.
129    ///
130    /// Workspace-owner capability today; the surface is documented in
131    /// the build brief at `.agents/redaction-primitive.md`. The
132    /// capability check is a TODO until Biscuit wiring lands; for now
133    /// `--force` is the explicit confirmation step.
134    Apply(PurgeApplyArgs),
135    /// List every `Purge` oplog entry — who removed bytes, when, and
136    /// which redaction the purge acted on.
137    List(PurgeListArgs),
138}
139
140#[derive(Clone, Debug, Args)]
141pub struct PurgeApplyArgs {
142    /// State whose redaction we're purging the blob of.
143    pub state: String,
144    /// Path within the state's tree.
145    #[arg(long)]
146    pub path: String,
147    /// Required acknowledgement. Purge is irreversible — without
148    /// `--force` the command refuses, listing what would be removed.
149    #[arg(long)]
150    pub force: bool,
151}
152
153#[derive(Clone, Debug, Args)]
154pub struct PurgeListArgs {}