phase: 6, status: plan-as-execute, persona: lead-developer, task: T-6.1..T-6.4
---ci--- project: acdl phase: 6 milestone: v1.1 status: plan-as-execute persona: lead-developer tasks: [T-6.1, T-6.2, T-6.3, T-6.4] ---/ci--- Archive the v1.0 demo under demo/ (D-037) and reorient the repo to the real platform. Wave 1 of the Phase 06 plan. - T-6.1: git mv modules/, scripts/, evidence-ui/, contracts/, contracts-repo/, .gitea/ -> demo/; mv ACDL_DEMO.md + runner-data/ -> demo/ - T-6.2: scaffold new v1.1 top-level dirs (platform/, schemas/, adapters/, terraform/, modules-ir/) with .gitkeep - T-6.3: create top-level scripts/verify_phase06.sh (v1.1 verify scripts live at top-level, NOT demo/scripts/ which holds the v1.0 demo verify scripts) - T-6.4: rewrite README.md to reflect the real platform (vision + architecture links, new layout, status v1.1 active); add runner-data/ to .gitignore All moves via git mv (history preserved). Repo root now contains only README.md, demo/, docs/, .ciagent/, and the new empty v1.1 dirs.
This commit is contained in:
@@ -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": "<violation code>"}
|
||||
|
||||
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 <contract.yaml>", 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())
|
||||
@@ -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": <iso8601 UTC>, "stage": "...", "event": "...",
|
||||
"prev_hash": "<sha256 or GENESIS>", "hash": "<sha256 of canonical json of this event with hash empty>"}
|
||||
|
||||
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 <dev|qa|prod|finalize|genesis> (required)
|
||||
--event "<text>" (required)
|
||||
--audit <path> (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())
|
||||
@@ -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 <token>`.
|
||||
|
||||
Input (argv flags):
|
||||
--audit <path> (required) local audit.json file to upload
|
||||
--owner <org> (optional, default continuous-intelligence)
|
||||
--repo <name> (optional, default acdl-evidence)
|
||||
--branch <name> (optional, default main)
|
||||
--path <remote path> (optional, default audit.json) path in the repo
|
||||
--token-env <env var> (optional, default ACDL_GITEA_TOKEN)
|
||||
--host <url> (optional, default https://git.cloudinit.dev)
|
||||
--message <commit msg> (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/<path>?ref=<branch> 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": "<path>", "sha": "<new sha>"} to
|
||||
stdout and exit 0.
|
||||
6. On any HTTP error: print
|
||||
{"uploaded": false, "status": <code>, "body": "<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())
|
||||
@@ -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=<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='<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>ACDL Evidence</title>
|
||||
<style>body{font-family:system-ui,sans-serif;margin:2rem;color:#333}</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>ACDL Evidence Stream</h1>
|
||||
<p>Evidence timeline will appear here in Phase 05.</p>
|
||||
<p>Placeholder served via Gitea raw file URL (D-012; Gitea has no native Pages).</p>
|
||||
</body>
|
||||
</html>'
|
||||
|
||||
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: <set, ${#TOKEN} chars>"
|
||||
|
||||
# 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
|
||||
@@ -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: <mapped L2 name>
|
||||
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 <path> = 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 <path> 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())
|
||||
@@ -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 <contract.yaml>" >&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"
|
||||
@@ -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 <contract.yaml>", 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())
|
||||
@@ -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: <stage> <event-text>
|
||||
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: <act-label> <dev-applied-event-text>
|
||||
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/<stack>/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
|
||||
@@ -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=<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: <set, ${#TOKEN} chars>"
|
||||
else
|
||||
echo "Token: <unset>"
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 '<style>' not in content or '</style>' not in content: problems.append('missing inline <style>')
|
||||
if '<script>' not in content or '</script>' not in content: problems.append('missing inline <script>')
|
||||
if 'fetch(' not in content: problems.append('missing fetch call')
|
||||
if "'./audit.json'" not in content and '"./audit.json"' not in content: problems.append('missing relative ./audit.json fetch')
|
||||
external = re.findall(r'(?:src|href)\s*=\s*["\']https?://', content)
|
||||
if external: problems.append(f'external resource refs: {external}')
|
||||
# Confirm a refresh button or refresh function exists
|
||||
if 'refresh' not in content.lower(): problems.append('no refresh button/function')
|
||||
print('OK' if not problems else 'FAIL: ' + '; '.join(problems))
|
||||
PYEOF
|
||||
)
|
||||
if [ "$ui_check" = "OK" ]; then
|
||||
pass "$UI structural checks (inline CSS/JS, fetch ./audit.json, no external refs, refresh)"
|
||||
else
|
||||
fail "$UI structural: $ui_check"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Check 2: run_demo.sh syntax ---
|
||||
echo "-- Check 2: run_demo.sh syntax + flags --"
|
||||
if bash -n scripts/run_demo.sh 2>/dev/null; then
|
||||
pass "run_demo.sh bash -n clean"
|
||||
else
|
||||
fail "run_demo.sh bash -n"
|
||||
fi
|
||||
if grep -q -- '--no-upload' scripts/run_demo.sh; then
|
||||
pass "run_demo.sh supports --no-upload flag"
|
||||
else
|
||||
fail "run_demo.sh missing --no-upload flag"
|
||||
fi
|
||||
|
||||
# --- Check 3: run_demo.sh dry-run (no upload) ---
|
||||
echo "-- Check 3: run_demo.sh --no-upload (4 acts, 11 events) --"
|
||||
rm -rf /tmp/acdl_demo_run
|
||||
out=$(ACDL_GITEA_TOKEN= bash scripts/run_demo.sh --no-upload 2>&1); rc=$?
|
||||
if [ "$rc" = "0" ]; then
|
||||
pass "run_demo.sh --no-upload exits 0"
|
||||
else
|
||||
fail "run_demo.sh --no-upload exit $rc"
|
||||
echo "$out" | tail -10
|
||||
fi
|
||||
audit="/tmp/acdl_demo_run/audit.json"
|
||||
if [ -f "$audit" ]; then
|
||||
pass "audit.json written to $audit"
|
||||
else
|
||||
fail "audit.json missing at $audit"
|
||||
fi
|
||||
|
||||
# --- Check 4: audit.json event count + Act 4 rejection ---
|
||||
echo "-- Check 4: audit.json event count + Act 4 rejection ---"
|
||||
if [ -f "$audit" ]; then
|
||||
audit_check=$(python3 << PYEOF
|
||||
import json, sys
|
||||
try:
|
||||
events = json.load(open("$audit"))
|
||||
n = len(events)
|
||||
if n < 11:
|
||||
print(f"FAIL: too few events ({n}, expected >= 11)")
|
||||
sys.exit(1)
|
||||
if not any('POLICY_VIOLATION:PUBLIC_INGRESS' in x.get('event', '') for x in events):
|
||||
print("FAIL: no Act 4 rejection event")
|
||||
sys.exit(1)
|
||||
if not any('Act 1 Friction' in x.get('event', '') for x in events):
|
||||
print("FAIL: no Act 1 event")
|
||||
sys.exit(1)
|
||||
if not any('Act 3' in x.get('event', '') for x in events):
|
||||
print("FAIL: no Act 3 event")
|
||||
sys.exit(1)
|
||||
if not any('l2-commodity-price-feed' in x.get('event', '') for x in events):
|
||||
print("FAIL: no l2-commodity-price-feed event")
|
||||
sys.exit(1)
|
||||
print(f"OK ({n} events; Act 1/2/3/4 + Act 4 rejection present)")
|
||||
except Exception as ex:
|
||||
print(f"FAIL: {ex}")
|
||||
sys.exit(1)
|
||||
PYEOF
|
||||
)
|
||||
if echo "$audit_check" | grep -q "^OK"; then
|
||||
pass "$audit_check"
|
||||
else
|
||||
fail "audit content: $audit_check"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Check 5: audit.json hash chain integrity ---
|
||||
echo "-- Check 5: audit.json hash chain (D-023) ---"
|
||||
if [ -f "$audit" ]; then
|
||||
chain_check=$(python3 << PYEOF
|
||||
import json, hashlib, sys
|
||||
try:
|
||||
events = json.load(open("$audit"))
|
||||
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_check" = "OK" ]; then
|
||||
pass "audit.json hash chain valid (GENESIS + chain links + SHA-256 recompute)"
|
||||
else
|
||||
fail "audit.json hash chain: $chain_check"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Check 6: no stray files in repo root ---
|
||||
echo "-- Check 6: no stray files in repo root ---"
|
||||
if [ -f "$ROOT/state.json" ]; then
|
||||
fail "state.json left in repo root"
|
||||
else
|
||||
pass "no state.json in repo root"
|
||||
fi
|
||||
if [ -d "$ROOT/contracts" ]; then
|
||||
fail "contracts/ directory left in repo root"
|
||||
else
|
||||
pass "no contracts/ directory in repo root"
|
||||
fi
|
||||
|
||||
# --- Check 7: real upload + raw URL fetch (if token available) ---
|
||||
echo "-- Check 7: real upload + raw URL fetch (REQ-13) ---"
|
||||
TOKEN="${ACDL_GITEA_TOKEN:-}"
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo " [SKIP] No ACDL_GITEA_TOKEN set; skipping real upload + raw URL fetch (Phase 05 dry-run is sufficient)"
|
||||
else
|
||||
echo " Running run_demo.sh (with upload)..."
|
||||
upload_out=$(bash scripts/run_demo.sh 2>&1); upload_rc=$?
|
||||
if [ "$upload_rc" = "0" ]; then
|
||||
pass "run_demo.sh (with upload) exits 0"
|
||||
else
|
||||
fail "run_demo.sh (with upload) exit $upload_rc"
|
||||
echo "$upload_out" | tail -5
|
||||
fi
|
||||
# Raw URL fetches
|
||||
audit_url="${GITEA_HOST}/${ORG}/${EVIDENCE_REPO}/raw/branch/main/audit.json"
|
||||
index_url="${GITEA_HOST}/${ORG}/${EVIDENCE_REPO}/raw/branch/main/index.html"
|
||||
audit_status=$(curl -sS -o /tmp/p05_audit_remote.json -w "%{http_code}" "$audit_url")
|
||||
if [ "$audit_status" = "200" ]; then
|
||||
remote_count=$(python3 -c "import json; print(len(json.load(open('/tmp/p05_audit_remote.json'))))" 2>/dev/null || echo "?")
|
||||
if [ "$remote_count" = "11" ] || [ "$remote_count" -ge 11 ] 2>/dev/null; then
|
||||
pass "raw audit.json returns 200 with ${remote_count} events"
|
||||
else
|
||||
pass "raw audit.json returns 200 (events: ${remote_count})"
|
||||
fi
|
||||
else
|
||||
fail "raw audit.json GET returned HTTP ${audit_status}"
|
||||
fi
|
||||
index_status=$(curl -sS -o /tmp/p05_index_remote.html -w "%{http_code}" "$index_url")
|
||||
if [ "$index_status" = "200" ]; then
|
||||
if grep -q "ACDL Evidence" /tmp/p05_index_remote.html && grep -q "audit.json" /tmp/p05_index_remote.html; then
|
||||
pass "raw index.html returns 200 with ACDL Evidence + audit.json reference"
|
||||
else
|
||||
fail "raw index.html returns 200 but missing ACDL Evidence / audit.json markers"
|
||||
fi
|
||||
else
|
||||
fail "raw index.html GET returned HTTP ${index_status}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "== Summary =="
|
||||
if [ "$fail_count" -eq 0 ]; then
|
||||
echo "Phase 05 verification PASSED (UI + 4-act dry-run, all checks ok)"
|
||||
exit 0
|
||||
else
|
||||
echo "Phase 05 verification FAILED (${fail_count} check(s) failed)"
|
||||
exit 1
|
||||
fi
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/verify_phase06.sh - Phase 06 archive regression + layout check.
|
||||
# Lives at TOP-LEVEL scripts/ (v1.1 verify scripts), NOT demo/scripts/.
|
||||
set -u
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
ok() { echo "ok: $*"; }
|
||||
|
||||
# --- Check 1: demo/ contains the full v1.0 demo tree ---
|
||||
for d in demo/modules demo/scripts demo/evidence-ui demo/contracts \
|
||||
demo/contracts-repo demo/.gitea/workflows; do
|
||||
[ -d "$d" ] || fail "missing $d"
|
||||
done
|
||||
[ -f demo/ACDL_DEMO.md ] || fail "missing demo/ACDL_DEMO.md"
|
||||
[ -f demo/scripts/run_demo.sh ] || fail "missing demo/scripts/run_demo.sh"
|
||||
ok "demo/ contains the full v1.0 demo"
|
||||
|
||||
# --- Check 2: regression - the archived demo still runs from demo/ ---
|
||||
out=$(ACDL_GITEA_TOKEN= bash demo/scripts/run_demo.sh --no-upload 2>&1); rc=$?
|
||||
[ "$rc" -eq 0 ] || { echo "$out" >&2; fail "demo/scripts/run_demo.sh --no-upload exited $rc"; }
|
||||
ok "demo/scripts/run_demo.sh --no-upload exits 0"
|
||||
|
||||
# --- Check 3: new top-level dirs exist and are scaffolded ---
|
||||
for d in platform schemas adapters terraform modules-ir; do
|
||||
[ -d "$d" ] || fail "missing new top-level dir $d"
|
||||
[ -f "$d/.gitkeep" ] || fail "missing $d/.gitkeep"
|
||||
done
|
||||
ok "new top-level dirs exist: platform/ schemas/ adapters/ terraform/ modules-ir/"
|
||||
|
||||
# --- Check 4: no stray v1.0 dirs left at repo root ---
|
||||
for stray in modules evidence-ui contracts contracts-repo ACDL_DEMO.md; do
|
||||
[ -e "$stray" ] && fail "stray $stray left at repo root (should be under demo/)"
|
||||
done
|
||||
[ -e ".gitea" ] && fail "stray .gitea/ left at repo root (moved to demo/.gitea/)"
|
||||
ok "no stray v1.0 dirs at repo root"
|
||||
|
||||
# --- Check 5: README reflects the real platform ---
|
||||
grep -q "Agentic Cloud Delivery Platform" README.md || fail "README missing platform name"
|
||||
grep -q "demo/" README.md || fail "README does not reference the archived demo/"
|
||||
grep -qi "vision\|architecture" README.md || fail "README missing vision/architecture links"
|
||||
ok "README reflects the real platform (name + demo/ ref + vision/arch links)"
|
||||
|
||||
echo "Phase 06: ALL CHECKS PASS"
|
||||
Reference in New Issue
Block a user