feat: expose typed Subject permission management catalog

Validate management additions in the existing CAS/replay transaction while retaining exact legacy entries in their original lane. Expose read-only account capability and test real saved-policy authorization, conflicts, replay and deletion fences.
This commit is contained in:
2026-10-29 09:25:09 +09:00
parent 7ad6030498
commit 38a953cd91
15 changed files with 1997 additions and 66 deletions
+25
View File
@@ -36,6 +36,27 @@ impl SubjektivStore {
&self,
subject_id: &str,
update: &SubjectPermissionsUpdate,
) -> Result<SubjectPermissions> {
self.replace_subject_permissions(subject_id, update, false)
}
/// Management replacement rejects new unsupported operation/resource pairs while
/// retaining exact legacy entries in their original execution/delegation lane.
/// Validation shares the existing CAS/receipt transaction so a stale editor cannot
/// retain a removed legacy grant. Exact replay remains historical evidence.
pub fn set_managed_subject_permissions(
&self,
subject_id: &str,
update: &SubjectPermissionsUpdate,
) -> Result<SubjectPermissions> {
self.replace_subject_permissions(subject_id, update, true)
}
fn replace_subject_permissions(
&self,
subject_id: &str,
update: &SubjectPermissionsUpdate,
managed: bool,
) -> Result<SubjectPermissions> {
validate_authorization_identifier(subject_id).map_err(SubjektivError::InvalidRecord)?;
validate_authorization_identifier(&update.operation_id)
@@ -70,6 +91,10 @@ impl SubjektivStore {
if current.as_ref().map(|source| &source.change_id) != update.expected_change_id.as_ref() {
return Err(SubjektivError::SubjectPermissionsConflict(subject_id.into()));
}
if managed {
server_api::authorization::validate_managed_subject_permissions(current.as_ref(), update)
.map_err(SubjektivError::InvalidRecord)?;
}
let source = SubjectPermissions {
change_id: issued_id("permissions"),
execution: update.execution.clone(),