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
This commit is contained in:
Jakub Zych
2026-10-22 11:29:25 +02:00
parent e2525d4956
commit 705f94d9e5
22 changed files with 588 additions and 60 deletions

View File

@@ -2301,6 +2301,25 @@
],
"type": "object"
},
"cabana.PermissionRecordLock": {
"properties": {
"code": {
"type": "string"
},
"note": {
"type": "string"
},
"value": {
"type": "integer"
}
},
"required": [
"code",
"note",
"value"
],
"type": "object"
},
"cabana.RecordAction": {
"properties": {
"confirm": {
@@ -2361,6 +2380,16 @@
},
"type": "array"
},
"permissionLocks": {
"additionalProperties": {
"items": {
"$ref": "#/components/schemas/cabana.PermissionRecordLock"
},
"type": "array"
},
"description": "PermissionLocks are, per permissioneditor field, the permissions this\nrecord fixes (the controller's PermissionRecordLockProvider), with the\nvalue to show and the note, translated in the request locale. The show\nand save responses carry it; it is omitted when no record lock applies\nand is never part of the form schema.",
"type": "object"
},
"title": {
"description": "Title is the controller's record title (pact.FormRecordTitle), omitted\nwhen the controller names none.",
"type": "string"
@@ -5936,7 +5965,7 @@
]
},
"get": {
"description": "meta.labels carries the display labels of relation fields. meta.actions lists the declared record actions the requesting admin may run and that apply to the record's current state; it is absent when none is offered.",
"description": "meta.labels carries the display labels of relation fields. meta.actions lists the declared record actions the requesting admin may run and that apply to the record's current state; it is absent when none is offered. meta.permissionLocks maps a `type: permissioneditor` field to the permissions this record fixes (the controller's cabana.PermissionRecordLockProvider), each with code, value (1, -1, or 0 for inherit) and note (localized): the admin shows that value and the note on the row and never sends the code; it is absent when no record lock applies.",
"parameters": [
{
"description": "Vendor",
@@ -6028,7 +6057,7 @@
]
},
"put": {
"description": "A plugin field type's value is decoded by its type inside the save; a value equal to the one the form was served for that field and locale is neither decoded nor written. When a type reports what it changed, meta.notices lists the localized messages per field (and content locale), and the key is absent otherwise.",
"description": "A plugin field type's value is decoded by its type inside the save; a value equal to the one the form was served for that field and locale is neither decoded nor written. When a type reports what it changed, meta.notices lists the localized messages per field (and content locale), and the key is absent otherwise. A record-locked permission (meta.permissionLocks) keeps its stored value when its code is absent from the body; a present value that differs from the stored one is a 403 on the field. The response carries meta.permissionLocks like the show route.",
"parameters": [
{
"description": "Vendor",