Skip to content

feat(security): R-07 - wire the managed model/provider allowlist fail-closed + pin omp's provider catalog - #348

Open
alexander-blackwell wants to merge 1 commit into
feat/windows-enterprise-packagesfrom
feat/r07-provider-allowlist
Open

feat(security): R-07 - wire the managed model/provider allowlist fail-closed + pin omp's provider catalog#348
alexander-blackwell wants to merge 1 commit into
feat/windows-enterprise-packagesfrom
feat/r07-provider-allowlist

Conversation

@alexander-blackwell

@alexander-blackwell alexander-blackwell commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Closes #347. Completes the core half of the add-on's R-07 provider-governance risk item (add-on #36); the add-on-side pack-to-managed-config emitter is its companion there.

Layer 2/2 of stack #349: lands with or after #346 (the stack enforces the order; the only overlap is the PROGRESS.md tail).

The gap this closes

P-ENT.1 (ADR-0068) shipped the ManagedModels schema (allowed/denied substring lists + lock + asksageOnly) and the pure modelAllowed() helper. ADR-0217 wired fail-closed enforcement for the AskSage lockdown only. The generalized allowed/denied policy had ZERO live call sites: an org could deploy a managed models policy and nothing enforced it. Separately, omp's builtin provider catalog (57 ids at our pinned 16.1.20, including qianfan, minimax-code-cn, xiaomi-token-plan-cn/-ams/-sgp, zhipu-coding-plan, deepseek, zai, moonshot, kimi-code, qwen-portal) grows every release with no early warning: the exact R-07 risk of provider churn routing around sovereignty governance.

What changed

1. Pure resolver (desktop/checker_model.ts)

resolveGovernedModel(locked, models, current, optionValues) composes the ADR-0217 lockdown with modelAllowed: the lock resolves first (sovereignty routing), then the allowlist; when the resolved model is denied, the first option satisfying BOTH constraints wins; when nothing qualifies it returns { ok:false } so callers BLOCK the turn rather than route to a denied provider. Unmanaged + unlocked passes through unchanged. The module stays pure: policy arrives as a parameter, never read ambiently.

2. Enforcement wiring (desktop/acp_backend.ts), mirroring the ADR-0217 pattern exactly

  • enforceAsksageLock generalized to enforceModelPolicy at the same two call sites: the fresh-session fire-and-forget UI sync (now unconditional; a no-op when unlocked + unmanaged) and the authoritative pre-turn clamp, which now REFUSES a turn when the managed policy cannot be satisfied. After a model switch it re-verifies BOTH constraints before letting the turn proceed.
  • resolveAgentRunModel: built-agent and scheduled-automation runs clamp through the same resolver, so a denied model is swapped or the run is refused, never routed.
  • accessibleModels: the policy narrows the pickers too, with the same only-narrow-if-nonempty fail-safe the AskSage narrowing uses (cosmetic; the pre-turn clamp is the guarantee that survives an omp respawn).

3. CI diff against omp's provider list (harness/omp/provider_catalog.test.ts)

@oh-my-pi/pi-catalog is now a direct dependency pinned 16.1.20 and joins the prefix_compaction OMP_PACKAGES same-version discipline. The new test pins the sorted 57-id builtin provider universe; any omp bump that adds or removes a provider fails with the exact delta and instructions: review additions against the sovereignty posture (managed allowlists, the add-on's governance classification packs) and removals against any policy that names them, then re-pin. Runtime-registered providers (the AskSage gov-gateway extension, local providers) are ours and deliberately outside the pin.

Proof the pin works: it caught real drift on its first run. The transitively-resolved catalog was newer than 16.1.20 and carried baseten, coreweave, and gitlab-duo-agent; the exact pin does not. That is precisely the silent-growth failure mode this test exists to surface.

What this deliberately does not do

  • No signed-pack parsing in core: pack verification stays add-on-side; packs feed the managed models block through the existing ADR-0068/A010 managed-config channel (the add-on emits the fragment). Core enforces whatever the channel delivers.
  • No UI changes: the models lock / 'Managed by org' plumbing already exists from P-ENT.1.
  • No new enforcement surface invented: same chokepoints ADR-0217 already proved.

Evidence

  • make test: 2640 pass / 0 fail on the stacked tree (8 resolveGovernedModel tests + 2 catalog-pin tests new).
  • desktop tsc --noEmit clean; zero em dashes in new code (AGENTS.md writing rule).

@alexander-blackwell alexander-blackwell self-assigned this Sep 3, 2026
@alexander-blackwell
alexander-blackwell marked this pull request as ready for review September 4, 2026 00:09
…-closed + pin omp's provider catalog

Closes #347. Core-side work for the add-on's R-07 provider-governance risk
item (add-on #36, POAM R-07).

The ManagedModels allowed/denied schema (ADR-0068, P-ENT.1) shipped ready-to-
consume but modelAllowed() had zero live call sites: the policy was never
enforced. Meanwhile omp's builtin provider registry (57 ids at the pinned
16.1.20, including qianfan / minimax-code-cn / xiaomi-token-plan-cn /
zhipu-coding-plan / deepseek / zai) grows every bump with no early warning.

- desktop/checker_model.ts: new pure resolveGovernedModel(locked, models,
  current, options) composing the ADR-0217 AskSage lockdown with
  modelAllowed. Lock resolves first, then the allowlist; a denied model swaps
  to the first option satisfying BOTH constraints; nothing qualifies means
  { ok:false } so callers BLOCK rather than route to a denied provider.
  Unmanaged + unlocked passes through unchanged. Module stays pure (policy
  arrives as a parameter).
- desktop/acp_backend.ts: enforceAsksageLock generalized to enforceModelPolicy
  at its two call sites. Fresh-session sync now fires unconditionally (no-op
  when unmanaged); the pre-turn clamp stays the authoritative fail-closed
  gate and refuses the turn when policy cannot be satisfied. Post-switch it
  re-verifies BOTH constraints. resolveAgentRunModel routes built-agent and
  scheduled runs through the same resolver. accessibleModels applies the
  policy as a cosmetic picker filter with the existing only-narrow-if-
  nonempty fail-safe.
- R-07 CI diff: @oh-my-pi/pi-catalog pinned 16.1.20 as a direct dependency
  (joins the prefix_compaction OMP_PACKAGES same-version discipline);
  harness/omp/provider_catalog.test.ts pins the sorted 57-id builtin provider
  universe and fails with review-then-re-pin instructions on any addition or
  removal. The pin caught real drift immediately: a newer transitive catalog
  carried baseten/coreweave/gitlab-duo-agent that exact-16.1.20 does not.

make test: 2640 pass / 0 fail (8 resolver tests + 2 catalog tests new);
desktop tsc clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

R-07: wire the managed model/provider allowlist (deny-by-default) + CI diff against omp's provider catalog

1 participant