Bind public Eyes evidence to exact source authority
This commit is contained in:
parent
43dc865e0f
commit
8fb61ac236
18 changed files with 1037 additions and 35 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2293,6 +2293,7 @@ dependencies = [
|
|||
"rmcp 2.2.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"toml",
|
||||
|
|
|
|||
|
|
@ -201,14 +201,48 @@ pub fn accept_coordinator_role_finding(
|
|||
let mut available = vec![MIND_GATEWAY_REVIEW_TYPE.to_string()];
|
||||
let mut prerequisites = Vec::new();
|
||||
if role_id == EpiphanyRoleResultRoleId::Research {
|
||||
let public_source_lookups = runtime_authenticated_public_source_lookups_for_worker(
|
||||
store,
|
||||
finding.runtime_job_id.as_deref().unwrap_or_default(),
|
||||
)?;
|
||||
for lookup in &public_source_lookups {
|
||||
if !finding
|
||||
.evidence_ids
|
||||
.iter()
|
||||
.any(|evidence_id| evidence_id == &lookup.receipt_id)
|
||||
|| !finding
|
||||
.files_inspected
|
||||
.iter()
|
||||
.any(|source_ref| source_ref == &lookup.source_ref)
|
||||
{
|
||||
return Err(anyhow::anyhow!(
|
||||
"Research finding did not cite its authenticated public source receipt and source ref"
|
||||
));
|
||||
}
|
||||
}
|
||||
if finding
|
||||
.evidence_ids
|
||||
.iter()
|
||||
.any(|evidence_id| evidence_id.starts_with("eyes-source-"))
|
||||
&& public_source_lookups.is_empty()
|
||||
{
|
||||
return Err(anyhow::anyhow!(
|
||||
"Research finding cited public source evidence without an authenticated lookup"
|
||||
));
|
||||
}
|
||||
let packet = eyes_evidence_packet_from_research_finding(
|
||||
format!("eyes-packet-{}", update.accepted_receipt_id),
|
||||
&finding,
|
||||
&update.applied_patch,
|
||||
&public_source_lookups,
|
||||
accepted_at.clone(),
|
||||
);
|
||||
available.push(EYES_EVIDENCE_PACKET_TYPE.to_string());
|
||||
prerequisites.push(EpiphanyAcceptancePrerequisite::Eyes(packet));
|
||||
for lookup in public_source_lookups {
|
||||
available.push(EYES_SOURCE_LOOKUP_RECEIPT_TYPE.to_string());
|
||||
prerequisites.push(EpiphanyAcceptancePrerequisite::EyesSourceLookup(lookup));
|
||||
}
|
||||
let grant_id = format!(
|
||||
"substrate-grant-{}",
|
||||
finding.runtime_job_id.as_deref().unwrap_or_default()
|
||||
|
|
@ -609,6 +643,7 @@ pub fn read_accepted_coordinator_state(
|
|||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EpiphanyAcceptancePrerequisite {
|
||||
Eyes(EyesEvidencePacket),
|
||||
EyesSourceLookup(EyesSourceLookupReceipt),
|
||||
SubstrateGate(SubstrateGateRepoAccessGrantReceipt),
|
||||
Soul(SoulVerdictReceipt),
|
||||
Continuity(ContinuityRecoveryReceipt),
|
||||
|
|
@ -641,6 +676,19 @@ fn commit_state_with_mind_witness(
|
|||
store_path,
|
||||
expected_state,
|
||||
)?;
|
||||
for prerequisite in prerequisites {
|
||||
if let EpiphanyAcceptancePrerequisite::EyesSourceLookup(document) = prerequisite {
|
||||
let authenticated = crate::runtime_spine::authenticated_public_source_lookups_for_worker(
|
||||
&cache,
|
||||
&document.source_job_id,
|
||||
)?;
|
||||
if !authenticated.iter().any(|candidate| candidate == document) {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Eyes source lookup lost its authenticated authority before Mind commit"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
let (review_envelope, _) = cache.prepare_entry(&mind_review.gateway_id, mind_review)?;
|
||||
let (commit_envelope, _) = cache.prepare_entry(&commit_receipt.receipt_id, commit_receipt)?;
|
||||
let mut batch = vec![review_envelope, commit_envelope];
|
||||
|
|
@ -649,6 +697,9 @@ fn commit_state_with_mind_witness(
|
|||
EpiphanyAcceptancePrerequisite::Eyes(document) => {
|
||||
cache.prepare_entry(&document.packet_id, document)?.0
|
||||
}
|
||||
EpiphanyAcceptancePrerequisite::EyesSourceLookup(document) => {
|
||||
cache.prepare_entry(&document.receipt_id, document)?.0
|
||||
}
|
||||
EpiphanyAcceptancePrerequisite::SubstrateGate(document) => {
|
||||
cache.prepare_entry(&document.receipt_id, document)?.0
|
||||
}
|
||||
|
|
@ -1067,6 +1118,7 @@ mod tests {
|
|||
evidence_ids: Vec::new(),
|
||||
observation_ids: Vec::new(),
|
||||
source_refs: Vec::new(),
|
||||
source_lookup_receipt_ids: Vec::new(),
|
||||
summary: "looked".to_string(),
|
||||
uncertainty: "none".to_string(),
|
||||
emitted_at: "2026-07-11T00:00:00Z".to_string(),
|
||||
|
|
|
|||
|
|
@ -532,7 +532,12 @@ fn commit_coordinator_job_launch_in_cache(
|
|||
}
|
||||
batch.push(cache.prepare_entry(&binding.binding_record_id, &binding)?.0);
|
||||
}
|
||||
if request.binding_id == EPIPHANY_RESEARCH_ROLE_BINDING_ID {
|
||||
if matches!(
|
||||
request.binding_id.as_str(),
|
||||
EPIPHANY_RESEARCH_ROLE_BINDING_ID
|
||||
| crate::EPIPHANY_MODELING_ROLE_BINDING_ID
|
||||
| crate::EPIPHANY_VERIFICATION_ROLE_BINDING_ID
|
||||
) {
|
||||
let grant = substrate_gate_repo_access_grant_for_launch(
|
||||
format!("substrate-grant-{}", plan.backend_job_id),
|
||||
plan.backend_job_id.clone(),
|
||||
|
|
@ -3887,11 +3892,14 @@ pub(crate) mod tests {
|
|||
.get_all::<crate::RepoFrontierHandsAuthority>()?
|
||||
.is_empty()
|
||||
);
|
||||
assert!(
|
||||
cache
|
||||
.get_all::<crate::SubstrateGateRepoAccessGrantReceipt>()?
|
||||
.is_empty()
|
||||
);
|
||||
let grants = cache.get_all::<crate::SubstrateGateRepoAccessGrantReceipt>()?;
|
||||
assert_eq!(grants.len(), 1);
|
||||
assert_eq!(grants[0].binding_id, crate::EPIPHANY_MODELING_ROLE_BINDING_ID);
|
||||
assert_eq!(grants[0].granted_operations, vec!["read", "snapshot"]);
|
||||
assert!(!grants[0]
|
||||
.granted_operations
|
||||
.iter()
|
||||
.any(|operation| operation == "publicSourceRead"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,44 @@ pub const EYES_EVIDENCE_PACKET_SCHEMA_VERSION: &str = "epiphany.eyes.evidence_pa
|
|||
pub const EYES_EVIDENCE_REFUSAL_RECEIPT_SCHEMA_VERSION: &str =
|
||||
"epiphany.eyes.evidence_refusal_receipt.v0";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "epiphany.eyes.source_lookup_receipt",
|
||||
schema = "EyesSourceLookupReceipt"
|
||||
)]
|
||||
pub struct EyesSourceLookupReceipt {
|
||||
#[cultcache(key = 0)]
|
||||
pub schema_version: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub receipt_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub source_job_id: String,
|
||||
#[cultcache(key = 3)]
|
||||
pub substrate_grant_receipt_id: String,
|
||||
#[cultcache(key = 4)]
|
||||
pub tool_intent_id: String,
|
||||
#[cultcache(key = 5)]
|
||||
pub tool_receipt_id: String,
|
||||
#[cultcache(key = 6)]
|
||||
pub provider: String,
|
||||
#[cultcache(key = 7)]
|
||||
pub repository: String,
|
||||
#[cultcache(key = 8)]
|
||||
pub revision: String,
|
||||
#[cultcache(key = 9)]
|
||||
pub path: String,
|
||||
#[cultcache(key = 10)]
|
||||
pub source_ref: String,
|
||||
#[cultcache(key = 11)]
|
||||
pub content_sha256: String,
|
||||
#[cultcache(key = 12)]
|
||||
pub byte_count: u64,
|
||||
#[cultcache(key = 13)]
|
||||
pub observed_at: String,
|
||||
#[cultcache(key = 14)]
|
||||
pub contract: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(type = "epiphany.eyes.evidence_packet", schema = "EyesEvidencePacket")]
|
||||
pub struct EyesEvidencePacket {
|
||||
|
|
@ -44,6 +82,8 @@ pub struct EyesEvidencePacket {
|
|||
pub emitted_at: String,
|
||||
#[cultcache(key = 11)]
|
||||
pub contract: String,
|
||||
#[cultcache(key = 12, default)]
|
||||
pub source_lookup_receipt_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -131,6 +171,7 @@ pub fn eyes_evidence_packet_from_research_finding(
|
|||
packet_id: String,
|
||||
finding: &EpiphanyRoleFindingInterpretation,
|
||||
patch: &EpiphanyRoleStatePatchDocument,
|
||||
source_lookups: &[EyesSourceLookupReceipt],
|
||||
emitted_at: String,
|
||||
) -> EyesEvidencePacket {
|
||||
let evidence_ids = patch
|
||||
|
|
@ -164,6 +205,9 @@ pub fn eyes_evidence_packet_from_research_finding(
|
|||
push_unique(&mut source_refs, rendered);
|
||||
}
|
||||
}
|
||||
for lookup in source_lookups {
|
||||
push_unique(&mut source_refs, lookup.source_ref.clone());
|
||||
}
|
||||
EyesEvidencePacket {
|
||||
schema_version: EYES_EVIDENCE_PACKET_SCHEMA_VERSION.to_string(),
|
||||
packet_id,
|
||||
|
|
@ -187,6 +231,10 @@ pub fn eyes_evidence_packet_from_research_finding(
|
|||
},
|
||||
emitted_at,
|
||||
contract: "Eyes packet emitted from a reviewed Research lane finding; it makes the source-gathering evidence claim citable before Mind admission.".to_string(),
|
||||
source_lookup_receipt_ids: source_lookups
|
||||
.iter()
|
||||
.map(|lookup| lookup.receipt_id.clone())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -286,6 +334,7 @@ mod tests {
|
|||
"eyes-packet-1".to_string(),
|
||||
&finding,
|
||||
&patch,
|
||||
&[],
|
||||
"2026-05-30T00:00:00Z".to_string(),
|
||||
);
|
||||
assert_eq!(packet.source_role_id, "research");
|
||||
|
|
|
|||
|
|
@ -618,6 +618,7 @@ pub use eyes_gateway::EYES_SOURCE_LOOKUP_RECEIPT_SCHEMA_VERSION;
|
|||
pub use eyes_gateway::EYES_SOURCE_LOOKUP_RECEIPT_TYPE;
|
||||
pub use eyes_gateway::EyesCultNetContract;
|
||||
pub use eyes_gateway::EyesEvidencePacket;
|
||||
pub use eyes_gateway::EyesSourceLookupReceipt;
|
||||
pub use eyes_gateway::default_eyes_cultnet_contracts;
|
||||
pub use eyes_gateway::eyes_evidence_packet_from_research_finding;
|
||||
pub use hands_gateway::*;
|
||||
|
|
@ -1111,6 +1112,7 @@ pub use runtime_spine::put_runtime_reorient_worker_result;
|
|||
pub use runtime_spine::put_runtime_role_worker_result;
|
||||
pub use runtime_spine::put_runtime_tool_execution_intent;
|
||||
pub use runtime_spine::put_runtime_tool_execution_receipt;
|
||||
pub use runtime_spine::runtime_authenticated_public_source_lookups_for_worker;
|
||||
#[cfg(test)]
|
||||
pub(crate) use runtime_spine::put_soul_verdict_receipt;
|
||||
pub use runtime_spine::put_substrate_gate_repo_access_grant_receipt;
|
||||
|
|
@ -1213,6 +1215,7 @@ pub use substrate_gate::SubstrateGateRepoAccessGrantReceipt;
|
|||
pub use substrate_gate::default_substrate_gate_cultnet_contracts;
|
||||
pub use substrate_gate::substrate_gate_coordinator_implementation_grant;
|
||||
pub use substrate_gate::substrate_gate_repo_access_grant_for_launch;
|
||||
pub use substrate_gate::substrate_gate_operation_for_governed_tool;
|
||||
pub use substrate_gate::substrate_gate_repo_work_planning_grant;
|
||||
pub use surfaces::AdmittedModelDirectionConsiderationContextProjection;
|
||||
pub use surfaces::EpiphanyAcceptanceBundle;
|
||||
|
|
|
|||
|
|
@ -1195,7 +1195,8 @@ mod tests {
|
|||
"heartbeat.scheduler",
|
||||
"coordinator.run",
|
||||
"persona.public_speech",
|
||||
"daemon.tool_invocation"
|
||||
"daemon.tool_invocation",
|
||||
"daemon.lifecycle_poke"
|
||||
]
|
||||
);
|
||||
assert!(
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use crate::eyes_gateway::EYES_EVIDENCE_REVIEW_TYPE;
|
|||
use crate::eyes_gateway::EYES_SOURCE_LOOKUP_RECEIPT_SCHEMA_VERSION;
|
||||
use crate::eyes_gateway::EYES_SOURCE_LOOKUP_RECEIPT_TYPE;
|
||||
use crate::eyes_gateway::EyesEvidencePacket;
|
||||
use crate::eyes_gateway::EyesSourceLookupReceipt;
|
||||
use crate::hands_gateway::*;
|
||||
use crate::heartbeat_state::HEARTBEAT_STATE_SCHEMA_VERSION;
|
||||
use crate::heartbeat_state::HEARTBEAT_STATE_TYPE;
|
||||
|
|
@ -358,6 +359,8 @@ pub struct EpiphanyRuntimeModelExecutionBinding {
|
|||
pub provider: String,
|
||||
#[cultcache(key = 6)]
|
||||
pub bound_at: String,
|
||||
#[cultcache(key = 7, default)]
|
||||
pub source_worker_job_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
|
||||
|
|
@ -1154,6 +1157,7 @@ pub fn runtime_spine_cache(store_path: impl AsRef<Path>) -> Result<CultCache> {
|
|||
cache.register_entry_type::<MindGatewayReview>()?;
|
||||
cache.register_entry_type::<MindStateCommitReceipt>()?;
|
||||
cache.register_entry_type::<EyesEvidencePacket>()?;
|
||||
cache.register_entry_type::<EyesSourceLookupReceipt>()?;
|
||||
cache.register_entry_type::<SubstrateGateRepoAccessGrantReceipt>()?;
|
||||
cache.register_entry_type::<HandsActionIntent>()?;
|
||||
cache.register_entry_type::<HandsActionReview>()?;
|
||||
|
|
@ -1675,6 +1679,47 @@ pub fn open_runtime_model_execution(
|
|||
require_runtime_identity_not_archived(&cache, "session", &session_options.session_id)?;
|
||||
require_runtime_identity_not_archived(&cache, "job", &job_options.job_id)?;
|
||||
require_runtime_identity_not_archived(&cache, "model-request", &model_request.request_id)?;
|
||||
let source_worker_envelopes = if let Some(worker_job_id) =
|
||||
model_request.source_worker_job_id.as_deref()
|
||||
{
|
||||
let launch = cache
|
||||
.get::<EpiphanyRuntimeWorkerLaunchRequest>(worker_job_id)?
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"model execution source worker {worker_job_id:?} has no launch request"
|
||||
)
|
||||
})?;
|
||||
if launch.job_id != worker_job_id {
|
||||
return Err(anyhow!(
|
||||
"model execution source worker launch identity disagrees"
|
||||
));
|
||||
}
|
||||
let worker_job = cache
|
||||
.get::<EpiphanyRuntimeJob>(worker_job_id)?
|
||||
.ok_or_else(|| anyhow!("model execution source worker has no runtime job"))?;
|
||||
if worker_job.role != launch.role
|
||||
|| matches!(
|
||||
worker_job.status,
|
||||
EpiphanyRuntimeJobStatus::Completed
|
||||
| EpiphanyRuntimeJobStatus::Failed
|
||||
| EpiphanyRuntimeJobStatus::Cancelled
|
||||
)
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"model execution source worker job is foreign or terminal"
|
||||
));
|
||||
}
|
||||
vec![
|
||||
cache
|
||||
.get_envelope::<EpiphanyRuntimeJob>(worker_job_id)?
|
||||
.ok_or_else(|| anyhow!("model execution lost source worker job envelope"))?,
|
||||
cache
|
||||
.get_envelope::<EpiphanyRuntimeWorkerLaunchRequest>(worker_job_id)?
|
||||
.ok_or_else(|| anyhow!("model execution lost source worker launch envelope"))?,
|
||||
]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let existing_session = cache.get::<EpiphanyRuntimeSession>(&session_options.session_id)?;
|
||||
let session = existing_session
|
||||
.clone()
|
||||
|
|
@ -1763,11 +1808,13 @@ pub fn open_runtime_model_execution(
|
|||
job_id: job.job_id.clone(),
|
||||
provider: model_request.provider.clone(),
|
||||
bound_at: bound_at.to_string(),
|
||||
source_worker_job_id: model_request.source_worker_job_id.clone(),
|
||||
};
|
||||
let identity_envelope = cache
|
||||
.get_envelope::<EpiphanyRuntimeIdentity>(RUNTIME_IDENTITY_KEY)?
|
||||
.ok_or_else(|| anyhow!("model execution lost its exact runtime identity envelope"))?;
|
||||
let mut expected = vec![identity_envelope.clone()];
|
||||
expected.extend(source_worker_envelopes.iter().cloned());
|
||||
if existing_session.is_some() {
|
||||
expected.push(
|
||||
cache
|
||||
|
|
@ -1775,7 +1822,7 @@ pub fn open_runtime_model_execution(
|
|||
.ok_or_else(|| anyhow!("model execution lost its exact session envelope"))?,
|
||||
);
|
||||
}
|
||||
let replacements = vec![
|
||||
let mut replacements = vec![
|
||||
identity_envelope,
|
||||
cache.prepare_entry(&session.session_id, &session)?.0,
|
||||
cache.prepare_entry(&job.job_id, &job)?.0,
|
||||
|
|
@ -1790,6 +1837,7 @@ pub fn open_runtime_model_execution(
|
|||
.prepare_entry(&provider_request.request_id, provider_request)?
|
||||
.0,
|
||||
];
|
||||
replacements.extend(source_worker_envelopes);
|
||||
if !runtime_spine_backing_store(store_path)?.compare_and_swap_batch(&expected, replacements)? {
|
||||
return Err(anyhow!(
|
||||
"model execution request publication lost its snapshot fence"
|
||||
|
|
@ -1843,6 +1891,8 @@ pub fn put_runtime_tool_execution_intent(
|
|||
"tool execution job {job_id:?} is foreign or terminal"
|
||||
));
|
||||
}
|
||||
let governed_source_authority =
|
||||
validate_governed_source_tool_intent(&cache, job_id, intent)?;
|
||||
if cache
|
||||
.get::<EpiphanyRuntimeToolExecutionBinding>(&intent.intent_id)?
|
||||
.is_some()
|
||||
|
|
@ -1874,6 +1924,8 @@ pub fn put_runtime_tool_execution_intent(
|
|||
job_envelope.clone(),
|
||||
];
|
||||
let mut replacements = expected.clone();
|
||||
expected.extend(governed_source_authority.iter().cloned());
|
||||
replacements.extend(governed_source_authority);
|
||||
if let Some(model_request_id) = intent.model_request_id.as_deref() {
|
||||
let model_binding = cache
|
||||
.get::<EpiphanyRuntimeModelExecutionBinding>(model_request_id)?
|
||||
|
|
@ -1892,6 +1944,15 @@ pub fn put_runtime_tool_execution_intent(
|
|||
intent.intent_id
|
||||
));
|
||||
}
|
||||
let persisted_model_request = cache
|
||||
.get::<EpiphanyModelRequest>(model_request_id)?
|
||||
.ok_or_else(|| anyhow!("model-derived tool intent lost its model request"))?;
|
||||
if persisted_model_request.source_worker_job_id != model_binding.source_worker_job_id {
|
||||
return Err(anyhow!(
|
||||
"model-derived tool intent {:?} has substituted worker provenance",
|
||||
intent.intent_id
|
||||
));
|
||||
}
|
||||
for envelope in [
|
||||
cache.get_envelope::<EpiphanyRuntimeModelExecutionBinding>(model_request_id)?,
|
||||
cache.get_envelope::<EpiphanyModelRequest>(model_request_id)?,
|
||||
|
|
@ -1971,6 +2032,7 @@ pub fn require_runtime_tool_execution_binding(
|
|||
"tool execution binding {intent_id:?} has foreign or archived ownership"
|
||||
));
|
||||
}
|
||||
let _ = validate_governed_source_tool_intent(&cache, &binding.job_id, &intent)?;
|
||||
if let Some(model_request_id) = binding.model_request_id.as_deref() {
|
||||
let model_binding = cache
|
||||
.get::<EpiphanyRuntimeModelExecutionBinding>(model_request_id)?
|
||||
|
|
@ -3527,6 +3589,260 @@ pub fn runtime_worker_process_claim(
|
|||
cache.get::<EpiphanyRuntimeWorkerProcessClaim>(&worker_process_claim_id(job_id))
|
||||
}
|
||||
|
||||
pub fn runtime_authenticated_public_source_lookups_for_worker(
|
||||
store_path: impl AsRef<Path>,
|
||||
worker_job_id: &str,
|
||||
) -> Result<Vec<EyesSourceLookupReceipt>> {
|
||||
validate_non_empty(worker_job_id, "public source worker job id")?;
|
||||
let mut cache = runtime_spine_cache(store_path)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
require_identity(&cache)?;
|
||||
authenticated_public_source_lookups_for_worker(&cache, worker_job_id)
|
||||
}
|
||||
|
||||
pub(crate) fn authenticated_public_source_lookups_for_worker(
|
||||
cache: &CultCache,
|
||||
worker_job_id: &str,
|
||||
) -> Result<Vec<EyesSourceLookupReceipt>> {
|
||||
validate_non_empty(worker_job_id, "public source worker job id")?;
|
||||
require_identity(cache)?;
|
||||
let launch = cache
|
||||
.get::<EpiphanyRuntimeWorkerLaunchRequest>(worker_job_id)?
|
||||
.ok_or_else(|| anyhow!("public source worker has no launch request"))?;
|
||||
if launch.binding_id != crate::EPIPHANY_RESEARCH_ROLE_BINDING_ID
|
||||
|| launch.role != crate::EPIPHANY_RESEARCH_OWNER_ROLE
|
||||
{
|
||||
return Err(anyhow!("public source evidence is Eyes-owned"));
|
||||
}
|
||||
let grant_id = format!("substrate-grant-{worker_job_id}");
|
||||
let grant = cache
|
||||
.get::<SubstrateGateRepoAccessGrantReceipt>(&grant_id)?
|
||||
.ok_or_else(|| anyhow!("public source worker lost its launch grant"))?;
|
||||
if grant.runtime_job_id != worker_job_id
|
||||
|| grant.binding_id != launch.binding_id
|
||||
|| grant.role != launch.role
|
||||
|| grant.authority_scope != launch.authority_scope
|
||||
|| !grant
|
||||
.granted_operations
|
||||
.iter()
|
||||
.any(|operation| operation == crate::substrate_gate::SUBSTRATE_GATE_PUBLIC_SOURCE_READ_OPERATION)
|
||||
{
|
||||
return Err(anyhow!("public source worker grant is not exact"));
|
||||
}
|
||||
let model_request_ids = cache
|
||||
.get_all::<EpiphanyRuntimeModelExecutionBinding>()?
|
||||
.into_iter()
|
||||
.filter(|binding| binding.source_worker_job_id.as_deref() == Some(worker_job_id))
|
||||
.map(|binding| binding.request_id)
|
||||
.collect::<BTreeSet<_>>();
|
||||
let mut lookups = Vec::new();
|
||||
for binding in cache.get_all::<EpiphanyRuntimeToolExecutionBinding>()? {
|
||||
let Some(model_request_id) = binding.model_request_id.as_deref() else {
|
||||
continue;
|
||||
};
|
||||
if !model_request_ids.contains(model_request_id) {
|
||||
continue;
|
||||
}
|
||||
let intent = cache
|
||||
.get::<EpiphanyToolInvocationIntent>(&tool_invocation_intent_key(
|
||||
&binding.intent_id,
|
||||
))?
|
||||
.ok_or_else(|| anyhow!("public source tool binding lost its intent"))?;
|
||||
if intent.server != "epiphany_public" || intent.tool_name != "github_file" {
|
||||
continue;
|
||||
}
|
||||
let receipt = cache
|
||||
.get::<EpiphanyToolInvocationReceipt>(&tool_invocation_receipt_key(
|
||||
&intent.intent_id,
|
||||
))?
|
||||
.ok_or_else(|| anyhow!("public source tool intent has no terminal receipt"))?;
|
||||
let _ = validate_governed_source_tool_intent(&cache, &binding.job_id, &intent)?;
|
||||
if binding.intent_id != intent.intent_id
|
||||
|| intent.model_request_id.as_deref() != Some(model_request_id)
|
||||
|| receipt.intent_id != intent.intent_id
|
||||
|| receipt.adapter != intent.adapter
|
||||
|| receipt.server != intent.server
|
||||
|| receipt.tool_name != intent.tool_name
|
||||
|| receipt.status != "completed"
|
||||
|| chrono::DateTime::parse_from_rfc3339(&receipt.completed_at).is_err()
|
||||
{
|
||||
return Err(anyhow!("public source tool family is not exact and terminal"));
|
||||
}
|
||||
let arguments: serde_json::Value = serde_json::from_str(&intent.arguments_json)
|
||||
.context("public source intent arguments are invalid")?;
|
||||
let result: serde_json::Value = serde_json::from_str(
|
||||
receipt
|
||||
.result_json
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow!("public source receipt has no result"))?,
|
||||
)
|
||||
.context("public source receipt result is invalid")?;
|
||||
let owner = public_source_json_string(&arguments, "owner")?;
|
||||
let repository_name = public_source_json_string(&arguments, "repository")?;
|
||||
let revision = public_source_json_string(&arguments, "revision")?.to_ascii_lowercase();
|
||||
let path = public_source_json_string(&arguments, "path")?;
|
||||
let content = public_source_json_string(&result, "content")?;
|
||||
let repository = format!("github://{owner}/{repository_name}");
|
||||
let source_ref = format!("{repository}@{revision}/{path}");
|
||||
let content_sha256 = format!("{:x}", Sha256::digest(content.as_bytes()));
|
||||
let byte_count = content.len() as u64;
|
||||
let lookup_receipt_id = format!("eyes-source-{}", intent.intent_id);
|
||||
if public_source_json_string(&result, "provider")? != "github"
|
||||
|| public_source_json_string(&result, "repository")? != repository
|
||||
|| public_source_json_string(&result, "revision")? != revision
|
||||
|| public_source_json_string(&result, "path")? != path
|
||||
|| public_source_json_string(&result, "sourceRef")? != source_ref
|
||||
|| public_source_json_string(&result, "contentSha256")? != content_sha256
|
||||
|| result.get("byteCount").and_then(serde_json::Value::as_u64)
|
||||
!= Some(byte_count)
|
||||
|| public_source_json_string(&result, "evidenceReceiptId")? != lookup_receipt_id
|
||||
{
|
||||
return Err(anyhow!("public source result provenance is substituted"));
|
||||
}
|
||||
lookups.push(EyesSourceLookupReceipt {
|
||||
schema_version: crate::EYES_SOURCE_LOOKUP_RECEIPT_SCHEMA_VERSION.to_string(),
|
||||
receipt_id: lookup_receipt_id,
|
||||
source_job_id: worker_job_id.to_string(),
|
||||
substrate_grant_receipt_id: grant_id.clone(),
|
||||
tool_intent_id: intent.intent_id,
|
||||
tool_receipt_id: receipt.receipt_id,
|
||||
provider: "github".to_string(),
|
||||
repository,
|
||||
revision,
|
||||
path: path.to_string(),
|
||||
source_ref,
|
||||
content_sha256,
|
||||
byte_count,
|
||||
observed_at: receipt.completed_at,
|
||||
contract: "Eyes admitted one bounded immutable public source only after authenticating its worker, model, tool, grant, provider identity, and content digest.".to_string(),
|
||||
});
|
||||
}
|
||||
lookups.sort_by(|left, right| left.receipt_id.cmp(&right.receipt_id));
|
||||
Ok(lookups)
|
||||
}
|
||||
|
||||
fn public_source_json_string<'a>(value: &'a serde_json::Value, field: &str) -> Result<&'a str> {
|
||||
value
|
||||
.get(field)
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.filter(|value| !value.is_empty())
|
||||
.ok_or_else(|| anyhow!("public source cargo omitted {field:?}"))
|
||||
}
|
||||
|
||||
fn validate_governed_source_tool_intent(
|
||||
cache: &CultCache,
|
||||
job_id: &str,
|
||||
intent: &EpiphanyToolInvocationIntent,
|
||||
) -> Result<Vec<CultCacheEnvelope>> {
|
||||
let required_operation =
|
||||
crate::substrate_gate_operation_for_governed_tool(&intent.server, &intent.tool_name);
|
||||
let source_worker_job_id = match intent.model_request_id.as_deref() {
|
||||
Some(model_request_id) => {
|
||||
let model_binding = cache
|
||||
.get::<EpiphanyRuntimeModelExecutionBinding>(model_request_id)?
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"model-derived tool intent {:?} has no execution binding",
|
||||
intent.intent_id
|
||||
)
|
||||
})?;
|
||||
match model_binding.source_worker_job_id {
|
||||
Some(worker_job_id) => Some(worker_job_id),
|
||||
None if required_operation.is_some() => {
|
||||
return Err(anyhow!(
|
||||
"governed source tool intent {:?} has no source worker authority",
|
||||
intent.intent_id
|
||||
));
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if cache
|
||||
.get::<EpiphanyRuntimeWorkerLaunchRequest>(job_id)?
|
||||
.is_some_and(|launch| {
|
||||
matches!(
|
||||
launch.binding_id.as_str(),
|
||||
crate::EPIPHANY_RESEARCH_ROLE_BINDING_ID
|
||||
| crate::EPIPHANY_MODELING_ROLE_BINDING_ID
|
||||
| crate::EPIPHANY_VERIFICATION_ROLE_BINDING_ID
|
||||
)
|
||||
})
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"governed source worker refuses a tool intent without model-call provenance"
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
};
|
||||
let Some(source_worker_job_id) = source_worker_job_id else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let launch = cache
|
||||
.get::<EpiphanyRuntimeWorkerLaunchRequest>(&source_worker_job_id)?
|
||||
.ok_or_else(|| anyhow!("governed source tool intent lost its source worker launch"))?;
|
||||
let source_worker_job = cache
|
||||
.get::<EpiphanyRuntimeJob>(&source_worker_job_id)?
|
||||
.ok_or_else(|| anyhow!("governed source tool intent lost its source worker job"))?;
|
||||
if !matches!(
|
||||
launch.binding_id.as_str(),
|
||||
crate::EPIPHANY_RESEARCH_ROLE_BINDING_ID
|
||||
| crate::EPIPHANY_MODELING_ROLE_BINDING_ID
|
||||
| crate::EPIPHANY_VERIFICATION_ROLE_BINDING_ID
|
||||
) {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let required_operation = required_operation.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"governed source worker rejected unknown tool {}::{}",
|
||||
intent.server,
|
||||
intent.tool_name
|
||||
)
|
||||
})?;
|
||||
let grant_id = format!("substrate-grant-{source_worker_job_id}");
|
||||
let grant = cache
|
||||
.get::<SubstrateGateRepoAccessGrantReceipt>(&grant_id)?
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"governed source tool intent {:?} has no exact launch grant",
|
||||
intent.intent_id
|
||||
)
|
||||
})?;
|
||||
if source_worker_job.role != launch.role
|
||||
|| matches!(
|
||||
source_worker_job.status,
|
||||
EpiphanyRuntimeJobStatus::Completed
|
||||
| EpiphanyRuntimeJobStatus::Failed
|
||||
| EpiphanyRuntimeJobStatus::Cancelled
|
||||
)
|
||||
|| grant.runtime_job_id != source_worker_job_id
|
||||
|| grant.binding_id != launch.binding_id
|
||||
|| grant.role != launch.role
|
||||
|| grant.authority_scope != launch.authority_scope
|
||||
|| !grant
|
||||
.granted_operations
|
||||
.iter()
|
||||
.any(|operation| operation == required_operation)
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"governed source tool intent {:?} is outside its launch grant",
|
||||
intent.intent_id
|
||||
));
|
||||
}
|
||||
Ok(vec![
|
||||
cache
|
||||
.get_envelope::<EpiphanyRuntimeJob>(&source_worker_job_id)?
|
||||
.ok_or_else(|| anyhow!("governed source tool lost worker job envelope"))?,
|
||||
cache
|
||||
.get_envelope::<EpiphanyRuntimeWorkerLaunchRequest>(&source_worker_job_id)?
|
||||
.ok_or_else(|| anyhow!("governed source tool lost worker launch envelope"))?,
|
||||
cache
|
||||
.get_envelope::<SubstrateGateRepoAccessGrantReceipt>(&grant_id)?
|
||||
.ok_or_else(|| anyhow!("governed source tool lost launch grant envelope"))?,
|
||||
])
|
||||
}
|
||||
|
||||
pub fn runtime_worker_process_claims(
|
||||
store_path: impl AsRef<Path>,
|
||||
) -> Result<Vec<EpiphanyRuntimeWorkerProcessClaim>> {
|
||||
|
|
@ -16047,6 +16363,7 @@ pub(crate) mod tests {
|
|||
evidence_ids: vec![format!("evidence-{suffix}")],
|
||||
observation_ids: vec![format!("observation-{suffix}")],
|
||||
source_refs: vec!["epiphany-core/src/runtime_spine.rs:1".into()],
|
||||
source_lookup_receipt_ids: Vec::new(),
|
||||
summary: "The exact claim is challenged by inspected source.".into(),
|
||||
uncertainty: "Bounded to the cited source.".into(),
|
||||
emitted_at: "2026-07-13T04:01:00Z".into(),
|
||||
|
|
@ -19160,6 +19477,202 @@ pub(crate) mod tests {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn governed_public_tool_intent_requires_exact_worker_launch_grant() -> Result<()> {
|
||||
let temp = tempdir()?;
|
||||
let store = temp.path().join("runtime.msgpack");
|
||||
initialize_runtime_spine(
|
||||
&store,
|
||||
RuntimeSpineInitOptions {
|
||||
runtime_id: "epiphany-public-tool-test".to_string(),
|
||||
display_name: "Epiphany Public Tool Test".to_string(),
|
||||
created_at: "2026-08-11T00:00:00Z".to_string(),
|
||||
},
|
||||
)?;
|
||||
let state = crate::EpiphanyThreadState::default();
|
||||
let launch = crate::build_epiphany_role_launch_request(
|
||||
"thread-public-tool",
|
||||
crate::EpiphanyRoleResultRoleId::Research,
|
||||
Some(state.revision),
|
||||
Some(60),
|
||||
&state,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let plan = crate::plan_coordinator_job_launch(
|
||||
&state,
|
||||
&launch,
|
||||
&store,
|
||||
"launcher-public-tool".to_string(),
|
||||
"worker-public-tool".to_string(),
|
||||
)?;
|
||||
crate::commit_coordinator_job_launch(
|
||||
&store,
|
||||
"thread-public-tool",
|
||||
&state,
|
||||
&launch,
|
||||
&plan,
|
||||
"2026-08-11T00:00:01Z".to_string(),
|
||||
)?;
|
||||
|
||||
let mut model_request = EpiphanyModelRequest::new(
|
||||
"request-public-tool",
|
||||
"conversation-public-tool",
|
||||
"openai-codex",
|
||||
"gpt-test",
|
||||
"Read one immutable public source.",
|
||||
);
|
||||
model_request.source_worker_job_id = Some("worker-public-tool".to_string());
|
||||
let provider_request = EpiphanyOpenAiModelRequest::new(
|
||||
"request-public-tool",
|
||||
"conversation-public-tool",
|
||||
"gpt-test",
|
||||
"Read one immutable public source.",
|
||||
);
|
||||
open_runtime_model_execution(
|
||||
&store,
|
||||
RuntimeSpineSessionOptions {
|
||||
session_id: "model-session-public-tool".to_string(),
|
||||
objective: "Read one immutable public source.".to_string(),
|
||||
created_at: "2026-08-11T00:00:02Z".to_string(),
|
||||
coordinator_note: "test".to_string(),
|
||||
},
|
||||
RuntimeSpineJobOptions {
|
||||
job_id: "model-job-public-tool".to_string(),
|
||||
session_id: "model-session-public-tool".to_string(),
|
||||
role: "openai-model-adapter".to_string(),
|
||||
created_at: "2026-08-11T00:00:02Z".to_string(),
|
||||
summary: "Bound public source model turn.".to_string(),
|
||||
artifact_refs: Vec::new(),
|
||||
},
|
||||
&model_request,
|
||||
&provider_request,
|
||||
"2026-08-11T00:00:02Z",
|
||||
)?;
|
||||
let exact = EpiphanyToolInvocationIntent::new(
|
||||
"intent-public-tool-exact",
|
||||
"epiphany-tools",
|
||||
"epiphany_public",
|
||||
"github_file",
|
||||
r#"{"owner":"openai","repository":"openai-openapi","revision":"0123456789abcdef0123456789abcdef01234567","path":"openapi.yaml"}"#,
|
||||
"model-runtime",
|
||||
"Read immutable source.",
|
||||
"2026-08-11T00:00:03Z",
|
||||
)
|
||||
.with_model_call("call-public-tool-exact", "request-public-tool");
|
||||
put_runtime_tool_execution_intent(
|
||||
&store,
|
||||
"model-session-public-tool",
|
||||
"model-job-public-tool",
|
||||
&exact,
|
||||
"2026-08-11T00:00:03Z",
|
||||
)?;
|
||||
let content = "openapi: 3.1.0\n";
|
||||
let content_sha256 = format!("{:x}", Sha256::digest(content.as_bytes()));
|
||||
let mut exact_receipt = EpiphanyToolInvocationReceipt::new(
|
||||
"receipt-public-tool-exact",
|
||||
&exact.intent_id,
|
||||
&exact.adapter,
|
||||
&exact.server,
|
||||
&exact.tool_name,
|
||||
"completed",
|
||||
"2026-08-11T00:00:04Z",
|
||||
);
|
||||
exact_receipt.result_json = Some(
|
||||
serde_json::json!({
|
||||
"provider": "github",
|
||||
"repository": "github://openai/openai-openapi",
|
||||
"revision": "0123456789abcdef0123456789abcdef01234567",
|
||||
"path": "openapi.yaml",
|
||||
"sourceRef": "github://openai/openai-openapi@0123456789abcdef0123456789abcdef01234567/openapi.yaml",
|
||||
"contentSha256": content_sha256,
|
||||
"byteCount": content.len(),
|
||||
"content": content,
|
||||
"evidenceReceiptId": "eyes-source-intent-public-tool-exact"
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
put_runtime_tool_execution_receipt(&store, &exact_receipt)?;
|
||||
let lookups = runtime_authenticated_public_source_lookups_for_worker(
|
||||
&store,
|
||||
"worker-public-tool",
|
||||
)?;
|
||||
assert_eq!(lookups.len(), 1);
|
||||
assert_eq!(
|
||||
lookups[0].receipt_id,
|
||||
"eyes-source-intent-public-tool-exact"
|
||||
);
|
||||
assert_eq!(lookups[0].content_sha256, content_sha256);
|
||||
|
||||
let mut cache = runtime_spine_cache(&store)?;
|
||||
cache.pull_all_backing_stores()?;
|
||||
let receipt_key = tool_invocation_receipt_key(&exact.intent_id);
|
||||
let mut substituted_receipt = cache
|
||||
.get::<EpiphanyToolInvocationReceipt>(&receipt_key)?
|
||||
.expect("exact public source receipt");
|
||||
let mut substituted_result: serde_json::Value = serde_json::from_str(
|
||||
substituted_receipt.result_json.as_deref().unwrap(),
|
||||
)?;
|
||||
substituted_result["byteCount"] = serde_json::json!(1);
|
||||
substituted_receipt.result_json = Some(substituted_result.to_string());
|
||||
cache.put(&receipt_key, &substituted_receipt)?;
|
||||
let before_refusal = runtime_spine_backing_store(&store)?.pull_all()?;
|
||||
assert!(
|
||||
runtime_authenticated_public_source_lookups_for_worker(
|
||||
&store,
|
||||
"worker-public-tool"
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(runtime_spine_backing_store(&store)?.pull_all()?, before_refusal);
|
||||
cache.put(&receipt_key, &exact_receipt)?;
|
||||
let grant_id = "substrate-grant-worker-public-tool";
|
||||
let mut grant = cache
|
||||
.get::<SubstrateGateRepoAccessGrantReceipt>(grant_id)?
|
||||
.expect("exact worker launch grant");
|
||||
grant
|
||||
.granted_operations
|
||||
.retain(|operation| operation != "publicSourceRead");
|
||||
cache.put(grant_id, &grant)?;
|
||||
let hostile = EpiphanyToolInvocationIntent::new(
|
||||
"intent-public-tool-hostile",
|
||||
"epiphany-tools",
|
||||
"epiphany_public",
|
||||
"github_file",
|
||||
"{}",
|
||||
"model-runtime",
|
||||
"Attempt public read without operation authority.",
|
||||
"2026-08-11T00:00:04Z",
|
||||
)
|
||||
.with_model_call("call-public-tool-hostile", "request-public-tool");
|
||||
let before = runtime_spine_backing_store(&store)?.pull_all()?;
|
||||
assert!(
|
||||
put_runtime_tool_execution_intent(
|
||||
&store,
|
||||
"model-session-public-tool",
|
||||
"model-job-public-tool",
|
||||
&hostile,
|
||||
"2026-08-11T00:00:04Z",
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(runtime_spine_backing_store(&store)?.pull_all()?, before);
|
||||
let mut reloaded = runtime_spine_cache(&store)?;
|
||||
reloaded.pull_all_backing_stores()?;
|
||||
assert!(
|
||||
reloaded
|
||||
.get::<EpiphanyRuntimeToolExecutionBinding>(&hostile.intent_id)?
|
||||
.is_none()
|
||||
);
|
||||
assert!(
|
||||
reloaded
|
||||
.get::<EpiphanyToolInvocationIntent>(&tool_invocation_intent_key(
|
||||
&hostile.intent_id,
|
||||
))?
|
||||
.is_none()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn archived_model_session_is_exact_terminal_tombstone() -> Result<()> {
|
||||
let temp = tempdir()?;
|
||||
|
|
@ -19599,6 +20112,7 @@ pub(crate) mod tests {
|
|||
evidence_ids: vec!["ev-1".to_string()],
|
||||
observation_ids: vec!["obs-1".to_string()],
|
||||
source_refs: vec!["src/lib.rs:1".to_string()],
|
||||
source_lookup_receipt_ids: Vec::new(),
|
||||
summary: "Evidence packet.".to_string(),
|
||||
uncertainty: "none declared".to_string(),
|
||||
emitted_at: "2026-05-06T00:05:00Z".to_string(),
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ pub const SUBSTRATE_GATE_REPO_SNAPSHOT_RECEIPT_SCHEMA_VERSION: &str =
|
|||
"epiphany.substrate_gate.repo_snapshot_receipt.v0";
|
||||
pub const SUBSTRATE_GATE_REPO_MUTATION_RECEIPT_SCHEMA_VERSION: &str =
|
||||
"epiphany.substrate_gate.repo_mutation_receipt.v0";
|
||||
pub const SUBSTRATE_GATE_SOURCE_READ_OPERATION: &str = "read";
|
||||
pub const SUBSTRATE_GATE_PUBLIC_SOURCE_READ_OPERATION: &str = "publicSourceRead";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
|
|
@ -143,6 +145,13 @@ pub fn substrate_gate_repo_access_grant_for_launch(
|
|||
request: &EpiphanyJobLaunchRequest,
|
||||
granted_at: String,
|
||||
) -> SubstrateGateRepoAccessGrantReceipt {
|
||||
let mut granted_operations = vec![
|
||||
SUBSTRATE_GATE_SOURCE_READ_OPERATION.to_string(),
|
||||
"snapshot".to_string(),
|
||||
];
|
||||
if request.binding_id == crate::EPIPHANY_RESEARCH_ROLE_BINDING_ID {
|
||||
granted_operations.push(SUBSTRATE_GATE_PUBLIC_SOURCE_READ_OPERATION.to_string());
|
||||
}
|
||||
SubstrateGateRepoAccessGrantReceipt {
|
||||
schema_version: SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION.to_string(),
|
||||
receipt_id,
|
||||
|
|
@ -150,10 +159,29 @@ pub fn substrate_gate_repo_access_grant_for_launch(
|
|||
binding_id: request.binding_id.clone(),
|
||||
role: request.owner_role.clone(),
|
||||
authority_scope: request.authority_scope.clone(),
|
||||
granted_operations: vec!["read".to_string(), "snapshot".to_string()],
|
||||
granted_operations,
|
||||
granted_paths: vec![".".to_string()],
|
||||
granted_at,
|
||||
contract: "Substrate Gate granted scoped repository read/snapshot access for this worker launch; mutation remains forbidden without a separate mutation receipt.".to_string(),
|
||||
contract: "Substrate Gate granted the exact worker launch its named governed source operations; public-source reads are Eyes-only and mutation remains forbidden without a separate mutation receipt.".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn substrate_gate_operation_for_governed_tool(
|
||||
server: &str,
|
||||
tool_name: &str,
|
||||
) -> Option<&'static str> {
|
||||
match (server, tool_name) {
|
||||
(
|
||||
"epiphany_source",
|
||||
"read_file" | "directory_inventory" | "git_show" | "read_hands_receipt",
|
||||
)
|
||||
| ("epiphany_state", "resident_grant_lifecycle") => {
|
||||
Some(SUBSTRATE_GATE_SOURCE_READ_OPERATION)
|
||||
}
|
||||
("epiphany_public", "github_file") => {
|
||||
Some(SUBSTRATE_GATE_PUBLIC_SOURCE_READ_OPERATION)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -294,10 +322,31 @@ mod tests {
|
|||
"2026-05-30T00:00:00Z".to_string(),
|
||||
);
|
||||
assert!(grant.granted_operations.contains(&"read".to_string()));
|
||||
assert!(
|
||||
grant
|
||||
.granted_operations
|
||||
.contains(&"publicSourceRead".to_string())
|
||||
);
|
||||
assert!(!grant.granted_operations.contains(&"write".to_string()));
|
||||
assert!(grant.contract.contains("mutation remains forbidden"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn governed_tools_map_to_fixed_grant_operations() {
|
||||
assert_eq!(
|
||||
substrate_gate_operation_for_governed_tool("epiphany_source", "read_file"),
|
||||
Some("read")
|
||||
);
|
||||
assert_eq!(
|
||||
substrate_gate_operation_for_governed_tool("epiphany_public", "github_file"),
|
||||
Some("publicSourceRead")
|
||||
);
|
||||
assert_eq!(
|
||||
substrate_gate_operation_for_governed_tool("epiphany_public", "arbitrary_url"),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repo_work_and_implementation_grants_have_fixed_policies() {
|
||||
let planning = substrate_gate_repo_work_planning_grant(
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ pub struct EpiphanyModelRequest {
|
|||
pub tools: Vec<EpiphanyModelToolDefinition>,
|
||||
#[cultcache(key = 13, default)]
|
||||
pub output_schema_json: Option<String>,
|
||||
#[cultcache(key = 14, default)]
|
||||
pub source_worker_job_id: Option<String>,
|
||||
}
|
||||
|
||||
impl EpiphanyModelRequest {
|
||||
|
|
@ -83,6 +85,7 @@ impl EpiphanyModelRequest {
|
|||
previous_response_id: None,
|
||||
tools: Vec::new(),
|
||||
output_schema_json: None,
|
||||
source_worker_job_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -595,7 +595,7 @@ pub fn build_worker_model_request(
|
|||
if launch_request.binding_id == epiphany_core::EPIPHANY_VERIFICATION_ROLE_BINDING_ID {
|
||||
instructions.push_str("\n\nTool mandate: before returning `needs-evidence` because source files, artifact directories, command artifacts, commit diffs, Hands receipt bodies, or resident grant lifecycle are not inspectable, call the governed read-only tools available on this request. Use `mcp__epiphany_source__read_file` for cited source/artifact files, `mcp__epiphany_source__directory_inventory` for bounded workspace directory counts and bytes, `mcp__epiphany_source__git_show` for commit diffs, `mcp__epiphany_source__read_hands_receipt` for Hands patch/command/commit receipts, and `mcp__epiphany_state__resident_grant_lifecycle` for exact or bounded recent grant-owned lifecycle state. Directory totals are authoritative only when the tool reports `complete=true`. Grant launchability is authoritative only from the typed state projection, never artifact names or acknowledgement presence. If a tool fails, cite that failed tool result and the exact remaining blocker.");
|
||||
} else if launch_request.binding_id == epiphany_core::EPIPHANY_RESEARCH_ROLE_BINDING_ID {
|
||||
instructions.push_str("\n\nTool mandate: Eyes must inspect current repository sources or typed resident state before emitting evidence. Call the bounded read-only file, directory-inventory, Git, or `mcp__epiphany_state__resident_grant_lifecycle` tool appropriate to the claim; cite exact inspected paths/revisions or grant identities in filesInspected and evidence, and report a source gap if the required body cannot be observed. Directory totals are authoritative only when the inventory reports `complete=true`; grant launchability is authoritative only from the typed lifecycle projection.");
|
||||
instructions.push_str("\n\nTool mandate: Eyes must inspect current repository sources, immutable public GitHub source, or typed resident state before emitting evidence. Call the bounded governed tool appropriate to the claim. Public evidence uses `mcp__epiphany_public__github_file` with an exact 40-hex commit, repository, and path; put its exact sourceRef in filesInspected and its evidenceReceiptId in evidence, and preserve its contentSha256 in the finding. Never substitute a branch, tag, arbitrary URL, or model memory for immutable public evidence. Cite exact inspected paths/revisions or grant identities in filesInspected and evidence, and report a source gap if the required body cannot be observed. Directory totals are authoritative only when the inventory reports `complete=true`; grant launchability is authoritative only from the typed lifecycle projection.");
|
||||
} else if launch_request.binding_id == epiphany_core::EPIPHANY_MODELING_ROLE_BINDING_ID {
|
||||
instructions.push_str("\n\nTool mandate: Modeling must inspect current repository sources or typed resident state before proposing repository anatomy. Call the bounded read-only file, directory-inventory, Git, or `mcp__epiphany_state__resident_grant_lifecycle` tool appropriate to the claim; cite exact inspected paths/revisions or grant identities in filesInspected and evidence, and emit regather-needed instead of inventing unobserved structure. Directory totals are authoritative only when the inventory reports `complete=true`; grant launchability is authoritative only from the typed lifecycle projection.");
|
||||
}
|
||||
|
|
@ -615,13 +615,16 @@ pub fn build_worker_model_request(
|
|||
request.reasoning_summary = Some("concise".to_string());
|
||||
request.output_contract_id = Some(launch_request.output_contract_id.clone());
|
||||
request.output_schema_json = Some(output_schema_json);
|
||||
request.source_worker_job_id = Some(launch_request.job_id.clone());
|
||||
if matches!(
|
||||
launch_request.binding_id.as_str(),
|
||||
epiphany_core::EPIPHANY_RESEARCH_ROLE_BINDING_ID
|
||||
| epiphany_core::EPIPHANY_MODELING_ROLE_BINDING_ID
|
||||
| epiphany_core::EPIPHANY_VERIFICATION_ROLE_BINDING_ID
|
||||
) {
|
||||
request.tools = repository_source_tools();
|
||||
request.tools = repository_source_tools(
|
||||
launch_request.binding_id == epiphany_core::EPIPHANY_RESEARCH_ROLE_BINDING_ID,
|
||||
);
|
||||
}
|
||||
Ok(request)
|
||||
}
|
||||
|
|
@ -1047,6 +1050,7 @@ pub fn model_request_from_openai_request(
|
|||
output_contract_id: request.output_contract_id.clone(),
|
||||
previous_response_id: request.previous_response_id.clone(),
|
||||
output_schema_json: request.output_schema_json.clone(),
|
||||
source_worker_job_id: None,
|
||||
tools: request
|
||||
.tools
|
||||
.iter()
|
||||
|
|
@ -1091,8 +1095,8 @@ pub fn openai_request_from_model_request(
|
|||
}
|
||||
}
|
||||
|
||||
fn repository_source_tools() -> Vec<EpiphanyModelToolDefinition> {
|
||||
vec![
|
||||
fn repository_source_tools(include_public_source: bool) -> Vec<EpiphanyModelToolDefinition> {
|
||||
let mut tools = vec![
|
||||
EpiphanyModelToolDefinition {
|
||||
name: "mcp__epiphany_source__read_file".to_string(),
|
||||
description: "Read a bounded UTF-8 text slice from the current workspace for source-grounded Eyes, Modeling, or Soul work. Use only for repository sources and operator-safe artifacts in scope.".to_string(),
|
||||
|
|
@ -1166,7 +1170,27 @@ fn repository_source_tools() -> Vec<EpiphanyModelToolDefinition> {
|
|||
})
|
||||
.to_string(),
|
||||
},
|
||||
]
|
||||
];
|
||||
if include_public_source {
|
||||
tools.push(EpiphanyModelToolDefinition {
|
||||
name: "mcp__epiphany_public__github_file".to_string(),
|
||||
description: "Read one bounded UTF-8 file from a public GitHub repository at an exact immutable 40-hex commit. Returns provider/repository/revision/path/sourceRef/contentSha256 provenance. Branches, tags, redirects, arbitrary hosts, and mutable URLs are refused.".to_string(),
|
||||
parameters_json: serde_json::json!({
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"owner": {"type": "string"},
|
||||
"repository": {"type": "string"},
|
||||
"revision": {"type": "string", "pattern": "^[0-9a-fA-F]{40}$"},
|
||||
"path": {"type": "string"},
|
||||
"maxBytes": {"type": "integer", "minimum": 512, "maximum": 65536}
|
||||
},
|
||||
"required": ["owner", "repository", "revision", "path"]
|
||||
})
|
||||
.to_string(),
|
||||
});
|
||||
}
|
||||
tools
|
||||
}
|
||||
|
||||
fn model_input_from_openai_input(input: &EpiphanyOpenAiInputItem) -> EpiphanyModelInputItem {
|
||||
|
|
@ -3186,6 +3210,10 @@ mod tests {
|
|||
.tools
|
||||
.iter()
|
||||
.any(|tool| tool.name == "mcp__epiphany_state__resident_grant_lifecycle"));
|
||||
assert!(!model_request
|
||||
.tools
|
||||
.iter()
|
||||
.any(|tool| tool.name == "mcp__epiphany_public__github_file"));
|
||||
assert!(model_request.instructions.contains("Modeling must inspect"));
|
||||
let openai_summary = EpiphanyOpenAiRuntimeRunSummary {
|
||||
store: store.display().to_string(),
|
||||
|
|
@ -3338,6 +3366,7 @@ mod tests {
|
|||
assert!(tool_names.contains(&"mcp__epiphany_source__git_show"));
|
||||
assert!(tool_names.contains(&"mcp__epiphany_source__read_hands_receipt"));
|
||||
assert!(tool_names.contains(&"mcp__epiphany_state__resident_grant_lifecycle"));
|
||||
assert!(!tool_names.contains(&"mcp__epiphany_public__github_file"));
|
||||
assert!(model_request
|
||||
.instructions
|
||||
.contains("mcp__epiphany_source__read_file"));
|
||||
|
|
@ -3347,6 +3376,21 @@ mod tests {
|
|||
assert!(model_request
|
||||
.instructions
|
||||
.contains("mcp__epiphany_state__resident_grant_lifecycle"));
|
||||
|
||||
let mut research_launch = launch_request;
|
||||
research_launch.binding_id =
|
||||
epiphany_core::EPIPHANY_RESEARCH_ROLE_BINDING_ID.to_string();
|
||||
research_launch.role = epiphany_core::EPIPHANY_RESEARCH_OWNER_ROLE.to_string();
|
||||
research_launch.authority_scope = "epiphany.role.research".to_string();
|
||||
let research_request =
|
||||
build_worker_model_request(&research_launch, DEFAULT_MODEL_PROVIDER, "gpt-5.4")?;
|
||||
assert!(research_request
|
||||
.tools
|
||||
.iter()
|
||||
.any(|tool| tool.name == "mcp__epiphany_public__github_file"));
|
||||
assert!(research_request
|
||||
.instructions
|
||||
.contains("exact 40-hex commit"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ epiphany-core = { path = "../epiphany-core" }
|
|||
cultcache-rs = { path = "../vendor/cultcache-rs" }
|
||||
http = "1"
|
||||
reqwest = { version = "0.13.2", default-features = false, features = ["json", "rustls"] }
|
||||
sha2 = "0.10"
|
||||
rmcp = { version = "2.2.0", default-features = false, features = ["client", "transport-child-process", "transport-streamable-http-client-reqwest"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
//! endpoint policy and translates between MCP cargo and typed tool receipts.
|
||||
|
||||
mod config;
|
||||
mod native_public;
|
||||
mod native_source;
|
||||
|
||||
pub use config::{McpRuntimeConfig, McpServerConfig, McpTransportConfig};
|
||||
pub use native_public::execute_epiphany_public;
|
||||
pub use native_source::execute_epiphany_source;
|
||||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ use epiphany_tool_adapter::{
|
|||
TOOL_ADAPTER_INVOCATION_RECEIPT_SCHEMA_ID, tool_invocation_intent_key,
|
||||
};
|
||||
use epiphany_tool_mcp_runtime::{
|
||||
McpRuntimeConfig, execute_epiphany_source, invoke, validate_intent,
|
||||
McpRuntimeConfig, execute_epiphany_public, execute_epiphany_source, invoke,
|
||||
validate_intent,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::collections::BTreeMap;
|
||||
|
|
@ -95,6 +96,8 @@ async fn execute_to_receipt(
|
|||
None => std::env::current_dir()?,
|
||||
};
|
||||
execute_epiphany_source(intent, &options.store, &cwd)
|
||||
} else if intent.server == "epiphany_public" {
|
||||
execute_epiphany_public(intent).await
|
||||
} else if intent.server == "epiphany_state" {
|
||||
let resident_store = options.resident_store.as_ref().ok_or_else(|| {
|
||||
anyhow!("epiphany_state requires an explicitly bound --resident-store")
|
||||
|
|
|
|||
182
epiphany-tool-mcp-runtime/src/native_public.rs
Normal file
182
epiphany-tool-mcp-runtime/src/native_public.rs
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
use anyhow::{Context, Result, anyhow};
|
||||
use epiphany_tool_adapter::EpiphanyToolInvocationIntent;
|
||||
use reqwest::redirect::Policy;
|
||||
use serde_json::{Value, json};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::time::Duration;
|
||||
|
||||
const MAX_PUBLIC_SOURCE_BYTES: usize = 64 * 1024;
|
||||
|
||||
pub async fn execute_epiphany_public(intent: &EpiphanyToolInvocationIntent) -> Result<Value> {
|
||||
let arguments: Value =
|
||||
serde_json::from_str(&intent.arguments_json).context("arguments_json is not valid JSON")?;
|
||||
if !arguments.is_object() {
|
||||
return Err(anyhow!("epiphany_public arguments must be an object"));
|
||||
}
|
||||
match intent.tool_name.as_str() {
|
||||
"github_file" => github_file(intent, &arguments).await,
|
||||
other => Err(anyhow!("unknown epiphany_public tool {other:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn github_file(intent: &EpiphanyToolInvocationIntent, arguments: &Value) -> Result<Value> {
|
||||
let owner = repository_component(arguments, "owner")?;
|
||||
let repository = repository_component(arguments, "repository")?;
|
||||
let revision = required_string(arguments, "revision")?;
|
||||
if revision.len() != 40 || !revision.bytes().all(|byte| byte.is_ascii_hexdigit()) {
|
||||
return Err(anyhow!(
|
||||
"github_file revision must be an immutable 40-hex commit id"
|
||||
));
|
||||
}
|
||||
let path = required_string(arguments, "path")?;
|
||||
validate_repository_path(path)?;
|
||||
let maximum = arguments
|
||||
.get("maxBytes")
|
||||
.map(|value| {
|
||||
value
|
||||
.as_u64()
|
||||
.ok_or_else(|| anyhow!("github_file maxBytes must be unsigned"))
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or(32_768)
|
||||
.clamp(512, MAX_PUBLIC_SOURCE_BYTES as u64) as usize;
|
||||
let revision = revision.to_ascii_lowercase();
|
||||
let url = format!("https://raw.githubusercontent.com/{owner}/{repository}/{revision}/{path}");
|
||||
let client = reqwest::Client::builder()
|
||||
.redirect(Policy::none())
|
||||
.no_proxy()
|
||||
.timeout(Duration::from_secs(15))
|
||||
.user_agent("epiphany-public-source/0")
|
||||
.build()
|
||||
.context("building bounded public-source client")?;
|
||||
let mut response = client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await
|
||||
.context("reading immutable GitHub source")?;
|
||||
if !response.status().is_success() {
|
||||
return Err(anyhow!(
|
||||
"immutable GitHub source returned HTTP {}",
|
||||
response.status()
|
||||
));
|
||||
}
|
||||
if response
|
||||
.content_length()
|
||||
.is_some_and(|length| length > maximum as u64)
|
||||
{
|
||||
return Err(anyhow!("immutable GitHub source exceeds maxBytes"));
|
||||
}
|
||||
let mut bytes = Vec::with_capacity(maximum.min(8_192));
|
||||
while let Some(chunk) = response
|
||||
.chunk()
|
||||
.await
|
||||
.context("reading immutable GitHub source body")?
|
||||
{
|
||||
if bytes.len() + chunk.len() > maximum {
|
||||
return Err(anyhow!("immutable GitHub source exceeds maxBytes"));
|
||||
}
|
||||
bytes.extend_from_slice(&chunk);
|
||||
}
|
||||
let content = String::from_utf8(bytes.clone())
|
||||
.map_err(|_| anyhow!("immutable GitHub source is not UTF-8 text"))?;
|
||||
let content_sha256 = format!("{:x}", Sha256::digest(&bytes));
|
||||
let evidence_receipt_id = format!("eyes-source-{}", intent.intent_id);
|
||||
Ok(json!({
|
||||
"provider": "github",
|
||||
"repository": format!("github://{owner}/{repository}"),
|
||||
"revision": revision,
|
||||
"path": path,
|
||||
"sourceRef": format!("github://{owner}/{repository}@{revision}/{path}"),
|
||||
"contentSha256": content_sha256,
|
||||
"evidenceReceiptId": evidence_receipt_id,
|
||||
"byteCount": bytes.len(),
|
||||
"content": content,
|
||||
}))
|
||||
}
|
||||
|
||||
fn repository_component<'a>(arguments: &'a Value, name: &str) -> Result<&'a str> {
|
||||
let value = required_string(arguments, name)?;
|
||||
if value.len() > 100
|
||||
|| !value
|
||||
.bytes()
|
||||
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.'))
|
||||
{
|
||||
return Err(anyhow!("github_file {name} is invalid"));
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn validate_repository_path(path: &str) -> Result<()> {
|
||||
if path.len() > 512
|
||||
|| path.starts_with('/')
|
||||
|| path.split('/').any(|segment| {
|
||||
segment.is_empty()
|
||||
|| matches!(segment, "." | "..")
|
||||
|| !segment
|
||||
.bytes()
|
||||
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.'))
|
||||
})
|
||||
{
|
||||
return Err(anyhow!("github_file path is invalid"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn required_string<'a>(value: &'a Value, name: &str) -> Result<&'a str> {
|
||||
value
|
||||
.get(name)
|
||||
.and_then(Value::as_str)
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
.ok_or_else(|| anyhow!("missing required string argument {name:?}"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn public_source_identity_requires_immutable_github_shape() {
|
||||
let valid = json!({
|
||||
"owner": "openai",
|
||||
"repository": "openai-openapi",
|
||||
"revision": "0123456789abcdef0123456789abcdef01234567",
|
||||
"path": "openapi.yaml"
|
||||
});
|
||||
assert_eq!(repository_component(&valid, "owner").unwrap(), "openai");
|
||||
assert!(validate_repository_path("docs/source_file.rs").is_ok());
|
||||
assert!(validate_repository_path("../secret").is_err());
|
||||
assert!(validate_repository_path("path with spaces").is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "live immutable GitHub source proof"]
|
||||
async fn reads_exact_public_epiphany_source_with_digest_provenance() -> Result<()> {
|
||||
let intent = EpiphanyToolInvocationIntent::new(
|
||||
"live-public-source",
|
||||
epiphany_tool_adapter::EPIPHANY_TOOL_RUNTIME_ADAPTER_ID,
|
||||
"epiphany_public",
|
||||
"github_file",
|
||||
r#"{"owner":"GameCult","repository":"Epiphany","revision":"43dc865e0f332b82de4b95292d5c600f8b901706","path":"README.md","maxBytes":65536}"#,
|
||||
"live-test",
|
||||
"Prove exact public source access.",
|
||||
"2026-08-11T00:00:00Z",
|
||||
);
|
||||
let result = execute_epiphany_public(&intent).await?;
|
||||
assert_eq!(
|
||||
result["sourceRef"],
|
||||
"github://GameCult/Epiphany@43dc865e0f332b82de4b95292d5c600f8b901706/README.md"
|
||||
);
|
||||
assert_eq!(result["contentSha256"].as_str().map(str::len), Some(64));
|
||||
assert!(result["byteCount"].as_u64().is_some_and(|count| count > 0));
|
||||
assert!(
|
||||
result["content"]
|
||||
.as_str()
|
||||
.is_some_and(|content| content.contains("Epiphany"))
|
||||
);
|
||||
println!(
|
||||
"sourceRef={} contentSha256={} byteCount={}",
|
||||
result["sourceRef"], result["contentSha256"], result["byteCount"]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -4327,6 +4327,49 @@ Soul binding, and split-admission replay.
|
|||
routing excludes manual regather. c010 remains alive, braked, and without an
|
||||
active lease after native Imagination produced no admitted work.
|
||||
|
||||
## Governed source-tool authority gap (2026-08-11)
|
||||
|
||||
The transport body is broader than the admitted source faculty. The generic
|
||||
MCP runtime can call configured subprocess or HTTP providers, while live Eyes,
|
||||
Modeling, and Soul requests advertise only workspace-confined `read_file`,
|
||||
`directory_inventory`, `git_show`, typed Hands-receipt reads, and the exact
|
||||
resident-grant projection. No public-source tool is currently model-visible.
|
||||
|
||||
- Owner: the immutable Substrate Gate grant created with an exact worker launch
|
||||
must own which governed source operations that job may invoke.
|
||||
- Inputs: exact runtime job, launch binding and role; exact tool server/name;
|
||||
and a fixed tool-to-operation classification owned by the source boundary.
|
||||
- Outputs: runtime admission accepts a model-derived tool intent only when the
|
||||
exact launch grant names its required operation. Public evidence additionally
|
||||
carries provider, repository, immutable revision, path, and content digest.
|
||||
- Derived state: MCP configuration, model-visible tool definitions, tool-call
|
||||
JSON, and transport reach are capability projections or protocol cargo. None
|
||||
grants source access by existing.
|
||||
- Forbidden writers: a model-selected tool name, generic MCP endpoint, prompt
|
||||
instruction, runtime binding, or Eyes packet may not synthesize Substrate
|
||||
authority. Arbitrary URLs and redirects are not public-source identity.
|
||||
- Shared paths: Eyes, Modeling, and Soul must receive source tools only through
|
||||
the same launch-grant and runtime-intent admission primitive. Local and public
|
||||
source operations differ in provider policy but share the grant check.
|
||||
- Cut line: publish an immutable grant for every launch that advertises source
|
||||
tools; reject an intent before adapter/network execution when its exact grant
|
||||
lacks the mapped operation. Then add one bounded provider-shaped public source
|
||||
operation rather than exposing the generic MCP transport as evidence authority.
|
||||
- Verification layer: focused grant/intent tests must refuse absent, foreign,
|
||||
role-swapped, job-swapped, and operation-missing grants byte-identically. The
|
||||
packaged Linux circuit must admit one immutable public source through Eyes and
|
||||
Mind while an ungranted public request produces no network consequence.
|
||||
|
||||
Current source enforces this map. Research, Modeling, and Verification launches
|
||||
publish exact read grants; Research alone receives `publicSourceRead` and the
|
||||
model-visible `epiphany_public.github_file` tool. Runtime admission follows the
|
||||
inner model binding back to the outer typed worker launch and exact grant before
|
||||
persisting an intent or executing an adapter. Public GitHub results become
|
||||
`EyesSourceLookupReceipt` only after the full worker/model/tool/grant family and
|
||||
the provider/repository/commit/path/content digest are reauthenticated inside
|
||||
the same full-snapshot CAS that admits the Eyes packet into Mind. Generic MCP
|
||||
transport and result JSON remain transport cargo, not evidence authority.
|
||||
|
||||
## Resident PID 1 owns adopted worker reaping (2026-08-11)
|
||||
|
||||
- Owner: resident Self owns OS cleanup for model-worker grandchildren that its
|
||||
|
|
|
|||
|
|
@ -12,17 +12,18 @@ the actual braked resident body are the live gates.
|
|||
## Current body
|
||||
|
||||
- Source branch: `codex/epiphany-shakedown-live`.
|
||||
- Current pushed source: exact `a07f6541`.
|
||||
- Accepted live runtime implementation: exact `a07f6541` through resident
|
||||
directive settlement; autonomous promotion remains failed closed.
|
||||
- Current pushed source: exact `43dc865e`.
|
||||
- Accepted live runtime implementation: exact `465af24d` through resident PID 1
|
||||
reaping across genuine Modeling and Imagination worker families.
|
||||
- Audited/live release:
|
||||
`sha256-ba51cbac5fe2400c27497c5df92fe128ce083a29c45e9ccf5eb95d21ec807b26`;
|
||||
`sha256-089e000511f0420bfaab1136952b8cf94677a72d5c30a3fb3e6a26996bd3726f`;
|
||||
witness
|
||||
`sha256-01a6d5d74129ffb3096a3aa3f4ea7cd98d13af38b27d5322a8ab1d8f458d7a03`.
|
||||
- Actual c008b: both owners stopped. The operator retry settled once at
|
||||
revision 320; later autonomous turnover killed resident Self on a promotion
|
||||
companion collision and left an active lease. Heartbeat stopped exit zero;
|
||||
the exact cognitive brake is engaged.
|
||||
`sha256-20ca293589b33d3b2e6ea5a7411db544741fe18360ecbd4423cdfa000d1ad7c6`.
|
||||
- Actual c011: both owners are alive under the engaged cognitive brake at
|
||||
resident revision 384, with no active lease, no admitted work, and zero
|
||||
defunct children. Exact claimed worker PIDs 152 and 275 terminalized through
|
||||
Modeling and Imagination respectively, settled once, and were reaped by the
|
||||
resident PID 1 owner.
|
||||
- Build body: Starfire. Yggdrasil is the small public crossing and never builds
|
||||
this graph.
|
||||
|
||||
|
|
@ -31,8 +32,8 @@ the actual braked resident body are the live gates.
|
|||
| Faculty | Owner | Accepted proof | Remaining live gate |
|
||||
|---|---|---|---|
|
||||
| Self | resident Self state machine | single-consumption Heartbeat grants, typed fulfillment, exact recovery/requeue, cooldown and brake behavior; actual c007h superseded one overtaken unlaunched continuation and accepted the recovered proposal-Modeling result exactly once | autonomous turnover from a genuinely new typed frontier; current `awaitFrontierProposal` is correctly idle |
|
||||
| Substrate Gate | `substrate_gate` | scoped read/mutation grants on Eyes and Hands paths; canonical brake covers direct daemon lifecycle actuation; non-repository process utilities retain explicit owners | one broader foreign/public Verse evidence adoption path with Eyes |
|
||||
| Eyes | Eyes gateway and authenticated source tools | challenged-claim Windows circuit and governed Linux tool circuit accepted into Mind | one broader foreign/public Verse evidence adoption path |
|
||||
| Substrate Gate | `substrate_gate` | immutable scoped grants now bind every advertised Eyes, Modeling, and Soul source operation through the outer worker launch; canonical brake covers direct daemon lifecycle actuation; non-repository process utilities retain explicit owners | packaged negative proof that an ungranted public request creates no network consequence |
|
||||
| Eyes | Eyes gateway and authenticated source tools | challenged-claim Windows circuit, governed Linux tool circuit, and exact immutable GitHub reader with typed receipt derivation | package and adopt one broader public source through Eyes into Mind |
|
||||
| Modeling | runtime-owned typed Modeling contracts | causal Eyes-to-Modeling acceptance, direct proposal Modeling, archived fulfillment | broader Body-map coverage across a changing live repository |
|
||||
| Imagination | typed proposal/consideration producers | genuine packaged multi-option generation, proposal-only candidates, exact fulfillment, no Hands adoption leak | autonomous turnover from the next genuine eligible frontier |
|
||||
| Hands | Hands gateway | bounded patch, command, commit, push, and changed-path receipts; Idunn, crossing, forge, IDE, repo-birth, and model-runtime processes retain separate owners; Unity method override is structurally refused | audit the next Soul verifier boundary rather than adding Hands gates around non-Hands owners |
|
||||
|
|
@ -41,7 +42,7 @@ the actual braked resident body are the live gates.
|
|||
| Persona | Persona turn, speech audit, public mouth | cross-host request/permit/receipt, signed failure and unknown no-repost behavior, conversation retention | one completed public consequence; currently blocked by absent Yggdrasil Discord credential |
|
||||
| Continuity | runtime/resident recovery and retention | graceful shutdown, exact process-death recovery, session closure/archive, lifecycle retention, non-resurrection, bounded endurance, and receipt-derived recovery on the current body | later deployed restart supervision |
|
||||
| Nervous system | Heartbeat, runtime spine, Idunn, CultMesh | detached scheduling, typed jobs, provider readiness, active/warming transitions across PID namespaces, bounded endurance, narrow local-Verse decision reads, bounded Docker logs, and causal grant turnover | audit non-repository actuator publication and command ownership |
|
||||
| Body | Rust binaries, named-volume CultCache stores, model transport | deterministic 24-binary packaging, fast warm replay, named-volume interruption proof | deployment-sized endurance and remaining inspection surfaces |
|
||||
| Body | Rust binaries, named-volume CultCache stores, model transport | deterministic 24-binary packaging, fast warm replay, named-volume interruption proof, and exact PID 1 reaping across two adopted worker kinds | deployment-sized endurance and remaining inspection surfaces |
|
||||
|
||||
## Accepted coordinated circuits
|
||||
|
||||
|
|
@ -118,11 +119,23 @@ these legitimate aggregates into ad hoc query piles.
|
|||
|
||||
## Selected next bounded cut
|
||||
|
||||
Keep c008b stopped. Inspect the partial autonomous proposal companion family,
|
||||
map the promotion transaction owner, and prove a copied-state repair that is
|
||||
idempotent without accepting substituted companions. Then recover the exact
|
||||
missing active process through Continuity before restarting the resident pair.
|
||||
Do not build on Yggdrasil.
|
||||
Keep c011 alive and braked. Source now binds every governed model-derived tool
|
||||
intent to its outer worker launch and exact Substrate Gate grant. Eyes alone can
|
||||
see the bounded `github_file` provider, which accepts only one GitHub repository,
|
||||
exact 40-hex commit, repository path, 64 KiB UTF-8 body, no redirects, and no
|
||||
proxy. Runtime recomputes its identity and bytes before a typed
|
||||
`EyesSourceLookupReceipt` can share the Mind acceptance transaction. Focused
|
||||
hostile substitution is byte-identical and the exact upstream README reader is
|
||||
already proven from Starfire.
|
||||
|
||||
Package this source once on Starfire. On copied named-volume state, launch an
|
||||
actual Research lane that cites the returned `sourceRef` and
|
||||
`evidenceReceiptId`, accept it into Mind, and inspect the typed lookup plus Eyes
|
||||
packet in the committed snapshot. Then exercise an otherwise exact worker whose
|
||||
grant lacks `publicSourceRead`; require refusal before adapter/network execution
|
||||
and byte-identical runtime state. Only after both gates pass may c011 be replaced.
|
||||
Generic MCP transport remains transport and arbitrary URL fetching remains
|
||||
absent. Do not build on Yggdrasil.
|
||||
|
||||
## Completion standard
|
||||
|
||||
|
|
|
|||
|
|
@ -18,12 +18,45 @@
|
|||
"reasoning_effort": { "type": ["string", "null"] },
|
||||
"reasoning_summary": { "type": ["string", "null"] },
|
||||
"service_tier": { "type": ["string", "null"] },
|
||||
"output_contract_id": { "type": ["string", "null"] }
|
||||
"output_contract_id": { "type": ["string", "null"] },
|
||||
"previous_response_id": { "type": ["string", "null"] },
|
||||
"tools": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name", "description", "parameters_json"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"description": { "type": "string" },
|
||||
"parameters_json": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"output_schema_json": { "type": ["string", "null"] },
|
||||
"source_worker_job_id": { "type": ["string", "null"] }
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"$defs": {
|
||||
"input_item": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["ToolCall"],
|
||||
"properties": {
|
||||
"ToolCall": {
|
||||
"type": "object",
|
||||
"required": ["call_id", "name", "arguments"],
|
||||
"properties": {
|
||||
"call_id": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"arguments": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["UserText"],
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
"sessionId": { "type": "string", "minLength": 1 },
|
||||
"jobId": { "type": "string", "minLength": 1 },
|
||||
"provider": { "type": "string", "minLength": 1 },
|
||||
"boundAt": { "type": "string", "format": "date-time" }
|
||||
"boundAt": { "type": "string", "format": "date-time" },
|
||||
"sourceWorkerJobId": { "type": ["string", "null"] }
|
||||
},
|
||||
"required": [
|
||||
"schemaVersion",
|
||||
|
|
|
|||
Reference in a new issue