Close Bifrost chains by typed identity

This commit is contained in:
Meta Vens 2026-07-13 01:25:18 +01:00
commit 98b6147365
6 changed files with 169 additions and 31 deletions

View file

@ -7924,27 +7924,35 @@ fn bifrost_accounting_rows(
let mut rows = Vec::new();
let mut tui_rows = Vec::new();
let body_publication = latest_intent.and_then(|intent| {
latest_publication.filter(|receipt| receipt.intent_id == intent.intent_id)
});
let body_github = body_publication.and_then(|publication| {
latest_github
.filter(|receipt| receipt.bifrost_publication_receipt_id == publication.receipt_id)
});
let body_private = latest_intent
.map(|intent| intent.private_state_included)
.unwrap_or(false)
|| latest_publication
|| body_publication
.map(|receipt| receipt.private_state_exposed)
.unwrap_or(false)
|| latest_github
|| body_github
.map(|receipt| receipt.private_state_exposed)
.unwrap_or(false);
let body_review_count = latest_publication
let body_review_count = body_publication
.map(|receipt| receipt.reviewer_ids.len())
.or_else(|| latest_intent.map(|intent| intent.review_receipt_ids.len()))
.unwrap_or(0);
let body_credit_count = latest_github
let body_credit_count = body_github
.map(|receipt| receipt.credit_receipt_ids.len())
.or_else(|| latest_publication.map(|receipt| receipt.credit_receipt_ids.len()))
.or_else(|| body_publication.map(|receipt| receipt.credit_receipt_ids.len()))
.or_else(|| latest_intent.map(|intent| intent.credit_subjects.len()))
.unwrap_or(0);
let body_chain_closed = latest_intent.is_some()
&& latest_publication.is_some()
&& latest_github.is_some()
&& body_publication.is_some()
&& body_github.is_some()
&& body_review_count > 0
&& body_credit_count > 0
&& !body_private;
@ -7962,28 +7970,28 @@ fn bifrost_accounting_rows(
closure: format!(
"intent={} publication={} github={} review={} credit={}",
present_word(latest_intent.is_some()),
present_word(latest_publication.is_some()),
present_word(latest_github.is_some()),
present_word(body_publication.is_some()),
present_word(body_github.is_some()),
body_review_count,
body_credit_count
),
ledger_entry_id: latest_publication
ledger_entry_id: body_publication
.map(|receipt| receipt.bifrost_ledger_entry_id.clone())
.or_else(|| latest_github.map(|receipt| receipt.ledger_entry_id.clone()))
.or_else(|| body_github.map(|receipt| receipt.ledger_entry_id.clone()))
.unwrap_or_else(|| "none".to_string()),
latest_receipt_id: latest_github
latest_receipt_id: body_github
.map(|receipt| receipt.receipt_id.clone())
.or_else(|| latest_publication.map(|receipt| receipt.receipt_id.clone()))
.or_else(|| body_publication.map(|receipt| receipt.receipt_id.clone()))
.or_else(|| latest_intent.map(|intent| intent.intent_id.clone()))
.unwrap_or_else(|| "none".to_string()),
public_ref: latest_github
public_ref: body_github
.map(|receipt| receipt.pull_request_url.clone())
.or_else(|| latest_publication.map(|receipt| receipt.publication_url.clone()))
.or_else(|| body_publication.map(|receipt| receipt.publication_url.clone()))
.or_else(|| latest_intent.map(|intent| intent.target_branch.clone()))
.unwrap_or_else(|| "none".to_string()),
review_receipt_count: body_review_count,
credit_receipt_count: body_credit_count,
public_artifact_count: usize::from(latest_github.is_some()),
public_artifact_count: usize::from(body_github.is_some()),
private_state_exposed: body_private,
},
);
@ -9426,6 +9434,73 @@ fn required_list(values: &Option<Vec<String>>, message: &str) -> Result<Vec<Stri
mod lifecycle_projection_tests {
use super::*;
fn bifrost_intent(id: &str) -> EpiphanyCultMeshBifrostBodyChangePublicationIntentEntry {
EpiphanyCultMeshBifrostBodyChangePublicationIntentEntry {
schema_version: epiphany_core::EPIPHANY_CULTMESH_BIFROST_BODY_CHANGE_PUBLICATION_INTENT_SCHEMA_VERSION.to_string(),
intent_id: id.to_string(),
source_cluster_id: "hands".to_string(),
source_agent_id: "epiphany.Hands".to_string(),
body_domain: "repo:test".to_string(),
target_repository: "test/repo".to_string(),
target_branch: "main".to_string(),
change_summary: "test".to_string(),
justification: "test".to_string(),
changed_paths: vec!["src/lib.rs".to_string()],
verification_receipt_ids: vec!["verify-1".to_string()],
review_receipt_ids: vec!["review-1".to_string()],
authorship_agent_ids: vec!["epiphany.Hands".to_string()],
credit_subjects: vec!["epiphany.Hands".to_string()],
github_publication_requested: true,
private_state_included: false,
notes: Vec::new(),
}
}
fn bifrost_publication(
id: &str,
intent_id: &str,
) -> EpiphanyCultMeshBifrostBodyChangePublicationReceiptEntry {
EpiphanyCultMeshBifrostBodyChangePublicationReceiptEntry {
schema_version: epiphany_core::EPIPHANY_CULTMESH_BIFROST_BODY_CHANGE_PUBLICATION_RECEIPT_SCHEMA_VERSION.to_string(),
receipt_id: id.to_string(),
intent_id: intent_id.to_string(),
status: "accepted".to_string(),
bifrost_ledger_entry_id: "ledger-1".to_string(),
github_publication_receipt_id: "github-1".to_string(),
credit_receipt_ids: vec!["credit-1".to_string()],
accepted_changed_paths: vec!["src/lib.rs".to_string()],
reviewer_ids: vec!["reviewer-1".to_string()],
publication_url: "https://example.invalid/publication".to_string(),
private_state_exposed: false,
notes: Vec::new(),
}
}
fn github_publication(
publication_id: &str,
) -> EpiphanyCultMeshBifrostGithubPublicationReceiptEntry {
EpiphanyCultMeshBifrostGithubPublicationReceiptEntry {
schema_version:
epiphany_core::EPIPHANY_CULTMESH_BIFROST_GITHUB_PUBLICATION_RECEIPT_SCHEMA_VERSION
.to_string(),
receipt_id: "github-1".to_string(),
bifrost_publication_receipt_id: publication_id.to_string(),
hands_pr_receipt_id: "hands-pr-1".to_string(),
target_repository: "test/repo".to_string(),
target_branch: "main".to_string(),
pull_request_url: "https://example.invalid/pr/1".to_string(),
pull_request_number: "1".to_string(),
commit_sha: "abc123".to_string(),
changed_paths: vec!["src/lib.rs".to_string()],
ledger_entry_id: "ledger-1".to_string(),
credit_receipt_ids: vec!["credit-1".to_string()],
published_by_agent_id: "epiphany.Hands".to_string(),
publication_status: "published".to_string(),
private_state_exposed: false,
notes: Vec::new(),
}
}
fn receipt(
id: &str,
status: &str,
@ -9480,4 +9555,40 @@ mod lifecycle_projection_tests {
assert_eq!(selected.receipt_id, "new-failure");
assert!(service_lifecycle_status_needs_attention(&selected.status));
}
#[test]
fn bifrost_accounting_closes_only_a_typed_identity_chain() {
let intent = bifrost_intent("intent-1");
let publication = bifrost_publication("publication-1", "intent-1");
let github = github_publication("publication-1");
let (matching, _) = bifrost_accounting_rows(
Some(&intent),
Some(&publication),
Some(&github),
None,
None,
None,
None,
None,
&[],
);
assert_eq!(matching[0].status, "closed");
let unrelated_publication = bifrost_publication("publication-2", "another-intent");
let unrelated_github = github_publication("another-publication");
let (mismatched, _) = bifrost_accounting_rows(
Some(&intent),
Some(&unrelated_publication),
Some(&unrelated_github),
None,
None,
None,
None,
None,
&[],
);
assert_eq!(mismatched[0].status, "open");
assert!(mismatched[0].closure.contains("publication=missing"));
assert!(mismatched[0].closure.contains("github=missing"));
}
}

View file

@ -226,6 +226,7 @@ pub fn render_epiphany_prompt_context(input: &EpiphanyPromptContextInput) -> Str
.local_verse
.latest_bifrost_body_change_publication_receipt
.as_ref()
.filter(|receipt| receipt.intent_id == intent.intent_id)
{
lines.push(format!(
"- Receipt `{}`: status={}, ledger={}, github_receipt={}, private_state_exposed={}",
@ -235,20 +236,21 @@ pub fn render_epiphany_prompt_context(input: &EpiphanyPromptContextInput) -> Str
receipt.github_publication_receipt_id,
receipt.private_state_exposed
));
}
if let Some(github) = input
.local_verse
.latest_bifrost_github_publication_receipt
.as_ref()
{
lines.push(format!(
"- GitHub `{}`: pr={}, hands_pr={}, ledger={}, private_state_exposed={}",
github.receipt_id,
github.pull_request_url,
github.hands_pr_receipt_id,
github.ledger_entry_id,
github.private_state_exposed
));
if let Some(github) = input
.local_verse
.latest_bifrost_github_publication_receipt
.as_ref()
.filter(|github| github.bifrost_publication_receipt_id == receipt.receipt_id)
{
lines.push(format!(
"- GitHub `{}`: pr={}, hands_pr={}, ledger={}, private_state_exposed={}",
github.receipt_id,
github.pull_request_url,
github.hands_pr_receipt_id,
github.ledger_entry_id,
github.private_state_exposed
));
}
}
}
@ -660,11 +662,20 @@ mod tests {
.as_mut()
.expect("Eve receipt remains available for mismatch probe")
.intent_id = "another-eve-intent".to_string();
prompt_input
.local_verse
.latest_bifrost_body_change_publication_receipt
.as_mut()
.expect("Bifrost receipt remains available for mismatch probe")
.intent_id = "another-bifrost-intent".to_string();
let mismatched = render_epiphany_prompt_context(&prompt_input);
assert!(mismatched.contains("daemon-tool-intent-prompt-test"));
assert!(!mismatched.contains("receipt_contract=epiphany.hands.action_review"));
assert!(mismatched.contains("eve-intent-prompt-test"));
assert!(!mismatched.contains("eve-receipt-prompt-test"));
assert!(mismatched.contains("bifrost-publication-intent-prompt-test"));
assert!(!mismatched.contains("bifrost-publication-receipt-prompt-test"));
assert!(!mismatched.contains("github-publication-receipt-prompt-test"));
Ok(())
}
}

View file

@ -658,6 +658,19 @@ only when their `intent_id` values match. Unmatched receipts remain persisted
for identity-aware diagnostics; adjacency in two convenience mirrors no longer
manufactures causality in the Mind's context.
## Independent Bifrost mirrors substituted for publication closure
The Bifrost prompt projection and accounting directory treated the latest body
change intent, latest Bifrost publication receipt, and latest GitHub receipt as
one closed chain merely because all three existed. Under concurrent or delayed
publication work, unrelated documents could therefore manufacture a closed
gate, borrowed review/credit counts, and a false public artifact reference.
Composition now follows the existing typed edges: publication
`intent_id -> intent.intent_id`, then GitHub
`bifrost_publication_receipt_id -> publication.receipt_id`. Prompt rendering
omits unmatched descendants, and accounting reports the lane open with missing
links instead of blessing adjacency as completion.
## Unresolved: provider receipts without chronology
Bifrost body-change/GitHub publication receipts are externally owned and their

View file

@ -165,6 +165,9 @@ The confirmed conceptual substitutions have been cut:
- Prompt assembly joins Eve and daemon-tool receipts to intents only by matching
`intent_id`; independent latest mirrors can no longer fabricate a causal
completion in model context.
- Bifrost prompt/accounting chains follow typed intent -> publication -> GitHub
receipt identity. Independent latest documents cannot close publication,
lend review/credit counts, or supply a counterfeit public reference.
- Bifrost provider receipt contracts still lack provider timestamp/revision.
Do not invent consumer-side ordering; repair that in a Bifrost-owned schema.

Binary file not shown.

View file

@ -660,4 +660,4 @@ current_status:
- Service lifecycle receipt history is now event-only: `load_epiphany_cultmesh_daemon_service_lifecycle_receipts` filters out the `epiphany-local/daemon-service-lifecycle-receipt/latest` mirror, while `load_latest_epiphany_cultmesh_daemon_service_lifecycle_receipt` remains the latest-state reader. Focused unit test `service_lifecycle_receipt_history_excludes_latest_mirror` proves two per-receipt writes return exactly two history records and latest points at the second. Fresh smoke `verse-query-lifecycle-history-filter-smoke` and live overview/triage `local-20260618-145632-497-efac6f0d` / `local-20260618-145632-321-867947af` prove compact swarm sights still expose service lifecycle attention plus the present elevated runbook artifact without daemon pokes or service mutation. Do not use a broad unscoped cargo test filter for this check on Windows; it fans out across bin harnesses and hit a broken pipe during this pass.
- `epiphany-verse-query seed-compact` is the compact seed receipt for wrappers and read-only rites. The older `seed` command still prints the full local Verse context for explicit debugging, but compact wrapper modes now avoid full `seed`/final `query` readback after the combined overview proof exposed Windows debug stack overflow from full-context serialization pressure.
last_completed_repo_swarm_cut: Huginn live-fire has crossed the first real repo Body from Persona pressure to branch-local Hands commit and Soul/Mind closure, and now has repeatable closure and redacted-public-proof mouths. On `E:\Projects\Huginn` branch `epiphany/huginn/first-awakening`, `tools/epiphany_local_run.ps1 -Mode repo-persona-intake` accepted item `huginn-inspection-readback-livefire`, Weksa/Persona intake remained sealed, and `epiphany-work derive-plan` lowered it to model-authored safe family `repo.status_section` for `README.md` only. `tools/epiphany_local_run.ps1 -Mode repo-swarm-run` advanced four bounded pulses: run-from-plan, adopt-from-plan, execute-from-plan, and close-from-execute. Huginn commit `e9b07c38fceabbdc89ea35c2156326c80c0393a2` updated only `README.md`; closure receipt `.epiphany\work\work-close-huginn-inspection-readback-livefire.json` records status `closed`, Soul verdict `passed`, Mind state commit `repo-work-close-huginn-inspection-readback-livefire-mind-commit`, safe family `repo.status_section`, path scope `README.md`, Bifrost publication gate, and `privateStateExposed=false`. Verification also ran Huginn's TypeScript compiler through concrete Node path: `C:\Program Files\nodejs\node.exe E:\Projects\Huginn\node_modules\typescript\bin\tsc -p tsconfig.json` passed. Native verifier `epiphany-repo-livefire-closure-smoke --workspace E:\Projects\Huginn --item huginn-inspection-readback-livefire --expected-path README.md --expected-family repo.status_section --expected-gate awaiting-publication --expected-blocker bifrost-publication-missing` checks closure receipt, Mind map entry, overview gate/blocker, authority seals, and `git show` changed paths, returning `status=ok`, `soulVerdict=passed`, `publicationGate=Bifrost`, and `privateStateExposed=false`. Wrapper `tools/epiphany_local_run.ps1 -Mode repo-livefire-closure -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exposes the same read-only proof through the local operator mouth; artifact `local-20260621-190156-803-2f85ba15` printed commit, path, gate, blocker, Soul verdict, Mind commit receipt, and `privateStateExposed=False` without push, merge, publication, or target mutation. New wrapper `tools/epiphany_local_run.ps1 -Mode repo-work-public-proof -Workspace E:\Projects\Huginn -RepoWorkItem huginn-inspection-readback-livefire -SkipBuild` exports the redacted proof bundle and repo-local Verse index without publishing; artifact `local-20260621-190645-345-4555b1dc` wrote `.epiphany\public\proof-bundles\repo-work-public-proof-huginn-inspection-readback-livefire.json`, proof id `repo-work-public-proof-huginn-inspection-readback-livefire`, SHA-256 `857545b199f3e37eb06c2e757e5cfc201958e46667bb1f8403fd333a22620f63`, nine artifact rows, zero publication rows, and `privateStateExposed=False`. swarm overview readback `local-20260621-190701-140-b9519ec6` sees the `REPO-WORK-PUBLIC-PROOF` row and action row from Huginn's repo-local Verse with the same SHA-256 while Bifrost publication remains missing. Launcher compact modes are now named compact operator-context modes instead of read-only modes, and `bifrost-public-proof` summaries print item, target public Verse, room, proof SHA-256, review receipts, and credit receipts so publication receipts are auditable without claiming upstream sync.
next_action: Continue the conceptual-substitution modeling pass beyond receipt writers. Operational/evidence/self-state and operator surfaces with owned clocks are event-time ordered; operator receipts bind intents by run identity rather than global chronology; prompt context joins Eve/tool receipts only by typed intent identity; repo-work projections follow owning time; writerless readiness-review is deleted. Bifrost provider receipts still need a provider-owned chronology contract rather than consumer inference. Continue auditing other evidence/security mirrors, service configuration claims, adapters, and destructive path consumers for borrowed authority.
next_action: Continue the conceptual-substitution modeling pass beyond receipt writers. Operational/evidence/self-state and operator surfaces with owned clocks are event-time ordered; operator receipts bind intents by run identity rather than global chronology; Eve/tool/Bifrost projections join chains only by typed identity; repo-work projections follow owning time; writerless readiness-review is deleted. Bifrost provider receipts still need a provider-owned chronology contract rather than consumer inference. Continue auditing other evidence/security mirrors, service configuration claims, adapters, and destructive path consumers for borrowed authority.