Catalog & Package System

How the package ecosystem works — format specification, dependency resolution, multi-registry architecture, signing, and air-gapped distribution.

System Overview

.hpkg Package Format

A .hpkg file is a deterministic tar.gz archive containing:

package-name-1.0.0.hpkg (tar.gz)
├── MANIFEST.toml        ← always first (streamable extraction)
├── SIGNATURE.ed25519    ← ed25519 signature (optional)
├── README.md            ← documentation (optional)
├── LICENSE              ← license text (optional)
├── sbom.json            ← software bill of materials (optional)
└── payload/             ← package contents (sorted recursively)
    ├── COMPETENCY.md    ← markdown with YAML frontmatter (the artifact)
    ├── references/      ← optional reference docs
    ├── assets/          ← optional templates, schemas
    └── ...

Markdown-First Payload Format

The .md file IS the package artifact. YAML frontmatter carries structured metadata, the markdown body carries the prompt/instructions. One .md file per package type:

Package TypePayload FileRequired Frontmatter
SkillSKILL.mddescription
CompetencyCOMPETENCY.mddescription, skills
AgentTemplateAGENT.mddescription
WorkflowTemplateworkflow.jsondescription
Integrationmcp.jsonname, description, transport

Identity (name/id) does not live in payload frontmatter for any package type — MANIFEST.toml [package] name is the sole identity everywhere. A frontmatter field named name or id is rejected (deny_unknown_fields).

Example SKILL.md:

---
description: "Rust programming expert"
runtime: prompt-only
---

# Rust Expert

You are an expert Rust developer...

The platform translates .md payloads to kernel-native format (e.g., skill.toml) only at the install boundary — never in the archive, never in the registry.

Determinism

Archives are reproducible:

  • Files sorted lexicographically by path
  • Fixed timestamp: 2024-01-01 00:00:00 UTC (hardcoded)
  • Same input always produces same output (byte-identical)

Size Limit

Maximum package size for upload: 50 MB (enforced client-side before publish).

MANIFEST.toml Schema

[package]
type = "competency"                    # skill | competency | agent-template | workflow-template | integration
name = "claims-intake"                 # kebab-case, 3-64 chars
title = "Claims Intake"                # required, human-readable display name
version = "1.2.0"                      # semver 2.0
description = "FNOL processing"        # ≤500 chars
license = "MIT"                        # SPDX identifier
min_platform_version = "0.5.0"         # minimum Hoziron version

# No [package.author] section — there's no self-declared author field on
# MANIFEST.toml. Provenance is the registry's signed publish record instead
# (see ../reference/config/manifest-schemas.md). `[author]` only exists on
# COLLECTION.toml, a different manifest format.

[package.metadata]
repository = "https://github.com/..."  # optional, must be http(s)
homepage = "https://..."               # optional
keywords = ["insurance", "claims"]     # max 20, each ≤50 chars
categories = ["insurance/claims"]      # taxonomy paths
regions = ["us", "uk"]                 # deployment regions (empty = all)

[dependencies]
document-ocr = "^1.0"                  # semver constraint
claims-core-adapter = ">=2.0.0, <3.0.0"

[standards]
mcp_compatible = true                  # exposes MCP tool interface
openapi_spec = "payload/openapi.yaml"  # only for skill/integration

[signing]
publisher_key = "ed25519:mK3xR7..."    # populated by `package sign`
content_hash = "sha256:a1b2c3d4..."    # populated by `package build`

Package Types

TypeContainsUse Case
skillTool bundle + config (covers single-tool packages too)Multi-tool skill package
competencyCOMPETENCY.md + prompts + knowledgeAgent behavior definition
agent-templateFull agent configPre-configured agent
workflow-templateWorkflow JSON + agent refsMulti-agent pipeline
integrationMCP server implementationExternal service connector

Validation Rules

FieldRule
nameLowercase, digits, hyphens only. No leading/trailing/consecutive hyphens. 3–64 chars.
versionValid semver (MAJOR.MINOR.PATCH, optional pre-release)
descriptionNon-empty, ≤500 characters
licenseNon-empty SPDX identifier
dependenciesEach constraint must be a valid semver range
standards.openapi_specOnly valid for skill, integration types
standards.mcp_compatibleOnly valid for skill, integration types

Package Lifecycle Contract (ADR-044, ADR-056)

All five package types share a small, tiered set of Core-owned lifecycle traits (crates/platform/hoziron-core/src/lifecycle/traits.rs), enforced by the type system rather than by five bespoke per-type handlers:

TypeTraitsRuntime state
Skill, CompetencyInstallable onlyNone — inert definitions, referenced from above
Agent, WorkflowInstallable + RunnableDurable — must survive reboot (activate/suspend, no start/stop/Terminated; see ../architecture/object-model.md)
IntegrationInstallable + ConnectableEphemeral — an OS subprocess/connection re-derived from the definition on boot, never persisted or resumed

install is structural only, never activating. It writes the definition to the Core store and cascade-installs dependencies (skills for a competency, competencies/skills for an agent, referenced agents for a workflow) — every entry point (CLI, API, console) produces the same result, a package landing Suspended. No entry point auto-activates as a side effect of install.

Install-only provenance (ADR-060): every package type enters the running kernel exclusively through its store-backed install path — there is no side door that mints a live kernel object with no corresponding store row. This was audited across all five types after a workflow-specific side door (Platform::register_workflow, which minted a fresh kernel WorkflowId with no WorkflowStore entry — the primary path the console's visual workflow editor used) was found and deleted outright. Agent, Competency, Skill, and Integration were confirmed to have no equivalent gap. Every successful install now emits a PackageLifecycle audit event carrying the store UUID, package type/name/version, and the authenticated caller who triggered it.

Integration Contracts (ADR-051) and REST/SOAP Packages (ADR-057)

An integration-type package supplies tools two ways:

  • MCP — a running MCP server process, connected/disconnected via Connectable.
  • REST/SOAP — a payload/adapter.json JSON contract (not the TOML sketch of earlier proposals) declaring transport, base URL, a normalized operation catalogue (ops, each single/two_phase/async_callback), and — critically — body construction and response/identity extraction performed by a named Starlark transform file (payload/transforms/*.star) rather than a static field-mapping table. Pure declarative mapping alone was found insufficient past roughly 70–80% coverage (the same wall MuleSoft/Camel/Zapier hit, addressed the same way — a scripting escape hatch): arrays needing iteration, computed/derived fields, conditional body shape, and SOAP's WS-Security/namespace/repeating-segment reality all need more than a flat path table. The contract carries no PII-specific annotations of any kind — PII detection stays entirely owned by the unified carrier policy and the mediator (see pii-data-protection.md), never duplicated into the package format.

Either way, a competency does not name a concrete integration package. It declares a contract — a role name like claims-core or policy-admin — and the integration package declares which contract(s) it fulfils and which tool names it provides for each:

# Integration package MANIFEST.toml
[contracts.claims-core]
provides = ["claim.search_history", "claim.create", "claim.update",
            "claim.set_status", "claim.attach_document", "claim.assign"]

At competency-equip time, the platform resolves the contract to whichever installed package declares it (integration_registry.find_by_contract) — see competency-system.md for the full equip-time resolution flow. This is what lets the same competency package run unmodified against Guidewire, Duck Creek, or a test rig: the swap is "install a different integration package under the same contract," never a competency edit.

A package built to the ADR-057 REST/SOAP contract is not automatically safe to route real carrier PII through — the contract defines the artifact format only; mediation, PII detection, and billing enforcement are the mediator's job (ADR-047/049), sequenced separately.

Cryptographic Signing

Key Generation

hoziron-cli package keygen
  • Algorithm: Ed25519
  • Private key: 32 random bytes (OS RNG), stored base64-encoded at $HOZIRON_HOME/keys/default.key
  • Public key format: ed25519:<base64-of-32-bytes>
  • File permissions: 0600 (owner read/write only)

Signing Flow

Verification Flow

Content Hash (Merkle Root)

The content hash is a deterministic SHA-256 over the payload:

For each file in payload/ (sorted lexicographically by relative path):
    entry_hash = SHA-256(relative_path_bytes + file_content_bytes)

content_hash = SHA-256(entry_hash_1 + entry_hash_2 + ... + entry_hash_N)

Dependency Resolution

Algorithm

Semver Constraints

SyntaxMeaningExample
*Any versionMatches everything
1.2.3Caret (same as ^1.2.3)>=1.2.3, <2.0.0
^1.2.3Compatible updates>=1.2.3, <2.0.0
~1.2.3Patch-level only>=1.2.3, <1.3.0
=1.2.3Exact matchOnly 1.2.3
>=1.0.0, <2.0.0RangeCompound constraint
>1.0.0Greater thanComparison

Conflict Detection

When two packages require incompatible versions of the same dependency:

Package A requires C@^1.0.0 (resolves to 1.x)
Package B requires C@^2.0.0 (resolves to 2.x)
→ No single version of C satisfies both → VersionConflict error

The error reports which packages imposed which constraints:

version conflict for package 'pkg-c':
  - pkg-a requires pkg-c@^1.0.0
  - pkg-b requires pkg-c@^2.0.0

Cycle Detection

DFS with visited/stack sets. When a back-edge is found, the full cycle path is reported:

dependency cycle detected: pkg-a → pkg-b → pkg-c → pkg-a

Diamond Dependencies

Handled correctly — the resolver selects the highest version that satisfies all constraints:

A requires C@^1.0.0
B requires C@^1.5.0
Available: C@1.0.0, C@1.5.0, C@1.8.0
→ Resolves C to 1.8.0 (satisfies both ^1.0.0 and ^1.5.0)

Lockfile (packages.lock)

Ensures reproducible installs by recording exact resolved versions:

[metadata]
generated_at = "2026-06-04T10:00:00Z"
resolver_version = 1

[[package]]
name = "claims-intake"
version = "1.2.0"
source = "https://catalog.hoziron.com"
content_hash = "sha256:a1b2c3d4..."
dependencies = ["document-ocr", "claims-core-adapter"]

[[package]]
name = "document-ocr"
version = "1.4.0"
source = "https://catalog.hoziron.com"
content_hash = "sha256:e5f6a7b8..."
dependencies = []

Behavior

  • If lockfile exists and all constraints are satisfied → use locked versions (no network)
  • If --force-resolve or lockfile is stale → re-resolve and rewrite
  • Supports merge (incremental installs) and remove (uninstalls)

Multi-Registry Architecture

Configuration

[catalog]
verify_signatures = true
packages_dir = "packages"
cache_ttl_secs = 3600
default_publish_registry = "internal"

[[catalog.registries]]
name = "internal"
url = "https://packages.internal.company.com"
priority = 1
auth_token_env = "INTERNAL_REGISTRY_TOKEN"
enabled = true

[[catalog.registries]]
name = "hoziron"
url = "https://catalog.hoziron.com"
priority = 100
enabled = true

[catalog.scopes]
internal = "https://packages.internal.company.com"
hoziron = "https://catalog.hoziron.com"

Scoped Packages

@scope/name syntax pins a package to a specific registry:

# This ONLY queries the registry mapped to "internal"
hoziron-cli catalog install @internal/proprietary-claims-tool

Scope rules:

  • Scope names: lowercase alphanumeric + hyphens
  • Must be mapped in [catalog.scopes] → error if unknown scope
  • Bypasses priority-order resolution entirely
  • Prevents name collisions between registries

Publish Target Resolution

When publishing, the target registry is resolved:

  1. Explicit --registry <name> flag
  2. Scope-based routing (if package name has @scope/ prefix)
  3. default_publish_registry config field
  4. Error: "no publish target specified"

Collections

Curated, self-contained sets of packages for quick onboarding.

COLLECTION.toml

name = "insurance-starter"
display_name = "Insurance Starter Kit"
description = "Everything needed to start processing claims"
version = "1.0.0"
license = "MIT"
featured = true
tags = ["insurance", "claims", "starter"]
icon = "shield-check"

[author]
name = "Hoziron Team"

[[packages]]
name = "claims-intake"
version = "1.2.0"
package_type = "competency"
is_dependency = false

[[packages]]
name = "document-ocr"
version = "1.4.0"
package_type = "skill"
is_dependency = true

[[packages]]
name = "claims-core-adapter"
version = "2.1.0"
package_type = "integration"
is_dependency = true

Closure Invariant

A collection must include ALL transitive dependencies of every package it contains. Installing a collection never requires fetching packages outside the collection.

Validated by validate_collection_closure() — reports which package is missing which dependency.

Installation

hoziron-cli collection install insurance-starter
# Installs all packages, skipping any already installed

Collections are additive — users can install multiple, overlapping packages are deduplicated.

Air-Gapped Transfer

For environments without network access:

  • Export creates a plain tar of the installed package directory
  • Import extracts, reads the manifest, and records installation
  • No network required on the air-gapped side
  • Content hash verified locally after import

Taxonomy

Packages are categorized using a hierarchical taxonomy:

Domain → Subdomain → Function → Variants

Structure (Insurance domain example)

insurance/
├── claims/
│   ├── fnol (variants: auto, property, liability, workers-comp, marine, aviation)
│   ├── adjudication
│   ├── subrogation
│   ├── reserving
│   ├── litigation
│   ├── catastrophe (variants: weather, wildfire, earthquake, flood)
│   └── total-loss
├── fraud/
│   ├── detection (variants: claims, application, premium)
│   ├── investigation
│   ├── prevention
│   └── network-analysis
├── underwriting/
│   ├── risk-assessment
│   ├── rating
│   ├── quoting
│   └── submission-intake
├── policy-admin/
│   ├── issuance
│   ├── endorsements
│   ├── renewals
│   └── cancellations
└── ... (actuarial, distribution, compliance, billing, reinsurance, LOB)

Additional Domains

  • Banking & Financial Services — lending, payments, KYC/AML
  • Healthcare — clinical, revenue cycle
  • Document Processing — extraction, generation
  • General — productivity, development, data

Related:

  • competency-system.md — contract resolution at equip time
  • ../architecture/object-model.md — the lifecycle trait diagram in domain-model context
  • docs/decisions/029-install-is-instantiation.md, 044-package-lifecycle-contract.md, 051-contract-based-integration-resolution.md, 056-unified-activation-gate.md, 057-rest-soap-integration-package-contract.md, 060-install-only-artifact-provenance.md

Taxonomy Versioning (ADR-004)

  • Append-only (nodes added, never removed)
  • Semver versioned (patch = description changes, minor = new nodes, major = structural)
  • Compiled into the binary as offline fallback
  • Registry serves canonical version at runtime
  • Unknown nodes gracefully degrade (display raw ID, never hard error)