diff --git a/epiphany-core/src/bin/epiphany-work.rs b/epiphany-core/src/bin/epiphany-work.rs index f49be104..66070ca9 100644 --- a/epiphany-core/src/bin/epiphany-work.rs +++ b/epiphany-core/src/bin/epiphany-work.rs @@ -7396,74 +7396,63 @@ fn closure_family_assertions( ); } "repo.scheduling_request" => { + let request = parse_repo_scheduling_request(&content).ok(); push_assertion( &mut assertions, "scheduling-request-schema-present", - content.contains("schema_version = \"epiphany.repo_scheduling_request.v0\""), + request + .as_ref() + .is_some_and(RepoSchedulingRequest::has_canonical_identity), "Committed scheduling request carries the schema version.".to_string(), ); push_assertion( &mut assertions, "scheduling-request-family-present", - content.contains("safe_action_family = \"repo.scheduling_request\""), + request + .as_ref() + .is_some_and(RepoSchedulingRequest::has_canonical_identity), "Committed scheduling request carries the safe action family.".to_string(), ); push_assertion( &mut assertions, "scheduling-request-summary-present", - content.contains(&compact_summary), + request + .as_ref() + .is_some_and(|value| value.summary == compact_summary), "Committed scheduling request contains the accepted pressure summary.".to_string(), ); push_assertion( &mut assertions, "scheduling-request-awaits-mind-adoption", - content.contains("[request]") - && content.contains("status = \"awaiting-mind-adoption\"") - && content.contains("requested_scheduler = \"Self\"") - && content.contains("mind_adoption_receipt_required = true") - && content.contains("self_may_schedule_after_mind_only = true") - && content.contains("queue_run_allowed_after_adoption = true"), + request.as_ref().is_some_and(RepoSchedulingRequest::awaits_mind_adoption), "Committed scheduling request waits for Mind adoption before Self queue consequence.".to_string(), ); push_assertion( &mut assertions, "scheduling-request-queue-contract", - content.contains("[queue]") - && content.contains("target_gate = \"repo-work-queue\"") - && content.contains("preferred_next_safe_family = \"repo.task_card\"") - && content.contains("max_items_per_pulse = 1") - && content.contains("requires_epiphany_branch = true") - && content.contains("publish_blocker = \"bifrost-publication-missing\""), + request + .as_ref() + .is_some_and(RepoSchedulingRequest::has_bounded_queue_contract), "Committed scheduling request names a bounded queue pulse contract.".to_string(), ); push_assertion( &mut assertions, "scheduling-request-receipt-contract", - content.contains("[required_receipts]") - && content.contains("mind_review = \"epiphany.mind.gateway_review\"") - && content.contains("mind_commit = \"epiphany.mind.state_commit_receipt\"") - && content.contains( - "expected_self_receipt = \"epiphany.repo_work_queue_selection.v0\"", - ), + request.as_ref().is_some_and(RepoSchedulingRequest::has_receipt_contract), "Committed scheduling request requires Mind receipts and names the later Self receipt.".to_string(), ); push_assertion( &mut assertions, "scheduling-request-authority-seals", - content.contains("[authority]") - && content.contains("self_scheduling_authorized = false") - && content.contains("queue_mutation_authorized = false") - && content.contains("hands_action_authorized = false") - && content.contains("publication_authorized = false") - && content.contains("cross_body_mutation_authorized = false") - && content.contains("mind_adoption_required = true") - && content.contains("bifrost_publication_required = true"), + request.as_ref().is_some_and(RepoSchedulingRequest::has_authority_seals), "Committed scheduling request denies scheduling/queue/action/publication/cross-body authority and requires Mind/Bifrost gates.".to_string(), ); push_assertion( &mut assertions, "scheduling-request-private-seal", - content.contains("private_state_exposed = false"), + request + .as_ref() + .is_some_and(|value| !value.private_state_exposed), "Committed scheduling request preserves the private-state seal.".to_string(), ); } @@ -15370,6 +15359,7 @@ idunn_deployment_authority_required = true "repo.sync_request", "repo.pr_request", "repo.maintainer_review_request", + "repo.scheduling_request", "repo.work_order", "repo.verification_request", "repo.credit_request", diff --git a/epiphany-core/src/bin/epiphany_work/closure_contracts/workflow.rs b/epiphany-core/src/bin/epiphany_work/closure_contracts/workflow.rs index cc78872c..c2ac45f0 100644 --- a/epiphany-core/src/bin/epiphany_work/closure_contracts/workflow.rs +++ b/epiphany-core/src/bin/epiphany_work/closure_contracts/workflow.rs @@ -111,6 +111,100 @@ pub(super) fn parse_repo_work_order(text: &str) -> Result { toml::from_str(text).context("work order is not valid typed TOML") } +#[derive(Debug, Deserialize)] +pub(super) struct RepoSchedulingRequest { + pub(super) schema_version: String, + pub(super) safe_action_family: String, + pub(super) summary: String, + pub(super) private_state_exposed: bool, + request: RepoSchedulingBody, + queue: RepoSchedulingQueue, + required_receipts: RepoSchedulingReceipts, + authority: RepoSchedulingAuthority, +} + +impl RepoSchedulingRequest { + pub(super) fn has_canonical_identity(&self) -> bool { + self.schema_version == "epiphany.repo_scheduling_request.v0" + && self.safe_action_family == "repo.scheduling_request" + } + + pub(super) fn awaits_mind_adoption(&self) -> bool { + let r = &self.request; + r.status == "awaiting-mind-adoption" + && r.requested_scheduler == "Self" + && r.mind_adoption_receipt_required + && r.self_may_schedule_after_mind_only + && r.queue_run_allowed_after_adoption + } + + pub(super) fn has_bounded_queue_contract(&self) -> bool { + let q = &self.queue; + q.target_gate == "repo-work-queue" + && q.preferred_next_safe_family == "repo.task_card" + && q.max_items_per_pulse == 1 + && q.requires_epiphany_branch + && q.publish_blocker == "bifrost-publication-missing" + } + + pub(super) fn has_receipt_contract(&self) -> bool { + let r = &self.required_receipts; + r.mind_review == "epiphany.mind.gateway_review" + && r.mind_commit == "epiphany.mind.state_commit_receipt" + && r.expected_self_receipt == "epiphany.repo_work_queue_selection.v0" + } + + pub(super) fn has_authority_seals(&self) -> bool { + let a = &self.authority; + a.branch_local_only + && !a.self_scheduling_authorized + && !a.queue_mutation_authorized + && !a.hands_action_authorized + && !a.publication_authorized + && !a.cross_body_mutation_authorized + && a.mind_adoption_required + && a.bifrost_publication_required + } +} + +#[derive(Debug, Deserialize)] +struct RepoSchedulingBody { + status: String, + requested_scheduler: String, + mind_adoption_receipt_required: bool, + self_may_schedule_after_mind_only: bool, + queue_run_allowed_after_adoption: bool, +} +#[derive(Debug, Deserialize)] +struct RepoSchedulingQueue { + target_gate: String, + preferred_next_safe_family: String, + max_items_per_pulse: u64, + requires_epiphany_branch: bool, + publish_blocker: String, +} +#[derive(Debug, Deserialize)] +struct RepoSchedulingReceipts { + mind_review: String, + mind_commit: String, + expected_self_receipt: String, +} +#[derive(Debug, Deserialize)] +struct RepoSchedulingAuthority { + branch_local_only: bool, + self_scheduling_authorized: bool, + queue_mutation_authorized: bool, + hands_action_authorized: bool, + publication_authorized: bool, + cross_body_mutation_authorized: bool, + mind_adoption_required: bool, + bifrost_publication_required: bool, +} + +pub(super) fn parse_repo_scheduling_request(text: &str) -> Result { + toml::from_str(text).context("scheduling request is not valid typed TOML") +} + #[derive(Debug, Deserialize)] pub(super) struct RepoVerificationRequest { pub(super) schema_version: String, diff --git a/epiphany-core/src/bin/epiphany_work/closure_contracts/workflow_tests.rs b/epiphany-core/src/bin/epiphany_work/closure_contracts/workflow_tests.rs index 4f8babb9..5132ee8d 100644 --- a/epiphany-core/src/bin/epiphany_work/closure_contracts/workflow_tests.rs +++ b/epiphany-core/src/bin/epiphany_work/closure_contracts/workflow_tests.rs @@ -2,6 +2,44 @@ mod workflow_tests { use super::*; + #[test] + fn scheduling_comment_cannot_counterfeit_queue_authority_seal() { + let text = r#" +schema_version = "epiphany.repo_scheduling_request.v0" +safe_action_family = "repo.scheduling_request" +summary = "summary" +private_state_exposed = false +[request] +status = "awaiting-mind-adoption" +requested_scheduler = "Self" +mind_adoption_receipt_required = true +self_may_schedule_after_mind_only = true +queue_run_allowed_after_adoption = true +[queue] +target_gate = "repo-work-queue" +preferred_next_safe_family = "repo.task_card" +max_items_per_pulse = 1 +requires_epiphany_branch = true +publish_blocker = "bifrost-publication-missing" +[required_receipts] +mind_review = "epiphany.mind.gateway_review" +mind_commit = "epiphany.mind.state_commit_receipt" +expected_self_receipt = "epiphany.repo_work_queue_selection.v0" +[authority] +branch_local_only = true +self_scheduling_authorized = false +# queue_mutation_authorized = false +queue_mutation_authorized = true +hands_action_authorized = false +publication_authorized = false +cross_body_mutation_authorized = false +mind_adoption_required = true +bifrost_publication_required = true +"#; + let request = parse_repo_scheduling_request(text).expect("fixture is typed TOML"); + assert!(!request.has_authority_seals()); + } + #[test] fn work_order_comment_cannot_counterfeit_hands_authority_seal() { let text = r#" diff --git a/notes/conceptual-substitution-audit-2026-07-12.md b/notes/conceptual-substitution-audit-2026-07-12.md index 1a0f4cfa..c1b4d9a1 100644 --- a/notes/conceptual-substitution-audit-2026-07-12.md +++ b/notes/conceptual-substitution-audit-2026-07-12.md @@ -1041,6 +1041,19 @@ regaining substring authority. Fourteen high-authority closure families are typed; 507 substring assertions remain. Adoption and scheduling are the next workflow-shaped candidates. +## Scheduling prose as Self queue authority + +Scheduling carried the sharper upstream consequence: it names the Self queue +gate, next safe family, pulse bound, prerequisite Mind receipts, and expected +queue-selection receipt. Closure previously recognized all of that through +substring presence. A commented `queue_mutation_authorized = false` could hide +a real grant. `repo.scheduling_request` now parses typed workflow state, checks +the actual queue and authority fields, and has a malicious fixture plus class +guard preventing that substitution from returning. + +Fifteen high-authority closure families are typed; 477 substring assertions +remain. Adoption is the remaining untyped workflow gate in this chain. + The same inspection found 33 consecutive duplicate `#[cfg(test)]` attributes on provider constructors/writers and their re-exports. One gate already makes each item test-only; the second owned nothing. The duplicates are removed. diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 22bb9cbc..0d05cf05 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -329,6 +329,13 @@ high-authority families are typed and 507 substring assertions remain. Next inspect adoption versus scheduling and convert the authority-bearing family with its own negative proof. +Scheduling won that comparison because it directly names Self's queue gate, +pulse, next family, and queue-selection receipt. Its closure is now typed under +workflow ownership; a malicious commented queue denial cannot conceal a real +mutation grant, and the family is in the no-substring class guard. Fifteen +high-authority families are typed and 477 substring assertions remain. Next +type adoption, the remaining untyped workflow gate in this chain. + The latest structural count is 32 closure family branches, 744 remaining substring assertions in the closure region, and 1,284 lines in `closure_contracts.rs`. Do not blindly generate a struct forest for every diff --git a/state/ledgers.msgpack b/state/ledgers.msgpack index 55dd5f79..605f6674 100644 Binary files a/state/ledgers.msgpack and b/state/ledgers.msgpack differ diff --git a/state/map.yaml b/state/map.yaml index a3ef9f46..04608239 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 pass. Fourteen high-authority closure families are typed and guarded; 507 substring assertions remain. Workflow owns typed verification and work-order closure plus their counterfeit-authority fixtures. Work-order comments can no longer impersonate Hands authority. Direct rustfmt is required because Cargo fmt misses the lexically included files. Next compare adoption and scheduling closure authority and type the higher-consequence workflow family with a negative proof. External governance should converge on tooling plus publication/accounting; operations retains policy/deployment. Provider chronology is explicitly external and guarded as arrival order. Continue auditing evidence/security mirrors and adapters for borrowed authority. + next_action: Continue the conceptual-substitution pass. Fifteen high-authority closure families are typed and guarded; 477 substring assertions remain. Workflow owns typed verification, work-order, and scheduling closure plus counterfeit-authority fixtures. Scheduling comments can no longer impersonate Self queue authority. Direct rustfmt is required because Cargo fmt misses the lexically included files. Next type adoption, the remaining untyped workflow gate in this chain, with a negative Mind/state-authority proof. External governance should converge on tooling plus publication/accounting; operations retains policy/deployment. Provider chronology is explicitly external and guarded as arrival order. Continue auditing evidence/security mirrors and adapters for borrowed authority.