Access Policy Linter
governance.policy_lint
Overview
You send an access-policy document — rules of subjects, actions, resources, optional conditions, and the algorithm that combines them — and we report where it contradicts itself. Ten findings, each with a fixed severity: opposite effects on a shared tuple, a rule an earlier rule makes unreachable, overlapping conditions, rules that can never fire, wildcards broader than the profile you declared, and references to subjects, actions or resources your dictionary does not define. Findings carry rule identifiers and field paths and never the subject, action, resource or condition value that triggered them, because a lint report gets pasted into tickets. We answer nothing about any single request: no decision comes out of here, and a clean lint predicts nothing about what an evaluator will allow.
An evaluator tells you what happens to one request; it cannot tell you that rule 7 is unreachable because rule 3 already covers it. Shadowing, contradiction and over-broad wildcards are properties of the whole rule set under a precedence order, and you want them before the policy ships, not after an audit finds them.
Reliability
Fully deterministic: the same document gives byte-identical findings, and the ordering is a total one, so nothing shifts between runs. The combining algorithm changes which findings are meaningful — under FIRST_APPLICABLE array order is the precedence, so an earlier rule that covers a later one makes it unreachable, while under DENY_OVERRIDES and ALLOW_OVERRIDES every rule is evaluated and the same pair is a conflict or a duplicate instead. Condition analysis is bounded, not a solver: it intersects numeric ranges and compares value sets over one attribute path, treats contains, starts_with and ends_with as opaque, and any pair it cannot settle produces no finding at all and is counted in undecidedConditionPairs — we would rather publish the size of what we did not decide than dress a guess up as a finding. The operator names we accept are exactly the fourteen an evaluator accepts, held there by a test that compares the two sets, so a document that lints clean here needs no operator rewritten before it is evaluated. Refusals, and what to do about each. GOV_LINT_INPUT_TOO_LARGE means the declared document is past the 65,536-byte inline limit, so split the rule set or shorten your tokens. GOV_LINT_POLICY_INVALID means a field is the wrong shape — a missing effect, a non-string token, a control character in a token, an unknown severityFloor, combiningAlgorithm or defaultDecision member — and the message names the exact path to fix. GOV_LINT_FIELD_UNKNOWN means a property we do not declare turned up somewhere in the request, which we refuse rather than ignore so a stray key can never carry a credential in, so delete it. GOV_LINT_RULE_ID_DUPLICATE means two rules share an id, which would make every finding about them ambiguous, so make the ids unique. GOV_LINT_RULE_LIMIT_EXCEEDED means more than 200 rules arrived, so lint the document in parts. GOV_LINT_TUPLE_LIMIT_EXCEEDED means one rule declares more than 32 tokens in a position, or more than 32768 tuples in total, so split that rule. GOV_LINT_CONDITION_DEPTH_EXCEEDED means a condition nests deeper than 8 levels, so flatten it or express it as a second rule. GOV_LINT_CONDITION_LEAF_LIMIT_EXCEEDED means one condition carries more than 32 comparisons, so split the rule. GOV_LINT_OPERATOR_UNSUPPORTED means an operator name is not one of the fourteen, so correct the name — we never accept a regular expression, a glob or a match expression as one. GOV_LINT_PROFILE_UNSUPPORTED means the profile is not one of the four, so pick the one your review actually applies. GOV_LINT_CHECK_UNSUPPORTED means checks names a code outside the ten in gov-lint-1, so drop it or fix the spelling. GOV_LINT_CATALOGUE_VERSION_UNSUPPORTED means catalogueVersion is not gov-lint-1; a new catalogue will always be an explicit opt-in rather than a silent upgrade under you. GOV_LINT_DICTIONARY_LIMIT_EXCEEDED means a dictionary list is longer than 500 subjects, 500 resources or 200 actions, so trim it to the vocabulary this policy really uses. GOV_LINT_WORK_REFUSED means the tuple count squared went past 25000000 comparisons, and the refusal reports both the projection and the budget, so reduce the tokens per rule or lint fewer rules per call.
Context footprint
Findings plus counters, capped by maxIssues. Your policy is never echoed back, so the response stays small no matter how large the document. A successful call costs 1 execution credit.
When to use it
When you have a rule set and want its contradictions, unreachable rules and over-broad grants named with rule ids and field paths.
When you need to know whether one particular request is allowed, or when your policy is a role-to-permission grant table rather than a subject/action/resource rule set.
Scope
Non-goals
- Does not decide whether any request is permitted — that is identity.evaluate_abac and identity.evaluate_rbac, and nothing here is an authorization answer
- Does not store, version, publish or distribute your policy
- Does not accept a subject, an environment or any request context
- Does not accept the role-grant table shape that identity.evaluate_rbac takes
- Does not quote a subject, action, resource or condition value back to you
- Does not certify compliance with any regulation or framework
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill only accepts structured technical values such as numbers, booleans, enums, timestamps, or identifiers. It does not process free-form text, files, or documents.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response does not contain submitted content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| policy | object | Yes | Closed access-policy document with 1 to 200 rules in precedence order plus combining algorithm and default decision. |
| dictionary | object | No | Optional vocabulary the policy may reference; omit a list to switch that undefined-reference check off rather than pass it. |
| options | object | Yes | Required lint options naming the wildcard profile plus optional check subset, severity floor, issue cap, and catalogue version. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| issues | array | Yes | Findings at or above the severity floor, ordered by rule index then code then related rule. |
| summary | object | Yes | Closed lint counters for rules, tuples compared, issues by severity, suppressed findings, combining algorithm, profile, catalogue, and truncation. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"governance.policy_lint","input":{"policy":{"rules":[{"id":"allow-finance-read","effect":"ALLOW","subjects":["group:finance"],"actions":["read"],"resources":["ledger:2026"]}]},"options":{"profile":"STRICT_NO_WILDCARD"}}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("governance.policy_lint", {"policy":{"rules":[{"id":"allow-finance-read","effect":"ALLOW","subjects":["group:finance"],"actions":["read"],"resources":["ledger:2026"]},{"id":"deny-finance-read","effect":"DENY","subjects":["group:finance"],"actions":["read"],"resources":["ledger:2026"]}],"defaultDecision":"DENY","combiningAlgorithm":"DENY_OVERRIDES"},"options":{"profile":"STRICT_NO_WILDCARD"}});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| GOV_LINT_INPUT_TOO_LARGE | 429 | No | A size, quota, rate, or time limit was exceeded. |
| GOV_LINT_POLICY_INVALID | 400 | No | The request payload failed this skill's input validation. |
| GOV_LINT_FIELD_UNKNOWN | 400 | No | The request payload failed this skill's input validation. |
| GOV_LINT_RULE_ID_DUPLICATE | 400 | No | The request payload failed this skill's input validation. |
| GOV_LINT_RULE_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| GOV_LINT_TUPLE_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| GOV_LINT_CONDITION_DEPTH_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| GOV_LINT_CONDITION_LEAF_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| GOV_LINT_OPERATOR_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| GOV_LINT_PROFILE_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| GOV_LINT_CHECK_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| GOV_LINT_CATALOGUE_VERSION_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| GOV_LINT_DICTIONARY_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| GOV_LINT_WORK_REFUSED | 429 | No | A size, quota, rate, or time limit was exceeded. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not decide whether any request is permitted — that is identity.evaluate_abac and identity.evaluate_rbac, and nothing here is an authorization answer
- Does not store, version, publish or distribute your policy
- Does not accept a subject, an environment or any request context
- Does not accept the role-grant table shape that identity.evaluate_rbac takes
- Does not quote a subject, action, resource or condition value back to you
- Does not certify compliance with any regulation or framework
- Deciding a live request — send that to an evaluator
- Linting a role-to-permission grant table, which is a different document shape
- Looking for a guarantee that a clean document will allow what you expect
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "governance.policy_lint",
"input": {
"policy": {
"rules": [
{
"id": "allow-finance-read",
"effect": "ALLOW",
"subjects": [
"group:finance"
],
"actions": [
"read"
],
"resources": [
"ledger:2026"
]
},
{
"id": "deny-finance-read",
"effect": "DENY",
"subjects": [
"group:finance"
],
"actions": [
"read"
],
"resources": [
"ledger:2026"
]
}
],
"defaultDecision": "DENY",
"combiningAlgorithm": "DENY_OVERRIDES"
},
"options": {
"profile": "STRICT_NO_WILDCARD"
}
}
}Examples
Allow and deny on the same tuple under deny-overrides
{
"policy": {
"rules": [
{
"id": "allow-finance-read",
"effect": "ALLOW",
"subjects": [
"group:finance"
],
"actions": [
"read"
],
"resources": [
"ledger:2026"
]
},
{
"id": "deny-finance-read",
"effect": "DENY",
"subjects": [
"group:finance"
],
"actions": [
"read"
],
"resources": [
"ledger:2026"
]
}
],
"defaultDecision": "DENY",
"combiningAlgorithm": "DENY_OVERRIDES"
},
"options": {
"profile": "STRICT_NO_WILDCARD"
}
}{
"issues": [
{
"code": "POLICY_EFFECT_CONFLICT",
"severity": "ERROR",
"ruleId": "deny-finance-read",
"relatedRuleId": "allow-finance-read",
"fieldPath": "policy.rules[1].effect",
"overlapKind": "EXACT"
}
],
"summary": {
"ruleCount": 2,
"tupleTotal": 2,
"tuplePairsCompared": 1,
"issueCount": 1,
"errorCount": 1,
"warningCount": 0,
"infoCount": 0,
"suppressedBelowFloor": 0,
"undecidedConditionPairs": 0,
"combiningAlgorithm": "DENY_OVERRIDES",
"profile": "STRICT_NO_WILDCARD",
"catalogueVersion": "gov-lint-1",
"truncated": false
}
}Wildcard subject makes a later rule unreachable under first-applicable
{
"policy": {
"rules": [
{
"id": "allow-any-read",
"effect": "ALLOW",
"subjects": [
"*"
],
"actions": [
"read"
],
"resources": [
"ledger:2026"
]
},
{
"id": "allow-audit-read",
"effect": "ALLOW",
"subjects": [
"group:audit"
],
"actions": [
"read"
],
"resources": [
"ledger:2026"
]
}
],
"combiningAlgorithm": "FIRST_APPLICABLE"
},
"options": {
"profile": "WILDCARD_ALLOWED",
"maxIssues": 50,
"severityFloor": "INFO"
}
}{
"issues": [
{
"code": "POLICY_RULE_SHADOWED",
"severity": "WARNING",
"ruleId": "allow-audit-read",
"relatedRuleId": "allow-any-read",
"fieldPath": "policy.rules[1]",
"overlapKind": "SUBJECT_WILDCARD"
}
],
"summary": {
"ruleCount": 2,
"tupleTotal": 2,
"tuplePairsCompared": 1,
"issueCount": 1,
"errorCount": 0,
"warningCount": 1,
"infoCount": 0,
"suppressedBelowFloor": 0,
"undecidedConditionPairs": 0,
"combiningAlgorithm": "FIRST_APPLICABLE",
"profile": "WILDCARD_ALLOWED",
"catalogueVersion": "gov-lint-1",
"truncated": false
}
}Recommended use cases
- Review a rule set before it ships, and catch a rule an earlier one already covers
- Prove a policy carries no allow/deny contradiction on the same subject, action and resource
- Hold a policy to a wildcard profile in review, so a broad grant is a finding rather than a habit
Avoid for
- Deciding a live request — send that to an evaluator
- Linting a role-to-permission grant table, which is a different document shape
- Looking for a guarantee that a clean document will allow what you expect
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.