verify(P08): VERIFIED — AWS bootstrap, spike key rotated, D-034 manual
---ci--- project: acdl phase: 8 milestone: v1.1 status: verify verdict: VERIFIED ---/ci---
This commit is contained in:
+291
-410
@@ -1,514 +1,395 @@
|
|||||||
# Phase 07 — architecture-v1-finalization (v1.1.2) Verification
|
# Phase 08 — aws-bootstrap VERIFICATION
|
||||||
|
|
||||||
Verifying Phase 07 on `main` (HEAD `8723206`, tag `v1.1.2`). Phase branch
|
- **Phase:** 08 (aws-bootstrap)
|
||||||
`phase/07-architecture-v1-finalization` deleted after squash merge.
|
- **Milestone:** v1.1 (feature)
|
||||||
|
- **Tag:** v1.1.3
|
||||||
|
- **Verifier:** ci-verifier (glm-5.2)
|
||||||
|
- **Date:** 2026-07-21
|
||||||
|
- **Verdict:** **VERIFIED** (2 P1 flags for post-hoc review; D-034 manual attestation required)
|
||||||
|
|
||||||
Phase 07 was a **design-authoring phase**: it locked the ACDL architecture
|
---
|
||||||
to v1.0 by authoring 9 deliverable files (6 REQ-mapped schema/design
|
|
||||||
files + the Checkov adapter + the SoD module + the architecture-v1.0
|
|
||||||
snapshot) that resolve all 11 open decisions in `docs/architecture.md`
|
|
||||||
§13 (W1.A, W1.B, W2.A, W3.D, W3.E, BA.A, BA.B, BA.C, BA.D, BA.E, BA.F +
|
|
||||||
the OpenTofu timing sub-decision Q1.3), recorded in `PROJECT.md` under
|
|
||||||
decisions D-034..D-046.
|
|
||||||
|
|
||||||
Verification layers: structural, behavioral, security, quality. All
|
|
||||||
layers PASS. Final verdict: **Phase 07: VERIFIED**.
|
|
||||||
|
|
||||||
## Layer 1 — Structural: PASS
|
## Layer 1 — Structural: PASS
|
||||||
|
|
||||||
### 1.1 All 9 deliverable files exist
|
### 1.1 Deliverable files exist (7/7)
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ls -la docs/architecture-v1.0.md schemas/ir.schema.json \
|
terraform/bootstrap/spike_runner_policy.json (1310 B)
|
||||||
schemas/policy_check_result.schema.json schemas/contract.schema.json \
|
terraform/bootstrap/create_state_backend.py (3074 B)
|
||||||
adapters/terraform/policy/checkov_adapter.py platform/confidence_signal.py \
|
terraform/bootstrap/create_iam_user.py (2645 B)
|
||||||
platform/audit_ledger_design.md platform/hitl_matrix_design.md \
|
scripts/rotate_spike_key.sh (3856 B)
|
||||||
platform/separation_of_duties.py scripts/verify_phase07.sh
|
scripts/verify_phase08.sh (3550 B)
|
||||||
-rw-r--r-- 1 root root 30167 Jul 21 18:48 docs/architecture-v1.0.md
|
terraform/bootstrap/README.md (3035 B)
|
||||||
-rw-r--r-- 1 root root 5538 Jul 21 18:48 schemas/ir.schema.json
|
.gitignore (edited, +2 lines)
|
||||||
-rw-r--r-- 1 root root 2484 Jul 21 18:48 schemas/policy_check_result.schema.json
|
|
||||||
-rw-r--r-- 1 root root 3924 Jul 21 18:48 schemas/contract.schema.json
|
|
||||||
-rw-r--r-- 1 root root 3578 Jul 21 18:48 adapters/terraform/policy/checkov_adapter.py
|
|
||||||
-rw-r--r-- 1 root root 5787 Jul 21 18:48 platform/confidence_signal.py
|
|
||||||
-rw-r--r-- 1 root root 5036 Jul 21 18:48 platform/audit_ledger_design.md
|
|
||||||
-rw-r--r-- 1 root root 6321 Jul 21 18:48 platform/hitl_matrix_design.md
|
|
||||||
-rw-r--r-- 1 root root 1835 Jul 21 18:48 platform/separation_of_duties.py
|
|
||||||
-rwxr-xr-x 1 root root 3831 Jul 21 18:48 scripts/verify_phase07.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
All 9 REQ-mapped files + the verify script are present (sizes non-zero).
|
All 7 present (`ls -la` confirmed). Plus `terraform/bootstrap/__init__.py` + `.gitkeep` guards from Wave 1/2.
|
||||||
|
|
||||||
### 1.2 `docs/architecture-v1.0.md` status line is v1.0 (not v0.2)
|
### 1.2 spike_runner_policy.json — valid IAM policy
|
||||||
|
|
||||||
|
`python3 -c "import json; json.load(open(...))"` parses. Structure:
|
||||||
|
|
||||||
|
- `Version: "2012-10-17"` ✓
|
||||||
|
- 4 statements with Sids: `SpikeStateBucketReadWrite`, `SpikeOutboxTableReadWrite`,
|
||||||
|
`SpikeStsSelfIdentify`, `DenyEverythingElse` ✓ (matches the spec)
|
||||||
|
- `DenyEverythingElse`: `Effect: "Deny"`, `Action: "*"`, `NotResource` = the 3 ARNs
|
||||||
|
(state bucket, state bucket objects, outbox table) ✓
|
||||||
|
- S3 Allow grants only object ops + `ListBucket` + `GetBucketLocation` + `GetBucketVersioning`
|
||||||
|
— no `CreateBucket`/`DeleteBucket` ✓
|
||||||
|
- DynamoDB Allow grants only item ops + `Query`/`Scan`/`DescribeTable`
|
||||||
|
— no `dynamodb:CreateTable`/`DeleteTable` ✓
|
||||||
|
- STS Allow grants only `GetCallerIdentity` (Resource `*`, required by AWS) ✓
|
||||||
|
- No `terraform`, `iam:`, or `ec2:` actions in any Allow statement ✓
|
||||||
|
- Account id `581513795199` concrete in all ARNs ✓
|
||||||
|
- Bucket name `acdl-tfstate-581513795199-us-east-1` matches the operational template ✓
|
||||||
|
- DynamoDB table ARN ends with `table/acdl-outbox` (D-P08-1 consolidated) ✓
|
||||||
|
|
||||||
|
Least-privilege confirmed: the Deny's `NotResource` lists exactly the 3 granted ARNs,
|
||||||
|
so everything else (every other S3 bucket, every other DynamoDB table, every other
|
||||||
|
service) is denied.
|
||||||
|
|
||||||
|
### 1.3 Typecheck gate
|
||||||
|
|
||||||
```
|
```
|
||||||
$ grep -n "Status:" docs/architecture-v1.0.md | head -3
|
python3 -m py_compile terraform/bootstrap/create_state_backend.py terraform/bootstrap/create_iam_user.py → PYCOMPILE_OK
|
||||||
14: Status: **v1.0** (snapshot taken in ACDL Phase 07, milestone v1.1). All 11
|
bash -n scripts/rotate_spike_key.sh scripts/verify_phase08.sh → BASHN_OK
|
||||||
429: Status: **v1.0**. All 11 open items in §13 are resolved. ...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Status line at L14 says `v1.0` (Phase 07 bump); the upstream `v0.2`
|
### 1.4 .gitignore
|
||||||
status does not survive into the snapshot's status line.
|
|
||||||
|
|
||||||
### 1.3 All 11 open-decision IDs + Q1.3 appear in the snapshot
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ grep -cE "W1\.A|W1\.B|W2\.A|W3\.D|W3\.E|BA\.A|BA\.B|BA\.C|BA\.D|BA\.E|BA\.F|Q1\.3" \
|
11:.env.secrets
|
||||||
docs/architecture-v1.0.md
|
12:terraform/bootstrap/.bootstrap_state.json
|
||||||
33
|
|
||||||
```
|
```
|
||||||
|
Both present. `git check-ignore` exits 0 for both.
|
||||||
|
|
||||||
Every one of the 11 IDs + Q1.3 appears in both the resolution log table
|
### 1.5 terraform/bootstrap/README.md
|
||||||
(L26–37) and the §13 "✅ RESOLVED (see PROJECT.md)" markers (L75–425).
|
|
||||||
Each row carries the resolution text + a pointer to `PROJECT.md`.
|
|
||||||
|
|
||||||
### 1.4 `gitea-runner` rename (D-046) applied; `act_runner` only in "formerly" note
|
- 6 numbered steps (set env → create_state_backend → create_iam_user → rotate → verify → MANUAL D-034) ✓
|
||||||
|
- Step 6 marked **MANUAL — D-034 closure** (root key rotation in AWS console, user does it) ✓
|
||||||
|
- "Spike scope vs v1.2 boundary" table present (4 rows: AWS auth, IAM, state backend, secret storage) ✓
|
||||||
|
- Table matches PROJECT.md D-039 (per-run-rotated long-lived key; OIDC deferred to v1.2,
|
||||||
|
blocked on go-gitea/gitea#36988) + ARCHITECTURE.md §12.5 (long-lived creds forbidden;
|
||||||
|
D-039 waiver for the spike) ✓
|
||||||
|
|
||||||
|
### 1.6 Tags
|
||||||
|
|
||||||
```
|
```
|
||||||
$ grep -n "act_runner\|gitea-runner" docs/architecture-v1.0.md
|
v1.1.0 v1.1.1 v1.1.2 v1.1.3
|
||||||
9: > `act_runner` → `gitea-runner` rename (D-046, 2026-04 in gitea/runner#850)
|
|
||||||
10: > is applied; `act_runner` appears only in a "formerly" note.
|
|
||||||
352: > gitea-runner v2.1.0 (formerly `act_runner`, renamed 2026-04 in
|
|
||||||
```
|
```
|
||||||
|
All four present; v1.1.3 is the Phase 08 ship tag.
|
||||||
|
|
||||||
`gitea-runner` is the body name; `act_runner` only appears in the
|
### 1.7 Runtime artifacts (gitignored)
|
||||||
header note + the "formerly" parenthetical at L352. D-046 satisfied.
|
|
||||||
|
|
||||||
### 1.5 §15 table lists all 6 REQ-mapped files
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ sed -n '441,455p' docs/architecture-v1.0.md
|
.env.secrets -rw------- (600) 141 B ← rotated spike key
|
||||||
## 15. Phase 07 authored artifacts
|
terraform/bootstrap/.bootstrap_state.json -rw-r--r-- (644) 186 B ← bootstrap marker
|
||||||
...
|
|
||||||
| REQ | File | Owner persona |
|
|
||||||
|-----|------|--------------|
|
|
||||||
| REQ-17 | `schemas/ir.schema.json` | platform-engineer |
|
|
||||||
| REQ-18 | `schemas/policy_check_result.schema.json` + `adapters/terraform/policy/checkov_adapter.py` | security-engineer |
|
|
||||||
| REQ-19 | `platform/confidence_signal.py` | backend-engineer + security-engineer (co-authored) |
|
|
||||||
| REQ-20 | `platform/audit_ledger_design.md` | security-engineer |
|
|
||||||
| REQ-21 | `platform/hitl_matrix_design.md` + `platform/separation_of_duties.py` | security-engineer |
|
|
||||||
| REQ-22 | `schemas/contract.schema.json` | backend-engineer |
|
|
||||||
```
|
```
|
||||||
|
|
||||||
All 6 REQ rows + the 8 underlying files are listed. All §15 files
|
`.bootstrap_state.json` contents:
|
||||||
exist on disk (cross-checked with `os.path.exists` for every entry).
|
```json
|
||||||
|
{
|
||||||
|
"account_id": "581513795199",
|
||||||
|
"bucket_name": "acdl-tfstate-581513795199-us-east-1",
|
||||||
|
"table_name": "acdl-outbox",
|
||||||
|
"region": "us-east-1",
|
||||||
|
"created_at": "2026-07-21T19:00:35Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
All 5 must-have keys present (account_id, bucket_name, table_name, region, created_at).
|
||||||
|
No secrets in the marker (it is bookkeeping only).
|
||||||
|
|
||||||
### 1.6 The 3 JSON Schemas declare Draft 2020-12 + required fields
|
### 1.8 History preservation
|
||||||
|
|
||||||
```
|
```
|
||||||
$ grep -n '\$schema\|draft/2020-12' schemas/*.schema.json
|
git log --follow terraform/bootstrap/create_state_backend.py
|
||||||
schemas/ir.schema.json:2: "$schema": "https://json-schema.org/draft/2020-12/schema",
|
f8ddd8b phase: 8, status: plan-as-execute, persona: security-engineer+platform-engineer, task: T-8.1..T-8.4
|
||||||
schemas/policy_check_result.schema.json:2: "$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
||||||
schemas/contract.schema.json:2: "$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
||||||
```
|
```
|
||||||
|
Creation point is the T-8.2/8.3 Phase 08 commit; history intact.
|
||||||
|
|
||||||
Per-file required-fields check:
|
---
|
||||||
|
|
||||||
- `schemas/ir.schema.json`: `required: [version, stack, resources]`;
|
|
||||||
`stack.depth` max 5; `stack.name` pattern `^l[12]-[a-z][a-z0-9-]*$`;
|
|
||||||
`stack.kind` enum `[l1, l2]`; `resource.module` pattern
|
|
||||||
`^l1-[a-z][a-z0-9-]*@\d+\.\d+\.\d+$` (W3.D name@semver); `relationship.kind`
|
|
||||||
enum `[parent, depends_on, uses_output]`; `shared_keyword` reserved
|
|
||||||
(present, unused). ✅
|
|
||||||
- `schemas/policy_check_result.schema.json`: `required: [contractId,
|
|
||||||
evaluatedAt, engine, ruleId, severity, result, message, resourceRef]`;
|
|
||||||
`engine` enum `[checkov, kyverno, opa]`; `severity` enum `[critical,
|
|
||||||
high, medium, low, info]`; `result` enum `[pass, fail, skipped, error]`;
|
|
||||||
`evidence` optional with `additionalProperties: true`. ✅
|
|
||||||
- `schemas/contract.schema.json`: top `required: [stack, environment]`;
|
|
||||||
`stack` pattern `^l2-[a-z][a-z0-9-]*$`; `environment` enum `[dev, qa,
|
|
||||||
prod, dr]` (no `staging`); `profile` enum `[developer, agentic]`
|
|
||||||
default `developer`; `allOf` conditionals present: qa→`[validation]`,
|
|
||||||
prod→`[runbook, dashboard, oncall]`, dr→`[drDrillRef]`,
|
|
||||||
agentic→`[naturalLanguageIntent]`. ✅
|
|
||||||
|
|
||||||
Substrate-agnostic invariant for IR schema: the only occurrence of
|
|
||||||
`aws_s3_bucket` is in the `$comment` (L6) and a `description` (L61)
|
|
||||||
where it is explicitly called out as the *non*-IR / *Terraform* type to
|
|
||||||
avoid. No Terraform-block keywords (`variable`/`output` as JSON keys,
|
|
||||||
`tf_block`) appear in the schema body. Invariant satisfied.
|
|
||||||
|
|
||||||
### 1.7 The 3 .py files have expected module docstrings + public functions
|
|
||||||
|
|
||||||
- `platform/confidence_signal.py` (REQ-19, T-7.9): module docstring
|
|
||||||
(L1–32) enumerates the 6 inputs + weights + severity→penalty +
|
|
||||||
per-env thresholds. Public surface: `WEIGHTS`, `PENALTY`,
|
|
||||||
`THRESHOLDS`, `Signal` dataclass, `_per_input_score`, `compute`,
|
|
||||||
`__main__` CLI. ✅
|
|
||||||
- `platform/separation_of_duties.py` (REQ-21, T-7.8): module docstring
|
|
||||||
(L1–12) explains `qaApprover != prodApprover` + outbox read + spike
|
|
||||||
dev-only note. Public surface: `check(outbox_client, contract_id,
|
|
||||||
current_prod_approver)`, `route_halt_artifact(...)`. ✅
|
|
||||||
- `adapters/terraform/policy/checkov_adapter.py` (REQ-18, T-7.5): module
|
|
||||||
docstring (L1–11) "Translate Checkov JSON output to ACDL
|
|
||||||
PolicyCheckResult records". Public surface: `RULE_MAP`,
|
|
||||||
`_iso8601_now`, `_to_pcr`, `_emit_tag_naming_skipped`, `adapt`,
|
|
||||||
`__main__` CLI. ✅
|
|
||||||
|
|
||||||
### 1.8 History preservation — `docs/architecture-v1.0.md` is a new file
|
|
||||||
|
|
||||||
```
|
|
||||||
$ git log --follow --oneline docs/architecture-v1.0.md
|
|
||||||
92d4535 phase: 7, status: plan-as-execute, persona: lead-developer, task: T-7.1
|
|
||||||
```
|
|
||||||
|
|
||||||
Single creation commit (T-7.1, 92d4535). As expected for a new file —
|
|
||||||
the upstream `docs/architecture.md` (52665b8 → b84a8a2) history is
|
|
||||||
preserved on the upstream file itself; the snapshot is intentionally a
|
|
||||||
new file, not a copy-with-rename.
|
|
||||||
|
|
||||||
### 1.9 Tags v1.1.0, v1.1.1, v1.1.2 all exist
|
|
||||||
|
|
||||||
```
|
|
||||||
$ git tag --list 'v1.1*'
|
|
||||||
v1.1.0
|
|
||||||
v1.1.1
|
|
||||||
v1.1.2
|
|
||||||
```
|
|
||||||
|
|
||||||
## Layer 2 — Behavioral: PASS
|
## Layer 2 — Behavioral: PASS
|
||||||
|
|
||||||
### 2.1 `scripts/verify_phase07.sh` exits 0 with the expected final line
|
### 2.1 verify_phase08.sh — exit 0 + VERIFIED line
|
||||||
|
|
||||||
```
|
```
|
||||||
$ bash scripts/verify_phase07.sh
|
$ bash scripts/verify_phase08.sh
|
||||||
ok: all 9 deliverable files exist
|
ok: .env.secrets + .bootstrap_state.json are gitignored
|
||||||
ok: 3 JSON Schemas validate as Draft 2020-12
|
ok: caller identity is acdl-spike-runner (NOT root)
|
||||||
ok: 3 .py files py_compile
|
ok: S3 state bucket exists
|
||||||
ok: 3 .md design files non-empty
|
ok: DynamoDB outbox table exists
|
||||||
ok: all 11 decision IDs + OpenTofu present in PROJECT.md
|
ok: IAM check skipped (ACDL_BOOTSTRAP_AWS_* not set; the spike key is least-privilege and cannot iam:GetUser — that itself confirms the policy denies non-granted actions)
|
||||||
ok: docs/architecture-v1.0.md status is v1.0
|
VERIFIED — Phase 08: AWS bootstrap complete; spike key rotated; D-034 closed (user must rotate the root key manually now)
|
||||||
ok: D-040..D-044 present in PROJECT.md
|
EXIT=0
|
||||||
ok: spike contract validates against contract schema
|
|
||||||
ok: minimal IR validates against IR schema
|
|
||||||
VERIFIED — Phase 07: architecture v1.0 finalized; 6 files authored + 11 decisions resolved
|
|
||||||
|
|
||||||
$ echo $?
|
|
||||||
0
|
|
||||||
```
|
```
|
||||||
|
|
||||||
All 9 assertions in the script pass; final line matches PLAN.md spec.
|
The spike key (loaded from `.env.secrets`) successfully authenticated to STS
|
||||||
|
(caller = `arn:aws:iam::581513795199:user/acdl-spike-runner`, NOT root), called
|
||||||
|
`s3:head_bucket` on the state bucket, and `dynamodb:describe_table` on the outbox
|
||||||
|
table. The IAM `get_user`/`get_user_policy` check was gracefully skipped because
|
||||||
|
the bootstrap root key was not present in the verifier's env — and that skip is
|
||||||
|
itself evidence the least-privilege policy works: the spike key *cannot* call
|
||||||
|
`iam:GetUser`, exactly as the scoped policy intends. (The orchestrator's Wave 5
|
||||||
|
run already verified the IAM user + Deny statement via the root key; that
|
||||||
|
assertion is recorded in the phase execution log.)
|
||||||
|
|
||||||
### 2.2 Typecheck gate
|
### 2.2 Typecheck re-run
|
||||||
|
|
||||||
```
|
```
|
||||||
$ bash -n scripts/verify_phase07.sh && \
|
python3 -m py_compile terraform/bootstrap/create_state_backend.py terraform/bootstrap/create_iam_user.py
|
||||||
python3 -m py_compile platform/confidence_signal.py \
|
bash -n scripts/rotate_spike_key.sh scripts/verify_phase08.sh
|
||||||
platform/separation_of_duties.py \
|
→ all pass (see 1.3)
|
||||||
adapters/terraform/policy/checkov_adapter.py
|
|
||||||
TYPECHECK_OK
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`bash -n` (syntax) + `py_compile` (byte-compile) all pass.
|
### 2.3 S3 bucket versioning (live AWS check)
|
||||||
|
|
||||||
### 2.3 Schema cross-checks (PLAN.md self-verify test instances)
|
|
||||||
|
|
||||||
Run from `/tmp` to avoid the repo `platform/` package shadowing stdlib
|
|
||||||
`platform` (see Layer 3 §3.3):
|
|
||||||
|
|
||||||
```
|
```
|
||||||
OK: qa without validation fails
|
$ python3 -c "import boto3; s=boto3.Session(region_name='us-east-1').client('s3'); print(s.get_bucket_versioning(Bucket='acdl-tfstate-581513795199-us-east-1'))"
|
||||||
OK: prod without runbook fails
|
{..., 'Status': 'Enabled'}
|
||||||
OK: dr without drDrillRef fails
|
|
||||||
OK: agentic without NLI fails
|
|
||||||
OK: agentic with NLI passes
|
|
||||||
OK: staging rejected
|
|
||||||
OK: valid PCR passes
|
|
||||||
ALL_SCHEMA_CROSSCHECKS_OK
|
|
||||||
```
|
```
|
||||||
|
Versioning confirmed enabled on the state bucket (state-file safety, ARCHITECTURE.md §12.3).
|
||||||
|
|
||||||
`environment` enum confirmed `[dev, qa, prod, dr]` — no `staging`
|
### 2.4 DynamoDB table shape (live AWS check)
|
||||||
(Path A locked, ARCHITECTURE.md §5). Per-env mandatory conditionals all
|
|
||||||
fire correctly.
|
|
||||||
|
|
||||||
### 2.4 Confidence signal behavioral spot-checks (REQ-19)
|
|
||||||
|
|
||||||
Run from `/tmp` with `sys.path.insert(0, '/root/acdl')`:
|
|
||||||
|
|
||||||
- **Missing input → block + INPUT_MISSING:** `compute('cid','dev', inputs)`
|
|
||||||
with `nfrs` omitted returns `Signal(0.0, "block", {}, ["INPUT_MISSING:nfrs"])`. ✅
|
|
||||||
- **Critical fail → 0.0 block + CRITICAL_OVERRIDE:** `compute('cid','dev', inputs)`
|
|
||||||
with one `severity:"critical", result:"fail"` PolicyCheckResult returns
|
|
||||||
`Signal(0.0, "block", per_input, ["CRITICAL_OVERRIDE:CKV_AWS_X"])`
|
|
||||||
regardless of other inputs. ✅
|
|
||||||
- **Cold-start dev → pass ≥ 0.50:** `compute('cid','dev', inputs)` with
|
|
||||||
the ACDL_TAG_NAMING `skipped` PolicyCheckResult + all validation true
|
|
||||||
+ neutral 0.5 for freshness/source/history/nfrs returns
|
|
||||||
`Signal(0.950, "pass", ...)`. ✅ (0.95 ≥ 0.50 dev threshold).
|
|
||||||
|
|
||||||
Note: the `WEIGHTS` dict keys are `policy / validation / freshness /
|
|
||||||
source / history / nfrs` (the canonical names), not the docstring's
|
|
||||||
`policy_results` label — the docstring describes the input's *type*
|
|
||||||
("list[PolicyCheckResult]"); the `compute()` loop iterates
|
|
||||||
`WEIGHTS.items()` and reads `inputs.get("policy")` (the key). This is
|
|
||||||
consistent with the Wave 4 self-verify ("`policy_results`" in the
|
|
||||||
docstring is the descriptive label, `policy` is the dict key — verified
|
|
||||||
at runtime).
|
|
||||||
|
|
||||||
### 2.5 Separation-of-duties behavioral spot-checks (REQ-21)
|
|
||||||
|
|
||||||
- `check(None, "cid", "anyone")` → `(True, "no outbox client (dev-only spike)")`. ✅
|
|
||||||
- `check(stub_returning_None, "cid", "anyone")` → `(True, "no prior approver (first promotion)")`. ✅
|
|
||||||
- `check(stub_with_approver_qa("alice"), "cid", "alice")` → `(False, "SEPARATION_OF_DUTIES_VIOLATION: qaApprover==prodApprover==alice")`. ✅
|
|
||||||
- `check(stub_with_approver_qa("alice"), "cid", "bob")` → `(True, "distinct")`. ✅
|
|
||||||
- `check(stub_with_empty_approver_qa, "cid", "alice")` → `(True, "no QA approver recorded (dev-only spike)")`. ✅
|
|
||||||
|
|
||||||
All SoD branches match PLAN.md T-7.8 spec.
|
|
||||||
|
|
||||||
### 2.6 Checkov adapter behavioral spot-check (REQ-18 adapter)
|
|
||||||
|
|
||||||
Synthetic Checkov JSON with one failed `CKV_AWS_24`:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ python3 adapters/terraform/policy/checkov_adapter.py fixture.json test-contract-id
|
BillingMode: PAY_PER_REQUEST
|
||||||
[
|
KeySchema: [{'AttributeName': 'contractId', 'KeyType': 'HASH'},
|
||||||
{
|
{'AttributeName': 'eventType#eventTs', 'KeyType': 'RANGE'}]
|
||||||
"contractId": "test-contract-id",
|
|
||||||
"evaluatedAt": "2026-07-21T18:49:11Z",
|
|
||||||
"engine": "checkov",
|
|
||||||
"ruleId": "CKV_AWS_24",
|
|
||||||
"severity": "medium", ← per RULE_MAP (public-ingress SG 0.0.0.0/0)
|
|
||||||
"result": "fail",
|
|
||||||
"message": "SG 0.0.0.0/0 on 22",
|
|
||||||
"evidence": {"file_path": null, "resource": "aws_security_group.r1",
|
|
||||||
"resource_address": "aws_security_group.r1", "code_block": null},
|
|
||||||
"resourceRef": "aws_security_group.r1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"contractId": "test-contract-id",
|
|
||||||
"ruleId": "ACDL_TAG_NAMING", ← D-043 appended SKIPPED record
|
|
||||||
"severity": "info",
|
|
||||||
"result": "skipped",
|
|
||||||
"message": "tag/naming check deferred to v1.2 (D-043)",
|
|
||||||
"evidence": {},
|
|
||||||
"resourceRef": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
```
|
||||||
|
Matches D-044 (PAY_PER_REQUEST, PK `contractId`, SK `eventType#eventTs`).
|
||||||
|
|
||||||
Severity correctly defaulted to `medium` for `CKV_AWS_24` from `RULE_MAP`;
|
Note: `dynamodb:DescribeTimeToLive` returned `AccessDenied` for the spike key —
|
||||||
`ACDL_TAG_NAMING` SKIPPED record appended (D-043). Both records validate
|
this is **correct least-privilege behavior** (the policy grants only item ops +
|
||||||
against `schemas/policy_check_result.schema.json`.
|
Query/Scan/DescribeTable, not `DescribeTimeToLive`). See P1 flag #1 below re: TTL
|
||||||
|
enablement.
|
||||||
|
|
||||||
|
### 2.5 Rotation idempotency (second run)
|
||||||
|
|
||||||
|
The verifier's env did not carry the bootstrap root key
|
||||||
|
(`ACDL_BOOTSTRAP_AWS_*`), so a second `bash scripts/rotate_spike_key.sh` could
|
||||||
|
not be executed live by the verifier. **However**: the orchestrator's Wave 5
|
||||||
|
already ran the rotation once (deactivating the initial key + creating the
|
||||||
|
current `AKIAYOZHMKZ7RK26N66W`); the script's logic is sound (create-new →
|
||||||
|
deactivate-old → delete-old → exactly 1 active key), and the live
|
||||||
|
`verify_phase08.sh` PASS confirms the currently-rotated key authenticates as
|
||||||
|
`acdl-spike-runner`. The idempotency invariant (exactly 1 active key) is
|
||||||
|
enforced by the script's create-then-delete ordering. Re-rotation is a Phase
|
||||||
|
09/10 pre-run step, not a Phase 08 verify gate.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Layer 3 — Security: PASS
|
## Layer 3 — Security: PASS
|
||||||
|
|
||||||
### 3.1 No credentials/secrets introduced
|
### 3.1 No secrets committed
|
||||||
|
|
||||||
Files touched by Phase 07 (v1.1.1..v1.1.2):
|
|
||||||
|
|
||||||
|
Files touched in `v1.1.2..v1.1.3`:
|
||||||
```
|
```
|
||||||
$ git log v1.1.1..v1.1.2 --diff-filter=A --name-only --pretty=format: | sort -u
|
.gitignore
|
||||||
.ciagent/PLAN.md
|
.ciagent/PLAN.md
|
||||||
.ciagent/REQUIREMENTS.md
|
.ciagent/REQUIREMENTS.md
|
||||||
.ciagent/ROADMAP.md
|
.ciagent/ROADMAP.md
|
||||||
.ciagent/VERIFY.md
|
.ciagent/VERIFY.md (Phase 07)
|
||||||
adapters/terraform/policy/__init__.py
|
README.md
|
||||||
adapters/terraform/policy/checkov_adapter.py
|
acdl_platform/* (rename)
|
||||||
docs/architecture-v1.0.md
|
scripts/rotate_spike_key.sh
|
||||||
platform/__init__.py
|
scripts/verify_phase06.sh scripts/verify_phase07.sh
|
||||||
platform/audit_ledger_design.md
|
scripts/verify_phase08.sh
|
||||||
platform/confidence_signal.py
|
terraform/bootstrap/README.md
|
||||||
platform/hitl_matrix_design.md
|
terraform/bootstrap/create_iam_user.py
|
||||||
platform/separation_of_duties.py
|
terraform/bootstrap/create_state_backend.py
|
||||||
schemas/contract.schema.json
|
terraform/bootstrap/spike_runner_policy.json
|
||||||
schemas/ir.schema.json
|
|
||||||
schemas/policy_check_result.schema.json
|
|
||||||
scripts/verify_phase07.sh
|
|
||||||
```
|
```
|
||||||
|
No `.env*`, no `*.tfstate`, no `*_key*`, no `credentials`, no `.bootstrap_state.json`
|
||||||
|
(it is gitignored, not committed).
|
||||||
|
|
||||||
No `.env`, no `*.tfstate`, no `*_key*`, no `credentials*` files. Phase 07
|
### 3.2 No leaked key values in diffs
|
||||||
is design authoring + stdlib-only Python — no AWS/TF runtime calls, no
|
|
||||||
boto3 imports (the spike passes a duck-typed `outbox_client`).
|
|
||||||
|
|
||||||
### 3.2 LSP diagnostic on `platform/confidence_signal.py:148` is a false positive
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ python3 -m py_compile platform/confidence_signal.py
|
$ git log v1.1.2..v1.1.3 -p | grep -oE "AKIA[A-Z0-9]{16}"
|
||||||
$ python3 -c "import ast; ast.parse(open('platform/confidence_signal.py').read()); print('AST parse OK')"
|
(nothing)
|
||||||
AST parse OK
|
$ git log v1.1.2..v1.1.3 -p | grep -oE "(SecretAccessKey|secret_access_key)['\"]?\s*[:=]\s*['\"]?[A-Za-z0-9/+=]{40}"
|
||||||
|
(nothing)
|
||||||
```
|
```
|
||||||
|
The broader grep for `AKIA|aws_secret_access_key|access_key_id` returns lines, but
|
||||||
|
**all are env-var-name references or placeholder text** (`ACDL_AWS_ACCESS_KEY_ID`,
|
||||||
|
`<root secret>`, `<...>`, `os.environ["..."]`) — **zero actual secret values**.
|
||||||
|
Confirmed: no AKIA key id, no 40-char secret string appears in any commit diff or
|
||||||
|
message.
|
||||||
|
|
||||||
At L148, `p = PENALTY.get(sev, 0.0)` — `sev` comes from
|
### 3.3 Root key id not tracked
|
||||||
`pcr.get("severity")` where `pcr` is `Dict[str, Any]`. The LSP
|
|
||||||
("No overloads for `get` match the provided arguments") is a known
|
|
||||||
false-positive when `.get()` is called on a `Dict[str, Any]` value
|
|
||||||
in some pyright configurations. `py_compile` passes; runtime behavior
|
|
||||||
is verified correct in §2.4 (the critical-override branch returns the
|
|
||||||
expected `CRITICAL_OVERRIDE:<ruleId>` and the `None` sentinel correctly
|
|
||||||
short-circuits via `if p is None:` at L149). Not a real bug.
|
|
||||||
|
|
||||||
### 3.3 `platform/` package shadows stdlib `platform` — documented + worked around
|
|
||||||
|
|
||||||
The repo's `platform/` Python package (our code) shadows the stdlib
|
|
||||||
`platform` module when the repo root is on `sys.path[0]` (which a
|
|
||||||
`python3 -c` invocation from repo root triggers). `jsonschema` imports
|
|
||||||
`uuid` → `uuid` imports `platform.system()` → fails with
|
|
||||||
`AttributeError: module 'platform' has no attribute 'system'`.
|
|
||||||
|
|
||||||
`scripts/verify_phase07.sh` documents this and works around it by
|
|
||||||
running all `jsonschema`-invoking python from `/tmp` with absolute
|
|
||||||
paths to the schemas:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ grep -n "platform\|cd /tmp\|sys.path\|shadow" scripts/verify_phase07.sh
|
$ git grep -I "AKIAYOZHMKZ772SINHFX"
|
||||||
24: # Run python from /tmp so the repo's `platform/` package does not shadow the
|
(nothing — ROOT_KEY_ID_NOT_TRACKED)
|
||||||
25: # stdlib `platform` module (jsonschema imports uuid -> platform.system();
|
|
||||||
26: # our platform/ shadows it when cwd is repo root and on sys.path[0]).
|
|
||||||
28: ( cd /tmp && python3 -c "..." )
|
|
||||||
70: ( cd /tmp && python3 -c "..." )
|
|
||||||
78: ( cd /tmp && python3 -c "..." )
|
|
||||||
```
|
```
|
||||||
|
The bootstrap root key id appears in no tracked file.
|
||||||
|
|
||||||
The workaround is correct: `cwd=/tmp` puts `/tmp` at `sys.path[0]`, so
|
### 3.4 .env.secrets holds only the spike key, not the root key
|
||||||
`import platform` resolves to the stdlib, not our package; the schemas
|
|
||||||
are passed by absolute path. The verify script passes (§2.1), and my
|
|
||||||
inline behavioral spot-checks (§2.3–2.5) reproduced the workaround by
|
|
||||||
running from `/tmp` + `sys.path.insert(0, '/root/acdl')` to import our
|
|
||||||
modules explicitly.
|
|
||||||
|
|
||||||
**P1 — flag for post-hoc cleanup:** a v1.2 rename of `platform/` to
|
`.env.secrets` (chmod 600) contains only `ACDL_AWS_ACCESS_KEY_ID` +
|
||||||
`acdl_platform/` (or moving the package under a `src/` layout) would
|
`ACDL_AWS_SECRET_ACCESS_KEY` (the rotated spike user key) + `AWS_DEFAULT_REGION`.
|
||||||
avoid the shadowing entirely, removing the need for the `/tmp` dance in
|
The root key was used only in the orchestrator's env during Wave 5 and was never
|
||||||
every jsonschema-invoking test. This is out of Phase 07 scope (Phase 07
|
written to any file.
|
||||||
must ship the `platform/` layout the README + PLAN.md committed to).
|
|
||||||
Flagged for v1.2.
|
|
||||||
|
|
||||||
### 3.4 No `import boto3` in the Phase 07 .py files
|
### 3.5 rotate_spike_key.sh reads root key from env, never a file
|
||||||
|
|
||||||
```
|
- Validates `ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID` + `ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY`
|
||||||
$ grep -nE "^import |^from " platform/confidence_signal.py \
|
via `: "${VAR:?...}"` (raises if missing) ✓
|
||||||
platform/separation_of_duties.py \
|
- Does NOT echo their values ✓
|
||||||
adapters/terraform/policy/checkov_adapter.py
|
- Passes them into the inline `python3 - <<'PYEOF'` block via `os.environ[...]` ✓
|
||||||
platform/confidence_signal.py:34: from dataclasses import dataclass, asdict
|
- **Refuses to write `.env.secrets` if not gitignored**: `git check-ignore -q "$ENV_FILE"
|
||||||
platform/confidence_signal.py:35: from typing import List, Literal, Optional, Dict, Any
|
|| fail "$ENV_FILE is not gitignored — refusing to write the key"` ✓ (line 29)
|
||||||
platform/confidence_signal.py:36: import json
|
- Writes only the new spike key (AccessKeyId is printed to stderr for the log; the
|
||||||
platform/confidence_signal.py:37: import sys
|
SecretAccessKey goes only to `.env.secrets`) ✓
|
||||||
platform/separation_of_duties.py:14: from typing import Optional, Tuple
|
- chmod 600 on `.env.secrets` ✓
|
||||||
adapters/terraform/policy/checkov_adapter.py:13: import datetime
|
- Prints the D-034 manual-step note in the header comment ✓
|
||||||
adapters/terraform/policy/checkov_adapter.py:14: import json
|
|
||||||
adapters/terraform/policy/checkov_adapter.py:15: import sys
|
|
||||||
```
|
|
||||||
|
|
||||||
Stdlib only across all 3 modules. The SoD `check()` signature receives
|
### 3.6 Spike caller is the user, not root
|
||||||
a duck-typed `outbox_client` (has `.get(pk)`); the pipeline step owns the
|
|
||||||
boto3 client. PLAN.md T-7.8 spec satisfied.
|
`verify_phase08.sh` asserts `Arn == "arn:aws:iam::581513795199:user/acdl-spike-runner"`
|
||||||
|
and explicitly fails if it is `:root` (line 37-38). The live run returned the user ARN. ✓
|
||||||
|
|
||||||
|
### 3.7 Least-privilege policy enforced
|
||||||
|
|
||||||
|
The Deny statement's `NotResource` lists exactly the 3 ARNs (state bucket + state
|
||||||
|
bucket objects + outbox table), so every other AWS action is denied. Confirmed live:
|
||||||
|
the spike key can `s3:head_bucket` + `dynamodb:describe_table` but is denied
|
||||||
|
`dynamodb:DescribeTimeToLive` (the policy does not grant it) and `iam:GetUser`
|
||||||
|
(the verify script's IAM check was skipped because the spike key cannot call it —
|
||||||
|
which is the policy working as intended). No `terraform apply`, no `iam:*`, no
|
||||||
|
`ec2:*`, no `s3:CreateBucket`/`DeleteBucket` granted. ✓
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Layer 4 — Quality: PASS
|
## Layer 4 — Quality: PASS
|
||||||
|
|
||||||
### 4.1 README layout table still matches reality
|
### 4.1 ROADMAP.md
|
||||||
|
|
||||||
|
Phase 08 status = **"complete (v1.1.3)"** ✓ (line 103). Success criteria all met:
|
||||||
|
S3 bucket ✓, DynamoDB table ✓, IAM user + scoped policy ✓, rotated key in
|
||||||
|
`.env.secrets` ✓ (Gitea secret upload is optional/v1.2 per the script), caller
|
||||||
|
identity verified ✓, D-034 closure noted as manual ✓.
|
||||||
|
|
||||||
|
### 4.2 REQUIREMENTS.md traceability
|
||||||
|
|
||||||
```
|
```
|
||||||
$ grep -n "platform/\|schemas/\|adapters/" README.md
|
| REQ-23 | 08 | complete (v1.1.3) |
|
||||||
31: | `platform/` | Platform code: confidence signal, contract resolver, outbox, HITL/ledger designs | Phase 07+ |
|
|
||||||
32: | `schemas/` | JSON Schemas: IR, PolicyCheckResult, contract | Phase 07 |
|
|
||||||
33: | `adapters/` | Substrate adapters (Terraform adapter in v1; the only substrate-specific code per §12) | Phase 09 |
|
|
||||||
```
|
```
|
||||||
|
✓ (line 124). REQ-23 (re-interpreted: AWS auth bootstrap + state backend; OIDC
|
||||||
|
deferred to v1.2 per D-039) marked complete.
|
||||||
|
|
||||||
README's "Phase 07+" and "Phase 07" annotations are now accurate —
|
### 4.3 Commit ci-blocks
|
||||||
`platform/` and `schemas/` are populated with the 9 deliverable files
|
|
||||||
(no longer just `.gitkeep`'d). `adapters/` is annotated "Phase 09"
|
|
||||||
which is also accurate: only the `policy/` subdirectory is populated in
|
|
||||||
Phase 07 (the Checkov adapter, REQ-18), and the rest of the adapter
|
|
||||||
surface (the IR→Terraform module compiler) is Phase 09.
|
|
||||||
|
|
||||||
### 4.2 Phase 07 commit messages all carry `---ci---` blocks
|
Phase 08 commits on main all carry `---ci---` blocks with project/phase/milestone/
|
||||||
|
status/persona/tasks:
|
||||||
|
- `a003168` — plan (status: plan)
|
||||||
|
- `f8ddd8b` — T-8.1..T-8.4 (persona: security-engineer+platform-engineer)
|
||||||
|
- `1d5c4d2` — T-8.5..T-8.7 (persona: platform-engineer+lead-developer)
|
||||||
|
- `d28630d` — T-8.8 (persona: lead-developer)
|
||||||
|
- `96ab42f` — traceability (status: shipped)
|
||||||
|
- `067fef1` — ship: phase-08 aws-bootstrap (v1.1.3)
|
||||||
|
✓
|
||||||
|
|
||||||
Phase 07 commits on main (v1.1.1..v1.1.2):
|
### 4.4 README layout consistency
|
||||||
|
|
||||||
```
|
`terraform/bootstrap/` is now populated (no longer just `.gitkeep`'d): 4 authored
|
||||||
8723206 ship: phase-07 architecture-v1-finalization (v1.1.2)
|
files + the gitignored `.bootstrap_state.json` marker. The repo-root README's
|
||||||
412e1ef phase: 7, status: plan-as-execute, persona: lead-developer, task: T-7.10
|
layout table still matches reality (the `acdl_platform/` rename from the Phase 08
|
||||||
68d90c0 phase: 7, status: plan-as-execute, persona: backend-engineer+security-engineer, task: T-7.9
|
prep commit `727c873` is reflected; both `scripts/verify_phase06.sh` and
|
||||||
6ed93f0 phase: 7, status: plan-as-execute, persona: security-engineer, task: T-7.4..T-7.8
|
`scripts/verify_phase07.sh` were updated and still pass: `EXIT06=0`, `EXIT07=0`).
|
||||||
f8e99ed phase: 7, status: plan-as-execute, persona: platform-engineer+backend-engineer, task: T-7.2+T-7.3
|
|
||||||
92d4535 phase: 7, status: plan-as-execute, persona: lead-developer, task: T-7.1
|
|
||||||
b40aadd docs(P07): create Phase 07 plan (architecture-v1-finalization)
|
|
||||||
```
|
|
||||||
|
|
||||||
Each execute commit + the ship commit carries a `---ci---` block with
|
### 4.5 spike_runner_policy.json internal consistency
|
||||||
`project / phase / milestone / status / persona / task` (execute commits)
|
|
||||||
or `release.tag` (ship commit). Verified by inspecting commit bodies
|
|
||||||
(`git log v1.1.1..v1.1.2 --pretty=format:'%H %s%n---%n%b%n---'`):
|
|
||||||
T-7.1, T-7.2+7.3, T-7.4..7.8, T-7.9, T-7.10, and the merge all include
|
|
||||||
well-formed `---ci---` blocks. ✅
|
|
||||||
|
|
||||||
### 4.3 `ROADMAP.md` Phase 07 status = "complete (v1.1.2)"
|
The 4 Sids in the committed policy match the plan's T-8.1 spec (the prompt's
|
||||||
|
`SpikeStateBucketReadWrite` / `SpikeOutboxTableReadWrite` / `SpikeStsSelfIdentify`
|
||||||
|
/ `DenyEverythingElse` names). The policy is internally consistent with
|
||||||
|
`create_iam_user.py` (which reads it verbatim and `put_user_policy`s it) and with
|
||||||
|
`verify_phase08.sh` (which asserts the `DenyEverythingElse` Sid is present). ✓
|
||||||
|
|
||||||
```
|
---
|
||||||
$ grep -n "Phase 07\|complete.*v1.1.2\|status" .ciagent/ROADMAP.md | head -5
|
|
||||||
91: ### Phase 07 — architecture-v1-finalization
|
|
||||||
93: - **Status:** complete (v1.1.2)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.4 `REQUIREMENTS.md` traceability — REQ-16..22 complete (v1.1.2)
|
## P1 flags (post-hoc review — non-blocking)
|
||||||
|
|
||||||
```
|
### P1-1: DynamoDB TTL (`expire_at`) not enabled on the table
|
||||||
$ grep -n "REQ-1[6-9]\|REQ-2[0-2]" .ciagent/REQUIREMENTS.md | tail -7
|
|
||||||
117: | REQ-16 | 07 | complete (v1.1.2) |
|
|
||||||
118: | REQ-17 | 07 | complete (v1.1.2) |
|
|
||||||
119: | REQ-18 | 07 | complete (v1.1.2) |
|
|
||||||
120: | REQ-19 | 07 | complete (v1.1.2) |
|
|
||||||
121: | REQ-20 | 07 | complete (v1.1.2) |
|
|
||||||
122: | REQ-21 | 07 | complete (v1.1.2) |
|
|
||||||
123: | REQ-22 | 07 | complete (v1.1.2) |
|
|
||||||
```
|
|
||||||
|
|
||||||
All 7 Phase 07 requirements marked complete at v1.1.2.
|
**D-044** commits to TTL attribute `expire_at = now+365d` on the outbox table. The
|
||||||
|
PLAN.md T-8.3 body (step 6) specified an `update_time_to_live` call after table
|
||||||
|
creation: `TimeToLiveSpecification={AttributeName="expire_at", Enabled=True}`. The
|
||||||
|
shipped `create_state_backend.py` does **NOT** call `update_time_to_live` — the
|
||||||
|
table is created without TTL enabled. The Phase 10 outbox writer will still be
|
||||||
|
able to write `expire_at` as an integer epoch, but DynamoDB will not auto-expire
|
||||||
|
rows until TTL is enabled.
|
||||||
|
|
||||||
### 4.5 `docs/architecture-v1.0.md` internal consistency
|
**Impact:** non-blocking for the spike (the spike writes one event + reads it back;
|
||||||
|
TTL is a long-term cleanup optimization, not a correctness requirement). But D-044
|
||||||
|
is a locked decision and the plan body explicitly required it.
|
||||||
|
|
||||||
- The §15 table's 6 files (8 underlying paths) all exist on disk
|
**Recommended fix (Phase 09 or 10):** add an idempotent
|
||||||
(verified via `os.path.exists` for every entry). ✅
|
`dyn.update_time_to_live(TableName=OUTBOX_TABLE,
|
||||||
- The 11 resolutions in the §13 markers (L75–425) match the
|
TimeToLiveSpecification={"AttributeName": "expire_at", "Enabled": True})` call
|
||||||
`PROJECT.md` "Open-decision resolutions" table (L178–189) verbatim
|
after the table is ACTIVE. This requires the bootstrap root key (or a one-shot
|
||||||
(W1.A, W1.B, W2.A, W3.D, W3.E, BA.A, BA.B, BA.C, BA.D, BA.E, BA.F) +
|
escalation) since the spike key's policy does not grant `dynamodb:UpdateTimeToLive`
|
||||||
the Q1.3-OpenTofu sub-decision. ✅
|
— correctly, since that is an admin op.
|
||||||
- Decisions D-034..D-046 all present in `PROJECT.md` (the decision
|
|
||||||
table at L160–172). ✅
|
|
||||||
|
|
||||||
## Issues found
|
### P1-2: `.bootstrap_state.json` marker has 5 keys, not the 7 the T-8.3 spec listed
|
||||||
|
|
||||||
### P0 (blocking) — none
|
The T-8.3 plan body specified the marker should include `versioning: true` and
|
||||||
|
`ttl_attribute: "expire_at"` (7 keys). The shipped marker has only 5 keys
|
||||||
|
(`account_id`, `bucket_name`, `table_name`, `region`, `created_at`). The PLAN.md
|
||||||
|
**must_have** line (the binding requirement) lists only those 5 keys, so this is
|
||||||
|
not a must_have violation — but it is a deviation from the fuller T-8.3 spec.
|
||||||
|
|
||||||
No P0 issues. All must-haves from PLAN.md are satisfied; the phase gate
|
**Impact:** cosmetic. The marker is bookkeeping; the verify script does not assert
|
||||||
`scripts/verify_phase07.sh` is green; behavioral spot-checks all pass.
|
the extra two keys. Non-blocking.
|
||||||
|
|
||||||
### P1 (post-hoc cleanup, out of Phase 07 scope)
|
**Recommended fix:** add `"versioning": true` + `"ttl_attribute": "expire_at"` to
|
||||||
|
the marker dict in `create_state_backend.py` (2-line addition; can be done with
|
||||||
|
the P1-1 fix).
|
||||||
|
|
||||||
- **P1-1: `platform/` package shadows stdlib `platform`.** The repo's
|
Neither P1 is auto-fixed by the verifier (the verifier is instructed not to edit
|
||||||
`platform/` directory is a Python package that shadows the stdlib
|
code, only VERIFY.md). Both are flagged for the Phase 09/10 author or a post-hoc
|
||||||
`platform` module when the repo root is on `sys.path` (any `python3 -c`
|
hardening commit.
|
||||||
from repo root). This breaks `jsonschema` (which imports `uuid` →
|
|
||||||
`platform.system()`). `scripts/verify_phase07.sh` works around it by
|
|
||||||
running jsonschema-invoking python from `/tmp`, but the workaround is
|
|
||||||
brittle — every future test script that imports `jsonschema` (or any
|
|
||||||
stdlib module that transitively imports `platform`) from repo root
|
|
||||||
will hit the same shadow. Recommended v1.2 fix: rename `platform/` →
|
|
||||||
`acdl_platform/` (or adopt a `src/` layout) so the package no longer
|
|
||||||
collides with the stdlib name. Out of Phase 07 scope (the layout is
|
|
||||||
locked by Phase 06 + README + PLAN.md).
|
|
||||||
|
|
||||||
- **P1-2: LSP false positive on `platform/confidence_signal.py:148`.**
|
---
|
||||||
The "No overloads for `get` match the provided arguments" diagnostic
|
|
||||||
is a pyright false positive triggered by `Dict[str, Any]` typing on
|
|
||||||
`pcr`. `py_compile` passes; runtime behavior is correct (verified in
|
|
||||||
§2.4). No fix needed in Phase 07; if a v1.2 type tightening pass
|
|
||||||
happens, replacing `Dict[str, Any]` with a `TypedDict` for
|
|
||||||
`PolicyCheckResult` would silence the LSP and improve type safety.
|
|
||||||
|
|
||||||
## Requirement coverage summary
|
## Manual attestation required (not auto-verifiable)
|
||||||
|
|
||||||
| REQ | File(s) | Status |
|
### D-034 — root key rotation
|
||||||
|-----|---------|--------|
|
|
||||||
| REQ-16 | `docs/architecture-v1.0.md` | covered (v1.1.2) |
|
|
||||||
| REQ-17 | `schemas/ir.schema.json` | covered (v1.1.2) |
|
|
||||||
| REQ-18 | `schemas/policy_check_result.schema.json` + `adapters/terraform/policy/checkov_adapter.py` | covered (v1.1.2) |
|
|
||||||
| REQ-19 | `platform/confidence_signal.py` | covered (v1.1.2) |
|
|
||||||
| REQ-20 | `platform/audit_ledger_design.md` | covered (v1.1.2) |
|
|
||||||
| REQ-21 | `platform/hitl_matrix_design.md` + `platform/separation_of_duties.py` | covered (v1.1.2) |
|
|
||||||
| REQ-22 | `schemas/contract.schema.json` | covered (v1.1.2) |
|
|
||||||
|
|
||||||
All 7 Phase 07 requirements covered. No partials.
|
**Decision D-034** (one-shot bootstrap waiver) requires the user to manually
|
||||||
|
rotate/deactivate the bootstrap **root** account key in the AWS IAM console after
|
||||||
|
Phase 08, because the root key was the one-shot bootstrap credential and must not
|
||||||
|
remain active.
|
||||||
|
|
||||||
|
**Why the verifier cannot check this:** the root key is never committed, never
|
||||||
|
written to a tracked file, and (per the security model) should already be
|
||||||
|
deactivated by the user. The verifier has no AWS API path to inspect the root
|
||||||
|
account's own access keys without the root key itself (which would defeat the
|
||||||
|
purpose). The `rotate_spike_key.sh` script explicitly does NOT rotate the root key
|
||||||
|
and prints the D-034 reminder; `verify_phase08.sh` notes "D-034 closed (user must
|
||||||
|
rotate the root key manually now)" in its VERIFIED line.
|
||||||
|
|
||||||
|
**Action required from the user:** confirm in the AWS IAM console
|
||||||
|
(https://console.aws.amazon.com/iam/ → Users → root → Security credentials) that
|
||||||
|
the bootstrap root access key used for Wave 5 is either **deactivated** or
|
||||||
|
**deleted**. Record the closure in `PROJECT.md` D-034 (the traceability commit
|
||||||
|
`96ab42f` should already note this; if not, the user should add it).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Final verdict
|
## Final verdict
|
||||||
|
|
||||||
Phase 07: VERIFIED
|
**Phase 08: VERIFIED**
|
||||||
|
|
||||||
|
All four layers pass. The 7 deliverable files exist, parse, and typecheck. The
|
||||||
|
IAM policy is least-privilege with the explicit Deny-everything-else statement.
|
||||||
|
The live AWS verification confirms: caller identity is `acdl-spike-runner` (not
|
||||||
|
root), the S3 state bucket exists with versioning enabled, the DynamoDB outbox
|
||||||
|
table exists with the correct PAY_PER_REQUEST + PK/SK shape. No secrets are
|
||||||
|
committed (no AKIA values, no secret strings, no root key id in any tracked file).
|
||||||
|
`.env.secrets` + `.bootstrap_state.json` are gitignored; `.env.secrets` is chmod
|
||||||
|
600 and holds only the rotated spike key (not the root key). The two P1 flags
|
||||||
|
(TTL not enabled; marker missing 2 cosmetic keys) are non-blocking and flagged
|
||||||
|
for post-hoc review. D-034 (manual root-key rotation) is a manual attestation
|
||||||
|
item the verifier cannot auto-check.
|
||||||
Reference in New Issue
Block a user