feat: add case package validation foundation
This commit is contained in:
1 parent
2e590d5e12
commit
112487242a
18 files changed
+806
-1
No files matched your search
@@ -0,0 +1,125 @@
|
||||
# Case package format v1
|
||||
|
||||
The authoritative design is `architecture.md`. This document specifies only the
|
||||
project-independent package foundation. A package is a long-lived, self-contained
|
||||
case record in a ZIP archive. Its filename and extension carry no program meaning.
|
||||
Exactly one root `manifest.json` is the authoritative entry point.
|
||||
|
||||
## Identity, state and routing
|
||||
|
||||
`format` is `viano-bug-case-package`; `format_version` is integer `1`.
|
||||
`package_id` is an opaque nonblank string identifying the case package throughout
|
||||
its lifetime. Enriching and emitting it again preserves this identity and, in a
|
||||
future writer, increases `package_generation` (a nonnegative integer).
|
||||
`case_id` identifies the case. `analysis_revision` is a separate nonnegative
|
||||
integer, initially zero; it is independent of package generation.
|
||||
The reader does not derive or increment any of these values.
|
||||
|
||||
`project` requires generic `type` and `profile` strings and accepts `display_name`.
|
||||
`created_at` and `updated_at` are RFC-3339 timestamps with timezone; updated time
|
||||
must not precede creation time.
|
||||
Timestamp validation rejects leap seconds because Python's datetime model cannot
|
||||
represent them; lowercase `t`/`z` and fractional seconds are supported.
|
||||
|
||||
`state` uses the lifecycle states defined in the architecture: NEW, VALIDATED,
|
||||
BUILDING_CASE, INTERPRETING_CASE, GENERATING_HYPOTHESES,
|
||||
INVESTIGATING_HYPOTHESES, BUILDING_DIAGNOSIS, OUTPUT_READY, HANDED_OFF,
|
||||
BLOCKED, FAILED_TO_ANALYZE, REOPENED.
|
||||
|
||||
`routing` is separate and requires:
|
||||
|
||||
- `destination`: null or inbox, quarantine, cases, outbox, review; a logical route,
|
||||
never a filesystem path. Cache and runtime are service storage, not routes.
|
||||
- `reason`: null or nonblank explanatory string.
|
||||
- `retry_count`: nonnegative integer, initially zero.
|
||||
- `retry_not_before`: null or RFC-3339 timestamp.
|
||||
|
||||
No state/route combination triggers execution. This milestone checks the contract
|
||||
only, without implementing routing, retry, locking or lifecycle transitions.
|
||||
|
||||
## References and integrity
|
||||
|
||||
`artifacts` requires `case`, `diagnosis`, `change_request`, each null or an internal
|
||||
file path. Additional artifact pointers follow the same rule. `history` requires
|
||||
nonnegative counters `analysis_revisions`, `reproductions`, `evidence`, `handoffs`,
|
||||
`feedback`, and nullable `events_ref`. Counters are supplied summaries; the reader
|
||||
does not infer them from directory names or impose diagnostic semantics.
|
||||
|
||||
`integrity` maps internal file paths to `{ "sha256": "<64 lowercase hex digits>",
|
||||
"size": <uncompressed byte count> }`. All supplied records are checked. A fresh
|
||||
package may have an empty map. Producers should cover important immutable
|
||||
artifacts. The format foundation does not infer which project artifacts are
|
||||
important. Hashes detect changes, not authenticity. The manifest cannot reference
|
||||
or hash itself. Every non-null reference must resolve to an actual ZIP file.
|
||||
|
||||
The bundled `manifest-v1.schema.json` uses JSON Schema Draft 2020-12 and only local
|
||||
schema references; validation requires no network. Unknown extension fields are
|
||||
accepted and retained. Unsupported format versions, duplicate JSON keys,
|
||||
non-UTF-8 manifests and nonstandard numeric constants are rejected.
|
||||
|
||||
## Preservation
|
||||
|
||||
`original/` and completed `analysis/revisions/NNNN/` are immutable. New evidence,
|
||||
feedback and revisions are appended. Only explicitly designated index/pointer
|
||||
files (`manifest.json`, `analysis/current.json`) may later be updated. Unknown
|
||||
files are preserved unchanged. Validator and reader never write, extract, delete
|
||||
or rewrite anything. Historical append-only and output-superset comparisons,
|
||||
atomic generation and generation increments belong to a future writer; they
|
||||
cannot be proven from one standalone incoming archive.
|
||||
|
||||
## ZIP acceptance and configurable security policy
|
||||
|
||||
This implementation accepts stored and deflated ZIP entries, including ZIP64
|
||||
within configured limits. It rejects encryption, symlinks and other special
|
||||
filesystem entries, unsafe paths, duplicate entries, case-insensitive collisions,
|
||||
and file/directory conflicts. Paths must be relative POSIX paths without empty,
|
||||
dot or parent segments, backslashes, colons or control characters. Explicit empty
|
||||
directory entries are accepted. Paths are rejected, never silently repaired.
|
||||
The reader does not extract files; future extraction requires its own checks.
|
||||
|
||||
`PackageLimits` is a configurable operational security policy. These defaults
|
||||
are **not immutable limits of the v1 package format** and do not appear in the
|
||||
manifest schema:
|
||||
|
||||
| Limit | Default |
|
||||
|---|---:|
|
||||
| Archive bytes | 512 MiB |
|
||||
| Expanded total bytes | 2 GiB |
|
||||
| Expanded file bytes | 256 MiB |
|
||||
| Manifest bytes | 1 MiB |
|
||||
| ZIP entries (including directories) | 10,000 |
|
||||
| Per-entry compression ratio | 200:1 |
|
||||
|
||||
Declared sizes are checked before reading. Actual reads are bounded and total
|
||||
bytes are counted while streaming every file for CRC verification; listed SHA-256
|
||||
hashes are checked during the same pass. Manifest parsing is bounded separately.
|
||||
Malformed archive, I/O, JSON, schema and integrity failures produce structured
|
||||
validation issues. Programmer errors and process-level failures are not hidden.
|
||||
Input files must not be modified concurrently while open; the reader retains the
|
||||
same file handle and archive used for validation.
|
||||
|
||||
## Public API
|
||||
|
||||
```python
|
||||
from viano_bug_analysis.package import (
|
||||
PackageLimits, PackageValidator, PackageReader, PackageReadError,
|
||||
)
|
||||
|
||||
result = PackageValidator(PackageLimits()).validate("renamed-package.bin")
|
||||
if not result.valid:
|
||||
for issue in result.issues:
|
||||
print(issue.code, issue.path, issue.message)
|
||||
|
||||
try:
|
||||
with PackageReader("renamed-package.bin") as package:
|
||||
manifest = package.manifest # defensive copy, including extensions
|
||||
inventory = package.files # all file paths, including unknown files
|
||||
data = package.read_bytes("original/bug_report.json")
|
||||
except PackageReadError as error:
|
||||
issues = error.result.issues
|
||||
```
|
||||
|
||||
Read access outside the context is a programming error (`RuntimeError`). Invalid
|
||||
packages and missing member requests raise `PackageReadError` with structured
|
||||
details. `read_bytes` is bounded by policy but materializes a single file in memory;
|
||||
validation itself streams. No artifact interpretation is performed.
|
||||
Reference in new issue
Block a user