Skip to content

feat(archive): add bounded model-neutral ZIP transport - #776

Draft
frederikgeth wants to merge 1 commit into
mainfrom
codex/bounded-zip-transport
Draft

frederikgeth wants to merge 1 commit into
mainfrom
codex/bounded-zip-transport

Conversation

@frederikgeth

Copy link
Copy Markdown
Collaborator

What and why

Part of #774 (P2). Add powerio-archive, a model-neutral, in-memory ZIP backend that lower adapters and collection ingestion can share. This PR targets main directly and is independent of P1 (#775).

Opening validates bounded ZIP32/ZIP64 metadata, exact UTF-8 names, portable namespace/collision rules, local headers, streaming descriptors and nonoverlapping member regions. Reads enforce declared size and CRC before publishing shared cached bytes. Defaults are 64 MiB input, 4096 entries, 64 MiB total declared expansion and a 200:1 member ratio; callers supply explicit overrides and optional smaller read caps. Original container bytes remain available. Indexing alone does not certify unread payload CRCs.

Metadata preflight precedes the ZIP decoder's index allocations. During decoder indexing, member regions are masked so embedded end signatures cannot drive fallback metadata parsing; original bytes are exposed again before local-header validation and reads. Collision indexing stores segments with parent IDs instead of copying every ancestor prefix. A one-off allocation probe with 128 disjoint 64-segment paths (994,710 input bytes) reduced peak indexing allocations from 34,449,437 to 3,612,528 bytes.

The transport and its admission tests form one review unit (~1.2k Rust lines). Suggested review order: preflight.rs, local.rs, the namespace/cache API in lib.rs, then adversarial tests.

Decisions for the maintainer

None. This is a draft and is not requesting maintainer review yet, respecting the existing review queue.

Not in this pull request

Electrical interpretation, automatic case discovery, source-tree integration, recursive ZIP traversal, CLI/bindings, and changes to existing parser entry points. P3 will evaluate reuse in CGMES/SINCAL while preserving their compatibility contracts; collection selection follows separately. Nested payloads remain opaque. These limits do not bound downstream decoded tables/models or repeated parser work.

Files outside the main change

  • Cargo.toml and Cargo.lock: workspace/default membership and the new crate; reuse the existing ZIP dependency without upgrading dependencies.
  • Rust/Docs/crates workflows and scripts/ci-clippy.sh, ci-mirror.sh, check-release-versions.sh, check-semver.sh, release-verified.py: test, document, package and publish the new crate after core; skip comparison only when the selected SemVer baseline predates the crate.
  • powerio/tests/crate_graph.rs: enforce that archive transport depends on core without electrical models.
  • README, AGENTS, changelog and crate-graph guide: document the crate boundary. docs/diagrams/architecture.dot and generated docs/src/assets/architecture.svg: add its dependency edge; regenerated with python3 scripts/check-architecture-map.py --render.
  • New crate license files are byte-identical copies of the root MIT/Apache notices.

Fixtures

No new fixture files. Small archives are generated in Rust tests, including malformed metadata/payloads, namespace conflicts, overlap, streaming descriptors, ZIP64 and systematic bit mutations. Independent Python zipfile probes cover eight stored/deflated, seekable/streaming, ordinary/forced-ZIP64 combinations; those temporary inputs are not committed.

Checks

Passed locally:

  • cargo fmt --all --check
  • cargo test and cargo test -p powerio-capi
  • cargo test -p powerio-archive (12 tests)
  • cargo clippy -p powerio-archive --all-targets -- -D warnings
  • cargo check -p powerio-archive --target wasm32-unknown-unknown
  • RUSTDOCFLAGS="-D warnings" cargo doc -p powerio-archive --no-deps
  • cargo package -p powerio-archive --allow-dirty --offline, plus scripts/audit-release-archives.py on the resulting package
  • Architecture, release-version, terminology, deprecated-inventory and documentation-symbol gates; release-helper unit tests (27); independent Python ZIP probes.

Existing Clippy failures, on rustc 1.96.0 / Clippy 0.1.96:

  • bash scripts/ci-clippy.sh stops in its workspace target on 17 clippy::float_cmp findings in powerio-tx/src/format/psse.rs: 13269–13274, 13287–13288, 13313–13321. The separately run no-sincal target has the same failures.
  • The separately run matrix-gridfm target has three float_cmp findings in powerio-matrix/tests/synth_facade_parity.rs: 25, 39, 40.
  • Both files are byte-identical to fetched origin/main at 8335e3bf85a158a05e3eeada372c7890ef6e7edc. They are outside this diff. The C ABI configurations and PyO3 Clippy target pass.

bash scripts/ci-mirror.sh was attempted and stops at the existing workspace Clippy failures, so its later stages were not reached; the checks listed above ran separately. cargo-semver-checks and mdbook are unavailable locally. Remaining platform/MSRV and integration gates are left to CI.

@github-actions

Copy link
Copy Markdown
Contributor

Conversion Matrix

Each cell writes the source as the target and reads it back. The symbol is the worst finding.

⚪ exact 🟢 lossless 🔵 restated: only values the target requires were synthesized

🟡 approximate: values changed to fit 🟠 dropped: source data has no target field

🔴 failed: error, invariant, or baseline change

Transmission: into a case format

From ↓ / to → Source MATPOWER .m PowerModels JSON PSS/E .raw 33 PSS/E RAWX 35 PowerWorld .aux egret JSON pandapower JSON Surge JSON PSLF .epc
MATPOWER .m 🟢 ⚪ 🟠 1 areas 🟠 16 field 🟠 16 field 🟠 12 field 🟠 7 field 🟠 12 field 🟠 23 field 🟠 14 field
PowerModels JSON 🟢 🟢 ⚪ 🟠 16 field 🟠 16 field 🟠 11 field 🟠 6 field 🟠 11 field 🟠 22 field 🟠 13 field
PSS/E .raw 33 🟢 🟠 1 field 🟠 1 areas ⚪ 🟢 🟠 2 areas 🟠 1 areas 🟠 2 areas 🟠 1 areas 🟠 2 areas
PSS/E RAWX 35 🟢 🟠 1 field 🟠 1 areas 🟢 ⚪ 🟠 2 areas 🟠 1 areas 🟠 2 areas 🟠 1 areas 🟠 2 areas
PowerWorld .aux 🟢 🟠 1 field 🟢 🟠 6 field 🟠 6 field ⚪ 🟢 🟠 1 field 🟢 🟠 6 field
egret JSON 🟢 🟢 🟢 🟠 10 field 🟠 10 field 🟠 6 field ⚪ 🟠 5 field 🟠 1 field 🟠 7 field
pandapower JSON 🟢 🟢 🟢 🟠 8 field 🟠 8 field 🟠 2 field 🟢 ⚪ 🟢 🟠 7 field
Surge JSON 🟢 🟢 🟢 🟠 10 field 🟠 10 field 🟠 6 field 🟢 🟠 4 field ⚪ 🟠 7 field
PSLF .epc 🟢 🟢 🟢 🟠 1 field 🟠 1 field 🟠 1 record 🟢 🟠 3 field 🟠 3 field ⚪
XIIDM 1.17 🟢 🟠 7 field 🟠 7 field 🟠 38 record 🟠 38 record 🟠 8 field 🟠 7 field 🟠 8 field 🟠 7 field 🟠 8 field
JIIDM 1.17 🟢 🟠 7 field 🟠 7 field 🟠 38 record 🟠 38 record 🟠 8 field 🟠 7 field 🟠 8 field 🟠 7 field 🟠 8 field
CGMES 3.0 🟢 🟠 6 field 🟠 6 field 🟠 73 field 🟠 42 field 🟠 6 field 🟠 6 field 🟠 7 field 🟠 6 field 🟠 6 field
UCTE-DEF .uct 🟢 🟠 18 field 🟠 12 areas 🟠 6 extras 🟠 6 extras 🟠 18 areas 🟠 12 areas 🟠 6 areas 🟠 12 areas 🟠 18 areas
PyPSA CSV 🟢 🟢 🟡 6 nonfinite 🟠 8 field 🟠 8 field 🟠 2 field 🟡 6 nonfinite 🟠 2 field 🟡 6 nonfinite 🟠 6 field
GridFM Parquet 🟢 🟠 6 field 🟢 🟠 14 field 🟠 14 field 🟠 8 field 🟠 6 field 🟠 8 field 🟢 🟠 12 field
PSS/E .raw 32 🟠 2 section 🟠 2 field 🟠 2 areas 🟢 🟢 🟠 2 areas 🟠 2 areas 🟠 2 areas 🟠 2 areas 🟠 4 field
IEEE CDF 🟡 2 value 🟠 2 field 🟠 2 areas 🟠 1 field 🟠 1 field 🟠 2 areas 🟠 2 areas 🟠 2 areas 🟠 2 areas 🟠 2 areas
GO Challenge 3 JSON 🟡 1 value 🟠 5 rating set 🟠 4 rating set 🟠 6 rating set 🟠 3 field 🟠 8 field 🟠 4 rating set 🟠 6 field 🟠 4 rating set 🟠 6 rating set
DeepMind OPFData JSON 🟠 1 source only 🟠 1 field 🟢 🟠 3 field 🟠 3 field 🟠 2 field 🟠 1 field 🟠 2 field 🟢 🟠 2 field

Transmission: into an exchange format

From ↓ / to → Source XIIDM 1.17 JIIDM 1.17 CGMES 3.0 UCTE-DEF .uct
MATPOWER .m 🟢 🟠 38 field 🟠 38 field 🟠 50 field 🟠 28 field
PowerModels JSON 🟢 🟠 38 field 🟠 38 field 🟠 49 field 🟠 27 field
PSS/E .raw 33 🟢 🟡 1 value 🟡 1 value 🟠 5 record 🟠 15 field
PSS/E RAWX 35 🟢 🟡 1 value 🟡 1 value 🟠 5 record 🟠 15 field
PowerWorld .aux 🟢 🟠 23 field 🟠 23 field 🟠 23 field 🟠 19 field
egret JSON 🟢 🟠 37 field 🟠 37 field 🟠 29 field 🟠 22 field
pandapower JSON 🟢 🟠 25 field 🟠 25 field 🟠 29 field 🟠 18 field
Surge JSON 🟢 🟠 36 field 🟠 36 field 🟠 29 field 🟠 22 field
PSLF .epc 🟢 🟠 7 field 🟠 7 field 🟠 4 record 🟠 14 field
XIIDM 1.17 🟢 ⚪ 🟢 🟠 179 field 🟠 21 field
JIIDM 1.17 🟢 🟢 ⚪ 🟠 179 field 🟠 21 field
CGMES 3.0 🟢 🟠 19 field 🟠 19 field ⚪ 🟠 17 field
UCTE-DEF .uct 🟢 🟠 6 field 🟠 6 field 🟠 6 record ⚪
PyPSA CSV 🟢 🟠 33 field 🟠 33 field 🟠 25 field 🟠 19 field
GridFM Parquet 🟢 🟠 33 field 🟠 33 field 🟠 25 field 🟠 26 field
PSS/E .raw 32 🟠 2 section 🟡 4 value 🟡 4 value 🟠 6 field 🟠 6 field
IEEE CDF 🟡 2 value 🟡 1 value 🟡 1 value 🟠 1 record 🟠 5 field
GO Challenge 3 JSON 🟡 1 value 🟠 2 field 🟠 2 field 🟠 5 rating set 🟠 10 field
DeepMind OPFData JSON 🟠 1 source only 🟠 25 field 🟠 25 field 🟠 5 field 🟠 7 field

Transmission: into a dataset directory

From ↓ / to → Source PyPSA CSV GridFM Parquet
MATPOWER .m 🟢 🟠 15 field 🟠 18 field
PowerModels JSON 🟢 🟠 14 field 🟠 17 field
PSS/E .raw 33 🟢 🟠 9 field 🟠 13 field
PSS/E RAWX 35 🟢 🟠 9 field 🟠 13 field
PowerWorld .aux 🟢 🟠 7 field 🟠 11 field
egret JSON 🟢 🟠 9 field 🟠 12 field
pandapower JSON 🟢 🟠 8 field 🟠 7 field
Surge JSON 🟢 🟠 9 field 🟠 12 field
PSLF .epc 🟢 🟠 8 field 🟠 12 field
XIIDM 1.17 🟢 🟠 15 field 🟠 39 field
JIIDM 1.17 🟢 🟠 15 field 🟠 39 field
CGMES 3.0 🟢 🟠 13 field 🟠 37 field
UCTE-DEF .uct 🟢 🟠 12 areas 🟠 24 field
PyPSA CSV 🟢 ⚪ 🟠 6 field
GridFM Parquet 🟢 🟠 12 field ⚪
PSS/E .raw 32 🟠 2 section 🟠 4 field 🟠 6 field
IEEE CDF 🟡 2 value 🟠 3 field 🟠 3 field
GO Challenge 3 JSON 🟡 1 value 🟠 8 field 🟠 7 field
DeepMind OPFData JSON 🟠 1 source only 🟠 3 field 🟠 2 field

Distribution: into a distribution format

From ↓ / to → Source OpenDSS .dss BMOPF JSON PMD JSON
OpenDSS .dss 🟠 11 source only ⚪ 🟠 81 field 🟠 188 field
BMOPF JSON 🟢 🟠 31 field ⚪ 🟠 11 field
PMD JSON 🟢 🟠 47 extras 🟠 45 field ⚪

Full warning details: download conversion-matrix-warning-details for this workflow run.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant