Bind Yggdrasil health to signed deployment lineage
This commit is contained in:
parent
b941f73202
commit
d64b3e2e6e
10 changed files with 918 additions and 98 deletions
|
|
@ -51,7 +51,7 @@ use epiphany_core::write_epiphany_cultmesh_semantic_projector_service_policy;
|
|||
use epiphany_core::write_epiphany_cultmesh_workspace_coverage_projector_service_policy;
|
||||
use epiphany_core::{
|
||||
EpiphanyAggregateRuntimeHealthInput, derive_epiphany_aggregate_runtime_health,
|
||||
publish_idunn_daemon_health_rudp,
|
||||
publish_idunn_daemon_health_rudp, sign_epiphany_runtime_health,
|
||||
};
|
||||
use epiphany_core::{EpiphanyPackagedReleaseEntry, authenticate_epiphany_packaged_release};
|
||||
use epiphany_core::{
|
||||
|
|
@ -69,6 +69,10 @@ use epiphany_core::{
|
|||
write_workspace_coverage_managed_process_launch, write_workspace_coverage_process_bootstrap,
|
||||
write_workspace_coverage_process_termination_observation,
|
||||
};
|
||||
use epiphany_core::{
|
||||
authenticate_current_workspace_coverage_advancement,
|
||||
authenticate_current_workspace_coverage_terminal_authority,
|
||||
};
|
||||
use epiphany_core::{
|
||||
epiphany_packaged_release_binary_path, epiphany_packaged_release_witness_sha256,
|
||||
};
|
||||
|
|
@ -94,6 +98,7 @@ const WORKSPACE_COVERAGE_PROJECTOR_SERVICE_ID: &str =
|
|||
"epiphany-workspace-coverage-projector-service";
|
||||
const WORKSPACE_COVERAGE_PROJECTOR_EXECUTOR_ID: &str = "epiphany-workspace-coverage-projector";
|
||||
const AGGREGATE_HEARTBEAT_FRESH_SECONDS: i64 = 180;
|
||||
const WORKSPACE_PROGRESS_NO_ADVANCE_LEASE_SECONDS: i64 = 300;
|
||||
|
||||
enum ManagedServiceLineage {
|
||||
Current,
|
||||
|
|
@ -798,10 +803,15 @@ fn managed_service_serve(args: Args) -> Result<()> {
|
|||
if fs::canonicalize(env::current_exe()?)? != expected_supervisor {
|
||||
anyhow::bail!("managed-service reconciler executable is not the pinned release supervisor");
|
||||
}
|
||||
let health_signer = open_default_host_identity()?;
|
||||
let health_publisher_incarnation = Uuid::new_v4().to_string();
|
||||
let health_publisher_process = capture_process_instance(std::process::id())?;
|
||||
let mut iteration = 0_u64;
|
||||
loop {
|
||||
pinned_packaged_release(&args, true)?;
|
||||
iteration = iteration.saturating_add(1);
|
||||
let (release, release_witness_sha256) = pinned_packaged_release(&args, true)?;
|
||||
iteration = iteration
|
||||
.checked_add(1)
|
||||
.context("aggregate runtime health sequence exhausted")?;
|
||||
let policies =
|
||||
load_epiphany_cultmesh_managed_service_policies(&args.store, args.runtime_id.clone())?;
|
||||
for policy in &policies {
|
||||
|
|
@ -809,7 +819,16 @@ fn managed_service_serve(args: Args) -> Result<()> {
|
|||
service_args.service_id = policy.service_id.clone();
|
||||
managed_service_reconcile(service_args)?;
|
||||
}
|
||||
publish_managed_service_iteration_health(&args, &release, &policies);
|
||||
publish_managed_service_iteration_health(
|
||||
&args,
|
||||
&release,
|
||||
&release_witness_sha256,
|
||||
&policies,
|
||||
&health_publisher_incarnation,
|
||||
iteration,
|
||||
&health_publisher_process,
|
||||
&health_signer,
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({
|
||||
|
|
@ -834,7 +853,12 @@ fn managed_service_serve(args: Args) -> Result<()> {
|
|||
fn publish_managed_service_iteration_health(
|
||||
args: &Args,
|
||||
release: &EpiphanyPackagedReleaseEntry,
|
||||
authenticated_release_witness_sha256: &str,
|
||||
policies: &[EpiphanyCultMeshManagedServicePolicyEntry],
|
||||
publisher_incarnation_id: &str,
|
||||
publisher_sequence: u64,
|
||||
publisher_process: &ProcessInstanceIdentity,
|
||||
health_signer: &epiphany_core::HostIdentitySigner,
|
||||
) {
|
||||
let (Some(endpoint), Some(daemon_id), Some(health_contract)) = (
|
||||
args.idunn_rudp_health,
|
||||
|
|
@ -848,7 +872,9 @@ fn publish_managed_service_iteration_health(
|
|||
WORKSPACE_COVERAGE_PROJECTOR_SERVICE_ID,
|
||||
];
|
||||
let expected = required.len();
|
||||
let mut current = 0_usize;
|
||||
let mut terminal_current = 0_usize;
|
||||
let mut warming = 0_usize;
|
||||
let mut workspace_evidence = None;
|
||||
let mut contradictions = Vec::new();
|
||||
for service_id in required {
|
||||
let Some(policy) = policies
|
||||
|
|
@ -861,7 +887,84 @@ fn publish_managed_service_iteration_health(
|
|||
continue;
|
||||
}
|
||||
match managed_service_lineage(args, release, policy) {
|
||||
Ok(ManagedServiceLineage::Current) => current += 1,
|
||||
Ok(ManagedServiceLineage::Current)
|
||||
if policy.service_id == WORKSPACE_COVERAGE_PROJECTOR_SERVICE_ID =>
|
||||
{
|
||||
let runtime_store = match args.runtime_store.as_deref() {
|
||||
Some(store) => store,
|
||||
None => {
|
||||
contradictions
|
||||
.push("workspace health lacks the bound runtime store".to_string());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match authenticate_current_workspace_coverage_terminal_authority(runtime_store) {
|
||||
Ok(Some(authority)) => {
|
||||
terminal_current += 1;
|
||||
workspace_evidence = Some(format!(
|
||||
"workspaceTerminalReceiptId={} workspacePlanId={} workspaceBodyObservationId={} workspaceBodyGeneration={}",
|
||||
authority.receipt_id,
|
||||
authority.plan_id,
|
||||
authority.body_observation_id,
|
||||
authority.body_generation
|
||||
));
|
||||
}
|
||||
Ok(None) => {
|
||||
let host = match open_default_host_identity() {
|
||||
Ok(host) => host,
|
||||
Err(error) => {
|
||||
contradictions.push(format!(
|
||||
"workspace health cannot authenticate host: {error:#}"
|
||||
));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let launch = match load_latest_workspace_coverage_managed_process_launch(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
) {
|
||||
Ok(Some(launch)) => launch,
|
||||
Ok(None) => continue,
|
||||
Err(error) => {
|
||||
contradictions.push(format!(
|
||||
"workspace health cannot load launch: {error:#}"
|
||||
));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match authenticate_current_workspace_coverage_advancement(
|
||||
runtime_store,
|
||||
&args.store,
|
||||
&args.runtime_id,
|
||||
&launch.launch_id,
|
||||
host.entry(),
|
||||
Utc::now(),
|
||||
Duration::seconds(WORKSPACE_PROGRESS_NO_ADVANCE_LEASE_SECONDS),
|
||||
) {
|
||||
Ok(Some(authority)) => {
|
||||
warming += 1;
|
||||
workspace_evidence = Some(format!(
|
||||
"workspaceProgressId={} workspaceCheckpointId={} workspacePlanId={} workspaceCompletedUnits={} workspaceTotalUnits={} workspaceLastAdvancedAt={}",
|
||||
authority.progress_id,
|
||||
authority.checkpoint_id,
|
||||
authority.plan_id,
|
||||
authority.completed_units,
|
||||
authority.total_units,
|
||||
authority.last_advanced_at_utc
|
||||
));
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(error) => contradictions.push(format!(
|
||||
"workspace advancement authority is invalid: {error:#}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
Err(error) => contradictions.push(format!(
|
||||
"workspace terminal authority is invalid: {error:#}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
Ok(ManagedServiceLineage::Current) => terminal_current += 1,
|
||||
Ok(ManagedServiceLineage::Pending) => {}
|
||||
Ok(ManagedServiceLineage::LegacyV1) => contradictions.push(format!(
|
||||
"{}: legacy v1 launch awaits typed retirement",
|
||||
|
|
@ -879,16 +982,38 @@ fn publish_managed_service_iteration_health(
|
|||
observed_at: Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
|
||||
release_authenticated: true,
|
||||
expected_service_count: expected,
|
||||
current_service_count: current,
|
||||
terminal_current_service_count: terminal_current,
|
||||
warming_service_count: warming,
|
||||
contradictions,
|
||||
});
|
||||
match health.and_then(|mut health| {
|
||||
let witness_sha256 = epiphany_packaged_release_witness_sha256(release)?;
|
||||
if witness_sha256 != authenticated_release_witness_sha256 {
|
||||
anyhow::bail!("aggregate health release witness changed after authentication");
|
||||
}
|
||||
health.detail.push_str(&format!(
|
||||
" releaseId={} witnessSha256={} sourceCommit={}",
|
||||
release.release_id, witness_sha256, release.source_commit_sha
|
||||
));
|
||||
publish_idunn_daemon_health_rudp(endpoint, "epiphany-daemon-supervisor", &health)
|
||||
if let Some(evidence) = &workspace_evidence {
|
||||
health.detail.push(' ');
|
||||
health.detail.push_str(evidence);
|
||||
}
|
||||
let signed = sign_epiphany_runtime_health(
|
||||
health,
|
||||
"epiphany-daemon-supervisor",
|
||||
&release.release_id,
|
||||
&witness_sha256,
|
||||
&release.source_commit_sha,
|
||||
args.idunn_deployment_request_id
|
||||
.as_deref()
|
||||
.context("aggregate health requires --idunn-deployment-request-id")?,
|
||||
publisher_incarnation_id,
|
||||
publisher_sequence,
|
||||
publisher_process,
|
||||
health_signer,
|
||||
)?;
|
||||
publish_idunn_daemon_health_rudp(endpoint, "epiphany-daemon-supervisor", &signed)
|
||||
}) {
|
||||
Ok(()) => {}
|
||||
Err(error) => eprintln!("Epiphany could not publish aggregate Idunn health: {error:#}"),
|
||||
|
|
@ -3932,17 +4057,18 @@ mod semantic_projector_authority_tests {
|
|||
#[test]
|
||||
fn idunn_health_configuration_is_explicit_and_all_or_none() {
|
||||
let endpoint: SocketAddr = "127.0.0.1:17870".parse().unwrap();
|
||||
assert!(validate_idunn_health_options(None, None, None).is_ok());
|
||||
assert!(validate_idunn_health_options(None, None, None, None).is_ok());
|
||||
assert!(
|
||||
validate_idunn_health_options(
|
||||
Some(&endpoint),
|
||||
Some("yggdrasil-epiphany"),
|
||||
Some("epiphany.cultnet-rudp-runtime-health"),
|
||||
Some("deploy-request-test"),
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(validate_idunn_health_options(Some(&endpoint), None, None).is_err());
|
||||
assert!(validate_idunn_health_options(None, Some("epiphany"), None).is_err());
|
||||
assert!(validate_idunn_health_options(Some(&endpoint), None, None, None).is_err());
|
||||
assert!(validate_idunn_health_options(None, Some("epiphany"), None, None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -4072,6 +4198,7 @@ struct Args {
|
|||
idunn_rudp_health: Option<SocketAddr>,
|
||||
idunn_daemon: Option<String>,
|
||||
idunn_health_contract: Option<String>,
|
||||
idunn_deployment_request_id: Option<String>,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
|
|
@ -4125,6 +4252,7 @@ impl Args {
|
|||
let mut idunn_rudp_health = None;
|
||||
let mut idunn_daemon = None;
|
||||
let mut idunn_health_contract = None;
|
||||
let mut idunn_deployment_request_id = None;
|
||||
|
||||
while let Some(arg) = values.next() {
|
||||
match arg.as_str() {
|
||||
|
|
@ -4321,6 +4449,13 @@ impl Args {
|
|||
.context("missing --idunn-health-contract value")?,
|
||||
);
|
||||
}
|
||||
"--idunn-deployment-request-id" => {
|
||||
idunn_deployment_request_id = Some(
|
||||
values
|
||||
.next()
|
||||
.context("missing --idunn-deployment-request-id value")?,
|
||||
);
|
||||
}
|
||||
other => anyhow::bail!("unknown argument {other:?}"),
|
||||
}
|
||||
}
|
||||
|
|
@ -4429,6 +4564,7 @@ impl Args {
|
|||
idunn_rudp_health.as_ref(),
|
||||
idunn_daemon.as_deref(),
|
||||
idunn_health_contract.as_deref(),
|
||||
idunn_deployment_request_id.as_deref(),
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
|
|
@ -4479,6 +4615,7 @@ impl Args {
|
|||
idunn_rudp_health,
|
||||
idunn_daemon,
|
||||
idunn_health_contract,
|
||||
idunn_deployment_request_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -4487,20 +4624,23 @@ fn validate_idunn_health_options(
|
|||
endpoint: Option<&SocketAddr>,
|
||||
daemon_id: Option<&str>,
|
||||
health_contract: Option<&str>,
|
||||
deployment_request_id: Option<&str>,
|
||||
) -> Result<()> {
|
||||
let fields = [
|
||||
endpoint.is_some(),
|
||||
daemon_id.is_some(),
|
||||
health_contract.is_some(),
|
||||
deployment_request_id.is_some(),
|
||||
];
|
||||
if fields.into_iter().any(|present| present) && !fields.into_iter().all(|present| present) {
|
||||
anyhow::bail!(
|
||||
"--idunn-rudp-health, --idunn-daemon, and --idunn-health-contract are all-or-none"
|
||||
"--idunn-rudp-health, --idunn-daemon, --idunn-health-contract, and --idunn-deployment-request-id are all-or-none"
|
||||
);
|
||||
}
|
||||
for (label, value) in [
|
||||
("--idunn-daemon", daemon_id),
|
||||
("--idunn-health-contract", health_contract),
|
||||
("--idunn-deployment-request-id", deployment_request_id),
|
||||
] {
|
||||
if value.is_some_and(|value| value.trim().is_empty()) {
|
||||
anyhow::bail!("{label} cannot be empty");
|
||||
|
|
|
|||
|
|
@ -1,17 +1,46 @@
|
|||
use anyhow::{Result, anyhow};
|
||||
use epiphany_core::{
|
||||
default_host_identity_store_path, enroll_default_host_identity, open_default_host_identity,
|
||||
default_host_identity_store_path, enroll_default_host_identity,
|
||||
export_host_identity_trust_anchor, open_default_host_identity,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let command = std::env::args()
|
||||
.nth(1)
|
||||
.ok_or_else(|| anyhow!("usage: epiphany-host-identity <enroll|status>"))?;
|
||||
let command = std::env::args().nth(1).ok_or_else(|| {
|
||||
anyhow!("usage: epiphany-host-identity <enroll|status|enroll-trust-anchor|export-trust-anchor> [output]")
|
||||
})?;
|
||||
if matches!(
|
||||
command.as_str(),
|
||||
"enroll-trust-anchor" | "export-trust-anchor"
|
||||
) {
|
||||
let output = std::env::args()
|
||||
.nth(2)
|
||||
.ok_or_else(|| anyhow!("{command} requires an output path"))?;
|
||||
let signer = if command == "enroll-trust-anchor" {
|
||||
enroll_default_host_identity()?
|
||||
} else {
|
||||
open_default_host_identity()?
|
||||
};
|
||||
let anchor = export_host_identity_trust_anchor(&signer, std::path::Path::new(&output))?;
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"schemaVersion": anchor.schema_version,
|
||||
"identityId": anchor.identity_id,
|
||||
"output": output,
|
||||
"privateStateExposed": false
|
||||
}))?
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
let signer = match command.as_str() {
|
||||
"enroll" => enroll_default_host_identity()?,
|
||||
"status" => open_default_host_identity()?,
|
||||
_ => return Err(anyhow!("usage: epiphany-host-identity <enroll|status>")),
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"usage: epiphany-host-identity <enroll|status|enroll-trust-anchor|export-trust-anchor> [output]"
|
||||
));
|
||||
}
|
||||
};
|
||||
let entry = signer.entry();
|
||||
println!(
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ use std::path::{Path, PathBuf};
|
|||
pub const HOST_IDENTITY_TYPE: &str = "epiphany.host_incarnation_identity.v0";
|
||||
pub const HOST_IDENTITY_SCHEMA_VERSION: &str = "epiphany.host_incarnation_identity.v0";
|
||||
pub const HOST_IDENTITY_KEY: &str = "host-incarnation";
|
||||
pub const HOST_IDENTITY_TRUST_ANCHOR_TYPE: &str = "epiphany.host_identity_trust_anchor.v0";
|
||||
pub const HOST_IDENTITY_TRUST_ANCHOR_KEY: &str = "host-incarnation-public";
|
||||
pub const WINDOWS_HOST_IDENTITY_ASSURANCE: &str = "os_user_installation_bound_best_effort";
|
||||
pub const LINUX_HOST_IDENTITY_ASSURANCE: &str = "os_installation_file_bound_cloneable_baseline";
|
||||
|
||||
|
|
@ -50,6 +52,26 @@ pub struct HostIdentitySignature {
|
|||
pub signature: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "epiphany.host_identity_trust_anchor.v0",
|
||||
schema = "HostIdentityTrustAnchorEntry"
|
||||
)]
|
||||
pub struct HostIdentityTrustAnchorEntry {
|
||||
#[cultcache(key = 0)]
|
||||
pub schema_version: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub identity_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub public_key: Vec<u8>,
|
||||
#[cultcache(key = 3)]
|
||||
pub assurance: String,
|
||||
#[cultcache(key = 4)]
|
||||
pub identity_created_at: String,
|
||||
#[cultcache(key = 5)]
|
||||
pub source_identity_record_sha256: String,
|
||||
}
|
||||
|
||||
/// A deliberately narrow signing handle. The private seed is never exposed by
|
||||
/// the public API and is unprotected only while this value exists.
|
||||
pub struct HostIdentitySigner {
|
||||
|
|
@ -109,6 +131,40 @@ pub fn open_default_host_identity() -> Result<HostIdentitySigner> {
|
|||
open_host_identity_at(&default_host_identity_store_path()?)
|
||||
}
|
||||
|
||||
pub fn export_host_identity_trust_anchor(
|
||||
signer: &HostIdentitySigner,
|
||||
output: &Path,
|
||||
) -> Result<HostIdentityTrustAnchorEntry> {
|
||||
let entry = signer.entry();
|
||||
let anchor = HostIdentityTrustAnchorEntry {
|
||||
schema_version: HOST_IDENTITY_TRUST_ANCHOR_TYPE.into(),
|
||||
identity_id: entry.identity_id.clone(),
|
||||
public_key: entry.public_key.clone(),
|
||||
assurance: entry.assurance.clone(),
|
||||
identity_created_at: entry.created_at.clone(),
|
||||
source_identity_record_sha256: format!(
|
||||
"sha256-{:x}",
|
||||
Sha256::digest(rmp_serde::to_vec(entry)?)
|
||||
),
|
||||
};
|
||||
prepare_parent(output)?;
|
||||
let envelope = CultCacheEnvelope {
|
||||
key: HOST_IDENTITY_TRUST_ANCHOR_KEY.into(),
|
||||
r#type: HOST_IDENTITY_TRUST_ANCHOR_TYPE.into(),
|
||||
payload: rmp_serde::to_vec(&anchor)?,
|
||||
stored_at: entry.created_at.clone(),
|
||||
schema_id: Some(HOST_IDENTITY_TRUST_ANCHOR_TYPE.into()),
|
||||
};
|
||||
let mut backing = SingleFileMessagePackBackingStore::new(output);
|
||||
let existing = backing.pull_all()?;
|
||||
match existing.as_slice() {
|
||||
[] => backing.push(&envelope)?,
|
||||
[current] if current == &envelope => {}
|
||||
_ => bail!("host identity trust anchor output already contains different state"),
|
||||
}
|
||||
Ok(anchor)
|
||||
}
|
||||
|
||||
/// Performs one immutable enrollment. Existing state is never reused by this
|
||||
/// operation and malformed state is never replaced.
|
||||
pub fn enroll_host_identity_at(store_path: &Path) -> Result<HostIdentitySigner> {
|
||||
|
|
@ -464,4 +520,33 @@ mod tests {
|
|||
assert_eq!(std::fs::read(&store)?, before);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exported_trust_anchor_is_public_only_and_immutable() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let identity_store = temp.path().join("host-identity.ccmp");
|
||||
let anchor_store = temp.path().join("host-identity-public.ccmp");
|
||||
let signer = enroll_host_identity_at(&identity_store)?;
|
||||
let anchor = export_host_identity_trust_anchor(&signer, &anchor_store)?;
|
||||
assert_eq!(anchor.identity_id, signer.entry().identity_id);
|
||||
assert_eq!(anchor.public_key, signer.entry().public_key);
|
||||
|
||||
let envelopes = SingleFileMessagePackBackingStore::new(&anchor_store).pull_all()?;
|
||||
assert_eq!(envelopes.len(), 1);
|
||||
assert_eq!(envelopes[0].r#type, HOST_IDENTITY_TRUST_ANCHOR_TYPE);
|
||||
assert_eq!(envelopes[0].key, HOST_IDENTITY_TRUST_ANCHOR_KEY);
|
||||
let decoded: HostIdentityTrustAnchorEntry = rmp_serde::from_slice(&envelopes[0].payload)?;
|
||||
assert_eq!(decoded, anchor);
|
||||
assert!(
|
||||
!envelopes[0]
|
||||
.payload
|
||||
.windows(signer.entry().protected_private_seed.len())
|
||||
.any(|window| window == signer.entry().protected_private_seed.as_slice())
|
||||
);
|
||||
|
||||
let before = std::fs::read(&anchor_store)?;
|
||||
export_host_identity_trust_anchor(&signer, &anchor_store)?;
|
||||
assert_eq!(std::fs::read(&anchor_store)?, before);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ use std::time::{Duration, Instant};
|
|||
|
||||
pub const IDUNN_DAEMON_HEALTH_TYPE: &str = "idunn.daemon_health";
|
||||
pub const IDUNN_DAEMON_HEALTH_SCHEMA_VERSION: &str = "idunn.daemon_health.v1";
|
||||
pub const EPIPHANY_SIGNED_RUNTIME_HEALTH_TYPE: &str = "epiphany.idunn_signed_runtime_health";
|
||||
pub const EPIPHANY_SIGNED_RUNTIME_HEALTH_SCHEMA_VERSION: &str =
|
||||
"epiphany.idunn_signed_runtime_health.v0";
|
||||
pub const EPIPHANY_IDUNN_RUNTIME_HEALTH_CONTRACT: &str = "epiphany.cultnet-rudp-runtime-health";
|
||||
pub const CULTNET_RUDP_PROTOCOL_ID: &str = "cultnet.transport.rudp.v0";
|
||||
const IDUNN_HEALTH_RUDP_CONNECTION_ID: u32 = 0x4944_554e;
|
||||
|
|
@ -25,6 +28,69 @@ pub struct IdunnDaemonHealthDocument {
|
|||
pub transport: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct EpiphanySignedRuntimeHealthDocument {
|
||||
pub schema_version: String,
|
||||
pub health: IdunnDaemonHealthDocument,
|
||||
pub source_runtime_id: String,
|
||||
pub release_id: String,
|
||||
pub release_witness_sha256: String,
|
||||
pub source_commit: String,
|
||||
pub deployment_request_id: String,
|
||||
pub publisher_incarnation_id: String,
|
||||
pub publisher_sequence: u64,
|
||||
pub publisher_process_id: u32,
|
||||
pub publisher_process_creation_token: u64,
|
||||
pub publisher_process_created_at: String,
|
||||
pub publisher_executable_path: String,
|
||||
pub signer_identity_id: String,
|
||||
pub signature_algorithm: String,
|
||||
pub signature: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn sign_epiphany_runtime_health(
|
||||
health: IdunnDaemonHealthDocument,
|
||||
source_runtime_id: &str,
|
||||
release_id: &str,
|
||||
release_witness_sha256: &str,
|
||||
source_commit: &str,
|
||||
deployment_request_id: &str,
|
||||
publisher_incarnation_id: &str,
|
||||
publisher_sequence: u64,
|
||||
process: &crate::ProcessInstanceIdentity,
|
||||
signer: &crate::HostIdentitySigner,
|
||||
) -> Result<EpiphanySignedRuntimeHealthDocument> {
|
||||
validate_health_document(&health)?;
|
||||
let mut document = EpiphanySignedRuntimeHealthDocument {
|
||||
schema_version: EPIPHANY_SIGNED_RUNTIME_HEALTH_SCHEMA_VERSION.into(),
|
||||
health,
|
||||
source_runtime_id: source_runtime_id.into(),
|
||||
release_id: release_id.into(),
|
||||
release_witness_sha256: release_witness_sha256.into(),
|
||||
source_commit: source_commit.into(),
|
||||
deployment_request_id: deployment_request_id.into(),
|
||||
publisher_incarnation_id: publisher_incarnation_id.into(),
|
||||
publisher_sequence,
|
||||
publisher_process_id: process.process_id,
|
||||
publisher_process_creation_token: process.creation_token,
|
||||
publisher_process_created_at: process
|
||||
.created_at_rfc3339
|
||||
.clone()
|
||||
.context("runtime health publisher process creation time is unavailable")?,
|
||||
publisher_executable_path: process.executable_path.display().to_string(),
|
||||
signer_identity_id: signer.entry().identity_id.clone(),
|
||||
signature_algorithm: "ed25519".into(),
|
||||
signature: Vec::new(),
|
||||
};
|
||||
validate_signed_health_shape(&document, false)?;
|
||||
let statement = signed_health_statement(&document)?;
|
||||
document.signature = signer
|
||||
.sign(EPIPHANY_SIGNED_RUNTIME_HEALTH_TYPE, &statement)?
|
||||
.signature;
|
||||
validate_signed_health_shape(&document, true)?;
|
||||
Ok(document)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct EpiphanyAggregateRuntimeHealthInput {
|
||||
pub daemon_id: String,
|
||||
|
|
@ -32,7 +98,8 @@ pub struct EpiphanyAggregateRuntimeHealthInput {
|
|||
pub observed_at: String,
|
||||
pub release_authenticated: bool,
|
||||
pub expected_service_count: usize,
|
||||
pub current_service_count: usize,
|
||||
pub terminal_current_service_count: usize,
|
||||
pub warming_service_count: usize,
|
||||
pub contradictions: Vec<String>,
|
||||
}
|
||||
|
||||
|
|
@ -45,8 +112,14 @@ pub fn derive_epiphany_aggregate_runtime_health(
|
|||
if input.expected_service_count == 0 {
|
||||
bail!("aggregate runtime health requires at least one managed service");
|
||||
}
|
||||
if input.current_service_count > input.expected_service_count {
|
||||
bail!("current managed service count exceeds the expected service count");
|
||||
if input.terminal_current_service_count > input.expected_service_count
|
||||
|| input.warming_service_count > input.expected_service_count
|
||||
|| input
|
||||
.terminal_current_service_count
|
||||
.saturating_add(input.warming_service_count)
|
||||
> input.expected_service_count
|
||||
{
|
||||
bail!("managed service health counts exceed the expected service count");
|
||||
}
|
||||
|
||||
let (state, detail) = if !input.contradictions.is_empty() {
|
||||
|
|
@ -62,19 +135,36 @@ pub fn derive_epiphany_aggregate_runtime_health(
|
|||
"failed",
|
||||
"Epiphany packaged release authentication failed.".to_string(),
|
||||
)
|
||||
} else if input.current_service_count != input.expected_service_count {
|
||||
} else if input.terminal_current_service_count == input.expected_service_count {
|
||||
(
|
||||
"degraded",
|
||||
"active",
|
||||
format!(
|
||||
"Epiphany is reconciling managed services: {}/{} have current authenticated lineage.",
|
||||
input.current_service_count, input.expected_service_count
|
||||
"Epiphany packaged release and all {} managed services have terminal current authority.",
|
||||
input.expected_service_count
|
||||
),
|
||||
)
|
||||
} else if input.warming_service_count > 0
|
||||
&& input
|
||||
.terminal_current_service_count
|
||||
.saturating_add(input.warming_service_count)
|
||||
== input.expected_service_count
|
||||
{
|
||||
(
|
||||
"warming",
|
||||
format!(
|
||||
"Epiphany is advancing authenticated durable work: {} terminal current, {} warming, {} expected.",
|
||||
input.terminal_current_service_count,
|
||||
input.warming_service_count,
|
||||
input.expected_service_count
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
"active",
|
||||
"degraded",
|
||||
format!(
|
||||
"Epiphany packaged release and all {} managed services have current authenticated lineage.",
|
||||
"Epiphany is reconciling managed services: {} terminal current, {} warming, {} expected.",
|
||||
input.terminal_current_service_count,
|
||||
input.warming_service_count,
|
||||
input.expected_service_count
|
||||
),
|
||||
)
|
||||
|
|
@ -94,22 +184,26 @@ pub fn derive_epiphany_aggregate_runtime_health(
|
|||
pub fn publish_idunn_daemon_health_rudp(
|
||||
endpoint: SocketAddr,
|
||||
source_runtime_id: &str,
|
||||
health: &IdunnDaemonHealthDocument,
|
||||
signed: &EpiphanySignedRuntimeHealthDocument,
|
||||
) -> Result<()> {
|
||||
validate_health_document(health)?;
|
||||
validate_signed_health_shape(signed, true)?;
|
||||
require_id(source_runtime_id, "health publisher runtime id")?;
|
||||
if source_runtime_id != signed.source_runtime_id {
|
||||
bail!("health transport source disagrees with signed source runtime");
|
||||
}
|
||||
let message = CultNetMessage::DocumentPutRaw {
|
||||
message_id: format!(
|
||||
"epiphany-health:{}:{}",
|
||||
health.daemon_id,
|
||||
health.observed_at.replace(':', "-")
|
||||
signed.health.daemon_id,
|
||||
signed.health.observed_at.replace(':', "-")
|
||||
),
|
||||
document: CultNetRawDocumentRecord {
|
||||
schema_id: IDUNN_DAEMON_HEALTH_TYPE.to_string(),
|
||||
record_key: health.daemon_id.clone(),
|
||||
stored_at: health.observed_at.clone(),
|
||||
schema_id: EPIPHANY_SIGNED_RUNTIME_HEALTH_TYPE.to_string(),
|
||||
record_key: signed.health.daemon_id.clone(),
|
||||
stored_at: signed.health.observed_at.clone(),
|
||||
payload_encoding: CultNetRawPayloadEncoding::Messagepack,
|
||||
payload: rmp_serde::to_vec(health).context("encoding Idunn daemon health")?,
|
||||
payload: rmp_serde::to_vec_named(signed)
|
||||
.context("encoding signed Epiphany Idunn daemon health")?,
|
||||
source_runtime_id: Some(source_runtime_id.to_string()),
|
||||
source_agent_id: None,
|
||||
source_role: Some("daemon-health-publisher".to_string()),
|
||||
|
|
@ -149,11 +243,84 @@ pub fn publish_idunn_daemon_health_rudp(
|
|||
.with_context(|| format!("sending Epiphany Idunn health to {endpoint}"))
|
||||
}
|
||||
|
||||
fn signed_health_statement(document: &EpiphanySignedRuntimeHealthDocument) -> Result<Vec<u8>> {
|
||||
let mut unsigned = document.clone();
|
||||
unsigned.signature.clear();
|
||||
validate_signed_health_shape(&unsigned, false)?;
|
||||
rmp_serde::to_vec_named(&unsigned).context("encoding signed runtime health statement")
|
||||
}
|
||||
|
||||
fn validate_signed_health_shape(
|
||||
document: &EpiphanySignedRuntimeHealthDocument,
|
||||
signed: bool,
|
||||
) -> Result<()> {
|
||||
if document.schema_version != EPIPHANY_SIGNED_RUNTIME_HEALTH_SCHEMA_VERSION {
|
||||
bail!("signed Epiphany runtime health schema is invalid");
|
||||
}
|
||||
validate_health_document(&document.health)?;
|
||||
for (label, value) in [
|
||||
("source runtime id", document.source_runtime_id.as_str()),
|
||||
("release id", document.release_id.as_str()),
|
||||
("source commit", document.source_commit.as_str()),
|
||||
(
|
||||
"deployment request id",
|
||||
document.deployment_request_id.as_str(),
|
||||
),
|
||||
(
|
||||
"publisher incarnation id",
|
||||
document.publisher_incarnation_id.as_str(),
|
||||
),
|
||||
(
|
||||
"publisher process creation time",
|
||||
document.publisher_process_created_at.as_str(),
|
||||
),
|
||||
(
|
||||
"publisher executable path",
|
||||
document.publisher_executable_path.as_str(),
|
||||
),
|
||||
("signer identity id", document.signer_identity_id.as_str()),
|
||||
] {
|
||||
require_id(value, label)?;
|
||||
}
|
||||
uuid::Uuid::parse_str(&document.publisher_incarnation_id)
|
||||
.context("publisher incarnation id must be UUID")?;
|
||||
if document.publisher_sequence == 0
|
||||
|| document.publisher_process_id == 0
|
||||
|| document.publisher_process_creation_token == 0
|
||||
{
|
||||
bail!("signed Epiphany runtime health publisher identity is invalid");
|
||||
}
|
||||
chrono::DateTime::parse_from_rfc3339(&document.publisher_process_created_at)?;
|
||||
let witness = document
|
||||
.release_witness_sha256
|
||||
.strip_prefix("sha256-")
|
||||
.unwrap_or(&document.release_witness_sha256);
|
||||
if witness.len() != 64
|
||||
|| !witness
|
||||
.bytes()
|
||||
.all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase())
|
||||
|| document.source_commit.len() != 40
|
||||
|| !document
|
||||
.source_commit
|
||||
.bytes()
|
||||
.all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase())
|
||||
|| document.signature_algorithm != "ed25519"
|
||||
|| (signed && document.signature.len() != 64)
|
||||
|| (!signed && !document.signature.is_empty())
|
||||
{
|
||||
bail!("signed Epiphany runtime health cryptographic shape is invalid");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_health_document(health: &IdunnDaemonHealthDocument) -> Result<()> {
|
||||
require_id(&health.daemon_id, "Idunn daemon id")?;
|
||||
require_id(&health.health_contract, "Idunn health contract")?;
|
||||
require_id(&health.observed_at, "Idunn health observation time")?;
|
||||
if !matches!(health.state.as_str(), "active" | "degraded" | "failed") {
|
||||
if !matches!(
|
||||
health.state.as_str(),
|
||||
"active" | "warming" | "degraded" | "failed"
|
||||
) {
|
||||
bail!("unsupported Idunn daemon health state");
|
||||
}
|
||||
if health.publication_source != "daemon-published"
|
||||
|
|
@ -177,6 +344,29 @@ mod tests {
|
|||
use cultnet_rs::decode_cultnet_message_from_slice;
|
||||
use std::thread;
|
||||
|
||||
fn signed_health(health: IdunnDaemonHealthDocument) -> EpiphanySignedRuntimeHealthDocument {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let signer = crate::enroll_host_identity_at(&root.path().join("host.ccmp")).unwrap();
|
||||
sign_epiphany_runtime_health(
|
||||
health,
|
||||
"epiphany-daemon-supervisor",
|
||||
"release-test",
|
||||
&format!("sha256-{}", "a".repeat(64)),
|
||||
&"b".repeat(40),
|
||||
"deploy-request-test",
|
||||
"00000000-0000-4000-8000-000000000001",
|
||||
1,
|
||||
&crate::ProcessInstanceIdentity {
|
||||
process_id: 42,
|
||||
creation_token: 7,
|
||||
created_at_rfc3339: Some("2026-07-16T00:00:00Z".into()),
|
||||
executable_path: std::path::PathBuf::from("/srv/epiphany/supervisor"),
|
||||
},
|
||||
&signer,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn input(current: usize) -> EpiphanyAggregateRuntimeHealthInput {
|
||||
EpiphanyAggregateRuntimeHealthInput {
|
||||
daemon_id: "yggdrasil-epiphany".into(),
|
||||
|
|
@ -184,7 +374,8 @@ mod tests {
|
|||
observed_at: "2026-07-16T00:00:00Z".into(),
|
||||
release_authenticated: true,
|
||||
expected_service_count: 2,
|
||||
current_service_count: current,
|
||||
terminal_current_service_count: current,
|
||||
warming_service_count: 0,
|
||||
contradictions: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
|
@ -205,6 +396,27 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_health_names_authenticated_nonterminal_work_as_warming() {
|
||||
let mut value = input(1);
|
||||
value.warming_service_count = 1;
|
||||
let health = derive_epiphany_aggregate_runtime_health(value).unwrap();
|
||||
assert_eq!(health.state, "warming");
|
||||
assert!(health.detail.contains("1 terminal current, 1 warming"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn warming_cannot_hide_a_missing_required_service() {
|
||||
let mut value = input(0);
|
||||
value.warming_service_count = 1;
|
||||
assert_eq!(
|
||||
derive_epiphany_aggregate_runtime_health(value)
|
||||
.unwrap()
|
||||
.state,
|
||||
"degraded"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aggregate_health_fails_authenticated_contradictions() {
|
||||
let mut value = input(2);
|
||||
|
|
@ -259,18 +471,20 @@ mod tests {
|
|||
}
|
||||
});
|
||||
let health = derive_epiphany_aggregate_runtime_health(input(2)).unwrap();
|
||||
publish_idunn_daemon_health_rudp(endpoint, "epiphany-daemon-supervisor", &health).unwrap();
|
||||
let signed = signed_health(health.clone());
|
||||
publish_idunn_daemon_health_rudp(endpoint, "epiphany-daemon-supervisor", &signed).unwrap();
|
||||
let CultNetMessage::DocumentPutRaw { document, .. } = server.join().unwrap() else {
|
||||
panic!("publisher emitted a non-document message")
|
||||
};
|
||||
assert_eq!(document.schema_id, IDUNN_DAEMON_HEALTH_TYPE);
|
||||
assert_eq!(document.schema_id, EPIPHANY_SIGNED_RUNTIME_HEALTH_TYPE);
|
||||
assert_eq!(document.record_key, health.daemon_id);
|
||||
assert_eq!(
|
||||
document.payload_encoding,
|
||||
CultNetRawPayloadEncoding::Messagepack
|
||||
);
|
||||
let decoded: IdunnDaemonHealthDocument = rmp_serde::from_slice(&document.payload).unwrap();
|
||||
assert_eq!(decoded, health);
|
||||
let decoded: EpiphanySignedRuntimeHealthDocument =
|
||||
rmp_serde::from_slice(&document.payload).unwrap();
|
||||
assert_eq!(decoded, signed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -278,7 +492,7 @@ mod tests {
|
|||
let reserved = UdpSocket::bind("127.0.0.1:0").unwrap();
|
||||
let endpoint = reserved.local_addr().unwrap();
|
||||
drop(reserved);
|
||||
let health = derive_epiphany_aggregate_runtime_health(input(2)).unwrap();
|
||||
let health = signed_health(derive_epiphany_aggregate_runtime_health(input(2)).unwrap());
|
||||
assert!(
|
||||
publish_idunn_daemon_health_rudp(endpoint, "epiphany-daemon-supervisor", &health,)
|
||||
.is_err()
|
||||
|
|
|
|||
|
|
@ -50,12 +50,6 @@ mod workspace_coverage_projector_service;
|
|||
mod workspace_retrieval_coverage;
|
||||
|
||||
pub use agent_launch::EPIPHANY_IMAGINATION_OWNER_ROLE;
|
||||
pub use idunn_runtime_health::{
|
||||
CULTNET_RUDP_PROTOCOL_ID, EPIPHANY_IDUNN_RUNTIME_HEALTH_CONTRACT,
|
||||
EpiphanyAggregateRuntimeHealthInput, IDUNN_DAEMON_HEALTH_SCHEMA_VERSION,
|
||||
IDUNN_DAEMON_HEALTH_TYPE, IdunnDaemonHealthDocument,
|
||||
derive_epiphany_aggregate_runtime_health, publish_idunn_daemon_health_rudp,
|
||||
};
|
||||
pub use agent_launch::EPIPHANY_IMAGINATION_ROLE_BINDING_ID;
|
||||
pub use agent_launch::EPIPHANY_MIND_OWNER_ROLE;
|
||||
pub use agent_launch::EPIPHANY_MIND_ROLE_BINDING_ID;
|
||||
|
|
@ -671,11 +665,20 @@ pub use heartbeat_state::write_heartbeat_cognition_entry;
|
|||
pub use heartbeat_state::write_heartbeat_stale_turn_repair_receipt;
|
||||
pub use heartbeat_state::write_heartbeat_state_entry;
|
||||
pub use host_identity::{
|
||||
HOST_IDENTITY_KEY, HOST_IDENTITY_SCHEMA_VERSION, HOST_IDENTITY_TYPE, HostIdentitySignature,
|
||||
HostIdentitySigner, HostIncarnationIdentityEntry, LINUX_HOST_IDENTITY_ASSURANCE,
|
||||
HOST_IDENTITY_KEY, HOST_IDENTITY_SCHEMA_VERSION, HOST_IDENTITY_TRUST_ANCHOR_KEY,
|
||||
HOST_IDENTITY_TRUST_ANCHOR_TYPE, HOST_IDENTITY_TYPE, HostIdentitySignature, HostIdentitySigner,
|
||||
HostIdentityTrustAnchorEntry, HostIncarnationIdentityEntry, LINUX_HOST_IDENTITY_ASSURANCE,
|
||||
WINDOWS_HOST_IDENTITY_ASSURANCE, default_host_identity_store_path,
|
||||
enroll_default_host_identity, enroll_host_identity_at, open_default_host_identity,
|
||||
open_host_identity_at, verify_host_identity_signature,
|
||||
enroll_default_host_identity, enroll_host_identity_at, export_host_identity_trust_anchor,
|
||||
open_default_host_identity, open_host_identity_at, verify_host_identity_signature,
|
||||
};
|
||||
pub use idunn_runtime_health::{
|
||||
CULTNET_RUDP_PROTOCOL_ID, EPIPHANY_IDUNN_RUNTIME_HEALTH_CONTRACT,
|
||||
EPIPHANY_SIGNED_RUNTIME_HEALTH_SCHEMA_VERSION, EPIPHANY_SIGNED_RUNTIME_HEALTH_TYPE,
|
||||
EpiphanyAggregateRuntimeHealthInput, EpiphanySignedRuntimeHealthDocument,
|
||||
IDUNN_DAEMON_HEALTH_SCHEMA_VERSION, IDUNN_DAEMON_HEALTH_TYPE, IdunnDaemonHealthDocument,
|
||||
derive_epiphany_aggregate_runtime_health, publish_idunn_daemon_health_rudp,
|
||||
sign_epiphany_runtime_health,
|
||||
};
|
||||
pub use memory_graph::EpiphanyMemoryEdgeKind;
|
||||
pub use memory_graph::EpiphanyMemoryEmbeddingManifest;
|
||||
|
|
@ -1215,15 +1218,18 @@ pub use workspace_coverage_process_documents::{
|
|||
};
|
||||
pub use workspace_coverage_projection_progress::{
|
||||
WORKSPACE_COVERAGE_PROJECTION_PROGRESS_SCHEMA_VERSION,
|
||||
WORKSPACE_COVERAGE_PROJECTION_PROGRESS_TYPE, WorkspaceCoverageProjectionProgressEntry,
|
||||
WORKSPACE_COVERAGE_PROJECTION_PROGRESS_TYPE, WorkspaceCoverageAdvancingAuthority,
|
||||
WorkspaceCoverageProjectionProgressEntry, authenticate_current_workspace_coverage_advancement,
|
||||
authenticate_workspace_coverage_projection_progress,
|
||||
load_latest_workspace_coverage_projection_progress,
|
||||
load_workspace_coverage_projection_progress,
|
||||
};
|
||||
pub use workspace_coverage_projector::{
|
||||
WORKSPACE_COVERAGE_MAXIMUM_FILE_BYTES, WorkspaceCoverageRecoveryOutcome,
|
||||
WorkspaceCoverageRecoveryTarget, authenticate_workspace_coverage_recovery_receipt,
|
||||
current_workspace_coverage_recovery_target, recover_workspace_coverage_projection,
|
||||
WorkspaceCoverageRecoveryTarget, WorkspaceCoverageTerminalAuthority,
|
||||
authenticate_current_workspace_coverage_terminal_authority,
|
||||
authenticate_workspace_coverage_recovery_receipt, current_workspace_coverage_recovery_target,
|
||||
recover_workspace_coverage_projection,
|
||||
};
|
||||
pub use workspace_coverage_projector_service::WorkspaceCoverageProjectorConfig;
|
||||
pub use workspace_coverage_projector_service::WorkspaceCoverageProjectorPulseStatus;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,32 @@
|
|||
use crate::workspace_coverage_process_documents::authenticate_workspace_coverage_managed_process_launch_with_envelope_digest;
|
||||
use crate::workspace_coverage_projection_batch_checkpoint::{
|
||||
load_authenticated_current_checkpoint, WorkspaceCoverageProjectionBatchCheckpointAdmission,
|
||||
WorkspaceCoverageProjectionBatchCheckpointAdmission, load_authenticated_current_checkpoint,
|
||||
};
|
||||
use crate::workspace_coverage_projector::{
|
||||
ATTEMPT_TYPE, CHUNKER_ID, CLAIM_KEY, CLAIM_TYPE, OBLIGATION_TYPE, PLAN_TYPE, PROJECTION_SCHEMA,
|
||||
WorkspaceCoverageProjectionAttempt, WorkspaceCoverageProjectionClaim,
|
||||
exact_obligation_body_authority, validate_claim_attempt_link, validate_projection_attempt,
|
||||
validate_projection_claim, WorkspaceCoverageProjectionAttempt,
|
||||
WorkspaceCoverageProjectionClaim, ATTEMPT_TYPE, CLAIM_KEY, CLAIM_TYPE, OBLIGATION_TYPE,
|
||||
PLAN_TYPE,
|
||||
validate_projection_claim,
|
||||
};
|
||||
use crate::workspace_retrieval_coverage::{
|
||||
validate_workspace_coverage_projection_plan, WorkspaceCoverageObligation,
|
||||
WorkspaceCoverageProjectionPlan,
|
||||
WorkspaceCoverageObligation, WorkspaceCoverageProjectionPlan,
|
||||
validate_workspace_coverage_projection_plan,
|
||||
};
|
||||
use crate::{
|
||||
HostIncarnationIdentityEntry, WORKSPACE_COVERAGE_MAXIMUM_FILE_BYTES,
|
||||
WorkspaceCoverageManagedProcessLaunchEntry, WorkspaceCoveragePolicy,
|
||||
authenticate_workspace_coverage_managed_process_launch,
|
||||
load_workspace_coverage_managed_process_launch_with_digest, open_epiphany_cultmesh_node,
|
||||
HostIncarnationIdentityEntry, WorkspaceCoverageManagedProcessLaunchEntry,
|
||||
runtime_repository_body_store_binding,
|
||||
};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use chrono::DateTime;
|
||||
use cultcache_rs::{
|
||||
CacheBackingStore, CultCacheEnvelope, DatabaseEntry, SingleFileMessagePackBackingStore,
|
||||
};
|
||||
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
|
||||
use serde::Serialize;
|
||||
use sha2::Digest;
|
||||
use std::path::Path;
|
||||
|
||||
pub const WORKSPACE_COVERAGE_PROJECTION_PROGRESS_TYPE: &str =
|
||||
|
|
@ -103,6 +106,19 @@ pub struct WorkspaceCoverageProjectionProgressEntry {
|
|||
pub signature_algorithm: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct WorkspaceCoverageAdvancingAuthority {
|
||||
pub progress_id: String,
|
||||
pub checkpoint_id: String,
|
||||
pub checkpoint_binding_sha256: String,
|
||||
pub claim_id: String,
|
||||
pub claim_epoch: u64,
|
||||
pub plan_id: String,
|
||||
pub completed_units: u64,
|
||||
pub total_units: u64,
|
||||
pub last_advanced_at_utc: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ProgressStatement<'a> {
|
||||
|
|
@ -321,7 +337,9 @@ pub(crate) fn publish_workspace_coverage_progress_for_checkpoint(
|
|||
if checkpoint_progress_alignment(&prior, admission)? == CheckpointProgressAlignment::Current {
|
||||
return Ok(prior);
|
||||
}
|
||||
if prior.sequence != checkpoint.sequence || checkpoint.cumulative_point_count <= prior.completed_units {
|
||||
if prior.sequence != checkpoint.sequence
|
||||
|| checkpoint.cumulative_point_count <= prior.completed_units
|
||||
{
|
||||
bail!("checkpoint progress does not durably advance completed units");
|
||||
}
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
|
|
@ -418,7 +436,8 @@ pub(crate) fn reconcile_workspace_coverage_checkpoint_progress(
|
|||
trusted_host,
|
||||
claim_id,
|
||||
claim_epoch,
|
||||
)? else {
|
||||
)?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
if admission.checkpoint.runtime_id != runtime_id {
|
||||
|
|
@ -532,6 +551,130 @@ pub fn authenticate_workspace_coverage_projection_progress(
|
|||
Ok(entry)
|
||||
}
|
||||
|
||||
/// Authenticates durable, current, nonterminal workspace advancement.
|
||||
/// Provider telemetry timeouts and heartbeat timestamps are intentionally not
|
||||
/// consulted here. The supervisor separately joins this evidence to the exact
|
||||
/// live managed process and applies its own no-advance lease.
|
||||
pub fn authenticate_current_workspace_coverage_advancement(
|
||||
runtime_store: impl AsRef<Path>,
|
||||
local_verse_store: impl AsRef<Path>,
|
||||
runtime_id: &str,
|
||||
launch_id: &str,
|
||||
host: &HostIncarnationIdentityEntry,
|
||||
observed_at: chrono::DateTime<chrono::Utc>,
|
||||
no_advance_lease: chrono::Duration,
|
||||
) -> Result<Option<WorkspaceCoverageAdvancingAuthority>> {
|
||||
if no_advance_lease <= chrono::Duration::zero() {
|
||||
bail!("workspace coverage no-advance lease must be positive");
|
||||
}
|
||||
let route = runtime_repository_body_store_binding(runtime_store.as_ref())?
|
||||
.ok_or_else(|| anyhow!("runtime has no repository Body-store binding"))?;
|
||||
let opening = SingleFileMessagePackBackingStore::new(&route.body_store_path).pull_all()?;
|
||||
let claim: WorkspaceCoverageProjectionClaim =
|
||||
decode(find_authority(&opening, CLAIM_TYPE, CLAIM_KEY)?)?;
|
||||
validate_projection_claim(&claim)?;
|
||||
let attempt: WorkspaceCoverageProjectionAttempt =
|
||||
decode(find_authority(&opening, ATTEMPT_TYPE, &claim.attempt_id)?)?;
|
||||
validate_projection_attempt(&attempt)?;
|
||||
validate_claim_attempt_link(&claim, &attempt)?;
|
||||
if claim.status != "running" || attempt.status != "running" {
|
||||
return Ok(None);
|
||||
}
|
||||
let obligation: WorkspaceCoverageObligation = decode(find_authority(
|
||||
&opening,
|
||||
OBLIGATION_TYPE,
|
||||
&claim.obligation_id,
|
||||
)?)?;
|
||||
let plan: WorkspaceCoverageProjectionPlan =
|
||||
decode(find_authority(&opening, PLAN_TYPE, &claim.plan_id)?)?;
|
||||
validate_workspace_coverage_projection_plan(&obligation, &plan)?;
|
||||
exact_obligation_body_authority(&opening, &obligation)?;
|
||||
let policy =
|
||||
WorkspaceCoveragePolicy::bounded_regular_files_v0(WORKSPACE_COVERAGE_MAXIMUM_FILE_BYTES)?;
|
||||
let policy_sha256 = format!(
|
||||
"{:x}",
|
||||
sha2::Sha256::digest(rmp_serde::to_vec_named(&policy).map_err(|error| anyhow!(error))?)
|
||||
);
|
||||
if obligation.policy_id != policy.policy_id
|
||||
|| obligation.policy_sha256 != policy_sha256
|
||||
|| plan.projection_schema_version != PROJECTION_SCHEMA
|
||||
|| plan.chunker_id != CHUNKER_ID
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let Some(latest) = load_latest_workspace_coverage_projection_progress(
|
||||
local_verse_store.as_ref(),
|
||||
runtime_id,
|
||||
launch_id,
|
||||
&claim.claim_id,
|
||||
)?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let progress = authenticate_workspace_coverage_projection_progress(
|
||||
local_verse_store.as_ref(),
|
||||
runtime_id,
|
||||
&latest.progress_id,
|
||||
host,
|
||||
)?;
|
||||
if progress.status != "warming"
|
||||
|| progress.completed_units == 0
|
||||
|| progress.launch_id != launch_id
|
||||
|| progress.claim_id != claim.claim_id
|
||||
|| progress.claim_epoch != claim.claim_epoch
|
||||
|| progress.attempt_id != attempt.attempt_id
|
||||
|| progress.plan_id != plan.plan_id
|
||||
|| progress.body_observation_id != obligation.body_observation_id
|
||||
|| progress.body_generation != obligation.body_generation
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
let Some(checkpoint) = load_authenticated_current_checkpoint(
|
||||
&route.body_store_path,
|
||||
local_verse_store.as_ref(),
|
||||
host,
|
||||
&claim.claim_id,
|
||||
claim.claim_epoch,
|
||||
)?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
if checkpoint_progress_alignment(&progress, &checkpoint)?
|
||||
!= CheckpointProgressAlignment::Current
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
if !progress_is_within_no_advance_lease(
|
||||
&progress.last_advanced_at_utc,
|
||||
observed_at,
|
||||
no_advance_lease,
|
||||
)? {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(Some(WorkspaceCoverageAdvancingAuthority {
|
||||
progress_id: progress.progress_id,
|
||||
checkpoint_id: checkpoint.checkpoint.checkpoint_id,
|
||||
checkpoint_binding_sha256: checkpoint.checkpoint_envelope_digest,
|
||||
claim_id: claim.claim_id,
|
||||
claim_epoch: claim.claim_epoch,
|
||||
plan_id: plan.plan_id,
|
||||
completed_units: progress.completed_units,
|
||||
total_units: progress.total_units,
|
||||
last_advanced_at_utc: progress.last_advanced_at_utc,
|
||||
}))
|
||||
}
|
||||
|
||||
fn progress_is_within_no_advance_lease(
|
||||
last_advanced_at_utc: &str,
|
||||
observed_at: chrono::DateTime<chrono::Utc>,
|
||||
no_advance_lease: chrono::Duration,
|
||||
) -> Result<bool> {
|
||||
let advanced = DateTime::parse_from_rfc3339(last_advanced_at_utc)?.with_timezone(&chrono::Utc);
|
||||
let age = observed_at.signed_duration_since(advanced);
|
||||
Ok(age >= chrono::Duration::zero() && age <= no_advance_lease)
|
||||
}
|
||||
|
||||
fn authenticate_against_launch(
|
||||
entry: &WorkspaceCoverageProjectionProgressEntry,
|
||||
launch: &WorkspaceCoverageManagedProcessLaunchEntry,
|
||||
|
|
@ -781,6 +924,9 @@ fn find_authority<'a>(
|
|||
.find(|entry| entry.r#type == ty && entry.key == key)
|
||||
.ok_or_else(|| anyhow!("workspace coverage progress authority {ty}/{key} is absent"))
|
||||
}
|
||||
fn decode<T: serde::de::DeserializeOwned>(entry: &CultCacheEnvelope) -> Result<T> {
|
||||
rmp_serde::from_slice(&entry.payload).context("decoding workspace coverage progress authority")
|
||||
}
|
||||
fn progress_key(id: &str) -> String {
|
||||
format!("epiphany-local/workspace-coverage/projection-progress/event/{id}")
|
||||
}
|
||||
|
|
@ -888,7 +1034,14 @@ mod tests {
|
|||
let k = SigningKey::from_bytes(&[7; 32]);
|
||||
let genesis = entry(&k);
|
||||
assert_eq!(
|
||||
checkpoint_progress_binding_alignment(&genesis, 1, "checkpoint", &format!("sha256-{}", "b".repeat(64)), 1).unwrap(),
|
||||
checkpoint_progress_binding_alignment(
|
||||
&genesis,
|
||||
1,
|
||||
"checkpoint",
|
||||
&format!("sha256-{}", "b".repeat(64)),
|
||||
1
|
||||
)
|
||||
.unwrap(),
|
||||
CheckpointProgressAlignment::NeedsReconciliation
|
||||
);
|
||||
let current = advance(&genesis, &k);
|
||||
|
|
@ -899,14 +1052,26 @@ mod tests {
|
|||
CheckpointProgressAlignment::Current
|
||||
);
|
||||
assert_eq!(
|
||||
checkpoint_progress_binding_alignment(¤t, 2, "next-checkpoint", &digest, 2).unwrap(),
|
||||
checkpoint_progress_binding_alignment(¤t, 2, "next-checkpoint", &digest, 2)
|
||||
.unwrap(),
|
||||
CheckpointProgressAlignment::NeedsReconciliation
|
||||
);
|
||||
let mut skipped = current.clone();
|
||||
skipped.sequence = 3;
|
||||
assert!(checkpoint_progress_binding_alignment(&skipped, 1, &id, &digest, 1).is_err());
|
||||
assert!(checkpoint_progress_binding_alignment(¤t, 1, "stale-head", &digest, 1).is_err());
|
||||
assert!(checkpoint_progress_binding_alignment(¤t, 1, &id, &format!("sha256-{}", "c".repeat(64)), 1).is_err());
|
||||
assert!(
|
||||
checkpoint_progress_binding_alignment(¤t, 1, "stale-head", &digest, 1).is_err()
|
||||
);
|
||||
assert!(
|
||||
checkpoint_progress_binding_alignment(
|
||||
¤t,
|
||||
1,
|
||||
&id,
|
||||
&format!("sha256-{}", "c".repeat(64)),
|
||||
1
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn provider_timeout_observation_is_not_progress_authority() {
|
||||
|
|
@ -1031,4 +1196,15 @@ mod tests {
|
|||
next.provider_signature[0] ^= 1;
|
||||
assert!(validate_transition(&prior, &next).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_advance_lease_uses_durable_advancement_time_only() {
|
||||
let now = chrono::DateTime::parse_from_rfc3339("2026-07-16T10:05:00Z")
|
||||
.unwrap()
|
||||
.with_timezone(&chrono::Utc);
|
||||
let lease = chrono::Duration::seconds(300);
|
||||
assert!(progress_is_within_no_advance_lease("2026-07-16T10:00:00Z", now, lease).unwrap());
|
||||
assert!(!progress_is_within_no_advance_lease("2026-07-16T09:59:59Z", now, lease).unwrap());
|
||||
assert!(!progress_is_within_no_advance_lease("2026-07-16T10:05:01Z", now, lease).unwrap());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@ const CLAIM_SCHEMA_V3: &str = "gamecult.epiphany.workspace_coverage_projection_c
|
|||
const ATTEMPT_SCHEMA_V3: &str = "gamecult.epiphany.workspace_coverage_projection_attempt.v3";
|
||||
const RECOVERY_TYPE: &str = "gamecult.epiphany.workspace_coverage_recovery_receipt";
|
||||
const RECOVERY_SCHEMA: &str = "gamecult.epiphany.workspace_coverage_recovery_receipt.v0";
|
||||
const PROJECTION_SCHEMA: &str = "gamecult.epiphany.workspace_bytes_projection.v0";
|
||||
const CHUNKER_ID: &str = "utf8_lines_96_overlap_8_v0";
|
||||
pub(crate) const PROJECTION_SCHEMA: &str = "gamecult.epiphany.workspace_bytes_projection.v0";
|
||||
pub(crate) const CHUNKER_ID: &str = "utf8_lines_96_overlap_8_v0";
|
||||
pub const WORKSPACE_COVERAGE_MAXIMUM_FILE_BYTES: u64 = 4 * 1024 * 1024;
|
||||
const CHUNK_LINES: usize = 96;
|
||||
const CHUNK_OVERLAP_LINES: usize = 8;
|
||||
|
|
@ -688,6 +688,97 @@ pub(crate) enum WorkspaceCoverageCurrentState {
|
|||
NeedsPreparation,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct WorkspaceCoverageTerminalAuthority {
|
||||
pub body_observation_id: String,
|
||||
pub body_generation: u64,
|
||||
pub plan_id: String,
|
||||
pub claim_id: String,
|
||||
pub claim_epoch: u64,
|
||||
pub managed_process_launch_id: String,
|
||||
pub receipt_id: String,
|
||||
}
|
||||
|
||||
/// Reads the provider-owned terminal authority used by deployment health.
|
||||
/// This deliberately does not re-read Qdrant: terminal receipt admission
|
||||
/// already performed the whole-collection readback. Mind readiness owns the
|
||||
/// stronger live query-evidence join.
|
||||
pub fn authenticate_current_workspace_coverage_terminal_authority(
|
||||
runtime_store: &Path,
|
||||
) -> Result<Option<WorkspaceCoverageTerminalAuthority>> {
|
||||
let basis = crate::load_current_runtime_repository_body_basis(runtime_store)?;
|
||||
let route = runtime_repository_body_store_binding(runtime_store)?
|
||||
.ok_or_else(|| anyhow!("runtime has no repository Body-store binding"))?;
|
||||
let opening = SingleFileMessagePackBackingStore::new(&route.body_store_path).pull_all()?;
|
||||
exact_body_authority(&opening, &basis)?;
|
||||
let Some(head_env) = find(&opening, HEAD_TYPE, HEAD_KEY) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let head: WorkspaceCoverageHead = decode(head_env)?;
|
||||
let obligation: WorkspaceCoverageObligation = decode(
|
||||
find(&opening, OBLIGATION_TYPE, &head.obligation_id)
|
||||
.ok_or_else(|| anyhow!("current workspace coverage head names a missing obligation"))?,
|
||||
)?;
|
||||
let plan: WorkspaceCoverageProjectionPlan = decode(
|
||||
find(&opening, PLAN_TYPE, &head.plan_id)
|
||||
.ok_or_else(|| anyhow!("current workspace coverage head names a missing plan"))?,
|
||||
)?;
|
||||
let receipt: WorkspaceCoverageReceipt = decode(
|
||||
find(&opening, RECEIPT_TYPE, &head.receipt_id)
|
||||
.ok_or_else(|| anyhow!("current workspace coverage head names a missing receipt"))?,
|
||||
)?;
|
||||
let claim: WorkspaceCoverageProjectionClaim = decode(
|
||||
find(&opening, CLAIM_TYPE, CLAIM_KEY)
|
||||
.ok_or_else(|| anyhow!("current workspace coverage lost its projection claim"))?,
|
||||
)?;
|
||||
let attempt: WorkspaceCoverageProjectionAttempt = decode(
|
||||
find(&opening, ATTEMPT_TYPE, &claim.attempt_id)
|
||||
.ok_or_else(|| anyhow!("current workspace coverage lost its projection attempt"))?,
|
||||
)?;
|
||||
validate_workspace_coverage_head(&obligation, &plan, &receipt, &head)?;
|
||||
validate_claim_attempt_link(&claim, &attempt)?;
|
||||
let policy =
|
||||
WorkspaceCoveragePolicy::bounded_regular_files_v0(WORKSPACE_COVERAGE_MAXIMUM_FILE_BYTES)?;
|
||||
let policy_sha256 = format!(
|
||||
"{:x}",
|
||||
Sha256::digest(rmp_serde::to_vec_named(&policy).map_err(|error| anyhow!(error))?)
|
||||
);
|
||||
let exact = head.workspace_id == basis.workspace_id
|
||||
&& head.body_observation_id == basis.observation_id
|
||||
&& head.body_generation == basis.generation
|
||||
&& head.manifest_root_sha256 == basis.manifest_root_sha256
|
||||
&& obligation.runtime_id == basis.runtime_id
|
||||
&& obligation.workspace_id == basis.workspace_id
|
||||
&& obligation.swarm_id == basis.swarm_id
|
||||
&& obligation.body_binding_sha256 == basis.body_binding_sha256
|
||||
&& obligation.body_observation_id == basis.observation_id
|
||||
&& obligation.body_generation == basis.generation
|
||||
&& obligation.manifest_root_sha256 == basis.manifest_root_sha256
|
||||
&& obligation.policy_id == policy.policy_id
|
||||
&& obligation.policy_sha256 == policy_sha256
|
||||
&& plan.projection_schema_version == PROJECTION_SCHEMA
|
||||
&& plan.chunker_id == CHUNKER_ID
|
||||
&& claim.status == "succeeded"
|
||||
&& attempt.status == "succeeded"
|
||||
&& claim.claim_id == receipt.claim_id
|
||||
&& claim.claim_epoch == receipt.claim_epoch
|
||||
&& claim.plan_id == plan.plan_id
|
||||
&& claim.obligation_id == obligation.obligation_id;
|
||||
if !exact {
|
||||
return Ok(None);
|
||||
}
|
||||
exact_obligation_body_authority(&opening, &obligation)?;
|
||||
Ok(Some(WorkspaceCoverageTerminalAuthority {
|
||||
body_observation_id: basis.observation_id,
|
||||
body_generation: basis.generation,
|
||||
plan_id: plan.plan_id,
|
||||
claim_id: claim.claim_id,
|
||||
claim_epoch: claim.claim_epoch,
|
||||
managed_process_launch_id: claim.managed_process_launch_id,
|
||||
receipt_id: receipt.receipt_id,
|
||||
}))
|
||||
}
|
||||
|
||||
fn collection_compatibility(plan: &WorkspaceCoverageProjectionPlan) -> CollectionCompatibility {
|
||||
CollectionCompatibility {
|
||||
managed_by: "epiphany-workspace-coverage-projector".into(),
|
||||
|
|
@ -2035,7 +2126,13 @@ pub(crate) fn observe_final_projection_against_authenticated_checkpoint_chain(
|
|||
&expected_points,
|
||||
&expected_vectors,
|
||||
)?;
|
||||
observe_sealed_bindings(obligation, plan, collection_name, ordered, Some(&expected_vectors))
|
||||
observe_sealed_bindings(
|
||||
obligation,
|
||||
plan,
|
||||
collection_name,
|
||||
ordered,
|
||||
Some(&expected_vectors),
|
||||
)
|
||||
}
|
||||
|
||||
fn authenticate_final_observation_against_checkpoint_bindings(
|
||||
|
|
@ -2066,7 +2163,9 @@ fn authenticate_final_observation_against_checkpoint_bindings(
|
|||
.vector
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("final Qdrant observation omitted checkpointed vector"))?;
|
||||
if digest(payload)? != point.payload_sha256 || digest(observed_vector)? != vector.vector_sha256 {
|
||||
if digest(payload)? != point.payload_sha256
|
||||
|| digest(observed_vector)? != vector.vector_sha256
|
||||
{
|
||||
bail!("final Qdrant payload/vector disagrees with authenticated checkpoint evidence");
|
||||
}
|
||||
ordered.push(observed);
|
||||
|
|
@ -2656,36 +2755,44 @@ mod tests {
|
|||
)?;
|
||||
let mut mutated = observed.clone();
|
||||
mutated[0].vector.as_mut().unwrap()[0] += 1.0;
|
||||
assert!(authenticate_final_observation_against_checkpoint_bindings(
|
||||
mutated,
|
||||
&point_bindings,
|
||||
&vector_bindings,
|
||||
)
|
||||
.is_err());
|
||||
assert!(authenticate_final_observation_against_checkpoint_bindings(
|
||||
Vec::new(),
|
||||
&point_bindings,
|
||||
&vector_bindings,
|
||||
)
|
||||
.is_err());
|
||||
assert!(
|
||||
authenticate_final_observation_against_checkpoint_bindings(
|
||||
mutated,
|
||||
&point_bindings,
|
||||
&vector_bindings,
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
authenticate_final_observation_against_checkpoint_bindings(
|
||||
Vec::new(),
|
||||
&point_bindings,
|
||||
&vector_bindings,
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
let mut duplicated = observed.clone();
|
||||
duplicated.push(observed[0].clone());
|
||||
assert!(authenticate_final_observation_against_checkpoint_bindings(
|
||||
duplicated,
|
||||
&point_bindings,
|
||||
&vector_bindings,
|
||||
)
|
||||
.is_err());
|
||||
assert!(
|
||||
authenticate_final_observation_against_checkpoint_bindings(
|
||||
duplicated,
|
||||
&point_bindings,
|
||||
&vector_bindings,
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
let mut substituted_duplicate = observed[0].clone();
|
||||
substituted_duplicate.vector.as_mut().unwrap()[0] += 1.0;
|
||||
let mut duplicated = observed.clone();
|
||||
duplicated.push(substituted_duplicate);
|
||||
assert!(authenticate_final_observation_against_checkpoint_bindings(
|
||||
duplicated,
|
||||
&point_bindings,
|
||||
&vector_bindings,
|
||||
)
|
||||
.is_err());
|
||||
assert!(
|
||||
authenticate_final_observation_against_checkpoint_bindings(
|
||||
duplicated,
|
||||
&point_bindings,
|
||||
&vector_bindings,
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
drop(repo);
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -2973,6 +3080,10 @@ mod tests {
|
|||
let inputs = projection_inputs(&acquisition, repo.path())?;
|
||||
let mut projection = FakeProjectionPort::new(Hostility::Honest);
|
||||
execute_preembedded_workspace_coverage_projection(&acquisition, inputs, &mut projection)?;
|
||||
let terminal = authenticate_current_workspace_coverage_terminal_authority(&runtime)?
|
||||
.expect("succeeded current receipt/head must own terminal health");
|
||||
assert_eq!(terminal.claim_id, acquisition.claim.claim_id);
|
||||
assert_eq!(terminal.plan_id, acquisition.plan.plan_id);
|
||||
assert!(matches!(
|
||||
classify_current_workspace_coverage(&runtime, &basis, "provider", "model", 3)?,
|
||||
WorkspaceCoverageCurrentState::Current(_)
|
||||
|
|
@ -3045,6 +3156,23 @@ mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_health_refuses_a_receipt_for_an_older_body() -> Result<()> {
|
||||
let (repo, _state, runtime, basis) = coverage_fixture()?;
|
||||
let (_, acquisition) = acquire_test(&runtime, &basis, "provider", "model", 3)?;
|
||||
let inputs = projection_inputs(&acquisition, repo.path())?;
|
||||
execute_preembedded_workspace_coverage_projection(
|
||||
&acquisition,
|
||||
inputs,
|
||||
&mut FakeProjectionPort::new(Hostility::Honest),
|
||||
)?;
|
||||
assert!(authenticate_current_workspace_coverage_terminal_authority(&runtime)?.is_some());
|
||||
std::fs::write(repo.path().join("source.rs"), "pub fn changed() {}\n")?;
|
||||
crate::observe_runtime_repository_body_basis(&runtime)?;
|
||||
assert!(authenticate_current_workspace_coverage_terminal_authority(&runtime)?.is_none());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_evidence_refuses_authority_advanced_during_qdrant_scroll() -> Result<()> {
|
||||
let (repo, _state, runtime, basis) = coverage_fixture()?;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,47 @@
|
|||
# Fresh Workspace Handoff
|
||||
|
||||
## Signed Yggdrasil deployment admission cut complete locally — 2026-07-17
|
||||
|
||||
The failed GPU pressure run was a pipeline-contract failure, not a CUDA
|
||||
failure. The GTX 1080 previously sustained near-full duty, but the monolithic
|
||||
workspace projection published no durable Qdrant progress before the shell
|
||||
deadline. The replacement executor now checkpoints bounded batches, resumes
|
||||
from authenticated checkpoints after restart, and exposes checkpoint-derived
|
||||
progress independently of its heartbeat. Aggregate health is `warming` only
|
||||
while exact managed-process lineage, fresh signed heartbeat, and authenticated
|
||||
checkpoint advancement remain inside the 300-second no-advance lease. Only the
|
||||
terminal current receipt/head can produce `active`; the deploy shell has no
|
||||
projection wall-clock verdict.
|
||||
|
||||
The cross-repo deployment authority cut is implemented and Soul passes it.
|
||||
Epiphany signs runtime health with exact release id, witness, source commit,
|
||||
deployment request id, process incarnation, and sequence. Idunn verifies the
|
||||
pinned public-only host anchor and persists generic health as observation only.
|
||||
Promotion reads `idunn.signed_health_admission.v1`, requires it to match the
|
||||
current health observation, expire within 180 seconds, and join the exact
|
||||
monotonic `idunn.current_deployment_request.v1` head plus its Bifrost-authorized
|
||||
`idunn.deployment_request.v2`. Health/admission and request/head transitions use
|
||||
cross-process CultCache compare-exchange over exact prior envelopes. CultCache
|
||||
now writes a synced unique temporary file and atomically replaces the live
|
||||
store without a delete gap. Concurrent N+1/N+2 admission proof finishes at
|
||||
N+2; equal-second request IDs no longer decide authority lexicographically.
|
||||
|
||||
First trust enrollment refuses any pre-existing app-owned private identity when
|
||||
no root anchor exists. One `enroll-trust-anchor` process creates the signer and
|
||||
exports its public anchor; root then pins only that public document as
|
||||
`root:idunn 0640`. Later deployments must byte-match it. The deploy actuator
|
||||
uses Idunn's typed verifier with the exact request/release tuple; journal prose,
|
||||
generic health, and `warming` cannot promote.
|
||||
|
||||
Local proof: Epiphany runtime-health 8, workspace-progress 13,
|
||||
workspace-projector 22, host-identity 3, supervisor 17; Odin core 15 and Idunn
|
||||
45 all pass. Gamecult shell syntax passes under Git Bash. Final hostile Soul
|
||||
audit reports no P0/P1. Next: commit/push all three repos, deploy updated Odin/
|
||||
Idunn to Yggdrasil first, then Bifrost-authorize and Idunn-deploy the exact
|
||||
Epiphany commit. Verify live signed warming, batchwise Qdrant growth, GPU duty,
|
||||
terminal exact-request active admission, and restart resume. No reboot is
|
||||
authorized.
|
||||
|
||||
## Live Body-grounded admission rite — 2026-07-16
|
||||
|
||||
Thread `readiness-grounding-20260716` now has a typed, immutable user objective
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -3,7 +3,7 @@ experiment:
|
|||
repository_body_observer: "Native Rust explicitly binds caller workspace ID to an existing validated runtime/swarm/source identity plus canonical Git root/object format/scope/policy. Bind also installs one immutable runtime-side RuntimeRepositoryBodyStoreBinding naming the canonical external Body-store locator and exact Body-binding hash; every read revalidates runtime/swarm/workspace/path/body identity and refuses a second store. Two equal sanitized temporary-index scans enumerate ignore-aware UTF-8 paths, Git modes, and nonrecursive gitlinks with global excludes disabled; raw regular bytes/lengths and non-followed symlink-target bytes are SHA-256 hashed. The domain-separated ordered raw manifest root, not the clean-filtered auxiliary tree OID, is authoritative Body identity. Every Git subprocess strips ambient repository/worktree/object/ref/index/namespace/replacement/graft/shallow and config-injection authority. Observe cannot mint identity. CultCache MessagePack atomically commits immutable manifest+observation and a manifest-root head by exact CAS. Canonical store-path checks precede writes; corrupt HEAD, sparse checkout, unsafe/unrepresentable paths, racing reads, and repo/runtime substitution fail closed. Only accepted stable observations persist; errors advance no head. Unchanged raw manifest preserves generation; status is read-pure. The observer owns bounded current-state identity, makes no historical continuity claim, and has no Ready field. The runtime route is locator authority only, not proof of model grounding."
|
||||
modeling_body_grounding: "Every coordinator-owned Modeling launch observes and carries one typed RepositoryBodyObservationBasis before worker thought. The immutable launch binds runtime/swarm/workspace/scope, Body-binding hash, observation generation/id/root, and scan interval. Modeling output contract v3 requires the exact worker-authored echo; ingress reloads the immutable launch and rejects missing/swapped basis, while non-Modeling cargo is forbidden. Mind admission review v1 and receipt v5 preserve that basis; admission validates exact launch/result/review equality plus the immutable historical Body artifacts, then copies it into the CAS receipt. Admission never samples current Body as a substitute. Direct Mind plan adoption and legacy migration remain explicitly ungrounded."
|
||||
workspace_retrieval_coverage: "The production-unwired retrieval.rs oracle is deleted. Legacy EpiphanyRetrievalState is presentation-only: clean Ready projects Missing and its indexing job is unavailable/unowned. Native coverage binds an authenticated Repository Body manifest, exhaustive versioned inclusion policy, deterministic chunks/point payloads, immutable Ollama tag+artifact digest identity, vector dimensions, exact Qdrant point/payload/vector observations, and one terminal-CAS receipt/head. A read-only live evidence reader now re-scrolls the exact compatible collection, recomputes payload/vector bindings, and byte-revalidates the entire authority chain after the external read. Coverage success proves only this exact Body projection; Mind alone may join it with the other repository authorities."
|
||||
workspace_coverage_projector: "The dedicated workspace-coverage projector is the only coverage writer. Its reserved supervisor/CultMesh policy fixes the packaged sibling binary and exact runtime/Verse/Qdrant/Ollama args; generic policy, arbitrary command, workspace, Body-store, collection, and dimension inputs are forbidden. The binary authenticates current policy-bound launch, exact process identity, runtime, host, and provider key before installing narrow execution authority. One persisted Body basis plus one authenticated read session prepares verified UTF-8 chunks; an exact Current fast path reads no worktree files. Claims/attempts bind exact managed-process launch and executor incarnation. Execution follows sealed plan order in batches no larger than 128: embed, waited Qdrant upsert, exact ID payload/vector readback, provider-signed Body checkpoint CAS, then exact checkpoint-derived Verse progress. Restart reconstructs the full authenticated genesis-to-head checkpoint chain against current Body/claim/attempt/plan/launch authority, re-observes every checkpoint batch live, reconciles missing progress idempotently, and resumes at the next sealed ordinal. Terminal success reconstructs independent expected payload/vector bindings from that full chain, requires complete nonoverlapping plan coverage, whole-scrolls Qdrant, rejects cycles/gaps/duplicates/missing/extra/substituted/nonfinite/wrong-dimension points, and only then terminal-CASes receipt/head. Heartbeat is a separate 10-second signed nerve; loss of heartbeat publication terminates the provider process. Heartbeat proves liveness, checkpoint-derived progress proves advancing work, and receipt/head alone prove Current. Body history owns bounded retirement of terminal non-current collections. Idunn recovery remains specialized exact process authority with predecessor termination, replacement launch/heartbeat, new epoch-fenced collection, and immutable recovery receipt. Idunn still must derive warming from the authenticated heartbeat+provider-progress join with a no-advance deadline and must never use a total projection wall clock as readiness. Query admission remains absent."
|
||||
workspace_coverage_projector: "The dedicated workspace-coverage projector is the only coverage writer. Its reserved supervisor/CultMesh policy fixes the packaged sibling binary and exact runtime/Verse/Qdrant/Ollama args; generic policy, arbitrary command, workspace, Body-store, collection, and dimension inputs are forbidden. The binary authenticates current policy-bound launch, exact process identity, runtime, host, and provider key before installing narrow execution authority. One persisted Body basis plus one authenticated read session prepares verified UTF-8 chunks; an exact Current fast path reads no worktree files. Claims/attempts bind exact managed-process launch and executor incarnation. Execution follows sealed plan order in batches no larger than 128: embed, waited Qdrant upsert, exact ID payload/vector readback, provider-signed Body checkpoint CAS, then exact checkpoint-derived Verse progress. Restart reconstructs the full authenticated genesis-to-head checkpoint chain against current Body/claim/attempt/plan/launch authority, re-observes every checkpoint batch live, reconciles missing progress idempotently, and resumes at the next sealed ordinal. Terminal success reconstructs independent expected payload/vector bindings from that full chain, requires complete nonoverlapping plan coverage, whole-scrolls Qdrant, rejects cycles/gaps/duplicates/missing/extra/substituted/nonfinite/wrong-dimension points, and only then terminal-CASes receipt/head. Heartbeat is a separate 10-second signed nerve; loss of heartbeat publication terminates the provider process. Heartbeat proves liveness, checkpoint-derived progress proves advancing work, and receipt/head alone prove Current. Body history owns bounded retirement of terminal non-current collections. Idunn recovery remains specialized exact process authority with predecessor termination, replacement launch/heartbeat, new epoch-fenced collection, and immutable recovery receipt. Aggregate runtime health is active only for exact terminal current authority; warming requires current managed process lineage plus fresh signed heartbeat and authenticated checkpoint advancement inside a 300-second no-advance lease. Idunn promotion consumes only a fresh signed admission bound to the exact monotonic current deployment-request head and release tuple; generic health is observation-only and no total projection wall clock owns readiness. Query admission remains absent."
|
||||
workspace_coverage_checkpoint: "The durable batch checkpoint belongs in the repository Body CultCache beside obligation/plan/claim/attempt authority; local Verse progress only references its exact CultCache envelope digest. The projector embeds one canonical contiguous plan batch, performs one waited Qdrant upsert, reads exactly those IDs back with payloads and vectors, compares exact payload and submitted-vector hashes, then provider-signs and Body-CAS-admits a checkpoint containing the exact ordered point/payload/vector bindings. A claim-scoped checkpoint head chains exact sequence, predecessor id+envelope digest, plan ordinals, cumulative/total count, collection, Body/plan/model/claim/attempt/launch authority, and provider incarnation. The CAS expects unchanged Body, obligation, plan, running claim, running attempt, and prior checkpoint head. Batch checkpoints never replace terminal whole-scroll verification or receipt/head. Same-claim retry resumes only from an authenticated contiguous chain re-observed live in Qdrant; an acknowledged write lacking a checkpoint is retried idempotently. Recovery always uses a new epoch-fenced collection: after exact predecessor termination and recovery authority, the successor may re-observe and copy authenticated predecessor batches into its new collection and issue new-claim checkpoints citing their source checkpoint envelopes. Missing or changed external evidence restarts from zero. No successor mutates or inherits the predecessor collection."
|
||||
repository_readiness_join: "Mind owns the implemented race-bounded RepositoryReadinessProjection operation. It freshly observes Body R1; derives canonical RepoModel, the unique current v5 Body-grounded admission, and the complete succeeded Modeling semantic chain from one raw opening CultCache snapshot; authenticates live semantic vectors and live workspace coverage bound to R1; observes Body R2 and requires the same content identity/root; repeats both live reads; derives the closing authority from one raw full snapshot; and atomically appends a historical observed-ready-at projection only if that complete snapshot is unchanged. Stored projections never grant current readiness. A→B→A is acceptable only as equal content-addressed endpoints. CultCache now fences full-snapshot cardinality with tuple (type,key) identity. Legacy retrieval Ready, timestamps, watcher silence, Git OIDs, collection existence/counts, Hands receipts, Eve/Gjallar, and prior readiness projections are forbidden substitutes."
|
||||
coordinator_grounding_loop: "Cold start is owned by one typed UserObjectiveIntake that atomically seeds thread state and refuses objective replacement; a missing checkpoint routes to Modeling rather than a fictional prepareCheckpoint actuator. Explicit local-Verse path authority flows into launch context without sibling-path inference. Eyes and Modeling receive bounded source tools. Accepted CRRC regather findings hand source gathering to Eyes instead of recursively relaunching the continuity judge. Runtime/display/coordinator reads share the canonical typed role and reorient result readers. Failed or completed-unreviewable role outputs may be explicitly superseded by a typed review receipt; JSON null is not a state patch. Provider output schema is formatting guidance only, while canonical typed ingress and Mind remain admission authority. Modeling's provider schema now describes tagged patch operations, closed node/edge/profile vocabularies, canonical node/edge fields, anchors, code refs, observations, evidence, frontier items, and Body-basis echo. Live dogfood has accepted one code-grounded Eyes result, but no current Body-grounded Modeling admission yet: the latest Modeling run used eleven source-tool rounds then failed at the provider request boundary before publishing typed output."
|
||||
|
|
@ -739,4 +739,4 @@ current_status:
|
|||
- Release `sha256-c94109f94ee42c1257089830f399cb87cd5ad672772c274194371995ce4df923` is the first deployed immutable sibling generation. Native packaging builds from detached exact source commit `dd80f4a51c18425a6665710698a357be61154abf`, initializes pinned submodules, uses the tracked lockfile under `--locked`, and publishes four exact binaries plus a typed CultCache witness/head only after atomic directory publication and byte verification. Task Scheduler pins release id/digest and the witnessed supervisor path; Idunn reauthenticates the complete set and reserved policy role paths. Current-boot proof: reconciler PID 21228 launched direct child 22736; witnessed Mind/Modeling queries ranked semantically; killing that exact-path child caused Idunn to launch PID 19748 with receipt `86a42d51-9930-4b5d-9e61-e74c82a0d808`, ready heartbeat `4fc91977-b635-474b-812b-c364847e2b4b`, and witnessed executable digest `sha256-1060dd64e8c2b520ec8744b67e8306db04a6618d463fc2117c352adf9e30e6b0`. This closes current-boot artifact generation and recurrence, not reboot/logon survival.
|
||||
- Release `sha256-6f9ab4848207a110e7e3836f41e6a74c0f463369ad4b04ce2d0d3831a6b262ae` packages repository-readiness source commit `360dcb433dda9599fca14282592608d26b8b6cc8`; witnessed Idunn PID 29476 directly owns witnessed projector PID 22840 with launch receipt `50879209-b65f-4411-ae3f-6f647d37bf42` and ready heartbeat `134df620-9e20-4220-ad4e-117ea4ca8188`. Deployment exposed receipt-v2 migration pressure: v1 receipts became correctly ineligible, but the old Succeeded pulse shortcut kept both partitions idle. The shortcut is now cut in source. Valid v2 terminal evidence derives Ready; an exact same-obligation succeeded claim opens Repair only after its consumed grant/recovery authority and exact succeeded attempt are authenticated. Failed repair retries through execute at the next epoch. Forged predecessor authority cannot be laundered by a repair grant.
|
||||
- Yggdrasil exact-SHA pressure release `586147a751c6eec2d59cd8dc10bd17ce0d02a4d1` proved useful GPU work but failed deployment honestly. GTX 1080 stayed near saturation at 2,419 MiB; bounded four-text requests completed continuously. Mind published 215 vectors, while workspace coverage completed 3,389 Ollama HTTP calls total (about 13.3k workspace chunks after Mind) before Idunn's 50-minute candidate-health deadline at 2026-07-17T01:23:50+02:00. Idunn then stopped the service, published no deployment witness, and reported no exact-candidate daemon-health proof; the Modeling collection remained at the inherited 22 points because the monolithic executor had not yet published its accumulated vectors. This is failure evidence for the execution/deployment contract, not for GPU acceleration. Total wall-time extension is forbidden as the repair.
|
||||
next_action: Make Epiphany aggregate health and Idunn deployment waiting consume the exact provider-owned workspace projection progress document directly. Derive warming only from a fresh authenticated heartbeat plus monotonically advancing exact-lineage checkpoint progress; use a per-operation no-advance lease, reserve active/Current for the terminal receipt/head, and remove the deploy script's total projection wall-clock verdict. Add negatives for frozen progress with live heartbeat, stale heartbeat with progress, 100 percent without receipt/head, and cross-release/launch/Body replay. Then reauthorize exact Epiphany commit `7362e54b` or its reviewed successor through Bifrost and redeploy only through live Idunn on Yggdrasil. Local Starfire execution is development-only. Do not reboot any host without explicit live operator approval.
|
||||
next_action: Finish the reviewed commits in Epiphany, Odin, and gamecult-ops; merge/deploy the updated Yggdrasil Idunn authority body first. Then authorize the exact reviewed Epiphany main commit through Bifrost and let Idunn deploy it. Observe signed warming only while authenticated checkpoints advance, Qdrant count growth, GTX 1080 duty/model residency, terminal active admission for the exact request, and restart-safe checkpoint resume. Local Starfire execution is development-only. Do not reboot any host without explicit live operator approval.
|
||||
|
|
|
|||
Reference in a new issue