Give Atlas publisher a durable identity lifecycle
This commit is contained in:
parent
c6857e48d9
commit
7b385b0445
4 changed files with 57 additions and 3 deletions
|
|
@ -7,7 +7,10 @@ use cultcache_rs::{
|
|||
CacheBackingStore, CultCache, CultCacheEnvelope, DatabaseEntry,
|
||||
SingleFileMessagePackBackingStore,
|
||||
};
|
||||
use cultnet_rs::{ServiceIdentityProfile, ServiceIdentityTrustAnchor, enroll_service_identity_at};
|
||||
use cultnet_rs::{
|
||||
ServiceIdentityProfile, ServiceIdentitySigner, ServiceIdentityTrustAnchor,
|
||||
enroll_service_identity_at, export_service_identity_trust_anchor, open_service_identity_at,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
@ -39,6 +42,7 @@ pub struct AtlasPublisherRunConfig {
|
|||
pub publisher_store: PathBuf,
|
||||
pub publisher_cultmesh_store: PathBuf,
|
||||
pub identity_store: PathBuf,
|
||||
pub trust_anchor_store: PathBuf,
|
||||
pub odin_endpoint: SocketAddr,
|
||||
pub now_unix_ms: u64,
|
||||
}
|
||||
|
|
@ -248,6 +252,7 @@ pub fn run_atlas_publisher_once(
|
|||
&config.publisher_store,
|
||||
&config.publisher_cultmesh_store,
|
||||
&config.identity_store,
|
||||
&config.trust_anchor_store,
|
||||
])?;
|
||||
if let Some(brake_id) = atlas_brake_id(
|
||||
&config.local_verse_store,
|
||||
|
|
@ -263,8 +268,8 @@ pub fn run_atlas_publisher_once(
|
|||
{
|
||||
bail!("Atlas publisher repository/runtime differs from its current Body basis")
|
||||
}
|
||||
let signer =
|
||||
enroll_service_identity_at::<AtlasRepositorySigningIdentity>(&config.identity_store)?;
|
||||
let signer = open_or_enroll_atlas_repository_identity(&config.identity_store)?;
|
||||
export_service_identity_trust_anchor(&signer, &config.trust_anchor_store)?;
|
||||
let store = AtlasCultCacheStore::new(&config.publisher_store, config.repository.clone())?;
|
||||
ensure_local_publisher_trust(&store, &signer, config.now_unix_ms)?;
|
||||
let heartbeat_sequence = next_heartbeat_sequence(&store)?;
|
||||
|
|
@ -319,6 +324,16 @@ pub fn run_atlas_publisher_once(
|
|||
})
|
||||
}
|
||||
|
||||
fn open_or_enroll_atlas_repository_identity(
|
||||
identity_store: &Path,
|
||||
) -> Result<ServiceIdentitySigner<AtlasRepositorySigningIdentity>> {
|
||||
if identity_store.exists() {
|
||||
open_service_identity_at::<AtlasRepositorySigningIdentity>(identity_store)
|
||||
} else {
|
||||
enroll_service_identity_at::<AtlasRepositorySigningIdentity>(identity_store)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_atlas_projector_once(
|
||||
config: &AtlasProjectorRunConfig,
|
||||
) -> Result<AtlasProjectorRunOutcome> {
|
||||
|
|
@ -1406,4 +1421,24 @@ mod tests {
|
|||
assert!(stale_source.load_local_atlas_mind_snapshot().is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn publisher_identity_reopens_and_exports_one_stable_public_anchor() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let identity_store = temp.path().join("publisher-identity.cc");
|
||||
let trust_anchor_store = temp.path().join("publisher-trust-anchor.cc");
|
||||
|
||||
let enrolled = open_or_enroll_atlas_repository_identity(&identity_store)?;
|
||||
let first_anchor = export_service_identity_trust_anchor(&enrolled, &trust_anchor_store)?;
|
||||
let reopened = open_or_enroll_atlas_repository_identity(&identity_store)?;
|
||||
let replayed_anchor = export_service_identity_trust_anchor(&reopened, &trust_anchor_store)?;
|
||||
|
||||
assert_eq!(enrolled.entry(), reopened.entry());
|
||||
assert_eq!(first_anchor, replayed_anchor);
|
||||
assert_eq!(
|
||||
read_atlas_repository_trust_anchor(&trust_anchor_store)?,
|
||||
first_anchor
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ fn main() -> Result<()> {
|
|||
publisher_store: path(&args, "--publisher-store")?,
|
||||
publisher_cultmesh_store: path(&args, "--cultmesh-store")?,
|
||||
identity_store: path(&args, "--identity-store")?,
|
||||
trust_anchor_store: path(&args, "--trust-anchor-store")?,
|
||||
odin_endpoint: required(&args, "--odin-endpoint")?.parse()?,
|
||||
now_unix_ms: now_unix_ms()?,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4753,3 +4753,12 @@ presentation-only; domain mutation routes back to the owning repository's
|
|||
Modeling interface. Brake scopes `atlas.publish`, `atlas.project`, and
|
||||
`atlas.impact_ingress` freeze writes and scheduling while preserving the last
|
||||
projection as visibly stale read-only state.
|
||||
|
||||
The repository publisher also owns the lifecycle of its signing identity.
|
||||
Its private identity store is enrolled only when absent and reopened exactly on
|
||||
later cycles; retries cannot rotate or replace it. Every unbraked cycle exports
|
||||
the same public trust anchor idempotently to a physically separate store for
|
||||
operator-gated projector pinning. The projector never reads the private store,
|
||||
and an existing divergent public anchor refuses instead of being repaired.
|
||||
This cut is required for both the second publisher heartbeat and real trust
|
||||
enrollment; one-shot identity creation is not a viable daemon lifecycle.
|
||||
|
|
|
|||
|
|
@ -47,6 +47,15 @@ publishers/projector establish signed sight; then engage all three Atlas brakes
|
|||
and prove later organ cycles are held while the last Eve projection remains
|
||||
visibly stale/read-only and no lane wakes.
|
||||
|
||||
Gate 1 preflight exposed and cut one publisher lifecycle defect before live
|
||||
evidence was admitted. The publisher now enrolls its private signing identity
|
||||
only when absent, reopens that exact identity on later heartbeats, and exports
|
||||
one idempotent public trust anchor to a separate operator-readable store. This
|
||||
keeps identity rotation impossible by retry and gives the projector a real
|
||||
public anchor to pin without reading private state. The focused identity
|
||||
reopen/export test passes; the operational gate remains in progress and is not
|
||||
yet accepted.
|
||||
|
||||
Public positioning was synchronized on 2026-08-16 without changing that next
|
||||
rite. `docs/positioning.md` is the current claim owner: Epiphany is the
|
||||
organizational layer above frontier AI workers; the primary failure is global
|
||||
|
|
|
|||
Reference in a new issue