diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 138195f..8359071 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.gitignore b/.gitignore index caae7da..d3655b9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,11 @@ __pycache__/ *.py[cod] .pytest_cache/ .keel/ +.DS_Store +.env* +.worktrees/ +.claude/ +.idea/ +.vscode/ +htmlcov/ +.coverage diff --git a/README.md b/README.md index 3c88fbe..ee3d49c 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/SHA256SUMS b/SHA256SUMS index 9a8b4fa..c3da143 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,22 +1,22 @@ -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 @@ -24,8 +24,8 @@ a891810ef4370ab3107627c972cfd66f18fa9554f5f2ff857312f0756c15a4f7 keel-setup/tes 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 @@ -33,4 +33,4 @@ e0195765e2b9a31938a455fc81f93b02127f7b769948607b19a821f2bd82b2d1 shared/CONSTIT 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 diff --git a/SOURCE.json b/SOURCE.json index 9e74406..bf0fbd9 100644 --- a/SOURCE.json +++ b/SOURCE.json @@ -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", @@ -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" + ] } diff --git a/keel-policy/SKILL.md b/keel-policy/SKILL.md index 10798f0..4917317 100644 --- a/keel-policy/SKILL.md +++ b/keel-policy/SKILL.md @@ -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 @@ -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 @@ -110,9 +115,10 @@ 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 @@ -120,21 +126,43 @@ or connector-asserted field to `trusted` by relabelling it. 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 @@ -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. @@ -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. @@ -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 diff --git a/keel-policy/reference/field-provenance.json b/keel-policy/reference/field-provenance.json index eade137..7ca05be 100644 --- a/keel-policy/reference/field-provenance.json +++ b/keel-policy/reference/field-provenance.json @@ -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", diff --git a/keel-policy/reference/fields.md b/keel-policy/reference/fields.md index d29d142..c07b868 100644 --- a/keel-policy/reference/fields.md +++ b/keel-policy/reference/fields.md @@ -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 @@ -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 | diff --git a/keel-policy/scripts/validate_enforceability_report.py b/keel-policy/scripts/validate_enforceability_report.py index abbe339..764c9f5 100644 --- a/keel-policy/scripts/validate_enforceability_report.py +++ b/keel-policy/scripts/validate_enforceability_report.py @@ -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") @@ -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: diff --git a/keel-setup/reference/unified-execute-request.contract.json b/keel-setup/reference/unified-execute-request.contract.json index 24841a7..c2be6e0 100644 --- a/keel-setup/reference/unified-execute-request.contract.json +++ b/keel-setup/reference/unified-execute-request.contract.json @@ -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" } diff --git a/scripts/check_execute_contract.py b/scripts/check_execute_contract.py index 75cc198..4b9f542 100644 --- a/scripts/check_execute_contract.py +++ b/scripts/check_execute_contract.py @@ -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: diff --git a/scripts/check_release_bundle.py b/scripts/check_release_bundle.py index 7310609..5e85a26 100644 --- a/scripts/check_release_bundle.py +++ b/scripts/check_release_bundle.py @@ -11,8 +11,17 @@ ROOT = Path(__file__).resolve().parents[1] -SOURCE_COMMIT = "ef6b92880f0729c336b3fad85e256135c91968da" -SOURCE_MERGE_COMMIT = "165c5f308bb339d8024f9fb1d66956e0940db2e7" +PUBLIC_RELEASE_VERSION = "2026-08-30.2" +PRODUCT_SOURCE_SHA256 = "ca6fcb9e63cd7fa42ccf970ee208155352ebda48cb40595a8e300a0861a9cd34" +PUBLICATION_LAYER_FILES = [ + ".github/workflows/ci.yml", + ".gitignore", + "README.md", + "SHA256SUMS", + "SOURCE.json", + "scripts/check_execute_contract.py", + "scripts/check_release_bundle.py", +] EXPECTED_FILES = { ".github/workflows/ci.yml", ".gitignore", @@ -73,6 +82,16 @@ def _sha256(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() +def _product_source_sha256(exemptions: set[str]) -> str: + digest = hashlib.sha256() + paths = sorted(EXPECTED_FILES - exemptions) + for relative in paths: + digest.update(relative.encode("utf-8")) + digest.update(b"\0") + digest.update(hashlib.sha256((ROOT / relative).read_bytes()).digest()) + return digest.hexdigest() + + def _check_manifest() -> None: lines = (ROOT / "SHA256SUMS").read_text(encoding="utf-8").splitlines() paths: list[str] = [] @@ -117,15 +136,40 @@ def main() -> int: extra = sorted(actual - EXPECTED_FILES) raise ValueError(f"release allowlist mismatch: missing={missing}, extra={extra}") - source = json.loads((ROOT / "SOURCE.json").read_text(encoding="utf-8")) - if source.get("source_commit") != SOURCE_COMMIT: - raise ValueError("SOURCE.json does not pin the reviewed source commit") - if source.get("source_merge_commit") != SOURCE_MERGE_COMMIT: - raise ValueError("SOURCE.json does not pin the merged source commit") + source_raw = (ROOT / "SOURCE.json").read_text(encoding="utf-8") + source = json.loads(source_raw) + if source.get("public_release_version") != PUBLIC_RELEASE_VERSION: + raise ValueError("SOURCE.json does not identify the reviewed public release version") + if source.get("product_source_sha256") != PRODUCT_SOURCE_SHA256: + raise ValueError("SOURCE.json does not pin the reviewed public product digest") + if {"source_repository", "source_commit", "source_merge_commit"} & set(source): + raise ValueError("SOURCE.json exposes private source provenance") + private_source_name = "keel-" + "skills" + if private_source_name in source_raw: + raise ValueError("SOURCE.json names the private source repository") if source.get("included_roots") != ["keel-policy", "keel-setup", "shared"]: raise ValueError("SOURCE.json included_roots changed") if source.get("included_files") != ["tools/public_surface.json"]: raise ValueError("SOURCE.json included_files changed") + if source.get("publication_layer_files") != PUBLICATION_LAYER_FILES: + raise ValueError("SOURCE.json publication-layer exemption changed") + computed_product_digest = _product_source_sha256(set(PUBLICATION_LAYER_FILES)) + if computed_product_digest != PRODUCT_SOURCE_SHA256: + raise ValueError("public product files differ from the immutable SOURCE.json commitment") + + manifest_raw = (ROOT / "tools/public_surface.json").read_text(encoding="utf-8") + manifest = json.loads(manifest_raw) + if set(manifest) != {"_comment", "actions", "requirements", "fields", "field_adjudications"}: + raise ValueError("public surface manifest is not the positive allowlist shape") + for section in ("actions", "requirements", "fields"): + if set(manifest.get(section, {})) != {"PUBLIC"}: + raise ValueError(f"public surface {section} exposes non-public buckets") + if private_source_name in manifest_raw: + raise ValueError("public surface manifest names the private source repository") + + fields_raw = (ROOT / "keel-policy/reference/fields.md").read_text(encoding="utf-8") + if "keel-" + "api" in fields_raw or "app/" + "services/" in fields_raw: + raise ValueError("fields.md exposes a private repository or source path") readme = (ROOT / "README.md").read_text(encoding="utf-8") for marker in ("# Keel setup", "[`keel-setup`](keel-setup/SKILL.md)", "[`keel-policy`](keel-policy/SKILL.md)"): diff --git a/tools/public_surface.json b/tools/public_surface.json index db37f72..85f0be1 100644 --- a/tools/public_surface.json +++ b/tools/public_surface.json @@ -1,6 +1,5 @@ { - "_comment": "Classification of every symbol the public keel-skills artifacts may emit. UNCLASSIFIED is a failure, not a default.", - "_measured": "2026-08-18 against keel-docs content/, docs/public-artifacts/openapi.json, and all 28 unauthenticated policy template documents.", + "_comment": "Positive allowlist of every symbol the reviewed public bundle may emit. Anything absent is refused.", "actions": { "PUBLIC": [ "allow", @@ -18,24 +17,13 @@ "require_budget_envelope", "require_human_review", "throttle_if_rate_exceeds" - ], - "INTERNAL": [ - "constrain_permit_subject", - "deny_if_action_class_not_in", - "require_identity_assurance", - "require_registered_key_counter_signature" ] }, "requirements": { "PUBLIC": [ "approval_requirement", "require_attestation" - ], - "PENDING": [ - "require_co_signature", - "witness_requirement" - ], - "_note": "approval_requirement is public via docs/public-artifacts/openapi.json (10 occurrences); require_attestation is public via the unauthenticated policy templates (7 occurrences). Neither appears in keel-docs prose." + ] }, "fields": { "PUBLIC": [ @@ -86,6 +74,8 @@ "context._keel.request_day_of_week", "context._keel.request_hour_utc", "context._keel.verified_agent_principal_id", + "context.provider_meta.data_retention", + "context.provider_meta.region", "estimated_cost", "estimated_cost_usd_micros", "model", @@ -93,61 +83,108 @@ "project_id", "provider", "token_estimate" - ], - "REMOVED": [ - "context._keel.declared_intent", - "context._keel.payment_currency" - ], - "PENDING_action_envelope": [ - "context._keel.action_envelope.mapping.assurance.value", - "context._keel.action_envelope.mapping.governance_action_id.value", - "context._keel.action_envelope.mapping.governance_action_version.value", - "context._keel.action_envelope.mapping.id.value", - "context._keel.action_envelope.mapping.revision.value" - ], - "PENDING_other": [] + ] }, "field_adjudications": { - "action.attributes.trusted_facts.environment": {"decision": "PUBLIC", "rationale": "Connector-asserted preflight environment is an authorable exact-match contract."}, - "action.attributes.trusted_facts.max_uses": {"decision": "PUBLIC", "rationale": "Connector-asserted numeric use limit is an authorable preflight contract."}, - "action.attributes.trusted_facts.tool_name": {"decision": "PUBLIC", "rationale": "Connector-asserted preflight tool name is required by the shipped refund policy pattern."}, - "attrs.estimated_input_tokens": {"decision": "PUBLIC", "rationale": "Caller-asserted input-token estimate is explicitly authorable and labeled with its weaker provenance."}, - "attrs.estimated_output_tokens": {"decision": "PUBLIC", "rationale": "Caller-asserted output-token estimate is explicitly authorable and labeled with its weaker provenance."}, - "attrs.execution_mode": {"decision": "PUBLIC", "rationale": "Caller-asserted execution mode is a bounded authorable enum."}, - "attrs.model": {"decision": "PUBLIC", "rationale": "Caller-asserted model attribute is an authorable request field distinct from Keel's derived top-level model."}, - "attrs.provider": {"decision": "PUBLIC", "rationale": "Caller-asserted provider attribute is an authorable request field distinct from Keel's derived top-level provider."}, - "context._keel.action_envelope.action.access_level.value": {"decision": "PUBLIC", "rationale": "Keel-derived access classification is authorable with explicit churn guidance in fields.md."}, - "context._keel.action_envelope.action.risk_tags.value": {"decision": "PUBLIC", "rationale": "Keel-derived risk tags are authorable with explicit registry-churn guidance in fields.md."}, - "context._keel.action_envelope.authority.compute_lineage_only.value": {"decision": "PUBLIC", "rationale": "Keel-derived agent.create_child lineage classification has a single documented boolean meaning."}, - "context._keel.action_envelope.authority.is_mutation.state": {"decision": "PUBLIC", "rationale": "Availability state lets authors fail closed when authority mutation classification is unknown."}, - "context._keel.action_envelope.authority.is_mutation.value": {"decision": "PUBLIC", "rationale": "Keel-derived authority-mutation value is published together with its availability state."}, - "context._keel.action_envelope.connector.tool_schema_hash.state": {"decision": "PUBLIC", "rationale": "State-only contract lets authors require that a verified decision trace recorded a schema hash without claiming custody proof."}, - "context._keel.action_envelope.financial.amount.state": {"decision": "PUBLIC", "rationale": "Availability state is required to distinguish a real zero from an unavailable rail-native amount."}, - "context._keel.action_envelope.financial.amount.value": {"decision": "PUBLIC", "rationale": "Rail-native amount is authorable with caller-asserted provenance and its state companion."}, - "context._keel.action_envelope.financial.currency.value": {"decision": "PUBLIC", "rationale": "Currency is authorable with caller-asserted provenance and guidance not to treat it as magnitude or conversion."}, - "context._keel.action_envelope.financial.destination_digest.state": {"decision": "PUBLIC", "rationale": "Availability state is required for fail-closed destination-digest policies."}, - "context._keel.action_envelope.financial.destination_digest.value": {"decision": "PUBLIC", "rationale": "Destination digest is authorable with caller-asserted provenance and its state companion."}, - "context._keel.action_envelope.financial.moves_customer_value.state": {"decision": "PUBLIC", "rationale": "Availability state prevents an unknown value-movement classification from silently missing a rule."}, - "context._keel.action_envelope.financial.moves_customer_value.value": {"decision": "PUBLIC", "rationale": "Keel-derived value-movement classification is published with its state companion and caveat."}, - "context._keel.action_envelope.financial.operation.value": {"decision": "PUBLIC", "rationale": "Financial operation is authorable with caller-asserted catalog provenance and an open-ended MCP value-space warning."}, - "context._keel.action_envelope.pricing.estimated_cost_usd_micros.state": {"decision": "PUBLIC", "rationale": "Availability state is the fail-closed branch for surfaces Keel cannot price."}, - "context._keel.action_envelope.pricing.estimated_cost_usd_micros.value": {"decision": "PUBLIC", "rationale": "Keel-derived estimate is authorable only where pricing can be present and is paired with its state."}, - "context._keel.agent_identity_verified": {"decision": "PUBLIC", "rationale": "Keel-derived runtime-key verification boolean is an authorable identity control."}, - "context._keel.declared_intent": {"decision": "REMOVED", "rationale": "This raw caller assertion is not authorable and adds no safe policy control to the public skill."}, - "context._keel.payment_currency": {"decision": "REMOVED", "rationale": "This legacy flat alias lacks the canonical envelope family's per-leaf state semantics; authors should use financial.currency.value with the amount state guard."} - }, - "_pending_rationale": { - "context._keel.action_envelope.mapping.assurance.value": "Action Mapping assurance is declared shadow and non-authorable in the backend catalog and has no served state-F runtime; publishing it would imply an authoring contract that cannot be exercised.", - "context._keel.action_envelope.mapping.governance_action_id.value": "The governance action a mapping resolves to is a policy interpretation, not established handler semantics, and the mapping surface is not served on this revision.", - "context._keel.action_envelope.mapping.governance_action_version.value": "Pinning a governance action version is only meaningful once the mapping surface is served and its revision behaviour has executed tests.", - "context._keel.action_envelope.mapping.id.value": "Mapping identity is declared shadow and non-authorable in the backend catalog; the catalog's own warning says it is not yet resolvable at decision time.", - "context._keel.action_envelope.mapping.revision.value": "Revision pinning depends on mapping identity being resolvable, which the backend catalog states it is not yet." - }, - "forbidden_strings": [ - "Phase-0", - "A3-K", - "A3-P", - "proof-of-possession", - "buyer-principal" - ] + "action.attributes.trusted_facts.environment": { + "decision": "PUBLIC", + "rationale": "Connector-asserted preflight environment is an authorable exact-match contract." + }, + "action.attributes.trusted_facts.max_uses": { + "decision": "PUBLIC", + "rationale": "Connector-asserted numeric use limit is an authorable preflight contract." + }, + "action.attributes.trusted_facts.tool_name": { + "decision": "PUBLIC", + "rationale": "Connector-asserted preflight tool name is required by the shipped refund policy pattern." + }, + "attrs.estimated_input_tokens": { + "decision": "PUBLIC", + "rationale": "Caller-asserted input-token estimate is explicitly authorable and labeled with its weaker provenance." + }, + "attrs.estimated_output_tokens": { + "decision": "PUBLIC", + "rationale": "Caller-asserted output-token estimate is explicitly authorable and labeled with its weaker provenance." + }, + "attrs.execution_mode": { + "decision": "PUBLIC", + "rationale": "Caller-asserted execution mode is a bounded authorable enum." + }, + "attrs.model": { + "decision": "PUBLIC", + "rationale": "Caller-asserted model attribute is an authorable request field distinct from Keel's derived top-level model." + }, + "attrs.provider": { + "decision": "PUBLIC", + "rationale": "Caller-asserted provider attribute is an authorable request field distinct from Keel's derived top-level provider." + }, + "context._keel.action_envelope.action.access_level.value": { + "decision": "PUBLIC", + "rationale": "Keel-derived access classification is authorable with explicit churn guidance in fields.md." + }, + "context._keel.action_envelope.action.risk_tags.value": { + "decision": "PUBLIC", + "rationale": "Keel-derived risk tags are authorable with explicit registry-churn guidance in fields.md." + }, + "context._keel.action_envelope.authority.compute_lineage_only.value": { + "decision": "PUBLIC", + "rationale": "Keel-derived agent.create_child lineage classification has a single documented boolean meaning." + }, + "context._keel.action_envelope.authority.is_mutation.state": { + "decision": "PUBLIC", + "rationale": "Availability state lets authors fail closed when authority mutation classification is unknown." + }, + "context._keel.action_envelope.authority.is_mutation.value": { + "decision": "PUBLIC", + "rationale": "Keel-derived authority-mutation value is published together with its availability state." + }, + "context._keel.action_envelope.connector.tool_schema_hash.state": { + "decision": "PUBLIC", + "rationale": "State-only contract lets authors require that a verified decision trace recorded a schema hash without claiming custody proof." + }, + "context._keel.action_envelope.financial.amount.state": { + "decision": "PUBLIC", + "rationale": "Availability state is required to distinguish a real zero from an unavailable rail-native amount." + }, + "context._keel.action_envelope.financial.amount.value": { + "decision": "PUBLIC", + "rationale": "Rail-native amount is authorable with caller-asserted provenance and its state companion." + }, + "context._keel.action_envelope.financial.currency.value": { + "decision": "PUBLIC", + "rationale": "Currency is authorable with caller-asserted provenance and guidance not to treat it as magnitude or conversion." + }, + "context._keel.action_envelope.financial.destination_digest.state": { + "decision": "PUBLIC", + "rationale": "Availability state is required for fail-closed destination-digest policies." + }, + "context._keel.action_envelope.financial.destination_digest.value": { + "decision": "PUBLIC", + "rationale": "Destination digest is authorable with caller-asserted provenance and its state companion." + }, + "context._keel.action_envelope.financial.moves_customer_value.state": { + "decision": "PUBLIC", + "rationale": "Availability state prevents an unknown value-movement classification from silently missing a rule." + }, + "context._keel.action_envelope.financial.moves_customer_value.value": { + "decision": "PUBLIC", + "rationale": "Keel-derived value-movement classification is published with its state companion and caveat." + }, + "context._keel.action_envelope.financial.operation.value": { + "decision": "PUBLIC", + "rationale": "Financial operation is authorable with caller-asserted catalog provenance and an open-ended MCP value-space warning." + }, + "context._keel.action_envelope.pricing.estimated_cost_usd_micros.state": { + "decision": "PUBLIC", + "rationale": "Availability state is the fail-closed branch for surfaces Keel cannot price." + }, + "context._keel.action_envelope.pricing.estimated_cost_usd_micros.value": { + "decision": "PUBLIC", + "rationale": "Keel-derived estimate is authorable only where pricing can be present and is paired with its state." + }, + "context._keel.agent_identity_verified": { + "decision": "PUBLIC", + "rationale": "Keel-derived runtime-key verification boolean is an authorable identity control." + } + } }