feat(P00): deprecation sweep + bash tooling gate + render contract + doc banners (v0.9 P00)

P00 — Re-architecture Foundation (deprecation/migration/test-infra/persona/docs).

Deprecation sweep (REQ-068, REQ-072, REQ-089):
- Add // Deprecated: doc comments to internal/daemon (R-001), internal/transport
  (REQ-073), internal/security/ca.go+csr.go (D-101/REQ-076), internal/engine/
  dispatcher.go+peer.go (CLI-side scheduler), internal/cli/daemon.go.
- orca daemon emits slog.Warn deprecation banner on every run (ungated); fires
  R-001 + v0.10-P05 drain-and-stop + v0.10-P14 deletion.
- orca cert and orca node join (mTLS path) emit deprecation warnings; proxmox
  SSH path (the v0.9 replacement) does not warn.
- Add --no-deprecation-warnings global flag on root command (PersistentPreRunE)
  for orca upgrade migrations.
- 12 new daemon/cert/node deprecation tests in internal/cli/daemon_test.go
  (cli coverage 81.9%, warnDeprecated 100%).
- Add DEPRECATED banners to v0.8 sections of ARCHITECTURE.md (verified the
  v0.9 supersession section + Supersession Table from prior turn are present).

Bash tooling gate (grill C-06, C-15, C-16, C-17, C-18):
- scripts/tests/test_helper.bash + example_test.bash — bats framework + helpers.
- scripts/lib/orca-log.sh — slog-compatible JSON logging to syslog (C-17).
- scripts/orca-verify-render.sh — render-contract validator skeleton (C-16).
- scripts/tests/orca-log_test.bash + orca-verify-render_test.bash — 20 bats
  tests total (happy + failure paths per C-15).
- .shellcheckrc — project shellcheck config.
- Makefile: test-bash + lint-bash targets (graceful skip if tools missing);
  wired into test + lint targets.
- internal/emit/contract.go + contract_test.go — versioned JSON render
  contract (orca.emit/v1) between Go emitters and bash appliers (C-16).
- .ciagent/BASH_CAPABILITY_MAP_v0.9.md — maps shipped internal/transport
  capabilities to bash-side equivalents or accepted drops (C-18).
- D-186 recorded in PROJECT.md: bash exempt from Go coverage gate; compensating
  control is bats + shellcheck + shfmt (C-06).

verify-reqs: 90 requirements consistent. Build/test/lint/fmt all green.
20 bats tests pass. Go tests pass. No v0.8 code deleted — only marked deprecated
(deletion deferred to v0.10-P14 per REQ-090 dual-write window).

---ci---
project: orca
phase: P00
milestone: v0.9
status: execute
---/ci---
This commit is contained in:
Jon Chery
2026-08-05 16:26:26 +00:00
parent 40b5e781ce
commit fc94326b0e
26 changed files with 983 additions and 8 deletions
+32
View File
@@ -0,0 +1,32 @@
# orca-log.sh — structured slog-compatible JSON logging for bash scripts (C-17).
# Source this library from any orca bash script: `source scripts/lib/orca-log.sh`.
# Emits JSON to syslog via `logger`; falls back to stderr if `logger` is missing.
# Field set matches the Go audit log (REQ-006): ts, level, actor, action, resource, result, error.
ORCA_LOG_ACTOR="${ORCA_LOG_ACTOR:-spiffe://orca/cli/operator}"
# _orca_log_emit <level> <action> <resource> <result> [error]
_orca_log_emit() {
local level="$1" action="$2" resource="$3" result="$4" error="${5:-}"
local ts
ts="$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)"
# Build JSON with proper escaping of error field (escape backslash and quote).
local err_json=""
if [ -n "$error" ]; then
local esc_error
esc_error="${error//\\/\\\\}"
esc_error="${esc_error//\"/\\\"}"
err_json=",\"error\":\"$esc_error\""
fi
local line
line="{\"ts\":\"$ts\",\"level\":\"$level\",\"actor\":\"$ORCA_LOG_ACTOR\",\"action\":\"$action\",\"resource\":\"$resource\",\"result\":\"$result\"$err_json}"
if command -v logger >/dev/null 2>&1; then
logger -t orca "$line"
else
echo "$line" >&2
fi
}
orca_log_info() { _orca_log_emit "info" "$1" "$2" "$3" "${4:-}"; }
orca_log_warn() { _orca_log_emit "warn" "$1" "$2" "$3" "${4:-}"; }
orca_log_error() { _orca_log_emit "error" "$1" "$2" "$3" "${4:-}"; }
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# orca-verify-render.sh — bash-side render-contract validator (grill C-16).
# Reads a render-bundle JSON file (one Artifact per line, or a JSON array)
# and validates each entry against the orca.emit/v1 schema.
# Exit 0 if all valid; non-zero with a structured error per failure to stderr.
# Source: scripts/lib/orca-log.sh for structured error logging (C-17).
#
# Usage: orca-verify-render.sh <bundle.json>
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/orca-log.sh
. "$SCRIPT_DIR/lib/orca-log.sh"
EXPECTED_SCHEMA="orca.emit/v1"
if [ "$#" -lt 1 ]; then
orca_log_error "verify-render" "-" "failed" "missing bundle argument"
echo "usage: $0 <bundle.json>" >&2
exit 2
fi
bundle="$1"
if [ ! -f "$bundle" ]; then
orca_log_error "verify-render" "$bundle" "failed" "bundle file not found"
echo "error: bundle not found: $bundle" >&2
exit 2
fi
errors=0
total=0
# Read the bundle line-by-line. Each line should be a JSON object.
# (The Go emitter writes one Artifact per line for line-delimited parsing.)
while IFS= read -r line; do
# Skip blank lines and comments.
[ -z "$line" ] && continue
case "$line" in \#*) continue ;; esac
total=$((total + 1))
# Validate schema_version field presence and value (crude JSON grep; no jq dep).
# Check schema_version via a simple substring test.
schema_match=0
if printf '%s' "$line" | grep -q "\"schema_version\":\"$EXPECTED_SCHEMA\""; then
schema_match=1
fi
if [ "$schema_match" -eq 1 ]; then
# schema_version matches. Check kind, path, mode presence.
for field in kind path mode; do
if ! printf '%s' "$line" | grep -q "\"$field\":"; then
orca_log_error "verify-render" "$bundle" "failed" "missing field: $field"
echo "error: line $total missing field: $field" >&2
errors=$((errors + 1))
continue 2
fi
done
elif printf '%s' "$line" | grep -q '"schema_version":'; then
orca_log_error "verify-render" "$bundle" "failed" "schema_version mismatch on line $total"
echo "error: line $total schema_version mismatch (expected $EXPECTED_SCHEMA)" >&2
errors=$((errors + 1))
else
orca_log_error "verify-render" "$bundle" "failed" "missing schema_version on line $total"
echo "error: line $total missing schema_version" >&2
errors=$((errors + 1))
fi
done < "$bundle"
if [ "$errors" -gt 0 ]; then
orca_log_error "verify-render" "$bundle" "failed" "$errors of $total artifacts invalid"
echo "verify-render: $errors of $total artifacts invalid" >&2
exit 1
fi
orca_log_info "verify-render" "$bundle" "ok" ""
echo "verify-render: $total artifacts valid"
exit 0
+39
View File
@@ -0,0 +1,39 @@
# Bash Testing Policy (grill C-15)
Every bash script under `scripts/` MUST have at least one bats test covering
the happy path and one covering the failure path. This is the compensating
control for bash being exempt from the Go coverage gate (D-186).
## Framework
- **bats** — `bats scripts/tests/*.bash` runs all bash tests.
- **shellcheck** — `shellcheck scripts/*.sh scripts/lib/*.sh scripts/tests/*.bash` static analysis.
- **shfmt** — `shfmt -d scripts/` formatting check (optional; skip if not installed).
## Install (if missing)
```bash
# bats
npm install -g bats # or: git clone https://github.com/bats-core/bats-core.git && ./bats-core/install.sh /usr/local
# shellcheck
apt-get install -y shellcheck
# shfmt (optional)
mvdan.cc/sh (go install mvdan.cc/sh/v3/cmd/shfmt@latest)
```
## Running
```bash
make test-bash # runs bats (skips gracefully if bats missing)
make lint-bash # runs shellcheck + shfmt (skips gracefully if missing)
make test # runs both Go + bash tests
make lint # runs both Go + bash lint
```
## Test file convention
- Test files live in `scripts/tests/<script-name>_test.bash`.
- Source `load test_helper` at the top of every test file.
- Happy path: `@test "<script> happy path" { ... }`
- Failure path: `@test "<script> failure path" { ... }`
- Use `run <command>` + `assert_status`/`assert_contains`/`assert_not_contains` from test_helper.
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bats
# Example bats test proving the framework works (C-15 smoke test).
# Real script tests live alongside each script under scripts/tests/.
load test_helper
@test "test_helper assert_status accepts matching status" {
assert_status 0 0
assert_status 1 1
}
@test "test_helper assert_status rejects mismatch" {
run assert_status 0 1
[ "$status" -ne 0 ]
}
@test "test_helper assert_contains finds substrings" {
assert_contains "hello world" "world"
}
@test "test_helper assert_contains rejects missing substrings" {
run assert_contains "hello world" "missing"
[ "$status" -ne 0 ]
}
@test "test_helper assert_not_contains passes when substring absent" {
assert_not_contains "hello world" "missing"
}
@test "test_helper assert_not_contains fails when substring present" {
run assert_not_contains "hello world" "world"
[ "$status" -ne 0 ]
}
@test "test_helper assert_json_field detects JSON fields" {
assert_json_field '{"ts":"2026-01-01T00:00:00Z","level":"info"}' "ts"
assert_json_field '{"ts":"2026-01-01T00:00:00Z","level":"info"}' "level"
}
@test "test_helper SCRIPTS_DIR resolves to scripts/ directory" {
[ -d "$SCRIPTS_DIR" ]
[ -f "$SCRIPTS_DIR/install.sh" ]
}
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env bats
# Tests for scripts/lib/orca-log.sh (C-17 — slog-compatible JSON to syslog).
# Verifies the JSON structure is valid, field set is present, level maps correctly,
# and the logger-fallback-to-stderr path works in test environments (no logger).
load test_helper
@test "orca_log_info emits valid JSON with all required fields" {
export ORCA_LOG_ACTOR="test-actor"
output="$(_orca_log_for_test info test-action test-resource ok "")"
assert_json_field "$output" "ts"
assert_json_field "$output" "level"
assert_json_field "$output" "actor"
assert_json_field "$output" "action"
assert_json_field "$output" "resource"
assert_json_field "$output" "result"
assert_contains "$output" '"level":"info"'
assert_contains "$output" '"action":"test-action"'
assert_contains "$output" '"resource":"test-resource"'
assert_contains "$output" '"result":"ok"'
}
@test "orca_log_warn maps level correctly" {
output="$(_orca_log_for_test warn w-action w-resource warn-result)"
assert_contains "$output" '"level":"warn"'
}
@test "orca_log_error maps level and includes error field when provided" {
output="$(_orca_log_for_test error err-action err-resource failed "something broke")"
assert_contains "$output" '"level":"error"'
assert_contains "$output" '"result":"failed"'
assert_contains "$output" '"error":"something broke"'
}
@test "orca_log_error omits error field when not provided" {
output="$(_orca_log_for_test error err-action err-resource failed)"
assert_contains "$output" '"level":"error"'
assert_not_contains "$output" '"error":'
}
@test "orca_log escapes quotes and backslashes in error field" {
output="$(_orca_log_for_test error a r failed 'has "quote" and \backslash')"
assert_contains "$output" '\"quote\"'
assert_contains "$output" '\\backslash'
}
@test "ORCA_LOG_ACTOR env var overrides the actor field" {
export ORCA_LOG_ACTOR="custom-actor-123"
output="$(_orca_log_for_test info a r ok)"
assert_contains "$output" '"actor":"custom-actor-123"'
}
# Test helper: source orca-log.sh and emit to stderr (force fallback by hiding logger).
_orca_log_for_test() {
local level="$1" action="$2" resource="$3" result="$4" error="${5:-}"
# Source the library in a subshell with logger hidden so it falls back to stderr.
(
PATH="/usr/bin:/bin" # hide logger if it's in /usr/local/bin etc.
# shellcheck disable=SC2317 # logger is overridden below for test capture
logger() { echo "$3"; } # $3 is the message arg (logger -t orca "$line")
# shellcheck disable=SC1091 # path is set at runtime by SCRIPTS_DIR
source "$SCRIPTS_DIR/lib/orca-log.sh"
case "$level" in
info) orca_log_info "$action" "$resource" "$result" "$error" ;;
warn) orca_log_warn "$action" "$resource" "$result" "$error" ;;
error) orca_log_error "$action" "$resource" "$result" "$error" ;;
esac
)
}
@@ -0,0 +1,69 @@
#!/usr/bin/env bats
# Tests for scripts/orca-verify-render.sh (C-16 render-format contract validator).
# Covers happy path (valid input returns 0) and failure paths (schema mismatch,
# missing fields, missing file, missing argument).
load test_helper
VERIFY_RENDER="$SCRIPTS_DIR/orca-verify-render.sh"
TMP_BUNDLE=""
setup() {
TMP_BUNDLE="$(mktemp)"
}
teardown() {
[ -n "$TMP_BUNDLE" ] && rm -f "$TMP_BUNDLE"
}
@test "verify-render happy path: valid artifacts return 0" {
cat >"$TMP_BUNDLE" <<'EOF'
{"schema_version":"orca.emit/v1","kind":"systemd","path":"/etc/systemd/system/x.service","content":"[Service]","mode":"0644"}
{"schema_version":"orca.emit/v1","kind":"traefik","path":"/etc/traefik/dynamic/orca.yml","content":"tls:{}","mode":"0644"}
EOF
run "$VERIFY_RENDER" "$TMP_BUNDLE"
assert_status 0 "$status"
assert_contains "$output" "2 artifacts valid"
}
@test "verify-render failure: schema_version mismatch returns non-zero" {
cat >"$TMP_BUNDLE" <<'EOF'
{"schema_version":"orca.emit/v2","kind":"systemd","path":"/x","mode":"0644"}
EOF
run "$VERIFY_RENDER" "$TMP_BUNDLE"
[ "$status" -ne 0 ]
assert_contains "$output" "schema_version mismatch"
}
@test "verify-render failure: missing schema_version returns non-zero" {
cat >"$TMP_BUNDLE" <<'EOF'
{"kind":"systemd","path":"/x","mode":"0644"}
EOF
run "$VERIFY_RENDER" "$TMP_BUNDLE"
[ "$status" -ne 0 ]
assert_contains "$output" "missing schema_version"
}
@test "verify-render failure: missing bundle argument returns 2" {
run "$VERIFY_RENDER"
assert_status 2 "$status"
assert_contains "$output" "usage:"
}
@test "verify-render failure: non-existent bundle returns 2" {
run "$VERIFY_RENDER" "/nonexistent/bundle.json"
assert_status 2 "$status"
assert_contains "$output" "bundle not found"
}
@test "verify-render skips blank lines and comments" {
cat >"$TMP_BUNDLE" <<'EOF'
# this is a comment
{"schema_version":"orca.emit/v1","kind":"systemd","path":"/x","content":"c","mode":"0644"}
EOF
run "$VERIFY_RENDER" "$TMP_BUNDLE"
assert_status 0 "$status"
assert_contains "$output" "1 artifacts valid"
}
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# Common helpers for orca bats tests (C-15). Sourced by every test file.
# See scripts/tests/README.md for the bash testing policy.
# Resolve the scripts/ dir relative to this test file.
# BASH_SOURCE[0] is this helper file (scripts/tests/test_helper.bash).
SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
export SCRIPTS_DIR
# assert_status <expected> <actual> — assert a command's exit status.
assert_status() {
local expected="$1" actual="$2"
[ "$expected" = "$actual" ] || {
echo "expected status $expected, got $actual" >&2
return 1
}
}
# assert_contains <haystack> <needle> — substring assertion.
assert_contains() {
local haystack="$1" needle="$2"
case "$haystack" in
*"$needle"*) return 0 ;;
*) echo "expected [$haystack] to contain [$needle]" >&2; return 1 ;;
esac
}
# assert_not_contains <haystack> <needle> — negative substring assertion.
assert_not_contains() {
local haystack="$1" needle="$2"
case "$haystack" in
*"$needle"*) echo "expected [$haystack] to NOT contain [$needle]" >&2; return 1 ;;
esac
:
}
# assert_json_field <json> <field> — crude JSON field presence check (no jq dep).
# Matches "<field>": present anywhere in the JSON string.
assert_json_field() {
local json="$1" field="$2"
assert_contains "$json" "\"$field\""
}