diff --git a/README.md b/README.md index 6705dee..777130e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ JP's personal assistant / chief of staff. Daily briefing, inbox triage, comms in - **BlueBubbles binding:** [`docs/contracts/personal-agent-bluebubbles-binding.json`](docs/contracts/personal-agent-bluebubbles-binding.json) — `imessage.read` binds to the existing BlueBubbles package without a duplicate connector. - **Proton/rclone package candidate:** [`docs/contracts/personal-agent-proton-rclone-package.json`](docs/contracts/personal-agent-proton-rclone-package.json) — Mail, Calendar, Contacts, and Drive surfaces with redacted runtime inventory and no readiness overclaim. - **Proton/rclone runtime reconciliation:** [`docs/evidence/2026-06-14-personal-agent-proton-rclone-runtime-reconciliation.md`](docs/evidence/2026-06-14-personal-agent-proton-rclone-runtime-reconciliation.md) — live redacted probe aligning systemd, Docker, MCP, and rclone posture. -- **Secondbrain proposal route:** [`docs/contracts/personal-agent-secondbrain-proposal-route.json`](docs/contracts/personal-agent-secondbrain-proposal-route.json) — proposal-only personal memory intake; durable apply remains owned by Secondbrain. +- **Secondbrain proposal/apply route:** [`docs/contracts/personal-agent-secondbrain-proposal-route.json`](docs/contracts/personal-agent-secondbrain-proposal-route.json) — proposal-only personal memory intake plus governed apply-route reference; live durable apply remains approval-gated in Secondbrain. - **Conductor/Curator service handoff:** [`docs/contracts/personal-agent-conductor-curator-service-handoff.json`](docs/contracts/personal-agent-conductor-curator-service-handoff.json) — redacted service map for future route selection and hygiene review pickup. - **Runtime readiness snapshot:** [`docs/contracts/personal-agent-runtime-readiness-snapshot.json`](docs/contracts/personal-agent-runtime-readiness-snapshot.json) — redacted per-surface runtime state and gaps; aggregate readiness remains degraded. - **Desktop exposure contract:** [`docs/contracts/personal-agent-desktop-exposure-contract.json`](docs/contracts/personal-agent-desktop-exposure-contract.json) — adapter-facing state rows for Desktop/Dashboard display; no UI wiring from this route. diff --git a/WORKBOARD.yaml b/WORKBOARD.yaml index 7ec072a..45f95e5 100644 --- a/WORKBOARD.yaml +++ b/WORKBOARD.yaml @@ -54,3 +54,8 @@ items: status: complete source: docs/evidence/2026-06-14-personal-agent-proton-rclone-runtime-reconciliation.md owner: jp + - id: PACR-010 + title: Secondbrain Governed Apply Route Reconciliation + status: complete + source: docs/contracts/personal-agent-secondbrain-proposal-route.json + owner: jp diff --git a/docs/contracts/personal-agent-bluebubbles-binding.json b/docs/contracts/personal-agent-bluebubbles-binding.json index b11ada2..55da71f 100644 --- a/docs/contracts/personal-agent-bluebubbles-binding.json +++ b/docs/contracts/personal-agent-bluebubbles-binding.json @@ -28,7 +28,7 @@ "forbidden": [ "orgbrain" ], - "durable_write_policy": "proposal-only-until-governed-secondbrain-curator-apply-route" + "durable_write_policy": "proposal-only; governed Secondbrain apply route is defined but live apply remains approval-gated" }, "allowed_effects": [ "read_message_stream", @@ -74,6 +74,7 @@ "contracts/personal-agent-imessage-readonly-contract.json", "contracts/runtime-compliance-boundary.json", "contracts/secondbrain-proposal-envelope-contract.json", + "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-apply-contract.md", ".sot/08-OUTPUTS/bluebubbles-live-service-package-proof.json", ".sot/08-OUTPUTS/bluebubbles-always-on-resilience-proof.json", "runtime/steev/hermes-personal-agent-bluebubbles.service", @@ -82,6 +83,7 @@ }, "remaining_gates": { "seed_package_pickup": "blocked-follow-up", + "secondbrain_governed_apply_route": "defined-no-live-apply", "secondbrain_durable_apply": "blocked-follow-up", "desktop_adapter_exposure": "blocked-follow-up", "browser_webwright_host_runtime": "separate-hitl-approval" diff --git a/docs/contracts/personal-agent-conductor-curator-service-handoff.json b/docs/contracts/personal-agent-conductor-curator-service-handoff.json index 564a0c9..51f6b4d 100644 --- a/docs/contracts/personal-agent-conductor-curator-service-handoff.json +++ b/docs/contracts/personal-agent-conductor-curator-service-handoff.json @@ -79,11 +79,12 @@ "owner_route": "steev", "surface": "secondbrain.memory.proposal", "health_shape": "redacted-proposal-envelope-contract", - "readiness_state": "profile-contract-ready-apply-blocked", + "readiness_state": "profile-contract-ready-governed-apply-defined", "allowed_effects": [ "emit_redacted_proposal", "emit_source_handle", - "emit_content_digest" + "emit_content_digest", + "reference_governed_apply_route" ], "denied_effects": [ "secondbrain_apply", @@ -159,7 +160,11 @@ "target": "secondbrain-personal", "apply_owner": "secondbrain", "hygiene_owner": "curator", + "apply_route_defined": true, + "apply_route_contract": "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-apply-contract.md", "apply_allowed_now": false, + "live_apply_executed": false, + "durable_apply_without_approval": false, "requires_proposal_envelope": true, "requires_approval": true, "requires_secondbrain_validator": "python3 tools/validate_secondbrain_child.py", @@ -172,7 +177,8 @@ "docs/contracts/personal-agent-profile-surface-contract.json", "docs/contracts/personal-agent-bluebubbles-binding.json", "docs/contracts/personal-agent-proton-rclone-package.json", - "docs/contracts/personal-agent-secondbrain-proposal-route.json" + "docs/contracts/personal-agent-secondbrain-proposal-route.json", + "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-apply-contract.md" ], "proof_policy": { "mode": "redacted-only", @@ -194,6 +200,7 @@ "remaining_gates": { "conductor_lane_pickup": "blocked-follow-up", "curator_personal_memory_hygiene_lane_pickup": "blocked-follow-up", + "secondbrain_governed_apply_route": "defined-no-live-apply", "secondbrain_durable_apply": "blocked-follow-up", "runtime_health_proof": "blocked-follow-up", "desktop_adapter_exposure": "blocked-follow-up", diff --git a/docs/contracts/personal-agent-desktop-exposure-contract.json b/docs/contracts/personal-agent-desktop-exposure-contract.json index c77dc15..29bc226 100644 --- a/docs/contracts/personal-agent-desktop-exposure-contract.json +++ b/docs/contracts/personal-agent-desktop-exposure-contract.json @@ -96,7 +96,7 @@ "surface": "capability.catalog", "state": "pending", "source_contract": "docs/contracts/personal-agent-secondbrain-proposal-route.json", - "visible_reason": "Proposal envelope route exists; durable Secondbrain apply remains blocked." + "visible_reason": "Proposal envelope route and governed apply route exist; live durable Secondbrain apply remains approval-gated." }, { "row_id": "personal-agent.browser.host-runtime", @@ -161,6 +161,7 @@ "adapter_lane_pickup": "blocked-follow-up", "desktop_ui_wiring": "blocked-follow-up", "seed_package_pickup": "blocked-follow-up", + "secondbrain_governed_apply_route": "defined-no-live-apply", "runtime_readiness_finalization": "blocked-follow-up", "browser_host_runtime_approval": "blocked-follow-up", "final_acceptance_packet": "blocked-follow-up" diff --git a/docs/contracts/personal-agent-runtime-readiness-snapshot.json b/docs/contracts/personal-agent-runtime-readiness-snapshot.json index 6532f7f..90a4294 100644 --- a/docs/contracts/personal-agent-runtime-readiness-snapshot.json +++ b/docs/contracts/personal-agent-runtime-readiness-snapshot.json @@ -24,9 +24,11 @@ "read_only_imessage": true, "memory_domain": "secondbrain-personal", "orgbrain_forbidden": true, + "secondbrain_intake_contract": "ready", + "secondbrain_governed_apply_route": "defined-no-live-apply", "package_runtime_claims": false }, - "remaining_gap": "Profile aggregate runtime readiness still false until final acceptance packet." + "remaining_gap": "Profile aggregate runtime readiness still false until final acceptance packet and approved live apply." }, { "surface": "mail.read", @@ -125,8 +127,8 @@ { "id": "secondbrain-apply-blocked", "severity": "must-fix", - "state": "proposal route exists; durable apply remains blocked", - "impact": "personal memory intake is not durable yet" + "state": "proposal route and governed apply route exist; live durable apply remains blocked without approval", + "impact": "personal memory intake can be proposed and checked, but is not live-applied yet" }, { "id": "desktop-adapter-exposure-blocked", @@ -171,6 +173,7 @@ "proton_contacts_gate_repair": "blocked-follow-up", "proton_bridge_systemd_convergence": "blocked-follow-up", "proton_rclone_child_registration": "blocked-follow-up", + "secondbrain_governed_apply_route": "defined-no-live-apply", "secondbrain_durable_apply": "blocked-follow-up", "desktop_adapter_exposure": "blocked-follow-up", "reboot_power_loss_drill": "optional-follow-up", diff --git a/docs/contracts/personal-agent-secondbrain-proposal-route.json b/docs/contracts/personal-agent-secondbrain-proposal-route.json index a0ef94f..1fd9510 100644 --- a/docs/contracts/personal-agent-secondbrain-proposal-route.json +++ b/docs/contracts/personal-agent-secondbrain-proposal-route.json @@ -23,7 +23,7 @@ "capability_packages_emit_proposals_only": true, "apply_owner": "secondbrain", "hygiene_owner": "curator", - "notes": "personal-agent capability packages may emit redacted proposal envelopes. Durable Memory Object writes wait for Secondbrain governed apply." + "notes": "personal-agent capability packages may emit redacted proposal envelopes. Secondbrain now defines the governed apply route; live durable Memory Object writes still require approval and Secondbrain evidence." }, "source_routes": [ { @@ -31,6 +31,7 @@ "capability_package": "bluebubbles", "proposal_type": "secondbrain.memory.propose_create_from_imessage", "secondbrain_intake_contract": "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-intake-contract.md", + "secondbrain_apply_contract": "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-apply-contract.md", "target_lifecycle_state": "inbox", "allowed_effects": [ "emit_redacted_proposal", @@ -149,10 +150,15 @@ }, "apply_policy": { "apply_route": "Secondbrain governed memory write path", + "apply_route_contract": "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-apply-contract.md", + "governed_apply_route_defined": true, "apply_allowed_now": false, + "live_apply_executed": false, + "durable_apply_without_approval": false, "requires_secondbrain_validator": "python3 tools/validate_secondbrain_child.py", "requires_focused_secondbrain_gate": true, "focused_secondbrain_gate_command": "python3 tools/check_secondbrain_personal_agent_imessage_intake.py", + "focused_secondbrain_apply_gate_command": "python3 tools/check_secondbrain_personal_agent_imessage_apply.py", "requires_human_or_governed_approval": true, "requires_local_evidence_and_handoff": true, "push_allowed": false @@ -190,7 +196,9 @@ "../secondbrain/docs/integration/2026-06-09-secondbrain-curator-hygiene-queue-contract.md", "../secondbrain/docs/integration/2026-06-09-secondbrain-hermes-runtime-boundary.md", "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-intake-contract.md", - "../secondbrain/docs/evidence/2026-06-14-secondbrain-personal-agent-imessage-intake-proof.md" + "../secondbrain/docs/evidence/2026-06-14-secondbrain-personal-agent-imessage-intake-proof.md", + "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-apply-contract.md", + "../secondbrain/docs/evidence/2026-06-14-secondbrain-personal-agent-imessage-apply-proof.md" ], "proof_policy": { "mode": "redacted-only", @@ -212,6 +220,7 @@ ] }, "remaining_gates": { + "secondbrain_governed_apply_route": "defined-no-live-apply", "secondbrain_imessage_intake_contract": "ready", "secondbrain_durable_apply": "blocked-follow-up", "curator_hygiene_apply_review": "blocked-follow-up", diff --git a/docs/supersession/2026-06-14-personal-agent-context-runtime-supersession-register.md b/docs/supersession/2026-06-14-personal-agent-context-runtime-supersession-register.md index c0c09c1..37c7e4a 100644 --- a/docs/supersession/2026-06-14-personal-agent-context-runtime-supersession-register.md +++ b/docs/supersession/2026-06-14-personal-agent-context-runtime-supersession-register.md @@ -36,8 +36,8 @@ desktop exposure must be treated as one of: | Proton/rclone package candidate | active-authority | `docs/contracts/personal-agent-proton-rclone-package.json` standardizes Mail, Calendar, Contacts, and Drive without child/runtime readiness overclaim | | Proton Mail/Calendar/Contacts | blocked-follow-up | Package child registration, degraded gate repair, and runtime proof remain follow-up work | | Proton Drive/rclone | blocked-follow-up | rclone read probe is redacted-ok; governed wrapper and write gates remain follow-up work | -| Personal-agent Secondbrain proposal route | active-authority | `docs/contracts/personal-agent-secondbrain-proposal-route.json` defines proposal-only personal memory intake | -| Personal memory durable apply | blocked-follow-up | Owning Secondbrain/curator route must approve and apply; profile/capability packages do not write durable memory | +| Personal-agent Secondbrain proposal/apply route | active-authority | `docs/contracts/personal-agent-secondbrain-proposal-route.json` defines proposal-only personal memory intake and references the governed Secondbrain apply route | +| Personal memory live durable apply | blocked-follow-up | Secondbrain apply route is defined, but live apply still requires approval; profile/capability packages do not write durable memory | | Personal-agent Conductor/Curator service handoff | active-authority | `docs/contracts/personal-agent-conductor-curator-service-handoff.json` gives route and hygiene lanes a redacted service map | | Conductor/curator adoption | blocked-follow-up | Owning lanes must explicitly pick up the handoff; this profile does not mutate them | | Personal-agent runtime readiness snapshot | active-authority | `docs/contracts/personal-agent-runtime-readiness-snapshot.json` names per-surface states and runtime gaps without aggregate readiness claim | @@ -78,7 +78,7 @@ desktop exposure must be treated as one of: - Graph context should treat Steev as display name / distribution alias only. - Graph context should expose BlueBubbles as the active iMessage capability package. - Graph context should expose the Proton/rclone package candidate as the active standardization pickup, not a runtime-ready child package. -- Graph context should expose the personal-agent Secondbrain proposal route as active while keeping durable apply blocked to Secondbrain/curator. +- Graph context should expose the personal-agent Secondbrain proposal/apply route as active while keeping live durable apply blocked to approval and Secondbrain/curator. - Graph context should expose the personal-agent Conductor/Curator service handoff as active, while adoption remains blocked to owning lanes. - Graph context should expose the personal-agent runtime snapshot as degraded until the named runtime gaps close. - Graph context should expose the personal-agent desktop exposure contract as active, while adapter UI wiring remains blocked to the adapter lane. diff --git a/tools/validate_steev_child.py b/tools/validate_steev_child.py index 3b731fc..1d8dd9e 100755 --- a/tools/validate_steev_child.py +++ b/tools/validate_steev_child.py @@ -199,6 +199,7 @@ def main() -> int: "PACR-006", "PACR-007", "PACR-008", + "PACR-010", "status: candidate", "owner: jp", ]: @@ -331,6 +332,7 @@ def main() -> int: "contracts/personal-agent-imessage-readonly-contract.json", "contracts/runtime-compliance-boundary.json", "contracts/secondbrain-proposal-envelope-contract.json", + "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-apply-contract.md", ]: if ref not in refs: errors.append(f"bluebubbles_binding_reference_missing:{ref}") @@ -556,6 +558,12 @@ def main() -> int: != "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-intake-contract.md" ): errors.append("memory_route_imessage_intake_contract_missing") + if ( + surface == "imessage.read" + and route.get("secondbrain_apply_contract") + != "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-apply-contract.md" + ): + errors.append("memory_route_imessage_apply_contract_missing") allowed = route.get("allowed_effects") if not isinstance(allowed, list) or "emit_redacted_proposal" not in allowed: errors.append(f"memory_route_redacted_proposal_not_allowed:{surface}") @@ -584,12 +592,22 @@ def main() -> int: apply_policy = memory_route.get("apply_policy", {}) if apply_policy.get("apply_route") != "Secondbrain governed memory write path": errors.append("memory_route_apply_policy_route_invalid") + if apply_policy.get("apply_route_contract") != "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-apply-contract.md": + errors.append("memory_route_apply_policy_contract_missing") + if apply_policy.get("governed_apply_route_defined") is not True: + errors.append("memory_route_apply_policy_route_not_defined") if apply_policy.get("apply_allowed_now") is not False: errors.append("memory_route_apply_allowed_now") + if apply_policy.get("live_apply_executed") is not False: + errors.append("memory_route_live_apply_executed") + if apply_policy.get("durable_apply_without_approval") is not False: + errors.append("memory_route_apply_without_approval") if apply_policy.get("requires_secondbrain_validator") != "python3 tools/validate_secondbrain_child.py": errors.append("memory_route_secondbrain_validator_missing") if apply_policy.get("focused_secondbrain_gate_command") != "python3 tools/check_secondbrain_personal_agent_imessage_intake.py": errors.append("memory_route_focused_secondbrain_gate_command_missing") + if apply_policy.get("focused_secondbrain_apply_gate_command") != "python3 tools/check_secondbrain_personal_agent_imessage_apply.py": + errors.append("memory_route_focused_secondbrain_apply_gate_command_missing") for key in [ "requires_focused_secondbrain_gate", "requires_human_or_governed_approval", @@ -618,6 +636,8 @@ def main() -> int: "../secondbrain/docs/integration/2026-06-09-secondbrain-hermes-runtime-boundary.md", "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-intake-contract.md", "../secondbrain/docs/evidence/2026-06-14-secondbrain-personal-agent-imessage-intake-proof.md", + "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-apply-contract.md", + "../secondbrain/docs/evidence/2026-06-14-secondbrain-personal-agent-imessage-apply-proof.md", ]: if ref not in refs: errors.append(f"memory_route_secondbrain_ref_missing:{ref}") @@ -636,6 +656,8 @@ def main() -> int: if field not in forbidden_fields: errors.append(f"memory_route_forbidden_field_missing:{field}") remaining_gates = memory_route.get("remaining_gates", {}) + if remaining_gates.get("secondbrain_governed_apply_route") != "defined-no-live-apply": + errors.append("memory_route_governed_apply_route_not_defined") for gate in [ "secondbrain_durable_apply", "curator_hygiene_apply_review", @@ -755,8 +777,16 @@ def main() -> int: errors.append("service_handoff_apply_owner_invalid") if apply.get("hygiene_owner") != "curator": errors.append("service_handoff_hygiene_owner_invalid") + if apply.get("apply_route_defined") is not True: + errors.append("service_handoff_apply_route_not_defined") + if apply.get("apply_route_contract") != "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-apply-contract.md": + errors.append("service_handoff_apply_route_contract_missing") if apply.get("apply_allowed_now") is not False: errors.append("service_handoff_apply_allowed_now") + if apply.get("live_apply_executed") is not False: + errors.append("service_handoff_live_apply_executed") + if apply.get("durable_apply_without_approval") is not False: + errors.append("service_handoff_apply_without_approval") for key in ["requires_proposal_envelope", "requires_approval", "requires_redacted_evidence"]: if apply.get(key) is not True: errors.append(f"service_handoff_apply_requirement_missing:{key}") @@ -770,6 +800,7 @@ def main() -> int: "docs/contracts/personal-agent-bluebubbles-binding.json", "docs/contracts/personal-agent-proton-rclone-package.json", "docs/contracts/personal-agent-secondbrain-proposal-route.json", + "../secondbrain/docs/integration/2026-06-14-secondbrain-personal-agent-imessage-apply-contract.md", ]: if ref not in source_contracts: errors.append(f"service_handoff_source_contract_missing:{ref}") @@ -789,6 +820,8 @@ def main() -> int: if field not in forbidden_fields: errors.append(f"service_handoff_forbidden_field_missing:{field}") remaining_gates = service_handoff.get("remaining_gates", {}) + if remaining_gates.get("secondbrain_governed_apply_route") != "defined-no-live-apply": + errors.append("service_handoff_governed_apply_route_not_defined") for gate in [ "conductor_lane_pickup", "curator_personal_memory_hygiene_lane_pickup", @@ -856,6 +889,8 @@ def main() -> int: errors.append("runtime_snapshot_bluebubbles_validator_not_ok") if imessage_health.get("read_only_imessage") is not True: errors.append("runtime_snapshot_imessage_not_readonly") + if imessage_health.get("secondbrain_governed_apply_route") != "defined-no-live-apply": + errors.append("runtime_snapshot_secondbrain_apply_route_not_defined") if imessage_health.get("package_runtime_claims") is not False: errors.append("runtime_snapshot_bluebubbles_runtime_claim_overclaimed") posture = runtime.get("supervisor_posture", {}) @@ -902,6 +937,8 @@ def main() -> int: if field not in forbidden_fields: errors.append(f"runtime_snapshot_forbidden_field_missing:{field}") remaining_gates = runtime.get("remaining_gates", {}) + if remaining_gates.get("secondbrain_governed_apply_route") != "defined-no-live-apply": + errors.append("runtime_snapshot_governed_apply_route_not_defined") for gate in [ "proton_email_gate_repair", "proton_contacts_gate_repair", @@ -1040,6 +1077,8 @@ def main() -> int: if field not in forbidden_fields: errors.append(f"desktop_exposure_forbidden_field_missing:{field}") remaining_gates = desktop.get("remaining_gates", {}) + if remaining_gates.get("secondbrain_governed_apply_route") != "defined-no-live-apply": + errors.append("desktop_exposure_governed_apply_route_not_defined") for gate in [ "adapter_lane_pickup", "desktop_ui_wiring", @@ -1070,7 +1109,7 @@ def main() -> int: "active-capability-package", "Personal-agent BlueBubbles binding", "Proton/rclone package candidate", - "Personal-agent Secondbrain proposal route", + "Personal-agent Secondbrain proposal/apply route", "Personal-agent Conductor/Curator service handoff", "Personal-agent runtime readiness snapshot", "Personal-agent desktop exposure contract",