forked from ChelseaKR/habitable
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpacket-bundle.schema.json
More file actions
260 lines (260 loc) · 13.7 KB
/
Copy pathpacket-bundle.schema.json
File metadata and controls
260 lines (260 loc) · 13.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://chelseakr.github.io/habitable/schema/packet-bundle-v1.schema.json",
"title": "habitable evidence packet — bundle.json (v1)",
"description": "The machine-readable contract for the bundle.json at the root of a habitable evidence packet. Stable per the compatibility policy in docs/bundle-schema.md: additive within a major packet_version; a breaking change is a major bump. The signed sibling file bundle.sig.json is described by $defs/signature. Hashes are lowercase hex SHA-256 (64 chars); the chain-of-custody genesis prev_hash is 64 zeros.",
"type": "object",
"required": [
"packet_version",
"case_id",
"generated_at",
"producer_fingerprint",
"hash_algorithm",
"items",
"custody_proof",
"appendix"
],
"additionalProperties": true,
"properties": {
"packet_version": {
"type": "integer",
"minimum": 1,
"description": "Packet format version. A verifier accepts 1..SUPPORTED_PACKET_VERSION and rejects newer-than-supported packets rather than mis-verifying."
},
"case_id": { "type": "string" },
"unit": { "type": "string", "description": "Unit label; may be empty." },
"scope": {
"type": "object",
"properties": {
"type": { "type": "string", "enum": ["issue", "unit"] },
"issue_id": { "type": "string" },
"since": { "type": "string", "description": "Lower bound on capture time; may be empty." },
"statement": {
"type": "string",
"description": "Human-readable minimal-disclosure scope summary (e.g. 'Scope: issue <id> only …' or 'Scope: the whole unit …'). Also rendered, localized, in packet.html/packet.pdf; see docs/legal/minimal-disclosure.md."
},
"exclusions": {
"type": "array",
"items": { "type": "string" },
"description": "Explicit statements of what this export does NOT include (vault contents outside the scope; items captured before 'since' when set). Defensible against over-broad discovery (item R-35)."
}
},
"additionalProperties": true
},
"generated_at": {
"type": "string",
"description": "Packet generation time, ISO 8601 UTC, e.g. 2026-01-02T00:00:00Z."
},
"producer_fingerprint": {
"type": "string",
"description": "Producing device's identity fingerprint (xxxx-xxxx-xxxx-xxxx)."
},
"hash_algorithm": { "type": "string", "const": "sha256" },
"language": { "type": "string", "description": "BCP 47-ish language tag of the rendered packet, e.g. en, es." },
"template": {
"type": "object",
"properties": {
"header": { "type": "string" },
"footer": { "type": "string" }
},
"additionalProperties": true
},
"issues": { "type": "array", "items": { "$ref": "#/$defs/issue" } },
"timeline": { "type": "array", "items": { "$ref": "#/$defs/timelineEntry" } },
"items": { "type": "array", "items": { "$ref": "#/$defs/item" } },
"custody_proof": { "$ref": "#/$defs/custodyProof" },
"disclosures": {
"type": "array",
"items": { "type": "string" },
"description": "Human-readable notes of what the packet reveals (e.g. location stripped from shared copies; custody identities not exported; sealed originals embedded with full metadata). Also rendered, localized, in packet.html/packet.pdf."
},
"appendix": {
"type": "object",
"required": ["item_count", "timestamped_count", "includes_originals"],
"properties": {
"item_count": { "type": "integer", "minimum": 0 },
"timestamped_count": { "type": "integer", "minimum": 0 },
"includes_originals": { "type": "boolean" }
},
"additionalProperties": true
}
},
"$defs": {
"hexSha256": {
"type": "string",
"pattern": "^[0-9a-f]{64}$",
"description": "Lowercase hex SHA-256 digest (64 chars). The custody genesis prev_hash is 64 zeros."
},
"issue": {
"type": "object",
"required": ["issue_id", "category", "room", "title", "status", "severity", "description"],
"properties": {
"issue_id": { "type": "string" },
"category": { "type": "string", "description": "e.g. heat, mold, pests, water, electrical, structural." },
"room": { "type": "string" },
"title": { "type": "string" },
"status": { "type": "string" },
"severity": { "type": "string" },
"description": { "type": "string" }
},
"additionalProperties": true
},
"timelineEntry": {
"type": "object",
"required": ["entry_id", "issue_id", "kind", "text", "hlc"],
"properties": {
"entry_id": { "type": "string" },
"issue_id": { "type": "string" },
"kind": { "type": "string" },
"text": { "type": "string" },
"hlc": { "type": "string", "description": "Ordering token. Opaque, per-case-salted digest in packet_version >= 2 (no wall-clock/node metadata); the raw wall_ms.counter.node_id HLC stamp in v1." }
},
"additionalProperties": true
},
"item": {
"type": "object",
"required": ["capture_id", "content_hash", "media_type", "captured_at", "shared_name", "shared_hash", "has_original", "archive_timestamps"],
"properties": {
"capture_id": { "type": "string" },
"issue_id": { "type": "string" },
"content_hash": { "$ref": "#/$defs/hexSha256", "description": "SHA-256 of the sealed original; the value the RFC 3161 token is taken over." },
"media_type": { "type": "string", "description": "MIME type, e.g. image/jpeg." },
"captured_at": { "type": "string" },
"shared_name": { "type": "string", "description": "Filename under media/ of the location-stripped shared copy; empty if none." },
"shared_hash": {
"oneOf": [{ "$ref": "#/$defs/hexSha256" }, { "type": "string", "maxLength": 0 }],
"description": "SHA-256 of the shared copy; empty when no shared media for this item."
},
"stripped": { "type": "string", "description": "Human-readable note of which metadata was removed from the shared copy (e.g. 'gps', 'none', 'skipped')." },
"has_original": { "type": "boolean", "description": "Whether the sealed original is embedded under originals/." },
"timestamp": {
"oneOf": [{ "$ref": "#/$defs/timestampToken" }, { "type": "null" }],
"description": "RFC 3161 (or dev) token over content_hash; null while awaiting timestamp."
},
"archive_timestamps": {
"type": "array",
"items": { "$ref": "#/$defs/timestampToken" },
"description": "Ordered archive (re-)timestamps chaining back to the primary token."
},
"additional_timestamps": {
"type": "array",
"items": { "$ref": "#/$defs/timestampToken" },
"description": "Independent redundant timestamps from other authorities over the SAME content_hash (not a chain). Provides multiple-authority redundancy: the verifier treats the item as timestamped if at least one authority (primary or additional) verifies. Absent in single-authority packets."
},
"sensor": {
"oneOf": [{ "$ref": "#/$defs/sensorSeries" }, { "type": "null" }],
"description": "Present (non-null) only for instrument data-file captures (EXP-09, e.g. a temperature-logger or moisture-meter CSV). The readings interpreted from the sealed original for accessible chart+table rendering; the CSV bytes themselves remain the hash-anchored evidence under content_hash. null (or absent) for photo/video captures."
}
},
"additionalProperties": true
},
"sensorSeries": {
"type": "object",
"description": "An instrument CSV interpreted into a renderable series (EXP-09). Corroboration only, never proof of cause; the chart is a visual convenience over the readings table, which is the accessible source of truth.",
"required": ["label_header", "value_header", "readings", "total_rows", "truncated", "minimum", "maximum", "mean"],
"properties": {
"label_header": { "type": "string", "description": "Column name for each reading's label (often a timestamp); 'Reading' when the CSV had no header." },
"value_header": { "type": "string", "description": "Column name for the numeric value; 'Value' when the CSV had no header. Any trailing '(unit)' is split out into 'unit'." },
"unit": { "oneOf": [{ "type": "string" }, { "type": "null" }], "description": "Unit parsed from the value header (e.g. 'F', '%RH'); null if none." },
"readings": {
"type": "array",
"description": "Rendered readings, in file order. Capped at 500 rows; see 'truncated'/'total_rows'. Full data remains in the sealed original.",
"items": {
"type": "object",
"required": ["label", "value"],
"properties": {
"label": { "type": "string" },
"value": { "type": "number" }
}
}
},
"total_rows": { "type": "integer", "description": "Total numeric readings parsed (before any truncation)." },
"truncated": { "type": "boolean", "description": "Whether 'readings' is a truncated prefix of the full series." },
"minimum": { "type": "number" },
"maximum": { "type": "number" },
"mean": { "type": "number" },
"warnings": {
"type": "array",
"items": { "type": "string" },
"description": "Non-fatal parse notes (rows skipped, truncation), surfaced verbatim in the packet."
}
},
"additionalProperties": false
},
"timestampToken": {
"type": "object",
"required": ["kind", "tsa_name", "token_b64"],
"properties": {
"kind": { "type": "string", "enum": ["rfc3161", "dev"], "description": "'dev' is a non-production offline authority; production packets use 'rfc3161'." },
"tsa_name": { "type": "string" },
"token_b64": { "type": "string", "contentEncoding": "base64", "description": "DER (rfc3161) or canonical-JSON (dev) token, base64-encoded." }
},
"additionalProperties": true
},
"custodyProof": {
"type": "object",
"required": ["algorithm", "length", "head_hash", "entries"],
"description": "Identity-stripped, independently checkable proof the chain of custody is intact. Actor identities, salts, signatures, and private_details are NOT present (they stay in the union's vault).",
"properties": {
"algorithm": { "type": "string", "const": "sha256" },
"length": { "type": "integer", "minimum": 0 },
"head_hash": {
"oneOf": [{ "$ref": "#/$defs/hexSha256" }, { "type": "string", "pattern": "^0{64}$" }],
"description": "entry_hash of the last entry, or 64 zeros for an empty chain."
},
"items": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"entries": { "type": "integer", "minimum": 0 },
"last_action": { "type": "string" },
"head_hash": { "$ref": "#/$defs/hexSha256" }
},
"additionalProperties": true
}
},
"entries": { "type": "array", "items": { "$ref": "#/$defs/custodyEntry" } }
},
"additionalProperties": true
},
"custodyEntry": {
"type": "object",
"description": "Exported (redacted) custody entry. entry_hash = SHA-256(canonical_json of {seq, action, item_id, hlc, actor_commitment, details(sorted), prev_hash}).",
"required": ["seq", "action", "item_id", "hlc", "actor_commitment", "details", "prev_hash", "entry_hash"],
"properties": {
"seq": { "type": "integer", "minimum": 1, "description": "1-based, strictly increasing position in the chain." },
"action": {
"type": "string",
"enum": ["captured", "imported", "fixity_checked", "timestamped", "viewed", "copied_for_sharing", "included_in_packet", "note_added"]
},
"item_id": { "type": "string" },
"hlc": { "type": "string", "description": "Ordering token. Opaque, per-case-salted digest in packet_version >= 2 (no wall-clock/node metadata); the raw HLC stamp in v1. The exported chain is re-hashed over this value so it still verifies standalone." },
"actor_commitment": { "$ref": "#/$defs/hexSha256", "description": "Salted commitment SHA-256(salt:actor); the clear actor and salt are vault-only." },
"details": {
"type": "object",
"additionalProperties": { "type": "string" },
"description": "Verification-relevant, non-identifying facts only (hashes, media_type, tsa, etc.). A copied_for_sharing entry carries content_hash + shared_hash + stripped."
},
"prev_hash": {
"oneOf": [{ "$ref": "#/$defs/hexSha256" }, { "type": "string", "pattern": "^0{64}$" }],
"description": "entry_hash of the previous entry; 64 zeros for the genesis link."
},
"entry_hash": { "$ref": "#/$defs/hexSha256" }
},
"additionalProperties": false
},
"signature": {
"type": "object",
"description": "Contents of the sibling file bundle.sig.json. The producer's Ed25519 key signs the ASCII hex of bundle_sha256.",
"required": ["sign_public", "bundle_sha256", "signature"],
"properties": {
"producer_fingerprint": { "type": "string" },
"sign_public": { "type": "string", "contentEncoding": "base64", "description": "Raw Ed25519 public key, base64." },
"bundle_sha256": { "$ref": "#/$defs/hexSha256", "description": "SHA-256 of the exact bundle.json bytes." },
"signature": { "type": "string", "contentEncoding": "base64", "description": "Ed25519 signature over the ASCII of bundle_sha256." }
},
"additionalProperties": true
}
}
}