docs: define personal-agent desktop exposure contract
This commit is contained in:
@@ -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", "<unknown>")
|
||||
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": [],
|
||||
|
||||
Reference in New Issue
Block a user