feat(provisioning): discovery endpoints for external writers#966
Draft
jghoman wants to merge 1 commit into
Draft
feat(provisioning): discovery endpoints for external writers#966jghoman wants to merge 1 commit into
jghoman wants to merge 1 commit into
Conversation
Two read-only endpoints on the internal provisioning API, the
machine-facing 'which tenants exist and where do I write' surface for
external writers (viaduck destination discovery, millpond's
include-values source):
- GET /warehouses: per ready/resharding warehouse — org_id, duckling
name, state, writable fence flag (false while resharding; a
resharding warehouse is LISTED, not hidden — hiding reads as tenant
removal downstream), teams array, metadata-store connection with a
k8s SecretRef (no plaintext credentials in the payload), bucket, and
config_generation (max updated_at over ALL warehouse+org rows
regardless of state, so rows LEAVING the discoverable set bump it).
- GET /warehouse-team-ids: bare sorted deduped array of team ids —
the values-only shape millpond's generic include-list poller
consumes. (Not nested under /warehouses/: org ids are free-form
slugs, so a static child would squat the namespace a per-org
GET /warehouses/:org_id needs later.)
Teams are M1-lite: derived from the org's default_team_id (one team
per org, empty table suffix) — the payload is shaped as a teams array
from day one so a real per-team table swaps in invisibly.
Error contract: transient store failures fail the WHOLE request (a
polling consumer keeps last-known-good — the safe direction); only a
genuinely-missing org row / nil team id degrades, per warehouse, to an
empty teams array, logged and counted on
duckgres_discovery_broken_org_rows_total{reason} (sustained nonzero =
a live tenant silently unroutable). Both endpoints project from ONE
assembly so the values projection can't drift to weaker semantics.
Known gap, documented in code + asserted as-is in the harness: cnpg
rows carry only the metadata-store KIND today (endpoint/user/db +
SecretRef have no production writer; truth is the Duckling CR status),
so cnpg tenants serve an empty connection block until the provisioner
backfills the row on Ready. Team-level consumers are unaffected.
Also: ListWarehousesByStates gains ORDER BY org_id (no phantom diffs
between polls), configstore gains ListOrgsByNames (batch, kills the
N+1) and LatestConfigChange; state-classification tripwire test (the
enum is open — an unclassified new state would read as fleet-wide
removal); e2e harness discovery_endpoints assertion.
Test Impact PlanDeterministic summary of how this PR changes tests, CI runners, and coverage-risk signals. Summary
Signals
Coverage risk: neutral or increased No coverage-reduction warnings detected. |
jghoman
marked this pull request as draft
July 17, 2026 16:28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two read-only endpoints on the internal provisioning API — the machine-facing "which tenants exist and where do I write" surface for external writers (viaduck destination discovery; millpond's include-values source, shipped in millpond#107):
GET /api/v1/warehouses— per ready/resharding warehouse: org_id, duckling name, state,writablefence flag (false while resharding — a resharding warehouse is listed, not hidden, because vanishing reads as tenant removal downstream), teams array, metadata-store connection with a k8s SecretRef (no plaintext credentials in any payload), bucket, andconfig_generation(maxupdated_atacross all warehouse+org rows regardless of state, so rows leaving the discoverable set bump it — genuinely monotonic).GET /api/v1/warehouse-team-ids— bare sorted deduped JSON array of team ids, the values-only shape millpond's generic poller consumes. Deliberately not nested under/warehouses/: org ids are free-form slugs, so a static child would squat the namespace a future per-orgGET /warehouses/:org_idneeds.Teams are M1-lite: derived from the org's
default_team_id(one team per org, empty table suffix). The payload is shaped as a teams array from day one so the eventual per-team table (multi-team orgs, real suffixes) swaps in with no wire change.Error contract (the load-bearing part)
An erroneous omission of a team from these responses eventually stops that team's ingestion silently (consumers damp removals, then apply them). So: transient store failures fail the whole request — a polling consumer keeps its last-known-good state; a 200 with a team silently absent is never served. Only a genuinely-missing org row / nil
default_team_iddegrades, per-warehouse, to an empty teams array — logged and counted onduckgres_discovery_broken_org_rows_total{reason}(sustained nonzero = a live tenant is silently unroutable; also counts cross-org duplicate-team conflicts). Both endpoints project from one shared assembly so the values projection can't drift to weaker semantics.Known gap (documented in code, asserted as-is in the harness)
cnpg-shard rows carry only the metadata-store kind — the provision handler never writes endpoint/db/user and
MetadataStoreCredentialshas no production writer; the truth lives in the Duckling CR status. So cnpg tenants serve an empty connection block until the provisioner backfills the row on Ready (follow-up: sync CR status → row in the provisioner's watch, guarded tostate == readyso the reshard runner keeps exclusive ownership of those columns mid-flip). Team-level consumers are unaffected; this only bites viaduck's connection-details consumption later.Also
ListWarehousesByStatesgainsORDER BY org_id(Postgres heap order shuffles between polls → phantom diffs for consumers).ListOrgsByNames(batch — kills the N+1 org lookup) andLatestConfigChange.TestDiscoveryStateClassificationforces a human decision when a state is added.Testing
Authored with an agent; adversarial lead-QE and principal-SWE review agents ran against the initial version and their findings (transient-vs-missing error split, non-monotonic generation, cnpg row gap, ordering, namespace squat, leak-test hardening, state tripwire, broken-org observability) are incorporated. Checks actually run:
go test ./controlplane/provisioning ./controlplane/configstore+ fulljust test-controlplane— passgolangci-lint— 0 issues;gofmtcleandiscovery_endpointsassertion intests/mw-dev/e2e/harness.sh(both provisioned orgs listed writable with their provisioned team ids, ext metadata round-trip + cnpg kind-only, no password-ish keys anywhere in the payload, bare sorted team-ids array) — runs in the per-PR mw-dev jobPasswordAWSSecretvalue + recursive key scanFollow-ups