diff --git a/README.md b/README.md index d61e465..b98ab81 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ JP's personal assistant / chief of staff. Daily briefing, inbox triage, comms in - **Identity:** [`AGENT.md`](AGENT.md) — role, mission, boundaries. - **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. - **Historical Steev reference redirect:** [`docs/STEEV-MASTER.md`](docs/STEEV-MASTER.md). ## Structure diff --git a/WORKBOARD.yaml b/WORKBOARD.yaml index 7a874c5..eb012bc 100644 --- a/WORKBOARD.yaml +++ b/WORKBOARD.yaml @@ -24,3 +24,8 @@ items: status: complete source: docs/contracts/personal-agent-bluebubbles-binding.json owner: jp + - id: PACR-004 + title: Proton And Rclone Capability Standardization + status: complete + source: docs/contracts/personal-agent-proton-rclone-package.json + owner: jp diff --git a/docs/STEEV-MASTER.md b/docs/STEEV-MASTER.md index f28e1f3..21dde54 100644 --- a/docs/STEEV-MASTER.md +++ b/docs/STEEV-MASTER.md @@ -15,6 +15,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/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-proton-rclone-package.json b/docs/contracts/personal-agent-proton-rclone-package.json new file mode 100644 index 0000000..b8788ea --- /dev/null +++ b/docs/contracts/personal-agent-proton-rclone-package.json @@ -0,0 +1,355 @@ +{ + "schema_version": "personal-agent-proton-rclone-package/v1", + "status": "package-candidate-unregistered", + "package_id": "proton-rclone", + "profile_identity": "personal-agent", + "display_name": "Steev", + "observed_date": "2026-06-14", + "child_workspace_registered": false, + "package_runtime_readiness_claimed": false, + "profile_runtime_readiness_claimed": false, + "seed_readiness_claimed": false, + "core_promotion_claimed": false, + "authority_boundary": { + "profile_owns_surface_exposure": true, + "package_candidate_owns_runtime_inventory": true, + "legacy_repositories_are_reference_only": true, + "duplicate_profile_local_connectors_allowed": false, + "notes": "This contract standardizes the Proton/rclone package shape for personal-agent. It does not register a new child workspace or claim full runtime readiness." + }, + "memory_policy": { + "target": "secondbrain-personal", + "forbidden": [ + "orgbrain" + ], + "durable_write_policy": "proposal-only-until-governed-secondbrain-curator-apply-route" + }, + "credential_policy": { + "mode": "keyvault-reference-names-only", + "secret_values_in_contract": false, + "credential_mutation_allowed": false + }, + "surfaces": [ + { + "name": "mail.read", + "runtime_route": "proton-email MCP facade through Proton gate", + "readiness": "degraded", + "allowed_effects": [ + "email_folders", + "email_list", + "email_search", + "email_read_metadata_or_body_when_requested" + ], + "denied_effects": [ + "send_without_confirmation", + "delete_mail", + "archive_mail", + "mark_read", + "mark_unread", + "orgbrain_write" + ], + "confirmation": "not-required-for-read" + }, + { + "name": "mail.draft", + "runtime_route": "proton-email MCP facade through Proton gate", + "readiness": "pending", + "allowed_effects": [ + "draft_reply", + "draft_new_mail" + ], + "denied_effects": [ + "send_without_confirmation", + "delete_mail", + "orgbrain_write" + ], + "confirmation": "draft-only" + }, + { + "name": "mail.send_with_confirmation", + "runtime_route": "proton-email MCP facade through Proton gate", + "readiness": "disabled", + "allowed_effects": [ + "send_after_explicit_jp_confirmation" + ], + "denied_effects": [ + "silent_send", + "send_without_confirmation", + "delete_mail", + "orgbrain_write" + ], + "confirmation": "explicit-jp-confirmation-required" + }, + { + "name": "calendar.read", + "runtime_route": "proton-calendar MCP facade through calendar gate", + "readiness": "degraded", + "allowed_effects": [ + "calendar_list", + "calendar_events", + "calendar_upcoming", + "calendar_search", + "calendar_event_get" + ], + "denied_effects": [ + "calendar_write_without_confirmation", + "calendar_delete", + "orgbrain_write" + ], + "confirmation": "not-required-for-read" + }, + { + "name": "calendar.propose_event", + "runtime_route": "proton-calendar MCP facade through calendar gate", + "readiness": "pending", + "allowed_effects": [ + "propose_calendar_create", + "propose_calendar_update" + ], + "denied_effects": [ + "calendar_write_without_confirmation", + "calendar_delete", + "orgbrain_write" + ], + "confirmation": "proposal-only" + }, + { + "name": "calendar.write_with_confirmation", + "runtime_route": "proton-calendar MCP facade through calendar gate", + "readiness": "disabled", + "allowed_effects": [ + "calendar_create_after_explicit_jp_confirmation", + "calendar_update_after_explicit_jp_confirmation" + ], + "denied_effects": [ + "silent_calendar_write", + "calendar_delete", + "orgbrain_write" + ], + "confirmation": "explicit-jp-confirmation-required" + }, + { + "name": "contacts.read", + "runtime_route": "proton-contacts MCP facade through contacts gate", + "readiness": "degraded", + "allowed_effects": [ + "contacts_list", + "contacts_search", + "contacts_get" + ], + "denied_effects": [ + "contact_mutation_without_confirmation", + "contacts_delete", + "orgbrain_write" + ], + "confirmation": "not-required-for-read" + }, + { + "name": "contacts.write_with_confirmation", + "runtime_route": "proton-contacts MCP facade through contacts gate", + "readiness": "disabled", + "allowed_effects": [ + "contacts_create_after_explicit_jp_confirmation", + "contacts_update_after_explicit_jp_confirmation" + ], + "denied_effects": [ + "silent_contact_write", + "contacts_delete", + "orgbrain_write" + ], + "confirmation": "explicit-jp-confirmation-required" + }, + { + "name": "drive.read", + "runtime_route": "rclone with explicit Proton config path", + "readiness": "degraded", + "allowed_effects": [ + "rclone_about_redacted", + "rclone_list_only_when_requested" + ], + "denied_effects": [ + "drive_file_name_proof", + "drive_file_content_download", + "drive_write_without_confirmation", + "drive_delete", + "orgbrain_write" + ], + "confirmation": "not-required-for-redacted-about" + }, + { + "name": "drive.write_with_confirmation", + "runtime_route": "rclone with explicit Proton config path", + "readiness": "disabled", + "allowed_effects": [ + "drive_write_after_explicit_jp_confirmation" + ], + "denied_effects": [ + "silent_drive_write", + "drive_delete", + "drive_purge", + "drive_share", + "orgbrain_write" + ], + "confirmation": "explicit-jp-confirmation-required" + } + ], + "runtime_inventory": { + "overall_state": "degraded", + "chosen_runtime_path": "MCP facades for Mail, Calendar, Contacts; explicit rclone config for Drive", + "pending_runtime_convergence": [ + "Repair or replace exited email and contacts gate containers.", + "Resolve auto-restarting user proton-bridge and proton-bridge-proxy units or explicitly abandon them.", + "Keep rclone RC/proxy units disabled unless a governed wrapper admits them.", + "Create registered proton-rclone child workspace before package runtime readiness is claimed." + ], + "mcp_servers": [ + { + "name": "proton-calendar", + "observed_status": "enabled" + }, + { + "name": "proton-email", + "observed_status": "enabled" + }, + { + "name": "proton-contacts", + "observed_status": "enabled" + } + ], + "docker_routes": [ + { + "name": "protonmail-bridge-active-container", + "observed_state": "up" + }, + { + "name": "sdo-calendar-gate", + "observed_state": "up" + }, + { + "name": "sdo-email-gate", + "observed_state": "exited-127" + }, + { + "name": "sdo-contacts-gate", + "observed_state": "exited-127" + }, + { + "name": "stale-sdo-protonmail-bridge-container", + "observed_state": "created" + } + ], + "systemd_user_units": [ + { + "name": "proton-bridge.service", + "observed_state": "activating-auto-restart", + "unit_file_state": "enabled" + }, + { + "name": "proton-bridge-proxy.service", + "observed_state": "activating-auto-restart", + "unit_file_state": "enabled" + }, + { + "name": "rclone-rc.service", + "observed_state": "inactive-dead", + "unit_file_state": "disabled" + }, + { + "name": "rclone-proxy.service", + "observed_state": "inactive-dead", + "unit_file_state": "disabled" + } + ], + "rclone": { + "config_path": "/home/svrnty/.config/rclone/rclone.conf", + "remote": "proton:", + "listremotes_observed": true, + "about_probe": "ok-redacted", + "file_names_observed": false, + "file_contents_observed": false + } + }, + "legacy_sources": [ + { + "path": "/home/svrnty/workspaces/cortex/L4-svrnty.api-proton", + "state": "legacy-reference", + "reason": "Mail, Calendar, Contacts source material, not Cortex OS child authority." + }, + { + "path": "/home/svrnty/workspaces/cortex/L4-svrnty.tool-storage", + "state": "legacy-reference", + "reason": "Storage/rclone source material, not the canonical personal-agent package." + }, + { + "path": "/home/svrnty/workspaces/cortex/L5-vendor.lib-proton-bridge", + "state": "legacy-reference", + "reason": "Vendor bridge code, not profile authority." + }, + { + "path": "/home/svrnty/workspaces/cortex/L6-vendor.lib-proton-api", + "state": "legacy-reference", + "reason": "Vendor Proton API code, not profile authority." + }, + { + "path": "/home/svrnty/workspaces/cortex/L6-vendor.lib-rclone", + "state": "legacy-reference", + "reason": "Vendor rclone code, not profile authority." + } + ], + "duplicate_skill_policy": [ + { + "id": "skills/proton-tools", + "state": "superseded-pending-package-install", + "reason": "Keep as tool reference until the package child exists; governance now lives in this contract." + }, + { + "id": "proton-access", + "state": "superseded-pending-consolidation", + "reason": "Must not become separate Proton authority." + }, + { + "id": "proton-mail-operations", + "state": "superseded-pending-consolidation", + "reason": "Must fold into the canonical Proton/rclone package." + }, + { + "id": "proton-services", + "state": "superseded-pending-consolidation", + "reason": "Must fold into the canonical Proton/rclone package." + } + ], + "proof_policy": { + "mode": "redacted-only", + "forbidden_fields": [ + "raw_messages", + "mail_bodies", + "mail_subjects", + "sender_address", + "recipient_address", + "contact_details", + "calendar_event_details", + "drive_file_names", + "drive_file_contents", + "endpoint_payloads", + "credentials", + "secret_values" + ] + }, + "observed_commands": [ + "hermes -p steev mcp list", + "systemctl --user list-unit-files --no-pager | rg -i 'proton|rclone|calendar|contacts|email'", + "systemctl --user show proton-bridge.service rclone-rc.service rclone-proxy.service -p Id -p LoadState -p ActiveState -p SubState -p UnitFileState -p FragmentPath --no-pager", + "systemctl --user show proton-bridge-proxy.service -p Id -p LoadState -p ActiveState -p SubState -p UnitFileState -p FragmentPath --no-pager", + "docker ps -a --format '' | rg -i 'proton|calendar|contacts|email|mail|rclone|sdo'", + "rclone --config /home/svrnty/.config/rclone/rclone.conf listremotes", + "rclone --config /home/svrnty/.config/rclone/rclone.conf about proton: --json" + ], + "remaining_gates": { + "registered_child_workspace": "blocked-follow-up", + "email_gate_repair": "blocked-follow-up", + "contacts_gate_repair": "blocked-follow-up", + "systemd_bridge_convergence": "blocked-follow-up", + "secondbrain_durable_apply": "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 586a182..f60579f 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 @@ -33,8 +33,9 @@ desktop exposure must be treated as one of: | Steev display name | active-alias | User-facing name for `personal-agent`, not separate authority | | Personal-agent BlueBubbles binding | active-authority | `docs/contracts/personal-agent-bluebubbles-binding.json` binds `imessage.read` to the package | | BlueBubbles iMessage | active-capability-package | BlueBubbles child completion-readiness package | -| Proton Mail/Calendar/Contacts | blocked-follow-up | New Proton/rclone capability package work from `PACR-004` | -| Proton Drive/rclone | blocked-follow-up | New Proton/rclone capability package work from `PACR-004` | +| 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 | | 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 | @@ -48,7 +49,7 @@ desktop exposure must be treated as one of: | `CONTRACT.md` v1 iMessage-as-v2 wording | superseded | iMessage is now main personal context intake, not a low-priority future messaging item. | | `AGENT.md` reused-skill summary | superseded | It names useful tools but not the new governed surface model. | | `skills/steev-agent` current memory protocol | superseded | It says episodic memory only but does not encode personal Secondbrain proposal/apply routing. | -| `skills/proton-tools` | active-source-to-consolidate | It contains live Proton tool knowledge but must be folded into a standardized capability package. | +| `skills/proton-tools` | superseded-pending-package-install | It remains tool reference material, but governance now lives in the Proton/rclone package candidate. | | `DISCLOSURE.md` Wave 8/8.5 runtime disclosure | superseded-pending-refresh | It is historical disclosure and must be refreshed after the profile capability contract changes. | | BlueBubbles runtime-readiness PRD | active-capability-package | It remains valid for the iMessage capability package, subordinate to the `personal-agent` profile contract. | | BlueBubbles completion-readiness PRD | active-capability-package | It remains the BlueBubbles package pickup for read-only iMessage readiness. | @@ -59,8 +60,8 @@ desktop exposure must be treated as one of: | Hermes installed `proton-access` skill | superseded-pending-consolidation | It overlaps with Steev `proton-tools` and should not be a separate authority. | | Hermes installed `proton-mail-operations` skill | superseded-pending-consolidation | It overlaps with Steev `proton-tools` and should fold into the canonical Proton package. | | Hermes installed `proton-services` skill | superseded-pending-consolidation | It overlaps with Steev `proton-tools` and should fold into the canonical Proton package. | -| Direct rclone CLI proofs | active-evidence-source | Read-only `about` proof is useful, but runtime authority must be packaged. | -| Docker Proton Bridge and calendar gate state | active-evidence-source | Current runtime fact, not a profile contract by itself. | +| Direct rclone CLI proofs | active-evidence-source | Read-only `about` proof is captured redacted in the package candidate, but runtime authority still needs a governed wrapper. | +| 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. | @@ -72,6 +73,7 @@ desktop exposure must be treated as one of: - Graph context should expose this PRD as the `personal-agent` profile-level pickup. - 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 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 545f50a..457dc27 100755 --- a/tools/validate_steev_child.py +++ b/tools/validate_steev_child.py @@ -19,6 +19,7 @@ REQUIRED = [ "docs/STEEV-MASTER.md", "docs/contracts/personal-agent-profile-surface-contract.json", "docs/contracts/personal-agent-bluebubbles-binding.json", + "docs/contracts/personal-agent-proton-rclone-package.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", @@ -53,6 +54,27 @@ REQUIRED_REDACTION_TERMS = { "secret_values", } +REQUIRED_PROTON_RCLONE_SURFACES = { + "mail.read", + "mail.draft", + "mail.send_with_confirmation", + "calendar.read", + "calendar.propose_event", + "calendar.write_with_confirmation", + "contacts.read", + "contacts.write_with_confirmation", + "drive.read", + "drive.write_with_confirmation", +} + +REQUIRED_PROTON_RCLONE_DENIED_EFFECTS = { + "send_without_confirmation", + "calendar_write_without_confirmation", + "contact_mutation_without_confirmation", + "drive_delete", + "orgbrain_write", +} + def read_text(rel: str) -> str: return (ROOT / rel).read_text(encoding="utf-8") @@ -81,7 +103,15 @@ def main() -> int: board = ROOT / "WORKBOARD.yaml" if board.exists(): text = board.read_text(encoding="utf-8") - for snippet in ["STEEV-WORK-001", "PACR-001", "PACR-002", "PACR-003", "status: candidate", "owner: jp"]: + for snippet in [ + "STEEV-WORK-001", + "PACR-001", + "PACR-002", + "PACR-003", + "PACR-004", + "status: candidate", + "owner: jp", + ]: if snippet not in text: errors.append(f"workboard_missing:{snippet}") agents = ROOT / "AGENTS.md" @@ -215,6 +245,168 @@ def main() -> int: if ref not in refs: errors.append(f"bluebubbles_binding_reference_missing:{ref}") + proton = load_json("docs/contracts/personal-agent-proton-rclone-package.json", errors) + if proton: + if proton.get("schema_version") != "personal-agent-proton-rclone-package/v1": + errors.append("proton_rclone_schema_version_invalid") + if proton.get("status") != "package-candidate-unregistered": + errors.append("proton_rclone_status_not_package_candidate") + if proton.get("package_id") != "proton-rclone": + errors.append("proton_rclone_package_id_invalid") + if proton.get("profile_identity") != "personal-agent": + errors.append("proton_rclone_profile_identity_not_personal_agent") + if proton.get("display_name") != "Steev": + errors.append("proton_rclone_display_name_not_steev") + for key in [ + "child_workspace_registered", + "package_runtime_readiness_claimed", + "profile_runtime_readiness_claimed", + "seed_readiness_claimed", + "core_promotion_claimed", + ]: + if proton.get(key) is not False: + errors.append(f"proton_rclone_overclaim:{key}") + boundary = proton.get("authority_boundary", {}) + if boundary.get("profile_owns_surface_exposure") is not True: + errors.append("proton_rclone_profile_surface_boundary_missing") + if boundary.get("package_candidate_owns_runtime_inventory") is not True: + errors.append("proton_rclone_inventory_boundary_missing") + if boundary.get("legacy_repositories_are_reference_only") is not True: + errors.append("proton_rclone_legacy_reference_boundary_missing") + if boundary.get("duplicate_profile_local_connectors_allowed") is not False: + errors.append("proton_rclone_duplicate_connectors_allowed") + memory = proton.get("memory_policy", {}) + if memory.get("target") != "secondbrain-personal": + errors.append("proton_rclone_memory_target_not_secondbrain_personal") + if "orgbrain" not in memory.get("forbidden", []): + errors.append("proton_rclone_orgbrain_not_forbidden") + if "proposal-only" not in memory.get("durable_write_policy", ""): + errors.append("proton_rclone_memory_not_proposal_only") + credential = proton.get("credential_policy", {}) + if credential.get("mode") != "keyvault-reference-names-only": + errors.append("proton_rclone_credential_mode_invalid") + if credential.get("secret_values_in_contract") is not False: + errors.append("proton_rclone_secret_values_allowed") + if credential.get("credential_mutation_allowed") is not False: + errors.append("proton_rclone_credential_mutation_allowed") + surfaces = proton.get("surfaces", []) + surface_names = {surface.get("name") for surface in surfaces} + missing_surfaces = sorted(REQUIRED_PROTON_RCLONE_SURFACES - surface_names) + if missing_surfaces: + errors.append(f"proton_rclone_surfaces_missing:{','.join(missing_surfaces)}") + readiness_by_surface = {surface.get("name"): surface.get("readiness") for surface in surfaces} + if readiness_by_surface.get("drive.read") != "degraded": + errors.append("proton_rclone_drive_read_not_degraded") + for disabled_surface in [ + "mail.send_with_confirmation", + "calendar.write_with_confirmation", + "contacts.write_with_confirmation", + "drive.write_with_confirmation", + ]: + if readiness_by_surface.get(disabled_surface) != "disabled": + errors.append(f"proton_rclone_confirmation_surface_not_disabled:{disabled_surface}") + observed_denied: set[str] = set() + for surface in surfaces: + name = surface.get("name", "") + if not surface.get("runtime_route"): + errors.append(f"proton_rclone_surface_missing_runtime_route:{name}") + if surface.get("readiness") not in {"ready", "degraded", "pending", "blocked", "disabled"}: + errors.append(f"proton_rclone_surface_readiness_invalid:{name}") + if not isinstance(surface.get("allowed_effects"), list): + errors.append(f"proton_rclone_allowed_effects_not_list:{name}") + denied = surface.get("denied_effects") + if not isinstance(denied, list): + errors.append(f"proton_rclone_denied_effects_not_list:{name}") + else: + observed_denied.update(denied) + if "orgbrain_write" not in denied: + errors.append(f"proton_rclone_orgbrain_write_not_denied:{name}") + if not surface.get("confirmation"): + errors.append(f"proton_rclone_confirmation_missing:{name}") + missing_denied = sorted(REQUIRED_PROTON_RCLONE_DENIED_EFFECTS - observed_denied) + if missing_denied: + errors.append(f"proton_rclone_denied_effects_missing:{','.join(missing_denied)}") + inventory = proton.get("runtime_inventory", {}) + if inventory.get("overall_state") != "degraded": + errors.append("proton_rclone_inventory_overall_state_not_degraded") + if "MCP facades" not in inventory.get("chosen_runtime_path", ""): + errors.append("proton_rclone_inventory_chosen_path_missing_mcp") + mcp = {item.get("name"): item.get("observed_status") for item in inventory.get("mcp_servers", [])} + for name in ["proton-calendar", "proton-email", "proton-contacts"]: + if mcp.get(name) != "enabled": + errors.append(f"proton_rclone_mcp_not_enabled:{name}") + docker = {item.get("name"): item.get("observed_state") for item in inventory.get("docker_routes", [])} + for name in ["sdo-calendar-gate", "sdo-email-gate", "sdo-contacts-gate"]: + if name not in docker: + errors.append(f"proton_rclone_docker_route_missing:{name}") + if docker.get("sdo-email-gate") != "exited-127": + errors.append("proton_rclone_email_gate_state_not_captured") + if docker.get("sdo-contacts-gate") != "exited-127": + errors.append("proton_rclone_contacts_gate_state_not_captured") + units = {item.get("name"): item for item in inventory.get("systemd_user_units", [])} + if units.get("proton-bridge.service", {}).get("observed_state") != "activating-auto-restart": + errors.append("proton_rclone_proton_bridge_gap_not_captured") + for unit in ["rclone-rc.service", "rclone-proxy.service"]: + if units.get(unit, {}).get("unit_file_state") != "disabled": + errors.append(f"proton_rclone_rclone_unit_not_disabled:{unit}") + rclone = inventory.get("rclone", {}) + if rclone.get("remote") != "proton:": + errors.append("proton_rclone_remote_missing") + if rclone.get("about_probe") != "ok-redacted": + errors.append("proton_rclone_about_probe_not_redacted_ok") + if rclone.get("file_names_observed") is not False: + errors.append("proton_rclone_file_names_observed") + if rclone.get("file_contents_observed") is not False: + errors.append("proton_rclone_file_contents_observed") + legacy_states = {item.get("path"): item.get("state") for item in proton.get("legacy_sources", [])} + for path in [ + "/home/svrnty/workspaces/cortex/L4-svrnty.api-proton", + "/home/svrnty/workspaces/cortex/L4-svrnty.tool-storage", + "/home/svrnty/workspaces/cortex/L5-vendor.lib-proton-bridge", + "/home/svrnty/workspaces/cortex/L6-vendor.lib-proton-api", + "/home/svrnty/workspaces/cortex/L6-vendor.lib-rclone", + ]: + if legacy_states.get(path) != "legacy-reference": + errors.append(f"proton_rclone_legacy_source_not_reference:{path}") + duplicate_states = {item.get("id"): item.get("state") for item in proton.get("duplicate_skill_policy", [])} + for skill in ["skills/proton-tools", "proton-access", "proton-mail-operations", "proton-services"]: + state = duplicate_states.get(skill, "") + if "superseded" not in state: + errors.append(f"proton_rclone_duplicate_skill_not_superseded:{skill}") + forbidden_fields = set(proton.get("proof_policy", {}).get("forbidden_fields", [])) + for field in [ + "mail_bodies", + "mail_subjects", + "contact_details", + "calendar_event_details", + "drive_file_names", + "drive_file_contents", + "endpoint_payloads", + "credentials", + "secret_values", + ]: + if field not in forbidden_fields: + errors.append(f"proton_rclone_forbidden_field_missing:{field}") + commands = set(proton.get("observed_commands", [])) + for command in [ + "hermes -p steev mcp list", + "rclone --config /home/svrnty/.config/rclone/rclone.conf listremotes", + "rclone --config /home/svrnty/.config/rclone/rclone.conf about proton: --json", + ]: + if command not in commands: + errors.append(f"proton_rclone_observed_command_missing:{command}") + remaining_gates = proton.get("remaining_gates", {}) + for gate in [ + "registered_child_workspace", + "email_gate_repair", + "contacts_gate_repair", + "systemd_bridge_convergence", + "secondbrain_durable_apply", + "seed_package_pickup", + ]: + if remaining_gates.get(gate) != "blocked-follow-up": + errors.append(f"proton_rclone_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(): @@ -233,6 +425,7 @@ def main() -> int: "active-alias", "active-capability-package", "Personal-agent BlueBubbles binding", + "Proton/rclone package candidate", "superseded", "legacy-reference", "blocked-follow-up", @@ -248,7 +441,7 @@ def main() -> int: result = { "ok": not errors, - "validator": "personal-agent-profile-distribution-v2", + "validator": "personal-agent-profile-distribution-v3", "checked": REQUIRED, "errors": errors, "warnings": [],