feat(guard): add deterministic policy engine#131
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0e72b298e5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Greptile SummaryThis PR introduces a deterministic policy engine (
Confidence Score: 3/5Safe to merge with the production-mutation predicate fixed; without that fix a directly constructed RiskEvent with an empty OperationClass in a production environment will be incorrectly denied. The production_mutation rule's When predicate excludes "unknown" and "read" but not the empty string, so any RiskEvent built without an OperationClass and with Environment="production" silently triggers a deny. This is a real false-positive path even if the current normalizer always sets the field; callers constructing RiskEvents directly (including future tests or integrations) will hit it. internal/guard/policy/pack_default.go — the production_mutation When predicate; internal/guard/policy/types.go — Validate allocation. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[RiskEvent] --> B[Engine.Evaluate]
B --> C{pack.ID empty?}
C -- yes --> D[use DefaultRulePack]
C -- no --> E[use provided pack]
D --> F[iterate rules]
E --> F
F --> G{categoryEnabled for profile?}
G -- no --> H[skip rule]
H --> F
G -- yes --> I{rule.When event ?}
I -- no --> H
I -- yes --> J[Result: Deny - fill RuleID, Category, ReasonCode, NonBypassable, MatchedSignals]
F -- exhausted --> K[Result: Allow - no rule matched]
Reviews (1): Last reviewed commit: "feat(guard): add deterministic policy en..." | Re-trigger Greptile |
0e72b29 to
067c32d
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 067c32dbb1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
db35ad7 to
39da86c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 39da86c5cd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
1cc4b8e to
0d47862
Compare
hasandemirkiran
left a comment
There was a problem hiding this comment.
No blocking comments from me. Two small non-blocking comments from the AI reviewer:
- Production mutation diagnostics may never include matched_signals.
pack_default.go declares MatchedSignals: []string{"production", "mutation"}, but engine.go only returns signals already present on risk.RiskEvent.Signals, and the normalizer sets Environment / OperationClass without emitting production or mutation signals. Real production mutation denies will likely have empty matched_signals. I’d either emit those signals during normalization or derive them from structured fields. - Config.RulePack looks like config surface that does not currently do anything meaningful.
withDefaults() sets empty RulePack to guard-default, making the later if cfg.RulePack == "" branch in Evaluate unreachable, and Validate() rejects every non-default pack even though NewEngine(pack) accepts arbitrary packs. I’d either remove RulePack from Config until there is pack selection, or make validation/evaluation validate against the engine’s pack ID.
0d47862 to
05bb15b
Compare
Merge activity
|
Adds the private deterministic policy package for Guard launch.\n\nThe package keeps normalized event extraction in risk and moves deterministic policy semantics into internal/guard/policy.\n\nIt does not wire runtime behavior yet; ENG-325 will call this from the Local Policy Provider.
05bb15b to
492185c
Compare
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
🤖 I have created a release *beep* *boop* --- ## [0.7.0](v0.6.0...v0.7.0) (2026-05-18) ### Features * add local guard judge contract ([#132](#132)) ([ef6a3cd](ef6a3cd)) * **cli:** make kontext start local-first ([#140](#140)) ([02a34bf](02a34bf)) * **dashboard:** show guard diagnostics ([#156](#156)) ([dcbdf4a](dcbdf4a)) * **guard:** add deterministic policy engine ([#131](#131)) ([3a0f16e](3a0f16e)) * **guard:** add policy config store ([#135](#135)) ([520c3b6](520c3b6)) * **guard:** add policy profile dashboard ([#137](#137)) ([0b0e856](0b0e856)) * **guard:** connect deterministic policy and judge ([#154](#154)) ([25f0d32](25f0d32)) * manage local judge runtime ([#136](#136)) ([09f4fc0](09f4fc0)) * **runtime service:** Guard now starts a Unix-socket localruntime.Service alongside the existing HTTP daemon ([#122](#122)) ([3e87e12](3e87e12)) * **runtime service:** Introduce runtime service and make existing Unix socket more generic ([#121](#121)) ([2bbaa93](2bbaa93)) ### Bug Fixes * **dashboard:** contain command drawer text ([#157](#157)) ([4107e31](4107e31)) * **dashboard:** polish activity summary and log groups ([#153](#153)) ([fc79101](fc79101)) * harden npm dependency resolution ([#114](#114)) ([86eadf5](86eadf5)) * optimize judge fixture category matching ([#146](#146)) ([0bad7ab](0bad7ab)) * **repo:** remove repo-wide codeowners ([#133](#133)) ([73557f0](73557f0)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).

Summary
This adds the deterministic policy package for the Local Guard LLM Judge launch.
Before this, Guard's deterministic safety checks lived inside
risk.DecideRisk, next to normalization, Markov/noop scoring, legacyaskbehavior, and the runtime decision shape. That made the launch path hard to reason about.Now the deterministic layer is isolated in
internal/guard/policy:It evaluates a normalized
risk.RiskEventagainst a curated Kontext rule pack and an active profile:relaxed,balanced, orstrict. It returns onlyallowordeny, plus reviewer/debug metadata like rule ID, category, profile, policy version, reason code, and matched signals.Why
This gives ENG-325 a clean seam for the launch flow:
This PR does not wire the package into the running Guard path yet. That stays with ENG-325.
What changed
internal/guard/policyrelaxed,balanced,strictpolicy.Resultdiagnosticsrisk.IsPersistentResourceClassso policy can reuse risk taxonomyVerification
go test ./internal/guard/policy ./internal/guard/risk ./internal/guard/app/server