OfflineKit
The consolidated offline-exchange service. One deployable family, two roles, one database.
It absorbed ExportCenter and Mirror whole, and AirGap’s two posture hosts, at SPRINT_20260722_025 (owner-approved EVL-3). The libraries those families contributed keep their original namespaces on purpose — the lift was byte-identical so no consumer using had to change.
| Role | Image | Serves |
|---|---|---|
offlinekit-web | stellaops/offlinekit-web | the whole /api/offlinekit/v1 surface: the export plane and the carrier-custody plane |
offlinekit-worker | stellaops/offlinekit-worker | the carried background loops (export-run executor, risk-bundle jobs, DevPortal offline); the audit-bundle loop is hosted by offlinekit-web; no HTTP surface |
Database: stellaops_offlinekit, one role, migrated forward-only on startup from embedded SQL (ADR-004). Schemas: offlinekit (carrier custody and part activation), export_center (the export engine), catalog_replica + eventing (the tenants and environment-state replicas).
Router name: offlinekit. One process publishes one HELLO name, which is why the predecessor’s exportcenter name died with it and every bare /v1/exports path had to be re-prefixed.
The two planes, and why the distinction matters
The host serves two things that look alike and are not:
- The export plane — producing bundles out of the estate. Tenant-scoped through the standard tenant middleware, per-endpoint
export:viewer/export:operator/export:admin. - The carrier-custody plane — taking signed bundles in. Q-25 verified-carrier custody, which resolves its tenant from the authenticated envelope and is therefore deliberately excluded from the tenant middleware. Applying that middleware globally made the import endpoint answer 403 where it had answered 422 for a digest mismatch — rejecting the request before the verifier could tell the operator what was wrong with their bundle.
Capabilities
| Capability | Where it lives |
|---|---|
| Export profiles, runs, artifacts, distributions | StellaOps.ExportCenter.Application/Api/, .Core/Persistence/ |
| Assurance exports (CRA, NIS2 SoA, DORA RoI) | .Application/AssuranceExports/, .Adapters.Cra, .Adapters.Nis2 |
| Audit bundles, incidents, lineage, exception reports | .Application/{AuditBundle,Incident,Lineage,ExceptionReport}/ |
| Risk bundles | .RiskBundles, .WorkerRuntime/RiskBundleWorker.cs |
| Attestation and promotion attestation | .Application/Attestation/ |
| Mirror bundle assembly | StellaOps.Mirror.Creator, .Application/Mirror/MirrorProfilePlanner.cs |
| Carrier custody: import, status, manifest, validate | StellaOps.OfflineKit.WebService/Imports/, Endpoints/ |
| Generic part activation | …/Imports/OfflineKitPartActivation.cs, OfflineKitPartExtractor.cs |
| Sealed-posture reads | StellaOps.AirGap.Policy.Replication over this host’s own replica |
| Time-anchor verification | StellaOps.AirGap.Time.Verification |
The part-activation seam
OfflineKit owns custody; it never owns another domain’s content. An adapter implementing IOfflineKitPartActivator is keyed on the (domain, mediaType, generation) triple that the manifest validates and the parts ledger keys on, and it owns exactly three things: its own payload type, its own bounded schema, and the call to its owner API. It never touches the ledger — an activator that could would be a second custody authority.
The registry refuses to compose when two activators claim one triple. That is deliberate: two owners each believing they activate the same signed content is not a runtime condition to recover from, and whichever the container resolved last would silently win.
A declared part that no registered activator claims is recorded failed with PART_ACTIVATOR_NOT_REGISTERED while the carrier stays verified. Custody and activation are separate verdicts; collapsing them would make a missing adapter look like an untrusted bundle.
Reference adapter: ScannerArtifactBomPartActivator. The Vulnerabilities/VEX adapter is OK-7V and is not built.
Where the detail still lives
This family’s own pages:
architecture.md— runtime topology, the two planes, the export run engine, the import pipeline, data model, configuration and custody (source-verified 2026-09-14)operations/import-cas-capacity-guard.md— the owned-state measurer and the write pause (OK-CAP)../../runbooks/airgap/offlinekit-import.md— the operator path forstella airgap import, exit codes and the posture gate
These dossiers are retained rather than merged — they hold format, protocol and signing detail that is still current, behind a pointer stub for the topology:
export-center/— profiles, determinism, provenance and signing, DevPortal offline manifests, mirror bundles, schemas; the retired service dossier is archived atdocs-archive/modules/export-center/mirror/— thin-bundle assembler, DSSE/TUF profile, signing runbookairgap/— evidence reconciliation, exporter/CLI coordination, runbooks and guidesairgap-controller/— pointer stub for the retired host
Operator-facing: docs/OFFLINE_KIT.md.
Known gaps, stated rather than implied
Re-measured 2026-09-14 at the close of SPRINT_20260722_025; the earlier entries here (no export-run executor, no owned-state measurer, predecessor database still present) are all false at HEAD and are removed rather than annotated.
- Closed 2026-09-14 (
SPRINT_20260914_002, live window): export runs ARE carriers (EA-1: the run engine writes every completed run throughOfflineKitCarrierWriter;stella airgap pull --runthenstella airgap importactivates the Scanner part on the same estate); a positive import under SEAL was observed (EA-5:stellaops-cliholdsenvstate:time-anchor:write, the host carries the timestamp-authority public trust the gate re-verifies against); the API reference is generated —api-reference.mdfromopenapi/v1.json(EA-6). Evidence:docs/implplan/_evidence/20260914-ea-window/. - One verification story (EA-2, 2026-09-14). The custody ledger has exactly one writer, the import service after
OfflineKitDsseVerifier; the architecture pack pins it (OfflineKitAdmissionConformanceTests). The legacy knowledge-snapshot and export-archive verifiers are withdrawn undersrc/__Obsoleted/OfflineKit/__Libraries/. The CLI’s client-side pre-checks (stella offline import --verify-*,stella verify offline) verify and admit nothing. TheAirGap.Syncjob-log dialect was withdrawn 2026-09-14 (SPRINT_20260914_002EA-8 on D-EA4-1): no production reader, no registered verb, no owner service; frozen undersrc/__Obsoleted/OfflineKit/__Libraries/StellaOps.AirGap.Sync/. The carrier is the one envelope.
