- 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
- 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)
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.
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.
- 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
- 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
- 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
- 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
- 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)
- 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
- 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
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
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.
- 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)
- 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)
- 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)