feat(sdk): ms.DependencyDB — restricted cross-module read client (Milestone H Phase 3)#135
Merged
Merged
Conversation
…tone H phase 3)
The consumer-side SDK client of the platform's read-exposed proxy
(decision 17 §2, option (b)+(d)): a dev-plane consumer reads a producer's
ExposeTable'd rows through dispatch's POST
/internal/apps/{appRef}/read-exposed — a STRUCTURED read (logical table +
projection + equality/IN filters), never raw SQL, never a pool. The
platform authorizes against the consent+exposure catalog and executes as
the consumer's own r_<app8>_<mod> role in a READ ONLY tx; the SDK joins
in application code (no cross-DB SQL JOIN exists, by construction).
Surface: ms.DependencyDB(ctx, "@owner/slug").Select(table).Columns(...).
Where(col, v).WhereIn(col, vs...).Limit(n).Rows(ctx) / .Result(ctx)
(Result adds the Truncated flag). Producer refs accept the DependsOn
spec forms (@owner/slug | slug | m<hex> | UUID; trailing @constraint
ignored). Builder validation mirrors the wire caps client-side
(snake_case identifiers, ≤64 columns, ≤16 filters, 1..200 IN values,
scalar-only filter values), latching the first error.
Transport rides the existing module->dispatch seam (MS_DISPATCH_URL +
dev fallback, shared callHTTP client); authn is the live tunnel
session's MS_INTERNAL_SECRET sent as X-MS-Service-Secret. Failure modes
are typed and fail-closed — ErrDependencyUnauthorized (401),
ErrNotExposed (403 read_not_authorized), ErrDependencyUnavailable
(403 dependency_unavailable + proxy-route-absent), ErrProducerNotFound
(404) — never silently empty rows. Row numerics decode as json.Number
so int64 join keys keep fidelity.
Dev-plane only in this PR: the Lambda envelope vends no proxy
credential and deployed→deployed reads use the direct GRANT via mod.DB,
so deployed mode fails fast with an explicit error (follow-up wires
envelope-vended proxy credentials if ever needed).
Unit tests cover the wire contract via an httptest fake: happy path,
every error mapping, ref parsing, builder validation, missing app
scope/secret, Lambda fail-fast.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
ms.DependencyDB— the consumer-side SDK client of the platform's read-exposed proxy (decision-17 §2, option (d)). A dev-plane consumer that declaredms.DependsOn("@owner/producer", n.Table("users"))can now read those exposed rows at runtime:Structured reads only — logical table + projection + equality/IN filters. No raw SQL, no pool: the dev plane holds no socket to the platform DB, so a cross-plane SQL JOIN is structurally impossible — fetch the exposed rows, then join in application code. The platform authorizes against the consent+exposure catalog and executes as the consumer's own
r_<app8>_<mod>role in a READ ONLY transaction.How
dispatchBase()/MS_DISPATCH_URL+ the sharedcallHTTPclient — the same seamms.Call/ms.Emit/meter use; no second client config). Authenticates with the live tunnel session'sMS_INTERNAL_SECRETasX-MS-Service-Secret.errors.Is, never silently empty rows):ErrDependencyUnauthorized— 401 (no/wrong tunnel session secret)ErrNotExposed— 403read_not_authorized(not exposed by the running version / no consent / no role — deliberately collapsed)ErrDependencyUnavailable— 403dependency_unavailableAND envelope-less 404/405 (proxy route absent)ErrProducerNotFound— 404producer_not_found^[a-z][a-z0-9_]{0,62}$identifiers, ≤64 columns, ≤16 filters, 1..200 IN values, scalar-only filter values, one predicate per column, ≤64KB envelope. The builder latches the FIRST error so chains stay ergonomic.DependsOnspec forms (@owner/slug|slug|m<hex>| dashed UUID; trailing@constraintignored — reads target the version the producer actually runs).json.Numberfor int64 join-key fidelity in the app-code join.mod.DB(GRANT SELECT); Lambda mode fails fast with an explicit error. Envelope-vended proxy credentials for deployed cross-plane reads are a documented follow-up.Wire contract cross-verified against the actual dispatch handler (field names, header, status codes,
{"error":{"code","message"}}envelope).Testing
11 unit tests against an httptest fake dispatch: happy path (full wire-shape assertion + json.Number fidelity + Truncated), empty-rows-never-nil, every error mapping (401/403×2/404×2), 500-is-generic, producer-ref parsing, builder validation (12 cases, all pre-network), first-error-wins, missing app scope, missing tunnel secret, Lambda fail-fast, panic-before-Init. Full
go test ./...green.🤖 Generated with Claude Code