1use std::{error::Error, fmt, num::NonZeroU16};
4
5use crate::{
6 ApplicationInstallationCampaignId, ApplicationPortabilityManifestHash, ApplicationRoleHash,
7 CapabilityPrincipalFactsV1, CommandId, ContractLineage, EntityTypeId, FieldId, IndexId,
8 PartitionKey, ProjectionId, QueryModuleHash, QueryOperationName, ReactiveModuleHash,
9 ReactiveOperationName, RowPolicyName, TenantScope,
10};
11
12pub const MAX_CAPABILITY_PARTITIONS: usize = 1_024;
14pub const MAX_CAPABILITY_PERMISSIONS: usize = 8_192;
16pub const MAX_CAPABILITY_FIELD_VISIBILITY: usize = 65_535;
18pub const MAX_CAPABILITY_ROW_POLICY_BINDINGS: usize = 1_024;
20pub const MAX_CAPABILITY_APPLICATION_EXPORT_GRANTS: usize = 256;
22pub const MAX_CAPABILITY_VECTOR_INSPECTION_TARGETS: usize = 256;
24pub const MAX_CAPABILITY_PAYLOAD_BYTES: usize = 1024 * 1024;
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum CapabilityGrantError {
30 Empty,
32 LimitExceeded,
34 Duplicate,
36 InvalidShape,
38 SizeOverflow,
40}
41
42impl fmt::Display for CapabilityGrantError {
43 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
44 formatter.write_str(match self {
45 Self::Empty => "required capability grant value is empty",
46 Self::LimitExceeded => "capability grant value exceeds a hard limit",
47 Self::Duplicate => "capability grant value contains a duplicate identity",
48 Self::InvalidShape => "capability grant value has an invalid semantic shape",
49 Self::SizeOverflow => "capability grant size calculation overflowed",
50 })
51 }
52}
53
54impl Error for CapabilityGrantError {}
55
56#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
58pub enum CapabilityPermissionKindV1 {
59 ValidateContract,
61 ReadContract,
63 ExplainCommand,
65 DeployContract,
67 InvokeCommand,
69 ReadEntity,
71 ScanIndex,
73 QueryProjection,
75 ReadProjectionStatus,
77 ReadCommit,
79 ScanCommits,
81 SubscribeCommits,
83 ReadProvenance,
85 InspectOutbox,
87 ReadHealth,
89 ReadStatistics,
91 CreateCapability,
93 RevokeCapability,
95 AdministerCapabilities,
97 CheckAdHocQuery,
99 ExplainAdHocQuery,
101 ExecuteAdHocQuery,
103 ExplainNamedQuery,
105 ExecuteNamedQuery,
107 ApplicationRoleIdentity,
109 MigrateContract,
111 ConsumeEventStream,
113 SeekEventStreamConsumer,
115 WatchNamedQuery,
117 ConsumeContextualSubscription,
119 InstallApplication,
121 InspectVectorState,
123 ReplicateChangelog,
125 FenceReplicationPrimary,
127}
128
129impl CapabilityPermissionKindV1 {
130 #[must_use]
132 pub const fn tag(self) -> u8 {
133 match self {
134 Self::ValidateContract => 0x01,
135 Self::ReadContract => 0x02,
136 Self::ExplainCommand => 0x03,
137 Self::DeployContract => 0x04,
138 Self::InvokeCommand => 0x05,
139 Self::ReadEntity => 0x06,
140 Self::ScanIndex => 0x07,
141 Self::QueryProjection => 0x08,
142 Self::ReadProjectionStatus => 0x09,
143 Self::ReadCommit => 0x0a,
144 Self::ScanCommits => 0x0b,
145 Self::SubscribeCommits => 0x0c,
146 Self::ReadProvenance => 0x0d,
147 Self::InspectOutbox => 0x0e,
148 Self::ReadHealth => 0x0f,
149 Self::ReadStatistics => 0x10,
150 Self::CreateCapability => 0x11,
151 Self::RevokeCapability => 0x12,
152 Self::AdministerCapabilities => 0x13,
153 Self::CheckAdHocQuery => 0x14,
154 Self::ExplainAdHocQuery => 0x15,
155 Self::ExecuteAdHocQuery => 0x16,
156 Self::ExplainNamedQuery => 0x17,
157 Self::ExecuteNamedQuery => 0x18,
158 Self::ApplicationRoleIdentity => 0x19,
159 Self::MigrateContract => 0x1a,
160 Self::ConsumeEventStream => 0x1b,
161 Self::SeekEventStreamConsumer => 0x1c,
162 Self::WatchNamedQuery => 0x1d,
163 Self::ConsumeContextualSubscription => 0x1e,
164 Self::InstallApplication => 0x1f,
165 Self::InspectVectorState => 0x20,
166 Self::ReplicateChangelog => 0x21,
167 Self::FenceReplicationPrimary => 0x22,
168 }
169 }
170
171 #[must_use]
173 pub const fn from_tag(tag: u8) -> Option<Self> {
174 match tag {
175 0x01 => Some(Self::ValidateContract),
176 0x02 => Some(Self::ReadContract),
177 0x03 => Some(Self::ExplainCommand),
178 0x04 => Some(Self::DeployContract),
179 0x05 => Some(Self::InvokeCommand),
180 0x06 => Some(Self::ReadEntity),
181 0x07 => Some(Self::ScanIndex),
182 0x08 => Some(Self::QueryProjection),
183 0x09 => Some(Self::ReadProjectionStatus),
184 0x0a => Some(Self::ReadCommit),
185 0x0b => Some(Self::ScanCommits),
186 0x0c => Some(Self::SubscribeCommits),
187 0x0d => Some(Self::ReadProvenance),
188 0x0e => Some(Self::InspectOutbox),
189 0x0f => Some(Self::ReadHealth),
190 0x10 => Some(Self::ReadStatistics),
191 0x11 => Some(Self::CreateCapability),
192 0x12 => Some(Self::RevokeCapability),
193 0x13 => Some(Self::AdministerCapabilities),
194 0x14 => Some(Self::CheckAdHocQuery),
195 0x15 => Some(Self::ExplainAdHocQuery),
196 0x16 => Some(Self::ExecuteAdHocQuery),
197 0x17 => Some(Self::ExplainNamedQuery),
198 0x18 => Some(Self::ExecuteNamedQuery),
199 0x19 => Some(Self::ApplicationRoleIdentity),
200 0x1a => Some(Self::MigrateContract),
201 0x1b => Some(Self::ConsumeEventStream),
202 0x1c => Some(Self::SeekEventStreamConsumer),
203 0x1d => Some(Self::WatchNamedQuery),
204 0x1e => Some(Self::ConsumeContextualSubscription),
205 0x1f => Some(Self::InstallApplication),
206 0x20 => Some(Self::InspectVectorState),
207 0x21 => Some(Self::ReplicateChangelog),
208 0x22 => Some(Self::FenceReplicationPrimary),
209 _ => None,
210 }
211 }
212
213 const fn requires_parameter(self) -> bool {
214 matches!(
215 self,
216 Self::ExplainCommand
217 | Self::InvokeCommand
218 | Self::ReadEntity
219 | Self::ScanIndex
220 | Self::QueryProjection
221 | Self::ReadProjectionStatus
222 | Self::ExplainNamedQuery
223 | Self::ExecuteNamedQuery
224 | Self::ApplicationRoleIdentity
225 | Self::MigrateContract
226 | Self::ConsumeEventStream
227 | Self::SeekEventStreamConsumer
228 | Self::WatchNamedQuery
229 | Self::ConsumeContextualSubscription
230 | Self::InstallApplication
231 )
232 }
233}
234
235#[derive(Clone, Debug, Eq, PartialEq)]
237pub enum CapabilityPermissionV1 {
238 Unparameterized(CapabilityPermissionKindV1),
240 ExplainCommand(ContractLineage, CommandId),
242 InvokeCommand(ContractLineage, CommandId),
244 ReadEntity(ContractLineage, EntityTypeId),
246 ScanIndex(ContractLineage, IndexId),
248 QueryProjection(ContractLineage, ProjectionId),
250 ReadProjectionStatus(ContractLineage, ProjectionId),
252 ExplainNamedQuery(ContractLineage, QueryModuleHash, QueryOperationName),
254 ExecuteNamedQuery(ContractLineage, QueryModuleHash, QueryOperationName),
256 ApplicationRoleIdentity(ApplicationRoleHash),
258 MigrateContract(ContractLineage),
260 ConsumeEventStream(ContractLineage, ReactiveModuleHash, ReactiveOperationName),
262 SeekEventStreamConsumer(ContractLineage, ReactiveModuleHash, ReactiveOperationName),
264 WatchNamedQuery(ContractLineage, ReactiveModuleHash, ReactiveOperationName),
266 ConsumeContextualSubscription(ContractLineage, ReactiveModuleHash, ReactiveOperationName),
268 InstallApplication(ContractLineage),
270}
271
272impl CapabilityPermissionV1 {
273 pub fn unparameterized(kind: CapabilityPermissionKindV1) -> Result<Self, CapabilityGrantError> {
275 if kind.requires_parameter() {
276 return Err(CapabilityGrantError::InvalidShape);
277 }
278 Ok(Self::Unparameterized(kind))
279 }
280
281 #[must_use]
283 pub const fn kind(&self) -> CapabilityPermissionKindV1 {
284 match self {
285 Self::Unparameterized(kind) => *kind,
286 Self::ExplainCommand(..) => CapabilityPermissionKindV1::ExplainCommand,
287 Self::InvokeCommand(..) => CapabilityPermissionKindV1::InvokeCommand,
288 Self::ReadEntity(..) => CapabilityPermissionKindV1::ReadEntity,
289 Self::ScanIndex(..) => CapabilityPermissionKindV1::ScanIndex,
290 Self::QueryProjection(..) => CapabilityPermissionKindV1::QueryProjection,
291 Self::ReadProjectionStatus(..) => CapabilityPermissionKindV1::ReadProjectionStatus,
292 Self::ExplainNamedQuery(..) => CapabilityPermissionKindV1::ExplainNamedQuery,
293 Self::ExecuteNamedQuery(..) => CapabilityPermissionKindV1::ExecuteNamedQuery,
294 Self::ApplicationRoleIdentity(..) => {
295 CapabilityPermissionKindV1::ApplicationRoleIdentity
296 }
297 Self::MigrateContract(..) => CapabilityPermissionKindV1::MigrateContract,
298 Self::ConsumeEventStream(..) => CapabilityPermissionKindV1::ConsumeEventStream,
299 Self::SeekEventStreamConsumer(..) => {
300 CapabilityPermissionKindV1::SeekEventStreamConsumer
301 }
302 Self::WatchNamedQuery(..) => CapabilityPermissionKindV1::WatchNamedQuery,
303 Self::ConsumeContextualSubscription(..) => {
304 CapabilityPermissionKindV1::ConsumeContextualSubscription
305 }
306 Self::InstallApplication(..) => CapabilityPermissionKindV1::InstallApplication,
307 }
308 }
309
310 #[must_use]
312 pub fn canonical_key(&self) -> Vec<u8> {
313 let mut bytes = vec![self.kind().tag()];
314 match self {
315 Self::Unparameterized(_) => {}
316 Self::ExplainCommand(lineage, id) | Self::InvokeCommand(lineage, id) => {
317 append_lineage(&mut bytes, lineage);
318 bytes.extend_from_slice(&id.to_be_bytes());
319 }
320 Self::ReadEntity(lineage, id) => {
321 append_lineage(&mut bytes, lineage);
322 bytes.extend_from_slice(&id.to_be_bytes());
323 }
324 Self::ScanIndex(lineage, id) => {
325 append_lineage(&mut bytes, lineage);
326 bytes.extend_from_slice(&id.to_be_bytes());
327 }
328 Self::QueryProjection(lineage, id) | Self::ReadProjectionStatus(lineage, id) => {
329 append_lineage(&mut bytes, lineage);
330 bytes.extend_from_slice(&id.to_be_bytes());
331 }
332 Self::ExplainNamedQuery(lineage, module_hash, query_name)
333 | Self::ExecuteNamedQuery(lineage, module_hash, query_name) => {
334 append_lineage(&mut bytes, lineage);
335 bytes.extend_from_slice(module_hash.as_bytes());
336 append_bytes(&mut bytes, query_name.as_str().as_bytes());
337 }
338 Self::ApplicationRoleIdentity(role_hash) => {
339 bytes.extend_from_slice(role_hash.as_bytes());
340 }
341 Self::MigrateContract(lineage) => append_lineage(&mut bytes, lineage),
342 Self::InstallApplication(lineage) => append_lineage(&mut bytes, lineage),
343 Self::ConsumeEventStream(lineage, module_hash, operation_name)
344 | Self::SeekEventStreamConsumer(lineage, module_hash, operation_name)
345 | Self::WatchNamedQuery(lineage, module_hash, operation_name)
346 | Self::ConsumeContextualSubscription(lineage, module_hash, operation_name) => {
347 append_lineage(&mut bytes, lineage);
348 bytes.extend_from_slice(module_hash.as_bytes());
349 append_bytes(&mut bytes, operation_name.as_str().as_bytes());
350 }
351 }
352 bytes
353 }
354}
355
356#[derive(Clone)]
358pub struct CapabilityPermissionsV1(
359 crate::OwnedCapacitySliceV1<CapabilityPermissionV1>,
360 crate::OwnedCapacitySliceV1<Vec<u8>>,
361);
362
363impl fmt::Debug for CapabilityPermissionsV1 {
364 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
365 formatter
366 .debug_tuple("CapabilityPermissionsV1")
367 .field(&self.0)
368 .finish()
369 }
370}
371
372impl PartialEq for CapabilityPermissionsV1 {
373 fn eq(&self, other: &Self) -> bool {
374 self.0 == other.0
375 }
376}
377
378impl Eq for CapabilityPermissionsV1 {}
379
380impl CapabilityPermissionsV1 {
381 pub fn new(values: Vec<CapabilityPermissionV1>) -> Result<Self, CapabilityGrantError> {
386 if values.len() > MAX_CAPABILITY_PERMISSIONS {
387 return Err(CapabilityGrantError::LimitExceeded);
388 }
389 validate_capability_payload_bytes(permission_set_semantic_bytes(&values)?)?;
390 let mut keyed = values
391 .into_iter()
392 .map(|permission| (permission.canonical_key(), permission))
393 .collect::<Vec<_>>();
394 keyed.sort_by(|left, right| left.0.cmp(&right.0));
395 if keyed.windows(2).any(|pair| pair[0].0 == pair[1].0) {
396 return Err(CapabilityGrantError::Duplicate);
397 }
398 let mut values = Vec::with_capacity(keyed.len());
399 let mut canonical_keys = Vec::with_capacity(keyed.len());
400 for (canonical_key, permission) in keyed {
401 canonical_keys.push(canonical_key);
402 values.push(permission);
403 }
404 Ok(Self(values.into(), canonical_keys.into()))
405 }
406
407 #[must_use]
409 pub fn as_slice(&self) -> &[CapabilityPermissionV1] {
410 &self.0
411 }
412
413 #[must_use]
415 pub fn contains_kind(&self, kind: CapabilityPermissionKindV1) -> bool {
416 self.0.iter().any(|permission| permission.kind() == kind)
417 }
418
419 #[must_use]
421 pub fn contains_exact(&self, permission: &CapabilityPermissionV1) -> bool {
422 let target = permission.canonical_key();
423 self.1
424 .binary_search_by(|candidate| candidate.as_slice().cmp(&target))
425 .is_ok()
426 }
427}
428
429#[derive(Clone, Debug, Eq, PartialEq)]
431pub struct ScopedPartitionV1 {
432 lineage: ContractLineage,
433 partition_key: PartitionKey,
434}
435
436impl ScopedPartitionV1 {
437 #[must_use]
439 pub const fn new(lineage: ContractLineage, partition_key: PartitionKey) -> Self {
440 Self {
441 lineage,
442 partition_key,
443 }
444 }
445
446 #[must_use]
448 pub fn canonical_key(&self) -> Vec<u8> {
449 let mut bytes = Vec::new();
450 append_lineage(&mut bytes, &self.lineage);
451 append_bytes(&mut bytes, self.partition_key.as_bytes());
452 bytes
453 }
454
455 #[must_use]
457 pub const fn lineage(&self) -> &ContractLineage {
458 &self.lineage
459 }
460
461 #[must_use]
463 pub const fn partition_key(&self) -> &PartitionKey {
464 &self.partition_key
465 }
466}
467
468#[derive(Clone, Debug, Eq, PartialEq)]
470pub enum PartitionScopeV1 {
471 All,
473 Explicit(Vec<ScopedPartitionV1>),
475}
476
477impl PartitionScopeV1 {
478 #[must_use]
480 pub const fn tag(&self) -> u8 {
481 match self {
482 Self::All => 0x01,
483 Self::Explicit(_) => 0x02,
484 }
485 }
486
487 pub fn explicit(mut values: Vec<ScopedPartitionV1>) -> Result<Self, CapabilityGrantError> {
489 if values.is_empty() {
490 return Err(CapabilityGrantError::Empty);
491 }
492 if values.len() > MAX_CAPABILITY_PARTITIONS {
493 return Err(CapabilityGrantError::LimitExceeded);
494 }
495 validate_capability_payload_bytes(explicit_partitions_semantic_bytes(&values)?)?;
496 values.sort_by_key(ScopedPartitionV1::canonical_key);
497 if values
498 .windows(2)
499 .any(|pair| pair[0].canonical_key() == pair[1].canonical_key())
500 {
501 return Err(CapabilityGrantError::Duplicate);
502 }
503 Ok(Self::Explicit(values))
504 }
505
506 #[must_use]
508 pub fn explicit_entries(&self) -> Option<&[ScopedPartitionV1]> {
509 match self {
510 Self::All => None,
511 Self::Explicit(entries) => Some(entries),
512 }
513 }
514}
515
516#[derive(Clone, Debug, Eq, PartialEq)]
524pub struct EntityFieldVisibilityV1 {
525 lineage: ContractLineage,
526 entity_type: EntityTypeId,
527 fields: crate::OwnedCapacitySliceV1<FieldId>,
528 secret_fields: crate::OwnedCapacitySliceV1<FieldId>,
529}
530
531impl EntityFieldVisibilityV1 {
532 pub fn new(
534 lineage: ContractLineage,
535 entity_type: EntityTypeId,
536 fields: Vec<FieldId>,
537 ) -> Result<Self, CapabilityGrantError> {
538 Self::with_secret_fields(lineage, entity_type, fields, Vec::new())
539 }
540
541 pub fn with_secret_fields(
548 lineage: ContractLineage,
549 entity_type: EntityTypeId,
550 mut fields: Vec<FieldId>,
551 mut secret_fields: Vec<FieldId>,
552 ) -> Result<Self, CapabilityGrantError> {
553 if fields.is_empty() && secret_fields.is_empty() {
554 return Err(CapabilityGrantError::Empty);
555 }
556 if fields.len().saturating_add(secret_fields.len()) > MAX_CAPABILITY_FIELD_VISIBILITY {
557 return Err(CapabilityGrantError::LimitExceeded);
558 }
559 fields.sort_unstable();
560 if fields.windows(2).any(|pair| pair[0] == pair[1]) {
561 return Err(CapabilityGrantError::Duplicate);
562 }
563 secret_fields.sort_unstable();
564 if secret_fields.windows(2).any(|pair| pair[0] == pair[1]) {
565 return Err(CapabilityGrantError::Duplicate);
566 }
567 Ok(Self {
568 lineage,
569 entity_type,
570 fields: fields.into(),
571 secret_fields: secret_fields.into(),
572 })
573 }
574
575 fn canonical_key(&self) -> Vec<u8> {
576 let mut bytes = Vec::new();
577 append_lineage(&mut bytes, &self.lineage);
578 bytes.extend_from_slice(&self.entity_type.to_be_bytes());
579 bytes
580 }
581
582 #[must_use]
584 pub const fn lineage(&self) -> &ContractLineage {
585 &self.lineage
586 }
587
588 #[must_use]
590 pub const fn entity_type(&self) -> EntityTypeId {
591 self.entity_type
592 }
593
594 #[must_use]
596 pub fn fields(&self) -> &[FieldId] {
597 &self.fields
598 }
599
600 #[must_use]
603 pub fn secret_fields(&self) -> &[FieldId] {
604 &self.secret_fields
605 }
606}
607
608#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
610#[repr(u8)]
611pub enum CapabilityRowPolicyOperationV1 {
612 Read = 1,
614 Create = 2,
616 Update = 3,
618 Delete = 4,
620}
621
622impl CapabilityRowPolicyOperationV1 {
623 #[must_use]
625 pub const fn from_tag(tag: u8) -> Option<Self> {
626 match tag {
627 1 => Some(Self::Read),
628 2 => Some(Self::Create),
629 3 => Some(Self::Update),
630 4 => Some(Self::Delete),
631 _ => None,
632 }
633 }
634
635 #[must_use]
637 pub const fn tag(self) -> u8 {
638 self as u8
639 }
640}
641
642#[derive(Clone, Debug, Eq, PartialEq)]
644pub struct CapabilityRowPolicyBindingV1 {
645 lineage: ContractLineage,
646 policy_name: RowPolicyName,
647 entity_type: EntityTypeId,
648 operations: crate::OwnedCapacitySliceV1<CapabilityRowPolicyOperationV1>,
649}
650
651impl CapabilityRowPolicyBindingV1 {
652 pub fn new(
654 lineage: ContractLineage,
655 policy_name: RowPolicyName,
656 entity_type: EntityTypeId,
657 mut operations: Vec<CapabilityRowPolicyOperationV1>,
658 ) -> Result<Self, CapabilityGrantError> {
659 if operations.is_empty() {
660 return Err(CapabilityGrantError::Empty);
661 }
662 if operations.len() > 4 {
663 return Err(CapabilityGrantError::LimitExceeded);
664 }
665 operations.sort_unstable();
666 if operations.windows(2).any(|pair| pair[0] == pair[1]) {
667 return Err(CapabilityGrantError::Duplicate);
668 }
669 Ok(Self {
670 lineage,
671 policy_name,
672 entity_type,
673 operations: operations.into(),
674 })
675 }
676
677 fn canonical_key(&self) -> Vec<u8> {
678 let mut bytes = Vec::new();
679 append_lineage(&mut bytes, &self.lineage);
680 bytes.extend_from_slice(&self.entity_type.to_be_bytes());
681 bytes.extend_from_slice(self.policy_name.as_str().as_bytes());
682 bytes
683 }
684
685 fn entity_key(&self) -> Vec<u8> {
686 let mut bytes = Vec::new();
687 append_lineage(&mut bytes, &self.lineage);
688 bytes.extend_from_slice(&self.entity_type.to_be_bytes());
689 bytes
690 }
691
692 #[must_use]
694 pub const fn lineage(&self) -> &ContractLineage {
695 &self.lineage
696 }
697
698 #[must_use]
700 pub const fn policy_name(&self) -> &RowPolicyName {
701 &self.policy_name
702 }
703
704 #[must_use]
706 pub const fn entity_type(&self) -> EntityTypeId {
707 self.entity_type
708 }
709
710 #[must_use]
712 pub fn operations(&self) -> &[CapabilityRowPolicyOperationV1] {
713 &self.operations
714 }
715}
716
717#[derive(Clone, Eq, PartialEq)]
719pub struct CapabilityRowPolicyGrantV1 {
720 application_role_hash: ApplicationRoleHash,
721 principal_facts: CapabilityPrincipalFactsV1,
722 bindings: crate::OwnedCapacitySliceV1<CapabilityRowPolicyBindingV1>,
723}
724
725impl CapabilityRowPolicyGrantV1 {
726 pub fn new(
728 application_role_hash: ApplicationRoleHash,
729 principal_facts: CapabilityPrincipalFactsV1,
730 mut bindings: Vec<CapabilityRowPolicyBindingV1>,
731 ) -> Result<Self, CapabilityGrantError> {
732 if bindings.is_empty() {
733 return Err(CapabilityGrantError::Empty);
734 }
735 if bindings.len() > MAX_CAPABILITY_ROW_POLICY_BINDINGS {
736 return Err(CapabilityGrantError::LimitExceeded);
737 }
738 bindings.sort_by_key(CapabilityRowPolicyBindingV1::canonical_key);
739 if bindings
740 .windows(2)
741 .any(|pair| pair[0].entity_key() == pair[1].entity_key())
742 {
743 return Err(CapabilityGrantError::Duplicate);
744 }
745 Ok(Self {
746 application_role_hash,
747 principal_facts,
748 bindings: bindings.into(),
749 })
750 }
751
752 #[must_use]
754 pub const fn application_role_hash(&self) -> ApplicationRoleHash {
755 self.application_role_hash
756 }
757
758 #[doc(hidden)]
760 #[must_use]
761 pub const fn internal_principal_facts(&self) -> &CapabilityPrincipalFactsV1 {
762 &self.principal_facts
763 }
764
765 #[must_use]
767 pub fn bindings(&self) -> &[CapabilityRowPolicyBindingV1] {
768 &self.bindings
769 }
770
771 #[must_use]
773 pub fn is_narrowing_of(&self, parent: &Self) -> bool {
774 self.application_role_hash == parent.application_role_hash
775 && self
776 .principal_facts
777 .is_narrowing_of(&parent.principal_facts)
778 && self.bindings.iter().all(|child| {
779 parent
780 .bindings
781 .binary_search_by_key(&child.canonical_key(), |binding| binding.canonical_key())
782 .ok()
783 .is_some_and(|index| {
784 child.operations.iter().all(|operation| {
785 parent.bindings[index]
786 .operations
787 .binary_search(operation)
788 .is_ok()
789 })
790 })
791 })
792 }
793}
794
795impl fmt::Debug for CapabilityRowPolicyGrantV1 {
796 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
797 formatter
798 .debug_struct("CapabilityRowPolicyGrantV1")
799 .field("application_role_hash", &self.application_role_hash)
800 .field("principal_facts", &"[REDACTED]")
801 .field("binding_count", &self.bindings.len())
802 .finish()
803 }
804}
805
806#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
808pub enum CapabilityApplicationExportScopeV1 {
809 PrincipalFiltered,
811 WholeApplication,
813}
814
815impl CapabilityApplicationExportScopeV1 {
816 #[must_use]
818 pub const fn tag(self) -> u8 {
819 match self {
820 Self::PrincipalFiltered => 1,
821 Self::WholeApplication => 2,
822 }
823 }
824
825 #[must_use]
827 pub const fn from_tag(tag: u8) -> Option<Self> {
828 match tag {
829 1 => Some(Self::PrincipalFiltered),
830 2 => Some(Self::WholeApplication),
831 _ => None,
832 }
833 }
834}
835
836#[derive(Clone, Debug, Eq, PartialEq)]
838pub struct CapabilityApplicationExportGrantV1 {
839 lineage: ContractLineage,
840 scope: CapabilityApplicationExportScopeV1,
841 entities: bool,
842 events: bool,
843 provenance: bool,
844 public_audit: bool,
845}
846
847impl CapabilityApplicationExportGrantV1 {
848 #[allow(clippy::fn_params_excessive_bools)]
851 pub fn new(
852 lineage: ContractLineage,
853 scope: CapabilityApplicationExportScopeV1,
854 entities: bool,
855 events: bool,
856 provenance: bool,
857 public_audit: bool,
858 ) -> Result<Self, CapabilityGrantError> {
859 if !entities && !events {
860 return Err(CapabilityGrantError::InvalidShape);
861 }
862 Ok(Self {
863 lineage,
864 scope,
865 entities,
866 events,
867 provenance,
868 public_audit,
869 })
870 }
871
872 #[must_use]
874 pub const fn lineage(&self) -> &ContractLineage {
875 &self.lineage
876 }
877
878 #[must_use]
880 pub const fn scope(&self) -> CapabilityApplicationExportScopeV1 {
881 self.scope
882 }
883
884 #[must_use]
886 pub const fn entities(&self) -> bool {
887 self.entities
888 }
889
890 #[must_use]
892 pub const fn events(&self) -> bool {
893 self.events
894 }
895
896 #[must_use]
898 pub const fn provenance(&self) -> bool {
899 self.provenance
900 }
901
902 #[must_use]
904 pub const fn public_audit(&self) -> bool {
905 self.public_audit
906 }
907
908 fn narrows(&self, parent: &Self, child_has_row_policy: bool) -> bool {
909 self.lineage == parent.lineage
910 && match (self.scope, parent.scope) {
911 (
912 CapabilityApplicationExportScopeV1::PrincipalFiltered,
913 CapabilityApplicationExportScopeV1::WholeApplication,
914 ) => child_has_row_policy,
915 (child, parent) => child == parent,
916 }
917 && (!self.entities || parent.entities)
918 && (!self.events || parent.events)
919 && (!self.provenance || parent.provenance)
920 && (!self.public_audit || parent.public_audit)
921 }
922}
923
924#[derive(Clone, Eq, PartialEq)]
926pub struct CapabilityExportGrantV1 {
927 applications: crate::OwnedCapacitySliceV1<CapabilityApplicationExportGrantV1>,
928}
929
930impl CapabilityExportGrantV1 {
931 pub fn new(
933 mut applications: Vec<CapabilityApplicationExportGrantV1>,
934 ) -> Result<Self, CapabilityGrantError> {
935 if applications.is_empty() {
936 return Err(CapabilityGrantError::Empty);
937 }
938 if applications.len() > MAX_CAPABILITY_APPLICATION_EXPORT_GRANTS {
939 return Err(CapabilityGrantError::LimitExceeded);
940 }
941 applications.sort_by(|left, right| left.lineage.as_bytes().cmp(right.lineage.as_bytes()));
942 if applications
943 .windows(2)
944 .any(|pair| pair[0].lineage == pair[1].lineage)
945 {
946 return Err(CapabilityGrantError::Duplicate);
947 }
948 Ok(Self {
949 applications: applications.into(),
950 })
951 }
952
953 #[must_use]
955 pub fn applications(&self) -> &[CapabilityApplicationExportGrantV1] {
956 &self.applications
957 }
958
959 #[must_use]
961 pub fn is_narrowing_of(&self, parent: &Self, child_has_row_policy: bool) -> bool {
962 self.applications.iter().all(|child| {
963 parent
964 .applications
965 .binary_search_by(|candidate| {
966 candidate.lineage.as_bytes().cmp(child.lineage.as_bytes())
967 })
968 .ok()
969 .is_some_and(|index| {
970 child.narrows(&parent.applications[index], child_has_row_policy)
971 })
972 })
973 }
974}
975
976impl fmt::Debug for CapabilityExportGrantV1 {
977 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
978 formatter
979 .debug_struct("CapabilityExportGrantV1")
980 .field("application_count", &self.applications.len())
981 .finish()
982 }
983}
984
985#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
987pub enum CapabilityApplicationReimportScopeV1 {
988 PrincipalFiltered,
990 WholeApplication,
992}
993
994impl CapabilityApplicationReimportScopeV1 {
995 #[must_use]
997 pub const fn tag(self) -> u8 {
998 match self {
999 Self::PrincipalFiltered => 1,
1000 Self::WholeApplication => 2,
1001 }
1002 }
1003
1004 #[must_use]
1006 pub const fn from_tag(tag: u8) -> Option<Self> {
1007 match tag {
1008 1 => Some(Self::PrincipalFiltered),
1009 2 => Some(Self::WholeApplication),
1010 _ => None,
1011 }
1012 }
1013}
1014
1015#[derive(Clone, Eq, PartialEq)]
1017pub struct CapabilityApplicationReimportGrantV1 {
1018 lineage: ContractLineage,
1019 campaign_id: ApplicationInstallationCampaignId,
1020 portability_manifest_hash: ApplicationPortabilityManifestHash,
1021 scope: CapabilityApplicationReimportScopeV1,
1022}
1023
1024impl CapabilityApplicationReimportGrantV1 {
1025 #[must_use]
1027 pub const fn new(
1028 lineage: ContractLineage,
1029 campaign_id: ApplicationInstallationCampaignId,
1030 portability_manifest_hash: ApplicationPortabilityManifestHash,
1031 scope: CapabilityApplicationReimportScopeV1,
1032 ) -> Self {
1033 Self {
1034 lineage,
1035 campaign_id,
1036 portability_manifest_hash,
1037 scope,
1038 }
1039 }
1040
1041 #[must_use]
1043 pub const fn lineage(&self) -> &ContractLineage {
1044 &self.lineage
1045 }
1046
1047 #[must_use]
1049 pub const fn campaign_id(&self) -> ApplicationInstallationCampaignId {
1050 self.campaign_id
1051 }
1052
1053 #[must_use]
1055 pub const fn portability_manifest_hash(&self) -> ApplicationPortabilityManifestHash {
1056 self.portability_manifest_hash
1057 }
1058
1059 #[must_use]
1061 pub const fn scope(&self) -> CapabilityApplicationReimportScopeV1 {
1062 self.scope
1063 }
1064
1065 #[must_use]
1067 pub fn is_narrowing_of(&self, parent: &Self, child_has_row_policy: bool) -> bool {
1068 self.lineage == parent.lineage
1069 && self.campaign_id == parent.campaign_id
1070 && self.portability_manifest_hash == parent.portability_manifest_hash
1071 && match (self.scope, parent.scope) {
1072 (
1073 CapabilityApplicationReimportScopeV1::PrincipalFiltered,
1074 CapabilityApplicationReimportScopeV1::WholeApplication,
1075 ) => child_has_row_policy,
1076 (child, parent) => child == parent,
1077 }
1078 }
1079}
1080
1081impl fmt::Debug for CapabilityApplicationReimportGrantV1 {
1082 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1083 formatter
1084 .debug_struct("CapabilityApplicationReimportGrantV1")
1085 .field("lineage", &self.lineage)
1086 .field("campaign_id", &self.campaign_id)
1087 .field("portability_manifest_hash", &self.portability_manifest_hash)
1088 .field("scope", &self.scope)
1089 .finish()
1090 }
1091}
1092
1093#[derive(Clone, Debug, Eq, PartialEq)]
1095pub struct CapabilityVectorInspectionTargetV1 {
1096 lineage: ContractLineage,
1097 entity_type: EntityTypeId,
1098 field: FieldId,
1099 allow_counts: bool,
1100}
1101
1102impl CapabilityVectorInspectionTargetV1 {
1103 #[must_use]
1105 pub const fn new(
1106 lineage: ContractLineage,
1107 entity_type: EntityTypeId,
1108 field: FieldId,
1109 allow_counts: bool,
1110 ) -> Self {
1111 Self {
1112 lineage,
1113 entity_type,
1114 field,
1115 allow_counts,
1116 }
1117 }
1118
1119 fn canonical_key(&self) -> Vec<u8> {
1120 let mut bytes = Vec::new();
1121 append_lineage(&mut bytes, &self.lineage);
1122 bytes.extend_from_slice(&self.entity_type.to_be_bytes());
1123 bytes.extend_from_slice(&self.field.to_be_bytes());
1124 bytes
1125 }
1126
1127 #[must_use]
1129 pub const fn lineage(&self) -> &ContractLineage {
1130 &self.lineage
1131 }
1132
1133 #[must_use]
1135 pub const fn entity_type(&self) -> EntityTypeId {
1136 self.entity_type
1137 }
1138
1139 #[must_use]
1141 pub const fn field(&self) -> FieldId {
1142 self.field
1143 }
1144
1145 #[must_use]
1147 pub const fn allow_counts(&self) -> bool {
1148 self.allow_counts
1149 }
1150
1151 fn narrows(&self, parent: &Self) -> bool {
1152 self.lineage == parent.lineage
1153 && self.entity_type == parent.entity_type
1154 && self.field == parent.field
1155 && (!self.allow_counts || parent.allow_counts)
1156 }
1157}
1158
1159#[derive(Clone, Eq, PartialEq)]
1161pub struct CapabilityVectorInspectionGrantV1 {
1162 application_role_hash: ApplicationRoleHash,
1163 targets: crate::OwnedCapacitySliceV1<CapabilityVectorInspectionTargetV1>,
1164}
1165
1166impl CapabilityVectorInspectionGrantV1 {
1167 pub fn new(
1169 application_role_hash: ApplicationRoleHash,
1170 mut targets: Vec<CapabilityVectorInspectionTargetV1>,
1171 ) -> Result<Self, CapabilityGrantError> {
1172 if targets.is_empty() {
1173 return Err(CapabilityGrantError::Empty);
1174 }
1175 if targets.len() > MAX_CAPABILITY_VECTOR_INSPECTION_TARGETS {
1176 return Err(CapabilityGrantError::LimitExceeded);
1177 }
1178 targets.sort_by_key(CapabilityVectorInspectionTargetV1::canonical_key);
1179 if targets
1180 .windows(2)
1181 .any(|pair| pair[0].canonical_key() == pair[1].canonical_key())
1182 {
1183 return Err(CapabilityGrantError::Duplicate);
1184 }
1185 Ok(Self {
1186 application_role_hash,
1187 targets: targets.into(),
1188 })
1189 }
1190
1191 #[must_use]
1193 pub const fn application_role_hash(&self) -> ApplicationRoleHash {
1194 self.application_role_hash
1195 }
1196
1197 #[must_use]
1199 pub fn targets(&self) -> &[CapabilityVectorInspectionTargetV1] {
1200 &self.targets
1201 }
1202
1203 #[must_use]
1205 pub fn target(
1206 &self,
1207 lineage: &ContractLineage,
1208 entity_type: EntityTypeId,
1209 field: FieldId,
1210 ) -> Option<&CapabilityVectorInspectionTargetV1> {
1211 self.targets.iter().find(|target| {
1212 target.lineage() == lineage
1213 && target.entity_type() == entity_type
1214 && target.field() == field
1215 })
1216 }
1217
1218 #[must_use]
1220 pub fn is_narrowing_of(&self, parent: &Self) -> bool {
1221 self.application_role_hash == parent.application_role_hash
1222 && self.targets.iter().all(|child| {
1223 parent
1224 .targets
1225 .binary_search_by_key(&child.canonical_key(), |target| target.canonical_key())
1226 .ok()
1227 .is_some_and(|index| child.narrows(&parent.targets[index]))
1228 })
1229 }
1230}
1231
1232impl fmt::Debug for CapabilityVectorInspectionGrantV1 {
1233 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1234 formatter
1235 .debug_struct("CapabilityVectorInspectionGrantV1")
1236 .field("application_role_hash", &self.application_role_hash)
1237 .field("target_count", &self.targets.len())
1238 .finish()
1239 }
1240}
1241
1242#[derive(Clone, Debug, Eq, PartialEq)]
1244pub struct CapabilityGrantV1 {
1245 tenant_scope: TenantScope,
1246 partition_scope: PartitionScopeV1,
1247 permissions: CapabilityPermissionsV1,
1248 field_visibility: crate::OwnedCapacitySliceV1<EntityFieldVisibilityV1>,
1249 max_scan_rows: NonZeroU16,
1250 approval_required: crate::OwnedCapacitySliceV1<CapabilityPermissionKindV1>,
1251 row_policy: Option<CapabilityRowPolicyGrantV1>,
1252 export: Option<CapabilityExportGrantV1>,
1253 reimport: Option<CapabilityApplicationReimportGrantV1>,
1254 vector_inspection: Option<CapabilityVectorInspectionGrantV1>,
1255}
1256
1257impl CapabilityGrantV1 {
1258 pub fn new(
1260 tenant_scope: TenantScope,
1261 partition_scope: PartitionScopeV1,
1262 permissions: CapabilityPermissionsV1,
1263 mut field_visibility: Vec<EntityFieldVisibilityV1>,
1264 max_scan_rows: NonZeroU16,
1265 mut approval_required: Vec<CapabilityPermissionKindV1>,
1266 ) -> Result<Self, CapabilityGrantError> {
1267 if u64::from(max_scan_rows.get()) > crate::MAX_APPLICATION_QUERY_SCANNED_ROWS
1268 || field_visibility.len() > MAX_CAPABILITY_FIELD_VISIBILITY
1269 || approval_required.len() > 31
1270 {
1271 return Err(CapabilityGrantError::LimitExceeded);
1272 }
1273 if (permissions.contains_kind(CapabilityPermissionKindV1::ReplicateChangelog)
1276 || permissions.contains_kind(CapabilityPermissionKindV1::FenceReplicationPrimary))
1277 && (tenant_scope != TenantScope::Global
1278 || partition_scope != PartitionScopeV1::All
1279 || permissions.contains_kind(CapabilityPermissionKindV1::ApplicationRoleIdentity))
1280 {
1281 return Err(CapabilityGrantError::InvalidShape);
1282 }
1283 let unchecked_grant_size = capability_grant_semantic_bytes_parts(
1284 &tenant_scope,
1285 &partition_scope,
1286 &permissions,
1287 &field_visibility,
1288 &approval_required,
1289 )?;
1290 validate_capability_payload_bytes(unchecked_grant_size)?;
1291 field_visibility.sort_by_key(EntityFieldVisibilityV1::canonical_key);
1292 if field_visibility
1293 .windows(2)
1294 .any(|pair| pair[0].canonical_key() == pair[1].canonical_key())
1295 {
1296 return Err(CapabilityGrantError::Duplicate);
1297 }
1298 let total_fields = field_visibility.iter().try_fold(0usize, |count, entry| {
1299 count
1300 .checked_add(entry.fields.len())
1301 .and_then(|count| count.checked_add(entry.secret_fields.len()))
1302 .ok_or(CapabilityGrantError::SizeOverflow)
1303 })?;
1304 if total_fields > MAX_CAPABILITY_FIELD_VISIBILITY {
1305 return Err(CapabilityGrantError::LimitExceeded);
1306 }
1307 approval_required.sort_unstable();
1308 if approval_required.windows(2).any(|pair| pair[0] == pair[1]) {
1309 return Err(CapabilityGrantError::Duplicate);
1310 }
1311 let value = Self {
1312 tenant_scope,
1313 partition_scope,
1314 permissions,
1315 field_visibility: field_visibility.into(),
1316 max_scan_rows,
1317 approval_required: approval_required.into(),
1318 row_policy: None,
1319 export: None,
1320 reimport: None,
1321 vector_inspection: None,
1322 };
1323 validate_capability_payload_bytes(value.semantic_bytes()?)?;
1324 Ok(value)
1325 }
1326
1327 #[must_use]
1329 pub const fn tenant_scope(&self) -> &TenantScope {
1330 &self.tenant_scope
1331 }
1332
1333 #[must_use]
1335 pub const fn partition_scope(&self) -> &PartitionScopeV1 {
1336 &self.partition_scope
1337 }
1338
1339 #[must_use]
1341 pub const fn permissions(&self) -> &CapabilityPermissionsV1 {
1342 &self.permissions
1343 }
1344
1345 #[must_use]
1347 pub fn field_visibility(&self) -> &[EntityFieldVisibilityV1] {
1348 &self.field_visibility
1349 }
1350
1351 #[must_use]
1353 pub const fn max_scan_rows(&self) -> NonZeroU16 {
1354 self.max_scan_rows
1355 }
1356
1357 #[must_use]
1359 pub fn approval_required(&self) -> &[CapabilityPermissionKindV1] {
1360 &self.approval_required
1361 }
1362
1363 pub fn with_row_policy(
1365 mut self,
1366 row_policy: CapabilityRowPolicyGrantV1,
1367 ) -> Result<Self, CapabilityGrantError> {
1368 if self.row_policy.is_some() {
1369 return Err(CapabilityGrantError::Duplicate);
1370 }
1371 let matching_roles = self
1372 .permissions
1373 .as_slice()
1374 .iter()
1375 .filter(|permission| {
1376 matches!(
1377 permission,
1378 CapabilityPermissionV1::ApplicationRoleIdentity(hash)
1379 if *hash == row_policy.application_role_hash
1380 )
1381 })
1382 .count();
1383 let all_roles = self
1384 .permissions
1385 .as_slice()
1386 .iter()
1387 .filter(|permission| {
1388 matches!(
1389 permission,
1390 CapabilityPermissionV1::ApplicationRoleIdentity(_)
1391 )
1392 })
1393 .count();
1394 if matching_roles != 1 || all_roles != 1 {
1395 return Err(CapabilityGrantError::InvalidShape);
1396 }
1397 if self.vector_inspection.as_ref().is_some_and(|inspection| {
1398 inspection.targets().iter().any(|target| {
1399 target.allow_counts()
1400 && row_policy.bindings().iter().any(|binding| {
1401 binding.lineage() == target.lineage()
1402 && binding.entity_type() == target.entity_type()
1403 })
1404 })
1405 }) {
1406 return Err(CapabilityGrantError::InvalidShape);
1407 }
1408 self.row_policy = Some(row_policy);
1409 validate_capability_payload_bytes(self.semantic_bytes()?)?;
1410 Ok(self)
1411 }
1412
1413 #[doc(hidden)]
1415 #[must_use]
1416 pub const fn internal_row_policy(&self) -> Option<&CapabilityRowPolicyGrantV1> {
1417 self.row_policy.as_ref()
1418 }
1419
1420 pub fn with_export(
1422 mut self,
1423 export: CapabilityExportGrantV1,
1424 ) -> Result<Self, CapabilityGrantError> {
1425 if self.export.is_some() {
1426 return Err(CapabilityGrantError::Duplicate);
1427 }
1428 for application in export.applications() {
1429 match application.scope() {
1430 CapabilityApplicationExportScopeV1::PrincipalFiltered
1431 if self.row_policy.is_none() =>
1432 {
1433 return Err(CapabilityGrantError::InvalidShape);
1434 }
1435 CapabilityApplicationExportScopeV1::WholeApplication
1436 if self.tenant_scope != TenantScope::Global
1437 || self.partition_scope != PartitionScopeV1::All =>
1438 {
1439 return Err(CapabilityGrantError::InvalidShape);
1440 }
1441 CapabilityApplicationExportScopeV1::PrincipalFiltered
1442 | CapabilityApplicationExportScopeV1::WholeApplication => {}
1443 }
1444 }
1445 self.export = Some(export);
1446 validate_capability_payload_bytes(self.semantic_bytes()?)?;
1447 Ok(self)
1448 }
1449
1450 #[doc(hidden)]
1452 #[must_use]
1453 pub const fn internal_export(&self) -> Option<&CapabilityExportGrantV1> {
1454 self.export.as_ref()
1455 }
1456
1457 pub fn with_reimport(
1459 mut self,
1460 reimport: CapabilityApplicationReimportGrantV1,
1461 ) -> Result<Self, CapabilityGrantError> {
1462 if self.reimport.is_some() || self.row_policy.is_none() {
1463 return Err(CapabilityGrantError::InvalidShape);
1464 }
1465 if reimport.scope() == CapabilityApplicationReimportScopeV1::WholeApplication
1466 && (self.tenant_scope != TenantScope::Global
1467 || self.partition_scope != PartitionScopeV1::All)
1468 {
1469 return Err(CapabilityGrantError::InvalidShape);
1470 }
1471 self.reimport = Some(reimport);
1472 validate_capability_payload_bytes(self.semantic_bytes()?)?;
1473 Ok(self)
1474 }
1475
1476 #[doc(hidden)]
1478 #[must_use]
1479 pub const fn internal_reimport(&self) -> Option<&CapabilityApplicationReimportGrantV1> {
1480 self.reimport.as_ref()
1481 }
1482
1483 pub fn with_vector_inspection(
1486 mut self,
1487 inspection: CapabilityVectorInspectionGrantV1,
1488 ) -> Result<Self, CapabilityGrantError> {
1489 if self.vector_inspection.is_some()
1490 || !self
1491 .permissions
1492 .contains_exact(&CapabilityPermissionV1::unparameterized(
1493 CapabilityPermissionKindV1::InspectVectorState,
1494 )?)
1495 {
1496 return Err(CapabilityGrantError::InvalidShape);
1497 }
1498 let matching_roles = self
1499 .permissions
1500 .as_slice()
1501 .iter()
1502 .filter(|permission| {
1503 matches!(
1504 permission,
1505 CapabilityPermissionV1::ApplicationRoleIdentity(hash)
1506 if *hash == inspection.application_role_hash()
1507 )
1508 })
1509 .count();
1510 let all_roles = self
1511 .permissions
1512 .as_slice()
1513 .iter()
1514 .filter(|permission| {
1515 matches!(
1516 permission,
1517 CapabilityPermissionV1::ApplicationRoleIdentity(_)
1518 )
1519 })
1520 .count();
1521 if matching_roles != 1 || all_roles != 1 {
1522 return Err(CapabilityGrantError::InvalidShape);
1523 }
1524 for target in inspection.targets() {
1525 let visible = self.field_visibility.iter().any(|entry| {
1526 entry.lineage() == target.lineage()
1527 && entry.entity_type() == target.entity_type()
1528 && (entry.fields().binary_search(&target.field()).is_ok()
1529 || entry.secret_fields().binary_search(&target.field()).is_ok())
1530 });
1531 let row_limited = self.row_policy.as_ref().is_some_and(|row_policy| {
1532 row_policy.bindings().iter().any(|binding| {
1533 binding.lineage() == target.lineage()
1534 && binding.entity_type() == target.entity_type()
1535 })
1536 });
1537 if !visible || (target.allow_counts() && row_limited) {
1538 return Err(CapabilityGrantError::InvalidShape);
1539 }
1540 }
1541 self.vector_inspection = Some(inspection);
1542 validate_capability_payload_bytes(self.semantic_bytes()?)?;
1543 Ok(self)
1544 }
1545
1546 #[doc(hidden)]
1548 #[must_use]
1549 pub const fn internal_vector_inspection(&self) -> Option<&CapabilityVectorInspectionGrantV1> {
1550 self.vector_inspection.as_ref()
1551 }
1552
1553 pub fn semantic_bytes(&self) -> Result<usize, CapabilityGrantError> {
1555 let base = capability_grant_semantic_bytes_parts(
1556 &self.tenant_scope,
1557 &self.partition_scope,
1558 &self.permissions,
1559 &self.field_visibility,
1560 &self.approval_required,
1561 )?;
1562 let with_row_policy = match &self.row_policy {
1563 Some(extension) => base
1564 .checked_add(row_policy_semantic_bytes(extension)?)
1565 .ok_or(CapabilityGrantError::SizeOverflow)?,
1566 None => base,
1567 };
1568 let with_export = match &self.export {
1569 Some(extension) => with_row_policy
1570 .checked_add(export_semantic_bytes(extension)?)
1571 .ok_or(CapabilityGrantError::SizeOverflow)?,
1572 None => with_row_policy,
1573 };
1574 let with_reimport = match &self.reimport {
1575 Some(extension) => with_export
1576 .checked_add(reimport_semantic_bytes(extension)?)
1577 .ok_or(CapabilityGrantError::SizeOverflow)?,
1578 None => with_export,
1579 };
1580 match &self.vector_inspection {
1581 Some(extension) => with_reimport
1582 .checked_add(vector_inspection_semantic_bytes(extension)?)
1583 .ok_or(CapabilityGrantError::SizeOverflow),
1584 None => Ok(with_reimport),
1585 }
1586 }
1587}
1588
1589fn vector_inspection_semantic_bytes(
1590 extension: &CapabilityVectorInspectionGrantV1,
1591) -> Result<usize, CapabilityGrantError> {
1592 extension
1593 .targets()
1594 .iter()
1595 .try_fold(36usize, |total, target| {
1596 total
1597 .checked_add(framed_capability_bytes(target.lineage().as_bytes().len())?)
1598 .and_then(|value| value.checked_add(9))
1599 .ok_or(CapabilityGrantError::SizeOverflow)
1600 })
1601}
1602
1603fn reimport_semantic_bytes(
1604 extension: &CapabilityApplicationReimportGrantV1,
1605) -> Result<usize, CapabilityGrantError> {
1606 checked_capability_sum([
1607 framed_capability_bytes(extension.lineage.as_bytes().len())?,
1608 16,
1609 32,
1610 1,
1611 ])
1612}
1613
1614fn export_semantic_bytes(
1615 extension: &CapabilityExportGrantV1,
1616) -> Result<usize, CapabilityGrantError> {
1617 extension
1618 .applications
1619 .iter()
1620 .try_fold(4usize, |total, grant| {
1621 total
1622 .checked_add(framed_capability_bytes(grant.lineage.as_bytes().len())?)
1623 .and_then(|value| value.checked_add(5))
1624 .ok_or(CapabilityGrantError::SizeOverflow)
1625 })
1626}
1627
1628fn row_policy_semantic_bytes(
1629 extension: &CapabilityRowPolicyGrantV1,
1630) -> Result<usize, CapabilityGrantError> {
1631 extension.bindings.iter().try_fold(
1632 32usize
1633 .checked_add(extension.principal_facts.internal_canonical_bytes().len())
1634 .and_then(|value| value.checked_add(4))
1635 .ok_or(CapabilityGrantError::SizeOverflow)?,
1636 |total, binding| {
1637 total
1638 .checked_add(binding.lineage.as_bytes().len())
1639 .and_then(|value| value.checked_add(binding.policy_name.as_str().len()))
1640 .and_then(|value| value.checked_add(4 + 4 + binding.operations.len()))
1641 .ok_or(CapabilityGrantError::SizeOverflow)
1642 },
1643 )
1644}
1645
1646fn permission_set_semantic_bytes(
1647 permissions: &[CapabilityPermissionV1],
1648) -> Result<usize, CapabilityGrantError> {
1649 permissions.iter().try_fold(4usize, |total, permission| {
1650 total
1651 .checked_add(framed_capability_bytes(
1652 capability_permission_semantic_bytes(permission),
1653 )?)
1654 .ok_or(CapabilityGrantError::SizeOverflow)
1655 })
1656}
1657
1658fn capability_permission_semantic_bytes(permission: &CapabilityPermissionV1) -> usize {
1659 match permission {
1660 CapabilityPermissionV1::Unparameterized(_) => 1,
1661 CapabilityPermissionV1::ExplainCommand(lineage, _)
1662 | CapabilityPermissionV1::InvokeCommand(lineage, _)
1663 | CapabilityPermissionV1::ReadEntity(lineage, _)
1664 | CapabilityPermissionV1::ScanIndex(lineage, _)
1665 | CapabilityPermissionV1::QueryProjection(lineage, _)
1666 | CapabilityPermissionV1::ReadProjectionStatus(lineage, _) => {
1667 1 + 4 + lineage.as_bytes().len() + 4
1668 }
1669 CapabilityPermissionV1::ExplainNamedQuery(lineage, _, name)
1670 | CapabilityPermissionV1::ExecuteNamedQuery(lineage, _, name) => {
1671 1 + 4 + lineage.as_bytes().len() + 32 + 4 + name.as_str().len()
1672 }
1673 CapabilityPermissionV1::ApplicationRoleIdentity(_) => 1 + 32,
1674 CapabilityPermissionV1::MigrateContract(lineage) => 1 + 4 + lineage.as_bytes().len(),
1675 CapabilityPermissionV1::InstallApplication(lineage) => 1 + 4 + lineage.as_bytes().len(),
1676 CapabilityPermissionV1::ConsumeEventStream(lineage, _, name)
1677 | CapabilityPermissionV1::SeekEventStreamConsumer(lineage, _, name)
1678 | CapabilityPermissionV1::WatchNamedQuery(lineage, _, name)
1679 | CapabilityPermissionV1::ConsumeContextualSubscription(lineage, _, name) => {
1680 1 + 4 + lineage.as_bytes().len() + 32 + 4 + name.as_str().len()
1681 }
1682 }
1683}
1684
1685fn explicit_partitions_semantic_bytes(
1686 values: &[ScopedPartitionV1],
1687) -> Result<usize, CapabilityGrantError> {
1688 values.iter().try_fold(5usize, |total, value| {
1689 let entry = checked_capability_sum([
1690 framed_capability_bytes(value.lineage.as_bytes().len())?,
1691 framed_capability_bytes(value.partition_key.as_bytes().len())?,
1692 ])?;
1693 total
1694 .checked_add(framed_capability_bytes(entry)?)
1695 .ok_or(CapabilityGrantError::SizeOverflow)
1696 })
1697}
1698
1699fn partition_scope_semantic_bytes(scope: &PartitionScopeV1) -> Result<usize, CapabilityGrantError> {
1700 match scope {
1701 PartitionScopeV1::All => Ok(1),
1702 PartitionScopeV1::Explicit(values) => explicit_partitions_semantic_bytes(values),
1703 }
1704}
1705
1706fn field_visibility_semantic_bytes(
1707 entries: &[EntityFieldVisibilityV1],
1708) -> Result<usize, CapabilityGrantError> {
1709 entries.iter().try_fold(4usize, |total, entry| {
1710 let fields = entry
1711 .fields
1712 .len()
1713 .checked_add(entry.secret_fields.len())
1714 .and_then(|count| count.checked_mul(4))
1715 .ok_or(CapabilityGrantError::SizeOverflow)?;
1716 let value = checked_capability_sum([
1717 framed_capability_bytes(entry.lineage.as_bytes().len())?,
1718 4,
1719 4,
1720 fields,
1721 ])?;
1722 total
1723 .checked_add(framed_capability_bytes(value)?)
1724 .ok_or(CapabilityGrantError::SizeOverflow)
1725 })
1726}
1727
1728fn capability_grant_semantic_bytes_parts(
1729 tenant_scope: &TenantScope,
1730 partition_scope: &PartitionScopeV1,
1731 permissions: &CapabilityPermissionsV1,
1732 field_visibility: &[EntityFieldVisibilityV1],
1733 approval_required: &[CapabilityPermissionKindV1],
1734) -> Result<usize, CapabilityGrantError> {
1735 checked_capability_sum([
1736 framed_capability_bytes(tenant_scope.to_canonical_bytes().len())?,
1737 partition_scope_semantic_bytes(partition_scope)?,
1738 permission_set_semantic_bytes(permissions.as_slice())?,
1739 field_visibility_semantic_bytes(field_visibility)?,
1740 2,
1741 4usize
1742 .checked_add(approval_required.len())
1743 .ok_or(CapabilityGrantError::SizeOverflow)?,
1744 ])
1745}
1746
1747fn framed_capability_bytes(content_bytes: usize) -> Result<usize, CapabilityGrantError> {
1748 4usize
1749 .checked_add(content_bytes)
1750 .ok_or(CapabilityGrantError::SizeOverflow)
1751}
1752
1753fn checked_capability_sum(
1754 parts: impl IntoIterator<Item = usize>,
1755) -> Result<usize, CapabilityGrantError> {
1756 parts.into_iter().try_fold(0usize, |total, part| {
1757 total
1758 .checked_add(part)
1759 .ok_or(CapabilityGrantError::SizeOverflow)
1760 })
1761}
1762
1763fn validate_capability_payload_bytes(bytes: usize) -> Result<(), CapabilityGrantError> {
1764 if bytes > MAX_CAPABILITY_PAYLOAD_BYTES {
1765 return Err(CapabilityGrantError::LimitExceeded);
1766 }
1767 Ok(())
1768}
1769
1770fn append_lineage(bytes: &mut Vec<u8>, lineage: &ContractLineage) {
1771 append_bytes(bytes, lineage.as_bytes());
1772}
1773
1774fn append_bytes(output: &mut Vec<u8>, value: &[u8]) {
1775 output.extend_from_slice(&(value.len() as u32).to_be_bytes());
1776 output.extend_from_slice(value);
1777}
1778
1779impl crate::InspectOwnedCapacityV1 for CapabilityRowPolicyGrantV1 {
1781 fn inspect_owned_capacity(
1782 &self,
1783 inspection: &mut crate::OwnedCapacityInspectionV1,
1784 ) -> Result<(), crate::OwnedCapacityInspectionErrorV1> {
1785 let Self {
1786 principal_facts,
1787 bindings,
1788 application_role_hash,
1789 } = self;
1790 inspection.visit(principal_facts)?;
1791 inspection.inline(application_role_hash)?;
1792 inspection.visit(bindings)?;
1793 Ok(())
1794 }
1795}
1796
1797impl crate::InspectOwnedCapacityV1 for CapabilityRowPolicyBindingV1 {
1798 fn inspect_owned_capacity(
1799 &self,
1800 inspection: &mut crate::OwnedCapacityInspectionV1,
1801 ) -> Result<(), crate::OwnedCapacityInspectionErrorV1> {
1802 let Self {
1803 lineage,
1804 policy_name,
1805 operations,
1806 entity_type,
1807 } = self;
1808 inspection.visit(lineage)?;
1809 inspection.visit(policy_name)?;
1810 inspection.inline(entity_type)?;
1811 inspection.visit(operations)?;
1812 Ok(())
1813 }
1814}
1815
1816crate::inspect_owned_capacity_copy_v1!(CapabilityRowPolicyOperationV1);
1817
1818#[cfg(test)]
1819mod tests {
1820 use super::*;
1821
1822 #[test]
1823 fn replication_permission_cannot_be_bound_to_an_application_role() {
1825 let kind = CapabilityPermissionKindV1::from_tag(0x21)
1826 .expect("replication has a dedicated administrative permission");
1827 let replication = CapabilityPermissionV1::unparameterized(kind).expect("permission");
1828 let grant = |permissions| {
1829 CapabilityGrantV1::new(
1830 TenantScope::Global,
1831 PartitionScopeV1::All,
1832 CapabilityPermissionsV1::new(permissions).expect("canonical permissions"),
1833 Vec::new(),
1834 NonZeroU16::new(1).expect("bound"),
1835 Vec::new(),
1836 )
1837 };
1838 assert!(grant(vec![replication.clone()]).is_ok());
1839 assert_eq!(
1840 grant(vec![
1841 replication,
1842 CapabilityPermissionV1::ApplicationRoleIdentity(ApplicationRoleHash::from_bytes(
1843 [0x42; 32],
1844 )),
1845 ]),
1846 Err(CapabilityGrantError::InvalidShape)
1847 );
1848 }
1849
1850 #[test]
1851 fn permission_tags_are_closed_and_stable() {
1852 for tag in 1..=34 {
1853 let kind = CapabilityPermissionKindV1::from_tag(tag).expect("known tag");
1854 assert_eq!(kind.tag(), tag);
1855 }
1856 assert_eq!(CapabilityPermissionKindV1::from_tag(0), None);
1857 assert_eq!(CapabilityPermissionKindV1::from_tag(35), None);
1858 }
1859
1860 #[test]
1861 fn parameterized_permission_cannot_be_bare() {
1862 assert_eq!(
1863 CapabilityPermissionV1::unparameterized(CapabilityPermissionKindV1::InvokeCommand),
1864 Err(CapabilityGrantError::InvalidShape)
1865 );
1866 assert!(
1867 CapabilityPermissionV1::unparameterized(CapabilityPermissionKindV1::ReadHealth).is_ok()
1868 );
1869 assert_eq!(
1870 CapabilityPermissionV1::unparameterized(CapabilityPermissionKindV1::ExecuteNamedQuery),
1871 Err(CapabilityGrantError::InvalidShape)
1872 );
1873 assert_eq!(
1874 CapabilityPermissionV1::unparameterized(CapabilityPermissionKindV1::MigrateContract),
1875 Err(CapabilityGrantError::InvalidShape)
1876 );
1877 assert_eq!(
1878 CapabilityPermissionV1::unparameterized(CapabilityPermissionKindV1::ConsumeEventStream),
1879 Err(CapabilityGrantError::InvalidShape)
1880 );
1881 assert!(
1882 CapabilityPermissionV1::unparameterized(
1883 CapabilityPermissionKindV1::InspectVectorState,
1884 )
1885 .is_ok()
1886 );
1887 assert!(
1888 CapabilityPermissionV1::unparameterized(CapabilityPermissionKindV1::ExecuteAdHocQuery)
1889 .is_ok()
1890 );
1891 }
1892
1893 #[test]
1894 fn migration_permission_binds_the_exact_lineage() {
1895 let permission = CapabilityPermissionV1::MigrateContract(
1896 ContractLineage::new("ticketdesk").expect("lineage"),
1897 );
1898 let mut expected = vec![0x1a, 0, 0, 0, 10];
1899 expected.extend_from_slice(b"ticketdesk");
1900 assert_eq!(permission.canonical_key(), expected);
1901 }
1902
1903 #[test]
1904 fn reactive_permissions_bind_lineage_module_and_operation() {
1905 let lineage = ContractLineage::new("ticketdesk").expect("lineage");
1906 let module = ReactiveModuleHash::from_bytes([7; 32]);
1907 let operation = ReactiveOperationName::new("TicketActivity").expect("operation");
1908 let consume =
1909 CapabilityPermissionV1::ConsumeEventStream(lineage.clone(), module, operation.clone());
1910 let seek = CapabilityPermissionV1::SeekEventStreamConsumer(lineage, module, operation);
1911 assert_ne!(consume.canonical_key(), seek.canonical_key());
1912 assert_eq!(consume.canonical_key()[0], 0x1b);
1913 assert_eq!(seek.canonical_key()[0], 0x1c);
1914 assert!(
1915 consume
1916 .canonical_key()
1917 .windows(32)
1918 .any(|window| window == [7; 32])
1919 );
1920 }
1921
1922 #[test]
1923 fn vector_inspection_is_role_field_and_count_bound() {
1924 let lineage = ContractLineage::new("ticketdesk").expect("lineage");
1925 let role = ApplicationRoleHash::from_bytes([9; 32]);
1926 let entity = EntityTypeId::first();
1927 let field = FieldId::first();
1928 let permissions = CapabilityPermissionsV1::new(vec![
1929 CapabilityPermissionV1::ApplicationRoleIdentity(role),
1930 CapabilityPermissionV1::unparameterized(CapabilityPermissionKindV1::InspectVectorState)
1931 .expect("inspection permission"),
1932 ])
1933 .expect("permissions");
1934 let grant = CapabilityGrantV1::new(
1935 TenantScope::Global,
1936 PartitionScopeV1::All,
1937 permissions,
1938 vec![
1939 EntityFieldVisibilityV1::new(lineage.clone(), entity, vec![field])
1940 .expect("visibility"),
1941 ],
1942 NonZeroU16::new(50).expect("limit"),
1943 Vec::new(),
1944 )
1945 .expect("grant");
1946 let counts = CapabilityVectorInspectionTargetV1::new(lineage.clone(), entity, field, true);
1947 let inspection =
1948 CapabilityVectorInspectionGrantV1::new(role, vec![counts.clone()]).expect("inspection");
1949 let granted = grant
1950 .clone()
1951 .with_vector_inspection(inspection)
1952 .expect("vector inspection grant");
1953 assert!(
1954 granted
1955 .internal_vector_inspection()
1956 .and_then(|value| value.target(&lineage, entity, field))
1957 .is_some_and(CapabilityVectorInspectionTargetV1::allow_counts)
1958 );
1959
1960 let narrowed = CapabilityVectorInspectionGrantV1::new(
1961 role,
1962 vec![CapabilityVectorInspectionTargetV1::new(
1963 lineage.clone(),
1964 entity,
1965 field,
1966 false,
1967 )],
1968 )
1969 .expect("narrowed");
1970 assert!(
1971 narrowed.is_narrowing_of(
1972 granted
1973 .internal_vector_inspection()
1974 .expect("parent inspection")
1975 )
1976 );
1977
1978 let row_policy = CapabilityRowPolicyGrantV1::new(
1979 role,
1980 CapabilityPrincipalFactsV1::empty(),
1981 vec![
1982 CapabilityRowPolicyBindingV1::new(
1983 lineage,
1984 RowPolicyName::new("ticket_visibility").expect("policy"),
1985 entity,
1986 vec![CapabilityRowPolicyOperationV1::Read],
1987 )
1988 .expect("binding"),
1989 ],
1990 )
1991 .expect("row policy");
1992 assert_eq!(
1993 granted.with_row_policy(row_policy),
1994 Err(CapabilityGrantError::InvalidShape)
1995 );
1996 }
1997
1998 #[test]
1999 fn permission_sets_are_canonical_and_duplicate_free() {
2000 let values = vec![
2001 CapabilityPermissionV1::unparameterized(CapabilityPermissionKindV1::ReadHealth)
2002 .expect("permission"),
2003 CapabilityPermissionV1::unparameterized(CapabilityPermissionKindV1::ReadContract)
2004 .expect("permission"),
2005 ];
2006 let permissions = CapabilityPermissionsV1::new(values).expect("permissions");
2007 assert_eq!(
2008 permissions
2009 .as_slice()
2010 .iter()
2011 .map(CapabilityPermissionV1::kind)
2012 .collect::<Vec<_>>(),
2013 vec![
2014 CapabilityPermissionKindV1::ReadContract,
2015 CapabilityPermissionKindV1::ReadHealth,
2016 ]
2017 );
2018 let duplicate =
2019 CapabilityPermissionV1::unparameterized(CapabilityPermissionKindV1::ReadHealth)
2020 .expect("permission");
2021 assert_eq!(
2022 CapabilityPermissionsV1::new(vec![duplicate.clone(), duplicate]),
2023 Err(CapabilityGrantError::Duplicate)
2024 );
2025
2026 let lineage = ContractLineage::new("ticketdesk").expect("lineage");
2027 let command = CapabilityPermissionV1::InvokeCommand(lineage, CommandId::first());
2028 let exact = CapabilityPermissionsV1::new(vec![command.clone()]).expect("permissions");
2029 assert!(exact.contains_exact(&command));
2030 assert!(
2031 !exact.contains_exact(
2032 &CapabilityPermissionV1::unparameterized(CapabilityPermissionKindV1::ReadHealth)
2033 .expect("permission")
2034 )
2035 );
2036 }
2037
2038 #[test]
2039 fn retained_permission_keys_cover_the_closed_registry_exactly() {
2040 let lineage = ContractLineage::new("ticketdesk").expect("lineage");
2041 let query_module = QueryModuleHash::from_bytes([3; 32]);
2042 let query_name = QueryOperationName::new("TicketPage").expect("query name");
2043 let reactive_module = ReactiveModuleHash::from_bytes([4; 32]);
2044 let reactive_name = ReactiveOperationName::new("TicketActivity").expect("reactive name");
2045 let mut values = (1..=33)
2046 .filter_map(CapabilityPermissionKindV1::from_tag)
2047 .filter_map(|kind| CapabilityPermissionV1::unparameterized(kind).ok())
2048 .collect::<Vec<_>>();
2049 values.extend([
2050 CapabilityPermissionV1::ExplainCommand(lineage.clone(), CommandId::first()),
2051 CapabilityPermissionV1::InvokeCommand(lineage.clone(), CommandId::first()),
2052 CapabilityPermissionV1::ReadEntity(lineage.clone(), EntityTypeId::first()),
2053 CapabilityPermissionV1::ScanIndex(lineage.clone(), IndexId::first()),
2054 CapabilityPermissionV1::QueryProjection(lineage.clone(), ProjectionId::first()),
2055 CapabilityPermissionV1::ReadProjectionStatus(lineage.clone(), ProjectionId::first()),
2056 CapabilityPermissionV1::ExplainNamedQuery(
2057 lineage.clone(),
2058 query_module,
2059 query_name.clone(),
2060 ),
2061 CapabilityPermissionV1::ExecuteNamedQuery(lineage.clone(), query_module, query_name),
2062 CapabilityPermissionV1::ApplicationRoleIdentity(ApplicationRoleHash::from_bytes(
2063 [5; 32],
2064 )),
2065 CapabilityPermissionV1::MigrateContract(lineage.clone()),
2066 CapabilityPermissionV1::ConsumeEventStream(
2067 lineage.clone(),
2068 reactive_module,
2069 reactive_name.clone(),
2070 ),
2071 CapabilityPermissionV1::SeekEventStreamConsumer(
2072 lineage.clone(),
2073 reactive_module,
2074 reactive_name.clone(),
2075 ),
2076 CapabilityPermissionV1::WatchNamedQuery(
2077 lineage.clone(),
2078 reactive_module,
2079 reactive_name.clone(),
2080 ),
2081 CapabilityPermissionV1::ConsumeContextualSubscription(
2082 lineage.clone(),
2083 reactive_module,
2084 reactive_name,
2085 ),
2086 CapabilityPermissionV1::InstallApplication(lineage.clone()),
2087 ]);
2088
2089 let permissions = CapabilityPermissionsV1::new(values).expect("complete permission set");
2090 assert_eq!(permissions.0.len(), permissions.1.len());
2091 for (permission, retained_key) in permissions.0.iter().zip(permissions.1.iter()) {
2092 assert_eq!(retained_key, &permission.canonical_key());
2093 assert!(permissions.contains_exact(permission));
2094 }
2095 assert!(permissions.1.windows(2).all(|pair| pair[0] < pair[1]));
2096 }
2097
2098 #[test]
2099 fn capability_grant_clones_share_only_checked_immutable_collections() {
2100 fn grant() -> CapabilityGrantV1 {
2101 let lineage = ContractLineage::new("ticketdesk").expect("lineage");
2102 let permissions = CapabilityPermissionsV1::new(vec![
2103 CapabilityPermissionV1::unparameterized(CapabilityPermissionKindV1::ReadContract)
2104 .expect("permission"),
2105 CapabilityPermissionV1::unparameterized(CapabilityPermissionKindV1::ReadHealth)
2106 .expect("permission"),
2107 ])
2108 .expect("permissions");
2109 let visibility = EntityFieldVisibilityV1::new(
2110 lineage,
2111 EntityTypeId::first(),
2112 vec![FieldId::first()],
2113 )
2114 .expect("visibility");
2115 CapabilityGrantV1::new(
2116 TenantScope::Global,
2117 PartitionScopeV1::All,
2118 permissions,
2119 vec![visibility],
2120 NonZeroU16::new(10).expect("nonzero"),
2121 vec![CapabilityPermissionKindV1::ReadContract],
2122 )
2123 .expect("grant")
2124 }
2125
2126 let original = grant();
2127 let cloned = original.clone();
2128 assert!(crate::OwnedCapacitySliceV1::same_allocation(
2129 &original.permissions.0,
2130 &cloned.permissions.0
2131 ));
2132 assert!(crate::OwnedCapacitySliceV1::same_allocation(
2133 &original.permissions.1,
2134 &cloned.permissions.1
2135 ));
2136 assert!(crate::OwnedCapacitySliceV1::same_allocation(
2137 &original.field_visibility,
2138 &cloned.field_visibility
2139 ));
2140 assert!(crate::OwnedCapacitySliceV1::same_allocation(
2141 &original.field_visibility[0].fields,
2142 &cloned.field_visibility[0].fields
2143 ));
2144 assert!(crate::OwnedCapacitySliceV1::same_allocation(
2145 &original.approval_required,
2146 &cloned.approval_required
2147 ));
2148
2149 let independently_built = grant();
2150 assert_eq!(original, independently_built);
2151 assert_eq!(
2152 original.semantic_bytes(),
2153 independently_built.semantic_bytes()
2154 );
2155 assert!(!crate::OwnedCapacitySliceV1::same_allocation(
2156 &original.permissions.0,
2157 &independently_built.permissions.0
2158 ));
2159 assert!(!crate::OwnedCapacitySliceV1::same_allocation(
2160 &original.permissions.1,
2161 &independently_built.permissions.1
2162 ));
2163 }
2164
2165 #[test]
2166 fn query_scan_authority_accepts_the_shared_u16_ceiling() {
2167 let permissions = CapabilityPermissionsV1::new(vec![
2168 CapabilityPermissionV1::unparameterized(CapabilityPermissionKindV1::ReadHealth)
2169 .expect("permission"),
2170 ])
2171 .expect("permissions");
2172 let grant = CapabilityGrantV1::new(
2173 TenantScope::Global,
2174 PartitionScopeV1::All,
2175 permissions,
2176 Vec::new(),
2177 NonZeroU16::new(
2178 u16::try_from(crate::MAX_APPLICATION_QUERY_SCANNED_ROWS)
2179 .expect("shared scan ceiling fits u16"),
2180 )
2181 .expect("nonzero"),
2182 Vec::new(),
2183 )
2184 .expect("shared maximum authority");
2185 assert_eq!(
2186 u64::from(grant.max_scan_rows().get()),
2187 crate::MAX_APPLICATION_QUERY_SCANNED_ROWS
2188 );
2189 }
2190
2191 #[test]
2192 fn capability_payload_bound_accepts_exact_limit_only() {
2193 assert_eq!(
2194 validate_capability_payload_bytes(MAX_CAPABILITY_PAYLOAD_BYTES),
2195 Ok(())
2196 );
2197 assert_eq!(
2198 validate_capability_payload_bytes(MAX_CAPABILITY_PAYLOAD_BYTES + 1),
2199 Err(CapabilityGrantError::LimitExceeded)
2200 );
2201 }
2202
2203 #[test]
2204 fn row_policy_grants_are_role_bound_canonical_and_narrowing_only() {
2205 fn facts(groups: &[&str]) -> CapabilityPrincipalFactsV1 {
2206 CapabilityPrincipalFactsV1::new(vec![
2207 crate::CapabilityPrincipalFactV1::new(
2208 "groups",
2209 crate::CanonicalValue::list(
2210 groups
2211 .iter()
2212 .map(|group| crate::CanonicalValue::string(*group).expect("text"))
2213 .collect(),
2214 )
2215 .expect("list"),
2216 )
2217 .expect("fact"),
2218 ])
2219 .expect("facts")
2220 }
2221
2222 let role = ApplicationRoleHash::from_bytes([0x51; 32]);
2223 let lineage = ContractLineage::new("ticketdesk").expect("lineage");
2224 let parent_binding = CapabilityRowPolicyBindingV1::new(
2225 lineage.clone(),
2226 RowPolicyName::new("TicketVisible").expect("policy"),
2227 EntityTypeId::first(),
2228 vec![
2229 CapabilityRowPolicyOperationV1::Update,
2230 CapabilityRowPolicyOperationV1::Read,
2231 ],
2232 )
2233 .expect("binding");
2234 assert_eq!(
2235 parent_binding.operations(),
2236 [
2237 CapabilityRowPolicyOperationV1::Read,
2238 CapabilityRowPolicyOperationV1::Update,
2239 ]
2240 );
2241 let parent = CapabilityRowPolicyGrantV1::new(
2242 role,
2243 facts(&["authors", "operators"]),
2244 vec![parent_binding],
2245 )
2246 .expect("parent extension");
2247 let child = CapabilityRowPolicyGrantV1::new(
2248 role,
2249 facts(&["authors"]),
2250 vec![
2251 CapabilityRowPolicyBindingV1::new(
2252 lineage.clone(),
2253 RowPolicyName::new("TicketVisible").expect("policy"),
2254 EntityTypeId::first(),
2255 vec![CapabilityRowPolicyOperationV1::Read],
2256 )
2257 .expect("binding"),
2258 ],
2259 )
2260 .expect("child extension");
2261 assert!(child.is_narrowing_of(&parent));
2262
2263 let duplicate_entity = CapabilityRowPolicyGrantV1::new(
2264 role,
2265 CapabilityPrincipalFactsV1::empty(),
2266 vec![
2267 CapabilityRowPolicyBindingV1::new(
2268 lineage.clone(),
2269 RowPolicyName::new("TicketVisible").expect("policy"),
2270 EntityTypeId::first(),
2271 vec![CapabilityRowPolicyOperationV1::Read],
2272 )
2273 .expect("binding"),
2274 CapabilityRowPolicyBindingV1::new(
2275 lineage,
2276 RowPolicyName::new("TicketOwned").expect("policy"),
2277 EntityTypeId::first(),
2278 vec![CapabilityRowPolicyOperationV1::Read],
2279 )
2280 .expect("binding"),
2281 ],
2282 );
2283 assert_eq!(duplicate_entity, Err(CapabilityGrantError::Duplicate));
2284
2285 let base = CapabilityGrantV1::new(
2286 TenantScope::Global,
2287 PartitionScopeV1::All,
2288 CapabilityPermissionsV1::new(vec![CapabilityPermissionV1::ApplicationRoleIdentity(
2289 ApplicationRoleHash::from_bytes([0x52; 32]),
2290 )])
2291 .expect("permissions"),
2292 Vec::new(),
2293 NonZeroU16::MIN,
2294 Vec::new(),
2295 )
2296 .expect("grant");
2297 assert_eq!(
2298 base.with_row_policy(parent),
2299 Err(CapabilityGrantError::InvalidShape)
2300 );
2301 }
2302
2303 #[test]
2304 fn export_grants_are_canonical_bounded_and_explicit() {
2305 let whole = CapabilityApplicationExportGrantV1::new(
2306 ContractLineage::new("zeta").expect("lineage"),
2307 CapabilityApplicationExportScopeV1::WholeApplication,
2308 true,
2309 false,
2310 true,
2311 true,
2312 )
2313 .expect("whole grant");
2314 let principal = CapabilityApplicationExportGrantV1::new(
2315 ContractLineage::new("alpha").expect("lineage"),
2316 CapabilityApplicationExportScopeV1::PrincipalFiltered,
2317 false,
2318 true,
2319 false,
2320 false,
2321 )
2322 .expect("principal grant");
2323 let extension = CapabilityExportGrantV1::new(vec![whole.clone(), principal.clone()])
2324 .expect("canonical extension");
2325 assert_eq!(extension.applications()[0].lineage().as_str(), "alpha");
2326 assert_eq!(extension.applications()[1].lineage().as_str(), "zeta");
2327
2328 assert_eq!(
2329 CapabilityApplicationExportGrantV1::new(
2330 ContractLineage::new("empty").expect("lineage"),
2331 CapabilityApplicationExportScopeV1::WholeApplication,
2332 false,
2333 false,
2334 true,
2335 true,
2336 ),
2337 Err(CapabilityGrantError::InvalidShape)
2338 );
2339 assert_eq!(
2340 CapabilityExportGrantV1::new(vec![whole.clone(), whole]),
2341 Err(CapabilityGrantError::Duplicate)
2342 );
2343 assert_eq!(
2344 CapabilityExportGrantV1::new(Vec::new()),
2345 Err(CapabilityGrantError::Empty)
2346 );
2347 let over_bound = (0..=MAX_CAPABILITY_APPLICATION_EXPORT_GRANTS)
2348 .map(|index| {
2349 CapabilityApplicationExportGrantV1::new(
2350 ContractLineage::new(format!("lineage_{index:03}")).expect("lineage"),
2351 CapabilityApplicationExportScopeV1::WholeApplication,
2352 true,
2353 false,
2354 false,
2355 false,
2356 )
2357 .expect("application grant")
2358 })
2359 .collect();
2360 assert_eq!(
2361 CapabilityExportGrantV1::new(over_bound),
2362 Err(CapabilityGrantError::LimitExceeded)
2363 );
2364 assert_eq!(CapabilityApplicationExportScopeV1::from_tag(0), None);
2365 assert_eq!(CapabilityApplicationExportScopeV1::from_tag(3), None);
2366 }
2367
2368 #[test]
2369 fn reimport_authority_is_exact_campaign_bound_and_narrowing_only() {
2370 let mut campaign_bytes = [0x61; 16];
2371 campaign_bytes[6] = 0x71;
2372 campaign_bytes[8] = 0x81;
2373 let campaign =
2374 ApplicationInstallationCampaignId::from_bytes(campaign_bytes).expect("UUIDv7 campaign");
2375 let parent = CapabilityApplicationReimportGrantV1::new(
2376 ContractLineage::new("ticketdesk").expect("lineage"),
2377 campaign,
2378 ApplicationPortabilityManifestHash::from_bytes([0x62; 32]),
2379 CapabilityApplicationReimportScopeV1::WholeApplication,
2380 );
2381 let principal = CapabilityApplicationReimportGrantV1::new(
2382 ContractLineage::new("ticketdesk").expect("lineage"),
2383 campaign,
2384 ApplicationPortabilityManifestHash::from_bytes([0x62; 32]),
2385 CapabilityApplicationReimportScopeV1::PrincipalFiltered,
2386 );
2387 let substituted = CapabilityApplicationReimportGrantV1::new(
2388 ContractLineage::new("ticketdesk").expect("lineage"),
2389 campaign,
2390 ApplicationPortabilityManifestHash::from_bytes([0x63; 32]),
2391 CapabilityApplicationReimportScopeV1::PrincipalFiltered,
2392 );
2393
2394 assert!(principal.is_narrowing_of(&parent, true));
2395 assert!(!principal.is_narrowing_of(&parent, false));
2396 assert!(!parent.is_narrowing_of(&principal, true));
2397 assert!(!substituted.is_narrowing_of(&parent, true));
2398 assert_eq!(CapabilityApplicationReimportScopeV1::from_tag(0), None);
2399 assert_eq!(CapabilityApplicationReimportScopeV1::from_tag(3), None);
2400 }
2401
2402 #[test]
2403 fn export_scope_requires_distinct_current_authority() {
2404 let role = ApplicationRoleHash::from_bytes([0x71; 32]);
2405 let base = || {
2406 CapabilityGrantV1::new(
2407 TenantScope::Global,
2408 PartitionScopeV1::All,
2409 CapabilityPermissionsV1::new(vec![
2410 CapabilityPermissionV1::ApplicationRoleIdentity(role),
2411 ])
2412 .expect("permissions"),
2413 Vec::new(),
2414 NonZeroU16::MIN,
2415 Vec::new(),
2416 )
2417 .expect("base grant")
2418 };
2419 let principal_export = || {
2420 CapabilityExportGrantV1::new(vec![
2421 CapabilityApplicationExportGrantV1::new(
2422 ContractLineage::new("ticketdesk").expect("lineage"),
2423 CapabilityApplicationExportScopeV1::PrincipalFiltered,
2424 true,
2425 true,
2426 false,
2427 false,
2428 )
2429 .expect("application grant"),
2430 ])
2431 .expect("export grant")
2432 };
2433 assert_eq!(
2434 base().with_export(principal_export()),
2435 Err(CapabilityGrantError::InvalidShape)
2436 );
2437
2438 let policy = CapabilityRowPolicyGrantV1::new(
2439 role,
2440 CapabilityPrincipalFactsV1::empty(),
2441 vec![
2442 CapabilityRowPolicyBindingV1::new(
2443 ContractLineage::new("ticketdesk").expect("lineage"),
2444 RowPolicyName::new("TicketVisible").expect("policy"),
2445 EntityTypeId::first(),
2446 vec![CapabilityRowPolicyOperationV1::Read],
2447 )
2448 .expect("binding"),
2449 ],
2450 )
2451 .expect("policy extension");
2452 let protected = base()
2453 .with_row_policy(policy)
2454 .expect("protected grant")
2455 .with_export(principal_export())
2456 .expect("principal export authority");
2457 assert_eq!(
2458 protected.internal_export().expect("export").applications()[0].scope(),
2459 CapabilityApplicationExportScopeV1::PrincipalFiltered
2460 );
2461
2462 let tenant_bound = CapabilityGrantV1::new(
2463 TenantScope::Tenant(crate::TenantId::new("tenant-a").expect("tenant")),
2464 PartitionScopeV1::All,
2465 CapabilityPermissionsV1::new(Vec::new()).expect("permissions"),
2466 Vec::new(),
2467 NonZeroU16::MIN,
2468 Vec::new(),
2469 )
2470 .expect("tenant-bound grant");
2471 let whole_export = CapabilityExportGrantV1::new(vec![
2472 CapabilityApplicationExportGrantV1::new(
2473 ContractLineage::new("ticketdesk").expect("lineage"),
2474 CapabilityApplicationExportScopeV1::WholeApplication,
2475 true,
2476 true,
2477 true,
2478 true,
2479 )
2480 .expect("application grant"),
2481 ])
2482 .expect("export grant");
2483 assert_eq!(
2484 tenant_bound.with_export(whole_export),
2485 Err(CapabilityGrantError::InvalidShape)
2486 );
2487 }
2488}