QTZPOC-20
QTZPOC-20: Implement .sientia model bundle import workflow Introduces a new Temporal workflow and activities to import .sientia model bundles. This enables: - Secure decryption and validation of bundles through a seven-gate process. - Provisioning imported models in MLflow and updating the MongoDB model listing. - Comprehensive status tracking in the public.experiment_run table with new IMPORT_SUCCESS/IMPORT_ERROR states. - Dedicated metrics, robust error handling, and configurable settings. - Includes updated e2e database fixtures and extensive unit/integration tests for bundle format, reader, and overall workflow.
This commit is contained in:
86
tests/fixtures/bundle/README.md
vendored
Normal file
86
tests/fixtures/bundle/README.md
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
# Golden `.sientia` fixtures
|
||||
|
||||
Binary conformance vectors for `laborious/utils/bundle/`. They are **bytes the producer's own code
|
||||
emitted**, not bytes this repository knows how to write: there is no production writer here (change
|
||||
`import-model-workflow`, design D1). If the producer's layout moves, the reader's tests fail against
|
||||
these files instead of failing in production.
|
||||
|
||||
| File | What it is |
|
||||
|---|---|
|
||||
| `golden_model_v3.sientia` | `format_version` 1 bundle, 110767 bytes, plaintext zip 110569 bytes — **two chunks**, so `MESSAGE`/`FINAL` framing is exercised by the golden fixture itself |
|
||||
| `golden_model_v3.metadata.json` | the `metadata.json` that went into that bundle, in plaintext, so a test can assert the recovered value without decrypting first |
|
||||
| `regenerate.py` | the script that produced them, importing the producer's `bundle.py`/`crypto.py` by path |
|
||||
|
||||
## Password
|
||||
|
||||
```
|
||||
Import1Model!Fixture
|
||||
```
|
||||
|
||||
Recorded deliberately: the fixture protects nothing. It satisfies the producer's export-side
|
||||
strength rule (≥ 12 characters, upper, lower, digit, symbol), which is checked by
|
||||
`crypto.validate_password_strength` at generation time.
|
||||
|
||||
## Provenance
|
||||
|
||||
- Producer repository: `sientia-projects-templates`
|
||||
- Producer commit: `8d3c1fd117c697e8b352c34f11c732809a4a129b`
|
||||
(`8d3c1fd QTZPOC-7: adapt to lint rules picked up from merging main`)
|
||||
- Producer modules used: `app/src/operations/model_export/bundle.py`,
|
||||
`app/src/operations/model_export/crypto.py` — loaded by path, so every constant, the `struct`
|
||||
layout, the zip layout, the KDF parameters and the chunk framing come from the producer and none of
|
||||
them is re-implemented here
|
||||
- Producer pin honoured: `pynacl==1.5.0` (this repository pins the same version)
|
||||
|
||||
One deviation from task 0.3, recorded rather than hidden: the bytes were generated with the
|
||||
producer's **code** under *this* repository's venv, not inside the producer's own venv, because that
|
||||
venv has neither `pynacl` nor `mlflow` installed. The dependency that matters for the bytes is
|
||||
`pynacl`, pinned identically on both sides, and `mlflow` is not reached — `regenerate.py` builds the
|
||||
artifact tree itself instead of calling `client.download_artifacts`.
|
||||
|
||||
## Contents of the bundle
|
||||
|
||||
```
|
||||
metadata.json (written first, no indent, by bundle.build_zip)
|
||||
artifacts/prediction_model/MLmodel
|
||||
artifacts/prediction_model/model.pkl (90 KiB of incompressible filler)
|
||||
artifacts/prediction_model/conda.yaml
|
||||
artifacts/prediction_model/python_env.yaml
|
||||
artifacts/prediction_model/requirements.txt
|
||||
artifacts/data_model/MLmodel
|
||||
artifacts/data_model/model.pkl (8 KiB)
|
||||
artifacts/data_model/conda.yaml
|
||||
artifacts/data_model/python_env.yaml
|
||||
artifacts/data_model/requirements.txt
|
||||
artifacts/model_card/model_card.json
|
||||
artifacts/train_data.csv
|
||||
artifacts/test_data.csv
|
||||
artifacts/raw_data.csv
|
||||
```
|
||||
|
||||
No directory entries are stored — the producer only adds paths for which `is_file()` is true.
|
||||
|
||||
`parameters` carries `target_variable: "SE"`, which gate 6 requires and which becomes
|
||||
`model_config.target` in the MongoDB model document.
|
||||
|
||||
## Digest boundary — the reason this fixture exists
|
||||
|
||||
The header's `ciphertext_digest` is `sha256(file[164:])`: the producer computes
|
||||
`hashlib.sha256(ciphertext)` over the joined chunks and only then writes
|
||||
`header + stream_header + ciphertext`, so the 24-byte `secretstream` header is **not** covered.
|
||||
Verified against this fixture and against a freshly produced bundle. Earlier prose (this change's own
|
||||
first draft, and `aig-docs/03-sientia-bundle-format.md`) said "everything after the plaintext
|
||||
header", i.e. from offset 140 — that is wrong, and `tests/laborious/utils/bundle/test_format.py`
|
||||
asserts both directions so it cannot come back.
|
||||
|
||||
## Regenerating
|
||||
|
||||
```bash
|
||||
source ./venv/bin/activate
|
||||
PRODUCER_REPO=../sientia-projects-templates python tests/fixtures/bundle/regenerate.py
|
||||
```
|
||||
|
||||
Two fields change on every run and are expected to: the 16-byte salt (`os.urandom`) and
|
||||
`metadata.metadata.export_timestamp` (`datetime.now(UTC)`). Everything else is deterministic —
|
||||
the filler bytes come from a seeded PRNG. After regenerating, update the producer commit above and
|
||||
the byte counts in the table.
|
||||
Reference in New Issue
Block a user