# Schema Stub Gate + Claim Schema Injector (MC #101065)

# Schema Stub Gate + Claim Schema Injector (MC #101065)

**MC:** #101065 (Deterministic Session Compiler — expanded scope)  
**Parent:** [Reality Anchor Doctrine v1](https://docs.alai.no/books/system-architecture/page/reality-anchor-doctrine-v1-final)  
**Owner:** CodeCraft / Petter Graff + FlowForge / Kelsey Hightower  
**Date Shipped:** 2026-05-16  
**Components:** `~/system/tools/schema-injector.js` + `~/.claude/hooks/schema-stub-gate.sh`

---

## Problem Statement

The claim schema was never pre-registered at task dispatch boundary. When John dispatches UAT, no template exists specifying "expected logins: N, expected a11y violations threshold: T, expected commits: SHA list". The verifier has no baseline to fill — so it fills from John prose (the same LLM surface the system is meant to bypass). This is the root cause of evidence padding incidents (Bilko UAT 2026-05-16: "4/4 logins working" claimed unverified).

> **Petter Graff (unified fix doc):** "Gap today: compiler exists but does not pre-register expected claim schema before dispatch."

---

## Solution: Pre-Dispatch Claim Schema Injection

The system now operates in three phases:

1. **mc.js start** → fires `schema-injector.js` → writes `/tmp/claim-schema-<mc_id>.json` with claim stubs
2. **Verifier/builder work** → runs deterministic probes → fills stubs from probe output JSON
3. **mc.js ready/done** → fires `schema-stub-gate.sh` → BLOCKS if any stub is PENDING or FAILED

---

## Component 1: Schema Injector

**File:** `~/system/tools/schema-injector.js`  
**Trigger:** Fires automatically at `mc.js start <id>` (line 2044 of mc.js)  
**Input:** MC title + description + ACs  
**Output:** `/tmp/claim-schema-<mc_id>.json`

### Claim Detection (Deterministic Regex)

No LLM inference. Keywords in AC text map to claim\_class via `~/system/probes/registry.json`:

<table id="bkmrk-ac-keyword-mapped-cl"><thead><tr><th>AC Keyword</th><th>Mapped claim\_class</th><th>Probe Script</th></tr></thead><tbody><tr><td>login, auth, sign-in, credentials</td><td>`login_works`</td><td>`~/system/probes/login-probe.sh`</td></tr><tr><td>commit, SHA, git, code change</td><td>`commit_verified`</td><td>`~/system/probes/git-diff-probe.sh`</td></tr><tr><td>a11y, accessibility, WCAG, violations</td><td>`a11y_count`</td><td>`~/system/probes/playwright-a11y-probe.js`</td></tr><tr><td>test, spec, @Test, it(, describe(</td><td>`test_count`</td><td>`~/system/probes/test-enumeration.sh`</td></tr><tr><td>deploy, URL, HTTP 200, curl</td><td>`http_200`</td><td>(Phase 2 — not yet shipped)</td></tr></tbody></table>

### Schema Structure

```json
{
  "mc_id": 101065,
  "generated_at": "2026-05-16T14:32:10Z",
  "task_started_at": "2026-05-16T14:32:10Z",
  "git_baseline": {
    "repos": ["/Users/makinja/projects/bilko"],
    "baseline_shas": ["a3f8bc4", "d9e2f01"]
  },
  "claim_stubs": [
    {
      "claim_class": "login_works",
      "probe": "~/system/probes/login-probe.sh",
      "expected": { "login_count": null },
      "filled_at": null,
      "probe_output_path": null,
      "status": "PENDING"
    },
    {
      "claim_class": "a11y_count",
      "probe": "~/system/probes/playwright-a11y-probe.js",
      "expected": { "violations_critical": 0, "violations_serious": 2 },
      "filled_at": null,
      "probe_output_path": null,
      "status": "PENDING"
    }
  ],
  "block_if_stubs_null": true
}

```

---

## Component 2: Verifier Fills Stubs

**Protocol:** At `mc.js ready` or `mc.js done` (before gate passes):

1. Read `/tmp/claim-schema-<mc_id>.json`
2. For each `PENDING` stub: 
    - Run mapped probe script (e.g., `bash ~/system/probes/login-probe.sh --url ...`)
    - Capture structured JSON output → write to `/tmp/probe-output-<mc_id>-<claim_class>.json`
    - Fill stub fields (`filled_at`, `probe_output_path`)
    - Set `status` to `FILLED` or `FAILED`
3. Any stub remains `PENDING` or `FAILED` → task BLOCKED
4. Write filled schema to `/tmp/claim-schema-<mc_id>-filled.json`

**Rule:** Verifier may NOT fill stubs from prose or John output. Only probe JSON is accepted.

---

## Component 3: Schema-Stub Gate Hook

**File:** `~/.claude/hooks/schema-stub-gate.sh`  
**Trigger:** PreToolUse on `mc.js ready` and `mc.js done`  
**Exit Codes:**

- `0` = Allow (all stubs filled or grace period)
- `1` = Block (pending/failed stubs or schema missing after grace period)

### Grace Period

**Until 2026-06-07:** Missing schema → WARN only (allow)  
**After 2026-06-07:** Missing schema → BLOCK

This gives 3 weeks for backfill of older MCs that started before the schema injector shipped.

### Blocking Logic

```bash
# Extract MC ID from stdin
MC_ID=$(echo "$INPUT" | jq -r '.args[0] // empty')

SCHEMA_PATH="/tmp/claim-schema-${MC_ID}.json"

# Check if schema exists
if [ ! -f "$SCHEMA_PATH" ]; then
  if [ "$NOW" -lt "$GRACE_CUTOFF" ]; then
    # Grace period — warn and allow
    echo "WARN: No claim schema for MC #${MC_ID}" >&2
    exit 0
  else
    # Past grace period — block
    echo "BLOCKED: No claim schema for MC #${MC_ID}" >&2
    exit 1
  fi
fi

# Check for pending/failed stubs
PENDING_COUNT=$(jq '[.claim_stubs[]? | select(.status == "PENDING" or .status == "FAILED")] | length' "$SCHEMA_PATH")

if [ "$PENDING_COUNT" -gt 0 ]; then
  echo "BLOCKED: MC #${MC_ID} has ${PENDING_COUNT} claim stub(s) not filled." >&2
  jq -r '.claim_stubs[]? | select(.status == "PENDING" or .status == "FAILED") | "  - \(.claim_class): \(.status)"' "$SCHEMA_PATH" >&2
  exit 1
fi

# All stubs filled — allow
exit 0

```

---

## Workflow Diagram

```

┌──────────────────────┐
│  mc.js start <id>    │
└──────┬───────────────┘
       │
       v
┌──────────────────────┐
│ schema-injector.js   │  ← reads MC title/ACs, detects claim_class via regex
│ writes /tmp/claim-   │
│ schema-<id>.json     │
│ with PENDING stubs   │
└──────┬───────────────┘
       │
       v
┌──────────────────────┐
│ Builder/Verifier     │
│ runs probes:         │
│  - login-probe.sh    │
│  - git-diff-probe.sh │
│  - playwright-a11y   │
│  - test-enumeration  │
└──────┬───────────────┘
       │
       v
┌──────────────────────┐
│ Fills stubs:         │
│  status: FILLED      │
│  probe_output_path   │
│  filled_at timestamp │
└──────┬───────────────┘
       │
       v
┌──────────────────────┐
│ mc.js ready/done     │
└──────┬───────────────┘
       │
       v
┌──────────────────────┐
│ schema-stub-gate.sh  │  ← hook checks stubs
│  - All FILLED? ALLOW │
│  - Any PENDING? BLOCK│
└──────────────────────┘

```

---

## Test Invocation

```bash
# Simulate mc.js ready call with MC ID
echo '{"args":["101065"]}' | bash ~/.claude/hooks/schema-stub-gate.sh

# Expected: exits 1 if any stubs PENDING, exits 0 if all FILLED

```

---

## Related

- **Parent MC:** #101065 (Deterministic Session Compiler)
- **Probe Registry:** [4 Deterministic Probes](https://docs.alai.no/books/infrastructure/page/4-deterministic-probes-mcs-101133-101136)
- **Reality Anchor Doctrine:** [v1 Final](https://docs.alai.no/books/system-architecture/page/reality-anchor-doctrine-v1-final)
- **Child MCs:** #101133 (login-probe), #101134 (git-diff-probe), #101135 (playwright-a11y), #101136 (test-enumeration)