@fluxpointstudios/orynq-sdk-process-trace 0.1.0 → 0.3.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.
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @summary Round-2 hardening regression tests for process-trace (#58, #59).
3
+ *
4
+ * 1. eip712 governance verifier must pin/validate the attacker-controlled
5
+ * `event.eip712.{domain,primaryType,types}` against the expected
6
+ * governance-attestation schema — an unexpected domain/primaryType is
7
+ * rejected even when the raw signature "verifies".
8
+ * 2. verifyBundle must also verify the publicView model-manifest fields (the
9
+ * shared artifact an external verifier reads), not just privateRun's — a
10
+ * fabricated publicView.modelManifestHash must fail.
11
+ */
12
+
13
+ import { describe, it, expect } from "vitest";
14
+ import {
15
+ createTrace,
16
+ addSpan,
17
+ addEvent,
18
+ closeSpan,
19
+ finalizeTrace,
20
+ verifyBundle,
21
+ verifyGovernanceAttestations,
22
+ createEip712GovernanceVerifier,
23
+ } from "../index.js";
24
+ import type { TraceBundle, TraceRun } from "../index.js";
25
+
26
+ const EXPECTED_DOMAIN = { name: "Orynq", version: "1" };
27
+ const EXPECTED_TYPES = {
28
+ Attestation: [
29
+ { name: "role", type: "string" },
30
+ { name: "policyRef", type: "string" },
31
+ { name: "decisionRef", type: "string" },
32
+ { name: "runId", type: "string" },
33
+ { name: "signedAt", type: "string" },
34
+ ],
35
+ };
36
+
37
+ async function eip712Bundle(bindingOverrides: Record<string, unknown>): Promise<TraceBundle> {
38
+ const run = await createTrace({ agentId: "agent-1" });
39
+ const span = addSpan(run, { name: "approve", visibility: "public" });
40
+ const signedAt = new Date().toISOString();
41
+ await addEvent(run, span.id, {
42
+ kind: "governance-attestation",
43
+ visibility: "public",
44
+ role: "compliance",
45
+ policyRef: "sha256:policy",
46
+ decisionRef: "decision-1",
47
+ attestor: {
48
+ address: "0x1111111111111111111111111111111111111111",
49
+ signatureScheme: "eip712",
50
+ },
51
+ signature: "0x" + "ab".repeat(65),
52
+ signedAt,
53
+ eip712: {
54
+ domain: EXPECTED_DOMAIN,
55
+ types: EXPECTED_TYPES,
56
+ primaryType: "Attestation",
57
+ message: {
58
+ role: "compliance",
59
+ policyRef: "sha256:policy",
60
+ decisionRef: "decision-1",
61
+ runId: run.id,
62
+ signedAt,
63
+ },
64
+ ...bindingOverrides,
65
+ },
66
+ });
67
+ await closeSpan(run, span.id);
68
+ return finalizeTrace(run);
69
+ }
70
+
71
+ describe("eip712 domain/primaryType pinning (#58)", () => {
72
+ const ATTESTOR = "0x1111111111111111111111111111111111111111";
73
+
74
+ it("rejects an unexpected primaryType even when the raw signature verifies", async () => {
75
+ const bundle = await eip712Bundle({ primaryType: "EvilType" });
76
+ const verifier = createEip712GovernanceVerifier({
77
+ verifyTypedData: async () => true, // signature is 'valid' over attacker data
78
+ expectedDomain: EXPECTED_DOMAIN,
79
+ expectedPrimaryType: "Attestation",
80
+ expectedTypes: EXPECTED_TYPES,
81
+ });
82
+ const summaries = await verifyGovernanceAttestations(bundle, {
83
+ verifiers: { eip712: verifier },
84
+ authorizedAttestors: [ATTESTOR],
85
+ });
86
+ expect(summaries[0]!.authorized).toBe(true);
87
+ expect(summaries[0]!.verified).toBe(false);
88
+ });
89
+
90
+ it("rejects an unexpected domain (attacker swaps verifyingContract/chainId)", async () => {
91
+ const bundle = await eip712Bundle({
92
+ domain: { name: "Orynq", version: "1", chainId: 1, verifyingContract: "0xevil" },
93
+ });
94
+ const verifier = createEip712GovernanceVerifier({
95
+ verifyTypedData: async () => true,
96
+ expectedDomain: EXPECTED_DOMAIN,
97
+ expectedPrimaryType: "Attestation",
98
+ expectedTypes: EXPECTED_TYPES,
99
+ });
100
+ const summaries = await verifyGovernanceAttestations(bundle, {
101
+ verifiers: { eip712: verifier },
102
+ authorizedAttestors: [ATTESTOR],
103
+ });
104
+ expect(summaries[0]!.authorized).toBe(true);
105
+ expect(summaries[0]!.verified).toBe(false);
106
+ });
107
+
108
+ it("accepts the expected domain + primaryType", async () => {
109
+ const bundle = await eip712Bundle({});
110
+ const verifier = createEip712GovernanceVerifier({
111
+ verifyTypedData: async () => true,
112
+ expectedDomain: EXPECTED_DOMAIN,
113
+ expectedPrimaryType: "Attestation",
114
+ expectedTypes: EXPECTED_TYPES,
115
+ });
116
+ const summaries = await verifyGovernanceAttestations(bundle, {
117
+ verifiers: { eip712: verifier },
118
+ authorizedAttestors: [ATTESTOR],
119
+ });
120
+ expect(summaries[0]!.verified).toBe(true);
121
+ });
122
+ });
123
+
124
+ describe("publicView model-manifest verification (#59)", () => {
125
+ async function manifestBundle(): Promise<TraceBundle> {
126
+ const run: TraceRun = await createTrace({
127
+ agentId: "agent-m",
128
+ manifest: {
129
+ modelHash: "sha256:" + "1".repeat(64),
130
+ modelId: "gpt-x",
131
+ framework: "acme",
132
+ },
133
+ });
134
+ const span = addSpan(run, { name: "work", visibility: "public" });
135
+ await addEvent(run, span.id, { kind: "command", command: "go", visibility: "public" });
136
+ await closeSpan(run, span.id);
137
+ return finalizeTrace(run);
138
+ }
139
+
140
+ it("an honest bundle with a pinned manifest verifies", async () => {
141
+ const bundle = await manifestBundle();
142
+ expect(bundle.publicView.modelManifestHash).toBeDefined();
143
+ const result = await verifyBundle(bundle, { });
144
+ expect(result.checks.modelManifestValid).toBe(true);
145
+ expect(result.valid).toBe(true);
146
+ });
147
+
148
+ it("a fabricated publicView.modelManifestHash fails verification", async () => {
149
+ const bundle = await manifestBundle();
150
+ const forged = JSON.parse(JSON.stringify(bundle)) as TraceBundle;
151
+ // privateRun manifest is left honest; only the shared publicView field an
152
+ // external verifier reads is fabricated.
153
+ forged.publicView.modelManifestHash = "f".repeat(64);
154
+
155
+ const result = await verifyBundle(forged);
156
+ expect(result.checks.modelManifestValid).toBe(false);
157
+ expect(result.valid).toBe(false);
158
+ });
159
+
160
+ it("a fabricated publicView.modelManifest (recomputes to a different hash) fails", async () => {
161
+ const bundle = await manifestBundle();
162
+ const forged = JSON.parse(JSON.stringify(bundle)) as TraceBundle;
163
+ (forged.publicView.modelManifest as Record<string, unknown>) = {
164
+ modelHash: "sha256:" + "9".repeat(64),
165
+ modelId: "EVIL",
166
+ framework: "attacker",
167
+ };
168
+
169
+ const result = await verifyBundle(forged);
170
+ expect(result.checks.modelManifestValid).toBe(false);
171
+ expect(result.valid).toBe(false);
172
+ });
173
+ });
@@ -0,0 +1,208 @@
1
+ /**
2
+ * @summary Round-3 hardening regression tests for process-trace governance (#58).
3
+ *
4
+ * 1. eip712 schema pins are MANDATORY. An attacker signs an EMPTY `Attestation`
5
+ * struct (`types:{Attestation:[]}`) with their own key and carries
6
+ * role/policyRef/decisionRef/runId as UNTYPED message extras — viem verifies
7
+ * the empty struct and the field-binding post-check reads never-signed
8
+ * fields. The verifier must reject unless the primaryType's declared type
9
+ * fields actually include role/policyRef/decisionRef/runId.
10
+ * 2. Governance attestations require an authorized-attestor allow-list. An
11
+ * attestation from an unlisted signer must NOT count toward a passing
12
+ * verdict, and with no allow-list governance verification must fail closed.
13
+ */
14
+
15
+ import { describe, it, expect } from "vitest";
16
+ import {
17
+ createTrace,
18
+ addSpan,
19
+ addEvent,
20
+ closeSpan,
21
+ finalizeTrace,
22
+ verifyBundle,
23
+ addGovernanceAttestation,
24
+ createSr25519GovernanceSigner,
25
+ verifyGovernanceAttestations,
26
+ createEip712GovernanceVerifier,
27
+ } from "../index.js";
28
+ import type { TraceBundle, GovernanceAttestationEvent } from "../index.js";
29
+
30
+ const SEED_A = "0x" + "11".repeat(32);
31
+ const SEED_B = "0x" + "22".repeat(32);
32
+
33
+ const EXPECTED_DOMAIN = { name: "Orynq", version: "1" };
34
+ const EXPECTED_TYPES = {
35
+ Attestation: [
36
+ { name: "role", type: "string" },
37
+ { name: "policyRef", type: "string" },
38
+ { name: "decisionRef", type: "string" },
39
+ { name: "runId", type: "string" },
40
+ { name: "signedAt", type: "string" },
41
+ ],
42
+ };
43
+
44
+ async function eip712Bundle(binding: Record<string, unknown>): Promise<TraceBundle> {
45
+ const run = await createTrace({ agentId: "agent-1" });
46
+ const span = addSpan(run, { name: "approve", visibility: "public" });
47
+ const signedAt = new Date().toISOString();
48
+ await addEvent(run, span.id, {
49
+ kind: "governance-attestation",
50
+ visibility: "public",
51
+ role: "release-authority",
52
+ policyRef: "sha256:policy",
53
+ decisionRef: "decision-1",
54
+ attestor: {
55
+ address: "0x1111111111111111111111111111111111111111",
56
+ signatureScheme: "eip712",
57
+ },
58
+ signature: "0x" + "ab".repeat(65),
59
+ signedAt,
60
+ eip712: {
61
+ domain: EXPECTED_DOMAIN,
62
+ types: EXPECTED_TYPES,
63
+ primaryType: "Attestation",
64
+ message: {
65
+ role: "release-authority",
66
+ policyRef: "sha256:policy",
67
+ decisionRef: "decision-1",
68
+ runId: run.id,
69
+ signedAt,
70
+ },
71
+ ...binding,
72
+ },
73
+ });
74
+ await closeSpan(run, span.id);
75
+ return finalizeTrace(run);
76
+ }
77
+
78
+ describe("eip712 schema pins are mandatory (#58 round-3)", () => {
79
+ it("rejects an EMPTY Attestation struct that carries fields as untyped extras", async () => {
80
+ // The signed struct declares NO fields; role/policyRef/decisionRef/runId ride
81
+ // as untyped message extras the signature does not commit to.
82
+ const bundle = await eip712Bundle({
83
+ types: { Attestation: [] },
84
+ });
85
+ const verifier = createEip712GovernanceVerifier({
86
+ verifyTypedData: async () => true, // empty struct 'verifies' with attacker key
87
+ expectedDomain: EXPECTED_DOMAIN,
88
+ expectedPrimaryType: "Attestation",
89
+ expectedTypes: EXPECTED_TYPES,
90
+ });
91
+ const summaries = await verifyGovernanceAttestations(bundle, {
92
+ verifiers: { eip712: verifier },
93
+ authorizedAttestors: ["0x1111111111111111111111111111111111111111"],
94
+ });
95
+ expect(summaries[0]!.verified).toBe(false);
96
+ });
97
+
98
+ it("throws at construction when the schema pins are omitted", () => {
99
+ expect(() =>
100
+ // @ts-expect-error — pins are now mandatory
101
+ createEip712GovernanceVerifier({ verifyTypedData: async () => true })
102
+ ).toThrow(/expectedTypes|expectedDomain|expectedPrimaryType|required/i);
103
+ });
104
+
105
+ it("throws when the pinned primaryType omits a required field", () => {
106
+ expect(() =>
107
+ createEip712GovernanceVerifier({
108
+ verifyTypedData: async () => true,
109
+ expectedDomain: EXPECTED_DOMAIN,
110
+ expectedPrimaryType: "Attestation",
111
+ expectedTypes: {
112
+ Attestation: [
113
+ { name: "role", type: "string" },
114
+ { name: "policyRef", type: "string" },
115
+ // decisionRef + runId missing → signature would not commit to them
116
+ ],
117
+ },
118
+ })
119
+ ).toThrow(/decisionRef|runId|must include/i);
120
+ });
121
+
122
+ it("accepts a correctly-typed genuine attestation", async () => {
123
+ const bundle = await eip712Bundle({});
124
+ const verifier = createEip712GovernanceVerifier({
125
+ verifyTypedData: async () => true,
126
+ expectedDomain: EXPECTED_DOMAIN,
127
+ expectedPrimaryType: "Attestation",
128
+ expectedTypes: EXPECTED_TYPES,
129
+ });
130
+ const summaries = await verifyGovernanceAttestations(bundle, {
131
+ verifiers: { eip712: verifier },
132
+ authorizedAttestors: ["0x1111111111111111111111111111111111111111"],
133
+ });
134
+ expect(summaries[0]!.verified).toBe(true);
135
+ });
136
+ });
137
+
138
+ describe("governance requires an authorized-attestor allow-list (#58 round-3)", () => {
139
+ async function attestedBundle(): Promise<{ bundle: TraceBundle; attestor: string }> {
140
+ const run = await createTrace({ agentId: "agent-1" });
141
+ const span = addSpan(run, { name: "release", visibility: "public" });
142
+ const decision = await addEvent(run, span.id, {
143
+ kind: "decision",
144
+ decision: "ship model v2",
145
+ visibility: "public",
146
+ });
147
+ const signer = await createSr25519GovernanceSigner({ seed: SEED_A });
148
+ await addGovernanceAttestation(run, span.id, {
149
+ role: "release-authority",
150
+ policyRef: "sha256:policy-doc-hash",
151
+ decisionRef: decision.id,
152
+ signer,
153
+ });
154
+ await closeSpan(run, span.id);
155
+ return { bundle: await finalizeTrace(run), attestor: signer.address };
156
+ }
157
+
158
+ it("an attestation from an UNLISTED signer is not authorized", async () => {
159
+ const { bundle } = await attestedBundle();
160
+ const otherSigner = await createSr25519GovernanceSigner({ seed: SEED_B });
161
+ const summaries = await verifyGovernanceAttestations(bundle, {
162
+ authorizedAttestors: [otherSigner.address], // genuine signer NOT listed
163
+ });
164
+ expect(summaries[0]!.verified).toBe(false);
165
+ expect(summaries[0]!.authorized).toBe(false);
166
+ });
167
+
168
+ it("an attestation from an ALLOW-LISTED signer verifies", async () => {
169
+ const { bundle, attestor } = await attestedBundle();
170
+ const summaries = await verifyGovernanceAttestations(bundle, {
171
+ authorizedAttestors: [attestor],
172
+ });
173
+ expect(summaries[0]!.verified).toBe(true);
174
+ expect(summaries[0]!.authorized).toBe(true);
175
+ });
176
+
177
+ it("with NO allow-list, governance fails closed and does not pass the bundle", async () => {
178
+ const { bundle } = await attestedBundle();
179
+ // A genuine self-signed attestation with a valid signature — but nobody said
180
+ // this key is a real release-authority. It must not fold into a pass.
181
+ const summaries = await verifyGovernanceAttestations(bundle);
182
+ expect(summaries[0]!.verified).toBe(false);
183
+ expect(summaries[0]!.authorized).toBe(false);
184
+
185
+ const result = await verifyBundle(bundle, { governance: true });
186
+ expect(result.checks.governanceValid).toBe(false);
187
+ expect(result.valid).toBe(false);
188
+ });
189
+
190
+ it("verifyBundle with an allow-list folds a real sign-off into valid", async () => {
191
+ const { bundle, attestor } = await attestedBundle();
192
+ const result = await verifyBundle(bundle, {
193
+ governance: { authorizedAttestors: [attestor] },
194
+ });
195
+ expect(result.checks.governanceValid).toBe(true);
196
+ expect(result.valid).toBe(true);
197
+ });
198
+
199
+ it("an unlisted signer fails the bundle", async () => {
200
+ const { bundle } = await attestedBundle();
201
+ const other = await createSr25519GovernanceSigner({ seed: SEED_B });
202
+ const result = await verifyBundle(bundle, {
203
+ governance: { authorizedAttestors: [other.address] },
204
+ });
205
+ expect(result.checks.governanceValid).toBe(false);
206
+ expect(result.valid).toBe(false);
207
+ });
208
+ });
@@ -0,0 +1,136 @@
1
+ /**
2
+ * @summary Tests for pre-execution model-manifest pinning (issue #59).
3
+ */
4
+
5
+ import { describe, it, expect, vi } from "vitest";
6
+ import {
7
+ createTrace,
8
+ addSpan,
9
+ addEvent,
10
+ closeSpan,
11
+ finalizeTrace,
12
+ verifyBundle,
13
+ computeModelManifestHash,
14
+ validateModelManifest,
15
+ manifestFromHuggingFace,
16
+ manifestFromOpenAI,
17
+ manifestFromAnthropic,
18
+ } from "../index.js";
19
+ import type { ModelManifest } from "../index.js";
20
+
21
+ async function sampleManifest(): Promise<ModelManifest> {
22
+ return manifestFromHuggingFace({
23
+ modelId: "meta-llama/Llama-3.1-8B",
24
+ revision: "0e9e39f249a16976918f6564b8830bc894c89659",
25
+ tokenizerHash: "sha256:deadbeef",
26
+ });
27
+ }
28
+
29
+ describe("model-manifest builders", () => {
30
+ it("produces a deterministic manifestHash for identical inputs", async () => {
31
+ const a = await manifestFromHuggingFace({ modelId: "m", revision: "abc" });
32
+ const b = await manifestFromHuggingFace({ modelId: "m", revision: "abc" });
33
+ expect(await computeModelManifestHash(a)).toBe(await computeModelManifestHash(b));
34
+ });
35
+
36
+ it("changes the manifestHash when the revision changes", async () => {
37
+ const a = await manifestFromHuggingFace({ modelId: "m", revision: "abc" });
38
+ const c = await manifestFromHuggingFace({ modelId: "m", revision: "def" });
39
+ expect(await computeModelManifestHash(a)).not.toBe(await computeModelManifestHash(c));
40
+ });
41
+
42
+ it("records the framework and identity for OpenAI / Anthropic", async () => {
43
+ const openai = await manifestFromOpenAI({ model: "gpt-4o", snapshotId: "gpt-4o-2024-08-06" });
44
+ expect(openai.framework).toBe("openai");
45
+ expect(openai.modelId).toBe("gpt-4o");
46
+ expect(openai.modelHash.startsWith("sha256:")).toBe(true);
47
+
48
+ const anthropic = await manifestFromAnthropic({
49
+ model: "claude-3-5-sonnet",
50
+ snapshotId: "claude-3-5-sonnet-20241022",
51
+ });
52
+ expect(anthropic.framework).toBe("anthropic");
53
+ expect(anthropic.revision).toBe("claude-3-5-sonnet-20241022");
54
+ });
55
+
56
+ it("hashes a raw system prompt into systemPromptHash", async () => {
57
+ const m = await manifestFromHuggingFace({
58
+ modelId: "m",
59
+ systemPrompt: "You are a helpful assistant.",
60
+ });
61
+ expect(m.systemPromptHash?.startsWith("sha256:")).toBe(true);
62
+ });
63
+
64
+ it("validateModelManifest requires a modelHash", () => {
65
+ expect(() => validateModelManifest({ modelHash: "" } as ModelManifest)).toThrow(/modelHash/);
66
+ });
67
+ });
68
+
69
+ describe("createTrace manifest pinning", () => {
70
+ it("pins the manifest hash at creation and freezes the manifest", async () => {
71
+ const manifest = await sampleManifest();
72
+ const run = await createTrace({ agentId: "agent-1", manifest });
73
+
74
+ expect(run.modelManifestHash).toBeDefined();
75
+ expect(run.modelManifestHash).toBe(await computeModelManifestHash(manifest));
76
+ expect(run.modelManifest).toBe(manifest);
77
+
78
+ // Immutability: mutating the pinned manifest throws (frozen).
79
+ expect(() => {
80
+ (run.modelManifest as unknown as Record<string, unknown>).modelHash = "tampered";
81
+ }).toThrow();
82
+ });
83
+
84
+ it("rejects strict createTrace without a manifest", async () => {
85
+ await expect(createTrace({ agentId: "agent-1", strict: true })).rejects.toThrow(
86
+ /strict mode requires/i
87
+ );
88
+ });
89
+ });
90
+
91
+ describe("finalizeTrace manifest enforcement", () => {
92
+ it("carries the manifest onto the bundle and public view, and verifies", async () => {
93
+ const manifest = await manifestFromOpenAI({
94
+ model: "gpt-4o",
95
+ snapshotId: "gpt-4o-2024-08-06",
96
+ });
97
+ const run = await createTrace({ agentId: "agent-1", manifest, strict: true });
98
+ const span = addSpan(run, { name: "infer", visibility: "public" });
99
+ await addEvent(run, span.id, {
100
+ kind: "observation",
101
+ observation: "ok",
102
+ visibility: "public",
103
+ });
104
+ await closeSpan(run, span.id);
105
+
106
+ const bundle = await finalizeTrace(run);
107
+
108
+ expect(bundle.modelManifestHash).toBe(run.modelManifestHash);
109
+ expect(bundle.modelManifest?.framework).toBe("openai");
110
+ expect(bundle.publicView.modelManifestHash).toBe(run.modelManifestHash);
111
+
112
+ const result = await verifyBundle(bundle);
113
+ expect(result.valid).toBe(true);
114
+ expect(result.errors).toHaveLength(0);
115
+ });
116
+
117
+ it("refuses to finalize a strict run with no manifest", async () => {
118
+ const run = await createTrace({ agentId: "agent-1" });
119
+ run.strict = true; // simulate a run that should have been pinned
120
+ await expect(finalizeTrace(run)).rejects.toThrow(/strict mode requires a model manifest/i);
121
+ });
122
+
123
+ it("warns (warn-only path) when finalizing an unpinned non-strict run", async () => {
124
+ const run = await createTrace({ agentId: "agent-1" });
125
+ const span = addSpan(run, { name: "s", visibility: "public" });
126
+ await addEvent(run, span.id, { kind: "observation", observation: "ok", visibility: "public" });
127
+ await closeSpan(run, span.id);
128
+
129
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => undefined);
130
+ const bundle = await finalizeTrace(run);
131
+ expect(warnSpy).toHaveBeenCalled();
132
+ warnSpy.mockRestore();
133
+
134
+ expect(bundle.modelManifestHash).toBeUndefined();
135
+ });
136
+ });
@@ -33,6 +33,8 @@ describe('HASH_DOMAIN_PREFIXES', () => {
33
33
  'node',
34
34
  'manifest',
35
35
  'root',
36
+ 'modelManifest',
37
+ 'governance',
36
38
  ];
37
39
 
38
40
  const actualKeys = Object.keys(HASH_DOMAIN_PREFIXES);
@@ -46,8 +48,10 @@ describe('HASH_DOMAIN_PREFIXES', () => {
46
48
  it('has correct prefix format for all domains', () => {
47
49
  for (const [domain, prefix] of Object.entries(HASH_DOMAIN_PREFIXES)) {
48
50
  // All prefixes should follow pattern "poi-trace:<domain>:v1|"
49
- expect(prefix).toMatch(/^poi-trace:[a-z]+:v1\|$/);
50
- expect(prefix).toContain(domain);
51
+ // (domain segment may be kebab-cased, e.g. "model-manifest").
52
+ expect(prefix).toMatch(/^poi-trace:[a-z-]+:v1\|$/);
53
+ // The camelCase key maps to the (possibly kebab-cased) prefix segment.
54
+ expect(prefix.replace(/-/g, '')).toContain(domain.toLowerCase());
51
55
  }
52
56
  });
53
57
 
@@ -101,6 +105,8 @@ describe('DEFAULT_EVENT_VISIBILITY', () => {
101
105
  'observation',
102
106
  'error',
103
107
  'custom',
108
+ 'governance-attestation',
109
+ 'tool-receipt',
104
110
  ];
105
111
 
106
112
  const actualKinds = Object.keys(DEFAULT_EVENT_VISIBILITY);