feat: add versioned output schemas

This commit is contained in:
Janik Besendorf
2026-08-23 10:06:01 +02:00
parent dac4acb180
commit 30e25f9692
31 changed files with 688 additions and 40 deletions
+4
View File
@@ -82,6 +82,10 @@ class ExampleCustomModule(MVTModule):
return None
```
Custom modules should declare an `output_model` using a Pydantic `RootModel`.
This validates saved results and lets consumers obtain the module's JSON Schema.
See [Output schemas](output-schemas.md#custom-modules) for a complete example.
Use `supported_commands` to declare the platform/command pairs a module
supports. Empty `supported_commands` means the module will not run and MVT logs
a warning. This explicit declaration is required for every command. Supported
+77
View File
@@ -0,0 +1,77 @@
# Output schemas
MVT validates its JSON output with versioned Pydantic models. The schema version
used for a run is recorded as `output_schema_version` in `info.json`.
MVT preserves the established on-disk format: most module files contain an array
of result objects, while modules whose results are naturally grouped contain an
object keyed by source or namespace. Detection files and `alerts.json` contain
arrays of alert objects. Timestamps remain strings because their timezone and
precision depend on the source artifact.
## Exporting JSON Schema
Both platform commands can print a versioned JSON Schema bundle:
```bash
mvt-ios schemas
mvt-android schemas
```
Use `--output` to write one Draft 2020-12 JSON Schema file for each output:
```bash
mvt-ios schemas --output ./mvt-ios-schemas
mvt-android schemas --output ./mvt-android-schemas
```
## Python API
Models and schema discovery functions are available from `mvt.schemas`:
```python
from mvt.schemas import get_output_model
SafariHistoryOutput = get_output_model("safari_history", platform="ios")
validated = SafariHistoryOutput.model_validate(records)
json_schema = SafariHistoryOutput.model_json_schema()
```
Common outputs have dedicated field-level models. Built-in module outputs have a
declared root shape, and modules with an established dedicated record model expose
its complete field schema.
## Custom modules
Custom modules can publish a precise contract by assigning a Pydantic root model
to `output_model`:
```python
from pydantic import BaseModel, RootModel
from mvt.common.module import MVTModule
class ExampleRecord(BaseModel):
message: str
timestamp: str | None = None
class ExampleOutput(RootModel[list[ExampleRecord]]):
pass
class ExampleModule(MVTModule):
output_model = ExampleOutput
```
For compatibility, a custom module without `output_model` can still write an
array or object containing JSON values. MVT logs a warning when it uses this
generic contract. A future major release may require custom modules to declare
their output models.
## Compatibility policy
Within one output schema major version, required fields are not removed and field
types are not narrowed. New optional fields may be added. A breaking output
change requires a new schema major version and a migration note.