# `Profile` Verify Guardrail For Permission Drift

This document records the sixth `Profile` validation track: adding a machine-checkable verify signal for profile permission drift.

## Scope

After `validation-04` and `validation-05`, the control plane could now converge profile business-object permission drift through both:

- `setup apply`
- `setup repair`

But one important gap remained:

> how do we detect future drift before a business user reports a missing page, field set, or CRUD behavior?

This pass adds that guardrail.

## Guardrail Design

The new check is intentionally wired into:

- `VerifyDeep` / `cmd/setup verify`

Current follow-up closure note:

- the current `#1970` mainline PR also adds a narrower operator path:
  - `VerifyProfile` / `cmd/setup verify-profile`
  - this surfaces only schema readiness + profile coverage drift, so unrelated
    global verify noise does not obscure the profile-specific signal

It is **not** part of pod-readiness `Verify`, because readiness should not flap on historical drift that is recoverable via `setup repair`.

Behavior:

1. count expected business objects from current metadata
2. count expected business fields from current metadata
3. count actual business-object `ObjectPermissions` / `FieldPermissions` coverage for every profile-owned `PermissionSet`
4. fail deep verify when any profile is below expected coverage

Signal quality choices:

- uses current metadata as the source of expected coverage
- excludes `%_bak_%` discovery leftovers so the signal stays actionable
- when a live environment has not yet added `_System_Object.AccessPolicy`, the
  guardrail now fails with an explicit schema-prerequisite error instead of
  switching to a legacy counting mode; operators must run `cmd/setup apply`
  before trusting the profile coverage signal
- returns a hard error for operator verify, making the drift machine-checkable
- logs the affected profiles and their actual vs expected counts

## Code Change

Primary files:

- `backend/internal/controlplane/verify.go`
- `backend/internal/controlplane/verify_profile_permissions.go`

New deep-verify helper:

- `VerifyProfilePermissionCoverage`

## Local Verification Completed

The following commands were run after the guardrail was added:

```bash
go test ./internal/controlplane/...
go test ./internal/features/permissions/...
go test ./internal/infrastructure/persistence/...
```

Observed result:

- all targeted packages passed

This validates:

- the new verify helper compiles
- `VerifyDeep` still builds and tests cleanly
- the guardrail integrates without breaking the existing control-plane package

## Operational Meaning

After this pass:

- `cmd/setup verify` can detect profile business-permission drift before the symptom reaches UI
- `cmd/setup verify-profile` can now isolate that same profile convergence signal without requiring operators to parse unrelated deep-verify failures
- `cmd/setup verify-profile` now fails fast on pre-`AccessPolicy` shared environments with a schema-prerequisite error instead of emitting a degraded profile-coverage verdict
- the failure message points operators toward:
  - `cmd/setup repair`
  - or `cmd/setup apply`

This turns the remaining `Profile` drift class into a detectable operational invariant instead of a purely user-reported problem.

## What This Validation Does Not Yet Claim

This document does **not** yet claim that a live drifted environment has already been exercised through the new verify command on this branch.

At this stage, the validated facts are:

- the guardrail exists
- it compiles
- it is wired into deep verify
- it rejects shared environments where `_System_Object.AccessPolicy` is not yet physically present until schema reconciliation runs

Live-environment execution remains part of the later staging confirmation round.

## Environment Validation Checklist

When running staging or `ci-test` validation for the full `Profile` convergence branch:

### 1. Before repair

If a drifted profile sample is available:

- run `go run ./cmd/setup verify`
- confirm the profile coverage guardrail fails
- capture the reported affected profile(s)

### 2. After repair

Run:

```bash
go run ./cmd/setup repair
go run ./cmd/setup verify
```

Confirm:

- repair completes successfully
- deep verify now passes the profile coverage guardrail

### 3. Idempotence

Re-run:

```bash
go run ./cmd/setup repair
go run ./cmd/setup verify
```

Confirm:

- stable pass result
- no recurring drift signal after the environment has converged

## Out of Scope

Still not covered by this pass:

- final staging screenshots
- non-profile permission regressions
- tenant-specific drift outside the platform-owned profile convergence path
