datavault-cli 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.4
2
+ Name: datavault-cli
3
+ Version: 1.0.0
4
+ Summary: Cryptographic provenance and version control for AI training datasets
5
+ Home-page: https://github.com/thiminhkhuedao/DataVault
6
+ Author: Ry Nguyen
7
+ Author-email: thiminhkhuedao@gmail.com
8
+ Keywords: data provenance versioning AI machine-learning CSV sha256
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
12
+ Classifier: Topic :: Software Development :: Version Control
13
+ Requires-Python: >=3.6
14
+ Description-Content-Type: text/markdown
15
+ Dynamic: author
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: home-page
21
+ Dynamic: keywords
22
+ Dynamic: requires-python
23
+ Dynamic: summary
24
+
25
+ # DataVault
26
+
27
+ **Cryptographic provenance and version control for AI training datasets.**
28
+
29
+ Git tracks code. DataVault tracks data — with SHA-256 tamper detection, row-level CSV diffing, cryptographic hash chains, and multi-user signing workflows.
30
+
31
+ Zero dependencies. Pure Python stdlib.
32
+
33
+ ---
34
+
35
+ ## Why not Git or DVC?
36
+
37
+ | | Git | DVC | DataVault |
38
+ |---|---|---|---|
39
+ | Tracks file versions | ✓ | ✓ | ✓ |
40
+ | Works on large files | ✗ | ✓ | ✓ |
41
+ | Row-level CSV diff | ✗ | ✗ | ✓ |
42
+ | Tamper detection (SHA-256) | partial | ✗ | ✓ |
43
+ | Cryptographic hash chain | ✓ | ✗ | ✓ |
44
+ | Multi-user signing & approval | ✗ | ✗ | ✓ |
45
+ | Zero dependencies | ✗ | ✗ | ✓ |
46
+
47
+ ---
48
+
49
+ ## Quickstart
50
+
51
+ ```bash
52
+ # 1. Clone the repo
53
+ git clone https://github.com/thiminhkhuedao/DataVault.git
54
+ cd DataVault
55
+
56
+ # 2. Run the full demo (no setup needed)
57
+ python test_datavault.py
58
+ ```
59
+
60
+ That's it. No pip install, no config, no database. Just Python 3.6+.
61
+
62
+ ---
63
+
64
+ ## Usage
65
+
66
+ ```bash
67
+ # Initialize a project in any folder
68
+ python datavault.py init my-project
69
+
70
+ # Track a dataset
71
+ python datavault.py add training_data.csv "raw data from source"
72
+
73
+ # After editing the file, commit the change
74
+ python datavault.py commit training_data.csv "removed 42 duplicate rows"
75
+
76
+ # View full history
77
+ python datavault.py log training_data.csv
78
+
79
+ # Prove the file hasn't been tampered with
80
+ python datavault.py verify training_data.csv
81
+
82
+ # See which rows changed between versions (CSV only)
83
+ python datavault.py rowdiff training_data.csv v1 v3
84
+
85
+ # Verify the full cryptographic chain
86
+ python datavault.py chain training_data.csv
87
+
88
+ # Sign a version
89
+ python datavault.py sign training_data.csv v3 alice reviewer
90
+ python datavault.py sign training_data.csv v3 bob approver
91
+ python datavault.py approve training_data.csv v3
92
+
93
+ # Open the web dashboard
94
+ python datavault.py dashboard
95
+
96
+ # Export a provenance report (HTML → print as PDF)
97
+ python datavault.py export
98
+ ```
99
+
100
+ ---
101
+
102
+ ## All commands
103
+
104
+ | Command | Description |
105
+ |---|---|
106
+ | `init <name>` | Start a new project |
107
+ | `add <file> "msg"` | Start tracking a file |
108
+ | `commit <file> "msg"` | Save a new version |
109
+ | `log <file>` | Show version history |
110
+ | `status` | Show all tracked files |
111
+ | `verify <file>` | Check for tampering |
112
+ | `diff <file> v1 v2` | Text diff between versions |
113
+ | `checkout <file> v1` | Restore a version |
114
+ | `tag <file> v2 "label"` | Label a version |
115
+ | `rowlog <file>` | Row change stats (CSV) |
116
+ | `rowdiff <file> v1 v2` | Which rows changed (CSV) |
117
+ | `rowhistory <file> col val` | History of one specific row |
118
+ | `chain <file>` | Verify cryptographic chain |
119
+ | `chainshow <file>` | Display chain visually |
120
+ | `sign <file> v <name> [role]` | Sign a version |
121
+ | `signatures <file> [v]` | List or verify signatures |
122
+ | `approve <file> v` | Check approval status |
123
+ | `export` | Export HTML/PDF report |
124
+ | `dashboard` | Open web dashboard |
125
+
126
+ ---
127
+
128
+ ## How it works
129
+
130
+ **SHA-256 hashing** — every version gets a 64-character fingerprint. Change one character anywhere in the file, the fingerprint changes completely. `verify` catches any modification made outside DataVault.
131
+
132
+ **Hash chain** — each version's hash includes the previous version's hash, like a blockchain. You cannot delete or reorder history without breaking the chain. `chain` verifies the full sequence back to genesis.
133
+
134
+ **Row-level tracking** — for CSV files, DataVault tracks individual rows, not just the whole file. `rowdiff v1 v3` shows exactly which rows were added, deleted, or moved between any two versions.
135
+
136
+ **Multi-user signing** — reviewers and approvers sign specific versions. Signatures are cryptographically tied to the file hash — if the file changes after signing, the signature is invalidated automatically.
137
+
138
+ **Pi Bridge** — optional module that connects DataVault to a Raspberry Pi cluster, automatically committing hourly power and temperature readings as tracked CSV files with anomaly detection and auto-tagging.
139
+
140
+ ---
141
+
142
+ ## Files
143
+
144
+ | File | Purpose |
145
+ |---|---|
146
+ | `datavault.py` | CLI entrypoint — the only file you run |
147
+ | `vault_core.py` | Core engine — hashing, versioning, storage |
148
+ | `row_tracker.py` | Row-level CSV diffing |
149
+ | `chain.py` | Cryptographic hash chain |
150
+ | `signing.py` | Multi-user signing and approval |
151
+ | `dashboard.py` | Local web dashboard (port 5000) |
152
+ | `pi_bridge.py` | Auto-commit sensor data from Raspberry Pi |
153
+ | `migrate.py` | Backfill snapshots for existing versions |
154
+ | `test_datavault.py` | Full demo — run this first |
155
+
156
+ ---
157
+
158
+ ## Requirements
159
+
160
+ - Python 3.6+
161
+ - Zero external dependencies — pure standard library
162
+
163
+ ---
164
+
165
+ ## Why this matters
166
+
167
+ When an AI system makes a wrong decision in a medical, legal, or financial context, regulators will ask: where did the training data come from? Who modified it? Which version was used? Today there is no standard tool that answers these questions reliably.
168
+
169
+ DataVault is a working prototype of that provenance layer.
170
+
171
+ ---
172
+
173
+ ## Copyright
174
+
175
+ Copyright © 2026 Ry Nguyen. All rights reserved.
176
+
177
+ This project and its source code are public for portfolio viewing purposes only.
178
+ No permission is granted to copy, distribute, modify, or use this code for any other purpose.
@@ -0,0 +1,154 @@
1
+ # DataVault
2
+
3
+ **Cryptographic provenance and version control for AI training datasets.**
4
+
5
+ Git tracks code. DataVault tracks data — with SHA-256 tamper detection, row-level CSV diffing, cryptographic hash chains, and multi-user signing workflows.
6
+
7
+ Zero dependencies. Pure Python stdlib.
8
+
9
+ ---
10
+
11
+ ## Why not Git or DVC?
12
+
13
+ | | Git | DVC | DataVault |
14
+ |---|---|---|---|
15
+ | Tracks file versions | ✓ | ✓ | ✓ |
16
+ | Works on large files | ✗ | ✓ | ✓ |
17
+ | Row-level CSV diff | ✗ | ✗ | ✓ |
18
+ | Tamper detection (SHA-256) | partial | ✗ | ✓ |
19
+ | Cryptographic hash chain | ✓ | ✗ | ✓ |
20
+ | Multi-user signing & approval | ✗ | ✗ | ✓ |
21
+ | Zero dependencies | ✗ | ✗ | ✓ |
22
+
23
+ ---
24
+
25
+ ## Quickstart
26
+
27
+ ```bash
28
+ # 1. Clone the repo
29
+ git clone https://github.com/thiminhkhuedao/DataVault.git
30
+ cd DataVault
31
+
32
+ # 2. Run the full demo (no setup needed)
33
+ python test_datavault.py
34
+ ```
35
+
36
+ That's it. No pip install, no config, no database. Just Python 3.6+.
37
+
38
+ ---
39
+
40
+ ## Usage
41
+
42
+ ```bash
43
+ # Initialize a project in any folder
44
+ python datavault.py init my-project
45
+
46
+ # Track a dataset
47
+ python datavault.py add training_data.csv "raw data from source"
48
+
49
+ # After editing the file, commit the change
50
+ python datavault.py commit training_data.csv "removed 42 duplicate rows"
51
+
52
+ # View full history
53
+ python datavault.py log training_data.csv
54
+
55
+ # Prove the file hasn't been tampered with
56
+ python datavault.py verify training_data.csv
57
+
58
+ # See which rows changed between versions (CSV only)
59
+ python datavault.py rowdiff training_data.csv v1 v3
60
+
61
+ # Verify the full cryptographic chain
62
+ python datavault.py chain training_data.csv
63
+
64
+ # Sign a version
65
+ python datavault.py sign training_data.csv v3 alice reviewer
66
+ python datavault.py sign training_data.csv v3 bob approver
67
+ python datavault.py approve training_data.csv v3
68
+
69
+ # Open the web dashboard
70
+ python datavault.py dashboard
71
+
72
+ # Export a provenance report (HTML → print as PDF)
73
+ python datavault.py export
74
+ ```
75
+
76
+ ---
77
+
78
+ ## All commands
79
+
80
+ | Command | Description |
81
+ |---|---|
82
+ | `init <name>` | Start a new project |
83
+ | `add <file> "msg"` | Start tracking a file |
84
+ | `commit <file> "msg"` | Save a new version |
85
+ | `log <file>` | Show version history |
86
+ | `status` | Show all tracked files |
87
+ | `verify <file>` | Check for tampering |
88
+ | `diff <file> v1 v2` | Text diff between versions |
89
+ | `checkout <file> v1` | Restore a version |
90
+ | `tag <file> v2 "label"` | Label a version |
91
+ | `rowlog <file>` | Row change stats (CSV) |
92
+ | `rowdiff <file> v1 v2` | Which rows changed (CSV) |
93
+ | `rowhistory <file> col val` | History of one specific row |
94
+ | `chain <file>` | Verify cryptographic chain |
95
+ | `chainshow <file>` | Display chain visually |
96
+ | `sign <file> v <name> [role]` | Sign a version |
97
+ | `signatures <file> [v]` | List or verify signatures |
98
+ | `approve <file> v` | Check approval status |
99
+ | `export` | Export HTML/PDF report |
100
+ | `dashboard` | Open web dashboard |
101
+
102
+ ---
103
+
104
+ ## How it works
105
+
106
+ **SHA-256 hashing** — every version gets a 64-character fingerprint. Change one character anywhere in the file, the fingerprint changes completely. `verify` catches any modification made outside DataVault.
107
+
108
+ **Hash chain** — each version's hash includes the previous version's hash, like a blockchain. You cannot delete or reorder history without breaking the chain. `chain` verifies the full sequence back to genesis.
109
+
110
+ **Row-level tracking** — for CSV files, DataVault tracks individual rows, not just the whole file. `rowdiff v1 v3` shows exactly which rows were added, deleted, or moved between any two versions.
111
+
112
+ **Multi-user signing** — reviewers and approvers sign specific versions. Signatures are cryptographically tied to the file hash — if the file changes after signing, the signature is invalidated automatically.
113
+
114
+ **Pi Bridge** — optional module that connects DataVault to a Raspberry Pi cluster, automatically committing hourly power and temperature readings as tracked CSV files with anomaly detection and auto-tagging.
115
+
116
+ ---
117
+
118
+ ## Files
119
+
120
+ | File | Purpose |
121
+ |---|---|
122
+ | `datavault.py` | CLI entrypoint — the only file you run |
123
+ | `vault_core.py` | Core engine — hashing, versioning, storage |
124
+ | `row_tracker.py` | Row-level CSV diffing |
125
+ | `chain.py` | Cryptographic hash chain |
126
+ | `signing.py` | Multi-user signing and approval |
127
+ | `dashboard.py` | Local web dashboard (port 5000) |
128
+ | `pi_bridge.py` | Auto-commit sensor data from Raspberry Pi |
129
+ | `migrate.py` | Backfill snapshots for existing versions |
130
+ | `test_datavault.py` | Full demo — run this first |
131
+
132
+ ---
133
+
134
+ ## Requirements
135
+
136
+ - Python 3.6+
137
+ - Zero external dependencies — pure standard library
138
+
139
+ ---
140
+
141
+ ## Why this matters
142
+
143
+ When an AI system makes a wrong decision in a medical, legal, or financial context, regulators will ask: where did the training data come from? Who modified it? Which version was used? Today there is no standard tool that answers these questions reliably.
144
+
145
+ DataVault is a working prototype of that provenance layer.
146
+
147
+ ---
148
+
149
+ ## Copyright
150
+
151
+ Copyright © 2026 Ry Nguyen. All rights reserved.
152
+
153
+ This project and its source code are public for portfolio viewing purposes only.
154
+ No permission is granted to copy, distribute, modify, or use this code for any other purpose.
@@ -0,0 +1,173 @@
1
+ import hashlib
2
+ import json
3
+ import os
4
+
5
+ HISTORY_FILE = ".datavault/history.json"
6
+ CHAIN_FILE = ".datavault/chain.json"
7
+
8
+
9
+ def compute_chain_hash(file_hash, prev_chain_hash, version_id, timestamp, author, message):
10
+ chain_input = "|".join([file_hash, prev_chain_hash, version_id, timestamp, author, message])
11
+ return hashlib.sha256(chain_input.encode()).hexdigest()
12
+
13
+
14
+ def build_chain(filename):
15
+ if not os.path.exists(HISTORY_FILE):
16
+ return False, "No DataVault project found."
17
+ with open(HISTORY_FILE, "r", encoding="utf-8") as f:
18
+ history = json.load(f)
19
+ if filename not in history.get("files", {}):
20
+ return False, f"'{filename}' is not tracked."
21
+
22
+ versions = history["files"][filename]["versions"]
23
+ prev_chain_hash = "GENESIS"
24
+ chain = []
25
+
26
+ for v in versions:
27
+ chain_hash = compute_chain_hash(
28
+ v["hash"], prev_chain_hash, v["version_id"],
29
+ v["timestamp"], v.get("author", "unknown"), v["message"]
30
+ )
31
+ chain.append({
32
+ "version_id": v["version_id"],
33
+ "file_hash": v["hash"],
34
+ "prev_chain_hash": prev_chain_hash,
35
+ "chain_hash": chain_hash,
36
+ "timestamp": v["timestamp"],
37
+ "author": v.get("author", "unknown"),
38
+ "message": v["message"]
39
+ })
40
+ v["chain_hash"] = chain_hash
41
+ v["prev_chain_hash"] = prev_chain_hash
42
+ prev_chain_hash = chain_hash
43
+
44
+ with open(HISTORY_FILE, "w", encoding="utf-8") as f:
45
+ json.dump(history, f, indent=2)
46
+
47
+ chain_data = _load_chain()
48
+ chain_data["files"][filename] = chain
49
+ _save_chain(chain_data)
50
+ return True, chain
51
+
52
+
53
+ def verify_chain(filename):
54
+ if not os.path.exists(HISTORY_FILE):
55
+ return False, "No DataVault project found."
56
+ with open(HISTORY_FILE, "r", encoding="utf-8") as f:
57
+ history = json.load(f)
58
+ if filename not in history.get("files", {}):
59
+ return False, f"'{filename}' is not tracked."
60
+
61
+ versions = history["files"][filename]["versions"]
62
+ if not any("chain_hash" in v for v in versions):
63
+ return False, f"No chain data for '{filename}'.\nRun: python datavault.py chainbuild {filename}"
64
+
65
+ prev_chain_hash = "GENESIS"
66
+ results = []
67
+ all_valid = True
68
+
69
+ for v in versions:
70
+ stored = v.get("chain_hash")
71
+ if not stored:
72
+ results.append({"version_id": v["version_id"], "valid": False, "reason": "no chain hash — run chainbuild"})
73
+ all_valid = False
74
+ continue
75
+
76
+ expected = compute_chain_hash(
77
+ v["hash"], prev_chain_hash, v["version_id"],
78
+ v["timestamp"], v.get("author", "unknown"), v["message"]
79
+ )
80
+ valid = (expected == stored)
81
+ if not valid:
82
+ all_valid = False
83
+
84
+ results.append({
85
+ "version_id": v["version_id"],
86
+ "valid": valid,
87
+ "stored": stored[:24],
88
+ "expected": expected[:24],
89
+ "prev": prev_chain_hash[:24] if prev_chain_hash != "GENESIS" else "GENESIS",
90
+ "timestamp": v["timestamp"][:19].replace("T", " "),
91
+ "message": v["message"]
92
+ })
93
+ prev_chain_hash = stored
94
+
95
+ return all_valid, results
96
+
97
+
98
+ def format_chain_verification(filename, results):
99
+ lines = [f"\nChain verification: '{filename}'\n" + "─"*55]
100
+ for r in results:
101
+ if r["valid"]:
102
+ lines.append(
103
+ f"\n ✓ {r['version_id']} | {r.get('timestamp','')}\n"
104
+ f" chain: {r.get('stored','?')}…\n"
105
+ f" prev: {r.get('prev','?')}…\n"
106
+ f" \"{r.get('message','')}\""
107
+ )
108
+ else:
109
+ lines.append(
110
+ f"\n ✗ {r['version_id']} CHAIN BROKEN\n"
111
+ f" stored: {r.get('stored','?')}…\n"
112
+ f" expected: {r.get('expected','?')}…\n"
113
+ f" reason: {r.get('reason','hash mismatch')}"
114
+ )
115
+ lines.append(f"\n{'─'*55}")
116
+ if all(r["valid"] for r in results):
117
+ lines.append(
118
+ f" ✓ CHAIN INTACT — {len(results)} version(s) verified\n"
119
+ f" Complete history is authentic and unmodified.\n"
120
+ f" No versions were deleted, reordered, or tampered with."
121
+ )
122
+ else:
123
+ broken = [r["version_id"] for r in results if not r["valid"]]
124
+ lines.append(
125
+ f" ✗ CHAIN BROKEN at: {', '.join(broken)}\n"
126
+ f" History has been tampered with."
127
+ )
128
+ return "\n".join(lines)
129
+
130
+
131
+ def display_chain(filename):
132
+ if not os.path.exists(HISTORY_FILE):
133
+ return False, "No DataVault project found."
134
+ with open(HISTORY_FILE, "r", encoding="utf-8") as f:
135
+ history = json.load(f)
136
+ if filename not in history.get("files", {}):
137
+ return False, f"'{filename}' is not tracked."
138
+
139
+ versions = history["files"][filename]["versions"]
140
+ lines = [f"\nHash chain: '{filename}'\n" + "─"*55]
141
+ lines.append(" (Each block's hash includes the previous block's hash)")
142
+ lines.append(" (Changing any block invalidates all blocks after it)\n")
143
+
144
+ for i, v in enumerate(versions):
145
+ chain_hash = v.get("chain_hash", "not computed")[:20]
146
+ file_hash = v["hash"][:20]
147
+ prev_hash = v.get("prev_chain_hash", "GENESIS")
148
+ if prev_hash != "GENESIS":
149
+ prev_hash = prev_hash[:20]
150
+ lines.append(f" ┌─────────────────────────────────────┐")
151
+ lines.append(f" │ {v['version_id']} {v['timestamp'][:10]} │")
152
+ lines.append(f" │ file hash: {file_hash}… │")
153
+ lines.append(f" │ prev hash: {prev_hash}… │")
154
+ lines.append(f" │ chain hash: {chain_hash}… │")
155
+ lines.append(f" │ \"{v['message'][:30]}\"")
156
+ lines.append(f" └──────────────────┬──────────────────┘")
157
+ if i < len(versions) - 1:
158
+ lines.append(f" │")
159
+ lines.append(f" ▼")
160
+
161
+ return True, "\n".join(lines)
162
+
163
+
164
+ def _load_chain():
165
+ if os.path.exists(CHAIN_FILE):
166
+ with open(CHAIN_FILE, "r", encoding="utf-8") as f:
167
+ return json.load(f)
168
+ return {"files": {}}
169
+
170
+
171
+ def _save_chain(data):
172
+ with open(CHAIN_FILE, "w", encoding="utf-8") as f:
173
+ json.dump(data, f, indent=2)