Files
mdella 1c42e913a8 feat: monky-deployd v0.1.0 — pull agent over the mesh (ADR-0028)
Stdlib-only Python 3.12 agent for docker VMs and laptops: flock → checkin
(bearer = the agent's OpenBao token, bootstrapped from the install kit's
jwt-tenancy deploy grant) → action apply|none|down → bundle (sha256
verified) → refusal checks (unresolved ${VAR} names only, manifest paths
pinned to monky/data/<env>/see/, privileged/host-network, rollback, disk
need×1.5+headroom) → lease → POST /v1/auth/jwt-tenancy/login → KV reads →
.env 0600 → promote → compose pull/up → wait healthy → report; finally
renew-self / re-lease before max TTL, scrub. Exit 0/75/78/1. Redactor log
filter. Transports sdk (openziti) / proxy (ziti tunnel proxy 18443/18200) /
system. Laptop mode.

Packaging: hardened oneshot + 60 s timer + proxy unit, nfpm .deb with
/opt/monky-deployd/venv, install.sh for Ubuntu 26.04 (Gitea release
download, enrol, ACLs, bootstrap from stdin), ansible role skeleton for
osg1-07. CI: lint/test on every change; wheel (openziti on ubuntu:26.04) and
package (nfpm) allow_failure until runner egress is proven; GitLab release +
release:gitea on v* tags. Docs: README, PROTOCOL, OPERATIONS, CHANGELOG,
CLAUDE/AGENTS.

Divergence noted: monky-tenancy main (MR !15) still ships the AppRole lease
and kit; this agent implements the plan's Gate 1 RESULT (login_jwt, no
unwrap) and refuses an AppRole lease loudly (LEASE_SHAPE).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLB7jieMNRkTsJ2epr4Ds1
2026-09-05 08:01:36 +00:00

2.4 KiB

AGENTS.md — rules for AI agents working in monky-deployd

Read CLAUDE.md first; these are the non-negotiables.

  1. Stdlib only. No runtime dependency in monky_deployd/; openziti is optional and imported lazily in transport.SdkTransport only.
  2. Never a secret value in a log, a report, state.json, an exception message or a test assertion output. Register every value/token/grant with REDACTOR.add() the moment it exists; name variables, never values. tests/test_agent.py asserts the report tail is clean — keep it so.
  3. Auth is the jwt-tenancy deploy grant (login_jwtPOST /v1/auth/jwt-tenancy/login). No AppRole, no unwrap, no compatibility fallback; an AppRole-shaped lease is LEASE_SHAPE (failed).
  4. Vocabulary: check-in action apply|none|down; report result applied|failed|down. env_id, backend, bundle, deploy grant. Never "tenant".
  5. Refuse before you lease, and refuse hard: a failed check is Refusal(code, detail) → report failed → exit 1. No "apply anyway with a warning".
  6. Exit codes 0 / 75 / 78 / 1 are a contract (timer SuccessExitStatus=75; 78 stops the loop).
  7. Prod never purges volumes, whatever tenancy sends.
  8. Config is the YAML subset config.parse_yaml_subset understands (maps, scalars, simple lists). If you add a key: Config dataclass + config.example.yaml + install.sh + the ansible template.
  9. Tests are hermetic (fake tenancy/OpenBao HTTP servers + the stub docker). Every new refusal code or protocol field gets a test against the fakes, and the fakes track the tenancy shapes of record (monky-tenancy app/schemas_backends.py, design doc 24 §3.3).
  10. Quality gates, each its own statement: ruff format --check ., ruff check ., pytest, bash -n packaging/install.sh, systemd-analyze verify where available.
  11. Branching: feat/* / fix/* / docs/* → MR into main → tag vX.Y.Z on main (protected v*). The version lives in monky_deployd/__init__.py, pyproject.toml, install.sh DEFAULT_VERSION, CHANGELOG.md — bump all four.
  12. YAML traps: single-quote every CI script line; never ": " in an unquoted scalar.
  13. Never edit a *.ru.md; edit English only, keep <!-- xlate:verbatim-fences --> on line 1.
  14. ADR citations are MONKY-ADR-NNNN (monky-design-docs); cite, don't restate.