The RuntimeFunctionNotFound response now documents all three 404 bodies:
- application/json (self-hosted): {error: 'Function not found or not active'}
- application/json (cloud Subhosting): {error: 'Function not found'}
- text/plain (local catch-all): 'Not Found'
Uses examples: (plural) for the two JSON variants so generated clients
can distinguish them. Does not imply Content-Type alone separates all cases.
Closes#1979
COMPUTE_DEFAULT_INGRESS had no effect on service creation, and neither did the
stored setting that replaces it. The service layer resolved an omitted ingress
to `capabilities.ingressModes[0]` — a static list whose first entry is 'none' —
so the provider's ingressFor() never saw an undefined value to fill in, and the
env var was dead from the day it shipped. Found by setting the new dashboard
default to `port` on a real daemon and watching the container come up private.
The default is now a provider method rather than a position in a list, because
for a single-host driver it is an operator setting that changes while the
process runs; a static list cannot express that. Both the stored row and the
launch call read it, since a row that disagrees with the container is the
failure the capability layer exists to prevent.
Reported in the /api/metadata compute slice too, and the create dialog
preselects it. Without that, the dialog showed 'none' while the server would
have applied 'port' — the setting would look ignored from the one screen that
offers it. Optional in the schema so a client keeps working against a backend
that predates the field.
Also parallelises the compute logs limiter test. 121 sequential supertest
round-trips timed out at 10s on a loaded machine; the memory store increments
synchronously, so concurrent requests still take distinct slots and the same
assertion holds in 60ms.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The proxied /functions/{slug} routes forward Deno runtime responses verbatim.
The runtime returns two distinct 404 shapes:
- JSON {error: string} when a valid slug has no active function
- Plain text 'Not Found' when the path fails the runtime's slug regex
Previous spec documented only application/json (referencing ErrorResponse),
so generated clients would throw parsing 'Not Found' as JSON.
Changes:
- GET, POST, PUT, PATCH, DELETE on /functions/{slug}: add dual 404 content
(application/json + text/plain) with inline schema (not ErrorResponse ref)
- Add missing 404 to PUT, PATCH, DELETE invoke operations
Closes#1926
Four small gaps, all mine, none needing a decision.
The /api/metadata response gained a compute slice but openapi/metadata.yaml never
described it, though it enumerates auth, database, storage, functions, realtime and
deployments. Added with the per-provider capability shape and an example. Worth
recording: there is no openapi/compute.yaml at all, so the build endpoint's absence
from the contract is part of that pre-existing hole rather than something this branch
introduced.
The markdown metadata export omitted compute for the same reason. It now renders a
Compute section, spelled out as what a client should stop offering (regions no,
scale-to-zero no) rather than dumping the capability object, and stays absent when no
driver is configured -- absence is how a client detects that compute is off.
listMachines passed a bare name to Docker's name filter, which is an unanchored regex
over container names, so `api` also matched `api-v2`. Now anchored (allowing for the
leading slash Docker puts on container names) with an exact-match filter behind it,
since reporting a sibling service's container as this service's instance is how the
wrong container gets stopped. No caller today; this was a trap waiting for one.
The ingress docblock I added earlier landed between protocol's docblock and protocol
itself, so the Edge-protocol comment documented ingress and protocol was left
undocumented. Reordered. updateServiceSchema was already correct.
All four verified: the openapi YAML is parsed and asserted against the schema shape,
and the other three have tests that fail when the fix is reverted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
schedules.delete_job() signals a missing row the same way it signals a real
failure, by returning success = false. deleteSchedule mapped every false
result to a 500 DATABASE_INTERNAL_ERROR, so deleting a schedule that does not
exist, or deleting the same schedule twice, reported a server error even
though nothing was wrong on the server.
GET and PATCH on the same id already return 404 SCHEDULE_NOT_FOUND, so this
also makes the three id routes agree. Check for the schedule first, mirroring
updateSchedule, and leave genuine delete_job failures as 500.
Also document the 404 on DELETE /api/schedules/{id} in openapi/schedules.yaml,
which listed it for GET and PATCH but not for DELETE.
The endpoint is admin-gated, per-project, and the client reconnects with
exponential backoff, so the limiter guarded against a load profile the
endpoint cannot realistically see.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- replace verifyProjectAdminJwt with the standard verifyAdmin middleware
(accepting API keys like every other admin endpoint) and delete the
dedicated middleware and its test
- remove the never-published 'realtime' dashboard event resource from the
shared schema and the client invalidation mapping
- stop joining sockets to role:/user: rooms; nothing has broadcast to them
since dashboard events left Socket.IO
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- rate-limit SSE connection attempts (30/min per IP) per API guidelines
- parse table references defensively and isolate the event handler so a
malformed producer name cannot reject the stream read loop
- release the stream reader when the read loop exits abnormally
- cancel the response body before throwing on non-ok stream responses
- document GET /api/dashboard/events in the OpenAPI specs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Security
- Add an otp_type discriminator to auth.email_otps and scope numeric-code
verification to NUMERIC_CODE rows, so a wrong 6-digit guess can no longer
consume a live magic-link (HASH_TOKEN) reset/verify token for the same
(email, purpose).
- Case-insensitive user lookup in signInWithOTP so a mixed-case OAuth email
no longer creates a duplicate lowercased account.
- Re-sanitize non-AppError failures in the OTP verify paths (no raw infra
error messages in 500 responses).
Refactor
- AuthOTPService.consumeNumericOTP owns the OTP transaction: it persists the
attempt counter on failure, commits caller work + consumption together, and
rolls back on caller error. signInWithOTP, verifyEmailWithCode and
exchangeCodeForToken route through it; remove the dead verifyEmailOTPWithCode.
- Resolve disableSignup via AuthConfigService; STRING_AGG(DISTINCT) providers
and drop the user_id reassignment in the email provider upsert; reuse
EMAIL_TEMPLATE_TYPES in the cloud provider.
Schema / contract / docs
- Session preprocess treats null/undefined method as the legacy password flow;
shared six-digit code schema; trim name before the length check.
- Add request-otp to the OpenAPI email-template enum; document the
unverified-account password-clearing behavior in the four REST guides.
Tests updated/added across the OTP service, route, schema, and migration suites.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses second-round review: an isBucketPublic DB failure between
getObjectStream and the close-handler registration would leak the open
S3 socket. Also sync the remaining AWS_REGION-only mentions in the
storage OpenAPI spec with the S3_REGION fallback, and add the
credential-override and proxy-mode notes to the README storage section.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Replace error-message string matching with a typed ObjectNotFoundError
for missing blobs; the S3 gateway GetObject handler now also maps it
to NoSuchKey instead of a 500.
- Set Cache-Control: private, no-store on proxied private-bucket bytes.
- Pin MinIO/mc/RustFS image tags in the overlays and test rig.
- Defer the dashboard gateway-config fetch until the dialog is open.
- Document the `available` field and S3_REGION fallback in the storage
OpenAPI spec; sync the security-guide env example; use S3_BUCKET in
the RustFS test rig instructions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The handlers for these endpoints accept admin API keys (x-api-key header)
via verifyUser/verifyAdmin middleware, but the specs only declared bearerAuth.
This aligns them with the other 10 specs that already declare both schemes.
- email.yaml: add apiKey to 1 operation (POST /api/email/send-raw)
- functions.yaml: add apiKey to 5 admin operations
- logs.yaml: add apiKey to 3 admin operations
Closes#1757
Following the move to standard S3 PUT semantics (create-or-replace, no
server-side rename), tighten the surface:
- Remove the autoKey upload-strategy flag from shared-schemas, the route,
and getUploadStrategy. It duplicated the existing POST /objects
server-key path and isn't an S3 concept; the SDK's uploadAuto mints the
key client-side instead. generateObjectKey stays for POST /objects.
- Restore the dashboard's friendly auto-rename, which the S3-semantics
rework had dropped (leaving duplicate uploads to silently overwrite).
Since PUT no longer 409s, the dashboard now proactively lists and picks
the next free "name (N).ext" before uploading, reserving keys chosen
earlier in the same batch. Client-only; the API never renames.
- confirm-upload returns 200 (was 201) to match the direct PUT route:
both finalize a create-or-replace and neither reads back to distinguish
create vs replace.
- Update OpenAPI and all REST/TypeScript doc locales accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Direct upload-strategy uploadUrl now carries ?upsert=true when upsert
was requested, so the strategy contract is self-contained for clients
that redeem the URL verbatim (the SDK appended the flag itself, which
masked the gap).
- confirmUpload's upsert path is a single INSERT ... ON CONFLICT DO
UPDATE, so concurrent upsert confirms can no longer race into a 409;
ownership stays out of the SET clause, matching putObject.
- Dashboard auto-rename retries feed rejected candidates back into the
next-key computation, so retries always advance even when the capped
substring listing misses the conflicting key.
- putObject upsert comment now pins why no rowCount check is needed
(the conflict arm raises 42501, mapped to 403), plus a unit test
driving a 42501 through putObject({ upsert: true }) → 403 with no
provider write.
- upload-strategy and confirm-upload bodies are validated with the
shared zod schemas (consistent 400s; size: 0 is now accepted on
confirm since the provider size check is authoritative).
- openapi/storage.yaml documents the new contract: PUT upsert query
param with 200/403/409, upload-strategy upsert/autoKey fields, and
the confirm-upload upsert field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>