Skip to main content

riffdb_types/
capability_grant.rs

1//! Canonical bounded capability grants shared by authentication and policy.
2
3use 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
12/// Maximum explicit partition entries retained by one grant.
13pub const MAX_CAPABILITY_PARTITIONS: usize = 1_024;
14/// Maximum permission atoms retained by one grant.
15pub const MAX_CAPABILITY_PERMISSIONS: usize = 8_192;
16/// Maximum field-visibility entries and total listed fields.
17pub const MAX_CAPABILITY_FIELD_VISIBILITY: usize = 65_535;
18/// Maximum compiler-selected row-policy bindings carried by one capability.
19pub const MAX_CAPABILITY_ROW_POLICY_BINDINGS: usize = 1_024;
20/// Maximum lineage-scoped application export grants carried by one capability.
21pub const MAX_CAPABILITY_APPLICATION_EXPORT_GRANTS: usize = 256;
22/// Maximum exact vector-inspection targets carried by one application role.
23pub const MAX_CAPABILITY_VECTOR_INSPECTION_TARGETS: usize = 256;
24/// Maximum semantic bytes in one complete durable capability payload.
25pub const MAX_CAPABILITY_PAYLOAD_BYTES: usize = 1024 * 1024;
26
27/// A safe failure to construct or account for a bounded capability grant.
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum CapabilityGrantError {
30    /// A supplied collection is empty when at least one item is required.
31    Empty,
32    /// A supplied count or semantic size exceeds its accepted bound.
33    LimitExceeded,
34    /// The same semantic identity occurs more than once.
35    Duplicate,
36    /// A permission has an invalid closed shape.
37    InvalidShape,
38    /// Checked semantic byte accounting overflowed.
39    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/// The closed v1 permission registry without its optional stable parameter.
57#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
58pub enum CapabilityPermissionKindV1 {
59    /// Validate contract source.
60    ValidateContract,
61    /// Read active or historical contract metadata.
62    ReadContract,
63    /// Explain a lineage-scoped command.
64    ExplainCommand,
65    /// Deploy a contract.
66    DeployContract,
67    /// Invoke and resolve a lineage-scoped command.
68    InvokeCommand,
69    /// Read a lineage-scoped entity type.
70    ReadEntity,
71    /// Scan a lineage-scoped index.
72    ScanIndex,
73    /// Query a lineage-scoped projection.
74    QueryProjection,
75    /// Read a lineage-scoped projection status.
76    ReadProjectionStatus,
77    /// Read one commit.
78    ReadCommit,
79    /// Scan commits.
80    ScanCommits,
81    /// Subscribe to commits.
82    SubscribeCommits,
83    /// Read provenance.
84    ReadProvenance,
85    /// Inspect outbox status.
86    InspectOutbox,
87    /// Read server health.
88    ReadHealth,
89    /// Read server statistics.
90    ReadStatistics,
91    /// Create a subset capability.
92    CreateCapability,
93    /// Revoke a subset capability.
94    RevokeCapability,
95    /// Administer capabilities in this database and environment.
96    AdministerCapabilities,
97    /// Check ad-hoc RiffQL source.
98    CheckAdHocQuery,
99    /// Explain ad-hoc RiffQL source.
100    ExplainAdHocQuery,
101    /// Execute ad-hoc RiffQL source.
102    ExecuteAdHocQuery,
103    /// Explain one exact named deployed query.
104    ExplainNamedQuery,
105    /// Execute one exact named deployed query.
106    ExecuteNamedQuery,
107    /// Non-authorizing identity of the compiled application role that produced the grant.
108    ApplicationRoleIdentity,
109    /// Migrate one exact contract lineage.
110    MigrateContract,
111    /// Consume one exact immutable event stream.
112    ConsumeEventStream,
113    /// Administratively seek one exact immutable event stream consumer.
114    SeekEventStreamConsumer,
115    /// Watch one exact immutable named-query projection.
116    WatchNamedQuery,
117    /// Consume one exact immutable contextual subscription.
118    ConsumeContextualSubscription,
119    /// Install or upgrade one exact application lineage.
120    InstallApplication,
121    /// Inspect one exact compiler-declared vector field.
122    InspectVectorState,
123    /// Replicate complete authoritative changelog bytes; administrative only.
124    ReplicateChangelog,
125    /// Durably fence a primary for one registered follower; administrative only.
126    FenceReplicationPrimary,
127}
128
129impl CapabilityPermissionKindV1 {
130    /// Returns the immutable v1 permission tag.
131    #[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    /// Decodes a stable v1 permission tag.
172    #[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/// One canonical permission atom with any required lineage-scoped ID.
236#[derive(Clone, Debug, Eq, PartialEq)]
237pub enum CapabilityPermissionV1 {
238    /// An unparameterized permission.
239    Unparameterized(CapabilityPermissionKindV1),
240    /// Explain a lineage-scoped command.
241    ExplainCommand(ContractLineage, CommandId),
242    /// Invoke a lineage-scoped command.
243    InvokeCommand(ContractLineage, CommandId),
244    /// Read a lineage-scoped entity type.
245    ReadEntity(ContractLineage, EntityTypeId),
246    /// Scan a lineage-scoped index.
247    ScanIndex(ContractLineage, IndexId),
248    /// Query a lineage-scoped projection.
249    QueryProjection(ContractLineage, ProjectionId),
250    /// Read status for a lineage-scoped projection.
251    ReadProjectionStatus(ContractLineage, ProjectionId),
252    /// Explain one exact named query in one immutable module.
253    ExplainNamedQuery(ContractLineage, QueryModuleHash, QueryOperationName),
254    /// Execute one exact named query in one immutable module.
255    ExecuteNamedQuery(ContractLineage, QueryModuleHash, QueryOperationName),
256    /// Audit-only identity of the exact compiled application role.
257    ApplicationRoleIdentity(ApplicationRoleHash),
258    /// Migrate one exact contract lineage.
259    MigrateContract(ContractLineage),
260    /// Consume an exact stream from one immutable reactive module.
261    ConsumeEventStream(ContractLineage, ReactiveModuleHash, ReactiveOperationName),
262    /// Seek a consumer of an exact stream from one immutable reactive module.
263    SeekEventStreamConsumer(ContractLineage, ReactiveModuleHash, ReactiveOperationName),
264    /// Watch an exact query watch from one immutable reactive module.
265    WatchNamedQuery(ContractLineage, ReactiveModuleHash, ReactiveOperationName),
266    /// Consume an exact contextual subscription from one immutable reactive module.
267    ConsumeContextualSubscription(ContractLineage, ReactiveModuleHash, ReactiveOperationName),
268    /// Install or upgrade one exact application lineage.
269    InstallApplication(ContractLineage),
270}
271
272impl CapabilityPermissionV1 {
273    /// Constructs an unparameterized atom, rejecting parameterized kinds.
274    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    /// Returns the stable permission kind.
282    #[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    /// Returns the canonical comparison key.
311    #[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/// Canonical permission set.
357#[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    /// Sorts by canonical bytes and rejects duplicates or excessive counts.
382    ///
383    /// An empty set is a valid grant with no operation permission. Bootstrap
384    /// separately requires the administration permission.
385    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    /// Returns atoms in canonical order.
408    #[must_use]
409    pub fn as_slice(&self) -> &[CapabilityPermissionV1] {
410        &self.0
411    }
412
413    /// Returns whether any atom has the supplied permission kind.
414    #[must_use]
415    pub fn contains_kind(&self, kind: CapabilityPermissionKindV1) -> bool {
416        self.0.iter().any(|permission| permission.kind() == kind)
417    }
418
419    /// Returns whether the exact canonical permission atom is present.
420    #[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/// One lineage-scoped complete partition key.
430#[derive(Clone, Debug, Eq, PartialEq)]
431pub struct ScopedPartitionV1 {
432    lineage: ContractLineage,
433    partition_key: PartitionKey,
434}
435
436impl ScopedPartitionV1 {
437    /// Constructs a scoped partition identity.
438    #[must_use]
439    pub const fn new(lineage: ContractLineage, partition_key: PartitionKey) -> Self {
440        Self {
441            lineage,
442            partition_key,
443        }
444    }
445
446    /// Returns the canonical comparison key.
447    #[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    /// Returns the exact contract lineage.
456    #[must_use]
457    pub const fn lineage(&self) -> &ContractLineage {
458        &self.lineage
459    }
460
461    /// Returns the complete partition key.
462    #[must_use]
463    pub const fn partition_key(&self) -> &PartitionKey {
464        &self.partition_key
465    }
466}
467
468/// Closed partition scope in one capability grant.
469#[derive(Clone, Debug, Eq, PartialEq)]
470pub enum PartitionScopeV1 {
471    /// Every partition within otherwise authorized contract operations.
472    All,
473    /// A nonempty canonical set of lineage-scoped complete keys.
474    Explicit(Vec<ScopedPartitionV1>),
475}
476
477impl PartitionScopeV1 {
478    /// Returns the stable v1 semantic tag.
479    #[must_use]
480    pub const fn tag(&self) -> u8 {
481        match self {
482            Self::All => 0x01,
483            Self::Explicit(_) => 0x02,
484        }
485    }
486
487    /// Constructs a canonical explicit scope.
488    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    /// Returns canonical explicit entries, or `None` for all partitions.
507    #[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/// One canonical entity-field visibility entry.
517///
518/// Ordinary `fields` never reveal a secret-classified field (ADR-0118):
519/// enumerating every field — the de-facto wildcard and the shape every role
520/// default produces — is inert for secrets. Revealing one requires naming it
521/// in the separate `secret_fields` list, which no derivation path populates
522/// implicitly.
523#[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    /// Sorts and validates a nonempty field set with no secret-field naming.
533    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    /// Sorts and validates a visibility entry that explicitly names
542    /// secret-classified fields for reveal (ADR-0118 item 3).
543    ///
544    /// The named secret fields count against the same bounds as ordinary
545    /// visibility. At least one of the ordinary or secret lists must be
546    /// nonempty.
547    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    /// Returns the exact contract lineage.
583    #[must_use]
584    pub const fn lineage(&self) -> &ContractLineage {
585        &self.lineage
586    }
587
588    /// Returns the stable entity type identity.
589    #[must_use]
590    pub const fn entity_type(&self) -> EntityTypeId {
591        self.entity_type
592    }
593
594    /// Returns visible stable fields in increasing order.
595    #[must_use]
596    pub fn fields(&self) -> &[FieldId] {
597        &self.fields
598    }
599
600    /// Returns explicitly named secret-classified fields in increasing order
601    /// (ADR-0118): the only naming surface that reveals a secret field.
602    #[must_use]
603    pub fn secret_fields(&self) -> &[FieldId] {
604        &self.secret_fields
605    }
606}
607
608/// Closed operation classes that one durable row-policy binding may authorize.
609#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
610#[repr(u8)]
611pub enum CapabilityRowPolicyOperationV1 {
612    /// Read one protected row.
613    Read = 1,
614    /// Create one protected row from proposed state.
615    Create = 2,
616    /// Update one protected row after checking current and successor state.
617    Update = 3,
618    /// Delete one protected transaction-current row.
619    Delete = 4,
620}
621
622impl CapabilityRowPolicyOperationV1 {
623    /// Decodes one closed durable operation tag.
624    #[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    /// Returns the stable durable operation tag.
636    #[must_use]
637    pub const fn tag(self) -> u8 {
638        self as u8
639    }
640}
641
642/// One exact compiler-selected row policy for one protected entity.
643#[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    /// Constructs one canonical nonempty operation binding.
653    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    /// Exact contract lineage.
693    #[must_use]
694    pub const fn lineage(&self) -> &ContractLineage {
695        &self.lineage
696    }
697
698    /// Exact symbolic policy name.
699    #[must_use]
700    pub const fn policy_name(&self) -> &RowPolicyName {
701        &self.policy_name
702    }
703
704    /// Protected stable entity identity.
705    #[must_use]
706    pub const fn entity_type(&self) -> EntityTypeId {
707        self.entity_type
708    }
709
710    /// Strictly increasing nonempty operation set.
711    #[must_use]
712    pub fn operations(&self) -> &[CapabilityRowPolicyOperationV1] {
713        &self.operations
714    }
715}
716
717/// Complete V1 row-policy authority extension carried only by CapabilityRecordV4.
718#[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    /// Constructs a canonical complete role/fact/policy selection.
727    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    /// Exact compiled application-role identity.
753    #[must_use]
754    pub const fn application_role_hash(&self) -> ApplicationRoleHash {
755        self.application_role_hash
756    }
757
758    /// Complete canonical current principal facts. Formatting remains redacted.
759    #[doc(hidden)]
760    #[must_use]
761    pub const fn internal_principal_facts(&self) -> &CapabilityPrincipalFactsV1 {
762        &self.principal_facts
763    }
764
765    /// Canonical compiler-selected policy bindings.
766    #[must_use]
767    pub fn bindings(&self) -> &[CapabilityRowPolicyBindingV1] {
768        &self.bindings
769    }
770
771    /// True only when facts, policies, and operation classes hold or narrow.
772    #[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/// Closed V1 scope for one lineage-scoped application export grant.
807#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
808pub enum CapabilityApplicationExportScopeV1 {
809    /// Apply current role, field-visibility, principal-fact, and row-policy authority.
810    PrincipalFiltered,
811    /// Explicit operator authority over the complete named application lineage.
812    WholeApplication,
813}
814
815impl CapabilityApplicationExportScopeV1 {
816    /// Returns the stable V1 semantic tag.
817    #[must_use]
818    pub const fn tag(self) -> u8 {
819        match self {
820            Self::PrincipalFiltered => 1,
821            Self::WholeApplication => 2,
822        }
823    }
824
825    /// Decodes a stable V1 semantic tag, rejecting unspecified and unknown values.
826    #[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/// One explicit lineage, scope, and portable record-class export authority.
837#[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    /// Constructs one checked grant. Supporting records cannot be granted
849    /// without at least one portable application-data class.
850    #[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    /// Exact symbolic application lineage.
873    #[must_use]
874    pub const fn lineage(&self) -> &ContractLineage {
875        &self.lineage
876    }
877
878    /// Selected current-policy or whole-application authority class.
879    #[must_use]
880    pub const fn scope(&self) -> CapabilityApplicationExportScopeV1 {
881        self.scope
882    }
883
884    /// Whether symbolic entity records may be exported.
885    #[must_use]
886    pub const fn entities(&self) -> bool {
887        self.entities
888    }
889
890    /// Whether symbolic durable events may be exported.
891    #[must_use]
892    pub const fn events(&self) -> bool {
893        self.events
894    }
895
896    /// Whether separately protected provenance records may accompany the data.
897    #[must_use]
898    pub const fn provenance(&self) -> bool {
899        self.provenance
900    }
901
902    /// Whether separately protected public-audit records may accompany the data.
903    #[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/// Complete canonical V1 application-export authority extension.
925#[derive(Clone, Eq, PartialEq)]
926pub struct CapabilityExportGrantV1 {
927    applications: crate::OwnedCapacitySliceV1<CapabilityApplicationExportGrantV1>,
928}
929
930impl CapabilityExportGrantV1 {
931    /// Constructs one bounded lineage-ordered extension.
932    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    /// Canonical lineage-ordered export grants.
954    #[must_use]
955    pub fn applications(&self) -> &[CapabilityApplicationExportGrantV1] {
956        &self.applications
957    }
958
959    /// True only when every child lineage, scope, and class is equal or narrower.
960    #[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/// Closed V1 scope for one exact application-reimport campaign grant.
986#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
987pub enum CapabilityApplicationReimportScopeV1 {
988    /// Apply the exact compiled application-role row policy during reconstitution.
989    PrincipalFiltered,
990    /// Operator authority over the complete application, still under the declared role policy.
991    WholeApplication,
992}
993
994impl CapabilityApplicationReimportScopeV1 {
995    /// Returns the stable V1 semantic tag.
996    #[must_use]
997    pub const fn tag(self) -> u8 {
998        match self {
999            Self::PrincipalFiltered => 1,
1000            Self::WholeApplication => 2,
1001        }
1002    }
1003
1004    /// Decodes a stable V1 semantic tag.
1005    #[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/// Distinct authority for one exact not-ready reimport campaign and manifest.
1016#[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    /// Constructs an exact campaign-bound reimport grant.
1026    #[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    /// Exact application lineage.
1042    #[must_use]
1043    pub const fn lineage(&self) -> &ContractLineage {
1044        &self.lineage
1045    }
1046
1047    /// Exact destination installation/reimport campaign.
1048    #[must_use]
1049    pub const fn campaign_id(&self) -> ApplicationInstallationCampaignId {
1050        self.campaign_id
1051    }
1052
1053    /// Exact adapter-owned portability manifest.
1054    #[must_use]
1055    pub const fn portability_manifest_hash(&self) -> ApplicationPortabilityManifestHash {
1056        self.portability_manifest_hash
1057    }
1058
1059    /// Current-policy or whole-application scope.
1060    #[must_use]
1061    pub const fn scope(&self) -> CapabilityApplicationReimportScopeV1 {
1062        self.scope
1063    }
1064
1065    /// True only for the exact same campaign and manifest with equal or narrower scope.
1066    #[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/// One exact compiler-selected vector field and its count-disclosure posture.
1094#[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    /// Constructs an exact symbolic vector target lowered to stable contract IDs.
1104    #[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    /// Exact contract lineage.
1128    #[must_use]
1129    pub const fn lineage(&self) -> &ContractLineage {
1130        &self.lineage
1131    }
1132
1133    /// Exact stable entity identity.
1134    #[must_use]
1135    pub const fn entity_type(&self) -> EntityTypeId {
1136        self.entity_type
1137    }
1138
1139    /// Exact stable vector-field identity.
1140    #[must_use]
1141    pub const fn field(&self) -> FieldId {
1142        self.field
1143    }
1144
1145    /// Whether whole-partition maintained counts may be disclosed.
1146    #[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/// Complete exact vector-inspection authority carried only by CapabilityRecordV8.
1160#[derive(Clone, Eq, PartialEq)]
1161pub struct CapabilityVectorInspectionGrantV1 {
1162    application_role_hash: ApplicationRoleHash,
1163    targets: crate::OwnedCapacitySliceV1<CapabilityVectorInspectionTargetV1>,
1164}
1165
1166impl CapabilityVectorInspectionGrantV1 {
1167    /// Constructs one role-bound, canonical, nonempty target set.
1168    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    /// Exact compiled application-role identity.
1192    #[must_use]
1193    pub const fn application_role_hash(&self) -> ApplicationRoleHash {
1194        self.application_role_hash
1195    }
1196
1197    /// Canonical exact vector targets.
1198    #[must_use]
1199    pub fn targets(&self) -> &[CapabilityVectorInspectionTargetV1] {
1200        &self.targets
1201    }
1202
1203    /// Finds one exact target without exposing a broader wildcard.
1204    #[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    /// True only when every child target and count posture is equal or narrower.
1219    #[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/// Complete bounded v1 grant persisted with a capability.
1243#[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    /// Constructs a canonically ordered complete grant.
1259    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        // Replication and source fencing are database-wide administrative
1274        // authority. Neither may be scoped or derived from an application role.
1275        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    /// Returns the authorization-resolved tenant constraint.
1328    #[must_use]
1329    pub const fn tenant_scope(&self) -> &TenantScope {
1330        &self.tenant_scope
1331    }
1332
1333    /// Returns the complete partition constraint.
1334    #[must_use]
1335    pub const fn partition_scope(&self) -> &PartitionScopeV1 {
1336        &self.partition_scope
1337    }
1338
1339    /// Returns the canonical permission set.
1340    #[must_use]
1341    pub const fn permissions(&self) -> &CapabilityPermissionsV1 {
1342        &self.permissions
1343    }
1344
1345    /// Returns canonical entity-field visibility entries.
1346    #[must_use]
1347    pub fn field_visibility(&self) -> &[EntityFieldVisibilityV1] {
1348        &self.field_visibility
1349    }
1350
1351    /// Returns the bounded maximum scan rows.
1352    #[must_use]
1353    pub const fn max_scan_rows(&self) -> NonZeroU16 {
1354        self.max_scan_rows
1355    }
1356
1357    /// Returns canonical permission kinds requiring validated approval.
1358    #[must_use]
1359    pub fn approval_required(&self) -> &[CapabilityPermissionKindV1] {
1360        &self.approval_required
1361    }
1362
1363    /// Adds the exact V4 row-policy extension after checking the matching role identity.
1364    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    /// Exact trusted row-policy extension, absent for V1/V2/V3 capabilities.
1414    #[doc(hidden)]
1415    #[must_use]
1416    pub const fn internal_row_policy(&self) -> Option<&CapabilityRowPolicyGrantV1> {
1417        self.row_policy.as_ref()
1418    }
1419
1420    /// Adds the exact V5 export extension after checking scope prerequisites.
1421    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    /// Exact trusted export extension, absent for V1 through V4 capabilities.
1451    #[doc(hidden)]
1452    #[must_use]
1453    pub const fn internal_export(&self) -> Option<&CapabilityExportGrantV1> {
1454        self.export.as_ref()
1455    }
1456
1457    /// Adds distinct V7 reimport authority after checking its role and scope prerequisites.
1458    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    /// Exact trusted reimport extension, absent for V1 through V6 capabilities.
1477    #[doc(hidden)]
1478    #[must_use]
1479    pub const fn internal_reimport(&self) -> Option<&CapabilityApplicationReimportGrantV1> {
1480        self.reimport.as_ref()
1481    }
1482
1483    /// Adds exact V8 vector-inspection authority after checking role, permission,
1484    /// field visibility, and row-policy count-disclosure prerequisites.
1485    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    /// Exact trusted vector-inspection extension, absent for V1 through V7 capabilities.
1547    #[doc(hidden)]
1548    #[must_use]
1549    pub const fn internal_vector_inspection(&self) -> Option<&CapabilityVectorInspectionGrantV1> {
1550        self.vector_inspection.as_ref()
1551    }
1552
1553    /// Returns the complete checked v1 semantic grant byte count.
1554    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
1779// Exhaustive non-durable ownership inventories for exact-provider admission.
1780impl 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    // req: REP-003
1824    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}