Close Bifrost chains by typed identity
This commit is contained in:
parent
d65e02d448
commit
98b6147365
6 changed files with 169 additions and 31 deletions
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -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.
|
||||
|
|
|
|||
Reference in a new issue