Shared pool hosts exempt from the per-org co-location
Defect (epic 4929, dev finding F5; operator decision 2026-10-06: "Exempt Shared pool"): out of the box no tenant can rent Shared and an approved trial gets no node. Both place a tenant node on an operator host marked for Shared rentals (`vnode_hosts.shared_capacity IS NOT NULL`), and the D15 isolation default refuses that unless the org holds `orgs.colocation_allowed`, which is false for every tenant/trial org.
Reproduction (dev, Task 4973 report): approved trial org c8trlco44 → no trial node, note "This organization runs on dedicated nodes only..."; tenant org-b Add node → Rent Shared disabled until the operator granted org-b's exception.
Cause:
- `apps/api/app/rental_catalog.py:137` — Shared offering disabled (`SHARED_NOT_ALLOWED`) unless `tenant_defaults.colocation_allowed`.
- `apps/api/app/vnodes.py:548-549` — `place_virtual_node` calls `tenant_defaults.require_colocation_allowed` for any host of another org (reached from `trial_node.py:303`, `rental_provisioning.py:283`, `node_orders.py:425/465`).
- WORK doors too: `apps/api/app/tenant_defaults.py` `_FOREIGN_HOSTED_AMONG_SQL` (read by `placement_grants.py:215` and `vnodes.py:176`) marks such a node as taking no work — so fixing only placement would create a node that can never run a task.
Fix: a node on an OPERATOR-org host that is marked for Shared rentals (`shared_capacity IS NOT NULL`) is allowed for a tenant org without the exception — at placement, in the Shared offering check, and in `foreign_hosted_among`. Single-home the rule in `app/tenant_defaults.py` (e.g. a helper "is a Shared pool host") and update its module docstring item 1. Every other cross-org co-location stays refused exactly as today. Add a risk-register entry in `docs/compliance/risk-register.md` (Shared is shared by design; tenant told so by the AC6.2 isolation sentence; tenants never see the host's name/org). No new setting.
Out of scope: no change to `apps/jaahd/vnodehelper/*`, nft rules or quotas; no change to `shared_store_allowed`; no change to the operator-grant UI; do not auto-flip `colocation_allowed` on any org; no new knob.
Done means:
- A tenant org with `colocation_allowed=false` sees Shared enabled in GET /api/rentals/catalog when a Shared pool host has capacity — regression test.
- Approving a trial for an org with `colocation_allowed=false` creates the trial node on the Shared pool host — regression test.
- That trial/Shared node is NOT in `foreign_hosted_among` and is returned by `placement_grants.allowed_nodes` — regression test.
- A tenant node on a NON-pool host of another org (shared_capacity NULL, or a tenant-owned host) is still refused at placement and for work — regression test asserting the existing refusal text.
- `./dc check full` green.
Outcome
Shared pool hosts exempt from D15 co-location at placement, catalog and work doors (tenant_defaults.is_shared_pool_host); R-SHARED risk entry; full gate green; PR #4491 merged. Deferred: un-marking a pool host strands its nodes.
Artifacts
Activity
- Ready
- Runner started
- Runner ended (done)