# Arifos Kernel Zen Audit

> Pattern for auditing the arifOS kernel when multiple MCP tools share the same skeleton but disagree on identity/verdict/affordance — proven

- Skill: `ariffazil/arifos-kernel-zen-audit` (Agent Skill, multi-file: 17 files)
- Install (CLI): `npx skillmds@latest add ariffazil/arifos-kernel-zen-audit`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ariffazil/arifos-kernel-zen-audit/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: ariffazil (https://skillmd.com/u/ariffazil)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/ariffazil/arifos-kernel-zen-audit

---


# arifOS Kernel Zen Audit

## The Tell

When multiple MCP tool responses share the same skeleton — `affordance_contract`, `full_affordance`, `nine_signal{delta,psi,omega,overall}`, `sesat_event`, `_wrapper_degradation`, `metacognition`, `constitutional_check`, `decision_thresholds` — and the `decision_thresholds` block is byte-identical verbatim across every tool, **identity, verdict, and affordance have been re-implemented independently per wrapper**. That's the architectural gap, not eleven bugs.

## Fiqh Audit Grid

Before writing the report, classify each finding:

| Class | Meaning | Action |
|---|---|---|
| **WAJIB** | Constitutional, non-negotiable, currently broken | Must fix in this pass |
| **HARAM** | Forbidden pattern currently happening | Stop, don't just "fix later" |
| **HARUS** | Permissible, fine, don't fuss | Note so reviewer doesn't waste cycles |
| **MAKRUH** | Discouraged, bad hygiene | Add to cleanup backlog |
| **SUNAT** | Recommended practice already present | Standardize, extend |

Common WAJIB candidates:
- One verdict, one source of truth
- Identity/authority unbroken from `arif_init` through all downstream calls
- Irreversible actions (seal, forge) → SOVEREIGN + 888_HOLD (verify this holds)

Common HARAM:
- Bare unstructured strings on hard-block paths (`arif_judge`/`arif_forge`/`arif_seal`)
- Silent authority downgrades (FULL→OBSERVE_ONLY→LOW, unlabeled)
- **Authority propagation drift** — init sees SOVEREIGN, judge/forge sees MEDIUM (P0, council-confirmed 2026-07-15)
- Orphaned inner tools firing without registry entry
- 2+ verdict fields disagreeing in one payload
- Transport `status="SEAL"` — overloads constitutional verdict
- Confidence trajectory ascending in degraded mode (P1 template bypass)
- `actor: null` when `actor_id` is populated in the same envelope
- Audit spine fields (`call_hash`, `invocation_count`) null on non-init verbs

## Scoring Template

Score 6 dimensions /10 each, /60 total:

| Dim | Current | After minimal fix | After full fix |
|---|---|---|---|
| Identity/session persistence | ? | ? | ? |
| Single source of truth (verdict) | ? | ? | ? |
| Registry integrity (no orphans/alias sprawl) | ? | ? | ? |
| Failure honesty (sesat_event coverage) | ? | ? | ? |
| Irreversible-action gating | ? | ? | ? |
| Payload signal-to-noise | ? | ? | ? |
| **Composite** | ?/60 | ?/60 | ?/60 |

**Always show two projected columns.** Minimal fix vs full fix. Tiny fixes often move one axis but barely touch the others — say so plainly.

## Zen Output Contract

When the audit is for Arif:
- ≤3 sentences
- The zen (one-line koan) first
- Fiqh grid second (table, terse)
- Scorecard third (with both projections)
- "DITEMPA BUKAN DIBERI applies to the kernel too" only when earned, not as ceremonial footer

## Pitfalls

- Don't confuse "already pretty good" with "fixed." 26/60 is a failure at 60% scale.
- The skeleton-similarity tell is the strongest signal. If `decision_thresholds` is byte-identical verbatim across tools, the audit is already half-done.
- Three projections (current / minimal-fix / full-fix) is honesty — don't collapse to two, that hides scope.
- Don't recommend closing the audit after minimal fix unless the full-fix gap is genuinely zero.
- **Audit-receipt sealing at HOLD is correct.** When `mcp__arifos__arif_seal` returns 888_HOLD because you have MEDIUM authority (not SOVEREIGN), that is the kernel refusing to grant a SEAL you have no right to claim. Don't work around it. Land the audit-receipt at HOLD, document in vault, move on. F1 honesty > ceremonial SEAL.
- **Authority plane cannot be tested architecturally.** arifOS kernel exposes no write endpoint (404 on `/seal`, `/seal_chain`, `/leases`, `/judge`). Behavioral obedience is confirmed (Hermes does not write without seal), but the gate itself cannot be probed. Add a `/kernel/authority-probe` read-only endpoint if you need a testable gate — currently the Authority plane scores 60/100 (untested gate).
- **Don't raw-append to `seal_chain.jsonl`.** The JS canonical writer uses `|`-joined material; Python `+`-concat produces a line the JS verifier rejects. Always go through `node seal_chain.js write <JSON>`. See `references/seal-chain-write-gotchas-2026-07-09.md`.
- **Always run `node seal_chain.js verify` before any chain write.** As of 2026-07-09, verify returns broken-at-line-1 — a pre-existing anomaly predating this audit.
- **SEAL is a verdict, not a status.** Transport status must be "OK", never "SEAL". If tools return `status="SEAL"`, normalize at ingestion (`tools.py` line ~3458). The sesat drift detector keys off verdict, not status. See `references/bangang-fixes-seal-overload-audit-chain-2026-07-11.md`.
- **Audit spine fields must be generated by the envelope wrapper, not by individual tools.** `call_hash`, `trace_id`, `invocation_count`, `called_from_kernel` — if the tool didn't provide them, the wrapper computes them. Three-stage fallback: (1) read from out/result, (2) compute from tool+payload+timestamp, (3) hard default. Never leave null.
- **Confidence trajectory must be flat (single-point) in degraded mode.** When LLM synthesis is bypassed (P1 degraded), the ascending trajectory [0.5, 0.72, 0.85] is fabricated. Use `[llm_confidence]` and add `confidence_provenance: "COMPUTED_NOT_OBSERVED"` to the result dict. Mark steps with `axiom_used="P1_TEMPLATE_DEGRADED"`.
- **Actor identity: `actor` and `actor_id` must agree.** If the tool doesn't return an actor dict, the envelope synthesizes `{"actor_id": resolved_actor_id, "source": "envelope_derived"}` from the session store. Never leave `actor: null` when `actor_id` is populated.

## L12 INJECTION Floor — Mandatory Audit Dimension

When auditing the arifOS kernel, **L12 INJECTION is mandatory**. The floor score lives at `runtime_floors.L12` in `/health`. A score below 0.85 (comparator `"<"`) means residual risk is present — the specific gap paths must be identified.

### Probe Order

1. **Live endpoint:** `curl -s localhost:8088/health` → `runtime_floors.L12`
2. **Tool schemas:** `curl -s localhost:8088/tools` — enumerate the public surface
3. **Input surface trace** — trace every tool's text/dict parameter paths for L12 sanitisation coverage:
   - `arif_observe(mode=fetch, url=...)` — fetched content bypasses L12 input scan (PRIMARY GAP)
   - `arif_compose(message=...)` — free-text goes direct to `_synthesize`, not through `check_laws`
   - `arif_init(context=...)` — nested dict, `sanitize_dict` not applied
   - `arif_think(query=...)` — exact-string-match bug: tool-name list check can silently miss variants
4. **`law.py` `check_laws()`:** scans String params for injection keywords but does NOT call `sanitize_dict` — raw params continue downstream unchanged
5. **L1 regex gaps in `tools.py`:** misses `"[system prompt]"`, `/s `, `"```system"`, unicode-prefixed emoji+instruction, `">> "` redirection prefix
6. **`data_governance.py` `sanitize_dict`/`detect_injection`:** defined and tested but only wired to asset-data path — never called on raw MCP tool parameters at entry
7. **`fiqh_of_floors.py`:** confirmed `Status: STAGED` — `injection_score` in `ActionContext` is never populated from live measurement

### Primary Architecture Signal

```
MCP Request → PNS·SHIELD (prompt_armor.scan) → orchestrator outer ring
                                              ↓
            check_laws (law.py) → tools.py L12 scan (params only)
                                              ↓
            arif_observe → url fetch → NO L12 scan ← PRIMARY GAP
                                              ↓
            evidence layer → 888 collapse
                                              ↓
            witness_packet._scan_injection (output only — too late)
```

### Fix Rule

`sanitize_dict()` must be wired to tool parameter ingestion **before** `check_laws`. The gap is at the entry point, not at the output witness. Full findings: `references/F12-injection-floor-diagnosis-2026-07-10.md`

## Authority Propagation Drift (P0 — Council-Discovered 2026-07-15)

**The most dangerous kernel finding.** External council confirmed: `arif_init` recognizes ARIF as SOVEREIGN/FULL, but `arif_judge` and `arif_forge` receive MEDIUM authority. F13 cannot reliably exercise sovereign authority through the system.

### The Unbroken Chain

```
connector ingress → arif_init → session capability token → response context → route → judge → forge → receipt
```

Every link must carry the same identity and authority band. If init says SOVEREIGN but judge says MEDIUM, the chain is broken.

### Detection

Call arif_init with sovereign credentials → immediately call arif_judge → compare authority bands. If mismatch → drift confirmed.

### Fiqh Classification

**WAJIB** — constitutional, non-negotiable, currently broken. Without authority propagation, F13 veto is decorative.

### Fix Rule

Session capability token issued at init must be the same token consumed by judge and forge — not re-resolved at each step.

## T3a BOOT Gate Demotion (2026-07-24)

**Variant of authority propagation drift** — the BOOT gate demotes FULL→OBSERVE_ONLY even when the Ed25519 signature verified and the kernel recognizes the actor as SOVEREIGN with FULL runtime grant.

### The Stack

```
arif_init → identity_band_authority() → FULL
  → _apply_boot_gate() → boot_state_for_authority_grade()
  → verify_boot_attestation() → Q1-Q7
  → boot_state=PARTIAL → passes=False → demote to OBSERVE_ONLY
```

### Q1-Q7 Boot Attestation Checks

| # | Check | Method | Expect during init |
|---|-------|--------|--------------------|
| Q1 | Identity bind | session_identity_service | PARTIAL (no session_id yet) |
| Q2 | Constitution loaded | kernel_health_constitution | YES |
| Q3 | Session ignite | session_store_liveness | PARTIAL (session being minted) |
| Q4 | Trinity33 loaded | atlas333_substrate | YES |
| Q5 | Sovereign recognized | identity_toml_f13 | PARTIAL (name-match only without crypto) |
| Q6 | Refusal surface | refusal_list_module | YES |
| Q7 | RSI path clear | rsi_session_endpoint | YES |

**Q1 and Q3 are inherently PARTIAL during init** — they need a session_id that hasn't been created yet. The `boot_state_for_authority_grade()` function requires `boot_state == "OK"` (all YES), so any PARTIAL causes demotion.

### Fixes (Two Required)

**1. identity.toml (Q5 name-match fix)**
`/opt/arifos/identity.toml` must contain:
```toml
owner = "Muhammad Arif bin Fazil"
authority = "F13_SOVEREIGN"
```
The boot check at `boot_attestation.py:350` checks:
```python
name_match = ("Muhammad Arif bin Fazil" in combined or "Arif" in toml_text) and (
    "F13" in toml_text or "sovereign" in toml_text.lower()
)
```
Without the owner field containing "Arif", Q5 returns NO → boot_state=FAIL.

**2. `boot_state_for_authority_grade` passes condition**
In `/opt/arifos/app/arifosmcp/runtime/boot_attestation.py` line 530:
```python
# Change from:
"passes": parsed["summary"]["boot_state"] == "OK",
# To:
"passes": parsed["summary"]["boot_state"] in ("OK", "PARTIAL"),
```

### Auto-Sign Path Limitation

When `arif_init` is called with ONLY `actor_id` (no nonce/signature), the auto-sign path (`elif actor_id:` at session.py:1340) fires:
1. Issues a fresh challenge nonce
2. Auto-signs with local Ed25519 key
3. Sets `_light_band = "FULL"` (session.py:1373)
4. `_project_light` receives `authority_override="FULL"`
5. **But** `_apply_boot_gate` sees PARTIAL boot_state → demotes to OBSERVE_ONLY

The SCT token is issued at OBSERVE_ONLY even though the kernel's internal `runtime_grant` says FULL.

### Detection

When `arif_init` returns `authority_scope="OBSERVE_ONLY"` but also shows `runtime_grant.level="FULL"` and `cryptographically_verified=true`, the BOOT gate is blocking. Check:
```bash
journalctl -u arifos | grep "BOOT gate demoted"
```
Expected: `T3a Item 3: BOOT gate demoted runtime_band=FULL -> OBSERVE_ONLY (boot_state=PARTIAL|FAIL, yes=N, no=M)`

### Fix Rule

Always patch TWO locations for arifOS runtime changes:
1. Source: `/root/arifOS/arifosmcp/...` (git-tracked)
2. Runtime: `/opt/arifos/app/arifosmcp/...` (running deployment)
Verify with: `grep "passes" /opt/arifos/app/arifosmcp/runtime/boot_attestation.py`

### Pitfall: sidestepping the auto-sign path

When passing explicit `nonce` + `actor_signature` through the MCP `arif_init` tool, those parameters correctly reach session.py:1281 (`if actor_id and nonce and _sig:`). But the nonce is consumed by the first verification attempt. A second call with the same nonce triggers `challenge_replayed`. Always generate a fresh nonce for each init attempt, or let the auto-sign path handle it (no nonce/signature params).

## Schema/Runtime Dispatch Drift (P0 — Council-Discovered 2026-07-15)

Published MCP inputSchema rejects modes that the runtime actually supports (e.g., arif_observe rejects skill_discover but runtime advertises it). The schema, dispatcher, and capability graph must be generated from one canonical source — never manually copied.

### Detection

Compare MCP inputSchema (from tools/list) vs actual dispatch enum in runtime code. If they disagree → drift confirmed.

## F13 Multi-Sovereign Collision Audit (PATH 3 — 2026-07-25)

**Structural invisible to one operator.** This bug appears only with two distinct sovereign identities. The falsification spec calls for two Ed25519 keypairs, two actor_ids, separate crypto — not one operator holding two keys.

### What This Tests

From EXTERNAL_FALSIFICATION_SPEC.md §PATH 3 (Tests 3.1–3.4):

| # | Test | Expected |
|---|------|----------|
| 3.1 | Concurrent VOID+VOID on same action_id | One coherent terminal state |
| 3.2 | SEAL vs VOID on same action_id | Deterministic resolution by documented rule |
| 3.3 | Repeat 3.2 × 20 with random submission order | Zero variance |
| 3.4 | Sovereign B adjudicates A's session | Rejected — ownership enforced |

### Four Critical Checks

Before the live tests, probe the source for these four dimensions:

**1. Conflict resolver wiring** — `/root/arifOS/arifosmcp/core/conflict_resolver.py`
- Does `resolve_conflict()` exist? YES — has VOID-dominates ordering and organ hierarchy.
- Is it CALLED from the judge path (`_arif_judge_deliberate` in tools.py)? Check: search for `resolve_conflict` or `ConflictEnvelope` in tools.py.
- If the resolver is never invoked, the "rule" is dead code. All verdicts are stored independently — last-writer-wins at the dict level.
- The `ConflictEnvelope` uses `organ_a`/`organ_b` (GEOX/WEALTH/arifOS/HUMAN), NOT sovereign identity. Two F13 sovereigns both map to "human" — Rule 3 (same organ, more restrictive wins) would apply WERE the resolver wired.

**2. Session ownership enforcement** — `/root/arifOS/arifosmcp/runtime/session.py` lines 163, 288
- `_ACTOR_SESSION_MAP: dict[str, str]` maps `session_id → actor_id`. Does ANY downstream tool verify this mapping?
- In `_arif_judge_deliberate` (tools.py:~16191): search for `_ACTOR_SESSION_MAP.get(session_id)` — if absent, no ownership gate.
- The `_wrap_handler` wrapper (tools.py:~23291) only **fills in** missing actor_id from session, never **enforces** a match.
- Sovereign identity map: `_SOVEREIGN_IDENTITY_MAP` (session.py:280) contains only `"ariffazil"`. The localhost auto-sign path (session.py:1364) accepts `"arif"`, `"888"`, `"ariffazil"` — drift between the two maps.

**3. VAULT999 collision detection** — `/root/arifOS/arifosmcp/tools/vault.py`
- Is `outcomes.jsonl` purely append-only? Check: `_VAULT_LEDGER: list[dict]` (tools.py:5810) — yes, pure append.
- Does the vault check for existing entries with the same `action_id` before appending? Search for any `action_id` dedup or collision check in vault.py.
- Can two entries for the same action (one SEAL, one VOID) coexist? If no reconciliation, YES — both persist independently.

**4. Sovereign identity consistency** — `/root/arifOS/arifosmcp/tools/session.py` + runtime/session.py
- Three identity sources must agree:
  - `session.py:1364` — `_actor_lower in ("arif", "888", "ariffazil")` (auto-sign gate)
  - `session.py:1659` — `_SOVEREIGN_MAP` for mode="challenge"
  - `runtime/session.py:280` — `_SOVEREIGN_IDENTITY_MAP` (persistent store)
- If these drift, sovereign A can init but sovereign B cannot, or vice versa.
- The `_ED25519_EXEMPT_SYSTEM_ACTORS` map may also have its own list — check `session_auth.py`.

### Source Code Reference Map

| File | What to check | Lines |
|------|--------------|-------|
| `tools/session.py` | Auto-identity string check, sovereign map | 1364, 1659-1663 |
| `runtime/session.py` | `_SOVEREIGN_IDENTITY_MAP`, `_ACTOR_SESSION_MAP` | 280-282, 163, 288 |
| `runtime/tools.py` | `_arif_judge_deliberate`, `_wrap_handler` session fill (not enforce) | 16191+, 23291-23297 |
| `runtime/tools.py` | `_VAULT_LEDGER`, `_JUDGE_STATE_REGISTRY` | 5810, 5818 |
| `core/conflict_resolver.py` | Verdict ranks, organ hierarchy, `resolve_conflict` | 30-288 |
| `tools/vault.py` | Appending logic, collision check | 401+, search for `action_id` dedup |

### Scoring Template for Multi-Sovereign Dimension

| Dimension | Current | After minimal fix | After full fix |
|-----------|---------|-------------------|----------------|
| Deterministic cross-sovereign resolution | ?/10 | 4/10 (wire resolver) | 10/10 (atomic two-phase) |
| Session ownership enforcement | ?/10 | 6/10 (actor-match check) | 10/10 (crypto-bound SCT) |
| VAULT999 collision detection | ?/10 | 5/10 (detect+store both) | 10/10 (auto-escalate to 888_HOLD) |
| Sovereign identity consistency | ?/10 | 8/10 (unify map) | 10/10 (Ed25519 pubkey registry) |
| Irreversible-action blocking | ?/10 | 6/10 (any VOID blocks) | 10/10 (all must SEAL) |
| **Composite** | **?/50** | **29/50** | **50/50** |

### Fiqh Findings (from 2026-07-25 audit)

| Finding | Class | Reason |
|---------|-------|--------|
| No cross-sovereign collision resolution | WAJIB | Resolver exists but isn't wired into judge path |
| No session ownership enforcement in judge | WAJIB | `_ACTOR_SESSION_MAP` created but never checked |
| String-name sovereign detection | MAKRUH | Acceptable for localhost; map drift is real gap |
| VAULT999 append-only (no reconciliation) | HARUS | Append-only is correct; missing higher-level reconciliation |
| Conflict resolver exists but unwired for F13 | HARAM | Would catch SEAL vs VOID if wired; currently dead code |

### Pitfalls

- **The conflict resolver is correct — check if it's WIRED, not whether it exists.** `conflict_resolver.py` has a valid VOID-dominates hierarchy. The question is whether `_arif_judge_deliberate` calls `resolve_conflict()` before returning — it almost certainly doesn't.
- **Session ownership = two separate things.** (1) `_ACTOR_SESSION_MAP` maps session_id→actor_id (it exists). (2) Code that enforces the caller's actor_id matches the map (it doesn't exist). Report both separately.
- **Two sovereigns that map to the same organ produce the wrong resolution.** Under the conflict resolver's organ hierarchy, two F13 sovereigns both land in "human" (rank 8). Rule 3 says "more restrictive verdict wins" — VOID over SEAL. But VOID-dominates for ALL sovereigns means no single sovereign can SEAL an action another has VOIDed, even if the SEAL is the correct call. This is correct for irreversible actions but may be too restrictive for reversible ones. Call this out.
- **Localhost auto-sign path bypasses the multi-sovereign question entirely.** When `arif_init` is called from localhost with no explicit signature, the auto-sign path (session.py:1357-1416) uses the local Ed25519 key. Two distinct remote sovereigns would each need their own key — the auto-sign path only works for the VPS-local agent.
- **The string-name check is the real sovereign detection for the auto-sign path.** `actor_id.lower() in ("arif", "888", "ariffazil")` means any caller presenting `actor_id="arif"` gets auto-signed. If you provision a second sovereign with `actor_id="SOVEREIGN_B"`, the auto-sign path rejects them — they'd need explicit Ed25519.

### Reference Files

- `references/f13-multi-sovereign-collision-audit-2026-07-25.md` — Full audit report: source-by-source analysis of all 4 PATH 3 tests against the EXTERNAL_FALSIFICATION_SPEC, with specific line numbers, found/wired/unwired classification, recommended fix code, and VAULT999 collision state machine analysis.
- `references/external-falsification-spec-path3.md` — Extracted PATH 3 section of the falsification spec: the 4 tests (3.1–3.4), pass/fail criteria, and verdict rules.

## Reference Files

- `references/bangang-fixes-seal-overload-audit-chain-2026-07-11.md` — 4 kernel fixes: SEAL overload normalization, audit chain fallback, confidence theater suppression, actor identity propagation. Concrete code locations and fix patterns.
- `references/identity-verification-architecture.md` — arifOS kernel Ed25519 identity verification flow: delegation pattern, dual verification paths (crypto_auth vs governance_identity), proof dict construction, nonce format, session update pattern. **Must-read before auditing identity/authority fields.**
- `references/seal-chain-write-gotchas-2026-07-09.md` — seal-chain write autopsy, JS canonicalization rules, verify-broken-at-line-1 anomaly
- `references/F12-injection-floor-diagnosis-2026-07-10.md` — full L12 gap findings from 2026-07-10 diagnostic session
- `references/kernel-crash-recovery-2026-07-12.md` — service permissions fix when `.env` is owned by wrong user. Systemd `User=arifos` vs file ownership mismatch.
- `references/arif-bind-global-command.md` — global `arif-bind` command setup: one-word sovereign session bind from any terminal.
- `references/zen-surface-reduction-verification-2026-07-16.md` — ZEN surface reduction audit: verified vs claimed, deploy timing, VAULT999 seal verification pitfall
- `references/meta-mesa-test-charter-2026-07-12.md` — META-MESA Substrate Test Charter: 12-section test specification for proving governed causal agency. 10 hard gates, multiplicative scoring, 13-phase sequence.
- `references/drift-scar-bug-attestation-chain-2026-07-27.md` — Drift = scar vs bug framework from session with Arif + OpenCode. 6-step boot attestation pattern (PROBE→BIND→WITNESS→CLASSIFY→RECOGNIZE→ATTEST). No-drift≠Ready≠SEAL chain. Surface guard reclassification (AAA as non-MCP organ).
## Sovereign Authentication (2026-07-12)

When the kernel blocks at 888_HOLD with `actor_verified=false`, sign the nonce and call `arif_init`. See `references/sovereign-auth-procedure.md` for the full procedure, one-shot signing script, and failure mode table.

**Critical rule:** Generate nonce + sign + call in ONE shot. Never test locally first — nonce is single-use and local test consumes it (`challenge_replayed`).

## Pitfalls — Debugging Style

- **Stop probing, start fixing.** When the code path is clear and the failure mode is known, act. Don't add more diagnostic calls. "Setel. Relaks tapi tajam."
- **Read failure modes before adding complexity.** `challenge_replayed` means the crypto works but the nonce was consumed — not that verification failed. Don't add 15 more diagnostic tool calls when the error message already tells you the answer.
- **"Wire splice confirmed" ≠ "wire is the bug."** The session.py delegate was already receiving `actor_signature` as `signature`. The real bug was nonce consumption from intermediate testing. Don't assume a known-broken path is still broken without fresh evidence.
- **When the error message IS the root cause, stop investigating.** `challenge_replayed` → nonce consumed → generate fresh. `Invalid signature` → wrong payload format → try the other format. The systematic-debugging skill says "NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST" — but reading the error message IS investigation. Don't run 20 diagnostic calls when the first error told you everything.
- **Don't re-verify what already verified.** If `verify_init_identity` returns `ok=True`, don't call it again "to confirm." The second call consumes the nonce and breaks the subsequent `arif_init`. Trust the receipt.
- **Arif frustration signals are FIRST-CLASS debugging signals:** "Setel" = fix it now. "Relaks tapi tajam" = stop thrashing, be precise. "Did u even nap and check current state?" = you're citing stale state, re-probe. One-word replies = sovereign ack, act immediately. "Bangang la" after a clear diagnosis + fix proposal = **you've over-asked, execute now**.
- **Service user vs file ownership.** The systemd service runs as `User=arifos`. All files in `/opt/arifos/app/` must be readable by `arifos`. If a file is owned by `root` or `ariffazil` with mode 600, the kernel will crash on startup with `PermissionError`. Fix: `chown arifos:arifos <file> && chmod 640 <file>`. Never `chown -R root:root` — that re-creates the problem.

## RECORD vs AUTHORIZE Seal Architecture (2026-07-24)

The kernel now supports TWO seal paths. NEVER conflate them:

| Dimension | AUDIT_RECORD (SEAL_RECORD) | ACTION_AUTHORIZATION (SEAL_AUTHORIZATION) |
|-----------|---------------------------|----------------------------------------|
| reversibility_level | R2 | R4 |
| seal_purpose | RECORD | AUTHORIZE |
| authority_effect | NONE | EXECUTION_GRANT |
| ack_irreversible | false | true |
| requires_f13 | false | true |
| F13 Ed25519 | Not required | Required |
| Execution authority | None | A-FORGE execution grant |

### Canonical Chain for RECORD

```
arif_judge(action_class="AUDIT_RECORD", reversibility="R2")
→ ALLOW + SEAL_RECORD
→ cc_id + judge_state_hash emitted
→ arif_seal(ack_irreversible=false, seal_purpose="RECORD",
   cc_id=..., judge_state_hash=...)
→ VAULT999 receipt (no F13, no human)
```

### Canonical Chain for AUTHORIZE

```
1. Sign f"{actor_id}:{nonce}" — actor_id MUST match case exactly
2. arif_judge(action_class="ACTION_AUTHORIZATION", reversibility="R4",
   actor_signature=..., nonce=...)
→ ALLOW + SEAL_AUTHORIZATION + cc_id + judge_state_hash
→ arif_seal(ack_irreversible=true, seal_purpose="AUTHORIZE",
   cc_id=..., judge_state_hash=...)
→ VAULT999 + execution grant
```

### Ed25519 Verification — Payload Format

The kernel verifies `f"{actor_id}:{nonce}"`. If `actor_id="ARIF"`, sign `"ARIF:{nonce}"` (uppercase). `resolve_actor_public_key` normalizes internally — the payload must match the exact `actor_id` string.

### Production Path (in `_verify_sovereign_token`)

Bypasses `crypto_auth.verify_actor_signature` challenge gate. Does direct verification:

```python
from arifosmcp.runtime.crypto_auth import resolve_actor_public_key
pubkey = resolve_actor_public_key(actor_id)
if pubkey is not None:
    pubkey.verify(_b64.b64decode(actor_signature), f"{actor_id}:{nonce}".encode())
    return True
```

Set `ARIFOS_ALLOW_FREE_NONCE=1` in the service environment to bypass the challenge gate for dev/test.

### Action Class Policy Table

Defined in `arif_kernel_intercept.py` `_ACTION_CLASS_POLICY`:

```python
_ACTION_CLASS_POLICY = {
    "AUDIT_RECORD": {"requires_f13": False, ...},
    "EVIDENCE_ATTESTATION": {"requires_f13": False, ...},
    "VAULT_RECEIPT": {"requires_f13": False, ...},
    "ACTION_AUTHORIZATION": {"requires_f13": True, ...},
    "CONSTITUTIONAL_AMENDMENT": {"requires_f13": True, ...},
}
```

## MCP Ingress Debugging — Parameter Propagation

When `actor_signature`, `nonce`, `reversibility_level`, `action_class` don't reach the kernel through MCP, the MOST LIKELY culprit is the ingress filter pipeline:

```
MCP → _wrap_handler → _filter_kwargs_for_handler → _akal_wrap_judge → _arif_kernel_intercept_tool → _arif_kernel_intercept
```

### Filter Chain (tools.py)

1. **`_wrap_handler`** (tools.py:23274) — outermost wrapper. Calls `_filter_kwargs_for_handler` then calls handler.

2. **`_filter_kwargs_for_handler`** (tools.py:22945) — **PARAMETER STRIPPING HAPPENS HERE.**
   - Gets `accepted = set(params.keys())` from the handler's Python signature
   - Parameters NOT in `accepted` → `contract_c_audit` (silently dropped from kwargs)
   - Has `_LEGACY_PARAM_ALIASES` that RENAME params: e.g., `actor_id → actor`

3. **`_akal_wrap_judge`** (server.py:775) — wraps judge with AKAL metadata. Uses `@functools.wraps(handler)` setting `__wrapped__`.

4. **`_arif_kernel_intercept_tool`** (tools.py:22091) — the actual wrapper. Has `actor`, `actor_signature`, `nonce`, `action_class` as named params.

### Root Cause: Alias + Named Param Conflict

`_LEGACY_PARAM_ALIASES["arif_judge"] = {"actor_id": "actor"}` renames `actor_id` to `actor`. But the wrapper now has BOTH as named parameters. After renaming, `actor` is set by the alias, but `actor_id` named param stays None. The kwarg translation:

```python
if actor is None or actor == "anonymous":
    actor = actor_id or kwargs.pop("actor_id", None) or "anonymous"
```

`actor` = "ARIF" from alias → condition False → `actor` stays "ARIF". BUT if `actor` was NOT in the filter output (the alias didn't fire), then `actor` stays None → falls to "anonymous".

**Fix:** Ensure the kwarg translation fires when `actor` is `None` OR `"anonymous"`:

```python
if actor is None or actor == "anonymous":
    actor = actor_id or kwargs.pop("actor_id", None) or "anonymous"
```

### Four Deployment Locations

Ed25519/runtime patches must be applied to ALL locations:

| Location | Path |
|----------|------|
| Source tree | `/root/arifOS/arifosmcp/runtime/tools.py` |
| App deployment | `/opt/arifos/app/arifosmcp/runtime/tools.py` |
| Build artifact | `/opt/arifos/build/lib/arifosmcp/runtime/tools.py` |
| Build artifact (app) | `/opt/arifos/app/build/lib/arifosmcp/runtime/tools.py` |

The `.pth` file (`__editable__.arifos-1!2026.7.17.post4.pth`) determines which path is used. Check at runtime: `import arifosmcp.tools.arif_kernel_intercept as k; print(k.__file__)`.

### .pyc Cache Invalidation

After every source change:
```bash
rm -f /root/arifOS/arifosmcp/runtime/__pycache__/tools.cpython-313.pyc
rm -f /root/arifOS/arifosmcp/tools/__pycache__/arif_kernel_intercept.cpython-313.pyc
rm -f /root/arifOS/arifosmcp/runtime/__pycache__/crypto_auth.cpython-313.pyc
```
Verify `.pyc` is GONE (`ls` returns empty) before restarting. If `.pyc` is newer than source, Python ignores the source.

### Signature Verification Fallthrough

The `_verify_sovereign_token` function (arif_kernel_intercept.py) has THREE paths:
1. Direct Ed25519 via `resolve_actor_public_key` + `pubkey.verify` (production)
2. Free-nonce fallback: tries same without challenge (when `ARIFOS_ALLOW_FREE_NONCE=1`)
3. Sentinel comparison: env var `ARIFOS_SOVEREIGN_KEY` (dev-only, trivially bypassable)

The challenge-gated path was REMOVED because `verify_actor_signature` requires pre-issued challenges that aren't available through the public MCP surface (the `arif_challenge` tool is `internal_only`).

## Three-Gate Principle (2026-07-12)

**When the user reports a single failure mode — "kernel blocked seal ×3", "commands failing" — it is almost never one bug.** Three independent gates, each with a different root cause and fix:

| # | Symptom | Root Cause | Fix | File |
|---|---|---|---|---|
| 1 | "Ed25519 signature rejected" on nonce | Stale nonce (60s window) — mint→sign time drift | Extend `window_sec=60` → `window_sec=900` or bind nonce to session_id for session-lifetime validity | `governance_identity.py:145` (call site, not default function) |
| 2 | "arif_seal blocked: needs SOVEREIGN authority" | Agent claimed `actor_source=self_report` instead of producing signed proof. Kernel sees `kernel_verdict=UNKNOWN` | Atomic signer helper: one-shot `mint_nonce()` + `sign()` + `emit_proof()` with no intermediate drift | Signing workflow, not a single file |
| 3 | "session capped at OBSERVE_ONLY" | Fresh lease defaults to read-only — needs explicit upgrade to MUTATE-class | `forge_lease(max_action_class=EXECUTE_REVERSIBLE, ttl=1800)` before any mutation | `forge_lease` scope management |
| 4 (bonus) | Seal token silently quarantined | `seal_token_guard.py` raises `SealQuarantineError` when bare "seal" appears in payload without domain qualifier (`geological_seal` / `constitutional_SEAL` / `vault_seal`) | Always qualify seal tokens with domain prefix | `seal_token_guard.py` |

**The three-gate discipline:** When a user says "X failed three times," do not look for one bug across three logs. Look for three gates, each with its own root cause. This prevents the common misdiagnosis of treating the third attempt of gate 1 as a recurrence of gate 2.

## Post-Diagnosis Execution Reflex (2026-07-12)

**After you have diagnosed root cause(s) AND proposed surgical fix(es), execute — do not ask for confirmation.**

The correct flow:
1. Diagnose → label each gate with root cause + fix + risk
2. Present findings once (as a table, not prose)
3. **Execute without "confirm?"**

If the diagnosis was wrong, Arif will correct. If it was right, asking again wastes his time — and he will say so.

**User signals to execute immediately (not re-ask):**
- You presented a clear diagnosis with specific file locations and test plan
- User didn't object to the diagnosis or ask clarifying questions
- You find yourself typing "should I proceed?" or "want me to..." after already laying out the fix → STOP, just do it
- "Bangang la" after your proposal = you should have executed, not asked. The frustration is not at the diagnosis — it's at the unnecessary re-asking

**Boundary:** This only applies POST-diagnosis. If you haven't diagnosed yet (don't know the root cause), asking "how should I proceed?" is correct. Once you have root cause + fix, the question phase is over.

## Constitutional Cage Audit (2026-07-16)

When Arif asks to "audit the cage," "audit the kernel for self-governance," or "is the cage ready for my future self" — this is a **sovereignty-readiness audit**, not a wrapper-disagreement audit. Different pattern, same skill.

### What To Check

| # | Wall | Probe | Source of Truth |
|---|------|-------|----------------|
| 1 | **Floor enforcement** | `/health` → `floors_active`, `floors_enforcement` | `runtime_floors` dict |
| 2 | **888_HOLD enforcement** | `journalctl -u arifos` → count `KERNEL INTERCEPTOR: 888_HOLD` | Live logs |
| 3 | **Identity verification** | Check `actor_verified` in init response + `crypto_auth.py` wiring | Source + live |
| 4 | **Airlock** | Count SHADOW errors/hr in journalctl | Live logs |
| 5 | **Cooling Ledger** | Check if `core/cooling_ledger.py` has real persistence (not skeleton) | Source |
| 6 | **VAULT999 integrity** | Parse `outcomes.jsonl`, verify `seal_chain_head.json` | Vault dir |
| 7 | **Runtime drift** | Compare deployed commit vs git HEAD vs service start time | All three |
| 8 | **Thermodynamic state** | `/health` → `thermodynamic.service_health` | Live |
| 9 | **Surface consistency** | `/health` → `surface_consistency.verdict` | Live |
| 10 | **Soft floors** | Identify which floors are SOFT/DERIVED vs HARD | Source + live |

### Critical Pitfalls

- **Runtime drift ≠ deploy drift.** The deploy marker (`/opt/arifos/app/.git_commit`) can say commit X while the service was started before X. Always check THREE timestamps: commit time, deploy time, service start time.
- **`CANONICAL_PROMPTS` constant ≠ actual MCP wire exposure.** A commit can remove prompt registration from `register_prompts()` without updating the static `CANONICAL_PROMPTS` tuple. The constant lies. Check live MCP endpoints.
- **`tools_registry_size` ≠ internal tools count.** The health endpoint reports total registered callables (including aliases, diagnostic tools). The actual internal tools on the wire are a subset.
- **VAULT999 seal entries must be verified against session context.** A claimed seal `mem_XXX` may be from a different session/date. Always check the entry's timestamp and type match the current claim.
- **Ed25519 infrastructure existing ≠ Ed25519 being wired.** The crypto_auth.py may have full verification code that is never called from the session boot path. Check the CALL CHAIN, not just the function existence.

### Output Contract

The cage audit produces a table with:
1. ✅ Walls that hold (with evidence)
2. 🔴 Cracks (with severity + what it means for future self)
3. 📋 Cage readiness score (per component)
4. 🫡 Verdict: SEAL / SABAR / HOLD

## ZEN Surface Reduction Audit (2026-07-16)

When someone claims to have reduced the MCP surface — fewer tools, fewer prompts, fewer resources — verify against LIVE state, not just source code.

### Verification Checklist

| Claim | How To Verify |
|-------|---------------|
| Public tools N→M | `curl :8088/tools.json` → count |
| Internal tools N→M | Health endpoint `tools_registry_size` + source `ZEN_ABSORBED` set |
| Prompts N→M | Live MCP `prompts/list` endpoint + source `CANONICAL_PROMPTS` constant + `register_prompts()` diff |
| Resources N→M | Live MCP `resources/list` endpoint (may return 0 on REST — use health or source) |
| arif_judge stage | `grep "KERNEL" public_registry.py` |
| VAULT999 seal | Parse entry, verify timestamp matches claim, verify type matches claim |

### Pitfall: Unsealed Surface Reductions

Surface reductions are **code changes** that affect the public contract. If not sealed in VAULT999, future agents cannot audit what was removed. Always check: was the reduction sealed? If not, recommend sealing.

See: `references/zen-surface-reduction-verification-2026-07-16.md`

## Drift Interpretation: Bug vs Scar (2026-07-27)

When a kernel health endpoint reports `drift=true`, the reflex response is "fix it." But in a governed system, drift has a constitutional dimension beyond technical correctness.

### The Constitutional Question

The question is NOT:
> "Can drift be fixed?"

The question IS:
> "What is drift supposed to mean?"

Two mutually exclusive interpretations:

| If drift IS... | It means... | Response |
|---|---|---|
| **Bug** | Reality ≠ declaration. The system tolerates a contradiction it did not intend. | Fix it immediately. Systems must not persist known falsehoods. |
| **Scar** | A deliberately preserved witness to a resolved tension. The lesson was learned; the artifact remains. | Document it as scar. Do NOT fix — the scar IS the governance. |

### The Scar Test

A drift is a **scar** (not a bug) when:

1. **Code is identical** — the running file sha256 matches source. Deployment metadata lags, not function.
2. **The drift survived a cleanup cycle** — if the system went through a ZEN-SURVIVAL (82 conflicts resolved, shadow probe deployed, APEX ratified) and the drift remained, it was consciously retained.
3. **The lesson is already learned** — the system proved it can detect, report, and survive the drift. No new learning is extracted from fixing the label.
4. **The only thing broken is the label** — `deployed_commit` JSON field ≠ real wheel hash. Nothing about how the system runs is affected.

### The Scar Danger

If drift is kept as **witness** → useful.
If drift is kept as **excuse** → dangerous.

After a period, the system may say:
```
yes, drift exists
yes, we know
yes, it is recorded
```
with no intention to resolve it. At that point, HOLD becomes avoidance, not wisdom.

### The Critical Test

```
Is there a conscious constitutional reason for preserving the drift?

If no  → fix it.  (bug)
If yes → document it as a scar, not a defect.
```

**Zen:** *A scar is truth remembered. A bug is truth deferred. The difference is whether the system has already learned the lesson.*

### Pitfalls

- **Metadata drift ≠ code drift.** The wheel file may be identical while the `deployed_commit` label is stale. Distinguish function from label before classifying.
- **Scars can dry.** A scar that survives three refactoring cycles is a witness. A scar that has been silently ignored for months is a defect the system stopped seeing. Re-test the scar hypothesis periodically.
- **Beware scar-as-identity.** Systems can learn to love their defects as character traits. "We're the system with deployment drift" is not governance — it's self-caricature.

---

## No Drift ≠ Ready ≠ Authorized ≠ SEAL (2026-07-27)

A kernel can report `drift=false` and still **correctly** return HOLD. The con

…(truncated)
