# Corvalon HRM Change Management Policy

Last updated: 2026-07-06

This policy documents how changes to the Corvalon HRM platform -- application code and
infrastructure alike -- are authorized, tested, approved, and deployed, and the
compensating control in place while Corvalon is a solo-founder company. It maps to SOC 1
/ SOC 2 CC8.1 (change management). See `Documents/trust/SOC2_Controls_Mapping.md`.

> **Filed under SOX-03** (HRM Backlog): change management lacked segregation of duties
> and a change-approval record, and Terraform was applied out-of-band with no reviewable
> trail. This policy plus the `terraform.yml` plan/apply pipeline and the `cd.yml`
> change-evidence steps are the remediation.

---

## The four-stage flow: authorize -> test -> approve -> deploy

Every change to the platform -- application code, database migrations, and
infrastructure (Terraform) alike -- goes through the same four stages.

### 1. Authorize

Every change traces to a recorded reason before work starts:

- **Application/feature work:** an item on the HRM Backlog GitHub Project (Corvalon org
  project #3) with a Priority, Track, and body describing the change. Architectural,
  security, or convention changes additionally get a `D-NNN` entry in
  `Documents/Decisions.md` (append-only decision log).
- **Infrastructure work:** the same -- a backlog item or `D-NNN` decision, cited in the
  Terraform PR description. `deploy/terraform/**` changes are further scoped by module
  (see `Documents/architecture/` and each module's own `README.md` where present).

### 2. Test

- Application code: TDD (test written first, `Documents/CLAUDE.md` "Definition of Done").
  The pre-commit hook (`scripts/git-hook-pre-commit.sh`) mechanically blocks a commit
  that adds new Go/TS source without a paired test or an explicit lint pass.
- Infrastructure: `terraform validate` + `terraform fmt -check` at minimum; a `plan` run
  is always produced before any apply (see Stage 3).
- CI (`ci.yml`) re-runs the full gate set (build, lint, unit + integration tests, drift
  checkers) on every push, independent of what ran locally.

### 3. Approve

This is the stage SOX-03 found missing a durable record for. Two parallel tracks:

**Application code (dev/stage/prod ECS deploys, `cd.yml`):**

- `prod` deploys (`promote-prod`, `rollback-prod`) already require a GitHub Environment
  approval (`environment: prod`) -- a named reviewer must approve the run before it
  executes. This was already in place before SOX-03.
- `dev`/`stage` deploys run on push with no reviewer gate. **Compensating control**
  while Corvalon is solo-founder (see "Solo-founder compensating control" below):
  self-review checklist + retained CI evidence stand in for a second-person review.
  **Adding a `stage` GitHub Environment approval gate is the one piece of this policy
  that requires operator action** (repo Settings > Environments; GitHub Environments
  cannot be created via Terraform or a workflow file) -- see Rollout below.

**Infrastructure (Terraform, `terraform.yml`, new as of this change):**

- Every PR touching `deploy/terraform/**` runs a `terraform plan` for each of
  dev/stage/mgmt and posts the diff as a PR comment (updated on every push to the PR).
  A reviewer sees the exact infrastructure change before approving the merge -- this is
  the human review step.
- Applying that change requires a separate `workflow_dispatch` run gated by a
  `terraform-<env>` GitHub Environment with required reviewers. The approval is enforced
  two ways: GitHub pauses the run until a reviewer approves, AND the AWS IAM trust policy
  on the `gha-terraform-apply` role only allows the role to be assumed by a run that has
  cleared that specific environment's approval (`modules/terraform-ci/main.tf`) -- so the
  segregation of duties does not depend solely on the CI YAML being correct.
- `prod` Terraform is out of scope for this pipeline until OP-01 bootstraps a real prod
  AWS backend (prod has never been applied; see `deploy/terraform/environments/prod/backend.tf`
  and `Documents/runbook/NonProd_Bootstrap.md`).
- **NF-01 (prod/main.tf structurally behind dev/stage) was already remediated in D-568**
  and re-verified in the 2026-07-01 full audit (`Documents/audit/Full_Audit_2026-07-01.md`)
  -- prod's ECS service flags, Vault module, WAF module, and `cpu_architecture` are at
  parity with dev/stage today (re-checked as part of this change: `web_enabled` /
  `admin_enabled` / `docs_enabled` / `marketing_enabled` all `true`, `vault`/`waf` modules
  present, `cpu_architecture = "ARM64"`). No further prod/main.tf changes were needed to
  bring Terraform into this plan/apply pipeline; folding NF-01 in here means this pipeline
  starts from an already-correct prod config once OP-01 applies it, rather than needing a
  parity fix mid-rollout.

### 4. Deploy + retain evidence

- Every `cd.yml` deploy/promote/rollback job and every `terraform.yml` apply job runs a
  **"Record change evidence"** step that writes who triggered it, the commit, and an
  optional change reference (backlog item id or `D-NNN`) to the GitHub Actions run's step
  summary (`$GITHUB_STEP_SUMMARY`). Combined with the run log itself (build output,
  `terraform plan`/`apply` output, test results), this is the durable, reviewable
  evidence an auditor samples -- retained for as long as GitHub retains workflow run
  history for the repository (Actions history retention, not manually pruned).
- Terraform apply additionally always applies the plan file the immediately-preceding
  step computed (never a stale one), so the run log's plan output and what was actually
  applied are guaranteed identical.

---

## Solo-founder compensating control

Corvalon is a solo-founder company today (see `Documents/As_Built_Specification.md`
governance section). A true second-person reviewer is not available for every change,
which is why `dev`/`stage` deploys and the `terraform-dev` environment do not (yet) gate
on a second human. The compensating control, until staff is hired:

1. **Self-review checklist** -- before merging, the author re-reads their own diff as if
   reviewing someone else's PR: does it match the backlog item / decision it claims to
   implement? Are the tests meaningful (not tautological)? Does it touch anything outside
   its stated scope? This is weaker than independent review but is a deliberate, recorded
   practice rather than no practice.
2. **Retained CI evidence** -- the automated gates (build, lint, full test suite, drift
   checkers, the payroll-accuracy golden suite, dependency-license, security scans) run
   independently of the author's local machine and cannot be silently skipped without
   leaving a red run in the retained history. This is evidence an auditor can sample even
   without a named second approver.
3. **`prod` and `terraform-stage`/`terraform-mgmt` keep a hard approval gate** even
   solo -- the founder approves their own `prod`/infra changes from a distinct GitHub
   review action (not the same click as the push that triggered CI), which at minimum
   forces a deliberate second look before the highest-impact changes ship, and produces
   an explicit "who approved this and when" record distinct from "who authored this."
4. **Re-evaluate when hiring the first engineer or ops hire.** At that point, `dev`
   should also move to a reviewer-gated GitHub Environment, and the self-review checklist
   should be replaced by an actual second-person review requirement on every environment,
   not just `prod`/`stage`/infra.

---

## SOC 1 / SOC 2 mapping (CC8.1)

| Criteria | Control | Implementation |
|----------|---------|-----------------|
| CC8.1 | Change authorization | Backlog item / `D-NNN` decision required before work starts (Stage 1) |
| CC8.1 | Change testing | TDD + pre-commit test-coverage gate + full CI suite (Stage 2) |
| CC8.1 | Change approval | `prod` + `terraform-*` GitHub Environment reviewer gates; PR-posted Terraform plan diff; solo-founder self-review checklist for `dev`/`stage` (Stage 3) |
| CC8.1 | Change evidence retention | Change-record step summary + full run log on every deploy/apply (Stage 4) |
| CC8.1 | Infrastructure change control | `terraform.yml` plan (PR comment) + environment-gated apply, replacing the prior out-of-band operator CLI apply |

See `Documents/trust/SOC2_Controls_Mapping.md` CC8: Change Management for the live
controls table this policy backs.

---

## Rollout: what's built vs. operator-owned

| Step | Status |
|------|--------|
| `terraform.yml` plan job (PR comment, every PR touching `deploy/terraform/**`) | Built |
| `terraform.yml` apply job (environment-gated) | Built |
| `modules/terraform-ci` (gha-terraform-plan / gha-terraform-apply IAM roles) | Built (not yet applied -- see caveat below) |
| Change-evidence step on every `cd.yml` deploy/promote/rollback + `terraform.yml` apply | Built |
| This policy document + CC8.1 trust-doc update | Built |
| **Create the `stage` GitHub Environment with required reviewers** (wires into the *existing* `cd.yml` `environment: stage` gate this policy assumes) | **Operator** (repo Settings > Environments) |
| **Create `terraform-dev` / `terraform-stage` / `terraform-mgmt` GitHub Environments with required reviewers** | **Operator** (repo Settings > Environments) |
| Apply `modules/terraform-ci` (creates the two new IAM roles in AWS) | **Operator** -- every `terraform apply`, including this one, is operator-run per `Documents/runbook/NonProd_Bootstrap.md`; the module's policy is author-time-enumerated and not yet AWS-verified (see the caveat comment in `modules/terraform-ci/main.tf`) -- validate with a real `terraform plan` before relying on it for a production-affecting apply |

Until the two operator steps above are done, `terraform.yml`'s `apply` job and `cd.yml`'s
implicit `stage` gate are wired but inert (the roles/environments they reference do not
exist yet) -- the `plan` job (read-only, PR-comment) works immediately once
`gha-terraform-plan` exists, since it needs no new GitHub Environment.
