diff --git a/.ciagent/REQUIREMENTS.md b/.ciagent/REQUIREMENTS.md index 4fefab6..dc55e44 100644 --- a/.ciagent/REQUIREMENTS.md +++ b/.ciagent/REQUIREMENTS.md @@ -120,6 +120,18 @@ ### Category: Pipeline Streaming - **REQ-45:** `scripts/run_platform.sh` streams output by default: terraform init/validate/plan output is piped to stdout via `tee` (visible to the user and logged), Checkov results are printed in human-readable form, and PolicyCheckResult records are displayed with severity, rule ID, and pass/fail status per record. The `--check-only` mode streams the emitted Terraform file content. A `--quiet` flag suppresses streaming (output to log files only) for backwards compatibility. Both gitea and github workflows are byte-identical (identical outcomes — the only difference is the forge runtime). +## v1.5 (Active — consumer happy path + zero-trust docs + reusable deploy workflow) + +### Category: Consumer Happy Path Documentation +- **REQ-46:** `README.md` is rewritten so the consumer model is unambiguous: this repo is the platform source; a consumer never clones it. A consumer repo contains only app code + `contract.yaml` referencing the central pipeline + contract. The platform-flow diagram is a mermaid `flowchart TD` (replacing the ASCII art). "L3A"/"L3B" nomenclature is removed from README (single-surface model). "spike" nomenclature is removed from prose (code paths in bash blocks are kept verbatim). +- **REQ-47:** `docs/CONSUMER_GUIDE.md` (all-caps) replaces `docs/consumer-guide-static-asset.md`. It is generic across all L2 modules (`static-asset` as the worked example), uses mermaid diagrams (model + pipeline flow), documents versioned `uses:` references (floating MAJOR+MINOR tags — bare/`@main` discouraged), scopes prerequisites to consumer-repo bootstrap only (no Terraform/Checkov/boto3/runner-key — those are platform-repo concerns), and documents that the pipeline fetches the ACDL repo at run time via a reusable workflow (consumers never invoke `scripts/run_platform.sh` locally for the happy path). +- **REQ-48:** `README.md` Credentials section is rewritten to express the zero-trust target model: consumer repos use OIDC federation (no long-lived keys) with attribute-based authorization (ABAC) — IAM roles + session policies scoped by repository identity and resource-creation tags so a consumer can only view/update resources it created (blast-radius containment). A documented override allows a static key in GitHub Secrets (consumer repo) or `.env.secrets` (local testing), rotated by a platform-managed scheduled pipeline on a daily cadence; when `.env.secrets` is used locally, rotating out of band is the consumer's responsibility. + +### Category: Reusable Deploy Workflow +- **REQ-49:** A reusable deploy workflow exists as byte-identical `.gitea/workflows/deploy.yml` (Gitea, dev) and `.github/workflows/deploy.yml` (GitHub, production), implementing the central deployment pipeline contract (`pipelines/deploy.yaml` validated against `schemas/deploy-pipeline.schema.json`). It is invoked by consumer repos via `uses: acdl/.gitea/workflows/deploy.yml@vMAJOR.MINOR` (versioned tag). The workflow checks out the consumer repo, checks out the ACDL platform repo into the runner workspace, installs runtime deps (Python, Terraform, Checkov), and invokes `scripts/run_platform.sh` against the consumer's contract path (passed as a workflow input). OIDC is the default auth (`permissions: id-token: write`); a static-key override reads from repository secrets. +- **REQ-50:** `contracts/static-asset.yaml` uses a versioned `uses:` reference (`@v1.4`, MAJOR+MINOR) — not bare `@v1` or `@main` — as the canonical example the consumer guide points at. +- **REQ-51:** `tests/test_pipeline_contract.py` is extended to validate the new deploy workflows: both files exist, are byte-identical, and conform to `schemas/deploy-pipeline.schema.json` (stages present, names match `pipelines/deploy.yaml` stage names). The existing CI-workflow conformance tests continue to pass unchanged. + ## Out of Scope (v1.2) | REQ | Original criterion | Clarified criterion (effective) | Decision | @@ -202,10 +214,21 @@ | REQ-41 | 18 | complete (v1.3.2) | | REQ-42 | 18 | complete (v1.3.2) | -### v1.4 (active — central pipeline contract + shell reproducibility + streaming) +### v1.4 (prior — central pipeline contract + shell reproducibility + streaming) | Requirement | Phase | Status | |-------------|-------|--------| | REQ-43 | 19 | complete (v1.4.1) | | REQ-44 | 19 | complete (v1.4.1) | -| REQ-45 | 19 | complete (v1.4.1) | \ No newline at end of file +| REQ-45 | 19 | complete (v1.4.1) | + +### v1.5 (active — consumer happy path + zero-trust docs + reusable deploy workflow) + +| Requirement | Phase | Status | +|-------------|-------|--------| +| REQ-46 | 20 | active | +| REQ-47 | 20 | active | +| REQ-48 | 20 | active | +| REQ-49 | 20 | active | +| REQ-50 | 20 | active | +| REQ-51 | 20 | active | \ No newline at end of file diff --git a/.ciagent/ROADMAP.md b/.ciagent/ROADMAP.md index 55ebfcf..2b71fce 100644 --- a/.ciagent/ROADMAP.md +++ b/.ciagent/ROADMAP.md @@ -6,7 +6,8 @@ - **v1.1 (complete):** architecture finalization + v1 spike. 5 phases (06–10). Tag `v1.2.0`, 2026-07-21. All 5 phases shipped + verified; review READY TO SHIP (0 P0); audit CLEAN. Gitea release id 202. - **v1.2 (complete):** platform hardening + first real consumer deployment. 6 phases (11–16). Tag `v1.3.0`, 2026-07-21. All 6 phases shipped + verified; review READY TO SHIP (1 P0 operator action, 1 P1 deferred); audit CLEAN. - **v1.3 (complete):** module documentation + thin-composition removal. The L2 composition layer is removed; module READMEs are built out. Tag `v1.3.2`. -- **v1.4 (active):** central pipeline contract + shell reproducibility + output streaming. A declarative pipeline contract (`schemas/pipeline.schema.json` + `pipelines/ci.yaml`) binds the Gitea and GitHub workflows to a single source of truth. `scripts/run_ci.sh` mirrors the CI pipeline locally. `scripts/run_platform.sh` streams terraform/checkov output by default. +- **v1.4 (complete):** central pipeline contract + shell reproducibility + output streaming. A declarative pipeline contract (`schemas/pipeline.schema.json` + `pipelines/ci.yaml`) binds the Gitea and GitHub workflows to a single source of truth. `scripts/run_ci.sh` mirrors the CI pipeline locally. `scripts/run_platform.sh` streams terraform/checkov output by default. +- **v1.5 (active):** consumer happy path + zero-trust docs + reusable deploy workflow. README rewritten so the consumer model is unambiguous (consumer owns only contract + app code; the rest is the platform source). Platform-flow + consumer-guide diagrams converted to mermaid. "L3A"/"L3B" + "spike" nomenclature removed from docs. Credentials section rewritten for zero-trust OIDC + ABAC (with a static-key override + daily rotation). A generic `docs/CONSUMER_GUIDE.md` (all L2 modules, versioned `uses:`, consumer-scoped prereqs, run-time platform fetch) replaces the static-asset guide. A byte-identical reusable `deploy.yml` workflow (Gitea + GitHub) implements `pipelines/deploy.yaml` and is invoked by consumer repos via a versioned tag. - **v1.0 demo URL:** https://git.cloudinit.dev/continuous-intelligence/acdl-evidence/raw/branch/main/index.html --- @@ -275,4 +276,30 @@ the platform is doing. - `scripts/run_ci.sh` exits 0 and outputs "CI PIPELINE OK". - `scripts/run_platform.sh --check-only` streams the emitted Terraform to stdout. - `scripts/run_platform.sh --check-only --quiet` suppresses the Terraform stream. - - `pytest` total count increases from 90 to 122 (32 new contract/streaming tests). \ No newline at end of file + - `pytest` total count increases from 90 to 122 (32 new contract/streaming tests). + +After Phase 19: COMPLETE gate — review → ship `v1.4.1` → audit. + +--- + +## v1.5 (Active — consumer happy path + zero-trust docs + reusable deploy workflow) + +The v1.5 milestone makes the consumer happy path self-evident, documents the +zero-trust credential model, and provides a reusable deploy workflow so +consumer repos never need to clone the platform repo or invoke its scripts +locally. + +### Phase 20 — consumer-happy-path-and-reusable-deploy-workflow +- **Description:** Rewrite `README.md` so the consumer model is unambiguous (this repo is the platform source; a consumer owns only `contract.yaml` + app code). Convert the platform-flow diagram to a mermaid `flowchart TD`. Remove "L3A"/"L3B" + "spike" nomenclature from README prose. Rewrite the Credentials section for zero-trust OIDC + ABAC (with a static-key override + daily rotation; consumer rotates out of band when using `.env.secrets` locally). Replace `docs/consumer-guide-static-asset.md` with a generic `docs/CONSUMER_GUIDE.md` (all L2 modules, mermaid diagrams, versioned `uses:` floating MAJOR+MINOR, consumer-scoped prerequisites, run-time platform fetch via a reusable workflow). Create byte-identical `.gitea/workflows/deploy.yml` + `.github/workflows/deploy.yml` implementing `pipelines/deploy.yaml` — a reusable workflow invoked by consumer repos via `uses: acdl/.gitea/workflows/deploy.yml@v1.4` that checks out the consumer repo + the ACDL platform repo and runs `scripts/run_platform.sh`. Update `contracts/static-asset.yaml` to `uses: acdl/pipelines/deploy.yaml@v1.4`. Extend `tests/test_pipeline_contract.py` to validate the new deploy workflows (byte-identical, schema-conformant). +- **Status:** active +- **Depends on:** [19] +- **Requirements:** REQ-46, REQ-47, REQ-48, REQ-49, REQ-50, REQ-51 +- **Success Criteria:** + - `README.md` states the platform-source vs consumer-repo distinction up front; platform flow is a mermaid `flowchart TD`; `grep L3B README.md` returns 0 hits; `grep -i spike README.md` returns 0 prose hits (code paths in bash blocks allowed). + - `docs/CONSUMER_GUIDE.md` exists; `docs/consumer-guide-static-asset.md` is deleted; `grep -R consumer-guide-static-asset` returns 0 dangling references; guide is generic (static-asset is the worked example, not the scope); diagrams are mermaid; `uses:` references use `@v1.4`. + - `README.md` Credentials section describes OIDC + ABAC zero-trust as the default and the static-key override + daily rotation + consumer out-of-band rotation duty for local `.env.secrets`. + - `.gitea/workflows/deploy.yml` and `.github/workflows/deploy.yml` exist, are byte-identical, conform to `schemas/deploy-pipeline.schema.json`, and are reusable (`on: workflow_call` with a `contract` input). + - `contracts/static-asset.yaml` uses `uses: acdl/pipelines/deploy.yaml@v1.4`. + - `tests/test_pipeline_contract.py` validates the deploy workflows (exist, byte-identical, schema-conformant); the extended test suite passes; `bash scripts/run_ci.sh` exits 0. + +After Phase 20: COMPLETE gate — review → ship `v1.5.0` → audit. \ No newline at end of file diff --git a/.ciagent/config.json b/.ciagent/config.json index 5e04e3f..70ca8ed 100644 --- a/.ciagent/config.json +++ b/.ciagent/config.json @@ -4,7 +4,7 @@ { "slug": "acdl", "name": "Agentic Cloud Delivery Platform", - "milestone": "v1.4", + "milestone": "v1.5", "status": "active" } ], diff --git a/demo/.gitea/workflows/pipeline.yml b/demo/.gitea/workflows/pipeline.yml deleted file mode 100644 index 71eccd2..0000000 --- a/demo/.gitea/workflows/pipeline.yml +++ /dev/null @@ -1,153 +0,0 @@ -# ACDL pipeline workflow (Phase 04 implementation). -# -# 3-dispatch approval-gate topology (D-027 / D-028; ARCHITECTURE.md -# "Phase 04 pipeline topology"): -# -# Dispatch 1 (initial): approve_qa=false, approve_prod=false -# -> runs the `dev` job (policy check, confidence -# gate, mock_executor, evidence + finalize). -# Dispatch 2 (QA approve): approve_qa=true, approve_prod=false -# -> runs the `qa-gate` job (records QA approval -# in the audit chain via evidence_writer + -# finalize_evidence). -# Dispatch 3 (Prod approve): approve_prod=true -# -> runs the `prod-gate` job, then the `finalize` -# job (needs: prod-gate) which writes the final -# evidence event and commits audit.json to -# acdl-evidence. -# -# Gitea Actions limitations driving this design: -# - No `repository_dispatch` trigger (D-014). -# - No environments API / `environment:` blocks are ignored (D-013). -# - Re-dispatch starts a NEW run; artifacts do NOT survive between runs, -# so state is persisted to acdl-evidence via the file-contents API -# (D-028 / finalize_evidence.py) instead of via artifacts. -# -# Branch-pin rule (ARCHITECTURE.md "Branch pinning rule"): -# This workflow lives on `acdl`'s default branch `milestone/v1.0-initial`. -# Cross-repo `uses:` references (e.g. the issue-trigger's checkout of -# l3b_agent_stub.py) MUST pin to `@milestone/v1.0-initial`, NOT `@main` -# (the `acdl` repo has no `main` branch). This workflow is invoked via -# the workflow_dispatch API (D-014), NOT via `workflow_call`, so the -# `uses:` rule applies to the issue-trigger's checkout of the acdl repo, -# not to this file itself. -name: acdl-pipeline - -"on": - workflow_dispatch: - inputs: - contract-ref: - description: "Ref on acdl-contracts that carries the contract" - required: false - type: string - default: main - approve_qa: - description: "Human approval to advance past QA" - required: false - type: boolean - default: false - approve_prod: - description: "Human approval to advance past Prod" - required: false - type: boolean - default: false - -jobs: - dev: - name: "Dev (autonomous)" - if: inputs.approve_qa != true && inputs.approve_prod != true - runs-on: ubuntu-latest - steps: - - name: "Checkout acdl (this repo, pinned to milestone/v1.0-initial)" - uses: actions/checkout@v4 - with: - ref: milestone/v1.0-initial - - - name: "Checkout acdl-contracts at contract-ref" - uses: actions/checkout@v4 - with: - repository: continuous-intelligence/acdl-contracts - ref: ${{ inputs.contract-ref }} - token: ${{ secrets.GITEA_TOKEN }} - path: acdl-contracts - - - name: "Policy check" - run: | - python3 scripts/policy_checker.py acdl-contracts/contract.yaml - - - name: "Confidence signal" - id: confidence - run: | - set +e - SCORE_JSON=$(python3 scripts/confidence_signal.py acdl-contracts/contract.yaml) - echo "$SCORE_JSON" - echo "score_json=$SCORE_JSON" >> "$GITHUB_OUTPUT" - - - name: "Apply or reject based on confidence (gate < 0.50)" - run: | - set +e - SCORE=$(python3 -c "import json,sys; print(json.load(sys.stdin)['score'])" <<< '${{ steps.confidence.outputs.score_json }}') - python3 -c "import sys; sys.exit(0 if float('${SCORE}') >= 0.50 else 1)" - THRESHOLD_RC=$? - if [ "$THRESHOLD_RC" -ne 0 ]; then - python3 scripts/evidence_writer.py --stage dev --event "dev rejected: confidence < 0.50" --audit audit.json - python3 scripts/finalize_evidence.py --audit audit.json - exit 1 - fi - STACK=$(python3 -c 'import yaml; print(yaml.safe_load(open("acdl-contracts/contract.yaml"))["stack"])') - bash scripts/mock_executor.sh acdl-contracts/contract.yaml - python3 scripts/evidence_writer.py --stage dev --event "dev applied: ${STACK}" --audit audit.json - python3 scripts/finalize_evidence.py --audit audit.json - - - name: "Upload dev state artifacts (best-effort)" - uses: actions/upload-artifact@v3 - with: - name: dev-state - path: | - audit.json - state.json - - qa-gate: - name: "QA (manual approval)" - if: inputs.approve_qa == true && inputs.approve_prod != true - runs-on: ubuntu-latest - steps: - - name: "Checkout acdl (this repo, pinned to milestone/v1.0-initial)" - uses: actions/checkout@v4 - with: - ref: milestone/v1.0-initial - - - name: "Record QA approval in evidence" - run: | - python3 scripts/evidence_writer.py --stage qa --event "qa approved" --audit audit.json - python3 scripts/finalize_evidence.py --audit audit.json - - prod-gate: - name: "Prod (manual approval)" - if: inputs.approve_prod == true - runs-on: ubuntu-latest - steps: - - name: "Checkout acdl (this repo, pinned to milestone/v1.0-initial)" - uses: actions/checkout@v4 - with: - ref: milestone/v1.0-initial - - - name: "Record Prod approval in evidence" - run: | - python3 scripts/evidence_writer.py --stage prod --event "prod approved" --audit audit.json - python3 scripts/finalize_evidence.py --audit audit.json - - finalize: - name: "Finalize (publish evidence)" - needs: [prod-gate] - runs-on: ubuntu-latest - steps: - - name: "Checkout acdl (this repo, pinned to milestone/v1.0-initial)" - uses: actions/checkout@v4 - with: - ref: milestone/v1.0-initial - - - name: "Write finalize event + commit audit.json to acdl-evidence" - run: | - python3 scripts/evidence_writer.py --stage finalize --event "pipeline complete: audit.json committed to acdl-evidence" --audit audit.json - python3 scripts/finalize_evidence.py --audit audit.json \ No newline at end of file diff --git a/demo/ACDL_DEMO.md b/demo/ACDL_DEMO.md deleted file mode 100644 index 2081f33..0000000 --- a/demo/ACDL_DEMO.md +++ /dev/null @@ -1,368 +0,0 @@ ---- -marp: true -theme: default -paginate: true -size: 16:9 -header: 'ACDL · Agentic Cloud Delivery Platform' -footer: 'Executive Demo · v1.0' -style: | - /* S&P Global-inspired palette */ - :root { - --sp-red: #C8102E; - --sp-red-dark: #8E0B20; - --sp-ink: #1A1A1A; - --sp-slate: #4A4A4A; - --sp-gray: #6E6E6E; - --sp-line: #D6D6D6; - --sp-bg: #FFFFFF; - --sp-tint: #F4F4F4; - } - section { - font-size: 24px; - color: var(--sp-ink); - background: var(--sp-bg); - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; - padding: 50px 60px; - } - section.title { - text-align: center; - background: var(--sp-red); - color: #FFFFFF; - display: flex; - flex-direction: column; - justify-content: center; - } - section.title h1 { - color: #FFFFFF; - font-size: 64px; - margin-bottom: 0; - border: none; - } - section.title h2 { - color: #FFFFFF; - border: none; - font-weight: 400; - } - section.title strong { color: #FFFFFF; } - h1 { - color: var(--sp-red); - font-size: 40px; - font-weight: 700; - margin-bottom: 12px; - } - h2 { - color: var(--sp-red); - border-bottom: 3px solid var(--sp-red); - padding-bottom: 6px; - font-weight: 700; - } - h3 { - color: var(--sp-red-dark); - font-weight: 600; - margin-top: 24px; - } - ul, ol { color: var(--sp-slate); } - li { margin-bottom: 6px; } - strong { color: var(--sp-ink); } - table { - font-size: 18px; - width: 100%; - border-collapse: collapse; - margin: 12px 0; - } - th { - background: var(--sp-red); - color: #FFFFFF; - text-align: left; - padding: 10px 12px; - font-weight: 600; - border: 1px solid var(--sp-red-dark); - } - td { - padding: 8px 12px; - border: 1px solid var(--sp-line); - color: var(--sp-slate); - } - tr:nth-child(even) td { background: var(--sp-tint); } - pre { - font-size: 13px; - background: var(--sp-tint); - border-left: 4px solid var(--sp-red); - padding: 14px 16px; - border-radius: 0; - color: var(--sp-ink); - } - code { - background: var(--sp-tint); - color: var(--sp-red-dark); - padding: 1px 5px; - border-radius: 2px; - font-family: 'Menlo', 'Consolas', monospace; - } - pre code { - background: none; - color: var(--sp-ink); - padding: 0; - } - blockquote { - border-left: 5px solid var(--sp-red); - background: var(--sp-tint); - padding: 10px 16px; - color: var(--sp-slate); - font-style: italic; - border-radius: 0; - } - header { - color: var(--sp-red); - font-weight: 700; - font-size: 14px; - } - footer { - color: var(--sp-gray); - font-size: 12px; - } - section::after { - color: var(--sp-red); - font-weight: 700; - } ---- - - - -# ACDL -## Agentic Cloud Delivery Platform - -Automatic. Safe. Audited. — in 30 minutes, on stubs. - -v1.0 · GitHub Actions · stub-driven - - - ---- - -# The Problem - -### Today, deploying infrastructure takes **weeks** - -- Ticket → triage queue → copy-paste config → peer review → security review → waiting for central IT to release - -> Two weeks of human latency for a service that should take minutes. - -### What we want instead - -- Developer commits a **contract** → pipeline runs -- Safety **computed** automatically (confidence signal) -- Manual gates only where they matter (QA, Prod) -- Every step written to a tamper-evident **evidence stream** - - - ---- - -# How It Works - -``` - ┌────────────── acdl-contracts ──────────────┐ - Developer ──▶│ commit contract.yaml │ - └───────┬───────────────────────────────────┘ - │ (push) - Citizen ┌─────────┴──────────┐ - │ "ingest gas pricing into data lake" - ▼ - Claude agent ──▶ contract.yaml ─┘ - │ (push) - ▼ - ┌─────────────────┐ - │ reusable │ - │ GitHub Actions │ - │ pipeline │ - └────────┬────────┘ - │ - ┌─────────────┼─────────────┐ - ▼ ▼ ▼ - Dev (auto) QA (approval) Prod (approval) - │ - ▼ - evidence_writer ─▶ audit.json (hash-chained) ─▶ Pages timeline -``` - -Two entry paths, **one** pipeline, **one** audit trail — developer via GitHub, citizen developer via their own Claude agent. - - - ---- - -# The Safety Story - -### Computed, not requested - -| Signal | Behavior | -|--------|----------| -| **Base confidence** | 0.90 | -| **On policy violation** | drop to 0.40 + reason code | -| **Gate threshold** | ≥ 0.50 to proceed past Dev | - -### Policy (v1) -- `public-ingress: true` → `POLICY_VIOLATION:PUBLIC_INGRESS` - -### Evidence -- Each event appended to `audit.json` with SHA-256 link to previous (`prev_hash` + `hash`) -- Published to Pages → vanilla-JS timeline - - - ---- - -# Scenario 1 — Developer Self-Service - -### Trigger -Developer commits a valid `contract.yaml` requesting **`l2-commodity-price-feed`** via **GitHub**. - -### What you'll see -- **Dev:** policy ✅ → apply api-gateway, lambda, s3 → confidence **0.90** → proceed -- **QA:** pipeline pauses → click **Approve** -- **Prod:** pipeline pauses → click **Approve** -- **Finalize:** `audit.json` committed → Pages timeline updates - -### Evidence outcome -Timeline shows: contract received → policy pass → apply × 3 → confidence 0.90 → QA → Prod → published. - - - ---- - -# Scenario 1 — Journey - -```mermaid -flowchart LR - classDef gh fill:#F4F4F4,stroke:#1A1A1A,stroke-width:2px,color:#1A1A1A - classDef stage fill:#C8102E,stroke:#8E0B20,stroke-width:1px,color:#FFFFFF - classDef gate fill:#FFFFFF,stroke:#1A1A1A,stroke-width:2px,color:#1A1A1A - classDef evidence fill:#F4F4F4,stroke:#C8102E,stroke-width:1px,color:#1A1A1A - - D["Developer"]:::gh -->|"writes contract.yaml"| GH["GitHub
acdl-contracts"]:::gh - GH -->|"push triggers
GitHub Action"| DEV["Dev
(autonomous)"]:::stage - DEV -->|"policy ✅ · confidence 0.90"| QA["QA
approval gate"]:::gate - QA -->|"approve"| PROD["Prod
approval gate"]:::gate - PROD -->|"approve"| FIN["Finalize
commit audit.json"]:::stage - FIN --> TL["GitHub Pages
timeline"]:::evidence -``` - - - ---- - -# Scenario 2 — Citizen Developer - -### Trigger -Non-technical user prompts their **own Claude agent** in natural language: - -> "I need a new service to ingest real-time natural gas pricing data into our data lake." - -### What you'll see -- Claude agent parses intent, writes `contract.yaml` for **`l2-commodity-price-feed`**, pushes a branch -- Issue **closed**; branch push triggers the **identical** pipeline from Scenario 1 -- Citizen developer follows the run all the way to **Prod** - -### Evidence outcome -Timeline is **indistinguishable** from Scenario 1 — the agentic surface is first-class, not a bolt-on. - - - ---- - -# Scenario 2 — Journey - -```mermaid -flowchart LR - classDef cit fill:#F4F4F4,stroke:#C8102E,stroke-width:2px,color:#1A1A1A - classDef agent fill:#C8102E,stroke:#8E0B20,stroke-width:1px,color:#FFFFFF - classDef stage fill:#1A1A1A,stroke:#1A1A1A,stroke-width:1px,color:#FFFFFF - classDef gate fill:#FFFFFF,stroke:#1A1A1A,stroke-width:2px,color:#1A1A1A - classDef evidence fill:#F4F4F4,stroke:#C8102E,stroke-width:1px,color:#1A1A1A - - CD["Citizen developer"]:::cit -->|"natural-language
prompt"| CL["Claude agent
(citizen-owned)"]:::agent - CL -->|"generates
contract.yaml"| GH["GitHub
acdl-contracts"]:::cit - GH -->|"push triggers
GitHub Action"| DEV["Dev
(autonomous)"]:::stage - DEV -->|"policy ✅ · confidence 0.90"| QA["QA
approval gate"]:::gate - QA -->|"approve"| PROD["Prod
approval gate"]:::gate - PROD -->|"approve"| FIN["Finalize
commit audit.json"]:::stage - FIN --> TL["GitHub Pages
timeline"]:::evidence -``` - - - ---- - -# Scenario 3 — The Safety Net - -### Trigger -Developer commits a **malicious** `contract.yaml` for `l2-regulatory-reporting` via **GitHub**: - -```yaml -stack: l2-regulatory-reporting -public-ingress: true -``` - -### What you'll see -- **Dev:** `policy_checker` → `POLICY_VIOLATION:PUBLIC_INGRESS` -- `confidence_signal` drops 0.90 → **0.40** -- `0.40 < 0.50` → pipeline **halts in Dev** -- Rejection reason written to the evidence stream - -### Evidence outcome -Timeline shows the attempted deploy, the violation, the confidence drop, and the **halt** — visible and explained. - - - ---- - -# Scenario 3 — Journey - -```mermaid -flowchart LR - classDef gh fill:#F4F4F4,stroke:#1A1A1A,stroke-width:2px,color:#1A1A1A - classDef stage fill:#C8102E,stroke:#8E0B20,stroke-width:1px,color:#FFFFFF - classDef halt fill:#1A1A1A,stroke:#1A1A1A,stroke-width:1px,color:#FFFFFF - classDef evidence fill:#F4F4F4,stroke:#C8102E,stroke-width:1px,color:#1A1A1A - - D["Developer"]:::gh -->|"writes malicious
contract.yaml"| GH["GitHub
acdl-contracts"]:::gh - GH -->|"push triggers
GitHub Action"| DEV["Dev
(autonomous)"]:::stage - DEV -->|"POLICY_VIOLATION:PUBLIC_INGRESS
confidence 0.90 → 0.40"| HALT["Halt in Dev
+ rejection reason"]:::halt - HALT --> TL["GitHub Pages
timeline"]:::evidence -``` - - \ No newline at end of file diff --git a/demo/contracts-repo/.gitea/workflows/.gitkeep b/demo/contracts-repo/.gitea/workflows/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/demo/contracts-repo/.gitea/workflows/issue-to-contract.yml b/demo/contracts-repo/.gitea/workflows/issue-to-contract.yml deleted file mode 100644 index f96420d..0000000 --- a/demo/contracts-repo/.gitea/workflows/issue-to-contract.yml +++ /dev/null @@ -1,145 +0,0 @@ -# ACDL issue-to-contract workflow (Phase 04 implementation). -# -# Trigger: a new Issue is opened in acdl-contracts. The workflow runs -# l3b_agent_stub.py (checked out from the `acdl` repo, pinned to -# @milestone/v1.0-initial) to map the Issue body to a contract.yaml, commits -# the contract to a new branch `contract/` on acdl-contracts -# via the Gitea file-contents API, closes the Issue with a comment, and -# dispatches the main pipeline in the `acdl` repo via the workflow_dispatch -# API (D-014; Gitea Actions does not support repository_dispatch). -# -# Cross-repo trigger (D-014): -# The final step POSTs to -# /api/v1/repos/continuous-intelligence/acdl/actions/workflows/pipeline.yml/dispatches -# with body {"ref": "milestone/v1.0-initial", -# "inputs": {"contract-ref": "contract/"}}. -# -# Branch-pin rule (ARCHITECTURE.md): -# The `acdl` repo's default branch is `milestone/v1.0-initial`, so the -# checkout step pins `ref: milestone/v1.0-initial`. The pipeline dispatch -# also pins `ref: milestone/v1.0-initial` (the workflow file lives on -# that branch). The new `contract/` branch is created on acdl-contracts -# (whose default branch is `main`, per D-015). -# -# File-contents POST with `new_branch` (D-030): -# The POST to /repos/.../contents/contract.yaml includes -# `new_branch: contract/`, which tells Gitea to create the file on a -# NEW branch off the current head of `branch: main` instead of committing -# directly to main. This avoids a separate branch-create + commit round -# trip. -name: issue-to-contract - -"on": - issues: - types: [opened] - -jobs: - parse-and-trigger: - runs-on: ubuntu-latest - steps: - - name: "Checkout acdl (pinned to milestone/v1.0-initial for l3b_agent_stub.py)" - uses: actions/checkout@v4 - with: - repository: continuous-intelligence/acdl - ref: milestone/v1.0-initial - token: ${{ secrets.GITEA_TOKEN }} - - - name: "Parse Issue body into contract.yaml" - env: - ISSUE_BODY: ${{ gitea.event.issue.body }} - run: | - # Pass the Issue body via an env var to avoid shell injection from - # arbitrary Issue text. l3b_agent_stub.py reads argv[1]; we pass - # the env var quoted so no metacharacter interpretation happens. - python3 scripts/l3b_agent_stub.py "$ISSUE_BODY" -o contract.yaml - echo "--- generated contract.yaml ---" - cat contract.yaml - - - name: "Commit contract.yaml to new branch contract/${{ gitea.event.issue.number }} on acdl-contracts" - env: - GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} - run: | - set -euo pipefail - STACK=$(python3 -c 'import yaml; print(yaml.safe_load(open("contract.yaml"))["stack"])') - ISSUE_NUMBER="${{ gitea.event.issue.number }}" - BRANCH="contract/${ISSUE_NUMBER}" - HOST="https://git.cloudinit.dev" - API="${HOST}/api/v1/repos/continuous-intelligence/acdl-contracts/contents/contract.yaml" - B64=$(base64 -w 0 contract.yaml) - BODY=$(python3 -c " - import json - print(json.dumps({ - 'content': '${B64}', - 'message': 'l3b: contract for issue #${ISSUE_NUMBER}', - 'branch': 'main', - 'new_branch': '${BRANCH}' - })) - ") - STATUS=$(curl -sS -o /tmp/contract_post.json -w "%{http_code}" \ - -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "$BODY" \ - "$API") - echo "POST contract.yaml -> HTTP ${STATUS}" - cat /tmp/contract_post.json || true - case "$STATUS" in - 201) echo "contract.yaml committed on branch ${BRANCH}" ;; - *) echo "ERROR: file-contents POST failed (HTTP ${STATUS})" >&2; exit 1 ;; - esac - echo "STACK=${STACK}" >> "$GITHUB_ENV" - echo "BRANCH=${BRANCH}" >> "$GITHUB_ENV" - - - name: "Comment on Issue + close it" - env: - GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} - run: | - set -euo pipefail - ISSUE_NUMBER="${{ gitea.event.issue.number }}" - HOST="https://git.cloudinit.dev" - ISSUES_API="${HOST}/api/v1/repos/continuous-intelligence/acdl-contracts/issues/${ISSUE_NUMBER}" - COMMENT_BODY=$(python3 -c " - import json - print(json.dumps({'body': 'Generated contract.yaml for stack \`' + '${STACK}' + '\` on branch \`' + '${BRANCH}' + '\`. Pipeline dispatched.'})) - ") - curl -sS -o /tmp/comment.json -w "comment HTTP %{http_code}\n" \ - -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "$COMMENT_BODY" \ - "${ISSUES_API}/comments" - CLOSE_BODY='{"state":"closed"}' - curl -sS -o /tmp/close.json -w "close HTTP %{http_code}\n" \ - -X PATCH \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "$CLOSE_BODY" \ - "${ISSUES_API}" - - - name: "Dispatch the pipeline on acdl (contract-ref = contract/${{ gitea.event.issue.number }})" - env: - GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} - run: | - set -euo pipefail - ISSUE_NUMBER="${{ gitea.event.issue.number }}" - HOST="https://git.cloudinit.dev" - DISPATCH_URL="${HOST}/api/v1/repos/continuous-intelligence/acdl/actions/workflows/pipeline.yml/dispatches" - BODY=$(python3 -c " - import json - print(json.dumps({ - 'ref': 'milestone/v1.0-initial', - 'inputs': {'contract-ref': 'contract/${ISSUE_NUMBER}'} - })) - ") - STATUS=$(curl -sS -o /tmp/dispatch.json -w "%{http_code}" \ - -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "$BODY" \ - "$DISPATCH_URL") - echo "pipeline dispatch -> HTTP ${STATUS}" - cat /tmp/dispatch.json || true - case "$STATUS" in - 201|202|204) echo "pipeline dispatched (contract-ref=contract/${ISSUE_NUMBER})" ;; - *) echo "ERROR: pipeline dispatch failed (HTTP ${STATUS})" >&2; exit 1 ;; - esac \ No newline at end of file diff --git a/demo/contracts/examples/contract-commodity-price-feed-prod.yaml b/demo/contracts/examples/contract-commodity-price-feed-prod.yaml deleted file mode 100644 index 03b8af7..0000000 --- a/demo/contracts/examples/contract-commodity-price-feed-prod.yaml +++ /dev/null @@ -1,55 +0,0 @@ -# Elaborate developer contract — energy trading price feed. -# -# Schema (D-021): stack + inputs (open-ended string map) + public-ingress. -# The `stack` field MUST match an L2 folder name under modules/l2/. -# The `inputs` map is free-form string values; these are L2-level params -# that travel with the deployment into state.json and the audit trail. -# The L1 input values are declared by the L2's manifest.yaml, not here. -# -# Commit this to acdl-contracts as contract.yaml to trigger the pipeline: -# git add contract.yaml && git commit -m "feat: deploy price feed (prod)" && git push - -stack: l2-commodity-price-feed - -inputs: - # --- Environment + ownership --- - environment: prod - owner: commodity-trading-platform-team - team: power-and-gas-desk - cost_center: CC-TRD-4471 - change_ticket: CHG-2026-07-21-093 - - # --- Business context (rides into the audit timeline) --- - business_owner: kchen@jccapital.xyz - oncall_email: sre-commodity@example.com - sla_tier: T1 - business_hours: "Mon-Fri 07:00-19:00 ET" - data_classification: internal - - # --- Source feed contract (business-facing) --- - feed_vendor: Platts - feed_name: natural-gas-daily-settlement - feed_cadence: daily - feed_timezone: US/Eastern - symbols: "NG-WTI-HH,NG-HH-M,NG-PJM" - retry_policy: backoff-3x-15min - dead_letter_queue: commodity-price-dlq - - # --- Deployment knobs (consumed by the pipeline; passed to L1s via L2 manifest) --- - replicas: "3" - cpu_request: "500m" - memory_request: "1Gi" - autoscale_min: "2" - autoscale_max: "8" - log_retention_days: "90" - archive_retention_days: "2555" - - # --- Operational flags --- - enable_canary: "true" - canary_percentage: "10" - enable_pagerduty: "true" - enable_cost_alerts: "true" - cost_alert_threshold_usd: "500" - -# Policy-gated field. true -> POLICY_VIOLATION:PUBLIC_INGRESS -> confidence 0.40 < 0.50 -> Dev rejects (Act 4). -public-ingress: false \ No newline at end of file diff --git a/demo/contracts/examples/contract-regulatory-reporting-violation.yaml b/demo/contracts/examples/contract-regulatory-reporting-violation.yaml deleted file mode 100644 index ec3dd95..0000000 --- a/demo/contracts/examples/contract-regulatory-reporting-violation.yaml +++ /dev/null @@ -1,55 +0,0 @@ -# Elaborate developer contract — regulatory reporting (with policy violation). -# -# Same schema as the price-feed example, but with public-ingress: true, -# which triggers Act 4: the policy_checker fails, the confidence_signal -# drops to 0.40, the 0.50 gate halts the pipeline in Dev, and the -# rejection appears on the evidence timeline. -# -# Commit this to acdl-contracts as contract.yaml to reproduce Act 4: -# git add contract.yaml && git commit -m "feat: deploy regulatory reporting" && git push - -stack: l2-regulatory-reporting - -inputs: - # --- Environment + ownership --- - environment: prod - owner: compliance-and-controls-team - team: regulatory-reporting-desk - cost_center: CC-CMP-9902 - change_ticket: CHG-2026-07-21-118 - business_owner: compliance@jccapital.xyz - oncall_email: sre-regulatory@example.com - sla_tier: T0 - business_hours: "24x7" - data_classification: confidential - - # --- Regulatory context --- - regulator: FERC - filing_frequency: monthly - filing_deadline_day_of_month: "15" - reporting_period: 2026-Q3 - jurisdiction: US-Federal - legal_hold: "false" - - # --- Deployment knobs --- - replicas: "2" - cpu_request: "1000m" - memory_request: "2Gi" - autoscale_min: "2" - autoscale_max: "4" - log_retention_days: "365" - archive_retention_days: "2555" - enable_encryption_at_rest: "true" - enable_kms_rotation: "true" - - # --- Operational flags --- - enable_canary: "false" - enable_pagerduty: "true" - enable_cost_alerts: "true" - cost_alert_threshold_usd: "1000" - -# POLICY VIOLATION — this is the Act 4 trigger. -# The policy_checker.py will emit: POLICY_VIOLATION:PUBLIC_INGRESS -# The confidence_signal.py will return: {"score": 0.40, "reason": "POLICY_VIOLATION:PUBLIC_INGRESS"} -# The 0.50 gate halts the pipeline in Dev; mock_executor never runs. -public-ingress: true \ No newline at end of file diff --git a/demo/evidence-ui/index.html b/demo/evidence-ui/index.html deleted file mode 100644 index 43f8e25..0000000 --- a/demo/evidence-ui/index.html +++ /dev/null @@ -1,334 +0,0 @@ - - - - - -ACDL Evidence Timeline - - - -
-

ACDL Evidence Timeline

-

ACDL — Agentic Cloud Delivery Platform · Audit Timeline

-
-
- - -
-
-
-
Loading…
-
-
-
- -
- - - \ No newline at end of file diff --git a/demo/modules/l1/l1-api-gateway/manifest.yaml b/demo/modules/l1/l1-api-gateway/manifest.yaml deleted file mode 100644 index 24093a0..0000000 --- a/demo/modules/l1/l1-api-gateway/manifest.yaml +++ /dev/null @@ -1,10 +0,0 @@ -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 \ No newline at end of file diff --git a/demo/modules/l1/l1-api-gateway/mock_apply.sh b/demo/modules/l1/l1-api-gateway/mock_apply.sh deleted file mode 100755 index 7e4614f..0000000 --- a/demo/modules/l1/l1-api-gateway/mock_apply.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -echo "[L1: l1-api-gateway] applying..." -sleep 1 -echo "[L1: l1-api-gateway] OK" -exit 0 \ No newline at end of file diff --git a/demo/modules/l1/l1-cloudwatch/manifest.yaml b/demo/modules/l1/l1-cloudwatch/manifest.yaml deleted file mode 100644 index 0d4b7bc..0000000 --- a/demo/modules/l1/l1-cloudwatch/manifest.yaml +++ /dev/null @@ -1,10 +0,0 @@ -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 \ No newline at end of file diff --git a/demo/modules/l1/l1-cloudwatch/mock_apply.sh b/demo/modules/l1/l1-cloudwatch/mock_apply.sh deleted file mode 100755 index d38f449..0000000 --- a/demo/modules/l1/l1-cloudwatch/mock_apply.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -echo "[L1: l1-cloudwatch] applying..." -sleep 1 -echo "[L1: l1-cloudwatch] OK" -exit 0 \ No newline at end of file diff --git a/demo/modules/l1/l1-eks-fargate/manifest.yaml b/demo/modules/l1/l1-eks-fargate/manifest.yaml deleted file mode 100644 index 1ddf8d1..0000000 --- a/demo/modules/l1/l1-eks-fargate/manifest.yaml +++ /dev/null @@ -1,13 +0,0 @@ -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 \ No newline at end of file diff --git a/demo/modules/l1/l1-eks-fargate/mock_apply.sh b/demo/modules/l1/l1-eks-fargate/mock_apply.sh deleted file mode 100755 index dd8f766..0000000 --- a/demo/modules/l1/l1-eks-fargate/mock_apply.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -echo "[L1: l1-eks-fargate] applying..." -sleep 1 -echo "[L1: l1-eks-fargate] OK" -exit 0 \ No newline at end of file diff --git a/demo/modules/l1/l1-eventbridge/manifest.yaml b/demo/modules/l1/l1-eventbridge/manifest.yaml deleted file mode 100644 index 5ed863b..0000000 --- a/demo/modules/l1/l1-eventbridge/manifest.yaml +++ /dev/null @@ -1,10 +0,0 @@ -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 \ No newline at end of file diff --git a/demo/modules/l1/l1-eventbridge/mock_apply.sh b/demo/modules/l1/l1-eventbridge/mock_apply.sh deleted file mode 100755 index 91c228b..0000000 --- a/demo/modules/l1/l1-eventbridge/mock_apply.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -echo "[L1: l1-eventbridge] applying..." -sleep 1 -echo "[L1: l1-eventbridge] OK" -exit 0 \ No newline at end of file diff --git a/demo/modules/l1/l1-iam-role/manifest.yaml b/demo/modules/l1/l1-iam-role/manifest.yaml deleted file mode 100644 index 13ea521..0000000 --- a/demo/modules/l1/l1-iam-role/manifest.yaml +++ /dev/null @@ -1,10 +0,0 @@ -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 \ No newline at end of file diff --git a/demo/modules/l1/l1-iam-role/mock_apply.sh b/demo/modules/l1/l1-iam-role/mock_apply.sh deleted file mode 100755 index fe48862..0000000 --- a/demo/modules/l1/l1-iam-role/mock_apply.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -echo "[L1: l1-iam-role] applying..." -sleep 1 -echo "[L1: l1-iam-role] OK" -exit 0 \ No newline at end of file diff --git a/demo/modules/l1/l1-lambda/manifest.yaml b/demo/modules/l1/l1-lambda/manifest.yaml deleted file mode 100644 index e189dbc..0000000 --- a/demo/modules/l1/l1-lambda/manifest.yaml +++ /dev/null @@ -1,13 +0,0 @@ -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 \ No newline at end of file diff --git a/demo/modules/l1/l1-lambda/mock_apply.sh b/demo/modules/l1/l1-lambda/mock_apply.sh deleted file mode 100755 index 1beb98f..0000000 --- a/demo/modules/l1/l1-lambda/mock_apply.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -echo "[L1: l1-lambda] applying..." -sleep 1 -echo "[L1: l1-lambda] OK" -exit 0 \ No newline at end of file diff --git a/demo/modules/l1/l1-s3/manifest.yaml b/demo/modules/l1/l1-s3/manifest.yaml deleted file mode 100644 index cd0c0ff..0000000 --- a/demo/modules/l1/l1-s3/manifest.yaml +++ /dev/null @@ -1,13 +0,0 @@ -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 \ No newline at end of file diff --git a/demo/modules/l1/l1-s3/mock_apply.sh b/demo/modules/l1/l1-s3/mock_apply.sh deleted file mode 100755 index d3ad62b..0000000 --- a/demo/modules/l1/l1-s3/mock_apply.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -echo "[L1: l1-s3] applying..." -sleep 1 -echo "[L1: l1-s3] OK" -exit 0 \ No newline at end of file diff --git a/demo/modules/l1/l1-sqs/manifest.yaml b/demo/modules/l1/l1-sqs/manifest.yaml deleted file mode 100644 index dc46476..0000000 --- a/demo/modules/l1/l1-sqs/manifest.yaml +++ /dev/null @@ -1,10 +0,0 @@ -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 \ No newline at end of file diff --git a/demo/modules/l1/l1-sqs/mock_apply.sh b/demo/modules/l1/l1-sqs/mock_apply.sh deleted file mode 100755 index 585d40b..0000000 --- a/demo/modules/l1/l1-sqs/mock_apply.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -echo "[L1: l1-sqs] applying..." -sleep 1 -echo "[L1: l1-sqs] OK" -exit 0 \ No newline at end of file diff --git a/demo/modules/l2/l2-commodity-price-feed/manifest.yaml b/demo/modules/l2/l2-commodity-price-feed/manifest.yaml deleted file mode 100644 index 38f9cb8..0000000 --- a/demo/modules/l2/l2-commodity-price-feed/manifest.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: l2-commodity-price-feed -kind: l2 -description: Real-time commodity price ingestion from Platts -l1s: - - name: l1-eks-fargate - inputs: - cluster_name: price-feed-cluster - region: us-east-1 - cpu_arch: arm64 - - name: l1-lambda - inputs: - function_name: price-ingest - runtime: python3.11 - handler: index.handler - - name: l1-api-gateway - inputs: - api_name: platts-price-api - stage_name: dev - - name: l1-eventbridge - inputs: - bus_name: price-events - rule_name: price-publish-rule - - name: l1-s3 - inputs: - bucket_name: acdl-price-archive - region: us-east-1 - retention_days: "90" \ No newline at end of file diff --git a/demo/modules/l2/l2-energy-analytics-api/manifest.yaml b/demo/modules/l2/l2-energy-analytics-api/manifest.yaml deleted file mode 100644 index a456faa..0000000 --- a/demo/modules/l2/l2-energy-analytics-api/manifest.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: l2-energy-analytics-api -kind: l2 -description: Historical energy analytics query API -l1s: - - name: l1-eks-fargate - inputs: - cluster_name: analytics-cluster - region: us-east-1 - cpu_arch: arm64 - - name: l1-api-gateway - inputs: - api_name: energy-analytics-api - stage_name: dev - - name: l1-lambda - inputs: - function_name: analytics-query - runtime: python3.11 - handler: index.handler - - name: l1-s3 - inputs: - bucket_name: acdl-analytics-data - region: us-east-1 - retention_days: "2555" - - name: l1-cloudwatch - inputs: - log_group_name: /acdl/analytics-api - metric_namespace: acdl/analytics \ No newline at end of file diff --git a/demo/modules/l2/l2-invoice-service/manifest.yaml b/demo/modules/l2/l2-invoice-service/manifest.yaml deleted file mode 100644 index e01f63f..0000000 --- a/demo/modules/l2/l2-invoice-service/manifest.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: l2-invoice-service -kind: l2 -description: Billing and invoicing microservice for energy trades -l1s: - - name: l1-eks-fargate - inputs: - cluster_name: invoice-cluster - region: us-east-1 - cpu_arch: arm64 - - name: l1-iam-role - inputs: - role_name: invoice-service-role - trust_policy: '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"eks.amazonaws.com"},"Action":"sts:AssumeRole"}]}' - - name: l1-lambda - inputs: - function_name: invoice-generator - runtime: python3.11 - handler: index.handler - - name: l1-sqs - inputs: - queue_name: invoice-queue - visibility_timeout: "60" - - name: l1-s3 - inputs: - bucket_name: acdl-invoice-archive - region: us-east-1 - retention_days: "365" \ No newline at end of file diff --git a/demo/modules/l2/l2-regulatory-reporting/manifest.yaml b/demo/modules/l2/l2-regulatory-reporting/manifest.yaml deleted file mode 100644 index b64ab58..0000000 --- a/demo/modules/l2/l2-regulatory-reporting/manifest.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: l2-regulatory-reporting -kind: l2 -description: Regulatory compliance and reporting for energy trading -l1s: - - name: l1-eks-fargate - inputs: - cluster_name: regulatory-cluster - region: us-east-1 - cpu_arch: arm64 - - name: l1-iam-role - inputs: - role_name: regulatory-reporting-role - trust_policy: '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"eks.amazonaws.com"},"Action":"sts:AssumeRole"}]}' - - name: l1-lambda - inputs: - function_name: regulatory-reporter - runtime: python3.11 - handler: index.handler - - name: l1-sqs - inputs: - queue_name: regulatory-queue - visibility_timeout: "120" - - name: l1-s3 - inputs: - bucket_name: acdl-regulatory-archive - region: us-east-1 - retention_days: "2555" \ No newline at end of file diff --git a/demo/scripts/.gitkeep b/demo/scripts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/demo/scripts/confidence_signal.py b/demo/scripts/confidence_signal.py deleted file mode 100755 index d47168b..0000000 --- a/demo/scripts/confidence_signal.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python3 -"""confidence_signal.py — REQ-08 / D-024 - -Reads a contract.yaml, invokes policy_checker.py as a subprocess, and emits -a deterministic JSON confidence score. - - policy pass -> {"score": 0.90, "reason": "POLICY_PASS"} - policy fail -> {"score": 0.40, "reason": ""} - -Exit 0 ALWAYS (per D-024): the pipeline decides the gate, not this script's -exit code. - -Input: argv[1] = path to a contract.yaml file. -""" -import json -import os -import subprocess -import sys - - -def main() -> int: - if len(sys.argv) < 2: - print("usage: confidence_signal.py ", file=sys.stderr) - return 1 - - contract_path = sys.argv[1] - - # Resolve policy_checker.py relative to this script so it works regardless - # of cwd. Use python3 + script path (not ./) per the contract. - here = os.path.dirname(os.path.abspath(__file__)) - policy_checker = os.path.join(here, "policy_checker.py") - - proc = subprocess.run( - ["python3", policy_checker, contract_path], - capture_output=True, - text=True, - ) - - if proc.returncode == 0: - score = "0.90" - # POLICY_PASS is the expected stdout; strip any trailing whitespace. - reason = proc.stdout.strip() or "POLICY_PASS" - else: - score = "0.40" - # The violation code (e.g. "POLICY_VIOLATION:PUBLIC_INGRESS") is on stdout. - reason = proc.stdout.strip() or "POLICY_VIOLATION:UNKNOWN" - - # Emit with literal score (two-decimal form per the contract) and a quoted - # reason. Constructed manually so json.dumps does not collapse 0.90 -> 0.9. - print('{"score": ' + score + ', "reason": ' + json.dumps(reason) + '}') - return 0 - - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/demo/scripts/evidence_writer.py b/demo/scripts/evidence_writer.py deleted file mode 100755 index 09472bc..0000000 --- a/demo/scripts/evidence_writer.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python3 -"""evidence_writer.py — REQ-11 / D-023 / D-005 - -Appends a hash-chained event to audit.json. - -Each event: {"seq": N, "ts": , "stage": "...", "event": "...", - "prev_hash": "", "hash": ""} - -Hash chain (D-023): - 1. Build event dict with hash = "" (empty string). - 2. canonical = json.dumps(event, sort_keys=True, separators=(",", ":")) - 3. hash = sha256(canonical.encode("utf-8")).hexdigest() - 4. event["hash"] = hash - 5. append to audit.json - -Auto-genesis: if audit.json is empty/missing and --stage is not "genesis", -a genesis event (seq 0, prev_hash "GENESIS") is inserted first. - -Input: - --stage (required) - --event "" (required) - --audit (optional, default ./audit.json) -Output: stdout {"seq": N, "hash": "..."} -Exit: 0 on success, 1 on I/O error. -""" -import argparse -import datetime -import hashlib -import json -import os -import sys - -GENESIS_EVENT_TEXT = "audit log initialized" - - -def now_iso8601_utc() -> str: - return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") - - -def compute_hash(event: dict) -> str: - """Compute the sha256 hash of an event using canonical JSON (D-023).""" - tmp = dict(event) - tmp["hash"] = "" - canonical = json.dumps(tmp, sort_keys=True, separators=(",", ":")) - return hashlib.sha256(canonical.encode("utf-8")).hexdigest() - - -def make_event(seq: int, stage: str, event_text: str, prev_hash: str) -> dict: - event = { - "seq": seq, - "ts": now_iso8601_utc(), - "stage": stage, - "event": event_text, - "prev_hash": prev_hash, - "hash": "", - } - event["hash"] = compute_hash(event) - return event - - -def load_audit(audit_path: str) -> list: - if not os.path.exists(audit_path): - return [] - try: - with open(audit_path, "r", encoding="utf-8") as fh: - data = json.load(fh) - except (json.JSONDecodeError, ValueError): - return [] - if not isinstance(data, list): - return [] - return data - - -def atomic_write(audit_path: str, data: list) -> None: - tmp_path = audit_path + ".tmp" - with open(tmp_path, "w", encoding="utf-8") as fh: - json.dump(data, fh, indent=2) - fh.write("\n") - os.replace(tmp_path, audit_path) - - -def main() -> int: - parser = argparse.ArgumentParser(description="Append a hash-chained event to audit.json") - parser.add_argument("--stage", required=True, - choices=["dev", "qa", "prod", "finalize", "genesis"]) - parser.add_argument("--event", required=True) - parser.add_argument("--audit", default="./audit.json") - args = parser.parse_args() - - events = load_audit(args.audit) - - # Auto-genesis: if the log is empty and the caller did not ask for a - # genesis event, seed one first. - if len(events) == 0 and args.stage != "genesis": - genesis = make_event(seq=0, stage="genesis", event_text=GENESIS_EVENT_TEXT, - prev_hash="GENESIS") - events.append(genesis) - - # Determine the new seq + prev_hash. - if events: - last = events[-1] - seq = last["seq"] + 1 - prev_hash = last["hash"] - else: - seq = 0 - prev_hash = "GENESIS" - - new_event = make_event(seq=seq, stage=args.stage, event_text=args.event, - prev_hash=prev_hash) - events.append(new_event) - - try: - atomic_write(args.audit, events) - except OSError as exc: - print(f"evidence_writer: I/O error: {exc}", file=sys.stderr) - return 1 - - print(json.dumps({"seq": new_event["seq"], "hash": new_event["hash"]})) - return 0 - - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/demo/scripts/finalize_evidence.py b/demo/scripts/finalize_evidence.py deleted file mode 100755 index fdde84e..0000000 --- a/demo/scripts/finalize_evidence.py +++ /dev/null @@ -1,182 +0,0 @@ -#!/usr/bin/env python3 -"""finalize_evidence.py — REQ-10 / D-028 / D-029 - -Uploads (PUT or POST) a local `audit.json` to the `acdl-evidence` repo on -Gitea via the file-contents API. Used by the pipeline workflow steps to -persist the hash-chained audit trail to `acdl-evidence` between dispatches -(D-028 state-persistence across re-dispatches; D-029 finalize step). - -Uses only the Python standard library (urllib.request) so it has no -external dependency on `requests`. Auth header: `Authorization: token `. - -Input (argv flags): - --audit (required) local audit.json file to upload - --owner (optional, default continuous-intelligence) - --repo (optional, default acdl-evidence) - --branch (optional, default main) - --path (optional, default audit.json) path in the repo - --token-env (optional, default ACDL_GITEA_TOKEN) - --host (optional, default https://git.cloudinit.dev) - --message (optional, default chore(evidence): update audit.json) - -Behavior: - 1. Read the token from os.environ[token_env]. Missing -> stderr + exit 1. - 2. Read the local audit file; base64-encode it. - 3. GET the current file at .../contents/?ref= to discover - the existing `sha`. 200 -> capture sha (update mode). 404 -> no sha - (create mode). Other errors -> exit 1. - 4. If sha set: PUT with body {content, message, branch, sha}. - If no sha: POST with body {content, message, branch}. - 5. Print {"uploaded": true, "path": "", "sha": ""} to - stdout and exit 0. - 6. On any HTTP error: print - {"uploaded": false, "status": , "body": ""} to stdout - and exit 1. -""" -import argparse -import base64 -import json -import os -import sys -import urllib.error -import urllib.parse -import urllib.request - - -def _request(method: str, url: str, token: str, body: dict = None): - """Perform an HTTP request with the Gitea auth header. Returns - (status_code, response_body_text). Raises URLError on network failure.""" - data = None - headers = {"Authorization": f"token {token}", - "Accept": "application/json"} - if body is not None: - data = json.dumps(body).encode("utf-8") - headers["Content-Type"] = "application/json" - req = urllib.request.Request(url, data=data, method=method, headers=headers) - try: - with urllib.request.urlopen(req) as resp: - return resp.getcode(), resp.read().decode("utf-8", "replace") - except urllib.error.HTTPError as exc: - # HTTPError carries the response body - try: - body_text = exc.read().decode("utf-8", "replace") - except Exception: - body_text = "" - return exc.code, body_text - except urllib.error.URLError as exc: - # Network-level failure (connection refused, DNS, timeout). Return - # a synthetic 0 status + the reason so callers can report cleanly - # without a stack trace. - return 0, f"URLError: {exc.reason}" - - -def get_existing_sha(host: str, owner: str, repo: str, path: str, - branch: str, token: str): - """Return (sha-or-None, error_status_or_None). On 200 returns the sha. - On 404 returns (None, None). Other codes return (None, (status, body)).""" - qs = urllib.parse.urlencode({"ref": branch}) - url = f"{host}/api/v1/repos/{owner}/{repo}/contents/{path}?{qs}" - status, body = _request("GET", url, token) - if status == 200: - try: - data = json.loads(body) - return data.get("sha"), None - except (ValueError, TypeError): - return None, (status, body) - if status == 404: - return None, None - return None, (status, body) - - -def upload(host: str, owner: str, repo: str, path: str, branch: str, - message: str, content_b64: str, sha, token: str): - """PUT (update) or POST (create) the file. Returns (new_sha, None) on - success or (None, (status, body)) on HTTP error.""" - url = f"{host}/api/v1/repos/{owner}/{repo}/contents/{path}" - if sha: - body = {"content": content_b64, "message": message, - "branch": branch, "sha": sha} - status, resp = _request("PUT", url, token, body) - else: - body = {"content": content_b64, "message": message, "branch": branch} - status, resp = _request("POST", url, token, body) - if status in (200, 201): - try: - data = json.loads(resp) - # The file-contents API returns the new content object either at - # top-level `content` (POST create) or `content` (PUT update). - new_sha = None - if isinstance(data, dict): - content_obj = data.get("content") or data - if isinstance(content_obj, dict): - new_sha = content_obj.get("sha") - return new_sha, None - except (ValueError, TypeError): - return None, None - return None, (status, resp) - - -def main() -> int: - parser = argparse.ArgumentParser( - description="Upload a local audit.json to the acdl-evidence Gitea " - "repo via the file-contents API (D-028/D-029).") - parser.add_argument("--audit", required=True, - help="Local audit.json file to upload") - parser.add_argument("--owner", default="continuous-intelligence", - help="Gitea org (default: continuous-intelligence)") - parser.add_argument("--repo", default="acdl-evidence", - help="Gitea repo (default: acdl-evidence)") - parser.add_argument("--branch", default="main", - help="Target branch (default: main)") - parser.add_argument("--path", default="audit.json", - help="Remote path in the repo (default: audit.json)") - parser.add_argument("--token-env", default="ACDL_GITEA_TOKEN", - help="Env var name holding the Gitea token " - "(default: ACDL_GITEA_TOKEN)") - parser.add_argument("--host", default="https://git.cloudinit.dev", - help="Gitea host URL (default: https://git.cloudinit.dev)") - parser.add_argument("--message", default="chore(evidence): update audit.json", - help="Commit message (default: chore(evidence): " - "update audit.json)") - args = parser.parse_args() - - token = os.environ.get(args.token_env) - if not token: - print(f"finalize_evidence: required env var {args.token_env} is not " - f"set", file=sys.stderr) - return 1 - - # Read + base64-encode the local audit file. Missing/unreadable file is - # a clean exit 1 (no stack trace). - try: - with open(args.audit, "rb") as fh: - raw = fh.read() - except OSError as exc: - print(f"finalize_evidence: cannot read {args.audit}: {exc}", - file=sys.stderr) - return 1 - content_b64 = base64.b64encode(raw).decode("ascii") - - # Discover existing sha (update vs create). - sha, err = get_existing_sha(args.host, args.owner, args.repo, - args.path, args.branch, token) - if err is not None: - status, body = err - print(json.dumps({"uploaded": False, "status": status, "body": body})) - return 1 - - # Upload (PUT if sha, POST otherwise). - new_sha, err = upload(args.host, args.owner, args.repo, args.path, - args.branch, args.message, content_b64, sha, token) - if err is not None: - status, body = err - print(json.dumps({"uploaded": False, "status": status, "body": body})) - return 1 - - print(json.dumps({"uploaded": True, "path": args.path, - "sha": new_sha})) - return 0 - - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/demo/scripts/gitea_setup.sh b/demo/scripts/gitea_setup.sh deleted file mode 100755 index adcff18..0000000 --- a/demo/scripts/gitea_setup.sh +++ /dev/null @@ -1,228 +0,0 @@ -#!/usr/bin/env bash -# Phase 01 Gitea scaffolding. Idempotent. -# -# Creates the two new repos under the continuous-intelligence org, pushes a -# placeholder index.html to acdl-evidence, and creates qa + prod branches on -# acdl-contracts. Running against existing repos / branches / files is a -# no-op (409 or 422 is treated as success). -# -# Usage: ACDL_GITEA_TOKEN= scripts/gitea_setup.sh -# Exit codes: 0 = success (created or already existed); 1 = unrecoverable error. - -set -euo pipefail - -GITEA_HOST="${GITEA_HOST:-https://git.cloudinit.dev}" -ORG="continuous-intelligence" -TOKEN="${ACDL_GITEA_TOKEN:?ACDL_GITEA_TOKEN is required}" -API="${GITEA_HOST}/api/v1" - -AUTH=(-H "Authorization: token ${TOKEN}" -H "Content-Type: application/json") - -log() { printf '[setup] %s\n' "$*"; } -warn() { printf '[setup][WARN] %s\n' "$*" >&2; } -err() { printf '[setup][ERROR] %s\n' "$*" >&2; } - -# --- helpers ---------------------------------------------------------------- - -# http_status_code URL -http_get_status() { - local url="$1" - curl -sS -o /dev/null -w "%{http_code}" "${AUTH[@]}" "$url" -} - -# repo_exists NAME -> 0 if exists, 1 otherwise -repo_exists() { - local name="$1" - local status - status=$(http_get_status "${API}/repos/${ORG}/${name}") - [ "$status" = "200" ] -} - -# create_repo NAME DESCRIPTION -create_repo() { - local name="$1" - local description="$2" - local body - body=$(python3 -c " -import json, sys -print(json.dumps({ - 'name': '${name}', - 'description': ${description@Q}, - 'private': True, - 'default_branch': 'main', - 'auto_init': True, - 'gitignores': 'Python', - 'license': '', - 'readme': 'Default' -})) -") - log "Creating repo ${ORG}/${name} (default_branch=main, auto_init=true)" - local status body_out - status=$(curl -sS -o /tmp/setup_repo_create.json -w "%{http_code}" \ - "${AUTH[@]}" -X POST -d "$body" \ - "${API}/orgs/${ORG}/repos") - case "$status" in - 201) log " created (HTTP 201)" ;; - 409) log " already exists (HTTP 409); skipping" ;; - *) - err "create_repo ${name} failed: HTTP ${status}" - cat /tmp/setup_repo_create.json >&2 || true - return 1 - ;; - esac -} - -# set_repo_visibility REPO VISIBILITY (public|private) -set_repo_visibility() { - local repo="$1" - local visibility="$2" - local body - body=$(python3 -c " -import json -is_private = ('${visibility}' == 'private') -print(json.dumps({'private': is_private, 'visibility': '${visibility}'})) -") - log "Setting ${repo} visibility to ${visibility}" - local status - status=$(curl -sS -o /tmp/setup_vis.json -w "%{http_code}" \ - "${AUTH[@]}" -X PATCH -d "$body" \ - "${API}/repos/${ORG}/${repo}") - case "$status" in - 200) log " ok (HTTP 200)" ;; - *) warn "set_repo_visibility ${repo} -> ${visibility} returned HTTP ${status} (continuing)"; cat /tmp/setup_vis.json >&2 || true ;; - esac -} - -# file_exists REPO PATH -> 0 if the file already exists on the default branch -file_exists_on_default() { - local repo="$1" - local path="$2" - local status - status=$(http_get_status "${API}/repos/${ORG}/${repo}/contents/${path}?ref=main") - [ "$status" = "200" ] -} - -# create_placeholder_index REPO -create_placeholder_index() { - local repo="$1" - local path="index.html" - local placeholder - placeholder=' - - - - ACDL Evidence - - - -

ACDL Evidence Stream

-

Evidence timeline will appear here in Phase 05.

-

Placeholder served via Gitea raw file URL (D-012; Gitea has no native Pages).

- -' - - if file_exists_on_default "$repo" "$path"; then - log "index.html already exists on ${repo} main; skipping" - return 0 - fi - - local body - body=$(python3 -c " -import json, base64 -content = '''${placeholder}''' -print(json.dumps({ - 'content': base64.b64encode(content.encode('utf-8')).decode('ascii'), - 'message': 'Initial placeholder index.html (Phase 01, D-016)', - 'branch': 'main' -})) -") - log "Pushing placeholder index.html to ${repo} main" - local status - status=$(curl -sS -o /tmp/setup_index_push.json -w "%{http_code}" \ - "${AUTH[@]}" -X POST -d "$body" \ - "${API}/repos/${ORG}/${repo}/contents/${path}") - case "$status" in - 201) log " pushed (HTTP 201)" ;; - 409|422) log " already exists or conflict (HTTP ${status}); skipping" ;; - *) - err "create_placeholder_index on ${repo} failed: HTTP ${status}" - cat /tmp/setup_index_push.json >&2 || true - return 1 - ;; - esac -} - -# branch_exists REPO BRANCH -> 0 if exists -branch_exists() { - local repo="$1" - local branch="$2" - local status - status=$(http_get_status "${API}/repos/${ORG}/${repo}/branches/${branch}") - [ "$status" = "200" ] -} - -# create_branch REPO BRANCH FROM_REF -create_branch() { - local repo="$1" - local branch="$2" - local from_ref="$3" - if branch_exists "$repo" "$branch"; then - log "Branch ${branch} already exists on ${repo}; skipping" - return 0 - fi - local body - body=$(python3 -c " -import json -print(json.dumps({'new_branch_name': '${branch}', 'old_branch_name': '${from_ref}'})) -") - log "Creating branch ${branch} on ${repo} from ${from_ref}" - local status - status=$(curl -sS -o /tmp/setup_branch.json -w "%{http_code}" \ - "${AUTH[@]}" -X POST -d "$body" \ - "${API}/repos/${ORG}/${repo}/branches") - case "$status" in - 201) log " created (HTTP 201)" ;; - 409) log " already exists (HTTP 409); skipping" ;; - *) - err "create_branch ${branch} on ${repo} failed: HTTP ${status}" - cat /tmp/setup_branch.json >&2 || true - return 1 - ;; - esac -} - -# --- main ------------------------------------------------------------------- - -log "Host: ${GITEA_HOST}" -log "Org: ${ORG}" -log "Token: " - -# Step 1: create acdl-contracts -if ! repo_exists acdl-contracts; then - create_repo acdl-contracts "ACDL developer + agentic entry surface (contract.yaml + issue trigger)" || exit 1 -else - log "acdl-contracts already exists; skipping create" -fi - -# Step 2: create acdl-evidence -if ! repo_exists acdl-evidence; then - create_repo acdl-evidence "ACDL hash-chained audit timeline served as a static site via raw file URLs" || exit 1 -else - log "acdl-evidence already exists; skipping create" -fi - -# Step 2b: make acdl-evidence public so the Phase 05 UI (index.html) can -# fetch audit.json from a browser without exposing the API token (D-012 -# raw-URL approach). acdl-contracts stays private. -set_repo_visibility acdl-evidence public - -# Step 3: push placeholder index.html to acdl-evidence -create_placeholder_index acdl-evidence || exit 1 - -# Step 4: create qa + prod branches on acdl-contracts (visible stand-in for -# the unsupported Gitea environments API; per D-013). -create_branch acdl-contracts qa main || exit 1 -create_branch acdl-contracts prod main || exit 1 - -log "Done. Run scripts/verify_phase01.sh to confirm success criteria." -exit 0 \ No newline at end of file diff --git a/demo/scripts/l3b_agent_stub.py b/demo/scripts/l3b_agent_stub.py deleted file mode 100755 index bf1c93f..0000000 --- a/demo/scripts/l3b_agent_stub.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env python3 -"""l3b_agent_stub.py — D-008 / D-026 / D-021 - -Parses a GitHub/Gitea Issue body by keywords and emits a contract.yaml that -selects an L2 stack. This is the agentic (L3B) entry surface: deterministic -keyword matching, no external AI APIs. - -D-008 keyword map (priority order — first match wins): - gas, price, ingest, data-lake -> l2-commodity-price-feed - invoice, billing -> l2-invoice-service - analytics, historical, query -> l2-energy-analytics-api - regulatory, compliance, reporting, trading - -> l2-regulatory-reporting - (no match) -> l2-invoice-service (fallback) - -Output contract.yaml (D-021 schema): - stack: - inputs: - environment: dev - owner: citizen-developer - source: l3b-agent-stub - public-ingress: false - -Input: - argv[1] = issue body text (or stdin if argv[1] absent/empty) - -o = write the contract to a file (default: stdout) -Exit: - 0 on success, 1 on empty input -""" -import sys - - -# Ordered keyword groups -> L2 stack mapping (D-008). First match wins. -KEYWORD_MAP = [ - (("gas", "price", "ingest", "data-lake"), "l2-commodity-price-feed"), - (("invoice", "billing"), "l2-invoice-service"), - (("analytics", "historical", "query"), "l2-energy-analytics-api"), - (("regulatory", "compliance", "reporting", "trading"), "l2-regulatory-reporting"), -] - -FALLBACK_STACK = "l2-invoice-service" - - -def map_issue_to_stack(text: str) -> str: - lowered = text.lower() - for keywords, stack in KEYWORD_MAP: - for kw in keywords: - if kw in lowered: - return stack - return FALLBACK_STACK - - -def render_contract(stack: str) -> str: - # Fixed-schema YAML (D-021). Emitted as text (no yaml dependency needed). - return ( - f"stack: {stack}\n" - "inputs:\n" - " environment: dev\n" - " owner: citizen-developer\n" - " source: l3b-agent-stub\n" - "public-ingress: false\n" - ) - - -def read_issue_body(args: list) -> str: - """Read issue body from args[0] (already-stripped argv, no script name) - or stdin. Empty -> error.""" - if len(args) >= 1 and args[0].strip(): - return args[0] - # Fall back to stdin if argv body is absent or empty. - if not sys.stdin.isatty(): - data = sys.stdin.read() - if data.strip(): - return data - return "" - - -def parse_output_flag(argv: list): - """Extract -o from argv (returns (rest, output_path)).""" - output_path = None - rest = [] - i = 1 - while i < len(argv): - arg = argv[i] - if arg == "-o": - if i + 1 < len(argv): - output_path = argv[i + 1] - i += 2 - continue - else: - print("l3b_agent_stub: -o requires a path argument", file=sys.stderr) - sys.exit(1) - rest.append(arg) - i += 1 - return rest, output_path - - -def main() -> int: - rest, output_path = parse_output_flag(sys.argv) - body = read_issue_body(rest) - if not body.strip(): - print("l3b_agent_stub: empty issue body (no argv[1] and no stdin)", file=sys.stderr) - return 1 - - stack = map_issue_to_stack(body) - contract = render_contract(stack) - - if output_path: - with open(output_path, "w", encoding="utf-8") as fh: - fh.write(contract) - else: - sys.stdout.write(contract) - - return 0 - - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/demo/scripts/mock_executor.sh b/demo/scripts/mock_executor.sh deleted file mode 100755 index df02770..0000000 --- a/demo/scripts/mock_executor.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env bash -# mock_executor.sh — REQ-06 / D-022 -# -# Reads a contract.yaml, resolves the L2 composition, invokes each L1's -# mock_apply.sh in order, and writes state.json to the current working -# directory. -# -# Input: argv[1] = path to a contract.yaml file. -# Output: -# - stdout: per-L1 progress (echoed from each mock_apply.sh) -# - state.json in cwd: {"l2": "...", "l1s": [...], "contract": {...}} -# Exit: -# 0 if all L1s exit 0; 1 if any L1 exited non-zero (state.json is still -# written with the recorded exit codes). -set -euo pipefail - -if [[ $# -lt 1 ]]; then - echo "usage: mock_executor.sh " >&2 - exit 1 -fi - -CONTRACT_PATH="$1" - -if [[ ! -f "$CONTRACT_PATH" ]]; then - echo "contract not found: $CONTRACT_PATH" >&2 - exit 1 -fi - -# --- Parse the contract (stack + full contract dict) via python3 + yaml. --- -# Emit stack on line 1 and the full contract JSON on line 2, then read both -# lines into separate bash variables (so the JSON's internal spaces survive). -CONTRACT_PARSED=$(python3 - "$CONTRACT_PATH" <<'PY' -import sys, json, yaml -path = sys.argv[1] -with open(path, "r", encoding="utf-8") as fh: - contract = yaml.safe_load(fh) -if not isinstance(contract, dict): - sys.stderr.write("contract is not a mapping\n") - sys.exit(2) -stack = contract.get("stack", "") -# Use a compact JSON (no spaces) so the single-line contract survives bash -# variable capture cleanly. -print(stack) -print(json.dumps(contract, sort_keys=True, separators=(",", ":"))) -PY -) - -STACK=$(printf '%s\n' "$CONTRACT_PARSED" | sed -n '1p') -CONTRACT_JSON=$(printf '%s\n' "$CONTRACT_PARSED" | sed -n '2p') - -if [[ -z "$STACK" ]]; then - echo "contract missing 'stack' key" >&2 - exit 1 -fi - -# --- Resolve the L2 manifest. --- -L2_MANIFEST="modules/l2/${STACK}/manifest.yaml" -if [[ ! -f "$L2_MANIFEST" ]]; then - echo "L2_NOT_FOUND: ${STACK}" >&2 - exit 1 -fi - -# --- Read the L2's l1s: list (ordered names) via python. --- -L1_NAMES_JSON=$(python3 - "$L2_MANIFEST" <<'PY' -import sys, json, yaml -path = sys.argv[1] -with open(path, "r", encoding="utf-8") as fh: - manifest = yaml.safe_load(fh) -l1s = manifest.get("l1s", []) if isinstance(manifest, dict) else [] -names = [entry.get("name", "") for entry in l1s if isinstance(entry, dict)] -print(json.dumps(names)) -PY -) - -# --- Invoke each L1's mock_apply.sh in order, recording exit codes. --- -# Build the l1s results array in JSON via python, appending as we go. -RESULTS_JSON="[]" - -ALL_OK=0 -while IFS= read -r L1_NAME; do - L1_SCRIPT="modules/l1/${L1_NAME}/mock_apply.sh" - if [[ ! -f "$L1_SCRIPT" ]]; then - echo "L1_NOT_FOUND: ${L1_NAME}" >&2 - exit 1 - fi - - # Capture stdout + exit code. stderr passes through. - L1_OUT=$(bash "$L1_SCRIPT") - L1_RC=$? - - # Echo the L1's stdout so the pipeline sees the progress lines. - printf '%s\n' "$L1_OUT" - - # Record {"name": ..., "applied": true, "exit_code": ...}. - RESULTS_JSON=$(python3 - "$RESULTS_JSON" "$L1_NAME" "$L1_RC" <<'PY' -import sys, json -results = json.loads(sys.argv[1]) -name = sys.argv[2] -rc = int(sys.argv[3]) -results.append({"name": name, "applied": True, "exit_code": rc}) -print(json.dumps(results)) -PY -) - - if [[ $L1_RC -ne 0 ]]; then - ALL_OK=1 - fi -done < <(python3 -c "import sys, json; print('\n'.join(json.loads(sys.argv[1])))" "$L1_NAMES_JSON") - -# --- Write state.json to the current working directory (D-022). --- -python3 - "$RESULTS_JSON" "$STACK" "$CONTRACT_JSON" <<'PY' -import sys, json -results = json.loads(sys.argv[1]) -stack = sys.argv[2] -contract = json.loads(sys.argv[3]) -state = { - "l2": stack, - "l1s": results, - "contract": contract, -} -with open("state.json", "w", encoding="utf-8") as fh: - json.dump(state, fh, indent=2) - fh.write("\n") -PY - -exit "$ALL_OK" \ No newline at end of file diff --git a/demo/scripts/policy_checker.py b/demo/scripts/policy_checker.py deleted file mode 100755 index 59fe163..0000000 --- a/demo/scripts/policy_checker.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python3 -"""policy_checker.py — REQ-07 / D-025 - -Reads a contract.yaml and enforces the single Phase-03 policy rule: -`public-ingress: true` is forbidden. - -Input: argv[1] = path to a contract.yaml file. -Output: stdout "POLICY_PASS" or "POLICY_VIOLATION:PUBLIC_INGRESS" -Exit: 0 on pass, 1 on violation. - -Idempotent, no side effects (no file writes). Treats an absent or falsy -`public-ingress` key as a pass. -""" -import sys -import yaml - - -def main() -> int: - if len(sys.argv) < 2: - print("usage: policy_checker.py ", file=sys.stderr) - return 2 - - contract_path = sys.argv[1] - - try: - with open(contract_path, "r", encoding="utf-8") as fh: - contract = yaml.safe_load(fh) - except FileNotFoundError: - print(f"contract not found: {contract_path}", file=sys.stderr) - return 2 - except yaml.YAMLError as exc: - print(f"invalid yaml: {exc}", file=sys.stderr) - return 2 - - # Treat missing/non-mapping as no policy violation. - if not isinstance(contract, dict): - print("POLICY_PASS") - return 0 - - public_ingress = contract.get("public-ingress", False) - - if public_ingress is True: - print("POLICY_VIOLATION:PUBLIC_INGRESS") - return 1 - - print("POLICY_PASS") - return 0 - - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/demo/scripts/run_demo.sh b/demo/scripts/run_demo.sh deleted file mode 100755 index 4c19739..0000000 --- a/demo/scripts/run_demo.sh +++ /dev/null @@ -1,258 +0,0 @@ -#!/usr/bin/env bash -# scripts/run_demo.sh — Phase 05 dry-run simulation of the 4 demo acts (T-5.2). -# -# Simulates the full 4-act demo locally (no act_runner) by calling the core -# scripts in sequence and writing hash-chained evidence events to audit.json, -# then optionally uploads audit.json + evidence-ui/index.html to acdl-evidence -# main via finalize_evidence.py (D-031, D-033). -# -# Usage: scripts/run_demo.sh [--no-upload] -# --no-upload skip the Gitea API calls (useful for testing without a token) - -set -uo pipefail - -# ----------------------------------------------------------------------------- -# Parse args -# ----------------------------------------------------------------------------- -UPLOAD=1 -for arg in "$@"; do - case "$arg" in - --no-upload) - UPLOAD=0 - ;; - *) - echo "run_demo.sh: unknown argument: $arg" >&2 - echo "usage: scripts/run_demo.sh [--no-upload]" >&2 - exit 2 - ;; - esac -done - -# ----------------------------------------------------------------------------- -# Paths -# ----------------------------------------------------------------------------- -# Repo root = location of this script's parent dir. -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" - -WORKDIR="/tmp/acdl_demo_run" -AUDIT="$WORKDIR/audit.json" -CONTRACTS="$WORKDIR/contracts" - -# Track failures so we can return non-zero at the end (we do NOT use set -e -# because policy_checker intentionally exits 1 on Act 4). -FAIL=0 - -# ----------------------------------------------------------------------------- -# Helpers -# ----------------------------------------------------------------------------- - -# Write one evidence event. Args: -ev() { - local stage="$1" - local text="$2" - if ! python3 "$SCRIPT_DIR/evidence_writer.py" --stage "$stage" --event "$text" --audit "$AUDIT"; then - echo "run_demo.sh: evidence_writer failed for stage=$stage text=$text" >&2 - FAIL=1 - fi -} - -# Run a contract through the Act 2/3 pipeline (policy -> confidence -> executor). -# Assumes the contract already passed policy (caller verifies). Writes the -# standard 4-event sequence. Args: -run_passing_pipeline() { - local dev_event="$1" - - ev dev "$dev_event" - ev qa "qa approved" - ev prod "prod approved" - ev finalize "finalize: audit.json committed to acdl-evidence" -} - -# ----------------------------------------------------------------------------- -# Setup working directory -# ----------------------------------------------------------------------------- -mkdir -p "$CONTRACTS" -rm -f "$AUDIT" - -# ----------------------------------------------------------------------------- -# Initialize audit (genesis) -# ----------------------------------------------------------------------------- -echo "== run_demo.sh: initializing audit at $AUDIT ==" -ev genesis "audit log initialized" - -# ----------------------------------------------------------------------------- -# Act 1 — Friction -# ----------------------------------------------------------------------------- -echo "== Act 1 — Friction ==" -ev dev "Act 1 Friction: manual 2-week deployment (legacy process)" - -# ----------------------------------------------------------------------------- -# Act 2 — Developer Self-Service -# ----------------------------------------------------------------------------- -echo "== Act 2 — Developer Self-Service ==" -cat > "$CONTRACTS/act2.yaml" <<'YAML' -stack: l2-commodity-price-feed -inputs: - environment: dev - owner: platform-team -public-ingress: false -YAML - -ACT2_POLICY="$(python3 "$SCRIPT_DIR/policy_checker.py" "$CONTRACTS/act2.yaml")" -ACT2_POLICY_RC=$? -echo " policy_checker: $ACT2_POLICY (rc=$ACT2_POLICY_RC)" -if [ "$ACT2_POLICY" != "POLICY_PASS" ]; then - echo "run_demo.sh: Act 2 expected POLICY_PASS, got '$ACT2_POLICY'" >&2 - FAIL=1 -fi - -ACT2_CONF="$(python3 "$SCRIPT_DIR/confidence_signal.py" "$CONTRACTS/act2.yaml")" -echo " confidence_signal: $ACT2_CONF" -# Expected: {"score": 0.90, "reason": "POLICY_PASS"} - -# mock_executor.sh resolves modules/l2//manifest.yaml relative to its -# cwd, so it must run from the repo root. It writes state.json to its cwd; -# clean it up from the repo root afterward so no stray file is left there. -( - cd "$REPO_ROOT" && bash "$SCRIPT_DIR/mock_executor.sh" "$CONTRACTS/act2.yaml" -) -MOCK_RC=$? -rm -f "$REPO_ROOT/state.json" -if [ "$MOCK_RC" -ne 0 ]; then - echo "run_demo.sh: Act 2 mock_executor failed (rc=$MOCK_RC)" >&2 - FAIL=1 -fi - -run_passing_pipeline "dev applied: l2-commodity-price-feed" - -# ----------------------------------------------------------------------------- -# Act 3 — Citizen Developer -# ----------------------------------------------------------------------------- -echo "== Act 3 — Citizen Developer ==" -ISSUE_BODY="We need to ingest natural gas prices from Platts and report on compliance for the trading desk." -if ! python3 "$SCRIPT_DIR/l3b_agent_stub.py" "$ISSUE_BODY" -o "$CONTRACTS/act3.yaml"; then - echo "run_demo.sh: l3b_agent_stub failed for Act 3" >&2 - FAIL=1 -fi - -# Confirm the generated contract's stack (D-008: gas/price matches first). -ACT3_STACK="$(python3 -c "import yaml,sys; print(yaml.safe_load(open('$CONTRACTS/act3.yaml'))['stack'])" 2>/dev/null || echo "")" -echo " l3b generated stack: $ACT3_STACK" -if [ "$ACT3_STACK" != "l2-commodity-price-feed" ]; then - echo "run_demo.sh: WARNING Act 3 expected stack l2-commodity-price-feed, got '$ACT3_STACK'" >&2 - # Continue anyway per the task spec. -fi - -ACT3_POLICY="$(python3 "$SCRIPT_DIR/policy_checker.py" "$CONTRACTS/act3.yaml")" -ACT3_POLICY_RC=$? -echo " policy_checker: $ACT3_POLICY (rc=$ACT3_POLICY_RC)" -if [ "$ACT3_POLICY" != "POLICY_PASS" ]; then - echo "run_demo.sh: Act 3 expected POLICY_PASS, got '$ACT3_POLICY'" >&2 - FAIL=1 -fi - -ACT3_CONF="$(python3 "$SCRIPT_DIR/confidence_signal.py" "$CONTRACTS/act3.yaml")" -echo " confidence_signal: $ACT3_CONF" - -( - cd "$REPO_ROOT" && bash "$SCRIPT_DIR/mock_executor.sh" "$CONTRACTS/act3.yaml" -) -MOCK_RC=$? -rm -f "$REPO_ROOT/state.json" -if [ "$MOCK_RC" -ne 0 ]; then - echo "run_demo.sh: Act 3 mock_executor failed (rc=$MOCK_RC)" >&2 - FAIL=1 -fi - -run_passing_pipeline "dev applied: l2-commodity-price-feed (Act 3 from issue)" - -# ----------------------------------------------------------------------------- -# Act 4 — Safety Net -# ----------------------------------------------------------------------------- -echo "== Act 4 — Safety Net ==" -cat > "$CONTRACTS/act4.yaml" <<'YAML' -stack: l2-regulatory-reporting -inputs: - environment: dev - owner: platform-team -public-ingress: true -YAML - -# policy_checker exits 1 on violation; capture without failing the script. -ACT4_POLICY="$(python3 "$SCRIPT_DIR/policy_checker.py" "$CONTRACTS/act4.yaml" 2>&1 || true)" -echo " policy_checker: $ACT4_POLICY" -if [ "$ACT4_POLICY" != "POLICY_VIOLATION:PUBLIC_INGRESS" ]; then - echo "run_demo.sh: Act 4 expected POLICY_VIOLATION:PUBLIC_INGRESS, got '$ACT4_POLICY'" >&2 - FAIL=1 -fi - -ACT4_CONF="$(python3 "$SCRIPT_DIR/confidence_signal.py" "$CONTRACTS/act4.yaml")" -echo " confidence_signal: $ACT4_CONF" -# Expected: {"score": 0.40, "reason": "POLICY_VIOLATION:PUBLIC_INGRESS"} - -# Score < 0.50 -> dev rejects. Do NOT run mock_executor, do NOT write qa/prod/finalize. -ev dev "dev rejected: POLICY_VIOLATION:PUBLIC_INGRESS (confidence 0.40 < 0.50)" - -# ----------------------------------------------------------------------------- -# Summary -# ----------------------------------------------------------------------------- -echo "== Summary ==" -python3 - "$AUDIT" <<'PY' -import json, sys -audit = json.load(open(sys.argv[1])) -for e in audit: - print(f"{e['seq']} | {e['stage']} | {e['event']} | {e['hash'][:12]}") -print(f"total events: {len(audit)}") -PY - -EVENT_COUNT="$(python3 -c "import json; print(len(json.load(open('$AUDIT'))))")" -echo "event count: $EVENT_COUNT" - -if [ "$EVENT_COUNT" -lt 11 ]; then - echo "run_demo.sh: expected >= 11 events, got $EVENT_COUNT" >&2 - FAIL=1 -fi - -# ----------------------------------------------------------------------------- -# Upload (optional) -# ----------------------------------------------------------------------------- -if [ "$UPLOAD" -eq 1 ]; then - echo "== Upload ==" - if [ -z "${ACDL_GITEA_TOKEN:-}" ]; then - echo "run_demo.sh: ACDL_GITEA_TOKEN not set; skipping upload (use --no-upload to silence)" >&2 - else - # Upload audit.json to acdl-evidence main. - if python3 "$SCRIPT_DIR/finalize_evidence.py" --audit "$AUDIT"; then - echo " audit.json uploaded" - else - echo "run_demo.sh: finalize_evidence failed for audit.json" >&2 - FAIL=1 - fi - # Upload index.html (the --audit flag accepts any local file path; --path - # sets the remote destination). - if python3 "$SCRIPT_DIR/finalize_evidence.py" \ - --audit "$REPO_ROOT/evidence-ui/index.html" \ - --path index.html \ - --message "chore(ui): update index.html (demo dry run)"; then - echo " index.html uploaded" - else - echo "run_demo.sh: finalize_evidence failed for index.html" >&2 - FAIL=1 - fi - echo "Uploaded audit.json + index.html to acdl-evidence main" - echo " raw URL: https://git.cloudinit.dev/continuous-intelligence/acdl-evidence/raw/branch/main/index.html" - fi -else - echo "== Upload skipped (--no-upload) ==" -fi - -# ----------------------------------------------------------------------------- -# Exit -# ----------------------------------------------------------------------------- -if [ "$FAIL" -ne 0 ]; then - echo "run_demo.sh: one or more steps failed (see warnings above)" >&2 - exit 1 -fi -echo "run_demo.sh: OK ($EVENT_COUNT events)" -exit 0 \ No newline at end of file diff --git a/demo/scripts/verify_phase01.sh b/demo/scripts/verify_phase01.sh deleted file mode 100755 index 425b464..0000000 --- a/demo/scripts/verify_phase01.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env bash -# Phase 01 verification script. -# Confirms the three-repo scaffold exists under the continuous-intelligence -# Gitea org and that the Phase 01 visible artifacts (placeholder index.html on -# acdl-evidence; qa + prod branches on acdl-contracts) are present. -# -# Usage: ACDL_GITEA_TOKEN= scripts/verify_phase01.sh -# Exit codes: 0 = all checks passed; 1 = one or more checks failed. - -set -euo pipefail - -GITEA_HOST="${GITEA_HOST:-https://git.cloudinit.dev}" -ORG="continuous-intelligence" -TOKEN="${ACDL_GITEA_TOKEN:-}" - -fail_count=0 -note() { printf ' [%s] %s\n' "$1" "$2"; } -pass() { note "PASS" "$1"; } -fail() { note "FAIL" "$1"; fail_count=$((fail_count + 1)); } -warn() { printf ' [WARN] %s\n' "$1" >&2; } - -echo "== Phase 01 verification ==" -echo "Host: $GITEA_HOST" -echo "Org: $ORG" -if [ -n "$TOKEN" ]; then - echo "Token: " -else - echo "Token: " -fi -echo - -# --- Check 1: acdl-contracts repo exists --- -echo "-- Check 1: acdl-contracts repo exists --" -status=$(curl -sS -o /tmp/p01_contracts.json -w "%{http_code}" \ - -H "Authorization: token ${TOKEN}" \ - "${GITEA_HOST}/api/v1/repos/${ORG}/acdl-contracts") -if [ "$status" = "200" ]; then - default_branch=$(python3 -c "import json; print(json.load(open('/tmp/p01_contracts.json')).get('default_branch','?'))") - pass "acdl-contracts exists (default_branch=${default_branch})" -else - fail "acdl-contracts GET returned HTTP ${status}" -fi - -# --- Check 2: acdl-evidence repo exists --- -echo "-- Check 2: acdl-evidence repo exists --" -status=$(curl -sS -o /tmp/p01_evidence.json -w "%{http_code}" \ - -H "Authorization: token ${TOKEN}" \ - "${GITEA_HOST}/api/v1/repos/${ORG}/acdl-evidence") -if [ "$status" = "200" ]; then - default_branch=$(python3 -c "import json; print(json.load(open('/tmp/p01_evidence.json')).get('default_branch','?'))") - pass "acdl-evidence exists (default_branch=${default_branch})" -else - fail "acdl-evidence GET returned HTTP ${status}" -fi - -# --- Check 3: acdl-evidence raw index.html returns 200 (Pages substitute per D-012/D-016) --- -# acdl-evidence is public per gitea_setup.sh step 2b, so the raw URL should -# work without auth. We also try with the auth header as a fallback so the -# check does not spuriously fail if the repo visibility was reset. -echo "-- Check 3: acdl-evidence raw index.html returns 200 --" -index_url="${GITEA_HOST}/${ORG}/acdl-evidence/raw/branch/main/index.html" -status=$(curl -sS -o /tmp/p01_index.html -w "%{http_code}" "${index_url}") -if [ "$status" != "200" ] && [ -n "$TOKEN" ]; then - warn "raw URL returned ${status} unauth; retrying with Authorization header" - status=$(curl -sS -o /tmp/p01_index.html -w "%{http_code}" \ - -H "Authorization: token ${TOKEN}" "${index_url}") -fi -if [ "$status" = "200" ]; then - body_size=$(wc -c < /tmp/p01_index.html) - if grep -q "ACDL Evidence" /tmp/p01_index.html; then - pass "raw index.html returns 200 with placeholder body (${body_size} bytes)" - else - fail "raw index.html returns 200 but body does not contain 'ACDL Evidence' marker" - fi -else - fail "GET ${index_url} returned HTTP ${status}" -fi - -# --- Check 4: qa + prod branches exist on acdl-contracts --- -echo "-- Check 4: qa + prod branches exist on acdl-contracts --" -status=$(curl -sS -o /tmp/p01_branches.json -w "%{http_code}" \ - -H "Authorization: token ${TOKEN}" \ - "${GITEA_HOST}/api/v1/repos/${ORG}/acdl-contracts/branches?limit=50") -if [ "$status" != "200" ]; then - fail "list branches on acdl-contracts returned HTTP ${status}" -else - for want in qa prod; do - if python3 -c " -import json, sys -branches = json.load(open('/tmp/p01_branches.json')) -names = [b.get('name', '') for b in branches] -sys.exit(0 if '${want}' in names else 1) -"; then - pass "branch '${want}' exists on acdl-contracts" - else - fail "branch '${want}' missing on acdl-contracts" - fi - done -fi - -echo -echo "== Summary ==" -if [ "$fail_count" -eq 0 ]; then - echo "Phase 01 verification PASSED (all checks ok)" - exit 0 -else - echo "Phase 01 verification FAILED (${fail_count} check(s) failed)" - exit 1 -fi \ No newline at end of file diff --git a/demo/scripts/verify_phase02.sh b/demo/scripts/verify_phase02.sh deleted file mode 100755 index ef4b6ac..0000000 --- a/demo/scripts/verify_phase02.sh +++ /dev/null @@ -1,135 +0,0 @@ -#!/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 \ No newline at end of file diff --git a/demo/scripts/verify_phase03.sh b/demo/scripts/verify_phase03.sh deleted file mode 100755 index 83cccf0..0000000 --- a/demo/scripts/verify_phase03.sh +++ /dev/null @@ -1,240 +0,0 @@ -#!/usr/bin/env bash -# Phase 03 verification script. -# Confirms the 4 L2 modules and the 5 core scripts conform to their contracts. -# -# Usage: scripts/verify_phase03.sh -# Exit codes: 0 = all checks passed; 1 = one or more checks failed. - -set -uo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$ROOT" - -fail_count=0 -pass() { printf ' [PASS] %s\n' "$1"; } -fail() { printf ' [FAIL] %s\n' "$1"; fail_count=$((fail_count + 1)); } - -# Expected L2 names per REQ-04. -EXPECTED_L2S=( - l2-invoice-service - l2-commodity-price-feed - l2-energy-analytics-api - l2-regulatory-reporting -) - -echo "== Phase 03 verification ==" -echo "Root: ${ROOT}" -echo - -# --- Check 1: exactly 4 L2 folders with the expected names --- -echo "-- Check 1: 4 L2 folders with expected names --" -actual=$(ls modules/l2/ 2>/dev/null | sort | tr '\n' ' ') -expected=$(printf '%s\n' "${EXPECTED_L2S[@]}" | sort | tr '\n' ' ') -if [ "$actual" = "$expected" ]; then - pass "exactly 4 L2 folders present and named correctly" -else - fail "L2 folder list mismatch" - echo " expected: $expected" - echo " actual: $actual" -fi - -# --- Check 2: each L2 manifest.yaml validates + references 5 existing L1s --- -echo "-- Check 2: L2 manifests reference 5 existing L1s --" -l2_validate=$(python3 << 'PYEOF' || true -import yaml, glob, os, sys -ok = True -l1s = set(os.listdir('modules/l1')) -for f in sorted(glob.glob('modules/l2/*/manifest.yaml')): - d = yaml.safe_load(open(f)) - folder = os.path.basename(os.path.dirname(f)) - problems = [] - if d.get('name') != folder: problems.append(f"name != {folder}") - if d.get('kind') != 'l2': problems.append("kind != l2") - refs = [x.get('name') for x in d.get('l1s', [])] - if len(refs) != 5: problems.append(f"expected 5 l1s, got {len(refs)}") - unknown = [r for r in refs if r not in l1s] - if unknown: problems.append(f"unknown L1 refs: {unknown}") - # each l1 entry must have an inputs: map - for x in d.get('l1s', []): - if not isinstance(x.get('inputs'), dict): problems.append(f"l1 {x.get('name')} missing inputs map") - status = 'OK' if not problems else 'FAIL: ' + '; '.join(problems) - print(f' [{status}] {f}') - if problems: ok = False -sys.exit(0 if ok else 1) -PYEOF -) -echo "$l2_validate" -if [ "$l2_validate" = "" ] || echo "$l2_validate" | grep -q FAIL; then - if ! echo "$l2_validate" | grep -q PASS; then - fail "one or more L2 manifests invalid (see above)" - fi -else - pass "all 4 L2 manifests valid" -fi -# Re-run for the explicit pass/fail count -python3 << 'PYEOF' > /tmp/l2_check.txt 2>&1 || true -import yaml, glob, os, sys -ok = True -l1s = set(os.listdir('modules/l1')) -for f in sorted(glob.glob('modules/l2/*/manifest.yaml')): - d = yaml.safe_load(open(f)) - folder = os.path.basename(os.path.dirname(f)) - if d.get('name') != folder: ok = False - if d.get('kind') != 'l2': ok = False - refs = [x.get('name') for x in d.get('l1s', [])] - if len(refs) != 5: ok = False - if any(r not in l1s for r in refs): ok = False - for x in d.get('l1s', []): - if not isinstance(x.get('inputs'), dict): ok = False -sys.exit(0 if ok else 1) -PYEOF -if [ $? -eq 0 ]; then pass "all 4 L2 manifests pass structural + reference checks"; else fail "L2 manifest structural check"; fi - -# --- Check 3: typecheck (bash -n + py_compile + yaml load) --- -echo "-- Check 3: typecheck --" -if bash -n scripts/mock_executor.sh; then pass "bash -n mock_executor.sh"; else fail "bash -n mock_executor.sh"; fi -if python3 -m py_compile scripts/policy_checker.py scripts/confidence_signal.py scripts/evidence_writer.py scripts/l3b_agent_stub.py 2>/dev/null; then - pass "py_compile all 4 python scripts" -else - fail "py_compile" -fi -if python3 -c "import yaml, glob; [yaml.safe_load(open(f)) for f in glob.glob('modules/l2/*/manifest.yaml')]" 2>/dev/null; then - pass "yaml load all L2 manifests" -else - fail "yaml load L2 manifests" -fi - -# --- Check 4: policy_checker (D-025) --- -echo "-- Check 4: policy_checker behavior (D-025) --" -WORK="$(mktemp -d)" -trap 'rm -rf "$WORK" "$ROOT/tmp_pass_contract.yaml" "$ROOT/tmp_fail_contract.yaml" "$ROOT/state.json" 2>/dev/null || true' EXIT -printf 'stack: l2-commodity-price-feed\npublic-ingress: false\n' > "$WORK/pass.yaml" -printf 'stack: l2-regulatory-reporting\npublic-ingress: true\n' > "$WORK/fail.yaml" -out=$(python3 scripts/policy_checker.py "$WORK/pass.yaml" 2>&1); rc=$? -if [ "$out" = "POLICY_PASS" ] && [ "$rc" = "0" ]; then - pass "policy_checker pass contract -> POLICY_PASS exit 0" -else - fail "policy_checker pass contract: got '$out' exit=$rc" -fi -out=$(python3 scripts/policy_checker.py "$WORK/fail.yaml" 2>&1); rc=$? -if [ "$out" = "POLICY_VIOLATION:PUBLIC_INGRESS" ] && [ "$rc" = "1" ]; then - pass "policy_checker fail contract -> POLICY_VIOLATION:PUBLIC_INGRESS exit 1" -else - fail "policy_checker fail contract: got '$out' exit=$rc" -fi - -# --- Check 5: confidence_signal (D-024) --- -echo "-- Check 5: confidence_signal behavior (D-024) --" -out=$(python3 scripts/confidence_signal.py "$WORK/pass.yaml" 2>&1); rc=$? -if echo "$out" | grep -q '"score": 0.90' && [ "$rc" = "0" ]; then - pass "confidence_signal pass -> score 0.90 exit 0" -else - fail "confidence_signal pass: got '$out' exit=$rc" -fi -out=$(python3 scripts/confidence_signal.py "$WORK/fail.yaml" 2>&1); rc=$? -if echo "$out" | grep -q '"score": 0.40' && [ "$rc" = "0" ]; then - pass "confidence_signal fail -> score 0.40 exit 0" -else - fail "confidence_signal fail: got '$out' exit=$rc" -fi - -# --- Check 6: evidence_writer hash chain (D-023) --- -echo "-- Check 6: evidence_writer hash chain (D-023) --" -rm -f "$WORK/audit.json" -python3 scripts/evidence_writer.py --stage dev --event "dev start" --audit "$WORK/audit.json" > /dev/null -python3 scripts/evidence_writer.py --stage qa --event "qa approved" --audit "$WORK/audit.json" > /dev/null -python3 scripts/evidence_writer.py --stage prod --event "prod approved" --audit "$WORK/audit.json" > /dev/null -chain_ok=$(python3 << PYEOF -import json, hashlib, sys -try: - events = json.load(open("$WORK/audit.json")) - assert len(events) == 4, f"expected 4 (genesis + 3), got {len(events)}" - assert events[0]['prev_hash'] == 'GENESIS', "genesis prev_hash" - for i in range(1, len(events)): - assert events[i]['prev_hash'] == events[i-1]['hash'], f"chain break at {i}" - e = dict(events[i]); h = e.pop('hash'); e['hash'] = '' - canon = json.dumps(e, sort_keys=True, separators=(',',':')) - assert hashlib.sha256(canon.encode()).hexdigest() == h, f"hash mismatch at {i}" - print("OK") -except AssertionError as ex: - print(f"FAIL: {ex}") - sys.exit(1) -PYEOF -) -if [ "$chain_ok" = "OK" ]; then - pass "evidence_writer: 4 events, GENESIS + 3, chain links + hashes valid" -else - fail "evidence_writer chain: $chain_ok" -fi - -# --- Check 7: mock_executor (D-022) --- -echo "-- Check 7: mock_executor writes state.json (D-022) --" -rm -f "$ROOT/state.json" -out=$(bash scripts/mock_executor.sh "$WORK/pass.yaml" 2>&1); rc=$? -if [ "$rc" != "0" ]; then - fail "mock_executor exit $rc (expected 0)" -else - me_ok=$(python3 << PYEOF -import json, sys -try: - s = json.load(open("$ROOT/state.json")) - assert s['l2'] == 'l2-commodity-price-feed', f"l2 mismatch: {s.get('l2')}" - assert 'l1s' in s and len(s['l1s']) == 5, f"expected 5 l1s, got {len(s.get('l1s', []))}" - assert all(x['applied'] is True and x['exit_code'] == 0 for x in s['l1s']), "l1 not all applied+0" - assert 'contract' in s, "missing contract field" - print("OK") -except Exception as ex: - print(f"FAIL: {ex}") - sys.exit(1) -PYEOF -) - if [ "$me_ok" = "OK" ]; then - pass "mock_executor: state.json with l2 + 5 l1s (all exit 0) + contract" - else - fail "mock_executor state.json: $me_ok" - fi -fi -rm -f "$ROOT/state.json" - -# --- Check 8: l3b_agent_stub D-008 keyword map --- -echo "-- Check 8: l3b_agent_stub keyword map (D-008) --" -act3=$(python3 scripts/l3b_agent_stub.py "We need to ingest natural gas prices from Platts and report on compliance." 2>&1) -if echo "$act3" | grep -q 'stack: l2-commodity-price-feed'; then - pass "l3b Act 3 example -> l2-commodity-price-feed" -else - fail "l3b Act 3 example: got '$act3'" -fi -fallback=$(python3 scripts/l3b_agent_stub.py "please deploy something" 2>&1) -if echo "$fallback" | grep -q 'stack: l2-invoice-service'; then - pass "l3b fallback (no keywords) -> l2-invoice-service" -else - fail "l3b fallback: got '$fallback'" -fi -regulatory=$(python3 scripts/l3b_agent_stub.py "regulatory compliance reporting for trading desk" 2>&1) -if echo "$regulatory" | grep -q 'stack: l2-regulatory-reporting'; then - pass "l3b regulatory keywords -> l2-regulatory-reporting" -else - fail "l3b regulatory: got '$regulatory'" -fi -invoice=$(python3 scripts/l3b_agent_stub.py "monthly invoice and billing reconciliation" 2>&1) -if echo "$invoice" | grep -q 'stack: l2-invoice-service'; then - pass "l3b invoice keywords -> l2-invoice-service" -else - fail "l3b invoice: got '$invoice'" -fi -analytics=$(python3 scripts/l3b_agent_stub.py "historical analytics and query API" 2>&1) -if echo "$analytics" | grep -q 'stack: l2-energy-analytics-api'; then - pass "l3b analytics keywords -> l2-energy-analytics-api" -else - fail "l3b analytics: got '$analytics'" -fi - -echo -echo "== Summary ==" -if [ "$fail_count" -eq 0 ]; then - echo "Phase 03 verification PASSED (4 L2s + 5 core scripts, all checks ok)" - exit 0 -else - echo "Phase 03 verification FAILED (${fail_count} check(s) failed)" - exit 1 -fi \ No newline at end of file diff --git a/demo/scripts/verify_phase04.sh b/demo/scripts/verify_phase04.sh deleted file mode 100755 index be98497..0000000 --- a/demo/scripts/verify_phase04.sh +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env bash -# Phase 04 verification script. -# Confirms the pipeline workflow + issue trigger + finalize_evidence.py -# conform to the Phase 04 plan and the Gitea Actions topology in -# ARCHITECTURE.md. Does NOT execute a real Gitea Actions run (act_runner -# is not registered in this environment); validates structure + syntax -# + a dry-run of finalize_evidence.py against a dead host. -# -# Usage: scripts/verify_phase04.sh -# Exit codes: 0 = all checks passed; 1 = one or more checks failed. - -set -uo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$ROOT" - -fail_count=0 -pass() { printf ' [PASS] %s\n' "$1"; } -fail() { printf ' [FAIL] %s\n' "$1"; fail_count=$((fail_count + 1)); } - -echo "== Phase 04 verification ==" -echo "Root: ${ROOT}" -echo - -# --- Check 1: typecheck --- -echo "-- Check 1: typecheck --" -if bash -n scripts/finalize_evidence.py 2>/dev/null || python3 -m py_compile scripts/finalize_evidence.py 2>/dev/null; then - pass "py_compile finalize_evidence.py" -else - fail "py_compile finalize_evidence.py" -fi -if python3 -c "import yaml; yaml.safe_load(open('.gitea/workflows/pipeline.yml')); yaml.safe_load(open('contracts-repo/.gitea/workflows/issue-to-contract.yml'))" 2>/dev/null; then - pass "yaml load both workflows" -else - fail "yaml load workflows" -fi - -# --- Check 2: pipeline.yml structure --- -echo "-- Check 2: pipeline.yml structure (D-027, D-028) --" -p_struct=$(python3 << 'PYEOF' -import yaml, sys -try: - d = yaml.safe_load(open('.gitea/workflows/pipeline.yml')) - on = d.get('on', d.get(True)) or {} - assert 'workflow_dispatch' in on, 'no workflow_dispatch trigger' - inputs = on['workflow_dispatch']['inputs'] - assert set(inputs.keys()) == {'contract-ref', 'approve_qa', 'approve_prod'}, f'inputs: {set(inputs.keys())}' - assert inputs['contract-ref']['type'] == 'string', 'contract-ref type' - assert inputs['approve_qa']['type'] == 'boolean', 'approve_qa type' - assert inputs['approve_prod']['type'] == 'boolean', 'approve_prod type' - jobs = d['jobs'] - assert set(jobs.keys()) == {'dev', 'qa-gate', 'prod-gate', 'finalize'}, f'jobs: {set(jobs.keys())}' - dev_if = jobs['dev'].get('if', '') - assert 'approve_qa' in dev_if and 'approve_prod' in dev_if, f'dev.if: {dev_if}' - qa_if = jobs['qa-gate'].get('if', '') - assert 'approve_qa' in qa_if, f'qa-gate.if: {qa_if}' - prod_if = jobs['prod-gate'].get('if', '') - assert 'approve_prod' in prod_if, f'prod-gate.if: {prod_if}' - fin_needs = jobs['finalize'].get('needs', []) - assert fin_needs == ['prod-gate'] or fin_needs == 'prod-gate', f'finalize.needs: {fin_needs}' - # All jobs runs-on ubuntu-latest - for name, job in jobs.items(): - assert job.get('runs-on') == 'ubuntu-latest', f'{name} runs-on: {job.get("runs-on")}' - print('OK') -except AssertionError as ex: - print(f'FAIL: {ex}') - sys.exit(1) -except Exception as ex: - print(f'FAIL: {ex}') - sys.exit(1) -PYEOF -) -if [ "$p_struct" = "OK" ]; then - pass "pipeline.yml: 3 inputs + 4 jobs + correct if: conditions + finalize.needs=prod-gate" -else - fail "pipeline.yml structure: $p_struct" -fi - -# --- Check 3: pipeline.yml references core scripts --- -echo "-- Check 3: pipeline.yml references core scripts (D-029) --" -text=$(cat .gitea/workflows/pipeline.yml) -missing="" -for ref in policy_checker.py confidence_signal.py mock_executor.sh evidence_writer.py finalize_evidence.py; do - if ! echo "$text" | grep -qF "$ref"; then - missing="$missing $ref" - fi -done -if [ -z "$missing" ]; then - pass "pipeline.yml references all 5 core scripts" -else - fail "pipeline.yml missing references:$missing" -fi -# Branch-pin documentation -if echo "$text" | grep -q 'milestone/v1.0-initial'; then - pass "pipeline.yml documents branch-pin to milestone/v1.0-initial" -else - fail "pipeline.yml missing branch-pin reference" -fi - -# --- Check 4: issue-to-contract.yml structure --- -echo "-- Check 4: issue-to-contract.yml structure (D-030) --" -i_struct=$(python3 << 'PYEOF' -import yaml, sys -try: - d = yaml.safe_load(open('contracts-repo/.gitea/workflows/issue-to-contract.yml')) - on = d.get('on', d.get(True)) or {} - assert 'issues' in on, 'no issues trigger' - assert on['issues']['types'] == ['opened'], f'types: {on["issues"]["types"]}' - assert 'parse-and-trigger' in d['jobs'], 'no parse-and-trigger job' - assert d['jobs']['parse-and-trigger'].get('runs-on') == 'ubuntu-latest', 'runs-on' - print('OK') -except AssertionError as ex: - print(f'FAIL: {ex}') - sys.exit(1) -PYEOF -) -if [ "$i_struct" = "OK" ]; then - pass "issue-to-contract.yml: issues[opened] + parse-and-trigger job" -else - fail "issue-to-contract.yml structure: $i_struct" -fi - -# --- Check 5: issue-to-contract.yml references + dispatch endpoint --- -echo "-- Check 5: issue-to-contract.yml references + dispatch (D-014, D-030) --" -text=$(cat contracts-repo/.gitea/workflows/issue-to-contract.yml) -missing="" -for ref in l3b_agent_stub.py 'actions/workflows/pipeline.yml/dispatches' 'contract-ref' 'gitea.event.issue.number' 'GITEA_TOKEN' 'new_branch'; do - if ! echo "$text" | grep -qF "$ref"; then - missing="$missing $ref" - fi -done -if [ -z "$missing" ]; then - pass "issue-to-contract.yml: l3b_agent_stub + dispatch + contract-ref + issue number + token + new_branch" -else - fail "issue-to-contract.yml missing references:$missing" -fi - -# --- Check 6: finalize_evidence.py --help + clean failure --- -echo "-- Check 6: finalize_evidence.py CLI + clean failure modes ---" -out=$(python3 scripts/finalize_evidence.py --help 2>&1); rc=$? -if [ "$rc" = "0" ] && echo "$out" | grep -qi 'usage\|--audit\|--owner'; then - pass "finalize_evidence.py --help exits 0 with usage" -else - fail "finalize_evidence.py --help: rc=$rc" -fi - -# Missing audit file (with a fake token so it gets past the env check) → exit 1, no stack trace -out=$(ACDL_GITEA_TOKEN=fake python3 scripts/finalize_evidence.py --audit /tmp/definitely_nonexistent_audit.json 2>&1); rc=$? -if [ "$rc" = "1" ] && ! echo "$out" | grep -q 'Traceback'; then - pass "finalize_evidence.py missing file → exit 1, no stack trace" -else - fail "finalize_evidence.py missing file: rc=$rc, out='$out'" -fi - -# Missing token env (audit file present) → exit 1, no stack trace -printf '[]\n' > /tmp/empty_audit.json -out=$(env -u ACDL_GITEA_TOKEN python3 scripts/finalize_evidence.py --audit /tmp/empty_audit.json 2>&1); rc=$? -if [ "$rc" = "1" ] && ! echo "$out" | grep -q 'Traceback'; then - pass "finalize_evidence.py missing token env → exit 1, no stack trace" -else - fail "finalize_evidence.py missing token: rc=$rc, out='$out'" -fi - -# --- Check 7: finalize_evidence.py dry-run against a dead host (clean failure) --- -echo "-- Check 7: finalize_evidence.py dry-run against dead host ---" -# Use a real audit.json but point at a host that will refuse the connection. -printf '[{"seq":0,"ts":"2026-07-21T00:00:00Z","stage":"genesis","event":"init","prev_hash":"GENESIS","hash":"x"}]\n' > /tmp/real_audit.json -out=$(ACDL_GITEA_TOKEN=fake GITEA_HOST=http://127.0.0.1:0 python3 scripts/finalize_evidence.py --audit /tmp/real_audit.json --host http://127.0.0.1:0 2>&1); rc=$? -if [ "$rc" = "1" ] && ! echo "$out" | grep -q 'Traceback'; then - pass "finalize_evidence.py dead host → exit 1, no stack trace (clean API failure)" -else - fail "finalize_evidence.py dead host: rc=$rc, out='$out'" -fi - -# Cleanup -rm -f /tmp/empty_audit.json /tmp/real_audit.json - -echo -echo "== Summary ==" -if [ "$fail_count" -eq 0 ]; then - echo "Phase 04 verification PASSED (pipeline + issue trigger + finalize helper, all checks ok)" - exit 0 -else - echo "Phase 04 verification FAILED (${fail_count} check(s) failed)" - exit 1 -fi \ No newline at end of file diff --git a/demo/scripts/verify_phase05.sh b/demo/scripts/verify_phase05.sh deleted file mode 100755 index 97d28c7..0000000 --- a/demo/scripts/verify_phase05.sh +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env bash -# Phase 05 verification script. -# Validates the evidence UI + the 4-act demo dry-run. -# -# Usage: scripts/verify_phase05.sh -# Exit codes: 0 = all checks passed; 1 = one or more checks failed. - -set -uo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$ROOT" - -fail_count=0 -pass() { printf ' [PASS] %s\n' "$1"; } -fail() { printf ' [FAIL] %s\n' "$1"; fail_count=$((fail_count + 1)); } - -GITEA_HOST="${GITEA_HOST:-https://git.cloudinit.dev}" -ORG="continuous-intelligence" -EVIDENCE_REPO="acdl-evidence" - -echo "== Phase 05 verification ==" -echo "Root: ${ROOT}" -echo - -# --- Check 1: evidence-ui/index.html structure --- -echo "-- Check 1: evidence-ui/index.html structure (D-032, REQ-14) --" -UI="evidence-ui/index.html" -if [ ! -f "$UI" ]; then - fail "$UI missing" -else - pass "$UI exists" - size=$(wc -c < "$UI") - if [ "$size" -ge 1000 ] && [ "$size" -le 30000 ]; then - pass "$UI size ${size} bytes (within 1-30 KB range)" - else - fail "$UI size ${size} bytes (expected 1-30 KB)" - fi - ui_check=$(python3 << 'PYEOF' -import re, sys -content = open('evidence-ui/index.html').read() -problems = [] -if '' not in content: problems.append('missing inline