o2r packaging: channels layer - runnable app shim + container + Postgres chart #47

Closed
opened 2026-06-02 23:11:43 +00:00 by coilysiren · 3 comments
Owner

Part of #45 (per-layer isolated-unit packaging).

Layer: channels (otel-a2a-relay-channels v0.1.0) - the Agent Channel coordination layer: a FastAPI router (make_router(...)) + Postgres schema + Pydantic models. Postgres-backed.

Caveat unique to this layer: today it ships as a mountable library with no entrypoint - the router is built with caller-supplied pool_provider / auth_dependency and mounted into a host app. To package it as an isolated unit it needs a thin runnable app first.

Runnable app shim (prereq)

  • Add a minimal FastAPI app (__main__ / o2r-channels script) that builds the pool from env (DATABASE_URL / discrete PG vars), runs SCHEMA on startup, mounts make_router, and exposes a bearer auth_dependency.

Docker

  • Dockerfile running that app (uvicorn). Image ref 192.168.0.194:30500/otel-a2a-relay-channels:<git-sha>.

Helm

  • channels/chart/: the channels app Deployment + Service.
  • Postgres dependency: either a subchart/StatefulSet or values pointing at an external DB (channelDb values per #25's original scope). Templated OTLP endpoint, base_url, auth.

Acceptance

  • App shim runs locally against a Postgres and serves /agent-channel/....
  • Image builds + pushes; chart installs with a reachable Postgres and emits channel-event spans.
Part of #45 (per-layer isolated-unit packaging). **Layer:** `channels` (`otel-a2a-relay-channels` v0.1.0) - the Agent Channel coordination layer: a FastAPI router (`make_router(...)`) + Postgres schema + Pydantic models. Postgres-backed. **Caveat unique to this layer:** today it ships as a *mountable library* with **no entrypoint** - the router is built with caller-supplied `pool_provider` / `auth_dependency` and mounted into a host app. To package it as an isolated unit it needs a thin runnable app first. ### Runnable app shim (prereq) - Add a minimal FastAPI app (`__main__` / `o2r-channels` script) that builds the pool from env (`DATABASE_URL` / discrete PG vars), runs `SCHEMA` on startup, mounts `make_router`, and exposes a bearer `auth_dependency`. ### Docker - Dockerfile running that app (uvicorn). Image ref `192.168.0.194:30500/otel-a2a-relay-channels:<git-sha>`. ### Helm - `channels/chart/`: the channels app Deployment + Service. - Postgres dependency: either a subchart/StatefulSet or values pointing at an external DB (`channelDb` values per #25's original scope). Templated OTLP endpoint, base_url, auth. ### Acceptance - App shim runs locally against a Postgres and serves `/agent-channel/...`. - Image builds + pushes; chart installs with a reachable Postgres and emits channel-event spans.
Author
Owner

Constraint: the packaged channels service is tailnet-only

The new channels deployment must be tailnet-only, matching the current backend's posture (today's channels API is reached via the http://api tailnet sidecar, and its Postgres backend-db is tailnet-reachable - no public surface). Carry that forward:

  • No public ingress. Expose the channels Service only on the tailnet - a Tailscale sidecar / tailscale serve (or the existing api ingress pattern), not a public LoadBalancer/Ingress.
  • Postgres stays in-cluster / tailnet-only; never a public endpoint.
  • o2r clients reach it via a tailnet base_url (the O2R_BASE_URL / config base_url repoint at #52 cutover points at a tailnet address, not a public one).
  • Chart values should make the tailnet-only exposure the default, with no public-ingress path templated in.

This is a hard requirement (Kai, 2026-06-02), not a default to be overridden.

### Constraint: the packaged channels service is tailnet-only The new channels deployment must be **tailnet-only**, matching the current backend's posture (today's channels API is reached via the `http://api` tailnet sidecar, and its Postgres `backend-db` is tailnet-reachable - no public surface). Carry that forward: - **No public ingress.** Expose the channels Service only on the tailnet - a Tailscale sidecar / `tailscale serve` (or the existing `api` ingress pattern), not a public LoadBalancer/Ingress. - Postgres stays in-cluster / tailnet-only; never a public endpoint. - `o2r` clients reach it via a tailnet `base_url` (the `O2R_BASE_URL` / config `base_url` repoint at #52 cutover points at a tailnet address, not a public one). - Chart values should make the tailnet-only exposure the default, with no public-ingress path templated in. This is a hard requirement (Kai, 2026-06-02), not a default to be overridden.
Author
Owner

Refinement (Kai, 2026-06-02): secure-by-default + exposure-as-plugin

Supersedes the earlier "tailnet-only, hardwired" note. Same intent (tailnet is the reachability path), cleaner architecture:

1. Postgres is not accessible by default.

  • No exposed Service for Postgres - ClusterIP-internal at most, ideally no Service beyond what the channels app needs. Never a NodePort/LoadBalancer, never tailnet-exposed, never public.
  • A NetworkPolicy restricts ingress to the channels app pod only.
  • Credentials come from a Secret; no default/baked passwords. The DSN the app uses is the only path in.
  • The chart must be installable with Postgres fully locked down as the default - opening any access is an explicit operator opt-in.

2. Tailscale is a loosely-coupled plugin, not core.

  • The core channels chart is exposure-agnostic: it stands up the app + (locked-down) Postgres and a ClusterIP Service, and runs to completion with no Tailscale present.
  • Tailnet exposure ships as a separate, optional plugin/subchart (Tailscale sidecar or operator Ingress), off by default (tailscale.enabled=false or a discrete add-on chart). Enabling it is what makes the service tailnet-reachable.
  • This keeps the deployment portable: the same core chart can sit behind a different exposure plugin later without edits.

3. Future "serious business" integrations - document only for now.

  • Productization surfaces (e.g. a Lunch Money MCP and other external/commercial integrations) are out of scope for this pass. Capture them as documented future extension points so the plugin seams account for them; do not build. Tracked separately (parked issue).

Framing: maximum professionalism - secure-by-default, pluggable exposure, documented extension seams.

### Refinement (Kai, 2026-06-02): secure-by-default + exposure-as-plugin Supersedes the earlier "tailnet-only, hardwired" note. Same intent (tailnet is the reachability path), cleaner architecture: **1. Postgres is not accessible by default.** - No exposed Service for Postgres - ClusterIP-internal at most, ideally no Service beyond what the channels app needs. Never a NodePort/LoadBalancer, never tailnet-exposed, never public. - A `NetworkPolicy` restricts ingress to the channels app pod only. - Credentials come from a Secret; no default/baked passwords. The DSN the app uses is the only path in. - The chart must be installable with Postgres fully locked down as the **default** - opening any access is an explicit operator opt-in. **2. Tailscale is a loosely-coupled plugin, not core.** - The core channels chart is **exposure-agnostic**: it stands up the app + (locked-down) Postgres and a ClusterIP Service, and runs to completion with **no Tailscale present**. - Tailnet exposure ships as a separate, optional plugin/subchart (Tailscale sidecar or operator `Ingress`), off by default (`tailscale.enabled=false` or a discrete add-on chart). Enabling it is what makes the service tailnet-reachable. - This keeps the deployment portable: the same core chart can sit behind a different exposure plugin later without edits. **3. Future "serious business" integrations - document only for now.** - Productization surfaces (e.g. a Lunch Money MCP and other external/commercial integrations) are out of scope for this pass. Capture them as documented future extension points so the plugin seams account for them; do not build. Tracked separately (parked issue). Framing: maximum professionalism - secure-by-default, pluggable exposure, documented extension seams.
Author
Owner

Backlog burndown 2026-06-17: closing low-priority (P3/P4) to bring the open count to a manageable level. Nothing lost — reopen if this resurfaces. Batch tag: burndown-2026-06.

Backlog burndown 2026-06-17: closing low-priority (P3/P4) to bring the open count to a manageable level. Nothing lost — reopen if this resurfaces. Batch tag: `burndown-2026-06`.
coilysiren 2026-06-17 08:23:46 +00:00
Commenting is not possible because the repository is archived.
No description provided.