Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Make the policy decision point pluggable (the rules are injectable, the evaluator is not)

オープン
#350 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
5/5
見積もり時間
1週間以上
初心者へのやさしさ
35/100
issue の種類
機能追加
明瞭さ
おおむね明確
活発さ
活発
技術スタック
typescript

調査の方向性

server/src/computer/gateway.ts の469-535行付近と、server/src/computer/policy.ts の4-12行、38-42行、44-153行、210-243行付近から始めてください。enterprise gateway にすでに pluggable な意思決定ポイントがあるか確認し、そのうえで snapshot の解決、dry-run の処理、audit の順序、fail-closed の動作を維持しながら decider の契約を定義して文書化してください。デフォルトパスと、実装例としての decider を1つ含めれば完了です。

索引モデルが issue の本文から書いたものです。

説明

What I see today

The policy data is already fully injectable. options.policy() is a getter
(server/src/computer/gateway.ts:523), backed by createPolicyStore with
ACTION_POLICY_TOPIC for live reload (server/src/computer/policy-store.ts).

The evaluator is not. evaluateActionPolicy is a direct import from ./policy,
called synchronously:

const decision = evaluateActionPolicy(options.policy(), context);

So a deployment can change the rules, but not who answers.

Why that's worth a seam

Three cases the current shape can't serve:

  1. A deployment that already owns a policy engine. OPA/Rego, Cedar, an internal
    service. Today they have to transcribe their rules into CEL and keep two sources in
    sync, which is exactly the setup where the two drift and nobody notices.

  2. Decisions that need state the gateway doesn't hold. "This bot has already
    submitted three orders today." "This action is above the amount this actor may
    approve alone." "A human granted this, twenty minutes ago, and the grant expires."
    CEL over a stateless PolicyContext can't express any of those — not a flaw in the
    expression language, just a different question.

  3. Decisions that take time. Anything involving a human in the loop is async by
    nature, and the current call site is synchronous.

The good news is that most of the work is already done: PolicyContext
(server/src/computer/policy.ts:44-153) is a clean, serialisable, effect-level
object — intent, mcp.effect, file.extension, command, resolved element. That
is a better PDP boundary than most systems that set out to build one.

Concrete shape

export type PolicyDecider = (
  context: PolicyContext,
) => PolicyDecision | Promise<PolicyDecision>;

Default stays exactly what happens now:

const decide: PolicyDecider = options.decide
  ?? ((ctx) => evaluateActionPolicy(options.policy(), ctx));
// ...
const decision = await decide(context);

Line 523 is already inside an async method that awaits write(...) on the next
line, so the await costs nothing structurally.

Two things I'd want written into the contract, not left to the implementer:

  • A decider that throws, or exceeds its timeout, is a refusal. Same discipline as
    matches() and its onError argument (policy.ts:210-243). A remote PDP that is
    merely unreachable must not be a way to get an action through, and the timeout should
    be required rather than optional.
  • dry-run stays in the gateway, not in the decider. Otherwise an external decider
    can silently disable enforcement for the whole deployment, which is the one thing this
    seam must not make possible.

What I would not touch

  • Snapshot resolution stays server-side (gateway.ts:469-471). A decider receives the
    element the server resolved, never what the caller claimed. The comment at
    policy.ts:38-42 is the reason the whole thing works and an external decider must not
    get a chance to weaken it.
  • The gateway still writes the audit row before forwarding (gateway.ts:524-535),
    whoever decided. The trail records the decision, not the decider's opinion of it.

One question before any of this

policy.ts:4-12 says this mirrors the policy engine in CopilotKit's enterprise agent
gateway, deliberately, so a rule means the same thing in both. Does that engine already
have a pluggable decision point — and if not, is divergence here something you'd rather
avoid? If so, say and I'll drop it; that's a good reason.

Otherwise I'm happy to send the PR: default path byte-for-byte unchanged, the type, the
fail-closed timeout, and one worked example decider.

Disclosure: I build an authorization service for agents, so an external PDP is
obviously useful to me. I've tried to write this as it would stand without that —
push back if it reads otherwise.

主要言語
TypeScript
スター
6.6k
フォーク
914
平均マージ
17時間 52分
マージ済み PR(30日)
155

環境構築

  • Dockerfile または Docker Compose ファイルあり
  • プルリクエストのテンプレートあり
  • コントリビューションガイドなし

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

CopilotKit/OpenBot のほかの issue

CopilotKit/OpenBot の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。