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.
- datavault_cli-1.0.0/PKG-INFO +178 -0
- datavault_cli-1.0.0/README.md +154 -0
- datavault_cli-1.0.0/chain.py +173 -0
- datavault_cli-1.0.0/dashboard.py +211 -0
- datavault_cli-1.0.0/datavault.py +214 -0
- datavault_cli-1.0.0/datavault_cli.egg-info/PKG-INFO +178 -0
- datavault_cli-1.0.0/datavault_cli.egg-info/SOURCES.txt +15 -0
- datavault_cli-1.0.0/datavault_cli.egg-info/dependency_links.txt +1 -0
- datavault_cli-1.0.0/datavault_cli.egg-info/entry_points.txt +2 -0
- datavault_cli-1.0.0/datavault_cli.egg-info/top_level.txt +8 -0
- datavault_cli-1.0.0/migrate.py +70 -0
- datavault_cli-1.0.0/pi_bridge.py +214 -0
- datavault_cli-1.0.0/row_tracker.py +201 -0
- datavault_cli-1.0.0/setup.cfg +4 -0
- datavault_cli-1.0.0/setup.py +22 -0
- datavault_cli-1.0.0/signing.py +166 -0
- datavault_cli-1.0.0/vault_core.py +424 -0
|
@@ -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)
|