Ships marketplace-design-v2 scoped to bundle hygiene (NO consumer UI, NO install dialog, NO marketplace). 2-3 day cap respected — Approach C narrow slice. Anti-feature commitment: scope creep → new design doc + /office-hours. ## What ships ### New module ordinis-bundle-spec - BundleManifest.java — canonical apiVersion: ordinis.io/v1 record (Metadata + DictionaryEntry, Jackson-bindable, forward-compat ignoreUnknown) - ManifestParser.java — YAML → record + structural validate (apiVersion/kind enforce, semver regex, bundle-id regex, path traversal guard на schemaRef, unique dict names, scope enum) - BundleSignatureVerifier.java — ed25519 sign/verify (JDK 21 native) - Zero Spring/JPA/Kafka deps — pure Java + Jackson YAML ### CUOD reference implementation - ordinis-cuod-bundle/manifest.yaml — 40 dicts canonical entries - Test fixture pin: cuod-manifest-fixture.yaml parses+validates clean (regression guard на каждом CI run) ### Tests - 12 cases ManifestParser (happy + reject paths) - 7 cases BundleSignatureVerifier (sign/verify roundtrip, tamper, wrong key) - 1 case CUOD fixture pin - Total 20 tests, all green ### Docs - docs/integration/bundle-format-spec.md — formal spec для Java handoff - docs/user-guide/bundle-authoring.md — how-to для новых bundle authors ## Why now (N=1 customer) Per office-hours v2 decision: marketplace UI build = N customers value scale, but bundle hygiene + sign format = AT N=1 ROI: - Java team handoff requires documented spec независимо - Sales demo «here's how customer #2 would ship custom bundle» - Optionality для marketplace UI когда customer #2 LOI surfaces ## Explicitly NOT shipped (Phase 2 scope if ever) - Publisher Maven plugin (separate artifact when actual publish needed) - Browse catalog UI / install dialog (no customer #2 with use case) - Dep resolution / topological install - Multi-tenant Nexus namespacing - Bundle uninstall flow
7.2 KiB
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/<id>/ # 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)
apiVersion: ordinis.io/v1 # REQUIRED, must equal "ordinis.io/v1"
kind: Bundle # REQUIRED, must equal "Bundle"
metadata: # REQUIRED block
id: <reverse-dns-bundle-id> # REQUIRED, regex ^[a-z][a-z0-9.-]{1,127}$
name: <human-readable> # REQUIRED, non-blank
version: <semver> # REQUIRED, regex X.Y.Z[-suffix]
description: | # OPTIONAL, multi-line
...
domain: <tag> # OPTIONAL (e.g. "dzz", "finance")
scope: PUBLIC|INTERNAL|RESTRICTED # OPTIONAL — default scope для dicts
author: <org-or-team> # OPTIONAL
license: <SPDX-or-proprietary> # OPTIONAL
signature: ed25519:<base64> # ADDED BY PUBLISHER CLI (см. §4)
dictionaries: # REQUIRED, non-empty list
- name: <snake_case> # REQUIRED, regex ^[a-z][a-z0-9_]{0,63}$, unique within bundle
schemaVersion: <semver> # REQUIRED
scope: PUBLIC|INTERNAL|RESTRICTED # REQUIRED
schemaRef: <relative-path> # REQUIRED, bundle-relative, no ".."
sampleDataRef: <relative-path> # OPTIONAL — seed records
displayName: <human-readable> # OPTIONAL — default humanized name
description: <text> # OPTIONAL
checksums: # OPTIONAL — SHA-256 hex для tamper detection
schemaSha256: <hex>
sampleSha256: <hex>
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):
- Build bundle archive:
tar -czf bundle.tar.gz manifest.yaml <schemaRef paths> - Sign archive bytes с ed25519 private key:
Реально просто sign'ить archive bytes напрямую — ed25519 internally hashes.
signature = ed25519:<base64(ed25519_sign(privateKey, sha256(archive)))> - Write signature в
metadata.signatureИЛИ в отдельныйbundle.sigfile.
Verification (BundleSignatureVerifier.verify):
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:
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):
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):
ManifestParser.parseAndValidate(manifest.yaml)— fail fast если errors- Resolve schema refs → tar.gz archive
BundleSignatureVerifier.sign(archive, privateKey)→ signature- Patch
metadata.signatureв archive's manifest - 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.