# 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.
