local_port means different things per direction, and conflating them is easy:
a `direction: local` tunnel binds it here (-L local_port:remote_host:remote_port),
while a reverse tunnel — the default — uses it as the *destination* on the
workstation (-R remote_port:remote_host:local_port) and binds on the remote.
Two local forwards on one port do not fail loudly: whichever binds first wins
and the losers reconnect forever, so the map reads healthy while the port
answers from a different machine than the one asked for. Reject that at config
load. Reverse tunnels stay exempt by design — every state-hub-* reverse tunnel
targets 8000 on purpose, so each remote box reaches this hub at its own 18000.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Surfaces the actor naming rules (adm-/agt-/atm- prefixes, legacy class
aliases) so users hitting a ConfigError have an in-CLI way to read the
spec without grepping the wiki.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ActorType enum (adm/agt/atm) replaces actor_class string; config validates
naming convention (adm-*/agt-*/atm-*) with hard ConfigError on mismatch;
legacy 'human'/'automation' values accepted with DeprecationWarning
- cert_command: pluggable shell string run before each SSH launch; cert written
to state dir; -i cert appended to SSH command alongside -i key
- TTL-aware cert refresh: parses Valid-to via ssh-keygen -L; pre-emptive restart
5 min before expiry (no backoff, no attempt increment); CERT_EXPIRING logged
- CertAcquisitionError: cert failures trigger normal backoff/retry loop
- cert_identity: Key ID parsed from cert and recorded in BRIDGE_CONNECTED event
- bridge cert-status: new CLI command; exit 1 on expired cert; --json flag
- 233 tests passing, ruff clean
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously build_ssh_command only generated -R (reverse) tunnels.
The k3s API tunnel needs -L (local forward: workstation:16443 →
CoulombCore:6443) so kubectl can reach the cluster API directly.
- TunnelConfig.direction: "reverse" (default) | "local"
- config.py: parse direction from YAML, validate allowed values
- manager.py: choose -R or -L flag based on direction
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the OpsCatalog subsystem: a Git-backed YAML catalog of operations
domains, targets, bridges, and actor classes. Includes catalog loader,
cross-reference validator, bridge resolver (inline-first, catalog
fallback), and new CLI commands: `bridge targets`, `bridge targets show`,
`bridge catalog list/validate/show`. Updates `up/down/restart` to resolve
bridge names from the catalog when not defined inline. 142 tests, all green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Full TDD implementation of the `bridge` CLI tool covering all phases
from BRIDGE-WP-0001: project scaffolding, config loading, state
management, audit logging, health checks, tunnel lifecycle manager, and
all CLI commands (up/down/restart/status/logs). 77 tests, all green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>