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 {}