ship: phase-02 l1-modules (v1.0.2)

Squash merge of phase/02-l1-modules into milestone/v1.0-initial; 8 L1 stub modules created; verify_phase02.sh green.
This commit is contained in:
2026-07-21 13:18:12 +00:00
parent fd423e2df1
commit 65a4814672
23 changed files with 375 additions and 68 deletions
+44 -1
View File
@@ -100,4 +100,47 @@ for pushes.
There is no `package.json`; ACDL is bash + python stubs. The verification gate
substitutes `bash -n` and `python -m py_compile` for `npm run typecheck`, and
per-phase `scripts/verify_phaseNN.sh` for `npm test`. `npm run build` is a
no-op (no build step). See PERSONAS.md / VERIFICATION note.
no-op (no build step). See PERSONAS.md / VERIFICATION note.
## L1 module schema (Phase 02 research)
Each L1 module lives at `modules/l1/<name>/` with exactly two files:
- `manifest.yaml` — declares the L1's identity + a flat `inputs:` map.
Schema (D-017):
```yaml
name: l1-eks-fargate # matches the folder name
kind: l1 # literal "l1"; substrate-agnostic
description: <one-line>
inputs:
<key>:
description: <one-line>
type: string # only "string" allowed (flat, max-depth-1)
```
- `mock_apply.sh` — uniform stub per D-007 + D-018:
```bash
#!/usr/bin/env bash
set -euo pipefail
echo "[L1: <name>] applying..."
sleep 1
echo "[L1: <name>] OK"
exit 0
```
`mock_apply.sh` does NOT read input values; the manifest is for traceability
and for Phase 03's `mock_executor.sh` to enumerate the L1s in an L2.
### L1 list (fixed per REQ-02 / D-019)
| Folder | Description |
|--------|-------------|
| `l1-eks-fargate` | Serverless container compute substrate |
| `l1-iam-role` | Identity and access role primitive |
| `l1-lambda` | Event-driven function primitive |
| `l1-api-gateway` | HTTP routing primitive |
| `l1-eventbridge` | Event bus primitive |
| `l1-sqs` | Queue primitive |
| `l1-s3` | Object store primitive |
| `l1-cloudwatch` | Observability primitive |
L1 modules are single-purpose, substrate-agnostic, max-depth-1 (per
PROJECT.md Constraints). They do not compose with other L1s.
+52 -63
View File
@@ -1,96 +1,85 @@
---
phase: 01
name: repo-scaffolding
phase: 02
name: l1-modules
milestone: v1.0
milestone_type: feature
status: planned
requirements: [REQ-01, REQ-09, REQ-10]
requirements: [REQ-02, REQ-03]
must_haves:
- "Repo acdl-contracts exists under continuous-intelligence and is pushable (HTTP 200 on GET /repos/continuous-intelligence/acdl-contracts)"
- "Repo acdl-evidence exists under continuous-intelligence and is pushable (HTTP 200 on GET /repos/continuous-intelligence/acdl-evidence)"
- "Raw URL https://git.cloudinit.dev/continuous-intelligence/acdl-evidence/raw/branch/main/index.html returns HTTP 200 with placeholder HTML (D-012/D-016 substitute for Pages check)"
- "Branches qa and prod exist on acdl-contracts (visible stand-in for unsupported Gitea environments; D-013)"
- "scripts/gitea_setup.sh is idempotent and exits 0 (re-running against existing repos is a no-op)"
- "scripts/verify_phase01.sh passes: enumerates repos, fetches the raw index.html, lists qa/prod branches, exits 0"
- "All 8 L1 module folders exist under modules/l1/ with the exact names from REQ-02"
- "Each L1 has a manifest.yaml matching the schema in ARCHITECTURE.md (name, kind: l1, description, inputs: map of string keys)"
- "Each L1 has a mock_apply.sh that echoes '[L1: <name>] applying...', sleeps 1s, echoes '[L1: <name>] OK', exits 0 (D-007)"
- "All mock_apply.sh are executable (chmod +x) and bash -n clean"
- "All manifest.yaml files parse as valid YAML"
- "scripts/verify_phase02.sh passes: enumerates 8 L1s, validates each manifest, runs each mock_apply.sh, confirms exit 0 + expected output"
verification:
typecheck: "bash -n scripts/*.sh"
test: "scripts/verify_phase01.sh"
typecheck: "bash -n modules/l1/*/mock_apply.sh scripts/*.sh && python3 -c 'import yaml; [yaml.safe_load(open(f)) for f in glob.glob(\"modules/l1/*/manifest.yaml\")]'"
test: "scripts/verify_phase02.sh"
build: no-op
---
# Phase 01repo-scaffolding PLAN
# Phase 02l1-modules PLAN
## Goal
Stand up the three-repo scaffold under the `continuous-intelligence` Gitea org
and the Phase 01 visible artifacts in the local `acdl` checkout: the
Gitea-setup script, the workflow skeletons, and the Phase 01 verification
script. After this phase, Phases 02-05 can push stub modules, core scripts,
and workflows into the right repos.
Create the 8 L1 stub modules under `modules/l1/`. Each module has a
`manifest.yaml` (declared inputs, flat string map per D-017) and a uniform
`mock_apply.sh` (echo + 1s sleep + exit 0 per D-007/D-018). After this phase,
Phase 03 can compose L1s into L2 modules and `mock_executor.sh` can iterate
over an L2's L1 references.
## Requirements covered
- REQ-01: All demo code lives under `continuous-intelligence` Gitea org
- REQ-09: Three repos exist (`acdl`, `acdl-contracts`, `acdl-evidence`)
- REQ-10: reusable pipeline runs Dev → QA (approval) → Prod (approval) → Finalize; Phase 01 builds the skeleton (gates wired in Phase 04)
- REQ-02: 8 L1 module folders exist (exact names)
- REQ-03: each L1 has manifest.yaml + mock_apply.sh with the uniform behavior
## Waves (vertical slices, executed in domain priority order)
## Waves (vertical slices)
### Wave 1 — coordination (lead-developer)
### Wave 1 — infra-stub-engineer (creates the 8 L1s)
**Tasks:**
- **T-1.1** Add `.gitignore` (ignore `audit.json` artifacts, `__pycache__/`, `*.pyc`, `state.json`, `.env`). Create repo directory layout markers: `scripts/.gitkeep`, `modules/l1/.gitkeep`, `modules/l2/.gitkeep`, `.gitea/workflows/.gitkeep`. Add a top-level `README.md` with the project name, the 4-act demo summary, and a pointer to `.ciagent/PROJECT.md`.
- **T-1.2** Add `scripts/verify_phase01.sh` — the Phase 01 verification script. It reads `$ACDL_GITEA_TOKEN` from the env, calls the Gitea API to confirm both new repos exist, curls the raw `index.html` URL, lists branches on `acdl-contracts` looking for `qa` and `prod`, and prints a PASS/FAIL summary. Exits 0 on success, non-zero on any failure. Idempotent.
- **T-2.1** Create `modules/l1/l1-eks-fargate/{manifest.yaml, mock_apply.sh}`
- **T-2.2** Create `modules/l1/l1-iam-role/{manifest.yaml, mock_apply.sh}`
- **T-2.3** Create `modules/l1/l1-lambda/{manifest.yaml, mock_apply.sh}`
- **T-2.4** Create `modules/l1/l1-api-gateway/{manifest.yaml, mock_apply.sh}`
- **T-2.5** Create `modules/l1/l1-eventbridge/{manifest.yaml, mock_apply.sh}`
- **T-2.6** Create `modules/l1/l1-sqs/{manifest.yaml, mock_apply.sh}`
- **T-2.7** Create `modules/l1/l1-s3/{manifest.yaml, mock_apply.sh}`
- **T-2.8** Create `modules/l1/l1-cloudwatch/{manifest.yaml, mock_apply.sh}`
**Files owned (territory):**
- `.gitignore`, `README.md`
- `scripts/.gitkeep`, `modules/l1/.gitkeep`, `modules/l2/.gitkeep`, `.gitea/workflows/.gitkeep`
- `scripts/verify_phase01.sh`
Each L1's `manifest.yaml` declares 1-3 plausible inputs for that primitive
(e.g., `l1-s3` declares `bucket_name`, `region`, `retention_days`; `l1-iam-role`
declares `role_name`, `trust_policy`). Each `mock_apply.sh` follows the exact
uniform template from ARCHITECTURE.md.
**Commits:** one per task, `---ci---` block has `phase: 1, status: plan-as-execute, persona: lead-developer, task: T-1.x, requirements.covered: [REQ-01]`.
**Files owned (territory):** `modules/l1/**`
### Wave 2 — backend (backend-engineer)
**Commits:** one per task, `---ci---` block has `phase: 2, status: plan-as-execute, persona: infra-stub-engineer, task: T-2.x, requirements.covered: [REQ-02, REQ-03]`.
### Wave 2 — lead-developer (verification script + traceability)
**Tasks:**
- **T-2.1** Add `scripts/gitea_setup.sh`. Idempotent. Reads `$ACDL_GITEA_TOKEN` and `$GITEA_HOST` (default `https://git.cloudinit.dev`). Creates `acdl-contracts` and `acdl-evidence` under `continuous-intelligence` if missing (POST `/orgs/continuous-intelligence/repos` with `auto_init: true`, `default_branch: "main"`, `private: true`). Pushes a placeholder `index.html` to `acdl-evidence` main via the Gitea file-contents API (POST `/repos/{owner}/{repo}/contents/{path}` with base64 content + "Initial placeholder" commit message). Creates `qa` and `prod` branches on `acdl-contracts` from `main` via the Gitea branch API (POST `/repos/{owner}/{repo}/branches`). All HTTP errors are logged with status + body; the script is idempotent (409 / "already exists" treated as success). Uses `curl` + `python3 -c` for base64 encoding; no jq dependency.
- **T-2.2** Add `acdl/.gitea/workflows/pipeline.yml` skeleton. `on: workflow_call`. Four jobs: `dev` (runs-on ubuntu-latest, placeholder "Dev stage" step), `qa-gate` (needs dev, runs-on ubuntu-latest, placeholder "Awaiting QA approval" step; in Phase 04 this becomes a `workflow_dispatch` approval input per D-013), `prod-gate` (needs qa-gate, placeholder "Awaiting Prod approval"), `finalize` (needs prod-gate, placeholder "Commit audit.json to acdl-evidence"). All steps are explicit placeholders marked `# Phase 04 will implement`. Comment at top documents the branch-pin rule (`@milestone/v1.0-initial`).
- **T-2.3** Add `acdl-contracts/.gitea/workflows/issue-to-contract.yml` skeleton (committed to the `acdl` repo under `contracts-repo/.gitea/workflows/` as a reference copy; pushed to the actual `acdl-contracts` repo in Phase 04). `on: issues` with `types: [opened]`. One job `parse-and-trigger` with placeholder steps for: checkout, run `l3b_agent_stub.py`, commit `contract.yaml`, push, trigger the `acdl` pipeline via `workflow_dispatch` API (D-014). Marked `# Phase 04 will implement`.
- **T-2.9** Create `scripts/verify_phase02.sh`. It:
1. Enumerates `modules/l1/*/` and confirms exactly 8 folders with the 8 expected names.
2. For each L1: confirms `manifest.yaml` exists and parses as YAML with `name` matching the folder, `kind: l1`, and an `inputs:` map.
3. For each L1: confirms `mock_apply.sh` is executable, `bash -n` clean, runs in <2s, exits 0, and its stdout contains the `[L1: <name>] applying...` and `[L1: <name>] OK` markers.
4. Prints a PASS/FAIL summary; exits 0 on full success.
- **T-2.10** Update `.ciagent/REQUIREMENTS.md` (REQ-02/03 → covered pending VERIFY) and `.ciagent/ROADMAP.md` (Phase 02 → executing). No README change.
**Files owned (territory):**
- `scripts/gitea_setup.sh`
- `.gitea/workflows/pipeline.yml`
- `contracts-repo/.gitea/workflows/issue-to-contract.yml` (reference copy in the `acdl` repo; source of truth for Phase 04)
**Files owned (territory):** `scripts/verify_phase02.sh`, `.ciagent/REQUIREMENTS.md`, `.ciagent/ROADMAP.md`
**Commits:** one per task, `---ci---` block has `persona: backend-engineer, task: T-2.x, requirements.covered: [REQ-01 or REQ-09 or REQ-10]`.
**Commits:** one per task, `---ci---` block has `phase: 2, status: plan-as-execute, persona: lead-developer, task: T-2.9/2.10`.
### Wave 3 — coordination (lead-developer, verification wiring)
## Wave ordering
**Tasks:**
- Wave 1 (infra-stub-engineer) creates all 8 L1s. A single subagent gets all 8 tasks; it commits per task.
- Wave 2 (lead-developer) adds the verify script and traceability after the L1s exist.
- **T-3.1** Run `scripts/gitea_setup.sh` against the live Gitea (executed as part of EXECUTE; recorded as a commit only if it modifies repo state — it does not, so no commit. Instead, the verify run in Wave 3 confirms the artifacts exist via the API.) Add `scripts/verify_phase01.sh` invocation note to README.md ("Run `scripts/verify_phase01.sh` after `scripts/gitea_setup.sh` to confirm Phase 01 success criteria"). Update `.ciagent/REQUIREMENTS.md` Traceability table to mark REQ-01/09/10 as `covered` (pending VERIFY confirmation). Update `.ciagent/ROADMAP.md` Phase 01 status to `executing` (will flip to `complete` on SHIP).
`backend-engineer`, `data-engineer`, `frontend-engineer` have 0 tasks this phase.
**Files owned (territory):**
- `README.md` (update)
- `.ciagent/REQUIREMENTS.md` (traceability update only)
- `.ciagent/ROADMAP.md` (phase status update only)
## Dependencies
**Commits:** one for T-3.1 (a `chore(P01)` update with `phase: 1, status: execute, persona: lead-developer, task: T-3.1`).
## Wave ordering rationale
- Wave 1 (coordination) creates the directory skeleton + verification script so Wave 2's scripts have a place to live and a check to satisfy.
- Wave 2 (backend) builds the Gitea setup script and workflow skeletons.
- Wave 3 (coordination) wires the verification script into README and updates traceability after Wave 2's scripts exist.
- infra-stub-engineer and frontend-engineer have 0 tasks this phase (per PERSONAS.md), so their persona groups are skipped.
## Dependencies on other phases
None. Phase 01 is self-contained. Phases 02-05 depend on Phase 01 having created the two new repos and the workflow skeletons.
## Risk notes
- If `gitea_setup.sh` hits a 401/403, treat as an escalation (token scope insufficient). Per run.md Step 4: retry once, then escalate. Do NOT proceed to SHIP with uncreated repos.
- The Gitea file-contents API requires the file to NOT already exist on first POST. The script must check existence first (GET) and skip POST if 200. Otherwise 422.
- Creating branches requires the default branch to exist first (`auto_init: true` handles this).
- Depends on Phase 01 (the `modules/l1/.gitkeep` from T-1.1 is replaced by real folders).
- Phase 03 depends on this phase for L1 references in L2 compositions.
+4 -1
View File
@@ -75,4 +75,7 @@ Build a runnable demo (Linux + GitHub/Gitea Actions) that walks executives throu
| D-013 | Gitea has no environments API and ignores `jobs.<id>.environment` — model QA/Prod gates as `workflow_dispatch` approval inputs (D-004 fallback) | Research confirms `environment:` blocks are ignored by act_runner | Approval gates become dispatch inputs; "environments" become workflow job names + optional branch protection on `qa`/`prod` branches |
| D-014 | Cross-repo triggering uses the `workflow_dispatch` Gitea API (POST `/actions/workflows/{id}/dispatches`) from inside a step instead of `repository_dispatch` | Gitea Actions does not support `repository_dispatch` | Issue-trigger workflow calls the main pipeline via authenticated dispatch from a step |
| D-015 | New repos `acdl-contracts` and `acdl-evidence` use `default_branch: "main"` with `auto_init: true` | Matches Gitea `DEFAULT_BRANCH=main`; required for the default branch to exist before any push | Reusable-workflow `uses:` references still pin `acdl` workflows to `@milestone/v1.0-initial` |
| D-016 | Pages placeholder for Phase 01 is a minimal HTML stub (`<title>ACDL Evidence</title>` + "evidence stream coming soon"); full UI deferred to Phase 05 | Phase 01 success criterion is "Pages returns 200 with placeholder index.html" but Gitea has no Pages | Raw-URL HTTP 200 against `index.html` substitutes for the Pages check; full timeline UI built in Phase 05 |
| D-016 | Pages placeholder for Phase 01 is a minimal HTML stub (`<title>ACDL Evidence</title>` + "evidence stream coming soon"); full UI deferred to Phase 05 | Phase 01 success criterion is "Pages returns 200 with placeholder index.html" but Gitea has no Pages | Raw-URL HTTP 200 against `index.html` substitutes for the Pages check; full timeline UI built in Phase 05 |
| D-017 | Each L1 `manifest.yaml` declares a single `inputs:` map of named string keys with descriptions; no nested types (substrate-agnostic, max-depth-1) | REQ-02/03 say "declared inputs"; spec forbids composition and cloud-specific types | Uniform, parseable schema that Phase 03's `mock_executor.sh` can read with python+yaml |
| D-018 | L1 `mock_apply.sh` reads its own `manifest.yaml` for self-identification but ignores the input values (uniform stub per D-007) | D-007 mandates a literal echo + 1s sleep + exit 0; inputs are declared for traceability, not consumed | Predictable evidence events + clean separation from Phase 03 where L2s pass inputs to L1s |
| D-019 | The 8 L1 names are fixed per REQ-02: `l1-eks-fargate`, `l1-iam-role`, `l1-lambda`, `l1-api-gateway`, `l1-eventbridge`, `l1-sqs`, `l1-s3`, `l1-cloudwatch` | REQ-02 literal | Phase 02 enumerates them exactly; no naming freedom |
+2 -2
View File
@@ -58,8 +58,8 @@
| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-01 | 1 | complete (v1.0.1) |
| REQ-02 | 2 | pending |
| REQ-03 | 2 | pending |
| REQ-02 | 2 | covered (pending VERIFY) |
| REQ-03 | 2 | covered (pending VERIFY) |
| REQ-04 | 3 | pending |
| REQ-05 | 3 | pending |
| REQ-06 | 3 | pending |
+1 -1
View File
@@ -18,7 +18,7 @@ Five-phase breakdown to take ACDL from empty repo to a reproducible 4-act execut
### Phase 02 — l1-modules
- **Description:** Create all 8 L1 module folders under `acdl/modules/l1/`, each with `manifest.yaml` (declared inputs) and `mock_apply.sh` (uniform echo + 1s sleep + exit 0).
- **Status:** not_started
- **Status:** executing
- **Depends on:** [1]
- **Requirements:** REQ-02, REQ-03
- **Success Criteria:**
View File
+10
View File
@@ -0,0 +1,10 @@
name: l1-api-gateway
kind: l1
description: HTTP routing primitive
inputs:
api_name:
description: Name of the API Gateway REST/HTTP API
type: string
stage_name:
description: Name of the deployment stage (e.g. dev, prod)
type: string
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[L1: l1-api-gateway] applying..."
sleep 1
echo "[L1: l1-api-gateway] OK"
exit 0
+10
View File
@@ -0,0 +1,10 @@
name: l1-cloudwatch
kind: l1
description: Observability primitive
inputs:
log_group_name:
description: Name of the CloudWatch log group
type: string
metric_namespace:
description: Namespace under which custom metrics are emitted
type: string
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[L1: l1-cloudwatch] applying..."
sleep 1
echo "[L1: l1-cloudwatch] OK"
exit 0
+13
View File
@@ -0,0 +1,13 @@
name: l1-eks-fargate
kind: l1
description: Serverless container compute substrate
inputs:
cluster_name:
description: Name of the EKS cluster to target
type: string
region:
description: AWS region the cluster runs in
type: string
cpu_arch:
description: CPU architecture for Fargate pods (x86_64 or arm64)
type: string
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[L1: l1-eks-fargate] applying..."
sleep 1
echo "[L1: l1-eks-fargate] OK"
exit 0
+10
View File
@@ -0,0 +1,10 @@
name: l1-eventbridge
kind: l1
description: Event bus primitive
inputs:
bus_name:
description: Name of the EventBridge bus
type: string
rule_name:
description: Name of the event rule on the bus
type: string
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[L1: l1-eventbridge] applying..."
sleep 1
echo "[L1: l1-eventbridge] OK"
exit 0
+10
View File
@@ -0,0 +1,10 @@
name: l1-iam-role
kind: l1
description: Identity and access role primitive
inputs:
role_name:
description: Name of the IAM role to create
type: string
trust_policy:
description: JSON trust policy document defining who can assume the role
type: string
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[L1: l1-iam-role] applying..."
sleep 1
echo "[L1: l1-iam-role] OK"
exit 0
+13
View File
@@ -0,0 +1,13 @@
name: l1-lambda
kind: l1
description: Event-driven function primitive
inputs:
function_name:
description: Name of the Lambda function
type: string
runtime:
description: Lambda runtime identifier (e.g. python3.12, nodejs20.x)
type: string
handler:
description: Handler entrypoint in the form module.function
type: string
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[L1: l1-lambda] applying..."
sleep 1
echo "[L1: l1-lambda] OK"
exit 0
+13
View File
@@ -0,0 +1,13 @@
name: l1-s3
kind: l1
description: Object store primitive
inputs:
bucket_name:
description: Globally unique name of the S3 bucket
type: string
region:
description: AWS region the bucket lives in
type: string
retention_days:
description: Number of days to retain objects before expiration
type: string
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[L1: l1-s3] applying..."
sleep 1
echo "[L1: l1-s3] OK"
exit 0
+10
View File
@@ -0,0 +1,10 @@
name: l1-sqs
kind: l1
description: Queue primitive
inputs:
queue_name:
description: Name of the SQS queue
type: string
visibility_timeout:
description: Visibility timeout in seconds for in-flight messages
type: string
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[L1: l1-sqs] applying..."
sleep 1
echo "[L1: l1-sqs] OK"
exit 0
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env bash
# Phase 02 verification script.
# Confirms the 8 L1 module folders exist under modules/l1/ with the exact
# names from REQ-02, each containing a valid manifest.yaml (D-017 schema)
# and a uniform mock_apply.sh (D-007 + D-018) that exits 0 with the
# expected echo markers.
#
# Usage: scripts/verify_phase02.sh
# Exit codes: 0 = all checks passed; 1 = one or more checks failed.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
L1_DIR="${ROOT}/modules/l1"
# Expected L1 names per REQ-02 / D-019.
EXPECTED_L1S=(
l1-eks-fargate
l1-iam-role
l1-lambda
l1-api-gateway
l1-eventbridge
l1-sqs
l1-s3
l1-cloudwatch
)
fail_count=0
pass() { printf ' [PASS] %s\n' "$1"; }
fail() { printf ' [FAIL] %s\n' "$1"; fail_count=$((fail_count + 1)); }
echo "== Phase 02 verification =="
echo "L1 dir: ${L1_DIR}"
echo
# --- Check 1: exactly 8 L1 folders with the expected names ---
echo "-- Check 1: 8 L1 folders with expected names --"
if [ ! -d "$L1_DIR" ]; then
fail "modules/l1/ does not exist"
echo
echo "== Summary =="
echo "Phase 02 verification FAILED (${fail_count} check(s) failed)"
exit 1
fi
actual_folders=$(ls "$L1_DIR" | sort | tr '\n' ' ')
expected_folders=$(printf '%s\n' "${EXPECTED_L1S[@]}" | sort | tr '\n' ' ')
if [ "$actual_folders" = "$expected_folders" ]; then
pass "exactly 8 L1 folders present and named correctly"
else
fail "L1 folder list mismatch"
echo " expected: $expected_folders"
echo " actual: $actual_folders"
fi
# --- Per-L1 checks ---
for l1 in "${EXPECTED_L1S[@]}"; do
echo "-- L1: ${l1} --"
dir="${L1_DIR}/${l1}"
# Check 2a: folder exists
if [ ! -d "$dir" ]; then
fail "${l1}: folder missing"
continue
fi
pass "${l1}: folder exists"
# Check 2b: manifest.yaml exists + parses + name matches folder + kind=l1
manifest="${dir}/manifest.yaml"
if [ ! -f "$manifest" ]; then
fail "${l1}: manifest.yaml missing"
else
manifest_ok=$(python3 -c "
import yaml, sys
try:
d = yaml.safe_load(open('${manifest}'))
name = d.get('name') == '${l1}'
kind = d.get('kind') == 'l1'
has_inputs = isinstance(d.get('inputs'), dict)
sys.exit(0 if (name and kind and has_inputs) else 1)
except Exception as e:
print(f' parse error: {e}', file=sys.stderr)
sys.exit(2)
" 2>/dev/null; echo $?)
if [ "$manifest_ok" = "0" ]; then
pass "${l1}: manifest.yaml valid (name=${l1}, kind=l1, inputs present)"
else
fail "${l1}: manifest.yaml invalid (name/kind/inputs check failed; rc=${manifest_ok})"
fi
fi
# Check 2c: mock_apply.sh exists + executable + bash -n clean
apply="${dir}/mock_apply.sh"
if [ ! -f "$apply" ]; then
fail "${l1}: mock_apply.sh missing"
continue
fi
if [ ! -x "$apply" ]; then
fail "${l1}: mock_apply.sh not executable"
else
pass "${l1}: mock_apply.sh is executable"
fi
if ! bash -n "$apply" 2>/dev/null; then
fail "${l1}: mock_apply.sh bash -n failed"
else
pass "${l1}: mock_apply.sh bash -n clean"
fi
# Check 2d: end-to-end run: exit 0 + expected markers, completes in <2s
start=$(date +%s)
output=$("$apply" 2>&1)
rc=$?
elapsed=$(( $(date +%s) - start ))
if [ "$rc" -ne 0 ]; then
fail "${l1}: mock_apply.sh exited ${rc}"
elif ! echo "$output" | grep -qF "[L1: ${l1}] applying..."; then
fail "${l1}: missing '[L1: ${l1}] applying...' marker"
elif ! echo "$output" | grep -qF "[L1: ${l1}] OK"; then
fail "${l1}: missing '[L1: ${l1}] OK' marker"
elif [ "$elapsed" -lt 1 ] || [ "$elapsed" -gt 2 ]; then
fail "${l1}: run took ${elapsed}s (expected ~1s; 1<=t<=2 ok)"
else
pass "${l1}: mock_apply.sh runs, exits 0, markers correct (${elapsed}s)"
fi
done
echo
echo "== Summary =="
if [ "$fail_count" -eq 0 ]; then
echo "Phase 02 verification PASSED (8 L1 modules, all checks ok)"
exit 0
else
echo "Phase 02 verification FAILED (${fail_count} check(s) failed)"
exit 1
fi