- Global options -v/-vv/-vvv, -q, --ansi/--no-ansi, -n and --env on every
command; PeekGlobals reads them before config loads, OnGlobals hooks
re-apply them after parsing (diary.ApplyGlobals in the generated main)
- Bare binary, --help and list print the Artisan list with the version
header from BuildVersion and namespace blocks
- Execute runs the root through fang (no version, man page or completions)
and returns 0, 1 or 2; Fail renders errors before the root exists
- NewRoot rejects duplicate command names (ErrDuplicateCommand) and flags
that reuse a global option (ErrReservedFlag)
- Generated main and summer use PeekGlobals, compass.Open with --env,
diary.FromGlobals and bonfire.Execute; docs checker knows the globals
- River's logger is diary.For("river") (warn by default), conga's own lines diary.For("conga"); an app-published logger still wins
- GORM logs through logger.NewSlogLogger(diary.For("db")): warn level, record-not-found ignored, parameterized SQL traces only at verbosity 3
- Smoke tests TestRiverLoggerUsesDiarySmoke and TestGormLoggerUsesDiarySmoke
- stderr and null drivers; per-component levels from log.components
- SUMMER_LOG__ overrides parse as strings and overlay the defaults key by key
- Validation names the key: driver, format, level, days, stack children and cycles, daily path outside the root
- summer new writes config/log.yaml (stack of console and daily, river warn, http info)
- docs/services/logging.md: channels, config/log.yaml, component loggers, daily files, redaction
- Log keys in the configuration docs; ExampleFor backs the docs snippet
- New diary module: For, Bootstrap, Setup, SetVerbosity, Reset, Close, ParseConfig
- Proxy component handler safe in package vars; styled console handler in bonfire's palette
- Daily writer: one file per day, 0640, lazy open, retention, disables itself on open failure
- Every channel leaf wrapped by sunscreen.Wrap; stack via slog.NewMultiHandler
- bonfire: ColorMode, Palette, DefaultPalette, DetectProfile (lipgloss v2, colorprofile)
- Generated main runs diary.Bootstrap first and diary.Setup after compass.Load
- Ignore /storage/ and /examples/hello/storage/
- TestPullConcurrent: 64 concurrent Pulls of one key, one winner, 20 rounds,
on memory and miniredis
- TestPullExpired: an expired value is never returned and is gone afterwards
- TestPullPrefixIsolation: Pull touches only its own <prefix>cache:<key>
- TestPullDocker: the same on redis:7-alpine (GETDEL)
Negotiation table (Sec-Fetch-Mode, X-Requested-With, Accept q values, the
32-range cap, /api, /_ and admin paths), byte equality with the
http.error_pages.enabled false router for curl, fetch, XHR and JSON
clients, page content and headers for every covered status, details
gating and escaping, renderer override and fallbacks, the admin prefix
and the uploads route.
A browser navigation (Sec-Fetch-Mode navigate, or GET/HEAD without it; no
X-Requested-With; Accept naming text/html above every JSON type; not /api,
/_<name> or the admin prefix) that hits a framework-generated 404, 405 or
500 gets a self-contained SummerCMS page: unmatched routes and wrong
methods at the ServeMux, failed Where/WhereIn constraints, overlap-family
misses, the unpublished uploads route and recovered panics. Every other
request keeps the exact stdlib text, wire's opaque JSON 500 or the bare
raw-group 500.
Details (method, path, X-Request-Id) only with app.debug or outside
production. Applications replace the page with a published
surf.ErrorPageRenderer; http.error_pages.enabled false turns it off.
The record and relation-child thumb routes now also serve the thumbnail
of a public JPEG, PNG, GIF or WebP file, under the same record or
pending-session scope and D-10 headers as a protected one, so the admin
can preview a public image whose public URL does not load. The download
routes still answer 404 for every public file, and a public non-image
thumb stays 404.
TestPublicImageThumb covers owner, foreign and hidden records, image
types, a public row under a protected field, another admin's pending
upload and the relation-child route.
- a code is refused for another client or redirect URI and burned
- authorize refuses plain and malformed challenges; verifier rules; 60 s code TTL
- rejected token exchanges log client id, IP and reason only; key parse errors quote no key
- a public sub is a random UUIDv4
- capture rule from: response.form reads the value of the <input> with
the rule's name (a server-rendered form's one-shot token), so a parity
flow can post a consent form back
- README and parity docs list the capture sources
- DefaultOptions().Resource is empty; comments name the host application,
no consuming application (folded todo)
- Tests set an explicit neutral resource; TestDefaultOptionsResourceEmpty
- wristband README: new summary, two flavours, OIDC usage, API reference,
OIDC defaults, dependencies, testing; root README row matches
- docs/services/oauth-server.md: OpenID Connect provider section;
Resource described as required with an empty default
- ExampleNewOIDCServer and the newOIDCServer docs region
- Full validation chain with PHP order and messages; repeated params last-wins
- is_dev loopback port matching (matchOIDCRedirectURI); PHP http_build_query redirects, no iss
- One-shot consent token burned under row lock; deny; SwitchAccount; JSON-mode
ConsentRequest and DecideConsent
- ClientRecord.Kind with ClientKindMCP/ClientKindOIDC; each flavour refuses the
other kind at authorize, token and userinfo (D-08)
- Smoke tests: start, resume/decide/switch, kind isolation, PHP encoding
- NewOIDCServer/SetOIDCBackend build a second Server on the same engine;
Authorize and Token dispatch by flavour, Metadata/Register 404 on OIDC
- OIDCBackend/OIDCTx, KeyStore and OIDCHost seams; MCP Backend untouched
- Discovery (PHP field order), JWKS (stdlib JWK encoding), authorize with
resume and one-shot consent token, token with an RS256 id_token signed
through golang-jwt map claims, userinfo
- TestOIDCTracerSmoke verifies the id_token against the served JWKS
- One shared localizer for ValidationError and ForbiddenError, tested in
writeCRUDError's order; replaces the ForbiddenError-only one.
- Applied on the CRUD and relation service transactions, the detached bulk
branch, runAction, the record read (ShowRecord), and the reorder,
singleton and relation-options routes, which localized neither type.
- Only strings containing "::" are looked up, so translated text passes
through unchanged and logs no missing-key warning.
- Copy-on-write: the plugin's error value is never modified.
- README and backend docs describe the localized ValidationError.
- modules/breakwater exports Render(markdown) and Purify(html): goldmark GFM
in its default safe mode (raw HTML stripped, unsafe links dropped), then a
named bluemonday policy built from NewPolicy, not the UGC preset
- the policy is the PHP golem15/forum HTMLPurifier config 1:1: the fixed
element list, a[href|rel|target], http/https/mailto only, rel=nofollow on
every link, target=_blank plus noopener on fully-qualified links
- tests cover the PHP ForumMarkdownRenderTest corpus, links per scheme,
blank input, the Purify allow-list and concurrent use; testable examples
feed the docs page snippets
- module README, root module table row, introduction.md Utilities row and
docs/services/markdown.md (which gate serves user content vs admin fields)
- go.mod and go.sum unchanged: bluemonday v1.0.27 and goldmark v1.8.6 were
already pinned
- pact.NavigationItem gains Visible func(ctx) bool (json:"-"); nil keeps the
entry always listed
- cabana.Registry.Metadata runs it after the permission check, drops a main
item (with its side menu) or a side item that answers false, and relinks a
parent whose controller is a hidden child's to its first visible openable
child
- TestNavigationHidesInvisibleItemsAndRepointsTarget covers the rules
- pact and cabana READMEs, users-and-permissions and admin-controllers docs
describe the hook
pact.ListExtendRecords fills unstored fields of one list page before its
columns are read (WinterCMS listExtendRecords); such columns compile
unsortable and refuse sortable/searchable. pact.FormRecordTitle names a
record; the show, create and update responses send it as meta.title.
- a detached Run sees no transaction and holds no row lock (FOR UPDATE
NOWAIT from another connection succeeds), with the empty, absent and
partial-selection rules, a ValidationError as 422 and earlier writes kept
- the same action without Detached still holds the transaction and locks
- test-only actions on the roster fixture through its wrap hook; README
Testing names the test
- pact.AdminBulkAction.Detached (json:"-"): opt-in; records loaded through the list scope without row locks, no write transaction in the context
- cabana BulkAction: detached branch with the same empty, partial-selection, failure and message rules; lockScoped is loadScoped with lock, the locked path unchanged
- docs: admin-controllers bulk route list, pact and cabana READMEs
- principalFrom sets PreferredLocale from metadata admin_locale; personal
API token principals clear it, so token clients keep the request locale
- the guarded admin group runs locale.from-principal after the backend
guard, overriding the LocaleResolver or Accept-Language locale
- GET /lang honours a supported ?locale= and reports meta.locales
(cabana.LangEnvelope, cabana.LangMeta)
- Docker test for the override, refusals and /lang; docs and OpenAPI
- GET and PUT /account/preferences (own row only, interactive sessions,
CSRF header on cookie writes) store admin_theme and admin_locale in
backend_users.metadata, keeping every other key
- GET /auth/me and the My account answers carry preferences
- phrasebook.Translator.Locales lists the locales defining a prefix
- route tables, conformance cases, OpenAPI document, TS types and docs
- caching.md: an https purge_url on another host goes through the site's
proxy; set NUXT_SITE_CACHE_ALLOW_PROXIED=true there, the token is then the
only gate
- cooler README: the purge_url row points at it
- PurgeSiteCacheAfterCommit queues the purge on a per-purger worker after
commit: one request in flight, saves committed meanwhile collapse into
one follow-up, so the admin request never waits for the site
- new cooler.SiteCachePurger.Wait drains the queue; Handle.Close waits for
it at shutdown (timeout plus one second)
- Purge stays the synchronous send; exported names and signatures unchanged
- the purge client's CheckRedirect returns http.ErrUseLastResponse: a 3xx
is logged as a failed purge with its status and the bearer token never
reaches the Location
- BuildRouter logs one warning with the route count and the production
flag when routes name turnstile:open or turnstile:closed and the
verifier has no secret; the secret is never logged
- attach.Store returns attach.ErrProtectedNeedsPrivateBucket for an
is_public false upload while storage.uploads.public_url is set, before
any blob is written
- OpenBucket's public_url line becomes a warning naming the refusal
- LoadCookieConfig refuses a Secure that resolves to false in production,
also when inferred from an http or unset app.url, naming
auth.cookie.secure and app.url
- with supports_credentials, a * origin must be the whole leftmost host
label, patterns must be anchored with ^ and $, and no origin rule may
match the probe origins outside any configured domain
- globally and per profile; non-credentialed configs load as before
- Router.wrap applies the route's profile outside recovery, body limit and
locale, whatever its position in the list or the group nesting
- bufferedResponse.commit merges Vary instead of replacing it
- TestCORSProfileHeadersOnRefusals, TestCORSProfileVaryKeptWithHandlerVary
required by name in the gate
- miniredis v2.40.0 as a test-only dependency (approved at the Task 4 check)
- memory and Redis cache suites, Flush limited to <prefix>cache:
- one parity table through surf.MemoryStore, miniredis and real Redis
- Install, FromApp and site_cache validation; Postgres after-commit purge
- gate: cooler names in the go and docker stages
- manifest_tags_test.go: build.tags load, trim, dedupe and validation
errors naming build.tags; SaveManifest keeps tags and plugin order;
goBuildArgs puts one -tags before -o
- s3_test.go: s3:// refused without summercms_s3 naming the tag, opens
with it (both build modes, any scheme case)
- diskext_test.go: RESEARCH probe rows and table consistency
- url_test.go: public_url base joining, File.URL and thumbnails,
malformed values refused naming the key
- store_test.go: Postgres regression table through Store
- gate: attach names in both tag modes, Store regression in docker
- cors_profile_test.go: credentials with * refused (global and profile),
pattern errors, anchored wildcard origins, Vary: Origin on dynamic
answers, api/www credentialed case, unchanged * answers, profile
preflight routes and boot errors, turnstile factory params
- apidocs_test.go: off by default, pinned 5.33.1 page with the RESEARCH
SRI hashes and nonce CSP, ordered compact JSON, provider count, path
validation, escaped title
- store_lookup_test.go: MemoryStore fallback, published Store, no dial
- reorder_hook_test.go (Postgres): hook sees the new order in the tx,
its error rolls back, no hook answers unchanged
- gate: surf names in the go stage, cabana names in the docker stage
- scripts/check-phase19.3.sh: fail-closed gate (go stage, self-test, --all
with NOT RUN docker); a required name that fails, skips or is missing
fails its stage
- cookie_test.go: LoadCookieConfig defaults and errors, TokenCookie
attributes and refresh-window Max-Age, forget cookie, refresh and logout
handlers with a real Refresh and MemoryBlacklist
- turnstile_test.go: open/closed verdict matrix, timeout, form fields,
token sources, body restore, statuses and secret-free logs
- optional ReorderAfterSave(ctx, partition) runs after the new order is
written; an error rolls the reorder back through lifecycleFailure
- reorderPost runs in lagoon.Transaction so lagoon.AfterCommit work a hook
registers (a site-cache purge) runs only after the commit
- pact and cabana READMEs, admin hook table, tree reorder docs
- cooler.SiteCachePurger moved from the Journal plugin: loopback-only http,
32-character token, 2 s detached request, no token in logs or errors
- NewSiteCachePurgerFor names caller keys (for plugin alias keys)
- PurgeSiteCacheAfterCommit through lagoon.AfterCommit, one POST per save
- SiteCacheFromApp for hooks outside serve; Install publishes the purger
unless a plugin already did, and fails boot on an invalid config
- caching docs section, cooler README, configuration row
- OpenBucket validates public_url (absolute http/https, host, no user info, query
or fragment; the error names the key) and records it next to public_path_prefix
- a set public_url logs one start-up line: the bucket behind it must hold only
public files, since it bypasses the is_public check
- attach.PublicURLBase; PublicURL, File.URL and thumbnail URLs use the base when
set and keep the public_path_prefix proxy URLs otherwise
- TestPublicURLSmoke; storage Public URL base section, attachments, configuration
and lagoon README
- DiskExtension: a fixed table of specific sniffed types keeps a matching client
extension or uses the canonical one; generic sniffs keep the client extension
unless it is script-capable (then txt or bin); never asks package mime
- Store names the blob with DiskExtension(sniffed, ext); file_name, the allow-list
and the stored content_type are unchanged
- TestStoreDiskExtensionSmoke: a PNG uploaded as x.html is stored and served as .png,
report.docx keeps .docx, an HTML body named note.txt keeps .txt
- attachments docs on disk names, storage deploy note on nosniff and allow-lists,
lagoon README