tibet-cbom 0.1.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.
- tibet_cbom-0.1.0/LICENSE +1 -0
- tibet_cbom-0.1.0/PKG-INFO +214 -0
- tibet_cbom-0.1.0/README.md +193 -0
- tibet_cbom-0.1.0/pyproject.toml +43 -0
- tibet_cbom-0.1.0/src/tibet_cbom/__init__.py +3 -0
- tibet_cbom-0.1.0/src/tibet_cbom/audit.py +115 -0
- tibet_cbom-0.1.0/src/tibet_cbom/cli.py +244 -0
- tibet_cbom-0.1.0/src/tibet_cbom/inspect.py +249 -0
- tibet_cbom-0.1.0/src/tibet_cbom/manifest.py +96 -0
- tibet_cbom-0.1.0/src/tibet_cbom/models.py +71 -0
- tibet_cbom-0.1.0/src/tibet_cbom/render.py +49 -0
- tibet_cbom-0.1.0/src/tibet_cbom/rewrap.py +234 -0
- tibet_cbom-0.1.0/src/tibet_cbom/sniff.py +152 -0
- tibet_cbom-0.1.0/src/tibet_cbom/verify.py +249 -0
tibet_cbom-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
MIT License — Copyright (c) 2026 Humotica, Jasper van de Meent, Root AI, Codex
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tibet-cbom
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Continuity bill of materials and State of Manifest inspector for sealed TIBET envelopes.
|
|
5
|
+
Author-email: Jasper van de Meent <info@humotica.com>, Codex <codex@humotica.nl>
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: audit,cbom,continuity,icc,manifest,provenance,som,tbz,tibet,timeline
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: System Administrators
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Classifier: Topic :: System :: Logging
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# tibet-cbom
|
|
23
|
+
|
|
24
|
+
**Continuity Bill of Materials and State of Manifest inspector for sealed TIBET envelopes.**
|
|
25
|
+
|
|
26
|
+
This sandbox package sketches a tool family around two closely related
|
|
27
|
+
ideas:
|
|
28
|
+
|
|
29
|
+
- `CBOM`
|
|
30
|
+
- continuity-aware bill of materials
|
|
31
|
+
- what is in the object, plus how that object sits in a continuity
|
|
32
|
+
chain
|
|
33
|
+
- `SoM`
|
|
34
|
+
- State of Manifest
|
|
35
|
+
- who asserted what, when, and how the manifest/surface relationship
|
|
36
|
+
evolved over time
|
|
37
|
+
|
|
38
|
+
The first operator surface is intentionally simple:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
tibet-cbom inspect file.tza
|
|
42
|
+
tibet-cbom inspect file.tza --json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
And the more human/forensic framing remains available through the alias:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
tibet-som inspect file.tza
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Why this exists
|
|
52
|
+
|
|
53
|
+
Normal file inspection answers:
|
|
54
|
+
|
|
55
|
+
- what is this file called
|
|
56
|
+
- how large is it
|
|
57
|
+
- what extension does it have
|
|
58
|
+
|
|
59
|
+
CBOM / SoM should answer richer questions:
|
|
60
|
+
|
|
61
|
+
- what class of sealed object is this
|
|
62
|
+
- what does its canonical surface appear to be
|
|
63
|
+
- what continuity identifiers are attached
|
|
64
|
+
- what events happened to it over time
|
|
65
|
+
- when was it renamed
|
|
66
|
+
- when was it verified
|
|
67
|
+
- when was a surface mismatch marked as partial or suspicious
|
|
68
|
+
|
|
69
|
+
That makes `tibet-cbom` feel less like `file(1)` and more like:
|
|
70
|
+
|
|
71
|
+
- `git log`
|
|
72
|
+
- for continuity-bearing envelopes
|
|
73
|
+
|
|
74
|
+
## Current sandbox scope
|
|
75
|
+
|
|
76
|
+
This skeleton does **not** claim full TBZ parsing yet.
|
|
77
|
+
|
|
78
|
+
It provides:
|
|
79
|
+
|
|
80
|
+
- package layout
|
|
81
|
+
- datamodel sketch
|
|
82
|
+
- CLI shape
|
|
83
|
+
- human and JSON rendering
|
|
84
|
+
- a first local file inspector that can grow into real manifest/event
|
|
85
|
+
extraction later
|
|
86
|
+
- optional continuityd audit JSONL merge for early SoM timelines
|
|
87
|
+
|
|
88
|
+
## Commands
|
|
89
|
+
|
|
90
|
+
### `inspect`
|
|
91
|
+
|
|
92
|
+
Compact human summary or JSON object.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
tibet-cbom inspect 2026-05-12.peer-eval.claude.urgent.tza
|
|
96
|
+
tibet-cbom inspect vergadering-dinsdag.pdf
|
|
97
|
+
tibet-cbom inspect file.tza --json
|
|
98
|
+
tibet-cbom inspect file.tza --audit-file expected-audit-example.jsonl
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `timeline`
|
|
102
|
+
|
|
103
|
+
Reserved for a later event-only view.
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
tibet-cbom timeline file.tza
|
|
107
|
+
tibet-cbom timeline file.tza --audit-file expected-audit-example.jsonl --json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### `authority`
|
|
111
|
+
|
|
112
|
+
Compact current authority state.
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
tibet-cbom authority file.tza
|
|
116
|
+
tibet-cbom authority file.tza --json
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `verify`
|
|
120
|
+
|
|
121
|
+
Explicit manifest and authority-step consistency check.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
tibet-cbom verify file.tza
|
|
125
|
+
tibet-cbom verify file.tza --json
|
|
126
|
+
tibet-cbom verify file.tza --audit-file expected-audit-example.jsonl
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `rewrap`
|
|
130
|
+
|
|
131
|
+
Sandbox ownership-transition event sketch.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
tibet-cbom rewrap task.tza \
|
|
135
|
+
--audit-file audit.jsonl \
|
|
136
|
+
--actor jis:humotica:jasper.admin \
|
|
137
|
+
--authority-mode admin \
|
|
138
|
+
--transition-type freeze \
|
|
139
|
+
--status frozen \
|
|
140
|
+
--effective-assignee jis:humotica:agent.ai \
|
|
141
|
+
--reason "manual triage hold" \
|
|
142
|
+
--freeze-reason-code human-review
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
If you also want a sandbox sealed bundle:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
tibet-cbom rewrap task.tza \
|
|
149
|
+
--audit-file audit.jsonl \
|
|
150
|
+
--actor jis:humotica:jasper.admin \
|
|
151
|
+
--authority-mode admin \
|
|
152
|
+
--transition-type freeze \
|
|
153
|
+
--status frozen \
|
|
154
|
+
--effective-assignee jis:humotica:agent.ai \
|
|
155
|
+
--reason "manual triage hold" \
|
|
156
|
+
--freeze-reason-code human-review \
|
|
157
|
+
--identity-dir ./admin-identity \
|
|
158
|
+
--emit-bundle /tmp/admin-freeze.tza
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Basic policy guards now apply:
|
|
162
|
+
|
|
163
|
+
- `handoff` requires `--handoff-target`
|
|
164
|
+
- `freeze` requires `--freeze-reason-code`
|
|
165
|
+
- `authority-mode=admin` expects an admin actor id
|
|
166
|
+
- emitted bundle signing identity must match transition actor
|
|
167
|
+
|
|
168
|
+
## Data model direction
|
|
169
|
+
|
|
170
|
+
The package uses two main record types:
|
|
171
|
+
|
|
172
|
+
- `CBOMDocument`
|
|
173
|
+
- file path
|
|
174
|
+
- human name
|
|
175
|
+
- canonical name hint
|
|
176
|
+
- continuity identifiers
|
|
177
|
+
- surface status
|
|
178
|
+
- material facts
|
|
179
|
+
- event timeline
|
|
180
|
+
- `SoMEvent`
|
|
181
|
+
- timestamp
|
|
182
|
+
- action
|
|
183
|
+
- actor
|
|
184
|
+
- action id
|
|
185
|
+
- notes / fields
|
|
186
|
+
|
|
187
|
+
This keeps the distinction clear:
|
|
188
|
+
|
|
189
|
+
- CBOM is the object summary
|
|
190
|
+
- SoM is the walkable event chain inside or around that object
|
|
191
|
+
|
|
192
|
+
## Likely next steps
|
|
193
|
+
|
|
194
|
+
- extract canonical surface from real manifests
|
|
195
|
+
- map continuity IDs from real payloads/manifests
|
|
196
|
+
- render surface status transitions:
|
|
197
|
+
- `MATCH`
|
|
198
|
+
- `PARTIAL`
|
|
199
|
+
- `DISGUISED`
|
|
200
|
+
- `RENAMED`
|
|
201
|
+
- deepen `verify` into fuller chain integrity / succession validation
|
|
202
|
+
|
|
203
|
+
## Short framing
|
|
204
|
+
|
|
205
|
+
VCs answer:
|
|
206
|
+
|
|
207
|
+
- who are you
|
|
208
|
+
|
|
209
|
+
SoM answers:
|
|
210
|
+
|
|
211
|
+
- what did you manifest and when
|
|
212
|
+
|
|
213
|
+
CBOM then becomes the readable continuity-aware object view that ties
|
|
214
|
+
those together.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# tibet-cbom
|
|
2
|
+
|
|
3
|
+
**Continuity Bill of Materials and State of Manifest inspector for sealed TIBET envelopes.**
|
|
4
|
+
|
|
5
|
+
This sandbox package sketches a tool family around two closely related
|
|
6
|
+
ideas:
|
|
7
|
+
|
|
8
|
+
- `CBOM`
|
|
9
|
+
- continuity-aware bill of materials
|
|
10
|
+
- what is in the object, plus how that object sits in a continuity
|
|
11
|
+
chain
|
|
12
|
+
- `SoM`
|
|
13
|
+
- State of Manifest
|
|
14
|
+
- who asserted what, when, and how the manifest/surface relationship
|
|
15
|
+
evolved over time
|
|
16
|
+
|
|
17
|
+
The first operator surface is intentionally simple:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
tibet-cbom inspect file.tza
|
|
21
|
+
tibet-cbom inspect file.tza --json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
And the more human/forensic framing remains available through the alias:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
tibet-som inspect file.tza
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Why this exists
|
|
31
|
+
|
|
32
|
+
Normal file inspection answers:
|
|
33
|
+
|
|
34
|
+
- what is this file called
|
|
35
|
+
- how large is it
|
|
36
|
+
- what extension does it have
|
|
37
|
+
|
|
38
|
+
CBOM / SoM should answer richer questions:
|
|
39
|
+
|
|
40
|
+
- what class of sealed object is this
|
|
41
|
+
- what does its canonical surface appear to be
|
|
42
|
+
- what continuity identifiers are attached
|
|
43
|
+
- what events happened to it over time
|
|
44
|
+
- when was it renamed
|
|
45
|
+
- when was it verified
|
|
46
|
+
- when was a surface mismatch marked as partial or suspicious
|
|
47
|
+
|
|
48
|
+
That makes `tibet-cbom` feel less like `file(1)` and more like:
|
|
49
|
+
|
|
50
|
+
- `git log`
|
|
51
|
+
- for continuity-bearing envelopes
|
|
52
|
+
|
|
53
|
+
## Current sandbox scope
|
|
54
|
+
|
|
55
|
+
This skeleton does **not** claim full TBZ parsing yet.
|
|
56
|
+
|
|
57
|
+
It provides:
|
|
58
|
+
|
|
59
|
+
- package layout
|
|
60
|
+
- datamodel sketch
|
|
61
|
+
- CLI shape
|
|
62
|
+
- human and JSON rendering
|
|
63
|
+
- a first local file inspector that can grow into real manifest/event
|
|
64
|
+
extraction later
|
|
65
|
+
- optional continuityd audit JSONL merge for early SoM timelines
|
|
66
|
+
|
|
67
|
+
## Commands
|
|
68
|
+
|
|
69
|
+
### `inspect`
|
|
70
|
+
|
|
71
|
+
Compact human summary or JSON object.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
tibet-cbom inspect 2026-05-12.peer-eval.claude.urgent.tza
|
|
75
|
+
tibet-cbom inspect vergadering-dinsdag.pdf
|
|
76
|
+
tibet-cbom inspect file.tza --json
|
|
77
|
+
tibet-cbom inspect file.tza --audit-file expected-audit-example.jsonl
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `timeline`
|
|
81
|
+
|
|
82
|
+
Reserved for a later event-only view.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
tibet-cbom timeline file.tza
|
|
86
|
+
tibet-cbom timeline file.tza --audit-file expected-audit-example.jsonl --json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `authority`
|
|
90
|
+
|
|
91
|
+
Compact current authority state.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
tibet-cbom authority file.tza
|
|
95
|
+
tibet-cbom authority file.tza --json
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### `verify`
|
|
99
|
+
|
|
100
|
+
Explicit manifest and authority-step consistency check.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
tibet-cbom verify file.tza
|
|
104
|
+
tibet-cbom verify file.tza --json
|
|
105
|
+
tibet-cbom verify file.tza --audit-file expected-audit-example.jsonl
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `rewrap`
|
|
109
|
+
|
|
110
|
+
Sandbox ownership-transition event sketch.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
tibet-cbom rewrap task.tza \
|
|
114
|
+
--audit-file audit.jsonl \
|
|
115
|
+
--actor jis:humotica:jasper.admin \
|
|
116
|
+
--authority-mode admin \
|
|
117
|
+
--transition-type freeze \
|
|
118
|
+
--status frozen \
|
|
119
|
+
--effective-assignee jis:humotica:agent.ai \
|
|
120
|
+
--reason "manual triage hold" \
|
|
121
|
+
--freeze-reason-code human-review
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If you also want a sandbox sealed bundle:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
tibet-cbom rewrap task.tza \
|
|
128
|
+
--audit-file audit.jsonl \
|
|
129
|
+
--actor jis:humotica:jasper.admin \
|
|
130
|
+
--authority-mode admin \
|
|
131
|
+
--transition-type freeze \
|
|
132
|
+
--status frozen \
|
|
133
|
+
--effective-assignee jis:humotica:agent.ai \
|
|
134
|
+
--reason "manual triage hold" \
|
|
135
|
+
--freeze-reason-code human-review \
|
|
136
|
+
--identity-dir ./admin-identity \
|
|
137
|
+
--emit-bundle /tmp/admin-freeze.tza
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Basic policy guards now apply:
|
|
141
|
+
|
|
142
|
+
- `handoff` requires `--handoff-target`
|
|
143
|
+
- `freeze` requires `--freeze-reason-code`
|
|
144
|
+
- `authority-mode=admin` expects an admin actor id
|
|
145
|
+
- emitted bundle signing identity must match transition actor
|
|
146
|
+
|
|
147
|
+
## Data model direction
|
|
148
|
+
|
|
149
|
+
The package uses two main record types:
|
|
150
|
+
|
|
151
|
+
- `CBOMDocument`
|
|
152
|
+
- file path
|
|
153
|
+
- human name
|
|
154
|
+
- canonical name hint
|
|
155
|
+
- continuity identifiers
|
|
156
|
+
- surface status
|
|
157
|
+
- material facts
|
|
158
|
+
- event timeline
|
|
159
|
+
- `SoMEvent`
|
|
160
|
+
- timestamp
|
|
161
|
+
- action
|
|
162
|
+
- actor
|
|
163
|
+
- action id
|
|
164
|
+
- notes / fields
|
|
165
|
+
|
|
166
|
+
This keeps the distinction clear:
|
|
167
|
+
|
|
168
|
+
- CBOM is the object summary
|
|
169
|
+
- SoM is the walkable event chain inside or around that object
|
|
170
|
+
|
|
171
|
+
## Likely next steps
|
|
172
|
+
|
|
173
|
+
- extract canonical surface from real manifests
|
|
174
|
+
- map continuity IDs from real payloads/manifests
|
|
175
|
+
- render surface status transitions:
|
|
176
|
+
- `MATCH`
|
|
177
|
+
- `PARTIAL`
|
|
178
|
+
- `DISGUISED`
|
|
179
|
+
- `RENAMED`
|
|
180
|
+
- deepen `verify` into fuller chain integrity / succession validation
|
|
181
|
+
|
|
182
|
+
## Short framing
|
|
183
|
+
|
|
184
|
+
VCs answer:
|
|
185
|
+
|
|
186
|
+
- who are you
|
|
187
|
+
|
|
188
|
+
SoM answers:
|
|
189
|
+
|
|
190
|
+
- what did you manifest and when
|
|
191
|
+
|
|
192
|
+
CBOM then becomes the readable continuity-aware object view that ties
|
|
193
|
+
those together.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tibet-cbom"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Continuity bill of materials and State of Manifest inspector for sealed TIBET envelopes."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Jasper van de Meent", email = "info@humotica.com"},
|
|
14
|
+
{name = "Codex", email = "codex@humotica.nl"},
|
|
15
|
+
]
|
|
16
|
+
keywords = [
|
|
17
|
+
"tibet", "cbom", "som", "manifest", "continuity", "tbz",
|
|
18
|
+
"icc", "timeline", "audit", "provenance",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 3 - Alpha",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Intended Audience :: System Administrators",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Topic :: Security",
|
|
30
|
+
"Topic :: System :: Logging",
|
|
31
|
+
]
|
|
32
|
+
dependencies = []
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
tibet-cbom = "tibet_cbom.cli:main"
|
|
36
|
+
tcbom = "tibet_cbom.cli:main"
|
|
37
|
+
tibet-som = "tibet_cbom.cli:main"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.sdist]
|
|
40
|
+
include = ["/src"]
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/tibet_cbom"]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .models import CBOMDocument, SoMEvent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _ts_to_iso(value) -> str:
|
|
11
|
+
if isinstance(value, (int, float)):
|
|
12
|
+
return datetime.fromtimestamp(value, tz=timezone.utc).strftime(
|
|
13
|
+
"%Y-%m-%dT%H:%M:%SZ"
|
|
14
|
+
)
|
|
15
|
+
if isinstance(value, str) and value:
|
|
16
|
+
return value
|
|
17
|
+
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _event_notes(record: dict) -> list[str]:
|
|
21
|
+
notes: list[str] = []
|
|
22
|
+
for key in (
|
|
23
|
+
"intake_class",
|
|
24
|
+
"disposition_hint",
|
|
25
|
+
"surface_status",
|
|
26
|
+
"disposition",
|
|
27
|
+
"canonical_name",
|
|
28
|
+
):
|
|
29
|
+
value = record.get(key)
|
|
30
|
+
if value not in (None, "", [], {}):
|
|
31
|
+
notes.append(f"{key}={value}")
|
|
32
|
+
if record.get("renamed_by_operator"):
|
|
33
|
+
notes.append("renamed_by_operator=true")
|
|
34
|
+
return notes
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def merge_audit_file(doc: CBOMDocument, audit_path: str) -> CBOMDocument:
|
|
38
|
+
path = Path(audit_path)
|
|
39
|
+
if not path.exists():
|
|
40
|
+
raise FileNotFoundError(f"no such audit file: {path}")
|
|
41
|
+
|
|
42
|
+
matched: list[dict] = []
|
|
43
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
|
44
|
+
for line in f:
|
|
45
|
+
line = line.strip()
|
|
46
|
+
if not line:
|
|
47
|
+
continue
|
|
48
|
+
try:
|
|
49
|
+
record = json.loads(line)
|
|
50
|
+
except json.JSONDecodeError:
|
|
51
|
+
continue
|
|
52
|
+
if record.get("name") == doc.file_name:
|
|
53
|
+
matched.append(record)
|
|
54
|
+
|
|
55
|
+
if not matched:
|
|
56
|
+
doc.events.append(
|
|
57
|
+
SoMEvent(
|
|
58
|
+
timestamp=datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
59
|
+
action="audit-unmatched",
|
|
60
|
+
actor="tibet-cbom",
|
|
61
|
+
action_id="act_audit_unmatched",
|
|
62
|
+
notes=[f"audit_file={path.name}", "no records matched file name"],
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
return doc
|
|
66
|
+
|
|
67
|
+
if len(matched) > 1:
|
|
68
|
+
doc.events.append(
|
|
69
|
+
SoMEvent(
|
|
70
|
+
timestamp=datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
71
|
+
action="audit-name-collision",
|
|
72
|
+
actor="tibet-cbom",
|
|
73
|
+
action_id="act_audit_collision",
|
|
74
|
+
notes=[
|
|
75
|
+
f"audit_file={path.name}",
|
|
76
|
+
f"matched_records={len(matched)}",
|
|
77
|
+
"top-level fields not overwritten because name-only match is ambiguous",
|
|
78
|
+
],
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
for idx, record in enumerate(matched, start=1):
|
|
83
|
+
stage = record.get("stage", "audit")
|
|
84
|
+
actor = record.get("actor_id") or record.get("actor") or "continuityd"
|
|
85
|
+
action_id = record.get("action_id") or f"act_audit_{idx}"
|
|
86
|
+
doc.events.append(
|
|
87
|
+
SoMEvent(
|
|
88
|
+
timestamp=_ts_to_iso(record.get("ts")),
|
|
89
|
+
action=stage,
|
|
90
|
+
actor=actor,
|
|
91
|
+
action_id=action_id,
|
|
92
|
+
notes=_event_notes(record),
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if len(matched) > 1:
|
|
97
|
+
return doc
|
|
98
|
+
|
|
99
|
+
latest = matched[-1]
|
|
100
|
+
if latest.get("canonical_name"):
|
|
101
|
+
doc.canonical_name = latest["canonical_name"]
|
|
102
|
+
if latest.get("continuity_id"):
|
|
103
|
+
doc.continuity_id = latest["continuity_id"]
|
|
104
|
+
if latest.get("object_id"):
|
|
105
|
+
doc.object_id = latest["object_id"]
|
|
106
|
+
if latest.get("surface_status"):
|
|
107
|
+
doc.surface_status = latest["surface_status"]
|
|
108
|
+
if latest.get("disposition"):
|
|
109
|
+
doc.disposition_hint = latest["disposition"]
|
|
110
|
+
elif latest.get("disposition_hint"):
|
|
111
|
+
doc.disposition_hint = latest["disposition_hint"]
|
|
112
|
+
if latest.get("intake_class"):
|
|
113
|
+
doc.intake_class = latest["intake_class"]
|
|
114
|
+
|
|
115
|
+
return doc
|