Brings the framework CRS repo up to date with all content that was living in agent-runtimes (local-dev fallback) but hadn't been promoted. New composites: feature-delivery-loop, integration-direct, scaffolding-repo, sonnet-impl-narrow, sonnet-manager, test-writing-repo New contexts: integration/v1, scaffolding/v1, sonnet-manager/v1, z-ai/v1, airouter/v1/bin (anthropic-compat-wrapper.sh), cp-harness/v1/init.sh New task-templates: sonnet-integrator.yaml, workflow/* (17 typed workflow task templates for the Epic 1 pipeline) Updated: agent-repo/v1/finalize.sh — adds AR-38/F97 empty-deliverable audit (SKIP_BRANCH_PUSH support, boilerplate-path filtering, ci_metadata.json flag) Also adds MEMORY.md index and memory/ topic files for the framework repo.
122 lines
4.2 KiB
Bash
Executable File
122 lines
4.2 KiB
Bash
Executable File
#!/bin/bash
|
|
# cp-harness init — CPH-4/5/6 startup verification.
|
|
#
|
|
# Verifies five dispatcher-injected files exist with correct modes,
|
|
# validates cp_url (https:// prefix, no whitespace/newlines), and checks
|
|
# the not_after RFC 3339 timestamp is not expired.
|
|
#
|
|
# CPH-4: file existence + mode checks (tls.key must be 0400)
|
|
# CPH-5: cp_url must start with https://, no whitespace/newlines
|
|
# CPH-6: not_after must be a valid RFC 3339 timestamp in the future
|
|
#
|
|
# RUN_DIR: defaults to /run; tests pass a tmpdir path via env.
|
|
# PROFILE_D_DIR: defaults to /etc/profile.d; override in tests if needed.
|
|
set -euo pipefail
|
|
|
|
RUN_DIR="${RUN_DIR:-/run}"
|
|
PROFILE_D_DIR="${PROFILE_D_DIR:-/etc/profile.d}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CPH-4: verify file existence and modes
|
|
# ---------------------------------------------------------------------------
|
|
|
|
TLS_CRT="$RUN_DIR/cp-client/tls.crt"
|
|
TLS_KEY="$RUN_DIR/cp-client/tls.key"
|
|
CA_CRT="$RUN_DIR/cp-client/ca.crt"
|
|
CP_URL_FILE="$RUN_DIR/cp-harness/cp_url"
|
|
NOT_AFTER_FILE="$RUN_DIR/cp-harness/not_after"
|
|
|
|
# Check all five files exist and are readable.
|
|
for f in "$TLS_CRT" "$TLS_KEY" "$CA_CRT" "$CP_URL_FILE" "$NOT_AFTER_FILE"; do
|
|
if [ ! -r "$f" ]; then
|
|
echo "ERROR: missing required file $f" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# tls.key must be strictly 0400 (private key — defence in depth).
|
|
key_mode=$(stat -c %a "$TLS_KEY")
|
|
if [ "$key_mode" != "400" ]; then
|
|
# Attempt to tighten the mode.
|
|
if ! chmod 0400 "$TLS_KEY" 2>/tmp/cp_harness_chmod_err; then
|
|
chmod_err=$(cat /tmp/cp_harness_chmod_err 2>/dev/null || true)
|
|
echo "ERROR: tls.key mode $key_mode is broader than 0400; chmod failed: $chmod_err" >&2
|
|
exit 1
|
|
fi
|
|
# Re-check after chmod.
|
|
key_mode=$(stat -c %a "$TLS_KEY")
|
|
if [ "$key_mode" != "400" ]; then
|
|
echo "ERROR: tls.key mode $key_mode remains broader than 0400 after chmod" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CPH-5: validate cp_url
|
|
# ---------------------------------------------------------------------------
|
|
|
|
cp_url=$(cat "$CP_URL_FILE")
|
|
|
|
# Must start with https:// (case-sensitive, literal).
|
|
if [[ "$cp_url" != https://* ]]; then
|
|
echo "ERROR: cp_url does not start with https:// prefix" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Must not contain carriage return, newline, or any whitespace.
|
|
# Use explicit byte checks plus [[:space:]] guard.
|
|
if printf '%s' "$cp_url" | grep -qP '\r|\n'; then
|
|
echo "ERROR: cp_url contains invalid whitespace/newline" >&2
|
|
exit 1
|
|
fi
|
|
if [[ "$cp_url" =~ [[:space:]] ]]; then
|
|
echo "ERROR: cp_url contains invalid whitespace/newline" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Export for downstream processes. Fail silently if /etc/profile.d is unwritable
|
|
# (test environments may not have it).
|
|
mkdir -p "$PROFILE_D_DIR" 2>/dev/null || true
|
|
printf 'export CP_URL=%s\n' "$cp_url" > "$PROFILE_D_DIR/cp-url.sh" 2>/dev/null || true
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CPH-6: validate not_after RFC 3339 timestamp
|
|
# ---------------------------------------------------------------------------
|
|
|
|
not_after=$(cat "$NOT_AFTER_FILE")
|
|
|
|
# Parse and validate: exit 1 if expired or unparseable.
|
|
if ! python3 -c "
|
|
import datetime, sys
|
|
raw = sys.argv[1].strip()
|
|
try:
|
|
t = datetime.datetime.fromisoformat(raw.rstrip('Z').replace('Z', '+00:00'))
|
|
if t.tzinfo is None:
|
|
t = t.replace(tzinfo=datetime.timezone.utc)
|
|
except Exception:
|
|
sys.exit(1)
|
|
now = datetime.datetime.now(datetime.timezone.utc)
|
|
delta = (t - now).total_seconds()
|
|
sys.exit(0 if delta > 0 else 1)
|
|
" "$not_after" 2>/dev/null; then
|
|
echo "ERROR: not_after expired or unparseable" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Warn if expiry is within 300s.
|
|
warn_seconds=$(python3 -c "
|
|
import datetime, sys
|
|
raw = sys.argv[1].strip()
|
|
t = datetime.datetime.fromisoformat(raw.rstrip('Z').replace('Z', '+00:00'))
|
|
if t.tzinfo is None:
|
|
t = t.replace(tzinfo=datetime.timezone.utc)
|
|
now = datetime.datetime.now(datetime.timezone.utc)
|
|
print(int((t - now).total_seconds()))
|
|
" "$not_after" 2>/dev/null || echo "0")
|
|
|
|
if [ "$warn_seconds" -lt 300 ]; then
|
|
echo "WARNING: cert expires in ${warn_seconds}s" >&2
|
|
fi
|
|
|
|
echo "cp-harness: all checks passed"
|