# Feature Spec: Internal vs User Policy Stratification (AOP)

> **Purpose of this document**: Define a clearer policy layering model by splitting non-user-editable internal controls from user-editable repository policy overrides.

**Version:** 1.0
**Date:** 2026-03-16
**Status:** Draft
**Roadmap Mapping:** M55
**Depends On:**

- `spec-files/completed/agentic_orchestrator_init_policy_ux_simplification_spec.md`
- `spec-files/completed/agentic_orchestrator_validator_hardening_spec.md`

---

## 0. Scope

This spec covers the following user request:

- split policy concerns into `internal.policy.yaml` for non-user-modifiable controls and `policy.yaml` for user-editable controls

Out of scope:

- changing gate semantics
- replacing `agents.yaml`
- changing dashboard authoring or runtime standby behavior directly

### 0.1 Required Standards

All implementation derived from this spec MUST preserve:

- schema-driven policy validation
- deterministic merged-policy composition
- backward-compatible migration behavior for repos that only have `policy.yaml`
- normalized startup failures when policy layering is invalid

### 0.2 OOP and Architecture Guardrails (Mandatory)

Implementation MUST follow:

- **KISS**: extend the existing loader into a clear three-layer model instead of introducing multiple overlapping loaders.
- **SRP**: policy loading, policy validation, init-file generation, and documentation ownership MUST remain separate responsibilities.
- **SOLID-oriented design**:
  - open/closed for future protected-path groups or additional policy layers without rewriting callers
  - interface segregation between full merged-policy validation and user-policy validation
  - dependency inversion so runtime configuration services depend on a composed-policy contract, not raw file-order assumptions
- **Hexagonal alignment**: policy composition stays in application/configuration services, not scattered across CLI/runtime consumers.
- **Composition over inheritance**: compose layered loaders/validators with reusable merge helpers instead of branching subclasses.
- **High cohesion / low coupling**: internal-policy concerns must not leak into unrelated agent/runtime/dashboard configuration paths.

---

## 1. Problem Statement

The current policy model has two layers:

- bundled `agentic/orchestrator/defaults/policy.defaults.yaml`
- repo `config/agentic/orchestrator/policy.yaml`

That helps with defaults, but it does not clearly separate:

- controls that ordinary repository users should be able to tune
- controls that should be fixed or centrally governed

The result is conceptual leakage:

- `policy.defaults.yaml` is package/distribution-level, not repository governance-level
- `policy.yaml` mixes user-facing knobs with settings that should be treated as protected internal controls

---

## 2. Design Summary

### 2.1 Three-Layer Effective Policy

Effective policy becomes:

1. bundled defaults: `agentic/orchestrator/defaults/policy.defaults.yaml`
2. repo internal policy: `config/agentic/orchestrator/internal.policy.yaml`
3. repo user policy: `config/agentic/orchestrator/policy.yaml`

Merge order:

- defaults <- internal <- user

But user overrides MUST be rejected for paths classified as internal-only.

### 2.2 Intent of Each Layer

`policy.defaults.yaml`

- distribution defaults shipped with AOP
- not the user contract

`internal.policy.yaml`

- repository or platform owner controls
- safety rails and non-negotiable runtime controls
- not intended for normal end-user modification

`policy.yaml`

- user-editable operational preferences
- constrained by user-policy schema
- cannot override internal-only paths

### 2.3 Loader Behavior

Policy loading MUST:

1. read bundled defaults
2. read `internal.policy.yaml`
3. read `policy.yaml`
4. validate effective merged policy against full schema
5. validate user policy against a user-facing schema that excludes internal-only fields
6. fail clearly if `policy.yaml` attempts to override an internal-only path

---

## 3. Functional Requirements

### FR-1 Add `internal.policy.yaml`

Add a first-class internal policy file under the existing config root.

Acceptance criteria:

1. The loader recognizes `config/agentic/orchestrator/internal.policy.yaml`.
2. Missing internal policy is allowed only if an explicit fallback policy is defined; otherwise init should generate it.
3. Runtime artifact/debug output can distinguish which layer contributed a final value.

### FR-2 Separate Schemas

Validation must distinguish internal policy from user policy.

Acceptance criteria:

1. Full effective policy still validates against `policy.schema.json`.
2. User-editable `policy.yaml` validates against a constrained user schema.
3. Internal-only paths can exist in `internal.policy.yaml` even if absent from user schema.

### FR-3 Reject Forbidden User Overrides

User policy MUST NOT override internal-only paths.

Acceptance criteria:

1. Loader throws a descriptive error when `policy.yaml` attempts to set an internal-only field.
2. The error includes the blocked path list.
3. Validation happens before the runtime starts executing features.

### FR-4 Init and Documentation Updates

`aop init` MUST generate the new split configuration intentionally.

Acceptance criteria:

1. Init produces both `internal.policy.yaml` and `policy.yaml`.
2. Generated comments explain ownership of each file.
3. AGENTS/CLAUDE/init help/docs reflect the new policy layout.

---

## 4. Internal-Only Examples

The exact list is repository-defined, but initial internal-only candidates SHOULD include controls such as:

- RBAC
- protected areas
- mutation idempotency and tool authorization safety rails
- run-lease ownership and recovery constraints
- provider execution fail-safe defaults
- internal observability retention controls where tamper resistance matters

User-editable examples SHOULD include:

- dashboard port and UX toggles
- notification preferences
- budget thresholds explicitly intended for operator tuning
- cleanup grace periods where safe

This split must be encoded as data, not only documentation.

---

## 5. File-Level Targets

- `apps/control-plane/src/application/services/policy-loader-service.ts`
  - load and merge the new three-layer policy model.
- `apps/control-plane/src/application/configuration-service.ts`
  - resolve internal/user policy paths and error messages.
- `apps/control-plane/src/cli/init-command-handler.ts`
  - generate and explain both policy files.
- `agentic/orchestrator/schemas/policy.schema.json`
  - remain the effective merged-policy schema.
- `agentic/orchestrator/schemas/policy.user.schema.json`
  - align to the new user-editable subset.
- `config/agentic/orchestrator/internal.policy.yaml`
  - new repo-level internal policy artifact.
- `config/agentic/orchestrator/policy.yaml`
  - reduced to user-editable concerns.
- `AGENTS.md`
  - update policy guidance.
- `CLAUDE.md`
  - keep mirrored guidance aligned.

---

## 6. Acceptance Criteria

1. Effective policy is composed from bundled defaults, internal policy, and user policy.
2. Internal-only controls live in `internal.policy.yaml`.
3. User policy cannot override internal-only paths.
4. `aop init` and documentation present the new split clearly.

---

## 7. Risks and Mitigations

- Risk: migration breaks existing repositories that only have `policy.yaml`.
  - Mitigation: support a compatibility migration path and clear init/upgrade docs.
- Risk: ownership boundaries remain implicit.
  - Mitigation: encode protected paths in schema/loader logic rather than comments alone.
- Risk: defaults/internal layering becomes harder to debug.
  - Mitigation: add traceable loader diagnostics describing the final source of each policy path.
