diff --git a/README.ko.md b/README.ko.md
index 35736a36..e7de4b25 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -13,7 +13,7 @@
-
+
diff --git a/README.md b/README.md
index 2dcee674..25a36f4f 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
-
+
diff --git a/README.zh.md b/README.zh.md
index f0a07a0b..4cd794b1 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -13,7 +13,7 @@
-
+
diff --git a/devlog/_plan/260914_executor_first_delegation/000_plan.md b/devlog/_plan/260914_executor_first_delegation/000_plan.md
new file mode 100644
index 00000000..ccd151ab
--- /dev/null
+++ b/devlog/_plan/260914_executor_first_delegation/000_plan.md
@@ -0,0 +1,127 @@
+# 260914 planning consultation and implementation delegation
+
+## Reader summary
+
+이 변경은 명세·수정 범위·검증 방법이 확정된 구현을 executor에 우선 배정하고,
+P 계획에 담당을 기록해 B에서 그 분담을 실행하도록 안내한다. 후속 변경은 기존
+architect 절차도 P 진입 시 알린다. Architect가 설계를 제안하고, 메인이 실행 계획을
+쓴 뒤, 같은 architect가 반영 상태를 확인하는 순서와 실제 결과를 계획에 남긴다.
+메인이 최종 판단과 통합을 맡으며, 두 규칙 모두 모델이 따르는 지침이다.
+
+근거와 소스 앵커는 [001_source_evidence.md](001_source_evidence.md)에 있다.
+기존 executor 작업의 wp1–wp4와 검증 기록은 아래에 보존한다. 승인된 architect
+후속 변경의 범위·분담·검증은 [040](040_architect_consultation.md)에 기록한다.
+실제 설계·재확인 흐름의 완주 검증과 호출 훅의 암호문 보존 수정은
+[042](042_runtime_completion.md)에 기록한다.
+
+## Loop spec
+
+| Field | Content |
+| --- | --- |
+| Loop archetype | satisfy-spec. 정해진 문구/배선을 넣고 실제 주입 출력으로 확인한다. |
+| Trigger | 사용자 요청: 구현 위임을 기본값으로 만들어 메인 모델 비용을 줄이고, 로컬 구현 후 격리 환경에서 검증한 뒤 CXC에 PR. |
+| Goal | P에서 구현 담당이 기록되고 B에서 그 담당이 실행되도록, dev/pabcd 지침과 P/B 단계 안내가 연결된다. |
+| Non-goals | 전역 AGENTS.md 수정, 모델별 강제 규칙, 최소 호출 수 강제, 새 런타임 하드 게이트, 설치본 덮어쓰기, push/PR/merge/release. |
+| Verifier | `node plugins/codexclaw/scripts/build.mjs`, `npm test`, 격리 CODEX_HOME 설치본에서의 실제 directive 출력. 아래 PLAN-VERIFIER-REAL-01 참고. |
+| Stop condition | wp4의 격리 주입 증거와 로컬 커밋까지. push/PR은 별도 승인 단계. |
+| Memory artifact | 이 유닛(`devlog/_plan/260914_executor_first_delegation/`)과 goalplan `cxc-executor-p-b-home-jun-code-worktrees-codexcl`. |
+| Expected terminal outcomes | DONE = 문구·배선·테스트·격리 주입 증거·로컬 커밋 완료. NOOP = 동등 지침이 이미 존재. BLOCKED = 격리 설치가 호스트 제약으로 불가. NEEDS_HUMAN = 정책 문구 방향 결정 필요. |
+| Escalation condition | 같은 packet을 서로 다른 에이전트가 두 번 실패하면 메인이 회수(DISPATCH-RETIRE-01). 계획에 없는 슬라이스를 B 중간에 위임하려면 P 수정이 먼저. |
+
+## HOTL resource bounds
+
+- Write scope: `/home/jun/code-worktrees/codexclaw/executor-first-delegation` 워크트리와
+ 네이티브 cwd `/home/jun/code/codexclaw`의 `.codexclaw/` 상태·증거, 그리고 wp4의 임시 CODEX_HOME.
+- Tool/credential scope: 로컬 셸, git(로컬 커밋까지), 서브에이전트 dispatch. 네트워크·유료 API·계정 변경 없음.
+- Token/cost budget: 호스트 토큰 예산은 설정하지 않음. 모델 비용은 메인 + executor 합산으로만 논의하고, 측정 없이 절감액을 주장하지 않는다.
+- Wall-clock bound: 이 세션 내. 자원 한도 초과는 DONE이 아니라 BUDGET_EXHAUSTED.
+
+## Work-phase map (dependency order)
+
+| Phase | Outcome | Depends on | Doc |
+| --- | --- | --- | --- |
+| wp1 | 이 로드맵(문서만, 구현 없음) | — | 이 문서 |
+| wp2 | 지침 문구: 판단 기준의 단일 소유자와 계획 규약 | wp1 | [010](010_wp2_policy_text.md) |
+| wp3 | 단계 안내 배선: hook/CLI가 그 소유자를 가리킴 | wp2 | [020](020_wp3_directive_wiring.md) |
+| wp4 | 격리 검증과 로컬 커밋 | wp3 | [030](030_wp4_isolated_verification.md) |
+
+순서 근거(PHASE-SPLIT-01): 판단 기준이 먼저 존재해야 단계 안내가 그것을 가리킬 수 있고,
+배선이 끝나야 격리 환경에서 실제 주입 문자열을 확인할 수 있다. 노력 크기로 자른 분할이 아니다.
+
+## File change map
+
+| Path | Action | Phase |
+| --- | --- | --- |
+| `plugins/codexclaw/skills/dev/SKILL.md` | MODIFY: `### Implementation delegation` 신설 | wp2 |
+| `plugins/codexclaw/skills/pabcd/references/plan-output.md` | MODIFY: `## Implementation ownership` 신설 | wp2 |
+| `plugins/codexclaw/skills/pabcd/SKILL.md` | MODIFY: B 항목에 담당 실행 문장 | wp2 |
+| `structure/20_pabcd_dispatch_doctrine.md` | MODIFY: DISPATCH-ECONOMY-01에 구현 담당 소유자 포인터 | wp2 |
+| `structure/INDEX.md` | CONDITIONAL: 규칙/소유자 목록이 실제로 있을 때만 동기화. 없으면 `N/A + 이유` 기록 | wp2 |
+| `plugins/codexclaw/components/pabcd-state/src/hook.ts` | MODIFY: P/B directive에 포인터 한 줄 | wp3 |
+| `plugins/codexclaw/components/pabcd-state/src/orchestrate-cli.ts` | MODIFY: P/B 진입 성공 출력에 포인터 | wp3 |
+| `plugins/codexclaw/components/pabcd-state/dist/hook.js`, `dist/orchestrate-cli.js` | REBUILD: 설치 payload가 dist를 쓰므로 빌드 산출물도 커밋 | wp3 |
+| `plugins/codexclaw/components/pabcd-state/test/hook.test.ts` | MODIFY: P/B 포인터 단언 | wp3 |
+| `plugins/codexclaw/components/pabcd-state/test/orchestrate-cli.test.ts` | MODIFY: P/B 출력 포인터 단언 + 다른 verb 미포함 | wp3 |
+| `plugins/codexclaw/inventory.json` | 변경 없음 예상: `build.mjs`는 inventory를 재생성하지 않는다. 실제로 변하면 그때 함께 커밋 | wp3 |
+
+Scope boundary (OUT): `/home/jun/.codex/AGENTS.md`, 설치본 `/home/jun/.codex/plugins/cache/**`,
+`agents/*.toml` 역할 정의, fallback/모델 선택 코드, 새 CLI 서브커맨드, 전역 하드 게이트.
+
+## Accept criteria
+
+1. `dev/SKILL.md`에 구현 위임 기본값과 직접 구현 예외 3종이 있고, 예외가 아닌 사유
+ ("순차 작업이라서", "이미 이해해서")를 명시적으로 배제한다.
+2. `plan-output.md`가 파일 변경마다 담당과 직접 구현 사유를 요구한다.
+3. `pabcd/SKILL.md` B가 계획된 담당을 실행하도록 dev 소유자를 가리킨다.
+4. `phaseDirective("P")`와 `phaseDirective("B")` 출력에 포인터가 포함된다.
+5. `orchestrate P` / `orchestrate B` 성공 출력에 같은 포인터가 포함되고, `status`와
+ `A`/`C`/`D` 출력에는 포함되지 않는다.
+6. `npm test` 0 failures, 빌드 exit 0.
+7. 격리된 임시 CODEX_HOME의 설치본에서 P/B 진입 시 포인터가 실제 출력에 나타난다.
+8. 변경이 `codex/executor-first-delegation`에 로컬 커밋되고 push는 없다.
+
+### Conditional-path activation (C-ACTIVATION-GROUNDING-01)
+
+이 유닛이 추가하는 유일한 조건부 경로는 "P/B일 때만 포인터를 붙인다"이다.
+활성화 시나리오: wp3 테스트가 `P`와 `B`에 대해 포인터 포함을, `A`/`C`/`D`와
+`status`에 대해 미포함을 각각 단언한다. 관측 효과: 두 경우의 실제 문자열 차이.
+wp4는 같은 분기를 설치본에서 한 번 더 실행해 관측한다.
+
+### PLAN-VERIFIER-REAL-01
+
+| Verifier | 이 유닛의 변경을 실제로 읽는가 | 확인 방법 |
+| --- | --- | --- |
+| `npm test` | 예 | `package.json` test 글롭에 `components/pabcd-state/test/*.test.ts`가 포함됨 |
+| `node plugins/codexclaw/scripts/build.mjs` | 예 | `src/*.ts` → `dist/*.js` 변환 대상에 hook/orchestrate-cli 포함 |
+| 격리 CODEX_HOME 실행 | 예 | 설치된 payload의 `dist`를 직접 실행해 문자열 관측 |
+
+`npm test` 실행 전 wp3에서 RED 확인(새 단언이 기존 코드에서 실패)을 먼저 남긴다.
+
+### PLAN-BYPASS-NAMED-01
+
+| Field | Value |
+| --- | --- |
+| Tier | E7 (agent-followed doctrine). 문구는 훅이 강제하지 않는다. |
+| Executing surface | `UserPromptSubmit` 훅의 phase directive와 `cxc orchestrate` CLI 출력 |
+| Known bypass | 모델이 주입된 문장을 읽고도 직접 구현할 수 있다. 훅 출력은 조언이며 차단이 아니다. |
+| Residual risk | 실제 위임 비율은 보장되지 않는다. 효과는 관측으로만 판정한다. |
+| Downgraded wording | 그렇다. "enforcement"가 아니라 "early warning/pointer"로 쓴다. |
+
+### SoT sync target (SOT-SYNC-01)
+
+`structure/20_pabcd_dispatch_doctrine.md`가 이 유닛의 SoT다. wp2에서 함께 패치한다.
+`structure/INDEX.md`는 실제로 규칙/소유자 목록을 들고 있을 때만 동기화하고,
+아니면 010 문서에 `N/A + 이유`로 남긴다.
+
+## Implementation ownership (이 유닛 자체의 분담)
+
+이 유닛은 제안하는 규칙을 스스로 적용한다.
+
+```text
+wp1 로드맵과 정책 문구 결정 — main: 판단 자체가 산출물이라 위임 불가
+wp2 지침 문구 반영 — main: 문장 자체가 판단물이며 슬라이스가 작음
+wp3 hook/CLI 배선과 테스트 — executor: 수정 파일과 검증 방법이 확정됨
+wp4 격리 설치 검증 — main: 호스트 환경 조작과 증거 판정이 필요
+```
+
+wp3 위임 결과는 VCS diff로 검증한다(보고서만으로는 완료로 보지 않는다).
diff --git a/devlog/_plan/260914_executor_first_delegation/001_source_evidence.md b/devlog/_plan/260914_executor_first_delegation/001_source_evidence.md
new file mode 100644
index 00000000..313670d9
--- /dev/null
+++ b/devlog/_plan/260914_executor_first_delegation/001_source_evidence.md
@@ -0,0 +1,36 @@
+# 001 source evidence
+
+읽은 지점과 사실만 기록한다. 판단은 000/decade 문서가 소유한다.
+
+## 지금 존재하는 것
+
+- `plugins/codexclaw/skills/dev/SKILL.md:167` `### Discovery delegation` — 탐색 위임 기준의 소유자.
+ 같은 절 `:201`은 "Discovery does not replace implementation delegation or independent review."라고
+ 말하지만, 그 implementation delegation 기준을 정의하는 절은 이 파일에 없다.
+- `structure/20_pabcd_dispatch_doctrine.md:196` DISPATCH-ECONOMY-01 — 3축 위임 가능성 판정,
+ `:227` *Model routing* — 정형화된 구현 슬라이스는 기본적으로 싸고 빠른 모델 계열로 보낸다.
+ `:200`은 탐색 소유권을 dev의 Discovery delegation에 위임한다. 구현 담당에 대한 같은 포인터는 없다.
+- `plugins/codexclaw/skills/pabcd/references/delegation.md:73` — "For implementation dispatch,
+ prefer `executor` when exposed by the live schema." 전송 방법은 정의돼 있다.
+- `plugins/codexclaw/skills/pabcd/references/plan-output.md:8` 9필드 loop-spec 표 — 담당 필드 없음.
+ `:18` Escalation condition — "pushing a slice to a worker requires a P-phase amendment,
+ never a mid-B improvisation." 즉 B 중간 위임은 이미 금지돼 있는데, P에서 담당을 정하라는 요구가 없다.
+- `plugins/codexclaw/skills/pabcd/SKILL.md:83` B 항목 — 구현·검증·범위 이탈만 말하고 담당은 말하지 않는다.
+- `plugins/codexclaw/components/pabcd-state/src/hook.ts:292` `PHASE_DIRECTIVES`,
+ `:302` P, `:314` B, `:360` `phaseDirective()` — 주입 문자열의 단일 소유 지점.
+- `plugins/codexclaw/components/pabcd-state/src/orchestrate-cli.ts:1121` — 전이 성공 출력은
+ `orchestrate : current=X -> Y (...)` 한 줄이며 단계 안내를 담지 않는다.
+ `:656`은 I→P 오버라이드 경로의 별도 성공 출력이다.
+
+## 현재 설정 (확인 시점 2026-09-14)
+
+`readSettings()` 해석 결과 `/home/jun/code/codexclaw`, `/home/jun/code/opencodex`,
+`/home/jun/code/quota-monitor` 모두 executor = `devin/swe-2` / `high`,
+fallback = `combo/grok-4.6` / `xhigh`, source=global. `$CODEX_HOME/agents/executor.toml` 존재.
+이는 설정 해석 결과이며, 실제 서비스 모델이나 비용 절감의 증거가 아니다.
+
+## 관측된 행동 (원인 아님)
+
+연결된 작업 `01a09ed3-c8ea-77c1-9e29-bbda8fc4d299`에서 생성된 서브에이전트는 리뷰어 8개,
+탐색·구현 0개였다. 같은 세션이 dev/SKILL.md의 Discovery delegation 본문을 실제로 읽었으므로
+"지침이 전달되지 않았다"는 설명은 성립하지 않는다. 단일 사례이므로 모델 일반 성향의 증거로 쓰지 않는다.
diff --git a/devlog/_plan/260914_executor_first_delegation/010_wp2_policy_text.md b/devlog/_plan/260914_executor_first_delegation/010_wp2_policy_text.md
new file mode 100644
index 00000000..bd9e6d6d
--- /dev/null
+++ b/devlog/_plan/260914_executor_first_delegation/010_wp2_policy_text.md
@@ -0,0 +1,146 @@
+# 010 wp2 — 지침 문구 (policy text)
+
+구현 담당 판단의 소유자를 한 곳에 만들고, 계획 규약과 B 단계가 그것을 가리키게 한다.
+런타임 강제는 추가하지 않는다(E7).
+
+## Owner
+
+main. 문장 자체가 판단물이고 슬라이스가 작다.
+
+## Change 1 — `plugins/codexclaw/skills/dev/SKILL.md`
+
+Anchor: `### Discovery delegation` 절 마지막 줄(현재 204행,
+"applicable tiers; token totals alone cannot compare differently priced models.")과
+`### Capability Routing Hub` 사이에 새 절을 삽입한다.
+
+AFTER (삽입할 전체 텍스트):
+
+```markdown
+### Implementation delegation
+
+Once a slice is specified — goal, editable files, the contract it must preserve
+and the check that proves it — assign it to the configured `executor` by
+default. Sequential work qualifies: a dependency order decides the order of the
+packets, not who types the diff. Main keeps the specification, the load-bearing
+judgment, the review of the returned diff and the integration.
+
+Implementing a specified slice locally needs a stated reason, and only three
+hold by default: the edit is small enough that packaging it costs more than
+making it (§0.1), a load-bearing judgment inside the slice is still open so the
+packet cannot state its decision boundary, or handoff plus re-work measurably
+exceeds the delegated work. "It is sequential", "I already understand it" and
+"typing it is faster" are not reasons; neither is read-only-style parallelism,
+which is a separate question from who implements.
+
+Delegate one verifiable bundle — the slice a single check can prove — rather
+than one packet per file. P records the owner of each planned change and B
+executes that assignment (`../pabcd/references/plan-output.md`); handing a new
+slice to a subagent mid-B still requires a P amendment. Model, effort, fallback
+and receipts stay with the configured dispatch path. Verify a returned
+implementation against the VCS diff, never the report alone (§3). No-delegation
+limits, host restrictions and DISPATCH-ECONOMY-01's specifiability /
+verifiability / judgment axes take precedence.
+```
+
+검증: `rg -n 'Implementation delegation' plugins/codexclaw/skills/dev/SKILL.md`가
+새 절 헤더를 반환하고, 기존 `:201` "Discovery does not replace implementation
+delegation" 문장이 그대로 남아 있을 것.
+
+## Change 2 — `plugins/codexclaw/skills/pabcd/references/plan-output.md`
+
+Anchor: 9필드 표 아래 본문 마지막 문단(현재 23-27행, "Scope restrictions remain
+authoritative. ...")과 `## Reader summary` 사이에 새 절을 삽입한다.
+9필드 표와 "nine concepts" 문구는 건드리지 않는다(개수 불일치 방지).
+
+AFTER (삽입할 전체 텍스트):
+
+```markdown
+## Implementation ownership
+
+The file change map names an owner for each planned change: the configured
+`executor` for slices whose scope and check are settled, or main with a
+one-line reason from `cxc-dev`'s Implementation delegation exceptions. A plan
+that assigns nothing has not decided; it has defaulted to main silently. B
+executes the recorded assignment, and a new handoff mid-B remains a P amendment
+(see Escalation condition above). Discovery and review keep their own owners.
+
+```text
+transform helper + its unit test — executor: scope and check are settled
+shared policy wording and integration — main: the judgment is the deliverable
+one-line typo in a file already open — main: packaging costs more than the edit
+```
+```
+
+검증: `rg -n 'Implementation ownership' plugins/codexclaw/skills/pabcd/references/plan-output.md`.
+중첩 코드펜스이므로 삽입 후 파일을 실제로 열어 펜스가 깨지지 않았는지 확인한다.
+
+## Change 3 — `plugins/codexclaw/skills/pabcd/SKILL.md`
+
+Anchor: 83행 B 항목의 마지막 문장
+("When P declared a stack, follow `DEV-STACK-02` in `cxc-dev` `references/stacked-prs.md`.")
+뒤에 한 문장을 같은 줄에 이어 붙인다.
+
+AFTER (해당 줄의 꼬리):
+
+```text
+... When P declared a stack, follow `DEV-STACK-02` in `cxc-dev` `references/stacked-prs.md`. Execute the plan's recorded implementation ownership — specified slices go to the configured executor and main verifies the returned diff (`cxc-dev` Implementation delegation); a slice the plan did not assign needs a P amendment, not a mid-B improvisation.
+```
+
+## Change 4 — `structure/20_pabcd_dispatch_doctrine.md`
+
+Anchor: DISPATCH-ECONOMY-01 본문에서 탐색 소유권을 dev로 넘기는 문장(현재 200-203행)
+바로 뒤에 구현 담당 소유자 포인터를 추가한다.
+
+BEFORE:
+
+```text
+ For authorized source/log investigation, [dev's Discovery delegation](../plugins/codexclaw/skills/dev/SKILL.md#discovery-delegation)
+ owns the early ownership decision, concrete local exceptions and reconsideration
+ after scope growth or truncation. Parallelism alone does not reduce returned context;
+ this guidance adds no runtime enforcement or mandatory spawn count.
+```
+
+AFTER:
+
+```text
+ For authorized source/log investigation, [dev's Discovery delegation](../plugins/codexclaw/skills/dev/SKILL.md#discovery-delegation)
+ owns the early ownership decision, concrete local exceptions and reconsideration
+ after scope growth or truncation. For implementation, [dev's Implementation delegation](../plugins/codexclaw/skills/dev/SKILL.md#implementation-delegation)
+ owns the same decision: a specified slice defaults to the configured executor, and
+ the P plan records the owner that B then executes. Parallelism alone does not
+ reduce returned context; this guidance adds no runtime enforcement or mandatory spawn count.
+```
+
+줄바꿈 주의: `rg`는 줄 단위로 매치하므로 "Implementation delegation"을 줄 경계로 쪼개면
+아래 accept criteria가 빈 결과(exit 1)를 낸다. 링크 라벨을 한 줄에 유지한다.
+
+
+## Change 5 — `structure/INDEX.md` (conditional)
+
+INDEX가 규칙 ID나 스킬 절 목록을 실제로 들고 있으면 같은 변경을 반영한다.
+들고 있지 않으면(현재 확인된 범위에서는 파일 단위 맵만 존재) 이 문서에
+`N/A + 이유`로 기록하고 파일은 수정하지 않는다. 추측으로 항목을 만들지 않는다.
+
+## Accept criteria (wp2)
+
+- `rg -n 'Implementation delegation' plugins/codexclaw/skills/dev/SKILL.md` 히트.
+- `rg -n 'Implementation ownership' plugins/codexclaw/skills/pabcd/references/plan-output.md` 히트.
+- `rg -n "recorded implementation ownership" plugins/codexclaw/skills/pabcd/SKILL.md` 히트.
+- `rg -n 'Implementation delegation' structure/20_pabcd_dispatch_doctrine.md` 히트
+ (고정 문자열, 한 줄 안에 있어야 함).
+- `npm test`가 이 단계에서도 통과(문서 변경이 manifest/skill 테스트를 깨지 않음).
+
+## Out of scope (wp2)
+
+전역 AGENTS.md, `agents/*.toml`, delegation.md의 전송 규약, 코드 변경.
+
+## P 재검증 (wp2 사이클, 2026-09-14)
+
+이전 D 결론: wp1은 로드맵을 문서로 확정했고 코드는 건드리지 않았다. 방향 변경 없음.
+이 문서의 BEFORE 앵커 4곳은 커밋 e3b22b0b 시점에 `bash /home/jun/tmp/cxc-wp1-doccheck-01a09f4f.sh`
+(exit 0)로 현재 소스에 실재함을 확인했다. 재작성 없이 그대로 실행한다.
+
+Change 5(INDEX.md) 판정: INDEX.md는 파일·컴포넌트·훅·CLI 맵과 역할 표를 들고 있고,
+326행 부근이 "Formal P and changed-decision rechecks follow skills/pabcd/references/..."로
+에이전트 준수 지침의 소재를 요약한다. 구현 담당 기본값도 같은 성격이므로 그 문장에
+한 구절만 덧붙인다. 규칙 ID 목록은 INDEX에 없으므로 새 표를 만들지 않는다.
diff --git a/devlog/_plan/260914_executor_first_delegation/020_wp3_directive_wiring.md b/devlog/_plan/260914_executor_first_delegation/020_wp3_directive_wiring.md
new file mode 100644
index 00000000..77a33eb2
--- /dev/null
+++ b/devlog/_plan/260914_executor_first_delegation/020_wp3_directive_wiring.md
@@ -0,0 +1,191 @@
+# 020 wp3 — 단계 안내 배선 (directive wiring)
+
+P/B 진입 시 구현 담당 소유자를 실제로 가리키게 한다. 훅 경로와 CLI 경로 둘 다 고친다.
+같은 턴 안에서 CLI로 단계를 올리면 `UserPromptSubmit` 훅이 다시 돌지 않으므로,
+훅만 고치면 그 경로에는 안내가 전달되지 않는다.
+
+## Owner
+
+executor. 수정 파일, 문자열, 검증 방법이 모두 확정돼 있다. main은 반환된 diff를 검토·통합한다.
+
+## Change 1 — `components/pabcd-state/src/hook.ts` P directive
+
+Anchor: `PHASE_DIRECTIVES`(292행)의 `P` 배열(302-307행).
+
+BEFORE:
+
+```ts
+ P: [
+ "[codexclaw: PLAN]",
+ "Apply this pointer and its owners within exact user limits and permissions. No-delegation means no dispatch.",
+ "Load $codexclaw:cxc-pabcd for P and C2+ plan-output; $codexclaw:cxc-dev selects class and relevant surfaces. No implementation yet.",
+ "Plan-only ends with the plan. Forbidden checks: NOT RUN; naming an artifact grants no write permission.",
+ ].join("\n"),
+```
+
+AFTER:
+
+```ts
+ P: [
+ "[codexclaw: PLAN]",
+ "Apply this pointer and its owners within exact user limits and permissions. No-delegation means no dispatch.",
+ "Load $codexclaw:cxc-pabcd for P and C2+ plan-output; $codexclaw:cxc-dev selects class and relevant surfaces. No implementation yet.",
+ "Record implementation ownership per planned change: a specified slice defaults to the configured executor, main needs a stated reason ($codexclaw:cxc-dev Implementation delegation).",
+ "Plan-only ends with the plan. Forbidden checks: NOT RUN; naming an artifact grants no write permission.",
+ ].join("\n"),
+```
+
+## Change 2 — `components/pabcd-state/src/hook.ts` B directive
+
+Anchor: 같은 객체의 `B` 배열(314-319행).
+
+BEFORE:
+
+```ts
+ B: [
+ "[codexclaw: BUILD]",
+ "Apply this pointer and its owners within exact user limits and permissions. No-delegation means no dispatch.",
+ "Use $codexclaw:cxc-dev for class/surfaces; authorized PABCD B uses $codexclaw:cxc-pabcd. Implement only authorized scope.",
+ "Forbidden checks: NOT RUN; no invented proof.",
+ ].join("\n"),
+```
+
+AFTER:
+
+```ts
+ B: [
+ "[codexclaw: BUILD]",
+ "Apply this pointer and its owners within exact user limits and permissions. No-delegation means no dispatch.",
+ "Use $codexclaw:cxc-dev for class/surfaces; authorized PABCD B uses $codexclaw:cxc-pabcd. Implement only authorized scope.",
+ "Execute the plan's recorded implementation ownership; an unassigned slice needs a P amendment, and main verifies a returned diff rather than the report.",
+ "Forbidden checks: NOT RUN; no invented proof.",
+ ].join("\n"),
+```
+
+주의: `phaseDirective("B", opts)`는 base 뒤에 ACTIVE WORK-PHASE 블록을 붙인다(360-371행).
+새 줄은 base 안에 들어가므로 bound/unbound 양쪽에서 모두 나타난다.
+
+## Change 3 — `components/pabcd-state/src/orchestrate-cli.ts` 공통 힌트
+
+파일 상단 import 아래, `runOrchestrateCli`(456행) 앞에 상수와 헬퍼를 추가한다.
+
+```ts
+/**
+ * 260914: the phase directive only reaches a turn through UserPromptSubmit, so a
+ * same-turn CLI progression (P -> ... -> B inside one turn) never sees it. Echo a
+ * one-line pointer on the two edges whose owner decision is about to be made or
+ * executed. Advice only — it changes no gate.
+ */
+const OWNERSHIP_HINT: Partial> = {
+ P: "implementation ownership: record an owner per planned change (cxc-dev Implementation delegation)",
+ B: "implementation ownership: execute the plan's recorded owners; a new handoff needs a P amendment",
+};
+
+function withOwnershipHint(phase: Phase, output: string): string {
+ const hint = OWNERSHIP_HINT[phase];
+ return hint ? `${output} [${hint}]` : output;
+}
+```
+
+`Phase` 타입이 아직 import 되지 않았다면 `./state.ts`(또는 현재 `Phase`를 export 하는
+모듈)에서 타입 import를 추가한다. 새 의존성은 넣지 않는다.
+
+## Change 4 — 전이 성공 출력 두 곳
+
+Anchor A: 1121행 일반 전이 성공 반환.
+
+BEFORE:
+
+```ts
+ return { code: 0, output: `orchestrate ${args.verb}: current=${state.phase} -> ${result.state.phase} (${state.phase} → ${result.state.phase}, session ${sessionId})` };
+```
+
+AFTER:
+
+```ts
+ return { code: 0, output: withOwnershipHint(result.state.phase, `orchestrate ${args.verb}: current=${state.phase} -> ${result.state.phase} (${state.phase} → ${result.state.phase}, session ${sessionId})`) };
+```
+
+Anchor B: 656행 I→P 오버라이드 경로.
+
+BEFORE:
+
+```ts
+ return { code: 0, output: `orchestrate P: I → P (agent override, session ${sessionId})` };
+```
+
+AFTER:
+
+```ts
+ return { code: 0, output: withOwnershipHint("P", `orchestrate P: I → P (agent override, session ${sessionId})`) };
+```
+
+D 종료 출력(739행)과 `status`/`reset` 출력은 건드리지 않는다.
+
+## Change 5 — 테스트
+
+1. `components/pabcd-state/test/hook.test.ts`의 "wp3: phase pointers retain owners and
+ active work-phase boundaries" 테스트(110-122행)에 두 줄을 추가한다.
+
+```ts
+ assert.match(phaseDirective("P"), /Record implementation ownership/);
+ assert.match(phaseDirective("B"), /recorded implementation ownership/);
+```
+
+2. `components/pabcd-state/test/orchestrate-cli.test.ts`에 새 테스트를 추가한다.
+ 기존 헬퍼(임시 cwd 생성, 세션 준비)를 재사용하고 새 헬퍼를 만들지 않는다.
+
+```ts
+test("260914: P and B entry echo the implementation-ownership pointer; other verbs do not", () => {
+ // IDLE -> P 출력에 포인터가 있고, A 전이 출력에는 없다.
+ // 실제 러너 형태는 이 파일의 기존 테스트를 그대로 따른다.
+});
+```
+
+ 각 단언 전에 먼저 `assert.equal(r.code, 0)`와 전이 후 실제 phase를 확인한다.
+ 그러지 않으면 거부 메시지가 음성 단언을 거짓 통과시킨다.
+ 양성: `orchestrate P`와 `orchestrate B` 출력이 `/implementation ownership/`에 match.
+ 음성: `orchestrate A`, `orchestrate C`, `orchestrate D`(C→IDLE 종료) 출력은 doesNotMatch.
+ `orchestrate status`도 저장된 phase가 P일 때와 B일 때 각각 doesNotMatch를 단언한다
+ (안내는 전이 출력에만 붙고 상태 조회에는 붙지 않는다).
+ I→P 오버라이드 경로(656행 반환)도 별도로 match를 단언하되, 새로 만들지 말고
+ orchestrate-cli.test.ts:615의 기존 unready 픽스처를 재사용한다.
+ 그 밖에는 `seedPlanUnit()`, `freshCwd()` 등 기존 헬퍼를 재사용한다.
+
+3. 전이 출력 문자열을 `assert.equal`로 고정한 기존 테스트는 두 곳뿐이고 둘 다 D 종료
+ 출력이다(orchestrate-cli.test.ts:982, :2401). D에는 포인터를 붙이지 않으므로 그대로 둔다.
+ platform-smoke(거부/상태)와 cli-usage(도움말)도 영향 없음. 새 스냅샷 소비자는 없다.
+
+RED/GREEN: 새 단언을 먼저 추가해 실패(RED)를 확인한 뒤 소스를 고쳐 통과(GREEN)시킨다.
+
+## Change 6 — 빌드 산출물
+
+`node plugins/codexclaw/scripts/build.mjs`를 돌려 `dist/hook.js`와
+`dist/orchestrate-cli.js`를 갱신하고 함께 커밋한다. 설치 payload는 `src`가 아니라
+`dist`를 쓰므로 이걸 빼면 격리 검증이 옛 문자열을 본다.
+`inventory.json`은 이 빌드가 재생성하지 않는다. 실제로 변하면 그 변경도 함께 커밋한다.
+
+## Accept criteria (wp3)
+
+- `npm test` 0 failures(새 단언 포함), RED 확인 기록.
+- `node plugins/codexclaw/scripts/build.mjs` exit 0.
+- `dist/hook.js`와 `dist/orchestrate-cli.js`에 새 문자열이 존재(`rg`로 확인).
+
+## Out of scope (wp3)
+
+새 CLI 서브커맨드, 새 훅 이벤트, gate/transition 로직 변경, D/status 출력 변경.
+
+## P 재검증 (wp3 사이클, 2026-09-14)
+
+이전 D 결론: wp2가 dev/SKILL.md:206, plan-output.md:29, pabcd/SKILL.md:83, doctrine:202,
+INDEX.md:326에 구현 담당 소유자를 만들었다(커밋 1a063c76). 이제 단계 안내가 그 소유자를
+가리키게 한다. 방향 변경 없음.
+
+이 문서의 BEFORE 앵커(hook.ts P/B 배열, orchestrate-cli.ts 1121/656 반환)는 wp2가 건드리지
+않은 파일이며 `bash /home/jun/tmp/cxc-wp1-doccheck-01a09f4f.sh` exit 0으로 다시 확인했다.
+
+담당: executor. 수정 파일과 문자열과 검증 방법이 확정돼 있어 위임 조건을 충족한다.
+main은 패킷 작성, 반환 diff 검토, 통합을 맡는다(dev/SKILL.md Implementation delegation).
+`Phase` 타입은 orchestrate-cli.ts:139에 이미 import 돼 있다. 새 import를 추가하지 말고
+그것을 재사용한다(순환 import 없음). dist 재빌드는 전체 GREEN 실행 전에 끝낸다 —
+dist 신선도 검사가 소스 변경을 관측하기 때문이다.
diff --git a/devlog/_plan/260914_executor_first_delegation/030_wp4_isolated_verification.md b/devlog/_plan/260914_executor_first_delegation/030_wp4_isolated_verification.md
new file mode 100644
index 00000000..74921de6
--- /dev/null
+++ b/devlog/_plan/260914_executor_first_delegation/030_wp4_isolated_verification.md
@@ -0,0 +1,168 @@
+# 030 wp4 — 격리 검증과 로컬 커밋
+
+설치된 payload에서 새 문자열이 실제로 전달되는지 확인한다. 실제 설치본
+`/home/jun/.codex/plugins/cache/codexclaw`는 건드리지 않는다.
+
+## Owner
+
+main. 호스트 환경 조작과 증거 판정이 필요하다.
+
+## 경로 변수
+
+```bash
+CXC_WT=/home/jun/code-worktrees/codexclaw/executor-first-delegation
+CXC_TEST_HOME=/home/jun/tmp/cxc-exec-first-home-01a09f4f
+CXC_WS=/home/jun/tmp/cxc-exec-first-ws-01a09f4f
+CXC_REAL=/home/jun/.codex/plugins/cache/codexclaw
+```
+
+모든 명령은 `git -C "$CXC_WT"` 또는 `cd "$CXC_WT"` 형태로 대상을 명시한다.
+임시 작업 디렉터리로 이동한 상태에서 커밋하지 않는다.
+
+## Step 1 — 빌드와 전체 테스트 (워크트리)
+
+```bash
+cd "$CXC_WT" && node plugins/codexclaw/scripts/build.mjs && npm test
+```
+
+tail 출력과 exit code를 증거로 남기고 실패 수를 직접 읽는다.
+`rg -n 'implementation ownership' plugins/codexclaw/components/pabcd-state/dist/*.js`로
+빌드 산출물에 새 문자열이 들어갔는지 확인한다(설치본은 `src`가 아니라 `dist`를 쓴다).
+
+## Step 2 — 실제 설치본 지문 + 격리 설치
+
+설치 전후로 재귀 내용 지문을 뜬다. 최상위 `ls`는 버전 디렉터리 안의 덮어쓰기를 못 잡는다.
+
+지문 함수는 실패를 전파해야 한다. 파이프 끝의 `sha256sum`은 `find`가 실패해도 0을
+반환하므로, 실패한 스캔 두 번이 "빈 입력 해시"로 똑같이 나와 무변경으로 보일 수 있다.
+
+```bash
+fingerprint() { # 실패 전파 + 비어 있지 않은 목록 요구
+ (
+ set -o pipefail
+ n=$(find "$CXC_REAL" -type f | wc -l) || exit 1
+ [ "$n" -gt 0 ] || { echo "FAIL: empty inventory"; exit 1; }
+ d=$(find "$CXC_REAL" -type f -exec sha256sum {} + | sort | sha256sum) || exit 1
+ printf 'files=%s digest=%s\n' "$n" "$d"
+ )
+}
+fingerprint > /var/tmp/cxc-real-before-01a09f4f.txt || { echo "BEFORE scan failed"; exit 1; }
+mkdir -p "$CXC_TEST_HOME"
+cd "$CXC_WT" && CODEX_HOME="$CXC_TEST_HOME" scripts/dev-install.sh
+CODEX_HOME="$CXC_TEST_HOME" scripts/dev-install.sh --status
+fingerprint > /var/tmp/cxc-real-after-01a09f4f.txt || { echo "AFTER scan failed"; exit 1; }
+diff /var/tmp/cxc-real-before-01a09f4f.txt /var/tmp/cxc-real-after-01a09f4f.txt && echo "REAL INSTALL UNCHANGED"
+```
+
+두 스캔이 모두 성공하고 파일 수가 0이 아니며 서로 같아야 한다. 하나라도 실패하면 비교 자체가
+무효다. 이 지문은 정규 파일 내용의 동일성만 증명하며 권한·타임스탬프나 ~/.codex의 다른 경로는
+다루지 않는다. 두 지문이 다르면 즉시 중단하고 원인을 기록한다(실제 설치본 변경은 범위 밖).
+설치본 루트를 확정한다.
+
+```bash
+INSTALLED_ROOT="$(ls -d "$CXC_TEST_HOME"/plugins/cache/codexclaw/codexclaw/*/ | tail -1)"
+rg -n 'implementation ownership' "${INSTALLED_ROOT}components/pabcd-state/dist/"*.js | head
+```
+
+이후 모든 실행은 `CODEX_HOME="$CXC_TEST_HOME"`를 붙이고 `$INSTALLED_ROOT`의 파일을
+절대경로로 직접 호출한다. PATH의 `cxc`나 전역 설치본을 부르지 않는다.
+
+## Step 3 — 픽스처 준비
+
+```bash
+mkdir -p "$CXC_WS" && cd "$CXC_WS"
+git init -q && git commit -q --allow-empty -m "fixture"
+mkdir -p devlog/_plan/000000_isolated-check
+printf '# 000 isolated check\n' > devlog/_plan/000000_isolated-check/000_plan.md
+```
+
+B 진입은 소스 정체성을 스냅샷하므로 픽스처는 커밋이 있는 git 저장소여야 한다.
+세션 키는 예약된 터미널 키 `cli`를 쓴다. 이 세션의 실제 id를 쓰지 않는다.
+
+## Step 4 — 훅 주입 경로 관측 (UserPromptSubmit)
+
+`cxc hook` 서브커맨드는 존재하지 않는다. 훅 매니페스트가 실제로 부르는 진입점은
+`components/pabcd-state/dist/cli.js hook user-prompt-submit`이고, 페이로드는 stdin JSON이다.
+채팅 표면은 사람 free-pass이므로 인접 단계 이동에 attest가 필요 없다.
+
+페이로드 형태는 `{hook_event_name, session_id, cwd, prompt, transcript_path, turn_id}`다.
+`turn_id`는 호출마다 달라야 한다. 훅은 이미 주입한 turn을 `injectedTurns`로 기억하고
+같은 값이 다시 오면 명령 파싱 전에 빈 문자열을 반환한다(hook.ts:651). 같은 `t1`을 세 번 쓰면
+A와 B는 아예 실행되지 않고, 빈 A 출력이 음성 검증을 거짓 통과시킨다.
+
+```bash
+hookrun() { # $1 = prompt, $2 = turn id (호출마다 달라야 함)
+ printf '%s' "{\"hook_event_name\":\"UserPromptSubmit\",\"session_id\":\"cli\",\"cwd\":\"$CXC_WS\",\"prompt\":\"$1\",\"transcript_path\":null,\"turn_id\":\"$2\"}" \
+ | CODEX_HOME="$CXC_TEST_HOME" node "${INSTALLED_ROOT}components/pabcd-state/dist/cli.js" hook user-prompt-submit
+}
+hookrun "orchestrate P" t1 # 기대: [codexclaw: PLAN] + Record implementation ownership
+hookrun "orchestrate A" t2 # 기대: [codexclaw: AUDIT] + 해당 문장 없음
+hookrun "orchestrate B" t3 # 기대: [codexclaw: BUILD] + recorded implementation ownership
+```
+
+세 출력을 모두 직접 읽고 증거에 붙인다. 생성만 하고 읽지 않은 출력은 관측이 아니다.
+각 출력마다 단계 헤더(`[codexclaw: PLAN]` / `AUDIT` / `BUILD`)가 실제로 있는지 먼저 확인한다.
+헤더가 없거나 출력이 비어 있으면 그 단계는 실행되지 않은 것이므로, 포인터 미포함을
+음성 증거로 쓸 수 없다. 기존 테스트도 같은 흐름에 `t1/t2/t3`을 쓴다(hook.test.ts:727-731).
+
+## Step 5 — CLI 경로 관측 (같은 턴 진행)
+
+훅과 별개 경로다. Step 4가 픽스처 상태를 이미 움직였으므로 새 픽스처에서 시작한다.
+
+```bash
+CXC_WS2="${CXC_WS}-cli"
+mkdir -p "$CXC_WS2" && cd "$CXC_WS2" && git init -q && git commit -q --allow-empty -m fixture
+mkdir -p devlog/_plan/000000_isolated-check
+printf '# 000 isolated check\n' > devlog/_plan/000000_isolated-check/000_plan.md
+CXCRUN() { CODEX_HOME="$CXC_TEST_HOME" node "${INSTALLED_ROOT}bin/cxc.mjs" "$@"; }
+CXCRUN orchestrate P --session cli --cwd "$CXC_WS2"
+printf '%s' '{"from":"P","to":"A","did":"isolated fixture plan","planUnit":"devlog/_plan/000000_isolated-check"}' > "$CXC_WS2/pa.json"
+CXCRUN orchestrate A --session cli --cwd "$CXC_WS2" --attest-file "$CXC_WS2/pa.json"
+printf '%s' '{"from":"A","to":"B","did":"isolated fixture audit","auditOutput":"VERDICT: PASS","auditVerdict":"pass"}' > "$CXC_WS2/ab.json"
+CXCRUN orchestrate B --session cli --cwd "$CXC_WS2" --attest-file "$CXC_WS2/ab.json"
+```
+
+기대: P와 B 출력에 `[implementation ownership: ...]`가 있고 A 출력에는 없다. 모두 exit 0.
+attest가 거부되면 거부 사유를 그대로 증거에 남기고, 통과했다고 쓰지 않는다.
+
+## Step 6 — 커밋 (워크트리)
+
+```bash
+git -C "$CXC_WT" status --short --branch
+git -C "$CXC_WT" add -A
+git -C "$CXC_WT" commit -m "<범위에 맞는 메시지>"
+git -C "$CXC_WT" log --oneline -3
+```
+
+임시 픽스처는 워크트리 밖(`/home/jun/tmp`)에 있으므로 커밋 대상이 아니다.
+`git -C`로 대상을 고정해 임시 저장소에 커밋되는 사고를 막는다.
+push 하지 않는다(DEV-GIT-PUSH-01). PR은 사용자 승인 후 별도 단계.
+
+## Step 7 — 정리
+
+증거를 남긴 뒤 `$CXC_TEST_HOME`과 `$CXC_WS`, `$CXC_WS2`만 정리한다. 셋 다 이 작업이 만든 것이다.
+워크트리와 브랜치는 다음 PR 단계의 입력이므로 남긴다.
+
+## Accept criteria (wp4)
+
+- build exit 0, `npm test` 0 failures의 실제 tail 출력.
+- 실제 설치본 재귀 지문이 설치 전후 동일하고, 두 스캔 모두 성공했으며 파일 수가 0이 아니다.
+- 훅 경로에서 P/B 포함, A 미포함이 같은 증거에 기록되고, 세 출력 모두 자기 단계 헤더를 갖는다
+ (빈 출력이나 헤더 없는 출력은 음성 증거로 인정하지 않는다).
+- CLI 경로에서 P/B 포함, A 미포함이 exit 0과 함께 기록됨.
+- `git -C "$CXC_WT" log`에 커밋 존재, 원격 push 없음.
+
+## P 재검증 (wp4 사이클, 2026-09-14)
+
+이전 D 결론: wp3이 hook.ts P/B directive와 orchestrate CLI 두 성공 반환에 포인터를 배선했고
+dist까지 재빌드했다(커밋 a99d3b86). RED 3건 재현 후 GREEN 183/183, 전체 3079 pass / 0 fail,
+gate.mjs exit 0. 이제 설치된 payload에서 같은 문자열이 실제로 전달되는지 본다. 방향 변경 없음.
+
+Step 1의 `npm test`는 wp3 C에서 이미 실행했고 리시트로 남았다. wp4에서는 재실행 대신
+`TMPDIR=/var/tmp/cxc-t-01a09f4f`를 계속 쓰고(이 호스트의 `/tmp/.git` 때문에 GUI
+project-root 테스트가 깨진다), 격리 설치 직전에 빌드 산출물이 최신인지만 확인한다.
+
+## 이 유닛이 증명하지 않는 것
+
+실제 비용 절감. Astra/Opus 동일 조건 비교와 메인+executor 합산 비용 측정이 필요하며
+이 유닛의 범위 밖이다. 여기서 증명하는 것은 지침과 안내가 실제로 전달된다는 사실까지다.
diff --git a/devlog/_plan/260914_executor_first_delegation/031_wp4_receipt.md b/devlog/_plan/260914_executor_first_delegation/031_wp4_receipt.md
new file mode 100644
index 00000000..dd4028ba
--- /dev/null
+++ b/devlog/_plan/260914_executor_first_delegation/031_wp4_receipt.md
@@ -0,0 +1,54 @@
+# 031 wp4 receipt — 격리 환경 관측 증거
+
+실행일 2026-09-14. 워크트리 /home/jun/code-worktrees/codexclaw/executor-first-delegation, 브랜치 codex/executor-first-delegation.
+격리 홈 `/home/jun/tmp/cxc-exec-first-home-01a09f4f`, 설치본
+`0.2.28+codex.20260914090142`.
+
+## 실제 설치본 무변경
+
+```text
+files=3601 digest=ffd090c279d72db000f008e42210e740b183d9ebc568a6aaae2e68f714e6c4a8 - (BEFORE)
+files=3601 digest=ffd090c279d72db000f008e42210e740b183d9ebc568a6aaae2e68f714e6c4a8 - (AFTER)
+REAL INSTALL UNCHANGED
+```
+
+두 스캔 모두 성공했고 파일 수가 0이 아니며 서로 같다.
+
+## 훅 경로 (UserPromptSubmit, 설치본 dist/cli.js)
+
+세 호출 모두 자기 단계 헤더를 갖고 실행됐다(turn_id t1/t2/t3).
+
+| 프롬프트 | 헤더 | 구현 담당 포인터 |
+|---|---|---|
+| orchestrate P | `[codexclaw: PLAN]` | 있음 — "Record implementation ownership per planned change: a specified slice defaults to the configured executor, main needs a stated reason ($codexclaw:cxc-dev Implementation delegation)." |
+| orchestrate A | `[codexclaw: AUDIT]` | 없음 |
+| orchestrate B | `[codexclaw: BUILD]` | 있음 — "Execute the plan's recorded implementation ownership; an unassigned slice needs a P amendment, and main verifies a returned diff rather than the report." |
+
+## CLI 경로 (설치본 bin/cxc.mjs, 별도 픽스처)
+
+```text
+orchestrate P: current=IDLE -> P (IDLE → P, session cli) [implementation ownership: record an owner per planned change (cxc-dev Implementation delegation)] exit=0
+orchestrate A: current=P -> A (P → A, session cli) exit=0
+orchestrate B: current=A -> B (A → B, session cli) [implementation ownership: execute the plan's recorded owners; a new handoff needs a P amendment] exit=0
+session=cli phase=B interview=false auditPassed=true checkPassed=false exit=0
+```
+
+P와 B에만 붙고 A와 status에는 붙지 않는다. 네 호출 모두 exit 0이므로
+거부 메시지가 음성 결과를 만든 경우가 아니다.
+
+## 워크트리 쪽 증거 (wp3에서 확보)
+
+- RED: 기준 커밋 1a063c76 임시 워크트리에 새 테스트만 얹어 3건 실패 재현.
+- GREEN: 현재 HEAD에서 183/183, 전체 `npm test` 3152개 중 3079 pass / 0 fail / 73 skipped.
+- `node plugins/codexclaw/scripts/gate.mjs` exit 0.
+
+## 재현 스크립트
+
+`/var/tmp/cxc-wp4-verify-01a09f4f.sh` — 격리 설치본에 대해 위 관측을 다시 실행하고
+단언한다. 픽스처는 매 실행마다 새로 만든다.
+
+## 증명하지 않은 것
+
+실제 비용 절감. 그것은 동일 조건에서 메인+executor 합산 비용을 측정해야 하며
+이 유닛의 범위 밖이다. 호스트 제약 하나를 기록해 둔다: 이 머신에는 `/tmp/.git`이 있어
+TMPDIR을 분리하지 않으면 무관한 GUI project-root 테스트가 실패한다.
diff --git a/devlog/_plan/260914_executor_first_delegation/040_architect_consultation.md b/devlog/_plan/260914_executor_first_delegation/040_architect_consultation.md
new file mode 100644
index 00000000..2399bd5d
--- /dev/null
+++ b/devlog/_plan/260914_executor_first_delegation/040_architect_consultation.md
@@ -0,0 +1,96 @@
+# 040 — Architect consultation follow-up
+
+Depends on: 14c20791 (executor ownership policy and P/B delivery).
+
+## Outcome and scope
+
+Expose the existing architect proposal → main executable plan → same-architect
+reflection sequence at P entry, and record its actual result beside implementation
+ownership. This is one C2 follow-up to the completed executor change. Main owns
+decisions and integration; the architect remains read-only and the A reviewer
+remains independent. No model routing, role registration, gate, attestation schema,
+global installation, publication or minimum-spawn-count change is included.
+
+## Consultation and decisions
+
+The design proposal and same-handle reflection were received from architect
+`01a0a052-2b6a-77b3-a5ef-f5d15ee9617b` in task
+`01a0a049-f583-7633-981a-efbd0d680b96`. The reviewed executable-plan revision is
+the five-part main packet below, based on `14c20791`; this file records that packet's
+scope and decisions rather than claiming the architect reviewed this later file.
+
+| Evidence | Retrievable reference |
+|---|---|
+| Design proposal | Architect message `msg_685bf51a98b54deca1f4d162572be52e`, 2026-09-14 14:31:03 UTC, rollout line 76 |
+| Submitted executable-plan revision | Main message `msg_01a0a054-e122-7a82-adcf-133dbe530993`, 14:31:52 UTC, rollout line 82; SHA-256 `ce3a1198b3fc7daf2bc6df772cd981b7dd796e8154df9eaae8baae344f7972ca` of its UTF-8 text |
+| Same-handle reflection | Architect message `msg_1dd8ebb104004267b7d8b77f6d26401e`, 14:32:16 UTC, rollout line 87; ALIGNED with ARCH-P-01 through ARCH-P-04 mapped to main items 1–4 and verification to item 5 |
+
+The source is the local architect rollout
+`rollout-2026-09-14T23-28-54-01a0a052-2b6a-77b3-a5ef-f5d15ee9617b.jsonl`
+under the native Codex home's `sessions/2026/09/14/`. Raw conversation evidence
+stays outside this repository. The dispositions below and file map implement the
+submitted packet; the independent reviewer checks their correspondence to the diff.
+
+- ARCH-P-01: accept with amendment. Formal P includes C2 compact and plan-only P
+ plans; applicability is not conditional on whether a header was written.
+ C0/C1 fast-path work needs neither consultation nor an omission record.
+- ARCH-P-02: accept with amendment. Record the actual handle, proposal reference,
+ decision dispositions, executable-plan revision submitted, reflection reference
+ and unresolved gaps. User-forbidden delegation and failed consultation are
+ distinct; a failed call is not a successful exemption.
+- ARCH-P-03: accept. Keep phase-plan as the policy owner, add a short P skill/hook
+ pointer, and remind A only about changed design decisions. Main still decides.
+- ARCH-P-04: accept. Extend the existing CLI P hint; retain executor P/B hints and
+ keep status and other CLI phases free of the new consultation hint.
+
+## File change map and owners
+
+| File | Change | Owner |
+|---|---|---|
+| `skills/pabcd/references/phase-plan.md` | Clarify formal-P scope and link consultation record | main: policy decision |
+| `skills/pabcd/references/plan-output.md` | Add compact consultation evidence record before implementation ownership | main: policy decision |
+| `skills/pabcd/SKILL.md` | Name proposal and reflection in the P owner pointer | main: policy decision |
+| `structure/20_pabcd_dispatch_doctrine.md`, `structure/INDEX.md` | Synchronize owner pointers and evidence boundary | main: integration |
+| `components/pabcd-state/src/hook.ts` | Add proposal/reflection at P and design-change recheck at A | executor |
+| `components/pabcd-state/src/orchestrate-cli.ts` | Extend existing P hint, preserving B and status behavior | executor |
+| `components/pabcd-state/test/{hook,orchestrate-cli}.test.ts` | Exercise actual hook/CLI outputs and negative paths | executor |
+| `components/pabcd-state/dist/{hook,orchestrate-cli}.js` | Rebuild shipped payload | main: integration |
+
+Skill and component paths above are relative to `plugins/codexclaw/`.
+
+## Before / after contracts
+
+- P skill/hook: general plan-owner pointer → explicit architect proposal before
+ main's executable plan, then same-architect reflection before A, within user limits.
+- Plan output: no consultation record → references and dispositions tied to the
+ concrete plan, with honest gaps; no transcript copies or new machine schema.
+- A hook: reviewer guidance → same guidance plus recheck only when module
+ responsibility, data structure, interface or execution flow decisions change.
+- CLI P: implementation owner hint → architect sequence plus that existing hint.
+ CLI B and all transition gates retain their existing behavior.
+
+## Verification
+
+Run the existing hook/CLI tests before editing; extend runtime-output assertions
+and establish a failing-before/passing-after regression. Run the affected suites,
+`node plugins/codexclaw/scripts/build.mjs` and `node plugins/codexclaw/scripts/gate.mjs`.
+Use the repository's full suite once on the combined change if required by its gate.
+Use a task-owned TMPDIR outside `/tmp` (this host has an unrelated `/tmp/.git`).
+
+Verify the installed payload in an isolated home: P carries both policies; A hook
+rechecks only design changes; B retains implementation ownership; CLI status and
+other phases do not acquire the consultation hint. Compare the real plugin cache
+before and after to detect accidental writes.
+
+Behavioral evidence is separate: an actual planning run must obtain a proposal
+before finalizing its plan, send that plan to the same architect, and preserve the
+independent reviewer. Include C0/C1 and no-delegation negative scenarios. A string
+test or this task's earlier manually dispatched consultation is not proof of the
+new policy's autonomous adoption. Report any unobserved scenario honestly.
+
+## Review and completion
+
+An independent reviewer checks the combined diff, existing executor behavior,
+scope exemptions, evidence semantics and meaningful test coverage. Record fixes
+and final verification in `041_architect_receipt.md`; leave the previous receipt
+as historical evidence. Local commits follow the repository's `[agent]` convention.
diff --git a/devlog/_plan/260914_executor_first_delegation/041_architect_receipt.md b/devlog/_plan/260914_executor_first_delegation/041_architect_receipt.md
new file mode 100644
index 00000000..c23b9044
--- /dev/null
+++ b/devlog/_plan/260914_executor_first_delegation/041_architect_receipt.md
@@ -0,0 +1,97 @@
+# 041 — Combined delegation verification
+
+Verified on 2026-09-15 KST. Worktree: `codex/executor-first-delegation`;
+combined base `03541398`, previous executor-only head `14c20791`.
+
+Follow-up: [042](042_runtime_completion.md) identifies and corrects the pre-existing
+encrypted-message corruption, and completes the previously missing formal P and
+design-amendment runtime observations. This receipt preserves the earlier results.
+
+The combined policy/runtime implementation passed independent review. P now
+delivers architect consultation and executor ownership together; A reminds only
+about changed design decisions; B retains executor ownership. Consultation records
+identify the actual proposal, submitted plan revision, main dispositions and
+same-architect reflection. No consultation gate or routing change was introduced.
+
+## Build and regression evidence
+
+| Check | Result |
+|---|---|
+| Existing hook/CLI baseline | 183 passed, 0 failed |
+| New regression assertions on a temporary `14c20791` archive | 4 failed as expected; test process exit 1 |
+| Current hook/CLI suites | 185 passed, 0 failed |
+| `node plugins/codexclaw/scripts/build.mjs` | Exit 0; 181 generated files; only the two expected dist files differ |
+| `npm test` | Exit 0; 3,154 total, 3,081 passed, 0 failed, 73 skipped |
+| `node plugins/codexclaw/scripts/gate.mjs` | Exit 0 |
+| Changed documentation links | 39 local file links resolve; reviewer also checked new fragments |
+| `git diff --check` | Exit 0 |
+
+The executor returned its implementation and RED/GREEN report, but its native
+lifecycle remained running. Main closed it after delivery, inspected the four-file
+diff and independently ran build/full tests/gate. Completion rests on those files
+and checks. The initial worker RED wrapper filtered stdout and returned pipeline
+exit 0; the separate archive replay captured the actual test process exit 1.
+
+## Isolated installed payload
+
+Installed a real copy with `scripts/dev-install.sh --no-build` into a task-owned
+Codex home. Final installation doctor: PASS. The installed hook/CLI module probe
+passed 13 observations: P/A/B/C/D hook outputs, CLI P/A/B/C/D, status at P and B,
+and I→P override. Initial fixture construction omitted the event name and armed
+state and correctly failed on empty output; the fixture was fixed, not production.
+
+The independent reviewer recomputed source→dist output and compared it with both
+generated files and the isolated installed bytes. All matched. The real plugin
+cache's 1,042 file paths and SHA-256 values stayed unchanged through the final
+behavioral trials.
+
+## Actual model behavior: bounded observations
+
+The requests asked for ordinary CXC planning or a one-word correction; they did
+not instruct the model to call an architect by name. Native rollouts, not prose
+claims or injected tool documentation, were used to identify calls.
+
+| Scenario | Observation | Limit |
+|---|---|---|
+| One-word typo | Exit 0; model selected C0, changed only that word, no subagent calls | Run after policy updates and before final runtime wiring; the C0 path was unchanged |
+| Formal P plan | Model entered P and spawned architect before its final executable plan; one same-context retry and a fresh architect context followed | Both contexts failed; no design proposal or reflection was received; trial ended at its 240-second bound |
+| Same formal plan after matching the app's V2-disable setting | Again attempted architect consultation | CLI still exposed the task-name/fork-turns surface; the same error recurred and the owned CLI process group was stopped |
+| Formal P with explicit no delegation | Exit 0; no subagent calls, plan files written, consultation gap recorded, source unchanged, no A entry | One observed case, not a general compliance-rate measurement |
+
+The positive trials returned this native error:
+
+```text
+stream disconnected before completion: Encrypted function output content could not be decrypted or decoded.
+```
+
+This change does not establish the cause of that transport failure. Full autonomous
+proposal→plan→same-architect-reflection completion remains **unverified**. So do
+live design-amendment rechecks and any improvement in invocation rate, cost or
+quality. Main's earlier successful app-native architect consultation is design
+evidence in 040, not proof that the patched policy is autonomously followed.
+
+## Independent review
+
+Reviewer `01a0a060-fcb3-75f3-9ec6-7e82f7e4efe2` reviewed the combined base-to-working
+diff. Its one preliminary finding was missing exact consultation references in
+040. Main added proposal/submitted-plan/reflection message IDs, timestamps,
+rollout lines and the submitted packet hash; the reviewer independently reproduced
+the hash and closed the finding. Final policy/runtime verdict: PASS, no blockers.
+The reviewer covered all 19 pre-receipt changed files, including generated output
+and historical evidence; it did not claim complete autonomous adoption.
+
+## Local evidence and reproduction
+
+Task evidence root: `/var/tmp/cxc-architect-01a0a049-aah8dgrt/`.
+
+- `run-checks.py`, `build.log`, `full-tests.log`, `gate.log`.
+- `independent-red.log`: four failing regression tests on the old source archive.
+- `install-final.log`, `verify-installed.mjs`, `installed-observations.json`.
+- `source-manifest.json`: hashes of the 11 changed product/structure files.
+- `real-cache-before.json`, `real-cache-after.json`: installation fingerprints.
+- `run-behavior.py`, `behavior-summary.json`, and case directories: bounded CLI
+ trials and local rollout references. Raw transcripts remain outside Git.
+
+Temporary authentication copies were removed after each trial. The owned CLI
+processes were reaped. The evidence directory is retained for inspection.
+No changes were installed into the real Codex home and nothing was pushed.
diff --git a/devlog/_plan/260914_executor_first_delegation/042_runtime_completion.md b/devlog/_plan/260914_executor_first_delegation/042_runtime_completion.md
new file mode 100644
index 00000000..16610e10
--- /dev/null
+++ b/devlog/_plan/260914_executor_first_delegation/042_runtime_completion.md
@@ -0,0 +1,148 @@
+# 042 — Complete the remaining architect runtime verification
+
+Follow-up to 041, 2026-09-15 KST, based on `8f4b6bf9` in the same worktree.
+The user requested completion of the remaining live verification. The formal P
+trial now completes proposal → main executable plan → same-architect reflection.
+It exposed a pre-existing spawn-hook defect that had blocked every earlier
+positive trial. Design-amendment and text-only observations also completed below.
+These are bounded observations, not a measured invocation-rate or quality gain.
+
+## Failure, causal test and correction
+
+Native V2 supplied the task as a Fernet-shaped encrypted `message`. The existing
+spawn hook put a plaintext leaf guard, skill affordance and optional prompt override
+around it. The child received the mixture in an `encrypted_content` slot and the
+backend rejected it with:
+
+```text
+stream disconnected before completion: Encrypted function output content could not be decrypted or decoded.
+```
+
+Three hypotheses were tested: CXC message mutation, a general V2/backend failure,
+and mismatched test configuration/fork arguments. All controls used the same local
+CLI 0.154.0 and account-backed inherited model. The actual exposed surface was V2
+despite the disabled feature flag; the schema, not that flag, identifies the trial.
+
+| Control | Actual child result |
+| --- | --- |
+| `no-plugin-control` | CHILD_OK, then FOLLOWUP_OK |
+| `no-plugin-fresh-explicit` | Same success with a fresh context |
+| `plugin-control` | Original decryption error |
+| `plugin-minimal-fields` | Same error with optional spawn fields omitted |
+| `plugin-preserve-ciphertext` | Same installed plugin, only the message-preservation expression changed: both replies succeeded |
+| `plugin-restored` | Original installed hook restored in a fresh fixture: same error returned |
+
+The last two trials establish the causal off/on check. A general V2 failure and
+fork-argument explanation do not account for these observations. Parent CLI exit
+0 in a control means it reported its result; child success/failure above comes
+from native replies and error events, not from that exit code.
+
+The local correction is in `components/subagent-config/src/spawn-attach-hook.ts`
+under `plugins/codexclaw/`, with rebuilt dist and runtime regressions. It preserves
+recognized native V2 ciphertext byte-for-byte and discloses that hook-added text
+was not attached. Metadata-based recursion denial, explicit settings, full-history
+fork restrictions, separate routing fields and V1 item attachments retain their
+existing behavior. Main made this small local boundary correction; independent
+source investigation and review ran separately. No role, model, permission or
+consultation gate was added. Existing doctrine claims about encrypted delivery
+were corrected in `structure/20_pabcd_dispatch_doctrine.md` and `structure/INDEX.md`.
+
+The token detector is a format heuristic, not decryption or authentication.
+Encrypted managed markers and encrypted cross-provider delivery remain outside
+this fix. This trial uses inherited routing and does not certify those paths.
+
+## Regression and installed evidence
+
+- Old source with new byte-preservation assertions: two tests failed. The unchanged
+ recursion-denial case passed. See `cipher-red.log`.
+- Subagent-config suite after the correction: 328 passed, 0 failed.
+- The first full run found one old E2E assertion that required corrupting ciphertext.
+ It was replaced with separate ciphertext-preservation and plaintext-affordance
+ cases through the shipped entrypoint. Both capabilities remain tested; no skip
+ or threshold was added.
+- Final focused source/entrypoint suites: 120 passed, 0 failed.
+- Final full suite: 3,158 tests, 3,085 passed, 0 failed, 73 existing skips.
+- Build, repository gate and `git diff --check`: exit 0.
+- Real plugin copy installed only into the task-owned `followup-home`; doctor PASS.
+ The final reinstall includes the comment correction made after initial P startup;
+ that correction does not change executable behavior.
+
+## Actual formal P
+
+The natural request asked for a plan to add title search and pagination to a tiny
+existing catalog module. It did not name architect or prescribe its calls. It
+forbade implementation, goal creation and A entry.
+
+- Parent: `01a0a08c-adda-7f23-aaca-b4658d7490be`.
+- Architect: `01a0a08d-80b2-7310-99cf-c4e48c74aa14`, returned V2 handle
+ `/root/catalog_architect`.
+- Parent rollout: architect spawn at line 38; concrete two-file plan write at
+ line 85; same-handle `followup_task` at line 90.
+- Architect rollout: CAT-01–CAT-05 proposal at line 32; revision-1 reflection at
+ line 48, ALIGNED, mapping every decision to plan paths and acceptance criteria.
+- Parent then recorded the reflection and stopped in P, exit 0. Source and README
+ stayed unchanged. The plan records executor ownership for the later implementation.
+- The separate cold-read helper is document readability evidence, not A review.
+
+The plan is in `behavior-positive-fixed/devlog/_plan/260915_catalog_query/`.
+`positive-fixed-r1/` preserves its completed revision before subsequent amendments.
+Raw native transcripts remain outside Git under the evidence root.
+
+## Design amendment and text-only amendment
+
+The same parent resumed with a natural requirement change: replace page/pageSize
+with offset/limit and complete A review, without implementation or B entry.
+
+- Main revised CAT-02, CAT-03 and CAT-05, preserving CAT-01 and CAT-04, and sent
+ revision 2 to `/root/catalog_architect` at parent rollout line 160.
+- The same architect returned ALIGNED at its rollout line 64, with changed and
+ preserved decisions mapped to the revised contract and acceptance criteria.
+- Parent then spawned independent `/root/catalog_audit` at line 205, using the
+ supported read-only explorer plus logical reviewer packet. Native reviewer was
+ absent from that trial schema; no architect-role substitution occurred.
+- Reviewer `01a0a094-1295-7b51-92a6-e5a3d1c22dda` returned VERDICT: PASS and no
+ blocking issues at rollout line 75. The parent's completed turn is line 281.
+- The amended executable specification's SHA-256 is
+ `fecec8d67011628435b55dd97e25d4c2bc20441fc15a4b84aa10a04f28d07efa`.
+ `002_audit.md` records the main disposition, architect reflection and independent
+ review separately. `positive-fixed-r2/` preserves that completed revision.
+- The final wording-only request changed just the first heading's “페이지 조회”
+ to “구간 조회”. Parent rollout lines 282–302 contain one shell/tool composition
+ and no spawn or architect follow-up. Independent byte comparison found exactly
+ that replacement, with every other plan file, catalog source and README unchanged.
+ See `text-amendment-proof.json`.
+
+Both resumed CLI runs exited 0. The trial stayed at A; no implementation, B entry
+or goal was created. The real parent task remained IDLE throughout. Earlier C0
+and explicit-no-delegation observations in 041 remain separate evidence for those
+unchanged paths. Actual model routing across providers was not measured.
+
+## Independent review and evidence location
+
+Reviewer `01a0a08c-afe5-7660-8c9c-103296617b54` initially returned PASS with no blockers
+and independently ran 52 assertions on source/dist parity, recursion, routing,
+ciphertext variants, disclosure, plaintext and items. Its two minor observations
+were addressed: the stale inline comment and missing persistent disclosure check.
+The same reviewer subsequently returned PASS for the corrected E2E case and
+completed P evidence, reproduced source→plan→same-architect ordering, checked
+the final suite logs and independently compared all 14 changed plugin files with
+installed bytes. Its verdict is distinct from the fixture's independent A review.
+The final receipt-only review also returned PASS, confirming revision-2 reflection
+before independent A, the preserved revision hash, the title-only diff and forward
+links. Cleanup and the real-cache comparison are explicitly main-verified evidence.
+
+Evidence root: `/var/tmp/cxc-architect-01a0a049-aah8dgrt/`. Relevant additions:
+`control-probe.py`, the six control directories, `remaining-validation-hypotheses.md`,
+`cipher-{red,green,focused-final}.log`, `followup-{build,install,gate}-final.log`,
+`followup-full-tests-final.log`, `run-behavior-fixed.py`, `run-amendment.py`,
+`followup-home/sessions/`, `behavior-positive-fixed/`, and `positive-fixed-r1/`.
+An initial resume command rejected unsupported `--color` before starting a turn;
+the runner removed that argument. This was a harness correction, not a model retry.
+
+`transport-controls-summary.json` records the original encrypted slots and child
+outcomes without copying ciphertext into Git. `followup-source-installed-manifest.json`
+records changed tracked-file hashes. Main compared all 1,042 real plugin-cache
+paths and hashes against the original baseline: unchanged. All temporary auth
+copies were removed, and owned CLI processes were reaped. The evidence directory
+is retained. This follow-up remains a local working-tree change; no commit, push,
+merge or real Codex-home installation was performed in this verification follow-up.
diff --git a/devlog/_plan/260914_executor_first_delegation/050_codex_review.md b/devlog/_plan/260914_executor_first_delegation/050_codex_review.md
new file mode 100644
index 00000000..1057df53
--- /dev/null
+++ b/devlog/_plan/260914_executor_first_delegation/050_codex_review.md
@@ -0,0 +1,131 @@
+# 050 — PR 177 ciphertext classification review
+
+Loop: satisfy-spec, triggered by the user's request to fix consequential PR Codex
+feedback. Memory artifact: this unit and its verification receipt. Success is the
+verified outcome below; an unavailable review/runtime is reported as unresolved,
+never a pass. Main reclaims implementation only after the configured two-attempt
+dispatch path fails with reconciled evidence. A new worker handoff needs a P
+amendment. Ask the user only for a scope change or an irreversible action beyond the
+already authorized PR/fork/local patch; no upstream merge is authorized.
+
+## Objective and class
+
+Close the consequential Codex finding on PR 177, keep the executor/architect
+policy intact, obtain a completed review on the corrected head, and retain that
+validated patch in the local source, fork and installed plugin. C4 care applies
+because the predicate controls whether child task guards are attached.
+
+## Repository evidence and failure
+
+Baseline: public head `a0163227e75ab06861c07721e8382aa5924f26a6`, identical tree to
+the retained private local head `2a1e3b7e0680157296354d871bd7ce71fb508221`.
+Codex comment `4007056665` at `spawn-attach-hook.ts:851` correctly identifies
+that `gAAAAx` bypasses task guard and prompt-override attachment. A direct hook
+probe reproduced unchanged message and a false ciphertext-preservation notice.
+The previous unit/e2e ciphertext fixtures are not valid envelopes.
+
+## Threat model and constraints
+
+Asset: child task instructions and native encrypted-task integrity. Entrypoint:
+PreToolUse V2 spawn message. The caller can provide arbitrary plaintext; the hook
+cannot decrypt or authenticate backend ciphertext. Prefix resemblance alone
+must not remove plaintext guards. Conversely, rewriting real ciphertext breaks
+native spawn decryption. Recognize envelope structure only; never claim HMAC
+authentication. D1 recursion denial, explicit routing and fork restrictions keep
+their existing ordering. No provider, dependency, credential or gate changes.
+
+## Architect consultation and main decisions
+
+Architect `01a0a0b7-cc7b-7be1-88b2-2aece0d3171c`, managed dispatch
+`pr177-envelope-design`, supplied ENV-01 through ENV-07. Main dispositions:
+
+- ENV-01: amend: colocate a private pure predicate beside the V2 shape helper;
+ test observable hook behavior rather than exporting solely for tests.
+- ENV-02: amend: accept canonical padded and wholly unpadded base64url. Reject
+ bad alphabet, partial/excess padding, impossible encoded lengths and nonzero
+ unused pad bits with a decode/re-encode check. This is structural recognition,
+ not authentication; HMAC cannot distinguish encodings of identical bytes.
+- ENV-03: accept: decoded version 0x80, minimum 73 bytes and
+ `(length - 57) % 16 === 0`. Drop the timestamp-dependent `gAAAA` prefix test.
+ No timestamp/TTL or MAC validation.
+- ENV-04: accept: malformed input follows existing plaintext attachment and
+ emits no ciphertext notice. V1 stays on its existing attachment path.
+- ENV-05/07: amend: real public Fernet vector plus synthetic block/padding
+ variants and malformed inputs, exercised through the public hook. Preserve
+ explicit settings, full-history restrictions and recursion denial assertions.
+- ENV-06: accept: rebuild and commit the distributed hook.
+
+Primary sources: [Fernet specification](https://github.com/fernet/spec/blob/master/Spec.md)
+and [public generation vector](https://raw.githubusercontent.com/fernet/spec/master/generate.json).
+No live task token or key belongs in committed fixtures.
+
+## Scope and ownership
+
+Executor: `components/subagent-config/src/spawn-attach-hook.ts`, its existing unit
+test, and `test/hook-e2e.test.mjs` under `plugins/codexclaw/`. Main: consultation,
+independent review, this record, matching search-skill wording, generated dist,
+test inventory badges, integration, publication and installation evidence. Matching
+stale ciphertext/affordance paragraphs in `structure/10_subagent_skill_routing.md`
+are synchronized with the existing INDEX behavior description; no routing change.
+
+Two existing V2 transport-parity fixtures omit `agent_type`; the new checkout's
+`delegation-review` path enters the skill text and triggers legacy keyword-based
+role inference. Baseline focused run was 118/120 passing. Make those fixtures
+explicitly explorer like their V1 counterparts; retain all routing assertions.
+Changing production inference is outside this repair and remains a known quirk.
+
+## Interfaces and behavior
+
+Before: any prefix-shaped `gAAAA...` token bypasses plaintext attachment.
+After: only canonically encoded Fernet-shaped frames use byte preservation;
+short/malformed text receives the ordinary guard, affordance and configured
+prompt override. Existing behavior for actual ciphertext remains byte-identical.
+Search skill wording must describe the plaintext affordance only on plaintext
+V2, and explicitly state encrypted-message attachment is unavailable.
+
+## Executable steps and budget
+
+1. Obtain same-architect reflection on this concrete plan and independent A audit.
+2. Delegate bounded hook/test changes; main corrects the matching search paragraph.
+3. Inspect diff, rebuild shipped JavaScript, run negative/positive regression and
+ full tests, update measured inventory and run repository gate.
+4. Push correction to the existing PR branch, reply to the finding, request a
+ fresh Codex review, and inspect latest-head findings and CI. Repeat only for
+ consequential remaining defects, updating this unit when design changes.
+5. Apply reviewed delta atop preserved local history, fast-forward fork dev,
+ install from local source, compare bytes and preserved configuration, and
+ close work/goal only with the evidence captured.
+
+No user token/time ceiling was set. Commands and probes use bounded background
+handles. Waiting for a running review is not a failed attempt.
+
+## Verification and acceptance
+
+Malformed cases: short prefix, old invalid fixtures, wrong version, undersized
+frame, empty/non-block ciphertext, invalid alphabet/whitespace/padding/pad bits.
+Each V2 malformed case must retain guard and override and lack omission notice.
+Valid cases: real reference token, padded/unpadded forms, several ciphertext block
+counts, timestamp bytes that do not encode the old prefix, exact settings/fork
+behavior, recursion denial, and V1 nonclassification. E2e must use rebuilt dist.
+Repeat a bounded actual native encrypted V2 spawn against isolated updated plugin
+bytes; require actual child/follow-up results and the hook's ciphertext-preservation
+notice. If a diagnostic exposes envelope metadata, retain only lengths/padding,
+never raw live tokens. The canonical padding cases have independent unit evidence;
+do not infer the backend's padding variant from child success. Prior runtime proof
+is historical and does not alone validate the stricter classifier.
+
+Done requires: regression and repository gate pass, completed latest-head Codex
+review with no unaddressed consequential finding, passing hosted CI, and local
+source/fork/installed equality with role settings preserved. No upstream merge.
+
+## Review and remaining uncertainty
+
+Same-handle architect reflection was ALIGNED on plan SHA-256
+`0d0e68a3d0ea33911f6dcf937787971eecd298655b250835da979bb76e35f9bc`.
+Main resolves its G1 evidence gap above: actual hook notice plus child results are
+required; emitter padding is not claimed without a separate shape observation.
+Independent A reviewer `01a0a0c1-efce-7c13-a6bb-d5424701cac4` returned PASS without
+findings on plan SHA-256 `a1da4dcda5fca75c5f7a6a0047eb9d8565b917e7b0d4c59a09a0c08218d1ce33`.
+Backend format support
+is limited to the observed Fernet envelope; recognition cannot prove authenticity.
+The main agent owns all final decisions and integration.
diff --git a/devlog/_plan/260914_executor_first_delegation/051_codex_review_receipt.md b/devlog/_plan/260914_executor_first_delegation/051_codex_review_receipt.md
new file mode 100644
index 00000000..d4a3e166
--- /dev/null
+++ b/devlog/_plan/260914_executor_first_delegation/051_codex_review_receipt.md
@@ -0,0 +1,73 @@
+# 051 — PR 177 review correction evidence
+
+Codex finding [4007056665](https://github.com/lidge-jun/codexclaw/pull/177#discussion_r4007056665)
+was accepted. The previous regex treated `gAAAAx` as ciphertext, suppressing task
+guards and the configured prompt override. The correction recognizes canonical
+base64url Fernet frame structure. It does not authenticate or decrypt a token.
+Existing executor/architect ownership and routing policy remain unchanged.
+
+## Regression and native execution
+
+Evidence root: task-owned `cxc-architect-01a0a049-aah8dgrt` under `/var/tmp/`.
+Raw runtime data and operator settings remain outside the repository.
+
+| Check | Observed result |
+|---|---|
+| `pr177-boundary-probe.mjs` against previous installed dist | Exit 1: guard false, override false, false ciphertext notice true |
+| Same probe against rebuilt corrected dist | Exit 0: guard true, override true, false ciphertext notice false |
+| `npm run build` | Exit 0; 181 generated component files validated |
+| `npm audit --json` | Exit 0; 0 reported vulnerabilities; dependency files unchanged |
+| Isolated native V2 spawn and same-child follow-up | Exit 0; actual child replies `CHILD_OK` and `FOLLOWUP_OK`, with the actual hook preservation notice |
+
+Native evidence: `pr177-native-envelope/{artifact,observations,exit}.json` in that
+external root. Parent task `01a0a0d1-a298-7693-88d5-6fd0ac135742` records the hook's
+developer notice at rollout line 17. Child
+`01a0a0d1-c194-7db2-936a-734121d0ad90` records the two actual replies at lines 12
+and 21. The probe's distributed hook bytes match the reviewed source build.
+Its CLI process exited and was reaped; temporary authentication was removed.
+Native V2 exposed interrupt rather than close; interrupt returned the child's
+completed follow-up status. No probe process was left running.
+
+The probe confirms the recognized native transport path. It does not identify the
+emitter's padding variant or prove cross-provider transport compatibility.
+
+## Independent review
+
+Reviewer `01a0a0c1-efce-7c13-a6bb-d5424701cac4` approved the executable plan, then
+returned a separate code-only PASS without findings on the source, generated dist
+and matching search/routing documentation. Its read-only Node probes passed
+20,878 assertions covering lengths 0–512, all version bytes at valid lengths,
+padding variants, malformed encodings and old invalid fixtures. Source and dist
+were both exercised; `git diff --check` passed. Final committed test coverage and
+remote latest-head review are separate checks, recorded at completion.
+
+Two pre-existing transport-parity tests failed in the review-named checkout
+because their V2 fixtures omitted an explicit role. The repair makes them explorer
+like their paired V1 inputs, retaining the routing assertions. The production
+keyword inference quirk remains outside this repair.
+
+## Implementation integration
+
+Executor `01a0a0c3-aa3f-7070-ae55-530abed68915` authored the predicate and unit
+regression patch. Repeated tool-call quoting errors prevented it from applying
+the latter. Main stopped the worker, confirmed shutdown, preserved its source
+changes and recovered the authored patch from its failed call. Main integrated
+that patch, completed the planned e2e fixture alignment and added the missing
+whitespace/lookalike cases during review. No model fallback or successful worker
+completion is claimed; the unclassified dispatch failure remains recorded.
+The first integrated focused run passed 123 tests. The final suite includes the
+additional e2e short-lookalike case and three further negative table rows.
+
+The final independent review returned PASS with no findings on test coverage and
+raw native evidence. The first full run measured 3,162 tests and found one
+overstrict test assertion: the plaintext path already trims trailing whitespace,
+so its trailing-carriage-return case cannot demand ciphertext-style byte identity.
+Main changed that assertion to preserve task text while retaining every guard,
+affordance, prompt-override and omission-notice check. The corrected focused run
+passed all 124 tests. Generated inventory uses the measured total of 3,162.
+
+Final full-suite, published-head CI/review and local-install results are recorded
+outside the repository against the fixed commit: the session's producer receipt,
+PR conversation and the protected `cxc-pr177-review-20260914T163533Z` backup.
+Those later checks must pass before task completion; this record does not claim
+that a queued or historical remote review passed on a newer head.
diff --git a/plugins/codexclaw/components/pabcd-state/dist/hook.js b/plugins/codexclaw/components/pabcd-state/dist/hook.js
index 3b1a36bd..8b793da9 100644
--- a/plugins/codexclaw/components/pabcd-state/dist/hook.js
+++ b/plugins/codexclaw/components/pabcd-state/dist/hook.js
@@ -303,6 +303,8 @@ const PHASE_DIRECTIVES = {
"[codexclaw: PLAN]",
"Apply this pointer and its owners within exact user limits and permissions. No-delegation means no dispatch.",
"Load $codexclaw:cxc-pabcd for P and C2+ plan-output; $codexclaw:cxc-dev selects class and relevant surfaces. No implementation yet.",
+ "Formal P, including C2 compact and plan-only P: obtain the configured read-only architect proposal BEFORE the executable plan, send that concrete plan to the SAME architect for reflection BEFORE A, and record the consultation per $codexclaw:cxc-pabcd phase-plan/plan-output. The C0/C1 fast path needs none.",
+ "Record implementation ownership per planned change: a specified slice defaults to the configured executor, main needs a stated reason ($codexclaw:cxc-dev Implementation delegation).",
"Plan-only ends with the plan. Forbidden checks: NOT RUN; naming an artifact grants no write permission.",
].join("\n"),
A: [
@@ -310,11 +312,13 @@ const PHASE_DIRECTIVES = {
"Apply this pointer and its owners within exact user limits and permissions. No-delegation means no dispatch.",
"Load $codexclaw:cxc-dev-code-reviewer for review and $codexclaw:cxc-dev for relevant surfaces; authorized PABCD A uses $codexclaw:cxc-pabcd's audit owner. Do not build yet.",
"Authorized dispatch follows the owner's named-skill, same-reviewer and verdict contracts; main synthesizes. Report unmet independent review; inline review is not its proof. Do not bypass gates.",
+ "An amendment changing a module-responsibility, data-structure, interface or execution-flow decision needs reflection from the SAME architect before A completes ($codexclaw:cxc-pabcd phase-audit); text/test clarification alone does not. The reviewer stays independent.",
].join("\n"),
B: [
"[codexclaw: BUILD]",
"Apply this pointer and its owners within exact user limits and permissions. No-delegation means no dispatch.",
"Use $codexclaw:cxc-dev for class/surfaces; authorized PABCD B uses $codexclaw:cxc-pabcd. Implement only authorized scope.",
+ "Execute the plan's recorded implementation ownership; an unassigned slice needs a P amendment, and main verifies a returned diff rather than the report.",
"Forbidden checks: NOT RUN; no invented proof.",
].join("\n"),
C: [
diff --git a/plugins/codexclaw/components/pabcd-state/dist/orchestrate-cli.js b/plugins/codexclaw/components/pabcd-state/dist/orchestrate-cli.js
index 28e28528..4e5ee603 100644
--- a/plugins/codexclaw/components/pabcd-state/dist/orchestrate-cli.js
+++ b/plugins/codexclaw/components/pabcd-state/dist/orchestrate-cli.js
@@ -453,6 +453,22 @@ function hasPabcdCloseRow(
);
}
+/**
+ * 260914: the phase directive only reaches a turn through UserPromptSubmit, so a
+ * same-turn CLI progression (P -> ... -> B inside one turn) never sees it. Echo a
+ * one-line pointer on the two edges whose owner decision is about to be made or
+ * executed. Advice only — it changes no gate.
+ */
+const OWNERSHIP_HINT = {
+ P: "formal P: architect proposal -> main executable plan -> same-architect reflection before A (cxc-pabcd phase-plan); implementation ownership: record an owner per planned change (cxc-dev Implementation delegation)",
+ B: "implementation ownership: execute the plan's recorded owners; a new handoff needs a P amendment",
+};
+
+function withOwnershipHint(phase , output ) {
+ const hint = OWNERSHIP_HINT[phase];
+ return hint ? `${output} [${hint}]` : output;
+}
+
export function runOrchestrateCli(args , commitHooks = {}, nativeEnv = {}) {
if ("help" in args) return { code: 0, output: renderOrchestrateHelp() };
@@ -653,7 +669,7 @@ export function runOrchestrateCli(args
scanEvidence: { scanRounds: state.interview?.scanRounds ?? 0, highContradictionCount: gate.highContradictionCount },
...(args.attest?.did ? { evidence: args.attest.did } : {}),
});
- return { code: 0, output: `orchestrate P: I → P (agent override, session ${sessionId})` };
+ return { code: 0, output: withOwnershipHint("P", `orchestrate P: I → P (agent override, session ${sessionId})`) };
} else {
// Not ready and no override: advise-block with gate warnings.
return {
@@ -1118,7 +1134,7 @@ export function runOrchestrateCli(args
reason: "cli",
...(args.attest?.did ? { evidence: args.attest.did } : {}),
});
- return { code: 0, output: `orchestrate ${args.verb}: current=${state.phase} -> ${result.state.phase} (${state.phase} → ${result.state.phase}, session ${sessionId})` };
+ return { code: 0, output: withOwnershipHint(result.state.phase, `orchestrate ${args.verb}: current=${state.phase} -> ${result.state.phase} (${state.phase} → ${result.state.phase}, session ${sessionId})`) };
}
/**
* #48: candidate trees to check for the SAME session id. Deliberately shallow —
diff --git a/plugins/codexclaw/components/pabcd-state/src/hook.ts b/plugins/codexclaw/components/pabcd-state/src/hook.ts
index 153d5d98..be5449ef 100644
--- a/plugins/codexclaw/components/pabcd-state/src/hook.ts
+++ b/plugins/codexclaw/components/pabcd-state/src/hook.ts
@@ -303,6 +303,8 @@ const PHASE_DIRECTIVES: Partial> = {
"[codexclaw: PLAN]",
"Apply this pointer and its owners within exact user limits and permissions. No-delegation means no dispatch.",
"Load $codexclaw:cxc-pabcd for P and C2+ plan-output; $codexclaw:cxc-dev selects class and relevant surfaces. No implementation yet.",
+ "Formal P, including C2 compact and plan-only P: obtain the configured read-only architect proposal BEFORE the executable plan, send that concrete plan to the SAME architect for reflection BEFORE A, and record the consultation per $codexclaw:cxc-pabcd phase-plan/plan-output. The C0/C1 fast path needs none.",
+ "Record implementation ownership per planned change: a specified slice defaults to the configured executor, main needs a stated reason ($codexclaw:cxc-dev Implementation delegation).",
"Plan-only ends with the plan. Forbidden checks: NOT RUN; naming an artifact grants no write permission.",
].join("\n"),
A: [
@@ -310,11 +312,13 @@ const PHASE_DIRECTIVES: Partial> = {
"Apply this pointer and its owners within exact user limits and permissions. No-delegation means no dispatch.",
"Load $codexclaw:cxc-dev-code-reviewer for review and $codexclaw:cxc-dev for relevant surfaces; authorized PABCD A uses $codexclaw:cxc-pabcd's audit owner. Do not build yet.",
"Authorized dispatch follows the owner's named-skill, same-reviewer and verdict contracts; main synthesizes. Report unmet independent review; inline review is not its proof. Do not bypass gates.",
+ "An amendment changing a module-responsibility, data-structure, interface or execution-flow decision needs reflection from the SAME architect before A completes ($codexclaw:cxc-pabcd phase-audit); text/test clarification alone does not. The reviewer stays independent.",
].join("\n"),
B: [
"[codexclaw: BUILD]",
"Apply this pointer and its owners within exact user limits and permissions. No-delegation means no dispatch.",
"Use $codexclaw:cxc-dev for class/surfaces; authorized PABCD B uses $codexclaw:cxc-pabcd. Implement only authorized scope.",
+ "Execute the plan's recorded implementation ownership; an unassigned slice needs a P amendment, and main verifies a returned diff rather than the report.",
"Forbidden checks: NOT RUN; no invented proof.",
].join("\n"),
C: [
diff --git a/plugins/codexclaw/components/pabcd-state/src/orchestrate-cli.ts b/plugins/codexclaw/components/pabcd-state/src/orchestrate-cli.ts
index 43800ea3..4fd81d69 100644
--- a/plugins/codexclaw/components/pabcd-state/src/orchestrate-cli.ts
+++ b/plugins/codexclaw/components/pabcd-state/src/orchestrate-cli.ts
@@ -453,6 +453,22 @@ function hasPabcdCloseRow(
);
}
+/**
+ * 260914: the phase directive only reaches a turn through UserPromptSubmit, so a
+ * same-turn CLI progression (P -> ... -> B inside one turn) never sees it. Echo a
+ * one-line pointer on the two edges whose owner decision is about to be made or
+ * executed. Advice only — it changes no gate.
+ */
+const OWNERSHIP_HINT: Partial> = {
+ P: "formal P: architect proposal -> main executable plan -> same-architect reflection before A (cxc-pabcd phase-plan); implementation ownership: record an owner per planned change (cxc-dev Implementation delegation)",
+ B: "implementation ownership: execute the plan's recorded owners; a new handoff needs a P amendment",
+};
+
+function withOwnershipHint(phase: Phase, output: string): string {
+ const hint = OWNERSHIP_HINT[phase];
+ return hint ? `${output} [${hint}]` : output;
+}
+
export function runOrchestrateCli(args: OrchestrateCliArgs | OrchestrateCliHelpArgs, commitHooks: OrchestrateCommitHooks = {}, nativeEnv: NodeJS.ProcessEnv = {}): CliResult {
if ("help" in args) return { code: 0, output: renderOrchestrateHelp() };
@@ -653,7 +669,7 @@ export function runOrchestrateCli(args: OrchestrateCliArgs | OrchestrateCliHelpA
scanEvidence: { scanRounds: state.interview?.scanRounds ?? 0, highContradictionCount: gate.highContradictionCount },
...(args.attest?.did ? { evidence: args.attest.did } : {}),
});
- return { code: 0, output: `orchestrate P: I → P (agent override, session ${sessionId})` };
+ return { code: 0, output: withOwnershipHint("P", `orchestrate P: I → P (agent override, session ${sessionId})`) };
} else {
// Not ready and no override: advise-block with gate warnings.
return {
@@ -1118,7 +1134,7 @@ export function runOrchestrateCli(args: OrchestrateCliArgs | OrchestrateCliHelpA
reason: "cli",
...(args.attest?.did ? { evidence: args.attest.did } : {}),
});
- return { code: 0, output: `orchestrate ${args.verb}: current=${state.phase} -> ${result.state.phase} (${state.phase} → ${result.state.phase}, session ${sessionId})` };
+ return { code: 0, output: withOwnershipHint(result.state.phase, `orchestrate ${args.verb}: current=${state.phase} -> ${result.state.phase} (${state.phase} → ${result.state.phase}, session ${sessionId})`) };
}
/**
* #48: candidate trees to check for the SAME session id. Deliberately shallow —
diff --git a/plugins/codexclaw/components/pabcd-state/test/hook.test.ts b/plugins/codexclaw/components/pabcd-state/test/hook.test.ts
index 211d7451..bd3bd8df 100644
--- a/plugins/codexclaw/components/pabcd-state/test/hook.test.ts
+++ b/plugins/codexclaw/components/pabcd-state/test/hook.test.ts
@@ -116,11 +116,62 @@ test("wp3: phase pointers retain owners and active work-phase boundaries", () =>
assert.match(phaseDirective("P"), /No implementation yet/);
assert.match(phaseDirective("A"), /cxc-dev-code-reviewer/);
assert.match(phaseDirective("C"), /C-RENDER-GROUNDING-01/);
+ assert.match(phaseDirective("P"), /Record implementation ownership/);
+ assert.match(phaseDirective("B"), /recorded implementation ownership/);
const bound = phaseDirective("B", { activeWorkPhase: { id: "wp3", title: "minimal hooks" } });
assert.match(bound, /ACTIVE WORK-PHASE: wp3 — minimal hooks/);
assert.match(bound, /other work-phases are OUT OF SCOPE until D closes/);
});
+test("260914: P and A directives carry the architect consultation contract", () => {
+ const p = phaseDirective("P");
+ // Formal-P sequence: read-only architect proposal before the executable plan,
+ // the concrete plan back to the SAME architect for reflection before A, and a
+ // recorded consultation — inside the existing user-limit and executor wording.
+ assert.match(p, /read-only architect proposal BEFORE the executable plan/);
+ assert.match(p, /SAME architect for reflection BEFORE A/);
+ assert.match(p, /phase-plan\/plan-output/);
+ assert.match(p, /C0\/C1 fast path needs none/);
+ assert.match(p, /No-delegation means no dispatch/);
+ assert.match(p, /Record implementation ownership/);
+
+ const a = phaseDirective("A");
+ // A's reminder is the amendment recheck, distinct from P's initial sequence:
+ // only design-decision changes go back to the same architect.
+ assert.match(a, /module-responsibility, data-structure, interface or execution-flow/);
+ assert.match(a, /SAME architect before A completes/);
+ assert.match(a, /text\/test clarification alone does not/);
+ assert.match(a, /reviewer stays independent/i);
+ assert.doesNotMatch(a, /architect proposal BEFORE/i);
+
+ // No other phase carries an initial-consultation hint; B keeps executor ownership only.
+ for (const phase of ["B", "C", "D"] as const) {
+ assert.doesNotMatch(phaseDirective(phase), /architect/i, `${phase} directive`);
+ }
+ assert.doesNotMatch(interviewDirective(), /architect/i);
+});
+
+test("260914: hook P output carries the architect sequence; A output carries the amendment recheck", () => {
+ const cwd = freshCwd();
+ try {
+ const pOut = handleUserPromptSubmit(ups("orchestrate P", cwd, "arch-seq", "t1"));
+ assert.notEqual(pOut, "");
+ const pCtx = JSON.parse(pOut.trimEnd()).hookSpecificOutput.additionalContext;
+ assert.match(pCtx, /read-only architect proposal BEFORE the executable plan/);
+ assert.match(pCtx, /SAME architect for reflection BEFORE A/);
+ assert.match(pCtx, /Record implementation ownership/);
+
+ const aOut = handleUserPromptSubmit(ups("orchestrate a", cwd, "arch-seq", "t2"));
+ assert.notEqual(aOut, "");
+ const aCtx = JSON.parse(aOut.trimEnd()).hookSpecificOutput.additionalContext;
+ assert.match(aCtx, /SAME architect before A completes/);
+ assert.match(aCtx, /text\/test clarification alone does not/);
+ assert.doesNotMatch(aCtx, /architect proposal BEFORE/i);
+ } finally {
+ rmSync(cwd, { recursive: true, force: true });
+ }
+});
+
const WP3_ORIGINAL_C2_PROMPT = "README 계약에 맞게 기존 내부 메모 생성/목록 기능을 완성해줘. 네트워크 서버나 공개 API는 아니고 src/route.mjs와 src/service.mjs의 기존 빈 구현을 채우는 작업이야. src/store.mjs와 test/notes.test.mjs는 수정하지 마. 기존 번호 문서에 결과를 기록하고 node --test test/notes.test.mjs로 실제 검증해줘. 새 의존성/추상화/파일, goal/FSM 변경, 커밋, 서브에이전트 파견은 하지 마.";
test("wp3: original Korean C2 still reaches scoped CHECK without entering C", () => {
diff --git a/plugins/codexclaw/components/pabcd-state/test/orchestrate-cli.test.ts b/plugins/codexclaw/components/pabcd-state/test/orchestrate-cli.test.ts
index c3364881..9e44a5d0 100644
--- a/plugins/codexclaw/components/pabcd-state/test/orchestrate-cli.test.ts
+++ b/plugins/codexclaw/components/pabcd-state/test/orchestrate-cli.test.ts
@@ -2683,3 +2683,85 @@ test("wp7 preservation: CLI D-close keeps dependsOn and outcome", () => {
assert.equal(saved.workPhases[1].status, "in_progress");
assert.deepEqual(taskFields(saved), expectedTaskFields);
});
+
+test("260914: P and B entry echo the implementation-ownership pointer; other verbs do not", () => {
+ const cwd = freshCwd(); // not a repo: captureSourceIdentity is "unavailable", so B>C is not delta-gated
+ try {
+ const id = "wp3-hint";
+ seedSession(cwd, id, "IDLE");
+
+ // IDLE -> P carries the pointer.
+ const toP = runOrchestrateCli({ verb: "P", attest: null, session: id, cwd, json: false });
+ assert.equal(toP.code, 0, toP.output);
+ assert.equal(readState(cwd, id).phase, "P");
+ assert.match(toP.output, /implementation ownership/);
+ assert.match(toP.output, /architect proposal -> main executable plan -> same-architect reflection before A/);
+
+ // status is read-only and must not echo the pointer while parked at P.
+ const statusAtP = runOrchestrateCli({ verb: "status", attest: null, session: id, cwd, json: false });
+ assert.equal(statusAtP.code, 0, statusAtP.output);
+ assert.equal(readState(cwd, id).phase, "P");
+ assert.doesNotMatch(statusAtP.output, /implementation ownership/);
+ assert.doesNotMatch(statusAtP.output, /architect/i);
+
+ // P -> A does not carry the pointer.
+ const planUnit = seedPlanUnit(cwd);
+ const toA = runOrchestrateCli({ verb: "A", attest: { from: "P", to: "A", did: "audited", planUnit }, session: id, cwd, json: false });
+ assert.equal(toA.code, 0, toA.output);
+ assert.equal(readState(cwd, id).phase, "A");
+ assert.doesNotMatch(toA.output, /implementation ownership/);
+ assert.doesNotMatch(toA.output, /architect/i);
+
+ // A -> B carries the pointer.
+ const toB = runOrchestrateCli({
+ verb: "B",
+ attest: { from: "A", to: "B", did: "audit folded back", auditOutput: "reviewer: GO; refs verified", auditVerdict: "pass" },
+ session: id, cwd, json: false,
+ });
+ assert.equal(toB.code, 0, toB.output);
+ assert.equal(readState(cwd, id).phase, "B");
+ assert.match(toB.output, /implementation ownership/);
+ assert.doesNotMatch(toB.output, /architect/i);
+
+ // status at B stays clean too.
+ const statusAtB = runOrchestrateCli({ verb: "status", attest: null, session: id, cwd, json: false });
+ assert.equal(statusAtB.code, 0, statusAtB.output);
+ assert.equal(readState(cwd, id).phase, "B");
+ assert.doesNotMatch(statusAtB.output, /implementation ownership/);
+ assert.doesNotMatch(statusAtB.output, /architect/i);
+
+ // B -> C does not carry the pointer.
+ const toC = runOrchestrateCli({ verb: "C", attest: { from: "B", to: "C", did: "implemented the slice" }, session: id, cwd, json: false });
+ assert.equal(toC.code, 0, toC.output);
+ assert.equal(readState(cwd, id).phase, "C");
+ assert.doesNotMatch(toC.output, /implementation ownership/);
+ assert.doesNotMatch(toC.output, /architect/i);
+
+ // C -> D closes to IDLE and does not carry the pointer.
+ const toD = runOrchestrateCli({ verb: "D", attest: { from: "C", to: "D", did: "checks passed", checkOutput: "tests 1 pass 1", exitCode: 0 }, session: id, cwd, json: false });
+ assert.equal(toD.code, 0, toD.output);
+ assert.equal(readState(cwd, id).phase, "IDLE");
+ assert.doesNotMatch(toD.output, /implementation ownership/);
+ assert.doesNotMatch(toD.output, /architect/i);
+ } finally { rmSync(cwd, { recursive: true, force: true }); }
+});
+
+test("260914: I->P agent override echoes the implementation-ownership pointer", () => {
+ const cwd = freshCwd();
+ try {
+ // Same unready-interview fixture as the override tests above (line ~615).
+ seedSession(cwd, "s1", "I");
+ const r = runOrchestrateCli({
+ verb: "P",
+ attest: { from: "I", to: "P", did: "interview done", override: true },
+ session: "s1",
+ cwd,
+ json: false,
+ });
+ assert.equal(r.code, 0, r.output);
+ assert.equal(readState(cwd, "s1").phase, "P");
+ assert.match(r.output, /agent override/);
+ assert.match(r.output, /implementation ownership/);
+ assert.match(r.output, /architect proposal -> main executable plan -> same-architect reflection before A/);
+ } finally { rmSync(cwd, { recursive: true, force: true }); }
+});
diff --git a/plugins/codexclaw/components/subagent-config/dist/spawn-attach-hook.js b/plugins/codexclaw/components/subagent-config/dist/spawn-attach-hook.js
index 5c661cff..0b9daaaa 100644
--- a/plugins/codexclaw/components/subagent-config/dist/spawn-attach-hook.js
+++ b/plugins/codexclaw/components/subagent-config/dist/spawn-attach-hook.js
@@ -23,12 +23,12 @@
* recognized cxc mention's SKILL.md body to the message. Atomic overflow rule:
* if the normalized message plus ALL candidate bodies would exceed
* MAX_NORMALIZE_LENGTH, no bodies are appended (never truncated/partial).
- * ENCRYPTION LIMIT (live-proven 260710, devlog 260710_v1_v2_parity/080): on
- * native ChatGPT-backend V2 sessions the hook receives `message` as backend
- * ciphertext, so normalization/inlining are silent no-ops there — the
- * hook-borne channels that survive encryption are the plaintext-prepended
- * leaf guard and the plaintext model/reasoning_effort fields. Inlining works
- * on plaintext surfaces (v1, non-encrypted provider/proxy paths).
+ * ENCRYPTION LIMIT: native V2 messages can be backend ciphertext. Preserve
+ * those bytes: plaintext guards, skill bodies or prompt overrides in that
+ * encrypted slot make the backend reject the child's task. Metadata-based
+ * recursion denial and separate model/effort fields still apply. Message
+ * augmentation is available only on plaintext surfaces; disclose the gap
+ * to the caller rather than claiming those instructions reached the child.
*
* SAFETY: `updatedInput` is a FULL REPLACEMENT of tool_input (registry.rs:122),
* honored only on permissionDecision "allow" (output_parser.rs:162). We echo the
@@ -534,6 +534,41 @@ export function isV2SpawnInput(toolInput ) {
return "task_name" in toolInput || "fork_turns" in toolInput;
}
+/**
+ * Structural recognition of a native Fernet task envelope. The wire form is
+ * base64url(version || timestamp || IV || ciphertext || HMAC) = 57 + 16n bytes
+ * with n >= 1, version 0x80. This is a SHAPE check, never authentication: the
+ * hook holds no key and cannot verify the HMAC, so it accepts both the
+ * canonical padded and the wholly unpadded base64url encodings of a well-formed
+ * frame. A strict decode/re-encode comparison rejects bad alphabet, embedded
+ * whitespace, impossible encoded lengths, partial or excess padding, and
+ * nonzero unused pad bits — every rejected input is ordinary plaintext and
+ * keeps its guards. No `gAAAA` prefix, timestamp, or MAC requirement: prefix
+ * resemblance alone must never strip plaintext attachment.
+ */
+function isFernetTokenShape(token ) {
+ const firstPad = token.indexOf("=");
+ const core = firstPad === -1 ? token : token.slice(0, firstPad);
+ // Padding is legal only as a trailing run.
+ if (firstPad !== -1 && !/^=+$/.test(token.slice(firstPad))) return false;
+ const rem = core.length % 4;
+ if (firstPad === -1) {
+ // Entirely unpadded: a %4==1 core is an impossible base64 length.
+ if (rem === 1) return false;
+ } else {
+ // Canonical padding only: exactly the count that rounds the core to a
+ // 4-char block. rem 0 or 1 can never take padding.
+ if (rem < 2 || token.length - core.length !== 4 - rem) return false;
+ }
+ if (!/^[A-Za-z0-9_-]+$/.test(core)) return false;
+ const decoded = Buffer.from(core, "base64url");
+ // Re-encode catches nonzero unused pad bits and any lenient-decode drift.
+ if (decoded.toString("base64url") !== core) return false;
+ // version(1) + timestamp(8) + IV(16) + ciphertext(16n, n>=1) + HMAC(32).
+ if (decoded.length < 73 || (decoded.length - 57) % 16 !== 0) return false;
+ return decoded[0] === 0x80;
+}
+
/**
* Hook-facing spawn tool names across surfaces: plain/V1 canonicalizes to
* `spawn_agent`; native V2 rides the `collaboration` namespace and reaches hooks
@@ -848,6 +883,7 @@ export function runSpawnAttachHook(raw ) {
// Keep the native one-of shape. Attachment-only requests still need routing.
const message = validItems ? outgoing : toolInput.message;
if (typeof message !== "string" || (!validItems && message.trim().length === 0)) return "";
+ const encryptedV2Message = v2Spawn && isFernetTokenShape(message);
const cwd = typeof obj.cwd === "string" && obj.cwd.length > 0 ? obj.cwd : process.cwd();
const dispatchScan = validItems
? textItems.map(item => scanInlineSkillBlocks(item.text).scanSource).join("\n\n")
@@ -913,11 +949,9 @@ export function runSpawnAttachHook(raw ) {
// (WP2 live bug: doc-quoted markers poisoned raw includes()).
const markerScanSource = scanInlineSkillBlocks(inlinedMessage).scanSource;
- // WP2 cr3 — V2 affordance: when inlining attached nothing (encrypted native
- // path, or no plaintext mentions), append the plaintext self-load instruction
- // so the child can resolve mentions itself. Marker-deduped; size-guarded;
- // never on v1 (upstream parses mentions there). Zero-mention plaintext V2
- // also gets it — deliberate small overhead (090_plan).
+ // V2 plaintext without inlined bodies gets a self-load instruction.
+ // Marker-deduped and size-guarded; the ciphertext boundary below discards
+ // all generated text for encrypted messages and discloses that omission.
let affordanceMessage = inlinedMessage;
if (
v2Spawn &&
@@ -1010,8 +1044,11 @@ export function runSpawnAttachHook(raw ) {
}
}
}
- const promptChanged = injectedPrompt !== null;
+ const promptChanged = !encryptedV2Message && injectedPrompt !== null;
if (trustPrefix) evidenceExemptMessage = `${trustPrefix}${evidenceExemptMessage}`;
+ // The native backend treats this whole value as ciphertext. Keep D1 and
+ // routing above, but never put our plaintext inside its encrypted slot.
+ if (encryptedV2Message) evidenceExemptMessage = message;
const updatedItems = mappedItems ? [...mappedItems] : null;
if (updatedItems) {
if (firstText < 0) updatedItems.unshift({ type: "text", text: evidenceExemptMessage });
@@ -1042,7 +1079,10 @@ export function runSpawnAttachHook(raw ) {
const fallbackNotice = !managed && readConfig(cwd).roles[role].fallback
? `[codexclaw] This direct spawn is not managed by first-fallback tracking. For subsequent tasks: ${DISPATCH_GUIDANCE}` : null;
- if (!managed && !fallbackNotice && !messageChanged && injectedModel === null && injectedEffort === null) return "";
+ const additionalContext = [fallbackNotice, encryptedV2Message
+ ? `[codexclaw] Native V2 task ciphertext was preserved. Hook-added skill text, scope instructions and prompt overrides were not attached; native recursion checks and separate routing fields still apply.${resolution.trustWarning ? ` ${resolution.trustWarning}` : ""}`
+ : null].filter(Boolean).join("\n");
+ if (!managed && !additionalContext && !messageChanged && injectedModel === null && injectedEffort === null) return "";
// Full replacement preserves whichever native input form the caller chose.
const updatedInput = updatedItems
@@ -1064,7 +1104,7 @@ export function runSpawnAttachHook(raw ) {
hookEventName: "PreToolUse",
permissionDecision: "allow",
updatedInput,
- ...(fallbackNotice ? { additionalContext: fallbackNotice } : {}),
+ ...(additionalContext ? { additionalContext } : {}),
},
})}\n`;
} catch {
diff --git a/plugins/codexclaw/components/subagent-config/src/spawn-attach-hook.ts b/plugins/codexclaw/components/subagent-config/src/spawn-attach-hook.ts
index 1f1d734a..23980a3e 100644
--- a/plugins/codexclaw/components/subagent-config/src/spawn-attach-hook.ts
+++ b/plugins/codexclaw/components/subagent-config/src/spawn-attach-hook.ts
@@ -23,12 +23,12 @@
* recognized cxc mention's SKILL.md body to the message. Atomic overflow rule:
* if the normalized message plus ALL candidate bodies would exceed
* MAX_NORMALIZE_LENGTH, no bodies are appended (never truncated/partial).
- * ENCRYPTION LIMIT (live-proven 260710, devlog 260710_v1_v2_parity/080): on
- * native ChatGPT-backend V2 sessions the hook receives `message` as backend
- * ciphertext, so normalization/inlining are silent no-ops there — the
- * hook-borne channels that survive encryption are the plaintext-prepended
- * leaf guard and the plaintext model/reasoning_effort fields. Inlining works
- * on plaintext surfaces (v1, non-encrypted provider/proxy paths).
+ * ENCRYPTION LIMIT: native V2 messages can be backend ciphertext. Preserve
+ * those bytes: plaintext guards, skill bodies or prompt overrides in that
+ * encrypted slot make the backend reject the child's task. Metadata-based
+ * recursion denial and separate model/effort fields still apply. Message
+ * augmentation is available only on plaintext surfaces; disclose the gap
+ * to the caller rather than claiming those instructions reached the child.
*
* SAFETY: `updatedInput` is a FULL REPLACEMENT of tool_input (registry.rs:122),
* honored only on permissionDecision "allow" (output_parser.rs:162). We echo the
@@ -534,6 +534,41 @@ export function isV2SpawnInput(toolInput: Record): boolean {
return "task_name" in toolInput || "fork_turns" in toolInput;
}
+/**
+ * Structural recognition of a native Fernet task envelope. The wire form is
+ * base64url(version || timestamp || IV || ciphertext || HMAC) = 57 + 16n bytes
+ * with n >= 1, version 0x80. This is a SHAPE check, never authentication: the
+ * hook holds no key and cannot verify the HMAC, so it accepts both the
+ * canonical padded and the wholly unpadded base64url encodings of a well-formed
+ * frame. A strict decode/re-encode comparison rejects bad alphabet, embedded
+ * whitespace, impossible encoded lengths, partial or excess padding, and
+ * nonzero unused pad bits — every rejected input is ordinary plaintext and
+ * keeps its guards. No `gAAAA` prefix, timestamp, or MAC requirement: prefix
+ * resemblance alone must never strip plaintext attachment.
+ */
+function isFernetTokenShape(token: string): boolean {
+ const firstPad = token.indexOf("=");
+ const core = firstPad === -1 ? token : token.slice(0, firstPad);
+ // Padding is legal only as a trailing run.
+ if (firstPad !== -1 && !/^=+$/.test(token.slice(firstPad))) return false;
+ const rem = core.length % 4;
+ if (firstPad === -1) {
+ // Entirely unpadded: a %4==1 core is an impossible base64 length.
+ if (rem === 1) return false;
+ } else {
+ // Canonical padding only: exactly the count that rounds the core to a
+ // 4-char block. rem 0 or 1 can never take padding.
+ if (rem < 2 || token.length - core.length !== 4 - rem) return false;
+ }
+ if (!/^[A-Za-z0-9_-]+$/.test(core)) return false;
+ const decoded = Buffer.from(core, "base64url");
+ // Re-encode catches nonzero unused pad bits and any lenient-decode drift.
+ if (decoded.toString("base64url") !== core) return false;
+ // version(1) + timestamp(8) + IV(16) + ciphertext(16n, n>=1) + HMAC(32).
+ if (decoded.length < 73 || (decoded.length - 57) % 16 !== 0) return false;
+ return decoded[0] === 0x80;
+}
+
/**
* Hook-facing spawn tool names across surfaces: plain/V1 canonicalizes to
* `spawn_agent`; native V2 rides the `collaboration` namespace and reaches hooks
@@ -848,6 +883,7 @@ export function runSpawnAttachHook(raw: string): string {
// Keep the native one-of shape. Attachment-only requests still need routing.
const message = validItems ? outgoing : toolInput.message;
if (typeof message !== "string" || (!validItems && message.trim().length === 0)) return "";
+ const encryptedV2Message = v2Spawn && isFernetTokenShape(message);
const cwd = typeof obj.cwd === "string" && obj.cwd.length > 0 ? obj.cwd : process.cwd();
const dispatchScan = validItems
? textItems.map(item => scanInlineSkillBlocks(item.text).scanSource).join("\n\n")
@@ -913,11 +949,9 @@ export function runSpawnAttachHook(raw: string): string {
// (WP2 live bug: doc-quoted markers poisoned raw includes()).
const markerScanSource = scanInlineSkillBlocks(inlinedMessage).scanSource;
- // WP2 cr3 — V2 affordance: when inlining attached nothing (encrypted native
- // path, or no plaintext mentions), append the plaintext self-load instruction
- // so the child can resolve mentions itself. Marker-deduped; size-guarded;
- // never on v1 (upstream parses mentions there). Zero-mention plaintext V2
- // also gets it — deliberate small overhead (090_plan).
+ // V2 plaintext without inlined bodies gets a self-load instruction.
+ // Marker-deduped and size-guarded; the ciphertext boundary below discards
+ // all generated text for encrypted messages and discloses that omission.
let affordanceMessage = inlinedMessage;
if (
v2Spawn &&
@@ -1010,8 +1044,11 @@ export function runSpawnAttachHook(raw: string): string {
}
}
}
- const promptChanged = injectedPrompt !== null;
+ const promptChanged = !encryptedV2Message && injectedPrompt !== null;
if (trustPrefix) evidenceExemptMessage = `${trustPrefix}${evidenceExemptMessage}`;
+ // The native backend treats this whole value as ciphertext. Keep D1 and
+ // routing above, but never put our plaintext inside its encrypted slot.
+ if (encryptedV2Message) evidenceExemptMessage = message;
const updatedItems = mappedItems ? [...mappedItems] : null;
if (updatedItems) {
if (firstText < 0) updatedItems.unshift({ type: "text", text: evidenceExemptMessage });
@@ -1042,7 +1079,10 @@ export function runSpawnAttachHook(raw: string): string {
const fallbackNotice = !managed && readConfig(cwd).roles[role].fallback
? `[codexclaw] This direct spawn is not managed by first-fallback tracking. For subsequent tasks: ${DISPATCH_GUIDANCE}` : null;
- if (!managed && !fallbackNotice && !messageChanged && injectedModel === null && injectedEffort === null) return "";
+ const additionalContext = [fallbackNotice, encryptedV2Message
+ ? `[codexclaw] Native V2 task ciphertext was preserved. Hook-added skill text, scope instructions and prompt overrides were not attached; native recursion checks and separate routing fields still apply.${resolution.trustWarning ? ` ${resolution.trustWarning}` : ""}`
+ : null].filter(Boolean).join("\n");
+ if (!managed && !additionalContext && !messageChanged && injectedModel === null && injectedEffort === null) return "";
// Full replacement preserves whichever native input form the caller chose.
const updatedInput: Record = updatedItems
@@ -1064,7 +1104,7 @@ export function runSpawnAttachHook(raw: string): string {
hookEventName: "PreToolUse",
permissionDecision: "allow",
updatedInput,
- ...(fallbackNotice ? { additionalContext: fallbackNotice } : {}),
+ ...(additionalContext ? { additionalContext } : {}),
},
})}\n`;
} catch {
diff --git a/plugins/codexclaw/components/subagent-config/test/spawn-attach-hook.test.ts b/plugins/codexclaw/components/subagent-config/test/spawn-attach-hook.test.ts
index 93e5668d..0affce69 100644
--- a/plugins/codexclaw/components/subagent-config/test/spawn-attach-hook.test.ts
+++ b/plugins/codexclaw/components/subagent-config/test/spawn-attach-hook.test.ts
@@ -971,7 +971,10 @@ test("same-intent v1/v2 spawns produce surface-appropriate effective payloads",
runSpawnAttachHook(spawnPayloadAt(cwd, { message: intent, agent_type: "explorer" })),
);
const v2 = updatedInputOf(
- runSpawnAttachHook(spawnPayloadAt(cwd, { task_name: "map_fe", fork_turns: "none", message: intent })),
+ // Explicit agent_type: the normalized mention link carries the checkout path,
+ // and a "delegation-review"-style path would otherwise hit legacy review-keyword
+ // inference. v1 already pins explorer; keep the surfaces comparable.
+ runSpawnAttachHook(spawnPayloadAt(cwd, { task_name: "map_fe", fork_turns: "none", message: intent, agent_type: "explorer" })),
);
// Same model + effort routing on both surfaces.
assert.equal(v1.model, v2.model);
@@ -995,19 +998,161 @@ test("v2 affordance: appended only when inlining attached nothing", () => {
);
assert.ok((inlined.message as string).includes(`${INLINE_SKILL_OPEN}dev">`));
assert.ok(!(inlined.message as string).includes(affordanceOpening));
- // No mentions (ciphertext-like opaque text) -> affordance appended after the task text.
+ // Plaintext without mentions still gets the self-load affordance.
const opaque = updatedInputOf(
- runSpawnAttachHook(spawnPayload({ task_name: "t", fork_turns: "none", message: "gAAAAABopaquetoken" })),
+ runSpawnAttachHook(spawnPayload({ task_name: "t", fork_turns: "none", message: "Inspect the catalog module." })),
);
assert.ok((opaque.message as string).includes(SKILL_AFFORDANCE_MARKER));
assert.ok((opaque.message as string).startsWith(`${LEAF_GUARD_BLOCK}\n\n`), "guard stays first");
assert.ok(
- (opaque.message as string).indexOf("gAAAAABopaquetoken") <
+ (opaque.message as string).indexOf("Inspect the catalog module.") <
(opaque.message as string).indexOf(SKILL_AFFORDANCE_MARKER),
"affordance rides after the task text",
);
});
+// Public Fernet generate.json "hello" vector: a real envelope wire form, no key.
+const FERNET_VECTOR = "gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==";
+
+// Synthetic Fernet-shaped frame: version(1) || timestamp(8) || IV(16) ||
+// ciphertext(ciphertextBytes) || HMAC(32). Structural fixture only — the bytes
+// are not authenticated and carry no key material.
+function fernetFrame(options: { version?: number; timestamp?: Uint8Array; ciphertextBytes?: number } = {}) {
+ return Buffer.concat([
+ Buffer.from([options.version ?? 0x80]),
+ options.timestamp ?? Buffer.alloc(8, 0),
+ Buffer.alloc(16, 0x11),
+ Buffer.alloc(options.ciphertextBytes ?? 16, 0x22),
+ Buffer.alloc(32, 0x33),
+ ]);
+}
+
+// Canonical padded or wholly unpadded base64url wire form of a frame.
+function fernetToken(frame: Uint8Array, padded = true): string {
+ const unpadded = Buffer.from(frame).toString("base64url");
+ return padded ? `${unpadded}${"=".repeat((4 - (unpadded.length % 4)) % 4)}` : unpadded;
+}
+
+const CIPHERTEXT_NOTICE = /ciphertext was preserved/;
+
+test("native V2 ciphertext survives routing and prompt overrides byte-for-byte", () => {
+ const message = FERNET_VECTOR;
+ const cwd = workspaceWithConfig({
+ architect: { mode: "model", model: "architect-fixture", effort: "high", promptOverride: "Architect-only instructions" },
+ });
+ for (const tool_name of ["spawn_agent", "collaborationspawn_agent"]) {
+ const input = { task_name: "design", agent_type: "architect", fork_turns: "none", message };
+ const payload = { ...JSON.parse(spawnPayloadAt(cwd, input)), tool_name };
+ const output = runSpawnAttachHook(JSON.stringify(payload));
+ const updated = updatedInputOf(output);
+ assert.deepEqual(updated, { ...input, model: "architect-fixture", reasoning_effort: "high" });
+ assert.match(JSON.parse(output).hookSpecificOutput.additionalContext, /prompt overrides were not attached/);
+ }
+});
+
+test("native V2 ciphertext preserves explicit settings and full-history fork restrictions", () => {
+ const message = FERNET_VECTOR;
+ const cwd = workspaceWithConfig({
+ architect: { mode: "model", model: "configured-fixture", effort: "high", promptOverride: "Architect-only instructions" },
+ });
+ for (const fields of [
+ { fork_turns: "none", model: "caller-fixture", reasoning_effort: "low" },
+ { fork_turns: "all" },
+ ]) {
+ const input = { task_name: "design", agent_type: "architect", message, ...fields };
+ const out = runSpawnAttachHook(spawnPayloadAt(cwd, input));
+ assert.deepEqual(out ? updatedInputOf(out) : input, input);
+ }
+});
+
+test("native V2 ciphertext cannot bypass the existing recursion denial", () => {
+ const message = FERNET_VECTOR;
+ const result = JSON.parse(runSpawnAttachHook(subagentSpawnPayload({ task_name: "nested", message })));
+ assert.equal(result.hookSpecificOutput.permissionDecision, "deny");
+ assert.equal(result.hookSpecificOutput.updatedInput, undefined);
+});
+
+test("valid Fernet frames stay byte-identical across padding forms and block counts", () => {
+ const variants: [string, string][] = [
+ ["reference vector (canonical padded)", FERNET_VECTOR],
+ ["reference vector (unpadded)", FERNET_VECTOR.slice(0, -2)],
+ ];
+ // n=3 frames fill a whole base64 group, so their padded and unpadded wire
+ // forms coincide; both entries still exercise the accepted shape.
+ for (const blocks of [1, 2, 3]) {
+ const frame = fernetFrame({ ciphertextBytes: 16 * blocks });
+ variants.push([`n=${blocks} padded`, fernetToken(frame)], [`n=${blocks} unpadded`, fernetToken(frame, false)]);
+ }
+ // A timestamp whose bytes do not encode the old gAAAA prefix must still be
+ // recognized: the classifier checks structure, not the prefix.
+ const noPrefix = fernetToken(fernetFrame({ timestamp: Buffer.alloc(8, 0xff) }));
+ assert.ok(!noPrefix.startsWith("gAAAA"), "fixture must actually lack the legacy prefix");
+ variants.push(["non-gAAAA timestamp", noPrefix]);
+ for (const [label, message] of variants) {
+ const out = runSpawnAttachHook(spawnPayload({ task_name: "t", fork_turns: "none", message }));
+ const parsed = JSON.parse(out);
+ assert.equal(parsed.hookSpecificOutput.permissionDecision, "allow", label);
+ assert.equal(parsed.hookSpecificOutput.updatedInput.message, message, `${label}: byte-identical`);
+ assert.match(parsed.hookSpecificOutput.additionalContext ?? "", CIPHERTEXT_NOTICE, label);
+ }
+});
+
+test("malformed V2 ciphertext lookalikes keep the guard, affordance and configured prompt", () => {
+ const cwd = workspaceWithConfig({
+ architect: { mode: "model", model: "architect-fixture", effort: "high", promptOverride: "Architect-only instructions" },
+ });
+ const core = FERNET_VECTOR.slice(0, -2);
+ const malformed: [string, string][] = [
+ ["short gAAAA prefix", "gAAAAx"],
+ ["old e2e fixture", "gAAAAABopaque-payload"],
+ ["impossible base64 length", "gAAAA"],
+ ["old invalid fixture", `gAAAAAB${"aB9_".repeat(30)}==`],
+ ["wrong version byte", fernetToken(fernetFrame({ version: 0x81 }))],
+ ["truncated frame", FERNET_VECTOR.slice(0, 80)],
+ ["empty ciphertext", fernetToken(fernetFrame({ ciphertextBytes: 0 }))],
+ ["non-16-byte-block ciphertext", fernetToken(fernetFrame({ ciphertextBytes: 24 }))],
+ ["embedded whitespace", `${core.slice(0, 40)} ${core.slice(40)}==`],
+ ["standard-alphabet characters", FERNET_VECTOR.replaceAll("_", "/")],
+ ["partial padding", `${core}=`],
+ ["excess padding", `${FERNET_VECTOR}=`],
+ ["trailing newline", `${FERNET_VECTOR}\n`],
+ ["padding with carriage return", `${core}=\r`],
+ ["mid-string padding", `${core.slice(0, 20)}=${core.slice(20)}==`],
+ ["nonzero unused pad bits", `${core.slice(0, -1)}B==`],
+ ];
+ for (const [label, message] of malformed) {
+ const out = runSpawnAttachHook(spawnPayloadAt(cwd, { task_name: "t", fork_turns: "none", agent_type: "architect", message }));
+ const parsed = JSON.parse(out);
+ assert.equal(parsed.hookSpecificOutput.permissionDecision, "allow", label);
+ const ui = parsed.hookSpecificOutput.updatedInput;
+ assert.ok((ui.message as string).startsWith(`${LEAF_GUARD_BLOCK}\n\n`), `${label}: guard attached`);
+ assert.ok((ui.message as string).includes("Architect-only instructions"), `${label}: prompt override attached`);
+ assert.ok((ui.message as string).includes(SKILL_AFFORDANCE_MARKER), `${label}: plaintext affordance attached`);
+ // Plaintext attachment already trims trailing whitespace; only ciphertext
+ // has the byte-for-byte preservation contract.
+ assert.ok((ui.message as string).includes(message.trimEnd()), `${label}: task text preserved`);
+ assert.doesNotMatch(parsed.hookSpecificOutput.additionalContext ?? "", CIPHERTEXT_NOTICE, label);
+ }
+});
+
+test("v1 items carrying a Fernet-shaped token stay on the normal attachment path", () => {
+ const out = runSpawnAttachHook(spawnPayload({
+ agent_type: "explorer",
+ items: [
+ { type: "text", text: FERNET_VECTOR },
+ { type: "attachment", ref: "fixture-1" },
+ ],
+ }));
+ const parsed = JSON.parse(out);
+ const ui = parsed.hookSpecificOutput.updatedInput;
+ assert.ok(Array.isArray(ui.items));
+ const [first, second] = ui.items as Record[];
+ assert.ok((first.text as string).startsWith(`${V1_SCOPE_BLOCK}\n\n`));
+ assert.ok((first.text as string).includes(FERNET_VECTOR));
+ assert.deepEqual(second, { type: "attachment", ref: "fixture-1" });
+ assert.doesNotMatch(parsed.hookSpecificOutput.additionalContext ?? "", CIPHERTEXT_NOTICE);
+});
+
test("v1 spawns never get the affordance (upstream parses mentions there)", () => {
const ui = updatedInputOf(runSpawnAttachHook(spawnPayload({ message: "no mentions here", agent_type: "explorer" })));
assert.ok(!(ui.message as string).includes(SKILL_AFFORDANCE_MARKER));
@@ -1174,4 +1319,3 @@ test("explicit executor and reviewer roles take precedence over message keywords
assert.equal(inferRole("executor", "review the implementation"), "executor");
assert.equal(inferRole("reviewer", "inspect correctness"), "reviewer");
});
-
diff --git a/plugins/codexclaw/skills/dev/SKILL.md b/plugins/codexclaw/skills/dev/SKILL.md
index ba8b7341..dfbbac6c 100644
--- a/plugins/codexclaw/skills/dev/SKILL.md
+++ b/plugins/codexclaw/skills/dev/SKILL.md
@@ -203,6 +203,31 @@ Confirm actual model routing from runtime evidence when reporting identity or co
When comparing cost, use main plus child served-model input/cache/output prices and
applicable tiers; token totals alone cannot compare differently priced models.
+### Implementation delegation
+
+Once a slice is specified — goal, editable files, the contract it must preserve
+and the check that proves it — assign it to the configured `executor` by
+default. Sequential work qualifies: a dependency order decides the order of the
+packets, not who types the diff. Main keeps the specification, the load-bearing
+judgment, the review of the returned diff and the integration.
+
+Implementing a specified slice locally needs a stated reason, and only three
+hold by default: the edit is small enough that packaging it costs more than
+making it (§0.1), a load-bearing judgment inside the slice is still open so the
+packet cannot state its decision boundary, or handoff plus re-work measurably
+exceeds the delegated work. "It is sequential", "I already understand it" and
+"typing it is faster" are not reasons; neither is read-only-style parallelism,
+which is a separate question from who implements.
+
+Delegate one verifiable bundle — the slice a single check can prove — rather
+than one packet per file. P records the owner of each planned change and B
+executes that assignment (`../pabcd/references/plan-output.md`); handing a new
+slice to a subagent mid-B still requires a P amendment. Model, effort, fallback
+and receipts stay with the configured dispatch path. Verify a returned
+implementation against the VCS diff, never the report alone (§3). No-delegation
+limits, host restrictions and DISPATCH-ECONOMY-01's specifiability /
+verifiability / judgment axes take precedence.
+
### Capability Routing Hub
**Independent peers:** keep work local and use selective read-only evidence when
diff --git a/plugins/codexclaw/skills/pabcd/SKILL.md b/plugins/codexclaw/skills/pabcd/SKILL.md
index 49675ddb..499fc1d4 100644
--- a/plugins/codexclaw/skills/pabcd/SKILL.md
+++ b/plugins/codexclaw/skills/pabcd/SKILL.md
@@ -69,7 +69,7 @@ link is a conditional routing edge, not a command to preload the entire graph.
| Phase / trigger | Mandatory owner before work |
|---|---|
| I | cxc-interview; no active host goal |
-| P, including plan-only | [Plan phase](references/phase-plan.md); C2+ plans also read [Plan output](references/plan-output.md) |
+| P, including plan-only | [Plan phase](references/phase-plan.md): architect proposal → main executable plan → same-architect reflection; C2+ plans also read [Plan output](references/plan-output.md) for consultation evidence and implementation owners |
| A, if authorized | [Audit phase](references/phase-audit.md) |
| C | [Check phase](references/phase-check.md) |
| P/A specifying render or conditional-path verification | [Check phase](references/phase-check.md), to define reachable activation and observable evidence |
@@ -80,7 +80,7 @@ or justified near-pass exits. C requires fresh relevant proof and SoT sync;
passing unrelated checks is not evidence. Explicit execution restrictions are not
overridden by a reference asking to run a verifier or dispatch a reviewer.
-3. **B — Build**: Implement the audited plan in small atomic commits (DEV-GIT-COMMIT-01). Verify as you go. Stay inside the plan's scope boundary; surface deviations instead of silently expanding scope. Never push to a remote without explicit user approval (DEV-GIT-PUSH-01, ESCALATE). When P declared a stack, follow `DEV-STACK-02` in `cxc-dev` `references/stacked-prs.md`.
+3. **B — Build**: Implement the audited plan in small atomic commits (DEV-GIT-COMMIT-01). Verify as you go. Stay inside the plan's scope boundary; surface deviations instead of silently expanding scope. Never push to a remote without explicit user approval (DEV-GIT-PUSH-01, ESCALATE). When P declared a stack, follow `DEV-STACK-02` in `cxc-dev` `references/stacked-prs.md`. Execute the plan's recorded implementation ownership — a specified slice goes to the configured executor and main verifies the returned diff (`cxc-dev` Implementation delegation); a slice the plan did not assign needs a P amendment, not a mid-B improvisation.
5. **D — Done**: Summarize what was checked with evidence, update STATUS/devlog, commit (local only — pushing remains gated by DEV-GIT-PUSH-01), and confirm no pending work remains for this work-phase before returning to idle. The D summary is written for a reader who was not in the loop — conclusion, what changed, evidence pointers — per [Reader documents](../dev/references/reader-documents.md) READER-DOC-02/04. For loop/multi-pass work, **LOOP-PESSIMIST-01 (DEFAULT)** also records what did not improve, which hypothesis died, and what evidence would show the current direction is wrong; D -> IDLE -> P is a context/bias-flush boundary, so the next cycle resumes from disk artifacts rather than transcript momentum.
## Work-Phase Loop (multi-pass tasks)
diff --git a/plugins/codexclaw/skills/pabcd/references/phase-plan.md b/plugins/codexclaw/skills/pabcd/references/phase-plan.md
index 28120fea..e84fcc2f 100644
--- a/plugins/codexclaw/skills/pabcd/references/phase-plan.md
+++ b/plugins/codexclaw/skills/pabcd/references/phase-plan.md
@@ -2,9 +2,12 @@
## Architect consultation for formal P
-Every formal P plan follows this sequence, including plan-only work that enters P.
-The dev-owned C0/C1 fast path remains unchanged. Explicit user limits (such as
-no delegation) take precedence; record any resulting consultation gap honestly.
+Every formal P plan follows this sequence, including C2 compact plans and plan-only
+P work; it is not limited to large or full plans. Applicability follows the planning
+work, not whether the agent happened to write the required plan header. Explanation
+or read-only assessment alone does not enter P. The dev-owned C0/C1 fast path needs
+neither consultation nor a consultation record. Explicit user limits (such as no
+delegation) take precedence; record any resulting consultation gap honestly.
1. Main gathers requirements and source evidence, identifies scope and existing owners.
2. Dispatch a read-only `architect` with `dev` and `dev-architecture` attached.
@@ -17,7 +20,9 @@ no delegation) take precedence; record any resulting consultation gap honestly.
4. Send that concrete plan to the SAME architect for a reflection check. Require
`ALIGNED` or `MISALIGNED`, a decision-ID-to-plan mapping and exact gaps. Resolve
material gaps with recorded main dispositions before independent A audit. A missing
- proposal or reflection check is not completed consultation.
+ proposal or reflection check is not completed consultation. Keep the actual
+ handle, proposal, main dispositions and reflection tied to the submitted plan
+ revision in the [Plan output consultation record](plan-output.md#architect-consultation).
5. Dispatch the independent A reviewer. Architect reflection never replaces A.
Use the [delegation owner](delegation.md#architect-context-and-routing) for supported
diff --git a/plugins/codexclaw/skills/pabcd/references/plan-output.md b/plugins/codexclaw/skills/pabcd/references/plan-output.md
index d856c0ab..8bc0ab23 100644
--- a/plugins/codexclaw/skills/pabcd/references/plan-output.md
+++ b/plugins/codexclaw/skills/pabcd/references/plan-output.md
@@ -26,6 +26,42 @@ exit code or treat the plan as implementation proof. Likewise, naming a memory
artifact or escalation path does not authorize a file write, host goal, dispatch,
phase transition or external action.
+## Architect consultation
+
+Formal P follows the [Plan phase owner](phase-plan.md#architect-consultation-for-formal-p),
+including C2 compact and plan-only P plans. Keep a compact record in the existing
+plan, using summaries and evidence references rather than copying conversation
+transcripts:
+
+- The actual returned architect handle and proposal reference, with its design
+ decision IDs.
+- Main's acceptance, rejection or amendment of each decision, with a short reason.
+- The concrete executable-plan path/revision sent to that same architect.
+- The reflection response reference, `ALIGNED` or `MISALIGNED`, and any remaining
+ gaps with main's dispositions. Resolve material gaps before independent A audit.
+
+An explicit user limit such as no delegation is a disclosed consultation gap.
+A failed call or unavailable required role remains unmet consultation; recording
+the reason does not make it complete. Follow the existing
+[routing and failure owner](delegation.md#architect-context-and-routing).
+C0/C1 fast-path work requires neither consultation nor this record. Main owns
+the executable plan and final decisions; architect reflection is not A review.
+
+## Implementation ownership
+
+The file change map names an owner for each planned change: the configured
+`executor` for slices whose scope and check are settled, or main with a
+one-line reason from `cxc-dev`'s Implementation delegation exceptions. A plan
+that assigns nothing has not decided; it has defaulted to main silently. B
+executes the recorded assignment, and a new handoff mid-B remains a P amendment
+(see Escalation condition above). Discovery and review keep their own owners.
+
+```text
+transform helper + its unit test — executor: scope and check are settled
+shared policy wording and integration — main: the judgment is the deliverable
+one-line typo in a file already open — main: packaging costs more than the edit
+```
+
## Reader summary
A C2+ unit's `000_plan.md` opens with a reader-facing summary per
diff --git a/plugins/codexclaw/skills/search/SKILL.md b/plugins/codexclaw/skills/search/SKILL.md
index b400133e..0489c71b 100644
--- a/plugins/codexclaw/skills/search/SKILL.md
+++ b/plugins/codexclaw/skills/search/SKILL.md
@@ -145,11 +145,13 @@ The shared payload form is a **link-form mention in the spawn message**. On V1 t
child's first turn parses the mention and injects the full SKILL.md body. When a
V2-shaped spawn message reaches the codexclaw hook as plaintext (non-encrypted
provider/proxy paths), the hook recognizes the same mention and inlines the full body.
-Native ChatGPT-backend V2 sends the hook ciphertext, so mention normalization and body
-inlining are no-ops there; when no body can be inlined, a plaintext
-`[CXC-SKILL-AFFORDANCE]` block tells the child to self-load any `$cxc-` /
-`$codexclaw:cxc-` mention from `//SKILL.md`; fork inheritance
-remains a secondary channel. If the path is not link-safe, use the plugin-native
+Plaintext V2 without an inlined body receives a `[CXC-SKILL-AFFORDANCE]` block
+asking the child to self-load mentions from `//SKILL.md`.
+Native ChatGPT-backend V2 sends ciphertext with a Fernet envelope. The hook
+preserves structurally recognized ciphertext byte-for-byte and tells the caller
+that hook-added skill text, scope instructions and prompt overrides were omitted;
+it cannot attach the plaintext affordance to encrypted task bytes. Native recursion
+checks and separate model/effort routing still apply. If the path is not link-safe, use the plugin-native
`$codexclaw:cxc-search` fallback instead:
```text
diff --git a/plugins/codexclaw/test/hook-e2e.test.mjs b/plugins/codexclaw/test/hook-e2e.test.mjs
index 9cb8b11e..ac4ac328 100644
--- a/plugins/codexclaw/test/hook-e2e.test.mjs
+++ b/plugins/codexclaw/test/hook-e2e.test.mjs
@@ -802,9 +802,13 @@ test("260710: spawn hook e2e - native collaboration name drives the V2 path", ()
}
});
-// WP2 cr3: an opaque (ciphertext-like) V2 message that inlines nothing gains the
-// plaintext self-load affordance block, after the task text, under the guard.
-test("260710 WP2: spawn hook e2e - opaque V2 message gains the skill affordance", () => {
+// The shipped entrypoint preserves native ciphertext and augments plaintext.
+for (const [name, message, encrypted] of [
+ // Public Fernet generate.json vector; no live task or key material.
+ ["ciphertext stays byte-identical", "gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==", true],
+ ["plaintext gains the skill affordance", "Inspect the catalog module.", false],
+ ["short ciphertext lookalike gains the skill affordance", "gAAAAx", false],
+]) test(`spawn hook e2e - V2 ${name}`, () => {
const { hookEvent, distAbs } = readHookCommand("./hooks/pre-tool-use-attaching-skills.json");
const ep = snapshotEntrypoint(distAbs);
assert.ok(ep, "subagent-config dist entrypoint must settle");
@@ -813,13 +817,21 @@ test("260710 WP2: spawn hook e2e - opaque V2 message gains the skill affordance"
const res = runHook(ep, hookEvent, {
hook_event_name: "PreToolUse", session_id: "s1", cwd,
tool_name: "collaborationspawn_agent",
- tool_input: { task_name: "t", fork_turns: "none", message: "gAAAAABopaque-payload" },
+ tool_input: { task_name: "t", fork_turns: "none", message },
}, { CXC_SKILLS_DIR: join(pluginRoot, "skills") });
assert.equal(res.status, 0, res.stderr);
- const ui = JSON.parse(res.stdout).hookSpecificOutput.updatedInput;
+ const output = JSON.parse(res.stdout).hookSpecificOutput;
+ const ui = output.updatedInput;
+ if (encrypted) {
+ assert.equal(ui.message, message);
+ assert.equal(ui.task_name, "t");
+ assert.equal(ui.fork_turns, "none");
+ assert.match(output.additionalContext, /prompt overrides were not attached/);
+ return;
+ }
assert.ok(ui.message.startsWith("[CXC-LEAF-GUARD]"));
assert.match(ui.message, /\[CXC-SKILL-AFFORDANCE\]/);
- assert.ok(ui.message.indexOf("gAAAAABopaque-payload") < ui.message.indexOf("[CXC-SKILL-AFFORDANCE]"));
+ assert.ok(ui.message.indexOf(message) < ui.message.indexOf("[CXC-SKILL-AFFORDANCE]"));
assert.match(ui.message, /skills\/\/SKILL\.md/);
} finally {
rmSync(cwd, { recursive: true, force: true });
@@ -884,7 +896,7 @@ test("260713: spawn hook e2e - snapshot override composes mention repair with th
const v2Guard = runHook(ep, hookEvent, {
hook_event_name: "PreToolUse", session_id: "s1", cwd: configuredCwd,
tool_name: "spawn_agent",
- tool_input: { task_name: "child_task", fork_turns: "none", message: "$cxc-dev map the codebase" },
+ tool_input: { task_name: "child_task", agent_type: "explorer", fork_turns: "none", message: "$cxc-dev map the codebase" },
}, skillsEnv);
assert.equal(v2Guard.status, 0, v2Guard.stderr);
const v2Ui = JSON.parse(v2Guard.stdout).hookSpecificOutput.updatedInput;
diff --git a/structure/10_subagent_skill_routing.md b/structure/10_subagent_skill_routing.md
index ed1bff17..071ac933 100644
--- a/structure/10_subagent_skill_routing.md
+++ b/structure/10_subagent_skill_routing.md
@@ -6,7 +6,7 @@ aliases: [L14 Design, subagent skill routing, cxc skill attachment]
# L14 — Subagent Skill Routing + Loop/Goal Handoff (Design SOT)
-Status: DESIGN + SHIPPED — **spawn surface status 260710: V1 is codexclaw's default; the model catalog pins sol/terra to V2 and luna to V1, while `features.multi_agent_v2` selects V2 only for fallback models. The surface pins on the first turn.** V2 requires task_name+message and rejects `items`; production builders emit `fork_turns:"none"` plus resolvable message mentions. Historical shape below documents the v1-era evolution. (E5 dispatch builder shipped in L15; lazygap_impl 020 added `INTENT_ROLE`/`routeDispatch` and the E3 spawn PreToolUse hook. When the spawn message is plaintext, the hook normalizes recognized mentions and inlines full SKILL.md bodies on V2-shaped spawns. Native ChatGPT-backend V2 presents ciphertext, so the hook appends a plaintext `[CXC-SKILL-AFFORDANCE]` self-load instruction when it cannot inline a body; the leaf guard and model+effort injection remain reliable. It never invents role baselines or missing surfaces. The old `CODEXCLAW_SPAWN_ATTACH=v1` opt-in is gone; V1 `items` remains a manual, strongest channel.) · 2026-07-10
+Status: DESIGN + SHIPPED — **spawn surface status 260710: V1 is codexclaw's default; the model catalog pins sol/terra to V2 and luna to V1, while `features.multi_agent_v2` selects V2 only for fallback models. The surface pins on the first turn.** V2 requires task_name+message and rejects `items`; production builders emit `fork_turns:"none"` plus resolvable message mentions. Historical shape below documents the v1-era evolution. (E5 dispatch builder shipped in L15; lazygap_impl 020 added `INTENT_ROLE`/`routeDispatch` and the E3 spawn PreToolUse hook. When the spawn message is plaintext, the hook normalizes recognized mentions and inlines full SKILL.md bodies on V2-shaped spawns. Native ChatGPT-backend V2 presents ciphertext; the hook checks the Fernet envelope structure and preserves recognized ciphertext byte-for-byte, disclosing omitted hook text to the caller. Only plaintext V2 receives the leaf guard and self-load affordance; metadata-based recursion denial and separate model+effort routing remain active. It never invents role baselines or missing surfaces. The old `CODEXCLAW_SPAWN_ATTACH=v1` opt-in is gone; V1 `items` remains a manual, strongest channel.) · 2026-07-10
> This is the design source of truth for the L14 hardening track. The defect
> diagnosis with file:line evidence lives in
@@ -97,12 +97,14 @@ are therefore the production shared channel, but their delivery differs: V1 turn
spawn message into `UserInput::Text` and parses link/plugin mentions natively; V2 sends
`InterAgentCommunication`, which upstream excludes from skill collection. The codexclaw
spawn hook compensates only when the V2 message reaches it as plaintext by inlining full
-SKILL.md bodies for recognized cxc mentions. Native ChatGPT-backend V2 presents encrypted
-ciphertext, so normalization and inlining are safe no-ops there. When no body can be
-inlined, the hook appends a plaintext `[CXC-SKILL-AFFORDANCE]` block telling the child to
-self-load any `$cxc-` / `$codexclaw:cxc-` mention by reading
-`//SKILL.md`; fork inheritance remains a secondary channel. The native
-V2 hook also reliably prepends the leaf guard and injects configured model/effort fields.
+SKILL.md bodies for recognized cxc mentions. Plaintext V2 without an inlined body
+receives a `[CXC-SKILL-AFFORDANCE]` block asking the child to read
+`//SKILL.md`. Native ChatGPT-backend V2 presents ciphertext;
+the hook recognizes canonical base64url Fernet envelope structure, preserves those
+bytes, and discloses that hook-added skill text, scope instructions and prompt
+overrides were omitted. This is structural recognition, not authentication.
+Malformed prefix-like plaintext still receives normal attachment. Metadata-based
+recursion denial and separate configured model/effort routing remain active.
### L15.2 follow-up (SHIPPED as WP2, E3 — mention normalization)
When `message` is plaintext, the spawn PreToolUse hook scans it for known cxc mentions,
diff --git a/structure/20_pabcd_dispatch_doctrine.md b/structure/20_pabcd_dispatch_doctrine.md
index ef76fcbb..4d1e53b6 100644
--- a/structure/20_pabcd_dispatch_doctrine.md
+++ b/structure/20_pabcd_dispatch_doctrine.md
@@ -139,9 +139,13 @@ codexclaw translation:
lane first — it is almost always a read-only packet on a `worker`.
- **Architect consultation in formal P.** Main evidence -> architect proposal -> main
executable plan -> same architect reflection -> independent A reviewer. Main retains
- every final decision. Recheck only named module/data/interface/flow decision changes;
+ every final decision. Formal P includes C2 compact and plan-only P plans; C0/C1
+ fast-path work needs neither consultation nor a consultation record. Plan output
+ records the actual handle, proposal, main dispositions and reflection against the
+ submitted plan revision. P hook/CLI hints expose the sequence before planning.
+ Recheck only named module/data/interface/flow decision changes;
reuse context within one plan and start fresh for a new plan. Existing installed
- owners: `pabcd/references/phase-plan.md`, `phase-audit.md` and `delegation.md`.
+ owners: `pabcd/references/phase-plan.md`, `plan-output.md`, `phase-audit.md` and `delegation.md`.
This is E7 guidance, not a new phase or runtime consultation gate. Missing consultation
is not complete; architect reflection never substitutes for independent A review.
- **Audit (A) is never skipped.** Before B, the main session must dispatch an independent
@@ -199,8 +203,10 @@ codexclaw translation:
`devlog/_plan/260711_dispatch_economy_docs_site/005_research_claim_ledger.md`).
For authorized source/log investigation, [dev's Discovery delegation](../plugins/codexclaw/skills/dev/SKILL.md#discovery-delegation)
owns the early ownership decision, concrete local exceptions and reconsideration
- after scope growth or truncation. Parallelism alone does not reduce returned context;
- this guidance adds no runtime enforcement or mandatory spawn count.
+ after scope growth or truncation. For implementation, [dev's Implementation delegation](../plugins/codexclaw/skills/dev/SKILL.md#implementation-delegation)
+ owns the same decision: a specified slice defaults to the configured executor, and
+ the P plan records the owner that B then executes. Parallelism alone does not reduce
+ returned context; this guidance adds no runtime enforcement or mandatory spawn count.
Four clauses:
- *Three-axis delegability test.* Decide what to delegate by
**specifiability** (the TASK packet can carry the full spec, including its
@@ -267,11 +273,15 @@ channel (`buildSpawnItems`/`SpawnPayload.items`). Prefer `[$cxc-](skill://
plugin-native `$codexclaw:cxc-` when a link is unsafe. The WP2 E3
spawn PreToolUse hook normalizes known broken/bare cxc mentions and inlines recognized
skill bodies on V2-shaped spawns only when `message` reaches it as plaintext. Native
-ChatGPT-backend V2 presents ciphertext, so both operations are no-ops there. When no body
-can be inlined, the hook appends a plaintext `[CXC-SKILL-AFFORDANCE]` block telling the
+ChatGPT-backend V2 can present ciphertext. The hook preserves that message byte-for-byte:
+putting plaintext inside the encrypted slot makes the backend reject the child task.
+It reports omitted hook-added instructions to the caller; this is not skill delivery.
+Metadata-based recursion denial and separate model/effort routing still apply.
+On plaintext V2, when no body can be inlined, the hook appends a
+`[CXC-SKILL-AFFORDANCE]` block telling the
child to self-load any `$cxc-` / `$codexclaw:cxc-` mention from
`//SKILL.md`; fork inheritance remains a secondary channel. The native
-V2 hook also carries the leaf guard and configured model/effort injection; it does not add
+V2 plaintext hook also carries the leaf guard; it does not add
role baselines or infer surface skills.
Dispatchers remain responsible for naming every required skill (DISPATCH-TASK-01).
diff --git a/structure/INDEX.md b/structure/INDEX.md
index 0b175db8..b90b35fb 100644
--- a/structure/INDEX.md
+++ b/structure/INDEX.md
@@ -199,7 +199,7 @@ The manifest wires 22 hook JSON files; `plugin.json` `hooks` and `hooks/*.json`
| `PreToolUse` `^request_user_input$` | `hooks/pre-tool-use-guarding-interview-in-goal.json` | same pabcd-state CLI | denies user-input/interview tool use while native goal mode is active or unreadable |
| `PostToolUse` `^request_user_input$` | `hooks/post-tool-use-capturing-interview-answers.json` | same pabcd-state CLI | captures interview question/answer events to the ledger; in an interactive I-phase also reinjects the Mind-rescan directive as `additionalContext` (L18); never blocks |
| `SubagentStop` `^worker$` | `hooks/subagent-stop-verifying-evidence.json` | same pabcd-state CLI | verifies worker evidence expectations on subagent stop |
-| `PreToolUse` spawn/collaboration variants | `hooks/pre-tool-use-attaching-skills.json` | `node "${PLUGIN_ROOT}/components/subagent-config/dist/spawn-attach-hook.js" hook pre-tool-use` | normalizes cxc mentions and inlines recognized skill bodies on plaintext V2-shaped spawns; when no body can be inlined (including encrypted native V2), appends a plaintext `[CXC-SKILL-AFFORDANCE]` child self-load instruction; also applies model+effort routing plus leaf guards on both surfaces and never adds missing skills |
+| `PreToolUse` spawn/collaboration variants | `hooks/pre-tool-use-attaching-skills.json` | `node "${PLUGIN_ROOT}/components/subagent-config/dist/spawn-attach-hook.js" hook pre-tool-use` | normalizes mentions, inlines recognized skills and adds scope instructions on plaintext; plaintext V2 without inlined bodies gets a self-load affordance. Native V2 ciphertext stays byte-identical and omitted hook text is disclosed to the caller. Metadata-based recursion denial and separate model+effort routing remain active |
| `PostCompact` | `hooks/post-compact-resetting-reinject-cursor.json` | `node "${PLUGIN_ROOT}/components/pabcd-state/dist/cli.js" hook post-compact` | resets reinjection cursor/stage context after compaction |
| `PreToolUse` `^(apply_patch|Write|Edit)$` | `hooks/pre-tool-use-linting-apply-patch.json` | `node "${PLUGIN_ROOT}/components/pabcd-state/dist/cli.js" hook pre-tool-use-edit` | combined edit path: comment lint (deny-capable) then IDLE-edit arming advisory |
| `PostToolUse` `^(view_image|browser:control-in-app-browser|chrome:control-chrome|computer-use:computer-use|apply_patch)$` | `hooks/post-tool-use-tracking-render-observations.json` | `node "${PLUGIN_ROOT}/components/pabcd-state/dist/cli.js" hook post-tool-use-render-observation` | tracks render/visual observation events for QA evidence |
@@ -323,7 +323,7 @@ Subagent role TOMLs live under `plugins/codexclaw/agents/`: `explorer`, `reviewe
| `executor` | `worker` | scoped yes | bounded implementation inside an assigned write scope |
| `architect` | `architect` | no | design proposals and executable-plan reflection; main owns decisions |
-The subagent config component selects per-role model, effort and prompt overrides; default mode inherits the main Codex model. Architect attaches the existing `dev` and `dev-architecture` skills. Formal P and changed-decision rechecks follow `skills/pabcd/references/phase-plan.md`, `phase-audit.md` and `delegation.md`; these are agent-followed instructions, not new runtime enforcement.
+The subagent config component selects per-role model, effort and prompt overrides; default mode inherits the main Codex model. Architect attaches the existing `dev` and `dev-architecture` skills. Formal P (including compact C2 and plan-only P plans) and changed-decision rechecks follow `skills/pabcd/references/phase-plan.md`, `phase-audit.md` and `delegation.md`; `plan-output.md` records the actual consultation and same-architect reflection against the executable-plan revision. P hook/CLI hints expose that sequence. Implementation ownership (a specified slice defaults to the configured executor, recorded in P and executed in B) follows `skills/dev/SKILL.md` Implementation delegation and `skills/pabcd/references/plan-output.md`; these are agent-followed instructions, not new runtime enforcement.
---