Commit Graph

374 Commits

Author SHA1 Message Date
Jakub Zych
104db9959f fix(pier): refuse path values the mux never delivers and harden dispatch
- a path field must be required in the reflected schema (boot error), and
  a missing value, an empty, . or .. segment, or a / inside one segment is
  a tool error before dispatch (A-WR-01, A-WR-02)
- a failed guard dispatch logs its cause and answers a fixed tool text;
  only an invalid token and an unknown or deactivated account keep their
  own fixed texts (A-WR-08)
- path and query scalars decode with UseNumber, so integers above 2^53
  keep their digits (A-IN-04)
- the dispatched request carries the outer Host, X-Forwarded-Proto and
  X-Forwarded-Host (A-IN-05)
- an embedded struct on a GET or DELETE tool fails boot (A-IN-07)
- a dispatch whose guard list or principal kind contradicts the tool's
  guard is refused (A-IN-08)
2026-10-23 16:05:32 +02:00
Jakub Zych
240457291f fix(pier): widen returning identities, warm the Redis store, end stateless listen streams, refuse personal tokens without a TokenID
- Identity.fingerprint covers scopes, both subjects, the ceiling and the
  token kind; a stateful lookup or stateless hit whose fingerprint changed
  re-filters the server with the event diff under its own mutex (A-WR-04)
- Mount warms a Redis store in the background with a 1 s to 30 s backoff
  and every request kicks a pending open without waiting, so stateless-only
  instances subscribe to fan-out (A-WR-05)
- a stateless subscriptions/listen POST re-verifies its bearer every
  recheck interval; an LRU-evicted stateless server closes its sessions
  (A-WR-06)
- resolvePersonalToken refuses a subject with TokenID 0, and the
  pact.MCPPersonalTokenResolver contract says one resolver per application,
  false means unauthorized and TokenID keys the binding (A-WR-09, A-IN-03)
2026-10-23 15:59:54 +02:00
Jakub Zych
c1c0c77ddc fix(pier): reserve session slots atomically and build servers outside router locks
- an unexported reserver (Reserve, Release) next to updater: the memory
  store reserves under its mutex, the Redis store in one Lua script
  (prune, SCARD, SET PX plus SADD); serveNew reserves instead of Count
  then Put and releases on a failed connect or initialize (A-WR-03)
- serveNew, lookup's rehydration and the stateless getServer build the
  SDK server with no router-wide lock held and insert or adopt under the
  lock afterwards, so a slow guard or database check no longer stalls
  the instance (A-WR-07)
2026-10-23 15:56:57 +02:00
Jakub Zych
2bf9dca603 fix(pier): reap stateful sessions whose store record expired
- a reaper started at Mount checks every local session against the store
  every min(idle_ttl/2, 1m) and closes each copy whose record is gone,
  logging the expired step (A-CR-01, A-IN-06)
- the GET stream re-check ends the stream when the record is gone (A-IN-01)
- tool calls served without a session on the stateful path log the
  session as ephemeral (A-IN-06)
- TestPierReaperSmoke and the ReapNow, LocalSessions, StopReaper seams
2026-10-23 15:50:04 +02:00
Jakub Zych
0b8e027b81 test(19.7-05): security review pins for the MCP endpoint's outer layers
- TestEndpointGuards: a foreign browser Origin is 403 before the bearer is
  read, a body over the endpoint cap is 413, a long GET stream ends once
  its grant stops verifying (T-19.7-18, T-19.7-19)
- TestDispatch: an argument key the input struct does not declare is
  refused before dispatch (T-19.7-17)
- the gate requires TestEndpointGuards (untagged and tagged) and runs the
  demo's deploy check (T-19.7-37)
2026-10-23 14:26:26 +02:00
Jakub Zych
f14eae96dd test(19.7-05): framework unit tests for every validation row and the gate's admin stage
- pier: TestCatalog, TestDispatch, TestGuardRules, TestSessions (Redis
  across two instances and the memory store), TestFanOut; the store clock
  and stateless-server seams in export_test.go
- surf: TestStreamingRoute (throttle, CORS profile and access-log pattern on
  a stream route) and TestPierMount in both builds; the streaming smoke
  restores the mount hook it replaces
- wristband TestMCPScopes; cabana TestAccountContributions (Postgres part:
  transaction and unchanged My account payloads), TestRecordActionPrompt,
  TestScopeLimitForMethods; TestRecordActionPromptRun pins the empty Value
  of an action without a prompt
- vitest: 403 and failed-load cases for My account contributions, the
  cancelled prompt
- gate: every name required (tagged runs included), account.go unchanged
  since 51d088a, admin stage live
2026-10-23 14:04:16 +02:00
Jakub Zych
1111828e8a fix(pier): an ended session is never written back by a request still in flight
- persisting a session's protocol state after a request now rewrites an
  existing record only (Redis SET XX, the memory store likewise) and closes
  the local copy when the record is gone
- before, a request that finished after DELETE, a revoked or deleted subject
  event or the idle expiry put the record back, and the next request
  rehydrated the ended session
- found by plan 19.7-05's TestFanOut (revocation and deletion cases, about
  one run in a hundred)
2026-10-23 13:55:04 +02:00
Jakub Zych
c29d2289d0 fix(pier): a tools/call without arguments or with null arguments is an empty object
- MCP's tools/call arguments are optional; pier validated a JSON null
  against the input schema and refused every call of a tool without
  required input that an agent sent without arguments
- found by plan 19.7-05's TestDispatch
2026-10-23 13:33:24 +02:00
Jakub Zych
3bbbe6c11e test(19.7-05): phase gate with the go and marker stages, pier test helpers, TestToolsList and TestDispatchMarker
- scripts/check-phase19.7.sh: fail-closed multi-repo gate (self-test, go,
  marker; plugins, consumers, docker, admin and docs are stubs until the
  later tasks fill them)
- modules/pier/helpers_test.go: the plan 02 fixtures moved out of the smokes
  plus fakeGuard, fakeVerifier, fakeLinkResolver, fakePersonalResolver,
  routePlugin, newApp, twoInstances, sdkClient and fireSubjectChanged
- TestToolsList (pier), TestDispatchMarker (bouncer, cabana with a Postgres
  part through the screens harness, which now keeps its app)
2026-10-23 13:31:41 +02:00
Jakub Zych
c49983d3f8 fix(cabana): the backend middleware refuses a dispatch marker carrying a frontend principal
- cabana wraps the bouncer backend guard middleware: a marker that lists the
  backend guard with Backend false answers 401 before the guard honours it,
  as ResolveBackend already does, so a frontend user id is never read as an
  administrator's by the admin API or a backend-guarded plugin route
- found by plan 19.7-05's TestDispatchMarker (admin API in-process case)
2026-10-23 13:27:25 +02:00
Jakub Zych
d96c726d6c fix(cabana): backend subject events reach pier as *pact.MCPSubjectChanged, fired after the transaction commits 2026-10-23 12:44:09 +02:00
Jakub Zych
604fee757e chore(quick-261022-k9k): rebuild embedded admin dist 2026-10-22 21:08:31 +02:00
Jakub Zych
49accf675e feat(quick-261022-k9k): admin save shortcuts and Create and new on record forms
- app/hotkeys.ts: dependency-free data-hotkey core (Ctrl/Cmd+S, Ctrl/Cmd+Enter,
  Ctrl/Cmd+Shift+S, Esc, / and ?), innermost-modal root, capture-phase modifier
  listener, platform labels and the help-dialog state
- Button hotkey prop (data-hotkey, aria-keyshortcuts); shortcuts installed by AppShell
- FormView: save keys and tooltips, Create and new on create forms via remountView
- phrasebook: form.create_and_new and the shortcuts group (en, pl)
2026-10-22 21:00:57 +02:00
Jakub Zych
8c6c0a3654 feat(19.7-02): identity kinds, ceilings, prompts and the MCP services page
- personal tokens only behind mcp.allow_personal_tokens (401 without a
  lookup otherwise); an admin token yields a backend-only identity with
  its scope limit per dispatched method, a user token a frontend-only one
- read/write ceiling and prompts proven by the tracer's subtests; the
  audit line asserted to carry the tool and never the arguments or token
- the mounted *pier.Pier is published on the application (test seams in
  export_test.go)
- docs/services/mcp.md with the tested acme.blog example, site.yaml note,
  cross-links from authentication.md, registration.md and the surf README
2026-10-22 18:34:39 +02:00
Jakub Zych
806e87c510 feat(19.7-02): Redis sessions, event fan-out, stateless protocol path and resource metadata
- store_redis.go: records with PX TTLs, per-binding sets, pub/sub events,
  opened lazily from redis.url under cooler's prefix (nothing dials at mount)
- events.go: pact.MCPSubjectChanged republished through the store; every
  instance recomputes the subject's sessions and stateless servers
  (list_changed) or ends them on revocation; the link cache is dropped
- stateless.go: the SDK's stateless handler over per-identity servers for
  2026-07-28 clients, routed by the protocol header or server/discover
- prm.go: RFC 9728 metadata at both well-known paths; long GET streams
  re-verify their bearer every minute
- TestPierSessionsSmoke, TestPierFanOutSmoke, TestPierStatelessSmoke on
  miniredis; README features, configuration and operational notes
2026-10-22 18:23:43 +02:00
Jakub Zych
cfdbaf0e47 feat(19.7-02): pier MCP module tracer with in-process dispatch and sessions
- modules/pier: Host contract, mcp.* config, catalog compiled from
  pact.HasMCP (prefixing, route binding, guard resolution, schema
  reflection, annotations), per-request identity through the published
  verifier and resolvers, built-in backend guard provider, in-process
  dispatch with the bouncer marker, per-identity SDK server with the
  per-request re-filter, stateful session router with rehydration over a
  memory store, audit lines
- modules/surf/pier_on.go: the summercms_mcp hook adapting the router to
  pier.Host; the untagged hello binary carries no SDK
- go.mod: go-sdk v1.8.0 and jsonschema-go v0.4.3 (decision note committed
  before this change)
- TestPierTracerSmoke: SDK client lists and calls acme_echo end to end
  through surf's compiled guard chain; README and root table row
2026-10-22 18:15:59 +02:00
Jakub Zych
fab99e0a00 chore(19.7-01): rebuild the embedded admin for account contributions and prompted actions 2026-10-22 16:38:37 +02:00
Jakub Zych
9f14cf603c feat(19.7-01): account contributions, prompted record actions and backend subject events
- pact.HasAccountContributions compiled at Activate; GET /account/contributions
  and POST /account/contributions/{name}/actions/{action} behind interactiveOnly
  (AccountContributionView, AccountContributionActionView, AccountActionResultView)
- RecordAction.prompt (RecordActionPrompt) for a pact.AdminRecordAction with a
  Prompt; the record action route accepts payload only for such an action
  (trimmed JSON string, non-empty, capped at max_length) and hands it to Run as
  Value through CRUDService.RecordActionWithValue; a prompt without a label
  fails boot
- fireSubjectChanged: pact.MCPSubjectChanged for revoked tokens, Administrators
  saves that changed role, permissions or superuser, Roles saves that changed
  grants, deactivation and deletion
- phrases backend::lang.form.action_prompt_required/_too_long (en, pl)
- smoke tests TestAccountContributionsSmoke and TestRecordActionPromptSmoke;
  TestRecordActionPromptRun on Postgres; route inventories, CSRF walk and
  OpenAPI conformance cases extended for the two routes
- regenerated admin/openapi/admin.json and admin/src/api/schema.d.ts; cabana
  README, docs/backend/{admin-controllers,users-and-permissions}.md
2026-10-22 16:31:29 +02:00
Jakub Zych
6887abf575 feat(19.7-01): SDK-free pact contracts for MCP, account contributions and prompted actions
- pact/mcp.go: HasMCP, MCP, MCPTool, MCPPrompt, MCPGuard, MCPGuards,
  MCPSubject, MCPDispatch, MCPTokenVerifier, MCPGrant, ErrMCPTokenInvalid,
  MCPPersonalTokenResolver, MCPLinkedBackendResolver, LinkedBackendUser,
  MCPSubjectChanged and its kinds, FrontendPermission, HasFrontendPermissions
- capabilities.go: AdminActionPrompt, AdminRecordAction.Prompt,
  AdminRecordActionInput.Value, HasAccountContributions, AccountContribution,
  AccountAction, AccountActionInput, AccountActionResult
- discovery tests, MCPGuards registry test and a go list -deps assertion
  that pact names neither the MCP SDK, jsonschema-go nor bouncer
- pact README and docs/plugins/registration.md rows
2026-10-22 16:16:35 +02:00
Jakub Zych
5c15dea569 feat(19.7-01): streaming routes, MCP mount hook, configurable authorize scopes
- surf.Router.MountStream and recoverStream: a raw, unbuffered route kind
  whose http.Flusher reaches the client; panic after the first byte only
  aborts the body
- surf.MCPBuildTag and the mountPier hook: mcp.enabled is inert without the
  summercms_mcp tag until it is true, then boot fails naming the tag
  (pier_off.go is the untagged side)
- wristband parseAuthorizeScopes takes the server's allowed set built from
  Options.ScopesSupported; the default set is unchanged
- cooler.KeyPrefix exported for the MCP module's Redis keys
- smoke tests TestStreamingRouteSmoke, TestPierMountSmoke, TestMCPScopesSmoke
- surf, wristband and cooler READMEs, docs/services/{routing,oauth-server}.md
2026-10-22 16:11:37 +02:00
Jakub Zych
1bd91a4522 feat(19.7-01): in-process dispatch marker and compiled handler lookup
- bouncer.Dispatch, WithDispatch, Dispatched: context-only pre-resolved
  principal marker honoured by Registry.Middleware for listed guard names
- cabana.ResolveBackend honours a backend-kind marker; additive
  ResolveBackendToken, ScopeLimitFor and BackendPrincipal resolvers
- surf.Router.Handler and compileHandlers: one compiled chain per route,
  shared by BuildRouter validation and compile
- smoke tests TestDispatchSmoke, TestDispatchedSmoke, TestDispatchMarkerSmoke
- READMEs and docs/services/{authentication,routing}.md updated
2026-10-22 16:00:19 +02:00
Jakub Zych
51e1a1ea6e chore(19.6.1-05): rebuild embedded admin dist
- modules/boardwalk/dist matches a fresh admin build
- remount-safe relation labels ship in the embedded SPA

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 14:42:18 +02:00
Jakub Zych
80bb5aee3d chore(quick-261022-imo): rebuild embedded admin dist 2026-10-22 13:53:06 +02:00
Jakub Zych
cb460b6000 docs(19.6.1-04): state Administrators outranking on the D-14 seam
- cabana, not the contributor, applies guardOutranked before a contributed Run
- A contribution to summercms.cabana.users is 403 user.outranked when the target outranks the caller

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 13:30:40 +02:00
Jakub Zych
068adaabf9 fix(19.6.1-04): refuse code-declared role capture and wrap Administrators Runs
- Roles create and update refuse a submitted code that code-declared grants attach to
- compileActionExtensions wraps contributed Administrators Runs with guardOutranked
- Prove both holes on Postgres; keep the boot smoke callable with a superuser principal

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-22 13:29:38 +02:00
Jakub Zych
085179b676 test(19.6.1-03): trigger boot rules and per-record permission locks
- TestFieldTriggerBoot (-short): every compileTrigger and checkTriggers
  refusal names controller, field and trigger; disable and hide reach the
  localized schema; settings and relation forms refuse the key
- TestPermissionRecordLocks (-short): the save rule in radio and checkbox
  modes, no lock lookup on create, meta only for stored records with a
  provider
- TestRolesCodeDeclaredLocks (Postgres): locked_by_code grants on a role,
  a save without them, locked_self on the own record
2026-10-22 12:11:57 +02:00
Jakub Zych
1a56294d9b test(19.6.1-03): last-superuser race, lifecycle, revocation, seam and roles tests
- TestAdministratorsLastSuperuser (D-09): A and B reduce each other on the
  five paths while a test transaction holds the guard's advisory lock until
  pg_locks shows both requests waiting; one succeeds, one gets last_superuser
- TestAdministratorsLastSuperuserConcurrent: ten unsynchronized rounds
- TestAdministratorsLifecycle (D-10) and TestAdministratorsRevocation (D-12),
  two-row bulk selections included
- TestActionExtensionBoot (-short) and TestActionExtensionRun (D-14, D-15)
- TestRolesLifecycle (D-16, D-17), TestRolesPermissionCeiling (D-07)
- TestBackendScreensActivation (-short, D-01, D-04)
2026-10-22 12:09:12 +02:00
Jakub Zych
83240706b2 fix(cabana): bulk lifecycle actions change every selected administrator
actionDB returned the Unscoped instance of the action's session, whose
conditions accumulate, so a bulk activate, deactivate or restore of two
applicable rows wrote only the first (the second update matched
"id = A AND id = B") while still counting it. A bulk deactivate
therefore left the other administrators active and signed in (D-10,
D-12). actionDB now ends with a plain Session, so every query starts
anew. Found by TestAdministratorsLifecycle.
2026-10-22 12:04:01 +02:00
Jakub Zych
b684bb7d8d test(19.6.1-03): save-path privilege rule tests
- TestAdministratorsSuperuserHidden (D-03, scoped-token superusers)
- TestAdministratorsSuperuserProjected (Pitfall 1)
- TestAdministratorsPermissionCeiling and TestAdministratorsRoleCeiling (D-06, D-07)
- TestAdministratorsSelfEdit (D-08), TestAdministratorsPasswordAndIdentity (D-11)
- TestAdministratorsOutranked (D-19), TestAdministratorsOwnPassword (D-20)
- TestRoleCeiling (-short) against grantsCovered
2026-10-22 12:01:06 +02:00
Jakub Zych
461cb21b3e fix(cabana): enforce the 4-character password minimum on the Administrators form
The form's min:4 rule never applied: the model validator skips string
min and max. storePassword now refuses a typed password under 4
characters with a 422 on the field, next to the 72-byte check (D-11).
Found by TestAdministratorsPasswordAndIdentity.
2026-10-22 12:00:18 +02:00
Jakub Zych
536ea8c1bf test(19.6.1-03): screens test env and the superuser flag rule over HTTP
- administrators_harness_test.go: newScreensEnv with extra plugins, seedAdmin,
  seedRole, login, isolateSuperusers, row snapshots and error decoders; the
  harness moved out of backend_screens_smoke_test.go and gained cleanup
- export_test.go: SuperuserLockKey for the last-superuser race
- TestAdministratorsSuperuserFlag (D-05) through the assembled handler
2026-10-22 11:54:48 +02:00
Jakub Zych
7091d09699 feat(19.6.1-02): field trigger and admin build
- fields.yaml trigger (action disable|hide, field, condition checked|unchecked) compiled to FormField.Trigger
- boot errors for malformed triggers, controlling fields that are not a switch or checkbox, and triggers in repeater rows, settings and relation forms
- Administrators permissions overrides are disabled while Superuser is on (D-06)
- SPA: trigger.ts (triggerState, applyTriggers), FORM_TRIGGER_NOTES, the trigger note in FormField, FormView tabs/banner/payload after triggers, PreviewView hide only
- regenerated admin OpenAPI and schema.d.ts; rebuilt modules/boardwalk/dist; docs and README
2026-10-22 11:43:58 +02:00
Jakub Zych
705f94d9e5 feat(19.6.1-02): per-record locked permission values
- cabana.PermissionRecordLock and the optional PermissionRecordLockProvider
- RecordMeta.PermissionLocks (meta.permissionLocks) on show and save responses, notes localized
- save rule: a record-locked code keeps its stored value, a differing present value is 403
- Roles lock code-declared grants (locked_by_code); Administrators lock every override on your own record (locked_self)
- SPA: FORM_PERMISSION_LOCKS, locked codes never sent, record-locked rows in the permission editor
- regenerated admin OpenAPI and schema.d.ts; docs and README
2026-10-22 11:29:25 +02:00
Jakub Zych
653307de0c feat(19.6.1-01): Roles screen
- Settings -> System -> Roles (summercms.cabana.roles, backend.manage_roles,
  order 110): name, code slugged from the name, description, checkbox
  permission editor with the editor's ceiling
- system roles (is_system or code-declared grants) keep their code and
  cannot be deleted; a role any admin holds (deleted ones included) cannot
  be deleted, the refusal names the role and the count
- role codes unique among roles
2026-10-22 11:18:30 +02:00
Jakub Zych
530b02af9c feat(19.6.1-01): administrator lifecycle and last-superuser protection
- activate, deactivate and restore as record and bulk actions; delete is
  the soft delete through deleted_at, never a hard delete
- deleted admins listed and opened (unscoped), deleted/deactivated row
  states, Deleted filter (hide / only), preview status hint
- deactivate and delete bump tokens_valid_after; activate and restore do not
- self delete and self deactivate refused; outranking and last active
  superuser guarded on delete, deactivate and bulk rows (whole selection
  rolls back)
2026-10-22 11:13:26 +02:00
Jakub Zych
88401737c9 feat(19.6.1-01): administrator form with server-side privilege rules
- create and edit administrators: password (min 4, max 72 bytes, bcrypt
  at admin.password.bcrypt_cost), role picker, radio permission overrides,
  superuser switch; no mail on create
- superusers hidden from non-superusers in list, lookups and schemas;
  is_superuser projected to superusers so an untouched save keeps it
- save-path rules: superuser-only switch, role and permission ceilings,
  no outranking, no own role/flag/overrides/password change, last active
  superuser not demoted (advisory lock + count in the write tx)
- password set for another admin bumps tokens_valid_after
- role, superuser, activation and last sign-in list filters
2026-10-22 11:08:12 +02:00
Jakub Zych
7340241f22 feat(19.6.1-01): Administrators screen and cross-controller record actions
- pact.HasAdminActionExtensions / AdminActionExtension: plugins add record
  actions to any admin controller by ID, merged at boot before permission
  validation (unknown target, no preview, duplicate name stop the boot)
- internal summercms.cabana pseudo-plugin, appended only when some plugin
  registers admin controllers: Administrators list and preview, settings
  link under System, backend.manage_users and backend.manage_roles with no
  role grants
- backend::lang user, user.role and permissions copy (en, pl)
- smoke tests: compile-time seam and settings, Postgres list/preview/action
2026-10-22 10:59:23 +02:00
Jakub Zych
139f5e3942 fix(lagoon): keep Status plugin-only; migrate:status adds the framework sets itself (WR-12)
57a849d made lagoon.Status read migrationSets, which changed its result
for consumers: the framework sets came first and a plugin with no
migrations lost its row. Status is back to its pre-57a849d behaviour.
migrate:status (table and --json) now reads the unexported setStatus,
which lists the framework sets and the plugins from migrationSets, so it
still agrees with migrate --pretend. Both share historyRows.

- TestStatusPluginRows pins Status: plugin rows only, an empty row for a
  plugin with no migrations or no history table, none without HasMigrations
- TestMigrateStatusJSON also checks the table lists the framework sets
- TestTwoPluginMigrationSetsIsolated and TestDocsMigrate are back to the
  plugin-only rows
2026-10-22 04:30:36 +02:00
Jakub Zych
0691d26f1f fix(lagoon): warn instead of failing when the re-read after a successful migrate fails (WR-13) 2026-10-22 04:04:32 +02:00
Jakub Zych
57a849d114 fix(lagoon): migrate:status reads the sets migrate applies (WR-12) 2026-10-22 04:03:19 +02:00
Jakub Zych
17c722da46 fix(summer): mask secret flag values in a delegate's error (WR-10) 2026-10-22 04:00:57 +02:00
Jakub Zych
707372bf65 fix(bonfire): mask secret flag values that start with a dash (WR-11) 2026-10-22 03:57:55 +02:00
Jakub Zych
fce232d7c3 fix(bonfire): end a live view when its context is cancelled and print later lines plainly (WR-09) 2026-10-22 03:56:37 +02:00
Jakub Zych
1ac75ad59a fix(bonfire): claim the live view slot before building the program (IN-15) 2026-10-22 03:53:29 +02:00
Jakub Zych
48e13f9b57 fix(bonfire): strip control characters from Process's plain item lines and the view's label (WR-14) 2026-10-22 03:52:37 +02:00
Jakub Zych
7c1509581b docs(bonfire): the ErrReported comment names the WithExitCode code (IN-14) 2026-10-22 03:51:55 +02:00
Jakub Zych
9516a2b36f feat(cabana): ClearOnChange clears dependent columns when a relation changes a foreign key
RelationContract (hasMany) and FieldRelationContract (belongsTo) accept
ClearOnChange, a list of nullable columns set to NULL whenever the relation
changes the foreign key: a hasMany link and unlink (deferred ones included)
clear them on the child, and a form save that moves a belongsTo key to
another record or to none clears them on the record unless the same save
writes the column through its own field. Boot refuses the option on a
belongsToMany and for a missing, non-nullable, foreign-key or primary-key
column. The zero value changes nothing.
2026-10-22 03:30:35 +02:00
Jakub Zych
020fe838ea fix(lagoon): one migration set list for Migrate and PendingMigrations
- Migrate iterates migrationSets, so the startup summary, --pretend, the
  production question, the applied lines and migrate:status --json read
  the same list; each set keeps its error wording through a label
- migrate reports pending-before minus pending-after as its applied
  lines and result line, also when Migrate fails partway (then it prints
  what was applied and returns the error)
- TestMigrateAppliedLines (Docker) pins both (WR-07)
2026-10-22 02:36:19 +02:00
Jakub Zych
a9025c0d12 fix(bonfire): strip control characters from error blocks, process views and tables
- bonfire.Sanitize (additive), moved from diary's console channel: tab
  kept, CR and LF to a space, every other control character dropped
- applied before styling to every error-block line (message,
  explanation, Try lines, -v chain), process-view item names and errors,
  the current item of spinner and progress, failure lines, and table
  headers and cells in both layouts
- diary's console handler calls bonfire.Sanitize; README and the
  writing-commands page say so (WR-05)
2026-10-22 02:31:55 +02:00
Jakub Zych
39677f1819 fix(diary): retire replaced daily writers and serialise ApplyGlobals
- install and Reset retire the channels they replace: a retired daily
  writer appends a late record and closes the file again, so no record is
  lost and no descriptor stays open; diary.Close keeps reopening the
  installed writers on the next record
- ApplyGlobals holds the setup lock for its whole run (lock-free
  installLocked and setVerbosityLocked), so concurrent calls rebuild the
  channels once (WR-06)
2026-10-22 02:27:39 +02:00