The prefix was only trimmed. Without a leading slash ServeMux read its first
segment as a host pattern, so every upload silently answered 404, and a CDN
URL panicked in ServeMux and surfaced as a misleading route conflict. With
storage.uploads.serve on, BuildRouter now refuses a prefix that is not an
absolute URL path (scheme, host, query, fragment, whitespace, wildcard
braces, empty, . or .. segments, or a bare /) with an error naming the key.
The route answered with the blob's stored Content-Type, which attach.Store
sniffs from the bytes, so a public text upload starting with <html> was
served as text/html on the application origin; nosniff does not help when
the declared type is already HTML. Every response now carries a sandboxing
Content-Security-Policy, and a successful response whose type is not a
raster image (JPEG, PNG, GIF, WebP, AVIF) gets Content-Disposition:
attachment, on GET and HEAD. Covers stay inline.
Every 5xx became 499 once the request context was cancelled, so a panic or
a real database error on a request the browser had abandoned passed the
phase gate's 5xx scan. Now a 5xx is logged as 499 only when the client had
already gone when the status was written and the handler did not panic;
recoverJSON and recoverBare mark the access log entry. Every record of an
abandoned request carries client_gone=true. The gate self-test plants a
5xx with client_gone=true and must refuse it.
An unmatched request logged its path. A token route reached with another
method (405) or with a trailing slash (404) is answered by ServeMux before
any route handler records the pattern, so the share or invitation token in
the path reached the log. The record now carries method, pattern, status
and duration only.
- TestUploadsMount: key off registers nothing; on, a public blob is served
with its content type and nosniff, HEAD has no body, private, missing,
partition and encoded traversal paths 404, an unpublished bucket or
database 404s, a custom prefix is honoured
- mountUploads refuses a plugin route at or under the prefix, so
BuildRouter fails instead of only an identical pattern failing at
Assemble (README and storage docs updated)
- TestAccessLog: pattern and status for 200, handler 404 and recovered
panic, path only for unmatched requests, locale-prefixed requests keep
the pattern, integer duration_ms, no query, header or Authorization
value, ResponseController flush still works
A client that goes away mid-request cancels the context; a handler then
fails on context.Canceled and writes a 500 nobody receives. The request
log now records it as 499 (client closed request) so a grep for 5xx shows
real server errors only.
- one Info record "http request" per request with method, matched route
pattern, status and duration_ms; path without the query only when no
route matched
- the matched route records its pattern through a request-scoped entry,
so locale prefix rewrites do not hide it
- logs through the published *slog.Logger or slog.Default; off by default
- surf README, logging and configuration docs describe the key
- surf mounts GET {storage.uploads.public_path_prefix}/{path...} on
attach.StaticHandlerPublic when storage.uploads.serve is true
- bucket and database resolved per request; X-Content-Type-Options: nosniff
- surf README, storage and configuration docs describe the key
- Add surf.Router.AdminPrefix (additive): the admin mount path and true
when BuildRouter mounted the cabana admin, false otherwise
- serve binds with net.Listen before printing, so a busy port fails
without a misleading URL, then prints the listen address with a
browser URL and an admin status line: the admin URL, a warning naming
summer admin:create when backend_users is empty or missing, or
admin: off with the steps to add an admin controller
- The administrator count is bounded by a 2 s timeout and never fails boot
- route:list ends with the same admin status line
- surf and cabana READMEs and the console and routing docs describe the output
Look up a backpack-published resolver so a compiled translate plugin can
strip an enabled URL prefix and write a validated locale onto context.
Co-authored-by: Cursor <cursoragent@cursor.com>
- every OPTIONS on a CORS path: 204 with Cache-Control no-cache, private
- a preflight echoes the requested method (upper-cased) and headers when * allows any, with Vary and PHP's default Content-Type, as recorded from PHP
- README and the routing docs describe the answer
- Wrap redacts sensitive keys at any depth and scrubs Bearer, sk- and x-api-key shapes
- InstallDefault is the first statement of the generated run; hello main regenerated
- surf test pins that recovered panics echo no credential
- sunscreen README, root modules row and the logging docs page
- TestOverlapConstraintFallsThrough: an earlier member that matches the
literals but not its Where constraint must let a later member answer;
the routes.php pairs cannot show this, since each member's own handler
re-checks its constraints and the pairs differ in their literals (T-13-23)
- surf: a transitive three-route family, HEAD and sorted Allow on family
paths, a family across two plugins with per-member middleware, refused
trailing-slash and multi-segment shapes
- conga: refusal messages name kind and queue, a configured queue counts
as served, a delayed unregistered dispatch waits scheduled
- lagoon: prohibited under a wildcard, on a dotted path and after bail
- tide: quoted, RFC 5987 and multi-date download names; a captured id
beside a masked notification id
- compile groups routes ServeMux refuses side by side into overlap families
and registers each under one generated method-less pattern
- the family handler tries members in registration order on literals and
Where constraints, sets their path values and runs their own wrapped chain
- no match answers the bare 404; a method mismatch answers ServeMux's 405
and Allow for the same table without the overlap
- unsupported shapes (same shape, {name...}, shadowing route) fail at boot
- README and docs/services/routing.md describe the behaviour
- docs/database: models, migrations, queries and pagination, relations,
casts and validation, attachments and transactions (lagoon.Transaction,
lagoon.AfterCommit, nested savepoints, lagoon.OnDatabase)
- docs/services: configuration, events, routing with auth groups, rate
limiting, authentication, the OAuth server, mail and localization
- runnable Examples for lagoon, attach, compass, surf, wire, bouncer,
wristband, postcard, phrasebook and festival; lagoon TestDocs* regions
run on the package's Postgres harness through DocsDB
- 15 new required pages
- Manager gains the apparatus JobManager surface: StartJob, UpdateJobState,
UpdateMetadata, FailJob, CancelJob (is_canceled + STOPPED + River JobCancel),
StopJob (STOPPED only), CheckIfCanceled and GetMetadata, all raw column
writes so updated_at is untouched
- serve starts the in-process worker unless queue.work_in_serve is false and
stops it on shutdown; an app without jobs gets an idle worker
- queue:work runs a foreground worker with repeatable --queue filters;
queue:clear deletes available, scheduled and retryable jobs of one queue
- the generated main appends conga.RuntimeCommands; summer delegates
queue:work and queue:clear; make:job scaffolds a conga.Job