actproof-events 1.4.0rc1__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.
- actproof_events-1.4.0rc1/.gitignore +218 -0
- actproof_events-1.4.0rc1/CATALOGUE_LOADER_CONTRACT.md +290 -0
- actproof_events-1.4.0rc1/CONTRIBUTING_ACTS.md +187 -0
- actproof_events-1.4.0rc1/LICENSE +201 -0
- actproof_events-1.4.0rc1/PKG-INFO +50 -0
- actproof_events-1.4.0rc1/README.md +18 -0
- actproof_events-1.4.0rc1/actproof_events/__init__.py +182 -0
- actproof_events-1.4.0rc1/catalogue/acts/actproof/STEP-2-NOTES.md +117 -0
- actproof_events-1.4.0rc1/catalogue/acts/actproof/software_release.v1.json +83 -0
- actproof_events-1.4.0rc1/catalogue/acts/actproof/software_release.v1.test_vectors.json +107 -0
- actproof_events-1.4.0rc1/catalogue/acts/actproof/standards_engagement_record.v1.json +108 -0
- actproof_events-1.4.0rc1/catalogue/acts/actproof/standards_engagement_record.v1.test_vectors.json +131 -0
- actproof_events-1.4.0rc1/catalogue/acts/corporate/_deprecated/README.md +23 -0
- actproof_events-1.4.0rc1/catalogue/acts/corporate/_deprecated/board_resolution_v1.json +46 -0
- actproof_events-1.4.0rc1/catalogue/acts/corporate/_deprecated/board_resolution_v1.test_vectors.json +249 -0
- actproof_events-1.4.0rc1/catalogue/acts/democracy/civil_society_mandate.settlement.v1.json +83 -0
- actproof_events-1.4.0rc1/catalogue/acts/democracy/civil_society_mandate.settlement.v1.test_vectors.json +107 -0
- actproof_events-1.4.0rc1/catalogue/acts/eu/ai_act/art26/_deprecated/README.md +23 -0
- actproof_events-1.4.0rc1/catalogue/acts/eu/ai_act/art26/_deprecated/risk_assessment.json +46 -0
- actproof_events-1.4.0rc1/catalogue/acts/eu/ai_act/art26/_deprecated/risk_assessment.test_vectors.json +296 -0
- actproof_events-1.4.0rc1/catalogue/acts/eu/dora/ict_incident_notification_initial.v1.json +142 -0
- actproof_events-1.4.0rc1/catalogue/acts/eu/dora/ict_incident_notification_initial.v1.test_vectors.json +136 -0
- actproof_events-1.4.0rc1/catalogue/acts/eu/eudr/dds_preparation.v1.json +98 -0
- actproof_events-1.4.0rc1/catalogue/acts/eu/eudr/dds_preparation.v1.test_vectors.json +114 -0
- actproof_events-1.4.0rc1/catalogue/acts/eu/nis2/art20/_deprecated/README.md +21 -0
- actproof_events-1.4.0rc1/catalogue/acts/eu/nis2/art20/_deprecated/approval.json +44 -0
- actproof_events-1.4.0rc1/catalogue/acts/eu/nis2/art20/_deprecated/approval.test_vectors.json +219 -0
- actproof_events-1.4.0rc1/catalogue/acts/eu/nis2/art20/management_body_approval.v1.json +98 -0
- actproof_events-1.4.0rc1/catalogue/acts/eu/nis2/art20/management_body_approval.v1.test_vectors.json +106 -0
- actproof_events-1.4.0rc1/docs/releases/v1.4-rc1.md +292 -0
- actproof_events-1.4.0rc1/docs/releases/v1.5-rc1.md +96 -0
- actproof_events-1.4.0rc1/pyproject.toml +103 -0
- actproof_events-1.4.0rc1/salt-erasure/README.md +181 -0
- actproof_events-1.4.0rc1/salt-erasure/salt_erasure.py +413 -0
- actproof_events-1.4.0rc1/salt-erasure/test_vectors.json +197 -0
- actproof_events-1.4.0rc1/scripts/compute_test_vectors.py +298 -0
- actproof_events-1.4.0rc1/scripts/deploy-v1.4-rc1.sh +453 -0
- actproof_events-1.4.0rc1/scripts/test_vector_inputs/README.md +46 -0
- actproof_events-1.4.0rc1/scripts/test_vector_inputs/actproof_software_release_v1_001.json +53 -0
- actproof_events-1.4.0rc1/scripts/test_vector_inputs/actproof_standards_engagement_record_v1_001.json +69 -0
- actproof_events-1.4.0rc1/scripts/test_vector_inputs/actproof_standards_engagement_record_v1_001_evidence/implementation_repository_state.txt +34 -0
- actproof_events-1.4.0rc1/scripts/test_vector_inputs/actproof_standards_engagement_record_v1_001_evidence/working_group_charter_reference.txt +39 -0
- actproof_events-1.4.0rc1/scripts/test_vector_inputs/democracy_civil_society_mandate_settlement_v1_001.json +53 -0
- actproof_events-1.4.0rc1/scripts/test_vector_inputs/eu_dora_ict_incident_notification_initial_v1_001.json +70 -0
- actproof_events-1.4.0rc1/scripts/test_vector_inputs/eu_dora_ict_incident_notification_initial_v1_001_evidence/classification_committee_record.txt +107 -0
- actproof_events-1.4.0rc1/scripts/test_vector_inputs/eu_dora_ict_incident_notification_initial_v1_001_evidence/detection_system_log_excerpt.txt +83 -0
- actproof_events-1.4.0rc1/scripts/test_vector_inputs/eudr_dds_v1_001.json +60 -0
- actproof_events-1.4.0rc1/scripts/test_vector_inputs/nis2_art20_v1_001.json +52 -0
- actproof_events-1.4.0rc1/spec/actproof-events.spec.md +472 -0
- actproof_events-1.4.0rc1/spec/schemas/act_catalogue_entry.v2.json +185 -0
- actproof_events-1.4.0rc1/spec/schemas/act_catalogue_entry.v3.json +357 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
# Temporary file for partial code execution
|
|
204
|
+
tempCodeRunnerFile.py
|
|
205
|
+
|
|
206
|
+
# Ruff stuff:
|
|
207
|
+
.ruff_cache/
|
|
208
|
+
|
|
209
|
+
# PyPI configuration file
|
|
210
|
+
.pypirc
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
216
|
+
|
|
217
|
+
# Streamlit
|
|
218
|
+
.streamlit/secrets.toml
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Catalogue loader contract
|
|
2
|
+
|
|
3
|
+
This document is the binding contract between the ActProof Events substrate and any implementation that loads catalogue entries from the substrate's filesystem layout. Implementations that satisfy this contract are conforming catalogue loaders for v1.4 of the specification.
|
|
4
|
+
|
|
5
|
+
The contract is intentionally implementation-agnostic. The reference implementation in Quoruna is written in Python and lives at `catalogue_loader.py` in the Quoruna repository. Future implementations in other languages (Go, Rust, TypeScript) MUST satisfy the same contract to be considered conforming.
|
|
6
|
+
|
|
7
|
+
## Scope
|
|
8
|
+
|
|
9
|
+
This document covers:
|
|
10
|
+
|
|
11
|
+
- How a loader reads catalogue entries from the filesystem
|
|
12
|
+
- How a loader distinguishes v2 entries from deprecated v1 entries
|
|
13
|
+
- How a loader validates a candidate manifest against a catalogue entry
|
|
14
|
+
- How a loader caches and reloads catalogue entries
|
|
15
|
+
- How a loader reports errors
|
|
16
|
+
|
|
17
|
+
This document does NOT cover:
|
|
18
|
+
|
|
19
|
+
- The transport layer between the loader and any UI or API surface
|
|
20
|
+
- The persistence layer for issued attestations (that lives in the consuming implementation)
|
|
21
|
+
- The signing or anchoring pipeline (that is downstream of the loader)
|
|
22
|
+
- Any internal database schema choices
|
|
23
|
+
|
|
24
|
+
## Terminology
|
|
25
|
+
|
|
26
|
+
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL are interpreted per RFC 2119.
|
|
27
|
+
|
|
28
|
+
**Catalogue entry** refers to a JSON document conforming to `actproof.act_catalogue_entry.v2` (defined in spec Section 2.1).
|
|
29
|
+
|
|
30
|
+
**v1 entry** refers to a JSON document under any `_deprecated/` directory that uses the predecessor voting-shaped schema.
|
|
31
|
+
|
|
32
|
+
**Manifest** refers to a candidate `actproof.attestation_manifest.v1` document (defined in spec Section 3).
|
|
33
|
+
|
|
34
|
+
**New issuance** means the loader is being asked to surface or accept an act_type_id for the purpose of creating a new attestation.
|
|
35
|
+
|
|
36
|
+
**Historical rendering** means the loader is being asked to resolve an act_type_id that was issued previously, in order to render a receipt or verification page for an existing attestation.
|
|
37
|
+
|
|
38
|
+
## 1. Loading requirements
|
|
39
|
+
|
|
40
|
+
### 1.1 Surfacing v2 entries
|
|
41
|
+
|
|
42
|
+
The loader MUST surface only v2 entries for new issuance. A v2 entry is one whose `schema` field equals the literal string `"actproof.act_catalogue_entry.v2"`.
|
|
43
|
+
|
|
44
|
+
The loader MUST validate each loaded entry against the JSON Schema at `spec/schemas/act_catalogue_entry.v2.json`. Entries that fail schema validation MUST NOT be surfaced for new issuance and SHOULD be logged at warning level for operator attention.
|
|
45
|
+
|
|
46
|
+
### 1.2 Refusing v1 entries for new issuance
|
|
47
|
+
|
|
48
|
+
The loader MUST refuse to surface entries from any `_deprecated/` directory for new issuance. Specifically:
|
|
49
|
+
|
|
50
|
+
- Any catalogue file whose path contains a `_deprecated/` segment MUST NOT appear in the list of available act types for new attestation creation.
|
|
51
|
+
- Any attempt to begin a new attestation against a v1 act_type_id (e.g., `op:eu.nis2.art20.approval`) MUST be rejected with a clear error message indicating the v1 entry has been superseded.
|
|
52
|
+
|
|
53
|
+
### 1.3 Allowing v1 read-only access for historical rendering
|
|
54
|
+
|
|
55
|
+
The loader MUST allow read-only access to v1 entries when resolving an act_type_id that was used in a previously committed attestation. This requirement preserves the namespace and ensures that historical receipts continue to render correctly.
|
|
56
|
+
|
|
57
|
+
The loader SHOULD distinguish at the API boundary between "list act types for new issuance" (returns only v2 entries) and "resolve act type for historical rendering" (returns v2 and v1 entries). Reference implementations expose two distinct functions for this purpose; see Section 6.
|
|
58
|
+
|
|
59
|
+
### 1.4 Path discovery
|
|
60
|
+
|
|
61
|
+
The loader MUST discover catalogue entries by walking the `catalogue/` directory tree. The discovery rules are:
|
|
62
|
+
|
|
63
|
+
- Files ending in `.json` are candidate catalogue entries.
|
|
64
|
+
- Files under a `_deprecated/` subdirectory are v1 entries (read-only, historical access only).
|
|
65
|
+
- Files under any other path are v2 entries (subject to v2 schema validation).
|
|
66
|
+
- Files ending in `.test_vectors.json` are NOT catalogue entries and MUST be ignored by the loader.
|
|
67
|
+
- `README.md` files and other non-JSON files MUST be ignored.
|
|
68
|
+
|
|
69
|
+
The loader MUST treat the `catalogue/` directory as the root. Catalogue entries deeper in the tree (under `catalogue/acts/eu/nis2/art20/` etc.) are discovered recursively.
|
|
70
|
+
|
|
71
|
+
## 2. Validation requirements
|
|
72
|
+
|
|
73
|
+
When a candidate manifest is submitted for transition to `awaiting_commit` state, the loader MUST perform the following validations against the corresponding catalogue entry. Failure of any validation MUST block the transition and return a structured error.
|
|
74
|
+
|
|
75
|
+
### 2.1 act_type_id match
|
|
76
|
+
|
|
77
|
+
The manifest's `act_type_id` MUST exactly match the catalogue entry's `act_type_id`. Case-sensitive comparison.
|
|
78
|
+
|
|
79
|
+
### 2.2 catalogue_entry_version match
|
|
80
|
+
|
|
81
|
+
The manifest's `catalogue_entry_version` MUST exactly match the catalogue entry's `version`. Mismatches indicate the manifest was created against a different version of the catalogue entry; the consuming implementation MUST either re-validate against the current version or surface the version mismatch as a structured error.
|
|
82
|
+
|
|
83
|
+
### 2.3 required_claim_fields presence
|
|
84
|
+
|
|
85
|
+
For every identifier in the catalogue entry's `required_claim_fields`, the manifest's `claim_fields` object MUST contain a key with the same identifier. The value of the key MUST NOT be null, empty string, or empty array.
|
|
86
|
+
|
|
87
|
+
The loader MUST report each missing required field individually, not as a single aggregate error, so the issuer can correct all gaps in a single revision cycle.
|
|
88
|
+
|
|
89
|
+
### 2.4 required_evidence_labels coverage
|
|
90
|
+
|
|
91
|
+
For every identifier in the catalogue entry's `required_evidence_labels`, the manifest's `evidence` array MUST contain at least one item whose `label` equals that identifier. The corresponding evidence item MUST have a non-null `sha256_hex` value.
|
|
92
|
+
|
|
93
|
+
The loader MUST report each missing required evidence label individually.
|
|
94
|
+
|
|
95
|
+
### 2.5 Recipient role validation
|
|
96
|
+
|
|
97
|
+
For every recipient in the manifest's `recipients` array, the recipient's `role` SHOULD match an identifier in the catalogue entry's `recommended_witness_roles`. Recipients with non-recommended roles MAY be permitted but the loader MUST flag them as non-standard in a warning that the consuming implementation surfaces to the issuer.
|
|
98
|
+
|
|
99
|
+
The loader MUST validate that every recipient has a syntactically valid email address in the `email` field. Invalid email addresses MUST be reported as structured errors.
|
|
100
|
+
|
|
101
|
+
The loader MUST validate that no two recipients share the same `(role, email)` pair. Duplicate designations MUST be reported as structured errors.
|
|
102
|
+
|
|
103
|
+
### 2.6 signature_policy compatibility
|
|
104
|
+
|
|
105
|
+
If the catalogue entry's `signature_policy.minimum` is `external_signature`, the manifest MUST include at least one evidence item whose label appears in the catalogue entry's `signature_policy.supports` list. If the manifest contains only `issuer_record`-equivalent evidence, the loader MUST block the transition.
|
|
106
|
+
|
|
107
|
+
If the catalogue entry's `signature_policy.minimum` is `issuer_record` or `either`, the loader MUST permit transition without external signature evidence (the platform-recorded commit action satisfies the floor).
|
|
108
|
+
|
|
109
|
+
## 3. Caching requirements
|
|
110
|
+
|
|
111
|
+
### 3.1 Startup load
|
|
112
|
+
|
|
113
|
+
The loader MUST load and cache all catalogue entries in memory at process startup. After successful startup, no catalogue entry SHALL be read from the filesystem on the path of a request.
|
|
114
|
+
|
|
115
|
+
### 3.2 Explicit reload
|
|
116
|
+
|
|
117
|
+
The loader MUST support an explicit reload operation triggered by an operator signal. Acceptable signal mechanisms include:
|
|
118
|
+
|
|
119
|
+
- A POSIX signal (e.g., `SIGHUP`) handled by the process
|
|
120
|
+
- An admin HTTP endpoint that triggers an in-process reload
|
|
121
|
+
- A scheduled reload at known process restart points (e.g., daily deploy)
|
|
122
|
+
|
|
123
|
+
The loader MUST NOT reload catalogue entries based on file modification time, inotify events, or any other implicit trigger. All reloads MUST be operator-initiated.
|
|
124
|
+
|
|
125
|
+
### 3.3 Atomicity
|
|
126
|
+
|
|
127
|
+
The reload operation MUST be atomic with respect to concurrent reads. A consuming request that is mid-flight when reload begins MUST observe either the pre-reload catalogue or the post-reload catalogue in its entirety, never a mixed state.
|
|
128
|
+
|
|
129
|
+
The reference implementation accomplishes this by computing the new catalogue cache fully in a temporary structure, then atomically swapping the active cache pointer.
|
|
130
|
+
|
|
131
|
+
### 3.4 Reload failure handling
|
|
132
|
+
|
|
133
|
+
If reload fails (e.g., a newly added catalogue entry fails schema validation), the loader MUST retain the previous valid cache and surface the failure as a structured error. A failed reload MUST NOT leave the loader in an unloaded or partially-loaded state.
|
|
134
|
+
|
|
135
|
+
## 4. Error reporting
|
|
136
|
+
|
|
137
|
+
The loader MUST report validation errors as structured objects, not as opaque strings. The recommended structure is:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
{
|
|
141
|
+
"error_type": "REQUIRED_CLAIM_FIELD_MISSING",
|
|
142
|
+
"field_path": "claim_fields.approving_body_name",
|
|
143
|
+
"act_type_id": "op:eu.nis2.art20.management_body_approval.v1",
|
|
144
|
+
"catalogue_entry_version": 1,
|
|
145
|
+
"human_message": "The required claim field 'approving_body_name' is missing from the manifest."
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Each structured error MUST include:
|
|
150
|
+
|
|
151
|
+
- `error_type`: an enumerated identifier from the error taxonomy in Section 4.1
|
|
152
|
+
- `human_message`: a human-readable explanation suitable for display to the issuer
|
|
153
|
+
|
|
154
|
+
Each structured error SHOULD include where applicable:
|
|
155
|
+
|
|
156
|
+
- `field_path`: a JSON pointer-like path identifying the offending field
|
|
157
|
+
- `act_type_id` and `catalogue_entry_version` for context
|
|
158
|
+
- `expected` and `actual` values for mismatches
|
|
159
|
+
- `valid_options` listing the permissible values when applicable
|
|
160
|
+
|
|
161
|
+
### 4.1 Error taxonomy
|
|
162
|
+
|
|
163
|
+
The loader MUST recognise and report the following error types:
|
|
164
|
+
|
|
165
|
+
- `CATALOGUE_ENTRY_NOT_FOUND`: requested act_type_id does not match any loaded entry
|
|
166
|
+
- `CATALOGUE_ENTRY_DEPRECATED`: requested act_type_id is a v1 entry; new issuance refused
|
|
167
|
+
- `CATALOGUE_ENTRY_VERSION_MISMATCH`: manifest's catalogue_entry_version does not match catalogue
|
|
168
|
+
- `REQUIRED_CLAIM_FIELD_MISSING`: a required claim field is absent or empty
|
|
169
|
+
- `REQUIRED_EVIDENCE_LABEL_MISSING`: no evidence item carries a required label
|
|
170
|
+
- `RECIPIENT_ROLE_NOT_RECOMMENDED`: a recipient's role is not in recommended_witness_roles (warning, not error, unless implementation enforces strict mode)
|
|
171
|
+
- `RECIPIENT_EMAIL_INVALID`: a recipient's email is malformed
|
|
172
|
+
- `RECIPIENT_DUPLICATE_DESIGNATION`: two recipients share the same (role, email) pair
|
|
173
|
+
- `SIGNATURE_POLICY_VIOLATED`: catalogue requires external signature but manifest provides none
|
|
174
|
+
- `CATALOGUE_ENTRY_SCHEMA_INVALID`: a catalogue entry on disk fails v2 schema validation (operator-facing error at startup or reload)
|
|
175
|
+
|
|
176
|
+
## 5. Concurrency
|
|
177
|
+
|
|
178
|
+
The loader's read operations (resolve catalogue entry, validate manifest) MUST be safe for concurrent invocation. Implementations MAY achieve concurrency safety by treating the cached catalogue as immutable after each reload and using atomic pointer swap for updates.
|
|
179
|
+
|
|
180
|
+
The loader's reload operation SHOULD be safe to invoke concurrently with read operations as described in Section 3.3. The loader MAY serialize concurrent reload calls to prevent redundant work.
|
|
181
|
+
|
|
182
|
+
## 6. Recommended interface
|
|
183
|
+
|
|
184
|
+
Reference implementations are RECOMMENDED to expose at least the following operations. Type signatures are written in Python-flavoured pseudocode for clarity; equivalent signatures in other languages are conforming.
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
class CatalogueLoader:
|
|
188
|
+
def load_all(self, catalogue_root: Path) -> None:
|
|
189
|
+
"""
|
|
190
|
+
Discover and cache all catalogue entries. Called at process startup
|
|
191
|
+
and on explicit reload signal.
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
def list_act_types_for_issuance(self) -> list[ActTypeDescriptor]:
|
|
195
|
+
"""
|
|
196
|
+
Return the list of v2 act types currently available for new
|
|
197
|
+
attestation creation. v1 entries are excluded.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
def resolve_for_issuance(self, act_type_id: str) -> CatalogueEntry:
|
|
201
|
+
"""
|
|
202
|
+
Return the v2 catalogue entry matching act_type_id. Raises
|
|
203
|
+
CatalogueEntryNotFound if no match exists, or CatalogueEntryDeprecated
|
|
204
|
+
if the match is a v1 entry.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
def resolve_for_history(self, act_type_id: str) -> CatalogueEntry:
|
|
208
|
+
"""
|
|
209
|
+
Return the catalogue entry matching act_type_id, including v1 entries.
|
|
210
|
+
Used for rendering receipts of attestations issued before the v2
|
|
211
|
+
migration.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
def validate_manifest(
|
|
215
|
+
self,
|
|
216
|
+
manifest: dict,
|
|
217
|
+
catalogue_entry: CatalogueEntry,
|
|
218
|
+
) -> list[ValidationError]:
|
|
219
|
+
"""
|
|
220
|
+
Validate a candidate manifest against a catalogue entry. Returns a
|
|
221
|
+
list of structured errors; empty list means the manifest is valid
|
|
222
|
+
for transition to awaiting_commit.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
def reload(self) -> ReloadResult:
|
|
226
|
+
"""
|
|
227
|
+
Re-discover and re-cache all catalogue entries atomically. Returns
|
|
228
|
+
a summary of what changed.
|
|
229
|
+
"""
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Implementations MAY add additional operations beyond this minimum (e.g., bulk validation, partial reload, schema diff). Such extensions MUST NOT change the semantics of the operations above.
|
|
233
|
+
|
|
234
|
+
## 7. Test scenarios
|
|
235
|
+
|
|
236
|
+
A conforming loader implementation MUST pass at least the following test scenarios. The reference implementation in Quoruna includes these as integration tests under `tests/test_catalogue_loader.py` (added in Batch B).
|
|
237
|
+
|
|
238
|
+
### Scenario A: Successful v2 load
|
|
239
|
+
|
|
240
|
+
Given a clean catalogue directory containing `management_body_approval.v1.json` and `dds_preparation.v1.json`, the loader's `load_all` succeeds, `list_act_types_for_issuance` returns both entries, and `resolve_for_issuance` returns the correct entry for each act_type_id.
|
|
241
|
+
|
|
242
|
+
### Scenario B: v1 namespace preservation
|
|
243
|
+
|
|
244
|
+
Given a catalogue directory containing the v1 `_deprecated/approval.json` alongside the v2 `management_body_approval.v1.json`:
|
|
245
|
+
|
|
246
|
+
- `list_act_types_for_issuance` returns only the v2 entry.
|
|
247
|
+
- `resolve_for_issuance("op:eu.nis2.art20.approval")` raises CatalogueEntryDeprecated.
|
|
248
|
+
- `resolve_for_history("op:eu.nis2.art20.approval")` succeeds and returns the v1 entry.
|
|
249
|
+
|
|
250
|
+
### Scenario C: Missing required claim field
|
|
251
|
+
|
|
252
|
+
Given a manifest against `management_body_approval.v1` that omits `responsible_officers`, `validate_manifest` returns a single structured error of type `REQUIRED_CLAIM_FIELD_MISSING` with `field_path: "claim_fields.responsible_officers"`.
|
|
253
|
+
|
|
254
|
+
### Scenario D: Missing required evidence label
|
|
255
|
+
|
|
256
|
+
Given a manifest against `dds_preparation.v1` that includes only the `geojson_plot_geometries` evidence and omits `due_diligence_screening_report`, `validate_manifest` returns a `REQUIRED_EVIDENCE_LABEL_MISSING` error for the missing label.
|
|
257
|
+
|
|
258
|
+
### Scenario E: Duplicate recipient
|
|
259
|
+
|
|
260
|
+
Given a manifest with two recipients sharing the same `(role, email)`, `validate_manifest` returns a `RECIPIENT_DUPLICATE_DESIGNATION` error identifying both indices.
|
|
261
|
+
|
|
262
|
+
### Scenario F: Non-recommended role warning
|
|
263
|
+
|
|
264
|
+
Given a manifest with a recipient whose role is not in `recommended_witness_roles`, `validate_manifest` returns a warning (not an error) of type `RECIPIENT_ROLE_NOT_RECOMMENDED`. The transition to `awaiting_commit` is permitted but the consuming implementation MUST surface the warning to the issuer.
|
|
265
|
+
|
|
266
|
+
### Scenario G: Invalid catalogue entry on disk
|
|
267
|
+
|
|
268
|
+
Given a catalogue directory where one of the JSON files fails v2 schema validation, `load_all` succeeds for the valid entries, logs the failure at warning level, and `list_act_types_for_issuance` returns only the valid v2 entries. The invalid entry is NOT surfaced.
|
|
269
|
+
|
|
270
|
+
### Scenario H: Reload preserves prior cache on failure
|
|
271
|
+
|
|
272
|
+
After successful initial load, given a `reload` call where one of the catalogue files has been corrupted, the reload fails atomically. The loader's existing cache remains active and serving requests. The failure is reported as a structured error.
|
|
273
|
+
|
|
274
|
+
### Scenario I: No filesystem read on issuance path
|
|
275
|
+
|
|
276
|
+
A profiling test confirms that calling `resolve_for_issuance` for any cached act_type_id triggers zero filesystem reads after the initial `load_all` completes.
|
|
277
|
+
|
|
278
|
+
## 8. Reference implementation
|
|
279
|
+
|
|
280
|
+
The reference implementation lives in the Quoruna repository at `catalogue_loader.py`. It is implemented in Python with `asyncpg` for any database-side persistence concerns and `jsonschema` for v2 schema validation. The full implementation lands in Quoruna Batch B alongside the attestation schema and the issuer flow.
|
|
281
|
+
|
|
282
|
+
Future implementations in other languages may diverge in idioms, frameworks, and persistence choices, but MUST satisfy the contract documented here to be considered conforming.
|
|
283
|
+
|
|
284
|
+
## 9. Versioning
|
|
285
|
+
|
|
286
|
+
This contract is bound to v1.4 of the ActProof Events specification. Substrate changes that affect the loader contract (new schema versions, new validation requirements, new error types) will be accompanied by a new version of this contract. Implementations that satisfy the v1.4 contract are NOT automatically conforming to future contract versions; each substrate release MUST be reviewed against the corresponding contract.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
*This contract is licensed CC0. Any implementation may incorporate or paraphrase this text without attribution.*
|