From 98b61473653905ae4efc40eac19116a28b61a577 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 13 Jul 2026 01:25:18 +0100 Subject: [PATCH] Close Bifrost chains by typed identity --- epiphany-core/src/bin/epiphany-verse-query.rs | 143 ++++++++++++++++-- epiphany-core/src/prompt_context.rs | 39 +++-- ...onceptual-substitution-audit-2026-07-12.md | 13 ++ notes/fresh-workspace-handoff.md | 3 + state/ledgers.msgpack | Bin 455077 -> 455479 bytes state/map.yaml | 2 +- 6 files changed, 169 insertions(+), 31 deletions(-) diff --git a/epiphany-core/src/bin/epiphany-verse-query.rs b/epiphany-core/src/bin/epiphany-verse-query.rs index 69cbfeb5..ea902be2 100644 --- a/epiphany-core/src/bin/epiphany-verse-query.rs +++ b/epiphany-core/src/bin/epiphany-verse-query.rs @@ -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>, message: &str) -> Result 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")); + } } diff --git a/epiphany-core/src/prompt_context.rs b/epiphany-core/src/prompt_context.rs index 134a12be..c6d422bb 100644 --- a/epiphany-core/src/prompt_context.rs +++ b/epiphany-core/src/prompt_context.rs @@ -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(()) } } diff --git a/notes/conceptual-substitution-audit-2026-07-12.md b/notes/conceptual-substitution-audit-2026-07-12.md index ea003568..47928b48 100644 --- a/notes/conceptual-substitution-audit-2026-07-12.md +++ b/notes/conceptual-substitution-audit-2026-07-12.md @@ -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 diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 31d36fcd..708c8c3c 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -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. diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index c38b54d84ed7059ebaf19d032c3b5e217b2cb881..2ffea42932248bf01ccd602d6439ef5d385856d1 100644 GIT binary patch delta 397 zcmY+Ay-EW?5Xar9S6C_7NDv0C1WiN;_yHCQe)Lv0VQ+6Px4O4`?#>ZXrLgb`LY}}r zXpuLt_8kOU#V2q!S}5%7FvIL`{{MY^Yfj&rlQpr_Iov(Cewj8aHTy06)UhLkWBJFGd z@+b*2&^E&g`LYCtJN#H%;;LQMsnJTt@%Fiz zt`E60h7%2{89&0hIs+=uLi!`H`v>0rrN$Y|dKiwN)S;B53JkoGwRz@3hCy*5ZQTFg zn8%Vegi~;A1^SwbhzQZM(e^g9;LNFdCN};B`W!Xw%!EU<9JV2^je)(+{&@0~YLo&*giCER%R?@WVCIb%G5rUi4llFG&2yh05R+KsZ4AF F?*S%D5-|V( diff --git a/state/map.yaml b/state/map.yaml index c620a256..fb96a1fd 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -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.