Bounded activity canaries and failure drills

Bounded activity canaries and failure drills

`scripts/observability/activity_validation.py` adds a small synthetic layer to the passive rolling evidence ledger. It is designed for timer invocation during normal operation; it does not need an idling agent or a continuous load runner. Every emitted sample is immutable `canary` or `drill` evidence, so none can satisfy organic one-hour, 24-hour, or seven-day duration.

Known-signal canary

The canary writes one metadata-only `validation.canary` event through the fixed activity ingest endpoint, then requires the exact UUIDv7 event to appear in both query and signed export. Its ledger record includes the unique identity, collector sequence, expected deadline, per-phase latency, visibility, and failure. Persistent state enforces the configured minimum interval and maximum outstanding count. The reference budget is one event per 15 minutes, at most two outstanding, with a two-minute deadline.

Dry-run resolves the event identity, exact endpoints, sequence, deadline, and budgets without sending a request or writing state:

python3 scripts/observability/activity_validation.py \
  --config configs/activity-validation.json \
  --evidence-config /etc/agentic-sandbox/operational-evidence.json \
  --output-dir /var/lib/agentic-sandbox/evidence/operational \
  canary --dry-run

Remove `--dry-run` to execute. If the management boundary requires a bearer credential, set `operator_bearer_token_file` to a root/operator-owned file with no group or world permissions. The value is read only into the request header; it is never placed in config output, state, or evidence. Prefer the existing local authenticated operator boundary where available.

Fixed drill profiles

The checked configuration contains exactly five executors:

ProfileHypothesis exercisedDefault scheduling
`fixture_collector_restart`restart and recovery evidence is continuousdaily disposable fixture
`fixture_exporter_outage`a brief outage is visible and buffered state recoverstimer-eligible, not scheduled by default
`fixture_backpressure`queue pressure stays below its cap and clearstimer-eligible, not scheduled by default
`fixture_authorization_rejection`denial is visible without protected-state changetimer-eligible, not scheduled by default
`fixture_evidence_corruption`mutation of a copied record is detectedmanual only

Each profile declares its exact target class, hypothesis, steady state, duration (15 or 30 seconds), single-fixture blast radius, abort thresholds, rollback, cleanup checks, and expected evidence. The built-in adapters mutate only in-process fixture state. There is no shell, command, argument-vector, wildcard-target, host-service, network-wide, or sandbox-lifecycle executor. Profiles longer than five minutes, missing safety declarations, manual-only schedules, and targets not explicitly disposable or validation-designated are rejected before a ledger write.

Inspect or run one profile:

python3 scripts/observability/activity_validation.py \
  --config configs/activity-validation.json \
  --evidence-config /etc/agentic-sandbox/operational-evidence.json \
  --output-dir /var/lib/agentic-sandbox/evidence/operational \
  drill --profile fixture_backpressure \
  --target local-disposable-fixture --dry-run

Execution records start, threshold/duration abort when applicable, rollback, cleanup, and terminal outcome. Rollback and cleanup are attempted even after an executor failure. Their failures are explicit terminal states rather than success. Host-wide, network-wide, production-target, or destructive experiments remain manual and outside this runner.

Timer schedule and cost bound

Invoke `schedule-once` from a systemd timer or equivalent. It reads durable last-run state, runs at most `maximum_runs_per_invocation`, and exits; a second invocation before the interval is a no-op. The reference schedule runs one 30-second-or-less local fixture per day. Together with the 15-minute canary budget, this caps generated activity at 96 metadata events/day plus a few small ledger records and avoids continuous compute allocation.

python3 scripts/observability/activity_validation.py \
  --config configs/activity-validation.json \
  --evidence-config /etc/agentic-sandbox/operational-evidence.json \
  --output-dir /var/lib/agentic-sandbox/evidence/operational \
  schedule-once

Recovery is bounded: stop further timer invocations, inspect the terminal drill records, confirm rollback and cleanup, then re-run the same profile in dry-run. If rollback or cleanup failed, keep the target out of service and recover the disposable fixture manually; never widen the target to compensate. A failed canary remains outstanding until its deadline and contributes failure evidence, not organic availability.

Tests

python3 -m unittest scripts/observability/test_activity_validation.py

The suite covers known-signal success and loss, rate/outstanding caps, unsafe profile denial, all named profiles, dry-run isolation, threshold abort, rollback/cleanup failures, timer state, and non-promotion into organic evidence.