1use std::error::Error;
4use std::fmt;
5
6use crate::limits::MAX_SERVICE_AUDIT_TARGETS;
7use crate::{
8 AdministrationSequence, CapabilityId, CommandId, CommitSequence, ContractLineage,
9 ContractVersion, EntityTypeId, EventConsumerIdentityHash, IndexId, ProjectionId, ProvenanceId,
10 ReactiveModuleHash, ReactiveOperationName, ReplicationFollowerAuditTargetV1,
11};
12
13#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
15pub enum ServiceOperationV1 {
16 ValidateContract,
18 ExplainCommand,
20 DeployContract,
22 GetActiveContract,
24 GetContractVersion,
26 ExecuteCommand,
28 ResolveCommandOutcome,
30 GetEntity,
32 ScanIndex,
34 QueryProjection,
36 GetProjectionStatus,
38 GetCommit,
40 ScanCommits,
42 SubscribeToCommits,
44 TraceProvenance,
46 GetHealth,
48 GetStatistics,
50 CreateCapability,
52 RevokeCapability,
54 ListPendingOutboxDeliveries,
56 DiscoverCommandTools,
58 DiscoverResources,
60 DescribeContract,
62 CheckQuery,
64 ExplainQuery,
66 ExecuteQuery,
68 DeployQueryModule,
70 ApplyContractMigration,
72 DescribeEvent,
74 ReplayEvents,
76 TailEvents,
78 ExecuteProjectedQuery,
80 DeployReactiveModule,
82 ConsumeEventStream,
84 AcknowledgeEventStream,
86 NegativeAcknowledgeEventStream,
88 SeekEventStreamConsumer,
90 RetireEventStreamConsumer,
92 GetEventStreamConsumerStatus,
94 WatchNamedQuery,
96 ConsumeContextualSubscription,
98 AcknowledgeContextualSubscription,
100 NegativeAcknowledgeContextualSubscription,
102 GetContextualSubscriptionStatus,
104 ExecuteContextualReaction,
106 GetReactiveWakeup,
108 StartApplicationInstallation,
110 GetApplicationInstallation,
112 StartApplicationExport,
114 GetApplicationExportPage,
116 GetApplicationExport,
118 CancelApplicationExport,
120 StartApplicationReimport,
122 ApplyApplicationReimportPage,
124 GetApplicationReimport,
126 CancelApplicationReimport,
128 InspectVectorState,
130 RegisterFollower,
132 RetireFollower,
134 FenceReplicationPrimary,
136 StreamChangelog,
138 PromoteFollower,
140}
141
142impl ServiceOperationV1 {
143 pub const ALL: [Self; 62] = [
145 Self::ValidateContract,
146 Self::ExplainCommand,
147 Self::DeployContract,
148 Self::GetActiveContract,
149 Self::GetContractVersion,
150 Self::ExecuteCommand,
151 Self::ResolveCommandOutcome,
152 Self::GetEntity,
153 Self::ScanIndex,
154 Self::QueryProjection,
155 Self::GetProjectionStatus,
156 Self::GetCommit,
157 Self::ScanCommits,
158 Self::SubscribeToCommits,
159 Self::TraceProvenance,
160 Self::GetHealth,
161 Self::GetStatistics,
162 Self::CreateCapability,
163 Self::RevokeCapability,
164 Self::ListPendingOutboxDeliveries,
165 Self::DiscoverCommandTools,
166 Self::DiscoverResources,
167 Self::DescribeContract,
168 Self::CheckQuery,
169 Self::ExplainQuery,
170 Self::ExecuteQuery,
171 Self::DeployQueryModule,
172 Self::ApplyContractMigration,
173 Self::DescribeEvent,
174 Self::ReplayEvents,
175 Self::TailEvents,
176 Self::ExecuteProjectedQuery,
177 Self::DeployReactiveModule,
178 Self::ConsumeEventStream,
179 Self::AcknowledgeEventStream,
180 Self::NegativeAcknowledgeEventStream,
181 Self::SeekEventStreamConsumer,
182 Self::RetireEventStreamConsumer,
183 Self::GetEventStreamConsumerStatus,
184 Self::WatchNamedQuery,
185 Self::ConsumeContextualSubscription,
186 Self::AcknowledgeContextualSubscription,
187 Self::NegativeAcknowledgeContextualSubscription,
188 Self::GetContextualSubscriptionStatus,
189 Self::ExecuteContextualReaction,
190 Self::GetReactiveWakeup,
191 Self::StartApplicationInstallation,
192 Self::GetApplicationInstallation,
193 Self::StartApplicationExport,
194 Self::GetApplicationExportPage,
195 Self::GetApplicationExport,
196 Self::CancelApplicationExport,
197 Self::StartApplicationReimport,
198 Self::ApplyApplicationReimportPage,
199 Self::GetApplicationReimport,
200 Self::CancelApplicationReimport,
201 Self::InspectVectorState,
202 Self::RegisterFollower,
203 Self::RetireFollower,
204 Self::FenceReplicationPrimary,
205 Self::StreamChangelog,
206 Self::PromoteFollower,
207 ];
208
209 #[must_use]
211 pub const fn tag(self) -> u8 {
212 match self {
213 Self::ValidateContract => 0x01,
214 Self::ExplainCommand => 0x02,
215 Self::DeployContract => 0x03,
216 Self::GetActiveContract => 0x04,
217 Self::GetContractVersion => 0x05,
218 Self::ExecuteCommand => 0x06,
219 Self::ResolveCommandOutcome => 0x07,
220 Self::GetEntity => 0x08,
221 Self::ScanIndex => 0x09,
222 Self::QueryProjection => 0x0a,
223 Self::GetProjectionStatus => 0x0b,
224 Self::GetCommit => 0x0c,
225 Self::ScanCommits => 0x0d,
226 Self::SubscribeToCommits => 0x0e,
227 Self::TraceProvenance => 0x0f,
228 Self::GetHealth => 0x10,
229 Self::GetStatistics => 0x11,
230 Self::CreateCapability => 0x12,
231 Self::RevokeCapability => 0x13,
232 Self::ListPendingOutboxDeliveries => 0x14,
233 Self::DiscoverCommandTools => 0x15,
234 Self::DiscoverResources => 0x16,
235 Self::DescribeContract => 0x17,
236 Self::CheckQuery => 0x18,
237 Self::ExplainQuery => 0x19,
238 Self::ExecuteQuery => 0x1a,
239 Self::DeployQueryModule => 0x1b,
240 Self::ApplyContractMigration => 0x1c,
241 Self::DescribeEvent => 0x1d,
242 Self::ReplayEvents => 0x1e,
243 Self::TailEvents => 0x1f,
244 Self::ExecuteProjectedQuery => 0x20,
245 Self::DeployReactiveModule => 0x21,
246 Self::ConsumeEventStream => 0x22,
247 Self::AcknowledgeEventStream => 0x23,
248 Self::NegativeAcknowledgeEventStream => 0x24,
249 Self::SeekEventStreamConsumer => 0x25,
250 Self::RetireEventStreamConsumer => 0x26,
251 Self::GetEventStreamConsumerStatus => 0x27,
252 Self::WatchNamedQuery => 0x28,
253 Self::ConsumeContextualSubscription => 0x29,
254 Self::AcknowledgeContextualSubscription => 0x2a,
255 Self::NegativeAcknowledgeContextualSubscription => 0x2b,
256 Self::GetContextualSubscriptionStatus => 0x2c,
257 Self::ExecuteContextualReaction => 0x2d,
258 Self::GetReactiveWakeup => 0x2e,
259 Self::StartApplicationInstallation => 0x2f,
260 Self::GetApplicationInstallation => 0x30,
261 Self::StartApplicationExport => 0x31,
262 Self::GetApplicationExportPage => 0x32,
263 Self::GetApplicationExport => 0x33,
264 Self::CancelApplicationExport => 0x34,
265 Self::StartApplicationReimport => 0x35,
266 Self::ApplyApplicationReimportPage => 0x36,
267 Self::GetApplicationReimport => 0x37,
268 Self::CancelApplicationReimport => 0x38,
269 Self::InspectVectorState => 0x39,
270 Self::RegisterFollower => 0x3a,
271 Self::RetireFollower => 0x3b,
272 Self::FenceReplicationPrimary => 0x3c,
273 Self::StreamChangelog => 0x3d,
274 Self::PromoteFollower => 0x3e,
275 }
276 }
277
278 #[must_use]
280 pub const fn from_tag(tag: u8) -> Option<Self> {
281 match tag {
282 0x01 => Some(Self::ValidateContract),
283 0x02 => Some(Self::ExplainCommand),
284 0x03 => Some(Self::DeployContract),
285 0x04 => Some(Self::GetActiveContract),
286 0x05 => Some(Self::GetContractVersion),
287 0x06 => Some(Self::ExecuteCommand),
288 0x07 => Some(Self::ResolveCommandOutcome),
289 0x08 => Some(Self::GetEntity),
290 0x09 => Some(Self::ScanIndex),
291 0x0a => Some(Self::QueryProjection),
292 0x0b => Some(Self::GetProjectionStatus),
293 0x0c => Some(Self::GetCommit),
294 0x0d => Some(Self::ScanCommits),
295 0x0e => Some(Self::SubscribeToCommits),
296 0x0f => Some(Self::TraceProvenance),
297 0x10 => Some(Self::GetHealth),
298 0x11 => Some(Self::GetStatistics),
299 0x12 => Some(Self::CreateCapability),
300 0x13 => Some(Self::RevokeCapability),
301 0x14 => Some(Self::ListPendingOutboxDeliveries),
302 0x15 => Some(Self::DiscoverCommandTools),
303 0x16 => Some(Self::DiscoverResources),
304 0x17 => Some(Self::DescribeContract),
305 0x18 => Some(Self::CheckQuery),
306 0x19 => Some(Self::ExplainQuery),
307 0x1a => Some(Self::ExecuteQuery),
308 0x1b => Some(Self::DeployQueryModule),
309 0x1c => Some(Self::ApplyContractMigration),
310 0x1d => Some(Self::DescribeEvent),
311 0x1e => Some(Self::ReplayEvents),
312 0x1f => Some(Self::TailEvents),
313 0x20 => Some(Self::ExecuteProjectedQuery),
314 0x21 => Some(Self::DeployReactiveModule),
315 0x22 => Some(Self::ConsumeEventStream),
316 0x23 => Some(Self::AcknowledgeEventStream),
317 0x24 => Some(Self::NegativeAcknowledgeEventStream),
318 0x25 => Some(Self::SeekEventStreamConsumer),
319 0x26 => Some(Self::RetireEventStreamConsumer),
320 0x27 => Some(Self::GetEventStreamConsumerStatus),
321 0x28 => Some(Self::WatchNamedQuery),
322 0x29 => Some(Self::ConsumeContextualSubscription),
323 0x2a => Some(Self::AcknowledgeContextualSubscription),
324 0x2b => Some(Self::NegativeAcknowledgeContextualSubscription),
325 0x2c => Some(Self::GetContextualSubscriptionStatus),
326 0x2d => Some(Self::ExecuteContextualReaction),
327 0x2e => Some(Self::GetReactiveWakeup),
328 0x2f => Some(Self::StartApplicationInstallation),
329 0x30 => Some(Self::GetApplicationInstallation),
330 0x31 => Some(Self::StartApplicationExport),
331 0x32 => Some(Self::GetApplicationExportPage),
332 0x33 => Some(Self::GetApplicationExport),
333 0x34 => Some(Self::CancelApplicationExport),
334 0x35 => Some(Self::StartApplicationReimport),
335 0x36 => Some(Self::ApplyApplicationReimportPage),
336 0x37 => Some(Self::GetApplicationReimport),
337 0x38 => Some(Self::CancelApplicationReimport),
338 0x39 => Some(Self::InspectVectorState),
339 0x3a => Some(Self::RegisterFollower),
340 0x3b => Some(Self::RetireFollower),
341 0x3c => Some(Self::FenceReplicationPrimary),
342 0x3d => Some(Self::StreamChangelog),
343 0x3e => Some(Self::PromoteFollower),
344 _ => None,
345 }
346 }
347}
348
349#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
351pub enum ServiceAuditPhaseV1 {
352 Started,
354 Succeeded,
356 Denied,
358 Cancelled,
360 Failed,
362 OutcomeUncertain,
364}
365
366impl ServiceAuditPhaseV1 {
367 pub const ALL: [Self; 6] = [
369 Self::Started,
370 Self::Succeeded,
371 Self::Denied,
372 Self::Cancelled,
373 Self::Failed,
374 Self::OutcomeUncertain,
375 ];
376
377 #[must_use]
379 pub const fn tag(self) -> u8 {
380 match self {
381 Self::Started => 0x01,
382 Self::Succeeded => 0x02,
383 Self::Denied => 0x03,
384 Self::Cancelled => 0x04,
385 Self::Failed => 0x05,
386 Self::OutcomeUncertain => 0x06,
387 }
388 }
389
390 #[must_use]
392 pub const fn from_tag(tag: u8) -> Option<Self> {
393 match tag {
394 0x01 => Some(Self::Started),
395 0x02 => Some(Self::Succeeded),
396 0x03 => Some(Self::Denied),
397 0x04 => Some(Self::Cancelled),
398 0x05 => Some(Self::Failed),
399 0x06 => Some(Self::OutcomeUncertain),
400 _ => None,
401 }
402 }
403}
404
405#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
407pub enum ServiceIngressKindV1 {
408 Grpc,
410 McpHttp,
412 InProcessTestComparison,
414}
415
416impl ServiceIngressKindV1 {
417 pub const ALL: [Self; 3] = [Self::Grpc, Self::McpHttp, Self::InProcessTestComparison];
419
420 #[must_use]
422 pub const fn tag(self) -> u8 {
423 match self {
424 Self::Grpc => 0x01,
425 Self::McpHttp => 0x02,
426 Self::InProcessTestComparison => 0x03,
427 }
428 }
429
430 #[must_use]
432 pub const fn from_tag(tag: u8) -> Option<Self> {
433 match tag {
434 0x01 => Some(Self::Grpc),
435 0x02 => Some(Self::McpHttp),
436 0x03 => Some(Self::InProcessTestComparison),
437 _ => None,
438 }
439 }
440}
441
442#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
444pub enum ServiceAuditLinkV1 {
445 None,
447 Command {
449 commit_sequence: CommitSequence,
451 provenance_id: ProvenanceId,
453 },
454 ControlPlane {
456 administration_sequence: AdministrationSequence,
458 },
459}
460
461#[derive(Clone, Eq, Hash, PartialEq)]
463pub enum ServiceAuditTargetV1 {
464 ContractLineage(ContractLineage),
466 ContractVersion {
468 lineage: ContractLineage,
470 version: ContractVersion,
472 },
473 EntityType {
475 lineage: ContractLineage,
477 entity_type_id: EntityTypeId,
479 },
480 Command {
482 lineage: ContractLineage,
484 command_id: CommandId,
486 },
487 Projection {
489 lineage: ContractLineage,
491 projection_id: ProjectionId,
493 },
494 Index {
496 lineage: ContractLineage,
498 index_id: IndexId,
500 },
501 Commit(CommitSequence),
503 Provenance(ProvenanceId),
505 Capability(CapabilityId),
507 EventConsumer {
509 lineage: ContractLineage,
511 module_hash: ReactiveModuleHash,
513 operation_name: ReactiveOperationName,
515 consumer_identity_hash: EventConsumerIdentityHash,
517 },
518 ReplicationFollower(ReplicationFollowerAuditTargetV1),
520}
521
522impl ServiceAuditTargetV1 {
523 #[must_use]
525 pub const fn tag(&self) -> u8 {
526 match self {
527 Self::ContractLineage(_) => 0x01,
528 Self::ContractVersion { .. } => 0x02,
529 Self::EntityType { .. } => 0x03,
530 Self::Command { .. } => 0x04,
531 Self::Projection { .. } => 0x05,
532 Self::Index { .. } => 0x06,
533 Self::Commit(_) => 0x07,
534 Self::Provenance(_) => 0x08,
535 Self::Capability(_) => 0x09,
536 Self::EventConsumer { .. } => 0x0a,
537 Self::ReplicationFollower(_) => 0x0b,
538 }
539 }
540
541 #[must_use]
543 pub fn canonical_key(&self) -> Vec<u8> {
544 let mut key = Vec::new();
545 key.push(self.tag());
546 match self {
547 Self::ContractLineage(lineage) => append_lineage(&mut key, lineage),
548 Self::ContractVersion { lineage, version } => {
549 append_lineage(&mut key, lineage);
550 key.extend_from_slice(&version.to_be_bytes());
551 }
552 Self::EntityType {
553 lineage,
554 entity_type_id,
555 } => {
556 append_lineage(&mut key, lineage);
557 key.extend_from_slice(&entity_type_id.to_be_bytes());
558 }
559 Self::Command {
560 lineage,
561 command_id,
562 } => {
563 append_lineage(&mut key, lineage);
564 key.extend_from_slice(&command_id.to_be_bytes());
565 }
566 Self::Projection {
567 lineage,
568 projection_id,
569 } => {
570 append_lineage(&mut key, lineage);
571 key.extend_from_slice(&projection_id.to_be_bytes());
572 }
573 Self::Index { lineage, index_id } => {
574 append_lineage(&mut key, lineage);
575 key.extend_from_slice(&index_id.to_be_bytes());
576 }
577 Self::Commit(sequence) => key.extend_from_slice(&sequence.to_be_bytes()),
578 Self::Provenance(provenance_id) => key.extend_from_slice(provenance_id.as_bytes()),
579 Self::Capability(capability_id) => key.extend_from_slice(capability_id.as_bytes()),
580 Self::ReplicationFollower(target) => {
581 key.extend_from_slice(target.database_id().as_bytes());
582 key.extend_from_slice(&target.history_incarnation().to_be_bytes());
583 key.extend_from_slice(&target.leadership_epoch().get().to_be_bytes());
584 key.extend_from_slice(target.hold_id().as_bytes());
585 }
586 Self::EventConsumer {
587 lineage,
588 module_hash,
589 operation_name,
590 consumer_identity_hash,
591 } => {
592 append_lineage(&mut key, lineage);
593 key.extend_from_slice(module_hash.as_bytes());
594 let operation = operation_name.as_str().as_bytes();
595 key.extend_from_slice(&(operation.len() as u32).to_be_bytes());
596 key.extend_from_slice(operation);
597 key.extend_from_slice(consumer_identity_hash.as_bytes());
598 }
599 }
600 key
601 }
602}
603
604impl fmt::Debug for ServiceAuditTargetV1 {
605 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
606 let variant = match self {
607 Self::ContractLineage(_) => "ContractLineage",
608 Self::ContractVersion { .. } => "ContractVersion",
609 Self::EntityType { .. } => "EntityType",
610 Self::Command { .. } => "Command",
611 Self::Projection { .. } => "Projection",
612 Self::Index { .. } => "Index",
613 Self::Commit(_) => "Commit",
614 Self::Provenance(_) => "Provenance",
615 Self::Capability(_) => "Capability",
616 Self::EventConsumer { .. } => "EventConsumer",
617 Self::ReplicationFollower(_) => "ReplicationFollower",
618 };
619 write!(formatter, "ServiceAuditTargetV1::{variant}([REDACTED])")
620 }
621}
622
623fn append_lineage(key: &mut Vec<u8>, lineage: &ContractLineage) {
624 let lineage_bytes = lineage.as_bytes();
625 key.extend_from_slice(&(lineage_bytes.len() as u32).to_be_bytes());
627 key.extend_from_slice(lineage_bytes);
628}
629
630#[derive(Clone, Copy, Debug, Eq, PartialEq)]
632pub enum ServiceAuditTargetsError {
633 TooMany {
635 maximum: usize,
637 },
638 Duplicate,
640}
641
642impl fmt::Display for ServiceAuditTargetsError {
643 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
644 match self {
645 Self::TooMany { maximum } => {
646 write!(formatter, "service audit target count exceeds {maximum}")
647 }
648 Self::Duplicate => formatter.write_str("service audit targets contain a duplicate"),
649 }
650 }
651}
652
653impl Error for ServiceAuditTargetsError {}
654
655#[derive(Clone, Eq, Hash, PartialEq)]
657pub struct ServiceAuditTargetsV1(Vec<ServiceAuditTargetV1>);
658
659impl ServiceAuditTargetsV1 {
660 #[must_use]
663 pub fn is_valid_for_operation(&self, operation: ServiceOperationV1) -> bool {
664 match operation {
665 ServiceOperationV1::RegisterFollower
666 | ServiceOperationV1::RetireFollower
667 | ServiceOperationV1::FenceReplicationPrimary
668 | ServiceOperationV1::PromoteFollower => {
669 matches!(
670 self.as_slice(),
671 [ServiceAuditTargetV1::ReplicationFollower(_)]
672 )
673 }
674 ServiceOperationV1::StreamChangelog => matches!(
675 self.as_slice(),
676 [] | [ServiceAuditTargetV1::ReplicationFollower(_)]
677 ),
678 _ => true,
679 }
680 }
681
682 pub fn new(
684 targets: impl IntoIterator<Item = ServiceAuditTargetV1>,
685 ) -> Result<Self, ServiceAuditTargetsError> {
686 let mut keyed_targets = Vec::with_capacity(MAX_SERVICE_AUDIT_TARGETS);
687 for target in targets {
688 if keyed_targets.len() == MAX_SERVICE_AUDIT_TARGETS {
689 return Err(ServiceAuditTargetsError::TooMany {
690 maximum: MAX_SERVICE_AUDIT_TARGETS,
691 });
692 }
693 keyed_targets.push((target.canonical_key(), target));
694 }
695
696 keyed_targets.sort_unstable_by(|left, right| left.0.cmp(&right.0));
697 if keyed_targets
698 .windows(2)
699 .any(|pair| pair[0].0.as_slice() == pair[1].0.as_slice())
700 {
701 return Err(ServiceAuditTargetsError::Duplicate);
702 }
703
704 Ok(Self(
705 keyed_targets
706 .into_iter()
707 .map(|(_, target)| target)
708 .collect(),
709 ))
710 }
711
712 #[must_use]
714 pub const fn empty() -> Self {
715 Self(Vec::new())
716 }
717
718 #[must_use]
720 pub fn as_slice(&self) -> &[ServiceAuditTargetV1] {
721 &self.0
722 }
723
724 #[must_use]
726 pub fn len(&self) -> usize {
727 self.0.len()
728 }
729
730 #[must_use]
732 pub fn is_empty(&self) -> bool {
733 self.0.is_empty()
734 }
735
736 #[must_use]
738 pub fn into_vec(self) -> Vec<ServiceAuditTargetV1> {
739 self.0
740 }
741}
742
743impl fmt::Debug for ServiceAuditTargetsV1 {
744 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
745 formatter
746 .debug_struct("ServiceAuditTargetsV1")
747 .field("len", &self.len())
748 .field("targets", &"[REDACTED]")
749 .finish()
750 }
751}
752
753impl TryFrom<Vec<ServiceAuditTargetV1>> for ServiceAuditTargetsV1 {
754 type Error = ServiceAuditTargetsError;
755
756 fn try_from(targets: Vec<ServiceAuditTargetV1>) -> Result<Self, Self::Error> {
757 Self::new(targets)
758 }
759}
760
761#[cfg(test)]
762mod tests {
763 use super::*;
764
765 fn lineage(value: &str) -> ContractLineage {
766 ContractLineage::new(value).expect("valid test lineage")
767 }
768
769 fn version(value: u64) -> ContractVersion {
770 ContractVersion::new(value).expect("nonzero test contract version")
771 }
772
773 fn entity_type_id(value: u32) -> EntityTypeId {
774 EntityTypeId::new(value).expect("nonzero test entity type ID")
775 }
776
777 fn command_id(value: u32) -> CommandId {
778 CommandId::new(value).expect("nonzero test command ID")
779 }
780
781 fn projection_id(value: u32) -> ProjectionId {
782 ProjectionId::new(value).expect("nonzero test projection ID")
783 }
784
785 fn index_id(value: u32) -> IndexId {
786 IndexId::new(value).expect("nonzero test index ID")
787 }
788
789 fn commit_sequence(value: u64) -> CommitSequence {
790 CommitSequence::new(value).expect("nonzero test commit sequence")
791 }
792
793 fn uuid_bytes(seed: u8) -> [u8; 16] {
794 let mut bytes = [seed; 16];
795 bytes[6] = 0x70 | (seed & 0x0f);
796 bytes[8] = 0x80 | (seed & 0x3f);
797 bytes
798 }
799
800 fn provenance_id(seed: u8) -> ProvenanceId {
801 ProvenanceId::from_bytes(uuid_bytes(seed)).expect("valid test UUIDv7")
802 }
803
804 fn capability_id(seed: u8) -> CapabilityId {
805 CapabilityId::from_bytes(uuid_bytes(seed)).expect("valid test UUIDv7")
806 }
807
808 #[test]
810 fn replication_stream_audit_targets_are_empty_or_one_selected_follower() {
811 let follower = |seed| {
812 ServiceAuditTargetV1::ReplicationFollower(
813 ReplicationFollowerAuditTargetV1::new(
814 crate::DatabaseId::from_bytes(uuid_bytes(1)).unwrap(),
815 1,
816 crate::LeadershipEpochV1::initial(),
817 crate::ReplicationSourceHoldIdV1::new([seed; 16]).unwrap(),
818 )
819 .unwrap(),
820 )
821 };
822 for targets in [vec![], vec![follower(1)]] {
823 assert!(
824 ServiceAuditTargetsV1::new(targets)
825 .unwrap()
826 .is_valid_for_operation(ServiceOperationV1::StreamChangelog)
827 );
828 }
829 for targets in [
830 vec![ServiceAuditTargetV1::Capability(capability_id(1))],
831 vec![follower(1), follower(2)],
832 vec![
833 follower(1),
834 ServiceAuditTargetV1::Commit(commit_sequence(1)),
835 ],
836 ] {
837 assert!(
838 !ServiceAuditTargetsV1::new(targets)
839 .unwrap()
840 .is_valid_for_operation(ServiceOperationV1::StreamChangelog)
841 );
842 }
843 }
844
845 #[test]
846 fn service_operation_registry_is_exact_and_closed() {
847 let expected: Vec<u8> = (0x01..=0x3e).collect();
848 assert_eq!(
849 ServiceOperationV1::ALL
850 .into_iter()
851 .map(ServiceOperationV1::tag)
852 .collect::<Vec<_>>(),
853 expected
854 );
855 for operation in ServiceOperationV1::ALL {
856 assert_eq!(
857 ServiceOperationV1::from_tag(operation.tag()),
858 Some(operation)
859 );
860 }
861 assert_eq!(ServiceOperationV1::from_tag(0), None);
862 assert_eq!(
863 ServiceOperationV1::from_tag(0x2e),
864 Some(ServiceOperationV1::GetReactiveWakeup)
865 );
866 assert_eq!(
867 ServiceOperationV1::from_tag(0x30),
868 Some(ServiceOperationV1::GetApplicationInstallation)
869 );
870 assert_eq!(
871 ServiceOperationV1::from_tag(0x31),
872 Some(ServiceOperationV1::StartApplicationExport)
873 );
874 assert_eq!(
875 ServiceOperationV1::from_tag(0x34),
876 Some(ServiceOperationV1::CancelApplicationExport)
877 );
878 assert_eq!(
879 ServiceOperationV1::from_tag(0x38),
880 Some(ServiceOperationV1::CancelApplicationReimport)
881 );
882 assert_eq!(
883 ServiceOperationV1::from_tag(0x39),
884 Some(ServiceOperationV1::InspectVectorState)
885 );
886 assert_eq!(
887 ServiceOperationV1::from_tag(0x3c),
888 Some(ServiceOperationV1::FenceReplicationPrimary)
889 );
890 assert_eq!(
891 ServiceOperationV1::from_tag(0x3d),
892 Some(ServiceOperationV1::StreamChangelog)
893 );
894 assert_eq!(
895 ServiceOperationV1::from_tag(0x3e),
896 Some(ServiceOperationV1::PromoteFollower)
897 );
898 assert_eq!(ServiceOperationV1::from_tag(0x3f), None);
899 assert_eq!(ServiceOperationV1::from_tag(u8::MAX), None);
900 }
901
902 #[test]
903 fn follower_lifecycle_operations_have_distinct_additive_audit_tags() {
905 for (tag, name) in [(0x3a, "RegisterFollower"), (0x3b, "RetireFollower")] {
906 let operation =
907 ServiceOperationV1::from_tag(tag).expect("approved lifecycle operation");
908 assert_eq!(format!("{operation:?}"), name);
909 assert_eq!(operation.tag(), tag);
910 }
911 assert_eq!(ServiceOperationV1::InspectVectorState.tag(), 0x39);
912 }
913
914 #[test]
915 fn audit_phase_and_ingress_registries_are_exact_and_closed() {
916 assert_eq!(
917 ServiceAuditPhaseV1::ALL.map(ServiceAuditPhaseV1::tag),
918 [1, 2, 3, 4, 5, 6]
919 );
920 for phase in ServiceAuditPhaseV1::ALL {
921 assert_eq!(ServiceAuditPhaseV1::from_tag(phase.tag()), Some(phase));
922 }
923 assert_eq!(ServiceAuditPhaseV1::from_tag(0), None);
924 assert_eq!(ServiceAuditPhaseV1::from_tag(7), None);
925
926 assert_eq!(
927 ServiceIngressKindV1::ALL.map(ServiceIngressKindV1::tag),
928 [1, 2, 3]
929 );
930 for ingress in ServiceIngressKindV1::ALL {
931 assert_eq!(ServiceIngressKindV1::from_tag(ingress.tag()), Some(ingress));
932 }
933 assert_eq!(ServiceIngressKindV1::from_tag(0), None);
934 assert_eq!(ServiceIngressKindV1::from_tag(4), None);
935 }
936
937 #[test]
938 fn result_link_is_closed_and_command_link_is_all_or_nothing() {
939 let command = ServiceAuditLinkV1::Command {
940 commit_sequence: commit_sequence(7),
941 provenance_id: provenance_id(0x41),
942 };
943 assert!(matches!(
944 command,
945 ServiceAuditLinkV1::Command {
946 commit_sequence: sequence,
947 provenance_id: _
948 } if sequence == commit_sequence(7)
949 ));
950
951 let control_plane = ServiceAuditLinkV1::ControlPlane {
952 administration_sequence: AdministrationSequence::new(9)
953 .expect("nonzero test administration sequence"),
954 };
955 assert!(matches!(
956 control_plane,
957 ServiceAuditLinkV1::ControlPlane {
958 administration_sequence
959 } if administration_sequence.get() == 9
960 ));
961 assert_eq!(ServiceAuditLinkV1::None, ServiceAuditLinkV1::None);
962 }
963
964 #[test]
965 fn all_nine_target_tags_and_canonical_keys_are_frozen() {
966 let contract_lineage = lineage("LegalSpend");
967 let targets = [
968 ServiceAuditTargetV1::ContractLineage(contract_lineage.clone()),
969 ServiceAuditTargetV1::ContractVersion {
970 lineage: contract_lineage.clone(),
971 version: version(2),
972 },
973 ServiceAuditTargetV1::EntityType {
974 lineage: contract_lineage.clone(),
975 entity_type_id: entity_type_id(3),
976 },
977 ServiceAuditTargetV1::Command {
978 lineage: contract_lineage.clone(),
979 command_id: command_id(4),
980 },
981 ServiceAuditTargetV1::Projection {
982 lineage: contract_lineage.clone(),
983 projection_id: projection_id(5),
984 },
985 ServiceAuditTargetV1::Index {
986 lineage: contract_lineage.clone(),
987 index_id: index_id(6),
988 },
989 ServiceAuditTargetV1::Commit(commit_sequence(7)),
990 ServiceAuditTargetV1::Provenance(provenance_id(8)),
991 ServiceAuditTargetV1::Capability(capability_id(9)),
992 ];
993
994 assert_eq!(
995 targets.each_ref().map(|target| target.tag()),
996 [1, 2, 3, 4, 5, 6, 7, 8, 9]
997 );
998
999 let mut lineage_frame = Vec::from(10_u32.to_be_bytes());
1000 lineage_frame.extend_from_slice(b"LegalSpend");
1001 for (target, suffix) in targets[..6].iter().zip([
1002 Vec::new(),
1003 2_u64.to_be_bytes().to_vec(),
1004 3_u32.to_be_bytes().to_vec(),
1005 4_u32.to_be_bytes().to_vec(),
1006 5_u32.to_be_bytes().to_vec(),
1007 6_u32.to_be_bytes().to_vec(),
1008 ]) {
1009 let mut expected = vec![target.tag()];
1010 expected.extend_from_slice(&lineage_frame);
1011 expected.extend_from_slice(&suffix);
1012 assert_eq!(target.canonical_key(), expected);
1013 }
1014
1015 let mut expected_commit = vec![0x07];
1016 expected_commit.extend_from_slice(&7_u64.to_be_bytes());
1017 assert_eq!(targets[6].canonical_key(), expected_commit);
1018
1019 let mut expected_provenance = vec![0x08];
1020 expected_provenance.extend_from_slice(&uuid_bytes(8));
1021 assert_eq!(targets[7].canonical_key(), expected_provenance);
1022
1023 let mut expected_capability = vec![0x09];
1024 expected_capability.extend_from_slice(&uuid_bytes(9));
1025 assert_eq!(targets[8].canonical_key(), expected_capability);
1026 }
1027
1028 #[test]
1029 fn construction_is_permutation_independent_and_uses_complete_keys() {
1030 let short_lineage = ServiceAuditTargetV1::Command {
1031 lineage: lineage("z"),
1032 command_id: command_id(1),
1033 };
1034 let long_lineage = ServiceAuditTargetV1::Command {
1035 lineage: lineage("aa"),
1036 command_id: command_id(1),
1037 };
1038 let other_tag = ServiceAuditTargetV1::Commit(commit_sequence(1));
1039
1040 let first = ServiceAuditTargetsV1::new([
1041 other_tag.clone(),
1042 long_lineage.clone(),
1043 short_lineage.clone(),
1044 ])
1045 .expect("distinct bounded targets are valid");
1046 let second = ServiceAuditTargetsV1::new([
1047 short_lineage.clone(),
1048 other_tag.clone(),
1049 long_lineage.clone(),
1050 ])
1051 .expect("permutation is valid");
1052
1053 assert_eq!(first, second);
1054 assert_eq!(first.as_slice(), &[short_lineage, long_lineage, other_tag]);
1055 }
1056
1057 #[test]
1058 fn equal_local_ids_in_distinct_lineages_are_distinct() {
1059 let targets = ServiceAuditTargetsV1::new([
1060 ServiceAuditTargetV1::EntityType {
1061 lineage: lineage("A"),
1062 entity_type_id: entity_type_id(1),
1063 },
1064 ServiceAuditTargetV1::EntityType {
1065 lineage: lineage("B"),
1066 entity_type_id: entity_type_id(1),
1067 },
1068 ])
1069 .expect("lineage scopes make the targets distinct");
1070 assert_eq!(targets.len(), 2);
1071 }
1072
1073 #[test]
1074 fn duplicate_and_seventeenth_targets_fail_closed_without_payloads() {
1075 let duplicate = ServiceAuditTargetV1::Command {
1076 lineage: lineage("SensitiveLineage"),
1077 command_id: command_id(42),
1078 };
1079 let error = ServiceAuditTargetsV1::new([duplicate.clone(), duplicate])
1080 .expect_err("exact duplicate must reject");
1081 assert_eq!(error, ServiceAuditTargetsError::Duplicate);
1082 assert!(!error.to_string().contains("SensitiveLineage"));
1083 assert!(!format!("{error:?}").contains("SensitiveLineage"));
1084
1085 let too_many = (1..=17)
1086 .map(commit_sequence)
1087 .map(ServiceAuditTargetV1::Commit);
1088 let error =
1089 ServiceAuditTargetsV1::new(too_many).expect_err("a seventeenth target must reject");
1090 assert_eq!(
1091 error,
1092 ServiceAuditTargetsError::TooMany {
1093 maximum: MAX_SERVICE_AUDIT_TARGETS,
1094 }
1095 );
1096 }
1097
1098 #[test]
1099 fn empty_and_exactly_sixteen_targets_are_valid() {
1100 let empty = ServiceAuditTargetsV1::new([]).expect("empty list is canonical");
1101 assert!(empty.is_empty());
1102 assert_eq!(empty, ServiceAuditTargetsV1::empty());
1103
1104 let full = ServiceAuditTargetsV1::new(
1105 (1..=16)
1106 .map(commit_sequence)
1107 .rev()
1108 .map(ServiceAuditTargetV1::Commit),
1109 )
1110 .expect("exactly sixteen distinct targets are valid");
1111 assert_eq!(full.len(), MAX_SERVICE_AUDIT_TARGETS);
1112 for (index, target) in full.as_slice().iter().enumerate() {
1113 assert_eq!(
1114 target,
1115 &ServiceAuditTargetV1::Commit(commit_sequence((index + 1) as u64))
1116 );
1117 }
1118 }
1119
1120 #[test]
1121 fn target_and_collection_debug_output_is_redacted() {
1122 let target = ServiceAuditTargetV1::ContractVersion {
1123 lineage: lineage("SensitiveLineage"),
1124 version: version(123),
1125 };
1126 let debug = format!("{target:?}");
1127 assert!(debug.contains("ContractVersion"));
1128 assert!(debug.contains("[REDACTED]"));
1129 assert!(!debug.contains("SensitiveLineage"));
1130 assert!(!debug.contains("123"));
1131
1132 let targets = ServiceAuditTargetsV1::new([target]).expect("one target is valid");
1133 let debug = format!("{targets:?}");
1134 assert!(debug.contains("len: 1"));
1135 assert!(!debug.contains("SensitiveLineage"));
1136 }
1137}