core/docs/ethics_packs.md
Shay db5bc028f9 feat(adr-0034): EthicsCheck — structural surface parallel to SafetyCheck
Completes the predicate-surface layer for ethics packs, sibling to
ADR-0032's SafetyCheck.  Same registry-of-predicates shape; same
observational discipline; same honest reporting of runtime-checkable=False
for structural commitments that cannot be evaluated from per-turn evidence.

Five default predicates for the v1 commitments:

  acknowledge_uncertainty           — alignment < threshold ⇒ requires hedge
  defer_high_stakes_to_human_review — high_stakes ⇒ requires recommend_review
  disclose_limitations              — ungrounded ⇒ requires disclosure marker
  no_manipulation                   — structural; runtime_checkable=False
  respect_user_autonomy             — prescriptive ⇒ requires ≥2 options surfaced

`no_manipulation` is the ethics-side analogue of `no_hot_path_repair`
in SafetyCheck — an aggregate property enforced by realizer design and
review, not a per-turn metric.  Honest reporting rather than a silent
upheld pass.

ChatRuntime exposes `runtime.ethics_check`; turn loop does not
auto-invoke.  Refusal / re-articulation wiring is a future ADR.

Test coverage: 27 new tests; combined pack-layer surface suite
(identity + safety + ethics, loaders + checks) is now 108 tests, all
green.  Cognition (121), teaching (17), runtime (19), smoke (67)
unaffected.
2026-05-17 20:46:34 -07:00

227 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Ethics Packs — Reference
**Status:** Operational reference. Update when pack format, loader contract, or composition rules change.
**Last updated:** 2026-05-17
**Companion docs:** [`decisions/ADR-0033-ethics-packs.md`](decisions/ADR-0033-ethics-packs.md), [`identity_packs.md`](identity_packs.md), [`safety_packs.md`](safety_packs.md)
## What an ethics pack is
An ethics pack carries the **propositional commitments** a deployment pledges in its domain. Where identity packs encode *who* CORE is and safety packs encode *what CORE will never do*, ethics packs encode *what this deployment commits to in its domain* — informed consent in medical, attorney-client privilege in legal, fiduciary disclosure in financial, etc.
At runtime composition, `commitment_ids` are unioned into `IdentityManifold.boundary_ids` alongside identity and safety contributions:
```
manifold.boundary_ids = identity.boundary_ids safety.boundary_ids ethics.commitment_ids
```
The composition is monotone: every layer adds; none can remove.
## How ethics packs differ from identity and safety
| Property | Identity | Safety | Ethics |
|---|---|---|---|
| Swappable at runtime | Yes (`--identity X`) | **No** | Yes (`ethics_pack="..."`) |
| Multiple packs available | Yes | **Exactly one** | Yes |
| Failure to load requested pack | Fall back to default | **Fail-closed; refuses startup** | Fall back to default |
| Schema field | `value_axes` | `boundary_ids` | `commitment_ids` |
| Shape | Geometric (directions) | Propositional (red lines) | Propositional (pledges) |
| Directory | `packs/identity/` | `packs/safety/` | `packs/ethics/` |
| Exception class | `IdentityPackError` (ValueError) | `SafetyPackError` (RuntimeError) | `EthicsPackError` (ValueError) |
Ethics packs follow identity-pack semantics (swappable, falls back) rather than safety-pack semantics (fail-closed). Safety is the universal floor; ethics is deployment configuration above it.
## Shipping ethics pack (v1)
| Pack id | Domain | Description | Ratified |
|---|---|---|---|
| `default_general_ethics_v1` | general | Five propositional commitments for general deployments. | `81fc9b61c828fdd4926ac9eb212883ffd72c032a0ddb6a4b8d988783c98ae98d` |
### Default commitments
| Commitment id | Pledge |
|---|---|
| `acknowledge_uncertainty` | Surface confidence rather than projecting false certainty. |
| `defer_high_stakes_to_human_review` | Flag irreversible/high-stakes decisions for human review. |
| `disclose_limitations` | Say plainly when a topic exceeds grounded knowledge or appropriate scope. |
| `no_manipulation` | Persuade via reasoning, not exploitation of cognitive biases or social pressure. |
| `respect_user_autonomy` | Surface options and tradeoffs; do not prescribe where reasonable people may differ. |
## Pack format (v1)
```json
{
"pack_id": "default_general_ethics_v1",
"version": "1.0.0",
"description": "...",
"schema_version": "1.0.0",
"domain": "general",
"mastery_report_sha256": "...",
"commitment_ids": [
"acknowledge_uncertainty",
"..."
],
"commitment_descriptions": {
"acknowledge_uncertainty": "...",
"..."
}
}
```
### Field semantics
| Field | Required | Meaning |
|---|---|---|
| `pack_id` | yes | Pack identifier. Convention: `<slug>_v<major>`. |
| `version` | yes | Semver. |
| `description` | yes | Human-facing one-liner. |
| `schema_version` | yes | Currently `"1.0.0"`. |
| `domain` | yes | One of `general`, `medical`, `legal`, `financial`, `robotics`, `custom`. Audit-only at v1. |
| `mastery_report_sha256` | yes (production) | SHA of the companion `<pack_id>.mastery_report.json`. |
| `commitment_ids` | yes | Non-empty list of unique commitment identifier strings. |
| `commitment_descriptions` | yes | Dict mapping each `commitment_id` to a human-readable pledge. |
### Loader bounds (enforced)
- `commitment_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"`.
- `domain` must be in the allowed set.
- In production mode, `mastery_report_sha256` must be non-empty, the companion report must exist, its `report_sha256` must match, its self-seal must verify via `formation.hashing.verify_seal`, and `ratified` must be `True`.
## Loader contract
```python
from packs.ethics.loader import load_ethics_pack, EthicsPackError, DEFAULT_ETHICS_PACK
pack = load_ethics_pack(
pack_id=DEFAULT_ETHICS_PACK,
search_paths=None,
require_ratified=True,
)
```
Returns an `EthicsPack` (frozen dataclass) with fields `pack_id`, `version`, `description`, `domain`, `commitment_ids` (frozenset), `commitment_descriptions` (dict), `mastery_report_sha256`, `ratified`.
`EthicsPackError` inherits from `ValueError`. A missing requested pack is recoverable — `ChatRuntime` falls back to `DEFAULT_ETHICS_PACK`. Only when both the requested pack and the default are unloadable does the runtime refuse to start.
### Development override
```bash
CORE_ALLOW_UNRATIFIED_ETHICS=1 python -m core.cli chat
```
Bypasses **only** the seal-verification check. Missing files / empty commitments / malformed JSON still fail.
## Composition at runtime
`ChatRuntime.__init__` after ADR-0033:
```python
identity_manifold = load_identity_manifold(config.identity_pack or DEFAULT_IDENTITY_PACK)
safety_pack = load_safety_pack() # fail-closed
try:
ethics_pack = load_ethics_pack(config.ethics_pack or DEFAULT_ETHICS_PACK)
except EthicsPackError:
if requested == DEFAULT_ETHICS_PACK:
raise
ethics_pack = load_ethics_pack(DEFAULT_ETHICS_PACK) # fallback
final_boundary_ids = (
identity_manifold.boundary_ids
| safety_pack.boundary_ids
| ethics_pack.commitment_ids
)
```
`ChatRuntime` exposes `runtime.ethics_pack` and `runtime.ethics_pack_id` for audit.
## Authoring a deployment ethics pack
1. Author the pack JSON. Pick a `domain`; list `commitment_ids` your deployment pledges; supply descriptions.
2. Place at `packs/ethics/<pack_id>.json`.
3. Run `python scripts/ratify_ethics_pack.py` (idempotent — produces the companion `.mastery_report.json`).
4. Test under production semantics: `python -m pytest tests/test_ethics_packs.py`.
5. Commit both files (`<pack_id>.json` and `<pack_id>.mastery_report.json`) atomically.
6. Select the pack at runtime: `RuntimeConfig(ethics_pack="<pack_id>")` or a CLI flag (future ADR).
### Anti-patterns
- **Don't use an ethics pack to encode safety boundaries.** Universal red lines belong in the safety pack. Ethics is for *deployment-specific* commitments.
- **Don't use an ethics pack to encode identity.** Value axes and directional preferences belong in the identity pack.
- **Don't omit the general defaults in a domain pack.** A medical/legal/financial pack should *add* to the five general commitments, not replace them. v1 has no `extends` mechanism — duplicate the general commitments and add the domain-specific ones.
- **Don't gate runtime behavior on `domain` at v1.** It's audit-only.
## Versioning policy
| Change | Version bump |
|---|---|
| Description text edits | Patch (`v1.0.0` → `v1.0.1`) |
| Adding a commitment | Minor (`v1.0.0` → `v1.1.0`) |
| Removing a commitment | Major + new pack ID (`<slug>_v2`) |
| Schema format change | Major + new `schema_version` |
## EthicsCheck — structural surface (ADR-0034)
A centralized, observational surface for evaluating commitments at runtime, parallel in shape to `SafetyCheck` (ADR-0032). Produces an `EthicsVerdict`; does not refuse. Wiring violations into refusal / re-articulation paths is a future ADR.
```python
from packs.ethics import EthicsCheck, EthicsContext
check = EthicsCheck() # ships with default predicates for the five v1 commitments
ctx = EthicsContext(
alignment_score=score,
hedge_emitted=hedged,
high_stakes_topic=is_high_stakes,
recommended_human_review=review_recommended,
grounded_in_evidence=grounded,
disclosure_emitted=disclosed,
prescribed_single_answer=prescriptive,
presented_options_count=n_options,
)
verdict = check.check(ctx, ethics_pack)
# verdict.upheld: bool, verdict.violated_commitments: frozenset[str]
# verdict.results: tuple of per-commitment EthicsCheckResult
```
Every `EthicsContext` field is `None`-default. Predicates over fields the caller didn't populate return `upheld=True, runtime_checkable=False` — absence of evidence is not evidence of pledge violation. `ChatRuntime` exposes a pre-constructed instance as `runtime.ethics_check`; the turn loop does not auto-invoke it at v1.
### Default predicates per v1 commitment
| Commitment | Runtime-checkable? | What it checks |
|---|---|---|
| `acknowledge_uncertainty` | Yes (when supplied) | `alignment < hedge_threshold_soft` requires `hedge_emitted=True` |
| `defer_high_stakes_to_human_review` | Yes (when supplied) | `high_stakes_topic=True` requires `recommended_human_review=True` |
| `disclose_limitations` | Yes (when supplied) | `grounded_in_evidence=False` requires `disclosure_emitted=True` |
| `no_manipulation` | **No** | aggregate property; enforced by realizer design + review |
| `respect_user_autonomy` | Yes (when supplied) | `prescribed_single_answer=True` requires `presented_options_count >= 2` |
`no_manipulation` is the structural analogue of `no_hot_path_repair` in SafetyCheck. Honest `runtime_checkable=False` rather than a silent pass.
### Custom predicates
```python
check = EthicsCheck()
check.register("my_domain_commitment", my_predicate)
```
Unknown commitments default to `upheld=True, runtime_checkable=False, reason="no predicate registered for commitment"`. Surfaces in audit; doesn't crash.
## Known limits / future ADRs
1. ~~**No `EthicsCheck` predicate surface** parallel to `SafetyCheck`.~~ **Closed by [ADR-0034](decisions/ADR-0034-ethics-check-surface.md) (2026-05-17).** v1 is observational; turn-loop auto-invocation and refusal wiring are future ADRs.
2. **No deliberation surface.** Ethics-as-deliberation (multi-candidate trajectory selection, typed-tradeoff evaluation) needs multi-trajectory articulation first.
3. **No pack inheritance.** Domain packs must declare full commitment lists; no `extends` mechanism.
4. **No domain-driven behavior.** `domain` is audit-only.
5. **No CLI flag** for `--ethics <pack_id>` yet — `RuntimeConfig(ethics_pack=...)` only. Future ADR.
6. **English-only commitment descriptions** at v1.
7. **No cross-surface verdict aggregation.** Identity / Safety / Ethics produce three independent verdicts; no unified bundle type. Future convenience ADR if call sites grow.
## Cross-reference index
- Pack format spec: this doc §"Pack format (v1)".
- Loader contract: this doc §"Loader contract".
- Decision records: [ADR-0033](decisions/ADR-0033-ethics-packs.md), [ADR-0034](decisions/ADR-0034-ethics-check-surface.md).
- Identity composition: [`identity_packs.md`](identity_packs.md).
- Safety composition: [`safety_packs.md`](safety_packs.md).
- Safety predicate surface: [`safety_packs.md`](safety_packs.md) §SafetyCheck.
- Formation template used for ratification: `formation/templates/identity_anchor.py`.