1pub mod allocation;
8pub mod attestation;
9pub mod boundary;
10pub mod classification;
11pub mod compliance;
12pub mod crd;
13pub mod encapsulates;
14pub mod env;
15pub mod ephemeral;
16pub mod export;
17pub mod hostname;
18pub mod identity;
19pub mod intent;
20pub mod lifetime;
21pub mod lifetime_clock;
22pub mod matrix;
23pub mod phase;
24pub mod pool;
25pub mod receipt;
26pub mod routing;
27pub mod signal;
28pub mod spec;
29pub mod status;
30pub mod table;
31pub mod tagged_union;
32
33pub mod prelude {
34 pub use crate::allocation::{
35 AllocationCondition, AllocationPhase, AllocationSpec, AllocationStatus,
36 EphemeralAllocation, Requestor,
37 };
38 pub use crate::attestation::ProcessAttestation;
39 pub use crate::boundary::{Boundary, Condition, ConditionKind, UnknownConditionKind};
40 pub use crate::classification::{
41 Arity, CalmClassification, Classification, ConvergencePointType, DataClassification,
42 Horizon, HorizonKind, OptimizationDirection, SubstrateType, UnknownCalmClassification,
43 UnknownConvergencePointType, UnknownDataClassification, UnknownHorizonKind,
44 UnknownOptimizationDirection, UnknownSubstrateType,
45 };
46 pub use crate::compliance::{
47 ComplianceBinding, ComplianceSpec, UnknownVerificationPhase, VerificationPhase,
48 };
49 pub use crate::crd::{Process, ProcessSpec, ProcessStatus};
50 pub use crate::encapsulates::{
51 BareWorkload, EncapsulatesSpec, EncapsulationKind, EncapsulationKindError,
52 EncapsulationKindVariant, EncapsulationMode, EncapsulationTarget, ExistingHelmRelease,
53 ExistingKustomization, UnknownEncapsulationMode, UnknownEncapsulationTarget,
54 };
55 pub use crate::ephemeral::{compile_ephemeral_source, EphemeralSpec};
56 pub use crate::export::{
57 ArtifactError, ArtifactKind, ArtifactSource, ArtifactVariant, ChannelError, ChannelKind,
58 ChannelVariant, ExportSpec, ExportTrigger, HttpEventChannel, NatsSubjectChannel,
59 ProcessSnapshotSource, ReceiptsSource, ReportFormat, ReportPayloadShape, RunMarkerSource,
60 StdoutChannel, TestReportSource, UnknownArtifactKind, UnknownChannelKind,
61 UnknownExportTrigger, UnknownReportFormat, VectorChannel, DEFAULT_NATS_URL,
62 DEFAULT_VECTOR_INGEST,
63 };
64 pub use crate::hostname::{
65 ephemeral_id_from_spec, fmt_fqdn, fmt_fqdn_stable, resolve_ephemeral_id, HostnameError,
66 EPHEMERAL_ID_HASH_LEN,
67 };
68 pub use crate::identity::{content_hash, derive_identity, format_process_address, Identity};
69 pub use crate::intent::{
70 AplicacaoIntent, ContainerIntent, FluxIntent, GuestIntent, Intent, IntentError, IntentKind,
71 IntentVariant, LispIntent, NixIntent, UnknownWorkloadKind, WorkloadKind,
72 };
73 pub use crate::lifetime::{
74 EphemeralLifetime, Lifetime, LifetimeError, LifetimeKind, LifetimeVariant,
75 PermanentLifetime, TeardownPolicy, UnknownTeardownPolicy,
76 };
77 pub use crate::lifetime_clock::{
78 evaluate as lifetime_clock_evaluate, AutoTerminate, AutoTerminateKind, TerminateReason,
79 TerminateReasonKind, UnknownAutoTerminateKind, UnknownTerminateReasonKind,
80 };
81 pub use crate::matrix::{
82 compile_env_matrix_source, EnvMatrixSpec, MatrixAxis, MatrixBudget, NamedEphemeral,
83 SelectStrategy, SelectStrategyKind, UnknownSelectStrategyKind,
84 };
85 pub use crate::phase::{ProcessPhase, UnknownPhase};
86 pub use crate::pool::{
87 AllocationRef, EphemeralPool, MatchKey, MemberState, PoolCondition, PoolMember, PoolPhase,
88 PoolSelector, PoolSpec, PoolStatus, ReplacementPolicy, ReturnPolicy, UnknownMemberState,
89 UnknownPoolPhase, UnknownReplacementPolicy,
90 };
91 pub use crate::receipt::{ReceiptEnvelope, ReceiptError, ReceiptKind, RECEIPT_VERSION};
92 pub use crate::routing::{RoutingBackend, RoutingHostname, RoutingSpec};
93 pub use crate::signal::{ProcessSignal, SighupStrategy, UnknownSighupStrategy};
94 pub use crate::spec::{
95 DependsOn, IdentitySpec, MustReachPhase, SignalPolicy, UnknownMustReachPhase,
96 };
97 pub use crate::status::{
98 BoundaryStatus, CheckedCondition, ComplianceStatus, FluxResourceRef, ProcessCondition,
99 };
100 pub use crate::table::{
101 ClaimRecord, ProcessEntry, ProcessTable, ProcessTableSpec, ProcessTableStatus,
102 };
103}
104
105pub const GROUP: &str = "tatara.pleme.io";
107pub const VERSION: &str = "v1alpha1";
109
110pub mod annotations {
112 pub const MANAGED_BY: &str = "tatara.pleme.io/managed-by";
113 pub const PROCESS: &str = "tatara.pleme.io/process";
114 pub const PID: &str = "tatara.pleme.io/pid";
115 pub const CONTENT_HASH: &str = "tatara.pleme.io/content-hash";
116 pub const ATTESTATION_ROOT: &str = "tatara.pleme.io/attestation-root";
117 pub const GENERATION: &str = "tatara.pleme.io/generation";
118 pub const SIGNAL: &str = "tatara.pleme.io/signal";
119 pub const RELEASED_FROM: &str = "tatara.pleme.io/released-from";
125 pub const ROLE: &str = "tatara.pleme.io/role";
128 pub const EXPORT_INDEX: &str = "tatara.pleme.io/export-index";
133}
134
135pub const PROCESS_FINALIZER: &str = "tatara.pleme.io/process-finalizer";
137
138pub mod schema_helpers {
145 use schemars::{gen::SchemaGenerator, schema::Schema};
146 pub fn preserve_unknown_object(_g: &mut SchemaGenerator) -> Schema {
151 serde_json::from_value(serde_json::json!({
152 "type": "object",
153 "x-kubernetes-preserve-unknown-fields": true
154 }))
155 .expect("static JSON literal parses as Schema")
156 }
157}
158
159pub type Definition = tatara_lisp::NamedDefinition<crate::crd::ProcessSpec>;
167
168pub fn compile_source(src: &str) -> tatara_lisp::Result<Vec<Definition>> {
171 tatara_lisp::compile_named::<crate::crd::ProcessSpec>(src)
172}
173
174pub fn register_all() {
181 tatara_lisp::domain::register::<crate::crd::ProcessSpec>();
182 tatara_lisp::domain::register::<crate::ephemeral::EphemeralSpec>();
183}
184
185#[cfg(test)]
186mod compile_tests {
187 use super::compile_source;
188 use crate::classification::{ConvergencePointType, SubstrateType};
189 use crate::compliance::VerificationPhase;
190 use crate::spec::MustReachPhase;
191
192 #[test]
195 fn full_processspec_round_trip_via_derive() {
196 let src = r#"
197 (defpoint observability-stack
198 :identity (:parent "seph.1")
199 :classification (:point-type Gate
200 :substrate Observability
201 :horizon (:kind Bounded)
202 :calm Monotone
203 :data-classification Internal)
204 :intent (:nix (:flake-ref "github:pleme-io/k8s"
205 :attribute "observability"
206 :attic-cache "main"))
207 :boundary (:postconditions
208 ((:kind KustomizationHealthy
209 :params (:name "observability-stack"
210 :namespace "flux-system"))
211 (:kind PromQL
212 :params (:query "up == 1")))
213 :timeout "15m")
214 :compliance (:baseline "fedramp-moderate"
215 :bindings ((:framework "nist-800-53"
216 :control-id "SC-7"
217 :phase AtBoundary)))
218 :depends-on ((:name "akeyless" :must-reach Attested))
219 :signals (:sigterm-grace-seconds 480
220 :sighup-strategy Reconverge))
221 "#;
222 let defs = compile_source(src).expect("compile");
223 assert_eq!(defs.len(), 1);
224 let d = &defs[0];
225 assert_eq!(d.name, "observability-stack");
226
227 assert_eq!(d.spec.identity.parent.as_deref(), Some("seph.1"));
229
230 assert_eq!(d.spec.classification.point_type, ConvergencePointType::Gate);
232 assert_eq!(
233 d.spec.classification.substrate,
234 SubstrateType::Observability
235 );
236
237 let nix = d.spec.intent.nix.as_ref().expect("nix intent");
239 assert_eq!(nix.flake_ref, "github:pleme-io/k8s");
240 assert_eq!(nix.attribute, "observability");
241 assert_eq!(nix.attic_cache.as_deref(), Some("main"));
242
243 assert_eq!(d.spec.boundary.postconditions.len(), 2);
245 assert_eq!(d.spec.boundary.timeout.as_deref(), Some("15m"));
246
247 assert_eq!(
249 d.spec.compliance.baseline.as_deref(),
250 Some("fedramp-moderate")
251 );
252 assert_eq!(d.spec.compliance.bindings.len(), 1);
253 assert_eq!(
254 d.spec.compliance.bindings[0].phase,
255 VerificationPhase::AtBoundary
256 );
257
258 assert_eq!(d.spec.depends_on.len(), 1);
260 assert_eq!(d.spec.depends_on[0].must_reach, MustReachPhase::Attested);
261
262 assert_eq!(d.spec.signals.sigterm_grace_seconds, 480);
264 }
265
266 #[test]
267 fn missing_required_field_errors() {
268 let src = r#"(defpoint x :intent (:nix (:flake-ref "f" :attribute "a")))"#;
270 assert!(compile_source(src).is_err());
271 }
272
273 #[test]
274 fn serde_default_fields_are_optional() {
275 let src = r#"
278 (defpoint x
279 :classification (:point-type Transform :substrate Compute)
280 :intent (:flux (:git-repository "g" :path ".")))
281 "#;
282 let defs = compile_source(src).expect("compile");
283 assert_eq!(defs.len(), 1);
284 let d = &defs[0];
285 assert!(d.spec.depends_on.is_empty());
286 assert!(d.spec.boundary.postconditions.is_empty());
287 assert!(d.spec.compliance.bindings.is_empty());
288 assert!(!d.spec.suspended);
289 assert!(d.spec.lifetime.is_default());
291 assert!(!d.spec.lifetime.is_ephemeral());
292 }
293
294 #[test]
297 fn register_all_resolves_defpoint_and_defephemeral() {
298 use tatara_lisp::domain::lookup;
299 super::register_all();
300 super::register_all(); assert!(lookup("defpoint").is_some(), "defpoint must resolve");
302 assert!(
303 lookup("defephemeral").is_some(),
304 "defephemeral must resolve"
305 );
306 }
307
308 #[test]
312 fn defpoint_with_aplicacao_intent_and_ephemeral_lifetime() {
313 use crate::intent::IntentVariant;
314 use crate::lifetime::{LifetimeVariant, TeardownPolicy};
315 let src = r#"
316 (defpoint akeyless-closed-loop-attest
317 :classification (:point-type Gate :substrate Compute)
318 :intent (:aplicacao
319 (:chart-ref "oci://ghcr.io/pleme-io/charts/lareira-akeyless-deployment"
320 :version "0.5.5"
321 :profile "gateway-with-internal-saas"
322 :values-overlay (:cluster (:name "ephemeral-test-01"))
323 :target-namespace "akeyless-test"))
324 :boundary (:postconditions
325 ((:kind HelmReleaseReleased
326 :params (:name "akeyless-saas-consolidated"
327 :namespace "akeyless-test"))
328 (:kind ClosedLoopAuth
329 :params (:issuer (:service "akeyless-saas-akeyless-gator" :port 8080)
330 :consumer (:service "akeyless-saas-akeyless-gateway" :port 8000)
331 :probeImage "ghcr.io/pleme-io/closed-loop-probe:0.1.0"))))
332 :lifetime (:ephemeral (:ttl "1h"
333 :teardown-policy OnAttested
334 :max-concurrent 1)))
335 "#;
336 let defs = compile_source(src).expect("compile");
337 assert_eq!(defs.len(), 1);
338 let d = &defs[0];
339
340 match d.spec.intent.variant().unwrap() {
342 IntentVariant::Aplicacao(a) => {
343 assert_eq!(a.profile, "gateway-with-internal-saas");
344 assert_eq!(a.version, "0.5.5");
345 assert_eq!(a.target_namespace.as_deref(), Some("akeyless-test"));
346 assert_eq!(a.values_overlay["cluster"]["name"], "ephemeral-test-01");
347 }
348 other => panic!("expected Aplicacao, got {other:?}"),
349 }
350
351 match d.spec.lifetime.variant().unwrap() {
353 LifetimeVariant::Ephemeral(e) => {
354 assert_eq!(e.ttl, "1h");
355 assert_eq!(e.teardown_policy, TeardownPolicy::OnAttested);
356 assert_eq!(e.max_concurrent, 1);
357 }
358 other => panic!("expected ephemeral, got {other:?}"),
359 }
360
361 assert_eq!(d.spec.boundary.postconditions.len(), 2);
363 assert_eq!(
364 d.spec.boundary.postconditions[1].kind,
365 crate::boundary::ConditionKind::ClosedLoopAuth
366 );
367 }
368}