Skip to content

Commit cc86adf

Browse files
committed
Phase B: CNO-law proptest, delete legacy src/januskey, honest obliteration
Three januskey honesty/quality items. B5 — runnable reversibility evidence. Add a property test (crates/januskey-cli/tests/property_tests.rs) asserting the CNO round-trip law execute∘undo ≡ identity on the filesystem, across all supported operations (Delete/Modify/Move/Copy/Create) over random content: set up a temp working tree + content/metadata stores, snapshot it, execute then undo with a fresh executor, and assert the tree is byte-for-byte restored. This is the honest substitute for the "formal proofs pending" badge. Chown is excluded (undo unimplemented); we test the filesystem effect, not OperationType::inverse (deliberately non-involutive, Copy⁻¹=Delete). B2 — delete the legacy src/januskey/ monolith. It was a non-workspace, pre-extraction duplicate of crates/januskey-cli (ARCHITECTURE.md labelled it LEGACY). Removed it and repointed every reference (README, ARCHITECTURE, EXPLAINME, docs/wiki/*, idrisiser/*.tsig) to crates/januskey-cli; simplified the two contractile checks (Trustfile, Adjustfile) that scanned it — they already preferred crates/januskey-cli and the src/januskey branches were dead. src/abi/*.idr (the Idris ABI, outside src/januskey/) is untouched. B8 — stop overstating obliteration. The module doc-comment claimed content is "cryptographically unrecoverable"; overwrite-in-place cannot guarantee physical erasure on SSD/CoW/journaling media (the caveat already lived in .machine_readable/threat-model.a2ml). Mirror that honest wording into obliteration.rs and soften the README "data loss impossible by construction" to "resistant", with the obliteration exception called out. Verified: cargo build --workspace clean; cargo test -p januskey --lib 26 passed; property_tests 4 passed (incl. the new CNO-law case); no dangling src/januskey references remain; dashboard-check still green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015qwVESTcbfanY2iJPQNoSz
1 parent 603ee56 commit cc86adf

28 files changed

Lines changed: 158 additions & 7224 deletions

.machine_readable/contractiles/Adjustfile.a2ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ Standard: WCAG-2.2-AA (CLI/TUI profile).
7777

7878
### panic-pattern-drift
7979
- description: No regression of the 2026-04 unwrap()/expect("TODO") sweeps in security-critical modules
80-
- tolerance: 0 new bare unwrap() in crates/*/src and src/januskey/src
80+
- tolerance: 0 new bare unwrap() in crates/*/src
8181
- corrective: Run `just assail` (panic-attacker pre-commit scan)
8282
- severity: advisory
8383

.machine_readable/contractiles/Trustfile.a2ml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,6 @@ unjustified, it has no place here. Trust never overrides MUST.
117117
# OR the file does not exist. Fails while the home-rolled pattern is present.
118118
if [ -f crates/januskey-cli/src/attestation.rs ]; then \
119119
! grep -qE 'Sha256::new\(\).*update.*key.*update.*data' crates/januskey-cli/src/attestation.rs; \
120-
elif [ -f src/januskey/src/attestation.rs ]; then \
121-
! grep -qE 'Sha256::new\(\).*update.*key.*update.*data' src/januskey/src/attestation.rs; \
122120
else true; fi
123121
- severity: critical
124122

@@ -129,7 +127,7 @@ unjustified, it has no place here. Trust never overrides MUST.
129127
# Passes if ed25519-dalek or x25519-dalek is a real dependency, OR if
130128
# Ed25519 / X25519 are not listed in any KeyAlgorithm enum.
131129
cargo tree 2>/dev/null | grep -qE '(ed25519-dalek|x25519-dalek)' || \
132-
! grep -rqE '(Ed25519|X25519)' crates/januskey-cli/src/ src/januskey/src/ 2>/dev/null
130+
! grep -rqE '(Ed25519|X25519)' crates/januskey-cli/src/ 2>/dev/null
133131

134132
## Secrets-hygiene (memory rule: SOPS at-rest / Rokur runtime / RGTV LLM-broker)
135133

ARCHITECTURE.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ januskey/
4242
│ ├── delta — Differential operations
4343
│ ├── main — jk CLI binary
4444
│ └── keys_cli — jk-keys CLI binary
45-
46-
└── src/januskey/ ← LEGACY (pre-extraction, superseded by crates/)
4745
```
4846

4947
## reversible-core: The Shared Foundation

EXPLAINME.adoc

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@ The README makes claims. This file backs them up.
1010

1111
=== Claim 1: "100% Reversible - Every operation can be undone, always"
1212

13-
**How it works:** JanusKey implements architectural reversibility by storing inverted operations alongside each mutation. The delete operation, implemented in `src/januskey/src/obliteration.rs`, calculates and stores a full restoration record before removing file content. The `delta.rs` module generates inverse metadata that sufficient to reconstruct the pre-operation state. Every operation in the transaction layer (`src/januskey/src/metadata.rs`) maintains a "prior state" record, enabling perfect reversal through the `undo` pathway.
13+
**How it works:** JanusKey implements architectural reversibility by storing inverted operations alongside each mutation. The delete operation, implemented in `crates/januskey-cli/src/obliteration.rs`, calculates and stores a full restoration record before removing file content. The `delta.rs` module generates inverse metadata that sufficient to reconstruct the pre-operation state. Every operation in the transaction layer (`crates/januskey-cli/src/metadata.rs`) maintains a "prior state" record, enabling perfect reversal through the `undo` pathway.
1414

1515
**Caveat:** Reversibility is strong only when the operation log remains intact. If `.jk/` metadata store is corrupted or lost, recovery becomes impossible. The system trades **write amplification** (each operation doubles metadata I/O) for guaranteed recoverability.
1616

17-
**Evidence:** `src/januskey/src/lib.rs` (public API) delegates to `obliteration.rs` and `delta.rs` for operation logic; see `Reversible` trait (if present) or reversibility-proof comments in `attestation.rs`.
17+
**Evidence:** `crates/januskey-cli/src/lib.rs` (public API) delegates to `obliteration.rs` and `delta.rs` for operation logic; see `Reversible` trait (if present) or reversibility-proof comments in `attestation.rs`.
1818

1919
=== Claim 2: "Complete Audit Trail - Every change tracked automatically"
2020

21-
**How it works:** The metadata store (`src/januskey/src/metadata.rs`) maintains an append-only operation log. The `attestation.rs` module calculates cryptographic commitments (SHA256 hashes) of each operation, creating a tamper-evident chain. Content-addressed storage (`src/januskey/src/content_store.rs`) ensures deduplication by content hash, preventing duplicate storage while maintaining full history.
21+
**How it works:** The metadata store (`crates/januskey-cli/src/metadata.rs`) maintains an append-only operation log. The `attestation.rs` module calculates cryptographic commitments (SHA256 hashes) of each operation, creating a tamper-evident chain. Content-addressed storage (`crates/januskey-cli/src/content_store.rs`) ensures deduplication by content hash, preventing duplicate storage while maintaining full history.
2222

2323
**Caveat:** The audit trail is only as trustworthy as the append-only mechanism. If the log is truncated or rewritten on-disk, the chain breaks. For forensic-grade auditing, integrate with an external append-only store (database, blockchain).
2424

25-
**Evidence:** `src/januskey/src/metadata.rs` defines the log structure; `attestation.rs` computes cryptographic bindings; `content_store.rs` handles SHA256 hashing.
25+
**Evidence:** `crates/januskey-cli/src/metadata.rs` defines the log structure; `attestation.rs` computes cryptographic bindings; `content_store.rs` handles SHA256 hashing.
2626

2727
== Technology Choices
2828

@@ -41,15 +41,15 @@ The README makes claims. This file backs them up.
4141
|===
4242
| Path | Purpose
4343

44-
| `src/januskey/src/main.rs` | CLI entry point, command dispatch (delete, modify, move, copy, undo, begin, commit, rollback, preview)
45-
| `src/januskey/src/lib.rs` | Public API: init, delete, modify, move, copy, undo, transaction logic
46-
| `src/januskey/src/obliteration.rs` | Delete operation with restoration metadata
47-
| `src/januskey/src/delta.rs` | Computes inverse operations (modify undo, move undo, copy undo)
48-
| `src/januskey/src/metadata.rs` | Append-only operation log, transaction tracking, rollback state
49-
| `src/januskey/src/content_store.rs` | SHA256 content hashing, deduplication, storage management
50-
| `src/januskey/src/attestation.rs` | Cryptographic commitments, audit trail, tamper detection
51-
| `src/januskey/src/keys.rs` | Cryptographic key management (if key-based reversibility)
52-
| `src/januskey/src/error.rs` | Error types and propagation
44+
| `crates/januskey-cli/src/main.rs` | CLI entry point, command dispatch (delete, modify, move, copy, undo, begin, commit, rollback, preview)
45+
| `crates/januskey-cli/src/lib.rs` | Public API: init, delete, modify, move, copy, undo, transaction logic
46+
| `crates/januskey-cli/src/obliteration.rs` | Delete operation with restoration metadata
47+
| `crates/januskey-cli/src/delta.rs` | Computes inverse operations (modify undo, move undo, copy undo)
48+
| `crates/januskey-cli/src/metadata.rs` | Append-only operation log, transaction tracking, rollback state
49+
| `crates/januskey-cli/src/content_store.rs` | SHA256 content hashing, deduplication, storage management
50+
| `crates/januskey-cli/src/attestation.rs` | Cryptographic commitments, audit trail, tamper detection
51+
| `crates/januskey-cli/src/keys.rs` | Cryptographic key management (if key-based reversibility)
52+
| `crates/januskey-cli/src/error.rs` | Error types and propagation
5353
|===
5454

5555
== Dogfooted Across The Account

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ pending).
3838

3939
- **Instant Rollback** - Undo changes in milliseconds
4040

41-
- **Data loss impossible by construction** - the architectural goal the
42-
pending proofs are to establish
41+
- **Data loss resistant by construction** - reversibility is the
42+
architectural goal the pending proofs are to establish. (Note: *secure
43+
obliteration* is the deliberate exception and is best-effort only —
44+
overwrite-in-place cannot guarantee physical erasure on SSD/CoW/journaling
45+
media; see the threat model.)
4346

4447
- **Complete Audit Trail** - Every change tracked automatically
4548

@@ -51,8 +54,7 @@ pending).
5154

5255
```bash
5356
# Build from source (requires Rust)
54-
cd src/januskey
55-
cargo build --release
57+
cargo build --workspace --release
5658

5759
# Initialize JanusKey in your project
5860
jk init
@@ -125,10 +127,10 @@ Every operation stores sufficient metadata for perfect inversion:
125127
```bash
126128
# Clone the repository
127129
git clone {url-github}
128-
cd januskey/src/januskey
130+
cd januskey
129131

130132
# Build release binary
131-
cargo build --release
133+
cargo build --workspace --release
132134

133135
# Install to PATH
134136
cargo install --path .

crates/januskey-cli/src/obliteration.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,22 @@
55
// RMO: Obliterative Wipe Primitive
66
// Implements GDPR Article 17 "Right to Erasure" with formal obliteration proofs
77
//
8-
// The RMO primitive guarantees:
9-
// 1. Content is cryptographically unrecoverable after obliteration
10-
// 2. A proof of non-existence is generated
11-
// 3. The fact of obliteration is logged (without content)
8+
// The RMO primitive provides:
9+
// 1. A best-effort DoD 5220.22-M-style multi-pass overwrite of the file's
10+
// bytes at its current path (0x00 / 0xFF / random), then removal
11+
// 2. A cryptographic proof-of-obliteration commitment (SHA256 of the prior
12+
// content hash + nonce + timestamp)
13+
// 3. A logged record of the obliteration (without content)
14+
//
15+
// THREAT-MODEL CAVEAT (do not overstate): overwrite-in-place does NOT
16+
// guarantee physical erasure on copy-on-write / journaling filesystems or
17+
// flash media (Btrfs, XFS, ZFS, and any SSD behind an FTL with
18+
// wear-levelling): the controller may write the new passes to different
19+
// physical blocks and leave the originals readable. The "unrecoverable"
20+
// guarantee holds only against logical/path-based recovery on overwrite-in-
21+
// place media; on modern storage it is best-effort. See
22+
// .machine_readable/threat-model.a2ml [primitives.secure-delete]. For a hard
23+
// guarantee, obliterate the key material (encrypt-then-shred), not the bytes.
1224

1325
use crate::content_store::{ContentHash, ContentStore};
1426
use crate::error::{JanusError, Result};

crates/januskey-cli/tests/property_tests.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66

77
use januskey::content_store::ContentStore;
88
use januskey::obliteration::{ObliterationManager, ObliterationProof};
9+
use januskey::operations::{FileOperation, OperationExecutor};
910
use proptest::prelude::*;
1011
use reversible_core::content_store::ContentHash;
12+
use reversible_core::metadata::MetadataStore;
13+
use std::collections::BTreeMap;
14+
use std::path::{Path, PathBuf};
1115
use tempfile::TempDir;
1216

1317
// --- Obliteration properties ---
@@ -54,3 +58,107 @@ proptest! {
5458
prop_assert_eq!(h1, h2);
5559
}
5660
}
61+
62+
// --- CNO round-trip law: execute ∘ undo ≡ identity on the filesystem ---
63+
//
64+
// This is the load-bearing, runnable evidence for JanusKey's reversibility
65+
// claim (the honest substitute for the "formal proofs pending" badge): for
66+
// every supported operation, executing it and then undoing it must return the
67+
// working tree to exactly its pre-execute state (bytes + existence).
68+
//
69+
// Chown is excluded: its undo is unimplemented (operations.rs). We test the
70+
// filesystem effect, not OperationType::inverse() (which is deliberately NOT an
71+
// involution — Copy⁻¹ = Delete — so double-inverse is not the law here).
72+
73+
/// What operation to exercise, plus the content it needs.
74+
#[derive(Debug, Clone)]
75+
enum OpSpec {
76+
Delete(Vec<u8>),
77+
Modify(Vec<u8>, Vec<u8>),
78+
Move(Vec<u8>),
79+
Copy(Vec<u8>),
80+
Create(Vec<u8>),
81+
}
82+
83+
fn op_strategy() -> impl Strategy<Value = OpSpec> {
84+
let bytes = || proptest::collection::vec(any::<u8>(), 0..256);
85+
prop_oneof![
86+
bytes().prop_map(OpSpec::Delete),
87+
(bytes(), bytes()).prop_map(|(a, b)| OpSpec::Modify(a, b)),
88+
bytes().prop_map(OpSpec::Move),
89+
bytes().prop_map(OpSpec::Copy),
90+
bytes().prop_map(OpSpec::Create),
91+
]
92+
}
93+
94+
/// Recursive {relative-path -> bytes} snapshot of a directory (no external deps).
95+
fn snapshot(dir: &Path, base: &Path, out: &mut BTreeMap<PathBuf, Vec<u8>>) {
96+
for entry in std::fs::read_dir(dir).unwrap() {
97+
let path = entry.unwrap().path();
98+
if path.is_dir() {
99+
snapshot(&path, base, out);
100+
} else {
101+
let rel = path.strip_prefix(base).unwrap().to_path_buf();
102+
out.insert(rel, std::fs::read(&path).unwrap());
103+
}
104+
}
105+
}
106+
107+
fn snapshot_of(work: &Path) -> BTreeMap<PathBuf, Vec<u8>> {
108+
let mut out = BTreeMap::new();
109+
snapshot(work, work, &mut out);
110+
out
111+
}
112+
113+
proptest! {
114+
/// execute(op) then undo restores the working tree exactly (the CNO law).
115+
#[test]
116+
fn execute_then_undo_is_identity(spec in op_strategy()) {
117+
let tmp = TempDir::new().unwrap();
118+
// Keep user files (snapshotted) separate from the store internals.
119+
let work = tmp.path().join("work");
120+
std::fs::create_dir_all(&work).unwrap();
121+
let content_store = ContentStore::new(tmp.path().join("content"), false).unwrap();
122+
let mut metadata_store =
123+
MetadataStore::new(tmp.path().join("metadata.json")).unwrap();
124+
125+
let a = work.join("a.bin");
126+
let b = work.join("b.bin");
127+
128+
// Establish the pre-state and choose the operation.
129+
let op = match &spec {
130+
OpSpec::Delete(c) => {
131+
std::fs::write(&a, c).unwrap();
132+
FileOperation::Delete { path: a.clone() }
133+
}
134+
OpSpec::Modify(orig, new) => {
135+
std::fs::write(&a, orig).unwrap();
136+
FileOperation::Modify { path: a.clone(), new_content: new.clone() }
137+
}
138+
OpSpec::Move(c) => {
139+
std::fs::write(&a, c).unwrap();
140+
FileOperation::Move { source: a.clone(), destination: b.clone() }
141+
}
142+
OpSpec::Copy(c) => {
143+
std::fs::write(&a, c).unwrap();
144+
FileOperation::Copy { source: a.clone(), destination: b.clone() }
145+
}
146+
OpSpec::Create(c) => {
147+
FileOperation::Create { path: a.clone(), content: c.clone() }
148+
}
149+
};
150+
151+
let before = snapshot_of(&work);
152+
153+
// execute — mutates the tree and records inversion metadata.
154+
let mut exec = OperationExecutor::new(&content_store, &mut metadata_store);
155+
let meta = exec.execute(op).unwrap();
156+
157+
// undo — a fresh executor, exactly as the CLI unlocks a later session.
158+
let mut exec2 = OperationExecutor::new(&content_store, &mut metadata_store);
159+
exec2.undo(&meta.id).unwrap();
160+
161+
let after = snapshot_of(&work);
162+
prop_assert_eq!(before, after, "execute∘undo must restore the tree (CNO law)");
163+
}
164+
}

docs/wiki/Home.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ The "Key" represents the cryptographic foundation (SHA256 content addressing) th
100100
[source,bash]
101101
----
102102
# Build from source
103-
cd src/januskey
103+
cd crates/januskey-cli
104104
cargo build --release
105105
106106
# Initialize in a directory

docs/wiki/architecture/index.adoc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ JanusKey follows a layered architecture that separates concerns and enables the
4444

4545
=== CLI Layer
4646

47-
Location: `src/januskey/src/main.rs`
47+
Location: `crates/januskey-cli/src/main.rs`
4848

4949
The CLI provides the user interface for JanusKey operations:
5050

@@ -80,7 +80,7 @@ See: link:../guides/cli.adoc[CLI Guide]
8080

8181
=== Operation Layer
8282

83-
Location: `src/januskey/src/operations.rs`
83+
Location: `crates/januskey-cli/src/operations.rs`
8484

8585
Defines all reversible file operations and their execution logic.
8686

@@ -127,7 +127,7 @@ See: link:../operations/index.adoc[Operations Reference]
127127

128128
=== Transaction Manager
129129

130-
Location: `src/januskey/src/transaction.rs`
130+
Location: `crates/januskey-cli/src/transaction.rs`
131131

132132
Provides transaction support for grouping operations.
133133

@@ -154,7 +154,7 @@ See: link:../guides/transactions.adoc[Transaction Guide]
154154

155155
=== Content Store
156156

157-
Location: `src/januskey/src/content_store.rs`
157+
Location: `crates/januskey-cli/src/content_store.rs`
158158

159159
Content-addressed storage with SHA256 hashing and optional compression.
160160

@@ -188,7 +188,7 @@ See: link:content-store.adoc[Content Store Details]
188188

189189
=== Metadata Store
190190

191-
Location: `src/januskey/src/metadata.rs`
191+
Location: `crates/januskey-cli/src/metadata.rs`
192192

193193
Append-only operation log in JSON format.
194194

docs/wiki/examples/index.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ jobs:
219219
220220
- name: Install JanusKey
221221
run: |
222-
cd src/januskey
222+
cd crates/januskey-cli
223223
cargo install --path .
224224
225225
- name: Initialize JanusKey

0 commit comments

Comments
 (0)