Port Allocation (#197)
How BoB assigns dev ports across the fleet. Band convention, the two-tier source-of-truth model, backfill, and inspection. Extracted from the dev lifecycle reference.
Port Allocation (#197)
Section titled “Port Allocation (#197)”Every BoB-provisioned project owns a deterministic port band so multiple projects can run their dev servers simultaneously without collisions or unpredictable auto-increment. Port assignment is declarative, version-controlled, reproducible, and owned by exactly one source of truth — per the prime directive.
Band convention
Section titled “Band convention”base = 5200 + slot*10 # contiguous 10-port band per project base + 0 Vite dev (dev.json server.port) base + 1 Wrangler dev base + 2 cds dashboard base + 3..9 reserved / extensible (other long-running dev services)slot is derived from a deterministic FNV-1a hash of the project name, so a
fresh clone proposes the same band before the ledger has any record of the
project. Bands overlapping well-known services (PostgreSQL 5432, VNC 5900) are
skipped; extend via the ledger’s reserved_bases.
Two-tier source-of-truth model
Section titled “Two-tier source-of-truth model”- Authoritative ledger —
BOB_SOURCE/provisions/ports.json. The single source of truth, version-controlled (validated byschemas/ports.schema.json). It is the only tier with the global view needed for collision detection. Managed byscripts/port-ledger.js. The ledger lives in BOB_SOURCE — not machine-local BOB_HOME — exactly likeprovisions/<project>.json. - Derived projection —
dev.json+ vite/wrangler configs.cdiwrites the concrete ports into a marked, regeneratable block (_bob_portsindev.json; anexport const BOB_PORTS = {…}sentinel invite.config.*; a comment sentinel inwrangler.toml). Never hand-edited — regenerating from the ledger is lossless and idempotent (re-runningcdiyields zero diff). - Name-hash = default proposal, not authority. The allocator tries the hash-derived band first; the ledger only records and arbitrates the rare collision (deterministically stepping to the next free band).
Accepted tradeoff: a project’s port lives in the bigbrain repo, not the
project’s own repo. Accepted because port allocation is inherently cross-project
and BoB already owns per-project state in provisions/.
Retrofit / backfill
Section titled “Retrofit / backfill”When cdi runs on a project with an explicit, non-default pinned port
(e.g. nanaawards 5180, pinned to coexist with a file:../-linked sibling),
that pin seeds the ledger as a preserved pinned entry and is never
reshuffled. Framework defaults (5173, 8787, 3000, …) are not treated as
pins — otherwise every SvelteKit project would pin to 5173 and perpetually
collide.
Inspecting bands (observable)
Section titled “Inspecting bands (observable)”cdb --ports # port-band table for all projectscds --ports # same, from the dashboard CLInode ~/.claude/scripts/port-ledger.js list # raw JSONnode ~/.claude/scripts/port-ledger.js path # ledger locationThe legacy machine-local ~/.claude/dashboard-ports.json (the old cds
3333–3400 registry) is folded into the ledger on first use and retired — no
parallel registry remains.
Onboarding the existing fleet (#198)
Section titled “Onboarding the existing fleet (#198)”cdi only allocates the project it is run on. Projects provisioned before
#197 land with no band. scripts/port-fleet-migrate.js is the one-shot
migration that backfills the whole fleet at once:
make port-migrate-dry # review the planned project → ports table (zero writes)make port-migrate # apply: backfill every un-allocated projectDiscovery source is BOB_SOURCE/provisions/*.json (minus the ledger, the
_default template, and manifests with no resolvable _meta.path). The
project name fed to the allocator is basename(_meta.path) — identical to
cdi’s PORT_PROJECT_NAME — so the band a project gets here is exactly the
band cdi would assign. Properties:
- Idempotent & stable — a project with an existing ledger band is never reassigned; a second run is a verified no-op (no ledger or file diff).
- Loud on collision — a pinned band overlapping a reserved range or another project’s band is reported and the run aborts with exit 1 before any write; never silently double-assigned.
- Confined writes — per-project changes go only through the lossless sentinel-block projector; content outside the marked blocks is preserved.
- Graceful skips — projects with no vite/Wrangler/
dev.jsonstill get a ledger band (reserved) and are listed as ledger-only; missing-on-disk and template manifests are listed as skipped with a reason.
deploy.sh invokes it idempotently and non-fatally on every deploy (honouring
the deploy --dry-run flag); the loud exit-1 path is make port-migrate. The
end-of-run summary reports assigned / ledger-only / already-allocated /
skipped / collisions. Tests: make test-port-fleet-migrate.