Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.12"
- name: Verify release allowlist and hashes
Expand Down
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,11 @@ __pycache__/
*.py[cod]
.pytest_cache/
.keel/
.DS_Store
.env*
.worktrees/
.claude/
.idea/
.vscode/
htmlcov/
.coverage
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ helpers are local, read-only analysis or schema validation.

## Provenance

`SOURCE.json` identifies the exact reviewed source revision from which this bundle was exported. This
repository has orphan release history: it intentionally excludes internal specifications, production
workflows, private repository history, and unrelated tools.
`SOURCE.json` identifies this public bundle version and the files maintained only by the publication
layer. This repository intentionally excludes internal specifications, production workflows, private
repository history, and unrelated tools.

Apache-2.0. See [`LICENSE`](LICENSE).
24 changes: 12 additions & 12 deletions SHA256SUMS
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
c25cb1fbeee655efde2fa1b12af630e6cf5657a5717f2ce923affbbbf7008da5 .github/workflows/ci.yml
5d87b13a525cd88e59552924d2b70e1ac4f05408ed88c1f09f2ad073657d938c .gitignore
b97a81728fc0d91f066bb92f27695c81f9bb25bd88d93bf470230132f33d0799 .github/workflows/ci.yml
b63eff93f485d563b24680091496a39b3a62ecf3c8bb229cfc4eecd80c7e2262 .gitignore
3c0871d9b30c3e9647426b5b756be7dbfc5eee8728e6f0e0c371385de7c5f8f7 LICENSE
5fa2ec165604ae3612566968097f69d354e1722ccd5e4272793eb533a8097d23 README.md
2bb3f3751259b6f45ef5a899491c156c2d73e953d08919af278f056a12cc98fe SOURCE.json
9eb38e845d150019366de6cf74cf5c8b7e7a321e795f13505fb3e0788b043594 keel-policy/SKILL.md
e73eba8da927ff48314ea4af4c3f24c19cd3c91e1df37d7165e95275395811bb README.md
e6c3b2cda2b50d0c6be2fa6e20c7edd0126419bb9c0d16d12151632b742b0c43 SOURCE.json
429874faf43769ec2cba325883750fca639b90744b2afc5ea28d1813f5999707 keel-policy/SKILL.md
6b449605b6b4fe6eb84f5b12befb90a57b7841d2973bbf9341cf40866e7dd81a keel-policy/examples/README.md
9a13b533704c1a37734bd6c28fc202c7acdaa8335773d55790dac50a693f8b2b keel-policy/examples/stripe-refund-approval-enterprise.json
2a52514e8db0901ab73e59b924f195008541d0069ebad9b514bd373cb905e2d2 keel-policy/examples/stripe-refund-approval.json
4ba18a72ba701fd0f21c8c341676bbbe823728fa7b286c97109df02b81b47936 keel-policy/reference/enforceability-report.schema.json
7a1b7880948c4898dcd4677e8f3d501566aa00ba78830d0cc194bc607dfa34e9 keel-policy/reference/field-provenance.json
7bbe841ca93b81021ddeb58a6354f9195195f59c5acde45b5690417dcc87d9d7 keel-policy/reference/fields.md
567b0ace9b1769037a105628a3fa6b48fa1440d0d50cea3a4f865935a47d6d9d keel-policy/reference/field-provenance.json
5015ccf898060b632aa5dbb56eceea9787debfbabde05f0e6fafcddf76c0438b keel-policy/reference/fields.md
4be41a65667952df8105e627026a921b66fc4fef19761242efabe69df0bd47cf keel-policy/reference/policy-document.schema.json
83fd6d47ded91e6b159ca3f2be5c486eb05bad7f289a591654f17141b980de99 keel-policy/scripts/validate_enforceability_report.py
856809b479fa506299896ed9d4288fad5e20c919eaba0a5a36a9027347a234ed keel-policy/scripts/validate_enforceability_report.py
322b430367e859dae341e9c225be34c6c5272c481074e7b1ae2242db527e1cbe keel-policy/tests/test_validate_enforceability_report.py
f2b711de6770931e5286759c94e77b97c17d35db8d3d189c22b2f5a5e7b9bd2e keel-setup/SKILL.md
2b9c2490e921e83b83372a09e422a30f0f7ee338a2809b8e07edbd381d9eb650 keel-setup/reference/coverage.schema.json
5e9c31ef991ec9c4fc2195b4907bacbd9383d47453c6a48a0da90fb158256541 keel-setup/reference/setup-state.schema.json
b0fe7edfc7b0780853c04968ca1caf8cb94731c38e8c56981987c8e334d5c951 keel-setup/reference/unified-execute-request.contract.json
7ef2361f3f10601f9a4d3681d04c8538836c37448787262b26e82bf18bcbbc70 keel-setup/reference/unified-execute-request.contract.json
fa9e64c1fb3e6851098350d6a83c8cb185c9dea075082b12d7816b166a64f2ad keel-setup/scripts/inventory.py
2786d8a6e89a36cced8af0452f2721ea4fb1b6c7b59a65e3f1ea0ac5f2837b78 keel-setup/scripts/setup_state.py
17dcf121577bd0d01d1912aabd59abb91e0c119ddccb20b3f500b662aedb3174 keel-setup/scripts/verify_execute.py
a891810ef4370ab3107627c972cfd66f18fa9554f5f2ff857312f0756c15a4f7 keel-setup/tests/test_execute_request_contract.py
3e952c3b7ebf382c39140a6a1a5e3bff9458732fe1a0ca9ea63b44206814bb80 keel-setup/tests/test_inventory.py
25148cb0368c3720b424573f38baf31e2baef2fb1aad9000f5f3eae3453ea374 keel-setup/tests/test_setup_state.py
2ef380a77b3eb9583e71fa99815233f7ffb6a9ab0986d82387853ea26b6117e8 keel-setup/tests/test_verify_execute.py
16b9e69e558e73eb7b32132b3382d5a7621573057fea02b101482fd9311c1bf3 scripts/check_execute_contract.py
4727d8c69971be67eeabf3662fdce88fb987c77fe9f88b507efa4a5d726ddd5e scripts/check_release_bundle.py
ba172f0dcadea1b19f3279e3c3151dabc1719cdc65ddce77eb5c74550c101ce0 scripts/check_execute_contract.py
2f649b4d03de6784955357138cf411c3f98e05bfcaf73a172b0da887ea3b80ed scripts/check_release_bundle.py
e0195765e2b9a31938a455fc81f93b02127f7b769948607b19a821f2bd82b2d1 shared/CONSTITUTION.md
6510e9f92c2a8fa3c40a2ca99eca06e2acaad146101c9526e06bb53f18085421 shared/feedback-report.schema.json
92b11e2380bb588b2a00be8d5eae9665b4667747fd044278a65ec2a698de7889 shared/feedback-report.template.md
82ffbacd09255c9311813cec37bd75d745df16ec21f457542ec5cd3b30739014 shared/scripts/schema_validation.py
7fef855399e1c4b7372d235c1d0fa3567583c4d428e8ee9f5cf122f596dd11e7 shared/scripts/test_schema_validation.py
c7ad8aa74f5325b8bad0a44c979e4089cf3f57da787c209ed5d9cb71c7aaac1d shared/scripts/test_validate_feedback_report.py
a0944ebb5705043c42ae83ce5b7948874ba8150532fb14da5f20da796b7925f1 shared/scripts/validate_feedback_report.py
e0eb6c5d6450733df516614b0665e6cffc68491b8b766a570463c697e5d73de2 tools/public_surface.json
a5e89769ef4c5adb857019dd23ba753f2ecf24d58443d5660e8ec4aac6974e2b tools/public_surface.json
14 changes: 11 additions & 3 deletions SOURCE.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"bundle_schema_version": "keel.public_setup_bundle.v1",
"public_release_version": "2026-08-30.2",
"product_source_sha256": "ca6fcb9e63cd7fa42ccf970ee208155352ebda48cb40595a8e300a0861a9cd34",
"included_roots": [
"keel-policy",
"keel-setup",
Expand All @@ -8,7 +10,13 @@
"included_files": [
"tools/public_surface.json"
],
"source_commit": "ef6b92880f0729c336b3fad85e256135c91968da",
"source_merge_commit": "165c5f308bb339d8024f9fb1d66956e0940db2e7",
"source_repository": "keelapi/keel-skills"
"publication_layer_files": [
".github/workflows/ci.yml",
".gitignore",
"README.md",
"SHA256SUMS",
"SOURCE.json",
"scripts/check_execute_contract.py",
"scripts/check_release_bundle.py"
]
}
72 changes: 57 additions & 15 deletions keel-policy/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: keel-policy
description: Author, audit, or assess enforceability of a Keel governance policy without activating it. Use for spending limits, approvals, blocked actions, model restrictions, policy safety reviews, or determining whether Keel has trustworthy runtime facts for a requested control. Produces a validated policy proposal and bounded enforceability report.
description: Author, audit, or assess enforceability of a Keel governance policy for a basic or full authoring profile without activating it. Use for spending limits, approvals, blocked actions, model restrictions, policy safety reviews, or determining whether Keel has trustworthy runtime facts for a requested control. Produces a validated policy proposal and bounded enforceability report.
---

# Authoring Keel policies
Expand Down Expand Up @@ -99,7 +99,12 @@ The validator reads `reference/field-provenance.json`, a release-pinned machine
set and provenance values are checked against the published catalog. A report cannot promote a caller-
or connector-asserted field to `trusted` by relabelling it.

1. **Gather evidence, opportunistically.** If the application's code or tool definitions are at
1. **Get the authoring profile.** Accept `basic` or `full` as an input. If it was not supplied,
ask once which authoring profile the dashboard shows, and wait for the answer before drafting.
Do not infer it from a plan name, fetch entitlements, request a credential, or call Keel to
discover it. This skill holds no Keel credential.

2. **Gather evidence, opportunistically.** If the application's code or tool definitions are at
hand, read them: which connectors and tools the agent actually calls, what the tool names
are, which ones move money or touch customers. This is evidence-gathering, not a
prerequisite. If there is no application to inspect — an empty repo, a policy written before
Expand All @@ -110,31 +115,54 @@ or connector-asserted field to `trusted` by relabelling it.

Code tells you which actions exist. It never tells you which ones the user wants governed.

2. **Check expressibility early.** Before interviewing in depth about a restriction, check
whether the field vocabulary in `reference/fields.md` can express it at all. If it cannot,
say so immediately rather than after a long interview.
3. **Check expressibility early.** Before interviewing in depth about a restriction, check
whether the selected profile's field vocabulary can express it at all. For `basic`, use the
whitelist below. For `full`, use `reference/fields.md`. If it cannot, say so immediately
rather than after a long interview.

**Never approximate an unsupported restriction with a different field.** There is no
file-path or repository-path field, so "may edit `src/` but not `.github/workflows/`" is not
expressible in a Keel policy. Deciding that `connector.tool_name` is close enough produces a
syntactically valid document that does not do what the user asked and cannot be observed
failing. Say plainly that the restriction is not expressible, and what is.

3. **Interview only where the answer changes the artifact.** Resolve material ambiguity, once —
4. **Interview only where the answer changes the artifact.** Resolve material ambiguity, once —
see *What requires a user decision*. This is not a questionnaire to complete before drafting;
it is a set of questions worth asking only when the answer changes what you write.

4. **Read the schema.** `reference/policy-document.schema.json` is the authoritative shape,
generated directly from Keel's own model. `reference/fields.md` lists every field you may
reference, with its type and provenance.
5. **Read the schema.** `reference/policy-document.schema.json` is the authoritative shape,
generated directly from Keel's own model. For `full`, `reference/fields.md` lists every field
you may reference, with its type and provenance.

5. **Draft, then work the checklist below.** Validate against the bundled JSON Schema before
6. **Draft, then work the checklist below.** Validate against the bundled JSON Schema before
showing anyone — it is standard Draft 2020-12, so any off-the-shelf validator works and you
need no access to Keel.

6. **Read it back in your own words, then hand it over.** See *Presentation and handoff*.
7. **Read it back in your own words, then hand it over.** See *Presentation and handoff*.

Never activate a policy yourself. Step 7 ends in a human decision, by design — see *Authority*.

### Authoring profiles

`full` uses the complete bundled schema and the authorable fields in `reference/fields.md`.

For `basic`, the document must satisfy the full schema **and** every restriction below:

Never activate a policy yourself. Step 6 ends in a human decision, by design — see *Authority*.
- at most 10 rules;
- only `eq`, `neq`, `in`, `not_in`, `gt`, `gte`, `lt`, `lte`, and `contains` operators;
- only `context.model`, `context.provider`, `context.provider_meta.region`,
`context.provider_meta.data_retention`, `context.estimated_cost_usd_micros`,
`context.prompt_token_count`, and `context.time_of_day` fields;
- each condition is one leaf, or one top-level `all`/`any` containing leaves only; no nesting
and no `not`;
- actions are limited to the schema's deny family (`deny` and `deny_if_*`),
`constrain_max_output_tokens`, and `require_human_review`; and
- when the schema's `require_attestation` property is present, its `attestor` must be exactly
`project_owner`. Its required `timeout_seconds` is still a user decision under invariant 3.

Never emit an explicit `allow` rule for `basic`. It is terminal at this authoring level and can
shadow later denies. This does not change the unmatched-action fallback: actions matching no
terminal rule are still allowed by the engine default.

## What requires a user decision

Expand Down Expand Up @@ -368,6 +396,8 @@ supposed to close.
Work this before showing anyone a draft.

- [ ] **Schema.** Validates against `reference/policy-document.schema.json`.
- [ ] **Profile.** The authoring profile is known, and the document satisfies its additional
grammar, field, action, composition, and rule-count limits.
- [ ] **Provenance.** For every rule, threshold, and parameter: name the user instruction that
authorized it, or the explicit delegation that let you choose it. Anything you cannot
attribute comes out of the JSON.
Expand All @@ -377,9 +407,9 @@ Work this before showing anyone a draft.
- [ ] **Scope.** Each rule inspected on its own; none matches a broader subject than requested.
- [ ] **Money.** Integer micros everywhere.
- [ ] **Order.** Rule order actually produces the behaviour your readback describes.
- [ ] **Fields.** Only fields from `fields.md`, and none used as a proxy for something it does
not mean. Any rule keyed on an envelope field marked ⚠ is deliberate, and the handoff says
the classification underneath it may move.
- [ ] **Fields.** Only fields allowed by the selected profile, and none used as a proxy for
something it does not mean. Any rule keyed on an envelope field marked ⚠ is deliberate,
and the handoff says the classification underneath it may move.
- [ ] **Trust.** Any threshold or gate on a `caller_asserted` field — which is every
`financial.*` envelope field — is disclosed in the handoff as constraining what the caller
declared, not what Keel measured.
Expand Down Expand Up @@ -439,6 +469,18 @@ considering, and that it is absent unless they ask for it.
Then give them the JSON to paste into the policy editor in the Keel dashboard, where Keel runs
its full validation, simulates the policy against recent real traffic, and only then saves it.

If the dashboard rejects the save with `AuthoringLevelExceeded`, use its structured fields
rather than giving a generic validation explanation:

- `authoring_level` identifies the profile whose restrictions were exceeded;
- `rule_index` is the zero-based index into `rules`; quote and explain that exact rule; and
- `upgrade_to` identifies the profile or plan that accepts the rejected construct.

Compare the indexed rule with the restrictions for `authoring_level` and name the precise
change required. Never weaken or delete a user-requested control silently. Return a revised
draft only after the human chooses between preserving the control by upgrading and changing
the policy decision to fit the current profile.

## Authority — read this before proposing anything

The agent running this skill may itself be governed by the Keel policies it is editing. That
Expand Down
2 changes: 2 additions & 0 deletions keel-policy/reference/field-provenance.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
"context._keel.request_day_of_week": "keel_derived",
"context._keel.request_hour_utc": "keel_derived",
"context._keel.verified_agent_principal_id": "keel_derived",
"context.provider_meta.data_retention": "keel_derived",
"context.provider_meta.region": "keel_derived",
"estimated_cost": "keel_derived",
"estimated_cost_usd_micros": "keel_derived",
"model": "keel_derived",
Expand Down
8 changes: 5 additions & 3 deletions keel-policy/reference/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ table below. The guidance around it is maintained by hand and does **not** regen
table — re-apply it if you regenerate.

**`CATALOG_VERSION` does not move when a field's provenance does.** Five financial fields were
demoted from `keel_derived` to `caller_asserted` in keel-api under this same version string,
and the stale table below gave no sign of it. Re-check provenance against
`app/services/policy_authoring_catalog.py` rather than trusting the version to signal drift.
demoted from `keel_derived` to `caller_asserted` under this same version string, and the stale
table below gave no sign of it. Re-check provenance against the published field-provenance map
for your release rather than trusting the version to signal drift.

Use **only** fields marked authorable = yes. Fields with provenance `caller_asserted` are
supplied by the caller and are not trustworthy evidence — Keel rejects allow/deny rules that
Expand Down Expand Up @@ -163,6 +163,8 @@ before relying on it; see *Still moving underneath* above. Provenance `caller_as
| `context._keel.request_day_of_week` | integer | yes | keel_derived |
| `context._keel.request_hour_utc` | integer | yes | keel_derived |
| `context._keel.verified_agent_principal_id` | string | yes | keel_derived |
| `context.provider_meta.data_retention` | string | yes | keel_derived |
| `context.provider_meta.region` | string | yes | keel_derived |
| `estimated_cost` | number | yes | keel_derived |
| `estimated_cost_usd_micros` | integer | yes | keel_derived |
| `model` | string | yes | keel_derived |
Expand Down
3 changes: 1 addition & 2 deletions keel-policy/scripts/validate_enforceability_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ def validate(
failures.append(f"prohibited authority or certified-contract field: {key}")

public_fields = set(manifest.get("fields", {}).get("PUBLIC", []))
pending_fields = set(manifest.get("fields", {}).get("PENDING_action_envelope", [])) | set(manifest.get("fields", {}).get("PENDING_other", []))
provenance = provenance_artifact.get("fields", {}) if isinstance(provenance_artifact, dict) else {}
if provenance_artifact.get("schema_version") != "1.0" or set(provenance) != public_fields:
failures.append("pinned field-provenance artifact does not exactly cover the published field set")
Expand All @@ -75,7 +74,7 @@ def validate(
by_field.setdefault(field, []).append((index, item))
status = item.get("status")
pinned_provenance = provenance.get(field) if isinstance(field, str) else None
if field in pending_fields or (isinstance(field, str) and field not in public_fields):
if isinstance(field, str) and field not in public_fields:
if status != "unresolved":
failures.append(f"enforceability[{index}] unpublished field must be unresolved")
elif isinstance(field, str) and item.get("provenance") != pinned_provenance:
Expand Down
6 changes: 3 additions & 3 deletions keel-setup/reference/unified-execute-request.contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@
},
"schema_sha256": "67beb5283aa68267eeaaa52bfcc4e7d799fe61d5e9f194445e8230de44478213",
"source_artifact": "docs/public-artifacts/openapi.json",
"source_artifact_sha256": "40f9b7ef1ad7ab38409c6b97d36ec654afc94a40f98475b6cda06b331b355db9",
"source_commit": "4ce5f5def6258004861dfaeeef512e49afc87cfd",
"source_merge_commit": "c130f73d966fce6572aa5dc8ff48164b9e9dbf15",
"source_artifact_sha256": "4a0269a7d0f1ad7901cb9e9ea0549c6264700823996521fd377ad51dc18c4d46",
"source_commit": "b0103a5cbe04c5dcc913fa990313b2e2ee6cbf24",
"source_merge_commit": "f0f2f188bc39a17318683e5015c6551b238130cb",
"source_repository": "keelapi/keel-api"
}
6 changes: 3 additions & 3 deletions scripts/check_execute_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
HELPER = ROOT / "keel-setup/scripts/verify_execute.py"
EXPECTED_CONTRACT_VERSION = "keel.public_unified_execute_request_contract.v1"
EXPECTED_API_REPOSITORY = "keelapi/keel-api"
EXPECTED_API_SOURCE_COMMIT = "4ce5f5def6258004861dfaeeef512e49afc87cfd"
EXPECTED_API_MERGE_COMMIT = "c130f73d966fce6572aa5dc8ff48164b9e9dbf15"
EXPECTED_API_OPENAPI_SHA256 = "40f9b7ef1ad7ab38409c6b97d36ec654afc94a40f98475b6cda06b331b355db9"
EXPECTED_API_SOURCE_COMMIT = "b0103a5cbe04c5dcc913fa990313b2e2ee6cbf24"
EXPECTED_API_MERGE_COMMIT = "f0f2f188bc39a17318683e5015c6551b238130cb"
EXPECTED_API_OPENAPI_SHA256 = "4a0269a7d0f1ad7901cb9e9ea0549c6264700823996521fd377ad51dc18c4d46"


def _canonical_sha256(value: Any) -> str:
Expand Down
Loading