Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/znippy

plugins/native/znippy-plugin-skidbladnir/.nornir/znippy-plugin-skidbladnir.md

znippy-plugin-skidbladnir — design

Class: design · siblings: ../../.nornir/plugins-design.md · ../../.nornir/plugins-guide.md. Strict: states only the shape we ship. It teaches znippy's Arrow-IPC index what each entry of a Skidbladnir airgap bundle is — the Tillsynia/Njord deploy payload. It adds only metadata columns; the format, pack/move/unfold mechanics and sealing stay owned by znippy + Skidbladnir.

f0 — what it is

A Skidbladnir bundle folds a whole deployment — DB dump, document blobs, container images, infra operators + Talos boot media, declarative config, encrypted secrets — into one sealed, content-addressed archive. This plugin makes that bundle self-describing: every entry is tagged into the index so the sealed archive is queryable without unfolding it:

SELECT logical_path, component FROM bundle WHERE member_kind = 's3-blob';
SELECT version, watermark FROM bundle WHERE member_kind = 'manifest';

Native builtin, not WASM. It is a plain rlib linked into znippy-cli and registered in handlers.rs::builtin_handlers(); it does not build a cdylib and is not compiled to WASM. Discovery is via meta() like every handler. Selected with --format skidbladnir (aliases skid · airgap · tillsynia), type_id = 40 (clear of built-ins 1–18 and media 25).

f1 — the bundle layout (what it claims)

Ownership is decided purely from the relative path — no read, no parse (src/member.rs::classify, total + exhaustively tested):

tillsynia-<version>.znippy
├── lakespec.json                # products + depends_on order + pki source
├── manifest/                    # bundle.json (version·scope·watermark) + consistency.json
├── dbdump/                      # atomic pg_dump stream (postgres)
├── s3/                          # document blobs (minio; dokument.fil_nyckel)
├── app/                         # container images: njord-web, njord-mail-server (oci tar)
├── infra/                       # unfold operators + boot media
│   ├── terraform/  redfish/  kvm/
│   └── talos/                   # talos-<ver>.iso + machineconfig.yaml
├── njordconf/                   # declarative config (serverspec/services; helm optional)
└── secrets/                     # *.age — encrypted; key supplied at unfold, never in bundle

f2 — the member_kind schema

MemberKind (src/member.rs) — the discriminant string in the member_kind column, and the component a member maps to:

member_kind claimed by component
lakespec lakespec.json — (bundle-level)
manifest manifest/* — (bundle-level)
dbdump dbdump/* postgres
s3-blob s3/* minio
image app/*.oci.tar web / mail-server (embedded image — archival/full tier)
image-ref app/*.imageref · *.ref web / mail-server (registry pointer — plain tier, needs the registry)
infra-terraform infra/terraform/* infra
infra-redfish infra/redfish/* infra
infra-kvm infra/kvm/* infra
talos-iso infra/talos/*.iso infra
talos-machineconfig infra/talos/* (non-iso) cluster
config njordconf/* (serverspec/services) config
helm-chart njordconf/**/*.tgz platform (the fresh-install chart — a fresh app self-migrates, so chart+values is all a platform-tier bundle needs)
helm-values njordconf/values*.yaml platform (values overlay for the chart)
secret secrets/* or any *.age secrets

Arrow columns the plugin contributes (schema_fields, all Utf8 nullable):

column source
member_kind MemberKind::as_str()
component component_of(kind, path)
logical_path the relative path (e.g. dokument.fil_nyckel)
version parsed from lakespec/manifest JSON (else null)
watermark parsed from manifest JSON — the delta clock (uppdaterad) (else null)
encrypted is_encrypted(kind, path) — secret or *.age

The per-entry BLAKE3 checksum already lives in the base index columns, so it is not duplicated here.

f3 — the ArchiveTypePlugin fill

Trait znippy-common/src/plugin.rs::ArchiveTypePlugin. Fill (NativeSkidbladnirPlugin, src/lib.rs):

method behaviour
name "skidbladnir"
type_id SKIDBLADNIR_TYPE_ID = 40
meta name + aliases (skid/airgap/tillsynia), ecosystem, extensions, the two subcommands
matches_path classify(path).is_some() — claims recognised members, ignores stray files
schema_fields the six columns above
extract_metadata classify → fill member_kind + logical_path + encrypted (+ component); for lakespec/manifest also parse version/watermark
run_command classify <path> (print kind/component/encrypted) · layout (print the canonical tree)

extract_batch uses the trait default (per-file); the payload is small and classification is O(path), so no batch pass is needed.

f4 — laws honoured

law how
P-1 one archive = one ecosystem --format skidbladnir selects this single handler
P-2 writes only declared columns fills only its six schema_fields
P-3 discovery by record listed via meta(), no name table
P-4 never panic garbage/oversized JSON → version/watermark degrade to null; the member still writes with its path-derived member_kind

f5 — read side (follow-up, not yet built)

Mirror the maven/rust/python typed views: a skidbladnir_view() that round-trips this branch of the DenseUnion out for the Skidbladnir CLI's verify / restore-order (they need an archive handle the plugin run_command does not have — hence they are not plugin subcommands). Round-trip coverage would land in plugin_read_api_test.rs.

Status

Built + green: crate (10 unit tests — classification, metadata, bomb-defense, schema/meta consistency), registered in builtin_handlers, znippy handlers lists it, classify/layout subcommands work, and an end-to-end compress --format skidbladnir round-trips all twelve member kinds through list. Read-side typed view is the open follow-up.