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 Type | Payload File | Required Frontmatter |
|---|---|---|
| Skill | SKILL.md | description |
| Competency | COMPETENCY.md | description, skills |
| AgentTemplate | AGENT.md | description |
| WorkflowTemplate | workflow.json | description |
| Integration | mcp.json | name, 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
| Type | Contains | Use Case |
|---|---|---|
skill | Tool bundle + config (covers single-tool packages too) | Multi-tool skill package |
competency | COMPETENCY.md + prompts + knowledge | Agent behavior definition |
agent-template | Full agent config | Pre-configured agent |
workflow-template | Workflow JSON + agent refs | Multi-agent pipeline |
integration | MCP server implementation | External service connector |
Validation Rules
| Field | Rule |
|---|---|
name | Lowercase, digits, hyphens only. No leading/trailing/consecutive hyphens. 3–64 chars. |
version | Valid semver (MAJOR.MINOR.PATCH, optional pre-release) |
description | Non-empty, ≤500 characters |
license | Non-empty SPDX identifier |
dependencies | Each constraint must be a valid semver range |
standards.openapi_spec | Only valid for skill, integration types |
standards.mcp_compatible | Only 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:
| Type | Traits | Runtime state |
|---|---|---|
| Skill, Competency | Installable only | None — inert definitions, referenced from above |
| Agent, Workflow | Installable + Runnable | Durable — must survive reboot (activate/suspend, no start/stop/Terminated; see ../architecture/object-model.md) |
| Integration | Installable + Connectable | Ephemeral — 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.jsonJSON contract (not the TOML sketch of earlier proposals) declaring transport, base URL, a normalized operation catalogue (ops, eachsingle/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
| Syntax | Meaning | Example |
|---|---|---|
* | Any version | Matches everything |
1.2.3 | Caret (same as ^1.2.3) | >=1.2.3, <2.0.0 |
^1.2.3 | Compatible updates | >=1.2.3, <2.0.0 |
~1.2.3 | Patch-level only | >=1.2.3, <1.3.0 |
=1.2.3 | Exact match | Only 1.2.3 |
>=1.0.0, <2.0.0 | Range | Compound constraint |
>1.0.0 | Greater than | Comparison |
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-resolveor 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:
- Explicit
--registry <name>flag - Scope-based routing (if package name has
@scope/prefix) default_publish_registryconfig field- 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)