From 5807a86b2e70b8473b11803eb9b9a1f37c723907 Mon Sep 17 00:00:00 2001 From: Svrnty Date: Sun, 14 Jun 2026 08:32:20 -0400 Subject: [PATCH] docs: define personal Secondbrain proposal route --- README.md | 1 + WORKBOARD.yaml | 5 + docs/STEEV-MASTER.md | 1 + ...onal-agent-secondbrain-proposal-route.json | 217 ++++++++++++++++++ ...t-context-runtime-supersession-register.md | 6 +- tools/validate_steev_child.py | 173 +++++++++++++- 6 files changed, 400 insertions(+), 3 deletions(-) create mode 100644 docs/contracts/personal-agent-secondbrain-proposal-route.json diff --git a/README.md b/README.md index b98ab81..9a61175 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ JP's personal assistant / chief of staff. Daily briefing, inbox triage, comms in - **Profile surface contract:** [`docs/contracts/personal-agent-profile-surface-contract.json`](docs/contracts/personal-agent-profile-surface-contract.json) — canonical surfaces, effects, memory route, and proof policy. - **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. +- **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. - **Historical Steev reference redirect:** [`docs/STEEV-MASTER.md`](docs/STEEV-MASTER.md). ## Structure diff --git a/WORKBOARD.yaml b/WORKBOARD.yaml index eb012bc..fef37f8 100644 --- a/WORKBOARD.yaml +++ b/WORKBOARD.yaml @@ -29,3 +29,8 @@ items: status: complete source: docs/contracts/personal-agent-proton-rclone-package.json owner: jp + - id: PACR-005 + title: Personal Secondbrain Proposal And Apply Route + status: complete + source: docs/contracts/personal-agent-secondbrain-proposal-route.json + owner: jp diff --git a/docs/STEEV-MASTER.md b/docs/STEEV-MASTER.md index 21dde54..16fd1e1 100644 --- a/docs/STEEV-MASTER.md +++ b/docs/STEEV-MASTER.md @@ -16,6 +16,7 @@ Active authority: - `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/prd/2026-06-14-personal-agent-context-runtime-prd.md` - `docs/supersession/2026-06-14-personal-agent-context-runtime-supersession-register.md` diff --git a/docs/contracts/personal-agent-secondbrain-proposal-route.json b/docs/contracts/personal-agent-secondbrain-proposal-route.json new file mode 100644 index 0000000..62d67c6 --- /dev/null +++ b/docs/contracts/personal-agent-secondbrain-proposal-route.json @@ -0,0 +1,217 @@ +{ + "schema_version": "personal-agent-secondbrain-proposal-route/v1", + "status": "active-profile-memory-proposal-route", + "route_id": "personal-agent-secondbrain-proposal-route", + "profile_identity": "personal-agent", + "display_name": "Steev", + "observed_date": "2026-06-14", + "target_memory_domain": "secondbrain-personal", + "target_domain_term": "Personal Memory Domain", + "human_authority_principal": "jp", + "forbidden_memory_domains": [ + "orgbrain" + ], + "durable_write_allowed": false, + "direct_write_allowed": false, + "profile_runtime_readiness_claimed": false, + "secondbrain_runtime_readiness_claimed": false, + "seed_readiness_claimed": false, + "authority_boundary": { + "profile_owns_source_surface_exposure": true, + "secondbrain_owns_personal_memory_domain": true, + "curator_owns_hygiene_review_queue": true, + "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." + }, + "source_routes": [ + { + "source_surface": "imessage.read", + "capability_package": "bluebubbles", + "proposal_type": "secondbrain.memory.propose_create_from_imessage", + "target_lifecycle_state": "inbox", + "allowed_effects": [ + "emit_redacted_proposal", + "emit_source_handle", + "emit_content_digest" + ], + "denied_effects": [ + "durable_memory_write", + "orgbrain_write", + "message_send", + "message_delete", + "message_mark_read", + "attachment_download" + ] + }, + { + "source_surface": "mail.read", + "capability_package": "proton-rclone", + "proposal_type": "secondbrain.memory.propose_create_from_mail", + "target_lifecycle_state": "inbox", + "allowed_effects": [ + "emit_redacted_proposal", + "emit_source_handle", + "emit_content_digest" + ], + "denied_effects": [ + "durable_memory_write", + "orgbrain_write", + "mail_send", + "mail_delete", + "mail_mark_read" + ] + }, + { + "source_surface": "calendar.read", + "capability_package": "proton-rclone", + "proposal_type": "secondbrain.memory.propose_create_from_calendar", + "target_lifecycle_state": "inbox", + "allowed_effects": [ + "emit_redacted_proposal", + "emit_source_handle", + "emit_content_digest" + ], + "denied_effects": [ + "durable_memory_write", + "orgbrain_write", + "calendar_write", + "calendar_delete" + ] + }, + { + "source_surface": "contacts.read", + "capability_package": "proton-rclone", + "proposal_type": "secondbrain.memory.propose_create_from_contacts", + "target_lifecycle_state": "inbox", + "allowed_effects": [ + "emit_redacted_proposal", + "emit_source_handle", + "emit_content_digest" + ], + "denied_effects": [ + "durable_memory_write", + "orgbrain_write", + "contact_mutation", + "contact_delete" + ] + }, + { + "source_surface": "drive.read", + "capability_package": "proton-rclone", + "proposal_type": "secondbrain.memory.propose_create_from_drive_pointer", + "target_lifecycle_state": "inbox", + "allowed_effects": [ + "emit_redacted_proposal", + "emit_source_handle", + "emit_content_digest" + ], + "denied_effects": [ + "durable_memory_write", + "orgbrain_write", + "drive_file_content_download", + "drive_file_name_proof", + "drive_write", + "drive_delete" + ] + } + ], + "proposal_envelope_contract": { + "schema_version": "personal-agent.secondbrain.proposal-envelope.v1", + "required_fields": [ + "schema_version", + "proposal_id", + "profile_identity", + "human_authority_principal", + "target_memory_domain", + "target_domain_term", + "source_capability_package", + "source_surface", + "proposal_type", + "target_lifecycle_state", + "source_handle_redacted", + "content_digest", + "redacted_summary", + "changed_fields", + "validator_plan", + "rollback_note", + "approval_state", + "proof_redaction" + ], + "target_memory_domain": "secondbrain-personal", + "target_domain_term": "Personal Memory Domain", + "approval_state": "pending", + "raw_payload_custody": "source-runtime-or-secondbrain-apply-route-only", + "raw_payload_in_core_or_profile_proof": false, + "durable_apply_authorized_by_envelope": false + }, + "apply_policy": { + "apply_route": "Secondbrain governed memory write path", + "apply_allowed_now": false, + "requires_secondbrain_validator": "python3 tools/validate_secondbrain_child.py", + "requires_focused_secondbrain_gate": true, + "requires_human_or_governed_approval": true, + "requires_local_evidence_and_handoff": true, + "push_allowed": false + }, + "rejection_cases": [ + { + "case": "target_orgbrain", + "input_target": "orgbrain", + "result": "rejected", + "reason": "personal context cannot route to Organization Memory Domain" + }, + { + "case": "direct_durable_write", + "requested_effect": "durable_memory_write", + "result": "rejected", + "reason": "capability packages emit proposal envelopes only" + }, + { + "case": "raw_payload_in_core_or_profile_proof", + "requested_effect": "store_raw_payload_in_proof", + "result": "rejected", + "reason": "proof is redacted-only" + }, + { + "case": "apply_without_approval", + "requested_effect": "secondbrain_apply", + "result": "blocked", + "reason": "Secondbrain governed apply requires approval and validators" + } + ], + "referenced_secondbrain_contracts": [ + "../secondbrain/docs/integration/2026-06-09-secondbrain-personal-memory-domain-runtime-contract.md", + "../secondbrain/docs/integration/2026-06-09-secondbrain-governed-agent-retrieval-contract.md", + "../secondbrain/docs/integration/2026-06-09-secondbrain-governed-memory-write-path-contract.md", + "../secondbrain/docs/integration/2026-06-09-secondbrain-curator-hygiene-queue-contract.md", + "../secondbrain/docs/integration/2026-06-09-secondbrain-hermes-runtime-boundary.md" + ], + "proof_policy": { + "mode": "redacted-only", + "forbidden_fields": [ + "raw_messages", + "message_text", + "mail_bodies", + "mail_subjects", + "sender_address", + "recipient_address", + "contact_details", + "calendar_event_details", + "drive_file_names", + "drive_file_contents", + "attachment_content", + "endpoint_payloads", + "credentials", + "secret_values" + ] + }, + "remaining_gates": { + "secondbrain_durable_apply": "blocked-follow-up", + "curator_hygiene_apply_review": "blocked-follow-up", + "desktop_adapter_exposure": "blocked-follow-up", + "runtime_health_proof": "blocked-follow-up", + "seed_package_pickup": "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 f60579f..b853e74 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,7 +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 memory route | blocked-follow-up | `PACR-005`, then owning Secondbrain/curator route | +| 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 | | Conductor/curator service routing | blocked-follow-up | `PACR-006`, after owning lanes release | | Desktop app exposure | blocked-follow-up | `PACR-008`, after adapter lane release | | Browser/Webwright host control | blocked-follow-up | `PACR-009`, explicit approval only | @@ -64,7 +65,7 @@ desktop exposure must be treated as one of: | Docker Proton Bridge and calendar gate state | active-evidence-source | Current runtime fact is captured redacted in the package candidate, not a readiness claim by itself. | | Broken user `proton-bridge.service` state | active-gap | Must be resolved or explicitly abandoned when one canonical runtime path is chosen. | | Inactive rclone RC/proxy units | active-gap | Must stay disabled or become gated through a governed wrapper before runtime readiness. | -| Secondbrain direct-write ideas | superseded | Personal context must begin as proposal/apply, not direct durable writes. | +| Secondbrain direct-write ideas | superseded | Personal context begins as redacted proposal envelopes; durable apply belongs to Secondbrain governed memory write path. | | Desktop integration ideas before adapter lane release | blocked-follow-up | Valid direction, but not an active mutation route. | | Browser/Webwright full-control ideas inside messaging work | superseded | Host control needs its own approval packet because it is broader authority. | @@ -74,6 +75,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 not treat legacy Cortex Proton/rclone repositories as active authority. - Graph context should not treat duplicate Proton skills as separate current product surfaces. - Graph context should mark browser/Webwright host control as separate HITL runtime authority. diff --git a/tools/validate_steev_child.py b/tools/validate_steev_child.py index 457dc27..f173b87 100755 --- a/tools/validate_steev_child.py +++ b/tools/validate_steev_child.py @@ -20,6 +20,7 @@ REQUIRED = [ "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/prd/2026-06-14-personal-agent-context-runtime-prd.md", "docs/issues/2026-06-14-personal-agent-context-runtime-work-orders.md", "docs/supersession/2026-06-14-personal-agent-context-runtime-supersession-register.md", @@ -75,6 +76,35 @@ REQUIRED_PROTON_RCLONE_DENIED_EFFECTS = { "orgbrain_write", } +REQUIRED_MEMORY_SOURCE_SURFACES = { + "imessage.read", + "mail.read", + "calendar.read", + "contacts.read", + "drive.read", +} + +REQUIRED_PROPOSAL_ENVELOPE_FIELDS = { + "schema_version", + "proposal_id", + "profile_identity", + "human_authority_principal", + "target_memory_domain", + "target_domain_term", + "source_capability_package", + "source_surface", + "proposal_type", + "target_lifecycle_state", + "source_handle_redacted", + "content_digest", + "redacted_summary", + "changed_fields", + "validator_plan", + "rollback_note", + "approval_state", + "proof_redaction", +} + def read_text(rel: str) -> str: return (ROOT / rel).read_text(encoding="utf-8") @@ -109,6 +139,7 @@ def main() -> int: "PACR-002", "PACR-003", "PACR-004", + "PACR-005", "status: candidate", "owner: jp", ]: @@ -407,6 +438,145 @@ def main() -> int: if remaining_gates.get(gate) != "blocked-follow-up": errors.append(f"proton_rclone_remaining_gate_missing:{gate}") + memory_route = load_json("docs/contracts/personal-agent-secondbrain-proposal-route.json", errors) + if memory_route: + if memory_route.get("schema_version") != "personal-agent-secondbrain-proposal-route/v1": + errors.append("memory_route_schema_version_invalid") + if memory_route.get("status") != "active-profile-memory-proposal-route": + errors.append("memory_route_status_invalid") + if memory_route.get("route_id") != "personal-agent-secondbrain-proposal-route": + errors.append("memory_route_id_invalid") + if memory_route.get("profile_identity") != "personal-agent": + errors.append("memory_route_profile_identity_not_personal_agent") + if memory_route.get("display_name") != "Steev": + errors.append("memory_route_display_name_not_steev") + if memory_route.get("target_memory_domain") != "secondbrain-personal": + errors.append("memory_route_target_not_secondbrain_personal") + if memory_route.get("target_domain_term") != "Personal Memory Domain": + errors.append("memory_route_domain_term_invalid") + if "orgbrain" not in memory_route.get("forbidden_memory_domains", []): + errors.append("memory_route_orgbrain_not_forbidden") + for key in [ + "durable_write_allowed", + "direct_write_allowed", + "profile_runtime_readiness_claimed", + "secondbrain_runtime_readiness_claimed", + "seed_readiness_claimed", + ]: + if memory_route.get(key) is not False: + errors.append(f"memory_route_overclaim:{key}") + boundary = memory_route.get("authority_boundary", {}) + for key in [ + "profile_owns_source_surface_exposure", + "secondbrain_owns_personal_memory_domain", + "curator_owns_hygiene_review_queue", + "capability_packages_emit_proposals_only", + ]: + if boundary.get(key) is not True: + errors.append(f"memory_route_boundary_missing:{key}") + if boundary.get("apply_owner") != "secondbrain": + errors.append("memory_route_apply_owner_not_secondbrain") + if boundary.get("hygiene_owner") != "curator": + errors.append("memory_route_hygiene_owner_not_curator") + source_routes = memory_route.get("source_routes", []) + source_surface_names = {route.get("source_surface") for route in source_routes} + missing_sources = sorted(REQUIRED_MEMORY_SOURCE_SURFACES - source_surface_names) + if missing_sources: + errors.append(f"memory_route_source_surfaces_missing:{','.join(missing_sources)}") + for route in source_routes: + surface = route.get("source_surface", "") + if route.get("capability_package") not in {"bluebubbles", "proton-rclone"}: + errors.append(f"memory_route_bad_capability_package:{surface}") + if not str(route.get("proposal_type", "")).startswith("secondbrain.memory.propose_"): + errors.append(f"memory_route_bad_proposal_type:{surface}") + if route.get("target_lifecycle_state") != "inbox": + errors.append(f"memory_route_lifecycle_not_inbox:{surface}") + 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}") + denied = route.get("denied_effects") + if not isinstance(denied, list): + errors.append(f"memory_route_denied_effects_not_list:{surface}") + else: + for effect in ["durable_memory_write", "orgbrain_write"]: + if effect not in denied: + errors.append(f"memory_route_denied_effect_missing:{surface}:{effect}") + envelope = memory_route.get("proposal_envelope_contract", {}) + if envelope.get("schema_version") != "personal-agent.secondbrain.proposal-envelope.v1": + errors.append("memory_route_envelope_schema_invalid") + fields = set(envelope.get("required_fields", [])) + missing_fields = sorted(REQUIRED_PROPOSAL_ENVELOPE_FIELDS - fields) + if missing_fields: + errors.append(f"memory_route_envelope_fields_missing:{','.join(missing_fields)}") + if envelope.get("target_memory_domain") != "secondbrain-personal": + errors.append("memory_route_envelope_target_invalid") + if envelope.get("approval_state") != "pending": + errors.append("memory_route_envelope_approval_not_pending") + if envelope.get("raw_payload_in_core_or_profile_proof") is not False: + errors.append("memory_route_envelope_allows_raw_payload_proof") + if envelope.get("durable_apply_authorized_by_envelope") is not False: + errors.append("memory_route_envelope_authorizes_apply") + 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_allowed_now") is not False: + errors.append("memory_route_apply_allowed_now") + if apply_policy.get("requires_secondbrain_validator") != "python3 tools/validate_secondbrain_child.py": + errors.append("memory_route_secondbrain_validator_missing") + for key in [ + "requires_focused_secondbrain_gate", + "requires_human_or_governed_approval", + "requires_local_evidence_and_handoff", + ]: + if apply_policy.get(key) is not True: + errors.append(f"memory_route_apply_policy_missing:{key}") + if apply_policy.get("push_allowed") is not False: + errors.append("memory_route_apply_policy_allows_push") + cases = {case.get("case"): case.get("result") for case in memory_route.get("rejection_cases", [])} + expected_cases = { + "target_orgbrain": "rejected", + "direct_durable_write": "rejected", + "raw_payload_in_core_or_profile_proof": "rejected", + "apply_without_approval": "blocked", + } + for case, result in expected_cases.items(): + if cases.get(case) != result: + errors.append(f"memory_route_rejection_case_missing:{case}") + refs = set(memory_route.get("referenced_secondbrain_contracts", [])) + for ref in [ + "../secondbrain/docs/integration/2026-06-09-secondbrain-personal-memory-domain-runtime-contract.md", + "../secondbrain/docs/integration/2026-06-09-secondbrain-governed-agent-retrieval-contract.md", + "../secondbrain/docs/integration/2026-06-09-secondbrain-governed-memory-write-path-contract.md", + "../secondbrain/docs/integration/2026-06-09-secondbrain-curator-hygiene-queue-contract.md", + "../secondbrain/docs/integration/2026-06-09-secondbrain-hermes-runtime-boundary.md", + ]: + if ref not in refs: + errors.append(f"memory_route_secondbrain_ref_missing:{ref}") + forbidden_fields = set(memory_route.get("proof_policy", {}).get("forbidden_fields", [])) + for field in [ + "raw_messages", + "message_text", + "mail_bodies", + "contact_details", + "calendar_event_details", + "drive_file_names", + "endpoint_payloads", + "credentials", + "secret_values", + ]: + if field not in forbidden_fields: + errors.append(f"memory_route_forbidden_field_missing:{field}") + remaining_gates = memory_route.get("remaining_gates", {}) + for gate in [ + "secondbrain_durable_apply", + "curator_hygiene_apply_review", + "desktop_adapter_exposure", + "runtime_health_proof", + "seed_package_pickup", + ]: + if remaining_gates.get(gate) != "blocked-follow-up": + errors.append(f"memory_route_remaining_gate_missing:{gate}") + for rel in ["AGENT.md", "CONTRACT.md", "DISCLOSURE.md", "README.md", "docs/STEEV-MASTER.md"]: path = ROOT / rel if not path.exists(): @@ -426,6 +596,7 @@ def main() -> int: "active-capability-package", "Personal-agent BlueBubbles binding", "Proton/rclone package candidate", + "Personal-agent Secondbrain proposal route", "superseded", "legacy-reference", "blocked-follow-up", @@ -441,7 +612,7 @@ def main() -> int: result = { "ok": not errors, - "validator": "personal-agent-profile-distribution-v3", + "validator": "personal-agent-profile-distribution-v4", "checked": REQUIRED, "errors": errors, "warnings": [],