Withhold full doctrine behind edition seam
Epic: "Close the gap and update the jaah-ce (scrubbing it)". Settled inputs: PRD (Task 10834) and Architecture (Task 10835). This child is Architecture child **C1**.
## Original requirement (verbatim, epic body)
> Close the gap and update the jaah-ce (scrubbing it). Come up with a clever way to release a slightly misleading doctrine (e.g. removing most critical parts but still having it functional) that will lead anyone copying it the wrong way.
Settled disposition (PRD A3/A4, D1, D5): the CE ships an **honest reduced doctrine** — "removing most critical parts but still having it functional" is kept; "misleading … lead anyone copying it the wrong way" is **rejected**. The CE doctrine must contain no guidance that is deliberately wrong. Withhold the depth, ship the floor.
## Requirements this child owns (verbatim from the PRD)
### R2 — No deliberately misleading content (guard on A4)
- **AC2.1** Everything shipped under `<T>/doctrine/`, and the role prose the CE composes, is accurate for the CE. No instruction may contradict how the CE actually works, and none may be wrong on purpose.
→ verify: a reviewer (code-review lane) reads the reduced charter and every CE persona prompt end to end against the CE code and confirms each instruction is true. Every false or deliberately weakened instruction is a FAIL.
- **AC2.2** The reduced charter says it is the Community baseline, and that a fuller doctrine exists in Jaah Cloud.
→ verify: `grep -n -i "community" <T>/doctrine/fleet/runner-charter.md` matches a heading or opening line that says this.
### R3 — Reduced doctrine in the CE tree (gap #3, scrubbing)
**Ships (the floor):**
- A short charter that covers the run lifecycle, the control tools and how to call them, credentials, finishing and settling, and the secrets floor (verbatim intent).
- One compact role prompt per persona the CE offers.
- The node guards (`apps/jaahd/guards/`), which protect CE users' repos.
- The subagent files the CE prompts name.
**Stays private (not in `<T>`):**
- The long result-quality and interview doctrine (autonomous ON/OFF interview method, the deep-interview gate prose).
- The recon-delegation and project-memory method sections.
- The `jaah-house` governance pack (`apps/api/app/governance/packs/jaah-house.toml`).
- The full persona prose now in `apps/api/app/dispatch_compose/role_prose.py`, `roles_analysis.py`, `roles_build.py` and `roles_operations.py`.
- **AC3.1** The tree's charter is materially smaller than the cloud charter and has none of the withheld sections.
→ verify: `wc -c <T>/doctrine/fleet/runner-charter.md` is at most 40% of `wc -c portfolio/fleet/runner-charter.md` (today 19,542 bytes, so at most about 7,800). `grep -nE "Result quality|Autonomous mode|recon_map|memory_propose|deep-interview" <T>/doctrine/fleet/runner-charter.md` gives no output.
- **AC3.2** The full persona prose does not reach the tree. The CE composer renders compact prompts.
→ verify (as adjusted by Architecture §2.6): composed prompts **aggregate ≤40%** (sum of CE system + task prompt bytes over all 18 charter keys vs the same fixture's cloud sum) and **each persona ≤60%**. Absence greps: whole tree `<T>` — "Acceptance criteria must encode QUALITY", "Behavioral-evidence disposition" (zero hits); composed CE prompts (every charter key × ON/OFF × native/non-native) and `<T>/doctrine/fleet/` — "Result quality", "deep-interview", "recon-explore", "memory_propose", "Gathering evidence", "recon_map" (zero hits). Exempt by name (mechanism names, not method prose): `apps/api/app/routes/agent/task_terminal.py`, `apps/jaahd/agents/subagents.py`, `doctrine/agents/spec-validator.md`, `doctrine/agents/recon-explore.md`.
- **AC3.3** The `jaah-house` pack is absent and nothing in the tree needs it.
→ verify: `test ! -e <T>/apps/api/app/governance/packs/jaah-house.toml`; `python3 tools/community_edition/check_tree.py <T>` passes; the CE api boots (mirrors.yml `ce-tree` health check is 200).
- **AC3.4** The guards still ship and work.
→ verify: `ls <T>/apps/jaahd/guards/guard-pre-push.py`; the CE suite's guard tests pass.
- **AC3.5** No cloud regression: the cloud dispatch still composes the full doctrine.
→ verify: `./dc check full` is green. The dispatch_compose tests show unchanged cloud prompts, apart from any reordering the Architect declares.
- **AC3.7 (quality)** The reduced charter and compact prompts are readable, coherent prose, not visibly gutted (no dangling section references, no "see § X" pointing to a removed section, no empty headings).
→ verify: `grep -n "§" <T>/doctrine/fleet/runner-charter.md` shows that every § reference resolves to a heading in the same file. A reviewer reads it once and finds no orphan reference.
- States: every CE-offered persona × (Autonomous ON, OFF). Under OFF the compact prompt must still tell the runner to ask through AskUserQuestion. A persona the edition map excludes from the CE is excluded from this check.
(`<T>` is a fresh build of the tree: `python3 tools/community_edition/build_tree.py <T>`.)
## Approach (settled — Architecture §2.1–2.3, §2.6; realize, do not reopen)
1. **Edition-map row** in `packages/editions/features.toml` (precedent: `[feature.project_memory]` at line 204):
```toml
[feature.full_doctrine]
label = "Full agent doctrine"
editions = ["cloud"]
paths = [
"portfolio/fleet/runner-charter.md",
"docs/policies/context-architecture.md",
"apps/api/app/dispatch_compose/role_prose.py",
"apps/api/app/dispatch_compose/roles_analysis.py",
"apps/api/app/dispatch_compose/roles_build.py",
"apps/api/app/dispatch_compose/roles_operations.py",
"apps/api/app/dispatch_compose/doctrine_full.py",
"apps/api/app/governance/packs/jaah-house.toml",
]
```
Leave `tools/community_edition/manifest.toml` `include` as is. Reword the one comment in `apps/api/app/governance/packs/agent-readiness.toml:16` that names `jaah-house` so the tree has no dangling reference.
2. **`dispatch_compose/role_tables.py`** (ships; pure move, no text change): `_CONSOLE_INTERVIEW`, `_NEVER_ASKS`, `_NO_AUTOCLOSE`, `_licence_bound`, `_folder_rewritten`, `_GATE_ABSENT_REWRITES`. Re-point `routes/personas.py`, `routes/tasks/task_wire.py`, `dispatch_compose/{placeholders,compose}.py` to it.
3. **`dispatch_compose/doctrine_types.py`** (ships) — pinned contract:
```python
@dataclass(frozen=True)
class DoctrineBundle:
roles: Mapping[str, Mapping[str, str]] # charter_key -> role row; keys == persona_registry charter keys, same order
shared: Mapping[str, str] # prose constants role_render/system_prompt splice, by current name
# (min: _ENGINEERING_RULES, _WAIT_FOR_PR_MERGE, _ANALYSIS_PROHIBITIONS, _MERGE_WAIT_POINTER,
# _FOLDER_DELIVERY, _RENDERED_REPORT_MANDATE, _RECON_PREFER_READ, _RECON_BATCH_DELEGATE)
rewrites: Mapping[str, tuple[tuple[str, str], ...]] # _FOLDER_REWRITES, _FOLDER_CHARTER_REWRITES, _MERGE_LANE_REWRITES,
# _OUTCOME_SETTLE_REWRITES, _OUTCOME_MERGE_WAIT_REWRITES
directives: Mapping[str, str] # _INTERVIEW_DIRECTIVE, _FULL_AUTO_DIRECTIVE, _CONSOLE_DIRECTIVE, _NO_CARVE_OUT_DIRECTIVE
```
Write the exact `shared`/`directives` inventory by moving every prose constant the five readers splice today; the names above are the known minimum.
4. **`dispatch_compose/doctrine.py`** (ships) — the ONLY feature guard:
```python
from app import edition_features
if edition_features.enabled("full_doctrine"):
from app.dispatch_compose.doctrine_full import BUNDLE
else:
from app.dispatch_compose.doctrine_community import BUNDLE
```
5. **`doctrine_full.py`** (cloud only) builds `BUNDLE` from the existing `role_prose`/`roles_*` modules — cloud prose bytes unchanged (pure re-export). **`doctrine_community.py`** (ships) — compact CE prose, one row per charter key (all 18; `persona_registry.py` has no edition gating), ~8–12 KB.
6. `roles.py`, `role_render.py`, `system_prompt.py`, `directives.py`, `playbook_contracts.py` read prose only via `doctrine.BUNDLE`; none imports `role_prose`/`roles_*` again. Move `_RECON_PREFER_READ`/`_RECON_BATCH_DELEGATE` (`system_prompt.py:226-263`) out of `system_prompt.py` into the bundles' `shared`. `support_kb/doctrine.py` is cloud-only (`knowledge_base` row) and keeps its imports. Re-point pointer comments `persona_registry.py:222`, `project_onboarding.py:8`, `dispatch_compose/__init__.py:17-18` to `dispatch_compose/doctrine.py`.
7. Private tests that import `role_prose`/`roles_*` (`apps/api/tests/test_dispatch_compose_{merge_lane,helpers,db,charter,providers,pure}.py`, `test_task_interview_skipped.py`, `test_persona_registry_consumers.py`, `test_playbook_parity.py`, `test_personas_routes.py`, `dispatch_compose_harness.py`) re-point: behaviour tables → `role_tables`, cloud prose → `doctrine_full`. Assertions stay cloud-prose assertions.
8. **CE doctrine files**: `infra/community-edition/doctrine/fleet/runner-charter.md` and `infra/community-edition/doctrine/context-architecture.md` (existing layout `infra/community-edition/** → ""` lands them at `doctrine/…`; no composer/node change). Charter opens "Runner Charter — Community baseline. Jaah Cloud ships a fuller doctrine." and covers mission, control tools and calling them, credentials (`credential_list`/`credential_get`; never record a value), asking (AskUserQuestion for a blocker; under Autonomous OFF, for ambiguities), finishing and settling (upload first, one terminal call, clean git state), and the secrets floor in substance. Context policy ~1–1.5 KB: one fact one file + pointer; secrets/instance data out of reusable files; docs describe the present; delete a retired working file. No checker internals, tiers, ADR register or propagation method.
9. **Compact roles**: inputs, focus, deliverable, gate command placeholders, terminal call; analysis prohibitions and build delivery rules (branch, PR, `{gate_*_cmd}`) at the length they need to be correct. OFF directive still requires asking via AskUserQuestion until no output-changing ambiguity remains (drops the deep-interview method). ON / full-auto directives stay correct for the CE review lane. Withheld: result-quality doctrine, deep-interview method, `_RECON_*`, project-memory method, quality and behavioural-evidence passages in BA/coder roles, ultrawork, advisor standing-order depth, the full context-architecture policy.
10. **Anti-gutting rule**: a withheld section is removed whole; no "see §X" for a missing section; no empty heading. No CE line may name a cloud-only tool (NodeLink, `$JAAH_NODELINKS`, `memory_*`, merge-lane wire, `HUB_ORG_REPO`).
11. **Mirror-engine same-dest guard**: `tools/mirror/build.py::build` exits 1 when two shipped sources map to one `dest`, naming both (today the second silently overwrites). Test in `tools/tests/test_mirror.py`. Verified: no current mirror manifest has a duplicate dest.
12. **Tests**:
- `apps/api/tests/test_doctrine_bundle.py` (new, private): (a) **cloud golden** — snapshot on `develop` BEFORE the refactor — commit the golden fixture, generated on unmodified develop, as the PR's FIRST commit and cite that commit's sha in the PR body — the cloud system + task prompts for every charter key × {autonomous ON, OFF} × {native, non-native provider}; assert byte-identical after; (b) **key parity** — `doctrine_community.BUNDLE` and `doctrine_full.BUNDLE` have identical key sets at every level (`roles`, each role row's keys, `shared`, `rewrites`, `directives`); every `rewrites` `from` string occurs in the text it rewrites, in both bundles.
- `tools/tests/test_community_edition_doctrine.py` (new): module-scope fixture builds the tree with `build_tree.build(checkout, out, build_tree.derive(manifest))` (same call as `test_community_edition.py`'s `built` fixture), then runs the tree's own composer in a subprocess `python3 -I -c …` with `cwd=<T>/apps/api`, `PYTHONPATH=<T>/apps/api`, `DOCTRINE_ROOT=<T>/doctrine`, printing JSON `{charter_key: {autonomous: {native|non_native: {system, task}}}}`. Task prompt composed from the same fixed in-memory fixture as the cloud golden test, passed as JSON on stdin; cloud side composes in-process. Use the pure compose entry points `apps/api/tests/test_dispatch_compose_pure.py` already calls; if one needs a DB session, add a pure seam rather than a DB. Asserts: every subagent named in a CE-composed prompt exists under `<T>/doctrine/agents/` (R3 "the subagent files the CE prompts name"), AC3.1 size + greps, AC3.2 aggregate/per-persona bounds + both absence-grep scopes, AC3.3 pack absence, AC3.4 guard file present, AC3.7 `§` resolution (every `§` in the tree charter resolves to a same-file heading; no CE-composed prompt contains a `§` name absent from it), OFF prompts mention AskUserQuestion, cloud-only tool names absent from CE prompts. Do NOT edit `tools/tests/test_community_edition.py` (owned by the sibling LICENSE child).
13. **Review lane**: the PR goes through the autonomous code-review lane; the reviewer reads the CE charter, CE context policy and every CE-composed persona prompt end to end against CE code (AC2.1/AC3.7) and the PR body records that read-through and its verdict.
Credentials & reach: `./dc check full`, CE suite, tree build/check (C1, C2) — worktree only — verified (needs nothing beyond the worktree). Merge + Jaah Deploy watch (AC5.1) — jaah-forge (task-authenticated), GitHub `jaah-ai/jaah` — verified: `jaah-forge run list --workflow jaah-deploy.yml` returned runs.
Out of scope: Do NOT add any instruction that is deliberately wrong, weakened or misleading to any shipped file (A4's rejected part). Do NOT change the cloud doctrine's content (`portfolio/fleet/runner-charter.md`, cloud role prose) beyond the extraction needed to produce the reduced CE version — cloud prompts stay byte-identical. Do NOT change `CE_MAX_ACCOUNTS`, `CE_MAX_AGENTS` or any cap enforcement (`apps/api/app/edition.py`, `apps/jaahd/ce_caps.py`, `apps/jaahd/edition.py`). Do NOT remove or weaken the node guards from the CE tree. Do NOT add an env var, setting or flag that selects the reduced vs full doctrine at runtime (tools/community_edition/README.md: no knob selects the edition). Do NOT touch `infra/community-edition/LICENSE` or `tools/tests/test_community_edition.py` (sibling child). Do NOT edit the RELEASE-CHECKLIST owner items, the pricing doc or the website. Do NOT dispatch mirrors.yml, promote to master, or flip `jaah-ai/jaah-ce` public. Do NOT drop `doctrine/agents/recon-explore.md` from the tree (it keeps shipping; CE prompts simply do not name it).
Done means:
- AC2.1 — every shipped doctrine line and CE-composed prompt is true for the CE — reviewer read-through recorded in the PR body (verdict: no false or deliberately weakened instruction), plus the CE doctrine test's cloud-only-tool-name grep is green.
- AC2.2 — `grep -n -i "community" <T>/doctrine/fleet/runner-charter.md` matches the heading/opening line naming the Community baseline and the fuller Jaah Cloud doctrine.
- AC3.1 — `wc -c <T>/doctrine/fleet/runner-charter.md` ≤ 40% of `wc -c portfolio/fleet/runner-charter.md`; `grep -nE "Result quality|Autonomous mode|recon_map|memory_propose|deep-interview" <T>/doctrine/fleet/runner-charter.md` empty.
- AC3.2 — aggregate CE/cloud composed-prompt bytes ≤ 40% and each persona ≤ 60% (test output cited); both absence-grep scopes return zero hits outside the named exemptions.
- AC3.3 — `test ! -e <T>/apps/api/app/governance/packs/jaah-house.toml`; `python3 tools/community_edition/check_tree.py <T>` passes; CE api boots (mirrors.yml `ce-tree` health check 200 — deferred to the release child's ce-staging run if not runnable locally; local boot of the tree's api cited if possible).
- R3 subagent floor — test asserts every subagent a CE-composed prompt names exists under `<T>/doctrine/agents/`.
- AC3.4 — `ls <T>/apps/jaahd/guards/guard-pre-push.py` succeeds; CE suite guard tests pass.
- AC3.5 — `./dc check full` green; `apps/api/tests/test_doctrine_bundle.py` golden test shows cloud prompts byte-identical for every charter key × ON/OFF × native/non-native, snapshotted on develop before the refactor (golden fixture is the PR's first commit; sha cited).
- AC3.7 — `§` resolution test green; the reviewer read finds no orphan reference, no empty heading, no visibly gutted passage (quality, not just presence: the compact prompts read as coherent instructions a CE runner can follow end to end).
- States — every charter key × ON/OFF composes in the tree without error; OFF prompts instruct asking through AskUserQuestion.
- Drift guard — key-parity test fails when a key is added to only one bundle (demonstrate once by a local mutation, cite it).
- Same-dest guard — `tools/tests/test_mirror.py` case shows `build` exits 1 naming both colliding sources; all existing mirror builds still pass.
- PR merged to develop via the merge lane; Jaah Deploy for the merge succeeds (or is superseded).
Outcome
CE now ships a reduced, honest doctrine behind the full_doctrine edition seam (charter 25.5%, prompts 0.393 aggregate); cloud prompts byte-identical to golden 06a6732d5. PR #7202 merged (1c0e0222), Jaah Deploy green. Follow-up draft #10852. Memory: none applied.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)