Skip to content

Commit 85075e2

Browse files
scotty595claude
andcommitted
Release governance-sdk 0.12.0
- Durable integrity chain: chain state persists to storage atomically and resumes on restart via getChainHead(); legacy adapters degrade gracefully with onAuditError warning - OTel GenAI semantic conventions: dual-emit gen_ai.* attributes alongside governance.* for APM correlation; conventions: 'both' | 'gen_ai' | 'governance' - Honest MCP naming: mcp-allowlist and mcp-call-recorder re-export aliases alongside original mcp-trust and mcp-chain-audit - Fix remote-enforce lastConnected staleness: 4xx errors no longer mark connection as offline — only network failures do - README: clarify Bedrock entry-gate scope and multi-modal gap - Postgres schema: integrity columns in base DDL, BIGINT sequence, unique partial index; auto-migration on connect Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent cdea38a commit 85075e2

16 files changed

Lines changed: 812 additions & 49 deletions

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,25 @@ detection — nothing more. To pre-empt scope questions:
6565
- **Simulator does not replay side effects** — it evaluates policy outcomes
6666
against synthetic scenarios, it does not execute tools.
6767
- **`enforce()` does not hash-chain by default** — opt in with
68-
`integrityAudit: { signingKey }` for tamper-evident audit.
68+
`integrityAudit: { signingKey }` for tamper-evident audit. Since 0.12
69+
the chain is persisted durably (survives process restart) when the
70+
storage adapter supports `createAuditEventWithIntegrity` (memory and
71+
Postgres adapters both do). HMAC chains are still only tamper-evident
72+
to holders of the signing secret — rotate and pair with an external
73+
anchor if you need adversary-grade non-repudiation.
6974
- **Cloud `register()` is a synthetic confirmation** — the API auto-registers
7075
on first `enforce()`.
7176
- **No built-in red team / jailbreak harness.** Use inspect-ai, PyRIT, or
7277
Garak — a policy-only harness would be easily mistaken for model coverage.
78+
- **Bedrock is entry-gate only.** The Bedrock adapter scans the prompt
79+
going into `invokeAgent` and (with a helper) the final response text.
80+
Tool executions **inside** AWS action groups are opaque — the adapter
81+
cannot see them, let alone block them. Use `guardToolUse()` to enforce
82+
at the tool level manually, or push tool calls onto the host side.
83+
- **Multi-modal content is not scanned.** Image, PDF, and audio blocks
84+
on Anthropic/Vercel AI/Genkit/LlamaIndex/Bedrock are passed through
85+
without injection detection. A vision-enabled agent bypasses every
86+
input scan in this release. Multi-modal coverage lands in 0.13.
7387

7488
## Packages
7589

packages/governance/CHANGELOG.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,91 @@
11
# Changelog
22

3+
## [0.12.0] - 2026-04-16 — Trust hardening
4+
5+
Closes the three most load-bearing honesty gaps surfaced by the post-0.11
6+
audit. Theme: the things the SDK already claims must actually hold up under
7+
restart, real observability, and real naming.
8+
9+
### Changed — integrity audit chain is now durable (BREAKING-ISH)
10+
11+
Before 0.12, `integrityAudit: { signingKey }` maintained chain state
12+
(latest hash, sequence, per-event integrity) in a `createGovernance()`
13+
closure. Process restart reset the chain to genesis and every event in
14+
Postgres lost its integrity metadata because the write path never touched
15+
the `integrity_*` columns the schema already defined.
16+
17+
**What changed:**
18+
- `GovernanceStorage` gained three optional methods —
19+
`createAuditEventWithIntegrity(event, integrity)`, `getChainHead()`,
20+
`getAuditIntegrity(eventId)`. Memory and Postgres adapters implement
21+
all three.
22+
- `createGovernance()` now writes the event and its integrity metadata
23+
in a single `INSERT` when the storage adapter is integrity-aware, and
24+
resumes the chain from `getChainHead()` on boot. Kill the process
25+
mid-stream, boot a fresh instance, and `integrityChain.stats()`
26+
returns the pre-crash sequence; `verifyAuditIntegrity()` passes across
27+
the restart boundary.
28+
- Third-party storage adapters written against the 0.11 interface still
29+
work. They fall back to the old in-process integrity map and emit an
30+
`onAuditError` notice explaining the chain is session-local on that
31+
adapter.
32+
33+
**Schema:** the base `getSchemaSQL()` now creates the integrity columns
34+
on fresh tables; the existing `getIntegrityMigrationSQL()` remains for
35+
0.11.x tables. Both paths are idempotent (`CREATE TABLE IF NOT EXISTS`,
36+
`ADD COLUMN IF NOT EXISTS`). `integrity_sequence` widened from `INTEGER`
37+
to `BIGINT`. A `UNIQUE` index on `integrity_sequence` enforces no
38+
duplicate sequences even under concurrent writers.
39+
40+
**Honesty update:** the "What this is NOT" section in the README was
41+
rewritten to state what HMAC chains prove and don't prove. No more
42+
"tamper-evident" without the caveat.
43+
44+
### Changed — OTel GenAI semantic conventions
45+
46+
`createOtelHooks()` gained a `conventions: "governance" | "gen_ai" | "both"`
47+
option. `"both"` (the 0.12 default) is additive: existing `governance.*`
48+
attributes and operation names still emit, and `gen_ai.system`,
49+
`gen_ai.request.model`, `gen_ai.usage.input_tokens` /
50+
`gen_ai.usage.output_tokens`, `gen_ai.response.finish_reasons`,
51+
`gen_ai.tool.name`, `gen_ai.tool.call.id` appear alongside when present
52+
in the event detail. `"gen_ai"` switches operation names to the GenAI
53+
form (`gen_ai.policy.evaluate`, `gen_ai.tool.execute`,
54+
`gen_ai.agent.register`, `gen_ai.audit.log`) so governance spans can
55+
correlate with Anthropic / OpenAI / Vercel-AI SDK spans in Honeycomb /
56+
Datadog / New Relic. The default flips to `"gen_ai"` in 0.13.
57+
58+
### Changed — honest naming for MCP plugins
59+
60+
`createMCPTrustRegistry` is a URI allowlist, not a cryptographic trust
61+
registry; `createChainAuditor` records caller-reported MCP calls, not
62+
auto-propagated sub-calls. Both are now also exported under honest
63+
names:
64+
65+
- `createMCPAllowlist` (new export path:
66+
`governance-sdk/plugins/mcp-allowlist`)
67+
- `createMCPCallRecorder` (new export path:
68+
`governance-sdk/plugins/mcp-call-recorder`)
69+
70+
The original exports stay at their original paths and behave
71+
identically. Rename on your next touch of the file; no rush.
72+
73+
### Fixed — remote status staleness after 4xx errors
74+
75+
`createRemoteEnforcer().status()` flipped `connected: false` whenever
76+
the last `enforce()` call threw a `RemoteEnforcementError`, even on a
77+
non-retryable 4xx. A 4xx means the API answered us — the connection is
78+
fine. Status now stays `connected: true` through API-layer errors and
79+
only reports `connected: false` on a network/timeout failure.
80+
81+
### Roadmap (0.13+)
82+
83+
Not in this release; on the roadmap:
84+
- Shipped ML injection classifier as an opt-in peer-dep package.
85+
- Multi-modal input scanning (image / PDF / audio) on Anthropic / Vercel
86+
AI / Genkit / LlamaIndex / Bedrock.
87+
- Compliance evidence export (signed, dated dossiers).
88+
389
## [0.11.2] - 2026-04-16 — Automate README sync
490

591
Adds infrastructure to prevent the npm README from drifting out of sync

packages/governance/README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,25 @@ detection — nothing more. To pre-empt scope questions:
6565
- **Simulator does not replay side effects** — it evaluates policy outcomes
6666
against synthetic scenarios, it does not execute tools.
6767
- **`enforce()` does not hash-chain by default** — opt in with
68-
`integrityAudit: { signingKey }` for tamper-evident audit.
68+
`integrityAudit: { signingKey }` for tamper-evident audit. Since 0.12
69+
the chain is persisted durably (survives process restart) when the
70+
storage adapter supports `createAuditEventWithIntegrity` (memory and
71+
Postgres adapters both do). HMAC chains are still only tamper-evident
72+
to holders of the signing secret — rotate and pair with an external
73+
anchor if you need adversary-grade non-repudiation.
6974
- **Cloud `register()` is a synthetic confirmation** — the API auto-registers
7075
on first `enforce()`.
7176
- **No built-in red team / jailbreak harness.** Use inspect-ai, PyRIT, or
7277
Garak — a policy-only harness would be easily mistaken for model coverage.
78+
- **Bedrock is entry-gate only.** The Bedrock adapter scans the prompt
79+
going into `invokeAgent` and (with a helper) the final response text.
80+
Tool executions **inside** AWS action groups are opaque — the adapter
81+
cannot see them, let alone block them. Use `guardToolUse()` to enforce
82+
at the tool level manually, or push tool calls onto the host side.
83+
- **Multi-modal content is not scanned.** Image, PDF, and audio blocks
84+
on Anthropic/Vercel AI/Genkit/LlamaIndex/Bedrock are passed through
85+
without injection detection. A vision-enabled agent bypasses every
86+
input scan in this release. Multi-modal coverage lands in 0.13.
7387

7488
## Packages
7589

packages/governance/package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "governance-sdk",
3-
"version": "0.11.2",
3+
"version": "0.12.0",
44
"description": "AI Agent Governance for TypeScript — policy enforcement, scoring, compliance, and audit for AI agents",
55
"type": "module",
66
"main": "./dist/index.js",
@@ -161,10 +161,18 @@
161161
"types": "./dist/plugins/mcp-trust.d.ts",
162162
"import": "./dist/plugins/mcp-trust.js"
163163
},
164+
"./plugins/mcp-allowlist": {
165+
"types": "./dist/plugins/mcp-allowlist.d.ts",
166+
"import": "./dist/plugins/mcp-allowlist.js"
167+
},
164168
"./plugins/mcp-chain-audit": {
165169
"types": "./dist/plugins/mcp-chain-audit.d.ts",
166170
"import": "./dist/plugins/mcp-chain-audit.js"
167171
},
172+
"./plugins/mcp-call-recorder": {
173+
"types": "./dist/plugins/mcp-call-recorder.d.ts",
174+
"import": "./dist/plugins/mcp-call-recorder.js"
175+
},
168176
"./injection-classifier": {
169177
"types": "./dist/injection-classifier.d.ts",
170178
"import": "./dist/injection-classifier.js"
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* Integration test: the HMAC integrity chain must survive process restart.
3+
*
4+
* Simulates the restart by discarding the original createGovernance()
5+
* instance (closures gone, chain state lost) and creating a fresh one
6+
* against the same storage. The second instance must resume the chain
7+
* and produce events whose sequence continues from where the first
8+
* instance left off, with verifyAuditIntegrity passing end-to-end.
9+
*/
10+
11+
import { describe, it } from "node:test";
12+
import assert from "node:assert/strict";
13+
import { createGovernance, createMemoryStorage } from "./index.js";
14+
import { verifyAuditIntegrity } from "./audit-integrity-verify.js";
15+
import type { GovernanceStorage } from "./storage.js";
16+
17+
const KEY = "test-signing-key-0.12-restart";
18+
19+
async function writeSomeEvents(gov: Awaited<ReturnType<typeof createGovernance>>, count: number) {
20+
for (let i = 0; i < count; i++) {
21+
await gov.audit.log({
22+
agentId: "restart-agent",
23+
eventType: "test_event",
24+
outcome: "success",
25+
severity: "info",
26+
detail: { iteration: i },
27+
});
28+
}
29+
}
30+
31+
describe("integrity chain restart durability (0.12)", () => {
32+
it("resumes sequence from storage on fresh createGovernance() call", async () => {
33+
const storage: GovernanceStorage = createMemoryStorage();
34+
35+
const gov1 = createGovernance({ storage, integrityAudit: { signingKey: KEY } });
36+
await writeSomeEvents(gov1, 5);
37+
const stats1 = gov1.integrityChain!.stats();
38+
assert.equal(stats1.latestSequence, 5, "first instance wrote sequences 1..5");
39+
const firstHash = stats1.latestHash;
40+
41+
// Simulate restart: drop gov1 entirely, keep storage.
42+
const gov2 = createGovernance({ storage, integrityAudit: { signingKey: KEY } });
43+
// Write one more event — must pick up at sequence 6 and chain to firstHash.
44+
await writeSomeEvents(gov2, 1);
45+
const stats2 = gov2.integrityChain!.stats();
46+
assert.equal(stats2.latestSequence, 6, "second instance resumed at sequence 6");
47+
assert.notEqual(stats2.latestHash, firstHash, "new event produced new head");
48+
49+
// Full chain must verify end-to-end across the boundary.
50+
const chain = await gov2.integrityChain!.export();
51+
assert.equal(chain.length, 6, "export includes all 6 events");
52+
assert.deepEqual(
53+
chain.map((e) => e.integrity.sequence),
54+
[1, 2, 3, 4, 5, 6],
55+
"sequences are contiguous across restart",
56+
);
57+
58+
const verification = await verifyAuditIntegrity(chain, KEY);
59+
assert.equal(verification.valid, true, verification.breakDetail ?? "chain should verify");
60+
assert.equal(verification.eventsVerified, 6);
61+
});
62+
63+
it("getChainHead returns null for empty storage (cold start)", async () => {
64+
const storage = createMemoryStorage();
65+
const head = await storage.getChainHead!();
66+
assert.equal(head, null);
67+
});
68+
69+
it("legacy storage adapter (no createAuditEventWithIntegrity) still works but warns", async () => {
70+
// Build an adapter that implements the core interface but omits the
71+
// new integrity methods, emulating a third-party 0.11.x adapter.
72+
const base = createMemoryStorage();
73+
const legacy: GovernanceStorage = {
74+
createAgent: base.createAgent,
75+
getAgent: base.getAgent,
76+
getAgentByName: base.getAgentByName,
77+
listAgents: base.listAgents,
78+
updateAgent: base.updateAgent,
79+
deleteAgent: base.deleteAgent,
80+
createAuditEvent: base.createAuditEvent,
81+
queryAuditEvents: base.queryAuditEvents,
82+
countAuditEvents: base.countAuditEvents,
83+
// intentionally omit: createAuditEventWithIntegrity, getChainHead, getAuditIntegrity
84+
};
85+
86+
const warnings: unknown[] = [];
87+
const gov = createGovernance({
88+
storage: legacy,
89+
integrityAudit: { signingKey: KEY },
90+
onAuditError: (e) => warnings.push(e),
91+
});
92+
93+
await writeSomeEvents(gov, 2);
94+
const chain = await gov.integrityChain!.export();
95+
assert.equal(chain.length, 2, "chain export still works via in-memory fallback");
96+
const verification = await verifyAuditIntegrity(chain, KEY);
97+
assert.equal(verification.valid, true);
98+
assert.ok(
99+
warnings.length >= 2,
100+
"onAuditError fired at least once per write on legacy adapter",
101+
);
102+
assert.ok(
103+
warnings.every((w) => w instanceof Error && /chain is session-local/.test(w.message)),
104+
"warning explains the session-local downgrade",
105+
);
106+
});
107+
});

0 commit comments

Comments
 (0)