# Bundle Format Specification **Status:** v1 (Stable contract — backward-compatible changes only) **Authority:** `ordinis-bundle-spec` module **Updated:** 2026-05-17 Formal spec для authoring и publish'инга справочников через Ordinis bundle artifacts. Этот документ — handoff артефакт: новые Java-инженеры могут shipпить bundles без чтения runtime loader кода. --- ## 1. Bundle layout ``` my-bundle/ ├── manifest.yaml # REQUIRED — canonical bundle metadata ├── src/main/resources/bundles// # schema files (canonical layout per CUOD example) │ ├── spacecraft.schema.json │ ├── satellite_type.schema.json │ └── ... └── pom.xml # OPTIONAL — если bundle distributed via Maven artifact ``` `manifest.yaml` — единственный required file. `schemaRef` paths могут быть произвольной структурой ниже bundle root (но не выходят выше через `..`). --- ## 2. Manifest schema (apiVersion: ordinis.io/v1) ```yaml apiVersion: ordinis.io/v1 # REQUIRED, must equal "ordinis.io/v1" kind: Bundle # REQUIRED, must equal "Bundle" metadata: # REQUIRED block id: # REQUIRED, regex ^[a-z][a-z0-9.-]{1,127}$ name: # REQUIRED, non-blank version: # REQUIRED, regex X.Y.Z[-suffix] description: | # OPTIONAL, multi-line ... domain: # OPTIONAL (e.g. "dzz", "finance") scope: PUBLIC|INTERNAL|RESTRICTED # OPTIONAL — default scope для dicts author: # OPTIONAL license: # OPTIONAL signature: ed25519: # ADDED BY PUBLISHER CLI (см. §4) dictionaries: # REQUIRED, non-empty list - name: # REQUIRED, regex ^[a-z][a-z0-9_]{0,63}$, unique within bundle schemaVersion: # REQUIRED scope: PUBLIC|INTERNAL|RESTRICTED # REQUIRED schemaRef: # REQUIRED, bundle-relative, no ".." sampleDataRef: # OPTIONAL — seed records displayName: # OPTIONAL — default humanized name description: # OPTIONAL checksums: # OPTIONAL — SHA-256 hex для tamper detection schemaSha256: sampleSha256: ``` Unknown top-level и nested fields игнорируются (forward-compat). --- ## 3. Validation rules `ManifestParser.validate()` enforces: | Rule | Enforced | |---|---| | `apiVersion == "ordinis.io/v1"` | ✅ | | `kind == "Bundle"` | ✅ | | `metadata.id` matches reverse-DNS regex | ✅ | | `metadata.version` valid semver | ✅ | | `metadata.scope` ∈ {PUBLIC, INTERNAL, RESTRICTED} if present | ✅ | | `metadata.signature` starts с `ed25519:` if present | ✅ | | `dictionaries` non-empty | ✅ | | `dictionaries[].name` regex match | ✅ | | `dictionaries[].name` unique within bundle | ✅ | | `dictionaries[].schemaVersion` valid semver | ✅ | | `dictionaries[].scope` ∈ {PUBLIC, INTERNAL, RESTRICTED} | ✅ | | `dictionaries[].schemaRef` no `..`, no leading `/` | ✅ (path traversal guard) | Не валидируется (downstream responsibility): - `schemaRef` файл существует - Referenced JSON Schema валидный draft-07 - FK references (`x-references`) resolve cross-dict --- ## 4. Signing protocol Bundle publisher CLI (см. §5 — future Maven plugin): 1. Build bundle archive: `tar -czf bundle.tar.gz manifest.yaml ` 2. Sign archive bytes с ed25519 private key: ``` signature = ed25519: ``` Реально просто sign'ить archive bytes напрямую — ed25519 internally hashes. 3. Write signature в `metadata.signature` ИЛИ в отдельный `bundle.sig` file. Verification (`BundleSignatureVerifier.verify`): ```java byte[] archive = Files.readAllBytes(Path.of("bundle.tar.gz")); String signature = manifest.metadata().signature(); // или из bundle.sig byte[] publicKeyDer = Files.readAllBytes(Path.of("publisher.pub.der")); boolean ok = BundleSignatureVerifier.verify(archive, signature, publicKeyDer); ``` Public key format: X.509 SubjectPublicKeyInfo DER. Generation: ```bash openssl genpkey -algorithm Ed25519 -out priv.pem openssl pkey -in priv.pem -pubout -outform DER -out pub.der openssl pkey -in priv.pem -pubout -outform PEM -out pub.pem # для distribution ``` Ed25519 chosen для: deterministic signatures, fast verify (~50µs), small keys (32 bytes pub). JDK 15+ native поддержка. --- ## 5. Publisher tooling (future) `ordinis-bundle-publisher` Maven plugin (отдельный artifact, NOT shipped в v2): ```bash mvn ordinis:bundle-publish \ -Dbundle.path=ordinis-cuod-bundle \ -Dnexus.url=https://nexus.corp/ordinis-bundles/private/cuod \ -Dsigning.key=$BUNDLE_SIGNING_KEY ``` Steps плагина (когда built): 1. `ManifestParser.parseAndValidate(manifest.yaml)` — fail fast если errors 2. Resolve schema refs → tar.gz archive 3. `BundleSignatureVerifier.sign(archive, privateKey)` → signature 4. Patch `metadata.signature` в archive's manifest 5. Upload tar.gz + sidecar bundle.sig к Nexus Скрипт можно написать через shell + openssl до того как plugin доступен. --- ## 6. Consumer protocol (future v3) NOT shipped в v2. Reserved для multi-tenant marketplace scenario: - Download bundle from Nexus + verify signature - Parse manifest + extract schemas - Apply dictionaries to target ordinis instance через standard write API + draft workflow Spec гарантирует что implementer этого consumer'а получит deterministic bundle behavior independent от ordinis runtime version. --- ## 7. Versioning policy | Component | Versioning | |---|---| | `apiVersion` | bumped только на breaking spec change (e.g. `ordinis.io/v2`). Backward-compat additions stay на v1. | | `metadata.version` | semver. Patch = data fix, minor = schemaVersion bump одного dict, major = breaking schema change | | `dictionaries[].schemaVersion` | per-dict semver — independent от bundle version | Breaking changes в schemas (`additionalProperties` add, required field remove, type change) требуют MAJOR bump на bundle level. --- ## 8. Reference implementation `ordinis-cuod-bundle/manifest.yaml` — 40 dicts, signed via CI на publish. Working example для bundle authoring. Parser test fixture: `ordinis-bundle-spec/src/test/resources/cuod-manifest-fixture.yaml` (snapshot — pin test catches regressions). --- ## 9. Anti-features (explicitly NOT in v1) - Bundle dependency declaration (no `requires:` block) - Topological install order - Multi-tenant namespace isolation - Bundle uninstall / migration scripts - Public marketplace catalog См. `docs-internal/design/dictionary-marketplace.md` — почему scoped down.