Bind deployment to one witnessed release
This commit is contained in:
parent
348024071e
commit
7970347af1
15 changed files with 1000 additions and 141 deletions
|
|
@ -23,6 +23,10 @@ name = "epiphany-workspace-coverage-recovery-smoke"
|
|||
path = "src/bin/epiphany-workspace-coverage-recovery-smoke.rs"
|
||||
required-features = ["workspace-coverage-recovery-smoke"]
|
||||
|
||||
[[bin]]
|
||||
name = "epiphany-release"
|
||||
path = "src/bin/epiphany-release.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
bm25 = "2.3.2"
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ use epiphany_core::load_epiphany_cultmesh_managed_service_policy;
|
|||
use epiphany_core::load_epiphany_cultmesh_managed_service_policy_with_digest;
|
||||
use epiphany_core::load_epiphany_cultmesh_status;
|
||||
use epiphany_core::load_epiphany_cultmesh_swarm_brake;
|
||||
use epiphany_core::load_epiphany_packaged_release;
|
||||
use epiphany_core::load_latest_epiphany_cultmesh_daemon_heartbeat;
|
||||
use epiphany_core::load_latest_epiphany_cultmesh_daemon_service_lifecycle_receipt_for_service;
|
||||
use epiphany_core::migrate_memory_semantic_projection_attempts_v0;
|
||||
|
|
@ -47,6 +48,7 @@ use epiphany_core::write_epiphany_cultmesh_daemon_service_lifecycle_receipt;
|
|||
use epiphany_core::write_epiphany_cultmesh_managed_service_policy;
|
||||
use epiphany_core::write_epiphany_cultmesh_semantic_projector_service_policy;
|
||||
use epiphany_core::write_epiphany_cultmesh_workspace_coverage_projector_service_policy;
|
||||
use epiphany_core::{EpiphanyPackagedReleaseEntry, authenticate_epiphany_packaged_release};
|
||||
use epiphany_core::{
|
||||
WORKSPACE_COVERAGE_PROCESS_LAUNCH_SCHEMA_VERSION, WorkspaceCoverageManagedProcessLaunchEntry,
|
||||
WorkspaceCoverageProcessBootstrap, authenticate_workspace_coverage_provider_heartbeat,
|
||||
|
|
@ -60,6 +62,9 @@ use epiphany_core::{
|
|||
write_workspace_coverage_managed_process_launch, write_workspace_coverage_process_bootstrap,
|
||||
write_workspace_coverage_process_termination_observation,
|
||||
};
|
||||
use epiphany_core::{
|
||||
epiphany_packaged_release_binary_path, epiphany_packaged_release_witness_sha256,
|
||||
};
|
||||
use rand_core::{OsRng, RngCore};
|
||||
use serde_json::Value;
|
||||
use serde_json::json;
|
||||
|
|
@ -200,9 +205,40 @@ fn managed_service_task_name(args: &Args) -> String {
|
|||
.unwrap_or_else(|| "Epiphany-Idunn-Managed-Service-Reconciler".to_string())
|
||||
}
|
||||
|
||||
fn pinned_packaged_release(
|
||||
args: &Args,
|
||||
require_digest: bool,
|
||||
) -> Result<(EpiphanyPackagedReleaseEntry, String)> {
|
||||
let release_id = args
|
||||
.release_id
|
||||
.as_deref()
|
||||
.context("managed-service deployment requires --release-id")?;
|
||||
let witness = load_epiphany_packaged_release(&args.store, &args.runtime_id, release_id)?
|
||||
.context("pinned packaged release is absent")?;
|
||||
let digest = match args.release_witness_sha256.as_deref() {
|
||||
Some(expected) => expected.to_string(),
|
||||
None if require_digest => {
|
||||
anyhow::bail!("managed-service runtime requires --release-witness-sha256")
|
||||
}
|
||||
None => epiphany_packaged_release_witness_sha256(&witness)?,
|
||||
};
|
||||
let authenticated =
|
||||
authenticate_epiphany_packaged_release(&args.store, &args.runtime_id, release_id, &digest)?;
|
||||
Ok((authenticated, digest))
|
||||
}
|
||||
|
||||
fn managed_service_task_action(args: &Args) -> Result<(PathBuf, PathBuf, Vec<String>)> {
|
||||
let command = fs::canonicalize(service_command_path(args)?)
|
||||
.context("managed-service task command must be an existing absolute executable")?;
|
||||
if args.service_command.is_some() {
|
||||
anyhow::bail!(
|
||||
"managed-service task command comes from --release-id; --service-command is forbidden"
|
||||
);
|
||||
}
|
||||
let (release, witness_digest) = pinned_packaged_release(args, false)?;
|
||||
let command = fs::canonicalize(epiphany_packaged_release_binary_path(
|
||||
&release,
|
||||
"supervisor",
|
||||
)?)
|
||||
.context("witnessed managed-service supervisor is absent")?;
|
||||
let cwd = fs::canonicalize(
|
||||
args.cwd
|
||||
.clone()
|
||||
|
|
@ -220,6 +256,10 @@ fn managed_service_task_action(args: &Args) -> Result<(PathBuf, PathBuf, Vec<Str
|
|||
args.runtime_id.clone(),
|
||||
"--loop-interval-seconds".to_string(),
|
||||
args.loop_interval_seconds.to_string(),
|
||||
"--release-id".to_string(),
|
||||
release.release_id.clone(),
|
||||
"--release-witness-sha256".to_string(),
|
||||
witness_digest,
|
||||
"--fatal-log".to_string(),
|
||||
absolutize_from(
|
||||
&cwd,
|
||||
|
|
@ -593,8 +633,6 @@ fn semantic_projector_service_status(args: Args) -> Result<()> {
|
|||
"providerIncarnation": heartbeat.provider_incarnation,
|
||||
"heartbeatStatus": heartbeat.status,
|
||||
"startupCorrelationMatches": correlation_matches,
|
||||
"readinessAuthority": "semantic-query-admission",
|
||||
"semanticReadiness": "not-asserted-by-this-status",
|
||||
"privateStateExposed": false,
|
||||
"authoritative": false,
|
||||
}))?
|
||||
|
|
@ -736,8 +774,17 @@ fn serve(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn managed_service_serve(args: Args) -> Result<()> {
|
||||
let (release, _) = pinned_packaged_release(&args, true)?;
|
||||
let expected_supervisor = fs::canonicalize(epiphany_packaged_release_binary_path(
|
||||
&release,
|
||||
"supervisor",
|
||||
)?)?;
|
||||
if fs::canonicalize(env::current_exe()?)? != expected_supervisor {
|
||||
anyhow::bail!("managed-service reconciler executable is not the pinned release supervisor");
|
||||
}
|
||||
let mut iteration = 0_u64;
|
||||
loop {
|
||||
pinned_packaged_release(&args, true)?;
|
||||
iteration = iteration.saturating_add(1);
|
||||
let policies =
|
||||
load_epiphany_cultmesh_managed_service_policies(&args.store, args.runtime_id.clone())?;
|
||||
|
|
@ -1260,7 +1307,7 @@ fn semantic_projector_service_policy(mut args: Args) -> Result<()> {
|
|||
"managed-service-policy-{SEMANTIC_PROJECTOR_SERVICE_ID}"
|
||||
));
|
||||
args.restart_mode = "always".to_string();
|
||||
args.service_command = Some(semantic_projector_command_path()?);
|
||||
args.service_command = Some(packaged_role_command_path(&args, "semantic-projector")?);
|
||||
args.service_args = semantic_projector_service_args(
|
||||
agent_store,
|
||||
runtime_store,
|
||||
|
|
@ -1300,7 +1347,10 @@ fn workspace_coverage_projector_service_policy(mut args: Args) -> Result<()> {
|
|||
"managed-service-policy-{WORKSPACE_COVERAGE_PROJECTOR_SERVICE_ID}"
|
||||
));
|
||||
args.restart_mode = "always".to_string();
|
||||
args.service_command = Some(workspace_coverage_projector_command_path()?);
|
||||
args.service_command = Some(packaged_role_command_path(
|
||||
&args,
|
||||
"workspace-coverage-projector",
|
||||
)?);
|
||||
args.service_args = workspace_coverage_projector_service_args(
|
||||
runtime_store,
|
||||
&args.store,
|
||||
|
|
@ -1318,22 +1368,28 @@ fn workspace_coverage_projector_service_policy(mut args: Args) -> Result<()> {
|
|||
write_managed_service_policy(args)
|
||||
}
|
||||
|
||||
fn workspace_coverage_projector_command_path() -> Result<PathBuf> {
|
||||
let binary = if cfg!(windows) {
|
||||
"epiphany-workspace-coverage-projector.exe"
|
||||
} else {
|
||||
"epiphany-workspace-coverage-projector"
|
||||
};
|
||||
let path = env::current_exe()
|
||||
.map(|path| path.with_file_name(binary))
|
||||
.context("failed to derive packaged workspace coverage projector executable")?;
|
||||
if !path.is_file() {
|
||||
anyhow::bail!(
|
||||
"packaged workspace coverage projector executable is missing at {}; build both supervisor and projector together",
|
||||
path.display()
|
||||
);
|
||||
fn packaged_role_command_path(args: &Args, role: &str) -> Result<PathBuf> {
|
||||
#[cfg(feature = "workspace-coverage-recovery-smoke")]
|
||||
if args.release_id.is_none()
|
||||
&& std::env::var_os("EPIPHANY_WORKSPACE_COVERAGE_SMOKE_DIAGNOSTICS").is_some()
|
||||
{
|
||||
let file_name = match role {
|
||||
"semantic-projector" if cfg!(windows) => "epiphany-memory-semantic-projector.exe",
|
||||
"semantic-projector" => "epiphany-memory-semantic-projector",
|
||||
"workspace-coverage-projector" if cfg!(windows) => {
|
||||
"epiphany-workspace-coverage-projector.exe"
|
||||
}
|
||||
"workspace-coverage-projector" => "epiphany-workspace-coverage-projector",
|
||||
_ => anyhow::bail!("unknown smoke-only packaged role {role}"),
|
||||
};
|
||||
let path = env::current_exe()?.with_file_name(file_name);
|
||||
if !path.is_file() {
|
||||
anyhow::bail!("smoke-only sibling is absent: {}", path.display());
|
||||
}
|
||||
return Ok(path);
|
||||
}
|
||||
Ok(path)
|
||||
let (release, _) = pinned_packaged_release(args, true)?;
|
||||
epiphany_packaged_release_binary_path(&release, role)
|
||||
}
|
||||
|
||||
fn workspace_coverage_projector_service_args(
|
||||
|
|
@ -1364,24 +1420,6 @@ fn workspace_coverage_projector_service_args(
|
|||
]
|
||||
}
|
||||
|
||||
fn semantic_projector_command_path() -> Result<PathBuf> {
|
||||
let binary = if cfg!(windows) {
|
||||
"epiphany-memory-semantic-projector.exe"
|
||||
} else {
|
||||
"epiphany-memory-semantic-projector"
|
||||
};
|
||||
let path = env::current_exe()
|
||||
.map(|path| path.with_file_name(binary))
|
||||
.context("failed to derive packaged semantic projector executable")?;
|
||||
if !path.is_file() {
|
||||
anyhow::bail!(
|
||||
"packaged semantic projector executable is missing at {}; build both supervisor and projector together",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn semantic_projector_service_args(
|
||||
agent_store: &Path,
|
||||
runtime_store: &Path,
|
||||
|
|
@ -1465,12 +1503,32 @@ fn managed_service_read(args: Args) -> Result<()> {
|
|||
}
|
||||
|
||||
fn managed_service_reconcile(mut args: Args) -> Result<()> {
|
||||
let pinned_release = if args.release_id.is_some() {
|
||||
Some(pinned_packaged_release(&args, true)?.0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let policy = load_epiphany_cultmesh_managed_service_policy(
|
||||
&args.store,
|
||||
args.runtime_id.clone(),
|
||||
&args.service_id,
|
||||
)?
|
||||
.with_context(|| format!("managed service policy missing for {}", args.service_id))?;
|
||||
if let Some(release) = &pinned_release {
|
||||
let expected_role = match policy.service_id.as_str() {
|
||||
SEMANTIC_PROJECTOR_SERVICE_ID => Some("semantic-projector"),
|
||||
WORKSPACE_COVERAGE_PROJECTOR_SERVICE_ID => Some("workspace-coverage-projector"),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(role) = expected_role
|
||||
&& fs::canonicalize(&policy.command)?
|
||||
!= fs::canonicalize(epiphany_packaged_release_binary_path(release, role)?)?
|
||||
{
|
||||
anyhow::bail!(
|
||||
"reserved managed-service policy command is outside pinned release role {role}"
|
||||
);
|
||||
}
|
||||
}
|
||||
if policy.service_id == WORKSPACE_COVERAGE_PROJECTOR_SERVICE_ID {
|
||||
return reconcile_workspace_coverage_projector(args, policy);
|
||||
}
|
||||
|
|
@ -3315,7 +3373,7 @@ mod semantic_projector_authority_tests {
|
|||
let specialized_end = specialized_tail.find("\nfn ").unwrap();
|
||||
let specialized = &specialized_tail[..specialized_end];
|
||||
assert!(specialized.contains("args.service_command.is_some()"));
|
||||
assert!(specialized.contains("semantic_projector_command_path()"));
|
||||
assert!(specialized.contains("packaged_role_command_path(&args, \"semantic-projector\")"));
|
||||
assert!(specialized.contains("write_managed_service_policy(args)"));
|
||||
|
||||
let legacy_cli = include_str!("epiphany-memory-semantic.rs");
|
||||
|
|
@ -3367,6 +3425,9 @@ mod semantic_projector_authority_tests {
|
|||
let body = &tail[..end];
|
||||
for required in [
|
||||
"managed-service-serve",
|
||||
"--release-id",
|
||||
"--release-witness-sha256",
|
||||
"pinned_packaged_release",
|
||||
"New-ScheduledTaskTrigger -AtLogOn",
|
||||
"LogonType Interactive",
|
||||
"RunLevel Limited",
|
||||
|
|
@ -3385,6 +3446,7 @@ mod semantic_projector_authority_tests {
|
|||
);
|
||||
}
|
||||
assert!(!body.contains("sc.exe"));
|
||||
assert!(body.contains("--service-command is forbidden"));
|
||||
assert!(source.contains("let mut task_restart_count = 999_u32"));
|
||||
assert!(source.contains("task_readback_has_drift"));
|
||||
assert!(source.contains("\"install-drift\""));
|
||||
|
|
@ -3461,6 +3523,8 @@ struct Args {
|
|||
task_logon_delay_seconds: u64,
|
||||
task_restart_interval_seconds: u64,
|
||||
task_restart_count: u32,
|
||||
release_id: Option<String>,
|
||||
release_witness_sha256: Option<String>,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
|
|
@ -3509,6 +3573,8 @@ impl Args {
|
|||
let mut task_restart_interval_seconds = 60_u64;
|
||||
let mut task_restart_count = 999_u32;
|
||||
let mut fatal_log = None;
|
||||
let mut release_id = None;
|
||||
let mut release_witness_sha256 = None;
|
||||
|
||||
while let Some(arg) = values.next() {
|
||||
match arg.as_str() {
|
||||
|
|
@ -3677,6 +3743,16 @@ impl Args {
|
|||
values.next().context("missing --fatal-log value")?,
|
||||
));
|
||||
}
|
||||
"--release-id" => {
|
||||
release_id = Some(values.next().context("missing --release-id value")?);
|
||||
}
|
||||
"--release-witness-sha256" => {
|
||||
release_witness_sha256 = Some(
|
||||
values
|
||||
.next()
|
||||
.context("missing --release-witness-sha256 value")?,
|
||||
);
|
||||
}
|
||||
other => anyhow::bail!("unknown argument {other:?}"),
|
||||
}
|
||||
}
|
||||
|
|
@ -3825,6 +3901,8 @@ impl Args {
|
|||
task_logon_delay_seconds,
|
||||
task_restart_interval_seconds,
|
||||
task_restart_count,
|
||||
release_id,
|
||||
release_witness_sha256,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,8 +123,7 @@ fn main() -> Result<()> {
|
|||
"healthPublicationFaults": health_publication_faults,
|
||||
"selectedScopeId": outcome.selected_scope_id,
|
||||
"privateStateExposed": false,
|
||||
"authoritative": false,
|
||||
"queryAdmission": false
|
||||
"authoritative": false
|
||||
}))?
|
||||
);
|
||||
if args.max_iterations != 0 && sequence >= args.max_iterations {
|
||||
|
|
|
|||
|
|
@ -254,7 +254,6 @@ fn print_semantic_health(
|
|||
"authoritative": health.authoritative,
|
||||
"nonAuthoritative": !health.authoritative,
|
||||
"authority": "sight-only",
|
||||
"queryAdmission": false,
|
||||
"queryEligibleDisplayOnly": health.query_eligible_display_only,
|
||||
"privateStateExposed": health.private_state_exposed,
|
||||
}))
|
||||
|
|
|
|||
74
epiphany-core/src/bin/epiphany-release.rs
Normal file
74
epiphany-core/src/bin/epiphany-release.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
use anyhow::{Context, Result, bail};
|
||||
use epiphany_core::{
|
||||
PackageReleaseRequest, epiphany_packaged_release_witness_sha256,
|
||||
package_and_publish_epiphany_release,
|
||||
};
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut args = env::args().skip(1);
|
||||
if args.next().as_deref() != Some("package") {
|
||||
bail!(
|
||||
"usage: epiphany-release package --repo PATH --destination PATH --store PATH --runtime-id ID [--target-triple TRIPLE]"
|
||||
);
|
||||
}
|
||||
let mut repo = None;
|
||||
let mut destination = None;
|
||||
let mut store = None;
|
||||
let mut runtime = None;
|
||||
let mut target = None;
|
||||
while let Some(flag) = args.next() {
|
||||
let value = args
|
||||
.next()
|
||||
.with_context(|| format!("missing value for {flag}"))?;
|
||||
match flag.as_str() {
|
||||
"--repo" => repo = Some(PathBuf::from(value)),
|
||||
"--destination" => destination = Some(PathBuf::from(value)),
|
||||
"--store" => store = Some(PathBuf::from(value)),
|
||||
"--runtime-id" => runtime = Some(value),
|
||||
"--target-triple" => target = Some(value),
|
||||
_ => bail!("unknown argument {flag}"),
|
||||
}
|
||||
}
|
||||
let repo = repo.context("missing --repo")?;
|
||||
let destination = destination.context("missing --destination")?;
|
||||
let store = store.context("missing --store")?;
|
||||
let runtime = runtime.context("missing --runtime-id")?;
|
||||
let rustc = command_output("rustc", &["-vV"])?;
|
||||
let target = target
|
||||
.or_else(|| {
|
||||
rustc
|
||||
.lines()
|
||||
.find_map(|line| line.strip_prefix("host: ").map(str::to_string))
|
||||
})
|
||||
.context("rustc did not report host target")?;
|
||||
let witness = package_and_publish_epiphany_release(PackageReleaseRequest {
|
||||
repo: &repo,
|
||||
destination: &destination,
|
||||
store: &store,
|
||||
runtime_id: &runtime,
|
||||
target_triple: &target,
|
||||
toolchain_fingerprint: &rustc,
|
||||
})?;
|
||||
let witness_sha256 = epiphany_packaged_release_witness_sha256(&witness)?;
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(
|
||||
&serde_json::json!({"schemaVersion":witness.schema_version,"status":"published","releaseId":witness.release_id,"witnessSha256":witness_sha256,"sourceCommit":witness.source_commit_sha,"packageRoot":witness.package_root,"binaryCount":witness.binaries.len(),"privateStateExposed":false})
|
||||
)?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn command_output(command: &str, args: &[&str]) -> Result<String> {
|
||||
let output = Command::new(command).args(args).output()?;
|
||||
if !output.status.success() {
|
||||
bail!(
|
||||
"{command} failed: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
Ok(String::from_utf8(output.stdout)?.trim().to_string())
|
||||
}
|
||||
|
|
@ -291,7 +291,6 @@ fn run_cli() -> Result<()> {
|
|||
"privateStateExposed": false,
|
||||
"authoritative": false,
|
||||
"authority": "sight-only",
|
||||
"queryAdmission": false,
|
||||
"rows": report_rows,
|
||||
"tuiRows": tui_rows,
|
||||
}))?
|
||||
|
|
@ -2218,7 +2217,7 @@ fn semantic_projection_health_tui_row(
|
|||
row: &EpiphanyCultMeshSemanticProjectionHealthEntry,
|
||||
) -> String {
|
||||
format!(
|
||||
"SEMANTIC-HEALTH swarm={} partition={} generation={} status=observed-{} documents={} dimensions={} provider={} incarnation={} authority=sight-only query-admission=false private=false",
|
||||
"SEMANTIC-HEALTH swarm={} partition={} generation={} status=observed-{} documents={} dimensions={} provider={} incarnation={} authority=sight-only private=false",
|
||||
row.swarm_id,
|
||||
row.partition,
|
||||
row.source_generation,
|
||||
|
|
@ -2253,7 +2252,6 @@ fn semantic_projection_health_report_row(
|
|||
"authoritative": row.authoritative,
|
||||
"nonAuthoritative": !row.authoritative,
|
||||
"authority": "sight-only",
|
||||
"queryAdmission": false,
|
||||
"queryEligibleDisplayOnly": row.query_eligible_display_only,
|
||||
"privateStateExposed": row.private_state_exposed,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use crate::default_hands_cultnet_contracts;
|
|||
use crate::default_mind_cultnet_contracts;
|
||||
use crate::default_soul_cultnet_contracts;
|
||||
use crate::default_substrate_gate_cultnet_contracts;
|
||||
use crate::packaged_release::{EpiphanyPackagedReleaseEntry, EpiphanyPackagedReleaseHead};
|
||||
use crate::workspace_coverage_process_documents::{
|
||||
WORKSPACE_COVERAGE_PROCESS_EVIDENCE_HEAD_SCHEMA_VERSION,
|
||||
WORKSPACE_COVERAGE_PROCESS_LAUNCH_SCHEMA_VERSION,
|
||||
|
|
@ -2650,6 +2651,8 @@ cultmesh_documents!(EpiphanyCultMeshDocuments {
|
|||
WorkspaceCoverageProcessEvidenceHead => WORKSPACE_COVERAGE_PROCESS_EVIDENCE_HEAD_SCHEMA_VERSION,
|
||||
WorkspaceCoverageProviderHeartbeatEntry => WORKSPACE_COVERAGE_PROVIDER_HEARTBEAT_SCHEMA_VERSION,
|
||||
WorkspaceCoverageProcessTerminationObservationEntry => WORKSPACE_COVERAGE_PROCESS_TERMINATION_SCHEMA_VERSION,
|
||||
EpiphanyPackagedReleaseEntry => crate::packaged_release::EPIPHANY_PACKAGED_RELEASE_SCHEMA_VERSION,
|
||||
EpiphanyPackagedReleaseHead => crate::packaged_release::EPIPHANY_PACKAGED_RELEASE_HEAD_SCHEMA_VERSION,
|
||||
EpiphanyCultMeshIdunnDeploymentReceiptEntry => EPIPHANY_CULTMESH_IDUNN_DEPLOYMENT_RECEIPT_SCHEMA_VERSION,
|
||||
EpiphanyCultMeshIdunnAftercareAuditReceiptEntry => EPIPHANY_CULTMESH_IDUNN_AFTERCARE_AUDIT_RECEIPT_SCHEMA_VERSION,
|
||||
EpiphanyCultMeshSwarmBrakeEntry => EPIPHANY_CULTMESH_SWARM_BRAKE_SCHEMA_VERSION,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ mod host_identity;
|
|||
mod memory_graph;
|
||||
mod mind_gateway;
|
||||
mod organ_dependencies;
|
||||
mod packaged_release;
|
||||
mod persona_turn;
|
||||
mod process_observation;
|
||||
mod promotion;
|
||||
|
|
@ -792,6 +793,16 @@ pub use organ_dependencies::evaluate_receipt_proof_profiles;
|
|||
pub use organ_dependencies::receipt_proof_evaluation_errors;
|
||||
pub use organ_dependencies::render_organ_dependencies;
|
||||
pub use organ_dependencies::render_organ_dependency;
|
||||
pub use packaged_release::{
|
||||
EPIPHANY_PACKAGED_RELEASE_HEAD_SCHEMA_VERSION, EPIPHANY_PACKAGED_RELEASE_SCHEMA_VERSION,
|
||||
EpiphanyPackagedReleaseBinary, EpiphanyPackagedReleaseEntry, EpiphanyPackagedReleaseHead,
|
||||
PackageReleaseRequest, authenticate_epiphany_packaged_release,
|
||||
epiphany_packaged_release_binary_path, epiphany_packaged_release_witness_sha256,
|
||||
load_epiphany_packaged_release, load_epiphany_packaged_release_head,
|
||||
package_and_publish_epiphany_release, publish_epiphany_packaged_release,
|
||||
required_packaged_release_binaries, validate_epiphany_packaged_release,
|
||||
verify_epiphany_packaged_release_files,
|
||||
};
|
||||
pub use persona_turn::PERSONA_INTERPRETER_PROMPT_SCHEMA_VERSION;
|
||||
pub use persona_turn::PERSONA_PROJECTOR_PROMPT_SCHEMA_VERSION;
|
||||
pub use persona_turn::PERSONA_TURN_PROMPT_SCHEMA_VERSION;
|
||||
|
|
|
|||
647
epiphany-core/src/packaged_release.rs
Normal file
647
epiphany-core/src/packaged_release.rs
Normal file
|
|
@ -0,0 +1,647 @@
|
|||
use crate::open_epiphany_cultmesh_node;
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use chrono::{DateTime, Utc};
|
||||
use cultcache_rs::{DatabaseEntry, SingleFileMessagePackBackingStore};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub const EPIPHANY_PACKAGED_RELEASE_SCHEMA_VERSION: &str = "epiphany.packaged_release.v0";
|
||||
pub const EPIPHANY_PACKAGED_RELEASE_HEAD_SCHEMA_VERSION: &str = "epiphany.packaged_release_head.v0";
|
||||
const RELEASE_KEY_PREFIX: &str = "epiphany-local/packaged-release/by-id/";
|
||||
const RELEASE_HEAD_KEY: &str = "epiphany-local/packaged-release/current";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct EpiphanyPackagedReleaseBinary {
|
||||
pub role: String,
|
||||
pub file_name: String,
|
||||
pub canonical_path: String,
|
||||
pub sha256: String,
|
||||
pub byte_len: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "epiphany.packaged_release",
|
||||
schema = "EpiphanyPackagedReleaseEntry"
|
||||
)]
|
||||
pub struct EpiphanyPackagedReleaseEntry {
|
||||
#[cultcache(key = 0)]
|
||||
pub schema_version: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub release_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub runtime_id: String,
|
||||
#[cultcache(key = 3)]
|
||||
pub source_commit_sha: String,
|
||||
#[cultcache(key = 4)]
|
||||
pub target_triple: String,
|
||||
#[cultcache(key = 5)]
|
||||
pub cargo_profile: String,
|
||||
#[cultcache(key = 6)]
|
||||
pub toolchain_fingerprint: String,
|
||||
#[cultcache(key = 7)]
|
||||
pub created_at_utc: String,
|
||||
#[cultcache(key = 8)]
|
||||
pub package_root: String,
|
||||
#[cultcache(key = 9)]
|
||||
pub binaries: Vec<EpiphanyPackagedReleaseBinary>,
|
||||
#[cultcache(key = 10)]
|
||||
pub private_state_exposed: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "epiphany.packaged_release_head",
|
||||
schema = "EpiphanyPackagedReleaseHead"
|
||||
)]
|
||||
pub struct EpiphanyPackagedReleaseHead {
|
||||
#[cultcache(key = 0)]
|
||||
pub schema_version: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub runtime_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub release_id: String,
|
||||
#[cultcache(key = 3)]
|
||||
pub witness_sha256: String,
|
||||
#[cultcache(key = 4)]
|
||||
pub published_at_utc: String,
|
||||
}
|
||||
|
||||
pub struct PackageReleaseRequest<'a> {
|
||||
pub repo: &'a Path,
|
||||
pub destination: &'a Path,
|
||||
pub store: &'a Path,
|
||||
pub runtime_id: &'a str,
|
||||
pub target_triple: &'a str,
|
||||
pub toolchain_fingerprint: &'a str,
|
||||
}
|
||||
|
||||
pub fn required_packaged_release_binaries() -> Vec<(&'static str, &'static str)> {
|
||||
vec![
|
||||
(
|
||||
"supervisor",
|
||||
if cfg!(windows) {
|
||||
"epiphany-daemon-supervisor.exe"
|
||||
} else {
|
||||
"epiphany-daemon-supervisor"
|
||||
},
|
||||
),
|
||||
(
|
||||
"semantic-projector",
|
||||
if cfg!(windows) {
|
||||
"epiphany-memory-semantic-projector.exe"
|
||||
} else {
|
||||
"epiphany-memory-semantic-projector"
|
||||
},
|
||||
),
|
||||
(
|
||||
"workspace-coverage-projector",
|
||||
if cfg!(windows) {
|
||||
"epiphany-workspace-coverage-projector.exe"
|
||||
} else {
|
||||
"epiphany-workspace-coverage-projector"
|
||||
},
|
||||
),
|
||||
(
|
||||
"semantic-query",
|
||||
if cfg!(windows) {
|
||||
"epiphany-memory-semantic.exe"
|
||||
} else {
|
||||
"epiphany-memory-semantic"
|
||||
},
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn package_and_publish_epiphany_release(
|
||||
request: PackageReleaseRequest<'_>,
|
||||
) -> Result<EpiphanyPackagedReleaseEntry> {
|
||||
let (source_commit_sha, source_commit_time) = clean_source_commit(request.repo)?;
|
||||
require_nonempty("runtime id", request.runtime_id)?;
|
||||
require_nonempty("target triple", request.target_triple)?;
|
||||
require_nonempty("toolchain fingerprint", request.toolchain_fingerprint)?;
|
||||
fs::create_dir_all(request.destination)?;
|
||||
let destination = fs::canonicalize(request.destination)?;
|
||||
let source_root = destination.join(format!(".source-{}", Uuid::new_v4()));
|
||||
let source_guard = GitWorktreeGuard::create(request.repo, &source_root, &source_commit_sha)?;
|
||||
let build_root = destination.join(format!(".build-{}", Uuid::new_v4()));
|
||||
let _build_guard = DirectoryCleanup(build_root.clone());
|
||||
build_required_release_siblings(&source_guard.path, &build_root, request.target_triple)?;
|
||||
let built_dir = build_root.join(request.target_triple).join("release");
|
||||
let staging = destination.join(format!(".staging-{}", Uuid::new_v4()));
|
||||
fs::create_dir(&staging)?;
|
||||
let result = (|| {
|
||||
let mut binaries = Vec::new();
|
||||
for (role, file_name) in required_packaged_release_binaries() {
|
||||
let source = built_dir.join(file_name);
|
||||
if !source.is_file() {
|
||||
bail!("required packaged sibling is absent: {}", source.display());
|
||||
}
|
||||
let target = staging.join(file_name);
|
||||
fs::copy(&source, &target)?;
|
||||
binaries.push(binary_record(role, file_name, &target)?);
|
||||
}
|
||||
binaries.sort_by(|left, right| left.role.cmp(&right.role));
|
||||
let release_id = release_id(
|
||||
request.runtime_id,
|
||||
&source_commit_sha,
|
||||
request.target_triple,
|
||||
request.toolchain_fingerprint,
|
||||
&binaries,
|
||||
);
|
||||
let final_root = destination.join(&source_commit_sha).join(&release_id);
|
||||
fs::create_dir_all(final_root.parent().expect("release root has parent"))?;
|
||||
for binary in &mut binaries {
|
||||
binary.canonical_path = final_root.join(&binary.file_name).display().to_string();
|
||||
}
|
||||
let witness = EpiphanyPackagedReleaseEntry {
|
||||
schema_version: EPIPHANY_PACKAGED_RELEASE_SCHEMA_VERSION.into(),
|
||||
release_id,
|
||||
runtime_id: request.runtime_id.into(),
|
||||
source_commit_sha,
|
||||
target_triple: request.target_triple.into(),
|
||||
cargo_profile: "release".into(),
|
||||
toolchain_fingerprint: request.toolchain_fingerprint.into(),
|
||||
created_at_utc: source_commit_time,
|
||||
package_root: final_root.display().to_string(),
|
||||
binaries,
|
||||
private_state_exposed: false,
|
||||
};
|
||||
validate_epiphany_packaged_release(&witness)?;
|
||||
if final_root.exists() {
|
||||
verify_epiphany_packaged_release_files(&witness)?;
|
||||
fs::remove_dir_all(&staging)?;
|
||||
} else {
|
||||
fs::rename(&staging, &final_root)
|
||||
.context("failed to atomically publish packaged release directory")?;
|
||||
verify_epiphany_packaged_release_files(&witness)?;
|
||||
}
|
||||
publish_epiphany_packaged_release(request.store, request.runtime_id, witness)
|
||||
})();
|
||||
if staging.exists() {
|
||||
let _ = fs::remove_dir_all(staging);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
struct DirectoryCleanup(PathBuf);
|
||||
|
||||
impl Drop for DirectoryCleanup {
|
||||
fn drop(&mut self) {
|
||||
if self.0.exists() {
|
||||
let _ = fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GitWorktreeGuard {
|
||||
repo: PathBuf,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl GitWorktreeGuard {
|
||||
fn create(repo: &Path, path: &Path, commit: &str) -> Result<Self> {
|
||||
let output = std::process::Command::new("git")
|
||||
.args(["worktree", "add", "--detach"])
|
||||
.arg(path)
|
||||
.arg(commit)
|
||||
.current_dir(repo)
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
bail!(
|
||||
"failed to create exact-source release worktree: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
Ok(Self {
|
||||
repo: repo.to_path_buf(),
|
||||
path: path.to_path_buf(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GitWorktreeGuard {
|
||||
fn drop(&mut self) {
|
||||
let removed = std::process::Command::new("git")
|
||||
.args(["worktree", "remove", "--force"])
|
||||
.arg(&self.path)
|
||||
.current_dir(&self.repo)
|
||||
.status()
|
||||
.is_ok_and(|status| status.success());
|
||||
if !removed && self.path.exists() {
|
||||
let _ = fs::remove_dir_all(&self.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_required_release_siblings(repo: &Path, target_dir: &Path, target: &str) -> Result<()> {
|
||||
let manifest = repo.join("epiphany-core").join("Cargo.toml");
|
||||
if !manifest.is_file() {
|
||||
bail!(
|
||||
"Epiphany release manifest is absent: {}",
|
||||
manifest.display()
|
||||
);
|
||||
}
|
||||
let mut command = std::process::Command::new("cargo");
|
||||
command
|
||||
.arg("build")
|
||||
.arg("--release")
|
||||
.arg("--manifest-path")
|
||||
.arg(&manifest)
|
||||
.arg("--target-dir")
|
||||
.arg(target_dir)
|
||||
.arg("--target")
|
||||
.arg(target);
|
||||
command.arg("--locked");
|
||||
for (role, _) in required_packaged_release_binaries() {
|
||||
let binary = match role {
|
||||
"supervisor" => "epiphany-daemon-supervisor",
|
||||
"semantic-projector" => "epiphany-memory-semantic-projector",
|
||||
"workspace-coverage-projector" => "epiphany-workspace-coverage-projector",
|
||||
"semantic-query" => "epiphany-memory-semantic",
|
||||
_ => bail!("unknown required release role {role}"),
|
||||
};
|
||||
command.arg("--bin").arg(binary);
|
||||
}
|
||||
let status = command
|
||||
.status()
|
||||
.context("failed to start owned release build")?;
|
||||
if !status.success() {
|
||||
bail!("owned Epiphany release build failed");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_epiphany_packaged_release(entry: &EpiphanyPackagedReleaseEntry) -> Result<()> {
|
||||
if entry.schema_version != EPIPHANY_PACKAGED_RELEASE_SCHEMA_VERSION {
|
||||
bail!("unsupported packaged release schema");
|
||||
}
|
||||
require_nonempty("runtime id", &entry.runtime_id)?;
|
||||
validate_commit(&entry.source_commit_sha)?;
|
||||
require_nonempty("target triple", &entry.target_triple)?;
|
||||
if entry.cargo_profile != "release" {
|
||||
bail!("packaged release profile must be release");
|
||||
}
|
||||
require_nonempty("toolchain fingerprint", &entry.toolchain_fingerprint)?;
|
||||
DateTime::parse_from_rfc3339(&entry.created_at_utc)?;
|
||||
if entry.private_state_exposed {
|
||||
bail!("packaged release may not expose private state");
|
||||
}
|
||||
let root = Path::new(&entry.package_root);
|
||||
if !root.is_absolute() {
|
||||
bail!("packaged release root must be absolute");
|
||||
}
|
||||
let required = required_packaged_release_binaries();
|
||||
if entry.binaries.len() != required.len() {
|
||||
bail!("packaged release sibling set is incomplete");
|
||||
}
|
||||
let mut roles = BTreeSet::new();
|
||||
let mut names = BTreeSet::new();
|
||||
for binary in &entry.binaries {
|
||||
if !roles.insert(binary.role.as_str()) || !names.insert(binary.file_name.as_str()) {
|
||||
bail!("packaged release contains duplicate role or filename");
|
||||
}
|
||||
if !required
|
||||
.iter()
|
||||
.any(|pair| pair.0 == binary.role && pair.1 == binary.file_name)
|
||||
{
|
||||
bail!("packaged release contains an unknown or swapped sibling role");
|
||||
}
|
||||
validate_sha256(&binary.sha256)?;
|
||||
if binary.byte_len == 0 {
|
||||
bail!("packaged release binary is empty");
|
||||
}
|
||||
if Path::new(&binary.canonical_path) != root.join(&binary.file_name) {
|
||||
bail!("packaged release binary path escapes or disagrees with package root");
|
||||
}
|
||||
}
|
||||
let mut identity_binaries = entry.binaries.clone();
|
||||
identity_binaries.sort_by(|left, right| left.role.cmp(&right.role));
|
||||
let expected = release_id(
|
||||
&entry.runtime_id,
|
||||
&entry.source_commit_sha,
|
||||
&entry.target_triple,
|
||||
&entry.toolchain_fingerprint,
|
||||
&identity_binaries,
|
||||
);
|
||||
if entry.release_id != expected {
|
||||
bail!("packaged release id does not authenticate sibling set");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_epiphany_packaged_release_files(entry: &EpiphanyPackagedReleaseEntry) -> Result<()> {
|
||||
validate_epiphany_packaged_release(entry)?;
|
||||
let root = fs::canonicalize(&entry.package_root).context("packaged release root is absent")?;
|
||||
if root != PathBuf::from(&entry.package_root) {
|
||||
bail!("packaged release root is not canonical");
|
||||
}
|
||||
let actual_names = fs::read_dir(&root)?
|
||||
.map(|item| Ok(item?.file_name().to_string_lossy().into_owned()))
|
||||
.collect::<Result<BTreeSet<_>>>()?;
|
||||
let expected_names = entry
|
||||
.binaries
|
||||
.iter()
|
||||
.map(|binary| binary.file_name.clone())
|
||||
.collect::<BTreeSet<_>>();
|
||||
if actual_names != expected_names {
|
||||
bail!("packaged release directory is not the exact witnessed sibling set");
|
||||
}
|
||||
for binary in &entry.binaries {
|
||||
let path = root.join(&binary.file_name);
|
||||
if fs::canonicalize(&path)? != PathBuf::from(&binary.canonical_path) {
|
||||
bail!("packaged sibling path aliases another file");
|
||||
}
|
||||
let metadata = fs::metadata(&path)?;
|
||||
if !metadata.is_file()
|
||||
|| metadata.len() != binary.byte_len
|
||||
|| file_sha256(&path)? != binary.sha256
|
||||
{
|
||||
bail!(
|
||||
"packaged sibling bytes disagree with witness: {}",
|
||||
binary.role
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn publish_epiphany_packaged_release(
|
||||
store: &Path,
|
||||
runtime_id: &str,
|
||||
entry: EpiphanyPackagedReleaseEntry,
|
||||
) -> Result<EpiphanyPackagedReleaseEntry> {
|
||||
validate_epiphany_packaged_release(&entry)?;
|
||||
verify_epiphany_packaged_release_files(&entry)?;
|
||||
if entry.runtime_id != runtime_id {
|
||||
bail!("release witness runtime disagrees with target Verse");
|
||||
}
|
||||
let node = open_epiphany_cultmesh_node(store, runtime_id.to_string())?;
|
||||
let identity_key = format!("{RELEASE_KEY_PREFIX}{}", entry.release_id);
|
||||
let existing = node.get::<EpiphanyPackagedReleaseEntry>(&identity_key)?;
|
||||
if existing.as_ref().is_some_and(|current| current != &entry) {
|
||||
bail!("immutable packaged release identity collision");
|
||||
}
|
||||
let head = EpiphanyPackagedReleaseHead {
|
||||
schema_version: EPIPHANY_PACKAGED_RELEASE_HEAD_SCHEMA_VERSION.into(),
|
||||
runtime_id: runtime_id.into(),
|
||||
release_id: entry.release_id.clone(),
|
||||
witness_sha256: witness_sha256(&entry)?,
|
||||
published_at_utc: Utc::now().to_rfc3339(),
|
||||
};
|
||||
let prior_head = node
|
||||
.cache()
|
||||
.get_envelope::<EpiphanyPackagedReleaseHead>(RELEASE_HEAD_KEY)?;
|
||||
let mut expected = Vec::new();
|
||||
if let Some(envelope) = prior_head {
|
||||
expected.push(envelope);
|
||||
}
|
||||
let mut replacements = Vec::new();
|
||||
if existing.is_none() {
|
||||
replacements.push(node.cache().prepare_entry(&identity_key, &entry)?.0);
|
||||
}
|
||||
replacements.push(node.cache().prepare_entry(RELEASE_HEAD_KEY, &head)?.0);
|
||||
if !SingleFileMessagePackBackingStore::new(store)
|
||||
.compare_and_swap_batch(&expected, replacements)?
|
||||
{
|
||||
bail!("packaged release publication lost current-head compare-and-swap");
|
||||
}
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
pub fn load_epiphany_packaged_release(
|
||||
store: &Path,
|
||||
runtime_id: &str,
|
||||
release_id: &str,
|
||||
) -> Result<Option<EpiphanyPackagedReleaseEntry>> {
|
||||
open_epiphany_cultmesh_node(store, runtime_id.to_string())?
|
||||
.get(&format!("{RELEASE_KEY_PREFIX}{release_id}"))
|
||||
}
|
||||
|
||||
pub fn load_epiphany_packaged_release_head(
|
||||
store: &Path,
|
||||
runtime_id: &str,
|
||||
) -> Result<Option<EpiphanyPackagedReleaseHead>> {
|
||||
open_epiphany_cultmesh_node(store, runtime_id.to_string())?.get(RELEASE_HEAD_KEY)
|
||||
}
|
||||
|
||||
pub fn authenticate_epiphany_packaged_release(
|
||||
store: &Path,
|
||||
runtime_id: &str,
|
||||
release_id: &str,
|
||||
expected_witness_sha256: &str,
|
||||
) -> Result<EpiphanyPackagedReleaseEntry> {
|
||||
validate_sha256(expected_witness_sha256)?;
|
||||
let entry = load_epiphany_packaged_release(store, runtime_id, release_id)?
|
||||
.context("packaged release witness is absent")?;
|
||||
validate_epiphany_packaged_release(&entry)?;
|
||||
verify_epiphany_packaged_release_files(&entry)?;
|
||||
if witness_sha256(&entry)? != expected_witness_sha256 {
|
||||
bail!("packaged release witness digest disagrees with pinned task authority");
|
||||
}
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
pub fn epiphany_packaged_release_witness_sha256(
|
||||
entry: &EpiphanyPackagedReleaseEntry,
|
||||
) -> Result<String> {
|
||||
witness_sha256(entry)
|
||||
}
|
||||
|
||||
pub fn epiphany_packaged_release_binary_path(
|
||||
entry: &EpiphanyPackagedReleaseEntry,
|
||||
role: &str,
|
||||
) -> Result<PathBuf> {
|
||||
entry
|
||||
.binaries
|
||||
.iter()
|
||||
.find(|binary| binary.role == role)
|
||||
.map(|binary| PathBuf::from(&binary.canonical_path))
|
||||
.with_context(|| format!("packaged release lacks required role {role}"))
|
||||
}
|
||||
|
||||
fn clean_source_commit(repo: &Path) -> Result<(String, String)> {
|
||||
let head = git(repo, &["rev-parse", "HEAD"])?;
|
||||
validate_commit(&head)?;
|
||||
if !git(repo, &["status", "--porcelain=v1", "--untracked-files=no"])?.is_empty() {
|
||||
bail!("packaged release requires a clean tracked source commit");
|
||||
}
|
||||
if git(repo, &["submodule", "status", "--recursive"])?
|
||||
.lines()
|
||||
.any(|line| line.starts_with(['-', '+', 'U']))
|
||||
{
|
||||
bail!("packaged release requires clean initialized submodules");
|
||||
}
|
||||
let committed_at = git(repo, &["show", "-s", "--format=%cI", "HEAD"])?;
|
||||
DateTime::parse_from_rfc3339(&committed_at).context("Git commit time is not RFC3339")?;
|
||||
Ok((head, committed_at))
|
||||
}
|
||||
|
||||
fn git(repo: &Path, args: &[&str]) -> Result<String> {
|
||||
let output = std::process::Command::new("git")
|
||||
.args(args)
|
||||
.current_dir(repo)
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
bail!(
|
||||
"git {} failed: {}",
|
||||
args.join(" "),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
Ok(String::from_utf8(output.stdout)?.trim().to_string())
|
||||
}
|
||||
|
||||
fn binary_record(
|
||||
role: &str,
|
||||
file_name: &str,
|
||||
path: &Path,
|
||||
) -> Result<EpiphanyPackagedReleaseBinary> {
|
||||
let canonical = fs::canonicalize(path)?;
|
||||
let len = fs::metadata(&canonical)?.len();
|
||||
if len == 0 {
|
||||
bail!("required packaged sibling is empty: {file_name}");
|
||||
}
|
||||
Ok(EpiphanyPackagedReleaseBinary {
|
||||
role: role.into(),
|
||||
file_name: file_name.into(),
|
||||
canonical_path: canonical.display().to_string(),
|
||||
sha256: file_sha256(&canonical)?,
|
||||
byte_len: len,
|
||||
})
|
||||
}
|
||||
|
||||
fn release_id(
|
||||
runtime: &str,
|
||||
commit: &str,
|
||||
target: &str,
|
||||
toolchain: &str,
|
||||
binaries: &[EpiphanyPackagedReleaseBinary],
|
||||
) -> String {
|
||||
let mut hash = Sha256::new();
|
||||
for value in [
|
||||
"epiphany.packaged-release.identity.v0",
|
||||
runtime,
|
||||
commit,
|
||||
target,
|
||||
"release",
|
||||
toolchain,
|
||||
] {
|
||||
hash.update((value.len() as u64).to_be_bytes());
|
||||
hash.update(value.as_bytes());
|
||||
}
|
||||
for binary in binaries {
|
||||
for value in [
|
||||
&binary.role,
|
||||
&binary.file_name,
|
||||
&binary.sha256,
|
||||
&binary.byte_len.to_string(),
|
||||
] {
|
||||
hash.update((value.len() as u64).to_be_bytes());
|
||||
hash.update(value.as_bytes());
|
||||
}
|
||||
}
|
||||
format!("sha256-{:x}", hash.finalize())
|
||||
}
|
||||
|
||||
fn witness_sha256(entry: &EpiphanyPackagedReleaseEntry) -> Result<String> {
|
||||
Ok(format!(
|
||||
"sha256-{:x}",
|
||||
Sha256::digest(rmp_serde::to_vec(entry)?)
|
||||
))
|
||||
}
|
||||
fn file_sha256(path: &Path) -> Result<String> {
|
||||
Ok(format!("sha256-{:x}", Sha256::digest(fs::read(path)?)))
|
||||
}
|
||||
fn validate_sha256(value: &str) -> Result<()> {
|
||||
if value.len() != 71
|
||||
|| !value.starts_with("sha256-")
|
||||
|| !value[7..]
|
||||
.bytes()
|
||||
.all(|b| b.is_ascii_hexdigit() && !b.is_ascii_uppercase())
|
||||
{
|
||||
bail!("invalid SHA-256 digest");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn validate_commit(value: &str) -> Result<()> {
|
||||
if value.len() != 40 || !value.bytes().all(|b| b.is_ascii_hexdigit()) {
|
||||
bail!("source commit must be a full 40-hex Git object id");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn require_nonempty(label: &str, value: &str) -> Result<()> {
|
||||
if value.trim().is_empty() {
|
||||
return Err(anyhow!("{label} must not be empty"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn fixture() -> (TempDir, EpiphanyPackagedReleaseEntry) {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let root = dir.path().join("release");
|
||||
fs::create_dir(&root).unwrap();
|
||||
let mut binaries = Vec::new();
|
||||
for (index, (role, name)) in required_packaged_release_binaries().into_iter().enumerate() {
|
||||
let path = root.join(name);
|
||||
fs::write(&path, format!("binary-{index}")).unwrap();
|
||||
binaries.push(binary_record(role, name, &path).unwrap());
|
||||
}
|
||||
binaries.sort_by(|a, b| a.role.cmp(&b.role));
|
||||
let id = release_id("runtime", &"a".repeat(40), "target", "rustc", &binaries);
|
||||
let entry = EpiphanyPackagedReleaseEntry {
|
||||
schema_version: EPIPHANY_PACKAGED_RELEASE_SCHEMA_VERSION.into(),
|
||||
release_id: id,
|
||||
runtime_id: "runtime".into(),
|
||||
source_commit_sha: "a".repeat(40),
|
||||
target_triple: "target".into(),
|
||||
cargo_profile: "release".into(),
|
||||
toolchain_fingerprint: "rustc".into(),
|
||||
created_at_utc: Utc::now().to_rfc3339(),
|
||||
package_root: fs::canonicalize(root).unwrap().display().to_string(),
|
||||
binaries,
|
||||
private_state_exposed: false,
|
||||
};
|
||||
(dir, entry)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_fixture_verifies() {
|
||||
let (_d, e) = fixture();
|
||||
verify_epiphany_packaged_release_files(&e).unwrap();
|
||||
}
|
||||
#[test]
|
||||
fn one_byte_replacement_is_rejected() {
|
||||
let (_d, e) = fixture();
|
||||
fs::write(&e.binaries[0].canonical_path, "hostile").unwrap();
|
||||
assert!(verify_epiphany_packaged_release_files(&e).is_err());
|
||||
}
|
||||
#[test]
|
||||
fn extra_sibling_is_rejected() {
|
||||
let (_d, e) = fixture();
|
||||
fs::write(Path::new(&e.package_root).join("stowaway.exe"), "x").unwrap();
|
||||
assert!(verify_epiphany_packaged_release_files(&e).is_err());
|
||||
}
|
||||
#[test]
|
||||
fn swapped_role_is_rejected() {
|
||||
let (_d, mut e) = fixture();
|
||||
let role = e.binaries[0].role.clone();
|
||||
e.binaries[0].role = e.binaries[1].role.clone();
|
||||
e.binaries[1].role = role;
|
||||
assert!(validate_epiphany_packaged_release(&e).is_err());
|
||||
}
|
||||
#[test]
|
||||
fn counterfeit_release_id_is_rejected() {
|
||||
let (_d, mut e) = fixture();
|
||||
e.release_id = format!("sha256-{}", "0".repeat(64));
|
||||
assert!(validate_epiphany_packaged_release(&e).is_err());
|
||||
}
|
||||
}
|
||||
|
|
@ -59,23 +59,31 @@ VoidBot's ordinary spine is not the rotten part. Its durable lessons are:
|
|||
|
||||
## Current Mechanism
|
||||
|
||||
Epiphany currently has the right ingredients, but not yet the final live swarm
|
||||
shape:
|
||||
Epiphany owns its state, organ scheduler, workers, process policy, and local
|
||||
operator surfaces in native Rust and typed CultCache/CultMesh documents. Codex
|
||||
survives only as the retained OpenAI subscription-auth/model-transport bridge;
|
||||
it no longer owns Epiphany routes, prompts, scheduling, or state mutation.
|
||||
|
||||
- `tools/epiphany_local_run.ps1` can build and smoke the compatibility shell.
|
||||
- `epiphany-runtime-spine` owns typed runtime identity/session/job/result/event
|
||||
documents and advertises CultNet contracts.
|
||||
- heartbeat state owns role dossiers, initiative, heat, active-turn freeze,
|
||||
sleep, rumination, appraisals, and derived reactions.
|
||||
- role memory accepts reviewed `selfPatch` petitions into typed Ghostlight-like
|
||||
dossiers.
|
||||
- coordinator/CRRC/role/status surfaces exist, but the practical local runner
|
||||
still goes through Codex app-server JSON-RPC for operator reads/actions.
|
||||
- Persona, Discord, Rider, Unity, Void memory, and repo birth bridges are typed
|
||||
surfaces or artifacts, but they are not yet an integrated live swarm loop.
|
||||
- Runtime spine, Mind gateway, Substrate Gate, Eyes, Hands, Soul, Modeling,
|
||||
Continuity, heartbeat, and Persona operations have typed intent/review/receipt
|
||||
families.
|
||||
- One heartbeat scheduler owns initiative, heat, active-turn freeze,
|
||||
completion-gated cooldown, stale-turn recovery, sleep, and rumination.
|
||||
- The local CultMesh swarm brake is typed and enforced at runner/action edges.
|
||||
- Persona public speech has parent-side repetition/saturation eligibility and
|
||||
Bifrost/Heimdall crossing gates.
|
||||
- Memory has reviewed revise/retire/crystallize/prune/merge operations; Qdrant
|
||||
remains rebuildable semantic sight rather than canonical Mind.
|
||||
- Idunn owns managed projector lifecycle and exact abandoned-claim recovery.
|
||||
Task Scheduler owns only current-user after-login Idunn presence.
|
||||
- Mind and Modeling semantic query admission reauthenticate canonical CultCache
|
||||
authority at query time against the Yggdrasil-hosted Qdrant/Ollama substrate.
|
||||
|
||||
The machine is pointed toward live use. It is not cleared for unattended swarm
|
||||
operation.
|
||||
The installed host is operational after login, but its mutable release directory
|
||||
does not yet prove that supervisor, projectors, and query gate are one source
|
||||
generation. Whole-repository readiness also lacks its Mind-owned race-bounded
|
||||
join. Those are the remaining non-permission architecture gates. A real reboot/
|
||||
logon survival proof remains a separate operator-authorized boundary.
|
||||
|
||||
## Invariants
|
||||
|
||||
|
|
@ -96,66 +104,27 @@ operation.
|
|||
- No broad live scheduler until the local runner can show the typed surfaces
|
||||
that explain what it would do and why.
|
||||
|
||||
## Readiness Gate
|
||||
## Readiness Evidence Matrix
|
||||
|
||||
Before live fire, Epiphany needs these gates closed:
|
||||
This matrix is the live gate. A green row names its authority and proof; it is
|
||||
not permission for another row to borrow the result.
|
||||
|
||||
1. **Operator Run Path**
|
||||
- Keep `tools/epiphany_local_run.ps1` as the human entrypoint for now.
|
||||
- It must keep printing a concise verdict.
|
||||
- It must keep artifacts operator-safe by default.
|
||||
- It must make Codex app-server dependency obvious as compatibility, not
|
||||
architecture.
|
||||
| Gate | Owner | Current evidence | State |
|
||||
| --- | --- | --- | --- |
|
||||
| Typed brake and initiative physiology | heartbeat/CultMesh | brake, completion cooldown, and stale-turn recovery tests and receipts | closed |
|
||||
| Reviewed Mind and organ-state mutation | Mind gateway and organ services | typed review/commit/lifecycle families; no whole-state model write path | closed |
|
||||
| Public speech/crossing eligibility | Persona + Bifrost/Heimdall | speech-audit receipts and fail-closed identity/capability checks | closed |
|
||||
| Branch-local autonomous action | Substrate Gate + Hands + Soul + Mind | scoped grants and patch/command/commit/verdict/admission chains | closed |
|
||||
| Semantic projection/query recovery | canonical admission + projector + query gate + Idunn | live Mind/Modeling semantic ranking and GUID-scoped exact process-recovery smokes | closed |
|
||||
| Atomic installed sibling generation | release packager + Idunn | no immutable commit-addressed package witness yet; mutable sibling directory can mix builds | open |
|
||||
| Whole-repository observed readiness | Mind | required Body R1/R2 + admitted model + live semantic + live workspace-coverage join does not yet exist | open |
|
||||
| Current host package | release deployment | installed supervisor/projectors predate current recovery repairs | open |
|
||||
| After-login recurrence | Task Scheduler + Idunn + ops tunnel | task recurrence and exact child recovery observed on current boot | closed for current boot |
|
||||
| Reboot/logon survival | Task Scheduler + Idunn + ops tunnel | requires fresh post-boot lineage and query proof | permission-bound |
|
||||
|
||||
2. **Typed Swarm Brake**
|
||||
- Add an Epiphany-owned pause document for swarm/heartbeat/live Persona
|
||||
operation.
|
||||
- Runners must fail closed when the document is missing, malformed, or
|
||||
paused for a teardown reason, according to the surface being protected.
|
||||
- The brake must be inspectable through Aquarium/status.
|
||||
|
||||
3. **Initiative Scheduler Boundary**
|
||||
- Keep one heartbeat scheduler for lanes/Personas/system organs.
|
||||
- Heat is a multiplier on recovery pressure, not a time machine.
|
||||
- Active-turn freeze and completion-gated cooldown are hard invariants.
|
||||
- Stale active-turn recovery must write typed receipts.
|
||||
|
||||
4. **Memory Lifecycle**
|
||||
- Keep role-local memory writes as reviewed `selfPatch` operations.
|
||||
- Add explicit lifecycle operations where missing: revise, retire,
|
||||
crystallize, prune short-term residue, and merge incubation support.
|
||||
- Sleep/maintenance may propose lifecycle operations; it must not silently
|
||||
rewrite role identity.
|
||||
|
||||
5. **Public Persona Safety**
|
||||
- Persona speech eligibility must be parent-side state policy, not prompt hope.
|
||||
- Track recent outputs, repeated topics, repeated openings, target/channel
|
||||
saturation, and action kind.
|
||||
- Work-shaped asks route to coordinator/Bifrost-style governance; banter
|
||||
does not secretly become work dispatch.
|
||||
|
||||
6. **CultNet Operator Surface**
|
||||
- Move the local status/coordinator loop toward native CultNet snapshots,
|
||||
intents, and receipts.
|
||||
- Codex JSON-RPC may remain as the compatibility projection while the wall is
|
||||
being evacuated.
|
||||
- The next run path should consume typed documents first and only use
|
||||
app-server JSON-RPC where no native route exists yet.
|
||||
|
||||
7. **Audit And Review**
|
||||
- Every automatic or semi-automatic action must produce a typed receipt.
|
||||
- Semantic findings remain review-gated.
|
||||
- Operator artifacts must seal direct thought, raw transcripts, and long
|
||||
prompt-shaped notes by default.
|
||||
|
||||
8. **Autonomous Branch Work**
|
||||
- A repo swarm must own a git branch/worktree as its sacrificial workbench.
|
||||
- Hands may edit, run checks, and commit autonomously inside that branch when
|
||||
Substrate Gate, Hands, Soul, and Mind receipts support the work.
|
||||
- Bifrost gates PR/upstream publication and credit; it does not gate every
|
||||
local branch edit.
|
||||
- Cross-body collaboration must travel through Odin/Eve/CultMesh/Bifrost
|
||||
contracts instead of direct workspace rummaging.
|
||||
`providerStatus`, heartbeats, Qdrant collection existence, task state, watcher
|
||||
silence, legacy retrieval Ready, Eve/Gjallar rows, and empty dirty-path lists are
|
||||
never substitutes for the Mind readiness join or query admission.
|
||||
|
||||
## Ranked Next Cuts
|
||||
|
||||
|
|
@ -195,16 +164,16 @@ Rebuild:
|
|||
|
||||
## Immediate Work Queue
|
||||
|
||||
0. Land the repo-swarm MVP contract and make repo initialization create a
|
||||
branch-local autonomous workbench by default.
|
||||
1. Add typed swarm pause/brake state and status projection.
|
||||
2. Add stale active-turn recovery receipts to heartbeat state if the current
|
||||
heartbeat store cannot prove recovery cleanly.
|
||||
3. Add a Persona output audit surface before increasing public speech cadence.
|
||||
4. Add memory lifecycle operations that match VoidBot's useful pattern without
|
||||
porting its TypeScript state shape wholesale.
|
||||
5. Move one local-run read path from Codex JSON-RPC compatibility to CultNet
|
||||
snapshot/receipt consumption.
|
||||
1. Build and authenticate one immutable, commit-addressed sibling release. Task
|
||||
installation and Idunn must consume that witness and reject mixed binaries.
|
||||
2. Implement Mind's race-bounded `RepositoryReadinessProjection`, including a
|
||||
live workspace-coverage Qdrant evidence reader; stored `Current` is not enough.
|
||||
3. Deploy the witnessed release, replace the stale running reconciler/projector,
|
||||
and repeat exact recurrence plus Mind/Modeling query admission proofs.
|
||||
4. With explicit live operator approval, reboot and prove the fresh Task
|
||||
Scheduler -> Idunn -> exactly-one-projector lineage plus tunnel and query
|
||||
admission. Do not silently widen after-login authority into boot service
|
||||
authority.
|
||||
|
||||
The correct posture is patient pressure. We are not rushing to live fire. We are
|
||||
aiming the barrel, checking the chamber, and making sure the thing does not try
|
||||
|
|
|
|||
|
|
@ -1964,3 +1964,32 @@ projection test passed before the recovery smoke.
|
|||
Next: audit deployability against the swarm readiness plan and live host state.
|
||||
Do not conflate code-complete physiology with the permission-bound reboot/logon
|
||||
proof; no reboot without explicit live operator approval.
|
||||
|
||||
## Fresh deployability audit (2026-07-16)
|
||||
|
||||
The host is operational after current-user login, not yet deployable as one
|
||||
proven artifact generation. `GameCult-Yggdrasil-Tunnel` and
|
||||
`Epiphany-Idunn-Managed-Service-Reconciler` are enabled/running; the Yggdrasil
|
||||
Qdrant tunnel and Ollama endpoint are live; packaged Mind and Modeling queries
|
||||
return semantic ranking. Local retired Qdrant remains stopped. This is real
|
||||
current-boot physiology.
|
||||
|
||||
Four non-permission wounds remain. Installed supervisor/projector binaries
|
||||
predate recovery commit `34802407`; Task Scheduler pins one mutable supervisor
|
||||
path while Idunn discovers siblings by filename/existence, so mixed generations
|
||||
are possible; Mind has no race-bounded whole-repository readiness join; and the
|
||||
old readiness plan described already-landed June work as future work. The plan
|
||||
now contains the live evidence matrix. Packaging must become an immutable typed
|
||||
commit-addressed release witness consumed by Task Scheduler and Idunn. Mind
|
||||
readiness must join fresh Body R1/R2, exact Body-grounded RepoModel admission,
|
||||
live Modeling semantic eligibility, and live workspace-coverage Qdrant evidence.
|
||||
Stored coverage `Current` is not sufficient.
|
||||
|
||||
The constant `queryAdmission=false` and supervisor fields naming an external
|
||||
readiness authority are removed from sight-only projector/health/status
|
||||
projections. Those surfaces never evaluated query admission and therefore have
|
||||
no opinion. Query-time reauthentication remains the semantic admission owner.
|
||||
|
||||
After the witnessed release is deployed, prove exact current-boot recurrence
|
||||
and Mind/Modeling query ranking. A reboot/logon proof remains the sole
|
||||
permission-bound host gate; do not reboot without explicit live approval.
|
||||
|
|
|
|||
|
|
@ -21,13 +21,26 @@ embedding work over WireGuard, but moving only the projector to Yggdrasil would
|
|||
split local store authority. Do not install a second projector on Yggdrasil or
|
||||
one process per partition.
|
||||
|
||||
## Build
|
||||
## Package one source generation
|
||||
|
||||
```powershell
|
||||
$env:CARGO_TARGET_DIR='C:\Users\Meta\.cargo-target-codex'
|
||||
cargo build --release --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-memory-semantic-projector --bin epiphany-memory-semantic --bin epiphany-daemon-supervisor
|
||||
cargo build --release --manifest-path .\epiphany-core\Cargo.toml --bin epiphany-release
|
||||
& "$env:CARGO_TARGET_DIR\release\epiphany-release.exe" package `
|
||||
--repo . `
|
||||
--destination .\.epiphany-run\releases `
|
||||
--store .\.epiphany-run\cultmesh\local-verse.ccmp `
|
||||
--runtime-id epiphany-local
|
||||
```
|
||||
|
||||
The packager refuses dirty tracked source and owns a fresh isolated Cargo release
|
||||
build of the supervisor, semantic projector, workspace-coverage projector, and
|
||||
semantic query gate. It copies exactly those four siblings into an immutable
|
||||
commit-addressed directory, verifies every byte, then atomically publishes a
|
||||
typed CultCache release witness and current head. Keep the returned `releaseId`,
|
||||
`witnessSha256`, and `packageRoot`; Task Scheduler and Idunn pin them. A mutable
|
||||
`target\release` directory is build output, not deployment authority.
|
||||
|
||||
## Transport configuration
|
||||
|
||||
The reserved typed service policy binds the non-secret transport coordinates:
|
||||
|
|
@ -65,10 +78,15 @@ REST/gRPC ports locally as `16333`/`16334`; do not restart the retired node.
|
|||
## Publish the typed service policy
|
||||
|
||||
```powershell
|
||||
$supervisor = '.\target\release\epiphany-daemon-supervisor.exe'
|
||||
$releaseRoot = '<packageRoot returned by epiphany-release>'
|
||||
$releaseId = '<releaseId>'
|
||||
$releaseDigest = '<witnessSha256>'
|
||||
$supervisor = Join-Path $releaseRoot 'epiphany-daemon-supervisor.exe'
|
||||
& $supervisor semantic-projector-service-policy `
|
||||
--store .\.epiphany-run\cultmesh\local-verse.ccmp `
|
||||
--runtime-id epiphany-local `
|
||||
--release-id $releaseId `
|
||||
--release-witness-sha256 $releaseDigest `
|
||||
--agent-store .\state\agents.msgpack `
|
||||
--runtime-store .\state\runtime-spine.msgpack `
|
||||
--qdrant-url http://127.0.0.1:16333 `
|
||||
|
|
@ -80,8 +98,8 @@ $supervisor = '.\target\release\epiphany-daemon-supervisor.exe'
|
|||
This command writes the fixed
|
||||
`epiphany-memory-semantic-projector-service` managed-service policy with
|
||||
`restartMode=always`. Its generated child command contains both canonical
|
||||
stores. The projector executable is derived as the packaged sibling of the
|
||||
running supervisor and must already exist. Caller-supplied executable paths,
|
||||
stores. The projector executable is resolved from the authenticated
|
||||
`semantic-projector` role in the pinned release witness. Caller-supplied executable paths,
|
||||
service IDs, restart modes, arbitrary child arguments, and finite iteration
|
||||
limits do not own this service shape. The generic managed-service writer
|
||||
refuses the reserved service id.
|
||||
|
|
@ -101,6 +119,25 @@ publishing a pulse or heartbeat. A launch receipt proves process creation; it
|
|||
does not claim semantic readiness. The first heartbeat comes later, after the
|
||||
projector holds the OS singleton and validates both canonical inputs.
|
||||
|
||||
## Install the pinned after-login reconciler
|
||||
|
||||
```powershell
|
||||
& $supervisor managed-service-task-install `
|
||||
--store .\.epiphany-run\cultmesh\local-verse.ccmp `
|
||||
--runtime-id epiphany-local `
|
||||
--release-id $releaseId `
|
||||
--cwd . `
|
||||
--loop-interval-seconds 60
|
||||
```
|
||||
|
||||
Task installation resolves the supervisor path and witness digest from the
|
||||
typed release. The scheduled action pins both `--release-id` and
|
||||
`--release-witness-sha256`; `managed-service-serve` authenticates the complete
|
||||
four-sibling set and its own path before reconciliation, then revalidates the
|
||||
set on every pulse. Reserved projector policies must name the exact witnessed
|
||||
role paths. A task pointing at the right supervisor with a missing/wrong witness
|
||||
argument, or a release directory with one changed sibling, is drift/failure.
|
||||
|
||||
## Recovery
|
||||
|
||||
Use `semantic-recover` only with the exact abandoned claim, the immutable launch
|
||||
|
|
@ -142,7 +179,7 @@ curl.exe -fsS http://127.0.0.1:16333/collections
|
|||
Get-CimInstance Win32_Process |
|
||||
Where-Object { $_.CommandLine -match 'managed-service-serve|memory-semantic-projector' } |
|
||||
Select-Object ProcessId, ParentProcessId, CreationDate, CommandLine
|
||||
C:\Users\Meta\.cargo-target-codex\release\epiphany-daemon-supervisor.exe `
|
||||
& $supervisor `
|
||||
semantic-projector-service-status `
|
||||
--store .\.epiphany-run\cultmesh\local-verse.ccmp `
|
||||
--runtime-id epiphany-local
|
||||
|
|
@ -181,7 +218,7 @@ BM25, which is a failed deployment proof rather than semantic readiness:
|
|||
```powershell
|
||||
$env:EPIPHANY_QDRANT_URL='http://127.0.0.1:16333'
|
||||
$env:EPIPHANY_OLLAMA_BASE_URL='http://10.77.0.1:11435'
|
||||
$semantic='C:\Users\Meta\.cargo-target-codex\release\epiphany-memory-semantic.exe'
|
||||
$semantic=Join-Path $releaseRoot 'epiphany-memory-semantic.exe'
|
||||
& $semantic context --agent-store .\state\agents.msgpack --partition mind `
|
||||
--text 'current Epiphany doctrine and memory' --query-id reboot-proof-mind --budget 8
|
||||
& $semantic context --runtime-store .\state\runtime-spine.msgpack --partition modeling `
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -732,4 +732,5 @@ current_status:
|
|||
- Abandoned workspace-coverage authority now has a complete typed transfer. Recovery derives the Body store from the runtime route, reopens the authenticated Body basis, joins obligation/plan/claim to that basis, authenticates current policy plus exact old launch and immutable termination, requires a signed causal termination-to-replacement edge and the replacement's latest ready heartbeat, then performs one Body CAS over Body authority, plan, old claim/attempt, immutable recovery receipt, failed claim history, and epoch+1 successor. The receipt stores exact termination, launch, and heartbeat envelope digests and can reconstruct both CultMesh evidence and current Body authority after restart. Claim/attempt schema and evidence pairs cannot split; impossible completion-before-start timelines refuse. Idunn's reserved reconciler no longer consults generic lifecycle PID receipts for this service. It persists/reuses termination before spawn, uses the specialized bootstrap launch, waits for signed readiness, and then invokes the recovery CAS. One termination admits one replacement slot; concurrent losers are killed when launch persistence loses CAS.
|
||||
- Workspace-coverage launch, heartbeat, and termination now serialize through one typed per-launch process-evidence head. Launch initializes generation 1 as `launched`; each signed heartbeat exact-CAS advances it to `heartbeat`; host-signed termination exact-CAS seals it as `terminated`. Termination v1 accepts exact launch plus enrolled host/boot/process proof before heartbeat sequence one and records heartbeat id/digest only when the head names current signed heartbeat evidence. A heartbeat cannot advance a terminated head. This removes the pre-readiness death strand without allowing heartbeat/termination split authority.
|
||||
- The feature-gated native `epiphany-workspace-coverage-recovery-smoke` proves the real packaged supervisor/projector boundary in GUID-scoped repo/runtime/Body/Verse state. It warms local Ollama, derives only its exact fenced Qdrant collection names, publishes reserved policy, launches the initial child through fixed bootstrap, observes epoch 1 claim, kills only after exact native launch-incarnation revalidation, invokes Idunn reconcile, authenticates epoch 2 successor authority plus the immutable recovery receipt through freshly reopened stores, kills the replacement under the same identity fence, and verifies only its owned collections absent before success. Live proof `C:\Users\Meta\AppData\Local\Temp\epiphany-workspace-coverage-a577f820-b0e2-4733-b628-529ee3bdb143\proof.json` passed with receipt `2b44f28f-7c0f-4ad2-aefe-d7c8222e8642` and binds the executed binary, exact embedded smoke source, source head, and tracked-source diff digests. Operator orchestration, not the harness, restored local Qdrant to its prior stopped state. The run also repaired normal-user Windows boot identity and exact recovered-owner claim resumption.
|
||||
next_action: Perform a fresh deployability audit against `notes/epiphany-swarm-readiness-plan.md`, the live machine map, packaged binaries, Task Scheduler presence, local dependency configuration, and current tests. Separate code-complete physiology from the permission-bound reboot/logon survival proof; do not reboot without explicit live operator approval. Cut any remaining non-permission architectural wound before calling the swarm deployable.
|
||||
- The 2026-07-16 deployability audit replaced `notes/epiphany-swarm-readiness-plan.md`'s obsolete June queue with the live evidence matrix. Brake, initiative recovery, Persona speech eligibility, memory lifecycle, CultNet-first operation, and branch-local Hands/Soul/Mind gates are landed. The installed after-login host is operational and real Mind/Modeling semantic queries pass against Yggdrasil, but it is not yet deployable as one proven machine: installed binaries predate recovery commit `34802407`; mutable sibling lookup permits mixed build generations; Mind lacks the race-bounded whole-repository readiness join; and reboot/logon lineage remains permission-bound. Health, heartbeat, Task Scheduler state, Qdrant existence, legacy retrieval Ready, and Eve/Gjallar rows cannot substitute for either query admission or repository readiness. Sight surfaces no longer publish the foreign constant `queryAdmission=false`; they report only facts they observe.
|
||||
next_action: Build a native immutable commit-addressed sibling release witness and make Idunn/Task Scheduler consume it before replacing the stale installed package. Then implement Mind's repository-readiness join with fresh Body R1/R2, exact admitted RepoModel, live Modeling semantic eligibility, and live workspace-coverage Qdrant evidence. Deploy and prove current-boot recurrence/query behavior; do not run the final reboot/logon proof without explicit live operator approval.
|
||||
|
|
|
|||
|
|
@ -186,3 +186,13 @@ replacement could not resume its own recovered running claim, so exact v3 owner
|
|||
resumption is allowed while all foreign incarnations remain contended.
|
||||
|
||||
Next: full deployability audit. Reboot/logon proof still requires permission.
|
||||
|
||||
## Deployability audit cut (2026-07-16)
|
||||
|
||||
Operational after login, not artifact-clean. Yggdrasil Qdrant/Ollama and both
|
||||
packaged semantic query gates pass. Installed siblings predate `34802407` and
|
||||
are mutable/mixable. Build one immutable commit-addressed release witness before
|
||||
replacement. Mind still needs the R1/model+admission/live-semantic/live-coverage/
|
||||
R2 repository-readiness join. The readiness plan's June queue was stale and is
|
||||
now an evidence matrix. Sight surfaces no longer say `queryAdmission=false`;
|
||||
they never owned that decision. Final reboot/logon proof needs live approval.
|
||||
|
|
|
|||
Reference in a new issue