feat(orchestrator): slice 6 — source-IP-keyed multi-tenant policy #361

Closed
didericis-claude wants to merge 1 commits from orchestrator-slice6 into orchestrator-slice5
Collaborator

Slice 6 of PRD 0070, stacked on #360. The orchestrator side of the multi-tenant consolidated sidecar: hold each bottle's sidecar policy and serve it by verified source IP, with live reload — so one shared sidecar can get per-bottle config keyed on who's calling.

  • registry.py — a policy column (migration v3; opaque JSON the sidecar interprets) on BottleRecord; register(..., policy=), set_policy(id, policy) (live), and attribute returns it (the source-IP-keyed resolution).
  • service.pylaunch_bottle(..., policy=) + set_policy.
  • control_plane.pyPOST /bottles accepts policy; PUT /bottles/<id>/policy live-reloads it; POST /resolve returns {bottle_id, policy} for a verified (source_ip, token) — the per-request call the multi-tenant sidecar makes. /attribute stays identity-only.

Scope note (honest): this is the control-plane / state half. The data-plane half — the egress mitmproxy addon (and git-gate) selecting allowlist / DLP / token-injection per client IP by calling /resolve — is the next slice (route agent bottles through the shared sidecar, #7). The orchestrator stays policy-agnostic: stores + serves the blob verbatim.

Tests: registry policy store/update/persist; Orchestrator launch-with-policy + live set_policy; control-plane /resolve returns policy (403 on bad token), PUT policy updates / 404 / 400. Verified live over HTTP (launch → resolve → PUT reload → resolve reflects). Full suite green.

Merge order: #352#356#357#358#360 → this.

🤖 Generated with Claude Code

Slice 6 of PRD 0070, **stacked on #360**. The orchestrator side of the multi-tenant consolidated sidecar: hold each bottle's sidecar policy and **serve it by verified source IP, with live reload** — so one shared sidecar can get per-bottle config keyed on who's calling. - **`registry.py`** — a `policy` column (migration v3; opaque JSON the sidecar interprets) on `BottleRecord`; `register(..., policy=)`, `set_policy(id, policy)` (live), and `attribute` returns it (the source-IP-keyed resolution). - **`service.py`** — `launch_bottle(..., policy=)` + `set_policy`. - **`control_plane.py`** — `POST /bottles` accepts `policy`; **`PUT /bottles/<id>/policy`** live-reloads it; **`POST /resolve`** returns `{bottle_id, policy}` for a verified `(source_ip, token)` — the per-request call the multi-tenant sidecar makes. `/attribute` stays identity-only. **Scope note (honest):** this is the **control-plane / state half**. The **data-plane half** — the egress mitmproxy addon (and git-gate) selecting allowlist / DLP / token-injection *per client IP* by calling `/resolve` — is the next slice (route agent bottles through the shared sidecar, #7). The orchestrator stays policy-agnostic: stores + serves the blob verbatim. **Tests:** registry policy store/update/persist; Orchestrator launch-with-policy + live `set_policy`; control-plane `/resolve` returns policy (403 on bad token), `PUT` policy updates / 404 / 400. Verified live over HTTP (launch → resolve → PUT reload → resolve reflects). Full suite green. Merge order: #352 → #356 → #357 → #358 → #360 → this. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
didericis-claude added 1 commit 2026-07-13 17:09:49 -04:00
feat(orchestrator): slice 6 — source-IP-keyed multi-tenant policy (#352)
lint / lint (push) Successful in 2m1s
test / unit (pull_request) Successful in 1m5s
test / integration (pull_request) Successful in 21s
test / coverage (pull_request) Successful in 1m16s
f754c575d7
The orchestrator side of the multi-tenant consolidated sidecar: hold each
bottle's sidecar policy and serve it by verified source IP, with live
reload. One shared sidecar can now get per-bottle config keyed on who's
calling.

  * registry.py — a `policy` column (migration v3, opaque JSON the sidecar
    interprets) on BottleRecord; `register(..., policy=)` stores it,
    `set_policy(bottle_id, policy)` updates it live, and `attribute` returns
    it (the source-IP-keyed resolution).
  * service.py — `launch_bottle(..., policy=)` and `set_policy`.
  * control_plane.py — `POST /bottles` accepts `policy`; `PUT
    /bottles/<id>/policy` live-reloads it; `POST /resolve` returns
    {bottle_id, policy} for a verified (source_ip, token) — the per-request
    call the multi-tenant sidecar makes; `/attribute` stays identity-only.

Scope note: this is the control-plane / state half. The data-plane half —
the egress mitmproxy addon (and git-gate) selecting allowlist / DLP /
token-injection per client IP by calling `/resolve` — is the next slice
(route agent bottles through the shared sidecar). The orchestrator stays
policy-agnostic: it stores and serves the blob verbatim.

Tests: registry policy store/update/persist; Orchestrator launch-with-policy
+ live set_policy; control-plane resolve returns policy (403 on bad token),
PUT policy updates / 404 / 400. Verified live over HTTP (launch -> resolve
-> PUT reload -> resolve reflects). Full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
didericis approved these changes 2026-07-13 17:14:50 -04:00
didericis left a comment
Owner

Should probably change the policy to be a structured type, but is fine for now.

Should probably change the policy to be a structured type, but is fine for now.
Author
Collaborator

Collapsed into the single stack PR #380 (per-host orchestrator, PRD 0070). Closing this slice PR; review + merge happens on #380.

Collapsed into the single stack PR #380 (per-host orchestrator, PRD 0070). Closing this slice PR; review + merge happens on #380.
didericis-claude closed this pull request 2026-07-14 00:57:21 -04:00
Some checks are pending
lint / lint (push) Successful in 2m1s
test / unit (pull_request) Successful in 1m5s
test / integration (pull_request) Successful in 21s
test / coverage (pull_request) Successful in 1m16s

Pull request closed

Sign in to join this conversation.