Governance
The GASP twin gives every agent the same view of what metrics exist and how they connect. That is a feature, not a bug. But agents act on behalf of users, and users have different access needs. This page is GASP's opinion on how to scope permissions in an agentic SaaS system without building a new policy engine.
The Problem
If every agent reads the same twin, and every user can invoke any agent, then by default every user has access to every metric. A Support Manager's retention agent could ask the Finance twin for CAC. A sales rep's pipeline agent could read competitor win rates in other territories. Without governance the twin becomes a bypass around carefully-designed enterprise access controls.
The mistake is treating this as a new problem. It is not. Enterprise access control is a well-studied domain with proven patterns. GASP's job is to give those patterns a shared vocabulary, not to reinvent them.
The Three-Layer Model
Access control in an agentic system operates at three distinct layers. Conflating them is where most implementations fail.
Model Layer
Always visible
Every user can see what metrics exist, their canonical definitions, their relationships and their required fields. The model is documentation. Withholding it creates shadow systems and duplicate definitions. A Support Manager should know what CAC is and where it lives in the graph, even if they cannot see the current number.
Computation Layer
Permission-gated
Can a user compute the current value of a metric? This is the real enforcement boundary. It lives in the warehouse via row-level security, column masking or a query rewriter. The agent does not make this decision. The warehouse does. When a query runs, it runs with the user's identity and the user's permissions.
Agent Layer
Delegated identity
Agents always act on behalf of a user and inherit that user's scope. A retention agent invoked by a Finance Manager sees different numbers than the same agent invoked by a Support Manager. Elevated service identities (board prep, compliance audit) are explicit, audited exceptions.
What GASP Adds
Two metric-level classifications give downstream policy systems stable identifiers to reference. These are recommendations, not enforcement. Your organization may reasonably choose different defaults.
Sensitivity Tier
Aligns with ISO 27001, OSSA data_classification and AWS Cedar common patterns.
See the glossary entry for the canonical definition.
| Tier | Description |
|---|---|
| public | Shareable externally with no restriction (rare for internal SaaS metrics) |
| internal | Shareable across the organization. Default for most operational metrics |
| confidential | Cross-functional but sensitive. Typically restricted to managers and above |
| restricted | Highly sensitive. Executive and finance-leadership only by default |
Access Scope
Describes whether a metric is naturally filterable per user context (the ReBAC/ABAC dimension). See the glossary entry for the canonical definition.
| Scope | Description |
|---|---|
| unscoped | Same value for everyone with access (e.g. company-wide ARR) |
| department-scoped | Filtered by the viewer's department context |
| owner-scoped | Visible only for records the viewer owns or manages (individual, team, territory, book or segment — interpretation lives in the adapter) |
Recommended Classifications
Default sensitivity and scope for every metric in the GASP relationship graph. Use these as a starting point. Your organization's risk profile may justify different choices.
| Metric | Sensitivity | Scope |
|---|---|---|
| rule_of_40 | restricted | unscoped |
| operating_margin | restricted | unscoped |
| fcf | restricted | unscoped |
| burn_rate | restricted | unscoped |
| revenue_per_employee | restricted | unscoped |
| magic_number | restricted | unscoped |
| employee_turnover | restricted | unscoped |
| enps | restricted | unscoped |
| pipeline_value | confidential | owner-scoped |
| pipeline_coverage | confidential | owner-scoped |
| acv | confidential | owner-scoped |
| quota_attainment | confidential | owner-scoped |
| win_rate | confidential | department-scoped |
| forecast_accuracy | confidential | department-scoped |
| cpl | confidential | department-scoped |
| marketing_sourced_pipeline | confidential | department-scoped |
| expansion_rate | confidential | department-scoped |
| renewal_rate | confidential | department-scoped |
| partner_sourced_revenue | confidential | department-scoped |
| arr | confidential | unscoped |
| mrr | confidential | unscoped |
| arpa | confidential | unscoped |
| mrr_growth_rate | confidential | unscoped |
| new_mrr | confidential | unscoped |
| expansion_mrr | confidential | unscoped |
| churned_mrr | confidential | unscoped |
| contraction_mrr | confidential | unscoped |
| net_new_mrr | confidential | unscoped |
| nrr | confidential | unscoped |
| grr | confidential | unscoped |
| gross_margin | confidential | unscoped |
| logo_churn_rate | confidential | unscoped |
| revenue_churn_rate | confidential | unscoped |
| cac | confidential | unscoped |
| ltv | confidential | unscoped |
| ltv_cac_ratio | confidential | unscoped |
| cac_payback | confidential | unscoped |
| marketing_cac | confidential | unscoped |
| dso | confidential | unscoped |
| health_score | internal | owner-scoped |
| at_risk_rate | internal | owner-scoped |
| sales_cycle | internal | department-scoped |
| qbr_completion | internal | department-scoped |
| csat | internal | department-scoped |
| frt | internal | department-scoped |
| ticket_volume | internal | department-scoped |
| ttfv | internal | department-scoped |
| onboarding_completion | internal | department-scoped |
| pqas | internal | department-scoped |
| deployment_frequency | internal | department-scoped |
| mttr | internal | department-scoped |
| nps | internal | unscoped |
| mqls | internal | unscoped |
| sqls | internal | unscoped |
| lvr | internal | unscoped |
| self_service_rate | internal | unscoped |
| activation_rate | internal | unscoped |
| dau_mau | internal | unscoped |
| feature_adoption | internal | unscoped |
| uptime | internal | unscoped |
A Decision Framework
When classifying a new metric, work through three questions in order:
- Who needs it by default?
If the answer is "the whole company," the tier is
internal. If the answer is "managers and above," it isconfidential. If it is "finance leadership and the CEO," it isrestricted. - Is it filterable?
If the same metric makes sense with per-user context (my pipeline, my accounts, my team's tickets),
give it a scope other than
unscoped. A sales rep should see pipeline but only their own. - Does computing it leak something the value alone does not? Aggregate marketing spend is less sensitive than per-campaign spend. Aggregate CAC is less sensitive than CAC broken down by channel. Choose the aggregation that provides insight without exposing competitive detail.
Integration Patterns
GASP tags are metadata. Enforcement happens in existing policy systems. Three patterns cover most implementations.
Cedar Policies
Cedar is an open-source policy language used by AWS Verified Permissions and referenced in the OSSA governance schema. Express GASP classifications as resource attributes:
// Principal: User with department and clearance attributes
// Resource: GASP metric with sensitivityTier and accessScope
permit (
principal in Department::"Finance",
action == Action::"query",
resource
)
when {
resource has sensitivityTier &&
resource.sensitivityTier in ["public", "internal", "confidential", "restricted"]
};
// Department managers see internal + confidential for their dept
permit (
principal,
action == Action::"query",
resource
)
when {
principal has role && principal.role == "manager" &&
resource.sensitivityTier in ["internal", "confidential"] &&
(resource.accessScope == "unscoped" ||
resource.department == principal.department)
};
// Everyone sees internal + unscoped
permit (
principal,
action == Action::"query",
resource
)
when {
resource.sensitivityTier == "internal" &&
resource.accessScope == "unscoped"
};
// Restricted tier: explicit allowlist only
forbid (
principal,
action == Action::"query",
resource
)
unless {
resource.sensitivityTier != "restricted" ||
principal in Group::"FinanceLeadership"
}; Open Policy Agent (Rego)
OPA is the enterprise default for policy-as-code. The same GASP classifications expressed in Rego:
package gasp.authz
import rego.v1
default allow := false
# Everyone sees internal + unscoped metrics
allow if {
input.resource.sensitivityTier == "internal"
input.resource.accessScope == "unscoped"
}
# Department managers see confidential within their department
allow if {
input.principal.role == "manager"
input.resource.sensitivityTier in {"internal", "confidential"}
manager_can_see_scope
}
manager_can_see_scope if input.resource.accessScope == "unscoped"
manager_can_see_scope if {
input.resource.accessScope == "department-scoped"
input.resource.department == input.principal.department
}
manager_can_see_scope if {
input.resource.accessScope == "owner-scoped"
input.resource.owner == input.principal.user_id
}
# Restricted requires explicit finance leadership membership
allow if {
input.resource.sensitivityTier == "restricted"
"finance_leadership" in input.principal.groups
} Warehouse Row-Level Security
The enforcement actually applies to data, not metadata. Snowflake, BigQuery and Postgres all support row-level security where the user's identity filters the result set. Use GASP classifications to generate these policies programmatically.
-- Snowflake: owner-scoped pipeline visibility
-- "Owner" can be an individual rep, a territory, a book or a segment.
-- Interpretation lives in the ownership_assignments table, not in GASP.
CREATE ROW ACCESS POLICY pipeline_owner_policy AS (owner_id VARCHAR)
RETURNS BOOLEAN ->
CASE
WHEN CURRENT_ROLE() IN ('SYSADMIN', 'FINANCE_LEADERSHIP') THEN TRUE
WHEN CURRENT_USER() IN (
SELECT user_id FROM governance.ownership_assignments
WHERE assigned_owner_id = owner_id
) THEN TRUE
ELSE FALSE
END;
ALTER TABLE fct_opportunities
ADD ROW ACCESS POLICY pipeline_owner_policy ON (owner_id);
-- BigQuery: restricted-tier column masking via data policy
CREATE OR REPLACE TABLE company_metrics OPTIONS (
policy_tags_on_columns = [
('burn_rate', 'projects/acme/locations/us/taxonomies/gasp/policyTags/restricted'),
('operating_margin', 'projects/acme/locations/us/taxonomies/gasp/policyTags/restricted')
]
); MCP OAuth Scopes
When an agent calls the GASP MCP server it passes a user token. Map GASP sensitivity tiers to OAuth scopes to gate which MCP tools the agent can invoke on the user's behalf.
// OAuth scope mapping for GASP MCP tools
{
"scopes": {
"gasp:read:public": ["lookup_metric", "search", "get_relationships"],
"gasp:read:internal": ["list_metrics", "get_data_requirements"],
"gasp:read:confidential": ["generate_query"],
"gasp:read:restricted": ["validate_adapter"]
},
"role_scopes": {
"ic": ["gasp:read:public", "gasp:read:internal"],
"manager": ["gasp:read:public", "gasp:read:internal", "gasp:read:confidential"],
"exec": ["gasp:read:public", "gasp:read:internal",
"gasp:read:confidential", "gasp:read:restricted"]
}
} Agent Integration Patterns
The protocols covered above handle single-user data access. Agentic systems add a second identity layer: agents act on behalf of users but have their own permissions too. This is where OSSA (agent contracts) and A2A (inter-agent communication) intersect with GASP.
The Two-Ceiling Model
The single most common mistake in agentic systems is letting agents run with service accounts that have more access than the user who invoked them. Effective access is the intersection of three ceilings:
- Agent clearance comes from the OSSA manifest (
authorization.clearance_level) - User clearance comes from your identity provider (Okta, Google Workspace, Azure AD)
- Metric sensitivity comes from GASP (
accessControl.classifications)
The warehouse enforces the intersection via row-level security and column masking. Neither the agent nor the application makes this decision. They describe constraints. The enforcement layer computes the answer.
Token Propagation (A2A)
When a coordinator agent calls a specialist via A2A, the user's token must propagate through the call chain. If the coordinator uses its own service token, privilege escalates silently. The pattern is OAuth 2 Token Exchange (RFC 8693):
// Coordinator receives a request with the user's token
// It does NOT use its own service token to call specialists
// 1. Coordinator exchanges the user token for a delegated token
// scoped to the specialist audience
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&audience=https://agents.acme.com/retention-specialist
&actor_token=
&actor_token_type=urn:ietf:params:oauth:token-type:access_token
&scope=gasp:read:confidential
// 2. Coordinator calls the specialist with the delegated token
POST https://agents.acme.com/retention-specialist/message:send
Authorization: Bearer
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": "req-coord-001",
"method": "SendMessage",
"params": { "message": { "role": "user", "parts": [...] } }
}
// The specialist now executes warehouse queries with the ORIGINAL user's
// identity. If the user cannot see restricted metrics, neither can the
// specialist acting on their behalf. No privilege escalation via chaining. Human Review for Restricted Access (OSSA)
OSSA's cognition.governance.human_review_triggers lets you require human approval before an agent accesses sensitive resources.
This is a cheap safety valve for GASP's restricted tier:
# In the agent's .ossa.yaml manifest
apiVersion: ossa/v0.5.0
kind: Agent
metadata:
name: gasp-retention-analyst
cognition:
governance:
human_review_triggers:
- trigger: tool_access
condition: resource.gasp_sensitivity == "restricted"
reviewer: finance_leadership
sla_hours: 4
- trigger: data_export
condition: result.record_count > 1000
reviewer: data_governance
audit_required: true
governance:
authorization:
clearance_level: 3 # can HANDLE up to confidential without review
tool_permissions:
- tool: generate_query
access: read
constraints:
- "resource.gasp_sensitivity != 'restricted' OR approved_via_review"
When the retention agent traces NRR down to Burn Rate (a restricted metric), the OSSA runtime pauses the agent
and requests approval from Finance Leadership. The user sees "waiting for review" rather than getting the number silently.
This converts the GASP classification into an enforced workflow without writing custom policy code.
The Audit Tuple Requirement
Traditional warehouse audit logs capture one identity: whoever ran the query. In agentic systems you need three: the user who asked, the agent that ran and the session that connected them. Without all three your audit trail is broken the first time an agent does something unexpected.
-- Extend warehouse queries to carry the full identity tuple
-- Snowflake query tag example
ALTER SESSION SET QUERY_TAG = OBJECT_CONSTRUCT(
'user_id', 'user-sarah-cfo',
'agent_id', 'gasp-retention-analyst@1.0.0',
'session_id', 'a2a-ctx-abc-123',
'parent_task', 'task-coord-001',
'gasp_metric', 'nrr',
'tier', 'confidential'
)::STRING;
-- Then correlate across four log sources:
-- 1. Identity provider (user authentication)
-- 2. A2A message log (agent invocations)
-- 3. MCP server log (tool calls)
-- 4. Warehouse query log (actual data access)
-- Joined on session_id, you get the complete chain from
-- "user Sarah asked a question" to "specifically these rows were read". Neither OSSA nor A2A standardize this audit format yet. GASP's opinion: always propagate the tuple, always join across the four logs, always retain for the period your compliance framework requires (typically 90-365 days).
Who Does What
| Concern | Owner | Where it is configured |
|---|---|---|
| Metric sensitivity classification | GASP | accessControl.classifications in ontology |
| Agent clearance ceiling | OSSA | authorization.clearance_level in agent manifest |
| User identity and role | Identity provider | Okta, Google Workspace, Azure AD |
| Token propagation between agents | A2A | OAuth 2 Token Exchange (RFC 8693) |
| Human-in-the-loop gates | OSSA | cognition.governance.human_review_triggers |
| Policy evaluation | Cedar / OPA | Policy-as-code referencing GASP tier tags |
| Row-level and column enforcement | Warehouse | Snowflake RLS, BigQuery policy tags, Postgres RLS |
| Audit logging | Identity + A2A + MCP + Warehouse | Propagate (user, agent, session, task) tuple through all four |
Common Dilemmas
The Support Manager and CAC
A Support Manager wants to understand CAC to argue that reducing support friction lowers churn and improves unit economics.
GASP tags CAC as confidential and unscoped. Should they see it?
Recommended resolution: Yes, in aggregate. A Support Manager should see company-level CAC as part of operating context. They should not see CAC broken down by channel, by quarter or by campaign. Those finer-grained views are where competitive and strategic sensitivity lives. The classification is a starting point, not a veto.
Board Prep Agent and Restricted Metrics
The board deck needs burn rate, runway and operating margin. All are restricted.
Should an automated agent be able to pull these?
Recommended resolution: Yes, via an explicit service identity with elevated scope, not via delegation from the user who invoked it. The agent runs as a governance-approved service account with its own audit trail. The CFO invokes the agent, but the agent's permissions come from its own identity, not the CFO's. Every invocation is logged. This is the same pattern as a CI pipeline with elevated deployment permissions.
Cross-Functional Dashboards
A company-wide health dashboard shows NRR, Gross Margin and Employee NPS. Three different tiers, three different intended audiences. How should this be governed?
Recommended resolution: The dashboard renders what each viewer is allowed to see. A department manager sees NRR and Gross Margin but the eNPS panel is masked or hidden. The dashboard is static code; the filtering happens at the warehouse layer per the row-level policies above. No special dashboard-level permission logic.
What about agents that call other agents?
A coordinator agent invokes a Finance specialist agent to compute Rule of 40. The coordinator was invoked by a Sales Manager. Whose permissions apply?
Recommended resolution: Always the originating human identity. Protocols like A2A propagate the caller token. The Finance specialist executes the query with the Sales Manager's credentials, which hit the warehouse's row-level policies. If the Sales Manager cannot see restricted metrics, neither can any agent acting on their behalf. Agents do not accumulate privilege through chaining.
What GASP Does Not Do
GASP is a standard, not a runtime. Specifically:
- GASP does not enforce permissions. Enforcement belongs in Cedar, OPA, your identity provider or your warehouse.
- GASP does not define user roles. Your organization designs its own role hierarchy. GASP just gives your roles a vocabulary to reference.
- GASP does not audit access. Audit logging happens where queries run (the warehouse) and where tokens are issued (the identity layer).
- GASP does not replace your data governance program. It complements SOC2, ISO 27001 and existing compliance frameworks.
The value is in the shared vocabulary. When every metric has a stable ID, a recommended tier and a recommended scope, policies across tools become portable. A Cedar policy written against GASP metric IDs works whether your warehouse is Snowflake or BigQuery, and whether your identity provider is Okta or Google Workspace.