Skip to content

feat(sdk): ms.DependencyDB — restricted cross-module read client (Milestone H Phase 3)#135

Merged
I-am-nothing merged 1 commit into
mainfrom
feat/mh-dependency-db
Jul 4, 2026
Merged

feat(sdk): ms.DependencyDB — restricted cross-module read client (Milestone H Phase 3)#135
I-am-nothing merged 1 commit into
mainfrom
feat/mh-dependency-db

Conversation

@I-am-nothing

Copy link
Copy Markdown
Contributor

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 declared ms.DependsOn("@owner/producer", n.Table("users")) can now read those exposed rows at runtime:

rows, err := ms.DependencyDB(ctx, "@owner/oauth-core").
    Select("users").
    Columns("id", "email").
    Where("status", "active").
    WhereIn("id", 1, 2, 3).
    Limit(500).
    Rows(ctx) // or .Result(ctx) for the Truncated flag

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

  • Transport: rides the existing module→dispatch seam (dispatchBase()/MS_DISPATCH_URL + the shared callHTTP client — the same seam ms.Call/ms.Emit/meter use; no second client config). Authenticates with the live tunnel session's MS_INTERNAL_SECRET as X-MS-Service-Secret.
  • Typed fail-closed errors (match with errors.Is, never silently empty rows):
    • ErrDependencyUnauthorized — 401 (no/wrong tunnel session secret)
    • ErrNotExposed — 403 read_not_authorized (not exposed by the running version / no consent / no role — deliberately collapsed)
    • ErrDependencyUnavailable — 403 dependency_unavailable AND envelope-less 404/405 (proxy route absent)
    • ErrProducerNotFound — 404 producer_not_found
  • Client-side validation mirrors the platform caps so oversized requests fail fast in-process: snake_case ^[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.
  • Producer refs accept the DependsOn spec forms (@owner/slug | slug | m<hex> | dashed UUID; trailing @constraint ignored — reads target the version the producer actually runs).
  • Row numerics decode as json.Number for int64 join-key fidelity in the app-code join.
  • Dev-plane only in this PR: a deployed consumer reads a co-located producer directly via 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

…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>
@I-am-nothing I-am-nothing merged commit 9801c79 into main Jul 4, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant