tool-approval-correlation-audit 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 — unreleased
4
+
5
+ - Original bounded metadata-only correlation audit for calls, approval requests, decisions and explicitly gated execution observations.
6
+ - Exact per-scope identities, duplicate/rebinding/conflict diagnostics, ordering and partial-trace semantics.
7
+ - Fixed redacted findings, immutable reports and ESM/CommonJS/TypeScript entry points.
8
+ - No authorization, execution, cryptography, network or storage.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Huzaifa Asif
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,130 @@
1
- # Temporary Holding Version
1
+ # tool-approval-correlation-audit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Bounded offline diagnostics for approval observation correlation. Check the npm registry for available published versions.
4
+
5
+ A small, zero-runtime-dependency diagnostic library for **saved observations** of tool calls, approval requests, decisions and explicitly approval-gated executions. It finds broken ID joins and ordering in adapter fixtures without importing a provider SDK or retaining tool arguments.
6
+
7
+ This is **not an authorization system**. A consistent trace does not prove human consent, a safe operation, a real execution, correct policy, trusted evidence or absence of omitted events. Never use this report as a permission check.
8
+
9
+ ## Why it exists
10
+
11
+ An adapter can emit an approval request before it emits the related call. A UI can associate a delayed answer with the wrong message. Transcript pruning can leave a response whose request no longer exists. Each looks like an approval problem but can be a correlation problem.
12
+
13
+ Vercel AI [issue 23514](https://github.com/vercel/ai/issues/23514), opened October 17, 2026, reports the first pattern and remained open when checked October 28. The report concerns evaluation jobs, not a verified production incident. [Issue 21193](https://github.com/vercel/ai/issues/21193) and [issue 16385](https://github.com/vercel/ai/issues/16385) are **closed/merged historical examples**, not claims that today's SDK still has those bugs. Upgrade SDKs for upstream fixes first. This package diagnoses synthetic or sanitized adapter fixtures; it does not patch those frameworks.
14
+
15
+ ## Quick start
16
+
17
+ Requires Node.js 20 or later. There are no runtime dependencies or lifecycle install scripts.
18
+
19
+ ```sh
20
+ npm run check
21
+ node examples/audit.js
22
+ ```
23
+
24
+ Import locally during evaluation:
25
+
26
+ ```js
27
+ import { auditApprovalTrace } from 'tool-approval-correlation-audit';
28
+ const report = auditApprovalTrace([
29
+ { type: 'call', scope: 'session-1', callId: 'call-1' },
30
+ { type: 'request', scope: 'session-1', callId: 'call-1', approvalId: 'approval-1' },
31
+ { type: 'decision', scope: 'session-1', approvalId: 'approval-1', decision: 'approved' },
32
+ { type: 'execution', scope: 'session-1', callId: 'call-1', approvalId: 'approval-1' }
33
+ ]);
34
+ console.log(report.consistent); // true: supplied complete observations correlate
35
+ ```
36
+
37
+ CommonJS uses `require('tool-approval-correlation-audit')`. ESM, CommonJS and TypeScript declarations are included. To work from source, run `npm ci --ignore-scripts` and `npm run check`.
38
+
39
+ For CI fixtures, fail your *test* if a complete trace is inconsistent. Do not use that boolean to decide whether to execute a real tool.
40
+
41
+ ## API
42
+
43
+ `auditApprovalTrace(events, options?) -> AuditReport`
44
+
45
+ `events` is a readonly array. Every event has a nonempty `scope`, which the caller chooses as an explicit session/run identity. IDs are compared literally, including case. Same IDs in different scopes are independent. Reuse within one scope is ambiguous. Do not truncate unrelated sessions into the same scope or put timestamps in place of identities.
46
+
47
+ | Type | Required fields besides scope/type | Meaning |
48
+ | --- | --- | --- |
49
+ | call | callId | Caller observed the tool call being registered |
50
+ | request | callId, approvalId | Caller observed an approval request for that call |
51
+ | decision | approvalId, decision | Reported label: approved or denied |
52
+ | execution | callId, approvalId | Caller observed an execution explicitly expected to be approval-gated |
53
+
54
+ There is deliberately no operation name, raw argument, result, approver name, reason, token, signature or policy field. Extra fields are ignored and not read. `approved` is merely a caller-supplied label; no authenticity is inferred. Do not fabricate execution observations from a model's claim that it ran a tool.
55
+
56
+ The API does **not** assume every tool execution requires approval. Only provide `execution` for calls whose approval requirement your adapter has independently established. Provider-executed tools and auto-approved tools can have different semantics; normalize them explicitly or leave them outside this narrow fixture. See the [AI SDK approval documentation](https://github.com/vercel/ai/blob/main/content/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx).
57
+
58
+ Repeated execution of a call is diagnosed even when the second observation uses a different approved request ID. This model allows at most one execution observation per scoped call ID; adapters that intentionally retry must assign distinct call-attempt IDs rather than reuse a call ID. Repeated execution through the same approval ID is also diagnosed.
59
+
60
+ ### Options and bounds
61
+
62
+ | Option | Default | Allowed |
63
+ | --- | --- | --- |
64
+ | maxEvents | 10,000 | integer 1–100,000 |
65
+ | maxIdentities | 10,000 | integer 1–100,000 |
66
+ | maxIdLength | 256 | integer 1–4,096 UTF-16 code units |
67
+ | maxFindings | 1,000 | integer 1–10,000 |
68
+ | traceStart | complete | complete or partial |
69
+ | traceEnd | complete | complete or partial |
70
+
71
+ Identity accounting includes each distinct retained scope, call and request ID. When a new identity would exceed the limit, scanning stops. Event overflow scans only the bounded prefix. Finding overflow retains the configured number but continues counting issues within the other bounds. No IDs are evicted and reused as if they were new.
72
+
73
+ Malformed options throw a fixed `TypeError('Invalid audit options')`. Invalid events/traces yield fixed diagnostics and `complete:false`. Getters/proxies that throw while required fields are read are caught, but arbitrary JavaScript getters can run code or never return; this is not a sandbox. Prefer objects produced by parsing sanitized JSON. Each required field is read once per event. Unknown extra fields are not inspected.
74
+
75
+ ### Result
76
+
77
+ - `consistent`: true only when evidence is declared complete, no limit/invalid evidence occurred and zero diagnostic issues were found. An empty complete trace is consistent because it declares no operations; this is not evidence that an operation occurred.
78
+ - `complete`: false for caller-declared partial boundaries, invalid evidence or a limit. This does not verify the caller's completeness claim.
79
+ - `findingsTruncated`: whether some diagnostics were omitted from the bounded findings array.
80
+ - `counts`: observed array entries, recognized calls/requests/decisions/executions, retained identities, unresolved requests, total issues.
81
+ - `findings`: fixed `code`, zero-based event `index`, and optional original `relatedIndex`. `invalid-trace` uses index -1; event-limit uses the first unexamined index.
82
+
83
+ All returned objects/arrays are frozen. Reports do not echo caller-controlled identifiers, values, error messages or arguments. The library does not mutate input objects, though hostile getters can mutate themselves.
84
+
85
+ ### Ordering, partial evidence and ambiguity
86
+
87
+ Input array order is *observation order*, not proven real-time causality. Correctly merge parallel event streams before analysis or keep separate scopes. The library does not reorder events or use timestamps to invent causality.
88
+
89
+ A request before a call remains a finding after the call arrives. A decision before a request is not applied later. A repeated/conflicting decision never replaces the first decision. Duplicate calls or rebound request IDs taint the related records. Later clean-looking events cannot erase existing findings.
90
+
91
+ At a declared complete end, unresolved requests produce `unresolved-request`. With `traceEnd:'partial'`, unresolved requests are counted but are not issues by themselves. Missing references in partial traces are **missing-context diagnostics**, not evidence that an unauthorized action happened. A positive decision without execution is permitted: consent does not guarantee an action was attempted.
92
+
93
+ ## Diagnostic codes
94
+
95
+ Input/bounds: `invalid-trace`, `invalid-event`, `event-limit`, `identity-limit`.
96
+
97
+ Correlation/order: `unknown-call`, `duplicate-call`, `ambiguous-call`, `duplicate-request`, `approval-rebound`, `decision-without-request`, `duplicate-decision`, `conflicting-decision`, `execution-without-request`, `execution-call-mismatch`, `ambiguous-approval`, `execution-before-decision`, `execution-after-denial`, `duplicate-execution`, `unresolved-request`.
98
+
99
+ A duplicate execution is a duplicate *observation*, not proof of duplicate side effects or charges. An execution-after-denial finding is contradictory reported evidence, not a verified security incident.
100
+
101
+ ## Alternatives and scope
102
+
103
+ - AI SDK `toolApproval` and message helpers provide the actual framework workflow. Prefer those for runtime behavior.
104
+ - [mcp-approval](https://www.npmjs.com/package/mcp-approval) implements MCP confirmation/elicitation. Its indexed documentation was reviewed; direct page retrieval was blocked. This library neither replaces its gate nor issues tokens.
105
+ - [FrontMCP's approval plugin](https://frontmcp.dev/reference/plugins/approval) adds an approval gate/store within that framework.
106
+ - Throughline approvals and Alquimia worklog reducers integrate approval workflows into their ecosystems. They may be preferable if you already use them.
107
+ - ApprovalTests compares snapshots rather than correlating request IDs.
108
+ - `tool-call-closure` settles pending calls; `tool-execution-checkpoint` tracks execution/delivery; `tool-call-budget-audit` audits batch size/completeness. This package only joins approval observations.
109
+
110
+ No claim of first, unique, exhaustive, production-proven or security-certified technology is made. Search coverage is limited to accessible public indexed material as of October 28, 2026.
111
+
112
+ ## Verification and supported runtimes
113
+
114
+ Declared support is Node 20+. Current local execution and CI configuration must be reported separately in release evidence. CI is configured for Node 20,22,24; an unrun matrix is not a passed matrix. Run `npm run check` for syntax checks, unit tests with coverage, file/export boundaries and the shipped synthetic example. Strict TypeScript consumer fixture: `tsc --strict --noEmit --module NodeNext --moduleResolution NodeNext test/types.ts test/types.cts` using an approved TypeScript toolchain.
115
+
116
+ No provider network integration, actual human approval, tool execution, persistence, cryptographic proof or browser UI is exercised by these tests. Release verification also checks the exact tarball through isolated ESM, CommonJS and strict TypeScript consumers.
117
+
118
+ ## Troubleshooting
119
+
120
+ - `consistent:false` with no findings: check partial boundaries and `complete`.
121
+ - `identity-limit`: raise the bounded limit within the hard ceiling or split independent scopes; never silently discard a relevant event.
122
+ - Unknown call/request: verify all relevant earlier messages were exported and the adapter emits call registration before approval.
123
+ - Duplicate findings: inspect replay behavior and identity scope; do not delete diagnostics merely to get a green result.
124
+ - Frozen output errors: copy the result before modifying presentation state.
125
+
126
+ ## Privacy, accessibility and maintenance
127
+
128
+ No network, logging, storage, environment reads, clocks or callbacks are used. Data remains in process memory for the duration of the call and is not returned except fixed codes/counts/indices. The caller remains responsible for sanitized source logs, retention and access control. The CLI example emits plain structured JSON that can be rendered accessibly; this library has no visual interface.
129
+
130
+ See CONTRIBUTING.md, SECURITY.md and CHANGELOG.md. Original code is MIT. Copyright/license notice retention is a license obligation, not a requirement to provide promotional credit.
package/SECURITY.md ADDED
@@ -0,0 +1,5 @@
1
+ # Security and privacy
2
+
3
+ This diagnostic package is not a security boundary or approval authority. It cannot authenticate an approver, verify event truth/completeness, stop execution or prove effects. Do not process secrets or use `consistent` to authorize operations. Getters may execute arbitrary code; prefer sanitized JSON values. No dependency audit can prove this package secure.
4
+
5
+ For a potential vulnerability, use GitHub private vulnerability reporting if enabled on the eventual public repository. Until a verified private channel exists, do not post sensitive details publicly; contact the maintainer through an established private channel. No response SLA is promised. Use synthetic minimal reproductions with no live credentials, tokens, customer identifiers or raw private traces.
@@ -0,0 +1,11 @@
1
+ import { auditApprovalTrace } from '../src/index.js';
2
+ // Synthetic metadata only. Never provide arguments, approval tokens or personal data.
3
+ const trace = [
4
+ { type: 'request', scope: 'run-1', callId: 'call-1', approvalId: 'approval-1' },
5
+ { type: 'call', scope: 'run-1', callId: 'call-1' },
6
+ { type: 'decision', scope: 'run-1', approvalId: 'approval-1', decision: 'approved' },
7
+ { type: 'execution', scope: 'run-1', callId: 'call-1', approvalId: 'approval-1' }
8
+ ];
9
+ const report = auditApprovalTrace(trace);
10
+ if (!report.findings.some(finding => finding.code === 'unknown-call')) throw new Error('Expected adapter ordering diagnostic');
11
+ console.log(JSON.stringify(report, null, 2));
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "tool-approval-correlation-audit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Bounded offline diagnostics for tool approval request, decision and execution observation correlation.",
5
+ "type": "module",
6
+ "main": "./src/index.cjs",
7
+ "module": "./src/index.js",
8
+ "types": "./src/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./src/index.d.ts",
13
+ "default": "./src/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./src/index.d.cts",
17
+ "default": "./src/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "src",
23
+ "examples",
24
+ "README.md",
25
+ "LICENSE",
26
+ "CHANGELOG.md",
27
+ "SECURITY.md"
28
+ ],
29
+ "engines": {
30
+ "node": ">=20"
31
+ },
32
+ "scripts": {
33
+ "test": "node --test --experimental-test-coverage test/*.test.js",
34
+ "lint": "node --check src/index.cjs && node --check src/index.js && node --check test/audit.test.js",
35
+ "build": "node scripts/check-package.js",
36
+ "example": "node examples/audit.js",
37
+ "check": "npm run lint && npm test && npm run build && npm run example"
38
+ },
39
+ "license": "MIT",
40
+ "author": "Huzaifa Asif",
41
+ "keywords": [
42
+ "tool-calling",
43
+ "approval",
44
+ "trace",
45
+ "diagnostics",
46
+ "testing"
47
+ ],
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/Huzaifa-Asif/tool-approval-correlation-audit.git"
51
+ },
52
+ "homepage": "https://github.com/Huzaifa-Asif/tool-approval-correlation-audit#readme",
53
+ "bugs": {
54
+ "url": "https://github.com/Huzaifa-Asif/tool-approval-correlation-audit/issues"
55
+ }
56
+ }
package/src/index.cjs ADDED
@@ -0,0 +1,127 @@
1
+ 'use strict';
2
+
3
+ const DEFAULTS = Object.freeze({ maxEvents: 10000, maxIdentities: 10000, maxIdLength: 256, maxFindings: 1000 });
4
+ const CEILINGS = Object.freeze({ maxEvents: 100000, maxIdentities: 100000, maxIdLength: 4096, maxFindings: 10000 });
5
+
6
+ /** Audit caller-supplied observations, never authorize an operation. */
7
+ function auditApprovalTrace(events, options = {}) {
8
+ let config;
9
+ try {
10
+ if (options === null || typeof options !== 'object' || Array.isArray(options)) throw 0;
11
+ config = {};
12
+ for (const key of Object.keys(DEFAULTS)) {
13
+ const value = options[key];
14
+ config[key] = value === undefined ? DEFAULTS[key] : value;
15
+ if (!Number.isSafeInteger(config[key]) || config[key] < 1 || config[key] > CEILINGS[key]) throw 0;
16
+ }
17
+ for (const key of ['traceStart', 'traceEnd']) {
18
+ const value = options[key];
19
+ config[key] = value === undefined ? 'complete' : value;
20
+ if (config[key] !== 'complete' && config[key] !== 'partial') throw 0;
21
+ }
22
+ } catch { throw new TypeError('Invalid audit options'); }
23
+
24
+ const findings = [];
25
+ const counts = { observed: 0, calls: 0, requests: 0, decisions: 0, executions: 0, identities: 0, unresolved: 0, issues: 0 };
26
+ let complete = config.traceStart === 'complete' && config.traceEnd === 'complete';
27
+ let findingsTruncated = false;
28
+ const scopes = new Map();
29
+ const issue = (code, index, relatedIndex) => {
30
+ counts.issues++;
31
+ if (findings.length < config.maxFindings) findings.push(Object.freeze(relatedIndex === undefined ? { code, index } : { code, index, relatedIndex }));
32
+ else { findingsTruncated = true; complete = false; }
33
+ };
34
+ const id = value => typeof value === 'string' && value.length > 0 && value.length <= config.maxIdLength;
35
+ const allocate = index => {
36
+ if (counts.identities >= config.maxIdentities) { complete = false; issue('identity-limit', index); return false; }
37
+ counts.identities++;
38
+ return true;
39
+ };
40
+ let length = 0;
41
+ try {
42
+ if (!Array.isArray(events)) throw 0;
43
+ const observedLength = events.length;
44
+ if (!Number.isSafeInteger(observedLength) || observedLength < 0) throw 0;
45
+ length = observedLength;
46
+ } catch { complete = false; issue('invalid-trace', -1); }
47
+ const readLength = Math.min(length, config.maxEvents);
48
+ if (length > readLength) { complete = false; issue('event-limit', readLength); }
49
+ for (let index = 0; index < readLength; index++) {
50
+ counts.observed++;
51
+ let event;
52
+ try {
53
+ const raw = events[index];
54
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) throw 0;
55
+ const type = raw.type;
56
+ const scope = raw.scope;
57
+ if (!id(scope) || !['call', 'request', 'decision', 'execution'].includes(type)) throw 0;
58
+ event = { type, scope };
59
+ if (type !== 'decision') { event.callId = raw.callId; if (!id(event.callId)) throw 0; }
60
+ if (type !== 'call') { event.approvalId = raw.approvalId; if (!id(event.approvalId)) throw 0; }
61
+ if (type === 'decision') { event.decision = raw.decision; if (!['approved', 'denied'].includes(event.decision)) throw 0; }
62
+ } catch { complete = false; issue('invalid-event', index); continue; }
63
+ let scope = scopes.get(event.scope);
64
+ if (!scope) {
65
+ if (!allocate(index)) break;
66
+ scope = { calls: new Map(), approvals: new Map() };
67
+ scopes.set(event.scope, scope);
68
+ }
69
+ const call = scope.calls.get(event.callId);
70
+ const approval = scope.approvals.get(event.approvalId);
71
+ if (event.type === 'call') {
72
+ counts.calls++;
73
+ if (call) { call.tainted = true; issue('duplicate-call', index, call.index); }
74
+ else {
75
+ if (!allocate(index)) break;
76
+ scope.calls.set(event.callId, { index, tainted: false, executions: 0, executionIndex: null });
77
+ }
78
+ } else if (event.type === 'request') {
79
+ counts.requests++;
80
+ if (approval) {
81
+ approval.tainted = true;
82
+ issue(approval.callId === event.callId ? 'duplicate-request' : 'approval-rebound', index, approval.index);
83
+ } else {
84
+ if (!allocate(index)) break;
85
+ const missing = !call;
86
+ if (missing) issue('unknown-call', index);
87
+ if (call && call.tainted) issue('ambiguous-call', index, call.index);
88
+ scope.approvals.set(event.approvalId, { index, callId: event.callId, tainted: missing || Boolean(call?.tainted), decision: null, decisionIndex: null, executions: 0 });
89
+ }
90
+ } else if (event.type === 'decision') {
91
+ counts.decisions++;
92
+ if (!approval) issue('decision-without-request', index);
93
+ else if (approval.decision !== null) {
94
+ approval.tainted = true;
95
+ issue(approval.decision === event.decision ? 'duplicate-decision' : 'conflicting-decision', index, approval.decisionIndex);
96
+ } else {
97
+ approval.decision = event.decision;
98
+ approval.decisionIndex = index;
99
+ }
100
+ } else {
101
+ counts.executions++;
102
+ const repeatedCall = Boolean(call && call.executions > 0);
103
+ if (call) {
104
+ if (repeatedCall) issue('duplicate-execution', index, call.executionIndex);
105
+ else call.executionIndex = index;
106
+ call.executions++;
107
+ }
108
+ if (!approval) { issue('execution-without-request', index); continue; }
109
+ if (approval.callId !== event.callId) { approval.tainted = true; issue('execution-call-mismatch', index, approval.index); }
110
+ if (!call) issue('unknown-call', index);
111
+ if (approval.tainted || call?.tainted) issue('ambiguous-approval', index, approval.index);
112
+ if (approval.decision === null) issue('execution-before-decision', index, approval.index);
113
+ else if (approval.decision === 'denied') issue('execution-after-denial', index, approval.decisionIndex);
114
+ if (approval.executions++ && !repeatedCall) issue('duplicate-execution', index, approval.index);
115
+ }
116
+ }
117
+ for (const scope of scopes.values()) {
118
+ for (const approval of scope.approvals.values()) {
119
+ if (approval.decision === null) {
120
+ counts.unresolved++;
121
+ if (config.traceEnd === 'complete') issue('unresolved-request', approval.index);
122
+ }
123
+ }
124
+ }
125
+ return Object.freeze({ consistent: complete && counts.issues === 0, complete, findingsTruncated, counts: Object.freeze(counts), findings: Object.freeze(findings) });
126
+ }
127
+ module.exports = { auditApprovalTrace };
@@ -0,0 +1,22 @@
1
+ export type ApprovalEvent =
2
+ | Readonly<{ type: 'call'; scope: string; callId: string }>
3
+ | Readonly<{ type: 'request'; scope: string; callId: string; approvalId: string }>
4
+ | Readonly<{ type: 'decision'; scope: string; approvalId: string; decision: 'approved' | 'denied' }>
5
+ | Readonly<{ type: 'execution'; scope: string; callId: string; approvalId: string }>;
6
+ export interface AuditOptions {
7
+ readonly maxEvents?: number;
8
+ readonly maxIdentities?: number;
9
+ readonly maxIdLength?: number;
10
+ readonly maxFindings?: number;
11
+ readonly traceStart?: 'complete' | 'partial';
12
+ readonly traceEnd?: 'complete' | 'partial';
13
+ }
14
+ export type FindingCode = 'invalid-trace' | 'invalid-event' | 'event-limit' | 'identity-limit' | 'duplicate-call' | 'duplicate-request' | 'approval-rebound' | 'unknown-call' | 'ambiguous-call' | 'decision-without-request' | 'duplicate-decision' | 'conflicting-decision' | 'execution-without-request' | 'execution-call-mismatch' | 'ambiguous-approval' | 'execution-before-decision' | 'execution-after-denial' | 'duplicate-execution' | 'unresolved-request';
15
+ export interface AuditReport {
16
+ readonly consistent: boolean;
17
+ readonly complete: boolean;
18
+ readonly findingsTruncated: boolean;
19
+ readonly counts: Readonly<{ observed: number; calls: number; requests: number; decisions: number; executions: number; identities: number; unresolved: number; issues: number }>;
20
+ readonly findings: readonly Readonly<{ code: FindingCode; index: number; relatedIndex?: number }>[];
21
+ }
22
+ export declare function auditApprovalTrace(events: readonly ApprovalEvent[], options?: AuditOptions): AuditReport;
package/src/index.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ export type ApprovalEvent =
2
+ | Readonly<{ type: 'call'; scope: string; callId: string }>
3
+ | Readonly<{ type: 'request'; scope: string; callId: string; approvalId: string }>
4
+ | Readonly<{ type: 'decision'; scope: string; approvalId: string; decision: 'approved' | 'denied' }>
5
+ | Readonly<{ type: 'execution'; scope: string; callId: string; approvalId: string }>;
6
+ export interface AuditOptions {
7
+ readonly maxEvents?: number;
8
+ readonly maxIdentities?: number;
9
+ readonly maxIdLength?: number;
10
+ readonly maxFindings?: number;
11
+ readonly traceStart?: 'complete' | 'partial';
12
+ readonly traceEnd?: 'complete' | 'partial';
13
+ }
14
+ export type FindingCode = 'invalid-trace' | 'invalid-event' | 'event-limit' | 'identity-limit' | 'duplicate-call' | 'duplicate-request' | 'approval-rebound' | 'unknown-call' | 'ambiguous-call' | 'decision-without-request' | 'duplicate-decision' | 'conflicting-decision' | 'execution-without-request' | 'execution-call-mismatch' | 'ambiguous-approval' | 'execution-before-decision' | 'execution-after-denial' | 'duplicate-execution' | 'unresolved-request';
15
+ export interface AuditReport {
16
+ readonly consistent: boolean;
17
+ readonly complete: boolean;
18
+ readonly findingsTruncated: boolean;
19
+ readonly counts: Readonly<{ observed: number; calls: number; requests: number; decisions: number; executions: number; identities: number; unresolved: number; issues: number }>;
20
+ readonly findings: readonly Readonly<{ code: FindingCode; index: number; relatedIndex?: number }>[];
21
+ }
22
+ export declare function auditApprovalTrace(events: readonly ApprovalEvent[], options?: AuditOptions): AuditReport;
package/src/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import core from './index.cjs';
2
+ export const { auditApprovalTrace } = core;