Skip to content
Documentation

Installable overlays for domain logic.

Packs extend Cordum without core code changes. Installation is declarative and safe — it registers data (YAML, JSON Schema, config patches) but never executes code. Workers are deployed separately.

Design Goals
  • API-native installs via gateway
  • No arbitrary code execution during install
  • Namespaced resources avoid collisions
  • Soft uninstall by default, --purge to hard delete
Directory Layout
my-pack/
├── pack.yaml# Manifest
├── workflows/# Workflow YAML
├── schemas/# JSON Schema
├── overlays/# Config + policy patches
│ ├── pools.patch.yaml
│ ├── timeouts.patch.yaml
│ └── policy.fragment.yaml
└── deploy/# Informational only

Distributed as a directory or .tgz archive. Max 256 MiB uncompressed, 2048 files, 32 MiB per file.

Pack Manifest

pack.yaml
apiVersion: cordum.io/v1alpha1
kind: Pack

metadata:
  id: sre-investigator          # ^[a-z0-9-]+$
  version: 0.3.1
  title: SRE Investigator
  description: Kubernetes incident triage pack.

compatibility:
  protocolVersion: 1            # Must match core
  minCoreVersion: 0.6.0         # Semver range
  maxCoreVersion: 1.0.0

topics:
  - name: job.sre-investigator.collect.k8s
    capability: sre.collect.k8s
    requires: [kubectl, network:egress]
    riskTags: [read]

resources:
  schemas:
    - id: sre-investigator/IncidentContext
      path: schemas/IncidentContext.json
  workflows:
    - id: sre-investigator.triage
      path: workflows/triage.yaml

overlays:
  config:
    - name: pools
      scope: system
      key: pools
      strategy: json_merge_patch
      path: overlays/pools.patch.yaml
  policy:
    - name: safety
      strategy: bundle_fragment
      path: overlays/policy.fragment.yaml

tests:
  policySimulations:
    - name: allow_collect
      request:
        topic: job.sre-investigator.collect.k8s
        capability: sre.collect.k8s
      expectDecision: ALLOW

Schema File Format

Schemas use JSON Schema Draft 2020-12. IDs follow the pattern <pack_id>/<name>. During install, each schema is registered to the Redis schema registry and assigned a SHA256 digest for collision detection.

schemas/EchoInput.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Hello Pack Echo Input",
  "type": "object",
  "properties": {
    "message": { "type": "string" },
    "author":  { "type": "string" }
  },
  "required": ["message"],
  "additionalProperties": false
}

Collision Detection

If a schema ID already exists with a different SHA256 digest, install fails. Pass --upgrade to overwrite.

Rollback

If install fails after schemas are registered, the original schema is restored (or the new one deleted). Digests verify state before rollback.

Overlay Mechanics

Config overlays use JSON Merge Patch (RFC 7396) to modify pool routing and timeout settings. Maps are merged recursively; null values delete keys (used during uninstall).

overlays/pools.patch.yaml
# Route topic to a namespaced pool
topics:
  job.hello-pack.echo: hello-pack

# Define the pool
pools:
  hello-pack:
    requires:
      - local
overlays/timeouts.patch.yaml
# Set workflow-level timeout (seconds)
workflows:
  hello-pack.echo: 120

# Set per-topic timeout
topics:
  job.hello-pack.echo: 60

Validation Rules

  • Pools overlay: Topic keys must start with job.<pack_id>.. New pool keys must start with <pack_id>. Only topics and pools keys accepted.
  • Timeouts overlay: Topic keys must start with job.<pack_id>.. Workflow keys must start with <pack_id>.. Only topics and workflows keys accepted.
  • Scope: Overlays target cfg:<scope>:<scope_id>.data.<key>. Most packs use system scope with default ID.

Policy Fragments

Packs inject policy rules via bundle_fragment strategy. Each fragment is stored at cfg:system:policy.data.bundles["<pack_id>/<name>"] and merged with file-based policies at evaluation time.

overlays/policy.fragment.yaml
rules:
  - id: hello-pack-allow
    match:
      topics:
        - job.hello-pack.echo
    decision: allow
    reason: "Allow the hello-pack echo demo."

  - id: hello-pack-deny-secrets
    match:
      topics:
        - job.hello-pack.echo
      secrets_present: true
    decision: deny
    reason: "Echo pack must not handle secrets."

Bundle Entry (stored in Redis)

cfg:system:policy → bundles
{
  "hello-pack/safety": {
    "content": "rules:\n  - id: hello-pack-allow\n    ...",
    "version": "0.1.0",
    "sha256": "a1b2c3...",
    "installed_at": "2025-03-15T10:00:00Z",
    "enabled": true,
    "author": "cordumctl"
  }
}

Fragment ID is <pack_id>/<overlay_name>. The Safety Kernel merges file-based policies + all config-service fragments at reload time. Snapshot hash covers both: baseSnapshot|cfg:<hash>.

Namespacing & Conventions

All pack resources are namespaced by pack ID. The installer validates every topic, schema, workflow, and pool name against these patterns before writing any data.

ResourcePatternExample
Pack ID^[a-z0-9-]+$sre-investigator
Topicsjob.<pack_id>.*job.sre-investigator.collect.k8s
Workflows<pack_id>.<name>sre-investigator.triage
Schemas<pack_id>/<name>sre-investigator/IncidentContext
Pools<pack_id>-*sre-investigator-primary
Collision detection: Schemas and workflows are compared by SHA256 digest. If an ID exists with a different digest, install fails unless --upgrade is passed. Policy fragments and overlays overwrite by pack+name key.

Installation Flow

  • 1
    Acquire global + pack-specific distributed locks
  • 2
    Validate pack.yaml: namespacing, protocol version, core compatibility
  • 3
    Plan: compute SHA256 digests, detect collisions (fail or --upgrade)
  • 4
    Register schemas to Redis schema registry
  • 5
    Upsert workflows to Redis workflow store
  • 6
    Apply config overlays via JSON merge patch (RFC 7396)
  • 7
    Apply policy fragments into cfg:system:policy bundles
  • 8
    Write pack record to cfg:system:packs registry
terminal
# Install from a directory
cordumctl pack install ./my-pack

# Install from a tarball URL
cordumctl pack install https://packs.cordum.io/packs/sre-k8s/0.3.1/pack.tgz

# Install with overwrite (upgrade existing schemas/workflows)
cordumctl pack install --upgrade ./my-pack

# Dry-run: print intent without writing
cordumctl pack install --dry-run ./my-pack

# Install inactive (no pool mappings, status: INACTIVE)
cordumctl pack install --inactive ./my-pack

Locking

Acquires global + pack-specific distributed locks (60 s TTL). Only one pack operation runs at a time.

Rollback

If any step fails after writes begin, previous state is restored in reverse order. Digests verify correctness.

Status

ACTIVE if pools overlay applied. INACTIVE if --inactive flag used. DISABLED after uninstall.

Uninstall

Soft Uninstall

Default behavior. Removes routing and policy but preserves data.

  • Config overlays reversed (keys set to null)
  • Policy fragments removed from bundles
  • Workflows and schemas kept intact
  • Pack status set to DISABLED

Purge Uninstall

Pass --purge to also delete registered data.

  • Everything from soft uninstall, plus:
  • Workflows deleted from Redis
  • Schemas deleted from registry
  • Pack record retained (status: DISABLED)
terminal
# Soft uninstall (preserves schemas + workflows)
cordumctl pack uninstall sre-investigator

# Hard uninstall (deletes everything)
cordumctl pack uninstall --purge sre-investigator

Testing & Validation

The tests.policySimulations block in pack.yaml defines policy simulation tests. Run them with cordumctl pack verify after install.

pack.yaml — test section
tests:
  policySimulations:
    - name: allow_echo
      request:
        tenantId: default
        topic: job.hello-pack.echo
        capability: hello-pack.echo
      expectDecision: ALLOW

    - name: deny_secrets
      request:
        topic: job.hello-pack.echo
        capability: hello-pack.echo
        riskTags: [secrets]
      expectDecision: DENY
terminal
# Verify installed pack (runs policy simulations)
cordumctl pack verify hello-pack

# Output:
# ✓ allow_echo: ALLOW (expected ALLOW)
# ✓ deny_secrets: DENY (expected DENY)
# pack hello-pack: 2/2 policy simulations passed

What verify checks

  • Each test calls POST /api/v1/policy/simulate with the request fields
  • Compares returned decision (case-insensitive) against expectDecision
  • Auto-fills packId and tenantId if omitted
  • Fails with expected vs actual if any simulation mismatches

Example: hello-pack

A minimal pack that registers one schema, one workflow, pool routing, a timeout, and a policy allow rule. See the full source at examples/hello-pack.

workflows/echo.yaml
id: hello-pack.echo
name: Hello Pack Echo
org_id: default
steps:
  echo:
    id: echo
    name: Echo input
    type: worker
    topic: job.hello-pack.echo
    input_schema_id: hello-pack/EchoInput
    input:
      message: "${input.message}"
      author: "${input.author}"
    meta:
      pack_id: hello-pack
      capability: hello-pack.echo
terminal — full lifecycle
# Install
cordumctl pack install ./examples/hello-pack

# Verify policy tests
cordumctl pack verify hello-pack

# Run the workflow
cordumctl workflow run hello-pack.echo \
  --input '{"message": "Hello, world!"}'

# List installed packs
cordumctl pack list

# Uninstall
cordumctl pack uninstall hello-pack