feat(typescript): action-bound approval protocol parity (TypeScript SDK)#3200
feat(typescript): action-bound approval protocol parity (TypeScript SDK)#3200nuthalapativarun wants to merge 1 commit into
Conversation
…t#3083) Port Python agent-mesh approval_protocol subpackage to the TypeScript SDK, bringing require_approval chain execution on par with the Python reference from microsoft#3076 (ADR-0030): - approval-protocol/digest.ts: RFC 8785 JCS canonicalization (UTF-16 key sort, format integers without decimal) + SHA-256 action digests with "sha256:" prefix. Deterministic across key insertion order. - approval-protocol/binding.ts: ActionBinding + ActionTarget, bindingDigest binds the exact operation/agent/target/parameters so an approval for one binding can never authorize a different action. - approval-protocol/models.ts: PolicyDecisionRecord, ApprovalRequest, ApprovalChainEntry (with seal/verifyDigest for hash-linked integrity), ApprovalResolution; utcnow(), inputDigest(), presentedCanonical(). - approval-protocol/store.ts: ApprovalStore interface + InMemoryApprovalStore; consume() is atomic and returns true exactly once. - approval-protocol/coordinator.ts: ApprovalCoordinator with openRequest, submitEntry (authority-checked, idempotent by chainEntryId, advisory entries never satisfy a stage), validateForExecution (pre-execution revalidation: digest/version/chain-integrity checks, one-time consume, fail-closed on any unexpected error), and _maybeResolve (single deny terminates immediately; all required stages must allow). - 31 new tests covering the JCS serializer, binding digest, coordinator lifecycle, chain integrity, consume-once, expiry, digest mismatches, unpermitted identity, advisory vote behavior, and idempotent resubmission. Semantic contract matches the Python reference. Node.js is single-threaded so InMemoryApprovalStore needs no mutex (parity with Python's RLock is not needed in this runtime). Closes microsoft#3083 Signed-off-by: Varun Nuthalapati <nuthalapativarun@gmail.com>
🤖 AI Agent: test-generator — `agent-governance-typescript/src/approval-protocol/binding.ts`
|
🤖 AI Agent: breaking-change-detector — API Compatibility
API Compatibility
|
🤖 AI Agent: security-scanner — View details
No security issues found. |
🤖 AI Agent: code-reviewer — Action items:
TL;DR: 0 blockers, 1 warning. The implementation appears robust and aligns with the Python reference, but a potential issue with hash-linked chain integrity verification should be reviewed.
Action items:
Warnings (fine as follow-up PRs):
|
🤖 AI Agent: docs-sync-checker — Docs Sync
Docs Sync
|
PR Review Summary
Verdict: AI review comments are untrusted advisory output. The summary reports workflow-generated completion status only, not model-authored pass/fail claims. |
|
🟡 Contributor Check: MEDIUM
Automated check by AGT Contributor Check. |
| return; | ||
| } | ||
| if (typeof value === 'boolean') { | ||
| out.push(value ? 'true' : 'false'); |
There was a problem hiding this comment.
nit: couldn't this be collapsed into the typeof string case?
| } | ||
| if (typeof value === 'object') { | ||
| const obj = value as Record<string, unknown>; | ||
| const keys = Object.keys(obj).sort((a, b) => { |
There was a problem hiding this comment.
nit: could just be const keys = Object.keys(obj).sort((a, b) => utf16Units(a).compare(utf16Units(b)));
| export const DIGEST_PREFIX = 'sha256:'; | ||
|
|
||
| function utf16Units(key: string): Buffer { | ||
| return Buffer.from(key, 'utf16le'); |
There was a problem hiding this comment.
I don't think this is right. This will do little endian byte comparison
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
| /** | ||
| * RFC 8785 JSON Canonicalization Scheme (JCS) and SHA-256 action digests. |
There was a problem hiding this comment.
I'm not really comfortable hand writing this implementation. Would prefer we use a library like canonicalize.
| if (!isFinite(value) || isNaN(value)) { | ||
| throw new TypeError('JCS cannot serialize NaN or Infinity'); | ||
| } | ||
| if (Number.isInteger(value)) return String(value); |
There was a problem hiding this comment.
What's the point of this? You return String(value) anyway in the next line...
liamcrumm
left a comment
There was a problem hiding this comment.
Please fix digest.ts to use library rather than reimplementing the RFC
Description
Adds TypeScript parity for the action-bound approval protocol (ADR-0030), as tracked in #3083.
The Python reference implementation landed in #3076 (routing
require_approvalthrough theApprovalCoordinator) plus the underlyingapproval_protocolsubpackage. This PR brings the same approval-chain execution layer to the TypeScript SDK. One PR per SDK per the pattern established in prior parity efforts.What this adds (
agent-governance-typescript/src/approval-protocol/):digest.ts— RFC 8785 JCS canonicalization (object keys sorted by UTF-16 code unit, integers without decimal point) +sha256Jcsfor action digests. Deterministic across key insertion order.binding.ts—ActionBinding/ActionTarget/bindingDigest. An approval for one binding can never authorize a different action: change a parameter, the target, the tool schema version, or the acting agent, and the digest changes.models.ts—PolicyDecisionRecord,ApprovalRequest,ApprovalChainEntry(withsealEntry/verifyEntryDigestfor hash-linked chain integrity),ApprovalResolution;inputDigest,presentedCanonical.store.ts—ApprovalStoreinterface +InMemoryApprovalStore.consume()is atomic (one-time-use guard, ADR-0030 section 6).coordinator.ts—ApprovalCoordinatorwith three public methods:openRequest: binds arequire_approvaldecision to a durable, TTL-bounded requestsubmitEntry: authority-checked (by identity or role), idempotent bychainEntryId, advisory (llm_advisory) entries never satisfy a stage, single deny terminates immediatelyvalidateForExecution: pre-execution revalidation — checks action digest, policy version, chain version, chain integrity, expiry, and consume-once; fails closed on any unexpected errorAll symbols re-exported from
src/index.ts.Semantic contract matches the Python reference precisely. Node.js is single-threaded so
InMemoryApprovalStoredoes not need a mutex (threading.RLockis not needed in this runtime — idiomatic adaptation per @imran-siddique's guidance).Type of Change
New feature — language parity
Package(s) Affected
agent-governance-typescriptChecklist
npm test— 31 new tests, 591 total, all green)npm run lint)npm run build)-s)Attribution & Prior Art
Python reference implementation:
agent-governance-python/agent-mesh/src/agentmesh/governance/approval_protocol/(coordinator + models + binding + digest + store). Python wiring intogovern()landed in #3076 by @carloshvp. TypeScript port mirrors the Python semantics; no new design.AI Assistance
I can explain every change. Tests were run locally. Not autonomously submitted.
IP, Patents, and Licensing
All new code is original and placed under the MIT License, consistent with the existing SDK. No third-party libraries added.
Related Issues
Closes #3083