mirror of
https://scm.tikali.ai/tikali/applications/monky/monky-deployd.git
synced 2026-09-18 04:36:15 +00:00
docs(protocol): the AppRole divergence is resolved history; the lease example and fake follow tenancy's AgentLeaseOut
DD-0524 — PROTOCOL.md §Divergences bullets 1-2 said monky-tenancy `main` "still implements the AppRole lease and install kit" and that "the kit's generated config uses tenancy.base_url". Both were false when the page was published: tenancy !17 (288df791, merged 07:48Z) shipped AgentLeaseOut{env_id, login_jwt, ttl_s, mount, role, addr}, the ES256 grant signer and the JWKS sixteen minutes before v0.1.0 was tagged, and !22 (61bd0281) made the kit run install.sh with flags instead of writing a config. The two bullets are now dated "Resolved" notes; bullets 3-4 (report `detail`, X-Bundle-Sha) stand. DD-0526 — the lease example sent `reason` and received a nested `vault{}`; tenancy's AgentLease is `{env_id}` and AgentLeaseOut carries `addr` at the top level (no vault object). The example now shows tenancy's shapes (with a note that the agent still sends `reason` and tenancy ignores unknown fields), the checkin example gains `auth_mount`/`auth_role` so it is the full AgentVaultOut, tests/fakes.py emits `addr` the way tenancy does, and tenancy.py's docstring and lease() read `addr` first (the `vault{}` fallback is kept so an older fake or tenancy still leases). Verified against monky-tenancy app/schemas_backends.py at 1fd51454 (AgentLease 296-297, AgentLeaseOut 300-309, AgentVaultOut 278-283). Gates (local, py3.12): ruff format --check, ruff check, pytest 50 passed, bash -n packaging/install.sh. Doc-Drift: DD-0524 fixed Doc-Drift: DD-0526 fixed Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AW3QqEpwLV69KHn24Re45Q
This commit is contained in:
+19
-12
@@ -70,7 +70,8 @@ same tick, otherwise it exits 1 and says "re-run the install kit".
|
|||||||
```json
|
```json
|
||||||
{"env_id": "env-qa-02", "desired_sha": "7a10…", "action": "apply", "purge_volumes": false,
|
{"env_id": "env-qa-02", "desired_sha": "7a10…", "action": "apply", "purge_volumes": false,
|
||||||
"bundle_url": "/v1/agent/bundle/env-qa-02/7a10…", "checkin_interval_s": 60,
|
"bundle_url": "/v1/agent/bundle/env-qa-02/7a10…", "checkin_interval_s": 60,
|
||||||
"vault": {"addr": "https://bao.cbs.tikali.net:8200", "mount": "monky", "prefix": "env-qa-02/see"}}
|
"vault": {"addr": "https://bao.cbs.tikali.net:8200", "mount": "monky", "prefix": "env-qa-02/see",
|
||||||
|
"auth_mount": "jwt-tenancy", "auth_role": "see-env"}}
|
||||||
```
|
```
|
||||||
`action`: `apply` (desired ≠ applied), `none` (converged → heartbeat), `down` (retire; `purge_volumes`
|
`action`: `apply` (desired ≠ applied), `none` (converged → heartbeat), `down` (retire; `purge_volumes`
|
||||||
is only meaningful here). `vault.mount` is the **KV** mount; the agent adopts it if it differs from
|
is only meaningful here). `vault.mount` is the **KV** mount; the agent adopts it if it differs from
|
||||||
@@ -109,12 +110,16 @@ address, pull, renderer, images_policy, secrets_provider, agent{…}, files[]`.
|
|||||||
### `POST /v1/agent/lease`
|
### `POST /v1/agent/lease`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"env_id": "env-qa-02", "reason": "apply"} // reason: apply | renew
|
{"env_id": "env-qa-02"}
|
||||||
```
|
```
|
||||||
|
(The agent also sends `"reason": "apply" | "renew"` for its own logs; tenancy's `AgentLease` is
|
||||||
|
`{env_id}` and ignores unknown fields.)
|
||||||
```json
|
```json
|
||||||
{"env_id": "env-qa-02", "login_jwt": "eyJ…", "ttl_s": 3600, "mount": "jwt-tenancy", "role": "see-env",
|
{"env_id": "env-qa-02", "login_jwt": "eyJ…", "ttl_s": 3600, "mount": "jwt-tenancy", "role": "see-env",
|
||||||
"vault": {"addr": "https://bao.cbs.tikali.net:8200", "mount": "monky"}}
|
"addr": "https://bao.cbs.tikali.net:8200"}
|
||||||
```
|
```
|
||||||
|
`addr` is the OpenBao address for the login below, top-level (tenancy `AgentLeaseOut`); the KV
|
||||||
|
mount/prefix come from `checkin`'s `vault`, not from the lease.
|
||||||
Then `POST /v1/auth/{mount}/login {"role": "{role}", "jwt": "{login_jwt}"}` on OpenBao. A body with
|
Then `POST /v1/auth/{mount}/login {"role": "{role}", "jwt": "{login_jwt}"}` on OpenBao. A body with
|
||||||
`wrapping_token` / `role_id` (the pre-Gate-1 AppRole lease) is refused with `LEASE_SHAPE` → report
|
`wrapping_token` / `role_id` (the pre-Gate-1 AppRole lease) is refused with `LEASE_SHAPE` → report
|
||||||
`failed`. `429 LEASE_RATE_LIMITED` → exit 75.
|
`failed`. `429 LEASE_RATE_LIMITED` → exit 75.
|
||||||
@@ -133,15 +138,17 @@ first 2 KiB land in the audit log — it has been through the redactor.
|
|||||||
|
|
||||||
## Divergences (2026-09-05)
|
## Divergences (2026-09-05)
|
||||||
|
|
||||||
- **monky-tenancy `main` (MR !15) still implements the AppRole lease and install kit**
|
- **Resolved 2026-09-05 (before v0.1.0 was tagged):** monky-tenancy !17 (`288df791`) landed the
|
||||||
(`AgentLeaseOut{wrapping_token, role_id}`, `bootstrap.wrap`, `bao.approle` in the kit's config). The
|
tenancy side of the Gate 1 RESULT / ADR-0028 amendment — `AgentLeaseOut{env_id, login_jwt, ttl_s,
|
||||||
binding design is the plan's Gate 1 RESULT / ADR-0028 amendment: `{login_jwt, ttl_s, mount, role}`
|
mount, role, addr}`, the ES256 deploy-grant signer (`app/agent_keys.py`), `GET
|
||||||
and `POST /v1/auth/jwt-tenancy/login`. This agent implements the latter; against an un-migrated
|
/.well-known/agent-jwks.json`, no AppRole and no response wrapping anywhere in tenancy. The
|
||||||
tenancy it reports `failed` with `LEASE_SHAPE` and refuses `bao.approle` in its config. The tenancy
|
`LEASE_SHAPE` refusal of a `wrapping_token`/`role_id` body stays in this agent as a guard against a
|
||||||
follow-up (deploy-grant signer, JWKS, `lease` shape, kit → `bootstrap.jwt`) is tracked on
|
stale tenancy, not as a description of `main`.
|
||||||
monky-tenancy.
|
- **Resolved 2026-09-05:** the kit no longer generates a config file. Since monky-tenancy !22
|
||||||
- The kit's generated config uses `tenancy.base_url: http://monky.tenancy.deploy:8081` — accepted as
|
(`61bd0281`) the one-time install script stages the bootstrap grant and runs `install.sh --env …
|
||||||
an alias for `tenancy.{scheme,host,port}`.
|
--site … --version … --bootstrap-file …`, and `install.sh` writes `/etc/monky-deployd/config.yaml`
|
||||||
|
(tenancy service name, OpenBao address and `jwt-tenancy`/`see-env`). `tenancy.base_url:` remains an
|
||||||
|
accepted alias for `tenancy.{scheme,host,port}` (`config.py`) for hand-written configs.
|
||||||
- `report` gains an optional `detail` (doc 24 §3.3); tenancy's `AgentReport` ignores unknown fields
|
- `report` gains an optional `detail` (doc 24 §3.3); tenancy's `AgentReport` ignores unknown fields
|
||||||
today — if `strict` bodies land, `detail` folds into `log_tail`.
|
today — if `strict` bodies land, `detail` folds into `log_tail`.
|
||||||
- Bundle sha header: tenancy sends `X-Bundle-Sha`, doc 24 says `X-Bundle-Sha256`; the agent reads
|
- Bundle sha header: tenancy sends `X-Bundle-Sha`, doc 24 says `X-Bundle-Sha256`; the agent reads
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Bearer = the agent's OpenBao token (from the `jwt-tenancy` login). Tenancy pins
|
|||||||
the token's `meta.env_id` (403 AGENT_ENV_MISMATCH -> exit 78, never retried) and refuses a token
|
the token's `meta.env_id` (403 AGENT_ENV_MISMATCH -> exit 78, never retried) and refuses a token
|
||||||
whose deploy grant was superseded (401 AGENT_UNAUTHENTICATED -> re-bootstrap or re-run the kit).
|
whose deploy grant was superseded (401 AGENT_UNAUTHENTICATED -> re-bootstrap or re-run the kit).
|
||||||
|
|
||||||
Lease shape of record (Gate 1 v2, 2026-09-05): `{login_jwt, ttl_s, mount, role, vault}`. An
|
Lease shape of record (Gate 1 v2, 2026-09-05): `{env_id, login_jwt, ttl_s, mount, role, addr}`. An
|
||||||
AppRole-era body (`wrapping_token`, `role_id`) is refused loudly — there is nothing to unwrap."""
|
AppRole-era body (`wrapping_token`, `role_id`) is refused loudly — there is nothing to unwrap."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -158,13 +158,15 @@ class TenancyClient:
|
|||||||
"(ADR-0028 amendment 2026-09-05)",
|
"(ADR-0028 amendment 2026-09-05)",
|
||||||
)
|
)
|
||||||
raise TenancyError(200, "LEASE_SHAPE", "lease response carries no login_jwt")
|
raise TenancyError(200, "LEASE_SHAPE", "lease response carries no login_jwt")
|
||||||
|
# shape of record (tenancy AgentLeaseOut): `addr` is top-level; a pre-0.1.x `vault{}` object
|
||||||
|
# is still read as a fallback so an older fake or tenancy does not break the lease
|
||||||
v = js.get("vault") or {}
|
v = js.get("vault") or {}
|
||||||
return Lease(
|
return Lease(
|
||||||
login_jwt=str(js["login_jwt"]),
|
login_jwt=str(js["login_jwt"]),
|
||||||
ttl_s=int(js.get("ttl_s") or 3600),
|
ttl_s=int(js.get("ttl_s") or 3600),
|
||||||
mount=str(js.get("mount") or "jwt-tenancy"),
|
mount=str(js.get("mount") or "jwt-tenancy"),
|
||||||
role=str(js.get("role") or "see-env"),
|
role=str(js.get("role") or "see-env"),
|
||||||
vault=Vault(addr=v.get("addr"), mount=v.get("mount"), prefix=v.get("prefix")),
|
vault=Vault(addr=js.get("addr") or v.get("addr"), mount=v.get("mount"), prefix=v.get("prefix")),
|
||||||
)
|
)
|
||||||
|
|
||||||
def report(
|
def report(
|
||||||
|
|||||||
+2
-1
@@ -354,7 +354,8 @@ class FakeTenancy:
|
|||||||
"ttl_s": 3600,
|
"ttl_s": 3600,
|
||||||
"mount": self.bao.mount,
|
"mount": self.bao.mount,
|
||||||
"role": self.bao.role,
|
"role": self.bao.role,
|
||||||
"vault": {"addr": "https://bao.cbs.tikali.net:8200", "mount": self.kv_mount},
|
# tenancy AgentLeaseOut: `addr` is top-level; KV mount/prefix come from checkin
|
||||||
|
"addr": "https://bao.cbs.tikali.net:8200",
|
||||||
},
|
},
|
||||||
headers={"Cache-Control": "no-store"},
|
headers={"Cache-Control": "no-store"},
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user