Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/rfc-validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,10 @@ jobs:

- name: Run install.mjs BP-1 run.key gitignore tests
run: node tests/test-install-bp1-runkey-gitignore.mjs

- name: Reject stale P4 phase status in RFC-008 phase docs (closes #403)
run: |
if grep -rnE "P4b.*(remains|reduced|cutover)" docs/rfcs/RFC-008*; then
echo "::error::stale P4b phase status in docs/rfcs/RFC-008: refresh the phase index (#403)"
exit 1
fi
2 changes: 1 addition & 1 deletion PRINCIPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ Enforcement — hooks, gates, classifiers — is activated and controlled **per

**Why:** A hook in global `~/.claude/settings.json` fires in *every* project — Claude Code merges hooks across scopes and a project cannot subtract a global one — so global enforcement reaches into unrelated projects and breaks them (a gate looking for a project-local lib that isn't there denies real work). Enforcement that cannot be scoped or switched off per project defeats the entire point of RFC-008: decoupling enforcement from the substrate. Memory must stay usable everywhere without dragging enforcement along.

**How to apply:** Enforcement adapters install their hook FILES + libs + hook-invoked scripts into the activating project's `<project>/.claude/hooks/` and register them only into `<project>/.claude/settings.json` (never `~/.claude/hooks/` or `~/.claude/settings.json`). Global install deploys ONLY substrate (the `em-*` memory tools, `patterns/`, the skill); it copies zero hook files and writes zero hook registrations. Each project carries its own enable/disable switch — `<project>/.episodic-memory/enforce-config.json` `active`, plus the presence of its hook registrations. Installers expose enforcement as an explicit per-project opt-in (Principle 3) with per-project uninstall (Principle 10); a project that did not opt in runs zero enforcement hooks. Core memory operations never depend on any hook being installed (Principle 9). **Test this:** a mock-project E2E must assert that after any global/core install, `~/.claude/hooks/` contains no enforcement file and `~/.claude/settings.json` contains no enforcement registration; that the enforcement set is installed only under `<project>/.claude/`; and any design/code/test that places an enforcement artifact in global scope is a P12 violation.
**How to apply:** Enforcement adapters install their hook FILES + libs + hook-invoked scripts into the activating project's `<project>/.claude/hooks/` and register them only into `<project>/.claude/settings.json` (never `~/.claude/hooks/` or `~/.claude/settings.json`). Global install deploys ONLY substrate (the `em-*` memory tools, `patterns/`, the skill); it copies zero hook files and writes zero hook registrations. Each project carries its own enable/disable switch — `<project>/.episodic-memory/enforce-config.json` `active`, plus the presence of its hook registrations. Installers expose enforcement as an explicit per-project opt-in (Principle 3) with per-project uninstall (Principle 10); a project that did not opt in runs zero enforcement hooks. Core memory operations never depend on any hook being installed (Principle 9). **Test this:** a mock-project E2E must assert that after any global/core install, `~/.claude/hooks/` contains no enforcement file and `~/.claude/settings.json` contains no enforcement registration; that the enforcement set is installed only under `<project>/.claude/`; and any design/code/test that places an enforcement artifact in global scope is a P12 violation. Per-project uninstall is implemented (P4d S5, #416): `install.mjs --uninstall-enforcement` removes the project's enforcement set while preserving the core set and the global substrate, and the round-trip invariant (core install, then enforce, then uninstall, restores the core-install state) is asserted by `tests/test-uninstall-enforcement.mjs`.

---

Expand Down
6 changes: 3 additions & 3 deletions docs/rfcs/RFC-008-decouple-enforcement-from-substrate.md
Original file line number Diff line number Diff line change
Expand Up @@ -1205,15 +1205,15 @@ Serves R1, R6, R8 · depends on: P0, R0b′ · **DONE (P1a #373, P1b #374, P1c #

#### P2 — BP contract instances + contract validators → [RFC-008/P2-bp-contracts.md](RFC-008/P2-bp-contracts.md)

Serves R2, R3, R4 · depends on: P0 · queued (parallelizable with P1). `bp-001..006, bp-008..012.json` (11 contracts; **bp-007 absent**) + `scaffold-bp.mjs` + `validate-bp-contract.mjs` + `validate-schemas.mjs`; lands the shared-negative-corpus drift guard (issue #368).
Serves R2, R3, R4 · depends on: P0 · **DONE (P2a #381, P2b #384, P2c #388).** `bp-001..006, bp-008..012.json` (11 contracts; **bp-007 absent**) + `scaffold-bp.mjs` + `validate-bp-contract.mjs` + `validate-schemas.mjs`; lands the shared-negative-corpus drift guard (issue #368).

#### P3 — Thin waist + classifier runtime-sourcing + em-recall purification → [RFC-008/P3-thin-waist.md](RFC-008/P3-thin-waist.md)

Serves R1, R2, R3, R4, R5, R9 (+ F38) · depends on: P0, P2 · queued. `enforce-contract.mjs` + `lib/marker-state.mjs` + classifier runtime-sourcing + **em-recall STRICT DELETION** (F38/F60). The load-bearing phase; where gauntlet steps 5/6 finally run.
Serves R1, R2, R3, R4, R5, R9 (+ F38) · depends on: P0, P2 · **DONE (P3a #389, P3b-1 #391, P3c #392, P3b-2 #393, P3d #395).** `enforce-contract.mjs` + `lib/marker-state.mjs` + classifier runtime-sourcing + **em-recall STRICT DELETION** (F38/F60). The load-bearing phase; where gauntlet steps 5/6 finally run.

#### P4 — Per-project `enforce-config.json` → [RFC-008/P4-enforce-config.md](RFC-008/P4-enforce-config.md)

Serves R3, R5 · depends on: P3 · queued *(legacy "Phase 5")*. `effective_tier = min(harness, contract, project_config)` clamps DOWN only; `active: false` makes the classifier silent (R5).
Serves R3, R5 · depends on: P3 · **IN PROGRESS**: P4a #397 + P4c #398 done; **P4d** per-project enforcement re-architecture (Principle 12) S1-S6 + ESC merged, S7-S8 open *(legacy "Phase 5")*. `effective_tier = min(harness, contract, project_config)` clamps DOWN only; `active: false` makes the classifier silent (R5).

#### P5–P7 — Per-tool plugins (OpenCode / Codex / Pi Agent) → [RFC-008/P5-P7-tool-plugins.md](RFC-008/P5-P7-tool-plugins.md)

Expand Down
21 changes: 20 additions & 1 deletion docs/rfcs/RFC-008/P4-enforce-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
> Part of [RFC-008](../RFC-008-decouple-enforcement-from-substrate.md). Index:
> [RFC-008/README.md](README.md).

**Status:** IN PROGRESSschema landed P3b-2 #393; **P4a #397** (per-project loader3 pre_tool_use contract gates) + **P4c #398** (layer-wide `active:false` kill switch across preflight + second-opinion + SessionStart, R5) merged + deployed to global. **P4b** (reduced live SessionStart→Stop E2E + cutover) remains. *(Legacy "Phase 5".)*
**Status:** IN PROGRESS: schema landed P3b-2 #393; **P4a #397** (per-project loader, 3 pre_tool_use contract gates) + **P4c #398** (layer-wide `active:false` kill switch across preflight + second-opinion + SessionStart, R5) merged + deployed. **P4d** (per-project enforcement re-architecture, Principle 12) S1-S6 + ESC merged; S7-S8 open. Original P4b superseded by the P4d re-architecture. *(Legacy "Phase 5".)*
**Serves:** R3, R5.
**Depends on:** P3.
**Estimate:** ~25K.
Expand Down Expand Up @@ -42,3 +42,22 @@ all plugins makes the classifier silent (R5 — no hook spawn, no token cost).
## Maps to

R3, R5. Principle anchor: the project-config leg of the R3 effective-tier formula.

## P4d: per-project enforcement re-architecture (Principle 12)

P4d decouples enforcement from the substrate: every enforcement artifact (gate `.sh`
hooks, the enforcement engine, the classifier, hook libs, the SessionEnd/SessionStart hook
scripts) installs under `<project>/.claude/` and registers only in
`<project>/.claude/settings.json`; the memory substrate stays global and hook-free. Each
project owns its own on/off switch (`<project>/.episodic-memory/enforce-config.json` `active`).

Slice status (phase index; full slice and PR detail live in the workplan episode):

| Slice | Concern | Status |
|---|---|---|
| S1-S4 | per-project install + scope + the 3 pre_tool_use gates | merged (#400 / #401 / #402) |
| ESC | gates block ONLY repo-source writes (R1-R3) | merged (#409) |
| S5 | `--uninstall-enforcement [--purge-config]` | merged (#416) |
| S6 | install seed matches `loadEnforceConfig` identity (coupling guard) | merged (#417) |
| S7 | this phase-index + Principle 12 docs refresh | in progress (closes #403) |
| S8 | the 3 P12 invariants as a required CI gate | open |
2 changes: 1 addition & 1 deletion docs/rfcs/RFC-008/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ requirement parent. Each phase file carries its own R-anchors and back-links to
| **P1** | [P1-plugin-registry.md](P1-plugin-registry.md) | R1, R6, R8 | P0, R0b′ | **DONE** — P1a #373 + P1b #374 + P1c #376 + Follow #378 |
| **P2** | [P2-bp-contracts.md](P2-bp-contracts.md) | R2, R3, R4 | P0 | **DONE** — P2a #381 + P2b #384 + P2c #388 |
| **P3** | [P3-thin-waist.md](P3-thin-waist.md) | R1, R2, R3, R4, R5, R9 | P0, P2 | **DONE** — P3a #389 + P3b-1 #391 + P3c #392 + P3b-2 #393 (P4 schema folded in) + P3d #395 |
| **P4** | [P4-enforce-config.md](P4-enforce-config.md) | R3, R5 | P3 | **IN PROGRESS** — schema P3b-2 #393 + P4a #397 (per-project loader3 pre_tool_use gates) + P4c #398 (layer-wide `active:false` kill switch) DONE; P4b reduced E2E + cutover remains |
| **P4** | [P4-enforce-config.md](P4-enforce-config.md) | R3, R5 | P3 | **IN PROGRESS**: P4a #397 (per-project loader, 3 pre_tool_use gates) + P4c #398 (`active:false` kill switch) DONE; **P4d** per-project enforcement re-architecture (Principle 12: enforcement per-project, substrate global) S1-S6 + ESC merged, S7-S8 open (slice detail in workplan). Original P4b superseded by the P4d re-architecture. |
| **P5–P7** | [P5-P7-tool-plugins.md](P5-P7-tool-plugins.md) | R6, R10 | P3 | queued |
| **P8** | [P8-cursor-windsurf.md](P8-cursor-windsurf.md) | R6, R10 | P3 | queued |
| **P9** | [P9-recall-strategies.md](P9-recall-strategies.md) | R7 | — | **DEFERRED** — own RFC |
Expand Down
Loading