microsoft / microsoft/agent-governance-toolkit
[Feature]: define Web-standard edge-runtime conformance for TypeScript policy enforcement
- Dominant language
- Python
- Stars
- 6.3k
- Forks
- 1.1k
- Avg merge
- 5d 11h
- Merged PRs (30d)
- 142
Description
### Package
policy-engine
### Problem Statement
A live deployment exercise exposed a narrow portability gap in the TypeScript policy-enforcement surface.
AGT 5.0.0 policy evaluation was exercised inside a standards-based edge Worker runtime using workerd. This went beyond import or bundling compatibility: the policy path made real allow and deny decisions as part of a pre-capability governance boundary around consequential execution.
The live test demonstrated that:
- an in-memory AGT policy engine can execute inside the Worker runtime;
- an allowed, registered capability can proceed through the governance boundary and perform a real durable external mutation;
- the execution claim can be kept separate from outcome evidence and independently reconciled against the external system by object version and content digest;
- cleanup can be modeled as a separate governed operation and independently verified by absence;
- prohibited capability names can produce native AGT deny decisions;
- separately, a host capability registry can reject an unregistered capability before AGT evaluation, preserving a clean distinction between maximum executable authority and policy authorization.
This substantially answers the practical question of whether AGT policy evaluation can participate in a real edge-runtime enforcement path.
The gap is that this behavior is currently incidental rather than an explicit, maintained compatibility contract.
The TypeScript policy implementation mixes portable in-memory policy evaluation with Node-specific conveniences in the same surface. For example, `agent-governance-typescript/src/policy.ts` statically imports `readFileSync` from `fs`, even though ordinary in-memory policy evaluation does not inherently require filesystem access. The public TypeScript documentation likewise presents portable policy construction alongside filesystem-backed YAML loading without defining which APIs are expected to work in Web-standard runtimes and which require Node.
As a result, a downstream application can work under a particular bundler or Node-compatibility configuration without having an upstream guarantee that the policy-evaluation path itself is intentionally portable.
For governance software, successful import or bundling is not sufficient evidence. The relevant compatibility property is behavioral: real policy evaluation must execute at the enforcement boundary and retain its fail-closed semantics.
### How does this feature impact your work and what are you trying to achieve?
I maintain the policy engine and am also consuming AGT in a real governed execution environment, which is how this gap surfaced.
The immediate use case is pre-capability governance in a standards-based edge runtime. The agent host exposes a bounded set of registered capabilities, and AGT evaluates policy before those registered capabilities are allowed to execute.
The live experiment proved that this architecture is viable under workerd. An allowed registered action reached the policy layer, received an AGT allow decision, executed, and produced a durable external effect. That effect was then verified independently rather than treating the agent or handler's own mutation claim as proof of success. Cleanup was separately governed and independently reconciled.
The exercise also demonstrated the importance of keeping two authority boundaries separate:
1. The host capability registry determines the maximum set of actions that can possibly execute.
2. AGT determines whether an action within that registered set is permitted under policy.
An unregistered capability was rejected by the host before AGT was consulted, which is the desired behavior. Native AGT deny decisions were also exercised for prohibited capability names.
The remaining narrow enforcement test I would want included in an upstream conformance suite is slightly different: register an otherwise executable test capability, allow it to reach the AGT policy boundary, have AGT deny it, and prove that the associated handler is never invoked. That would directly establish deny enforcement at the exact execution seam rather than relying only on a capability that was unavailable one layer earlier.
What I want to avoid is making edge-runtime compatibility dependent on downstream knowledge such as:
- which Node compatibility flag happens to make the package bundle;
- which Node APIs are actually exercised by a given code path;
- which imports can safely be ignored by a bundler;
- or whether a successful import happens to imply that enforcement semantics work.
The goal is a small upstream-maintained contract saying, in effect:
"The TypeScript policy-enforcement path can run in a Web-standard runtime, these APIs comprise that portable surface, and these behavioral tests prove allow, deny, and fail-closed execution there."
That gives consumers a stable contract instead of requiring each adopter to experimentally rediscover the same runtime boundary.
### Timeline
No hard external deadline. This is suitable for the next policy-engine compatibility/hardening cycle. The current downstream path is working, so this is not blocking an immediate deployment. The value is in converting a behavior we have now demonstrated experimentally into an explicit upstream contract before additional consumers start depending on the same accidental portability characteristics.
### Proposed Solution
Define and maintain an explicit Web-standard / edge-runtime conformance contract for the TypeScript policy-enforcement surface.
I do not think this needs to become a Cloudflare-specific feature. Cloudflare Workers/workerd is simply a useful standards-based conformance environment because it exposes Node assumptions quickly.
The implementation shape can remain open to maintainer preference. Two reasonable approaches are:
1. Expose a Web-standard policy-engine entry point that has no mandatory Node runtime dependencies.
or
2. Keep the existing package structure but explicitly define a portable subset and isolate Node-only filesystem/process conveniences behind separate imports or entry points.
Node functionality should remain available and backward compatible. File-backed YAML loading and similar conveniences are useful; they simply should not implicitly determine whether the core in-memory governance path is portable.
Add a small CI/reference-runtime conformance lane using workerd or another Web-standard runtime. The important requirement is that the lane test actual governance behavior, not only compilation or package import.
The portable conformance suite should prove at minimum:
- in-memory policy construction succeeds without filesystem or process dependencies;
- a permitted pre-capability action evaluates to allow;
- a registered, otherwise executable test capability reaches AGT evaluation and can be denied;
- when that registered capability is denied, its handler is never invoked;
- an unknown or unmatched action remains fail-closed according to the policy contract;
- policy/backend failure or unavailability cannot silently become allow;
- no Node filesystem or process API is required by the portable evaluation path;
- Node-specific file/YAML helpers remain usable through the Node-capable surface;
- documentation identifies which TypeScript APIs are Web-standard portable and which require Node.
I would also keep one architectural boundary explicit in the tests and documentation:
Host capability registration and AGT authorization are complementary but different controls.
The host defines the maximum executable capability surface. AGT may narrow that surface through policy. AGT should not be treated as creating capabilities that the host never registered.
The downstream live exercise also used independent observation of external effects. That was valuable, but I would explicitly keep external side-effect verification outside this feature. AGT answered "was this action permitted?" An independent observer answered "did the permitted action actually occur?" Those are separate assurance properties and should not be conflated just because the same integration exercised both.
The desired outcome is intentionally small: a consumer should be able to use the published TypeScript policy surface inside a standards-based edge runtime and rely on an upstream-maintained behavioral contract that pre-capability policy enforcement works there.
### Alternatives Considered
1. Document Cloudflare Workers as a supported platform.
Rejected because the useful property is not Cloudflare-specific. The actual requirement is a Web-standard portable TypeScript policy surface. workerd is a useful conformance runtime, not the product boundary.
2. Require Node compatibility mode or bundler polyfills.
This can make deployments work, but it does not tell consumers which AGT APIs are genuinely portable. It also risks allowing accidental Node dependencies to enter the enforcement path without any upstream regression signal.
A governance engine should not rely on "the bundler seemed happy" as its compatibility contract.
3. Treat successful import or bundling as edge-runtime conformance.
Rejected because this does not test the property that matters.
A package can import successfully while policy evaluation, deny handling, backend failure semantics, or handler suppression behaves differently at runtime. Conformance should therefore test real allow/deny execution at the enforcement boundary.
4. Maintain a downstream edge adapter indefinitely.
This is viable and is effectively what a consumer can do today. It is not ideal because every downstream adopter must independently determine which parts of the TypeScript surface are safe to use outside Node.
The portability boundary belongs close enough to the policy engine that it can be protected by upstream CI.
5. Make the entire TypeScript SDK Web-standard-only.
Rejected as unnecessarily broad and potentially disruptive.
There is no reason to remove useful Node functionality such as filesystem-backed policy loading. The request is only to identify and protect the policy-evaluation portion that does not inherently require Node, while keeping Node-specific conveniences available through an explicit Node-capable surface.
6. Fold external outcome verification into this work.
Rejected because it is a different assurance problem.
AGT policy evaluation establishes whether an action is authorized. Independent observation establishes whether an authorized external effect actually occurred. The live test deliberately kept those claims separate, and this proposal should preserve that boundary rather than expanding an edge-runtime compatibility feature into a general evidence architecture.
### Priority
Important
### Contribution
- [x] I would be willing to submit a PR for this feature
Contributor guide
Research direction
Start with agent-governance-typescript/src/policy.ts and the public TypeScript policy documentation, then review the proposed workerd or Web-standard CI conformance lane. Done means the maintained contract identifies the portable and Node-specific APIs and behavioral tests cover in-memory allow, registered-capability deny with no handler invocation, fail-closed behavior, and continued Node YAML support.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100