# Feature Spec: Init UX + Policy Defaults Simplification (AOP)

> **Purpose of this document**: Define implementation-ready changes that make `aop init` simpler for default users while preserving full control for superusers and maintaining deterministic runtime behavior.

**Version:** 1.1  
**Date:** 2026-03-03  
**Status:** Draft (Expanded Implementation Detail)  
**Roadmap Mapping:** M39

---

## 0. Standards and Dependencies

### 0.1 Required Standards

All implementation MUST preserve:

- deterministic runtime behavior and MCP contract semantics
- existing CLI command names (`aop init`, `aop run`, etc.)
- schema validation as a hard gate for loaded policy
- full backward compatibility for existing full `policy.yaml` files
- test naming style (`GIVEN_..._WHEN_..._THEN_...`) and Vitest usage

### 0.2 Upstream Inputs

Implementing agents MUST read:

- `apps/control-plane/src/cli/init-command-handler.ts`
- `apps/control-plane/src/cli/cli-argument-parser.ts`
- `apps/control-plane/src/cli/types.ts`
- `apps/control-plane/src/cli/help-command-handler.ts`
- `apps/control-plane/src/interfaces/cli/bootstrap.ts`
- `apps/control-plane/src/core/kernel.ts`
- `apps/control-plane/src/core/schemas.ts`
- `agentic/orchestrator/policy.yaml`
- `agentic/orchestrator/schemas/policy.schema.json`
- `apps/control-plane/test/init-wizard.spec.ts`
- `apps/control-plane/test/kernel.spec.ts`

### 0.3 Current Baseline (Already Implemented in M39 Pre-Work)

As of 2026-03-03, these pieces are already done:

- `aop init` no longer copies schema files into target repos.
- schema loading can fall back to bundled AOP schemas when repo-local schemas are absent.
- `aop init` captures provider/model/SCM defaults.
- `activity-detector` in `adapters.yaml` is derived from provider.

This spec defines the next step: policy simplification and runtime composition.

---

## 1. Problem Analysis

### 1.1 Primary UX Gap

`aop init` currently emits a verbose `policy.yaml` with many low-frequency controls (locks, RBAC internals, retry internals, path rules, hard constraints). Most teams do not need to edit these fields, but still must parse and own them.

### 1.2 Resulting Risks

- Setup friction: first-time users face a large policy surface area.
- Misconfiguration risk: accidental edits to advanced controls.
- Upgrade friction: defaults cannot evolve cleanly when every repo stores a fully expanded copy.

### 1.3 Desired UX Direction

Default users should configure only common controls; advanced users can opt into full explicit policy authoring.

---

## 2. Objectives

### 2.1 Must-Have Outcomes

1. Default `aop init` emits a lean, user-editable policy file.
2. Runtime composes lean policy over bundled defaults into one canonical full snapshot.
3. Superusers can still generate and maintain a full explicit policy.
4. Existing full policies continue to work without migration.
5. Validation errors remain clear and actionable.

### 2.2 Non-Goals

- No redesign of locking, RBAC, or gate semantics.
- No changes to tool names or tool schemas.
- No removal of full policy support.
- No behavioral weakening of schema validation.

---

## 3. Architecture Decisions

### ADP-1: Defaults-First Policy Composition

Runtime will always build a canonical full policy by:

1. loading bundled `policy.defaults.yaml`,
2. loading user `agentic/orchestrator/policy.yaml`,
3. deep-merging user overrides over defaults,
4. validating merged result against full `policy.schema.json`.

This removes the need for brittle full-vs-lean mode detection and keeps backward compatibility.

### ADP-2: Explicit Advanced Init Mode

Add `aop init --advanced-policy` to generate the full explicit policy template (current behavior).

Default `aop init` generates lean policy.

### ADP-3: Deterministic Merge Semantics

Policy merge must use deterministic rules:

- scalar values: override defaults
- objects: recursive merge
- arrays: replace when key exists in user policy
- absent keys: inherit defaults
- `null` values: rejected by schema unless explicitly allowed

### ADP-4: Validation of User Shape + Canonical Shape

Two validations are required:

1. optional user-shape validation (`policy.user.schema.json`) for clearer authoring errors
2. mandatory canonical full validation (`policy.schema.json`) after merge

If user-shape validation is omitted for MVP, canonical full validation remains mandatory.

---

## 4. UX and Configuration Model

### 4.1 Default `aop init` Output (Lean Policy)

Lean policy should include only common knobs:

- `version`
- `worktree.base_branch`
- `supervisor.max_parallel_gate_runs`
- `dashboard.enabled`
- `dashboard.port`
- `notifications`
- `merge_policy.require_user_approval`
- optional `recovery.orchestrator_session_reattach_timeout_ms`

Example shape:

```yaml
version: 1
worktree:
  base_branch: main
supervisor:
  max_parallel_gate_runs: 3
dashboard:
  enabled: true
  port: 3000
merge_policy:
  require_user_approval: true
notifications:
  enabled: false
  channels:
    desktop:
      enabled: false
    slack:
      enabled: false
      webhook: ''
      channel: '#aop-alerts'
    webhook:
      enabled: false
      url: ''
```

### 4.2 Advanced `aop init --advanced-policy` Output

Generates current fully expanded `policy.yaml` template with all advanced fields.

### 4.3 Init Prompt Surface

Interactive wizard continues to collect:

- default provider
- default model
- SCM provider
- base branch
- max parallel gates
- dashboard port
- notification channels + webhook values
- framework for gates

`adapters.yaml` activity-detector mapping:

- `claude` -> `claude-jsonl`
- `codex` -> `codex-rpc`
- everything else -> `process-heuristic`

---

## 5. Implementation Plan

### M39-M1: CLI and Init Generation

### INIT-T-001: Add Advanced Policy CLI Flag

**Files:**

- `apps/control-plane/src/cli/types.ts`
- `apps/control-plane/src/cli/cli-argument-parser.ts`
- `apps/control-plane/src/cli/help-command-handler.ts`
- `apps/control-plane/src/interfaces/cli/bootstrap.ts`

**Changes:**

- add `advanced_policy?: boolean` to `CliOptions`
- parse `--advanced-policy`
- pass through to `InitCommandHandler.execute({ auto, force, advanced_policy })`
- document flag in `aop help init`

### INIT-T-002: Extend Init Options Contract

**File:** `apps/control-plane/src/cli/init-command-handler.ts`

**Changes:**

- add `advanced_policy?: boolean` to `InitOptions`
- branch policy template generation:
  - `advanced_policy === true` -> full template
  - else -> lean template

### INIT-T-003: Split Policy Template Generation

**File:** `apps/control-plane/src/cli/init-command-handler.ts`

**Changes:**

- keep existing full generator as `generateFullPolicyYaml(...)`
- add `generateLeanPolicyYaml(...)`
- keep `gates.yaml`, `agents.yaml`, `adapters.yaml`, prompts generation unchanged

---

### M39-M2: Runtime Policy Composition

### INIT-T-004: Add Bundled Defaults Artifact

**New file:**

- `agentic/orchestrator/defaults/policy.defaults.yaml`

**Requirements:**

- this file represents canonical defaults equivalent to current full template semantics
- should be versioned and reviewed like other bundled contracts

### INIT-T-005: Implement Policy Composition Loader

**New file (recommended):**

- `apps/control-plane/src/application/services/policy-loader-service.ts`

**Interface (suggested):**

```ts
interface LoadPolicyResult {
  userPolicy: Record<string, unknown>;
  mergedPolicy: Record<string, unknown>;
}

async function loadComposedPolicy(
  repoRoot: string,
  policyPath: string,
  schemaRegistry: SchemaRegistry,
): Promise<LoadPolicyResult>;
```

**Behavior:**

- read defaults YAML (bundled path, not repo path)
- read user policy YAML (repo path)
- deep merge according to ADP-3 semantics
- validate merged policy against `policy.schema.json`
- return merged result

**Implementation details (normative):**

1. Defaults path resolution:
   - resolve from module location first (`import.meta.url` + upward walk), not from user repo.
   - fail with explicit error if defaults artifact cannot be located.

2. Merge algorithm:
   - perform pure-function merge (do not mutate defaults or user objects).
   - object + object -> recursive merge.
   - array in user value -> replace defaults array wholesale.
   - primitive in user value -> replace defaults primitive.
   - `undefined` user fields -> ignored.

3. Validation order:
   - optional: validate user file against `policy.user.schema.json`.
   - mandatory: validate merged output against `policy.schema.json`.

4. Error contract:
   - user-shape errors must point to user `policy.yaml`.
   - canonical-shape errors must include merged key path and mention defaults composition.

**Reference merge pseudocode:**

```ts
function mergePolicy(defaults: unknown, user: unknown): unknown {
  if (Array.isArray(user)) return user.slice();
  if (isObject(defaults) && isObject(user)) {
    const out: Record<string, unknown> = { ...defaults };
    for (const key of Object.keys(user)) {
      const userVal = (user as Record<string, unknown>)[key];
      if (userVal === undefined) continue;
      out[key] = key in out ? mergePolicy(out[key], userVal) : clone(userVal);
    }
    return out;
  }
  return clone(user);
}
```

### INIT-T-006: Wire Kernel to Composed Policy

**File:** `apps/control-plane/src/core/kernel.ts`

**Current behavior:** direct `loadAndValidateYaml(... 'policy.schema.json', policyPath)`

**Target behavior:** load policy via composition loader from INIT-T-005, then assign merged policy to `this.policy`.

### INIT-T-007: Optional User-Shape Schema

**New file (optional but recommended):**

- `agentic/orchestrator/schemas/policy.user.schema.json`

**Purpose:**

- validate lean authoring keys for better error messages before merge
- canonical enforcement still happens via full schema on merged result

**Schema policy:**

- `additionalProperties: false` at top level for lean mode.
- explicitly allow only intended user-editable sections.
- do not include internal sections (`locks`, `rbac`, `config_precedence`, etc.) in lean schema.

---

### M39-M3: Docs and Migration Clarity

### INIT-T-008: Documentation Updates

**Files:**

- `README.md`
- `agentic/orchestrator/tools.md` (if references policy generation behavior)
- any init command docs/examples

**Documentation requirements:**

- clearly separate “common config” vs “advanced policy config”
- add `--advanced-policy` usage examples
- document merge semantics and where defaults live

### INIT-T-009: Init Output Messaging

**File:** `apps/control-plane/src/cli/init-command-handler.ts`

**Changes:**

- `next_steps` output should state whether lean or advanced policy was generated
- mention `--advanced-policy` as escape hatch when lean mode is used

---

## 6. File-Level Delta Matrix

| File                                                                   | Change Type    | Scope                             |
| ---------------------------------------------------------------------- | -------------- | --------------------------------- |
| `apps/control-plane/src/cli/types.ts`                                  | modify         | add `advanced_policy` option      |
| `apps/control-plane/src/cli/cli-argument-parser.ts`                    | modify         | parse `--advanced-policy`         |
| `apps/control-plane/src/cli/help-command-handler.ts`                   | modify         | help text for new flag            |
| `apps/control-plane/src/interfaces/cli/bootstrap.ts`                   | modify         | pass flag into init handler       |
| `apps/control-plane/src/cli/init-command-handler.ts`                   | modify         | lean/full policy generation       |
| `apps/control-plane/src/core/kernel.ts`                                | modify         | use composed policy loader        |
| `apps/control-plane/src/application/services/policy-loader-service.ts` | add            | defaults + user merge + validate  |
| `agentic/orchestrator/defaults/policy.defaults.yaml`                   | add            | bundled canonical defaults        |
| `agentic/orchestrator/schemas/policy.user.schema.json`                 | add (optional) | lean user policy shape            |
| `README.md`                                                            | modify         | init UX and policy authoring docs |

---

## 7. Acceptance Criteria

### 7.1 Init UX

- [ ] `aop init` writes lean `policy.yaml` by default.
- [ ] `aop init --advanced-policy` writes full explicit `policy.yaml`.
- [ ] `aop init --auto` works in both lean and advanced modes.
- [ ] `aop help init` documents `--advanced-policy`.

### 7.2 Runtime Composition

- [ ] Kernel boots correctly with lean policy.
- [ ] Kernel boots correctly with existing full legacy policy.
- [ ] Merged policy always passes full schema validation.
- [ ] Policy merge behavior is deterministic and documented.

### 7.3 Compatibility

- [ ] Existing repositories with full policy require no changes.
- [ ] Existing tests for non-init commands remain green.
- [ ] No tool contract or transport behavior regressions.

---

## 8. Test Plan

### 8.1 Init Tests

**File:** `apps/control-plane/test/init-wizard.spec.ts`

Add:

- `GIVEN_default_init_WHEN_policy_generated_THEN_policy_is_lean_shape`
- `GIVEN_advanced_policy_flag_WHEN_policy_generated_THEN_policy_is_full_shape`
- `GIVEN_advanced_policy_flag_WHEN_help_rendered_THEN_flag_is_documented`

### 8.2 Policy Composition Tests

**New file:** `apps/control-plane/test/policy-loader-service.spec.ts`

Add:

- `GIVEN_defaults_and_lean_user_policy_WHEN_composed_THEN_merged_policy_is_valid`
- `GIVEN_full_user_policy_WHEN_composed_THEN_user_values_override_defaults`
- `GIVEN_array_override_WHEN_composed_THEN_array_replaces_default_array`
- `GIVEN_invalid_user_policy_WHEN_loaded_THEN_validation_error_is_actionable`

### 8.3 Kernel Integration Tests

**File:** `apps/control-plane/test/kernel.spec.ts`

Add:

- `GIVEN_lean_policy_repo_WHEN_kernel_loads_THEN_runtime_boots_successfully`
- `GIVEN_full_policy_repo_WHEN_kernel_loads_THEN_runtime_boots_successfully`

### 8.4 Regression Test Runs

Required run list:

- `npm run lint`
- `npm run typecheck`
- `npm test`
- `npm run validate:mcp-contracts`
- `npm run validate:architecture`

### 8.5 Determinism and Edge Cases (Required)

Add explicit tests for:

- merge determinism with object key order differences (same result hash)
- array replacement semantics for `required_modes` and `protected_areas`
- missing defaults artifact error path (clear actionable message)
- behavior when user policy is empty `{}` (defaults-only canonical policy)
- behavior when user policy file is absent (hard failure or auto-init guidance; choose one and test)

---

## 9. Rollout Strategy

### Phase A (Safe Introduction)

Ship runtime composition first while keeping full-policy init generation as default.

### Phase B (UX Flip)

Flip `aop init` default to lean policy once composition is stable and tested.

### Phase C (Docs and Adoption)

Document lean default and advanced mode; include migration note for teams that prefer explicit full policy.

---

## 10. Risks and Mitigations

- Risk: ambiguity around merge semantics.
  - Mitigation: explicit deterministic rules + unit tests for scalars/objects/arrays.
- Risk: defaults drift from real runtime assumptions.
  - Mitigation: keep defaults artifact under version control and validate merged output with full schema.
- Risk: advanced users lose discoverability of lower-level knobs.
  - Mitigation: `--advanced-policy` and dedicated docs section with full template.
- Risk: error messages become less actionable due to merge layer.
  - Mitigation: preserve source-path context (`policy.yaml` field path) in validation errors.

---

## 11. Definition of Done

This spec is complete when:

1. INIT-T-001 through INIT-T-009 are implemented.
2. acceptance criteria in Section 7 are all checked.
3. test plan in Section 8 passes in CI.
4. `spec-files/progress.md` is updated with delivered tasks and residual follow-ups.

---

## 12. Open Questions

1. Should missing `agentic/orchestrator/policy.yaml` trigger hard failure or auto-bootstrap from defaults?
2. Do we want `aop init --advanced-policy` as a one-time generator only, or also a conversion command from lean to full?
3. Should lean mode permit `cleanup.*` (frequently edited in some teams) or keep it advanced-only?
