pub mod allocation;
pub mod attestation;
pub mod boundary;
pub mod classification;
pub mod compliance;
pub mod crd;
pub mod encapsulates;
pub mod env;
pub mod ephemeral;
pub mod export;
pub mod hostname;
pub mod identity;
pub mod intent;
pub mod lifetime;
pub mod lifetime_clock;
pub mod matrix;
pub mod phase;
pub mod pool;
pub mod receipt;
pub mod routing;
pub mod signal;
pub mod spec;
pub mod status;
pub mod table;
pub mod tagged_union;
pub mod prelude {
pub use crate::allocation::{
AllocationCondition, AllocationPhase, AllocationSpec, AllocationStatus,
EphemeralAllocation, Requestor,
};
pub use crate::attestation::ProcessAttestation;
pub use crate::boundary::{Boundary, Condition, ConditionKind, UnknownConditionKind};
pub use crate::classification::{
Arity, CalmClassification, Classification, ConvergencePointType, DataClassification,
Horizon, HorizonKind, OptimizationDirection, SubstrateType, UnknownCalmClassification,
UnknownConvergencePointType, UnknownDataClassification, UnknownHorizonKind,
UnknownOptimizationDirection, UnknownSubstrateType,
};
pub use crate::compliance::{
ComplianceBinding, ComplianceSpec, UnknownVerificationPhase, VerificationPhase,
};
pub use crate::crd::{Process, ProcessSpec, ProcessStatus};
pub use crate::encapsulates::{
BareWorkload, EncapsulatesSpec, EncapsulationKind, EncapsulationKindError,
EncapsulationKindVariant, EncapsulationMode, EncapsulationTarget, ExistingHelmRelease,
ExistingKustomization, UnknownEncapsulationMode, UnknownEncapsulationTarget,
};
pub use crate::ephemeral::{compile_ephemeral_source, EphemeralSpec};
pub use crate::export::{
ArtifactError, ArtifactKind, ArtifactSource, ArtifactVariant, ChannelError, ChannelKind,
ChannelVariant, ExportSpec, ExportTrigger, HttpEventChannel, NatsSubjectChannel,
ProcessSnapshotSource, ReceiptsSource, ReportFormat, ReportPayloadShape, RunMarkerSource,
StdoutChannel, TestReportSource, UnknownArtifactKind, UnknownChannelKind,
UnknownExportTrigger, UnknownReportFormat, VectorChannel, DEFAULT_NATS_URL,
DEFAULT_VECTOR_INGEST,
};
pub use crate::hostname::{
ephemeral_id_from_spec, fmt_fqdn, fmt_fqdn_stable, resolve_ephemeral_id, HostnameError,
EPHEMERAL_ID_HASH_LEN,
};
pub use crate::identity::{content_hash, derive_identity, format_process_address, Identity};
pub use crate::intent::{
AplicacaoIntent, ContainerIntent, FluxIntent, GuestIntent, HelmLifecyclePolicy,
HelmRemediationPolicy, Intent, IntentError, IntentKind, IntentVariant, LispIntent,
NixIntent, UnknownWorkloadKind, WorkloadKind, FLUX_HELM_DEFAULT_INTERVAL,
HELM_LIFECYCLE_DEFAULT_RETRIES, HELM_LIFECYCLE_DEFAULT_TIMEOUT,
};
pub use crate::lifetime::{
EphemeralLifetime, Lifetime, LifetimeError, LifetimeKind, LifetimeVariant,
PermanentLifetime, TeardownPolicy, UnknownTeardownPolicy,
};
pub use crate::lifetime_clock::{
evaluate as lifetime_clock_evaluate, AutoTerminate, AutoTerminateKind, TerminateReason,
TerminateReasonKind, UnknownAutoTerminateKind, UnknownTerminateReasonKind,
};
pub use crate::matrix::{
compile_env_matrix_source, EnvMatrixSpec, MatrixAxis, MatrixBudget, NamedEphemeral,
SelectStrategy, SelectStrategyKind, UnknownSelectStrategyKind,
};
pub use crate::phase::{ProcessPhase, UnknownPhase};
pub use crate::pool::{
AllocationRef, EphemeralPool, MatchKey, MemberState, PoolCondition, PoolMember, PoolPhase,
PoolSelector, PoolSpec, PoolStatus, ReplacementPolicy, ReturnPolicy, UnknownMemberState,
UnknownPoolPhase, UnknownReplacementPolicy,
};
pub use crate::receipt::{
default_receipt_config_map_name, ReceiptEnvelope, ReceiptError, ReceiptKind,
RECEIPT_CM_SUFFIX, RECEIPT_VERSION,
};
pub use crate::routing::{RoutingBackend, RoutingForm, RoutingHostname, RoutingSpec};
pub use crate::signal::{ProcessSignal, SighupStrategy, UnknownSighupStrategy};
pub use crate::spec::{
DependsOn, IdentitySpec, MustReachPhase, SignalPolicy, UnknownMustReachPhase,
};
pub use crate::status::{
BoundaryStatus, CheckedCondition, ComplianceStatus, FluxResourceRef, ProcessCondition,
RenderedResourceCoords,
};
pub use crate::table::{
ClaimRecord, ProcessEntry, ProcessTable, ProcessTableSpec, ProcessTableStatus,
};
}
pub const GROUP: &str = "tatara.pleme.io";
pub const VERSION: &str = "v1alpha1";
pub const PROCESS_KIND: &str = "Process";
pub fn api_version() -> String {
format!("{GROUP}/{VERSION}")
}
pub fn owner_reference_json(name: &str, uid: &str) -> serde_json::Value {
serde_json::json!({
"apiVersion": api_version(),
"kind": PROCESS_KIND,
"name": name,
"uid": uid,
"controller": true,
"blockOwnerDeletion": true,
})
}
pub fn owner_references_json(name: &str, uid: &str) -> Vec<serde_json::Value> {
if uid.is_empty() {
vec![]
} else {
vec![owner_reference_json(name, uid)]
}
}
pub mod annotations {
pub const MANAGED_BY: &str = "tatara.pleme.io/managed-by";
pub const PROCESS: &str = "tatara.pleme.io/process";
pub const PID: &str = "tatara.pleme.io/pid";
pub const CONTENT_HASH: &str = "tatara.pleme.io/content-hash";
pub const ATTESTATION_ROOT: &str = "tatara.pleme.io/attestation-root";
pub const GENERATION: &str = "tatara.pleme.io/generation";
pub const SIGNAL: &str = "tatara.pleme.io/signal";
pub const RELEASED_FROM: &str = "tatara.pleme.io/released-from";
pub const ROLE: &str = "tatara.pleme.io/role";
pub const EXPORT_INDEX: &str = "tatara.pleme.io/export-index";
pub const APP: &str = "tatara.pleme.io/app";
pub const ROUTING_FORM: &str = "tatara.pleme.io/routing-form";
}
pub const PROCESS_FINALIZER: &str = "tatara.pleme.io/process-finalizer";
pub mod schema_helpers {
use schemars::{gen::SchemaGenerator, schema::Schema};
pub fn preserve_unknown_object(_g: &mut SchemaGenerator) -> Schema {
serde_json::from_value(serde_json::json!({
"type": "object",
"x-kubernetes-preserve-unknown-fields": true
}))
.expect("static JSON literal parses as Schema")
}
}
#[cfg(test)]
mod owner_reference_tests {
use super::{
api_version, owner_reference_json, owner_references_json, GROUP, PROCESS_KIND, VERSION,
};
use serde_json::json;
#[test]
fn api_version_composes_group_and_version() {
assert_eq!(api_version(), format!("{GROUP}/{VERSION}"));
}
#[test]
fn api_version_byte_matches_wire_form_pre_lift() {
assert_eq!(api_version(), "tatara.pleme.io/v1alpha1");
}
#[test]
fn process_kind_is_process_literal() {
assert_eq!(PROCESS_KIND, "Process");
}
#[test]
fn owner_reference_json_has_all_six_slots_present() {
let v = owner_reference_json("my-process", "abc-uid");
let obj = v.as_object().expect("owner reference is a JSON object");
for k in [
"apiVersion",
"kind",
"name",
"uid",
"controller",
"blockOwnerDeletion",
] {
assert!(obj.contains_key(k), "missing owner-reference slot: {k}");
}
assert_eq!(obj.len(), 6, "owner reference must have exactly 6 slots");
}
#[test]
fn owner_reference_json_apiversion_routes_through_api_version_owner() {
let v = owner_reference_json("x", "y");
assert_eq!(v["apiVersion"], api_version());
}
#[test]
fn owner_reference_json_kind_routes_through_process_kind_const() {
let v = owner_reference_json("x", "y");
assert_eq!(v["kind"], PROCESS_KIND);
}
#[test]
fn owner_reference_json_stamps_supplied_name_and_uid() {
let v = owner_reference_json("some-name", "some-uid");
assert_eq!(v["name"], "some-name");
assert_eq!(v["uid"], "some-uid");
}
#[test]
fn owner_reference_json_controller_and_block_owner_deletion_are_true() {
let v = owner_reference_json("x", "y");
assert_eq!(v["controller"], true);
assert_eq!(v["blockOwnerDeletion"], true);
}
#[test]
fn owner_reference_json_matches_hand_authored_shape_pre_lift() {
let via_owner = owner_reference_json("p", "u");
let hand_authored = json!({
"apiVersion": "tatara.pleme.io/v1alpha1",
"kind": "Process",
"name": "p",
"uid": "u",
"controller": true,
"blockOwnerDeletion": true,
});
assert_eq!(via_owner, hand_authored);
}
#[test]
fn owner_reference_json_preserves_empty_name_and_uid_bytewise() {
let v = owner_reference_json("", "");
assert_eq!(v["name"], "");
assert_eq!(v["uid"], "");
}
#[test]
fn owner_references_json_emits_single_entry_when_uid_present() {
let refs = owner_references_json("demo-app", "abc-uid");
assert_eq!(refs.len(), 1);
assert_eq!(refs[0]["kind"], PROCESS_KIND);
assert_eq!(refs[0]["name"], "demo-app");
assert_eq!(refs[0]["uid"], "abc-uid");
assert_eq!(refs[0]["controller"], true);
assert_eq!(refs[0]["blockOwnerDeletion"], true);
}
#[test]
fn owner_references_json_emits_empty_when_uid_empty() {
let refs = owner_references_json("demo-app", "");
assert!(
refs.is_empty(),
"empty uid must produce zero owner references, not a placeholder-uid entry"
);
}
#[test]
fn owner_references_json_gates_on_uid_not_name() {
assert!(
owner_references_json("has-name", "").is_empty(),
"empty uid gates to []; name presence is irrelevant"
);
let refs = owner_references_json("", "has-uid");
assert_eq!(
refs.len(),
1,
"empty name but present uid still emits one entry (name is not the gate)"
);
assert_eq!(refs[0]["name"], "");
assert_eq!(refs[0]["uid"], "has-uid");
}
#[test]
fn owner_references_json_matches_hand_authored_pre_lift_bytewise() {
for (name, uid) in [
("demo-app", "uid-abc"),
("demo-app", ""),
("", "uid-abc"),
("", ""),
] {
let via_primitive = owner_references_json(name, uid);
let mut hand_authored: Vec<serde_json::Value> = vec![];
if !uid.is_empty() {
hand_authored.push(owner_reference_json(name, uid));
}
assert_eq!(
via_primitive, hand_authored,
"owner_references_json must be byte-identical to the pre-lift 3-line gate on ({name:?}, {uid:?})"
);
}
}
#[test]
fn owner_references_json_interpolates_cleanly_as_owner_refs_slot() {
let refs = owner_references_json("demo-app", "abc-uid");
let wrapped = json!({
"metadata": {
"name": "resource",
"ownerReferences": refs,
},
});
let owner_refs = &wrapped["metadata"]["ownerReferences"];
assert!(
owner_refs.is_array(),
"ownerReferences must land as a JSON array"
);
assert_eq!(owner_refs.as_array().unwrap().len(), 1);
assert_eq!(owner_refs[0]["kind"], PROCESS_KIND);
let empty_refs = owner_references_json("demo-app", "");
let wrapped_empty = json!({
"metadata": {
"name": "resource",
"ownerReferences": empty_refs,
},
});
let owner_refs_empty = &wrapped_empty["metadata"]["ownerReferences"];
assert!(owner_refs_empty.is_array());
assert!(owner_refs_empty.as_array().unwrap().is_empty());
}
}
pub type Definition = tatara_lisp::NamedDefinition<crate::crd::ProcessSpec>;
pub fn compile_source(src: &str) -> tatara_lisp::Result<Vec<Definition>> {
tatara_lisp::compile_named::<crate::crd::ProcessSpec>(src)
}
pub fn register_all() {
tatara_lisp::domain::register::<crate::crd::ProcessSpec>();
tatara_lisp::domain::register::<crate::ephemeral::EphemeralSpec>();
}
#[cfg(test)]
mod compile_tests {
use super::compile_source;
use crate::classification::{ConvergencePointType, SubstrateType};
use crate::compliance::VerificationPhase;
use crate::spec::MustReachPhase;
#[test]
fn full_processspec_round_trip_via_derive() {
let src = r#"
(defpoint observability-stack
:identity (:parent "seph.1")
:classification (:point-type Gate
:substrate Observability
:horizon (:kind Bounded)
:calm Monotone
:data-classification Internal)
:intent (:nix (:flake-ref "github:pleme-io/k8s"
:attribute "observability"
:attic-cache "main"))
:boundary (:postconditions
((:kind KustomizationHealthy
:params (:name "observability-stack"
:namespace "flux-system"))
(:kind PromQL
:params (:query "up == 1")))
:timeout "15m")
:compliance (:baseline "fedramp-moderate"
:bindings ((:framework "nist-800-53"
:control-id "SC-7"
:phase AtBoundary)))
:depends-on ((:name "secret-injection" :must-reach Attested))
:signals (:sigterm-grace-seconds 480
:sighup-strategy Reconverge))
"#;
let defs = compile_source(src).expect("compile");
assert_eq!(defs.len(), 1);
let d = &defs[0];
assert_eq!(d.name, "observability-stack");
assert_eq!(d.spec.identity.parent.as_deref(), Some("seph.1"));
assert_eq!(d.spec.classification.point_type, ConvergencePointType::Gate);
assert_eq!(
d.spec.classification.substrate,
SubstrateType::Observability
);
let nix = d.spec.intent.nix.as_ref().expect("nix intent");
assert_eq!(nix.flake_ref, "github:pleme-io/k8s");
assert_eq!(nix.attribute, "observability");
assert_eq!(nix.attic_cache.as_deref(), Some("main"));
assert_eq!(d.spec.boundary.postconditions.len(), 2);
assert_eq!(d.spec.boundary.timeout.as_deref(), Some("15m"));
assert_eq!(
d.spec.compliance.baseline.as_deref(),
Some("fedramp-moderate")
);
assert_eq!(d.spec.compliance.bindings.len(), 1);
assert_eq!(
d.spec.compliance.bindings[0].phase,
VerificationPhase::AtBoundary
);
assert_eq!(d.spec.depends_on.len(), 1);
assert_eq!(d.spec.depends_on[0].must_reach, MustReachPhase::Attested);
assert_eq!(d.spec.signals.sigterm_grace_seconds, 480);
}
#[test]
fn missing_required_field_errors() {
let src = r#"(defpoint x :intent (:nix (:flake-ref "f" :attribute "a")))"#;
assert!(compile_source(src).is_err());
}
#[test]
fn serde_default_fields_are_optional() {
let src = r#"
(defpoint x
:classification (:point-type Transform :substrate Compute)
:intent (:flux (:git-repository "g" :path ".")))
"#;
let defs = compile_source(src).expect("compile");
assert_eq!(defs.len(), 1);
let d = &defs[0];
assert!(d.spec.depends_on.is_empty());
assert!(d.spec.boundary.postconditions.is_empty());
assert!(d.spec.compliance.bindings.is_empty());
assert!(!d.spec.suspended);
assert!(d.spec.lifetime.is_default());
assert!(!d.spec.lifetime.is_ephemeral());
}
#[test]
fn register_all_resolves_defpoint_and_defephemeral() {
use tatara_lisp::domain::lookup;
super::register_all();
super::register_all(); assert!(lookup("defpoint").is_some(), "defpoint must resolve");
assert!(
lookup("defephemeral").is_some(),
"defephemeral must resolve"
);
}
#[test]
fn defpoint_with_aplicacao_intent_and_ephemeral_lifetime() {
use crate::intent::IntentVariant;
use crate::lifetime::{LifetimeVariant, TeardownPolicy};
let src = r#"
(defpoint closed-loop-attest
:classification (:point-type Gate :substrate Compute)
:intent (:aplicacao
(:chart-ref "oci://ghcr.io/pleme-io/charts/lareira-demo-app"
:version "0.5.5"
:profile "all-in-one"
:values-overlay (:cluster (:name "ephemeral-test-01"))
:target-namespace "demo-test"))
:boundary (:postconditions
((:kind HelmReleaseReleased
:params (:name "demo-app-consolidated"
:namespace "demo-test"))
(:kind ClosedLoopAuth
:params (:issuer (:service "demo-app-issuer" :port 8080)
:consumer (:service "demo-app-gateway" :port 8000)
:probeImage "ghcr.io/pleme-io/closed-loop-probe:0.1.0"))))
:lifetime (:ephemeral (:ttl "1h"
:teardown-policy OnAttested
:max-concurrent 1)))
"#;
let defs = compile_source(src).expect("compile");
assert_eq!(defs.len(), 1);
let d = &defs[0];
match d.spec.intent.variant().unwrap() {
IntentVariant::Aplicacao(a) => {
assert_eq!(a.profile, "all-in-one");
assert_eq!(a.version, "0.5.5");
assert_eq!(a.target_namespace.as_deref(), Some("demo-test"));
assert_eq!(a.values_overlay["cluster"]["name"], "ephemeral-test-01");
}
other => panic!("expected Aplicacao, got {other:?}"),
}
match d.spec.lifetime.variant().unwrap() {
LifetimeVariant::Ephemeral(e) => {
assert_eq!(e.ttl, "1h");
assert_eq!(e.teardown_policy, TeardownPolicy::OnAttested);
assert_eq!(e.max_concurrent, 1);
}
other => panic!("expected ephemeral, got {other:?}"),
}
assert_eq!(d.spec.boundary.postconditions.len(), 2);
assert_eq!(
d.spec.boundary.postconditions[1].kind,
crate::boundary::ConditionKind::ClosedLoopAuth
);
}
}