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

183 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
```yaml
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`):
```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.