From 0944fc7fd04831bf2ec6b37babc2b8de06310a23 Mon Sep 17 00:00:00 2001 From: Svrnty Date: Sun, 14 Jun 2026 08:46:33 -0400 Subject: [PATCH] docs: define personal-agent desktop exposure contract --- README.md | 1 + WORKBOARD.yaml | 5 + docs/STEEV-MASTER.md | 1 + ...sonal-agent-desktop-exposure-contract.json | 168 +++++++++++++++++ ...t-context-runtime-supersession-register.md | 4 +- tools/validate_steev_child.py | 169 +++++++++++++++++- 6 files changed, 346 insertions(+), 2 deletions(-) create mode 100644 docs/contracts/personal-agent-desktop-exposure-contract.json diff --git a/README.md b/README.md index 3ecf6be..23ac2e9 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ JP's personal assistant / chief of staff. Daily briefing, inbox triage, comms in - **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. - **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. - **Historical Steev reference redirect:** [`docs/STEEV-MASTER.md`](docs/STEEV-MASTER.md). ## Structure diff --git a/WORKBOARD.yaml b/WORKBOARD.yaml index b171326..7b403fe 100644 --- a/WORKBOARD.yaml +++ b/WORKBOARD.yaml @@ -44,3 +44,8 @@ items: status: complete source: docs/contracts/personal-agent-runtime-readiness-snapshot.json owner: jp + - id: PACR-008 + title: Desktop Adapter Exposure Contract + status: complete + source: docs/contracts/personal-agent-desktop-exposure-contract.json + owner: jp diff --git a/docs/STEEV-MASTER.md b/docs/STEEV-MASTER.md index 6186753..b2a4054 100644 --- a/docs/STEEV-MASTER.md +++ b/docs/STEEV-MASTER.md @@ -19,6 +19,7 @@ Active authority: - `docs/contracts/personal-agent-secondbrain-proposal-route.json` - `docs/contracts/personal-agent-conductor-curator-service-handoff.json` - `docs/contracts/personal-agent-runtime-readiness-snapshot.json` +- `docs/contracts/personal-agent-desktop-exposure-contract.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-desktop-exposure-contract.json b/docs/contracts/personal-agent-desktop-exposure-contract.json new file mode 100644 index 0000000..c77dc15 --- /dev/null +++ b/docs/contracts/personal-agent-desktop-exposure-contract.json @@ -0,0 +1,168 @@ +{ + "schema_version": "personal-agent-desktop-exposure-contract/v1", + "status": "active-profile-desktop-exposure-contract", + "contract_id": "personal-agent-desktop-exposure-contract", + "profile_identity": "personal-agent", + "display_name": "Steev", + "observed_date": "2026-06-14", + "desktop_integration_claimed": false, + "runtime_readiness_claimed": false, + "seed_readiness_claimed": false, + "core_promotion_claimed": false, + "adapter_workspace": "../cortex-hermes-adapter", + "adapter_validator_command": "python3 tools/validate_cortex_hermes_adapter_child.py", + "adapter_validator_result_observed": "ok", + "adapter_reference_contracts": [ + "../cortex-hermes-adapter/contracts/desktop-dashboard-host-surface.md", + "../cortex-hermes-adapter/contracts/personal-agent-s518-runtime-host-surface-intake.json", + "../cortex-hermes-adapter/contracts/first-open-evidence.schema.json", + "../cortex-hermes-adapter/dashboard/package-view.sample.json" + ], + "authority_boundary": { + "profile_owns_desktop_exposure_contract": true, + "adapter_owns_desktop_rendering": true, + "seed_owns_package_first_open_proof": true, + "core_owns_acceptance": true, + "profile_mutates_adapter": false, + "notes": "This contract is a profile-side handoff for desktop-visible readiness. It does not wire UI or mutate the adapter workspace." + }, + "allowed_adapter_surfaces": [ + "package.status", + "runtime.health", + "onboarding.state", + "profile.distribution", + "capability.catalog" + ], + "state_vocabulary": [ + "ready", + "degraded", + "pending", + "blocked", + "disabled" + ], + "desktop_rows": [ + { + "row_id": "personal-agent.profile", + "label": "personal-agent", + "display_name": "Steev", + "surface": "profile.distribution", + "state": "degraded", + "source_contract": "docs/contracts/personal-agent-runtime-readiness-snapshot.json", + "visible_reason": "Profile exists and capability contracts are present, but aggregate runtime readiness is degraded." + }, + { + "row_id": "personal-agent.imessage.read", + "label": "iMessage read", + "surface": "runtime.health", + "state": "ready", + "source_contract": "docs/contracts/personal-agent-bluebubbles-binding.json", + "visible_reason": "BlueBubbles package validator is OK, read-only, secondbrain-personal, and orgbrain-forbidden." + }, + { + "row_id": "personal-agent.mail.read", + "label": "Proton Mail read", + "surface": "runtime.health", + "state": "degraded", + "source_contract": "docs/contracts/personal-agent-runtime-readiness-snapshot.json", + "visible_reason": "Proton MCP is enabled and bridge is running, but email gate remains exited." + }, + { + "row_id": "personal-agent.calendar.read", + "label": "Proton Calendar read", + "surface": "runtime.health", + "state": "degraded", + "source_contract": "docs/contracts/personal-agent-runtime-readiness-snapshot.json", + "visible_reason": "Calendar gate is running, but Proton/rclone package child and final runtime proof are not complete." + }, + { + "row_id": "personal-agent.contacts.read", + "label": "Proton Contacts read", + "surface": "runtime.health", + "state": "degraded", + "source_contract": "docs/contracts/personal-agent-runtime-readiness-snapshot.json", + "visible_reason": "Proton MCP is enabled, but contacts gate remains exited." + }, + { + "row_id": "personal-agent.drive.read", + "label": "Proton Drive read", + "surface": "runtime.health", + "state": "degraded", + "source_contract": "docs/contracts/personal-agent-runtime-readiness-snapshot.json", + "visible_reason": "rclone about probe is redacted-ok, but governed wrapper and package child are not complete." + }, + { + "row_id": "personal-agent.secondbrain.proposal", + "label": "Personal memory proposals", + "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." + }, + { + "row_id": "personal-agent.browser.host-runtime", + "label": "Browser host runtime", + "surface": "capability.catalog", + "state": "blocked", + "source_contract": "docs/prd/2026-06-14-personal-agent-context-runtime-prd.md", + "visible_reason": "Full desktop/browser control requires separate PACR-009 approval packet." + }, + { + "row_id": "personal-agent.write-actions", + "label": "Writes and sends", + "surface": "capability.catalog", + "state": "disabled", + "source_contract": "docs/contracts/personal-agent-profile-surface-contract.json", + "visible_reason": "Silent sends, deletes, mark-read/read receipts, contact/calendar/file mutation, and durable writes are denied unless a confirmation surface is explicitly approved." + } + ], + "desktop_false_effects": { + "adapter_mutated_by_profile": false, + "desktop_or_dashboard_opened": false, + "runtime_started": false, + "runtime_stopped": false, + "docker_started": false, + "profile_exposure_changed": false, + "memory_domain_access_granted": false, + "provider_call": false, + "secret_value_read": false, + "raw_payload_imported": false, + "seed_release_claim": false, + "runtime_readiness_claim": false, + "public_release_claim": false + }, + "memory_policy": { + "target": "secondbrain-personal", + "forbidden": [ + "orgbrain" + ], + "desktop_displays_memory_content": false, + "desktop_displays_redacted_state_only": true + }, + "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": { + "adapter_lane_pickup": "blocked-follow-up", + "desktop_ui_wiring": "blocked-follow-up", + "seed_package_pickup": "blocked-follow-up", + "runtime_readiness_finalization": "blocked-follow-up", + "browser_host_runtime_approval": "blocked-follow-up", + "final_acceptance_packet": "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 26ed545..c0c09c1 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 @@ -41,7 +41,8 @@ desktop exposure must be treated as one of: | 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 | -| Desktop app exposure | blocked-follow-up | `PACR-008`, after adapter lane release | +| Personal-agent desktop exposure contract | active-authority | `docs/contracts/personal-agent-desktop-exposure-contract.json` defines adapter-visible rows without UI wiring | +| Desktop app exposure wiring | blocked-follow-up | Owning adapter lane must pick up the contract and wire UI after approval | | Browser/Webwright host control | blocked-follow-up | `PACR-009`, explicit approval only | ## Known Artifacts And Supersession State @@ -80,6 +81,7 @@ desktop exposure must be treated as one of: - 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 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. - 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 3845579..14893a0 100755 --- a/tools/validate_steev_child.py +++ b/tools/validate_steev_child.py @@ -23,6 +23,7 @@ REQUIRED = [ "docs/contracts/personal-agent-secondbrain-proposal-route.json", "docs/contracts/personal-agent-conductor-curator-service-handoff.json", "docs/contracts/personal-agent-runtime-readiness-snapshot.json", + "docs/contracts/personal-agent-desktop-exposure-contract.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", @@ -130,6 +131,34 @@ REQUIRED_RUNTIME_GAPS = { "desktop-adapter-exposure-blocked", } +REQUIRED_ADAPTER_SURFACES = { + "package.status", + "runtime.health", + "onboarding.state", + "profile.distribution", + "capability.catalog", +} + +REQUIRED_DESKTOP_STATE_VOCABULARY = { + "ready", + "degraded", + "pending", + "blocked", + "disabled", +} + +REQUIRED_DESKTOP_ROWS = { + "personal-agent.profile", + "personal-agent.imessage.read", + "personal-agent.mail.read", + "personal-agent.calendar.read", + "personal-agent.contacts.read", + "personal-agent.drive.read", + "personal-agent.secondbrain.proposal", + "personal-agent.browser.host-runtime", + "personal-agent.write-actions", +} + def read_text(rel: str) -> str: return (ROOT / rel).read_text(encoding="utf-8") @@ -167,6 +196,7 @@ def main() -> int: "PACR-005", "PACR-006", "PACR-007", + "PACR-008", "status: candidate", "owner: jp", ]: @@ -873,6 +903,142 @@ def main() -> int: if remaining_gates.get("reboot_power_loss_drill") != "optional-follow-up": errors.append("runtime_snapshot_reboot_gate_missing") + desktop = load_json("docs/contracts/personal-agent-desktop-exposure-contract.json", errors) + if desktop: + if desktop.get("schema_version") != "personal-agent-desktop-exposure-contract/v1": + errors.append("desktop_exposure_schema_version_invalid") + if desktop.get("status") != "active-profile-desktop-exposure-contract": + errors.append("desktop_exposure_status_invalid") + if desktop.get("contract_id") != "personal-agent-desktop-exposure-contract": + errors.append("desktop_exposure_contract_id_invalid") + if desktop.get("profile_identity") != "personal-agent": + errors.append("desktop_exposure_profile_identity_not_personal_agent") + if desktop.get("display_name") != "Steev": + errors.append("desktop_exposure_display_name_not_steev") + for key in [ + "desktop_integration_claimed", + "runtime_readiness_claimed", + "seed_readiness_claimed", + "core_promotion_claimed", + ]: + if desktop.get(key) is not False: + errors.append(f"desktop_exposure_overclaim:{key}") + if desktop.get("adapter_workspace") != "../cortex-hermes-adapter": + errors.append("desktop_exposure_adapter_workspace_invalid") + if desktop.get("adapter_validator_command") != "python3 tools/validate_cortex_hermes_adapter_child.py": + errors.append("desktop_exposure_adapter_validator_missing") + if desktop.get("adapter_validator_result_observed") != "ok": + errors.append("desktop_exposure_adapter_validator_not_ok") + refs = set(desktop.get("adapter_reference_contracts", [])) + for ref in [ + "../cortex-hermes-adapter/contracts/desktop-dashboard-host-surface.md", + "../cortex-hermes-adapter/contracts/personal-agent-s518-runtime-host-surface-intake.json", + "../cortex-hermes-adapter/contracts/first-open-evidence.schema.json", + "../cortex-hermes-adapter/dashboard/package-view.sample.json", + ]: + if ref not in refs: + errors.append(f"desktop_exposure_adapter_ref_missing:{ref}") + boundary = desktop.get("authority_boundary", {}) + for key in [ + "profile_owns_desktop_exposure_contract", + "adapter_owns_desktop_rendering", + "seed_owns_package_first_open_proof", + "core_owns_acceptance", + ]: + if boundary.get(key) is not True: + errors.append(f"desktop_exposure_boundary_missing:{key}") + if boundary.get("profile_mutates_adapter") is not False: + errors.append("desktop_exposure_profile_mutates_adapter") + adapter_surfaces = set(desktop.get("allowed_adapter_surfaces", [])) + if adapter_surfaces != REQUIRED_ADAPTER_SURFACES: + errors.append("desktop_exposure_adapter_surfaces_invalid") + state_vocabulary = set(desktop.get("state_vocabulary", [])) + if state_vocabulary != REQUIRED_DESKTOP_STATE_VOCABULARY: + errors.append("desktop_exposure_state_vocabulary_invalid") + rows = desktop.get("desktop_rows", []) + row_ids = {row.get("row_id") for row in rows} + missing_rows = sorted(REQUIRED_DESKTOP_ROWS - row_ids) + if missing_rows: + errors.append(f"desktop_exposure_rows_missing:{','.join(missing_rows)}") + state_by_row = {row.get("row_id"): row.get("state") for row in rows} + expected_states = { + "personal-agent.profile": "degraded", + "personal-agent.imessage.read": "ready", + "personal-agent.mail.read": "degraded", + "personal-agent.calendar.read": "degraded", + "personal-agent.contacts.read": "degraded", + "personal-agent.drive.read": "degraded", + "personal-agent.secondbrain.proposal": "pending", + "personal-agent.browser.host-runtime": "blocked", + "personal-agent.write-actions": "disabled", + } + for row_id, expected in expected_states.items(): + if state_by_row.get(row_id) != expected: + errors.append(f"desktop_exposure_row_state_invalid:{row_id}") + for row in rows: + row_id = row.get("row_id", "") + if row.get("surface") not in REQUIRED_ADAPTER_SURFACES: + errors.append(f"desktop_exposure_row_surface_invalid:{row_id}") + if row.get("state") not in REQUIRED_DESKTOP_STATE_VOCABULARY: + errors.append(f"desktop_exposure_row_state_not_vocab:{row_id}") + if not row.get("source_contract"): + errors.append(f"desktop_exposure_row_source_missing:{row_id}") + if not row.get("visible_reason"): + errors.append(f"desktop_exposure_row_reason_missing:{row_id}") + false_effects = desktop.get("desktop_false_effects", {}) + for effect in [ + "adapter_mutated_by_profile", + "desktop_or_dashboard_opened", + "runtime_started", + "runtime_stopped", + "docker_started", + "profile_exposure_changed", + "memory_domain_access_granted", + "provider_call", + "secret_value_read", + "raw_payload_imported", + "seed_release_claim", + "runtime_readiness_claim", + "public_release_claim", + ]: + if false_effects.get(effect) is not False: + errors.append(f"desktop_exposure_false_effect_not_false:{effect}") + memory = desktop.get("memory_policy", {}) + if memory.get("target") != "secondbrain-personal": + errors.append("desktop_exposure_memory_target_not_secondbrain_personal") + if "orgbrain" not in memory.get("forbidden", []): + errors.append("desktop_exposure_orgbrain_not_forbidden") + if memory.get("desktop_displays_memory_content") is not False: + errors.append("desktop_exposure_displays_memory_content") + if memory.get("desktop_displays_redacted_state_only") is not True: + errors.append("desktop_exposure_not_redacted_state_only") + forbidden_fields = set(desktop.get("proof_policy", {}).get("forbidden_fields", [])) + for field in [ + "raw_messages", + "message_text", + "mail_bodies", + "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"desktop_exposure_forbidden_field_missing:{field}") + remaining_gates = desktop.get("remaining_gates", {}) + for gate in [ + "adapter_lane_pickup", + "desktop_ui_wiring", + "seed_package_pickup", + "runtime_readiness_finalization", + "browser_host_runtime_approval", + "final_acceptance_packet", + ]: + if remaining_gates.get(gate) != "blocked-follow-up": + errors.append(f"desktop_exposure_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(): @@ -895,6 +1061,7 @@ def main() -> int: "Personal-agent Secondbrain proposal route", "Personal-agent Conductor/Curator service handoff", "Personal-agent runtime readiness snapshot", + "Personal-agent desktop exposure contract", "superseded", "legacy-reference", "blocked-follow-up", @@ -910,7 +1077,7 @@ def main() -> int: result = { "ok": not errors, - "validator": "personal-agent-profile-distribution-v6", + "validator": "personal-agent-profile-distribution-v7", "checked": REQUIRED, "errors": errors, "warnings": [],