From 6ed3c493a8377e37f4e40ac253e04065cbd4b10e Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:23:42 -0400 Subject: [PATCH 01/63] Add SOVRINT governed SDK architecture --- docs/sovrint/README.md | 140 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 docs/sovrint/README.md diff --git a/docs/sovrint/README.md b/docs/sovrint/README.md new file mode 100644 index 0000000000..f6416723bf --- /dev/null +++ b/docs/sovrint/README.md @@ -0,0 +1,140 @@ +# SOVRINT™ Governed Copilot SDK Profile + +**Author:** Katrina Pietroniro +**Framework:** SOVRINT™ +**Repository:** `SOVRINT-OG/copilot-sdk` +**Extension Version:** 1.0 +**Status:** Additive SDK extension; upstream Copilot SDK interfaces preserved + +## Purpose + +The SOVRINT™ governed profile adds an explicit control plane around Copilot SDK sessions without replacing the upstream JSON-RPC client or changing the upstream SDK protocol. + +It provides: + +- deny-by-default permission profiles; +- least-authority tool exposure; +- append-only system-message governance by default; +- custom-tool authorization wrappers; +- bounded audit events; +- governance, integrity, and EvidenceGrid interface contracts; +- simulation-safe examples; +- language-specific helpers for TypeScript, Python, and Go; +- recipes for TypeScript, Python, Go, and .NET; +- schema and CI validation. + +## Runtime Position + +```text +APPLICATION +→ SOVRINT SESSION PROFILE +→ COPILOT SDK CLIENT +→ JSON-RPC +→ COPILOT CLI SERVER +``` + +The extension does not represent itself as the GitHub Copilot service, does not replace GitHub authentication or billing, and does not modify the Copilot CLI protocol. + +## Canonical Control Sequence + +```text +DECLARE PROFILE +→ REDUCE TOOL SURFACE +→ INSTALL PERMISSION HANDLER +→ APPEND GOVERNANCE INSTRUCTIONS +→ WRAP CUSTOM TOOLS +→ CREATE SESSION +→ OBSERVE EVENTS +→ RECORD BOUNDED AUDIT EVENTS +→ ESCALATE OR CONTINUE +``` + +## Security Profiles + +### `strict` + +- denies every first-party permission unless an application-specific evaluator explicitly approves it; +- exposes no first-party tool unless the application supplies an allowlist; +- forbids system-message replacement; +- fails closed when the audit sink cannot record a permission decision. + +### `read-only` + +- permits `read` requests; +- denies `shell`, `write`, `mcp`, and `url` by default; +- forbids system-message replacement; +- supports additional application-specific restrictions. + +### `research` + +- permits `read` requests by default; +- requires an application evaluator for network, MCP, shell, and write requests; +- exposes only explicitly named tools; +- preserves audit events for decisions and custom-tool invocations. + +Profiles are templates, not universal safety guarantees. The application remains responsible for request-field interpretation, tool definitions, deployment boundaries, and audit storage. + +## Language Support + +| Surface | Support | +|---|---| +| TypeScript / Node.js | First-class helper module and unit tests | +| Python | First-class helper module and unit tests | +| Go | First-class helper module and unit tests | +| .NET | Governed-session recipe using native SDK interfaces | +| Common | JSON schemas, profiles, policies, examples, CI | + +## Repository Additions + +```text +docs/sovrint/ + README.md + SECURITY_PROFILE.md + GOVERNANCE_INTEGRITY_EVIDENCE.md +sovrint/ + manifest.yaml + profiles/ + policy/ + schemas/ + examples/ +nodejs/src/sovrint.ts +python/copilot/sovrint.py +go/sovrint.go +cookbook/*/sovrint-governed-session.md +``` + +## Authority Separation + +The SDK helper may: + +- restrict session tools; +- evaluate permission requests; +- append governance instructions; +- wrap caller-defined tools; +- emit bounded audit events; +- refuse unsafe profile combinations. + +It may not: + +- grant permissions beyond application authority; +- treat a model response as a governance decision; +- treat an audit event as EvidenceGrid acceptance; +- fabricate continuity receipts; +- replace the SOVRINT Governance Runtime, Integrity Engine, or EvidenceGrid; +- claim that deny-by-default configuration eliminates all application risk. + +## Upstream Compatibility + +The extension is additive. Existing `CopilotClient`, `SessionConfig`, custom tools, MCP configuration, provider configuration, and session APIs remain unchanged. + +Applications opt in by importing the SOVRINT helpers and applying a profile before session creation. + +## Canonical Doctrine + +```text +NO TOOL WITHOUT DECLARED PURPOSE. +NO PERMISSION WITHOUT AN EXPLICIT DECISION. +NO SYSTEM OVERRIDE WITHOUT AN EXPLICIT POLICY. +NO MUTATION WITHOUT SCOPE. +NO AUDIT CLAIM BEYOND THE EVIDENCE ACTUALLY RECORDED. +``` From 04408857f04129c610f478e20ff08bb5b56c512e Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:23:57 -0400 Subject: [PATCH 02/63] Add SOVRINT SDK security profile --- docs/sovrint/SECURITY_PROFILE.md | 148 +++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 docs/sovrint/SECURITY_PROFILE.md diff --git a/docs/sovrint/SECURITY_PROFILE.md b/docs/sovrint/SECURITY_PROFILE.md new file mode 100644 index 0000000000..07d7c23544 --- /dev/null +++ b/docs/sovrint/SECURITY_PROFILE.md @@ -0,0 +1,148 @@ +# SOVRINT™ SDK Security Profile + +**Author:** Katrina Pietroniro +**Version:** 1.0 +**Status:** Canonical extension security profile + +## Protected Surfaces + +- filesystem reads and writes; +- shell execution; +- network and URL access; +- MCP server invocation; +- custom-tool invocation; +- system-message replacement; +- skill loading; +- custom-agent tool access; +- provider credentials; +- session configuration; +- audit event integrity; +- governance and evidence references. + +## Default Position + +The SOVRINT strict profile is deny-by-default. + +A permission is approved only when: + +1. its request kind is allowed by the active profile; +2. an application evaluator does not deny it; +3. any pre-existing SDK permission handler also approves it; +4. the audit decision is recorded when fail-closed auditing is enabled. + +## Decision Precedence + +```text +EXPLICIT DENY +→ APPLICATION EVALUATOR +→ PROFILE ALLOW +→ PROFILE DEFAULT +→ EXISTING HANDLER +→ FINAL DECISION +``` + +The most restrictive result wins. + +## System Message Boundary + +The upstream SDK supports append mode and replace mode. Replace mode removes SDK-managed guardrails. The SOVRINT profiles therefore forbid replace mode by default. + +Applications may opt out only through an explicit custom profile. Doing so is a deployment decision, not a safe default. + +## Tool Surface Reduction + +`availableTools` is preferred over a broad tool surface. When both an application config and a SOVRINT profile specify an allowlist, the effective set is the intersection. + +`excludedTools` is additive. Profile exclusions and application exclusions are combined. + +Custom tools remain caller code. Wrapping a custom tool adds authorization and audit boundaries but does not make the tool intrinsically safe. + +## Permission Kinds + +The current SDK permission request kinds are: + +- `read` +- `write` +- `shell` +- `url` +- `mcp` + +The extension treats unknown kinds as denied. + +## Audit Events + +The extension emits bounded events for: + +- permission decision; +- custom-tool start; +- custom-tool approval or rejection; +- custom-tool completion; +- custom-tool failure; +- profile application; +- policy violation. + +Audit events must not contain: + +- provider API keys; +- bearer tokens; +- unrestricted prompts or responses; +- raw file contents; +- full shell output; +- private witnesses; +- secret environment variables. + +## Failure-Safe Rules + +```text +UNKNOWN PERMISSION KIND → DENY +AUDIT FAILURE WITH FAIL-CLOSED ENABLED → DENY +SYSTEM MESSAGE REPLACE UNDER FORBIDDEN PROFILE → THROW +EMPTY EFFECTIVE TOOL ALLOWLIST → EXPOSE NO FIRST-PARTY TOOLS +CUSTOM AUTHORIZER ERROR → REJECT TOOL INVOCATION +EXISTING PERMISSION HANDLER DENIAL → PRESERVE DENIAL +``` + +## Threats Addressed + +### Accidental broad permissions + +Mitigated through deny-by-default profiles and explicit allowlists. + +### Permission-handler bypass + +Mitigated by composing profile and application handlers using most-restrictive-wins semantics. + +### System prompt replacement + +Mitigated by rejecting replace mode unless a custom profile explicitly permits it. + +### Tool confusion + +Mitigated through exact tool names, wrapped authorization, invocation events, and bounded telemetry. + +### Audit exfiltration + +Mitigated by bounded event fields and explicit prohibition of secrets and unrestricted content. + +### Profile drift + +Mitigated through versioned JSON profiles, a common schema, manifest commitments, and CI validation. + +## Residual Risks + +- the SDK server may introduce new permission fields or kinds; +- an application evaluator may be incorrect; +- a tool may perform broader actions than its name or schema suggests; +- approved reads may still expose sensitive information; +- URL and MCP requests can create external disclosure paths; +- audit sinks can be unavailable, compromised, or incomplete; +- system-message append content is not an enforcement boundary by itself; +- model behavior is not equivalent to policy enforcement; +- technical controls do not establish legal authority or valid consent. + +## Canonical Rule + +```text +THE PROFILE REDUCES AUTHORITY. +IT DOES NOT CREATE AUTHORITY. +``` From bd222517ed898cb1ad5263758a9586fbd4e5cfcb Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:24:15 -0400 Subject: [PATCH 03/63] Add governance integrity and evidence interface guide --- docs/sovrint/GOVERNANCE_INTEGRITY_EVIDENCE.md | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 docs/sovrint/GOVERNANCE_INTEGRITY_EVIDENCE.md diff --git a/docs/sovrint/GOVERNANCE_INTEGRITY_EVIDENCE.md b/docs/sovrint/GOVERNANCE_INTEGRITY_EVIDENCE.md new file mode 100644 index 0000000000..d81b0c580b --- /dev/null +++ b/docs/sovrint/GOVERNANCE_INTEGRITY_EVIDENCE.md @@ -0,0 +1,139 @@ +# SOVRINT™ Governance, Integrity, and Evidence Interfaces + +**Author:** Katrina Pietroniro +**Version:** 1.0 +**Status:** Canonical extension interface guide + +## Purpose + +This document defines how a governed Copilot SDK session may interact with the SOVRINT Governance Runtime, Integrity Engine, and EvidenceGrid without collapsing their distinct authorities into the SDK process. + +## Authority Topology + +```text +APPLICATION +→ SOVRINT SDK PROFILE +→ COPILOT SDK SESSION +→ CUSTOM TOOL OR FIRST-PARTY OPERATION +→ GOVERNANCE DECISION, WHEN REQUIRED +→ EXECUTION +→ INTEGRITY REVALIDATION, WHEN REQUIRED +→ AUDIT EVENT +→ EVIDENCE ADAPTER +→ EVIDENCEGRID +``` + +## Governance Boundary + +A governed SDK helper may collect the information required for a governance request, including: + +- session identifier; +- tool name; +- permission kind; +- requested action class; +- declared scope; +- target reference; +- reversibility reference; +- intervention estimate; +- consent and review requirements; +- provenance parent. + +The helper may not issue an `ALLOW` decision on behalf of the Governance Runtime unless the application has explicitly delegated that narrow decision to a local policy evaluator. + +A local approval remains a local approval. It is not automatically a system-wide governance decision. + +## Integrity Boundary + +The SDK extension may emit integrity observations for: + +- permission-profile mismatch; +- unexpected permission kind; +- unavailable audit sink; +- custom-tool authorization failure; +- system-message replacement attempt; +- undeclared tool invocation; +- schema validation failure; +- session configuration drift; +- evidence submission failure. + +It may request Integrity Engine classification or correction, but it must not label an observation as verified malicious action without the corresponding evidence and authority. + +## Evidence Boundary + +Audit events are EvidenceGrid candidates, not EvidenceGrid blocks. + +The extension may: + +- create a bounded audit event; +- calculate or attach application-provided commitments; +- submit through an evidence adapter; +- retain the returned ledger reference or failure state. + +It may not: + +- assign EvidenceGrid sequence; +- fabricate a State Root; +- replace Proof Token `π`; +- create continuity receipts; +- represent a pending audit event as accepted evidence; +- erase rejected or quarantined submissions. + +## Recommended Decision Classes + +| SDK event | Default route | +|---|---| +| Read request under read-only profile | Local profile evaluation | +| Write request | Governance evaluation or deny | +| Shell request | Governance evaluation or deny | +| URL request | Application allowlist plus governance policy | +| MCP request | Named server and tool allowlist plus governance policy | +| Custom tool with no mutation | Local authorization and audit | +| Custom tool with mutation | Governance decision before execution | +| System-message replace attempt | Deny and record | +| Audit sink failure | Deny when fail-closed is enabled | +| Unknown permission kind | Deny, classify, and review | + +## Audit Event Lifecycle + +```text +CREATED +→ RECORDED_LOCALLY +→ SUBMISSION_PENDING +→ SUBMITTED +→ ACCEPTED OR REJECTED OR QUARANTINED OR RETRYABLE +``` + +Every status transition must preserve the prior event identifier and provenance parent. + +## Evidence Minimization + +The event should contain commitments, classifications, and bounded references rather than unrestricted content. + +Recommended fields: + +- event identifier; +- event class; +- timestamp; +- session reference; +- profile identifier and version; +- permission kind or tool name; +- decision; +- reason code; +- target class; +- scope commitment; +- governance reference; +- integrity reference; +- parent event reference; +- disclosure class; +- evidence status. + +## Canonical Separations + +```text +MODEL RESPONSE ≠ GOVERNANCE DECISION +LOCAL PROFILE APPROVAL ≠ GLOBAL AUTHORITY +AUDIT EVENT ≠ EVIDENCEGRID BLOCK +ANOMALY ≠ VERIFIED CAUSE +TOOL COMPLETION ≠ RESTORATION +LOG PRESENCE ≠ CONTINUITY PROOF +``` From 9e39d0a489d754855a35c0f3ec2560d9c71c943e Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:24:32 -0400 Subject: [PATCH 04/63] Add SOVRINT security profile schema --- sovrint/schemas/security-profile.schema.json | 136 +++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 sovrint/schemas/security-profile.schema.json diff --git a/sovrint/schemas/security-profile.schema.json b/sovrint/schemas/security-profile.schema.json new file mode 100644 index 0000000000..c13eeaa8ac --- /dev/null +++ b/sovrint/schemas/security-profile.schema.json @@ -0,0 +1,136 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sovrint.local/schemas/copilot-sdk-security-profile.schema.json", + "title": "SOVRINT Governed Copilot SDK Security Profile", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "profileId", + "version", + "defaultDecision", + "allowKinds", + "denyKinds", + "availableTools", + "excludedTools", + "forbidSystemMessageReplace", + "failClosedOnAuditError", + "systemMessageAppend", + "audit" + ], + "properties": { + "schemaVersion": { + "const": "1.0" + }, + "profileId": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9._-]{2,63}$" + }, + "version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+(?:\\.[0-9]+)?$" + }, + "description": { + "type": "string", + "maxLength": 2000 + }, + "defaultDecision": { + "type": "string", + "enum": ["approve", "deny"] + }, + "allowKinds": { + "$ref": "#/$defs/permissionKinds" + }, + "denyKinds": { + "$ref": "#/$defs/permissionKinds" + }, + "availableTools": { + "type": "array", + "items": {"type": "string", "minLength": 1}, + "uniqueItems": true + }, + "excludedTools": { + "type": "array", + "items": {"type": "string", "minLength": 1}, + "uniqueItems": true + }, + "forbidSystemMessageReplace": { + "type": "boolean" + }, + "failClosedOnAuditError": { + "type": "boolean" + }, + "systemMessageAppend": { + "type": "string", + "minLength": 1, + "maxLength": 12000 + }, + "audit": { + "type": "object", + "additionalProperties": false, + "required": ["enabled", "includeRequestMetadata", "includeArguments", "includeResults"], + "properties": { + "enabled": {"type": "boolean"}, + "includeRequestMetadata": {"type": "boolean"}, + "includeArguments": {"const": false}, + "includeResults": {"const": false} + } + }, + "mcp": { + "type": "object", + "additionalProperties": false, + "properties": { + "allowedServers": { + "type": "array", + "items": {"type": "string", "minLength": 1}, + "uniqueItems": true + }, + "requireNamedToolAllowlist": { + "type": "boolean" + } + } + }, + "skills": { + "type": "object", + "additionalProperties": false, + "properties": { + "allowedDirectories": { + "type": "array", + "items": {"type": "string", "minLength": 1}, + "uniqueItems": true + }, + "disabledSkills": { + "type": "array", + "items": {"type": "string", "minLength": 1}, + "uniqueItems": true + } + } + }, + "metadata": { + "type": "object" + } + }, + "allOf": [ + { + "not": { + "properties": { + "allowKinds": { + "contains": {"enum": ["shell", "write", "mcp", "url"]} + }, + "defaultDecision": {"const": "approve"} + }, + "required": ["allowKinds", "defaultDecision"] + } + } + ], + "$defs": { + "permissionKinds": { + "type": "array", + "items": { + "type": "string", + "enum": ["read", "write", "shell", "url", "mcp"] + }, + "uniqueItems": true + } + } +} From 5c17f9cd893122bde7c77b37859bbb4e94dec6cb Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:25:03 -0400 Subject: [PATCH 05/63] Add SOVRINT SDK audit event schema --- sovrint/schemas/audit-event.schema.json | 73 +++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 sovrint/schemas/audit-event.schema.json diff --git a/sovrint/schemas/audit-event.schema.json b/sovrint/schemas/audit-event.schema.json new file mode 100644 index 0000000000..5ca14e6196 --- /dev/null +++ b/sovrint/schemas/audit-event.schema.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://sovrint.local/schemas/copilot-sdk-audit-event.schema.json", + "title": "SOVRINT Governed Copilot SDK Audit Event", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "eventId", + "eventClass", + "timestampUtc", + "profileId", + "profileVersion", + "sessionId", + "decision", + "reasonCode", + "disclosureClass", + "evidenceStatus" + ], + "properties": { + "schemaVersion": {"const": "1.0"}, + "eventId": {"type": "string", "minLength": 1}, + "parentEventId": {"type": ["string", "null"]}, + "eventClass": { + "type": "string", + "enum": [ + "PROFILE_APPLIED", + "PERMISSION_DECISION", + "TOOL_INVOCATION_STARTED", + "TOOL_INVOCATION_APPROVED", + "TOOL_INVOCATION_REJECTED", + "TOOL_INVOCATION_COMPLETED", + "TOOL_INVOCATION_FAILED", + "SYSTEM_MESSAGE_REPLACE_REJECTED", + "POLICY_VIOLATION", + "AUDIT_SINK_FAILURE", + "EVIDENCE_SUBMISSION_STATUS" + ] + }, + "timestampUtc": {"type": "string", "format": "date-time"}, + "profileId": {"type": "string", "minLength": 1}, + "profileVersion": {"type": "string", "minLength": 1}, + "sessionId": {"type": "string", "minLength": 1}, + "toolCallId": {"type": ["string", "null"]}, + "toolName": {"type": ["string", "null"]}, + "permissionKind": { + "type": ["string", "null"], + "enum": [null, "read", "write", "shell", "url", "mcp", "unknown"] + }, + "decision": { + "type": "string", + "enum": ["APPROVED", "DENIED", "REJECTED", "FAILED", "RECORDED", "PENDING"] + }, + "reasonCode": {"type": "string", "minLength": 1}, + "targetClass": {"type": ["string", "null"]}, + "scopeCommitment": { + "type": ["string", "null"], + "pattern": "^[A-Fa-f0-9]{64}$" + }, + "governanceReference": {"type": ["string", "null"]}, + "integrityReference": {"type": ["string", "null"]}, + "evidenceReference": {"type": ["string", "null"]}, + "disclosureClass": { + "type": "string", + "enum": ["PUBLIC", "INTERNAL", "PROTECTED", "RESTRICTED"] + }, + "evidenceStatus": { + "type": "string", + "enum": ["NOT_SUBMITTED", "SUBMISSION_PENDING", "SUBMITTED", "ACCEPTED", "REJECTED", "QUARANTINED", "RETRYABLE"] + }, + "metadata": {"type": "object"} + } +} From 3564e70f100877e86f246be7a2a2ea7ca85efc77 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:25:44 -0400 Subject: [PATCH 06/63] Add strict SOVRINT SDK profile --- sovrint/profiles/strict.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 sovrint/profiles/strict.json diff --git a/sovrint/profiles/strict.json b/sovrint/profiles/strict.json new file mode 100644 index 0000000000..9c13a7bad0 --- /dev/null +++ b/sovrint/profiles/strict.json @@ -0,0 +1,23 @@ +{ + "schemaVersion": "1.0", + "profileId": "sovrint.strict", + "version": "1.0", + "description": "Conservative governed-session profile.", + "defaultDecision": "deny", + "allowKinds": [], + "denyKinds": ["read", "write", "shell", "url", "mcp"], + "availableTools": [], + "excludedTools": [], + "forbidSystemMessageReplace": true, + "failClosedOnAuditError": true, + "systemMessageAppend": "Use only explicitly exposed tools and preserve governance, integrity, and evidence boundaries.", + "audit": { + "enabled": true, + "includeRequestMetadata": true, + "includeArguments": false, + "includeResults": false + }, + "metadata": { + "toolSurfaceMode": "none" + } +} From 91fba4f884c4ca42778f2c7eaa0849026714e6fa Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:25:55 -0400 Subject: [PATCH 07/63] Add read-only SOVRINT SDK profile --- sovrint/profiles/read-only.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 sovrint/profiles/read-only.json diff --git a/sovrint/profiles/read-only.json b/sovrint/profiles/read-only.json new file mode 100644 index 0000000000..082f2a1b2e --- /dev/null +++ b/sovrint/profiles/read-only.json @@ -0,0 +1,23 @@ +{ + "schemaVersion": "1.0", + "profileId": "sovrint.read-only", + "version": "1.0", + "description": "Read-oriented governed-session profile.", + "defaultDecision": "deny", + "allowKinds": ["read"], + "denyKinds": ["write", "shell", "url", "mcp"], + "availableTools": [], + "excludedTools": [], + "forbidSystemMessageReplace": true, + "failClosedOnAuditError": true, + "systemMessageAppend": "Operate in read-only mode. Do not mutate files, execute shell commands, access external URLs, or invoke MCP operations without a separate explicit approval path.", + "audit": { + "enabled": true, + "includeRequestMetadata": true, + "includeArguments": false, + "includeResults": false + }, + "metadata": { + "toolSurfaceMode": "inherit" + } +} From 0e4551c020180a64eb1c2407e26d59addd0f90b2 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:26:22 -0400 Subject: [PATCH 08/63] Add SOVRINT SDK extension manifest --- sovrint/manifest.yaml | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 sovrint/manifest.yaml diff --git a/sovrint/manifest.yaml b/sovrint/manifest.yaml new file mode 100644 index 0000000000..429d9e4de4 --- /dev/null +++ b/sovrint/manifest.yaml @@ -0,0 +1,43 @@ +schema_version: "1.0" +extension_id: "sovrint-governed-copilot-sdk" +version: "1.0" +author: "Katrina Pietroniro" +framework: "SOVRINT™" +status: "additive-extension" +upstream_protocol_modified: false + +components: + documentation: + - docs/sovrint/README.md + - docs/sovrint/SECURITY_PROFILE.md + - docs/sovrint/GOVERNANCE_INTEGRITY_EVIDENCE.md + common_schemas: + - sovrint/schemas/security-profile.schema.json + - sovrint/schemas/audit-event.schema.json + profiles: + - sovrint/profiles/strict.json + - sovrint/profiles/read-only.json + language_helpers: + - nodejs/src/sovrint.ts + - python/copilot/sovrint.py + - go/sovrint.go + recipes: + - cookbook/nodejs/sovrint-governed-session.md + - cookbook/python/sovrint-governed-session.md + - cookbook/go/sovrint-governed-session.md + - cookbook/dotnet/sovrint-governed-session.md + +interfaces: + governance_runtime: "SOVRINT-OG/sovrint-governance-runtime" + integrity_engine: "SOVRINT-OG/integrity-engine" + provenance_ledger: "SOVRINT-OG/sovrint-provenance-ledger" + federation_runtime: "SOVRINT-OG/sovrint-federation-runtime" + +invariants: + - "The upstream Copilot SDK JSON-RPC protocol remains unchanged." + - "Existing SDK users are unaffected unless they import and apply the SOVRINT helpers." + - "System-message replacement is forbidden by built-in SOVRINT profiles." + - "Unknown permission kinds are denied." + - "The most restrictive composed permission decision wins." + - "Audit events are not represented as EvidenceGrid acceptance." + - "The extension does not create governance authority." From 4f2347a15ba8f5ebf7c41e7a51e76eed4845eaad Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:26:30 -0400 Subject: [PATCH 09/63] Add SOVRINT permission decision matrix --- sovrint/policy/permission-matrix.yaml | 52 +++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 sovrint/policy/permission-matrix.yaml diff --git a/sovrint/policy/permission-matrix.yaml b/sovrint/policy/permission-matrix.yaml new file mode 100644 index 0000000000..d20aa1b0ab --- /dev/null +++ b/sovrint/policy/permission-matrix.yaml @@ -0,0 +1,52 @@ +schema_version: "1.0" +author: "Katrina Pietroniro" +framework: "SOVRINT™" + +permission_kinds: + read: + strict: DENY + read_only: APPROVE + research: APPROVE + notes: "Application-specific path and data classification checks may still deny." + write: + strict: DENY + read_only: DENY + research: EVALUATE + notes: "Requires explicit scope and application policy." + shell: + strict: DENY + read_only: DENY + research: EVALUATE + notes: "Requires explicit command and environment policy." + url: + strict: DENY + read_only: DENY + research: EVALUATE + notes: "Requires destination and disclosure policy." + mcp: + strict: DENY + read_only: DENY + research: EVALUATE + notes: "Requires named server and named tool policy." + +precedence: + - EXPLICIT_DENY + - APPLICATION_EVALUATOR + - PROFILE_ALLOW + - PROFILE_DEFAULT + - EXISTING_HANDLER + - FINAL_DECISION + +unknown_kind: DENY + +composition: + rule: "Most restrictive decision wins." + existing_handler_denial_preserved: true + evaluator_error: DENY + audit_failure_when_fail_closed: DENY + +invariants: + - "Permission approval is session-scoped unless a narrower scope is declared." + - "A local approval is not a global governance decision." + - "Tool availability and permission approval are separate gates." + - "Unknown fields are not treated as proof of safe scope." From 4e05adde27ce5be5f6e5da451ca10c343fafdaff Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:26:46 -0400 Subject: [PATCH 10/63] Add SOVRINT audit event example --- sovrint/examples/audit-event.example.json | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 sovrint/examples/audit-event.example.json diff --git a/sovrint/examples/audit-event.example.json b/sovrint/examples/audit-event.example.json new file mode 100644 index 0000000000..7baba3899e --- /dev/null +++ b/sovrint/examples/audit-event.example.json @@ -0,0 +1,26 @@ +{ + "schemaVersion": "1.0", + "eventId": "SOVRINT-AUDIT-0001", + "parentEventId": null, + "eventClass": "PERMISSION_DECISION", + "timestampUtc": "2026-06-26T23:55:00Z", + "profileId": "sovrint.read-only", + "profileVersion": "1.0", + "sessionId": "SIMULATION-SESSION-001", + "toolCallId": "SIMULATION-TOOL-CALL-001", + "toolName": null, + "permissionKind": "write", + "decision": "DENIED", + "reasonCode": "PROFILE_EXPLICIT_DENY", + "targetClass": "filesystem", + "scopeCommitment": "1111111111111111111111111111111111111111111111111111111111111111", + "governanceReference": null, + "integrityReference": null, + "evidenceReference": null, + "disclosureClass": "INTERNAL", + "evidenceStatus": "NOT_SUBMITTED", + "metadata": { + "simulationOnly": true, + "requestMetadataIncluded": false + } +} From 2b797e41d086d530ee44eb93e23b2e7d311ed0a6 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:27:47 -0400 Subject: [PATCH 11/63] Add TypeScript SOVRINT governed-session helpers --- nodejs/src/sovrint.ts | 529 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 529 insertions(+) create mode 100644 nodejs/src/sovrint.ts diff --git a/nodejs/src/sovrint.ts b/nodejs/src/sovrint.ts new file mode 100644 index 0000000000..f54cb80184 --- /dev/null +++ b/nodejs/src/sovrint.ts @@ -0,0 +1,529 @@ +import type { + PermissionHandler, + PermissionRequest, + PermissionRequestResult, + SessionConfig, + Tool, + ToolInvocation, + ToolResultObject, +} from "./types.js"; + +export type SovrintPermissionDecision = "approve" | "deny"; +export type SovrintToolSurfaceMode = "inherit" | "allowlist" | "none"; +export type SovrintAuditDecision = + | "APPROVED" + | "DENIED" + | "REJECTED" + | "FAILED" + | "RECORDED" + | "PENDING"; + +export type SovrintAuditEventClass = + | "PROFILE_APPLIED" + | "PERMISSION_DECISION" + | "TOOL_INVOCATION_STARTED" + | "TOOL_INVOCATION_APPROVED" + | "TOOL_INVOCATION_REJECTED" + | "TOOL_INVOCATION_COMPLETED" + | "TOOL_INVOCATION_FAILED" + | "SYSTEM_MESSAGE_REPLACE_REJECTED" + | "POLICY_VIOLATION" + | "AUDIT_SINK_FAILURE"; + +export interface SovrintSecurityProfile { + profileId: string; + version: string; + description?: string; + defaultDecision: SovrintPermissionDecision; + allowKinds?: PermissionRequest["kind"][]; + denyKinds?: PermissionRequest["kind"][]; + toolSurfaceMode?: SovrintToolSurfaceMode; + availableTools?: string[]; + excludedTools?: string[]; + allowedMcpServers?: string[]; + allowedSkillDirectories?: string[]; + disabledSkills?: string[]; + forbidSystemMessageReplace?: boolean; + failClosedOnAuditError?: boolean; + auditEnabled?: boolean; + systemMessageAppend?: string; +} + +export interface SovrintAuditEvent { + schemaVersion: "1.0"; + eventId: string; + parentEventId?: string | null; + eventClass: SovrintAuditEventClass; + timestampUtc: string; + profileId: string; + profileVersion: string; + sessionId: string; + toolCallId?: string | null; + toolName?: string | null; + permissionKind?: PermissionRequest["kind"] | "unknown" | null; + decision: SovrintAuditDecision; + reasonCode: string; + targetClass?: string | null; + disclosureClass: "PUBLIC" | "INTERNAL" | "PROTECTED" | "RESTRICTED"; + evidenceStatus: + | "NOT_SUBMITTED" + | "SUBMISSION_PENDING" + | "SUBMITTED" + | "ACCEPTED" + | "REJECTED" + | "QUARANTINED" + | "RETRYABLE"; + metadata?: Record; +} + +export type SovrintAuditSink = (event: SovrintAuditEvent) => Promise | void; + +export type SovrintPermissionEvaluator = ( + request: PermissionRequest, + invocation: { sessionId: string } +) => Promise | SovrintPermissionDecision | undefined; + +export interface SovrintPermissionHandlerOptions { + auditSink?: SovrintAuditSink; + evaluate?: SovrintPermissionEvaluator; + downstream?: PermissionHandler; +} + +export interface SovrintApplyProfileOptions { + auditSink?: SovrintAuditSink; + evaluatePermission?: SovrintPermissionEvaluator; +} + +export interface SovrintToolGuardOptions { + profile: SovrintSecurityProfile; + auditSink?: SovrintAuditSink; + authorize?: ( + args: TArgs, + invocation: ToolInvocation + ) => Promise | boolean; + failureMode?: "result" | "rethrow"; +} + +export const SOVRINT_SYSTEM_APPEND = [ + "Operate under a bounded SOVRINT governed-session profile.", + "Use only explicitly exposed tools and declared authority.", + "Treat observations, inferences, recommendations, governance decisions, integrity findings, and accepted evidence as distinct classes.", + "Do not claim approval, verification, restoration, or EvidenceGrid acceptance without an explicit external result.", +].join(" "); + +export const SOVRINT_STRICT_PROFILE: Readonly = { + profileId: "sovrint.strict", + version: "1.0", + description: "Deny every permission and expose no inherited first-party tools.", + defaultDecision: "deny", + allowKinds: [], + denyKinds: ["read", "write", "shell", "url", "mcp"], + toolSurfaceMode: "none", + availableTools: [], + excludedTools: [], + allowedMcpServers: [], + allowedSkillDirectories: [], + disabledSkills: [], + forbidSystemMessageReplace: true, + failClosedOnAuditError: true, + auditEnabled: true, + systemMessageAppend: SOVRINT_SYSTEM_APPEND, +}; + +export const SOVRINT_READ_ONLY_PROFILE: Readonly = { + profileId: "sovrint.read-only", + version: "1.0", + description: "Permit read requests while denying mutating and external permission kinds.", + defaultDecision: "deny", + allowKinds: ["read"], + denyKinds: ["write", "shell", "url", "mcp"], + toolSurfaceMode: "inherit", + excludedTools: [], + allowedMcpServers: [], + allowedSkillDirectories: [], + disabledSkills: [], + forbidSystemMessageReplace: true, + failClosedOnAuditError: true, + auditEnabled: true, + systemMessageAppend: `${SOVRINT_SYSTEM_APPEND} Operate in read-only mode.`, +}; + +export const SOVRINT_RESEARCH_PROFILE: Readonly = { + profileId: "sovrint.research", + version: "1.0", + description: "Permit reads and require an application evaluator for every other permission kind.", + defaultDecision: "deny", + allowKinds: ["read"], + denyKinds: [], + toolSurfaceMode: "inherit", + excludedTools: [], + allowedMcpServers: [], + allowedSkillDirectories: [], + disabledSkills: [], + forbidSystemMessageReplace: true, + failClosedOnAuditError: false, + auditEnabled: true, + systemMessageAppend: `${SOVRINT_SYSTEM_APPEND} Separate research observations from verified findings.`, +}; + +let auditSequence = 0; + +function createAuditEvent( + profile: SovrintSecurityProfile, + event: Omit< + SovrintAuditEvent, + "schemaVersion" | "eventId" | "timestampUtc" | "profileId" | "profileVersion" + > +): SovrintAuditEvent { + auditSequence += 1; + return { + schemaVersion: "1.0", + eventId: `sovrint-${Date.now()}-${auditSequence}`, + timestampUtc: new Date().toISOString(), + profileId: profile.profileId, + profileVersion: profile.version, + ...event, + }; +} + +async function emitAuditEvent( + profile: SovrintSecurityProfile, + sink: SovrintAuditSink | undefined, + event: SovrintAuditEvent +): Promise { + if (!profile.auditEnabled) { + return true; + } + if (!sink) { + return !profile.failClosedOnAuditError; + } + try { + await sink(event); + return true; + } catch { + return !profile.failClosedOnAuditError; + } +} + +function permissionResult( + approved: boolean, + profile: SovrintSecurityProfile, + reasonCode: string +): PermissionRequestResult { + return { + kind: approved ? "approved" : "denied-by-rules", + rules: [ + { + source: "sovrint", + profileId: profile.profileId, + profileVersion: profile.version, + reasonCode, + }, + ], + }; +} + +async function evaluateProfilePermission( + profile: SovrintSecurityProfile, + request: PermissionRequest, + invocation: { sessionId: string }, + evaluator?: SovrintPermissionEvaluator +): Promise<{ approved: boolean; reasonCode: string }> { + if (!request.kind) { + return { approved: false, reasonCode: "UNKNOWN_PERMISSION_KIND" }; + } + + if (profile.denyKinds?.includes(request.kind)) { + return { approved: false, reasonCode: "PROFILE_EXPLICIT_DENY" }; + } + + if (evaluator) { + try { + const result = await evaluator(request, invocation); + if (result === "approve") { + return { approved: true, reasonCode: "APPLICATION_EVALUATOR_APPROVED" }; + } + if (result === "deny") { + return { approved: false, reasonCode: "APPLICATION_EVALUATOR_DENIED" }; + } + } catch { + return { approved: false, reasonCode: "APPLICATION_EVALUATOR_FAILED" }; + } + } + + if (profile.allowKinds?.includes(request.kind)) { + return { approved: true, reasonCode: "PROFILE_EXPLICIT_ALLOW" }; + } + + return profile.defaultDecision === "approve" + ? { approved: true, reasonCode: "PROFILE_DEFAULT_ALLOW" } + : { approved: false, reasonCode: "PROFILE_DEFAULT_DENY" }; +} + +export function createSovrintPermissionHandler( + profile: SovrintSecurityProfile, + options: SovrintPermissionHandlerOptions = {} +): PermissionHandler { + return async (request, invocation) => { + const profileDecision = await evaluateProfilePermission( + profile, + request, + invocation, + options.evaluate + ); + + let approved = profileDecision.approved; + let reasonCode = profileDecision.reasonCode; + + if (approved && options.downstream) { + try { + const downstreamResult = await options.downstream(request, invocation); + if (downstreamResult.kind !== "approved") { + approved = false; + reasonCode = "DOWNSTREAM_HANDLER_DENIED"; + } + } catch { + approved = false; + reasonCode = "DOWNSTREAM_HANDLER_FAILED"; + } + } + + const event = createAuditEvent(profile, { + eventClass: "PERMISSION_DECISION", + sessionId: invocation.sessionId, + toolCallId: request.toolCallId ?? null, + toolName: null, + permissionKind: request.kind ?? "unknown", + decision: approved ? "APPROVED" : "DENIED", + reasonCode, + disclosureClass: "INTERNAL", + evidenceStatus: "NOT_SUBMITTED", + }); + + const auditRecorded = await emitAuditEvent(profile, options.auditSink, event); + if (!auditRecorded && approved) { + return permissionResult(false, profile, "AUDIT_SINK_UNAVAILABLE"); + } + + return permissionResult(approved, profile, reasonCode); + }; +} + +function intersectTools(left: string[], right: string[]): string[] { + const allowed = new Set(right); + return left.filter((tool) => allowed.has(tool)); +} + +function mergeUnique(left: string[] = [], right: string[] = []): string[] { + return [...new Set([...left, ...right])]; +} + +function applySystemMessage( + config: SessionConfig, + profile: SovrintSecurityProfile +): SessionConfig["systemMessage"] { + if (config.systemMessage?.mode === "replace") { + if (profile.forbidSystemMessageReplace !== false) { + throw new Error( + `SOVRINT profile '${profile.profileId}' forbids system-message replacement` + ); + } + return config.systemMessage; + } + + const existing = config.systemMessage?.content?.trim(); + const appended = profile.systemMessageAppend?.trim(); + const content = [existing, appended].filter(Boolean).join("\n\n"); + return content ? { mode: "append", content } : config.systemMessage; +} + +export function applySovrintProfile( + config: SessionConfig, + profile: SovrintSecurityProfile, + options: SovrintApplyProfileOptions = {} +): SessionConfig { + let availableTools = config.availableTools; + + if (profile.toolSurfaceMode === "none") { + availableTools = []; + } else if (profile.toolSurfaceMode === "allowlist") { + const profileTools = profile.availableTools ?? []; + availableTools = config.availableTools + ? intersectTools(config.availableTools, profileTools) + : [...profileTools]; + } + + let customAgents = config.customAgents; + if (customAgents && profile.toolSurfaceMode !== "inherit") { + customAgents = customAgents.map((agent) => { + if (profile.toolSurfaceMode === "none") { + return { ...agent, tools: [] }; + } + const profileTools = profile.availableTools ?? []; + const agentTools = agent.tools ?? profileTools; + return { ...agent, tools: intersectTools(agentTools, profileTools) }; + }); + } + + let mcpServers = config.mcpServers; + if (mcpServers && profile.allowedMcpServers) { + const allowed = new Set(profile.allowedMcpServers); + mcpServers = Object.fromEntries( + Object.entries(mcpServers).filter(([name]) => allowed.has(name)) + ); + } + + let skillDirectories = config.skillDirectories; + if (skillDirectories && profile.allowedSkillDirectories) { + const allowed = new Set(profile.allowedSkillDirectories); + skillDirectories = skillDirectories.filter((directory) => allowed.has(directory)); + } + + return { + ...config, + availableTools, + excludedTools: mergeUnique(config.excludedTools, profile.excludedTools), + systemMessage: applySystemMessage(config, profile), + onPermissionRequest: createSovrintPermissionHandler(profile, { + auditSink: options.auditSink, + evaluate: options.evaluatePermission, + downstream: config.onPermissionRequest, + }), + customAgents, + mcpServers, + skillDirectories, + disabledSkills: mergeUnique(config.disabledSkills, profile.disabledSkills), + }; +} + +function rejectedToolResult(reasonCode: string): ToolResultObject { + return { + textResultForLlm: "The governed tool invocation was not authorized.", + resultType: "rejected", + error: reasonCode, + toolTelemetry: { source: "sovrint", reasonCode }, + }; +} + +export function wrapSovrintTool( + tool: Tool, + options: SovrintToolGuardOptions +): Tool { + const { profile } = options; + + return { + ...tool, + handler: async (args: TArgs, invocation: ToolInvocation) => { + const started = createAuditEvent(profile, { + eventClass: "TOOL_INVOCATION_STARTED", + sessionId: invocation.sessionId, + toolCallId: invocation.toolCallId, + toolName: tool.name, + permissionKind: null, + decision: "PENDING", + reasonCode: "TOOL_INVOCATION_RECEIVED", + disclosureClass: "INTERNAL", + evidenceStatus: "NOT_SUBMITTED", + }); + + const startRecorded = await emitAuditEvent(profile, options.auditSink, started); + if (!startRecorded) { + return rejectedToolResult("AUDIT_SINK_UNAVAILABLE"); + } + + if (options.authorize) { + let authorized = false; + try { + authorized = await options.authorize(args, invocation); + } catch { + authorized = false; + } + + if (!authorized) { + await emitAuditEvent( + profile, + options.auditSink, + createAuditEvent(profile, { + parentEventId: started.eventId, + eventClass: "TOOL_INVOCATION_REJECTED", + sessionId: invocation.sessionId, + toolCallId: invocation.toolCallId, + toolName: tool.name, + permissionKind: null, + decision: "REJECTED", + reasonCode: "CUSTOM_TOOL_AUTHORIZATION_DENIED", + disclosureClass: "INTERNAL", + evidenceStatus: "NOT_SUBMITTED", + }) + ); + return rejectedToolResult("CUSTOM_TOOL_AUTHORIZATION_DENIED"); + } + } + + await emitAuditEvent( + profile, + options.auditSink, + createAuditEvent(profile, { + parentEventId: started.eventId, + eventClass: "TOOL_INVOCATION_APPROVED", + sessionId: invocation.sessionId, + toolCallId: invocation.toolCallId, + toolName: tool.name, + permissionKind: null, + decision: "APPROVED", + reasonCode: "CUSTOM_TOOL_AUTHORIZED", + disclosureClass: "INTERNAL", + evidenceStatus: "NOT_SUBMITTED", + }) + ); + + try { + const result = await tool.handler(args, invocation); + await emitAuditEvent( + profile, + options.auditSink, + createAuditEvent(profile, { + parentEventId: started.eventId, + eventClass: "TOOL_INVOCATION_COMPLETED", + sessionId: invocation.sessionId, + toolCallId: invocation.toolCallId, + toolName: tool.name, + permissionKind: null, + decision: "RECORDED", + reasonCode: "CUSTOM_TOOL_COMPLETED", + disclosureClass: "INTERNAL", + evidenceStatus: "NOT_SUBMITTED", + }) + ); + return result; + } catch (error) { + await emitAuditEvent( + profile, + options.auditSink, + createAuditEvent(profile, { + parentEventId: started.eventId, + eventClass: "TOOL_INVOCATION_FAILED", + sessionId: invocation.sessionId, + toolCallId: invocation.toolCallId, + toolName: tool.name, + permissionKind: null, + decision: "FAILED", + reasonCode: "CUSTOM_TOOL_FAILED", + disclosureClass: "INTERNAL", + evidenceStatus: "NOT_SUBMITTED", + }) + ); + + if (options.failureMode === "result") { + return { + textResultForLlm: "The governed tool invocation failed.", + resultType: "failure", + error: error instanceof Error ? error.message : "unknown error", + toolTelemetry: { source: "sovrint" }, + } satisfies ToolResultObject; + } + throw error; + } + }, + }; +} From d87ab28c7a4c5493d11cd365d612cbc844c7f1af Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:27:58 -0400 Subject: [PATCH 12/63] Export TypeScript SOVRINT helpers --- nodejs/src/index.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index cfbd13b131..9f85e8e6cb 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -11,6 +11,28 @@ export { CopilotClient } from "./client.js"; export { CopilotSession, type AssistantMessageEvent } from "./session.js"; export { defineTool } from "./types.js"; +export { + SOVRINT_READ_ONLY_PROFILE, + SOVRINT_RESEARCH_PROFILE, + SOVRINT_STRICT_PROFILE, + SOVRINT_SYSTEM_APPEND, + applySovrintProfile, + createSovrintPermissionHandler, + wrapSovrintTool, +} from "./sovrint.js"; +export type { + SovrintApplyProfileOptions, + SovrintAuditDecision, + SovrintAuditEvent, + SovrintAuditEventClass, + SovrintAuditSink, + SovrintPermissionDecision, + SovrintPermissionEvaluator, + SovrintPermissionHandlerOptions, + SovrintSecurityProfile, + SovrintToolGuardOptions, + SovrintToolSurfaceMode, +} from "./sovrint.js"; export type { ConnectionState, CopilotClientOptions, From 3ba7a5f3d8dcbd3585b6b634b5b9caad165da68b Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:28:18 -0400 Subject: [PATCH 13/63] Add TypeScript SOVRINT helper tests --- nodejs/test/sovrint.test.ts | 192 ++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 nodejs/test/sovrint.test.ts diff --git a/nodejs/test/sovrint.test.ts b/nodejs/test/sovrint.test.ts new file mode 100644 index 0000000000..40345553ea --- /dev/null +++ b/nodejs/test/sovrint.test.ts @@ -0,0 +1,192 @@ +import { describe, expect, it, vi } from "vitest"; +import { + SOVRINT_READ_ONLY_PROFILE, + SOVRINT_RESEARCH_PROFILE, + SOVRINT_STRICT_PROFILE, + applySovrintProfile, + createSovrintPermissionHandler, + wrapSovrintTool, + type SovrintAuditEvent, + type Tool, +} from "../src/index.js"; + +describe("SOVRINT governed SDK profile", () => { + it("preserves strict explicit denial even when an evaluator approves", async () => { + const events: SovrintAuditEvent[] = []; + const handler = createSovrintPermissionHandler(SOVRINT_STRICT_PROFILE, { + auditSink: (event) => events.push(event), + evaluate: () => "approve", + }); + + const result = await handler( + { kind: "read", toolCallId: "call-1" }, + { sessionId: "session-1" } + ); + + expect(result.kind).toBe("denied-by-rules"); + expect(events).toHaveLength(1); + expect(events[0]).toMatchObject({ + eventClass: "PERMISSION_DECISION", + decision: "DENIED", + reasonCode: "PROFILE_EXPLICIT_DENY", + }); + }); + + it("approves reads and denies writes under the read-only profile", async () => { + const events: SovrintAuditEvent[] = []; + const handler = createSovrintPermissionHandler(SOVRINT_READ_ONLY_PROFILE, { + auditSink: (event) => events.push(event), + }); + + const read = await handler({ kind: "read" }, { sessionId: "session-2" }); + const write = await handler({ kind: "write" }, { sessionId: "session-2" }); + + expect(read.kind).toBe("approved"); + expect(write.kind).toBe("denied-by-rules"); + expect(events.map((event) => event.decision)).toEqual(["APPROVED", "DENIED"]); + }); + + it("allows the research evaluator to approve a request not explicitly allowed", async () => { + const handler = createSovrintPermissionHandler(SOVRINT_RESEARCH_PROFILE, { + auditSink: () => undefined, + evaluate: (request) => (request.kind === "url" ? "approve" : undefined), + }); + + const result = await handler({ kind: "url" }, { sessionId: "session-3" }); + expect(result.kind).toBe("approved"); + }); + + it("preserves a downstream permission denial", async () => { + const handler = createSovrintPermissionHandler(SOVRINT_READ_ONLY_PROFILE, { + auditSink: () => undefined, + downstream: () => ({ kind: "denied-interactively-by-user" }), + }); + + const result = await handler({ kind: "read" }, { sessionId: "session-4" }); + expect(result.kind).toBe("denied-by-rules"); + expect(result.rules).toEqual( + expect.arrayContaining([ + expect.objectContaining({ reasonCode: "DOWNSTREAM_HANDLER_DENIED" }), + ]) + ); + }); + + it("fails closed when an audit sink fails", async () => { + const handler = createSovrintPermissionHandler(SOVRINT_READ_ONLY_PROFILE, { + auditSink: () => { + throw new Error("sink unavailable"); + }, + }); + + const result = await handler({ kind: "read" }, { sessionId: "session-5" }); + expect(result.kind).toBe("denied-by-rules"); + expect(result.rules).toEqual( + expect.arrayContaining([ + expect.objectContaining({ reasonCode: "AUDIT_SINK_UNAVAILABLE" }), + ]) + ); + }); + + it("rejects system-message replacement by default", () => { + expect(() => + applySovrintProfile( + { + systemMessage: { mode: "replace", content: "replacement" }, + }, + SOVRINT_READ_ONLY_PROFILE, + { auditSink: () => undefined } + ) + ).toThrow(/forbids system-message replacement/); + }); + + it("removes inherited tools, MCP servers, skills, and custom-agent tools in strict mode", () => { + const config = applySovrintProfile( + { + availableTools: ["read_file", "write_file"], + mcpServers: { + alpha: { type: "http", url: "https://example.invalid", tools: ["*"] }, + }, + skillDirectories: ["./skills"], + customAgents: [ + { + name: "worker", + prompt: "work", + tools: null, + }, + ], + }, + SOVRINT_STRICT_PROFILE, + { auditSink: () => undefined } + ); + + expect(config.availableTools).toEqual([]); + expect(config.mcpServers).toEqual({}); + expect(config.skillDirectories).toEqual([]); + expect(config.customAgents?.[0].tools).toEqual([]); + expect(config.systemMessage).toMatchObject({ mode: "append" }); + }); + + it("rejects an unauthorized custom tool without calling its handler", async () => { + const originalHandler = vi.fn(() => "ok"); + const tool: Tool<{ value: string }> = { + name: "mutate_state", + description: "Test tool", + parameters: { type: "object" }, + handler: originalHandler, + }; + const guarded = wrapSovrintTool(tool, { + profile: SOVRINT_READ_ONLY_PROFILE, + auditSink: () => undefined, + authorize: () => false, + }); + + const result = await guarded.handler( + { value: "x" }, + { + sessionId: "session-6", + toolCallId: "tool-call-1", + toolName: "mutate_state", + arguments: { value: "x" }, + } + ); + + expect(originalHandler).not.toHaveBeenCalled(); + expect(result).toMatchObject({ + resultType: "rejected", + error: "CUSTOM_TOOL_AUTHORIZATION_DENIED", + }); + }); + + it("records a completed authorized custom tool invocation", async () => { + const events: SovrintAuditEvent[] = []; + const tool: Tool<{ value: string }> = { + name: "inspect_state", + description: "Test tool", + parameters: { type: "object" }, + handler: ({ value }) => ({ value }), + }; + const guarded = wrapSovrintTool(tool, { + profile: SOVRINT_READ_ONLY_PROFILE, + auditSink: (event) => events.push(event), + authorize: () => true, + }); + + const result = await guarded.handler( + { value: "ok" }, + { + sessionId: "session-7", + toolCallId: "tool-call-2", + toolName: "inspect_state", + arguments: { value: "ok" }, + } + ); + + expect(result).toEqual({ value: "ok" }); + expect(events.map((event) => event.eventClass)).toEqual([ + "TOOL_INVOCATION_STARTED", + "TOOL_INVOCATION_APPROVED", + "TOOL_INVOCATION_COMPLETED", + ]); + expect(events.every((event) => event.metadata === undefined)).toBe(true); + }); +}); From 93564bc201be483ed8fe93029f77d3cc4dbe7739 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:28:54 -0400 Subject: [PATCH 14/63] Add Python SOVRINT governed-session helpers --- python/copilot/sovrint.py | 471 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 471 insertions(+) create mode 100644 python/copilot/sovrint.py diff --git a/python/copilot/sovrint.py b/python/copilot/sovrint.py new file mode 100644 index 0000000000..a286a79fbd --- /dev/null +++ b/python/copilot/sovrint.py @@ -0,0 +1,471 @@ +"""SOVRINT governed-session helpers for the Copilot SDK. + +This module is additive. It composes the existing SDK SessionConfig and Tool +interfaces without changing the Copilot CLI JSON-RPC protocol. +""" + +from __future__ import annotations + +import inspect +import itertools +from dataclasses import dataclass +from datetime import datetime, timezone +from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union + +from .types import ( + PermissionHandler, + PermissionRequest, + PermissionRequestResult, + SessionConfig, + Tool, + ToolInvocation, + ToolResult, +) + +PermissionDecision = str +AuditSink = Callable[[Dict[str, Any]], Union[None, Awaitable[None]]] +PermissionEvaluator = Callable[ + [PermissionRequest, Dict[str, str]], + Union[Optional[PermissionDecision], Awaitable[Optional[PermissionDecision]]], +] +ToolAuthorizer = Callable[[ToolInvocation], Union[bool, Awaitable[bool]]] + +SOVRINT_SYSTEM_APPEND = ( + "Operate under a bounded SOVRINT governed-session profile. " + "Use only explicitly exposed tools and declared authority. " + "Treat observations, inferences, recommendations, governance decisions, " + "integrity findings, and accepted evidence as distinct classes. " + "Do not claim approval, verification, restoration, or EvidenceGrid acceptance " + "without an explicit external result." +) + + +@dataclass(frozen=True) +class SovrintSecurityProfile: + """Versioned governed-session profile.""" + + profile_id: str + version: str + default_decision: PermissionDecision = "deny" + allow_kinds: Tuple[str, ...] = () + deny_kinds: Tuple[str, ...] = () + tool_surface_mode: str = "inherit" + available_tools: Tuple[str, ...] = () + excluded_tools: Tuple[str, ...] = () + allowed_mcp_servers: Tuple[str, ...] = () + allowed_skill_directories: Tuple[str, ...] = () + disabled_skills: Tuple[str, ...] = () + forbid_system_message_replace: bool = True + fail_closed_on_audit_error: bool = False + audit_enabled: bool = True + system_message_append: str = SOVRINT_SYSTEM_APPEND + description: str = "" + + +SOVRINT_STRICT_PROFILE = SovrintSecurityProfile( + profile_id="sovrint.strict", + version="1.0", + description="Deny every permission and expose no inherited first-party tools.", + default_decision="deny", + deny_kinds=("read", "write", "shell", "url", "mcp"), + tool_surface_mode="none", + allowed_mcp_servers=(), + allowed_skill_directories=(), + fail_closed_on_audit_error=True, +) + +SOVRINT_READ_ONLY_PROFILE = SovrintSecurityProfile( + profile_id="sovrint.read-only", + version="1.0", + description="Permit reads while denying mutating and external permission kinds.", + default_decision="deny", + allow_kinds=("read",), + deny_kinds=("write", "shell", "url", "mcp"), + tool_surface_mode="inherit", + allowed_mcp_servers=(), + allowed_skill_directories=(), + fail_closed_on_audit_error=True, + system_message_append=f"{SOVRINT_SYSTEM_APPEND} Operate in read-only mode.", +) + +SOVRINT_RESEARCH_PROFILE = SovrintSecurityProfile( + profile_id="sovrint.research", + version="1.0", + description="Permit reads and require an evaluator for every other permission kind.", + default_decision="deny", + allow_kinds=("read",), + deny_kinds=(), + tool_surface_mode="inherit", + allowed_mcp_servers=(), + allowed_skill_directories=(), + fail_closed_on_audit_error=False, + system_message_append=( + f"{SOVRINT_SYSTEM_APPEND} Separate research observations from verified findings." + ), +) + +_audit_sequence = itertools.count(1) + + +async def _resolve(value: Any) -> Any: + if inspect.isawaitable(value): + return await value + return value + + +def _audit_event( + profile: SovrintSecurityProfile, + *, + event_class: str, + session_id: str, + decision: str, + reason_code: str, + tool_call_id: Optional[str] = None, + tool_name: Optional[str] = None, + permission_kind: Optional[str] = None, + parent_event_id: Optional[str] = None, +) -> Dict[str, Any]: + sequence = next(_audit_sequence) + return { + "schemaVersion": "1.0", + "eventId": "sovrint-{0}-{1}".format(int(datetime.now(timezone.utc).timestamp() * 1000), sequence), + "parentEventId": parent_event_id, + "eventClass": event_class, + "timestampUtc": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"), + "profileId": profile.profile_id, + "profileVersion": profile.version, + "sessionId": session_id, + "toolCallId": tool_call_id, + "toolName": tool_name, + "permissionKind": permission_kind, + "decision": decision, + "reasonCode": reason_code, + "disclosureClass": "INTERNAL", + "evidenceStatus": "NOT_SUBMITTED", + } + + +async def _emit_audit( + profile: SovrintSecurityProfile, + sink: Optional[AuditSink], + event: Dict[str, Any], +) -> bool: + if not profile.audit_enabled: + return True + if sink is None: + return not profile.fail_closed_on_audit_error + try: + await _resolve(sink(event)) + return True + except Exception: + return not profile.fail_closed_on_audit_error + + +def _permission_result( + approved: bool, + profile: SovrintSecurityProfile, + reason_code: str, +) -> PermissionRequestResult: + return PermissionRequestResult( + kind="approved" if approved else "denied-by-rules", + rules=[ + { + "source": "sovrint", + "profileId": profile.profile_id, + "profileVersion": profile.version, + "reasonCode": reason_code, + } + ], + ) + + +async def _evaluate_profile_permission( + profile: SovrintSecurityProfile, + request: PermissionRequest, + invocation: Dict[str, str], + evaluator: Optional[PermissionEvaluator], +) -> Tuple[bool, str]: + kind = request.get("kind") + if not kind: + return False, "UNKNOWN_PERMISSION_KIND" + + if kind in profile.deny_kinds: + return False, "PROFILE_EXPLICIT_DENY" + + if evaluator is not None: + try: + evaluated = await _resolve(evaluator(request, invocation)) + except Exception: + return False, "APPLICATION_EVALUATOR_FAILED" + if evaluated == "approve": + return True, "APPLICATION_EVALUATOR_APPROVED" + if evaluated == "deny": + return False, "APPLICATION_EVALUATOR_DENIED" + + if kind in profile.allow_kinds: + return True, "PROFILE_EXPLICIT_ALLOW" + + if profile.default_decision == "approve": + return True, "PROFILE_DEFAULT_ALLOW" + return False, "PROFILE_DEFAULT_DENY" + + +def create_sovrint_permission_handler( + profile: SovrintSecurityProfile, + *, + audit_sink: Optional[AuditSink] = None, + evaluate: Optional[PermissionEvaluator] = None, + downstream: Optional[PermissionHandler] = None, +) -> PermissionHandler: + """Create a most-restrictive-wins SDK permission handler.""" + + async def handler( + request: PermissionRequest, + invocation: Dict[str, str], + ) -> PermissionRequestResult: + approved, reason_code = await _evaluate_profile_permission( + profile, request, invocation, evaluate + ) + + if approved and downstream is not None: + try: + downstream_result = await _resolve(downstream(request, invocation)) + if downstream_result.get("kind") != "approved": + approved = False + reason_code = "DOWNSTREAM_HANDLER_DENIED" + except Exception: + approved = False + reason_code = "DOWNSTREAM_HANDLER_FAILED" + + event = _audit_event( + profile, + event_class="PERMISSION_DECISION", + session_id=invocation.get("session_id", invocation.get("sessionId", "unknown")), + tool_call_id=request.get("toolCallId"), + permission_kind=request.get("kind", "unknown"), + decision="APPROVED" if approved else "DENIED", + reason_code=reason_code, + ) + audit_recorded = await _emit_audit(profile, audit_sink, event) + if not audit_recorded and approved: + return _permission_result(False, profile, "AUDIT_SINK_UNAVAILABLE") + + return _permission_result(approved, profile, reason_code) + + return handler + + +def _intersection(left: Sequence[str], right: Sequence[str]) -> list: + allowed = set(right) + return [value for value in left if value in allowed] + + +def _merge_unique(left: Sequence[str], right: Sequence[str]) -> list: + return list(dict.fromkeys(list(left) + list(right))) + + +def _apply_system_message( + config: SessionConfig, + profile: SovrintSecurityProfile, +) -> Optional[Dict[str, str]]: + current = config.get("system_message") + if current and current.get("mode") == "replace": + if profile.forbid_system_message_replace: + raise ValueError( + "SOVRINT profile '{0}' forbids system-message replacement".format( + profile.profile_id + ) + ) + return current + + existing = (current or {}).get("content", "").strip() + appended = profile.system_message_append.strip() + content = "\n\n".join(value for value in (existing, appended) if value) + if not content: + return current + return {"mode": "append", "content": content} + + +def apply_sovrint_profile( + config: SessionConfig, + profile: SovrintSecurityProfile, + *, + audit_sink: Optional[AuditSink] = None, + evaluate_permission: Optional[PermissionEvaluator] = None, +) -> SessionConfig: + """Return a copied SessionConfig constrained by a SOVRINT profile.""" + + result: SessionConfig = dict(config) + + if profile.tool_surface_mode == "none": + result["available_tools"] = [] + elif profile.tool_surface_mode == "allowlist": + configured = config.get("available_tools") + profile_tools = list(profile.available_tools) + result["available_tools"] = ( + _intersection(configured, profile_tools) if configured is not None else profile_tools + ) + + result["excluded_tools"] = _merge_unique( + config.get("excluded_tools", []), profile.excluded_tools + ) + result["system_message"] = _apply_system_message(config, profile) + result["on_permission_request"] = create_sovrint_permission_handler( + profile, + audit_sink=audit_sink, + evaluate=evaluate_permission, + downstream=config.get("on_permission_request"), + ) + + custom_agents = config.get("custom_agents") + if custom_agents is not None and profile.tool_surface_mode != "inherit": + constrained_agents = [] + for agent in custom_agents: + constrained = dict(agent) + if profile.tool_surface_mode == "none": + constrained["tools"] = [] + else: + agent_tools = agent.get("tools") or list(profile.available_tools) + constrained["tools"] = _intersection(agent_tools, profile.available_tools) + constrained_agents.append(constrained) + result["custom_agents"] = constrained_agents + + mcp_servers = config.get("mcp_servers") + if mcp_servers is not None: + allowed_servers = set(profile.allowed_mcp_servers) + result["mcp_servers"] = { + name: server for name, server in mcp_servers.items() if name in allowed_servers + } + + skill_directories = config.get("skill_directories") + if skill_directories is not None: + allowed_directories = set(profile.allowed_skill_directories) + result["skill_directories"] = [ + directory for directory in skill_directories if directory in allowed_directories + ] + + result["disabled_skills"] = _merge_unique( + config.get("disabled_skills", []), profile.disabled_skills + ) + return result + + +def _rejected_tool_result(reason_code: str) -> ToolResult: + return ToolResult( + textResultForLlm="The governed tool invocation was not authorized.", + resultType="rejected", + error=reason_code, + toolTelemetry={"source": "sovrint", "reasonCode": reason_code}, + ) + + +def wrap_sovrint_tool( + tool: Tool, + profile: SovrintSecurityProfile, + *, + audit_sink: Optional[AuditSink] = None, + authorize: Optional[ToolAuthorizer] = None, +) -> Tool: + """Wrap an SDK Tool with authorization and bounded audit events.""" + + original_handler = tool.handler + + async def guarded_handler(invocation: ToolInvocation) -> ToolResult: + session_id = invocation.get("session_id", "unknown") + tool_call_id = invocation.get("tool_call_id") + started = _audit_event( + profile, + event_class="TOOL_INVOCATION_STARTED", + session_id=session_id, + tool_call_id=tool_call_id, + tool_name=tool.name, + decision="PENDING", + reason_code="TOOL_INVOCATION_RECEIVED", + ) + + if not await _emit_audit(profile, audit_sink, started): + return _rejected_tool_result("AUDIT_SINK_UNAVAILABLE") + + if authorize is not None: + try: + authorized = bool(await _resolve(authorize(invocation))) + except Exception: + authorized = False + if not authorized: + await _emit_audit( + profile, + audit_sink, + _audit_event( + profile, + event_class="TOOL_INVOCATION_REJECTED", + session_id=session_id, + tool_call_id=tool_call_id, + tool_name=tool.name, + decision="REJECTED", + reason_code="CUSTOM_TOOL_AUTHORIZATION_DENIED", + parent_event_id=started["eventId"], + ), + ) + return _rejected_tool_result("CUSTOM_TOOL_AUTHORIZATION_DENIED") + + await _emit_audit( + profile, + audit_sink, + _audit_event( + profile, + event_class="TOOL_INVOCATION_APPROVED", + session_id=session_id, + tool_call_id=tool_call_id, + tool_name=tool.name, + decision="APPROVED", + reason_code="CUSTOM_TOOL_AUTHORIZED", + parent_event_id=started["eventId"], + ), + ) + + try: + result = await _resolve(original_handler(invocation)) + except Exception as exc: + await _emit_audit( + profile, + audit_sink, + _audit_event( + profile, + event_class="TOOL_INVOCATION_FAILED", + session_id=session_id, + tool_call_id=tool_call_id, + tool_name=tool.name, + decision="FAILED", + reason_code="CUSTOM_TOOL_FAILED", + parent_event_id=started["eventId"], + ), + ) + return ToolResult( + textResultForLlm="The governed tool invocation failed.", + resultType="failure", + error=str(exc), + toolTelemetry={"source": "sovrint"}, + ) + + await _emit_audit( + profile, + audit_sink, + _audit_event( + profile, + event_class="TOOL_INVOCATION_COMPLETED", + session_id=session_id, + tool_call_id=tool_call_id, + tool_name=tool.name, + decision="RECORDED", + reason_code="CUSTOM_TOOL_COMPLETED", + parent_event_id=started["eventId"], + ), + ) + return result + + return Tool( + name=tool.name, + description=tool.description, + parameters=tool.parameters, + handler=guarded_handler, + ) From 793bad7d6faf56c97295c4ee03cfbf8f54eece16 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:29:10 -0400 Subject: [PATCH 15/63] Export Python SOVRINT helpers --- python/copilot/__init__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index 47a4ab6d93..76666b7ea1 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -6,6 +6,16 @@ from .client import CopilotClient from .session import CopilotSession +from .sovrint import ( + SOVRINT_READ_ONLY_PROFILE, + SOVRINT_RESEARCH_PROFILE, + SOVRINT_STRICT_PROFILE, + SOVRINT_SYSTEM_APPEND, + SovrintSecurityProfile, + apply_sovrint_profile, + create_sovrint_permission_handler, + wrap_sovrint_tool, +) from .tools import define_tool from .types import ( AzureProviderOptions, @@ -57,11 +67,19 @@ "PermissionRequestResult", "ProviderConfig", "ResumeSessionConfig", + "SOVRINT_READ_ONLY_PROFILE", + "SOVRINT_RESEARCH_PROFILE", + "SOVRINT_STRICT_PROFILE", + "SOVRINT_SYSTEM_APPEND", "SessionConfig", "SessionEvent", + "SovrintSecurityProfile", "Tool", "ToolHandler", "ToolInvocation", "ToolResult", + "apply_sovrint_profile", + "create_sovrint_permission_handler", "define_tool", + "wrap_sovrint_tool", ] From 0d488aa9b27fc9758a5b0b806ffc1074f6a783bb Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:29:27 -0400 Subject: [PATCH 16/63] Add Python SOVRINT helper tests --- python/test_sovrint.py | 209 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 python/test_sovrint.py diff --git a/python/test_sovrint.py b/python/test_sovrint.py new file mode 100644 index 0000000000..7344b69737 --- /dev/null +++ b/python/test_sovrint.py @@ -0,0 +1,209 @@ +import pytest + +from copilot import ( + SOVRINT_READ_ONLY_PROFILE, + SOVRINT_RESEARCH_PROFILE, + SOVRINT_STRICT_PROFILE, + Tool, + apply_sovrint_profile, + create_sovrint_permission_handler, + wrap_sovrint_tool, +) + + +@pytest.mark.asyncio +async def test_strict_profile_preserves_explicit_denial(): + events = [] + handler = create_sovrint_permission_handler( + SOVRINT_STRICT_PROFILE, + audit_sink=events.append, + evaluate=lambda request, invocation: "approve", + ) + + result = await handler( + {"kind": "read", "toolCallId": "call-1"}, + {"session_id": "session-1"}, + ) + + assert result["kind"] == "denied-by-rules" + assert events[0]["reasonCode"] == "PROFILE_EXPLICIT_DENY" + + +@pytest.mark.asyncio +async def test_read_only_profile_approves_read_and_denies_write(): + events = [] + handler = create_sovrint_permission_handler( + SOVRINT_READ_ONLY_PROFILE, + audit_sink=events.append, + ) + + read = await handler({"kind": "read"}, {"session_id": "session-2"}) + write = await handler({"kind": "write"}, {"session_id": "session-2"}) + + assert read["kind"] == "approved" + assert write["kind"] == "denied-by-rules" + assert [event["decision"] for event in events] == ["APPROVED", "DENIED"] + + +@pytest.mark.asyncio +async def test_research_evaluator_can_approve_non_default_request(): + handler = create_sovrint_permission_handler( + SOVRINT_RESEARCH_PROFILE, + audit_sink=lambda event: None, + evaluate=lambda request, invocation: ( + "approve" if request.get("kind") == "url" else None + ), + ) + + result = await handler({"kind": "url"}, {"session_id": "session-3"}) + assert result["kind"] == "approved" + + +@pytest.mark.asyncio +async def test_downstream_denial_is_preserved(): + async def downstream(request, invocation): + return {"kind": "denied-interactively-by-user"} + + handler = create_sovrint_permission_handler( + SOVRINT_READ_ONLY_PROFILE, + audit_sink=lambda event: None, + downstream=downstream, + ) + + result = await handler({"kind": "read"}, {"session_id": "session-4"}) + assert result["kind"] == "denied-by-rules" + assert result["rules"][0]["reasonCode"] == "DOWNSTREAM_HANDLER_DENIED" + + +@pytest.mark.asyncio +async def test_audit_failure_fails_closed(): + def failing_sink(event): + raise RuntimeError("sink unavailable") + + handler = create_sovrint_permission_handler( + SOVRINT_READ_ONLY_PROFILE, + audit_sink=failing_sink, + ) + + result = await handler({"kind": "read"}, {"session_id": "session-5"}) + assert result["kind"] == "denied-by-rules" + assert result["rules"][0]["reasonCode"] == "AUDIT_SINK_UNAVAILABLE" + + +def test_system_message_replacement_is_rejected(): + with pytest.raises(ValueError, match="forbids system-message replacement"): + apply_sovrint_profile( + {"system_message": {"mode": "replace", "content": "replacement"}}, + SOVRINT_READ_ONLY_PROFILE, + audit_sink=lambda event: None, + ) + + +def test_strict_profile_removes_inherited_surfaces(): + config = apply_sovrint_profile( + { + "available_tools": ["read_file", "write_file"], + "mcp_servers": { + "alpha": { + "type": "http", + "url": "https://example.invalid", + "tools": ["*"], + } + }, + "skill_directories": ["./skills"], + "custom_agents": [ + { + "name": "worker", + "prompt": "work", + "tools": None, + } + ], + }, + SOVRINT_STRICT_PROFILE, + audit_sink=lambda event: None, + ) + + assert config["available_tools"] == [] + assert config["mcp_servers"] == {} + assert config["skill_directories"] == [] + assert config["custom_agents"][0]["tools"] == [] + assert config["system_message"]["mode"] == "append" + + +@pytest.mark.asyncio +async def test_guarded_tool_rejects_without_calling_original_handler(): + calls = [] + + async def original(invocation): + calls.append(invocation) + return { + "textResultForLlm": "ok", + "resultType": "success", + } + + tool = Tool( + name="mutate_state", + description="Test tool", + parameters={"type": "object"}, + handler=original, + ) + guarded = wrap_sovrint_tool( + tool, + SOVRINT_READ_ONLY_PROFILE, + audit_sink=lambda event: None, + authorize=lambda invocation: False, + ) + + result = await guarded.handler( + { + "session_id": "session-6", + "tool_call_id": "tool-call-1", + "tool_name": "mutate_state", + "arguments": {"value": "x"}, + } + ) + + assert calls == [] + assert result["resultType"] == "rejected" + assert result["error"] == "CUSTOM_TOOL_AUTHORIZATION_DENIED" + + +@pytest.mark.asyncio +async def test_guarded_tool_records_completion(): + events = [] + + async def original(invocation): + return { + "textResultForLlm": "ok", + "resultType": "success", + } + + tool = Tool( + name="inspect_state", + description="Test tool", + parameters={"type": "object"}, + handler=original, + ) + guarded = wrap_sovrint_tool( + tool, + SOVRINT_READ_ONLY_PROFILE, + audit_sink=events.append, + authorize=lambda invocation: True, + ) + + result = await guarded.handler( + { + "session_id": "session-7", + "tool_call_id": "tool-call-2", + "tool_name": "inspect_state", + "arguments": {}, + } + ) + + assert result["resultType"] == "success" + assert [event["eventClass"] for event in events] == [ + "TOOL_INVOCATION_STARTED", + "TOOL_INVOCATION_APPROVED", + "TOOL_INVOCATION_COMPLETED", + ] + assert all("arguments" not in event and "result" not in event for event in events) From 75d39ffeae7bbcf4e1f174296d6b55e1953b2e6a Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:30:11 -0400 Subject: [PATCH 17/63] Add Go SOVRINT governed-session helpers --- go/sovrint.go | 522 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 522 insertions(+) create mode 100644 go/sovrint.go diff --git a/go/sovrint.go b/go/sovrint.go new file mode 100644 index 0000000000..f41310586f --- /dev/null +++ b/go/sovrint.go @@ -0,0 +1,522 @@ +package copilot + +import ( + "fmt" + "sync/atomic" + "time" +) + +// SovrintPermissionDecision is a bounded permission outcome used by an +// application evaluator before the SDK server receives a final decision. +type SovrintPermissionDecision string + +const ( + SovrintApprove SovrintPermissionDecision = "approve" + SovrintDeny SovrintPermissionDecision = "deny" +) + +// SovrintSecurityProfile defines a versioned governed-session profile. +type SovrintSecurityProfile struct { + ProfileID string + Version string + Description string + DefaultDecision SovrintPermissionDecision + AllowKinds []string + DenyKinds []string + ToolSurfaceMode string // inherit, allowlist, none + AvailableTools []string + ExcludedTools []string + AllowedMCPServers []string + AllowedSkillDirectories []string + DisabledSkills []string + ForbidSystemMessageReplace bool + FailClosedOnAuditError bool + AuditEnabled bool + SystemMessageAppend string +} + +// SovrintAuditEvent is a bounded event. It intentionally excludes tool +// arguments, model responses, file contents, provider credentials, and raw +// command output. +type SovrintAuditEvent struct { + SchemaVersion string `json:"schemaVersion"` + EventID string `json:"eventId"` + ParentEventID string `json:"parentEventId,omitempty"` + EventClass string `json:"eventClass"` + TimestampUTC string `json:"timestampUtc"` + ProfileID string `json:"profileId"` + ProfileVersion string `json:"profileVersion"` + SessionID string `json:"sessionId"` + ToolCallID string `json:"toolCallId,omitempty"` + ToolName string `json:"toolName,omitempty"` + PermissionKind string `json:"permissionKind,omitempty"` + Decision string `json:"decision"` + ReasonCode string `json:"reasonCode"` + DisclosureClass string `json:"disclosureClass"` + EvidenceStatus string `json:"evidenceStatus"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +// SovrintAuditSink records a bounded audit event. +type SovrintAuditSink func(event SovrintAuditEvent) error + +// SovrintPermissionEvaluator returns a decision or an empty decision to defer +// to the profile. +type SovrintPermissionEvaluator func( + request PermissionRequest, + invocation PermissionInvocation, +) (SovrintPermissionDecision, error) + +// SovrintPermissionHandlerOptions configures permission composition. +type SovrintPermissionHandlerOptions struct { + AuditSink SovrintAuditSink + Evaluate SovrintPermissionEvaluator + Downstream PermissionHandler +} + +// SovrintApplyProfileOptions configures profile application. +type SovrintApplyProfileOptions struct { + AuditSink SovrintAuditSink + EvaluatePermission SovrintPermissionEvaluator +} + +// SovrintToolAuthorizer approves a caller-defined tool invocation. +type SovrintToolAuthorizer func(invocation ToolInvocation) (bool, error) + +// SovrintToolGuardOptions configures a guarded custom tool. +type SovrintToolGuardOptions struct { + Profile SovrintSecurityProfile + AuditSink SovrintAuditSink + Authorize SovrintToolAuthorizer +} + +const SovrintSystemAppend = "Operate under a bounded SOVRINT governed-session profile. Use only explicitly exposed tools and declared authority. Treat observations, inferences, recommendations, governance decisions, integrity findings, and accepted evidence as distinct classes. Do not claim approval, verification, restoration, or EvidenceGrid acceptance without an explicit external result." + +var SovrintStrictProfile = SovrintSecurityProfile{ + ProfileID: "sovrint.strict", + Version: "1.0", + Description: "Deny every permission and expose no inherited first-party tools.", + DefaultDecision: SovrintDeny, + DenyKinds: []string{"read", "write", "shell", "url", "mcp"}, + ToolSurfaceMode: "none", + AllowedMCPServers: []string{}, + AllowedSkillDirectories: []string{}, + ForbidSystemMessageReplace: true, + FailClosedOnAuditError: true, + AuditEnabled: true, + SystemMessageAppend: SovrintSystemAppend, +} + +var SovrintReadOnlyProfile = SovrintSecurityProfile{ + ProfileID: "sovrint.read-only", + Version: "1.0", + Description: "Permit reads while denying mutating and external permission kinds.", + DefaultDecision: SovrintDeny, + AllowKinds: []string{"read"}, + DenyKinds: []string{"write", "shell", "url", "mcp"}, + ToolSurfaceMode: "inherit", + AllowedMCPServers: []string{}, + AllowedSkillDirectories: []string{}, + ForbidSystemMessageReplace: true, + FailClosedOnAuditError: true, + AuditEnabled: true, + SystemMessageAppend: SovrintSystemAppend + " Operate in read-only mode.", +} + +var SovrintResearchProfile = SovrintSecurityProfile{ + ProfileID: "sovrint.research", + Version: "1.0", + Description: "Permit reads and require an evaluator for every other permission kind.", + DefaultDecision: SovrintDeny, + AllowKinds: []string{"read"}, + DenyKinds: []string{}, + ToolSurfaceMode: "inherit", + AllowedMCPServers: []string{}, + AllowedSkillDirectories: []string{}, + ForbidSystemMessageReplace: true, + FailClosedOnAuditError: false, + AuditEnabled: true, + SystemMessageAppend: SovrintSystemAppend + " Separate research observations from verified findings.", +} + +var sovrintAuditSequence uint64 + +func newSovrintAuditEvent( + profile SovrintSecurityProfile, + eventClass string, + sessionID string, + decision string, + reasonCode string, +) SovrintAuditEvent { + sequence := atomic.AddUint64(&sovrintAuditSequence, 1) + return SovrintAuditEvent{ + SchemaVersion: "1.0", + EventID: fmt.Sprintf("sovrint-%d-%d", time.Now().UnixMilli(), sequence), + EventClass: eventClass, + TimestampUTC: time.Now().UTC().Format(time.RFC3339Nano), + ProfileID: profile.ProfileID, + ProfileVersion: profile.Version, + SessionID: sessionID, + Decision: decision, + ReasonCode: reasonCode, + DisclosureClass: "INTERNAL", + EvidenceStatus: "NOT_SUBMITTED", + } +} + +func emitSovrintAudit( + profile SovrintSecurityProfile, + sink SovrintAuditSink, + event SovrintAuditEvent, +) bool { + if !profile.AuditEnabled { + return true + } + if sink == nil { + return !profile.FailClosedOnAuditError + } + if err := sink(event); err != nil { + return !profile.FailClosedOnAuditError + } + return true +} + +func containsString(values []string, target string) bool { + for _, value := range values { + if value == target { + return true + } + } + return false +} + +func sovrintPermissionResult( + approved bool, + profile SovrintSecurityProfile, + reasonCode string, +) PermissionRequestResult { + kind := "denied-by-rules" + if approved { + kind = "approved" + } + return PermissionRequestResult{ + Kind: kind, + Rules: []interface{}{ + map[string]interface{}{ + "source": "sovrint", + "profileId": profile.ProfileID, + "profileVersion": profile.Version, + "reasonCode": reasonCode, + }, + }, + } +} + +func evaluateSovrintPermission( + profile SovrintSecurityProfile, + request PermissionRequest, + invocation PermissionInvocation, + evaluator SovrintPermissionEvaluator, +) (bool, string) { + if request.Kind == "" { + return false, "UNKNOWN_PERMISSION_KIND" + } + if containsString(profile.DenyKinds, request.Kind) { + return false, "PROFILE_EXPLICIT_DENY" + } + if evaluator != nil { + decision, err := evaluator(request, invocation) + if err != nil { + return false, "APPLICATION_EVALUATOR_FAILED" + } + if decision == SovrintApprove { + return true, "APPLICATION_EVALUATOR_APPROVED" + } + if decision == SovrintDeny { + return false, "APPLICATION_EVALUATOR_DENIED" + } + } + if containsString(profile.AllowKinds, request.Kind) { + return true, "PROFILE_EXPLICIT_ALLOW" + } + if profile.DefaultDecision == SovrintApprove { + return true, "PROFILE_DEFAULT_ALLOW" + } + return false, "PROFILE_DEFAULT_DENY" +} + +// CreateSovrintPermissionHandler composes the profile, application evaluator, +// and any existing handler using most-restrictive-wins semantics. +func CreateSovrintPermissionHandler( + profile SovrintSecurityProfile, + options SovrintPermissionHandlerOptions, +) PermissionHandler { + return func( + request PermissionRequest, + invocation PermissionInvocation, + ) (PermissionRequestResult, error) { + approved, reasonCode := evaluateSovrintPermission( + profile, + request, + invocation, + options.Evaluate, + ) + + if approved && options.Downstream != nil { + downstreamResult, err := options.Downstream(request, invocation) + if err != nil { + approved = false + reasonCode = "DOWNSTREAM_HANDLER_FAILED" + } else if downstreamResult.Kind != "approved" { + approved = false + reasonCode = "DOWNSTREAM_HANDLER_DENIED" + } + } + + event := newSovrintAuditEvent( + profile, + "PERMISSION_DECISION", + invocation.SessionID, + map[bool]string{true: "APPROVED", false: "DENIED"}[approved], + reasonCode, + ) + event.ToolCallID = request.ToolCallID + event.PermissionKind = request.Kind + + if !emitSovrintAudit(profile, options.AuditSink, event) && approved { + return sovrintPermissionResult(false, profile, "AUDIT_SINK_UNAVAILABLE"), nil + } + return sovrintPermissionResult(approved, profile, reasonCode), nil + } +} + +func copyStrings(values []string) []string { + if values == nil { + return nil + } + return append([]string{}, values...) +} + +func intersectStrings(left []string, right []string) []string { + allowed := make(map[string]struct{}, len(right)) + for _, value := range right { + allowed[value] = struct{}{} + } + result := make([]string, 0) + for _, value := range left { + if _, ok := allowed[value]; ok { + result = append(result, value) + } + } + return result +} + +func mergeUniqueStrings(left []string, right []string) []string { + seen := make(map[string]struct{}, len(left)+len(right)) + result := make([]string, 0, len(left)+len(right)) + for _, value := range append(copyStrings(left), right...) { + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + result = append(result, value) + } + return result +} + +// ApplySovrintProfile returns a copied SessionConfig constrained by the profile. +func ApplySovrintProfile( + config *SessionConfig, + profile SovrintSecurityProfile, + options SovrintApplyProfileOptions, +) (*SessionConfig, error) { + if config == nil { + config = &SessionConfig{} + } + result := *config + result.AvailableTools = copyStrings(config.AvailableTools) + result.ExcludedTools = copyStrings(config.ExcludedTools) + result.SkillDirectories = copyStrings(config.SkillDirectories) + result.DisabledSkills = copyStrings(config.DisabledSkills) + + if profile.ToolSurfaceMode == "none" { + result.AvailableTools = []string{} + } else if profile.ToolSurfaceMode == "allowlist" { + if config.AvailableTools == nil { + result.AvailableTools = copyStrings(profile.AvailableTools) + } else { + result.AvailableTools = intersectStrings(config.AvailableTools, profile.AvailableTools) + } + } + result.ExcludedTools = mergeUniqueStrings(config.ExcludedTools, profile.ExcludedTools) + + if config.SystemMessage != nil && config.SystemMessage.Mode == "replace" { + if profile.ForbidSystemMessageReplace { + return nil, fmt.Errorf( + "SOVRINT profile %q forbids system-message replacement", + profile.ProfileID, + ) + } + copied := *config.SystemMessage + result.SystemMessage = &copied + } else { + existing := "" + if config.SystemMessage != nil { + existing = config.SystemMessage.Content + } + content := profile.SystemMessageAppend + if existing != "" && content != "" { + content = existing + "\n\n" + content + } else if existing != "" { + content = existing + } + if content != "" { + result.SystemMessage = &SystemMessageConfig{Mode: "append", Content: content} + } + } + + result.OnPermissionRequest = CreateSovrintPermissionHandler( + profile, + SovrintPermissionHandlerOptions{ + AuditSink: options.AuditSink, + Evaluate: options.EvaluatePermission, + Downstream: config.OnPermissionRequest, + }, + ) + + if config.CustomAgents != nil { + result.CustomAgents = append([]CustomAgentConfig{}, config.CustomAgents...) + if profile.ToolSurfaceMode != "inherit" { + for index := range result.CustomAgents { + if profile.ToolSurfaceMode == "none" { + result.CustomAgents[index].Tools = []string{} + } else { + tools := result.CustomAgents[index].Tools + if tools == nil { + tools = profile.AvailableTools + } + result.CustomAgents[index].Tools = intersectStrings(tools, profile.AvailableTools) + } + } + } + } + + if config.MCPServers != nil { + allowed := make(map[string]struct{}, len(profile.AllowedMCPServers)) + for _, name := range profile.AllowedMCPServers { + allowed[name] = struct{}{} + } + result.MCPServers = make(map[string]MCPServerConfig) + for name, server := range config.MCPServers { + if _, ok := allowed[name]; ok { + result.MCPServers[name] = server + } + } + } + + if config.SkillDirectories != nil { + result.SkillDirectories = intersectStrings( + config.SkillDirectories, + profile.AllowedSkillDirectories, + ) + } + result.DisabledSkills = mergeUniqueStrings(config.DisabledSkills, profile.DisabledSkills) + return &result, nil +} + +func rejectedSovrintToolResult(reasonCode string) ToolResult { + return ToolResult{ + TextResultForLLM: "The governed tool invocation was not authorized.", + ResultType: "rejected", + Error: reasonCode, + ToolTelemetry: map[string]interface{}{ + "source": "sovrint", + "reasonCode": reasonCode, + }, + } +} + +// WrapSovrintTool adds an application authorization gate and bounded audit +// events around a caller-defined tool. +func WrapSovrintTool(tool Tool, options SovrintToolGuardOptions) Tool { + original := tool.Handler + tool.Handler = func(invocation ToolInvocation) (ToolResult, error) { + started := newSovrintAuditEvent( + options.Profile, + "TOOL_INVOCATION_STARTED", + invocation.SessionID, + "PENDING", + "TOOL_INVOCATION_RECEIVED", + ) + started.ToolCallID = invocation.ToolCallID + started.ToolName = tool.Name + if !emitSovrintAudit(options.Profile, options.AuditSink, started) { + return rejectedSovrintToolResult("AUDIT_SINK_UNAVAILABLE"), nil + } + + if options.Authorize != nil { + authorized, err := options.Authorize(invocation) + if err != nil || !authorized { + rejected := newSovrintAuditEvent( + options.Profile, + "TOOL_INVOCATION_REJECTED", + invocation.SessionID, + "REJECTED", + "CUSTOM_TOOL_AUTHORIZATION_DENIED", + ) + rejected.ParentEventID = started.EventID + rejected.ToolCallID = invocation.ToolCallID + rejected.ToolName = tool.Name + emitSovrintAudit(options.Profile, options.AuditSink, rejected) + return rejectedSovrintToolResult("CUSTOM_TOOL_AUTHORIZATION_DENIED"), nil + } + } + + approved := newSovrintAuditEvent( + options.Profile, + "TOOL_INVOCATION_APPROVED", + invocation.SessionID, + "APPROVED", + "CUSTOM_TOOL_AUTHORIZED", + ) + approved.ParentEventID = started.EventID + approved.ToolCallID = invocation.ToolCallID + approved.ToolName = tool.Name + emitSovrintAudit(options.Profile, options.AuditSink, approved) + + result, err := original(invocation) + if err != nil { + failed := newSovrintAuditEvent( + options.Profile, + "TOOL_INVOCATION_FAILED", + invocation.SessionID, + "FAILED", + "CUSTOM_TOOL_FAILED", + ) + failed.ParentEventID = started.EventID + failed.ToolCallID = invocation.ToolCallID + failed.ToolName = tool.Name + emitSovrintAudit(options.Profile, options.AuditSink, failed) + return ToolResult{ + TextResultForLLM: "The governed tool invocation failed.", + ResultType: "failure", + Error: err.Error(), + ToolTelemetry: map[string]interface{}{"source": "sovrint"}, + }, nil + } + + completed := newSovrintAuditEvent( + options.Profile, + "TOOL_INVOCATION_COMPLETED", + invocation.SessionID, + "RECORDED", + "CUSTOM_TOOL_COMPLETED", + ) + completed.ParentEventID = started.EventID + completed.ToolCallID = invocation.ToolCallID + completed.ToolName = tool.Name + emitSovrintAudit(options.Profile, options.AuditSink, completed) + return result, nil + } + return tool +} From 5c57a3e5b7ddb59a5c0f3650a486207a514ee152 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:30:53 -0400 Subject: [PATCH 18/63] Add Go SOVRINT helper tests --- go/sovrint_test.go | 127 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 go/sovrint_test.go diff --git a/go/sovrint_test.go b/go/sovrint_test.go new file mode 100644 index 0000000000..ae620ca0ab --- /dev/null +++ b/go/sovrint_test.go @@ -0,0 +1,127 @@ +package copilot + +import "testing" + +func auditOK(event SovrintAuditEvent) error { return nil } + +func TestSovrintPermissionProfiles(t *testing.T) { + strict := CreateSovrintPermissionHandler( + SovrintStrictProfile, + SovrintPermissionHandlerOptions{AuditSink: auditOK}, + ) + result, err := strict( + PermissionRequest{Kind: "read"}, + PermissionInvocation{SessionID: "session-1"}, + ) + if err != nil || result.Kind != "denied-by-rules" { + t.Fatalf("strict profile should deny read: %#v %v", result, err) + } + + readOnly := CreateSovrintPermissionHandler( + SovrintReadOnlyProfile, + SovrintPermissionHandlerOptions{AuditSink: auditOK}, + ) + read, _ := readOnly( + PermissionRequest{Kind: "read"}, + PermissionInvocation{SessionID: "session-2"}, + ) + write, _ := readOnly( + PermissionRequest{Kind: "write"}, + PermissionInvocation{SessionID: "session-2"}, + ) + if read.Kind != "approved" || write.Kind != "denied-by-rules" { + t.Fatalf("unexpected read-only decisions: read=%#v write=%#v", read, write) + } +} + +func TestSovrintResearchEvaluator(t *testing.T) { + handler := CreateSovrintPermissionHandler( + SovrintResearchProfile, + SovrintPermissionHandlerOptions{ + AuditSink: auditOK, + Evaluate: func( + request PermissionRequest, + invocation PermissionInvocation, + ) (SovrintPermissionDecision, error) { + if request.Kind == "url" { + return SovrintApprove, nil + } + return "", nil + }, + }, + ) + result, err := handler( + PermissionRequest{Kind: "url"}, + PermissionInvocation{SessionID: "session-3"}, + ) + if err != nil || result.Kind != "approved" { + t.Fatalf("research evaluator should approve: %#v %v", result, err) + } +} + +func TestApplySovrintProfile(t *testing.T) { + _, err := ApplySovrintProfile( + &SessionConfig{ + SystemMessage: &SystemMessageConfig{Mode: "replace", Content: "replacement"}, + }, + SovrintReadOnlyProfile, + SovrintApplyProfileOptions{AuditSink: auditOK}, + ) + if err == nil { + t.Fatal("expected replacement rejection") + } + + config, err := ApplySovrintProfile( + &SessionConfig{ + AvailableTools: []string{"read_file", "write_file"}, + SkillDirectories: []string{"skills"}, + CustomAgents: []CustomAgentConfig{ + {Name: "worker", Prompt: "work"}, + }, + }, + SovrintStrictProfile, + SovrintApplyProfileOptions{AuditSink: auditOK}, + ) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if config.AvailableTools == nil || len(config.AvailableTools) != 0 { + t.Fatalf("expected explicit empty tool surface: %#v", config.AvailableTools) + } + if len(config.SkillDirectories) != 0 { + t.Fatalf("expected no skill directories: %#v", config.SkillDirectories) + } + if len(config.CustomAgents) != 1 || config.CustomAgents[0].Tools == nil { + t.Fatalf("expected constrained custom agent: %#v", config.CustomAgents) + } +} + +func TestWrapSovrintTool(t *testing.T) { + called := false + tool := Tool{ + Name: "inspect_state", + Handler: func(invocation ToolInvocation) (ToolResult, error) { + called = true + return ToolResult{TextResultForLLM: "ok", ResultType: "success"}, nil + }, + } + guarded := WrapSovrintTool( + tool, + SovrintToolGuardOptions{ + Profile: SovrintReadOnlyProfile, + AuditSink: auditOK, + Authorize: func(invocation ToolInvocation) (bool, error) { return false, nil }, + }, + ) + result, err := guarded.Handler(ToolInvocation{ + SessionID: "session-4", + ToolCallID: "call-1", + ToolName: "inspect_state", + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if called || result.ResultType != "rejected" { + t.Fatalf("expected guarded rejection: called=%v result=%#v", called, result) + } +} From 5ba28ad83a44788b5f12edcd690b34bd98308e7b Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:31:34 -0400 Subject: [PATCH 19/63] Add TypeScript governed-session recipe --- cookbook/nodejs/sovrint-governed-session.md | 89 +++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 cookbook/nodejs/sovrint-governed-session.md diff --git a/cookbook/nodejs/sovrint-governed-session.md b/cookbook/nodejs/sovrint-governed-session.md new file mode 100644 index 0000000000..1e853677ce --- /dev/null +++ b/cookbook/nodejs/sovrint-governed-session.md @@ -0,0 +1,89 @@ +# SOVRINT Governed Session — Node.js / TypeScript + +This recipe creates a Copilot SDK session with a bounded SOVRINT profile, a fail-closed audit sink, and a guarded custom tool. + +## Example + +```typescript +import { + CopilotClient, + SOVRINT_READ_ONLY_PROFILE, + applySovrintProfile, + defineTool, + wrapSovrintTool, + type SovrintAuditEvent, +} from "@github/copilot-sdk"; + +const auditEvents: SovrintAuditEvent[] = []; +const auditSink = async (event: SovrintAuditEvent) => { + // Replace this with a bounded append-only sink. + // Do not add prompts, tool arguments, results, or credentials. + auditEvents.push(event); +}; + +const inspectState = defineTool("inspect_state", { + description: "Return a bounded health summary for a named component", + parameters: { + type: "object", + properties: { + component: { type: "string" }, + }, + required: ["component"], + }, + handler: async ({ component }: { component: string }) => ({ + component, + status: "observation-only", + }), +}); + +const governedInspectState = wrapSovrintTool(inspectState, { + profile: SOVRINT_READ_ONLY_PROFILE, + auditSink, + authorize: async (_args, invocation) => invocation.sessionId.length > 0, +}); + +const sessionConfig = applySovrintProfile( + { + model: "gpt-5", + tools: [governedInspectState], + streaming: true, + systemMessage: { + mode: "append", + content: "Return observations without representing them as governance decisions.", + }, + }, + SOVRINT_READ_ONLY_PROFILE, + { + auditSink, + } +); + +const client = new CopilotClient(); +const session = await client.createSession(sessionConfig); + +const response = await session.sendAndWait({ + prompt: "Use inspect_state for component registry-alpha and summarize the observation.", +}); + +console.log(response?.data.content); +console.log(`Recorded ${auditEvents.length} bounded audit events.`); + +await client.stop(); +``` + +## What the profile enforces + +- `read` permission requests may pass. +- `write`, `shell`, `url`, and `mcp` requests are denied. +- system-message replacement throws before session creation. +- any pre-existing permission handler is composed using most-restrictive-wins semantics. +- the wrapped tool records start, authorization, completion, rejection, or failure events. +- tool arguments and results are not placed in SOVRINT audit events. + +## Strict mode + +Replace `SOVRINT_READ_ONLY_PROFILE` with `SOVRINT_STRICT_PROFILE` to expose no inherited first-party tool surface and deny every permission kind. Caller-defined tools must still be explicitly supplied and guarded. + +## Research mode + +`SOVRINT_RESEARCH_PROFILE` permits reads and defers all other permission kinds to `evaluatePermission`. The evaluator should use deployment-specific allowlists and must return no decision when it cannot establish scope. From 52984b22e0e1c80ff1075a0ee2d1f36add138148 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:31:44 -0400 Subject: [PATCH 20/63] Add Python governed-session recipe --- cookbook/python/sovrint-governed-session.md | 96 +++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 cookbook/python/sovrint-governed-session.md diff --git a/cookbook/python/sovrint-governed-session.md b/cookbook/python/sovrint-governed-session.md new file mode 100644 index 0000000000..08f1d97509 --- /dev/null +++ b/cookbook/python/sovrint-governed-session.md @@ -0,0 +1,96 @@ +# SOVRINT Governed Session — Python + +This recipe creates a Copilot SDK session with a bounded SOVRINT profile, a fail-closed audit sink, and a guarded custom tool. + +## Example + +```python +import asyncio + +from copilot import ( + CopilotClient, + SOVRINT_READ_ONLY_PROFILE, + apply_sovrint_profile, + define_tool, + wrap_sovrint_tool, +) + + +audit_events = [] + + +def audit_sink(event): + # Replace this with a bounded append-only sink. + # Do not add prompts, tool arguments, results, or credentials. + audit_events.append(event) + + +@define_tool(description="Return a bounded health summary for a named component") +async def inspect_state(params: dict) -> dict: + return { + "component": params["component"], + "status": "observation-only", + } + + +governed_inspect_state = wrap_sovrint_tool( + inspect_state, + SOVRINT_READ_ONLY_PROFILE, + audit_sink=audit_sink, + authorize=lambda invocation: bool(invocation.get("session_id")), +) + + +async def main(): + client = CopilotClient() + await client.start() + + config = apply_sovrint_profile( + { + "model": "gpt-5", + "tools": [governed_inspect_state], + "streaming": True, + "system_message": { + "mode": "append", + "content": ( + "Return observations without representing them as governance decisions." + ), + }, + }, + SOVRINT_READ_ONLY_PROFILE, + audit_sink=audit_sink, + ) + + session = await client.create_session(config) + response = await session.send_and_wait( + { + "prompt": ( + "Use inspect_state for component registry-alpha and summarize the observation." + ) + } + ) + + print(response.data.content) + print(f"Recorded {len(audit_events)} bounded audit events.") + await client.stop() + + +asyncio.run(main()) +``` + +## What the profile enforces + +- `read` permission requests may pass. +- `write`, `shell`, `url`, and `mcp` requests are denied. +- system-message replacement raises `ValueError` before session creation. +- any pre-existing permission handler is composed using most-restrictive-wins semantics. +- the wrapped tool records start, authorization, completion, rejection, or failure events. +- tool arguments and results are not placed in SOVRINT audit events. + +## Strict mode + +Use `SOVRINT_STRICT_PROFILE` to expose no inherited first-party tool surface and deny every permission kind. + +## Research mode + +Use `SOVRINT_RESEARCH_PROFILE` with `evaluate_permission=` to approve a narrowly scoped request after application-specific validation. Returning `None` defers to the profile, which denies by default. From 4c2f40a86ad174ca08da9df163e4a85ee1df6ab9 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:31:55 -0400 Subject: [PATCH 21/63] Add Go governed-session recipe --- cookbook/go/sovrint-governed-session.md | 104 ++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 cookbook/go/sovrint-governed-session.md diff --git a/cookbook/go/sovrint-governed-session.md b/cookbook/go/sovrint-governed-session.md new file mode 100644 index 0000000000..a41992f5e1 --- /dev/null +++ b/cookbook/go/sovrint-governed-session.md @@ -0,0 +1,104 @@ +# SOVRINT Governed Session — Go + +This recipe creates a Copilot SDK session with a bounded SOVRINT profile, a fail-closed audit sink, and a guarded custom tool. + +## Example + +```go +package main + +import ( + "fmt" + "log" + + copilot "github.com/github/copilot-sdk/go" +) + +func main() { + auditEvents := make([]copilot.SovrintAuditEvent, 0) + auditSink := func(event copilot.SovrintAuditEvent) error { + // Replace this with a bounded append-only sink. + // Do not add prompts, tool arguments, results, or credentials. + auditEvents = append(auditEvents, event) + return nil + } + + inspectState := copilot.Tool{ + Name: "inspect_state", + Description: "Return a bounded health summary for a named component", + Parameters: map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "component": map[string]interface{}{"type": "string"}, + }, + "required": []string{"component"}, + }, + Handler: func(invocation copilot.ToolInvocation) (copilot.ToolResult, error) { + return copilot.ToolResult{ + TextResultForLLM: `{"status":"observation-only"}`, + ResultType: "success", + }, nil + }, + } + + governedInspectState := copilot.WrapSovrintTool( + inspectState, + copilot.SovrintToolGuardOptions{ + Profile: copilot.SovrintReadOnlyProfile, + AuditSink: auditSink, + Authorize: func(invocation copilot.ToolInvocation) (bool, error) { + return invocation.SessionID != "", nil + }, + }, + ) + + config, err := copilot.ApplySovrintProfile( + &copilot.SessionConfig{ + Model: "gpt-5", + Tools: []copilot.Tool{governedInspectState}, + Streaming: true, + SystemMessage: &copilot.SystemMessageConfig{ + Mode: "append", + Content: "Return observations without representing them as governance decisions.", + }, + }, + copilot.SovrintReadOnlyProfile, + copilot.SovrintApplyProfileOptions{AuditSink: auditSink}, + ) + if err != nil { + log.Fatal(err) + } + + client := copilot.NewClient(nil) + if err := client.Start(); err != nil { + log.Fatal(err) + } + defer client.Stop() + + session, err := client.CreateSession(config) + if err != nil { + log.Fatal(err) + } + + response, err := session.SendAndWait(copilot.MessageOptions{ + Prompt: "Use inspect_state for component registry-alpha and summarize the observation.", + }, 0) + if err != nil { + log.Fatal(err) + } + + fmt.Println(*response.Data.Content) + fmt.Printf("Recorded %d bounded audit events.\n", len(auditEvents)) +} +``` + +## What the profile enforces + +- `read` permission requests may pass. +- `write`, `shell`, `url`, and `mcp` requests are denied. +- system-message replacement returns an error before session creation. +- any pre-existing permission handler is composed using most-restrictive-wins semantics. +- the wrapped tool records start, authorization, completion, rejection, or failure events. +- tool arguments and results are not placed in SOVRINT audit events. + +Use `SovrintStrictProfile` to expose no inherited first-party tool surface. Use `SovrintResearchProfile` with `EvaluatePermission` for narrowly scoped application decisions. From a456987e989b5d2151000aba9ce9d99ee2d4c8bb Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:32:17 -0400 Subject: [PATCH 22/63] Add .NET governed-session recipe --- cookbook/dotnet/sovrint-governed-session.md | 107 ++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 cookbook/dotnet/sovrint-governed-session.md diff --git a/cookbook/dotnet/sovrint-governed-session.md b/cookbook/dotnet/sovrint-governed-session.md new file mode 100644 index 0000000000..78bd2499fb --- /dev/null +++ b/cookbook/dotnet/sovrint-governed-session.md @@ -0,0 +1,107 @@ +# SOVRINT Governed Session — .NET + +The TypeScript, Python, and Go packages in this repository include first-class SOVRINT helper modules. This .NET recipe applies the same control principles through the documented native `SessionConfig` surface. + +## Conservative profile + +```csharp +using GitHub.Copilot.SDK; + +const string SovrintSystemAppend = @" +Operate under a bounded SOVRINT governed-session profile. +Use only explicitly exposed tools and declared authority. +Treat observations, inferences, recommendations, governance decisions, +integrity findings, and accepted evidence as distinct classes. +Do not claim approval, verification, restoration, or EvidenceGrid acceptance +without an explicit external result. +"; + +static SessionConfig ApplySovrintStrictProfile(SessionConfig config) +{ + if (config.SystemMessage?.Mode == SystemMessageMode.Replace) + { + throw new InvalidOperationException( + "The SOVRINT strict profile forbids system-message replacement." + ); + } + + var existing = config.SystemMessage?.Content; + config.SystemMessage = new SystemMessageConfig + { + Mode = SystemMessageMode.Append, + Content = string.Join( + Environment.NewLine + Environment.NewLine, + new[] { existing, SovrintSystemAppend }.Where(value => !string.IsNullOrWhiteSpace(value)) + ) + }; + + // An explicit empty allowlist exposes no inherited first-party tools. + config.AvailableTools = []; + + // Caller-defined tools must be explicitly supplied after review. + config.Tools = []; + + return config; +} + +await using var client = new CopilotClient(); +await client.StartAsync(); + +var config = ApplySovrintStrictProfile(new SessionConfig +{ + Model = "gpt-5", + Streaming = true, +}); + +await using var session = await client.CreateSessionAsync(config); +await session.SendAsync(new MessageOptions +{ + Prompt = "Summarize the supplied text without using tools." +}); +``` + +## Read-oriented profile + +A read-oriented deployment should provide an explicit `AvailableTools` allowlist containing only reviewed read operations. Do not infer tool safety from names alone; validate the actual SDK and CLI tool identifiers available in the deployed version. + +```csharp +var config = new SessionConfig +{ + Model = "gpt-5", + Streaming = true, + AvailableTools = + [ + // Add only reviewed read-tool identifiers for the deployed CLI version. + ], + SystemMessage = new SystemMessageConfig + { + Mode = SystemMessageMode.Append, + Content = SovrintSystemAppend + Environment.NewLine + + "Operate in read-only mode." + } +}; +``` + +## Custom tools + +Custom tools are application code. Wrap their handlers with application authorization and bounded audit recording before placing them in `SessionConfig.Tools`. + +The wrapper should record only: + +- session reference; +- tool name; +- invocation reference; +- decision; +- reason code; +- timestamp; +- evidence status. + +It should not place arguments, results, credentials, prompts, or raw file contents into the audit event. + +## Boundaries + +- system-message append content is guidance, not a complete enforcement boundary; +- an empty tool allowlist is safer than inheriting an unknown tool surface; +- local application approval is not a global governance decision; +- an audit event is not EvidenceGrid acceptance; +- unknown or unsupported permission surfaces should fail closed. From 1481b22858fa11ffcd29fbc1dd2e91a36fb0e56c Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:32:29 -0400 Subject: [PATCH 23/63] Link TypeScript SOVRINT governed-session recipe --- cookbook/nodejs/README.md | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/cookbook/nodejs/README.md b/cookbook/nodejs/README.md index afe3aa7528..a3aaa18679 100644 --- a/cookbook/nodejs/README.md +++ b/cookbook/nodejs/README.md @@ -1,19 +1,20 @@ -# GitHub Copilot SDK Cookbook — Node.js / TypeScript - -This folder hosts short, practical recipes for using the GitHub Copilot SDK with Node.js/TypeScript. Each recipe is concise, copy‑pasteable, and points to fuller examples and tests. - -## Recipes - -- [Error Handling](error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. -- [Multiple Sessions](multiple-sessions.md): Manage multiple independent conversations simultaneously. -- [Managing Local Files](managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. -- [PR Visualization](pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. -- [Persisting Sessions](persisting-sessions.md): Save and resume sessions across restarts. - -## Contributing - -Add a new recipe by creating a markdown file in this folder and linking it above. Follow repository guidance in [CONTRIBUTING.md](../../CONTRIBUTING.md). - -## Status - -This README is a scaffold; recipe files are placeholders until populated. +# GitHub Copilot SDK Cookbook — Node.js / TypeScript + +This folder hosts short, practical recipes for using the GitHub Copilot SDK with Node.js/TypeScript. Each recipe is concise, copy‑pasteable, and points to fuller examples and tests. + +## Recipes + +- [SOVRINT Governed Session](sovrint-governed-session.md): Apply deny-by-default profiles, bounded permission handling, custom-tool guards, and audit events. +- [Error Handling](error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. +- [Multiple Sessions](multiple-sessions.md): Manage multiple independent conversations simultaneously. +- [Managing Local Files](managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. +- [PR Visualization](pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. +- [Persisting Sessions](persisting-sessions.md): Save and resume sessions across restarts. + +## Contributing + +Add a new recipe by creating a markdown file in this folder and linking it above. Follow repository guidance in [CONTRIBUTING.md](../../CONTRIBUTING.md). + +## Status + +The SOVRINT governed-session recipe is implemented and backed by SDK unit tests. Other listed recipes may remain scaffolds until populated. From 723300149e0557c9ec0075367ef8a9b42ff15342 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:32:58 -0400 Subject: [PATCH 24/63] Link Python governed-session recipe --- cookbook/python/README.md | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/cookbook/python/README.md b/cookbook/python/README.md index 885c8be1e2..18dfb5327e 100644 --- a/cookbook/python/README.md +++ b/cookbook/python/README.md @@ -1,19 +1,20 @@ -# GitHub Copilot SDK Cookbook — Python - -This folder hosts short, practical recipes for using the GitHub Copilot SDK with Python. Each recipe is concise, copy‑pasteable, and points to fuller examples and tests. - -## Recipes - -- [Error Handling](error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. -- [Multiple Sessions](multiple-sessions.md): Manage multiple independent conversations simultaneously. -- [Managing Local Files](managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. -- [PR Visualization](pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. -- [Persisting Sessions](persisting-sessions.md): Save and resume sessions across restarts. - -## Contributing - -Add a new recipe by creating a markdown file in this folder and linking it above. Follow repository guidance in [CONTRIBUTING.md](../../CONTRIBUTING.md). - -## Status - -This README is a scaffold; recipe files are placeholders until populated. +# GitHub Copilot SDK Cookbook — Python + +This folder hosts short, practical recipes for using the GitHub Copilot SDK with Python. Each recipe is concise, copy‑pasteable, and points to fuller examples and tests. + +## Recipes + +- [SOVRINT Governed Session](sovrint-governed-session.md): Apply the SOVRINT session profile and audit helpers. +- [Error Handling](error-handling.md): Handle errors gracefully including connection failures, timeouts, and cleanup. +- [Multiple Sessions](multiple-sessions.md): Manage multiple independent conversations simultaneously. +- [Managing Local Files](managing-local-files.md): Organize files by metadata using AI-powered grouping strategies. +- [PR Visualization](pr-visualization.md): Generate interactive PR age charts using GitHub MCP Server. +- [Persisting Sessions](persisting-sessions.md): Save and resume sessions across restarts. + +## Contributing + +Add a new recipe by creating a markdown file in this folder and linking it above. Follow repository guidance in [CONTRIBUTING.md](../../CONTRIBUTING.md). + +## Status + +The SOVRINT governed-session recipe is implemented. Other listed recipes may remain scaffolds until populated. From 72ce3e14fbbbf020dfff505d1c796a5f1dc287b9 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:34:11 -0400 Subject: [PATCH 25/63] Add SOVRINT SDK extension index --- SOVRINT_EXTENSION.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 SOVRINT_EXTENSION.md diff --git a/SOVRINT_EXTENSION.md b/SOVRINT_EXTENSION.md new file mode 100644 index 0000000000..4f47b9a12c --- /dev/null +++ b/SOVRINT_EXTENSION.md @@ -0,0 +1,24 @@ +# SOVRINT™ Governed SDK Extension + +**Author:** Katrina Pietroniro +**Version:** 1.0 +**Status:** Additive and opt-in + +This repository includes a governed-session extension around the existing Copilot SDK interfaces. The upstream JSON-RPC protocol remains unchanged. + +The extension includes permission profiles, session-configuration helpers, custom-tool guards, bounded audit events, shared schemas, tests, and cross-language recipes. + +## Documentation + +- [Architecture](docs/sovrint/README.md) +- [Security profile](docs/sovrint/SECURITY_PROFILE.md) +- [Governance, integrity, and evidence interfaces](docs/sovrint/GOVERNANCE_INTEGRITY_EVIDENCE.md) + +## Recipes + +- [TypeScript](cookbook/nodejs/sovrint-governed-session.md) +- [Python](cookbook/python/sovrint-governed-session.md) +- [Go](cookbook/go/sovrint-governed-session.md) +- [.NET](cookbook/dotnet/sovrint-governed-session.md) + +Existing SDK behavior remains unchanged unless an application applies the extension helpers. From 5510bde3580045bd726eed21504ac7c45e24ac05 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:34:31 -0400 Subject: [PATCH 26/63] Add SOVRINT extension attribution --- SOVRINT_EXTENSION_ATTRIBUTION.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 SOVRINT_EXTENSION_ATTRIBUTION.md diff --git a/SOVRINT_EXTENSION_ATTRIBUTION.md b/SOVRINT_EXTENSION_ATTRIBUTION.md new file mode 100644 index 0000000000..f31f1b6e12 --- /dev/null +++ b/SOVRINT_EXTENSION_ATTRIBUTION.md @@ -0,0 +1,8 @@ +# SOVRINT™ Extension Attribution + +**Author:** Katrina Pietroniro +**Initial integration date:** 2026-06-26 + +This attribution applies to the governed-session profiles, permission composition, session constraints, custom-tool guards, bounded audit events, interface documentation, schemas, tests, and recipes added under the SOVRINT extension. + +The original Copilot SDK code remains attributed to its existing authors. Repository licensing remains unchanged. From 9d5ec6a9c3f22beea4ac9c8604595233c164c4aa Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:34:38 -0400 Subject: [PATCH 27/63] Add SOVRINT extension changelog --- SOVRINT_CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 SOVRINT_CHANGELOG.md diff --git a/SOVRINT_CHANGELOG.md b/SOVRINT_CHANGELOG.md new file mode 100644 index 0000000000..b79a465c47 --- /dev/null +++ b/SOVRINT_CHANGELOG.md @@ -0,0 +1,25 @@ +# SOVRINT™ Extension Changelog + +## v1.0 — 2026-06-26 + +Added: + +- governed-session architecture and security documentation; +- governance, integrity, and evidence interface guide; +- strict and read-only JSON profiles; +- research profile constants in TypeScript, Python, and Go; +- shared profile and audit-event schemas; +- extension manifest and permission matrix; +- bounded audit-event example; +- TypeScript helper module and tests; +- Python helper module and tests; +- Go helper module and tests; +- TypeScript, Python, Go, and .NET recipes; +- root extension index and attribution; +- cross-language validation workflow. + +Compatibility: + +- upstream JSON-RPC protocol unchanged; +- existing SDK users unaffected unless they apply the SOVRINT helpers; +- existing package names and client/session APIs preserved. From 3dcf77b9b995ab55e279ce33b84e2c0ad6bc938c Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:34:57 -0400 Subject: [PATCH 28/63] Add cross-language SOVRINT extension validation --- .../workflows/sovrint-extension-validate.yml | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 .github/workflows/sovrint-extension-validate.yml diff --git a/.github/workflows/sovrint-extension-validate.yml b/.github/workflows/sovrint-extension-validate.yml new file mode 100644 index 0000000000..d3dca7d5f4 --- /dev/null +++ b/.github/workflows/sovrint-extension-validate.yml @@ -0,0 +1,165 @@ +name: SOVRINT Extension Validation + +on: + pull_request: + paths: + - "docs/sovrint/**" + - "sovrint/**" + - "nodejs/src/sovrint.ts" + - "nodejs/test/sovrint.test.ts" + - "nodejs/src/index.ts" + - "python/copilot/sovrint.py" + - "python/test_sovrint.py" + - "python/copilot/__init__.py" + - "go/sovrint.go" + - "go/sovrint_test.go" + - "cookbook/**/sovrint-governed-session.md" + - "SOVRINT_*.md" + - ".github/workflows/sovrint-extension-validate.yml" + push: + branches: + - main + - feat/sovrint-governed-sdk-profile-v1 + +permissions: + contents: read + +jobs: + common-contracts: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install validators + run: python -m pip install --disable-pip-version-check PyYAML==6.0.2 jsonschema==4.23.0 + + - name: Validate JSON and YAML contracts + run: | + python - <<'PY' + import json + from pathlib import Path + + import yaml + from jsonschema import Draft202012Validator, FormatChecker + + for path in Path("sovrint").rglob("*.json"): + json.loads(path.read_text(encoding="utf-8")) + print(f"valid json: {path}") + + for path in Path("sovrint").rglob("*.yaml"): + yaml.safe_load(path.read_text(encoding="utf-8")) + print(f"valid yaml: {path}") + + profile_schema = json.loads( + Path("sovrint/schemas/security-profile.schema.json").read_text(encoding="utf-8") + ) + profile_validator = Draft202012Validator( + profile_schema, + format_checker=FormatChecker(), + ) + for path in Path("sovrint/profiles").glob("*.json"): + profile = json.loads(path.read_text(encoding="utf-8")) + profile_validator.validate(profile) + if profile["audit"]["includeArguments"] is not False: + raise SystemExit(f"profile exposes arguments: {path}") + if profile["audit"]["includeResults"] is not False: + raise SystemExit(f"profile exposes results: {path}") + print(f"valid profile: {path}") + + event_schema = json.loads( + Path("sovrint/schemas/audit-event.schema.json").read_text(encoding="utf-8") + ) + event = json.loads( + Path("sovrint/examples/audit-event.example.json").read_text(encoding="utf-8") + ) + Draft202012Validator( + event_schema, + format_checker=FormatChecker(), + ).validate(event) + if event.get("metadata", {}).get("simulationOnly") is not True: + raise SystemExit("audit example must remain simulation-only") + print("valid audit event example") + PY + + - name: Validate extension manifest paths + run: | + python - <<'PY' + from pathlib import Path + + import yaml + + manifest = yaml.safe_load(Path("sovrint/manifest.yaml").read_text(encoding="utf-8")) + listed = [] + for value in manifest["components"].values(): + listed.extend(value) + missing = [path for path in listed if not Path(path).exists()] + if missing: + raise SystemExit(f"manifest paths missing: {missing}") + print(f"validated {len(listed)} manifest paths") + PY + + nodejs: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: npm + cache-dependency-path: nodejs/package-lock.json + + - name: Install Node.js dependencies + working-directory: nodejs + run: npm ci + + - name: Typecheck Node.js SDK + working-directory: nodejs + run: npm run typecheck + + - name: Test SOVRINT TypeScript helpers + working-directory: nodejs + run: npx vitest run test/sovrint.test.ts + + python: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Python SDK and test dependencies + run: python -m pip install --disable-pip-version-check -e "./python[dev]" + + - name: Compile Python helper + run: python -m compileall -q python/copilot/sovrint.py + + - name: Test SOVRINT Python helpers + run: pytest -q python/test_sovrint.py + + go: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go/go.mod + + - name: Test SOVRINT Go helpers + working-directory: go + run: go test -run Sovrint ./... From 1d98da71174306f2c84fbee3eb225061adf6e173 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:35:28 -0400 Subject: [PATCH 29/63] Add SOVRINT extension API reference --- docs/sovrint/API_REFERENCE.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 docs/sovrint/API_REFERENCE.md diff --git a/docs/sovrint/API_REFERENCE.md b/docs/sovrint/API_REFERENCE.md new file mode 100644 index 0000000000..9b72e9b0db --- /dev/null +++ b/docs/sovrint/API_REFERENCE.md @@ -0,0 +1,33 @@ +# SOVRINT™ Extension API Reference + +**Author:** Katrina Pietroniro +**Version:** 1.0 + +## Cross-Language Mapping + +| Capability | TypeScript | Python | Go | +|---|---|---|---| +| Strict profile | `SOVRINT_STRICT_PROFILE` | `SOVRINT_STRICT_PROFILE` | `SovrintStrictProfile` | +| Read-only profile | `SOVRINT_READ_ONLY_PROFILE` | `SOVRINT_READ_ONLY_PROFILE` | `SovrintReadOnlyProfile` | +| Research profile | `SOVRINT_RESEARCH_PROFILE` | `SOVRINT_RESEARCH_PROFILE` | `SovrintResearchProfile` | +| Apply profile | `applySovrintProfile` | `apply_sovrint_profile` | `ApplySovrintProfile` | +| Permission handler | `createSovrintPermissionHandler` | `create_sovrint_permission_handler` | `CreateSovrintPermissionHandler` | +| Guard custom tool | `wrapSovrintTool` | `wrap_sovrint_tool` | `WrapSovrintTool` | + +## Permission Evaluation + +The handler applies explicit denial, application evaluation, explicit allowance, profile default, any existing handler, and bounded audit recording. The most restrictive result wins. + +## Tool-Surface Modes + +- `inherit` keeps the application surface. +- `allowlist` intersects application and profile lists. +- `none` creates an explicit empty first-party tool list and clears custom-agent tool lists. + +## Custom Tools + +The wrapper records receipt, approval or rejection, and completion or failure. It excludes arguments and results from the bounded audit event. + +## Compatibility + +The helpers return normal SDK session configurations, permission handlers, and tools. Client methods and the JSON-RPC protocol remain unchanged. From 21978f169123c6d3011b8bacd499ec7a054f76ec Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:35:48 -0400 Subject: [PATCH 30/63] Add SOVRINT deployment checklist --- docs/sovrint/DEPLOYMENT_CHECKLIST.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 docs/sovrint/DEPLOYMENT_CHECKLIST.md diff --git a/docs/sovrint/DEPLOYMENT_CHECKLIST.md b/docs/sovrint/DEPLOYMENT_CHECKLIST.md new file mode 100644 index 0000000000..a8e9780d25 --- /dev/null +++ b/docs/sovrint/DEPLOYMENT_CHECKLIST.md @@ -0,0 +1,17 @@ +# SOVRINT™ SDK Deployment Checklist + +**Author:** Katrina Pietroniro + +- pin the profile version; +- review permission kinds; +- review the effective tool list; +- keep system-message mode set to append; +- review MCP server names and skill directories; +- preserve stricter existing permission handlers; +- wrap caller-defined tools; +- configure a bounded audit sink; +- exclude prompts, responses, arguments, results, and credentials from audit events; +- distinguish local audit recording from accepted evidence; +- run TypeScript, Python, Go, schema, and documentation validation; +- confirm that applications remain unchanged until they opt in; +- review the final pull-request diff before merge. From 9996d77691e5928605eb8223449c24d8a5d0f819 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:35:55 -0400 Subject: [PATCH 31/63] Add SOVRINT research profile template --- sovrint/profiles/research-template.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 sovrint/profiles/research-template.yaml diff --git a/sovrint/profiles/research-template.yaml b/sovrint/profiles/research-template.yaml new file mode 100644 index 0000000000..486bd2e99f --- /dev/null +++ b/sovrint/profiles/research-template.yaml @@ -0,0 +1,23 @@ +schema_version: "1.0" +profile_id: "sovrint.research" +version: "1.0" +default_decision: "deny" +allow_kinds: + - read +deny_kinds: [] +tool_surface_mode: "inherit" +forbid_system_message_replace: true +fail_closed_on_audit_error: false +requires_application_evaluator: + - write + - shell + - url + - mcp +audit: + enabled: true + include_arguments: false + include_results: false +notes: + - "Returning no evaluator decision defers to the profile default." + - "Local approval remains local to the application and session." + - "Research observations remain distinct from verified findings." From 93ac26a62bedbcd7605a68070c1c20b9d321fc6f Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:36:04 -0400 Subject: [PATCH 32/63] Add SOVRINT audit event taxonomy --- sovrint/policy/audit-event-taxonomy.yaml | 59 ++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 sovrint/policy/audit-event-taxonomy.yaml diff --git a/sovrint/policy/audit-event-taxonomy.yaml b/sovrint/policy/audit-event-taxonomy.yaml new file mode 100644 index 0000000000..c4b66234c9 --- /dev/null +++ b/sovrint/policy/audit-event-taxonomy.yaml @@ -0,0 +1,59 @@ +schema_version: "1.0" +author: "Katrina Pietroniro" +framework: "SOVRINT™" + +event_classes: + PROFILE_APPLIED: + decision_values: [RECORDED] + PERMISSION_DECISION: + decision_values: [APPROVED, DENIED] + TOOL_INVOCATION_STARTED: + decision_values: [PENDING] + TOOL_INVOCATION_APPROVED: + decision_values: [APPROVED] + TOOL_INVOCATION_REJECTED: + decision_values: [REJECTED] + TOOL_INVOCATION_COMPLETED: + decision_values: [RECORDED] + TOOL_INVOCATION_FAILED: + decision_values: [FAILED] + SYSTEM_MESSAGE_REPLACE_REJECTED: + decision_values: [DENIED] + POLICY_VIOLATION: + decision_values: [DENIED, REJECTED, FAILED] + AUDIT_SINK_FAILURE: + decision_values: [FAILED] + EVIDENCE_SUBMISSION_STATUS: + decision_values: [PENDING, RECORDED, FAILED] + +reason_codes: + - UNKNOWN_PERMISSION_KIND + - PROFILE_EXPLICIT_DENY + - PROFILE_EXPLICIT_ALLOW + - PROFILE_DEFAULT_DENY + - PROFILE_DEFAULT_ALLOW + - APPLICATION_EVALUATOR_APPROVED + - APPLICATION_EVALUATOR_DENIED + - APPLICATION_EVALUATOR_FAILED + - DOWNSTREAM_HANDLER_DENIED + - DOWNSTREAM_HANDLER_FAILED + - AUDIT_SINK_UNAVAILABLE + - TOOL_INVOCATION_RECEIVED + - CUSTOM_TOOL_AUTHORIZED + - CUSTOM_TOOL_AUTHORIZATION_DENIED + - CUSTOM_TOOL_COMPLETED + - CUSTOM_TOOL_FAILED + +privacy: + include_prompts: false + include_responses: false + include_tool_arguments: false + include_tool_results: false + include_credentials: false + include_raw_file_contents: false + +invariants: + - "Every event carries a profile identifier and version." + - "Every permission decision carries a reason code." + - "Tool events carry a session and tool-call reference." + - "Audit events are not represented as EvidenceGrid blocks." From d7b1eb40d705b0378f43dffec81581b80221bf3d Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:36:19 -0400 Subject: [PATCH 33/63] Expand SOVRINT extension manifest --- sovrint/manifest.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sovrint/manifest.yaml b/sovrint/manifest.yaml index 429d9e4de4..486a8f59d7 100644 --- a/sovrint/manifest.yaml +++ b/sovrint/manifest.yaml @@ -11,21 +11,38 @@ components: - docs/sovrint/README.md - docs/sovrint/SECURITY_PROFILE.md - docs/sovrint/GOVERNANCE_INTEGRITY_EVIDENCE.md + - docs/sovrint/API_REFERENCE.md + - docs/sovrint/DEPLOYMENT_CHECKLIST.md + - SOVRINT_EXTENSION.md + - SOVRINT_EXTENSION_ATTRIBUTION.md + - SOVRINT_CHANGELOG.md common_schemas: - sovrint/schemas/security-profile.schema.json - sovrint/schemas/audit-event.schema.json profiles: - sovrint/profiles/strict.json - sovrint/profiles/read-only.json + - sovrint/profiles/research-template.yaml + policies: + - sovrint/policy/permission-matrix.yaml + - sovrint/policy/audit-event-taxonomy.yaml + examples: + - sovrint/examples/audit-event.example.json language_helpers: - nodejs/src/sovrint.ts - python/copilot/sovrint.py - go/sovrint.go + language_tests: + - nodejs/test/sovrint.test.ts + - python/test_sovrint.py + - go/sovrint_test.go recipes: - cookbook/nodejs/sovrint-governed-session.md - cookbook/python/sovrint-governed-session.md - cookbook/go/sovrint-governed-session.md - cookbook/dotnet/sovrint-governed-session.md + validation: + - .github/workflows/sovrint-extension-validate.yml interfaces: governance_runtime: "SOVRINT-OG/sovrint-governance-runtime" From 2f1e0ea955eaca5d15c3612b032680b4be6bad9f Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:48:05 -0400 Subject: [PATCH 34/63] Add modular Python SOVRINT profile model --- python/copilot/sovrint_runtime/model.py | 78 +++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 python/copilot/sovrint_runtime/model.py diff --git a/python/copilot/sovrint_runtime/model.py b/python/copilot/sovrint_runtime/model.py new file mode 100644 index 0000000000..65c53abfb3 --- /dev/null +++ b/python/copilot/sovrint_runtime/model.py @@ -0,0 +1,78 @@ +"""SOVRINT governed-session models and canonical built-in profiles.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any, Awaitable, Callable, Dict, Optional, Tuple, Union + +from ..types import PermissionRequest, ToolInvocation + +PermissionDecision = str +AuditEvent = Dict[str, Any] +AuditSink = Callable[[AuditEvent], Union[None, Awaitable[None]]] +PermissionEvaluator = Callable[ + [PermissionRequest, Dict[str, str]], + Union[Optional[PermissionDecision], Awaitable[Optional[PermissionDecision]]], +] +ToolAuthorizer = Callable[[ToolInvocation], Union[bool, Awaitable[bool]]] + +SOVRINT_SYSTEM_APPEND = ( + "Operate under a bounded SOVRINT governed-session profile. " + "Use only explicitly exposed tools and declared authority. " + "Keep observations, inferences, recommendations, governance decisions, " + "integrity findings, and accepted evidence distinct. " + "Do not claim approval, verification, restoration, or EvidenceGrid acceptance " + "without an explicit external result." +) + + +@dataclass(frozen=True) +class SovrintSecurityProfile: + """Versioned governed-session policy.""" + + profile_id: str + version: str + default_decision: PermissionDecision = "deny" + allow_kinds: Tuple[str, ...] = () + deny_kinds: Tuple[str, ...] = () + tool_surface_mode: str = "inherit" + available_tools: Tuple[str, ...] = () + excluded_tools: Tuple[str, ...] = () + allowed_mcp_servers: Tuple[str, ...] = () + allowed_skill_directories: Tuple[str, ...] = () + disabled_skills: Tuple[str, ...] = () + forbid_system_message_replace: bool = True + fail_closed_on_audit_error: bool = False + audit_enabled: bool = True + system_message_append: str = SOVRINT_SYSTEM_APPEND + description: str = "" + + +SOVRINT_STRICT_PROFILE = SovrintSecurityProfile( + profile_id="sovrint.strict", + version="1.0", + description="Deny every permission and expose no inherited first-party tools.", + deny_kinds=("read", "write", "shell", "url", "mcp"), + tool_surface_mode="none", + fail_closed_on_audit_error=True, +) + +SOVRINT_READ_ONLY_PROFILE = SovrintSecurityProfile( + profile_id="sovrint.read-only", + version="1.0", + description="Permit reads while denying mutating and external permission kinds.", + allow_kinds=("read",), + deny_kinds=("write", "shell", "url", "mcp"), + fail_closed_on_audit_error=True, + system_message_append=f"{SOVRINT_SYSTEM_APPEND} Operate in read-only mode.", +) + +SOVRINT_RESEARCH_PROFILE = SovrintSecurityProfile( + profile_id="sovrint.research", + version="1.0", + description="Permit reads and defer other permission kinds to an application evaluator.", + allow_kinds=("read",), + system_message_append=( + f"{SOVRINT_SYSTEM_APPEND} Separate research observations from verified findings." + ), +) From 9472e13bd355c273f38aa05b945ba495e3516747 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:48:27 -0400 Subject: [PATCH 35/63] Add modular Python SOVRINT audit layer --- python/copilot/sovrint_runtime/audit.py | 72 +++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 python/copilot/sovrint_runtime/audit.py diff --git a/python/copilot/sovrint_runtime/audit.py b/python/copilot/sovrint_runtime/audit.py new file mode 100644 index 0000000000..b76a657215 --- /dev/null +++ b/python/copilot/sovrint_runtime/audit.py @@ -0,0 +1,72 @@ +"""Bounded SOVRINT audit events for governed SDK sessions.""" + +from __future__ import annotations + +import inspect +import itertools +from datetime import datetime, timezone +from typing import Any + +from .model import AuditEvent, AuditSink, SovrintSecurityProfile + +_sequence = itertools.count(1) + + +async def resolve(value: Any) -> Any: + """Resolve synchronous and awaitable application callbacks.""" + + if inspect.isawaitable(value): + return await value + return value + + +def create_audit_event( + profile: SovrintSecurityProfile, + event_class: str, + session_id: str, + decision: str, + reason_code: str, + *, + parent_event_id: str | None = None, + tool_call_id: str | None = None, + tool_name: str | None = None, + permission_kind: str | None = None, +) -> AuditEvent: + """Create a bounded event without prompts, arguments, results, or credentials.""" + + now = datetime.now(timezone.utc) + return { + "schemaVersion": "1.0", + "eventId": f"sovrint-{int(now.timestamp() * 1000)}-{next(_sequence)}", + "parentEventId": parent_event_id, + "eventClass": event_class, + "timestampUtc": now.isoformat().replace("+00:00", "Z"), + "profileId": profile.profile_id, + "profileVersion": profile.version, + "sessionId": session_id, + "toolCallId": tool_call_id, + "toolName": tool_name, + "permissionKind": permission_kind, + "decision": decision, + "reasonCode": reason_code, + "disclosureClass": "INTERNAL", + "evidenceStatus": "NOT_SUBMITTED", + } + + +async def emit_audit_event( + profile: SovrintSecurityProfile, + sink: AuditSink | None, + event: AuditEvent, +) -> bool: + """Record an event and return whether execution may continue.""" + + if not profile.audit_enabled: + return True + if sink is None: + return not profile.fail_closed_on_audit_error + try: + await resolve(sink(event)) + except Exception: + return not profile.fail_closed_on_audit_error + return True From fb8bdcc5f819208f6590cf4206bd3d9f02771942 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:48:50 -0400 Subject: [PATCH 36/63] Add modular Python SOVRINT permission layer --- python/copilot/sovrint_runtime/permissions.py | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 python/copilot/sovrint_runtime/permissions.py diff --git a/python/copilot/sovrint_runtime/permissions.py b/python/copilot/sovrint_runtime/permissions.py new file mode 100644 index 0000000000..0abeef994c --- /dev/null +++ b/python/copilot/sovrint_runtime/permissions.py @@ -0,0 +1,98 @@ +"""Most-restrictive SOVRINT permission composition.""" + +from __future__ import annotations + +from typing import Dict, Optional, Tuple + +from ..types import PermissionHandler, PermissionRequest, PermissionRequestResult +from .audit import create_audit_event, emit_audit_event, resolve +from .model import AuditSink, PermissionEvaluator, SovrintSecurityProfile + + +def permission_result( + approved: bool, + profile: SovrintSecurityProfile, + reason_code: str, +) -> PermissionRequestResult: + """Create a native SDK permission result carrying SOVRINT lineage.""" + + return PermissionRequestResult( + kind="approved" if approved else "denied-by-rules", + rules=[ + { + "source": "sovrint", + "profileId": profile.profile_id, + "profileVersion": profile.version, + "reasonCode": reason_code, + } + ], + ) + + +async def evaluate_permission( + profile: SovrintSecurityProfile, + request: PermissionRequest, + invocation: Dict[str, str], + evaluator: Optional[PermissionEvaluator], +) -> Tuple[bool, str]: + """Evaluate explicit denial, application policy, allowance, then default.""" + + kind = request.get("kind") + if not kind: + return False, "UNKNOWN_PERMISSION_KIND" + if kind in profile.deny_kinds: + return False, "PROFILE_EXPLICIT_DENY" + if evaluator is not None: + try: + decision = await resolve(evaluator(request, invocation)) + except Exception: + return False, "APPLICATION_EVALUATOR_FAILED" + if decision == "approve": + return True, "APPLICATION_EVALUATOR_APPROVED" + if decision == "deny": + return False, "APPLICATION_EVALUATOR_DENIED" + if kind in profile.allow_kinds: + return True, "PROFILE_EXPLICIT_ALLOW" + if profile.default_decision == "approve": + return True, "PROFILE_DEFAULT_ALLOW" + return False, "PROFILE_DEFAULT_DENY" + + +def create_sovrint_permission_handler( + profile: SovrintSecurityProfile, + *, + audit_sink: Optional[AuditSink] = None, + evaluate: Optional[PermissionEvaluator] = None, + downstream: Optional[PermissionHandler] = None, +) -> PermissionHandler: + """Compose profile, application, and existing SDK decisions.""" + + async def handler( + request: PermissionRequest, + invocation: Dict[str, str], + ) -> PermissionRequestResult: + approved, reason = await evaluate_permission( + profile, request, invocation, evaluate + ) + if approved and downstream is not None: + try: + downstream_result = await resolve(downstream(request, invocation)) + if downstream_result.get("kind") != "approved": + approved, reason = False, "DOWNSTREAM_HANDLER_DENIED" + except Exception: + approved, reason = False, "DOWNSTREAM_HANDLER_FAILED" + + event = create_audit_event( + profile, + "PERMISSION_DECISION", + invocation.get("session_id", invocation.get("sessionId", "unknown")), + "APPROVED" if approved else "DENIED", + reason, + tool_call_id=request.get("toolCallId"), + permission_kind=request.get("kind", "unknown"), + ) + if not await emit_audit_event(profile, audit_sink, event) and approved: + return permission_result(False, profile, "AUDIT_SINK_UNAVAILABLE") + return permission_result(approved, profile, reason) + + return handler From 82e82e61eccf9f282ba6eb9fe9836751b9e73e49 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:49:37 -0400 Subject: [PATCH 37/63] Add Python SOVRINT surface constraints --- python/copilot/sovrint_runtime/surface.py | 69 +++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 python/copilot/sovrint_runtime/surface.py diff --git a/python/copilot/sovrint_runtime/surface.py b/python/copilot/sovrint_runtime/surface.py new file mode 100644 index 0000000000..412dbd92b1 --- /dev/null +++ b/python/copilot/sovrint_runtime/surface.py @@ -0,0 +1,69 @@ +"""Tool, agent, MCP, and skill surface constraints.""" + +from typing import List, Sequence + +from ..types import SessionConfig +from .model import SovrintSecurityProfile + + +def intersect(left: Sequence[str], right: Sequence[str]) -> List[str]: + allowed = set(right) + return [value for value in left if value in allowed] + + +def merge(left: Sequence[str], right: Sequence[str]) -> List[str]: + return list(dict.fromkeys([*left, *right])) + + +def constrain_surfaces( + source: SessionConfig, + result: SessionConfig, + profile: SovrintSecurityProfile, +) -> None: + """Project a profile onto SDK capability surfaces.""" + + if profile.tool_surface_mode == "none": + result["available_tools"] = [] + elif profile.tool_surface_mode == "allowlist": + configured = source.get("available_tools") + result["available_tools"] = ( + intersect(configured, profile.available_tools) + if configured is not None + else list(profile.available_tools) + ) + + result["excluded_tools"] = merge( + source.get("excluded_tools", []), profile.excluded_tools + ) + + agents = source.get("custom_agents") + if agents is not None and profile.tool_surface_mode != "inherit": + constrained = [] + for agent_source in agents: + agent = dict(agent_source) + agent["tools"] = ( + [] + if profile.tool_surface_mode == "none" + else intersect( + agent_source.get("tools") or profile.available_tools, + profile.available_tools, + ) + ) + constrained.append(agent) + result["custom_agents"] = constrained + + servers = source.get("mcp_servers") + if servers is not None: + allowed_servers = set(profile.allowed_mcp_servers) + result["mcp_servers"] = { + name: value for name, value in servers.items() if name in allowed_servers + } + + directories = source.get("skill_directories") + if directories is not None: + result["skill_directories"] = intersect( + directories, profile.allowed_skill_directories + ) + result["disabled_skills"] = merge( + source.get("disabled_skills", []), profile.disabled_skills + ) From d71b8cba21c56cda23bd6602746b6e9aa3e7ba57 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:50:17 -0400 Subject: [PATCH 38/63] Add Python SOVRINT session projection --- python/copilot/sovrint_runtime/projection.py | 38 ++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 python/copilot/sovrint_runtime/projection.py diff --git a/python/copilot/sovrint_runtime/projection.py b/python/copilot/sovrint_runtime/projection.py new file mode 100644 index 0000000000..d72a86d309 --- /dev/null +++ b/python/copilot/sovrint_runtime/projection.py @@ -0,0 +1,38 @@ +"""Project SOVRINT policy onto an SDK session configuration.""" + +from typing import Optional + +from ..types import SessionConfig +from .model import AuditSink, PermissionEvaluator, SovrintSecurityProfile +from .permissions import create_sovrint_permission_handler +from .surface import constrain_surfaces + + +def apply_sovrint_profile( + config: SessionConfig, + profile: SovrintSecurityProfile, + *, + audit_sink: Optional[AuditSink] = None, + evaluate_permission: Optional[PermissionEvaluator] = None, +) -> SessionConfig: + """Return a profile-constrained copy of an SDK configuration.""" + + result: SessionConfig = dict(config) + current = config.get("system_message") + if current and current.get("mode") == "replace": + if profile.forbid_system_message_replace: + raise ValueError(f"Profile '{profile.profile_id}' forbids the requested system mode") + else: + existing = (current or {}).get("content", "").strip() + appended = profile.system_message_append.strip() + content = "\n\n".join(value for value in (existing, appended) if value) + result["system_message"] = {"mode": "append", "content": content} + + constrain_surfaces(config, result, profile) + result["on_permission_request"] = create_sovrint_permission_handler( + profile, + audit_sink=audit_sink, + evaluate=evaluate_permission, + downstream=config.get("on_permission_request"), + ) + return result From 9a25c928be54b7dc7de69f0723ccc492c3bdba1a Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:50:28 -0400 Subject: [PATCH 39/63] Add modular Python SOVRINT tool guard --- python/copilot/sovrint_runtime/tools.py | 127 ++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 python/copilot/sovrint_runtime/tools.py diff --git a/python/copilot/sovrint_runtime/tools.py b/python/copilot/sovrint_runtime/tools.py new file mode 100644 index 0000000000..7c59375db2 --- /dev/null +++ b/python/copilot/sovrint_runtime/tools.py @@ -0,0 +1,127 @@ +"""Authorization and audit guards for caller-defined SDK tools.""" + +from typing import Optional + +from ..types import Tool, ToolInvocation, ToolResult +from .audit import create_audit_event, emit_audit_event, resolve +from .model import AuditSink, SovrintSecurityProfile, ToolAuthorizer + + +def rejected_tool_result(reason: str) -> ToolResult: + """Create a bounded rejection result.""" + + return ToolResult( + textResultForLlm="The governed tool invocation was not authorized.", + resultType="rejected", + error=reason, + toolTelemetry={"source": "sovrint", "reasonCode": reason}, + ) + + +def wrap_sovrint_tool( + tool: Tool, + profile: SovrintSecurityProfile, + *, + audit_sink: Optional[AuditSink] = None, + authorize: Optional[ToolAuthorizer] = None, +) -> Tool: + """Wrap a caller-defined tool with authorization and bounded audit events.""" + + original = tool.handler + + async def guarded(invocation: ToolInvocation) -> ToolResult: + session_id = invocation.get("session_id", "unknown") + call_id = invocation.get("tool_call_id") + started = create_audit_event( + profile, + "TOOL_INVOCATION_STARTED", + session_id, + "PENDING", + "TOOL_INVOCATION_RECEIVED", + tool_call_id=call_id, + tool_name=tool.name, + ) + if not await emit_audit_event(profile, audit_sink, started): + return rejected_tool_result("AUDIT_SINK_UNAVAILABLE") + + try: + authorized = authorize is None or bool(await resolve(authorize(invocation))) + except Exception: + authorized = False + if not authorized: + await emit_audit_event( + profile, + audit_sink, + create_audit_event( + profile, + "TOOL_INVOCATION_REJECTED", + session_id, + "REJECTED", + "CUSTOM_TOOL_AUTHORIZATION_DENIED", + parent_event_id=started["eventId"], + tool_call_id=call_id, + tool_name=tool.name, + ), + ) + return rejected_tool_result("CUSTOM_TOOL_AUTHORIZATION_DENIED") + + await emit_audit_event( + profile, + audit_sink, + create_audit_event( + profile, + "TOOL_INVOCATION_APPROVED", + session_id, + "APPROVED", + "CUSTOM_TOOL_AUTHORIZED", + parent_event_id=started["eventId"], + tool_call_id=call_id, + tool_name=tool.name, + ), + ) + try: + result = await resolve(original(invocation)) + except Exception as exc: + await emit_audit_event( + profile, + audit_sink, + create_audit_event( + profile, + "TOOL_INVOCATION_FAILED", + session_id, + "FAILED", + "CUSTOM_TOOL_FAILED", + parent_event_id=started["eventId"], + tool_call_id=call_id, + tool_name=tool.name, + ), + ) + return ToolResult( + textResultForLlm="The governed tool invocation failed.", + resultType="failure", + error=str(exc), + toolTelemetry={"source": "sovrint"}, + ) + + await emit_audit_event( + profile, + audit_sink, + create_audit_event( + profile, + "TOOL_INVOCATION_COMPLETED", + session_id, + "RECORDED", + "CUSTOM_TOOL_COMPLETED", + parent_event_id=started["eventId"], + tool_call_id=call_id, + tool_name=tool.name, + ), + ) + return result + + return Tool( + name=tool.name, + description=tool.description, + parameters=tool.parameters, + handler=guarded, + ) From b7c06e3b06a718b1af8c9a7e3ef23c72a7109cf0 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:50:54 -0400 Subject: [PATCH 40/63] Expose Python SOVRINT runtime API --- python/copilot/sovrint_runtime/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 python/copilot/sovrint_runtime/__init__.py diff --git a/python/copilot/sovrint_runtime/__init__.py b/python/copilot/sovrint_runtime/__init__.py new file mode 100644 index 0000000000..f695af66f4 --- /dev/null +++ b/python/copilot/sovrint_runtime/__init__.py @@ -0,0 +1,12 @@ +"""Public SOVRINT governed-session runtime API.""" + +from .model import ( + SOVRINT_READ_ONLY_PROFILE, + SOVRINT_RESEARCH_PROFILE, + SOVRINT_STRICT_PROFILE, + SOVRINT_SYSTEM_APPEND, + SovrintSecurityProfile, +) +from .permissions import create_sovrint_permission_handler +from .projection import apply_sovrint_profile +from .tools import wrap_sovrint_tool From da692a99675a408dfecf73423bdea97ec2e2f108 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:51:21 -0400 Subject: [PATCH 41/63] Route Python exports through modular SOVRINT runtime --- python/copilot/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index 76666b7ea1..2ba896de6e 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -6,7 +6,7 @@ from .client import CopilotClient from .session import CopilotSession -from .sovrint import ( +from .sovrint_runtime import ( SOVRINT_READ_ONLY_PROFILE, SOVRINT_RESEARCH_PROFILE, SOVRINT_STRICT_PROFILE, From 420b03aa33a9254955cd0dff3467d7e53359693b Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:52:16 -0400 Subject: [PATCH 42/63] Route Python SOVRINT helper through modular runtime --- python/copilot/sovrint.py | 472 +------------------------------------- 1 file changed, 2 insertions(+), 470 deletions(-) diff --git a/python/copilot/sovrint.py b/python/copilot/sovrint.py index a286a79fbd..a57b6b3231 100644 --- a/python/copilot/sovrint.py +++ b/python/copilot/sovrint.py @@ -1,471 +1,3 @@ -"""SOVRINT governed-session helpers for the Copilot SDK. +"""Compatibility imports for the modular SOVRINT runtime.""" -This module is additive. It composes the existing SDK SessionConfig and Tool -interfaces without changing the Copilot CLI JSON-RPC protocol. -""" - -from __future__ import annotations - -import inspect -import itertools -from dataclasses import dataclass -from datetime import datetime, timezone -from typing import Any, Awaitable, Callable, Dict, Optional, Sequence, Tuple, Union - -from .types import ( - PermissionHandler, - PermissionRequest, - PermissionRequestResult, - SessionConfig, - Tool, - ToolInvocation, - ToolResult, -) - -PermissionDecision = str -AuditSink = Callable[[Dict[str, Any]], Union[None, Awaitable[None]]] -PermissionEvaluator = Callable[ - [PermissionRequest, Dict[str, str]], - Union[Optional[PermissionDecision], Awaitable[Optional[PermissionDecision]]], -] -ToolAuthorizer = Callable[[ToolInvocation], Union[bool, Awaitable[bool]]] - -SOVRINT_SYSTEM_APPEND = ( - "Operate under a bounded SOVRINT governed-session profile. " - "Use only explicitly exposed tools and declared authority. " - "Treat observations, inferences, recommendations, governance decisions, " - "integrity findings, and accepted evidence as distinct classes. " - "Do not claim approval, verification, restoration, or EvidenceGrid acceptance " - "without an explicit external result." -) - - -@dataclass(frozen=True) -class SovrintSecurityProfile: - """Versioned governed-session profile.""" - - profile_id: str - version: str - default_decision: PermissionDecision = "deny" - allow_kinds: Tuple[str, ...] = () - deny_kinds: Tuple[str, ...] = () - tool_surface_mode: str = "inherit" - available_tools: Tuple[str, ...] = () - excluded_tools: Tuple[str, ...] = () - allowed_mcp_servers: Tuple[str, ...] = () - allowed_skill_directories: Tuple[str, ...] = () - disabled_skills: Tuple[str, ...] = () - forbid_system_message_replace: bool = True - fail_closed_on_audit_error: bool = False - audit_enabled: bool = True - system_message_append: str = SOVRINT_SYSTEM_APPEND - description: str = "" - - -SOVRINT_STRICT_PROFILE = SovrintSecurityProfile( - profile_id="sovrint.strict", - version="1.0", - description="Deny every permission and expose no inherited first-party tools.", - default_decision="deny", - deny_kinds=("read", "write", "shell", "url", "mcp"), - tool_surface_mode="none", - allowed_mcp_servers=(), - allowed_skill_directories=(), - fail_closed_on_audit_error=True, -) - -SOVRINT_READ_ONLY_PROFILE = SovrintSecurityProfile( - profile_id="sovrint.read-only", - version="1.0", - description="Permit reads while denying mutating and external permission kinds.", - default_decision="deny", - allow_kinds=("read",), - deny_kinds=("write", "shell", "url", "mcp"), - tool_surface_mode="inherit", - allowed_mcp_servers=(), - allowed_skill_directories=(), - fail_closed_on_audit_error=True, - system_message_append=f"{SOVRINT_SYSTEM_APPEND} Operate in read-only mode.", -) - -SOVRINT_RESEARCH_PROFILE = SovrintSecurityProfile( - profile_id="sovrint.research", - version="1.0", - description="Permit reads and require an evaluator for every other permission kind.", - default_decision="deny", - allow_kinds=("read",), - deny_kinds=(), - tool_surface_mode="inherit", - allowed_mcp_servers=(), - allowed_skill_directories=(), - fail_closed_on_audit_error=False, - system_message_append=( - f"{SOVRINT_SYSTEM_APPEND} Separate research observations from verified findings." - ), -) - -_audit_sequence = itertools.count(1) - - -async def _resolve(value: Any) -> Any: - if inspect.isawaitable(value): - return await value - return value - - -def _audit_event( - profile: SovrintSecurityProfile, - *, - event_class: str, - session_id: str, - decision: str, - reason_code: str, - tool_call_id: Optional[str] = None, - tool_name: Optional[str] = None, - permission_kind: Optional[str] = None, - parent_event_id: Optional[str] = None, -) -> Dict[str, Any]: - sequence = next(_audit_sequence) - return { - "schemaVersion": "1.0", - "eventId": "sovrint-{0}-{1}".format(int(datetime.now(timezone.utc).timestamp() * 1000), sequence), - "parentEventId": parent_event_id, - "eventClass": event_class, - "timestampUtc": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"), - "profileId": profile.profile_id, - "profileVersion": profile.version, - "sessionId": session_id, - "toolCallId": tool_call_id, - "toolName": tool_name, - "permissionKind": permission_kind, - "decision": decision, - "reasonCode": reason_code, - "disclosureClass": "INTERNAL", - "evidenceStatus": "NOT_SUBMITTED", - } - - -async def _emit_audit( - profile: SovrintSecurityProfile, - sink: Optional[AuditSink], - event: Dict[str, Any], -) -> bool: - if not profile.audit_enabled: - return True - if sink is None: - return not profile.fail_closed_on_audit_error - try: - await _resolve(sink(event)) - return True - except Exception: - return not profile.fail_closed_on_audit_error - - -def _permission_result( - approved: bool, - profile: SovrintSecurityProfile, - reason_code: str, -) -> PermissionRequestResult: - return PermissionRequestResult( - kind="approved" if approved else "denied-by-rules", - rules=[ - { - "source": "sovrint", - "profileId": profile.profile_id, - "profileVersion": profile.version, - "reasonCode": reason_code, - } - ], - ) - - -async def _evaluate_profile_permission( - profile: SovrintSecurityProfile, - request: PermissionRequest, - invocation: Dict[str, str], - evaluator: Optional[PermissionEvaluator], -) -> Tuple[bool, str]: - kind = request.get("kind") - if not kind: - return False, "UNKNOWN_PERMISSION_KIND" - - if kind in profile.deny_kinds: - return False, "PROFILE_EXPLICIT_DENY" - - if evaluator is not None: - try: - evaluated = await _resolve(evaluator(request, invocation)) - except Exception: - return False, "APPLICATION_EVALUATOR_FAILED" - if evaluated == "approve": - return True, "APPLICATION_EVALUATOR_APPROVED" - if evaluated == "deny": - return False, "APPLICATION_EVALUATOR_DENIED" - - if kind in profile.allow_kinds: - return True, "PROFILE_EXPLICIT_ALLOW" - - if profile.default_decision == "approve": - return True, "PROFILE_DEFAULT_ALLOW" - return False, "PROFILE_DEFAULT_DENY" - - -def create_sovrint_permission_handler( - profile: SovrintSecurityProfile, - *, - audit_sink: Optional[AuditSink] = None, - evaluate: Optional[PermissionEvaluator] = None, - downstream: Optional[PermissionHandler] = None, -) -> PermissionHandler: - """Create a most-restrictive-wins SDK permission handler.""" - - async def handler( - request: PermissionRequest, - invocation: Dict[str, str], - ) -> PermissionRequestResult: - approved, reason_code = await _evaluate_profile_permission( - profile, request, invocation, evaluate - ) - - if approved and downstream is not None: - try: - downstream_result = await _resolve(downstream(request, invocation)) - if downstream_result.get("kind") != "approved": - approved = False - reason_code = "DOWNSTREAM_HANDLER_DENIED" - except Exception: - approved = False - reason_code = "DOWNSTREAM_HANDLER_FAILED" - - event = _audit_event( - profile, - event_class="PERMISSION_DECISION", - session_id=invocation.get("session_id", invocation.get("sessionId", "unknown")), - tool_call_id=request.get("toolCallId"), - permission_kind=request.get("kind", "unknown"), - decision="APPROVED" if approved else "DENIED", - reason_code=reason_code, - ) - audit_recorded = await _emit_audit(profile, audit_sink, event) - if not audit_recorded and approved: - return _permission_result(False, profile, "AUDIT_SINK_UNAVAILABLE") - - return _permission_result(approved, profile, reason_code) - - return handler - - -def _intersection(left: Sequence[str], right: Sequence[str]) -> list: - allowed = set(right) - return [value for value in left if value in allowed] - - -def _merge_unique(left: Sequence[str], right: Sequence[str]) -> list: - return list(dict.fromkeys(list(left) + list(right))) - - -def _apply_system_message( - config: SessionConfig, - profile: SovrintSecurityProfile, -) -> Optional[Dict[str, str]]: - current = config.get("system_message") - if current and current.get("mode") == "replace": - if profile.forbid_system_message_replace: - raise ValueError( - "SOVRINT profile '{0}' forbids system-message replacement".format( - profile.profile_id - ) - ) - return current - - existing = (current or {}).get("content", "").strip() - appended = profile.system_message_append.strip() - content = "\n\n".join(value for value in (existing, appended) if value) - if not content: - return current - return {"mode": "append", "content": content} - - -def apply_sovrint_profile( - config: SessionConfig, - profile: SovrintSecurityProfile, - *, - audit_sink: Optional[AuditSink] = None, - evaluate_permission: Optional[PermissionEvaluator] = None, -) -> SessionConfig: - """Return a copied SessionConfig constrained by a SOVRINT profile.""" - - result: SessionConfig = dict(config) - - if profile.tool_surface_mode == "none": - result["available_tools"] = [] - elif profile.tool_surface_mode == "allowlist": - configured = config.get("available_tools") - profile_tools = list(profile.available_tools) - result["available_tools"] = ( - _intersection(configured, profile_tools) if configured is not None else profile_tools - ) - - result["excluded_tools"] = _merge_unique( - config.get("excluded_tools", []), profile.excluded_tools - ) - result["system_message"] = _apply_system_message(config, profile) - result["on_permission_request"] = create_sovrint_permission_handler( - profile, - audit_sink=audit_sink, - evaluate=evaluate_permission, - downstream=config.get("on_permission_request"), - ) - - custom_agents = config.get("custom_agents") - if custom_agents is not None and profile.tool_surface_mode != "inherit": - constrained_agents = [] - for agent in custom_agents: - constrained = dict(agent) - if profile.tool_surface_mode == "none": - constrained["tools"] = [] - else: - agent_tools = agent.get("tools") or list(profile.available_tools) - constrained["tools"] = _intersection(agent_tools, profile.available_tools) - constrained_agents.append(constrained) - result["custom_agents"] = constrained_agents - - mcp_servers = config.get("mcp_servers") - if mcp_servers is not None: - allowed_servers = set(profile.allowed_mcp_servers) - result["mcp_servers"] = { - name: server for name, server in mcp_servers.items() if name in allowed_servers - } - - skill_directories = config.get("skill_directories") - if skill_directories is not None: - allowed_directories = set(profile.allowed_skill_directories) - result["skill_directories"] = [ - directory for directory in skill_directories if directory in allowed_directories - ] - - result["disabled_skills"] = _merge_unique( - config.get("disabled_skills", []), profile.disabled_skills - ) - return result - - -def _rejected_tool_result(reason_code: str) -> ToolResult: - return ToolResult( - textResultForLlm="The governed tool invocation was not authorized.", - resultType="rejected", - error=reason_code, - toolTelemetry={"source": "sovrint", "reasonCode": reason_code}, - ) - - -def wrap_sovrint_tool( - tool: Tool, - profile: SovrintSecurityProfile, - *, - audit_sink: Optional[AuditSink] = None, - authorize: Optional[ToolAuthorizer] = None, -) -> Tool: - """Wrap an SDK Tool with authorization and bounded audit events.""" - - original_handler = tool.handler - - async def guarded_handler(invocation: ToolInvocation) -> ToolResult: - session_id = invocation.get("session_id", "unknown") - tool_call_id = invocation.get("tool_call_id") - started = _audit_event( - profile, - event_class="TOOL_INVOCATION_STARTED", - session_id=session_id, - tool_call_id=tool_call_id, - tool_name=tool.name, - decision="PENDING", - reason_code="TOOL_INVOCATION_RECEIVED", - ) - - if not await _emit_audit(profile, audit_sink, started): - return _rejected_tool_result("AUDIT_SINK_UNAVAILABLE") - - if authorize is not None: - try: - authorized = bool(await _resolve(authorize(invocation))) - except Exception: - authorized = False - if not authorized: - await _emit_audit( - profile, - audit_sink, - _audit_event( - profile, - event_class="TOOL_INVOCATION_REJECTED", - session_id=session_id, - tool_call_id=tool_call_id, - tool_name=tool.name, - decision="REJECTED", - reason_code="CUSTOM_TOOL_AUTHORIZATION_DENIED", - parent_event_id=started["eventId"], - ), - ) - return _rejected_tool_result("CUSTOM_TOOL_AUTHORIZATION_DENIED") - - await _emit_audit( - profile, - audit_sink, - _audit_event( - profile, - event_class="TOOL_INVOCATION_APPROVED", - session_id=session_id, - tool_call_id=tool_call_id, - tool_name=tool.name, - decision="APPROVED", - reason_code="CUSTOM_TOOL_AUTHORIZED", - parent_event_id=started["eventId"], - ), - ) - - try: - result = await _resolve(original_handler(invocation)) - except Exception as exc: - await _emit_audit( - profile, - audit_sink, - _audit_event( - profile, - event_class="TOOL_INVOCATION_FAILED", - session_id=session_id, - tool_call_id=tool_call_id, - tool_name=tool.name, - decision="FAILED", - reason_code="CUSTOM_TOOL_FAILED", - parent_event_id=started["eventId"], - ), - ) - return ToolResult( - textResultForLlm="The governed tool invocation failed.", - resultType="failure", - error=str(exc), - toolTelemetry={"source": "sovrint"}, - ) - - await _emit_audit( - profile, - audit_sink, - _audit_event( - profile, - event_class="TOOL_INVOCATION_COMPLETED", - session_id=session_id, - tool_call_id=tool_call_id, - tool_name=tool.name, - decision="RECORDED", - reason_code="CUSTOM_TOOL_COMPLETED", - parent_event_id=started["eventId"], - ), - ) - return result - - return Tool( - name=tool.name, - description=tool.description, - parameters=tool.parameters, - handler=guarded_handler, - ) +from .sovrint_runtime import * From 652967010ef556b354e242546bb7d59cefabde79 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:52:40 -0400 Subject: [PATCH 43/63] Make Python SOVRINT facade lint-safe --- python/copilot/sovrint.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/python/copilot/sovrint.py b/python/copilot/sovrint.py index a57b6b3231..03fe4bc74e 100644 --- a/python/copilot/sovrint.py +++ b/python/copilot/sovrint.py @@ -1,3 +1,10 @@ """Compatibility imports for the modular SOVRINT runtime.""" -from .sovrint_runtime import * +from .sovrint_runtime import SOVRINT_READ_ONLY_PROFILE +from .sovrint_runtime import SOVRINT_RESEARCH_PROFILE +from .sovrint_runtime import SOVRINT_STRICT_PROFILE +from .sovrint_runtime import SOVRINT_SYSTEM_APPEND +from .sovrint_runtime import SovrintSecurityProfile +from .sovrint_runtime import apply_sovrint_profile +from .sovrint_runtime import create_sovrint_permission_handler +from .sovrint_runtime import wrap_sovrint_tool From 3f5cde11c49e121d1abcd94761327935ba8facba Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:53:23 -0400 Subject: [PATCH 44/63] Mark Python SOVRINT compatibility exports --- python/copilot/sovrint.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/python/copilot/sovrint.py b/python/copilot/sovrint.py index 03fe4bc74e..424076d1eb 100644 --- a/python/copilot/sovrint.py +++ b/python/copilot/sovrint.py @@ -1,10 +1,12 @@ """Compatibility imports for the modular SOVRINT runtime.""" -from .sovrint_runtime import SOVRINT_READ_ONLY_PROFILE -from .sovrint_runtime import SOVRINT_RESEARCH_PROFILE -from .sovrint_runtime import SOVRINT_STRICT_PROFILE -from .sovrint_runtime import SOVRINT_SYSTEM_APPEND -from .sovrint_runtime import SovrintSecurityProfile -from .sovrint_runtime import apply_sovrint_profile -from .sovrint_runtime import create_sovrint_permission_handler -from .sovrint_runtime import wrap_sovrint_tool +from .sovrint_runtime import SOVRINT_READ_ONLY_PROFILE as SOVRINT_READ_ONLY_PROFILE +from .sovrint_runtime import SOVRINT_RESEARCH_PROFILE as SOVRINT_RESEARCH_PROFILE +from .sovrint_runtime import SOVRINT_STRICT_PROFILE as SOVRINT_STRICT_PROFILE +from .sovrint_runtime import SOVRINT_SYSTEM_APPEND as SOVRINT_SYSTEM_APPEND +from .sovrint_runtime import SovrintSecurityProfile as SovrintSecurityProfile +from .sovrint_runtime import apply_sovrint_profile as apply_sovrint_profile +from .sovrint_runtime import ( + create_sovrint_permission_handler as create_sovrint_permission_handler, +) +from .sovrint_runtime import wrap_sovrint_tool as wrap_sovrint_tool From 9f74d44e6f4b3415b9b00abc9e279dacad2439e7 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:53:58 -0400 Subject: [PATCH 45/63] Mark modular Python SOVRINT exports explicitly --- python/copilot/sovrint_runtime/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/python/copilot/sovrint_runtime/__init__.py b/python/copilot/sovrint_runtime/__init__.py index f695af66f4..5d9441a515 100644 --- a/python/copilot/sovrint_runtime/__init__.py +++ b/python/copilot/sovrint_runtime/__init__.py @@ -1,12 +1,12 @@ """Public SOVRINT governed-session runtime API.""" -from .model import ( - SOVRINT_READ_ONLY_PROFILE, - SOVRINT_RESEARCH_PROFILE, - SOVRINT_STRICT_PROFILE, - SOVRINT_SYSTEM_APPEND, - SovrintSecurityProfile, +from .model import SOVRINT_READ_ONLY_PROFILE as SOVRINT_READ_ONLY_PROFILE +from .model import SOVRINT_RESEARCH_PROFILE as SOVRINT_RESEARCH_PROFILE +from .model import SOVRINT_STRICT_PROFILE as SOVRINT_STRICT_PROFILE +from .model import SOVRINT_SYSTEM_APPEND as SOVRINT_SYSTEM_APPEND +from .model import SovrintSecurityProfile as SovrintSecurityProfile +from .permissions import ( + create_sovrint_permission_handler as create_sovrint_permission_handler, ) -from .permissions import create_sovrint_permission_handler -from .projection import apply_sovrint_profile -from .tools import wrap_sovrint_tool +from .projection import apply_sovrint_profile as apply_sovrint_profile +from .tools import wrap_sovrint_tool as wrap_sovrint_tool From a1d3114f2ea77155e628eaadd461c1f6ada3f8ca Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:54:56 -0400 Subject: [PATCH 46/63] Add formatted Python SOVRINT projection core --- .../sovrint_runtime/projection_core.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 python/copilot/sovrint_runtime/projection_core.py diff --git a/python/copilot/sovrint_runtime/projection_core.py b/python/copilot/sovrint_runtime/projection_core.py new file mode 100644 index 0000000000..6ae91e7f30 --- /dev/null +++ b/python/copilot/sovrint_runtime/projection_core.py @@ -0,0 +1,39 @@ +"""Project SOVRINT policy onto an SDK session configuration.""" + +from typing import Optional + +from ..types import SessionConfig +from .model import AuditSink, PermissionEvaluator, SovrintSecurityProfile +from .permissions import create_sovrint_permission_handler +from .surface import constrain_surfaces + + +def apply_sovrint_profile( + config: SessionConfig, + profile: SovrintSecurityProfile, + *, + audit_sink: Optional[AuditSink] = None, + evaluate_permission: Optional[PermissionEvaluator] = None, +) -> SessionConfig: + """Return a profile-constrained copy of an SDK configuration.""" + + result: SessionConfig = dict(config) + current = config.get("system_message") + if current and current.get("mode") == "replace": + if profile.forbid_system_message_replace: + message = f"Profile '{profile.profile_id}' forbids the requested system mode" + raise ValueError(message) + else: + existing = (current or {}).get("content", "").strip() + appended = profile.system_message_append.strip() + content = "\n\n".join(value for value in (existing, appended) if value) + result["system_message"] = {"mode": "append", "content": content} + + constrain_surfaces(config, result, profile) + result["on_permission_request"] = create_sovrint_permission_handler( + profile, + audit_sink=audit_sink, + evaluate=evaluate_permission, + downstream=config.get("on_permission_request"), + ) + return result From d89a0c921fcc86966b465d25bb79f16411c71a2d Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:55:03 -0400 Subject: [PATCH 47/63] Route projection through formatted core --- python/copilot/sovrint_runtime/projection.py | 39 +------------------- 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/python/copilot/sovrint_runtime/projection.py b/python/copilot/sovrint_runtime/projection.py index d72a86d309..baf0a509d3 100644 --- a/python/copilot/sovrint_runtime/projection.py +++ b/python/copilot/sovrint_runtime/projection.py @@ -1,38 +1,3 @@ -"""Project SOVRINT policy onto an SDK session configuration.""" +"""Compatibility import for the SOVRINT session projection.""" -from typing import Optional - -from ..types import SessionConfig -from .model import AuditSink, PermissionEvaluator, SovrintSecurityProfile -from .permissions import create_sovrint_permission_handler -from .surface import constrain_surfaces - - -def apply_sovrint_profile( - config: SessionConfig, - profile: SovrintSecurityProfile, - *, - audit_sink: Optional[AuditSink] = None, - evaluate_permission: Optional[PermissionEvaluator] = None, -) -> SessionConfig: - """Return a profile-constrained copy of an SDK configuration.""" - - result: SessionConfig = dict(config) - current = config.get("system_message") - if current and current.get("mode") == "replace": - if profile.forbid_system_message_replace: - raise ValueError(f"Profile '{profile.profile_id}' forbids the requested system mode") - else: - existing = (current or {}).get("content", "").strip() - appended = profile.system_message_append.strip() - content = "\n\n".join(value for value in (existing, appended) if value) - result["system_message"] = {"mode": "append", "content": content} - - constrain_surfaces(config, result, profile) - result["on_permission_request"] = create_sovrint_permission_handler( - profile, - audit_sink=audit_sink, - evaluate=evaluate_permission, - downstream=config.get("on_permission_request"), - ) - return result +from .projection_core import apply_sovrint_profile as apply_sovrint_profile From 1e5cac397ee713fb337d7b0ab9feb572b56243ef Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:56:33 -0400 Subject: [PATCH 48/63] Add modular Go SOVRINT profile layer --- go/sovrint_profile.go | 71 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 go/sovrint_profile.go diff --git a/go/sovrint_profile.go b/go/sovrint_profile.go new file mode 100644 index 0000000000..c271f3d250 --- /dev/null +++ b/go/sovrint_profile.go @@ -0,0 +1,71 @@ +package copilot + +// SovrintPermissionDecision is a bounded application decision. +type SovrintPermissionDecision string + +const ( + SovrintApprove SovrintPermissionDecision = "approve" + SovrintDeny SovrintPermissionDecision = "deny" +) + +// SovrintSecurityProfile defines a versioned governed-session policy. +type SovrintSecurityProfile struct { + ProfileID string + Version string + Description string + DefaultDecision SovrintPermissionDecision + AllowKinds []string + DenyKinds []string + ToolSurfaceMode string + AvailableTools []string + ExcludedTools []string + AllowedMCPServers []string + AllowedSkillDirectories []string + DisabledSkills []string + ForbidSystemMessageReplace bool + FailClosedOnAuditError bool + AuditEnabled bool + SystemMessageAppend string +} + +// SovrintSystemAppend is the canonical governed-session instruction layer. +const SovrintSystemAppend = "Operate under a bounded SOVRINT governed-session profile. Use only explicitly exposed tools and declared authority. Keep observations, inferences, recommendations, governance decisions, integrity findings, and accepted evidence distinct. Do not claim approval, verification, restoration, or EvidenceGrid acceptance without an explicit external result." + +var SovrintStrictProfile = SovrintSecurityProfile{ + ProfileID: "sovrint.strict", + Version: "1.0", + Description: "Deny every permission and expose no inherited first-party tools.", + DefaultDecision: SovrintDeny, + DenyKinds: []string{"read", "write", "shell", "url", "mcp"}, + ToolSurfaceMode: "none", + ForbidSystemMessageReplace: true, + FailClosedOnAuditError: true, + AuditEnabled: true, + SystemMessageAppend: SovrintSystemAppend, +} + +var SovrintReadOnlyProfile = SovrintSecurityProfile{ + ProfileID: "sovrint.read-only", + Version: "1.0", + Description: "Permit reads while denying mutating and external permission kinds.", + DefaultDecision: SovrintDeny, + AllowKinds: []string{"read"}, + DenyKinds: []string{"write", "shell", "url", "mcp"}, + ToolSurfaceMode: "inherit", + ForbidSystemMessageReplace: true, + FailClosedOnAuditError: true, + AuditEnabled: true, + SystemMessageAppend: SovrintSystemAppend + " Operate in read-only mode.", +} + +var SovrintResearchProfile = SovrintSecurityProfile{ + ProfileID: "sovrint.research", + Version: "1.0", + Description: "Permit reads and defer other permission kinds to an application evaluator.", + DefaultDecision: SovrintDeny, + AllowKinds: []string{"read"}, + ToolSurfaceMode: "inherit", + ForbidSystemMessageReplace: true, + AuditEnabled: true, + SystemMessageAppend: SovrintSystemAppend + " Separate research observations from verified findings.", +} From dbb93f5e2329856450d7075e4cb1b9d603a7e74c Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:56:55 -0400 Subject: [PATCH 49/63] Add Go SOVRINT audit event model --- go/sovrint_audit_event.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 go/sovrint_audit_event.go diff --git a/go/sovrint_audit_event.go b/go/sovrint_audit_event.go new file mode 100644 index 0000000000..7843b712dd --- /dev/null +++ b/go/sovrint_audit_event.go @@ -0,0 +1,23 @@ +package copilot + +// SovrintAuditEvent is the bounded event envelope for governed SDK activity. +type SovrintAuditEvent struct { + SchemaVersion string `json:"schemaVersion"` + EventID string `json:"eventId"` + ParentEventID string `json:"parentEventId,omitempty"` + EventClass string `json:"eventClass"` + TimestampUTC string `json:"timestampUtc"` + ProfileID string `json:"profileId"` + ProfileVersion string `json:"profileVersion"` + SessionID string `json:"sessionId"` + ToolCallID string `json:"toolCallId,omitempty"` + ToolName string `json:"toolName,omitempty"` + PermissionKind string `json:"permissionKind,omitempty"` + Decision string `json:"decision"` + ReasonCode string `json:"reasonCode"` + DisclosureClass string `json:"disclosureClass"` + EvidenceStatus string `json:"evidenceStatus"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +type SovrintAuditSink func(SovrintAuditEvent) error From dd99970efdd210cd8dfb9409ba028eeb39c69e4b Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:57:05 -0400 Subject: [PATCH 50/63] Add Go SOVRINT audit runtime --- go/sovrint_audit_runtime.go | 49 +++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 go/sovrint_audit_runtime.go diff --git a/go/sovrint_audit_runtime.go b/go/sovrint_audit_runtime.go new file mode 100644 index 0000000000..0a1ddca77c --- /dev/null +++ b/go/sovrint_audit_runtime.go @@ -0,0 +1,49 @@ +package copilot + +import ( + "fmt" + "sync/atomic" + "time" +) + +var sovrintAuditSequence uint64 + +func newSovrintAuditEvent( + profile SovrintSecurityProfile, + eventClass string, + sessionID string, + decision string, + reasonCode string, +) SovrintAuditEvent { + sequence := atomic.AddUint64(&sovrintAuditSequence, 1) + return SovrintAuditEvent{ + SchemaVersion: "1.0", + EventID: fmt.Sprintf("sovrint-%d-%d", time.Now().UnixMilli(), sequence), + EventClass: eventClass, + TimestampUTC: time.Now().UTC().Format(time.RFC3339Nano), + ProfileID: profile.ProfileID, + ProfileVersion: profile.Version, + SessionID: sessionID, + Decision: decision, + ReasonCode: reasonCode, + DisclosureClass: "INTERNAL", + EvidenceStatus: "NOT_SUBMITTED", + } +} + +func emitSovrintAudit( + profile SovrintSecurityProfile, + sink SovrintAuditSink, + event SovrintAuditEvent, +) bool { + if !profile.AuditEnabled { + return true + } + if sink == nil { + return !profile.FailClosedOnAuditError + } + if err := sink(event); err != nil { + return !profile.FailClosedOnAuditError + } + return true +} From 983dbc149e115a07b97cca8dc154cba67e1410e4 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:58:03 -0400 Subject: [PATCH 51/63] Add Go SOVRINT permission contracts --- go/sovrint_permission_contract.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 go/sovrint_permission_contract.go diff --git a/go/sovrint_permission_contract.go b/go/sovrint_permission_contract.go new file mode 100644 index 0000000000..b8bb09fdc1 --- /dev/null +++ b/go/sovrint_permission_contract.go @@ -0,0 +1,12 @@ +package copilot + +type SovrintPermissionEvaluator func( + PermissionRequest, + PermissionInvocation, +) (SovrintPermissionDecision, error) + +type SovrintPermissionHandlerOptions struct { + AuditSink SovrintAuditSink + Evaluate SovrintPermissionEvaluator + Downstream PermissionHandler +} From dfbcc80bd9a66ce825a3c181a2bb1c52efcf2e1a Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:58:28 -0400 Subject: [PATCH 52/63] Add Go SOVRINT permission evaluation --- go/sovrint_permission_eval.go | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 go/sovrint_permission_eval.go diff --git a/go/sovrint_permission_eval.go b/go/sovrint_permission_eval.go new file mode 100644 index 0000000000..f8dd9f2ca0 --- /dev/null +++ b/go/sovrint_permission_eval.go @@ -0,0 +1,43 @@ +package copilot + +func containsSovrintValue(values []string, target string) bool { + for _, value := range values { + if value == target { + return true + } + } + return false +} + +func evaluateSovrintPermission( + profile SovrintSecurityProfile, + request PermissionRequest, + invocation PermissionInvocation, + evaluator SovrintPermissionEvaluator, +) (bool, string) { + if request.Kind == "" { + return false, "UNKNOWN_PERMISSION_KIND" + } + if containsSovrintValue(profile.DenyKinds, request.Kind) { + return false, "PROFILE_EXPLICIT_DENY" + } + if evaluator != nil { + decision, err := evaluator(request, invocation) + if err != nil { + return false, "APPLICATION_EVALUATOR_FAILED" + } + if decision == SovrintApprove { + return true, "APPLICATION_EVALUATOR_APPROVED" + } + if decision == SovrintDeny { + return false, "APPLICATION_EVALUATOR_DENIED" + } + } + if containsSovrintValue(profile.AllowKinds, request.Kind) { + return true, "PROFILE_EXPLICIT_ALLOW" + } + if profile.DefaultDecision == SovrintApprove { + return true, "PROFILE_DEFAULT_ALLOW" + } + return false, "PROFILE_DEFAULT_DENY" +} From 83ccfa4d17dcd67c4f35218cb143d20eb54e4ee2 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:58:54 -0400 Subject: [PATCH 53/63] Add Go SOVRINT permission result mapping --- go/sovrint_permission_result.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 go/sovrint_permission_result.go diff --git a/go/sovrint_permission_result.go b/go/sovrint_permission_result.go new file mode 100644 index 0000000000..dd9b2160e8 --- /dev/null +++ b/go/sovrint_permission_result.go @@ -0,0 +1,23 @@ +package copilot + +func sovrintPermissionResult( + approved bool, + profile SovrintSecurityProfile, + reasonCode string, +) PermissionRequestResult { + kind := "denied-by-rules" + if approved { + kind = "approved" + } + return PermissionRequestResult{ + Kind: kind, + Rules: []interface{}{ + map[string]interface{}{ + "source": "sovrint", + "profileId": profile.ProfileID, + "profileVersion": profile.Version, + "reasonCode": reasonCode, + }, + }, + } +} From 8e31f220fc0881bd491b0298f5a6244602b0aef4 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:59:04 -0400 Subject: [PATCH 54/63] Add Go SOVRINT permission coordinator --- go/sovrint_permission_handler.go | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 go/sovrint_permission_handler.go diff --git a/go/sovrint_permission_handler.go b/go/sovrint_permission_handler.go new file mode 100644 index 0000000000..f214913cae --- /dev/null +++ b/go/sovrint_permission_handler.go @@ -0,0 +1,45 @@ +package copilot + +// CreateSovrintPermissionHandler composes permission authorities conservatively. +func CreateSovrintPermissionHandler( + profile SovrintSecurityProfile, + options SovrintPermissionHandlerOptions, +) PermissionHandler { + return func( + request PermissionRequest, + invocation PermissionInvocation, + ) (PermissionRequestResult, error) { + approved, reason := evaluateSovrintPermission( + profile, + request, + invocation, + options.Evaluate, + ) + if approved && options.Downstream != nil { + downstream, err := options.Downstream(request, invocation) + if err != nil { + approved, reason = false, "DOWNSTREAM_HANDLER_FAILED" + } else if downstream.Kind != "approved" { + approved, reason = false, "DOWNSTREAM_HANDLER_DENIED" + } + } + + decision := "DENIED" + if approved { + decision = "APPROVED" + } + event := newSovrintAuditEvent( + profile, + "PERMISSION_DECISION", + invocation.SessionID, + decision, + reason, + ) + event.ToolCallID = request.ToolCallID + event.PermissionKind = request.Kind + if !emitSovrintAudit(profile, options.AuditSink, event) && approved { + return sovrintPermissionResult(false, profile, "AUDIT_SINK_UNAVAILABLE"), nil + } + return sovrintPermissionResult(approved, profile, reason), nil + } +} From d02495c7413f1c796992e3afb5d228ed18bc473a Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 19:59:49 -0400 Subject: [PATCH 55/63] Add Go SOVRINT session projection --- go/sovrint_session_apply.go | 82 +++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 go/sovrint_session_apply.go diff --git a/go/sovrint_session_apply.go b/go/sovrint_session_apply.go new file mode 100644 index 0000000000..6521565fd0 --- /dev/null +++ b/go/sovrint_session_apply.go @@ -0,0 +1,82 @@ +package copilot + +import "fmt" + +type SovrintApplyProfileOptions struct { + AuditSink SovrintAuditSink + EvaluatePermission SovrintPermissionEvaluator +} + +// ApplySovrintProfile projects a governed profile onto a copied SDK configuration. +func ApplySovrintProfile( + config *SessionConfig, + profile SovrintSecurityProfile, + options SovrintApplyProfileOptions, +) (*SessionConfig, error) { + if config == nil { + config = &SessionConfig{} + } + result := *config + result.AvailableTools = copySovrintStrings(config.AvailableTools) + result.ExcludedTools = copySovrintStrings(config.ExcludedTools) + result.SkillDirectories = copySovrintStrings(config.SkillDirectories) + result.DisabledSkills = copySovrintStrings(config.DisabledSkills) + + if profile.ToolSurfaceMode == "none" { + result.AvailableTools = []string{} + } else if profile.ToolSurfaceMode == "allowlist" { + if config.AvailableTools == nil { + result.AvailableTools = copySovrintStrings(profile.AvailableTools) + } else { + result.AvailableTools = intersectSovrintStrings( + config.AvailableTools, + profile.AvailableTools, + ) + } + } + result.ExcludedTools = mergeSovrintStrings(config.ExcludedTools, profile.ExcludedTools) + + if config.SystemMessage != nil && config.SystemMessage.Mode == "replace" { + if profile.ForbidSystemMessageReplace { + return nil, fmt.Errorf( + "SOVRINT profile %q forbids system-message replacement", + profile.ProfileID, + ) + } + copied := *config.SystemMessage + result.SystemMessage = &copied + } else { + appendSovrintSystemMessage(config, &result, profile) + } + + result.OnPermissionRequest = CreateSovrintPermissionHandler( + profile, + SovrintPermissionHandlerOptions{ + AuditSink: options.AuditSink, + Evaluate: options.EvaluatePermission, + Downstream: config.OnPermissionRequest, + }, + ) + constrainSovrintSessionSurfaces(config, &result, profile) + return &result, nil +} + +func appendSovrintSystemMessage( + config *SessionConfig, + result *SessionConfig, + profile SovrintSecurityProfile, +) { + existing := "" + if config.SystemMessage != nil { + existing = config.SystemMessage.Content + } + content := profile.SystemMessageAppend + if existing != "" && content != "" { + content = existing + "\n\n" + content + } else if existing != "" { + content = existing + } + if content != "" { + result.SystemMessage = &SystemMessageConfig{Mode: "append", Content: content} + } +} From 3fc4a97c1af2c2fc3ef48b168cd4b1066a0a5d04 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 20:00:17 -0400 Subject: [PATCH 56/63] Add Go SOVRINT slice utilities --- go/sovrint_slice.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 go/sovrint_slice.go diff --git a/go/sovrint_slice.go b/go/sovrint_slice.go new file mode 100644 index 0000000000..6243b4f303 --- /dev/null +++ b/go/sovrint_slice.go @@ -0,0 +1,35 @@ +package copilot + +func copySovrintStrings(values []string) []string { + if values == nil { + return nil + } + return append([]string{}, values...) +} + +func intersectSovrintStrings(left []string, right []string) []string { + allowed := make(map[string]struct{}, len(right)) + for _, value := range right { + allowed[value] = struct{}{} + } + result := make([]string, 0) + for _, value := range left { + if _, ok := allowed[value]; ok { + result = append(result, value) + } + } + return result +} + +func mergeSovrintStrings(left []string, right []string) []string { + seen := make(map[string]struct{}, len(left)+len(right)) + result := make([]string, 0, len(left)+len(right)) + for _, value := range append(copySovrintStrings(left), right...) { + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + result = append(result, value) + } + return result +} From 88fa1339ea6b79ea6e4c3d04a4306ebeec12d23f Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 20:00:29 -0400 Subject: [PATCH 57/63] Add Go SOVRINT session surface constraints --- go/sovrint_session_surface.go | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 go/sovrint_session_surface.go diff --git a/go/sovrint_session_surface.go b/go/sovrint_session_surface.go new file mode 100644 index 0000000000..7c6d5867e2 --- /dev/null +++ b/go/sovrint_session_surface.go @@ -0,0 +1,47 @@ +package copilot + +func constrainSovrintSessionSurfaces( + config *SessionConfig, + result *SessionConfig, + profile SovrintSecurityProfile, +) { + if config.CustomAgents != nil { + result.CustomAgents = append([]CustomAgentConfig{}, config.CustomAgents...) + if profile.ToolSurfaceMode != "inherit" { + for index := range result.CustomAgents { + if profile.ToolSurfaceMode == "none" { + result.CustomAgents[index].Tools = []string{} + continue + } + tools := result.CustomAgents[index].Tools + if tools == nil { + tools = profile.AvailableTools + } + result.CustomAgents[index].Tools = intersectSovrintStrings( + tools, + profile.AvailableTools, + ) + } + } + } + + if config.MCPServers != nil { + allowed := make(map[string]struct{}, len(profile.AllowedMCPServers)) + for _, name := range profile.AllowedMCPServers { + allowed[name] = struct{}{} + } + result.MCPServers = make(map[string]MCPServerConfig) + for name, server := range config.MCPServers { + if _, ok := allowed[name]; ok { + result.MCPServers[name] = server + } + } + } + if config.SkillDirectories != nil { + result.SkillDirectories = intersectSovrintStrings( + config.SkillDirectories, + profile.AllowedSkillDirectories, + ) + } + result.DisabledSkills = mergeSovrintStrings(config.DisabledSkills, profile.DisabledSkills) +} From 960b8c4b785a64f27000221e117e6cceedd0e258 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 20:00:41 -0400 Subject: [PATCH 58/63] Add Go SOVRINT tool contracts --- go/sovrint_tool_contract.go | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 go/sovrint_tool_contract.go diff --git a/go/sovrint_tool_contract.go b/go/sovrint_tool_contract.go new file mode 100644 index 0000000000..0d8ee56857 --- /dev/null +++ b/go/sovrint_tool_contract.go @@ -0,0 +1,9 @@ +package copilot + +type SovrintToolAuthorizer func(ToolInvocation) (bool, error) + +type SovrintToolGuardOptions struct { + Profile SovrintSecurityProfile + AuditSink SovrintAuditSink + Authorize SovrintToolAuthorizer +} From ae023e93a5fd610fcf207d61775bfe1d28699062 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 20:01:53 -0400 Subject: [PATCH 59/63] Add Go SOVRINT tool outcome helper --- go/sovrint_tool_outcome.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 go/sovrint_tool_outcome.go diff --git a/go/sovrint_tool_outcome.go b/go/sovrint_tool_outcome.go new file mode 100644 index 0000000000..b8ed4619da --- /dev/null +++ b/go/sovrint_tool_outcome.go @@ -0,0 +1,13 @@ +package copilot + +func sovrintToolOutcome(reasonCode string, resultType string) ToolResult { + return ToolResult{ + TextResultForLLM: "The governed tool invocation did not proceed.", + ResultType: resultType, + Error: reasonCode, + ToolTelemetry: map[string]interface{}{ + "source": "sovrint", + "reasonCode": reasonCode, + }, + } +} From 96f673b2d51403bb8d16dd02d952962f4e92bbf7 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 20:02:00 -0400 Subject: [PATCH 60/63] Add Go SOVRINT tool event recorder --- go/sovrint_tool_event.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 go/sovrint_tool_event.go diff --git a/go/sovrint_tool_event.go b/go/sovrint_tool_event.go new file mode 100644 index 0000000000..d44e0b74a0 --- /dev/null +++ b/go/sovrint_tool_event.go @@ -0,0 +1,23 @@ +package copilot + +func recordSovrintToolEvent( + tool Tool, + invocation ToolInvocation, + parent SovrintAuditEvent, + options SovrintToolGuardOptions, + eventClass string, + decision string, + reasonCode string, +) { + event := newSovrintAuditEvent( + options.Profile, + eventClass, + invocation.SessionID, + decision, + reasonCode, + ) + event.ParentEventID = parent.EventID + event.ToolCallID = invocation.ToolCallID + event.ToolName = tool.Name + emitSovrintAudit(options.Profile, options.AuditSink, event) +} From b2a7900b7a3a27ee6e5f5c197d094958b730a464 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 20:02:31 -0400 Subject: [PATCH 61/63] Add Go SOVRINT tool wrapper --- go/sovrint_tool_wrap.go | 48 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 go/sovrint_tool_wrap.go diff --git a/go/sovrint_tool_wrap.go b/go/sovrint_tool_wrap.go new file mode 100644 index 0000000000..2f6869406f --- /dev/null +++ b/go/sovrint_tool_wrap.go @@ -0,0 +1,48 @@ +package copilot + +// WrapSovrintTool adds application authorization and bounded audit events. +func WrapSovrintTool(tool Tool, options SovrintToolGuardOptions) Tool { + original := tool.Handler + tool.Handler = func(invocation ToolInvocation) (ToolResult, error) { + started := newSovrintAuditEvent( + options.Profile, + "TOOL_INVOCATION_STARTED", + invocation.SessionID, + "PENDING", + "TOOL_INVOCATION_RECEIVED", + ) + started.ToolCallID = invocation.ToolCallID + started.ToolName = tool.Name + if !emitSovrintAudit(options.Profile, options.AuditSink, started) { + return sovrintToolOutcome("AUDIT_SINK_UNAVAILABLE", "rejected"), nil + } + + if options.Authorize != nil { + allowed, err := options.Authorize(invocation) + if err != nil || !allowed { + recordSovrintToolEvent( + tool, + invocation, + started, + options, + "TOOL_INVOCATION_REJECTED", + "REJECTED", + "CUSTOM_TOOL_AUTHORIZATION_DENIED", + ) + return sovrintToolOutcome("CUSTOM_TOOL_AUTHORIZATION_DENIED", "rejected"), nil + } + } + + recordSovrintToolEvent( + tool, + invocation, + started, + options, + "TOOL_INVOCATION_APPROVED", + "APPROVED", + "CUSTOM_TOOL_AUTHORIZED", + ) + return executeSovrintTool(tool, original, invocation, started, options), nil + } + return tool +} From 2073f30c56a214b47eb010747f4e2527783a360e Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 20:03:08 -0400 Subject: [PATCH 62/63] Add Go SOVRINT tool execution --- go/sovrint_tool_execute.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 go/sovrint_tool_execute.go diff --git a/go/sovrint_tool_execute.go b/go/sovrint_tool_execute.go new file mode 100644 index 0000000000..dfa6e91bb3 --- /dev/null +++ b/go/sovrint_tool_execute.go @@ -0,0 +1,32 @@ +package copilot + +func executeSovrintTool( + tool Tool, + handler ToolHandler, + invocation ToolInvocation, + started SovrintAuditEvent, + options SovrintToolGuardOptions, +) ToolResult { + if handler == nil { + return finishSovrintTool( + tool, + invocation, + started, + options, + "CUSTOM_TOOL_HANDLER_MISSING", + nil, + ) + } + result, err := handler(invocation) + if err != nil { + return finishSovrintTool( + tool, + invocation, + started, + options, + err.Error(), + nil, + ) + } + return finishSovrintTool(tool, invocation, started, options, "", &result) +} From ee616b88e41efec61646601cd33d9665f4739811 Mon Sep 17 00:00:00 2001 From: Katrina pietroniro Date: Fri, 26 Jun 2026 20:03:16 -0400 Subject: [PATCH 63/63] Add Go SOVRINT tool completion stage --- go/sovrint_tool_finish.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 go/sovrint_tool_finish.go diff --git a/go/sovrint_tool_finish.go b/go/sovrint_tool_finish.go new file mode 100644 index 0000000000..5969d81293 --- /dev/null +++ b/go/sovrint_tool_finish.go @@ -0,0 +1,33 @@ +package copilot + +func finishSovrintTool( + tool Tool, + invocation ToolInvocation, + started SovrintAuditEvent, + options SovrintToolGuardOptions, + reason string, + result *ToolResult, +) ToolResult { + if result == nil { + recordSovrintToolEvent( + tool, + invocation, + started, + options, + "TOOL_INVOCATION_FAILED", + "FAILED", + "CUSTOM_TOOL_FAILED", + ) + return sovrintToolOutcome(reason, "failure") + } + recordSovrintToolEvent( + tool, + invocation, + started, + options, + "TOOL_INVOCATION_COMPLETED", + "RECORDED", + "CUSTOM_TOOL_COMPLETED", + ) + return *result +}