diff --git a/.well-known/agents-shipgate.json b/.well-known/agents-shipgate.json index e7d4713e..640427d5 100644 --- a/.well-known/agents-shipgate.json +++ b/.well-known/agents-shipgate.json @@ -309,9 +309,9 @@ "attestation_schema_version": "0.5", "registry_schema_version": "0.4", "org_evidence_bundle_schema_version": "shipgate.org_evidence_bundle/v2", - "host_grants_inventory_schema_version": "0.8", - "host_grants_baseline_schema_version": "0.8", - "host_grants_drift_schema_version": "0.8", + "host_grants_inventory_schema_version": "0.9", + "host_grants_baseline_schema_version": "0.9", + "host_grants_drift_schema_version": "0.9", "trigger_catalog_schema_version": "0.4", "capability_standard_version": "0.5", "governance_benchmark_catalog_schema_version": "0.2", @@ -525,9 +525,9 @@ "org_governance": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/org-governance-schema.v0.1.json", "org_evidence_bundle": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/org-evidence-bundle-schema.v2.json", "registry": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/registry-schema.v0.4.json", - "host_grants_inventory": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/host-grants-inventory-schema.v0.8.json", - "host_grants_baseline": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/host-grants-baseline-schema.v0.8.json", - "host_grants_drift": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/host-grants-drift-schema.v0.8.json", + "host_grants_inventory": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/host-grants-inventory-schema.v0.9.json", + "host_grants_baseline": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/host-grants-baseline-schema.v0.9.json", + "host_grants_drift": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/host-grants-drift-schema.v0.9.json", "scenario": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/scenario-schema.v0.1.json", "checks_catalog": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/checks.json", "determinism_boundary": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/determinism-boundary.json", diff --git a/docs/agent-contract-current.md b/docs/agent-contract-current.md index 37a9b36d..59e82122 100644 --- a/docs/agent-contract-current.md +++ b/docs/agent-contract-current.md @@ -820,7 +820,7 @@ Downstream repos generated with - Current attestation schema: `0.5` — [`docs/attestation-schema.v0.5.json`](attestation-schema.v0.5.json) - Current registry schema: `0.4` — [`docs/registry-schema.v0.4.json`](registry-schema.v0.4.json) - Current org evidence bundle schema: `shipgate.org_evidence_bundle/v2` — [`docs/org-evidence-bundle-schema.v2.json`](org-evidence-bundle-schema.v2.json) -- Current host-grants inventory, baseline, and drift schemas: `0.8` — [`inventory`](host-grants-inventory-schema.v0.8.json), [`baseline`](host-grants-baseline-schema.v0.8.json), [`drift`](host-grants-drift-schema.v0.8.json). Version 0.8 adds selected OpenShell document facts; historical host schemas remain frozen. See [OpenShell support](openshell-support.md). +- Current host-grants inventory, baseline, and drift schemas: `0.9` — [`inventory`](host-grants-inventory-schema.v0.9.json), [`baseline`](host-grants-baseline-schema.v0.9.json), [`drift`](host-grants-drift-schema.v0.9.json). Version 0.9 adds explicit local OpenShell composition, provider profile provenance and effective-snapshot metadata; historical host schemas remain frozen. See [OpenShell support](openshell-support.md). - Current trigger catalog schema: `0.4` — [`docs/triggers.json`](triggers.json) - Current governance benchmark catalog schema: `0.2` — [`docs/governance-benchmark-catalog-schema.v0.2.json`](governance-benchmark-catalog-schema.v0.2.json) - Current governance benchmark result schema: `0.2` — [`docs/governance-benchmark-result-schema.v0.2.json`](governance-benchmark-result-schema.v0.2.json) diff --git a/docs/host-grants-baseline-schema.v0.9.json b/docs/host-grants-baseline-schema.v0.9.json new file mode 100644 index 00000000..34fce958 --- /dev/null +++ b/docs/host-grants-baseline-schema.v0.9.json @@ -0,0 +1,3211 @@ +{ + "$defs": { + "CompositionContributor": { + "additionalProperties": false, + "properties": { + "content_sha256": { + "pattern": "^sha256:[a-f0-9]{64}$", + "title": "Content Sha256", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "profile_id": { + "default": "", + "title": "Profile Id", + "type": "string" + }, + "provider": { + "default": "", + "title": "Provider", + "type": "string" + }, + "resource_version": { + "default": 0, + "title": "Resource Version", + "type": "integer" + }, + "role": { + "enum": [ + "global", + "saved", + "image", + "profile" + ], + "title": "Role", + "type": "string" + }, + "rule_key": { + "default": "", + "title": "Rule Key", + "type": "string" + }, + "scope": { + "default": "", + "enum": [ + "", + "platform", + "workspace", + "interceptor" + ], + "title": "Scope", + "type": "string" + }, + "selected": { + "title": "Selected", + "type": "boolean" + }, + "workspace": { + "default": "", + "title": "Workspace", + "type": "string" + } + }, + "required": [ + "path", + "role", + "content_sha256", + "selected" + ], + "title": "CompositionContributor", + "type": "object" + }, + "CompositionProvenance": { + "additionalProperties": false, + "properties": { + "contributors": { + "items": { + "$ref": "#/$defs/CompositionContributor" + }, + "title": "Contributors", + "type": "array" + }, + "creation_bound_fields": { + "items": { + "type": "string" + }, + "title": "Creation Bound Fields", + "type": "array" + }, + "dynamic_fields": { + "items": { + "type": "string" + }, + "title": "Dynamic Fields", + "type": "array" + }, + "name": { + "title": "Name", + "type": "string" + }, + "selected_policy": { + "enum": [ + "global", + "saved", + "image" + ], + "title": "Selected Policy", + "type": "string" + }, + "startup_bound_fields": { + "items": { + "type": "string" + }, + "title": "Startup Bound Fields", + "type": "array" + }, + "workspace": { + "title": "Workspace", + "type": "string" + } + }, + "required": [ + "name", + "workspace", + "selected_policy", + "contributors" + ], + "title": "CompositionProvenance", + "type": "object" + }, + "CredentialEndpointFacts": { + "additionalProperties": false, + "properties": { + "allow_uninspected_credentials": { + "title": "Allow Uninspected Credentials", + "type": "boolean" + }, + "host": { + "title": "Host", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "port": { + "title": "Port", + "type": "integer" + }, + "ports": { + "items": { + "type": "integer" + }, + "title": "Ports", + "type": "array" + }, + "request_body_credential_rewrite": { + "title": "Request Body Credential Rewrite", + "type": "boolean" + }, + "tls": { + "title": "Tls", + "type": "string" + }, + "websocket_credential_rewrite": { + "title": "Websocket Credential Rewrite", + "type": "boolean" + } + }, + "required": [ + "host", + "port", + "ports", + "path", + "tls", + "allow_uninspected_credentials", + "websocket_credential_rewrite", + "request_body_credential_rewrite" + ], + "title": "CredentialEndpointFacts", + "type": "object" + }, + "CredentialUseFacts": { + "additionalProperties": false, + "properties": { + "credential_values_read": { + "const": false, + "default": false, + "title": "Credential Values Read", + "type": "boolean" + }, + "credentials": { + "items": { + "$ref": "#/$defs/StaticCredential" + }, + "title": "Credentials", + "type": "array" + }, + "endpoints": { + "items": { + "$ref": "#/$defs/CredentialEndpointFacts" + }, + "title": "Endpoints", + "type": "array" + }, + "profile_id": { + "title": "Profile Id", + "type": "string" + }, + "provider": { + "title": "Provider", + "type": "string" + }, + "runtime_authorization_verified": { + "const": false, + "default": false, + "title": "Runtime Authorization Verified", + "type": "boolean" + }, + "scope": { + "enum": [ + "platform", + "workspace", + "interceptor" + ], + "title": "Scope", + "type": "string" + }, + "workspace": { + "title": "Workspace", + "type": "string" + } + }, + "required": [ + "provider", + "profile_id", + "scope", + "workspace", + "credentials", + "endpoints" + ], + "title": "CredentialUseFacts", + "type": "object" + }, + "HostAdditionalPathGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "additional_path", + "default": "additional_path", + "title": "Kind", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "path" + ], + "title": "HostAdditionalPathGrantV2", + "type": "object" + }, + "HostArtifactV9": { + "additionalProperties": false, + "properties": { + "artifact_id": { + "title": "Artifact Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github", + "openshell" + ], + "title": "Host", + "type": "string" + }, + "instruction_structure": { + "anyOf": [ + { + "$ref": "#/$defs/InstructionStructureEvidence" + }, + { + "type": "null" + } + ], + "default": null + }, + "kind": { + "enum": [ + "config", + "mcp", + "hooks", + "workflow", + "instructions", + "requirements", + "hook_script", + "openshell_selection", + "openshell_policy", + "openshell_profile" + ], + "title": "Kind", + "type": "string" + }, + "parse_status": { + "enum": [ + "parsed", + "failed", + "unsupported" + ], + "title": "Parse Status", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "redacted_sha256": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Redacted Sha256" + }, + "resolved_through": { + "items": { + "type": "string" + }, + "title": "Resolved Through", + "type": "array" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + } + }, + "required": [ + "artifact_id", + "host", + "scope", + "path", + "kind", + "parse_status" + ], + "title": "HostArtifactV9", + "type": "object" + }, + "HostCoverageV8": { + "additionalProperties": false, + "properties": { + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github", + "openshell" + ], + "title": "Host", + "type": "string" + }, + "issue_ids": { + "items": { + "type": "string" + }, + "title": "Issue Ids", + "type": "array" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "sources_expected": { + "items": { + "type": "string" + }, + "title": "Sources Expected", + "type": "array" + }, + "sources_observed": { + "items": { + "type": "string" + }, + "title": "Sources Observed", + "type": "array" + }, + "status": { + "enum": [ + "complete", + "partial", + "experimental" + ], + "title": "Status", + "type": "string" + } + }, + "required": [ + "host", + "scope", + "status" + ], + "title": "HostCoverageV8", + "type": "object" + }, + "HostGrantsBaselineV9": { + "additionalProperties": false, + "properties": { + "host_grants_schema_version": { + "const": "0.9", + "default": "0.9", + "title": "Host Grants Schema Version", + "type": "string" + }, + "inventory": { + "$ref": "#/$defs/HostGrantsNormalizedSnapshotV9" + }, + "inventory_sha256": { + "title": "Inventory Sha256", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + } + }, + "required": [ + "scope", + "inventory_sha256", + "inventory" + ], + "title": "HostGrantsBaselineV9", + "type": "object" + }, + "HostGrantsNormalizedSnapshotV9": { + "additionalProperties": false, + "properties": { + "artifacts": { + "items": { + "$ref": "#/$defs/HostArtifactV9" + }, + "title": "Artifacts", + "type": "array" + }, + "grants": { + "items": { + "discriminator": { + "mapping": { + "additional_path": "#/$defs/HostAdditionalPathGrantV2", + "hook": "#/$defs/HostHookComparisonV7", + "instruction_trust_root": "#/$defs/HostInstructionGrantV2", + "mcp_server": "#/$defs/HostMcpServerGrantV2", + "openshell_policy": "#/$defs/HostOpenShellPolicyGrantV9", + "permission_mode": "#/$defs/HostPermissionModeGrantV2", + "permission_rule": "#/$defs/HostPermissionRuleGrantV2", + "plugin_or_app": "#/$defs/HostPluginGrantV2", + "profile": "#/$defs/HostProfileGrantV2", + "requirement": "#/$defs/HostRequirementGrantV2", + "sandbox": "#/$defs/HostSandboxGrantV2", + "workflow": "#/$defs/HostWorkflowGrantV7" + }, + "propertyName": "kind" + }, + "oneOf": [ + { + "$ref": "#/$defs/HostMcpServerGrantV2" + }, + { + "$ref": "#/$defs/HostPermissionRuleGrantV2" + }, + { + "$ref": "#/$defs/HostPermissionModeGrantV2" + }, + { + "$ref": "#/$defs/HostHookComparisonV7" + }, + { + "$ref": "#/$defs/HostSandboxGrantV2" + }, + { + "$ref": "#/$defs/HostAdditionalPathGrantV2" + }, + { + "$ref": "#/$defs/HostPluginGrantV2" + }, + { + "$ref": "#/$defs/HostProfileGrantV2" + }, + { + "$ref": "#/$defs/HostRequirementGrantV2" + }, + { + "$ref": "#/$defs/HostWorkflowGrantV7" + }, + { + "$ref": "#/$defs/HostInstructionGrantV2" + }, + { + "$ref": "#/$defs/HostOpenShellPolicyGrantV9" + } + ] + }, + "title": "Grants", + "type": "array" + }, + "host_coverage": { + "items": { + "$ref": "#/$defs/HostCoverageV8" + }, + "title": "Host Coverage", + "type": "array" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + } + }, + "required": [ + "scope" + ], + "title": "HostGrantsNormalizedSnapshotV9", + "type": "object" + }, + "HostHookComparisonV7": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "event": { + "title": "Event", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "hook", + "default": "hook", + "title": "Kind", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "script_inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/HostHookScriptInputV7" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Script Inputs" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "event" + ], + "title": "HostHookComparisonV7", + "type": "object" + }, + "HostHookScriptInputV7": { + "additionalProperties": false, + "description": "A direct executable reference and its byte reading, never script semantics.", + "properties": { + "basis": { + "anyOf": [ + { + "enum": [ + "project_root_placeholder", + "plugin_root_placeholder", + "absolute_workspace_path" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Basis" + }, + "handler": { + "minimum": 0, + "title": "Handler", + "type": "integer" + }, + "limit": { + "anyOf": [ + { + "enum": [ + "unsupported_host", + "not_command_handler", + "unsupported_command_shape", + "platform_command_override", + "unsupported_shell", + "unsupported_exec_form", + "unsupported_shell_command", + "dynamic_command_argument", + "unexpanded_path_placeholder", + "unsupported_path_placeholder", + "plugin_root_not_established", + "unsupported_or_escaping_path", + "dynamic_or_conditional_path", + "working_directory_not_established", + "interpreter_wrapper", + "path_lookup", + "external_executable", + "unsupported_hook_shape", + "handler_bound_exceeded", + "hook_selection_not_established", + "redacted_dependency_path", + "escaping_path", + "missing_input", + "symlink_input", + "non_regular_input", + "oversized_input", + "unsafe_or_unreadable_input", + "unreadable_input" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Limit" + }, + "path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Path" + }, + "sha256": { + "anyOf": [ + { + "pattern": "^[0-9a-f]{64}$", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sha256" + }, + "size_bytes": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Size Bytes" + } + }, + "required": [ + "handler" + ], + "title": "HostHookScriptInputV7", + "type": "object" + }, + "HostInstructionGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "instruction_trust_root", + "default": "instruction_trust_root", + "title": "Kind", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "path" + ], + "title": "HostInstructionGrantV2", + "type": "object" + }, + "HostMcpServerGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "endpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Endpoint" + }, + "env_keys": { + "items": { + "type": "string" + }, + "title": "Env Keys", + "type": "array" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "header_keys": { + "items": { + "type": "string" + }, + "title": "Header Keys", + "type": "array" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "mcp_server", + "default": "mcp_server", + "title": "Kind", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "server": { + "title": "Server", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "transport": { + "title": "Transport", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "server", + "transport" + ], + "title": "HostMcpServerGrantV2", + "type": "object" + }, + "HostOpenShellPolicyGrantV9": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "facts": { + "anyOf": [ + { + "$ref": "#/$defs/OpenShellPolicyFacts" + }, + { + "$ref": "#/$defs/OpenShellComposedPolicyFacts" + }, + { + "$ref": "#/$defs/OpenShellSnapshotFactsV2" + } + ], + "title": "Facts" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "const": "openshell", + "default": "openshell", + "title": "Host", + "type": "string" + }, + "kind": { + "const": "openshell_policy", + "default": "openshell_policy", + "title": "Kind", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "grant_id", + "scope", + "source", + "config_sha256", + "access", + "risk", + "facts" + ], + "title": "HostOpenShellPolicyGrantV9", + "type": "object" + }, + "HostPermissionModeGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "permission_mode", + "default": "permission_mode", + "title": "Kind", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "setting": { + "title": "Setting", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "value": { + "title": "Value", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "setting", + "value" + ], + "title": "HostPermissionModeGrantV2", + "type": "object" + }, + "HostPermissionRuleGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "disposition": { + "enum": [ + "allow", + "ask", + "deny" + ], + "title": "Disposition", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "permission_rule", + "default": "permission_rule", + "title": "Kind", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "rule": { + "title": "Rule", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "wildcard": { + "default": false, + "title": "Wildcard", + "type": "boolean" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "disposition", + "rule" + ], + "title": "HostPermissionRuleGrantV2", + "type": "object" + }, + "HostPluginGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "enabled": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Enabled" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "plugin_or_app", + "default": "plugin_or_app", + "title": "Kind", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "name" + ], + "title": "HostPluginGrantV2", + "type": "object" + }, + "HostProfileGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "profile", + "default": "profile", + "title": "Kind", + "type": "string" + }, + "profile": { + "title": "Profile", + "type": "string" + }, + "resolved": { + "title": "Resolved", + "type": "boolean" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "profile", + "resolved" + ], + "title": "HostProfileGrantV2", + "type": "object" + }, + "HostRequirementGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "requirement", + "default": "requirement", + "title": "Kind", + "type": "string" + }, + "requirement": { + "title": "Requirement", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "value": { + "title": "Value", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "requirement", + "value" + ], + "title": "HostRequirementGrantV2", + "type": "object" + }, + "HostReusableWorkflowCallV6": { + "additionalProperties": false, + "properties": { + "job": { + "title": "Job", + "type": "string" + }, + "secret_mappings": { + "items": { + "$ref": "#/$defs/HostReusableWorkflowSecretV6" + }, + "title": "Secret Mappings", + "type": "array" + }, + "secrets_inherit": { + "title": "Secrets Inherit", + "type": "boolean" + }, + "uses": { + "title": "Uses", + "type": "string" + }, + "uses_redacted": { + "default": false, + "title": "Uses Redacted", + "type": "boolean" + } + }, + "required": [ + "job", + "uses", + "secrets_inherit" + ], + "title": "HostReusableWorkflowCallV6", + "type": "object" + }, + "HostReusableWorkflowSecretV6": { + "additionalProperties": false, + "description": "One named secret a job passes to the reusable workflow it calls (#693).\n\n``destination`` is the callee's secret input name as the caller writes it.\n``source`` is ``NAME`` from a whole-value ``${{ secrets.NAME }}``, and\n``form`` is then ``secret``. The name is a reference, never a value: it\ndoes not establish the secret's privilege, whether the caller has it, or\nwhat the called workflow does with it. Anything else is ``unresolved``,\nand none of its value is published or digested: a literal value, any\nother expression, a non-string, or a ``secrets`` that is neither\n``inherit`` nor a mapping (``destination`` is then ``null``). A name the\ncredential redactors rewrite is ``redacted`` and records a blocking\ncoverage issue, because two values that redact alike must never compare as\nunchanged. Every other unresolved mapping records a non-blocking one naming\nits ``job/destination``: only that value is uncompared, so the rest of the\nfile still compares.", + "properties": { + "destination": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Destination" + }, + "form": { + "enum": [ + "secret", + "unresolved" + ], + "title": "Form", + "type": "string" + }, + "source": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Source" + }, + "unresolved_reason": { + "anyOf": [ + { + "enum": [ + "literal_value", + "expression", + "not_a_string", + "redacted", + "secrets_not_a_mapping" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Unresolved Reason" + } + }, + "required": [ + "destination", + "source", + "form" + ], + "title": "HostReusableWorkflowSecretV6", + "type": "object" + }, + "HostSandboxGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "sandbox", + "default": "sandbox", + "title": "Kind", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "setting": { + "title": "Setting", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "value": { + "title": "Value", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "setting", + "value" + ], + "title": "HostSandboxGrantV2", + "type": "object" + }, + "HostWorkflowAgentLaunchV7": { + "additionalProperties": false, + "description": "A step that launches a known coding agent, read as text and never run (#823).\n\n``agent`` is a documented action reference's ``owner/repo`` (the step's\n``uses:`` at any ref; the Claude base action also as the ``base-action``\ndirectory of ``anthropics/claude-code-action``), or a known agent CLI a\n``run:`` launches when the whole ``run:`` is one line of plain words\n(letters, digits and ``_ . / : = , % + -``, separated by spaces or tabs),\nrun by ``bash``, ``sh`` or the runner's default shell, whose program,\nafter any ``NAME=value`` assignments, has the file name ``claude`` and\npasses ``-p``/``--print``, or ``codex`` followed by ``exec`` (``e``).\n``form: read`` lists the documented permission inputs or flags the step\ndeclares in ``settings``, and the documented widening rules they meet in\n``widening_rules``, omitted when none. ``form: unresolved`` is an agent\naction whose ``with:`` is not a mapping (``inputs_not_a_mapping``), with\nno settings, and records a non-blocking coverage issue. Any other\n``run:`` that mentions an agent CLI is not a launch: it is listed in\n``unread_agent_runs``. ``job_secrets`` names the secrets the step's job\nreferences (``${{ secrets.NAME }}``) and the workflow-level ``env``\npasses: context for the row that names this step, never compared.\n``job`` and ``step`` are published labels (#802).", + "properties": { + "agent": { + "enum": [ + "anthropics/claude-code-action", + "anthropics/claude-code-base-action", + "anthropics/claude-code-action/base-action", + "openai/codex-action", + "claude", + "codex" + ], + "title": "Agent", + "type": "string" + }, + "form": { + "enum": [ + "read", + "unresolved" + ], + "title": "Form", + "type": "string" + }, + "job": { + "title": "Job", + "type": "string" + }, + "job_secrets": { + "items": { + "type": "string" + }, + "title": "Job Secrets", + "type": "array" + }, + "settings": { + "items": { + "$ref": "#/$defs/HostWorkflowAgentSettingV7" + }, + "title": "Settings", + "type": "array" + }, + "step": { + "title": "Step", + "type": "string" + }, + "unresolved_reason": { + "anyOf": [ + { + "const": "inputs_not_a_mapping", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Unresolved Reason" + }, + "widening_rules": { + "items": { + "$ref": "#/$defs/HostWorkflowAgentRuleV7" + }, + "title": "Widening Rules", + "type": "array" + } + }, + "required": [ + "job", + "step", + "agent", + "form" + ], + "title": "HostWorkflowAgentLaunchV7", + "type": "object" + }, + "HostWorkflowAgentRuleV7": { + "additionalProperties": false, + "description": "One documented widening rule an agent launch meets, and the setting it was read from (#823).\n\nDecided when the workflow is read, from the declared text, before any of\nit is withheld for publication, so redaction never hides a rule. Only\ntext this reader reads exactly meets one: ``claude_args`` or\n``codex-args`` only when it is a plain list of words (never when it holds\na ``${{ }}`` expression), the entries of a user gate that hold no\nexpression, and a mode or ``settings`` input that holds none. Claude Code\nsettings written as JSON in the ``settings`` input meet\n``bypass_permissions`` when their ``defaultMode`` is\n``bypassPermissions``, read as the settings reader reads it; a path to a\nsettings file is not read. ``setting`` is the input (``claude_args``,\n``allowed_bots``, ``sandbox``, ``permission-profile``, \u2026) or the CLI\nflag's primary spelling. One rule compares as one whatever setting meets\nit, except ``open_gate``, which is one rule per gate input.", + "properties": { + "rule": { + "enum": [ + "bypass_permissions", + "bypass_approvals_and_sandbox", + "danger_full_access", + "unsafe_safety_strategy", + "open_gate" + ], + "title": "Rule", + "type": "string" + }, + "setting": { + "title": "Setting", + "type": "string" + } + }, + "required": [ + "rule", + "setting" + ], + "title": "HostWorkflowAgentRuleV7", + "type": "object" + }, + "HostWorkflowAgentSettingV7": { + "additionalProperties": false, + "description": "One permission input or flag an agent launch declares, compared as text (#823).\n\n``name`` is the documented input (``claude_args``, ``sandbox``, \u2026) or the\nflag's primary spelling (``--allowedTools`` for ``--allowed-tools`` too).\n``value`` is the declared text, stripped, as it may be published; a flag\nthat takes no value has ``null``.\n\n``claude_args`` and ``codex-args`` are read only when they are a plain\nlist of words: letters, digits and ``_ . / : = , % + - ( )``, separated by\nblanks or newlines, with no ``--settings`` or ``--mcp-config`` flag. Every\nparser involved splits such text the same way, so it is published as\nthose words, one space apart. Any other value \u2014 holding a quote, a\n``${{ }}`` expression, ``$``, a backtick, a comment, a shell operator,\nJSON or another character \u2014 is ``unread_arguments``: ``value`` is\n````, a short digest, so an edit to it is still a change\nwhile none of its text is published; no documented widening rule is read\nfrom it; and it records a non-blocking coverage issue naming its\n``job/step`` (#823 review cycle 4). A codex ``--config`` override keeps\nits key; its value is ```` under ``env``, ``headers`` or a\nsecret-named key, as the host readers redact such values, published as\nwritten for ``sandbox_mode``, ``default_permissions``,\n``approval_policy`` and ``model``, and ```` otherwise.\n\nEvery other input is one value. A JSON object (a ``settings`` or\n``mcp_config`` value) publishes its shape and none of its free text: key\nnames, numbers, booleans and ``null``, with each string replaced by\n````, a short digest of what the host readers digest for it,\nso an edit to it is still a change. ``env`` and ``headers`` values,\n``apiKeyHelper`` and every secret-named value are ````, as the\nhost readers redact them. The strings a host reader publishes are kept:\na ``permissions.allow``/``ask``/``deny`` rule and a documented Claude\nCode setting's value such as ``defaultMode``, and an MCP server's command\nname and its URL's scheme and host, each followed by the digest when it\ndrops something the digest reads (a command's arguments, a URL's query).\nSo an MCP server's arguments and a hook's command publish nothing, as\n`.mcp.json` and `.claude/settings.json` do not (#823 review). A\n``settings`` or ``mcp_config`` value that neither starts like a JSON\nobject nor is a plain file path (path characters, and a ``${{ }}``\nexpression only as a plain context reference) is ````, a\ndigest and none of its text (#823 review cycle 5). A URL in\nother text publishes its scheme and host with ```` for its\npath and query (#723). Other text \u2014 a prompt, a flag's value \u2014 is\npublished through the workflow label redaction (#802). A value it\nrewrites is credential-shaped \u2014 a token, but also prose such as \"never\nprint bearer tokens\" \u2014 and is published redacted with\n``unresolved_reason: redacted``: it is compared as published, beside the\nrules read from its declared text, and records a non-blocking coverage\nissue naming its ``job/step``, because an edit inside what is redacted is\nnot reported. A value that is not a string (``not_a_string``), or one\nholding text that starts like JSON and does not parse (``unparsed_json``),\nis ``null`` and records a non-blocking coverage issue naming its\n``job/step``: it is neither published nor compared.\n\n``holds_expression`` is ``true`` when an input other than an argument\ninput holds a ``${{ }}`` expression, which GitHub substitutes before the\naction reads the input, and is omitted otherwise. A documented widening\nrule is then read only from the entries of a user gate that hold none,\nand from no mode or settings input, and a rule the launch gains in the\nsame job afterwards is not claimed, because the substituted text may\nalready have met it.", + "properties": { + "holds_expression": { + "default": false, + "title": "Holds Expression", + "type": "boolean" + }, + "name": { + "title": "Name", + "type": "string" + }, + "unresolved_reason": { + "anyOf": [ + { + "enum": [ + "not_a_string", + "redacted", + "unparsed_json", + "unread_arguments" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Unresolved Reason" + }, + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Value" + } + }, + "required": [ + "name", + "value" + ], + "title": "HostWorkflowAgentSettingV7", + "type": "object" + }, + "HostWorkflowCheckoutRefV7": { + "additionalProperties": false, + "description": "One ``actions/checkout`` step and the ``with.ref`` it declares, as text (#823).\n\n``ref`` is ``null`` when the step declares none, or an empty one: the\ncheckout's default for the triggering event. A ref the label redaction\nrewrites is published redacted with ``unresolved_reason: redacted`` and\nmakes the workflow a blocking limit, as a redacted step reference does\n(#767): a ref names the code the job runs, as a step reference does. A\nvalue that is not a string, or ``with:`` that is not a mapping,\nis ``null`` with ``unresolved_reason`` and records a non-blocking coverage\nissue. The ref is never resolved or fetched.", + "properties": { + "job": { + "title": "Job", + "type": "string" + }, + "ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Ref" + }, + "step": { + "title": "Step", + "type": "string" + }, + "unresolved_reason": { + "anyOf": [ + { + "enum": [ + "not_a_string", + "redacted", + "inputs_not_a_mapping" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Unresolved Reason" + } + }, + "required": [ + "job", + "step", + "ref" + ], + "title": "HostWorkflowCheckoutRefV7", + "type": "object" + }, + "HostWorkflowGrantV7": { + "additionalProperties": false, + "description": "A v0.6 workflow grant plus the agent launches, unread agent steps and checkout refs its steps declare.\n\nEach list is present only when a step declares one. In a v0.7 grant an\nabsent list means the steps were read and declare none; the schema\nversion, not the key, separates that from a legacy grant that never read\nthem. ``unread_agent_runs`` is a named limit and is never compared.\n``access`` and ``risk`` still describe the workflow's token and triggers\nalone.", + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "agent_launches": { + "items": { + "$ref": "#/$defs/HostWorkflowAgentLaunchV7" + }, + "title": "Agent Launches", + "type": "array" + }, + "checkout_refs": { + "items": { + "$ref": "#/$defs/HostWorkflowCheckoutRefV7" + }, + "title": "Checkout Refs", + "type": "array" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "effective_write_scopes": { + "items": { + "type": "string" + }, + "title": "Effective Write Scopes", + "type": "array" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "workflow", + "default": "workflow", + "title": "Kind", + "type": "string" + }, + "permission_contexts": { + "items": { + "$ref": "#/$defs/HostWorkflowPermissionsV4" + }, + "title": "Permission Contexts", + "type": "array" + }, + "pull_request_target": { + "default": false, + "title": "Pull Request Target", + "type": "boolean" + }, + "reusable_calls": { + "items": { + "$ref": "#/$defs/HostReusableWorkflowCallV6" + }, + "title": "Reusable Calls", + "type": "array" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "step_actions": { + "items": { + "$ref": "#/$defs/HostWorkflowStepActionV6" + }, + "title": "Step Actions", + "type": "array" + }, + "triggers": { + "items": { + "type": "string" + }, + "title": "Triggers", + "type": "array" + }, + "unread_agent_runs": { + "items": { + "$ref": "#/$defs/HostWorkflowUnreadAgentRunV7" + }, + "title": "Unread Agent Runs", + "type": "array" + }, + "write_all": { + "default": false, + "title": "Write All", + "type": "boolean" + }, + "write_scopes": { + "items": { + "type": "string" + }, + "title": "Write Scopes", + "type": "array" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "permission_contexts", + "effective_write_scopes", + "reusable_calls" + ], + "title": "HostWorkflowGrantV7", + "type": "object" + }, + "HostWorkflowPermissionsV4": { + "additionalProperties": false, + "properties": { + "job": { + "title": "Job", + "type": "string" + }, + "permissions": { + "additionalProperties": { + "enum": [ + "read", + "write" + ], + "type": "string" + }, + "title": "Permissions", + "type": "object" + }, + "state": { + "enum": [ + "explicit", + "repository_default", + "unresolved" + ], + "title": "State", + "type": "string" + } + }, + "required": [ + "job", + "state", + "permissions" + ], + "title": "HostWorkflowPermissionsV4", + "type": "object" + }, + "HostWorkflowStepActionV6": { + "additionalProperties": false, + "description": "One step's declared action reference, read as text and never fetched.\n\n``form`` is ``remote`` for ``owner/repo[/path]@ref``, ``docker`` for\n``docker://\u2026``, and ``unresolved`` for a value Shipgate does not resolve\nto an action identity; ``unresolved_reason`` then says which. A job whose\n``steps`` is not a list, or a step that is not a mapping, is listed as\nunresolved too, with no ``uses``, so an absent list still means the steps\nwere read and declare nothing. A local\n``./\u2026`` reference is not listed: composite actions remain unread (#701).\n``step`` is the step's ``id``, else its ``name``, else ``steps[N]`` \u2014 the\nevidence a reviewer uses to find it, not part of the comparison. ``job``\nand ``step`` are published labels: credential-shaped text in either, and\nthe userinfo of any ``scheme://\u2026@`` inside it, is redacted (#802).", + "properties": { + "form": { + "enum": [ + "remote", + "docker", + "unresolved" + ], + "title": "Form", + "type": "string" + }, + "job": { + "title": "Job", + "type": "string" + }, + "step": { + "title": "Step", + "type": "string" + }, + "unresolved_reason": { + "anyOf": [ + { + "enum": [ + "expression", + "unsupported_reference", + "not_a_string", + "redacted", + "steps_not_a_list", + "step_not_a_mapping" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Unresolved Reason" + }, + "uses": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Uses" + } + }, + "required": [ + "job", + "step", + "uses", + "form" + ], + "title": "HostWorkflowStepActionV6", + "type": "object" + }, + "HostWorkflowUnreadAgentRunV7": { + "additionalProperties": false, + "description": "A ``run:`` step that mentions a known agent CLI and is not read as an agent launch (#823 review cycle 4).\n\nAny ``run:`` holding ``claude`` or ``codex`` as a word of its own that is\nnot an agent launch this reader reads \u2014 more than one line or command, a\nquote, an expansion, a redirection, a comment, a continuation, a\n``${{ }}`` expression, another program such as ``npx`` or ``timeout``, a\nsubcommand that is not a headless launch, or a declared ``shell:`` other\nthan ``bash`` or ``sh`` run on the script alone (so ``bash -c '\u2026' {0}``\ntoo) \u2014 once for each agent CLI it mentions. It is a\nnamed, non-blocking limit and nothing more: none of the step's text is\npublished, it is never compared, so adding, removing or editing it gives\nno row, and it never says that the step starts, or does not start, an\nagent. ``job`` and ``step`` are published labels (#802).", + "properties": { + "agent": { + "enum": [ + "claude", + "codex" + ], + "title": "Agent", + "type": "string" + }, + "job": { + "title": "Job", + "type": "string" + }, + "step": { + "title": "Step", + "type": "string" + } + }, + "required": [ + "job", + "step", + "agent" + ], + "title": "HostWorkflowUnreadAgentRunV7", + "type": "object" + }, + "InstructionStructureEvidence": { + "additionalProperties": false, + "properties": { + "profile": { + "title": "Profile", + "type": "string" + }, + "reason": { + "title": "Reason", + "type": "string" + }, + "sha256": { + "anyOf": [ + { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sha256" + }, + "status": { + "enum": [ + "guidance", + "structured", + "unresolved" + ], + "title": "Status", + "type": "string" + } + }, + "required": [ + "profile", + "status", + "reason" + ], + "title": "InstructionStructureEvidence", + "type": "object" + }, + "OpenShellAllowRule": { + "additionalProperties": false, + "properties": { + "allow": { + "$ref": "#/$defs/OpenShellRequestMatcher" + } + }, + "required": [ + "allow" + ], + "title": "OpenShellAllowRule", + "type": "object" + }, + "OpenShellAnyMatcher": { + "additionalProperties": false, + "properties": { + "any": { + "items": { + "type": "string" + }, + "minItems": 1, + "title": "Any", + "type": "array" + } + }, + "required": [ + "any" + ], + "title": "OpenShellAnyMatcher", + "type": "object" + }, + "OpenShellBinary": { + "additionalProperties": false, + "properties": { + "path": { + "title": "Path", + "type": "string" + } + }, + "required": [ + "path" + ], + "title": "OpenShellBinary", + "type": "object" + }, + "OpenShellComposedPolicyFacts": { + "additionalProperties": false, + "properties": { + "composition": { + "$ref": "#/$defs/CompositionProvenance" + }, + "credential_use": { + "items": { + "$ref": "#/$defs/CredentialUseFacts" + }, + "title": "Credential Use", + "type": "array" + }, + "defaulted_fields": { + "items": { + "type": "string" + }, + "title": "Defaulted Fields", + "type": "array" + }, + "field_paths": { + "items": { + "type": "string" + }, + "title": "Field Paths", + "type": "array" + }, + "filesystem_baseline": { + "const": "runtime_dependent_not_resolved", + "default": "runtime_dependent_not_resolved", + "title": "Filesystem Baseline", + "type": "string" + }, + "include_workdir_when_filesystem_omitted": { + "const": true, + "default": true, + "title": "Include Workdir When Filesystem Omitted", + "type": "boolean" + }, + "landlock_when_omitted": { + "const": "best_effort", + "default": "best_effort", + "title": "Landlock When Omitted", + "type": "string" + }, + "policy": { + "$ref": "#/$defs/OpenShellPolicy" + }, + "policy_schema_version": { + "const": 1, + "default": 1, + "title": "Policy Schema Version", + "type": "integer" + }, + "process_omission": { + "const": "driver_default", + "default": "driver_default", + "title": "Process Omission", + "type": "string" + }, + "registration": { + "title": "Registration", + "type": "string" + }, + "role": { + "const": "composed", + "default": "composed", + "title": "Role", + "type": "string" + }, + "runtime_freshness_verified": { + "const": false, + "default": false, + "title": "Runtime Freshness Verified", + "type": "boolean" + }, + "runtime_version": { + "const": "0.1.2", + "title": "Runtime Version", + "type": "string" + } + }, + "required": [ + "registration", + "runtime_version", + "policy", + "composition", + "credential_use" + ], + "title": "OpenShellComposedPolicyFacts", + "type": "object" + }, + "OpenShellCredentialBinding": { + "additionalProperties": false, + "properties": { + "provider": { + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenShellCredentialBinding", + "type": "object" + }, + "OpenShellEndpoint": { + "additionalProperties": false, + "properties": { + "access": { + "default": "", + "enum": [ + "", + "read-only", + "read-write", + "full" + ], + "title": "Access", + "type": "string" + }, + "allow_encoded_slash": { + "default": false, + "title": "Allow Encoded Slash", + "type": "boolean" + }, + "allow_uninspected_credentials": { + "default": false, + "title": "Allow Uninspected Credentials", + "type": "boolean" + }, + "allowed_ips": { + "items": { + "type": "string" + }, + "title": "Allowed Ips", + "type": "array" + }, + "credential_binding": { + "anyOf": [ + { + "$ref": "#/$defs/OpenShellCredentialBinding" + }, + { + "type": "null" + } + ], + "default": null + }, + "credential_signing": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Credential Signing" + }, + "deny_rules": { + "items": { + "$ref": "#/$defs/OpenShellRequestMatcher" + }, + "title": "Deny Rules", + "type": "array" + }, + "enforcement": { + "default": "audit", + "enum": [ + "audit", + "enforce", + "" + ], + "title": "Enforcement", + "type": "string" + }, + "graphql_max_body_bytes": { + "default": 65536, + "maximum": 4294967295, + "minimum": 0, + "title": "Graphql Max Body Bytes", + "type": "integer" + }, + "graphql_persisted_queries": { + "additionalProperties": { + "$ref": "#/$defs/OpenShellGraphqlOperation" + }, + "title": "Graphql Persisted Queries", + "type": "object" + }, + "host": { + "default": "", + "title": "Host", + "type": "string" + }, + "json_rpc": { + "anyOf": [ + { + "$ref": "#/$defs/OpenShellJsonRpcOptions" + }, + { + "type": "null" + } + ], + "default": null + }, + "mcp": { + "anyOf": [ + { + "$ref": "#/$defs/OpenShellMcpOptions" + }, + { + "type": "null" + } + ], + "default": null + }, + "path": { + "default": "", + "title": "Path", + "type": "string" + }, + "persisted_queries": { + "default": "deny", + "enum": [ + "", + "deny", + "allow_registered" + ], + "title": "Persisted Queries", + "type": "string" + }, + "port": { + "default": 0, + "maximum": 65535, + "minimum": 0, + "title": "Port", + "type": "integer" + }, + "ports": { + "items": { + "type": "integer" + }, + "title": "Ports", + "type": "array" + }, + "protocol": { + "default": "", + "enum": [ + "", + "rest", + "websocket", + "graphql", + "mcp", + "json-rpc", + "tcp" + ], + "title": "Protocol", + "type": "string" + }, + "request_body_credential_rewrite": { + "default": false, + "title": "Request Body Credential Rewrite", + "type": "boolean" + }, + "rules": { + "items": { + "$ref": "#/$defs/OpenShellAllowRule" + }, + "title": "Rules", + "type": "array" + }, + "signing_region": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Signing Region" + }, + "signing_service": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Signing Service" + }, + "tls": { + "default": "", + "enum": [ + "", + "skip" + ], + "title": "Tls", + "type": "string" + }, + "websocket_credential_rewrite": { + "default": false, + "title": "Websocket Credential Rewrite", + "type": "boolean" + } + }, + "title": "OpenShellEndpoint", + "type": "object" + }, + "OpenShellFilesystem": { + "additionalProperties": false, + "properties": { + "include_workdir": { + "default": false, + "title": "Include Workdir", + "type": "boolean" + }, + "read_only": { + "items": { + "type": "string" + }, + "title": "Read Only", + "type": "array" + }, + "read_write": { + "items": { + "type": "string" + }, + "title": "Read Write", + "type": "array" + } + }, + "title": "OpenShellFilesystem", + "type": "object" + }, + "OpenShellGraphqlOperation": { + "additionalProperties": false, + "properties": { + "fields": { + "items": { + "type": "string" + }, + "title": "Fields", + "type": "array" + }, + "operation_name": { + "default": "", + "title": "Operation Name", + "type": "string" + }, + "operation_type": { + "default": "", + "title": "Operation Type", + "type": "string" + } + }, + "title": "OpenShellGraphqlOperation", + "type": "object" + }, + "OpenShellJsonRpcOptions": { + "additionalProperties": false, + "properties": { + "max_body_bytes": { + "default": 65536, + "maximum": 4294967295, + "minimum": 0, + "title": "Max Body Bytes", + "type": "integer" + } + }, + "title": "OpenShellJsonRpcOptions", + "type": "object" + }, + "OpenShellLandlock": { + "additionalProperties": false, + "properties": { + "compatibility": { + "default": "best_effort", + "enum": [ + "best_effort", + "hard_requirement" + ], + "title": "Compatibility", + "type": "string" + } + }, + "title": "OpenShellLandlock", + "type": "object" + }, + "OpenShellMcpOptions": { + "additionalProperties": false, + "properties": { + "allow_all_known_mcp_methods": { + "default": false, + "title": "Allow All Known Mcp Methods", + "type": "boolean" + }, + "max_body_bytes": { + "default": 65536, + "maximum": 4294967295, + "minimum": 0, + "title": "Max Body Bytes", + "type": "integer" + }, + "strict_tool_names": { + "default": true, + "title": "Strict Tool Names", + "type": "boolean" + }, + "versions": { + "items": { + "type": "string" + }, + "title": "Versions", + "type": "array" + } + }, + "title": "OpenShellMcpOptions", + "type": "object" + }, + "OpenShellNetworkRule": { + "additionalProperties": false, + "properties": { + "binaries": { + "items": { + "$ref": "#/$defs/OpenShellBinary" + }, + "title": "Binaries", + "type": "array" + }, + "endpoints": { + "items": { + "$ref": "#/$defs/OpenShellEndpoint" + }, + "title": "Endpoints", + "type": "array" + }, + "name": { + "default": "", + "title": "Name", + "type": "string" + } + }, + "title": "OpenShellNetworkRule", + "type": "object" + }, + "OpenShellPolicy": { + "additionalProperties": false, + "properties": { + "filesystem_policy": { + "anyOf": [ + { + "$ref": "#/$defs/OpenShellFilesystem" + }, + { + "type": "null" + } + ], + "default": null + }, + "landlock": { + "anyOf": [ + { + "$ref": "#/$defs/OpenShellLandlock" + }, + { + "type": "null" + } + ], + "default": null + }, + "network_middlewares": { + "additionalProperties": true, + "title": "Network Middlewares", + "type": "object" + }, + "network_policies": { + "additionalProperties": { + "$ref": "#/$defs/OpenShellNetworkRule" + }, + "title": "Network Policies", + "type": "object" + }, + "process": { + "anyOf": [ + { + "$ref": "#/$defs/OpenShellProcess" + }, + { + "type": "null" + } + ], + "default": null + }, + "version": { + "const": 1, + "title": "Version", + "type": "integer" + } + }, + "required": [ + "version" + ], + "title": "OpenShellPolicy", + "type": "object" + }, + "OpenShellPolicyFacts": { + "additionalProperties": false, + "properties": { + "defaulted_fields": { + "items": { + "type": "string" + }, + "title": "Defaulted Fields", + "type": "array" + }, + "field_paths": { + "items": { + "type": "string" + }, + "title": "Field Paths", + "type": "array" + }, + "filesystem_baseline": { + "const": "runtime_dependent_not_resolved", + "default": "runtime_dependent_not_resolved", + "title": "Filesystem Baseline", + "type": "string" + }, + "include_workdir_when_filesystem_omitted": { + "const": true, + "default": true, + "title": "Include Workdir When Filesystem Omitted", + "type": "boolean" + }, + "landlock_when_omitted": { + "const": "best_effort", + "default": "best_effort", + "title": "Landlock When Omitted", + "type": "string" + }, + "policy": { + "$ref": "#/$defs/OpenShellPolicy" + }, + "policy_schema_version": { + "const": 1, + "default": 1, + "title": "Policy Schema Version", + "type": "integer" + }, + "process_omission": { + "const": "driver_default", + "default": "driver_default", + "title": "Process Omission", + "type": "string" + }, + "registration": { + "title": "Registration", + "type": "string" + }, + "role": { + "enum": [ + "authored", + "effective_snapshot" + ], + "title": "Role", + "type": "string" + }, + "runtime_freshness_verified": { + "const": false, + "default": false, + "title": "Runtime Freshness Verified", + "type": "boolean" + }, + "runtime_version": { + "const": "0.1.2", + "title": "Runtime Version", + "type": "string" + } + }, + "required": [ + "registration", + "role", + "runtime_version", + "policy" + ], + "title": "OpenShellPolicyFacts", + "type": "object" + }, + "OpenShellProcess": { + "additionalProperties": false, + "properties": { + "run_as_group": { + "default": "", + "title": "Run As Group", + "type": "string" + }, + "run_as_user": { + "default": "", + "title": "Run As User", + "type": "string" + } + }, + "title": "OpenShellProcess", + "type": "object" + }, + "OpenShellRequestMatcher": { + "additionalProperties": false, + "properties": { + "command": { + "default": "", + "title": "Command", + "type": "string" + }, + "fields": { + "items": { + "type": "string" + }, + "title": "Fields", + "type": "array" + }, + "method": { + "default": "", + "title": "Method", + "type": "string" + }, + "operation_name": { + "default": "", + "title": "Operation Name", + "type": "string" + }, + "operation_type": { + "default": "", + "title": "Operation Type", + "type": "string" + }, + "params": { + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/OpenShellAnyMatcher" + } + ] + }, + "title": "Params", + "type": "object" + }, + "path": { + "default": "", + "title": "Path", + "type": "string" + }, + "query": { + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/OpenShellAnyMatcher" + } + ] + }, + "title": "Query", + "type": "object" + }, + "tool": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/OpenShellAnyMatcher" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Tool" + } + }, + "title": "OpenShellRequestMatcher", + "type": "object" + }, + "OpenShellSnapshotFactsV2": { + "additionalProperties": false, + "properties": { + "creation_bound_fields": { + "items": { + "type": "string" + }, + "title": "Creation Bound Fields", + "type": "array" + }, + "defaulted_fields": { + "items": { + "type": "string" + }, + "title": "Defaulted Fields", + "type": "array" + }, + "dynamic_fields": { + "items": { + "type": "string" + }, + "title": "Dynamic Fields", + "type": "array" + }, + "field_paths": { + "items": { + "type": "string" + }, + "title": "Field Paths", + "type": "array" + }, + "filesystem_baseline": { + "const": "runtime_dependent_not_resolved", + "default": "runtime_dependent_not_resolved", + "title": "Filesystem Baseline", + "type": "string" + }, + "include_workdir_when_filesystem_omitted": { + "const": true, + "default": true, + "title": "Include Workdir When Filesystem Omitted", + "type": "boolean" + }, + "landlock_when_omitted": { + "const": "best_effort", + "default": "best_effort", + "title": "Landlock When Omitted", + "type": "string" + }, + "policy": { + "$ref": "#/$defs/OpenShellPolicy" + }, + "policy_schema_version": { + "const": 1, + "default": 1, + "title": "Policy Schema Version", + "type": "integer" + }, + "process_omission": { + "const": "driver_default", + "default": "driver_default", + "title": "Process Omission", + "type": "string" + }, + "registration": { + "title": "Registration", + "type": "string" + }, + "role": { + "const": "effective_snapshot", + "default": "effective_snapshot", + "title": "Role", + "type": "string" + }, + "runtime_freshness_verified": { + "const": false, + "default": false, + "title": "Runtime Freshness Verified", + "type": "boolean" + }, + "runtime_version": { + "const": "0.1.2", + "title": "Runtime Version", + "type": "string" + }, + "snapshot": { + "$ref": "#/$defs/SnapshotMetadata" + }, + "startup_bound_fields": { + "items": { + "type": "string" + }, + "title": "Startup Bound Fields", + "type": "array" + } + }, + "required": [ + "registration", + "runtime_version", + "policy", + "snapshot" + ], + "title": "OpenShellSnapshotFactsV2", + "type": "object" + }, + "SnapshotMetadata": { + "additionalProperties": false, + "properties": { + "revision": { + "default": "", + "maxLength": 256, + "title": "Revision", + "type": "string" + }, + "source": { + "default": "", + "maxLength": 1024, + "title": "Source", + "type": "string" + } + }, + "title": "SnapshotMetadata", + "type": "object" + }, + "StaticCredential": { + "additionalProperties": false, + "properties": { + "auth_style": { + "default": "", + "enum": [ + "", + "basic", + "bearer", + "header", + "query", + "path" + ], + "title": "Auth Style", + "type": "string" + }, + "description": { + "default": "", + "title": "Description", + "type": "string" + }, + "env_vars": { + "items": { + "type": "string" + }, + "maxItems": 32, + "title": "Env Vars", + "type": "array" + }, + "header_name": { + "default": "", + "title": "Header Name", + "type": "string" + }, + "name": { + "maxLength": 128, + "minLength": 1, + "title": "Name", + "type": "string" + }, + "path_template": { + "default": "", + "title": "Path Template", + "type": "string" + }, + "query_param": { + "default": "", + "title": "Query Param", + "type": "string" + }, + "required": { + "default": false, + "title": "Required", + "type": "boolean" + } + }, + "required": [ + "name" + ], + "title": "StaticCredential", + "type": "object" + } + }, + "$id": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/host-grants-baseline-schema.v0.9.json", + "$ref": "#/$defs/HostGrantsBaselineV9", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "JSON Schema for a human-acknowledged, scope-bound host-grants baseline.", + "title": "Agents Shipgate Host Grants Baseline v0.9" +} diff --git a/docs/host-grants-drift-schema.v0.9.json b/docs/host-grants-drift-schema.v0.9.json new file mode 100644 index 00000000..94ae4b82 --- /dev/null +++ b/docs/host-grants-drift-schema.v0.9.json @@ -0,0 +1,456 @@ +{ + "$defs": { + "HostArtifactChangeV9": { + "additionalProperties": false, + "properties": { + "artifact_id": { + "title": "Artifact Id", + "type": "string" + }, + "baseline": { + "anyOf": [ + { + "$ref": "#/$defs/HostArtifactV9" + }, + { + "type": "null" + } + ], + "default": null + }, + "current": { + "anyOf": [ + { + "$ref": "#/$defs/HostArtifactV9" + }, + { + "type": "null" + } + ], + "default": null + } + }, + "required": [ + "artifact_id" + ], + "title": "HostArtifactChangeV9", + "type": "object" + }, + "HostArtifactV9": { + "additionalProperties": false, + "properties": { + "artifact_id": { + "title": "Artifact Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github", + "openshell" + ], + "title": "Host", + "type": "string" + }, + "instruction_structure": { + "anyOf": [ + { + "$ref": "#/$defs/InstructionStructureEvidence" + }, + { + "type": "null" + } + ], + "default": null + }, + "kind": { + "enum": [ + "config", + "mcp", + "hooks", + "workflow", + "instructions", + "requirements", + "hook_script", + "openshell_selection", + "openshell_policy", + "openshell_profile" + ], + "title": "Kind", + "type": "string" + }, + "parse_status": { + "enum": [ + "parsed", + "failed", + "unsupported" + ], + "title": "Parse Status", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "redacted_sha256": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Redacted Sha256" + }, + "resolved_through": { + "items": { + "type": "string" + }, + "title": "Resolved Through", + "type": "array" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + } + }, + "required": [ + "artifact_id", + "host", + "scope", + "path", + "kind", + "parse_status" + ], + "title": "HostArtifactV9", + "type": "object" + }, + "HostCoverageChangeV8": { + "additionalProperties": false, + "properties": { + "baseline": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline" + }, + "current": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Current" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github", + "openshell" + ], + "title": "Host", + "type": "string" + } + }, + "required": [ + "host" + ], + "title": "HostCoverageChangeV8", + "type": "object" + }, + "HostGrantChangeV2": { + "additionalProperties": false, + "properties": { + "baseline": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline" + }, + "current": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Current" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + } + }, + "required": [ + "grant_id" + ], + "title": "HostGrantChangeV2", + "type": "object" + }, + "HostGrantsDriftV9": { + "additionalProperties": false, + "properties": { + "artifact_changes": { + "items": { + "$ref": "#/$defs/HostArtifactChangeV9" + }, + "title": "Artifact Changes", + "type": "array" + }, + "baseline_file": { + "title": "Baseline File", + "type": "string" + }, + "baseline_sha256": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Baseline Sha256" + }, + "changes": { + "items": { + "$ref": "#/$defs/HostGrantChangeV2" + }, + "title": "Changes", + "type": "array" + }, + "comparison_status": { + "enum": [ + "comparable", + "incomparable" + ], + "title": "Comparison Status", + "type": "string" + }, + "coverage_changes": { + "items": { + "$ref": "#/$defs/HostCoverageChangeV8" + }, + "title": "Coverage Changes", + "type": "array" + }, + "current_sha256": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Current Sha256" + }, + "expansion_signals": { + "items": { + "type": "string" + }, + "title": "Expansion Signals", + "type": "array" + }, + "has_drift": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Has Drift" + }, + "host_grants_schema_version": { + "const": "0.9", + "default": "0.9", + "title": "Host Grants Schema Version", + "type": "string" + }, + "incomparable_reasons": { + "items": { + "type": "string" + }, + "title": "Incomparable Reasons", + "type": "array" + }, + "issues": { + "items": { + "$ref": "#/$defs/HostInventoryIssueV8" + }, + "title": "Issues", + "type": "array" + }, + "next_action": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Next Action" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + } + }, + "required": [ + "baseline_file", + "scope", + "comparison_status", + "has_drift" + ], + "title": "HostGrantsDriftV9", + "type": "object" + }, + "HostInventoryIssueV8": { + "additionalProperties": false, + "properties": { + "blocking": { + "title": "Blocking", + "type": "boolean" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github", + "openshell" + ], + "title": "Host", + "type": "string" + }, + "issue_id": { + "title": "Issue Id", + "type": "string" + }, + "kind": { + "enum": [ + "parse_failed", + "unreadable", + "unsupported", + "unresolved_precedence", + "dynamic_source_excluded", + "remote_source_excluded" + ], + "title": "Kind", + "type": "string" + }, + "message": { + "title": "Message", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "issue_id", + "kind", + "host", + "source", + "message", + "blocking" + ], + "title": "HostInventoryIssueV8", + "type": "object" + }, + "InstructionStructureEvidence": { + "additionalProperties": false, + "properties": { + "profile": { + "title": "Profile", + "type": "string" + }, + "reason": { + "title": "Reason", + "type": "string" + }, + "sha256": { + "anyOf": [ + { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sha256" + }, + "status": { + "enum": [ + "guidance", + "structured", + "unresolved" + ], + "title": "Status", + "type": "string" + } + }, + "required": [ + "profile", + "status", + "reason" + ], + "title": "InstructionStructureEvidence", + "type": "object" + } + }, + "$id": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/host-grants-drift-schema.v0.9.json", + "$ref": "#/$defs/HostGrantsDriftV9", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "JSON Schema for scope-aware host-grant drift and incomparability.", + "title": "Agents Shipgate Host Grants Drift v0.9" +} diff --git a/docs/host-grants-inventory-schema.v0.9.json b/docs/host-grants-inventory-schema.v0.9.json new file mode 100644 index 00000000..9e026ae5 --- /dev/null +++ b/docs/host-grants-inventory-schema.v0.9.json @@ -0,0 +1,3456 @@ +{ + "$defs": { + "CompositionContributor": { + "additionalProperties": false, + "properties": { + "content_sha256": { + "pattern": "^sha256:[a-f0-9]{64}$", + "title": "Content Sha256", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "profile_id": { + "default": "", + "title": "Profile Id", + "type": "string" + }, + "provider": { + "default": "", + "title": "Provider", + "type": "string" + }, + "resource_version": { + "default": 0, + "title": "Resource Version", + "type": "integer" + }, + "role": { + "enum": [ + "global", + "saved", + "image", + "profile" + ], + "title": "Role", + "type": "string" + }, + "rule_key": { + "default": "", + "title": "Rule Key", + "type": "string" + }, + "scope": { + "default": "", + "enum": [ + "", + "platform", + "workspace", + "interceptor" + ], + "title": "Scope", + "type": "string" + }, + "selected": { + "title": "Selected", + "type": "boolean" + }, + "workspace": { + "default": "", + "title": "Workspace", + "type": "string" + } + }, + "required": [ + "path", + "role", + "content_sha256", + "selected" + ], + "title": "CompositionContributor", + "type": "object" + }, + "CompositionProvenance": { + "additionalProperties": false, + "properties": { + "contributors": { + "items": { + "$ref": "#/$defs/CompositionContributor" + }, + "title": "Contributors", + "type": "array" + }, + "creation_bound_fields": { + "items": { + "type": "string" + }, + "title": "Creation Bound Fields", + "type": "array" + }, + "dynamic_fields": { + "items": { + "type": "string" + }, + "title": "Dynamic Fields", + "type": "array" + }, + "name": { + "title": "Name", + "type": "string" + }, + "selected_policy": { + "enum": [ + "global", + "saved", + "image" + ], + "title": "Selected Policy", + "type": "string" + }, + "startup_bound_fields": { + "items": { + "type": "string" + }, + "title": "Startup Bound Fields", + "type": "array" + }, + "workspace": { + "title": "Workspace", + "type": "string" + } + }, + "required": [ + "name", + "workspace", + "selected_policy", + "contributors" + ], + "title": "CompositionProvenance", + "type": "object" + }, + "CredentialEndpointFacts": { + "additionalProperties": false, + "properties": { + "allow_uninspected_credentials": { + "title": "Allow Uninspected Credentials", + "type": "boolean" + }, + "host": { + "title": "Host", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "port": { + "title": "Port", + "type": "integer" + }, + "ports": { + "items": { + "type": "integer" + }, + "title": "Ports", + "type": "array" + }, + "request_body_credential_rewrite": { + "title": "Request Body Credential Rewrite", + "type": "boolean" + }, + "tls": { + "title": "Tls", + "type": "string" + }, + "websocket_credential_rewrite": { + "title": "Websocket Credential Rewrite", + "type": "boolean" + } + }, + "required": [ + "host", + "port", + "ports", + "path", + "tls", + "allow_uninspected_credentials", + "websocket_credential_rewrite", + "request_body_credential_rewrite" + ], + "title": "CredentialEndpointFacts", + "type": "object" + }, + "CredentialUseFacts": { + "additionalProperties": false, + "properties": { + "credential_values_read": { + "const": false, + "default": false, + "title": "Credential Values Read", + "type": "boolean" + }, + "credentials": { + "items": { + "$ref": "#/$defs/StaticCredential" + }, + "title": "Credentials", + "type": "array" + }, + "endpoints": { + "items": { + "$ref": "#/$defs/CredentialEndpointFacts" + }, + "title": "Endpoints", + "type": "array" + }, + "profile_id": { + "title": "Profile Id", + "type": "string" + }, + "provider": { + "title": "Provider", + "type": "string" + }, + "runtime_authorization_verified": { + "const": false, + "default": false, + "title": "Runtime Authorization Verified", + "type": "boolean" + }, + "scope": { + "enum": [ + "platform", + "workspace", + "interceptor" + ], + "title": "Scope", + "type": "string" + }, + "workspace": { + "title": "Workspace", + "type": "string" + } + }, + "required": [ + "provider", + "profile_id", + "scope", + "workspace", + "credentials", + "endpoints" + ], + "title": "CredentialUseFacts", + "type": "object" + }, + "HostAdditionalPathGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "additional_path", + "default": "additional_path", + "title": "Kind", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "path" + ], + "title": "HostAdditionalPathGrantV2", + "type": "object" + }, + "HostArtifactV9": { + "additionalProperties": false, + "properties": { + "artifact_id": { + "title": "Artifact Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github", + "openshell" + ], + "title": "Host", + "type": "string" + }, + "instruction_structure": { + "anyOf": [ + { + "$ref": "#/$defs/InstructionStructureEvidence" + }, + { + "type": "null" + } + ], + "default": null + }, + "kind": { + "enum": [ + "config", + "mcp", + "hooks", + "workflow", + "instructions", + "requirements", + "hook_script", + "openshell_selection", + "openshell_policy", + "openshell_profile" + ], + "title": "Kind", + "type": "string" + }, + "parse_status": { + "enum": [ + "parsed", + "failed", + "unsupported" + ], + "title": "Parse Status", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "redacted_sha256": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Redacted Sha256" + }, + "resolved_through": { + "items": { + "type": "string" + }, + "title": "Resolved Through", + "type": "array" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + } + }, + "required": [ + "artifact_id", + "host", + "scope", + "path", + "kind", + "parse_status" + ], + "title": "HostArtifactV9", + "type": "object" + }, + "HostCoverageV8": { + "additionalProperties": false, + "properties": { + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github", + "openshell" + ], + "title": "Host", + "type": "string" + }, + "issue_ids": { + "items": { + "type": "string" + }, + "title": "Issue Ids", + "type": "array" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "sources_expected": { + "items": { + "type": "string" + }, + "title": "Sources Expected", + "type": "array" + }, + "sources_observed": { + "items": { + "type": "string" + }, + "title": "Sources Observed", + "type": "array" + }, + "status": { + "enum": [ + "complete", + "partial", + "experimental" + ], + "title": "Status", + "type": "string" + } + }, + "required": [ + "host", + "scope", + "status" + ], + "title": "HostCoverageV8", + "type": "object" + }, + "HostGrantsInventoryV9": { + "additionalProperties": false, + "properties": { + "artifacts": { + "items": { + "$ref": "#/$defs/HostArtifactV9" + }, + "title": "Artifacts", + "type": "array" + }, + "excluded_scopes": { + "items": { + "type": "string" + }, + "title": "Excluded Scopes", + "type": "array" + }, + "grants": { + "items": { + "discriminator": { + "mapping": { + "additional_path": "#/$defs/HostAdditionalPathGrantV2", + "hook": "#/$defs/HostHookGrantV7", + "instruction_trust_root": "#/$defs/HostInstructionGrantV2", + "mcp_server": "#/$defs/HostMcpServerGrantV7", + "openshell_policy": "#/$defs/HostOpenShellPolicyGrantV9", + "permission_mode": "#/$defs/HostPermissionModeGrantV2", + "permission_rule": "#/$defs/HostPermissionRuleGrantV2", + "plugin_or_app": "#/$defs/HostPluginGrantV2", + "profile": "#/$defs/HostProfileGrantV2", + "requirement": "#/$defs/HostRequirementGrantV2", + "sandbox": "#/$defs/HostSandboxGrantV2", + "workflow": "#/$defs/HostWorkflowGrantV7" + }, + "propertyName": "kind" + }, + "oneOf": [ + { + "$ref": "#/$defs/HostMcpServerGrantV7" + }, + { + "$ref": "#/$defs/HostPermissionRuleGrantV2" + }, + { + "$ref": "#/$defs/HostPermissionModeGrantV2" + }, + { + "$ref": "#/$defs/HostHookGrantV7" + }, + { + "$ref": "#/$defs/HostSandboxGrantV2" + }, + { + "$ref": "#/$defs/HostAdditionalPathGrantV2" + }, + { + "$ref": "#/$defs/HostPluginGrantV2" + }, + { + "$ref": "#/$defs/HostProfileGrantV2" + }, + { + "$ref": "#/$defs/HostRequirementGrantV2" + }, + { + "$ref": "#/$defs/HostWorkflowGrantV7" + }, + { + "$ref": "#/$defs/HostInstructionGrantV2" + }, + { + "$ref": "#/$defs/HostOpenShellPolicyGrantV9" + } + ] + }, + "title": "Grants", + "type": "array" + }, + "host_coverage": { + "items": { + "$ref": "#/$defs/HostCoverageV8" + }, + "title": "Host Coverage", + "type": "array" + }, + "host_grants_inventory_schema_version": { + "const": "0.9", + "default": "0.9", + "title": "Host Grants Inventory Schema Version", + "type": "string" + }, + "issues": { + "items": { + "$ref": "#/$defs/HostInventoryIssueV8" + }, + "title": "Issues", + "type": "array" + }, + "runtime_session_verified": { + "const": false, + "default": false, + "title": "Runtime Session Verified", + "type": "boolean" + }, + "scope": { + "default": "repository", + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "static_analysis_only": { + "const": true, + "default": true, + "title": "Static Analysis Only", + "type": "boolean" + }, + "workspace": { + "title": "Workspace", + "type": "string" + } + }, + "required": [ + "workspace" + ], + "title": "HostGrantsInventoryV9", + "type": "object" + }, + "HostHookCommandV7": { + "additionalProperties": false, + "description": "A hook command as its grant publishes it: the executable's name and a digest of the whole command.\n\n``executable`` is the last path segment of the command's first\nwhitespace-separated word, when it is a plain token\n(``[A-Za-z0-9._+-]``, at most 80 characters) no redaction rule rewrites\nand the word is no shell reserved word and holds no ``://``, and\n```` otherwise, so no part of a URL is named. It\nis a label, not a claim about what a host runs. ``sha256`` is the digest of the whole command as\n``config_sha256``'s input holds it, so it moves only when that digest\ndoes; a value that input redacts moves neither. The command's text is\nnever published.", + "properties": { + "executable": { + "title": "Executable", + "type": "string" + }, + "sha256": { + "pattern": "^[0-9a-f]{64}$", + "title": "Sha256", + "type": "string" + } + }, + "required": [ + "executable", + "sha256" + ], + "title": "HostHookCommandV7", + "type": "object" + }, + "HostHookGrantV7": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "event": { + "title": "Event", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "handlers": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/HostHookHandlerV7" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Handlers" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "hook", + "default": "hook", + "title": "Kind", + "type": "string" + }, + "omitted_handlers": { + "default": 0, + "minimum": 0, + "title": "Omitted Handlers", + "type": "integer" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "script_inputs": { + "anyOf": [ + { + "items": { + "$ref": "#/$defs/HostHookScriptInputV7" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Script Inputs" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "event", + "handlers" + ], + "title": "HostHookGrantV7", + "type": "object" + }, + "HostHookHandlerV7": { + "additionalProperties": false, + "description": "One hook handler under an event: its group's matcher, its command and its timeout.\n\n``matcher`` is ``None`` when its group declares none, which the host reads\nas every tool or source, and ```` when it is not a string or is\nlonger than 1,024 characters as ``config_sha256``'s input holds it;\notherwise it passes through the published-label redaction and is cut at\n120 characters. ``command`` is ``None`` for a handler with\nno command string, such as a ``prompt`` handler, whose prompt is not\npublished. ``timeout`` is the declared number or boolean; an integer of\nmore than 80 digits is published as its digits cut with ``\u2026``, a string\nas written when it is a plain token, and any other value, a non-finite\nfloat among them, as ````. Other handler settings are not\npublished; a change confined to them is a row whose text says it is not\nshown.", + "properties": { + "command": { + "anyOf": [ + { + "$ref": "#/$defs/HostHookCommandV7" + }, + { + "type": "null" + } + ], + "default": null + }, + "decision_limit": { + "anyOf": [ + { + "const": "script_or_command_behavior_not_read", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Decision Limit" + }, + "inline_allow": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inline Allow" + }, + "matcher": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Matcher" + }, + "timeout": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Timeout" + } + }, + "title": "HostHookHandlerV7", + "type": "object" + }, + "HostHookScriptInputV7": { + "additionalProperties": false, + "description": "A direct executable reference and its byte reading, never script semantics.", + "properties": { + "basis": { + "anyOf": [ + { + "enum": [ + "project_root_placeholder", + "plugin_root_placeholder", + "absolute_workspace_path" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Basis" + }, + "handler": { + "minimum": 0, + "title": "Handler", + "type": "integer" + }, + "limit": { + "anyOf": [ + { + "enum": [ + "unsupported_host", + "not_command_handler", + "unsupported_command_shape", + "platform_command_override", + "unsupported_shell", + "unsupported_exec_form", + "unsupported_shell_command", + "dynamic_command_argument", + "unexpanded_path_placeholder", + "unsupported_path_placeholder", + "plugin_root_not_established", + "unsupported_or_escaping_path", + "dynamic_or_conditional_path", + "working_directory_not_established", + "interpreter_wrapper", + "path_lookup", + "external_executable", + "unsupported_hook_shape", + "handler_bound_exceeded", + "hook_selection_not_established", + "redacted_dependency_path", + "escaping_path", + "missing_input", + "symlink_input", + "non_regular_input", + "oversized_input", + "unsafe_or_unreadable_input", + "unreadable_input" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Limit" + }, + "path": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Path" + }, + "sha256": { + "anyOf": [ + { + "pattern": "^[0-9a-f]{64}$", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sha256" + }, + "size_bytes": { + "anyOf": [ + { + "minimum": 0, + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Size Bytes" + } + }, + "required": [ + "handler" + ], + "title": "HostHookScriptInputV7", + "type": "object" + }, + "HostInstructionGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "instruction_trust_root", + "default": "instruction_trust_root", + "title": "Kind", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "path" + ], + "title": "HostInstructionGrantV2", + "type": "object" + }, + "HostInventoryIssueV8": { + "additionalProperties": false, + "properties": { + "blocking": { + "title": "Blocking", + "type": "boolean" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github", + "openshell" + ], + "title": "Host", + "type": "string" + }, + "issue_id": { + "title": "Issue Id", + "type": "string" + }, + "kind": { + "enum": [ + "parse_failed", + "unreadable", + "unsupported", + "unresolved_precedence", + "dynamic_source_excluded", + "remote_source_excluded" + ], + "title": "Kind", + "type": "string" + }, + "message": { + "title": "Message", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "issue_id", + "kind", + "host", + "source", + "message", + "blocking" + ], + "title": "HostInventoryIssueV8", + "type": "object" + }, + "HostMcpLaunchSourceV7": { + "additionalProperties": false, + "properties": { + "package": { + "anyOf": [ + { + "maxLength": 200, + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Package" + }, + "pin": { + "enum": [ + "pinned", + "mutable" + ], + "title": "Pin", + "type": "string" + } + }, + "required": [ + "pin" + ], + "title": "HostMcpLaunchSourceV7", + "type": "object" + }, + "HostMcpServerGrantV7": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "args_sha256": { + "anyOf": [ + { + "pattern": "^[0-9a-f]{64}$", + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Args Sha256" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "endpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Endpoint" + }, + "env_keys": { + "items": { + "type": "string" + }, + "title": "Env Keys", + "type": "array" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "header_keys": { + "items": { + "type": "string" + }, + "title": "Header Keys", + "type": "array" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "mcp_server", + "default": "mcp_server", + "title": "Kind", + "type": "string" + }, + "launch_source": { + "anyOf": [ + { + "$ref": "#/$defs/HostMcpLaunchSourceV7" + }, + { + "type": "null" + } + ], + "default": null + }, + "package": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Package" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "server": { + "title": "Server", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "transport": { + "title": "Transport", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "server", + "transport", + "package", + "args_sha256" + ], + "title": "HostMcpServerGrantV7", + "type": "object" + }, + "HostOpenShellPolicyGrantV9": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "facts": { + "anyOf": [ + { + "$ref": "#/$defs/OpenShellPolicyFacts" + }, + { + "$ref": "#/$defs/OpenShellComposedPolicyFacts" + }, + { + "$ref": "#/$defs/OpenShellSnapshotFactsV2" + } + ], + "title": "Facts" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "const": "openshell", + "default": "openshell", + "title": "Host", + "type": "string" + }, + "kind": { + "const": "openshell_policy", + "default": "openshell_policy", + "title": "Kind", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "grant_id", + "scope", + "source", + "config_sha256", + "access", + "risk", + "facts" + ], + "title": "HostOpenShellPolicyGrantV9", + "type": "object" + }, + "HostPermissionModeGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "permission_mode", + "default": "permission_mode", + "title": "Kind", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "setting": { + "title": "Setting", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "value": { + "title": "Value", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "setting", + "value" + ], + "title": "HostPermissionModeGrantV2", + "type": "object" + }, + "HostPermissionRuleGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "disposition": { + "enum": [ + "allow", + "ask", + "deny" + ], + "title": "Disposition", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "permission_rule", + "default": "permission_rule", + "title": "Kind", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "rule": { + "title": "Rule", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "wildcard": { + "default": false, + "title": "Wildcard", + "type": "boolean" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "disposition", + "rule" + ], + "title": "HostPermissionRuleGrantV2", + "type": "object" + }, + "HostPluginGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "enabled": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Enabled" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "plugin_or_app", + "default": "plugin_or_app", + "title": "Kind", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "name" + ], + "title": "HostPluginGrantV2", + "type": "object" + }, + "HostProfileGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "profile", + "default": "profile", + "title": "Kind", + "type": "string" + }, + "profile": { + "title": "Profile", + "type": "string" + }, + "resolved": { + "title": "Resolved", + "type": "boolean" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "profile", + "resolved" + ], + "title": "HostProfileGrantV2", + "type": "object" + }, + "HostRequirementGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "requirement", + "default": "requirement", + "title": "Kind", + "type": "string" + }, + "requirement": { + "title": "Requirement", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "value": { + "title": "Value", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "requirement", + "value" + ], + "title": "HostRequirementGrantV2", + "type": "object" + }, + "HostReusableWorkflowCallV6": { + "additionalProperties": false, + "properties": { + "job": { + "title": "Job", + "type": "string" + }, + "secret_mappings": { + "items": { + "$ref": "#/$defs/HostReusableWorkflowSecretV6" + }, + "title": "Secret Mappings", + "type": "array" + }, + "secrets_inherit": { + "title": "Secrets Inherit", + "type": "boolean" + }, + "uses": { + "title": "Uses", + "type": "string" + }, + "uses_redacted": { + "default": false, + "title": "Uses Redacted", + "type": "boolean" + } + }, + "required": [ + "job", + "uses", + "secrets_inherit" + ], + "title": "HostReusableWorkflowCallV6", + "type": "object" + }, + "HostReusableWorkflowSecretV6": { + "additionalProperties": false, + "description": "One named secret a job passes to the reusable workflow it calls (#693).\n\n``destination`` is the callee's secret input name as the caller writes it.\n``source`` is ``NAME`` from a whole-value ``${{ secrets.NAME }}``, and\n``form`` is then ``secret``. The name is a reference, never a value: it\ndoes not establish the secret's privilege, whether the caller has it, or\nwhat the called workflow does with it. Anything else is ``unresolved``,\nand none of its value is published or digested: a literal value, any\nother expression, a non-string, or a ``secrets`` that is neither\n``inherit`` nor a mapping (``destination`` is then ``null``). A name the\ncredential redactors rewrite is ``redacted`` and records a blocking\ncoverage issue, because two values that redact alike must never compare as\nunchanged. Every other unresolved mapping records a non-blocking one naming\nits ``job/destination``: only that value is uncompared, so the rest of the\nfile still compares.", + "properties": { + "destination": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Destination" + }, + "form": { + "enum": [ + "secret", + "unresolved" + ], + "title": "Form", + "type": "string" + }, + "source": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Source" + }, + "unresolved_reason": { + "anyOf": [ + { + "enum": [ + "literal_value", + "expression", + "not_a_string", + "redacted", + "secrets_not_a_mapping" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Unresolved Reason" + } + }, + "required": [ + "destination", + "source", + "form" + ], + "title": "HostReusableWorkflowSecretV6", + "type": "object" + }, + "HostSandboxGrantV2": { + "additionalProperties": false, + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "sandbox", + "default": "sandbox", + "title": "Kind", + "type": "string" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "setting": { + "title": "Setting", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "value": { + "title": "Value", + "type": "string" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "setting", + "value" + ], + "title": "HostSandboxGrantV2", + "type": "object" + }, + "HostWorkflowAgentLaunchV7": { + "additionalProperties": false, + "description": "A step that launches a known coding agent, read as text and never run (#823).\n\n``agent`` is a documented action reference's ``owner/repo`` (the step's\n``uses:`` at any ref; the Claude base action also as the ``base-action``\ndirectory of ``anthropics/claude-code-action``), or a known agent CLI a\n``run:`` launches when the whole ``run:`` is one line of plain words\n(letters, digits and ``_ . / : = , % + -``, separated by spaces or tabs),\nrun by ``bash``, ``sh`` or the runner's default shell, whose program,\nafter any ``NAME=value`` assignments, has the file name ``claude`` and\npasses ``-p``/``--print``, or ``codex`` followed by ``exec`` (``e``).\n``form: read`` lists the documented permission inputs or flags the step\ndeclares in ``settings``, and the documented widening rules they meet in\n``widening_rules``, omitted when none. ``form: unresolved`` is an agent\naction whose ``with:`` is not a mapping (``inputs_not_a_mapping``), with\nno settings, and records a non-blocking coverage issue. Any other\n``run:`` that mentions an agent CLI is not a launch: it is listed in\n``unread_agent_runs``. ``job_secrets`` names the secrets the step's job\nreferences (``${{ secrets.NAME }}``) and the workflow-level ``env``\npasses: context for the row that names this step, never compared.\n``job`` and ``step`` are published labels (#802).", + "properties": { + "agent": { + "enum": [ + "anthropics/claude-code-action", + "anthropics/claude-code-base-action", + "anthropics/claude-code-action/base-action", + "openai/codex-action", + "claude", + "codex" + ], + "title": "Agent", + "type": "string" + }, + "form": { + "enum": [ + "read", + "unresolved" + ], + "title": "Form", + "type": "string" + }, + "job": { + "title": "Job", + "type": "string" + }, + "job_secrets": { + "items": { + "type": "string" + }, + "title": "Job Secrets", + "type": "array" + }, + "settings": { + "items": { + "$ref": "#/$defs/HostWorkflowAgentSettingV7" + }, + "title": "Settings", + "type": "array" + }, + "step": { + "title": "Step", + "type": "string" + }, + "unresolved_reason": { + "anyOf": [ + { + "const": "inputs_not_a_mapping", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Unresolved Reason" + }, + "widening_rules": { + "items": { + "$ref": "#/$defs/HostWorkflowAgentRuleV7" + }, + "title": "Widening Rules", + "type": "array" + } + }, + "required": [ + "job", + "step", + "agent", + "form" + ], + "title": "HostWorkflowAgentLaunchV7", + "type": "object" + }, + "HostWorkflowAgentRuleV7": { + "additionalProperties": false, + "description": "One documented widening rule an agent launch meets, and the setting it was read from (#823).\n\nDecided when the workflow is read, from the declared text, before any of\nit is withheld for publication, so redaction never hides a rule. Only\ntext this reader reads exactly meets one: ``claude_args`` or\n``codex-args`` only when it is a plain list of words (never when it holds\na ``${{ }}`` expression), the entries of a user gate that hold no\nexpression, and a mode or ``settings`` input that holds none. Claude Code\nsettings written as JSON in the ``settings`` input meet\n``bypass_permissions`` when their ``defaultMode`` is\n``bypassPermissions``, read as the settings reader reads it; a path to a\nsettings file is not read. ``setting`` is the input (``claude_args``,\n``allowed_bots``, ``sandbox``, ``permission-profile``, \u2026) or the CLI\nflag's primary spelling. One rule compares as one whatever setting meets\nit, except ``open_gate``, which is one rule per gate input.", + "properties": { + "rule": { + "enum": [ + "bypass_permissions", + "bypass_approvals_and_sandbox", + "danger_full_access", + "unsafe_safety_strategy", + "open_gate" + ], + "title": "Rule", + "type": "string" + }, + "setting": { + "title": "Setting", + "type": "string" + } + }, + "required": [ + "rule", + "setting" + ], + "title": "HostWorkflowAgentRuleV7", + "type": "object" + }, + "HostWorkflowAgentSettingV7": { + "additionalProperties": false, + "description": "One permission input or flag an agent launch declares, compared as text (#823).\n\n``name`` is the documented input (``claude_args``, ``sandbox``, \u2026) or the\nflag's primary spelling (``--allowedTools`` for ``--allowed-tools`` too).\n``value`` is the declared text, stripped, as it may be published; a flag\nthat takes no value has ``null``.\n\n``claude_args`` and ``codex-args`` are read only when they are a plain\nlist of words: letters, digits and ``_ . / : = , % + - ( )``, separated by\nblanks or newlines, with no ``--settings`` or ``--mcp-config`` flag. Every\nparser involved splits such text the same way, so it is published as\nthose words, one space apart. Any other value \u2014 holding a quote, a\n``${{ }}`` expression, ``$``, a backtick, a comment, a shell operator,\nJSON or another character \u2014 is ``unread_arguments``: ``value`` is\n````, a short digest, so an edit to it is still a change\nwhile none of its text is published; no documented widening rule is read\nfrom it; and it records a non-blocking coverage issue naming its\n``job/step`` (#823 review cycle 4). A codex ``--config`` override keeps\nits key; its value is ```` under ``env``, ``headers`` or a\nsecret-named key, as the host readers redact such values, published as\nwritten for ``sandbox_mode``, ``default_permissions``,\n``approval_policy`` and ``model``, and ```` otherwise.\n\nEvery other input is one value. A JSON object (a ``settings`` or\n``mcp_config`` value) publishes its shape and none of its free text: key\nnames, numbers, booleans and ``null``, with each string replaced by\n````, a short digest of what the host readers digest for it,\nso an edit to it is still a change. ``env`` and ``headers`` values,\n``apiKeyHelper`` and every secret-named value are ````, as the\nhost readers redact them. The strings a host reader publishes are kept:\na ``permissions.allow``/``ask``/``deny`` rule and a documented Claude\nCode setting's value such as ``defaultMode``, and an MCP server's command\nname and its URL's scheme and host, each followed by the digest when it\ndrops something the digest reads (a command's arguments, a URL's query).\nSo an MCP server's arguments and a hook's command publish nothing, as\n`.mcp.json` and `.claude/settings.json` do not (#823 review). A\n``settings`` or ``mcp_config`` value that neither starts like a JSON\nobject nor is a plain file path (path characters, and a ``${{ }}``\nexpression only as a plain context reference) is ````, a\ndigest and none of its text (#823 review cycle 5). A URL in\nother text publishes its scheme and host with ```` for its\npath and query (#723). Other text \u2014 a prompt, a flag's value \u2014 is\npublished through the workflow label redaction (#802). A value it\nrewrites is credential-shaped \u2014 a token, but also prose such as \"never\nprint bearer tokens\" \u2014 and is published redacted with\n``unresolved_reason: redacted``: it is compared as published, beside the\nrules read from its declared text, and records a non-blocking coverage\nissue naming its ``job/step``, because an edit inside what is redacted is\nnot reported. A value that is not a string (``not_a_string``), or one\nholding text that starts like JSON and does not parse (``unparsed_json``),\nis ``null`` and records a non-blocking coverage issue naming its\n``job/step``: it is neither published nor compared.\n\n``holds_expression`` is ``true`` when an input other than an argument\ninput holds a ``${{ }}`` expression, which GitHub substitutes before the\naction reads the input, and is omitted otherwise. A documented widening\nrule is then read only from the entries of a user gate that hold none,\nand from no mode or settings input, and a rule the launch gains in the\nsame job afterwards is not claimed, because the substituted text may\nalready have met it.", + "properties": { + "holds_expression": { + "default": false, + "title": "Holds Expression", + "type": "boolean" + }, + "name": { + "title": "Name", + "type": "string" + }, + "unresolved_reason": { + "anyOf": [ + { + "enum": [ + "not_a_string", + "redacted", + "unparsed_json", + "unread_arguments" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Unresolved Reason" + }, + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Value" + } + }, + "required": [ + "name", + "value" + ], + "title": "HostWorkflowAgentSettingV7", + "type": "object" + }, + "HostWorkflowCheckoutRefV7": { + "additionalProperties": false, + "description": "One ``actions/checkout`` step and the ``with.ref`` it declares, as text (#823).\n\n``ref`` is ``null`` when the step declares none, or an empty one: the\ncheckout's default for the triggering event. A ref the label redaction\nrewrites is published redacted with ``unresolved_reason: redacted`` and\nmakes the workflow a blocking limit, as a redacted step reference does\n(#767): a ref names the code the job runs, as a step reference does. A\nvalue that is not a string, or ``with:`` that is not a mapping,\nis ``null`` with ``unresolved_reason`` and records a non-blocking coverage\nissue. The ref is never resolved or fetched.", + "properties": { + "job": { + "title": "Job", + "type": "string" + }, + "ref": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Ref" + }, + "step": { + "title": "Step", + "type": "string" + }, + "unresolved_reason": { + "anyOf": [ + { + "enum": [ + "not_a_string", + "redacted", + "inputs_not_a_mapping" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Unresolved Reason" + } + }, + "required": [ + "job", + "step", + "ref" + ], + "title": "HostWorkflowCheckoutRefV7", + "type": "object" + }, + "HostWorkflowGrantV7": { + "additionalProperties": false, + "description": "A v0.6 workflow grant plus the agent launches, unread agent steps and checkout refs its steps declare.\n\nEach list is present only when a step declares one. In a v0.7 grant an\nabsent list means the steps were read and declare none; the schema\nversion, not the key, separates that from a legacy grant that never read\nthem. ``unread_agent_runs`` is a named limit and is never compared.\n``access`` and ``risk`` still describe the workflow's token and triggers\nalone.", + "properties": { + "access": { + "enum": [ + "none", + "read", + "write", + "execute", + "external", + "admin", + "unknown" + ], + "title": "Access", + "type": "string" + }, + "agent_launches": { + "items": { + "$ref": "#/$defs/HostWorkflowAgentLaunchV7" + }, + "title": "Agent Launches", + "type": "array" + }, + "checkout_refs": { + "items": { + "$ref": "#/$defs/HostWorkflowCheckoutRefV7" + }, + "title": "Checkout Refs", + "type": "array" + }, + "config_sha256": { + "title": "Config Sha256", + "type": "string" + }, + "effective_write_scopes": { + "items": { + "type": "string" + }, + "title": "Effective Write Scopes", + "type": "array" + }, + "grant_id": { + "title": "Grant Id", + "type": "string" + }, + "host": { + "enum": [ + "codex", + "claude-code", + "cursor", + "vscode", + "github" + ], + "title": "Host", + "type": "string" + }, + "kind": { + "const": "workflow", + "default": "workflow", + "title": "Kind", + "type": "string" + }, + "permission_contexts": { + "items": { + "$ref": "#/$defs/HostWorkflowPermissionsV4" + }, + "title": "Permission Contexts", + "type": "array" + }, + "pull_request_target": { + "default": false, + "title": "Pull Request Target", + "type": "boolean" + }, + "reusable_calls": { + "items": { + "$ref": "#/$defs/HostReusableWorkflowCallV6" + }, + "title": "Reusable Calls", + "type": "array" + }, + "risk": { + "enum": [ + "none", + "low", + "medium", + "high", + "critical", + "unknown" + ], + "title": "Risk", + "type": "string" + }, + "scope": { + "enum": [ + "repository", + "local_static" + ], + "title": "Scope", + "type": "string" + }, + "source": { + "title": "Source", + "type": "string" + }, + "step_actions": { + "items": { + "$ref": "#/$defs/HostWorkflowStepActionV6" + }, + "title": "Step Actions", + "type": "array" + }, + "triggers": { + "items": { + "type": "string" + }, + "title": "Triggers", + "type": "array" + }, + "unread_agent_runs": { + "items": { + "$ref": "#/$defs/HostWorkflowUnreadAgentRunV7" + }, + "title": "Unread Agent Runs", + "type": "array" + }, + "write_all": { + "default": false, + "title": "Write All", + "type": "boolean" + }, + "write_scopes": { + "items": { + "type": "string" + }, + "title": "Write Scopes", + "type": "array" + } + }, + "required": [ + "grant_id", + "host", + "scope", + "source", + "config_sha256", + "access", + "risk", + "permission_contexts", + "effective_write_scopes", + "reusable_calls" + ], + "title": "HostWorkflowGrantV7", + "type": "object" + }, + "HostWorkflowPermissionsV4": { + "additionalProperties": false, + "properties": { + "job": { + "title": "Job", + "type": "string" + }, + "permissions": { + "additionalProperties": { + "enum": [ + "read", + "write" + ], + "type": "string" + }, + "title": "Permissions", + "type": "object" + }, + "state": { + "enum": [ + "explicit", + "repository_default", + "unresolved" + ], + "title": "State", + "type": "string" + } + }, + "required": [ + "job", + "state", + "permissions" + ], + "title": "HostWorkflowPermissionsV4", + "type": "object" + }, + "HostWorkflowStepActionV6": { + "additionalProperties": false, + "description": "One step's declared action reference, read as text and never fetched.\n\n``form`` is ``remote`` for ``owner/repo[/path]@ref``, ``docker`` for\n``docker://\u2026``, and ``unresolved`` for a value Shipgate does not resolve\nto an action identity; ``unresolved_reason`` then says which. A job whose\n``steps`` is not a list, or a step that is not a mapping, is listed as\nunresolved too, with no ``uses``, so an absent list still means the steps\nwere read and declare nothing. A local\n``./\u2026`` reference is not listed: composite actions remain unread (#701).\n``step`` is the step's ``id``, else its ``name``, else ``steps[N]`` \u2014 the\nevidence a reviewer uses to find it, not part of the comparison. ``job``\nand ``step`` are published labels: credential-shaped text in either, and\nthe userinfo of any ``scheme://\u2026@`` inside it, is redacted (#802).", + "properties": { + "form": { + "enum": [ + "remote", + "docker", + "unresolved" + ], + "title": "Form", + "type": "string" + }, + "job": { + "title": "Job", + "type": "string" + }, + "step": { + "title": "Step", + "type": "string" + }, + "unresolved_reason": { + "anyOf": [ + { + "enum": [ + "expression", + "unsupported_reference", + "not_a_string", + "redacted", + "steps_not_a_list", + "step_not_a_mapping" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Unresolved Reason" + }, + "uses": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Uses" + } + }, + "required": [ + "job", + "step", + "uses", + "form" + ], + "title": "HostWorkflowStepActionV6", + "type": "object" + }, + "HostWorkflowUnreadAgentRunV7": { + "additionalProperties": false, + "description": "A ``run:`` step that mentions a known agent CLI and is not read as an agent launch (#823 review cycle 4).\n\nAny ``run:`` holding ``claude`` or ``codex`` as a word of its own that is\nnot an agent launch this reader reads \u2014 more than one line or command, a\nquote, an expansion, a redirection, a comment, a continuation, a\n``${{ }}`` expression, another program such as ``npx`` or ``timeout``, a\nsubcommand that is not a headless launch, or a declared ``shell:`` other\nthan ``bash`` or ``sh`` run on the script alone (so ``bash -c '\u2026' {0}``\ntoo) \u2014 once for each agent CLI it mentions. It is a\nnamed, non-blocking limit and nothing more: none of the step's text is\npublished, it is never compared, so adding, removing or editing it gives\nno row, and it never says that the step starts, or does not start, an\nagent. ``job`` and ``step`` are published labels (#802).", + "properties": { + "agent": { + "enum": [ + "claude", + "codex" + ], + "title": "Agent", + "type": "string" + }, + "job": { + "title": "Job", + "type": "string" + }, + "step": { + "title": "Step", + "type": "string" + } + }, + "required": [ + "job", + "step", + "agent" + ], + "title": "HostWorkflowUnreadAgentRunV7", + "type": "object" + }, + "InstructionStructureEvidence": { + "additionalProperties": false, + "properties": { + "profile": { + "title": "Profile", + "type": "string" + }, + "reason": { + "title": "Reason", + "type": "string" + }, + "sha256": { + "anyOf": [ + { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Sha256" + }, + "status": { + "enum": [ + "guidance", + "structured", + "unresolved" + ], + "title": "Status", + "type": "string" + } + }, + "required": [ + "profile", + "status", + "reason" + ], + "title": "InstructionStructureEvidence", + "type": "object" + }, + "OpenShellAllowRule": { + "additionalProperties": false, + "properties": { + "allow": { + "$ref": "#/$defs/OpenShellRequestMatcher" + } + }, + "required": [ + "allow" + ], + "title": "OpenShellAllowRule", + "type": "object" + }, + "OpenShellAnyMatcher": { + "additionalProperties": false, + "properties": { + "any": { + "items": { + "type": "string" + }, + "minItems": 1, + "title": "Any", + "type": "array" + } + }, + "required": [ + "any" + ], + "title": "OpenShellAnyMatcher", + "type": "object" + }, + "OpenShellBinary": { + "additionalProperties": false, + "properties": { + "path": { + "title": "Path", + "type": "string" + } + }, + "required": [ + "path" + ], + "title": "OpenShellBinary", + "type": "object" + }, + "OpenShellComposedPolicyFacts": { + "additionalProperties": false, + "properties": { + "composition": { + "$ref": "#/$defs/CompositionProvenance" + }, + "credential_use": { + "items": { + "$ref": "#/$defs/CredentialUseFacts" + }, + "title": "Credential Use", + "type": "array" + }, + "defaulted_fields": { + "items": { + "type": "string" + }, + "title": "Defaulted Fields", + "type": "array" + }, + "field_paths": { + "items": { + "type": "string" + }, + "title": "Field Paths", + "type": "array" + }, + "filesystem_baseline": { + "const": "runtime_dependent_not_resolved", + "default": "runtime_dependent_not_resolved", + "title": "Filesystem Baseline", + "type": "string" + }, + "include_workdir_when_filesystem_omitted": { + "const": true, + "default": true, + "title": "Include Workdir When Filesystem Omitted", + "type": "boolean" + }, + "landlock_when_omitted": { + "const": "best_effort", + "default": "best_effort", + "title": "Landlock When Omitted", + "type": "string" + }, + "policy": { + "$ref": "#/$defs/OpenShellPolicy" + }, + "policy_schema_version": { + "const": 1, + "default": 1, + "title": "Policy Schema Version", + "type": "integer" + }, + "process_omission": { + "const": "driver_default", + "default": "driver_default", + "title": "Process Omission", + "type": "string" + }, + "registration": { + "title": "Registration", + "type": "string" + }, + "role": { + "const": "composed", + "default": "composed", + "title": "Role", + "type": "string" + }, + "runtime_freshness_verified": { + "const": false, + "default": false, + "title": "Runtime Freshness Verified", + "type": "boolean" + }, + "runtime_version": { + "const": "0.1.2", + "title": "Runtime Version", + "type": "string" + } + }, + "required": [ + "registration", + "runtime_version", + "policy", + "composition", + "credential_use" + ], + "title": "OpenShellComposedPolicyFacts", + "type": "object" + }, + "OpenShellCredentialBinding": { + "additionalProperties": false, + "properties": { + "provider": { + "title": "Provider", + "type": "string" + } + }, + "required": [ + "provider" + ], + "title": "OpenShellCredentialBinding", + "type": "object" + }, + "OpenShellEndpoint": { + "additionalProperties": false, + "properties": { + "access": { + "default": "", + "enum": [ + "", + "read-only", + "read-write", + "full" + ], + "title": "Access", + "type": "string" + }, + "allow_encoded_slash": { + "default": false, + "title": "Allow Encoded Slash", + "type": "boolean" + }, + "allow_uninspected_credentials": { + "default": false, + "title": "Allow Uninspected Credentials", + "type": "boolean" + }, + "allowed_ips": { + "items": { + "type": "string" + }, + "title": "Allowed Ips", + "type": "array" + }, + "credential_binding": { + "anyOf": [ + { + "$ref": "#/$defs/OpenShellCredentialBinding" + }, + { + "type": "null" + } + ], + "default": null + }, + "credential_signing": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Credential Signing" + }, + "deny_rules": { + "items": { + "$ref": "#/$defs/OpenShellRequestMatcher" + }, + "title": "Deny Rules", + "type": "array" + }, + "enforcement": { + "default": "audit", + "enum": [ + "audit", + "enforce", + "" + ], + "title": "Enforcement", + "type": "string" + }, + "graphql_max_body_bytes": { + "default": 65536, + "maximum": 4294967295, + "minimum": 0, + "title": "Graphql Max Body Bytes", + "type": "integer" + }, + "graphql_persisted_queries": { + "additionalProperties": { + "$ref": "#/$defs/OpenShellGraphqlOperation" + }, + "title": "Graphql Persisted Queries", + "type": "object" + }, + "host": { + "default": "", + "title": "Host", + "type": "string" + }, + "json_rpc": { + "anyOf": [ + { + "$ref": "#/$defs/OpenShellJsonRpcOptions" + }, + { + "type": "null" + } + ], + "default": null + }, + "mcp": { + "anyOf": [ + { + "$ref": "#/$defs/OpenShellMcpOptions" + }, + { + "type": "null" + } + ], + "default": null + }, + "path": { + "default": "", + "title": "Path", + "type": "string" + }, + "persisted_queries": { + "default": "deny", + "enum": [ + "", + "deny", + "allow_registered" + ], + "title": "Persisted Queries", + "type": "string" + }, + "port": { + "default": 0, + "maximum": 65535, + "minimum": 0, + "title": "Port", + "type": "integer" + }, + "ports": { + "items": { + "type": "integer" + }, + "title": "Ports", + "type": "array" + }, + "protocol": { + "default": "", + "enum": [ + "", + "rest", + "websocket", + "graphql", + "mcp", + "json-rpc", + "tcp" + ], + "title": "Protocol", + "type": "string" + }, + "request_body_credential_rewrite": { + "default": false, + "title": "Request Body Credential Rewrite", + "type": "boolean" + }, + "rules": { + "items": { + "$ref": "#/$defs/OpenShellAllowRule" + }, + "title": "Rules", + "type": "array" + }, + "signing_region": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Signing Region" + }, + "signing_service": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Signing Service" + }, + "tls": { + "default": "", + "enum": [ + "", + "skip" + ], + "title": "Tls", + "type": "string" + }, + "websocket_credential_rewrite": { + "default": false, + "title": "Websocket Credential Rewrite", + "type": "boolean" + } + }, + "title": "OpenShellEndpoint", + "type": "object" + }, + "OpenShellFilesystem": { + "additionalProperties": false, + "properties": { + "include_workdir": { + "default": false, + "title": "Include Workdir", + "type": "boolean" + }, + "read_only": { + "items": { + "type": "string" + }, + "title": "Read Only", + "type": "array" + }, + "read_write": { + "items": { + "type": "string" + }, + "title": "Read Write", + "type": "array" + } + }, + "title": "OpenShellFilesystem", + "type": "object" + }, + "OpenShellGraphqlOperation": { + "additionalProperties": false, + "properties": { + "fields": { + "items": { + "type": "string" + }, + "title": "Fields", + "type": "array" + }, + "operation_name": { + "default": "", + "title": "Operation Name", + "type": "string" + }, + "operation_type": { + "default": "", + "title": "Operation Type", + "type": "string" + } + }, + "title": "OpenShellGraphqlOperation", + "type": "object" + }, + "OpenShellJsonRpcOptions": { + "additionalProperties": false, + "properties": { + "max_body_bytes": { + "default": 65536, + "maximum": 4294967295, + "minimum": 0, + "title": "Max Body Bytes", + "type": "integer" + } + }, + "title": "OpenShellJsonRpcOptions", + "type": "object" + }, + "OpenShellLandlock": { + "additionalProperties": false, + "properties": { + "compatibility": { + "default": "best_effort", + "enum": [ + "best_effort", + "hard_requirement" + ], + "title": "Compatibility", + "type": "string" + } + }, + "title": "OpenShellLandlock", + "type": "object" + }, + "OpenShellMcpOptions": { + "additionalProperties": false, + "properties": { + "allow_all_known_mcp_methods": { + "default": false, + "title": "Allow All Known Mcp Methods", + "type": "boolean" + }, + "max_body_bytes": { + "default": 65536, + "maximum": 4294967295, + "minimum": 0, + "title": "Max Body Bytes", + "type": "integer" + }, + "strict_tool_names": { + "default": true, + "title": "Strict Tool Names", + "type": "boolean" + }, + "versions": { + "items": { + "type": "string" + }, + "title": "Versions", + "type": "array" + } + }, + "title": "OpenShellMcpOptions", + "type": "object" + }, + "OpenShellNetworkRule": { + "additionalProperties": false, + "properties": { + "binaries": { + "items": { + "$ref": "#/$defs/OpenShellBinary" + }, + "title": "Binaries", + "type": "array" + }, + "endpoints": { + "items": { + "$ref": "#/$defs/OpenShellEndpoint" + }, + "title": "Endpoints", + "type": "array" + }, + "name": { + "default": "", + "title": "Name", + "type": "string" + } + }, + "title": "OpenShellNetworkRule", + "type": "object" + }, + "OpenShellPolicy": { + "additionalProperties": false, + "properties": { + "filesystem_policy": { + "anyOf": [ + { + "$ref": "#/$defs/OpenShellFilesystem" + }, + { + "type": "null" + } + ], + "default": null + }, + "landlock": { + "anyOf": [ + { + "$ref": "#/$defs/OpenShellLandlock" + }, + { + "type": "null" + } + ], + "default": null + }, + "network_middlewares": { + "additionalProperties": true, + "title": "Network Middlewares", + "type": "object" + }, + "network_policies": { + "additionalProperties": { + "$ref": "#/$defs/OpenShellNetworkRule" + }, + "title": "Network Policies", + "type": "object" + }, + "process": { + "anyOf": [ + { + "$ref": "#/$defs/OpenShellProcess" + }, + { + "type": "null" + } + ], + "default": null + }, + "version": { + "const": 1, + "title": "Version", + "type": "integer" + } + }, + "required": [ + "version" + ], + "title": "OpenShellPolicy", + "type": "object" + }, + "OpenShellPolicyFacts": { + "additionalProperties": false, + "properties": { + "defaulted_fields": { + "items": { + "type": "string" + }, + "title": "Defaulted Fields", + "type": "array" + }, + "field_paths": { + "items": { + "type": "string" + }, + "title": "Field Paths", + "type": "array" + }, + "filesystem_baseline": { + "const": "runtime_dependent_not_resolved", + "default": "runtime_dependent_not_resolved", + "title": "Filesystem Baseline", + "type": "string" + }, + "include_workdir_when_filesystem_omitted": { + "const": true, + "default": true, + "title": "Include Workdir When Filesystem Omitted", + "type": "boolean" + }, + "landlock_when_omitted": { + "const": "best_effort", + "default": "best_effort", + "title": "Landlock When Omitted", + "type": "string" + }, + "policy": { + "$ref": "#/$defs/OpenShellPolicy" + }, + "policy_schema_version": { + "const": 1, + "default": 1, + "title": "Policy Schema Version", + "type": "integer" + }, + "process_omission": { + "const": "driver_default", + "default": "driver_default", + "title": "Process Omission", + "type": "string" + }, + "registration": { + "title": "Registration", + "type": "string" + }, + "role": { + "enum": [ + "authored", + "effective_snapshot" + ], + "title": "Role", + "type": "string" + }, + "runtime_freshness_verified": { + "const": false, + "default": false, + "title": "Runtime Freshness Verified", + "type": "boolean" + }, + "runtime_version": { + "const": "0.1.2", + "title": "Runtime Version", + "type": "string" + } + }, + "required": [ + "registration", + "role", + "runtime_version", + "policy" + ], + "title": "OpenShellPolicyFacts", + "type": "object" + }, + "OpenShellProcess": { + "additionalProperties": false, + "properties": { + "run_as_group": { + "default": "", + "title": "Run As Group", + "type": "string" + }, + "run_as_user": { + "default": "", + "title": "Run As User", + "type": "string" + } + }, + "title": "OpenShellProcess", + "type": "object" + }, + "OpenShellRequestMatcher": { + "additionalProperties": false, + "properties": { + "command": { + "default": "", + "title": "Command", + "type": "string" + }, + "fields": { + "items": { + "type": "string" + }, + "title": "Fields", + "type": "array" + }, + "method": { + "default": "", + "title": "Method", + "type": "string" + }, + "operation_name": { + "default": "", + "title": "Operation Name", + "type": "string" + }, + "operation_type": { + "default": "", + "title": "Operation Type", + "type": "string" + }, + "params": { + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/OpenShellAnyMatcher" + } + ] + }, + "title": "Params", + "type": "object" + }, + "path": { + "default": "", + "title": "Path", + "type": "string" + }, + "query": { + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/OpenShellAnyMatcher" + } + ] + }, + "title": "Query", + "type": "object" + }, + "tool": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/OpenShellAnyMatcher" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Tool" + } + }, + "title": "OpenShellRequestMatcher", + "type": "object" + }, + "OpenShellSnapshotFactsV2": { + "additionalProperties": false, + "properties": { + "creation_bound_fields": { + "items": { + "type": "string" + }, + "title": "Creation Bound Fields", + "type": "array" + }, + "defaulted_fields": { + "items": { + "type": "string" + }, + "title": "Defaulted Fields", + "type": "array" + }, + "dynamic_fields": { + "items": { + "type": "string" + }, + "title": "Dynamic Fields", + "type": "array" + }, + "field_paths": { + "items": { + "type": "string" + }, + "title": "Field Paths", + "type": "array" + }, + "filesystem_baseline": { + "const": "runtime_dependent_not_resolved", + "default": "runtime_dependent_not_resolved", + "title": "Filesystem Baseline", + "type": "string" + }, + "include_workdir_when_filesystem_omitted": { + "const": true, + "default": true, + "title": "Include Workdir When Filesystem Omitted", + "type": "boolean" + }, + "landlock_when_omitted": { + "const": "best_effort", + "default": "best_effort", + "title": "Landlock When Omitted", + "type": "string" + }, + "policy": { + "$ref": "#/$defs/OpenShellPolicy" + }, + "policy_schema_version": { + "const": 1, + "default": 1, + "title": "Policy Schema Version", + "type": "integer" + }, + "process_omission": { + "const": "driver_default", + "default": "driver_default", + "title": "Process Omission", + "type": "string" + }, + "registration": { + "title": "Registration", + "type": "string" + }, + "role": { + "const": "effective_snapshot", + "default": "effective_snapshot", + "title": "Role", + "type": "string" + }, + "runtime_freshness_verified": { + "const": false, + "default": false, + "title": "Runtime Freshness Verified", + "type": "boolean" + }, + "runtime_version": { + "const": "0.1.2", + "title": "Runtime Version", + "type": "string" + }, + "snapshot": { + "$ref": "#/$defs/SnapshotMetadata" + }, + "startup_bound_fields": { + "items": { + "type": "string" + }, + "title": "Startup Bound Fields", + "type": "array" + } + }, + "required": [ + "registration", + "runtime_version", + "policy", + "snapshot" + ], + "title": "OpenShellSnapshotFactsV2", + "type": "object" + }, + "SnapshotMetadata": { + "additionalProperties": false, + "properties": { + "revision": { + "default": "", + "maxLength": 256, + "title": "Revision", + "type": "string" + }, + "source": { + "default": "", + "maxLength": 1024, + "title": "Source", + "type": "string" + } + }, + "title": "SnapshotMetadata", + "type": "object" + }, + "StaticCredential": { + "additionalProperties": false, + "properties": { + "auth_style": { + "default": "", + "enum": [ + "", + "basic", + "bearer", + "header", + "query", + "path" + ], + "title": "Auth Style", + "type": "string" + }, + "description": { + "default": "", + "title": "Description", + "type": "string" + }, + "env_vars": { + "items": { + "type": "string" + }, + "maxItems": 32, + "title": "Env Vars", + "type": "array" + }, + "header_name": { + "default": "", + "title": "Header Name", + "type": "string" + }, + "name": { + "maxLength": 128, + "minLength": 1, + "title": "Name", + "type": "string" + }, + "path_template": { + "default": "", + "title": "Path Template", + "type": "string" + }, + "query_param": { + "default": "", + "title": "Query Param", + "type": "string" + }, + "required": { + "default": false, + "title": "Required", + "type": "boolean" + } + }, + "required": [ + "name" + ], + "title": "StaticCredential", + "type": "object" + } + }, + "$id": "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/main/docs/host-grants-inventory-schema.v0.9.json", + "$ref": "#/$defs/HostGrantsInventoryV9", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "JSON Schema for shipgate audit --host --json. The inventory summarizes local coding-agent host grants and does not gate releases.", + "title": "Agents Shipgate Host Grants Inventory v0.9" +} diff --git a/docs/openshell-support.md b/docs/openshell-support.md index 30bd6f26..b2761862 100644 --- a/docs/openshell-support.md +++ b/docs/openshell-support.md @@ -29,9 +29,8 @@ the policy. It never classifies every YAML file as an OpenShell policy. `authored` identifies a proposed sandbox policy. `effective_snapshot` identifies a locally supplied export in the same policy YAML/JSON shape. Both are static documents reviewed independently. An export's presence establishes no live -freshness or enforcement. Provider/global composition is outside this reader's -scope. The runtime pin `0.1.2`, OpenShell policy schema `1`, registration schema -`1` and host inventory schema `0.8` are separate version axes. +freshness or enforcement. Version 2 selections additionally describe explicit local composition. The runtime pin `0.1.2`, OpenShell policy schema `1`, registration schemas +`1`/`2` and host inventory schema `0.9` are separate version axes. The `openshell_policy` grant contains typed filesystem, Landlock, process and network facts. Endpoint and binary lists remain together under their rule; @@ -48,7 +47,7 @@ Defaults follow the pinned [OpenShell v0.1.2 authored schema](https://github.com and [conversion code](https://github.com/NVIDIA/OpenShell/blob/v0.1.2/crates/openshell-policy/src/lib.rs). The [upstream schema reference](https://docs.nvidia.com/openshell/how-it-works/policies/schema) describes runtime constraints beyond document inventory. This reader is not a -substitute for upstream policy validation. Local composition and native proof are separate implementation stages (#947–#948). +substitute for upstream policy validation. Native proof remains an optional, separate implementation stage (#948). ## Conservative declared-authority comparison @@ -116,8 +115,8 @@ Reports publish redacted evidence digests and sanitized errors, never parser excerpts or credential values. If a credential-shaped authority label would change under redaction, that document becomes unsupported instead of letting different labels compare as equal. Exact file identity remains internal to the -read session. Historical v0.1–v0.7 host schemas remain frozen and readable; -current inventories/baselines/drift use v0.8. +read session. Historical v0.1–v0.8 host schemas remain frozen and readable; +current inventories/baselines/drift use v0.9. The checked-in [example](../samples/openshell/sandbox.yaml) is selected by `samples/openshell/.shipgate/openshell.json` when auditing this repository root. @@ -196,3 +195,79 @@ filter cannot identify an arbitrary selected policy path on its own. Run `shipgate check` or `agents-shipgate verify` for those changes, or set `always_run: true` on the local hook. The GitHub verifier reads the explicit selection and evaluates its dependencies. + +## Reproducible local composition + +Version 2 selections can describe a composed view without executing OpenShell: + +```json +{ + "version": 2, + "runtime_version": "0.1.2", + "compositions": [{ + "name": "worker", + "workspace": "team-a", + "global_policy": {"state": "absent"}, + "saved_policy": {"state": "selected", "path": "configs/base.yaml"}, + "image_policy": {"state": "absent"}, + "catalog_mode": "imported", + "profile_catalog": [ + {"path": "profiles/github.yaml", "scope": "platform"} + ], + "providers": [ + {"name": "work-github", "profile_id": "github", "endpoint_resolution": "profile"} + ] + }] +} +``` + +Supply the selected policy and profile files locally. A profile uses the pinned +upstream shape: `id`, `endpoints`, `binaries`, and optional credential metadata, +resource version and annotations. This bundle describes a proposed local +resolution; it does not attest which profile a gateway resolved. Familiar +profile IDs are mutable content, with their scope, workspace, normalized content +digest and contributing rule keys recorded beside the composed policy. + +Selection follows global override, saved sandbox policy, then image policy. +An active global policy replaces the effective policy and suppresses provider +network layers. Removing it restores the saved/image policy and provider layers. +All declared inputs, including suppressed policies and unused catalog entries, +participate in Git comparison and receipt currency. At least a saved or image +policy must be supplied: runtime driver defaults are unresolved. Explicit absent +selection states prevent a missing context from becoming an inferred default. + +Without a global override, provider network rules are concatenated with the base. +Reserved `_provider_*` keys are rejected in authored composition inputs. Provider +names use upstream ASCII sanitization, with numeric suffixes for collisions; +no layer overwrites another. Workspace profiles override platform profiles of +the same ID only in their named workspace. Duplicate scope/ID pairs and +interceptor/imported ID collisions are incomplete inputs. `catalog_mode` must +name imported, interceptor or combined local inputs. Remote discovery, base-URL +overrides, endpointless profiles and unresolved provider context are unsupported. +`endpoint_resolution: "profile"` explicitly selects the supplied endpoint set. + +Network endpoint/binary reach and credential placement are separate facts. +Profiles may supply credential names, environment-variable names and placement +metadata, but never values, refresh tokens or runtime authorization claims. +Changes to credential placement produce an unproven authorization limitation +while retaining any independently established network expansion. + +The local subset is pinned to [upstream composition at v0.1.2](https://github.com/NVIDIA/OpenShell/blob/v0.1.2/crates/openshell-policy/src/compose.rs), +[policy selection](https://docs.nvidia.com/openshell/how-it-works/policies/overview) +and [profile resolution](https://docs.nvidia.com/openshell/how-it-works/providers/profiles). +A registration supports up to 16 compositions, 32 catalog entries and 32 +attachments per composition, with the shared 64-reference read budget and +1 MiB derived-input bound. Unsupported fields remain named coverage limits. + +Effective snapshots can carry optional metadata in a version 2 policy reference: + +```json +{"path": "exports/effective.yaml", "role": "effective_snapshot", + "snapshot": {"source": "operator export", "revision": "sandbox-42"}} +``` + +Metadata is supplied evidence, with `runtime_freshness_verified: false`. +Filesystem and Landlock fields are startup-bound, process identity is fixed +at sandbox creation, and network policy/middleware fields may update +dynamically. A composed proposal and an effective snapshot retain distinct roles. +Neither establishes installed policy or live workload behavior. diff --git a/llms-full.txt b/llms-full.txt index 174bcd1f..05bf8f0e 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -2399,7 +2399,7 @@ Downstream repos generated with - Current attestation schema: `0.5` — [`docs/attestation-schema.v0.5.json`](attestation-schema.v0.5.json) - Current registry schema: `0.4` — [`docs/registry-schema.v0.4.json`](registry-schema.v0.4.json) - Current org evidence bundle schema: `shipgate.org_evidence_bundle/v2` — [`docs/org-evidence-bundle-schema.v2.json`](org-evidence-bundle-schema.v2.json) -- Current host-grants inventory, baseline, and drift schemas: `0.8` — [`inventory`](host-grants-inventory-schema.v0.8.json), [`baseline`](host-grants-baseline-schema.v0.8.json), [`drift`](host-grants-drift-schema.v0.8.json). Version 0.8 adds selected OpenShell document facts; historical host schemas remain frozen. See [OpenShell support](openshell-support.md). +- Current host-grants inventory, baseline, and drift schemas: `0.9` — [`inventory`](host-grants-inventory-schema.v0.9.json), [`baseline`](host-grants-baseline-schema.v0.9.json), [`drift`](host-grants-drift-schema.v0.9.json). Version 0.9 adds explicit local OpenShell composition, provider profile provenance and effective-snapshot metadata; historical host schemas remain frozen. See [OpenShell support](openshell-support.md). - Current trigger catalog schema: `0.4` — [`docs/triggers.json`](triggers.json) - Current governance benchmark catalog schema: `0.2` — [`docs/governance-benchmark-catalog-schema.v0.2.json`](governance-benchmark-catalog-schema.v0.2.json) - Current governance benchmark result schema: `0.2` — [`docs/governance-benchmark-result-schema.v0.2.json`](governance-benchmark-result-schema.v0.2.json) diff --git a/scripts/generate_schemas.py b/scripts/generate_schemas.py index 89c9f696..a3e37453 100644 --- a/scripts/generate_schemas.py +++ b/scripts/generate_schemas.py @@ -51,13 +51,13 @@ - docs/registry-schema.v0.4.json (from agents_shipgate.schemas.registry. RegistryQueryResultV1) -- docs/host-grants-inventory-schema.v0.8.json +- docs/host-grants-inventory-schema.v0.9.json (from agents_shipgate.schemas.host_grants. - HostGrantsInventoryArtifactV8) -- docs/host-grants-baseline-schema.v0.8.json - (from HostGrantsBaselineArtifactV8) -- docs/host-grants-drift-schema.v0.8.json - (from HostGrantsDriftArtifactV8) + HostGrantsInventoryArtifactV9) +- docs/host-grants-baseline-schema.v0.9.json + (from HostGrantsBaselineArtifactV9) +- docs/host-grants-drift-schema.v0.9.json + (from HostGrantsDriftArtifactV9) - docs/capability-lock-schema.v0.8.json (from agents_shipgate.schemas.capabilities. CapabilityLockFileArtifactV1) @@ -2494,10 +2494,10 @@ def build_host_grants_inventory_schema() -> tuple[Path, str]: from agents_shipgate.schemas.host_grants import ( HOST_GRANTS_INVENTORY_SCHEMA_VERSION, - HostGrantsInventoryArtifactV8, + HostGrantsInventoryArtifactV9, ) - schema = HostGrantsInventoryArtifactV8.model_json_schema() + schema = HostGrantsInventoryArtifactV9.model_json_schema() minor = HOST_GRANTS_INVENTORY_SCHEMA_VERSION schema["$id"] = ( "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/" @@ -2518,10 +2518,10 @@ def build_host_grants_baseline_schema() -> tuple[Path, str]: from agents_shipgate.schemas.host_grants import ( HOST_GRANTS_BASELINE_SCHEMA_VERSION, - HostGrantsBaselineArtifactV8, + HostGrantsBaselineArtifactV9, ) - schema = HostGrantsBaselineArtifactV8.model_json_schema() + schema = HostGrantsBaselineArtifactV9.model_json_schema() minor = HOST_GRANTS_BASELINE_SCHEMA_VERSION schema["$id"] = ( "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/" @@ -2541,10 +2541,10 @@ def build_host_grants_drift_schema() -> tuple[Path, str]: from agents_shipgate.schemas.host_grants import ( HOST_GRANTS_DRIFT_SCHEMA_VERSION, - HostGrantsDriftArtifactV8, + HostGrantsDriftArtifactV9, ) - schema = HostGrantsDriftArtifactV8.model_json_schema() + schema = HostGrantsDriftArtifactV9.model_json_schema() minor = HOST_GRANTS_DRIFT_SCHEMA_VERSION schema["$id"] = ( "https://raw.githubusercontent.com/ThreeMoonsLab/agents-shipgate/" diff --git a/src/agents_shipgate/core/host_grants.py b/src/agents_shipgate/core/host_grants.py index bb36800a..5b32de92 100644 --- a/src/agents_shipgate/core/host_grants.py +++ b/src/agents_shipgate/core/host_grants.py @@ -106,8 +106,9 @@ HostGrantsBaselineV6, HostGrantsBaselineV7, HostGrantsBaselineV8, - HostGrantsDriftV8, - HostGrantsInventoryV8, + HostGrantsBaselineV9, + HostGrantsDriftV9, + HostGrantsInventoryV9, ) HOST_GRANTS_SCHEMA_VERSION = HOST_GRANTS_BASELINE_SCHEMA_VERSION @@ -3921,26 +3922,29 @@ def record(artifact: dict[str, Any]) -> None: selection = read_document(path, source, "openshell_selection", parse_selection, resolved_through) if selection is None: return - for reference in sorted(selection.policies, key=lambda item: item.path): + def read_selected(selected_path: str, artifact_kind: str, parser): if not budget.reserve(): - issues.append(_inventory_issue( - kind="unsupported", host="openshell", source=source, - message="OpenShell selection exceeds the workspace limit of 64 policy references", - blocking=True, - )) - break - policy_path = root / reference.path + raise OpenShellReadError("unsupported", "OpenShell selection exceeds the workspace limit of 64 policy references") + policy_path = root / selected_path hops: tuple[str, ...] = () reader = cache.reader_for(root) try: - kind = reader.directory_entry_kind(Path(reference.path)) + kind = reader.directory_entry_kind(Path(selected_path)) except (OSError, ValueError): kind = None if kind == "symlink": - resolution = _resolve_in_tree_link(reader, Path(reference.path)) + resolution = _resolve_in_tree_link(reader, Path(selected_path)) if resolution is not None and resolution[1] == "file": policy_path, hops = root / resolution[0], resolution[2] - policy = read_document(policy_path, reference.path, "openshell_policy", parse_policy, hops) + return read_document(policy_path, selected_path, artifact_kind, parser, hops) + + for reference in sorted(selection.policies, key=lambda item: item.path): + try: + policy = read_selected(reference.path, "openshell_policy", parse_policy) + except OpenShellReadError as exc: + issues.append(_inventory_issue(kind=exc.kind, host="openshell", source=source, + message=exc.message, blocking=True)) + break if policy is None: continue fields, defaults = policy_field_paths(policy) @@ -3950,14 +3954,48 @@ def record(artifact: dict[str, Any]) -> None: "policy": policy.model_dump(mode="json"), "defaulted_fields": defaults, "field_paths": fields, } + if getattr(reference, "snapshot", None) is not None: + facts["snapshot"] = reference.snapshot.model_dump(mode="json") grants.append({ - **_grant_base( - host="openshell", scope="repository", source=reference.path, - kind="openshell_policy", identity=source, config=facts, - access="unknown", risk="unknown", - ), + **_grant_base(host="openshell", scope="repository", source=reference.path, + kind="openshell_policy", identity=source, config=facts, access="unknown", risk="unknown"), "facts": facts, }) + for spec in getattr(selection, "compositions", []): + from agents_shipgate.core.openshell_composition import compose_local, composed_fields + + try: + # Discover and capture the entire explicit closure before selecting + # a layer. The first archive intentionally lacks dependency bytes; + # one missing input must not conceal later catalog references. + from agents_shipgate.core.openshell_composition import parse_profile + + inputs = {} + for role in ("global", "saved", "image"): + context = getattr(spec, role + "_policy") + if context.state == "selected": + inputs[(context.path, "openshell_policy")] = read_selected( + context.path, "openshell_policy", parse_policy, + ) + for reference in spec.profile_catalog: + inputs[(reference.path, "openshell_profile")] = read_selected( + reference.path, "openshell_profile", parse_profile, + ) + policy, provenance, credential_use = compose_local( + spec, lambda path, kind, parser, inputs=inputs: inputs[(path, kind)], parse_policy, + ) + facts = {"registration": public_host_path(source), "role": "composed", + "runtime_version": selection.runtime_version, "policy_schema_version": policy.version, + "policy": policy.model_dump(mode="json"), "composition": provenance, + "credential_use": credential_use, "field_paths": composed_fields(policy)} + if _openshell_public_value(facts) != facts: + raise OpenShellReadError("unsupported", "Composition facts cannot be compared after credential redaction") + grants.append({**_grant_base(host="openshell", scope="repository", source=source, + kind="openshell_policy", identity="composition:" + spec.name, + config=facts, access="unknown", risk="unknown"), "facts": facts}) + except OpenShellReadError as exc: + issues.append(_inventory_issue(kind=exc.kind, host="openshell", source=source, + message=exc.message, blocking=True)) def _openshell_public_value(value: Any, *, parent_key: str = "") -> Any: @@ -5482,7 +5520,7 @@ def note_unusable_selected_hooks(data: Any, *, source: str) -> None: if any(item["host"] == "openshell" for item in artifacts): excluded.extend([ - "OpenShell provider and gateway/global policy composition", + "OpenShell live gateway/provider catalog and inference-policy resolution", "OpenShell driver defaults, runtime-added filesystem baseline paths and live policy freshness", "OpenShell runtime validation, DNS resolution, executable identity and native policy proof", ]) @@ -5546,7 +5584,7 @@ def note_unusable_selected_hooks(data: Any, *, source: str) -> None: "static_analysis_only": True, "runtime_session_verified": False, } - inventory = HostGrantsInventoryV8.model_validate(payload).model_dump(mode="json") + inventory = HostGrantsInventoryV9.model_validate(payload).model_dump(mode="json") return HostBoundarySnapshot( inventory=inventory, cache=cache, input_failures=dict(cache.input_failures), plugin_reference_issue_ids=frozenset(plugin_reference_issue_ids), @@ -5585,7 +5623,7 @@ def host_audit_inventory( if snapshot is None: snapshot = build_host_boundary_snapshot(workspace, scope=scope, cache=cache) - inventory = HostGrantsInventoryV8.model_validate(snapshot.inventory) + inventory = HostGrantsInventoryV9.model_validate(snapshot.inventory) if inventory.scope != scope: raise ValueError( f"Host boundary snapshot scope {inventory.scope!r} does not match {scope!r}" @@ -5818,7 +5856,7 @@ def build_host_grants_baseline(inventory: dict[str, Any]) -> dict[str, Any]: "grants": [compared_grant(grant) for grant in normalized["grants"]], }, } - return HostGrantsBaselineV8.model_validate(payload).model_dump(mode="json") + return HostGrantsBaselineV9.model_validate(payload).model_dump(mode="json") def host_comparison_baseline(inventory: dict[str, Any]) -> dict[str, Any]: @@ -5877,7 +5915,7 @@ def load_host_grants_baseline_with_text( "and repair or replace it deliberately." ) return data, text - if version not in {"0.2", "0.3", "0.4", "0.5", "0.6", "0.7", HOST_GRANTS_BASELINE_SCHEMA_VERSION}: + if version not in {"0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8", HOST_GRANTS_BASELINE_SCHEMA_VERSION}: raise ValueError( f"Host-grants baseline {path} has unsupported schema version " f"{version!r}. A human must review migration or replacement." @@ -5886,7 +5924,7 @@ def load_host_grants_baseline_with_text( model = {"0.2": HostGrantsBaselineV2, "0.3": HostGrantsBaselineV3, "0.4": HostGrantsBaselineV4, "0.5": HostGrantsBaselineV5, "0.6": HostGrantsBaselineV6, "0.7": HostGrantsBaselineV7, - "0.8": HostGrantsBaselineV8}[version] + "0.8": HostGrantsBaselineV8, "0.9": HostGrantsBaselineV9}[version] parsed = model.model_validate(data).model_dump(mode="json") except ValidationError: return ( @@ -6713,7 +6751,7 @@ def _incomparable_payload( # and also route to a human before any first acknowledgement. "next_action": None, } - return HostGrantsDriftV8.model_validate(payload).model_dump(mode="json") + return HostGrantsDriftV9.model_validate(payload).model_dump(mode="json") #: Baseline versions a drift comparison reads as current. v0.5 only adds @@ -6725,11 +6763,11 @@ def _incomparable_payload( #: checkout refs (#823); the rules below narrow which older baselines that #: acceptance still covers. _COMPARABLE_BASELINE_SCHEMA_VERSIONS = frozenset( - {"0.4", "0.5", "0.6", "0.7", HOST_GRANTS_BASELINE_SCHEMA_VERSION} + {"0.4", "0.5", "0.6", "0.7", "0.8", HOST_GRANTS_BASELINE_SCHEMA_VERSION} ) #: Baselines without workflow grants retain the v0.6 replacement route (#819). -OVERWRITABLE_BASELINE_SCHEMA_VERSIONS = frozenset({"0.6", "0.7", HOST_GRANTS_BASELINE_SCHEMA_VERSION}) +OVERWRITABLE_BASELINE_SCHEMA_VERSIONS = frozenset({"0.6", "0.7", "0.8", HOST_GRANTS_BASELINE_SCHEMA_VERSION}) #: Baseline versions whose workflow grants never read step action references #: (#771). Such a grant's missing ``step_actions`` is not evidence that no @@ -6843,7 +6881,7 @@ def _comparable_drift_payload( "incomparable_reasons": [], "next_action": None, } - return HostGrantsDriftV8.model_validate(payload).model_dump(mode="json") + return HostGrantsDriftV9.model_validate(payload).model_dump(mode="json") def build_host_comparison_payload( diff --git a/src/agents_shipgate/core/openshell.py b/src/agents_shipgate/core/openshell.py index dfe835c7..3cbb91af 100644 --- a/src/agents_shipgate/core/openshell.py +++ b/src/agents_shipgate/core/openshell.py @@ -118,6 +118,10 @@ def parse_selection(text: str) -> OpenShellSelection: json.loads(text) except (ValueError, RecursionError) as exc: raise OpenShellReadError("parse_failed", "OpenShell registration must be JSON") from exc + if data.get("version") == 2: + from agents_shipgate.schemas.openshell_composition import OpenShellSelectionV2 + + return _validate(OpenShellSelectionV2, data) return _validate(OpenShellSelection, data) diff --git a/src/agents_shipgate/core/openshell_compare.py b/src/agents_shipgate/core/openshell_compare.py index 330cd4fe..6c28c6cd 100644 --- a/src/agents_shipgate/core/openshell_compare.py +++ b/src/agents_shipgate/core/openshell_compare.py @@ -73,6 +73,10 @@ def compare_openshell_grants(before: dict | None, after: dict | None) -> OpenShe widened: list[str] = [] narrowed: list[str] = [] limits: list[str] = [] + if _canonical(left.get("credential_use", [])) != _canonical(right.get("credential_use", [])): + limits.append("credential-use placement or profile resolution changed; authorization is unproven") + if left.get("composition", {}).get("workspace") != right.get("composition", {}).get("workspace"): + limits.append("composition workspace resolution changed") def record(label: str, grows: bool, shrinks: bool) -> None: if grows: diff --git a/src/agents_shipgate/core/openshell_composition.py b/src/agents_shipgate/core/openshell_composition.py new file mode 100644 index 00000000..72e3c452 --- /dev/null +++ b/src/agents_shipgate/core/openshell_composition.py @@ -0,0 +1,131 @@ +"""Bounded local reproduction of OpenShell v0.1.2 selection/composition.""" +from __future__ import annotations + +import hashlib +import json +from collections.abc import Callable +from typing import Any + +from pydantic import ValidationError + +from agents_shipgate.core.openshell import OpenShellReadError, load_document, policy_field_paths +from agents_shipgate.schemas.openshell import OpenShellPolicy +from agents_shipgate.schemas.openshell_composition import ( + CredentialEndpointFacts, + LocalComposition, + OpenShellProviderProfile, +) + + +def parse_profile(text: str) -> OpenShellProviderProfile: + try: + return OpenShellProviderProfile.model_validate(load_document(text)) + except ValidationError as exc: + raise OpenShellReadError("unsupported", "Provider profile contains unsupported fields, types or constraints") from exc + + +def digest(model) -> str: + raw = json.dumps(model.model_dump(mode="json"), sort_keys=True, separators=(",", ":")) + return "sha256:" + hashlib.sha256(raw.encode()).hexdigest() + + +def compose_local(spec: LocalComposition, read: Callable[[str, str, Callable], Any], + parse_policy: Callable) -> tuple[OpenShellPolicy, dict, list]: + policies = {} + contributors = [] + for role in ("global", "saved", "image"): + context = getattr(spec, role + "_policy") + if context.state == "absent": + continue + policy = read(context.path, "openshell_policy", parse_policy) + if policy is None: + raise OpenShellReadError("unsupported", "A selected composition policy could not be read") + if any(key.startswith("_provider_") for key in policy.network_policies): + raise OpenShellReadError("unsupported", "Authored composition inputs contain reserved provider rule names") + policies[role] = policy + contributors.append({"path": context.path, "role": role, "content_sha256": digest(policy), "selected": False}) + # Even a global override retains the saved/image inputs for restoration. + underlying = next((role for role in ("saved", "image") if role in policies), None) + if underlying is None: + raise OpenShellReadError("unsupported", "Saved/image selection is absent; driver default policy is unresolved") + selected = "global" if "global" in policies else underlying + for item in contributors: + item["selected"] = item["role"] == selected + entries = [] + identities = set() + imported_ids, interceptor_ids = set(), set() + for reference in spec.profile_catalog: + profile = read(reference.path, "openshell_profile", parse_profile) + if profile is None: + raise OpenShellReadError("unsupported", "A selected profile catalog input could not be read") + identity = (reference.scope, reference.workspace, profile.id) + if identity in identities: + raise OpenShellReadError("unsupported", "Profile catalog contains a duplicate scope and ID") + identities.add(identity) + (interceptor_ids if reference.scope == "interceptor" else imported_ids).add(profile.id) + entries.append((reference, profile)) + if imported_ids & interceptor_ids: + raise OpenShellReadError("unsupported", "Interceptor and imported profile IDs collide") + if ((spec.catalog_mode == "imported" and interceptor_ids) + or (spec.catalog_mode == "interceptor" and imported_ids)): + raise OpenShellReadError("unsupported", "Profile catalog source context contradicts its declared mode") + effective = policies[selected].model_copy(deep=True) + credential_use = [] + derived_bytes = len(json.dumps(effective.model_dump(mode="json")).encode()) + used = set() + for attachment in spec.providers: + candidates = [(ref, profile) for ref, profile in entries + if profile.id == attachment.profile_id and ( + ref.scope != "workspace" or ref.workspace == spec.workspace)] + candidates.sort(key=lambda item: 0 if item[0].scope == "workspace" else 1) + if not candidates: + raise OpenShellReadError("unsupported", "An attached provider has no profile in the selected resolution scope") + reference, profile = candidates[0] + if not profile.endpoints or not profile.binaries: + raise OpenShellReadError("unsupported", "Endpointless or binary-free provider composition requires unresolved runtime context") + derived_bytes += len(json.dumps(profile.model_dump(mode="json")).encode()) + if derived_bytes > 1024 * 1024: + raise OpenShellReadError("unsupported", "Composition exceeds the 1 MiB derived-input bound") + used.add((reference.scope, reference.workspace, profile.id)) + name = "".join(char.lower() if char.isascii() and (char.isalnum() or char == "_") else "_" + for char in attachment.name).strip("_") or "unnamed" + preferred = "_provider_" + name + key, suffix = preferred, 2 + while key in effective.network_policies: + key = preferred + "_" + str(suffix) + suffix += 1 + if selected != "global": + from agents_shipgate.schemas.openshell import OpenShellBinary, OpenShellNetworkRule + + effective.network_policies[key] = OpenShellNetworkRule(name=key, + endpoints=[item.model_copy(deep=True) for item in profile.endpoints], + binaries=[OpenShellBinary(path=path) for path in profile.binaries]) + contributors.append({"path": reference.path, "role": "profile", "content_sha256": digest(profile), + "selected": selected != "global", "profile_id": profile.id, "scope": reference.scope, + "workspace": reference.workspace, "resource_version": profile.resource_version, + "provider": attachment.name, "rule_key": key if selected != "global" else ""}) + if profile.credentials: + fields = CredentialEndpointFacts.model_fields + credential_use.append({"provider": attachment.name, "profile_id": profile.id, + "scope": reference.scope, "workspace": reference.workspace, + "credentials": [item.model_dump(mode="json") for item in profile.credentials], + "endpoints": [{key: value for key, value in item.model_dump(mode="json").items() + if key in fields} for item in profile.endpoints]}) + for reference, profile in entries: + if (reference.scope, reference.workspace, profile.id) not in used: + contributors.append({"path": reference.path, "role": "profile", "content_sha256": digest(profile), + "selected": False, "profile_id": profile.id, "scope": reference.scope, + "workspace": reference.workspace, "resource_version": profile.resource_version}) + provenance = {"name": spec.name, "workspace": spec.workspace, + "selected_policy": selected, "contributors": contributors} + try: + effective = OpenShellPolicy.model_validate(effective.model_dump(mode="json")) + except ValidationError as exc: + raise OpenShellReadError("unsupported", "Composed policy exceeds the supported static constraints") from exc + return effective, provenance, sorted(credential_use, key=lambda item: item["provider"]) + + +def composed_fields(policy): + # Generated policy spelling is derived; the authored sources remain in provenance. + fields, _ = policy_field_paths(policy) + return fields diff --git a/src/agents_shipgate/schemas/host_grants.py b/src/agents_shipgate/schemas/host_grants.py index f22e5067..a36f7efe 100644 --- a/src/agents_shipgate/schemas/host_grants.py +++ b/src/agents_shipgate/schemas/host_grants.py @@ -6,10 +6,14 @@ from agents_shipgate.schemas.instruction_structure import InstructionStructureEvidence from agents_shipgate.schemas.openshell import OpenShellPolicyFacts +from agents_shipgate.schemas.openshell_composition import ( + OpenShellComposedPolicyFacts, + OpenShellSnapshotFactsV2, +) -HOST_GRANTS_INVENTORY_SCHEMA_VERSION = "0.8" -HOST_GRANTS_BASELINE_SCHEMA_VERSION = "0.8" -HOST_GRANTS_DRIFT_SCHEMA_VERSION = "0.8" +HOST_GRANTS_INVENTORY_SCHEMA_VERSION = "0.9" +HOST_GRANTS_BASELINE_SCHEMA_VERSION = "0.9" +HOST_GRANTS_DRIFT_SCHEMA_VERSION = "0.9" HostName = Literal["codex", "claude-code", "cursor", "vscode", "github"] HostGrantScope = Literal["repository", "local_static"] @@ -1145,4 +1149,55 @@ class HostGrantsDriftArtifactV8(RootModel[HostGrantsDriftV8]): root: HostGrantsDriftV8 +class HostOpenShellPolicyGrantV9(HostOpenShellPolicyGrantV8): + facts: OpenShellPolicyFacts | OpenShellComposedPolicyFacts | OpenShellSnapshotFactsV2 + + +HostGrantV9 = Annotated[HostGrantV7 | HostOpenShellPolicyGrantV9, Field(discriminator="kind")] +HostBaselineGrantV9 = Annotated[HostBaselineGrantV7 | HostOpenShellPolicyGrantV9, Field(discriminator="kind")] + + +class HostArtifactV9(HostArtifactV8): + kind: Literal["config", "mcp", "hooks", "workflow", "instructions", "requirements", "hook_script", + "openshell_selection", "openshell_policy", "openshell_profile"] + + +class HostArtifactChangeV9(HostArtifactChangeV8): + baseline: HostArtifactV9 | None = None + current: HostArtifactV9 | None = None + + +class HostGrantsInventoryV9(HostGrantsInventoryV8): + host_grants_inventory_schema_version: Literal["0.9"] = "0.9" + grants: list[HostGrantV9] = Field(default_factory=list) + artifacts: list[HostArtifactV9] = Field(default_factory=list) + + +class HostGrantsNormalizedSnapshotV9(HostGrantsNormalizedSnapshotV8): + grants: list[HostBaselineGrantV9] = Field(default_factory=list) + artifacts: list[HostArtifactV9] = Field(default_factory=list) + + +class HostGrantsBaselineV9(HostGrantsBaselineV8): + host_grants_schema_version: Literal["0.9"] = "0.9" + inventory: HostGrantsNormalizedSnapshotV9 + + +class HostGrantsDriftV9(HostGrantsDriftV8): + host_grants_schema_version: Literal["0.9"] = "0.9" + artifact_changes: list[HostArtifactChangeV9] = Field(default_factory=list) + + +class HostGrantsInventoryArtifactV9(RootModel[HostGrantsInventoryV9]): + root: HostGrantsInventoryV9 + + +class HostGrantsBaselineArtifactV9(RootModel[HostGrantsBaselineV9]): + root: HostGrantsBaselineV9 + + +class HostGrantsDriftArtifactV9(RootModel[HostGrantsDriftV9]): + root: HostGrantsDriftV9 + + __all__ = [name for name in globals() if name.startswith("Host") or name.startswith("HOST_")] diff --git a/src/agents_shipgate/schemas/openshell_composition.py b/src/agents_shipgate/schemas/openshell_composition.py new file mode 100644 index 00000000..a24adf5f --- /dev/null +++ b/src/agents_shipgate/schemas/openshell_composition.py @@ -0,0 +1,211 @@ +"""Versioned local composition inputs; no deployed or credential-value claims.""" +from __future__ import annotations + +import re +from typing import Literal + +from pydantic import Field, model_validator + +from agents_shipgate.schemas.openshell import ( + OpenShellEndpoint, + OpenShellObject, + OpenShellPolicyFacts, + OpenShellPolicyReference, +) + + +def local(path: str) -> str: + OpenShellPolicyReference(path=path, role="authored") + return path + + +class LocalReference(OpenShellObject): + path: str + + @model_validator(mode="after") + def contained(self): + local(self.path) + return self + + +class PolicySelectionContext(OpenShellObject): + state: Literal["absent", "selected"] + path: str = "" + + @model_validator(mode="after") + def selected_path(self): + if self.state == "selected": + local(self.path) + elif self.path: + raise ValueError("absent selection cannot name an input") + return self + + +class ProfileReference(LocalReference): + scope: Literal["platform", "workspace", "interceptor"] + workspace: str = "" + + @model_validator(mode="after") + def scope_identity(self): + if (self.scope == "workspace") != bool(self.workspace): + raise ValueError("workspace scope must name its workspace") + return self + + +class ProviderAttachment(OpenShellObject): + name: str = Field(min_length=1, max_length=128) + profile_id: str = Field(pattern=r"^[a-z0-9]+(?:-[a-z0-9]+)*$") + # A missing base-URL context cannot be treated as the profile's endpoints. + endpoint_resolution: Literal["profile"] + + +class LocalComposition(OpenShellObject): + name: str = Field(pattern=r"^[a-z0-9]+(?:-[a-z0-9]+)*$") + workspace: str = Field(min_length=1, max_length=128) + global_policy: PolicySelectionContext + saved_policy: PolicySelectionContext + image_policy: PolicySelectionContext + catalog_mode: Literal["imported", "interceptor", "combined"] + profile_catalog: list[ProfileReference] = Field(max_length=32) + providers: list[ProviderAttachment] = Field(max_length=32) + + @model_validator(mode="after") + def distinct_attachments(self): + names = [provider.name for provider in self.providers] + if len(set(names)) != len(names): + raise ValueError("provider instance names must be distinct") + paths = [item.path for item in self.profile_catalog] + if len(set(paths)) != len(paths): + raise ValueError("profile catalog paths must be distinct") + return self + + +class SnapshotMetadata(OpenShellObject): + source: str = Field(default="", max_length=1024) + revision: str = Field(default="", max_length=256) + + +class PolicyReferenceV2(OpenShellPolicyReference): + snapshot: SnapshotMetadata | None = None + + @model_validator(mode="after") + def snapshot_role(self): + if self.snapshot is not None and self.role != "effective_snapshot": + raise ValueError("snapshot metadata requires an effective snapshot") + return self + + +class OpenShellSelectionV2(OpenShellObject): + version: Literal[2] + runtime_version: Literal["0.1.2"] + policies: list[PolicyReferenceV2] = Field(default_factory=list, max_length=64) + compositions: list[LocalComposition] = Field(default_factory=list, max_length=16) + + @model_validator(mode="after") + def distinct_inputs(self): + if not self.policies and not self.compositions: + raise ValueError("select at least one policy or composition") + for values in ([item.path for item in self.policies], [item.name for item in self.compositions]): + if len(set(values)) != len(values): + raise ValueError("duplicate policy or composition identity") + return self + + +class StaticCredential(OpenShellObject): + name: str = Field(min_length=1, max_length=128) + description: str = "" + env_vars: list[str] = Field(default_factory=list, max_length=32) + required: bool = False + auth_style: Literal["", "basic", "bearer", "header", "query", "path"] = "" + header_name: str = "" + query_param: str = "" + path_template: str = "" + + +class ProfileDiscovery(OpenShellObject): + credentials: list[str] = Field(default_factory=list, max_length=32) + + +class OpenShellProviderProfile(OpenShellObject): + id: str = Field(pattern=r"^[a-z0-9]+(?:-[a-z0-9]+)*$") + resource_version: int = Field(default=0, ge=0) + annotations: dict[str, str] = Field(default_factory=dict, max_length=32) + display_name: str = "" + description: str = "" + category: Literal["other", "inference", "agent", "source_control", "messaging", "data", "knowledge"] = "other" + inference_capable: bool = False + credentials: list[StaticCredential] = Field(default_factory=list, max_length=32) + discovery: ProfileDiscovery | None = None + endpoints: list[OpenShellEndpoint] = Field(max_length=128) + binaries: list[str] = Field(max_length=128) + + @model_validator(mode="after") + def credential_names(self): + names = [item.name for item in self.credentials] + if len(set(names)) != len(names): + raise ValueError("duplicate credential name") + if self.discovery and not set(self.discovery.credentials) <= set(names): + raise ValueError("unknown discovery credential") + if any(not path.startswith("/") or not re.fullmatch(r"[^\x00-\x1f]+", path) for path in self.binaries): + raise ValueError("invalid binary path") + return self + + +class CompositionContributor(OpenShellObject): + path: str + role: Literal["global", "saved", "image", "profile"] + content_sha256: str = Field(pattern=r"^sha256:[a-f0-9]{64}$") + selected: bool + profile_id: str = "" + scope: Literal["", "platform", "workspace", "interceptor"] = "" + workspace: str = "" + resource_version: int = 0 + provider: str = "" + rule_key: str = "" + + +class CompositionProvenance(OpenShellObject): + name: str + workspace: str + selected_policy: Literal["global", "saved", "image"] + contributors: list[CompositionContributor] + startup_bound_fields: list[str] = Field(default_factory=lambda: ["filesystem_policy", "landlock"]) + creation_bound_fields: list[str] = Field(default_factory=lambda: ["process"]) + dynamic_fields: list[str] = Field(default_factory=lambda: ["network_policies", "network_middlewares"]) + + +class CredentialEndpointFacts(OpenShellObject): + host: str + port: int + ports: list[int] + path: str + tls: str + allow_uninspected_credentials: bool + websocket_credential_rewrite: bool + request_body_credential_rewrite: bool + + +class CredentialUseFacts(OpenShellObject): + provider: str + profile_id: str + scope: Literal["platform", "workspace", "interceptor"] + workspace: str + credentials: list[StaticCredential] + # These endpoints select credential placement; binaries select network reach. + endpoints: list[CredentialEndpointFacts] + credential_values_read: Literal[False] = False + runtime_authorization_verified: Literal[False] = False + + +class OpenShellComposedPolicyFacts(OpenShellPolicyFacts): + role: Literal["composed"] = "composed" + composition: CompositionProvenance + credential_use: list[CredentialUseFacts] + + +class OpenShellSnapshotFactsV2(OpenShellPolicyFacts): + role: Literal["effective_snapshot"] = "effective_snapshot" + snapshot: SnapshotMetadata + startup_bound_fields: list[str] = Field(default_factory=lambda: ["filesystem_policy", "landlock"]) + creation_bound_fields: list[str] = Field(default_factory=lambda: ["process"]) + dynamic_fields: list[str] = Field(default_factory=lambda: ["network_policies", "network_middlewares"]) diff --git a/tests/test_agent_instructions_apply.py b/tests/test_agent_instructions_apply.py index 2bf03e5a..aa5600f1 100644 --- a/tests/test_agent_instructions_apply.py +++ b/tests/test_agent_instructions_apply.py @@ -205,9 +205,9 @@ def test_local_contract_renderer_has_required_fields() -> None: assert payload["registry_schema_version"] == "0.4" assert payload["org_evidence_bundle_schema_version"] == ("shipgate.org_evidence_bundle/v2") assert payload["agent_boundary_result_schema_version"] == ("shipgate.agent_boundary_result/v3") - assert payload["host_grants_inventory_schema_version"] == "0.8" - assert payload["host_grants_baseline_schema_version"] == "0.8" - assert payload["host_grants_drift_schema_version"] == "0.8" + assert payload["host_grants_inventory_schema_version"] == "0.9" + assert payload["host_grants_baseline_schema_version"] == "0.9" + assert payload["host_grants_drift_schema_version"] == "0.9" assert payload["trigger_catalog_schema_version"] == "0.4" assert payload["gating_signal"] == "release_decision.decision" assert payload["default_paths"]["local_contract"] == ".shipgate/agent-contract.json" diff --git a/tests/test_agent_instructions_renderers.py b/tests/test_agent_instructions_renderers.py index 73c19578..734ae940 100644 --- a/tests/test_agent_instructions_renderers.py +++ b/tests/test_agent_instructions_renderers.py @@ -220,9 +220,9 @@ def test_local_contract_renderer_exposes_agent_operational_fields() -> None: assert payload["attestation_schema_version"] == "0.5" assert payload["registry_schema_version"] == "0.4" assert payload["org_evidence_bundle_schema_version"] == ("shipgate.org_evidence_bundle/v2") - assert payload["host_grants_inventory_schema_version"] == "0.8" - assert payload["host_grants_baseline_schema_version"] == "0.8" - assert payload["host_grants_drift_schema_version"] == "0.8" + assert payload["host_grants_inventory_schema_version"] == "0.9" + assert payload["host_grants_baseline_schema_version"] == "0.9" + assert payload["host_grants_drift_schema_version"] == "0.9" assert payload["trigger_catalog_schema_version"] == "0.4" assert payload["agent_result_control_fields"] == [ "decision", diff --git a/tests/test_hook_mcp_detail_fields.py b/tests/test_hook_mcp_detail_fields.py index 0ae76096..9acf224d 100644 --- a/tests/test_hook_mcp_detail_fields.py +++ b/tests/test_hook_mcp_detail_fields.py @@ -234,7 +234,7 @@ def test_the_grants_publish_the_detail_the_rows_render(tmp_path: Path) -> None: assert baseline["inventory"]["grants"] == [compared_grant(grant) for grant in inventory["grants"]] drift = build_host_drift_payload(baseline=baseline, inventory=inventory, baseline_file="b.json") for name, payload in (("inventory", inventory), ("baseline", baseline), ("drift", drift)): - schema = json.loads((ROOT / f"docs/host-grants-{name}-schema.v0.8.json").read_text()) + schema = json.loads((ROOT / f"docs/host-grants-{name}-schema.v0.9.json").read_text()) Draft202012Validator(schema).validate(payload) @@ -1227,7 +1227,7 @@ def test_a_0_6_baseline_stays_comparable_and_may_be_re_saved(tmp_path: Path) -> saved = json.loads(_invoke(["audit", "--host", "--workspace", str(root), "--save-baseline", "--json"])) assert saved["status"] == "updated" resaved = json.loads(path.read_text()) - assert resaved["host_grants_schema_version"] == "0.8" + assert resaved["host_grants_schema_version"] == "0.9" # Re-saving records the current comparison facts, the unresolved # reference's limit among them; nothing else moved. assert resaved == build_host_grants_baseline(_inventory(root)) @@ -1318,7 +1318,7 @@ def test_a_local_static_baseline_holds_no_home_directory_detail( for fact in ("canary", "homematcher", "homepkg", kinds["hook"]["handlers"][0]["command"]["sha256"]): assert fact not in text baseline = json.loads(text) - assert baseline["host_grants_schema_version"] == "0.8" + assert baseline["host_grants_schema_version"] == "0.9" assert _saved_detail(baseline) == [] # It still acknowledges both grants, and the next drift compares as before. assert sorted(grant["kind"] for grant in baseline["inventory"]["grants"]) == ["hook", "mcp_server"] diff --git a/tests/test_host_audit.py b/tests/test_host_audit.py index 5f3ecb5e..942b4af0 100644 --- a/tests/test_host_audit.py +++ b/tests/test_host_audit.py @@ -30,10 +30,10 @@ load_host_grants_baseline, ) from agents_shipgate.schemas.host_grants import ( - HostGrantsBaselineV8, - HostGrantsDriftV8, + HostGrantsBaselineV9, + HostGrantsDriftV9, HostGrantsInventoryArtifactV4, - HostGrantsInventoryV8, + HostGrantsInventoryV9, ) runner = CliRunner() @@ -170,8 +170,8 @@ def _drift_json(tmp_path: Path, *extra: str) -> tuple[int, dict]: def test_inventory_v02_collects_typed_multi_host_grants(tmp_path: Path) -> None: inventory = host_audit_inventory(_seed_workspace(tmp_path)) - assert inventory["host_grants_inventory_schema_version"] == "0.8" - HostGrantsInventoryV8.model_validate(inventory) + assert inventory["host_grants_inventory_schema_version"] == "0.9" + HostGrantsInventoryV9.model_validate(inventory) assert inventory["scope"] == "repository" assert inventory["static_analysis_only"] is True assert inventory["runtime_session_verified"] is False @@ -749,8 +749,8 @@ def test_v02_baseline_is_typed_portable_redacted_and_idempotent(tmp_path: Path) _seed_workspace(tmp_path) baseline_path = _save_baseline(tmp_path) payload = json.loads(baseline_path.read_text(encoding="utf-8")) - HostGrantsBaselineV8.model_validate(payload) - assert payload["host_grants_schema_version"] == "0.8" + HostGrantsBaselineV9.model_validate(payload) + assert payload["host_grants_schema_version"] == "0.9" assert payload["scope"] == "repository" assert "workspace" not in payload["inventory"] assert payload["inventory"]["artifacts"] @@ -956,7 +956,7 @@ def test_clean_and_changed_v02_drift(tmp_path: Path) -> None: _save_baseline(tmp_path) code, clean = _drift_json(tmp_path) assert code == 0 - HostGrantsDriftV8.model_validate(clean) + HostGrantsDriftV9.model_validate(clean) assert clean["comparison_status"] == "comparable" assert clean["has_drift"] is False assert clean["baseline_sha256"] == clean["current_sha256"] @@ -1213,14 +1213,14 @@ def test_legacy_v01_baseline_is_incomparable_advisory_and_strict_20(tmp_path: Pa inventory=host_audit_inventory(tmp_path), baseline_file=".agents-shipgate/host-grants.json", ) - HostGrantsDriftV8.model_validate(shared) + HostGrantsDriftV9.model_validate(shared) assert shared["comparison_status"] == "incomparable" assert shared["next_action"] is None assert "--save-baseline" not in json.dumps(shared) code, payload = _drift_json(tmp_path) assert code == 0 - HostGrantsDriftV8.model_validate(payload) + HostGrantsDriftV9.model_validate(payload) assert payload["comparison_status"] == "incomparable" assert payload["has_drift"] is None assert "baseline_schema_v0.1" in payload["incomparable_reasons"][0] @@ -1272,7 +1272,7 @@ def test_malformed_nested_v02_baseline_is_incomparable_not_a_crash(tmp_path: Pat ) code, payload = _drift_json(tmp_path) assert code == 0 - HostGrantsDriftV8.model_validate(payload) + HostGrantsDriftV9.model_validate(payload) assert payload["comparison_status"] == "incomparable" assert payload["has_drift"] is None assert payload["incomparable_reasons"] == ["malformed_v0.2_baseline"] @@ -1629,9 +1629,9 @@ def denied_read_text( def test_generated_models_reject_unknown_fields_and_invalid_literals(tmp_path: Path) -> None: payload = host_audit_inventory(tmp_path) with pytest.raises(ValidationError): - HostGrantsInventoryV8.model_validate({**payload, "legacy_parse_warnings": []}) + HostGrantsInventoryV9.model_validate({**payload, "legacy_parse_warnings": []}) with pytest.raises(ValidationError): - HostGrantsInventoryV8.model_validate({**payload, "scope": "runtime"}) + HostGrantsInventoryV9.model_validate({**payload, "scope": "runtime"}) def test_inventory_schema_uses_discriminated_typed_grants() -> None: diff --git a/tests/test_host_input_recovery.py b/tests/test_host_input_recovery.py index df153e8d..219ef5fc 100644 --- a/tests/test_host_input_recovery.py +++ b/tests/test_host_input_recovery.py @@ -303,6 +303,6 @@ def fail(self): snapshot = build_host_boundary_snapshot(tmp_path) for name, payload in ( ("agent-boundary-result-schema.v3.json", _result(tmp_path, snapshot)), - ("host-grants-inventory-schema.v0.8.json", snapshot.inventory), + ("host-grants-inventory-schema.v0.9.json", snapshot.inventory), ): jsonschema.validate(payload, json.loads((Path("docs") / name).read_text())) diff --git a/tests/test_instruction_structure_contracts.py b/tests/test_instruction_structure_contracts.py index 3c3fee1b..3cd2a1da 100644 --- a/tests/test_instruction_structure_contracts.py +++ b/tests/test_instruction_structure_contracts.py @@ -49,7 +49,7 @@ def test_old_models_reject_structural_claims_and_old_baseline_is_not_restamped(r assert drift["has_drift"] is None assert "baseline_instruction_structure_unavailable" in drift["incomparable_reasons"] assert path.read_bytes() == captured - schema = json.loads((ROOT / "docs/host-grants-inventory-schema.v0.8.json").read_text()) + schema = json.loads((ROOT / "docs/host-grants-inventory-schema.v0.9.json").read_text()) Draft202012Validator(schema).validate(inventory) diff --git a/tests/test_local_contract.py b/tests/test_local_contract.py index b0eb876c..ebf690c2 100644 --- a/tests/test_local_contract.py +++ b/tests/test_local_contract.py @@ -156,9 +156,9 @@ def test_local_agent_contract_is_minimal_agent_operational_payload() -> None: assert payload["attestation_schema_version"] == "0.5" assert payload["registry_schema_version"] == "0.4" assert payload["org_evidence_bundle_schema_version"] == ("shipgate.org_evidence_bundle/v2") - assert payload["host_grants_inventory_schema_version"] == "0.8" - assert payload["host_grants_baseline_schema_version"] == "0.8" - assert payload["host_grants_drift_schema_version"] == "0.8" + assert payload["host_grants_inventory_schema_version"] == "0.9" + assert payload["host_grants_baseline_schema_version"] == "0.9" + assert payload["host_grants_drift_schema_version"] == "0.9" assert payload["trigger_catalog_schema_version"] == "0.4" assert payload["agent_result_schema_version"] == "agent_result_v3" assert payload["agent_result_schema_path"] == "docs/agent-result-schema.v3.json" diff --git a/tests/test_openshell_composition.py b/tests/test_openshell_composition.py new file mode 100644 index 00000000..6a01ad22 --- /dev/null +++ b/tests/test_openshell_composition.py @@ -0,0 +1,202 @@ +from __future__ import annotations + +import json +from copy import deepcopy + +import pytest +from jsonschema import Draft202012Validator +from test_current_control import _live, _verify +from test_current_control import repo as repo # noqa: F401 +from test_openshell_inputs import POLICY, REGISTRATION, comparison +from test_openshell_routes import invoke +from test_partial_host_comparison import _repository + +from agents_shipgate.core.current_control import CurrentControlUnavailable, read_current_control +from agents_shipgate.core.host_grants import ( + build_host_boundary_snapshot, + host_audit_inventory, + inventory_is_complete, +) +from agents_shipgate.core.openshell_compare import compare_openshell_grants + + +def profile(host="api.provider.example", *, access="read-only"): + return {"id": "github", "resource_version": 1, + "endpoints": [{"host": host, "port": 443, "protocol": "rest", + "access": access, "enforcement": "enforce"}], + "binaries": ["/usr/bin/client"]} + + +def selection(*, providers=True): + return {"version": 2, "runtime_version": "0.1.2", "compositions": [{ + "name": "worker", "workspace": "team-a", "catalog_mode": "imported", + "global_policy": {"state": "absent"}, "image_policy": {"state": "absent"}, + "saved_policy": {"state": "selected", "path": "base.policy"}, + "profile_catalog": [{"path": "profiles/github.profile", "scope": "platform"}], + "providers": [{"name": "Work GitHub!", "profile_id": "github", "endpoint_resolution": "profile"}] if providers else [], + }]} + + +def files(selected=None, provider=None): + return {REGISTRATION: selected or selection(), "base.policy": POLICY, + "profiles/github.profile": provider or profile()} + + +def inventory(root, contents): + for path, content in contents.items(): + target = root / path + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(content if isinstance(content, str) else json.dumps(content)) + return host_audit_inventory(root) + + +def grant(inv): + row, = [row for row in inv["grants"] if row["kind"] == "openshell_policy"] + return row + + +def test_unchanged_base_changed_provider_routes_composed_authority(tmp_path): + root = _repository(tmp_path, files(), {"profiles/github.profile": profile(access="read-write")}) + result = comparison(root, head="HEAD") + assert result.comparison_status == "comparable" + row, = [row for row in result.rows if row.subject.startswith("openshell")] + assert row.direction == "widened" and row.expands + checked = invoke(root, "check", "--base", "main", "--head", "HEAD", "--format", "agent-boundary-json") + assert any(row["evidence"].get("direction") == "widened" for row in checked["violations"]) + assert checked["control"]["permissions"]["report_complete"] is False + + +@pytest.mark.parametrize("attach", [True, False]) +def test_provider_attach_detach_preserves_base_and_names_direction(tmp_path, attach): + before, after = selection(providers=not attach), selection(providers=attach) + root = _repository(tmp_path, files(before), {REGISTRATION: after}) + result = comparison(root, head="HEAD") + assert any(row.direction == ("widened" if attach else "narrowed") for row in result.rows) + + +def test_workspace_override_and_content_identity_are_explicit(tmp_path): + selected = selection() + selected["compositions"][0]["profile_catalog"].append({"path": "profiles/local", "scope": "workspace", "workspace": "team-a"}) + inv = inventory(tmp_path, {**files(selected), "profiles/local": profile("local.example")}) + assert inventory_is_complete(inv) + facts = grant(inv)["facts"] + endpoints = facts["policy"]["network_policies"]["_provider_work_github"]["endpoints"] + assert endpoints[0]["host"] == "local.example" + contributors = facts["composition"]["contributors"] + local, = [item for item in contributors if item["scope"] == "workspace"] + platform, = [item for item in contributors if item["scope"] == "platform"] + assert local["selected"] and not platform["selected"] + assert local["profile_id"] == platform["profile_id"] == "github" + assert local["content_sha256"] != platform["content_sha256"] + + +def test_provider_name_collisions_append_without_overwriting(tmp_path): + selected = selection() + spec = selected["compositions"][0] + for name in ("work-github", "K", "..."): + spec["providers"].append({"name": name, "profile_id": "github", "endpoint_resolution": "profile"}) + inv = inventory(tmp_path, files(selected)) + assert inventory_is_complete(inv) + facts = grant(inv)["facts"] + assert set(facts["policy"]["network_policies"]) == {"api", "_provider_work_github", "_provider_work_github_2", "_provider_unnamed", "_provider_unnamed_2"} + assert len([item for item in facts["composition"]["contributors"] if item["provider"]]) == 4 + + +@pytest.mark.parametrize("change", ["activate", "remove", "replace"]) +def test_global_replaces_and_removal_restores_provider_and_saved_policy(tmp_path, change): + selected = selection() + active = deepcopy(selected) + active["compositions"][0]["global_policy"] = {"state": "selected", "path": "global.policy"} + global_policy = POLICY.replace("api.example.com", "global.example") + before = selected if change == "activate" else active + updates = {REGISTRATION: selected if change == "remove" else active} + if change == "replace": + updates["global.policy"] = global_policy.replace("global.example", "replacement.example") + root = _repository(tmp_path, {**files(before), "global.policy": global_policy}, updates) + result = comparison(root, head="HEAD") + assert any(row.expands for row in result.rows) + inv = host_audit_inventory(root) + facts = grant(inv)["facts"] + assert facts["composition"]["selected_policy"] == ("saved" if change == "remove" else "global") + assert ("_provider_work_github" in facts["policy"]["network_policies"]) == (change == "remove") + + +@pytest.mark.parametrize("missing", ["saved", "profile", "context", "duplicate", "interceptor", "endpointless", "reserved"]) +def test_unresolved_composition_is_named_incomplete(tmp_path, missing): + selected = selection() + spec = selected["compositions"][0] + contents = files(selected) + if missing == "saved": + spec["saved_policy"] = {"state": "absent"} + elif missing == "profile": + del contents["profiles/github.profile"] + elif missing == "context": + del spec["providers"][0]["endpoint_resolution"] + elif missing in {"duplicate", "interceptor"}: + spec["catalog_mode"] = "combined" + spec["profile_catalog"].append({"path": "profiles/other", "scope": "platform" if missing == "duplicate" else "interceptor"}) + contents["profiles/other"] = profile() + elif missing == "endpointless": + contents["profiles/github.profile"]["endpoints"] = [] + else: + contents["base.policy"] = POLICY.replace(" api:", " _provider_api:") + inv = inventory(tmp_path, contents) + assert not inventory_is_complete(inv) + assert any(issue["host"] == "openshell" and issue["blocking"] for issue in inv["issues"]) + assert not inv["grants"] + + +def test_credentials_are_separate_from_network_and_values_are_rejected(tmp_path): + provider = profile() + provider["credentials"] = [{"name": "github_token", "env_vars": ["GITHUB_TOKEN"], "auth_style": "bearer", "header_name": "authorization"}] + inv = inventory(tmp_path, files(provider=provider)) + assert inventory_is_complete(inv) + facts = grant(inv)["facts"] + credentials, = facts["credential_use"] + assert credentials["credential_values_read"] is False + assert credentials["runtime_authorization_verified"] is False + assert "binaries" not in credentials and "access" not in credentials["endpoints"][0] + old = grant(inv) + provider["endpoints"][0]["access"] = "read-write" + changed = grant(inventory(tmp_path, files(provider=provider))) + assert compare_openshell_grants(old, changed).direction == "widened" + provider["credentials"][0]["value"] = "sk-live-0123456789abcdef0123456789abcdef" + failed = inventory(tmp_path, files(provider=provider)) + assert not inventory_is_complete(failed) + assert provider["credentials"][0]["value"] not in json.dumps(failed) + + +def test_snapshot_metadata_keeps_freshness_and_field_lifetimes_explicit(tmp_path): + selected = {"version": 2, "runtime_version": "0.1.2", "policies": [{"path": "export", "role": "effective_snapshot", "snapshot": {"source": "operator export", "revision": "sandbox-42"}}]} + inv = inventory(tmp_path, {REGISTRATION: selected, "export": POLICY}) + facts = grant(inv)["facts"] + assert facts["snapshot"]["revision"] == "sandbox-42" + assert facts["runtime_freshness_verified"] is False + assert "filesystem_policy" in facts["startup_bound_fields"] + assert "network_policies" in facts["dynamic_fields"] + from pathlib import Path + + schema = json.loads((Path(__file__).parents[1] / "docs/host-grants-inventory-schema.v0.9.json").read_text()) + Draft202012Validator(schema).validate(inv) + + +def test_every_dependency_is_bound_including_ignored_suppressed_profile(repo): + selected = selection() + selected["compositions"][0]["global_policy"] = {"state": "selected", "path": "global.policy"} + inventory(repo, {**files(selected), "global.policy": POLICY}) + # This profile has no network contribution while a global policy is active. + (repo / ".gitignore").write_text("agents-shipgate-reports/\nprofiles/\n") + _verify(repo, archive_head=False) + plan = json.loads((repo / "agents-shipgate-reports/verification-plan.json").read_text()) + paths = {item["path"] for item in plan["inputs"]["options"]["dependency_inputs"]["files"]} + assert {REGISTRATION, "base.policy", "global.policy", "profiles/github.profile"} <= paths + read_current_control(repo / "agents-shipgate-reports", live=lambda: _live(repo)) + (repo / "profiles/github.profile").write_text(json.dumps(profile("changed.example"))) + with pytest.raises(CurrentControlUnavailable): + read_current_control(repo / "agents-shipgate-reports", live=lambda: _live(repo)) + + +def test_shared_read_session_captures_all_local_contributors(tmp_path): + inventory(tmp_path, files()) + snapshot = build_host_boundary_snapshot(tmp_path) + assert {REGISTRATION, "base.policy", "profiles/github.profile"} <= snapshot.cache.openshell_selected_paths diff --git a/tests/test_openshell_inventory.py b/tests/test_openshell_inventory.py index faf8d82a..df95014c 100644 --- a/tests/test_openshell_inventory.py +++ b/tests/test_openshell_inventory.py @@ -65,7 +65,7 @@ def test_selected_arbitrary_filename_has_typed_facts_and_defaults(tmp_path: Path assert "/network_policies/github/endpoints/0/enforcement" in facts["defaulted_fields"] assert facts["runtime_freshness_verified"] is False assert "openshell_policy" in render_host_audit_markdown(inventory) - schema = json.loads((Path(__file__).parents[1] / "docs/host-grants-inventory-schema.v0.8.json").read_text()) + schema = json.loads((Path(__file__).parents[1] / "docs/host-grants-inventory-schema.v0.9.json").read_text()) Draft202012Validator(schema).validate(inventory) with pytest.raises(ValidationError): HostGrantsInventoryV7.model_validate(inventory) diff --git a/tests/test_org_governance.py b/tests/test_org_governance.py index 1aa251ba..3b59d4df 100644 --- a/tests/test_org_governance.py +++ b/tests/test_org_governance.py @@ -575,7 +575,7 @@ def test_org_bundle_projects_platform_artifacts_without_second_gate( assert payload["registry_row"]["source_attestation_sha256"] == attestation_sha256 assert payload["org_status"]["summary"]["policy_pack_count"] == 1 assert payload["policy_packs"][0]["status"] == "verified" - assert payload["host_grants"]["host_grants_inventory_schema_version"] == "0.8" + assert payload["host_grants"]["host_grants_inventory_schema_version"] == "0.9" assert payload["artifacts"]["verifier"]["sha256"] diff --git a/tests/test_reusable_workflow_secret_mappings.py b/tests/test_reusable_workflow_secret_mappings.py index 3381cf72..cca0f02d 100644 --- a/tests/test_reusable_workflow_secret_mappings.py +++ b/tests/test_reusable_workflow_secret_mappings.py @@ -807,9 +807,9 @@ def test_a_current_baseline_compares_mappings_and_validates_against_the_schemas( path.write_text(STAGING) inventory = host_audit_inventory(tmp_path) baseline = build_host_grants_baseline(inventory) - assert baseline["host_grants_schema_version"] == "0.8" - Draft202012Validator(json.loads((ROOT / "docs/host-grants-inventory-schema.v0.8.json").read_text())).validate(inventory) - Draft202012Validator(json.loads((ROOT / "docs/host-grants-baseline-schema.v0.8.json").read_text())).validate(baseline) + assert baseline["host_grants_schema_version"] == "0.9" + Draft202012Validator(json.loads((ROOT / "docs/host-grants-inventory-schema.v0.9.json").read_text())).validate(inventory) + Draft202012Validator(json.loads((ROOT / "docs/host-grants-baseline-schema.v0.9.json").read_text())).validate(baseline) unchanged = build_host_drift_payload(baseline=baseline, inventory=inventory, baseline_file="b.json") assert (unchanged["comparison_status"], unchanged["has_drift"]) == ("comparable", False) @@ -818,7 +818,7 @@ def test_a_current_baseline_compares_mappings_and_validates_against_the_schemas( drift = build_host_drift_payload(baseline=baseline, inventory=host_audit_inventory(tmp_path), baseline_file="b.json") assert drift["comparison_status"] == "comparable" and drift["has_drift"] is True assert len(drift["changes"]) == 1 and drift["expansion_signals"] == [] - Draft202012Validator(json.loads((ROOT / "docs/host-grants-drift-schema.v0.8.json").read_text())).validate(drift) + Draft202012Validator(json.loads((ROOT / "docs/host-grants-drift-schema.v0.9.json").read_text())).validate(drift) def test_a_saved_baseline_listing_mappings_out_of_order_still_compares_equal(tmp_path): diff --git a/tests/test_workflow_agent_launches.py b/tests/test_workflow_agent_launches.py index 49af0a17..4d398791 100644 --- a/tests/test_workflow_agent_launches.py +++ b/tests/test_workflow_agent_launches.py @@ -1911,7 +1911,7 @@ def test_a_v0_6_baseline_without_a_workflow_stays_comparable_and_can_be_replaced audit = ["audit", "--host", "--workspace", str(tmp_path), "--baseline-file", str(path)] resaved = CliRunner().invoke(app, [*audit, "--save-baseline"]) assert resaved.exit_code == 0, resaved.output - assert json.loads(path.read_text())["host_grants_schema_version"] == "0.8" + assert json.loads(path.read_text())["host_grants_schema_version"] == "0.9" def test_the_documented_migration_from_a_v0_6_baseline_holding_a_workflow(tmp_path): @@ -1965,18 +1965,18 @@ def test_a_current_baseline_compares_agent_launches_and_validates_against_the_sc path.write_text(_yaml(_reproduction(run="npm ci && claude -p 'x'", ref=HEAD_SHA))) inventory = host_audit_inventory(tmp_path) baseline = build_host_grants_baseline(inventory) - assert baseline["host_grants_schema_version"] == "0.8" + assert baseline["host_grants_schema_version"] == "0.9" workflow, = [grant for grant in baseline["inventory"]["grants"] if grant["kind"] == "workflow"] assert workflow["unread_agent_runs"] == [{"job": "review", "step": "steps[2]", "agent": "claude"}] for name, payload in (("inventory", inventory), ("baseline", baseline)): - schema = json.loads((ROOT / f"docs/host-grants-{name}-schema.v0.8.json").read_text()) + schema = json.loads((ROOT / f"docs/host-grants-{name}-schema.v0.9.json").read_text()) Draft202012Validator(schema).validate(payload) path.write_text(_yaml(_reproduction(claude_args="--dangerously-skip-permissions", ref=HEAD_SHA))) drift = build_host_drift_payload(baseline=baseline, inventory=host_audit_inventory(tmp_path), baseline_file="b.json") assert (drift["comparison_status"], drift["has_drift"]) == ("comparable", True) assert drift["expansion_signals"] == [f"workflow_agent_widened_changed: {SOURCE}"] - schema = json.loads((ROOT / "docs/host-grants-drift-schema.v0.8.json").read_text()) + schema = json.loads((ROOT / "docs/host-grants-drift-schema.v0.9.json").read_text()) Draft202012Validator(schema).validate(drift) diff --git a/tests/test_workflow_step_action_references.py b/tests/test_workflow_step_action_references.py index 9460ea34..0ed922b5 100644 --- a/tests/test_workflow_step_action_references.py +++ b/tests/test_workflow_step_action_references.py @@ -460,7 +460,7 @@ def test_a_current_baseline_compares_step_references(tmp_path): path.parent.mkdir(parents=True) path.write_text(_yaml({"uses": f"actions/checkout@{PINNED}"})) baseline = build_host_grants_baseline(host_audit_inventory(tmp_path)) - assert baseline["host_grants_schema_version"] == "0.8" + assert baseline["host_grants_schema_version"] == "0.9" path.write_text(_yaml({"uses": "actions/checkout@main"})) drift = build_host_drift_payload(baseline=baseline, inventory=host_audit_inventory(tmp_path), baseline_file="b.json") @@ -830,7 +830,7 @@ def test_saving_over_a_legacy_baseline_without_a_workflow_is_refused(tmp_path, v path.rename(path.with_name(f"host-grants.v{version}.json")) resaved = CliRunner().invoke(app, [*audit, "--save-baseline"]) assert resaved.exit_code == 0, _output(resaved) - assert json.loads(path.read_text())["host_grants_schema_version"] == "0.8" + assert json.loads(path.read_text())["host_grants_schema_version"] == "0.9" def _output(result) -> str: @@ -959,7 +959,7 @@ def test_the_documented_migration_from_a_legacy_baseline_holding_a_workflow(tmp_ path.rename(path.with_name("host-grants.v0.5.json")) resaved = CliRunner().invoke(app, [*audit, "--save-baseline"]) assert resaved.exit_code == 0, resaved.output - assert json.loads(path.read_text())["host_grants_schema_version"] == "0.8" + assert json.loads(path.read_text())["host_grants_schema_version"] == "0.9" after = json.loads(CliRunner().invoke(app, [*audit, "--drift", "--json"]).stdout) assert (after["comparison_status"], after["has_drift"]) == ("comparable", False) assert path.with_name("host-grants.v0.5.json").read_text() == original