myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

commit 4b6d7b86985b21f5d146fbf265554954ea7147c5
parent 6d44817cb58e46903618796deda1f91633638899
Author: triesap <tyson@radroots.org>
Date:   Mon, 10 Aug 2026 21:44:36 +0000

contracts: reserve Myc operator surface

- Freeze the closed Unix-admin route and model inventory.
- Define identity roles, provider variants, and pagination semantics.
- Bind doctor, exit, readiness, and mutation commit behavior.
- Lock canonical NIP-46 permission vectors and bounds.

Diffstat:
MAGENTS.md | 6++++++
Acontracts/services_hardening/operator_contract.v1.json | 282+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atests/services_hardening_contracts.rs | 385+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 673 insertions(+), 0 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md @@ -30,6 +30,12 @@ - Before editing, read this file, `README`, `Cargo.toml`, `radroots.lib.source-lock.v1.toml`, the relevant implementation and tests, and `flake.nix` or migrations when they are in scope. +- `.radroots-consumer-root` is the standalone source-lock identity and must + remain exactly `myc`. The reserved pre-implementation control-plane + contract is `contracts/services_hardening/operator_contract.v1.json`. + Service implementation must use its exact routes, operation IDs, model + fields, doctor checks, and shared host/exit references; prototype CLI or + HTTP behavior is not authority to reinterpret that contract. - Treat checked-in source, tests, and prototype behavior as implementation evidence, not permission to preserve behavior that the active requirement removes. diff --git a/contracts/services_hardening/operator_contract.v1.json b/contracts/services_hardening/operator_contract.v1.json @@ -0,0 +1,282 @@ +{ + "schema": "radroots.myc.operator-contract.v1", + "contract_version": 1, + "decision_state": "reserved_preimplementation", + "service": "myc", + "shared_host_contract": { + "repository": "https://github.com/radrootslabs/lib", + "path": "contracts/architecture/decisions/services_hardening_host.v1.json", + "schema": "radroots.services-hardening.host-decisions.v1", + "contract_version": 1 + }, + "admin": { + "transport": "http_1_1_over_unix_domain_socket", + "base_path": "/v1", + "route_inventory_closed": true, + "routes": [ + { "method": "GET", "path": "/v1/status", "operation_id": "radroots.myc.status.get.v1", "request_model": "empty", "response_model": "service_status_v1", "mutation": false }, + { "method": "GET", "path": "/v1/config/effective", "operation_id": "radroots.myc.config.effective.get.v1", "request_model": "empty", "response_model": "effective_config_v1", "mutation": false }, + { "method": "GET", "path": "/v1/identity/status", "operation_id": "radroots.myc.identity.status.get.v1", "request_model": "identity_status_query_v1", "response_model": "identity_status_v1", "mutation": false }, + { "method": "POST", "path": "/v1/identity/rekey", "operation_id": "radroots.myc.identity.rekey.v1", "request_model": "identity_rekey_request_v1", "response_model": "identity_mutation_receipt_v1", "mutation": true }, + { "method": "POST", "path": "/v1/identity/replace", "operation_id": "radroots.myc.identity.replace.v1", "request_model": "identity_replace_request_v1", "response_model": "identity_mutation_receipt_v1", "mutation": true }, + { "method": "GET", "path": "/v1/identity/public", "operation_id": "radroots.myc.identity.public.get.v1", "request_model": "identity_public_query_v1", "response_model": "identity_public_v1", "mutation": false }, + { "method": "GET", "path": "/v1/state/status", "operation_id": "radroots.myc.state.status.get.v1", "request_model": "empty", "response_model": "state_status_v1", "mutation": false }, + { "method": "POST", "path": "/v1/state/backup", "operation_id": "radroots.myc.state.backup.create.v1", "request_model": "state_backup_request_v1", "response_model": "state_backup_receipt_v1", "mutation": true }, + { "method": "GET", "path": "/v1/metrics/snapshot", "operation_id": "radroots.myc.metrics.snapshot.get.v1", "request_model": "empty", "response_model": "metrics_snapshot_v1", "mutation": false }, + { "method": "GET", "path": "/v1/connections", "operation_id": "radroots.myc.connections.list.v1", "request_model": "connections_query_v1", "response_model": "connections_page_v1", "mutation": false }, + { "method": "POST", "path": "/v1/connections/{connection_id}/approve", "operation_id": "radroots.myc.connection.approve.v1", "request_model": "connection_approve_request_v1", "response_model": "connection_mutation_receipt_v1", "mutation": true }, + { "method": "POST", "path": "/v1/connections/{connection_id}/reject", "operation_id": "radroots.myc.connection.reject.v1", "request_model": "connection_reject_request_v1", "response_model": "connection_mutation_receipt_v1", "mutation": true }, + { "method": "POST", "path": "/v1/connections/{connection_id}/revoke", "operation_id": "radroots.myc.connection.revoke.v1", "request_model": "connection_revoke_request_v1", "response_model": "connection_mutation_receipt_v1", "mutation": true }, + { "method": "POST", "path": "/v1/authorization/challenges/require", "operation_id": "radroots.myc.authorization.challenge.require.v1", "request_model": "challenge_require_request_v1", "response_model": "challenge_v1", "mutation": true }, + { "method": "POST", "path": "/v1/authorization/challenges/{challenge_id}/authorize", "operation_id": "radroots.myc.authorization.challenge.authorize.v1", "request_model": "challenge_authorize_request_v1", "response_model": "challenge_authorization_receipt_v1", "mutation": true }, + { "method": "GET", "path": "/v1/audit/events", "operation_id": "radroots.myc.audit.events.list.v1", "request_model": "audit_events_query_v1", "response_model": "audit_events_page_v1", "mutation": false }, + { "method": "GET", "path": "/v1/audit/summary", "operation_id": "radroots.myc.audit.summary.get.v1", "request_model": "audit_summary_query_v1", "response_model": "audit_summary_v1", "mutation": false }, + { "method": "GET", "path": "/v1/discovery/desired", "operation_id": "radroots.myc.discovery.desired.get.v1", "request_model": "empty", "response_model": "discovery_desired_v1", "mutation": false }, + { "method": "POST", "path": "/v1/discovery/render", "operation_id": "radroots.myc.discovery.render.v1", "request_model": "discovery_render_request_v1", "response_model": "discovery_render_receipt_v1", "mutation": true }, + { "method": "POST", "path": "/v1/discovery/refresh", "operation_id": "radroots.myc.discovery.refresh.v1", "request_model": "discovery_refresh_request_v1", "response_model": "discovery_refresh_receipt_v1", "mutation": true }, + { "method": "POST", "path": "/v1/discovery/publish", "operation_id": "radroots.myc.discovery.publish.v1", "request_model": "discovery_publish_request_v1", "response_model": "discovery_publish_receipt_v1", "mutation": true } + ], + "model_wire_contract": { + "encoding": "utf8_json", + "object_unknown_fields": "reject", + "duplicate_fields": "reject", + "null": "forbidden", + "optional_field_representation": "absent", + "integer_representation": "exact_json_integer", + "utc_time_unit": "signed_integer_unix_seconds", + "string_length_unit": "utf8_bytes", + "array_order": "preserved_unless_type_declares_canonical_sort", + "response_body_max_utf8_bytes": 1048576 + }, + "identity_contract": { + "roles": [ + { "id": "transport", "required": true, "disabled_allowed": false, "providers": ["encrypted_file", "local_signer"] }, + { "id": "user", "required": true, "disabled_allowed": false, "providers": ["encrypted_file", "local_signer"] }, + { "id": "discovery", "required": false, "disabled_allowed": true, "providers": ["encrypted_file", "local_signer"] } + ], + "rekey_provider": "encrypted_file_only", + "replace_provider_variants": { + "discriminator": "provider", + "encrypted_file": { + "required_fields": ["provider", "envelope_path", "credential_reference", "expected_public_key"], + "provider_value": "encrypted_file" + }, + "local_signer": { + "required_fields": ["provider", "socket_path", "request_deadline_ms", "request_max_bytes", "response_max_bytes", "concurrency", "expected_public_key"], + "provider_value": "local_signer" + } + } + }, + "types": { + "myc_literal": { "kind": "literal", "value": "myc" }, + "myc_config_schema_literal": { "kind": "literal", "value": "radroots.myc.config" }, + "contract_version_literal": { "kind": "literal", "value": 1 }, + "bool": { "kind": "boolean" }, + "u64": { "kind": "integer", "minimum": 0, "maximum": 18446744073709551615 }, + "positive_u32": { "kind": "integer", "minimum": 1, "maximum": 4294967295 }, + "utc_seconds": { "kind": "integer", "unit": "unix_seconds", "minimum": 0, "maximum": 9223372036854775807 }, + "bounded_id": { "kind": "string", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 128, "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$" }, + "operation_id": { "kind": "string", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 128 }, + "reason_code": { "kind": "string", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 64, "pattern": "^[a-z][a-z0-9_]*$" }, + "reason_codes": { "kind": "array", "items": "reason_code", "maximum_items": 32, "unique": true, "canonical_sort": "ascending_utf8" }, + "sha256_hex": { "kind": "string", "utf8_bytes": 64, "pattern": "^[0-9a-f]{64}$" }, + "public_key_hex": { "kind": "string", "utf8_bytes": 64, "pattern": "^[0-9a-f]{64}$" }, + "absolute_path": { "kind": "string", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 4096, "pattern": "^/" }, + "credential_reference": { "kind": "string", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 256 }, + "confirmation": { "kind": "literal", "value": "confirm" }, + "identity_role": { "kind": "enum", "values": ["transport", "user", "discovery"] }, + "encrypted_file_provider": { "kind": "literal", "value": "encrypted_file" }, + "identity_provider_kind": { "kind": "enum", "values": ["encrypted_file", "local_signer"] }, + "identity_provider_replacement": { "kind": "tagged_union", "discriminator": "provider", "variants": ["encrypted_file_replacement", "local_signer_replacement"] }, + "encrypted_file_replacement": { "kind": "closed_object", "fields": { "provider": "encrypted_file_provider", "envelope_path": "absolute_path", "credential_reference": "credential_reference", "expected_public_key": "public_key_hex" } }, + "local_signer_replacement": { "kind": "closed_object", "fields": { "provider": "local_signer_provider", "socket_path": "absolute_path", "request_deadline_ms": "deadline_ms", "request_max_bytes": "bounded_io_bytes", "response_max_bytes": "bounded_io_bytes", "concurrency": "bounded_concurrency", "expected_public_key": "public_key_hex" } }, + "local_signer_provider": { "kind": "literal", "value": "local_signer" }, + "deadline_ms": { "kind": "integer", "minimum": 1, "maximum": 30000, "unit": "milliseconds" }, + "bounded_io_bytes": { "kind": "integer", "minimum": 1024, "maximum": 1048576, "unit": "bytes" }, + "bounded_concurrency": { "kind": "integer", "minimum": 1, "maximum": 64 }, + "service_phase": { "kind": "enum", "values": ["starting", "ready", "degraded", "unready", "stopping", "failed"] }, + "uptime_millis": { "kind": "integer", "minimum": 0, "maximum": 18446744073709551615, "unit": "milliseconds_from_injected_monotonic_clock" }, + "integrity_state": { "kind": "enum", "values": ["verified", "verification_required", "failed"] }, + "writer_lock_state": { "kind": "enum", "values": ["held_by_daemon", "free", "conflict"] }, + "configuration_source": { "kind": "enum", "values": ["explicit_config", "derived_repo_local"] }, + "configuration_identity": { "kind": "closed_object", "fields": { "schema": "myc_config_schema_literal", "schema_version": "contract_version_literal", "digest": "sha256_hex", "source": "configuration_source" } }, + "persistence_health": { "kind": "enum", "values": ["ready", "read_only", "repair_required", "unavailable"] }, + "persistence_state": { "kind": "closed_object", "fields": { "health": "persistence_health", "schema_version": "positive_u32", "generation": "u64", "integrity": "integrity_state", "reason_codes": "reason_codes" } }, + "provider_health": { "kind": "enum", "values": ["ready", "degraded", "unavailable"] }, + "provider_state": { "kind": "closed_object", "fields": { "health": "provider_health", "transport": "identity_health", "user": "identity_health", "discovery": "identity_health", "reason_codes": "reason_codes" } }, + "transport_health": { "kind": "enum", "values": ["ready", "degraded", "unavailable"] }, + "transport_state": { "kind": "closed_object", "fields": { "health": "transport_health", "required_relays_ready": "bool", "connected_relay_count": "u64", "reason_codes": "reason_codes" } }, + "connection_state": { "kind": "enum", "values": ["pending", "approved", "rejected", "revoked"] }, + "challenge_state": { "kind": "enum", "values": ["pending", "authorized", "expired", "rejected"] }, + "audit_outcome": { "kind": "enum", "values": ["succeeded", "rejected", "failed"] }, + "nip46_permission": { + "kind": "string_union", + "simple_values": ["nip04_decrypt", "nip04_encrypt", "nip44_decrypt", "nip44_encrypt", "switch_relays"], + "sign_event_pattern": "^sign_event:kind:(0|[1-9][0-9]{0,9})$", + "sign_event_kind_minimum": 0, + "sign_event_kind_maximum": 4294967295, + "sign_event_kind_encoding": "canonical_unsigned_decimal_no_leading_zeroes", + "maximum_utf8_bytes": 64, + "unpermissioned_methods": ["connect", "get_public_key", "get_session_capability", "ping", "logout"], + "custom_methods": "unsupported_v1", + "required_permission_api": "radroots_nostr_connect::server::required_permission", + "permission_type": "radroots_nostr_connect::Permission", + "fixed_vectors": { + "valid": ["sign_event:kind:0", "sign_event:kind:1", "sign_event:kind:4294967295", "switch_relays"], + "invalid": ["get_public_key", "ping", "sign_event", "sign_event:kind:", "sign_event:kind:00", "sign_event:kind:4294967296", "sign_event:kind:9999999999"] + } + }, + "permission_set": { + "kind": "canonical_delimited_set", + "items": "nip46_permission", + "delimiter": ",", + "maximum_items": 64, + "maximum_utf8_bytes": 4096, + "unique": true, + "canonical_sort": "ascending_rendered_permission_utf8", + "empty_allowed": true, + "sign_event_kind_ceiling": "every_parameterized_kind_must_also_be_allowed_by_configured_policy" + }, + "page_limit": { "kind": "integer", "minimum": 1, "maximum": 200 }, + "page_cursor": { "kind": "string", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 512, "encoding": "canonical_base64url_no_padding", "integrity": "server_authenticated" }, + "connection_state_filter": { "kind": "enum", "values": ["pending", "approved", "rejected", "revoked"] }, + "safe_code": { "kind": "string", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 64, "pattern": "^[a-z][a-z0-9_]*$" }, + "safe_url": { "kind": "string", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 2048, "allowed_schemes": ["https"] }, + "safe_counts": { "kind": "map", "key": "safe_code", "value": "u64", "maximum_entries": 128, "canonical_sort": "ascending_key_utf8" }, + "stable_metrics": { "kind": "map", "key": "safe_code", "value": "u64", "maximum_entries": 256, "high_cardinality_values_forbidden": true, "canonical_sort": "ascending_key_utf8" }, + "config_provenance": { "kind": "map", "key": "safe_code", "value": "config_provenance_value", "maximum_entries": 512, "canonical_sort": "ascending_key_utf8" }, + "config_provenance_value": { "kind": "enum", "values": ["bootstrap_cli", "toml", "safe_default", "derived_path"] }, + "redacted_config": { "kind": "canonical_json_object", "schema": "radroots.myc.config@1", "maximum_utf8_bytes": 786432, "protected_material": "redacted_or_omitted" }, + "build_info": { "kind": "closed_object", "fields": { "version": "bounded_id", "revision": "git_revision", "toolchain": "bounded_id", "contract_versions": "safe_counts" } }, + "git_revision": { "kind": "string", "utf8_bytes": 40, "pattern": "^[0-9a-f]{40}$" }, + "myc_status": { "kind": "closed_object", "fields": { "transport": "identity_health", "user": "identity_health", "discovery": "identity_health", "connection_counts": "safe_counts", "outbox": "outbox_status" } }, + "identity_health": { "kind": "closed_object", "fields": { "configured": "bool", "available": "bool", "reason_codes": "reason_codes" } }, + "outbox_status": { "kind": "closed_object", "fields": { "pending": "u64", "unknown": "u64", "oldest_pending_at_utc": "optional_utc_seconds" } }, + "optional_utc_seconds": { "kind": "optional", "representation": "absent_parent_field", "value": "utc_seconds" }, + "connection_summaries": { "kind": "array", "items": "connection_summary", "maximum_items": 200, "ordered": true }, + "connection_summary": { "kind": "closed_object", "fields": { "connection_id": "bounded_id", "client_public_key": "public_key_hex", "state": "connection_state", "permissions": "permission_set", "generation": "u64", "created_at_utc": "utc_seconds", "updated_at_utc": "utc_seconds" } }, + "request_identity": { "kind": "string", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 256 }, + "audit_kind": { "kind": "string", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 64, "pattern": "^[a-z][a-z0-9_]*$" }, + "audit_events": { "kind": "array", "items": "audit_event", "maximum_items": 200, "ordered": true }, + "audit_event": { "kind": "closed_object", "fields": { "audit_id": "bounded_id", "occurred_at_utc": "utc_seconds", "kind": "audit_kind", "outcome": "audit_outcome", "reason_code": "reason_code", "correlation_id": "bounded_id" } }, + "document_digests": { "kind": "map", "key": "bounded_id", "value": "sha256_hex", "maximum_entries": 16, "canonical_sort": "ascending_key_utf8" }, + "publication_state": { "kind": "enum", "values": ["disabled", "pending", "submitted", "delivered", "failed", "unknown"] }, + "source_completion": { "kind": "enum", "values": ["complete", "partial", "unavailable"] }, + "discovery_diff_state": { "kind": "enum", "values": ["in_sync", "drift", "conflict", "unavailable"] }, + "workflow_id": { "kind": "alias", "target": "bounded_id" }, + "target_count": { "kind": "integer", "minimum": 0, "maximum": 64 } + }, + "models": { + "empty": { "fields": {} }, + "service_status_v1": { "fields": { "contract_version": { "type": "contract_version_literal", "presence": "required" }, "service": { "type": "myc_literal", "presence": "required" }, "instance": { "type": "bounded_id", "presence": "required" }, "phase": { "type": "service_phase", "presence": "required" }, "ready": { "type": "bool", "presence": "required" }, "uptime_millis": { "type": "uptime_millis", "presence": "required" }, "reason_codes": { "type": "reason_codes", "presence": "required" }, "build_info": { "type": "build_info", "presence": "required" }, "configuration": { "type": "configuration_identity", "presence": "required" }, "persistence": { "type": "persistence_state", "presence": "required" }, "provider": { "type": "provider_state", "presence": "required" }, "transport": { "type": "transport_state", "presence": "required" }, "myc": { "type": "myc_status", "presence": "required" } } }, + "effective_config_v1": { "fields": { "schema": { "type": "myc_config_schema_literal", "presence": "required" }, "schema_version": { "type": "contract_version_literal", "presence": "required" }, "provenance": { "type": "config_provenance", "presence": "required" }, "redacted_config": { "type": "redacted_config", "presence": "required" }, "config_digest": { "type": "sha256_hex", "presence": "required" } } }, + "identity_status_query_v1": { "fields": { "role": { "type": "identity_role", "presence": "required" } } }, + "identity_status_v1": { "fields": { "role": { "type": "identity_role", "presence": "required" }, "provider": { "type": "identity_provider_kind", "presence": "optional" }, "configured": { "type": "bool", "presence": "required" }, "available": { "type": "bool", "presence": "required" }, "generation": { "type": "u64", "presence": "required" }, "reason_codes": { "type": "reason_codes", "presence": "required" }, "public_key": { "type": "public_key_hex", "presence": "optional" } } }, + "identity_rekey_request_v1": { "fields": { "role": { "type": "identity_role", "presence": "required" }, "provider": { "type": "encrypted_file_provider", "presence": "required" }, "expected_generation": { "type": "u64", "presence": "required" }, "new_credential_reference": { "type": "credential_reference", "presence": "required" }, "confirmation": { "type": "confirmation", "presence": "required" } } }, + "identity_replace_request_v1": { "fields": { "role": { "type": "identity_role", "presence": "required" }, "expected_generation": { "type": "u64", "presence": "required" }, "replacement": { "type": "identity_provider_replacement", "presence": "required" }, "confirmation": { "type": "confirmation", "presence": "required" } } }, + "identity_mutation_receipt_v1": { "fields": { "operation_id": { "type": "operation_id", "presence": "required" }, "role": { "type": "identity_role", "presence": "required" }, "previous_public_key": { "type": "public_key_hex", "presence": "required" }, "current_public_key": { "type": "public_key_hex", "presence": "required" }, "generation": { "type": "u64", "presence": "required" } } }, + "identity_public_query_v1": { "fields": { "role": { "type": "identity_role", "presence": "required" } } }, + "identity_public_v1": { "fields": { "role": { "type": "identity_role", "presence": "required" }, "public_key": { "type": "public_key_hex", "presence": "required" }, "generation": { "type": "u64", "presence": "required" } } }, + "state_status_v1": { "fields": { "schema_version": { "type": "positive_u32", "presence": "required" }, "generation": { "type": "u64", "presence": "required" }, "integrity": { "type": "integrity_state", "presence": "required" }, "writer_lock": { "type": "writer_lock_state", "presence": "required" }, "backup_eligible": { "type": "bool", "presence": "required" }, "reason_codes": { "type": "reason_codes", "presence": "required" } } }, + "state_backup_request_v1": { "fields": { "target_path": { "type": "absolute_path", "presence": "required" }, "expected_generation": { "type": "u64", "presence": "required" }, "confirmation": { "type": "confirmation", "presence": "required" } } }, + "state_backup_receipt_v1": { "fields": { "operation_id": { "type": "operation_id", "presence": "required" }, "manifest_digest": { "type": "sha256_hex", "presence": "required" }, "snapshot_generation": { "type": "u64", "presence": "required" }, "completed_at_utc": { "type": "utc_seconds", "presence": "required" } } }, + "metrics_snapshot_v1": { "fields": { "captured_at_utc": { "type": "utc_seconds", "presence": "required" }, "metrics": { "type": "stable_metrics", "presence": "required" } } }, + "connections_query_v1": { "fields": { "limit": { "type": "page_limit", "presence": "required" }, "cursor": { "type": "page_cursor", "presence": "optional" }, "state": { "type": "connection_state_filter", "presence": "optional" } } }, + "connections_page_v1": { "fields": { "snapshot_generation": { "type": "u64", "presence": "required" }, "items": { "type": "connection_summaries", "presence": "required" }, "next_cursor": { "type": "page_cursor", "presence": "optional" } } }, + "connection_approve_request_v1": { "fields": { "expected_generation": { "type": "u64", "presence": "required" }, "permissions": { "type": "permission_set", "presence": "required" }, "confirmation": { "type": "confirmation", "presence": "required" } } }, + "connection_reject_request_v1": { "fields": { "expected_generation": { "type": "u64", "presence": "required" }, "reason_code": { "type": "reason_code", "presence": "required" }, "confirmation": { "type": "confirmation", "presence": "required" } } }, + "connection_revoke_request_v1": { "fields": { "expected_generation": { "type": "u64", "presence": "required" }, "reason_code": { "type": "reason_code", "presence": "required" }, "confirmation": { "type": "confirmation", "presence": "required" } } }, + "connection_mutation_receipt_v1": { "fields": { "operation_id": { "type": "operation_id", "presence": "required" }, "connection_id": { "type": "bounded_id", "presence": "required" }, "previous_state": { "type": "connection_state", "presence": "required" }, "current_state": { "type": "connection_state", "presence": "required" }, "generation": { "type": "u64", "presence": "required" } } }, + "challenge_require_request_v1": { "fields": { "connection_id": { "type": "bounded_id", "presence": "required" }, "request_identity": { "type": "request_identity", "presence": "required" }, "expected_policy_generation": { "type": "u64", "presence": "required" } } }, + "challenge_v1": { "fields": { "challenge_id": { "type": "bounded_id", "presence": "required" }, "connection_id": { "type": "bounded_id", "presence": "required" }, "request_identity": { "type": "request_identity", "presence": "required" }, "challenge_url": { "type": "safe_url", "presence": "required" }, "issued_at_utc": { "type": "utc_seconds", "presence": "required" }, "expires_at_utc": { "type": "utc_seconds", "presence": "required" }, "state": { "type": "challenge_state", "presence": "required" } } }, + "challenge_authorize_request_v1": { "fields": { "expected_generation": { "type": "u64", "presence": "required" }, "confirmation": { "type": "confirmation", "presence": "required" } } }, + "challenge_authorization_receipt_v1": { "fields": { "operation_id": { "type": "operation_id", "presence": "required" }, "challenge_id": { "type": "bounded_id", "presence": "required" }, "request_identity": { "type": "request_identity", "presence": "required" }, "state": { "type": "challenge_state", "presence": "required" }, "generation": { "type": "u64", "presence": "required" } } }, + "audit_events_query_v1": { "fields": { "limit": { "type": "page_limit", "presence": "required" }, "cursor": { "type": "page_cursor", "presence": "optional" }, "from_utc": { "type": "utc_seconds", "presence": "optional" }, "to_utc": { "type": "utc_seconds", "presence": "optional" }, "kind": { "type": "audit_kind", "presence": "optional" }, "outcome": { "type": "audit_outcome", "presence": "optional" } } }, + "audit_events_page_v1": { "fields": { "snapshot_generation": { "type": "u64", "presence": "required" }, "items": { "type": "audit_events", "presence": "required" }, "next_cursor": { "type": "page_cursor", "presence": "optional" } } }, + "audit_summary_query_v1": { "fields": { "from_utc": { "type": "utc_seconds", "presence": "required" }, "to_utc": { "type": "utc_seconds", "presence": "required" } } }, + "audit_summary_v1": { "fields": { "from_utc": { "type": "utc_seconds", "presence": "required" }, "to_utc": { "type": "utc_seconds", "presence": "required" }, "counts": { "type": "safe_counts", "presence": "required" } } }, + "discovery_desired_v1": { "fields": { "enabled": { "type": "bool", "presence": "required" }, "generation": { "type": "u64", "presence": "required" }, "document_digests": { "type": "document_digests", "presence": "required" }, "publication_state": { "type": "publication_state", "presence": "required" } } }, + "discovery_render_request_v1": { "fields": { "expected_generation": { "type": "u64", "presence": "required" } } }, + "discovery_render_receipt_v1": { "fields": { "operation_id": { "type": "operation_id", "presence": "required" }, "generation": { "type": "u64", "presence": "required" }, "document_digests": { "type": "document_digests", "presence": "required" } } }, + "discovery_refresh_request_v1": { "fields": { "expected_generation": { "type": "u64", "presence": "required" } } }, + "discovery_refresh_receipt_v1": { "fields": { "operation_id": { "type": "operation_id", "presence": "required" }, "generation": { "type": "u64", "presence": "required" }, "source_completion": { "type": "source_completion", "presence": "required" }, "diff_state": { "type": "discovery_diff_state", "presence": "required" } } }, + "discovery_publish_request_v1": { "fields": { "expected_generation": { "type": "u64", "presence": "required" }, "confirmation": { "type": "confirmation", "presence": "required" } } }, + "discovery_publish_receipt_v1": { "fields": { "operation_id": { "type": "operation_id", "presence": "required" }, "generation": { "type": "u64", "presence": "required" }, "workflow_id": { "type": "workflow_id", "presence": "required" }, "exact_bytes_digest": { "type": "sha256_hex", "presence": "required" }, "target_count": { "type": "target_count", "presence": "required" } } } + }, + "path_parameters": { + "connection_id": { "type": "bounded_id", "source": "percent_decoded_single_path_segment", "slash_allowed": false }, + "challenge_id": { "type": "bounded_id", "source": "percent_decoded_single_path_segment", "slash_allowed": false } + }, + "pagination": { + "cursor_type": "page_cursor", + "limit_min": 1, + "limit_max": 200, + "terminal_page": "next_cursor_field_absent", + "cursor_reuse": "same_route_same_filters_only", + "filter_or_route_mismatch": "invalid_cursor", + "connections_order": ["created_at_utc_ascending", "connection_id_ascending"], + "connections_snapshot": "generation_fixed_by_first_page_cursor", + "audit_order": ["occurred_at_utc_descending", "audit_id_descending"], + "audit_snapshot": "maximum_audit_sequence_fixed_by_first_page_cursor" + }, + "mutation_contract": { + "request_envelope_required_fields": ["contract_version", "operation_id", "request"], + "request_envelope_optional_fields": ["correlation_id"], + "effective_correlation_id_required_in_response": true, + "identical_operation_id_reuse": "return_original_committed_result", + "different_request_operation_id_reuse": { "admin_error_code": "operation_id_conflict", "cli_exit": 5 }, + "success_visibility": "response_only_after_local_authoritative_commit", + "relay_submission_or_delivery_required_for_success": false, + "committed_effects": { + "radroots.myc.identity.rekey.v1": "identity_generation_provider_binding_session_invalidation_and_operation_audit", + "radroots.myc.identity.replace.v1": "identity_generation_provider_binding_session_invalidation_and_operation_audit", + "radroots.myc.state.backup.create.v1": "verified_snapshot_manifest_digest_and_operation_audit", + "radroots.myc.connection.approve.v1": "connection_state_permissions_generation_and_operation_audit", + "radroots.myc.connection.reject.v1": "connection_state_reason_generation_and_operation_audit", + "radroots.myc.connection.revoke.v1": "connection_state_session_revocation_generation_and_operation_audit", + "radroots.myc.authorization.challenge.require.v1": "bounded_challenge_binding_rate_window_and_operation_audit", + "radroots.myc.authorization.challenge.authorize.v1": "challenge_state_bound_request_resume_generation_and_operation_audit", + "radroots.myc.discovery.render.v1": "deterministic_document_generation_digests_and_operation_audit", + "radroots.myc.discovery.refresh.v1": "source_completion_diff_state_generation_and_operation_audit", + "radroots.myc.discovery.publish.v1": "exact_signed_bytes_immutable_targets_outbox_workflow_and_operation_audit" + } + } + }, + "doctor": { + "shared_schema": "radroots.service.doctor.v1", + "contract_version": 1, + "checks": [ + { "id": "paths_permissions", "required": true }, + { "id": "writer_lock", "required": true }, + { "id": "sqlite_schema", "required": true }, + { "id": "sqlite_integrity", "required": true }, + { "id": "sqlite_free_space", "required": true }, + { "id": "identity_binding", "required": true }, + { "id": "signer_provider", "required": true }, + { "id": "admin_bind_policy", "required": true }, + { "id": "operations_bind_policy", "required": true }, + { "id": "network_policy", "required": true }, + { "id": "required_relays", "required": true }, + { "id": "outbox_invariants", "required": true }, + { "id": "clock_skew", "required": false } + ] + }, + "exit_codes": [ + { "code": 0, "name": "success", "meaning": "successful command or completed graceful first-signal shutdown" }, + { "code": 1, "name": "unexpected_internal", "meaning": "unexpected invariant, critical task, or internal failure" }, + { "code": 2, "name": "input_or_configuration", "meaning": "CLI, config, validation, or unsupported contract input" }, + { "code": 3, "name": "service_or_dependency_unavailable", "meaning": "daemon, required provider, relay, source, or local dependency unavailable" }, + { "code": 4, "name": "state_or_identity_unavailable", "meaning": "state, schema, lock, credential, or identity unavailable" }, + { "code": 5, "name": "operation_rejected_or_conflict", "meaning": "authorization rejection, idempotency conflict, stale generation, or domain conflict" }, + { "code": 6, "name": "doctor_required_check_failed", "meaning": "one or more required doctor checks failed or timed out" } + ], + "tcp_operations": { + "routes": [ + { "method": "GET", "path": "/livez", "source": "cached_supervisor_state" }, + { "method": "GET", "path": "/readyz", "source": "cached_readiness_state" }, + { "method": "GET", "path": "/metrics", "source": "cached_bounded_metrics_snapshot" } + ], + "active_probe_per_request": false, + "additional_routes": false + } +} diff --git a/tests/services_hardening_contracts.rs b/tests/services_hardening_contracts.rs @@ -0,0 +1,385 @@ +#![forbid(unsafe_code)] + +use serde_json::Value; +use sha2::{Digest, Sha256}; +use std::collections::BTreeSet; + +const OPERATOR_CONTRACT: &str = + include_str!("../contracts/services_hardening/operator_contract.v1.json"); +const CONSUMER_ROOT: &str = include_str!("../.radroots-consumer-root"); + +fn contract() -> Value { + serde_json::from_str(OPERATOR_CONTRACT).expect("operator contract must be valid JSON") +} + +fn decision_sections_digest(value: &Value) -> String { + let sections = serde_json::json!({ + "identity_contract": value["admin"]["identity_contract"], + "model_wire_contract": value["admin"]["model_wire_contract"], + "models": value["admin"]["models"], + "mutation_contract": value["admin"]["mutation_contract"], + "pagination": value["admin"]["pagination"], + "path_parameters": value["admin"]["path_parameters"], + "types": value["admin"]["types"] + }); + hex::encode(Sha256::digest( + serde_json::to_vec(&sections).expect("serialize decision sections"), + )) +} + +fn permission_vector_is_valid(value: &str) -> bool { + if matches!( + value, + "nip04_decrypt" | "nip04_encrypt" | "nip44_decrypt" | "nip44_encrypt" | "switch_relays" + ) { + return true; + } + let Some(kind) = value.strip_prefix("sign_event:kind:") else { + return false; + }; + if kind.is_empty() || (kind.len() > 1 && kind.starts_with('0')) { + return false; + } + kind.bytes().all(|byte| byte.is_ascii_digit()) && kind.parse::<u32>().is_ok() +} + +#[test] +fn source_lock_identity_and_shared_host_reference_are_exact() { + assert_eq!(CONSUMER_ROOT, "myc\n"); + let value = contract(); + assert_eq!(value["schema"], "radroots.myc.operator-contract.v1"); + assert_eq!(value["contract_version"], 1); + assert_eq!(value["decision_state"], "reserved_preimplementation"); + assert_eq!(value["service"], "myc"); + assert_eq!( + value["shared_host_contract"], + serde_json::json!({ + "repository": "https://github.com/radrootslabs/lib", + "path": "contracts/architecture/decisions/services_hardening_host.v1.json", + "schema": "radroots.services-hardening.host-decisions.v1", + "contract_version": 1 + }) + ); +} + +#[test] +fn admin_inventory_is_closed_unique_and_model_complete() { + let value = contract(); + assert_eq!( + value["admin"]["transport"], + "http_1_1_over_unix_domain_socket" + ); + assert_eq!(value["admin"]["base_path"], "/v1"); + assert_eq!(value["admin"]["route_inventory_closed"], true); + + let routes = value["admin"]["routes"].as_array().expect("routes"); + let exact_routes = routes + .iter() + .map(|route| { + format!( + "{}|{}|{}|{}|{}|{}", + route["method"].as_str().unwrap(), + route["path"].as_str().unwrap(), + route["operation_id"].as_str().unwrap(), + route["request_model"].as_str().unwrap(), + route["response_model"].as_str().unwrap(), + route["mutation"].as_bool().unwrap() + ) + }) + .collect::<Vec<_>>(); + assert_eq!( + exact_routes, + [ + "GET|/v1/status|radroots.myc.status.get.v1|empty|service_status_v1|false", + "GET|/v1/config/effective|radroots.myc.config.effective.get.v1|empty|effective_config_v1|false", + "GET|/v1/identity/status|radroots.myc.identity.status.get.v1|identity_status_query_v1|identity_status_v1|false", + "POST|/v1/identity/rekey|radroots.myc.identity.rekey.v1|identity_rekey_request_v1|identity_mutation_receipt_v1|true", + "POST|/v1/identity/replace|radroots.myc.identity.replace.v1|identity_replace_request_v1|identity_mutation_receipt_v1|true", + "GET|/v1/identity/public|radroots.myc.identity.public.get.v1|identity_public_query_v1|identity_public_v1|false", + "GET|/v1/state/status|radroots.myc.state.status.get.v1|empty|state_status_v1|false", + "POST|/v1/state/backup|radroots.myc.state.backup.create.v1|state_backup_request_v1|state_backup_receipt_v1|true", + "GET|/v1/metrics/snapshot|radroots.myc.metrics.snapshot.get.v1|empty|metrics_snapshot_v1|false", + "GET|/v1/connections|radroots.myc.connections.list.v1|connections_query_v1|connections_page_v1|false", + "POST|/v1/connections/{connection_id}/approve|radroots.myc.connection.approve.v1|connection_approve_request_v1|connection_mutation_receipt_v1|true", + "POST|/v1/connections/{connection_id}/reject|radroots.myc.connection.reject.v1|connection_reject_request_v1|connection_mutation_receipt_v1|true", + "POST|/v1/connections/{connection_id}/revoke|radroots.myc.connection.revoke.v1|connection_revoke_request_v1|connection_mutation_receipt_v1|true", + "POST|/v1/authorization/challenges/require|radroots.myc.authorization.challenge.require.v1|challenge_require_request_v1|challenge_v1|true", + "POST|/v1/authorization/challenges/{challenge_id}/authorize|radroots.myc.authorization.challenge.authorize.v1|challenge_authorize_request_v1|challenge_authorization_receipt_v1|true", + "GET|/v1/audit/events|radroots.myc.audit.events.list.v1|audit_events_query_v1|audit_events_page_v1|false", + "GET|/v1/audit/summary|radroots.myc.audit.summary.get.v1|audit_summary_query_v1|audit_summary_v1|false", + "GET|/v1/discovery/desired|radroots.myc.discovery.desired.get.v1|empty|discovery_desired_v1|false", + "POST|/v1/discovery/render|radroots.myc.discovery.render.v1|discovery_render_request_v1|discovery_render_receipt_v1|true", + "POST|/v1/discovery/refresh|radroots.myc.discovery.refresh.v1|discovery_refresh_request_v1|discovery_refresh_receipt_v1|true", + "POST|/v1/discovery/publish|radroots.myc.discovery.publish.v1|discovery_publish_request_v1|discovery_publish_receipt_v1|true" + ] + ); + let route_keys = routes + .iter() + .map(|route| format!("{} {}", route["method"], route["path"])) + .collect::<BTreeSet<_>>(); + let operation_ids = routes + .iter() + .map(|route| route["operation_id"].as_str().expect("operation ID")) + .collect::<BTreeSet<_>>(); + assert_eq!(route_keys.len(), routes.len()); + assert_eq!(operation_ids.len(), routes.len()); + assert!( + operation_ids + .iter() + .all(|id| id.starts_with("radroots.myc.") && id.ends_with(".v1")) + ); + + let models = value["admin"]["models"].as_object().expect("models"); + let types = value["admin"]["types"].as_object().expect("types"); + for route in routes { + for key in ["request_model", "response_model"] { + let model = route[key].as_str().expect("model reference"); + assert!(models.contains_key(model), "missing model {model}"); + } + assert_eq!(route["mutation"], route["method"] == "POST"); + } + for (model_name, model) in models { + assert_eq!( + model + .as_object() + .unwrap() + .keys() + .map(String::as_str) + .collect::<Vec<_>>(), + ["fields"], + "model {model_name} must be a closed field inventory" + ); + for (field_name, field) in model["fields"].as_object().unwrap() { + assert_eq!( + field + .as_object() + .unwrap() + .keys() + .map(String::as_str) + .collect::<Vec<_>>(), + ["presence", "type"], + "field {model_name}.{field_name} must bind only type and presence" + ); + assert!( + matches!(field["presence"].as_str(), Some("required" | "optional")), + "invalid presence for {model_name}.{field_name}" + ); + let type_name = field["type"].as_str().unwrap(); + assert!(types.contains_key(type_name), "unknown type {type_name}"); + } + } + for (type_name, descriptor) in types { + let referenced = match descriptor["kind"].as_str().unwrap() { + "array" | "canonical_delimited_set" => { + vec![descriptor["items"].as_str().unwrap()] + } + "map" => vec![ + descriptor["key"].as_str().unwrap(), + descriptor["value"].as_str().unwrap(), + ], + "closed_object" => descriptor["fields"] + .as_object() + .unwrap() + .values() + .map(|field| field.as_str().unwrap()) + .collect(), + "tagged_union" => descriptor["variants"] + .as_array() + .unwrap() + .iter() + .map(|variant| variant.as_str().unwrap()) + .collect(), + "alias" => vec![descriptor["target"].as_str().unwrap()], + "optional" => vec![descriptor["value"].as_str().unwrap()], + "boolean" + | "canonical_json_object" + | "enum" + | "integer" + | "literal" + | "string" + | "string_union" => Vec::new(), + kind => panic!("unknown descriptor kind {kind} for {type_name}"), + }; + for reference in referenced { + assert!( + types.contains_key(reference), + "type {type_name} references missing type {reference}" + ); + } + } + assert_eq!( + value["admin"]["identity_contract"]["roles"], + serde_json::json!([ + { "id": "transport", "required": true, "disabled_allowed": false, "providers": ["encrypted_file", "local_signer"] }, + { "id": "user", "required": true, "disabled_allowed": false, "providers": ["encrypted_file", "local_signer"] }, + { "id": "discovery", "required": false, "disabled_allowed": true, "providers": ["encrypted_file", "local_signer"] } + ]) + ); + assert_eq!( + value["admin"]["types"]["service_phase"], + serde_json::json!({ + "kind": "enum", + "values": ["starting", "ready", "degraded", "unready", "stopping", "failed"] + }) + ); + assert_eq!( + value["admin"]["types"]["nip46_permission"], + serde_json::json!({ + "kind": "string_union", + "simple_values": ["nip04_decrypt", "nip04_encrypt", "nip44_decrypt", "nip44_encrypt", "switch_relays"], + "sign_event_pattern": "^sign_event:kind:(0|[1-9][0-9]{0,9})$", + "sign_event_kind_minimum": 0, + "sign_event_kind_maximum": 4_294_967_295_u64, + "sign_event_kind_encoding": "canonical_unsigned_decimal_no_leading_zeroes", + "maximum_utf8_bytes": 64, + "unpermissioned_methods": ["connect", "get_public_key", "get_session_capability", "ping", "logout"], + "custom_methods": "unsupported_v1", + "required_permission_api": "radroots_nostr_connect::server::required_permission", + "permission_type": "radroots_nostr_connect::Permission", + "fixed_vectors": { + "valid": ["sign_event:kind:0", "sign_event:kind:1", "sign_event:kind:4294967295", "switch_relays"], + "invalid": ["get_public_key", "ping", "sign_event", "sign_event:kind:", "sign_event:kind:00", "sign_event:kind:4294967296", "sign_event:kind:9999999999"] + } + }) + ); + for vector in value["admin"]["types"]["nip46_permission"]["fixed_vectors"]["valid"] + .as_array() + .unwrap() + { + assert!(permission_vector_is_valid(vector.as_str().unwrap())); + } + for vector in value["admin"]["types"]["nip46_permission"]["fixed_vectors"]["invalid"] + .as_array() + .unwrap() + { + assert!(!permission_vector_is_valid(vector.as_str().unwrap())); + } + assert_eq!( + value["admin"]["models"]["service_status_v1"]["fields"] + .as_object() + .unwrap() + .keys() + .map(String::as_str) + .collect::<Vec<_>>(), + [ + "build_info", + "configuration", + "contract_version", + "instance", + "myc", + "persistence", + "phase", + "provider", + "ready", + "reason_codes", + "service", + "transport", + "uptime_millis" + ] + ); + assert_eq!( + value["admin"]["identity_contract"]["replace_provider_variants"], + serde_json::json!({ + "discriminator": "provider", + "encrypted_file": { + "required_fields": ["provider", "envelope_path", "credential_reference", "expected_public_key"], + "provider_value": "encrypted_file" + }, + "local_signer": { + "required_fields": ["provider", "socket_path", "request_deadline_ms", "request_max_bytes", "response_max_bytes", "concurrency", "expected_public_key"], + "provider_value": "local_signer" + } + }) + ); + assert_eq!( + value["admin"]["path_parameters"], + serde_json::json!({ + "connection_id": { "type": "bounded_id", "source": "percent_decoded_single_path_segment", "slash_allowed": false }, + "challenge_id": { "type": "bounded_id", "source": "percent_decoded_single_path_segment", "slash_allowed": false } + }) + ); + assert_eq!( + value["admin"]["pagination"], + serde_json::json!({ + "cursor_type": "page_cursor", + "limit_min": 1, + "limit_max": 200, + "terminal_page": "next_cursor_field_absent", + "cursor_reuse": "same_route_same_filters_only", + "filter_or_route_mismatch": "invalid_cursor", + "connections_order": ["created_at_utc_ascending", "connection_id_ascending"], + "connections_snapshot": "generation_fixed_by_first_page_cursor", + "audit_order": ["occurred_at_utc_descending", "audit_id_descending"], + "audit_snapshot": "maximum_audit_sequence_fixed_by_first_page_cursor" + }) + ); + + let mutation_operations = routes + .iter() + .filter(|route| route["mutation"] == true) + .map(|route| route["operation_id"].as_str().unwrap()) + .collect::<BTreeSet<_>>(); + let committed_effects = value["admin"]["mutation_contract"]["committed_effects"] + .as_object() + .unwrap() + .keys() + .map(String::as_str) + .collect::<BTreeSet<_>>(); + assert_eq!(mutation_operations, committed_effects); + assert_eq!( + decision_sections_digest(&value), + "55890c43b1ad17e897e20afe1df72941589b3d6df554bc3c81731aefe1c55d58" + ); +} + +#[test] +fn doctor_exit_and_tcp_contracts_are_exact() { + let value = contract(); + assert_eq!( + value["doctor"]["shared_schema"], + "radroots.service.doctor.v1" + ); + assert_eq!(value["doctor"]["contract_version"], 1); + assert_eq!( + value["doctor"]["checks"], + serde_json::json!([ + { "id": "paths_permissions", "required": true }, + { "id": "writer_lock", "required": true }, + { "id": "sqlite_schema", "required": true }, + { "id": "sqlite_integrity", "required": true }, + { "id": "sqlite_free_space", "required": true }, + { "id": "identity_binding", "required": true }, + { "id": "signer_provider", "required": true }, + { "id": "admin_bind_policy", "required": true }, + { "id": "operations_bind_policy", "required": true }, + { "id": "network_policy", "required": true }, + { "id": "required_relays", "required": true }, + { "id": "outbox_invariants", "required": true }, + { "id": "clock_skew", "required": false } + ]) + ); + assert_eq!( + value["exit_codes"], + serde_json::json!([ + { "code": 0, "name": "success", "meaning": "successful command or completed graceful first-signal shutdown" }, + { "code": 1, "name": "unexpected_internal", "meaning": "unexpected invariant, critical task, or internal failure" }, + { "code": 2, "name": "input_or_configuration", "meaning": "CLI, config, validation, or unsupported contract input" }, + { "code": 3, "name": "service_or_dependency_unavailable", "meaning": "daemon, required provider, relay, source, or local dependency unavailable" }, + { "code": 4, "name": "state_or_identity_unavailable", "meaning": "state, schema, lock, credential, or identity unavailable" }, + { "code": 5, "name": "operation_rejected_or_conflict", "meaning": "authorization rejection, idempotency conflict, stale generation, or domain conflict" }, + { "code": 6, "name": "doctor_required_check_failed", "meaning": "one or more required doctor checks failed or timed out" } + ]) + ); + assert_eq!( + value["tcp_operations"], + serde_json::json!({ + "routes": [ + { "method": "GET", "path": "/livez", "source": "cached_supervisor_state" }, + { "method": "GET", "path": "/readyz", "source": "cached_readiness_state" }, + { "method": "GET", "path": "/metrics", "source": "cached_bounded_metrics_snapshot" } + ], + "active_probe_per_request": false, + "additional_routes": false + }) + ); +}