Repository navigation
feat: answer unnamed governance scopes from an enterprise baseline - #23689
iamwhatever wants to merge 1 commit into
Conversation
UX Review (Opus 5.5) — ⏭️ skippedRevision |
First Principles Review (Opus 5.5) — 🔴 BLOCKPremise-level review of First-Principles-Verdict: BLOCK Product-shape change without an accepted RFC: every governance scope the policy leaves unnamed in an enterprise build changes from permitted to a baseline minimal set or off. The base-commit RFC for this is Notes
What this change shipsInventory (7 items) — 6 justifiedIntent: an enterprise operator wants a scope their policy forgot to come out closed, not open. This is an ADDITION.
Blockersproduct-shape change without accepted RFC: enterprise deny-unnamed and the baseline minimal sets (lens 9) [FIRST-PRINCIPLES-REVIEWED] 4f9bc12 Description read[DESCRIPTION-READ] 98995dc8c19f1c7c841eaea98178da2309b89ed91b89c1ae3e3013b11fd26221 That sha256 names the PR description this verdict read. Recompute it with |
Design Review (Opus 5.5) — ✅ PASSDesign-level review of Design-Verdict: PASS Nothing blocks: the change only runs in enterprise builds, Notes
[DESIGN-REVIEWED] 4f9bc12 Description read[DESCRIPTION-READ] ffa874dd5f27fcc7070b43a37693f97f27c5eb0a2a7f49a021d7fd69af65dd97 That sha256 names everything this verdict read: the PR description, plus the 1 evidence file(s) the workflow collected from it and handed this review. It covers that evidence because the media strip replaces every attachment URL with the same placeholder, so an edit swapping one attachment for another would leave the description's captured text identical while what this review judged changed. Recompute it with |
Opus 5.5 Review — ✅ no blocking findingsReviewed Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
GPT 6.1 Review — ✅ no blocking findingsGPT 6.1 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
b19536a to
86c3a04
Compare
|
GPT 6.1 finding on Verified in code: The |
|
Replies to the advisory notes on Design note 1 (direct Design note 2 and First Principles item 2 (read-only tools that cannot pass while First Principles item 1 (RFC status |
86c3a04 to
a42712b
Compare
|
Scope change on
|
a42712b to
9b356f0
Compare
|
Correction to the scope-change comment above: the commit is |
9b356f0 to
97cfd42
Compare
|
GPT 6.1 finding on Verified in code: The owner chose not to change that classification in this PR. It is gate code and has its own follow-up item. So the baseline no longer claims a read-only set it cannot deliver: |
In an enterprise build a scope the remote policy does not name was permitted, exactly as in oss. RFC D3 (rfc-crew-enterprise-edition.md) locks such a scope to its minimal set, or off when it has none. Add governance_parts/baseline.py, a data table of each catalog scope's minimal set from the RFC appendix, parsed by the same per-control parser a remote policy uses. resolve fills the POLICY level from it when the policy names nothing and the build is enterprise; the profile level is untouched, so a profile can still only narrow and cannot block a remote widening. may_skip_gate reads the same lookup, so an auto-approve grant cannot skip the gate for an unnamed scope. oss is unchanged.
97cfd42 to
4f9bc12
Compare
|
Opus 5.5 finding on |
Problem / Motivation
Goal: In an enterprise build, a governance scope the remote policy does not name answers its minimal set (or off) instead of being permitted; the
ossedition is unchanged.Today
_query_levelanswers aNonecontrol with_PERMIT_NOT_GOVERNED, in every edition. RFCrfc-crew-enterprise-edition.mddecision D3 (merged in #23208) says that in an enterprise build a control the remote policy does not name is locked to its minimal set, or off when it has none, through a built-in baseline policy.edition.is_enterprise()exists since #23213; nothing reads it in governance yet.Why it matters
Without this, an enterprise policy that forgets a scope leaves that scope fully open, which is the opposite of the "off by default" the RFC promises (Design section 3, Migration plan phase 3). Every later enterprise item (local-side narrowing, hardcoded defaults for keyless controls) assumes an unnamed scope is already closed.
Not a goal
ceiling is None and profile is Noneearly returns inhook_runtime/governance_gate.py,mcp_gateway/app_call.pyandgovernance_profiles.governance_permits. An enterprise host with no policy at all is the enterprise gate's to refuse (RFC Migration plan phase 2).What changed (motivation → approach → change)
Goal: an unnamed scope in an enterprise build answers the RFC appendix's minimal set; the remote policy can still widen it.
Approach: fill the POLICY level, not every level.
_query_levelanswers both the policy and the profile level, so turning itsNonebranch into a deny would make a profile that does not name a scope impose the baseline again beneath a remote widening, which D3 forbids ("Remote policy: can widen"). So_query_levelkeeps permittingNone, andresolveasks_policy_level(ceiling, scope)for the policy's control: the remote control when the policy names the scope, else (enterprise only) the baseline control.platform/governance_parts/baseline.py(new, data only):BASELINE_CONTROLS, one raw control per catalog scope, andBASELINE_UNGATEDfor the two ordinals with the reason each keeps Crew's default. The raw controls go through the same_parse_controla remote policy uses, so the catalog'salways_permittedfloor applies.platform/governance_parts/evaluator.py:_policy_level/policy_level_control;resolveuses it and prefixes a baseline answer's reason withenterprise baseline:. A scope in neither table (registered at run time) is denied in enterprise. Afolders.*query looks up the policy'sfilesystem.*rule first, since the parser stores it there. No baseline is added for the profile layer alone:gate_decision's alias-group pass asksresolve(None, profile, ..., baseline=False), and aProfilepassed in the ceiling slot (governance_profiles.any_configured_profile_governs→may_skip_gate(ref, profile)) gets none.platform/governance.py:may_skip_gateand_ceiling_mentions_mcp_serverreadpolicy_level_controlinstead ofceiling.get, andmay_skip_gateno longer returns "skip" for aNoneceiling in enterprise. Without this an auto-approved built-in (web_fetch) skips the PreToolUse gate, so the baseline is never asked.platform/governance_parts/tiers.py(added to scope by the conductor, from it_d6f8af58):compose_tier_ladderfills the authority tier's unnamed scopes from the baseline (_with_enterprise_baseline) BEFORE the fold, so the existing tighten-only_intersect_ceilingsmakes a local tier (env,home,bundled) narrow the baseline and never widen it. Without it a local tier naming a scope the central document leaves out supplied that scope's whole answer, e.g. a home file enablingcapabilities.browse. With no central document the top tier is itself local, so_with_enterprise_baselineintersects it with the baseline scope by scope (_compose_controls) instead of filling only its gaps; only a central authority's named scopes replace the baseline.ossreturns the authority unchanged (same object). The parsed baseline moved toparsing.baseline_controlsso bothtiersandevaluatorread it.dashboard/handlers/member_dashboard.py:_egress_decision(whether an agent-written page may run script) readspolicy_level_control(ceiling, "network.egress"), so an enterprise policy that leaves egress out keeps the page static instead of treating egress as unrestricted.dashboard/handlers/security_governance_policy.py: the policy viewer readspolicy_level_control, so an enterprise scope the policy leaves out is reported as governed by its baseline, not "ungoverned".platform/governance.pyanddashboard/handlers/security.pyfacades:baselinejoins_OWNERS; manifests updated..github/black-baseline.txtdropstest/test_governance_policy_viewer.py, now black-clean.docs/system-specs/modules/governance.md: new "Enterprise built-in baseline" section, Files and Tests entries.catalog.py: the CAPABILITY-DEFAULT contract notes the enterprise exception.Baseline per scope (RFC appendix "Minimal set"; everything not listed with a set is off):
toolsapproval_modesnormal,trust,trust_readsalways_permittedmcp@kirocrew-coreagent.pyfilesystem.read~/workplace/kirocrew-workspaceand/Volumes/workplace/kirocrew-workspace(+/**)config/paths.py_default_workspace_base()+_WORKSPACE_DIR_NAMEagent_backendkiroagent_sdk/backends.pyPOLICY_ID_KIROcapabilities.publishdestinations=defaultpublish_provider.DEFAULT_PROVIDERapps,commands,filesystem.write,network.egress,channels,yolo_durationcapabilities.*enabled: falseapproval_mode,sandbox.min_levelfolders.read/folders.writefilesystem.*_SCOPE_ALIASESScopes covered: all 32 rows of
SCOPE_CATALOG(28 inBASELINE_CONTROLS, 2 inBASELINE_UNGATED, and the 2folders.*aliases, which resolve throughfilesystem.*).Not mapped:
updates.source("official source"). It is acarriers.UpdatePinsfield outsidecontrols, never reaches_query_level, and no official-remote constant exists in code; an empty pin permits any remote.distribution("Crew's current defaults") is likewise outsidecontrolsand keeps its defaults.Appendix rows whose meaning changed, for a later RFC appendix update:
fs_read,glob,grep, fromhooks._HOST_READ_ONLY_BUILTIN_TOOLS) could not pass whilecommandsis off (Follow-up), so the baseline does not claim one. Read-only tools wait on the tools/commands classifier fix, a separate follow-up item. With an empty policy every built-in tool is denied.normal"): meansnormal+trust+trust_reads(the catalog floor); onlyyolois off.KIROCREW_WORKSPACE, the savedworkspace_dirfile orworkspaces[].dir, all local sources D3 says cannot widen. A profile may narrow it to a sub-path; nothing local can move it.Follow-up: bare tool names are checked against
commands.classify_tool_titlesends every unprefixed name (and every prose display title) to bothcommandsandtools(tool_calls.py:192), so withcommandsoff a read-only tool such asfs_readis refused bycommands, and an allow-modetoolsset also refuses a prose title likeSearching for foo. Read-only tools cannot pass while commands is off, so thetoolsbaseline is off until that fix lands; the fix then sets it to the read-only set. Tracked as a separate item, not this PR.Backwards compatibility
Compatible: every new branch is behind
edition.is_enterprise(), and the checked-in edition isoss;test_oss_unnamed_scope_is_not_governedandtest_oss_auto_approve_is_unchangedpin theossanswers. There is no installed enterprise base (RFC Backward compatibility).Reader: src/kiro_crew/platform/governance.py:may_skip_gate -- entry: chat|subagent|app -- oss path unchanged (
ceiling is Nonestill returns True when not enterprise); test_oss_auto_approve_is_unchangedReader: src/kiro_crew/platform/governance_parts/evaluator.py:resolve -- entry: chat|cron|subagent|app|crew page -- oss path reads
ceiling.getexactly as before; test_oss_unnamed_scope_is_not_governedTests
test/test_governance_enterprise_baseline.py:test_every_catalog_scope_has_a_baseline_entry: fails when a catalog scope is in neitherBASELINE_CONTROLSnorBASELINE_UNGATED.test_enterprise_unnamed_scope_answers_the_baseline: table-driven over every catalog scope, with no ceiling and with an empty one; minimal-set items permit, others deny at layerpolicywith anenterprise baselinereason.test_enterprise_policy_naming_a_scope_answers_and_may_widen: a named scope answers from the remote policy, including past the baseline.test_enterprise_profile_not_naming_a_scope_keeps_the_remote_wideningandtest_enterprise_profile_narrows_the_baseline.test_enterprise_alias_group_keeps_the_remote_grant_under_a_profile: a("shell", "execute_bash")group the remote policy permits stays permitted with an unrelated profile bound.test_enterprise_may_skip_gate_asked_about_a_profile_adds_no_baseline.test_governance_policy_viewer.py::test_enterprise_unnamed_scope_reports_the_baseline.test_enterprise_local_curl_deny_stays_denied,test_enterprise_local_write_allowlist_still_narrows_writes,test_enterprise_local_tier_cannot_permit_what_the_baseline_denies(env and home tiers),test_enterprise_local_authority_with_no_central_only_narrows_the_baseline(env, bundled and home as the top tier),test_oss_tier_fold_is_unchanged.test_enterprise_alias_query_finds_the_policy_rule:folders.readanswers from afilesystem.readrule.test_dashboard_page_scripts_ceiling.py::test_enterprise_unnamed_egress_is_the_baseline_and_gets_NO_scripts.test_enterprise_auto_approve_cannot_skip_the_gate_for_an_unnamed_scope:may_skip_gateis False forweb_fetch,web_search,use_subagentand MCP refs.test_enterprise_scope_with_no_baseline_row_is_denied,test_enterprise_gate_refuses_a_shell_command_with_an_empty_policy.test_oss_unnamed_scope_is_not_governed,test_oss_auto_approve_is_unchanged:ossunchanged (_query_level(None)is_PERMIT_NOT_GOVERNED).Mutation checks: forcing
_policy_levelto skip the baseline fails 73 cases; re-adding the baseline to the profile pass and to aProfileceiling fails both profile tests; reverting the viewer, member-dashboard or alias lines fails its test. Skipping the tier fill fails bothlocal_tier_cannot_permitcases. Treating a local authority like a central one fails all three no-central cases.Local: the new file plus
test_governance_*.py,test_hooks*.py,test_facade*.py,test_governance_parts_facade.py,test_security*.py,test_policy_distribution*.py,test_dashboard_page_scripts_ceiling.py,test_platform_context.py,test_edition*.pyandtest/integration/test_governance.pypass; flake8, isort, mypy, black gate, comment-history, brand, file-line-cap, docs-lint and the flake-pattern ratchet pass.Manual verification
N/A — the change is pure evaluator logic with no I/O, covered table-driven over the whole catalog; enterprise builds are not enabled yet.
Related Issues
Refs #23206. RFC: #23208 (D3, Migration plan phase 3). Builds on #23213.
Checklist
feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert: ...)Contribution License Agreement
Contributions are accepted under the project's Apache-2.0 license; see the Licensing section of CONTRIBUTING.md.