feat(sdk): add CreateStack/GetStack, ProvisionVector, DeploymentEvents#22
Merged
Conversation
Close the SDK endpoint-coverage gap surfaced by the live durability probe —
the SDK could provision db/cache/nosql/queue/storage/webhook + Deploy but was
missing live, documented prod endpoints, most importantly the ANONYMOUS deploy
path (Deploy/POST /deploy/new is RequireAuth; anon agents deploy via stacks).
New methods (all mirror the existing provision-method pattern, the 120 s
provisioning-timeout client for slow build pods, and the full APIError
envelope handling):
- CreateStack -> POST /stacks/new (multipart manifest + per-service tarballs;
the documented anonymous deploy path). Returns *Stack (slug/status/tier/env/
expires_in). Synthesises instant.yaml from []StackServiceSpec.
- GetStack -> GET /stacks/:slug (poll status + per-service URLs).
- ProvisionVector -> POST /vector/new (pgvector; optional Dimensions hint;
returns *VectorResult embedding ProvisionResult + Extension/Dimensions).
- DeploymentEvents -> GET /api/v1/deployments/:id/events (rule-27 failure
autopsy: kind/reason/exit_code/last_lines/hint; ExitCode is *int32 for null).
Verified each request/response shape against api router.go + the stack/vector/
deploy handlers before wiring. Brief said /stacks/new takes a JSON
{services/name/env} body — the api actually takes multipart/form-data (manifest
YAML + tarball-per-service); implemented to the real contract and synthesise the
manifest client-side. Create response key is stack_id (mapped to Stack.Slug);
there is no top-level url on create (per-service URLs come from GetStack).
Tests: httptest contract tests for each method (success + error-envelope +
preflight + reachable transport/build/decode arms). A writeStackMultipart seam
takes an io.Writer so the multipart error arms are reachable via a failing
writer. 100% patch coverage (185 diff lines, 0 missing); project floor 98.4%.
go build/vet, go test -short -race, gofmt, golangci-lint (0 issues) all green.
README method tables + anonymous-deploy/autopsy examples + CHANGELOG updated.
Co-Authored-By: Claude Opus 4.8 <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
Closes the SDK endpoint-coverage gap surfaced by the live SDK durability probe. The SDK could provision db/cache/nosql/queue/storage/webhook +
Deploy, but was missing methods for endpoints that exist and are live on prod — most importantly it could not deploy anonymously (Deploy→POST /deploy/newisRequireAuth; the documented anonymous deploy path isPOST /stacks/new, which the SDK lacked).New methods
All mirror the existing provision-method pattern, the 120 s provisioning-timeout client (build pods are slow), and the full
APIErrorenvelope handling.CreateStack(ctx, CreateStackOpts) (*Stack, error)POST /stacks/newinstant.yamlfrom[]StackServiceSpecand uploads it + per-service tarballs asmultipart/form-data. Async (Status:"building").GetStack(ctx, slug) (*Stack, error)GET /stacks/:slugIsNotFound.ProvisionVector(ctx, *VectorOpts) (*VectorResult, error)POST /vector/newDimensionshint (0 → server default 1536).VectorResultembedsProvisionResult+Extension/Dimensions.DeploymentEvents(ctx, id, limit) (*DeploymentEventList, error)GET /api/v1/deployments/:id/eventsExitCodeis*int32(null-distinguishable).Contract verification (against
api/internal/router/router.go+ handlers)Each request/response shape was verified against the live api before wiring.
One deviation from the brief, implemented to the real contract: the brief suggested
/stacks/newtakes a JSON{services/name/env}body. The api actually takesmultipart/form-data— amanifestfield (instant.yaml) + one tarball field per service (keyed by service name) +name+ optionalenv. The SDK builds the manifest client-side from the service specs. The create response key isstack_id(mapped toStack.Slug); there is no top-levelurlon create — per-service URLs come fromGetStack(services[].url).Tests (mirror existing httptest contract-test style)
vector_test.go— happy path (body assertions), dimensions-omitted-when-zero, error envelope, preflight + empty-token/url branches.stack_test.go— happy path (multipart parse + manifest assertions), env-omitted, error envelope, preflight (no services / empty name / nil tarball / duplicate), tarball read error, two-service sort, transport/build-request/decode error arms,GetStackhappy + 404 + empty-slug.stack_internal_test.go— white-box sweep ofwriteStackMultipartfailing-writer arms (manifest/name/env/CreateFormFile/io.Copy/Close).deploy_events_test.go— happy path (nullable exit_code), default-limit, 404, 401.A
writeStackMultipart(io.Writer, ...)seam makes the multipart error arms reachable (a*bytes.Buffercan never fail them).Gate (all green)
go build ./...✓ ·go vet ./... ./examples/...✓go test ./... -short -race -count=1✓ (CI command)gofmt(real, via$(go env GOROOT)/bin/gofmt) ✓ ·golangci-lint run ./...→ 0 issuesDocs
README method tables + anonymous-deploy/autopsy usage examples + "what's not covered" trim; CHANGELOG
Addedentries.🤖 Generated with Claude Code