Files
mdm-ordinis/docs/integration/bundle-format-spec.md
T
Zimin A.N. 8adfd39bff feat(bundle): bundle hygiene v2 — manifest spec + signing verifier + docs
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
2026-05-17 10:39:07 +03:00

7.2 KiB
Raw Blame History

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):

  1. Build bundle archive: tar -czf bundle.tar.gz manifest.yaml <schemaRef paths>
  2. Sign archive bytes с ed25519 private key:
    signature = ed25519:<base64(ed25519_sign(privateKey, sha256(archive)))>
    
    Реально просто sign'ить archive bytes напрямую — ed25519 internally hashes.
  3. Write signature в metadata.signature ИЛИ в отдельный bundle.sig file.

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):

  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.