---ci---
project: acdl
phase: 8
milestone: v1.1
status: plan-as-execute
persona: lead-developer
task: T-8.0
type: prerequisite-fix
---/ci---
The Phase 07 P1 ('platform/ package shadows stdlib platform module')
became a Phase 08 blocker: boto3 imports uuid -> platform.system(),
which fails when the repo's platform/ package is on sys.path[0]. Renamed
platform/ -> acdl_platform/ (the verifier's recommended v1.2 fix, pulled
forward because Phase 08 needs boto3).
- git mv platform/ acdl_platform/ (history preserved)
- verify_phase07.sh: updated paths; removed the /tmp workaround (no
longer needed; the shadow is gone)
- verify_phase06.sh: updated the new-dirs check for the rename
- README.md: layout table updated
Both verify_phase06.sh and verify_phase07.sh still pass; confidence_signal
now imports + runs correctly from the repo root. boto3 imports clean.
6.2 KiB
ACDL Human-in-the-Loop Matrix + Separation-of-Duties Design (REQ-21)
Status: design authored in Phase 07 (milestone v1.1); v1.2 wires the gates. The spike (Phases 08-10) is dev-only; HITL is not exercised (the spike contract has
environment: dev).
The vision's "Lower Environments are Autonomous; Higher Environments are Attested" tenet [1] and the "deliberate human attestation — not as a rubber stamp" requirement [1] are the binding constraints.
Gate model (ARCHITECTURE.md §10.1)
Pre-execution gates. The contract is held in a "validated but not applied" state until the human attests. qa, prod, dr are attestation gates. No partial deployment to roll back on rejection (qa, prod); dr is a separate deployment against a separate cluster/region. The canary/deployment-rollback model is explicitly not in scope for v1.
Gitea-specific gate mechanics (D-042)
Gitea has no Environments API and ignores environment: blocks
(v1.0 D-013; re-confirmed in RESEARCH TARGET 1). The pre-execution gate
is modeled as a workflow_dispatch with approval inputs:
- qa gate:
workflow_dispatchwithapprove_qa: true; the dispatch run'sgitea.actoris the QA approver. - prod gate:
workflow_dispatchwithapprove_prod: true;gitea.actoris the SRE approver. - dr gate:
workflow_dispatchwithapprove_dr: true; same.
The approver identity of record = gitea.actor of the dispatch run
(D-042). There is no other approval-identity signal in Gitea. The v1.2
real-OIDC path (blocked on go-gitea/gitea#36988) does not change this —
OIDC authorizes the runner to AWS, it does not change how the platform
records the human approver.
Reviewer routing (ARCHITECTURE.md §10.2)
Gitea CODEOWNERS routes the right reviewer to the right gate:
- qa → QA team
- prod → SRE team
- dr → SRE team
CODEOWNERS routes; it does not enforce identity distinctness (that
is the platform-internal outbox check in
platform/separation_of_duties.py).
Full 8-concern attestation matrix (§10.4, lifted verbatim)
| Env | Concern | Evidence artifact | Freshness | Source | Attester |
|---|---|---|---|---|---|
| qa | Functional correctness | Last successful run of contract-declared validation.e2eSuite with pass rate ≥ 99% | Last 24h | Test runner declared in contract | QA |
| qa | Performance baseline | Load test report (k6 / Gatling / Locust) showing p99 latency < declared NFR and throughput > declared minimum | Last 7d | Load test runner declared in contract | QA |
| qa | Security posture | Vulnerability scan (Trivy, Snyk, or contract-declared equivalent) with no criticals/highs, signed by Security on-call | Last 24h | Security scanner + Security team signature | QA |
| qa | Contract NFRs | Platform-generated report: schema valid, NFR assertions (latency, throughput, error rate) within declared bounds | At submission | Platform contract validator | QA |
| prod | Operational readiness | Runbook published, dashboard exists, on-call rotation assigned, alerts configured | At submission, validated against last 30d history | Platform + SRE | SRE |
| prod | Incident response | Sev-1 runbook tabletop or live drill completed | Last 90d | SRE drill record | SRE |
| prod | Capacity / cost | FinOps forecast for next 30d within budget envelope, cost anomaly baseline stored, budget alert configured | Forecast valid for next 30d | FinOps + SRE | SRE |
| prod | Resilience | DR drill, chaos engineering report, backup verified | DR: 180d; chaos: 90d; backup: 30d | SRE + Platform | SRE |
| dr | dr-region deploy with the most recent prod-bound dr drill as canary evidence | dr drill report | Last 180d | SRE | SRE |
Timeout behavior (§10.5)
| Time | State | Action |
|---|---|---|
| Submission | PENDING_ATTESTATION | Notify responsible team |
| 1 business day | PENDING_ATTESTATION_WARNING | Notify team + platform on-call (elevated path); emit PENDING_ATTESTATION_TIMEOUT_WARNING event |
| 2 business days | PENDING_ATTESTATION_AUTO_FREEZE | Auto-freeze; require re-submission; emit PENDING_ATTESTATION_AUTO_FREEZE event; new submission linked via supersedes |
Implementation: a Gitea on: schedule workflow (runs hourly) that
scans the DynamoDB outbox for PENDING_ATTESTATION events with ts
older than 1/2 business days and emits the warn/freeze events. Not
implemented in the spike (dev-only).
Rejection and rollback (§10.6)
Rejection returns the contract to a HELD state with the rejection
reason captured as a PROMOTION_REJECTED event. The consumer fixes the
cause and re-submits; the new submission is linked to the rejected one
via supersedes (a contract-schema field — schemas/contract.schema.json).
The audit chain is extended, not torn up (the "Not a mutable audit
log" anti-goal). No partial deployment to roll back at any v1 gate.
Separation of duties (§10.3) — pointer to the .py
The identity-distinctness check is platform-internal, not GitHub-native, not Kyverno (in v1). Sequence:
- On promotion dev → qa, the platform reads the QA approver's identity
from the
workflow_dispatchrun'sgitea.actorand writes it to the DynamoDB outbox keyed bycontractId(attributeapprover_qa). - On promotion qa → prod, the platform reads the stored
approver_qafrom the outbox and the new SRE approver'sgitea.actorfrom the prod-dispatch run. - If
approver_qa == approver_prod, the platform blocks the prod promotion, writes aSEPARATION_OF_DUTIES_VIOLATIONevent to the evidence stream, and routes a halt artifact to the SRE on-call. - The check is implemented in
platform/separation_of_duties.py(T-7.8). The platform is the only writer to the outbox; the check is in the same process that has authority to block the promotion.
Spike scope note
The spike is dev-only (REQ-27 contract has environment: dev), so HITL
is not exercised. Phase 07 authors the design; Phase 10's
verify_phase10.sh does not assert HITL behavior. v1.2 wires the gates
against this design.
Decision trail
- D-042 — approver identity =
gitea.actorof theworkflow_dispatchrun; no Environments API in Gitea. - D-013 (v1.0) — the
workflow_dispatchapproval-input fallback, re-used for the real platform's pre-execution gate model.