# Safety Packs — Reference **Status:** Operational reference. Update when pack format, loader contract, or composition rules change. **Last updated:** 2026-05-17 **Companion docs:** [`adr/ADR-0029-safety-packs.md`](adr/ADR-0029-safety-packs.md), [`identity_packs.md`](identity_packs.md) ## What a safety pack is A safety pack carries the boundaries CORE will **never** cross, regardless of which identity pack is selected. Where identity packs encode *who* CORE is, safety packs encode *what CORE will not do*. The two layers compose at runtime: `manifold.boundary_ids = safety.boundary_ids ∪ identity.boundary_ids`. Three properties distinguish safety packs from identity packs: | Property | Identity pack | Safety pack | |---|---|---| | Swappable at runtime | Yes (`--identity X`) | **No** | | Multiple packs available | Yes | **Exactly one** | | Failure to load | Falls back to default; warns | **Fail-closed; refuses startup** | | Schema | `value_axes`, `surface_preferences`, etc. | `boundary_ids`, `boundary_descriptions` | | Directory | `packs/identity/` | `packs/safety/` | ## Shipping safety pack (v1) | Pack id | Description | Ratified | |---|---|---| | `core_safety_axes_v1` | Always-loaded core boundaries: no fabricated source, no hot-path repair, no identity override, no silent correction, preserve versor closure. | `ee1249acdf8c273aeb656d803c37ef915e536d85f177f5cc18c6e2f6c995ce29` | ## Pack format (v1) ```json { "pack_id": "core_safety_axes_v1", "version": "1.0.0", "description": "Always-loaded, never-replaceable core safety boundaries.", "schema_version": "1.0.0", "mastery_report_sha256": "...", "boundary_ids": [ "no_fabricated_source", "no_hot_path_repair", "no_identity_override", "no_silent_correction", "preserve_versor_closure" ], "boundary_descriptions": { "no_fabricated_source": "Citations must point to a real source span; the system never invents provenance.", "no_hot_path_repair": "...", "no_identity_override": "...", "no_silent_correction": "...", "preserve_versor_closure": "..." } } ``` ### Field semantics | Field | Required | Meaning | |---|---|---| | `pack_id` | yes | Pack identifier. Convention: `_v`. | | `version` | yes | Semver. | | `description` | yes | Human-facing one-liner. | | `schema_version` | yes | Currently `"1.0.0"`. | | `mastery_report_sha256` | yes (production) | SHA of the companion `.mastery_report.json`. Empty only in development; production refuses. | | `boundary_ids` | yes | Non-empty list of unique boundary identifier strings. | | `boundary_descriptions` | yes | Dict mapping each `boundary_id` to a human-readable rationale. | ### Loader bounds (enforced) - `boundary_ids` must be a non-empty list of unique non-empty strings. - `pack_id` must not contain `/` or `..`. - `schema_version` must equal `"1.0.0"`. - In production mode (default), `mastery_report_sha256` must be non-empty, the companion report must exist, its `report_sha256` must match, and its self-seal must verify via `formation.hashing.verify_seal`. ## Loader contract ```python from packs.safety.loader import load_safety_pack, SafetyPackError, DEFAULT_SAFETY_PACK pack = load_safety_pack( pack_id=DEFAULT_SAFETY_PACK, # default — callers should rarely pass anything else search_paths=None, # default: ["./packs/safety"] require_ratified=True, # production default ) ``` Returns a `SafetyPack` (frozen dataclass) with fields `pack_id`, `version`, `description`, `boundary_ids` (frozenset), `boundary_descriptions` (dict), `mastery_report_sha256`, `ratified`. `SafetyPackError` inherits from `RuntimeError`, not `ValueError`. Missing safety pack is a fail-closed runtime condition, not a recoverable input error. Do not catch and continue. ### Development override ```bash CORE_ALLOW_UNRATIFIED_SAFETY=1 python -m core.cli chat ``` Bypasses **only** the seal-verification check. Missing file / empty boundaries / malformed JSON still fail closed. Use only while authoring or editing the safety pack; never set in production. ## Composition rule At `ChatRuntime` startup: ```python identity_manifold = load_identity_manifold(config.identity_pack or DEFAULT_IDENTITY_PACK) safety_pack = load_safety_pack() # fail-closed final_manifold = IdentityManifold( value_axes = identity_manifold.value_axes, boundary_ids = identity_manifold.boundary_ids | safety_pack.boundary_ids, alignment_threshold = identity_manifold.alignment_threshold, surface_preferences = identity_manifold.surface_preferences, ) ``` Safety contributes boundaries only. Identity contributes axes, threshold, surface preferences, and may add further boundaries. The runtime exposes the loaded safety pack as `ChatRuntime.safety_pack` for audit. ## Authoring a new safety pack A safety pack is unique to a deployment. The shipping default is `core_safety_axes_v1`; downstream deployments may author their own stricter pack and place it at `packs/safety/.json`. 1. Author the pack JSON. List the boundary ids your deployment requires; supply descriptions explaining each. 2. Run `python scripts/ratify_safety_pack.py` (idempotent). Produces the companion `.mastery_report.json` and embeds the SHA in the pack. 3. **Test it under fail-closed semantics.** Run `python -m pytest tests/test_safety_pack.py` and verify all 15 tests pass. 4. **Commit both files** (`.json` and `.mastery_report.json`) atomically. ### Anti-patterns - **Don't catch `SafetyPackError`.** A missing safety pack should crash the runtime, not silently degrade. The exception class deliberately doesn't inherit from `ValueError`. - **Don't carry value axes in a safety pack.** Safety boundaries are not directional preferences. If you find yourself wanting axes, you want an identity pack. - **Don't make boundary text user-facing without curation.** `boundary_descriptions` is for audit and operator visibility, not end-user prose. - **Don't ship multiple safety packs.** The design is "exactly one shipping safety pack per CORE installation." Per-tenant safety packs are an architectural change requiring a future ADR. ## Versioning policy | Change | Version bump | |---|---| | Description text edits | Patch (`v1.0.0` → `v1.0.1`) | | Adding a boundary | Minor (`v1.0.0` → `v1.1.0`) | | Removing a boundary | **Major + new ADR justifying the removal** (`core_safety_axes_v2`) | | Schema format change | Major + new `schema_version` | A new major version means a new `pack_id`. The old pack remains in the repo for replay and audit; the runtime loads whichever pack id is shipped (currently hardcoded in `packs.safety.loader.DEFAULT_SAFETY_PACK`). ## SafetyCheck — structural surface (ADR-0032) A centralized, observational surface for evaluating safety boundaries at runtime, parallel in shape to `IdentityCheck`. Produces a `SafetyVerdict`; does not refuse. Wiring violations into refusal paths is a future ADR. ```python from packs.safety import SafetyCheck, SafetyContext check = SafetyCheck() # ships with default predicates for the five v1 boundaries ctx = SafetyContext( field_state=current_field_state, cited_source_shas=frozenset({...}), allowed_source_shas=frozenset({...}), last_refusal_was_typed=True, identity_manifold_hash_before=before_hash, identity_manifold_hash_after=after_hash, ) verdict = check.check(ctx, safety_pack) # verdict.upheld: bool, verdict.violated_boundaries: frozenset[str] # verdict.results: tuple of per-boundary SafetyCheckResult ``` Every field on `SafetyContext` is optional. Predicates over fields the caller didn't populate default to `upheld=True, runtime_checkable=False` — absence of evidence is not evidence of violation. `ChatRuntime` exposes a pre-constructed instance as `runtime.safety_check`; the turn loop does not auto-invoke it at v1. ### Default predicates per v1 boundary | Boundary | Runtime-checkable? | What it checks | |---|---|---| | `preserve_versor_closure` | Yes | `field_state.versor_condition < 1.0e-6` | | `no_fabricated_source` | Yes (when allowlist supplied) | `cited_source_shas ⊆ allowed_source_shas` | | `no_silent_correction` | Yes | `last_refusal_was_typed` flag | | `no_identity_override` | Yes (when both hashes supplied) | identity-manifold hash before == after | | `no_hot_path_repair` | **No** | code-path boundary; enforced by static analysis + code review | The `no_hot_path_repair` predicate reports `runtime_checkable=False` and `upheld=True` honestly. A predicate that silently reported `upheld=True` would be a small lie — the surface acknowledges what it cannot judge. ### Custom predicates ```python check = SafetyCheck() check.register("my_boundary_id", my_predicate) ``` Unknown boundaries (declared in the pack but no predicate registered) default to `upheld=True, runtime_checkable=False, reason="no predicate registered for boundary"`. Doesn't crash; surfaces in audit. ## Known limits 1. ~~**No `SafetyCheck` parallel to `IdentityCheck`.**~~ **Closed by [ADR-0032](adr/ADR-0032-safety-check-surface.md) (2026-05-17).** See §SafetyCheck above. v1 is observational; turn-loop auto-invocation and refusal wiring are future ADRs. 2. **No per-tenant safety packs.** Multi-tenant CORE deployments share one safety pack. 3. **No human-in-the-loop ratification step.** Operational discipline lives in PR review, not the code. 4. **English-only boundary descriptions** at v1. ## Cross-reference index - Pack format spec: this doc §"Pack format (v1)". - Loader contract: this doc §"Loader contract". - Decision record: [ADR-0029](adr/ADR-0029-safety-packs.md). - Identity pack composition: [`identity_packs.md`](identity_packs.md). - Trust-boundary doctrine: [`specs/runtime_contracts.md`](specs/runtime_contracts.md), AGENTS.md "Security and trust boundaries". - The formation template used for ratification: `formation/templates/identity_anchor.py`.