witnora 0.13.2 → 0.13.4

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,318 @@
1
+ import { chmod, mkdir, open, readFile, rename, writeFile } from "node:fs/promises";
2
+ import { dirname, resolve } from "node:path";
3
+ import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, randomUUID, sign } from "node:crypto";
4
+ import { canonicalJson } from "./canonical.js";
5
+ export class CustomerSourceKeyRing {
6
+ filePath;
7
+ value;
8
+ constructor(filePath, value) {
9
+ this.filePath = filePath;
10
+ this.value = value;
11
+ }
12
+ static async create(filePath, collectorId, keyId = `${collectorId}-${new Date().toISOString().slice(0, 10)}-${randomUUID().slice(0, 8)}`) {
13
+ identifier(collectorId, "collectorId");
14
+ identifier(keyId, "keyId");
15
+ const target = resolve(filePath);
16
+ try {
17
+ await readFile(target);
18
+ throw new Error(`Customer source key ring already exists at ${target}.`);
19
+ }
20
+ catch (error) {
21
+ if (error.code !== "ENOENT")
22
+ throw error;
23
+ }
24
+ const key = generatedKey(keyId, new Date().toISOString());
25
+ const ring = new CustomerSourceKeyRing(target, { schemaVersion: "agentcert.customer_source_keyring.v0.2", collectorId, activeKeyId: keyId, keys: [key] });
26
+ await ring.persist();
27
+ return ring;
28
+ }
29
+ static async open(filePath) {
30
+ const target = resolve(filePath);
31
+ const parsed = JSON.parse(await readFile(target, "utf8"));
32
+ validateRing(parsed);
33
+ return new CustomerSourceKeyRing(target, parsed);
34
+ }
35
+ get collectorId() { return this.value.collectorId; }
36
+ activeSigner() {
37
+ const key = this.value.keys.find((item) => item.keyId === this.value.activeKeyId && item.status === "active");
38
+ if (!key)
39
+ throw new Error("Customer source key ring has no active signing key.");
40
+ return { keyId: key.keyId, privateKeyPem: key.privateKeyPem, publicKeyPem: key.publicKeyPem };
41
+ }
42
+ signerFor(keyId) {
43
+ const key = this.value.keys.find((item) => item.keyId === keyId);
44
+ if (!key)
45
+ throw new Error(`Customer source key ${keyId} is not present in this key ring.`);
46
+ return { keyId: key.keyId, privateKeyPem: key.privateKeyPem, publicKeyPem: key.publicKeyPem };
47
+ }
48
+ registration(previousKeyId) {
49
+ const signer = this.activeSigner();
50
+ const previous = previousKeyId ?? [...this.value.keys].reverse().find((item) => item.status === "retired")?.keyId;
51
+ return { collectorId: this.collectorId, keyId: signer.keyId, publicKeyPem: signer.publicKeyPem, previousKeyId: previous };
52
+ }
53
+ async rotate(keyId = `${this.collectorId}-${new Date().toISOString().slice(0, 10)}-${randomUUID().slice(0, 8)}`) {
54
+ identifier(keyId, "keyId");
55
+ if (this.value.keys.some((item) => item.keyId === keyId))
56
+ throw new Error(`Customer source key ${keyId} already exists.`);
57
+ const now = new Date().toISOString();
58
+ const previousKeyId = this.value.activeKeyId;
59
+ this.value = {
60
+ ...this.value,
61
+ activeKeyId: keyId,
62
+ keys: [...this.value.keys.map((item) => item.keyId === previousKeyId ? { ...item, status: "retired", retiredAt: now } : item), generatedKey(keyId, now)],
63
+ };
64
+ await this.persist();
65
+ return { previousKeyId, signer: this.activeSigner() };
66
+ }
67
+ async persist() {
68
+ await mkdir(dirname(this.filePath), { recursive: true });
69
+ const temporary = `${this.filePath}.${process.pid}.${randomUUID()}.tmp`;
70
+ await writeFile(temporary, `${JSON.stringify(this.value, null, 2)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
71
+ await chmod(temporary, 0o600);
72
+ await rename(temporary, this.filePath);
73
+ }
74
+ }
75
+ export class RemoteCollectorClient {
76
+ baseUrl;
77
+ projectId;
78
+ apiKey;
79
+ requestFetch;
80
+ constructor(options) {
81
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
82
+ this.projectId = options.projectId;
83
+ this.apiKey = options.apiKey;
84
+ this.requestFetch = options.fetch ?? fetch;
85
+ if (!this.baseUrl || !this.projectId || !this.apiKey)
86
+ throw new Error("baseUrl, projectId, and apiKey are required.");
87
+ }
88
+ registerSourceKey(input) {
89
+ return this.json("collector-keys", { method: "POST", body: JSON.stringify(input) });
90
+ }
91
+ append(runId, records, idempotencyKey = `record-${records[0]?.eventHash ?? randomUUID()}`) {
92
+ return this.json(`trusted-runs/${encodeURIComponent(runId)}/records`, {
93
+ method: "POST",
94
+ headers: { "idempotency-key": idempotencyKey },
95
+ body: JSON.stringify({ records }),
96
+ });
97
+ }
98
+ heartbeat(input) {
99
+ const payload = {
100
+ schemaVersion: "agentcert.collector_heartbeat.v0.2",
101
+ collectorId: input.collectorId,
102
+ sourceKeyId: input.signer.keyId,
103
+ runId: input.runId,
104
+ occurredAt: input.occurredAt ?? new Date().toISOString(),
105
+ pendingRecordCount: input.pendingRecordCount,
106
+ lastAckSequence: input.lastAckSequence,
107
+ };
108
+ const payloadSha256 = sha256(canonicalJson(payload));
109
+ return this.json("collector-heartbeats", {
110
+ method: "POST",
111
+ body: JSON.stringify({ payload, payloadSha256, signature: signSourceDigest(payloadSha256, input.signer) }),
112
+ });
113
+ }
114
+ reconcile(runId, receipt) {
115
+ return this.json(`trusted-runs/${encodeURIComponent(runId)}/reconcile`, {
116
+ method: "POST",
117
+ headers: { "idempotency-key": `reconcile-${String(receipt.receiptSha256 ?? randomUUID())}` },
118
+ body: JSON.stringify(receipt),
119
+ });
120
+ }
121
+ proposeAction(proposal, idempotencyKey) {
122
+ return this.json("actions", {
123
+ method: "POST",
124
+ headers: { "idempotency-key": idempotencyKey },
125
+ body: JSON.stringify(proposal),
126
+ });
127
+ }
128
+ getAction(actionId) {
129
+ return this.json(`actions/${encodeURIComponent(actionId)}`);
130
+ }
131
+ issueExecutionGrant(actionId, grant, idempotencyKey) {
132
+ return this.json(`actions/${encodeURIComponent(actionId)}/execution-grant`, {
133
+ method: "POST",
134
+ headers: { "idempotency-key": idempotencyKey },
135
+ body: JSON.stringify(grant),
136
+ });
137
+ }
138
+ status() { return this.json("collector-status"); }
139
+ async revokeSourceKey(keyId) {
140
+ return this.json(`collector-keys/${encodeURIComponent(keyId)}`, { method: "DELETE" });
141
+ }
142
+ sink() {
143
+ return {
144
+ name: "agentcert-remote-collector-v0.2",
145
+ write: async (record) => { await this.append(record.runId, [record], `record-${record.eventHash}`); },
146
+ };
147
+ }
148
+ async json(suffix, init = {}) {
149
+ const response = await this.requestFetch(`${this.baseUrl}/v1/projects/${encodeURIComponent(this.projectId)}/${suffix}`, {
150
+ ...init,
151
+ headers: { authorization: `Bearer ${this.apiKey}`, ...(init.body ? { "content-type": "application/json" } : {}), ...init.headers },
152
+ });
153
+ const body = await response.json().catch(() => ({}));
154
+ if (!response.ok)
155
+ throw new RemoteCollectorApiError(response.status, String(body.code ?? "remote_collector_error"), String(body.error ?? `Witnora API request failed (${response.status}).`), typeof body.recovery === "string" ? body.recovery : undefined);
156
+ return body;
157
+ }
158
+ }
159
+ export class DurableRemoteCollectorQueue {
160
+ runId;
161
+ journalPath;
162
+ ackPath;
163
+ replayChain = Promise.resolve();
164
+ constructor(directory, runId) {
165
+ this.runId = runId;
166
+ this.journalPath = resolve(directory, `${safeFileName(runId)}.remote.jsonl`);
167
+ this.ackPath = resolve(directory, `${safeFileName(runId)}.remote-ack.json`);
168
+ }
169
+ async enqueue(record) {
170
+ if (record.runId !== this.runId)
171
+ throw new Error("Queued record runId does not match the durable queue runId.");
172
+ await mkdir(dirname(this.journalPath), { recursive: true });
173
+ const handle = await open(this.journalPath, "a");
174
+ try {
175
+ await handle.write(`${JSON.stringify(record)}\n`);
176
+ await handle.sync();
177
+ }
178
+ finally {
179
+ await handle.close();
180
+ }
181
+ }
182
+ async pending() {
183
+ const records = await this.all();
184
+ const ack = await this.readAck();
185
+ validateAck(records, ack);
186
+ return records.filter((record) => record.sequence > ack.sequence);
187
+ }
188
+ async all() {
189
+ let raw = "";
190
+ try {
191
+ raw = await readFile(this.journalPath, "utf8");
192
+ }
193
+ catch (error) {
194
+ if (error.code === "ENOENT")
195
+ return [];
196
+ throw error;
197
+ }
198
+ return raw.split(/\r?\n/).filter(Boolean).map((line, index) => {
199
+ try {
200
+ return JSON.parse(line);
201
+ }
202
+ catch {
203
+ throw new Error(`Remote collector queue contains invalid JSON at line ${index + 1}.`);
204
+ }
205
+ });
206
+ }
207
+ async currentAck() {
208
+ const records = await this.all();
209
+ const ack = await this.readAck();
210
+ validateAck(records, ack);
211
+ return ack;
212
+ }
213
+ async replay(client) {
214
+ let result;
215
+ const operation = this.replayChain.then(async () => {
216
+ let delivered = 0;
217
+ let latest;
218
+ for (const record of await this.pending()) {
219
+ const response = await client.append(record.runId, [record], `record-${record.eventHash}`);
220
+ latest = response.ack;
221
+ await this.writeAck(latest);
222
+ delivered += 1;
223
+ }
224
+ result = { delivered, ack: latest };
225
+ });
226
+ this.replayChain = operation.catch(() => undefined);
227
+ await operation;
228
+ return result;
229
+ }
230
+ async readAck() {
231
+ try {
232
+ const value = JSON.parse(await readFile(this.ackPath, "utf8"));
233
+ if (!Number.isSafeInteger(value.sequence) || Number(value.sequence) < 0 || typeof value.eventHash !== "string" || !/^[a-f0-9]{64}$/.test(value.eventHash)) {
234
+ throw new Error("Remote collector ACK file is invalid.");
235
+ }
236
+ return { sequence: Number(value.sequence), eventHash: value.eventHash };
237
+ }
238
+ catch (error) {
239
+ if (error.code === "ENOENT")
240
+ return { sequence: -1 };
241
+ throw error;
242
+ }
243
+ }
244
+ async writeAck(ack) {
245
+ const records = await this.all();
246
+ validateAck(records, ack);
247
+ const current = await this.readAck();
248
+ if (current.sequence > ack.sequence)
249
+ return;
250
+ if (current.sequence === ack.sequence) {
251
+ if (current.eventHash !== ack.eventHash)
252
+ throw new Error(`Remote collector ACK hash conflicts at sequence ${ack.sequence}.`);
253
+ return;
254
+ }
255
+ await mkdir(dirname(this.ackPath), { recursive: true });
256
+ const temporary = `${this.ackPath}.${process.pid}.${randomUUID()}.tmp`;
257
+ await writeFile(temporary, JSON.stringify(ack), { encoding: "utf8", mode: 0o600 });
258
+ await rename(temporary, this.ackPath);
259
+ }
260
+ }
261
+ function validateAck(records, ack) {
262
+ if (ack.sequence === -1)
263
+ return;
264
+ const acknowledged = records.find((record) => record.sequence === ack.sequence);
265
+ if (!acknowledged || acknowledged.eventHash !== ack.eventHash) {
266
+ throw new Error(`Remote collector ACK does not match the local journal at sequence ${ack.sequence}.`);
267
+ }
268
+ }
269
+ export class RemoteCollectorApiError extends Error {
270
+ status;
271
+ code;
272
+ recovery;
273
+ constructor(status, code, message, recovery) {
274
+ super(message);
275
+ this.status = status;
276
+ this.code = code;
277
+ this.recovery = recovery;
278
+ this.name = "RemoteCollectorApiError";
279
+ }
280
+ }
281
+ function generatedKey(keyId, createdAt) {
282
+ const { privateKey, publicKey } = generateKeyPairSync("ed25519");
283
+ return {
284
+ keyId,
285
+ privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
286
+ publicKeyPem: publicKey.export({ type: "spki", format: "pem" }).toString(),
287
+ createdAt,
288
+ status: "active",
289
+ };
290
+ }
291
+ function signSourceDigest(digest, signer) {
292
+ if (!/^[a-f0-9]{64}$/.test(digest))
293
+ throw new Error("A SHA-256 digest is required for source signing.");
294
+ return { algorithm: "Ed25519", keyId: signer.keyId, signature: sign(null, Buffer.from(digest, "hex"), createPrivateKey(signer.privateKeyPem)).toString("base64url") };
295
+ }
296
+ function validateRing(value) {
297
+ if (value.schemaVersion !== "agentcert.customer_source_keyring.v0.2")
298
+ throw new Error("Customer source key ring schemaVersion is not supported.");
299
+ identifier(value.collectorId, "collectorId");
300
+ identifier(value.activeKeyId, "activeKeyId");
301
+ if (!Array.isArray(value.keys) || value.keys.length === 0)
302
+ throw new Error("Customer source key ring has no keys.");
303
+ for (const key of value.keys) {
304
+ identifier(key.keyId, "keys[].keyId");
305
+ const privateKey = createPrivateKey(key.privateKeyPem);
306
+ const derivedPublic = createPublicKey(privateKey).export({ type: "spki", format: "pem" }).toString();
307
+ if (privateKey.asymmetricKeyType !== "ed25519" || derivedPublic !== createPublicKey(key.publicKeyPem).export({ type: "spki", format: "pem" }).toString()) {
308
+ throw new Error(`Customer source key ${key.keyId} is invalid.`);
309
+ }
310
+ }
311
+ }
312
+ function sha256(value) { return createHash("sha256").update(value).digest("hex"); }
313
+ function identifier(value, field) {
314
+ if (!value || value.length > 160 || !/^[A-Za-z0-9._:-]+$/.test(value))
315
+ throw new Error(`${field} must use 1 to 160 URL-safe identifier characters.`);
316
+ return value;
317
+ }
318
+ function safeFileName(value) { return value.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 160); }
@@ -131,7 +131,7 @@ export function starterInstructions(template, subject) {
131
131
  return `
132
132
  Next:
133
133
  1. Edit tripwire.yml so startUrl and agent.command/agent.args match your app and browser agent.
134
- 2. Run in CI with Kakarottoooo/agentcert/actions/tripwire@v0, or re-run init with --github-action.
134
+ 2. Run in CI with Kakarottoooo/witnora/actions/tripwire@v0, or re-run init with --github-action.
135
135
  3. Run: npx witnora@latest run --tripwire .tripwire/latest/tripwire-result.json --subject ${JSON.stringify(subject)} --fail-on-verdict --push
136
136
  `;
137
137
  if (template === "mcp")
@@ -203,7 +203,7 @@ jobs:
203
203
  with:
204
204
  node-version: "20"
205
205
  - id: agentcert
206
- uses: Kakarottoooo/agentcert/actions/tripwire@v0
206
+ uses: Kakarottoooo/witnora/actions/tripwire@v0
207
207
  with:
208
208
  config: tripwire.yml
209
209
  out: .tripwire/latest
@@ -0,0 +1,135 @@
1
+ import { spawn } from "node:child_process";
2
+ export async function inspectIsolatedOutcomeProbe(config) {
3
+ const result = await invokeProbeChild({ mode: "health", config });
4
+ if (result.ready !== true)
5
+ throw new Error("Independent outcome-probe child did not become ready.");
6
+ }
7
+ export async function observeInIsolatedOutcomeProbe(config, input) {
8
+ const result = await invokeProbeChild({ mode: "observe", config, input });
9
+ if (!result.observation || typeof result.observation !== "object" || Array.isArray(result.observation))
10
+ throw new Error("Independent outcome-probe child returned no bounded observation.");
11
+ return result.observation;
12
+ }
13
+ async function invokeProbeChild(request) {
14
+ const serializedRequest = `${JSON.stringify(request)}\n`;
15
+ if (Buffer.byteLength(serializedRequest) > 262_144)
16
+ throw new Error("Independent outcome-probe input exceeded its bounded input limit.");
17
+ const child = spawn(process.execPath, ["--input-type=module", "--eval", PROBE_CHILD_SOURCE], {
18
+ env: {}, windowsHide: true, stdio: ["pipe", "pipe", "pipe"],
19
+ });
20
+ const stdout = [];
21
+ const stderr = [];
22
+ let stdoutBytes = 0;
23
+ let stderrBytes = 0;
24
+ child.stdout.on("data", (chunk) => {
25
+ stdoutBytes += chunk.length;
26
+ if (stdoutBytes <= 65_536)
27
+ stdout.push(chunk);
28
+ else
29
+ child.kill();
30
+ });
31
+ child.stderr.on("data", (chunk) => {
32
+ stderrBytes += chunk.length;
33
+ if (stderrBytes <= 8_192)
34
+ stderr.push(chunk);
35
+ });
36
+ const timeout = setTimeout(() => child.kill(), 10_000);
37
+ child.stdin.end(serializedRequest);
38
+ const exitCode = await new Promise((resolveExit, reject) => {
39
+ child.once("error", reject);
40
+ child.once("close", resolveExit);
41
+ }).finally(() => clearTimeout(timeout));
42
+ if (stdoutBytes > 65_536)
43
+ throw new Error("Independent outcome-probe child exceeded its bounded output limit.");
44
+ const output = Buffer.concat(stdout).toString("utf8");
45
+ let value;
46
+ try {
47
+ value = JSON.parse(output);
48
+ }
49
+ catch {
50
+ throw new Error(`Independent outcome-probe child failed (${exitCode ?? "unknown"}): ${Buffer.concat(stderr).toString("utf8").slice(0, 500) || "invalid response"}`);
51
+ }
52
+ if (exitCode !== 0 || value.ok !== true)
53
+ throw new Error(`Independent outcome-probe child failed: ${String(value.error ?? "unknown error")}`);
54
+ return value;
55
+ }
56
+ const PROBE_CHILD_SOURCE = String.raw `
57
+ import { createHash } from "node:crypto";
58
+ import { readFile } from "node:fs/promises";
59
+ import { pathToFileURL } from "node:url";
60
+
61
+ const chunks = [];
62
+ let bytes = 0;
63
+ for await (const chunk of process.stdin) {
64
+ bytes += chunk.length;
65
+ if (bytes > 262144) throw new Error("probe_input_too_large");
66
+ chunks.push(chunk);
67
+ }
68
+
69
+ try {
70
+ const request = JSON.parse(Buffer.concat(chunks).toString("utf8"));
71
+ const config = request.config ?? {};
72
+ const moduleBytes = await readFile(config.modulePath);
73
+ const digest = createHash("sha256").update(moduleBytes).digest("hex");
74
+ if (digest !== config.moduleSha256) throw new Error("probe_module_digest_mismatch");
75
+ const loaded = await import(pathToFileURL(config.modulePath).href + "?sha256=" + digest);
76
+ if (typeof loaded.createWitnoraOutcomeProbe !== "function") throw new Error("probe_factory_missing");
77
+ const context = Object.freeze({
78
+ probeId: config.probeId,
79
+ projectId: config.projectId,
80
+ targetReadCredential: Object.freeze({ handle: config.credentialHandle, access: "READ_ONLY" }),
81
+ });
82
+ const probe = await loaded.createWitnoraOutcomeProbe(context);
83
+ if (probe?.id !== config.probeId || probe?.credentialHandle !== config.credentialHandle || probe?.readOnly !== true || typeof probe?.observe !== "function") throw new Error("probe_contract_mismatch");
84
+ if (request.mode === "health") {
85
+ respond({ ok: true, ready: true });
86
+ } else if (request.mode === "observe") {
87
+ const raw = await probe.observe(request.input ?? {});
88
+ const observedState = record(raw?.observedState, "observedState");
89
+ rejectSecrets(observedState);
90
+ const allowedMethods = new Set(["TARGET_API", "TARGET_UI", "TARGET_AUDIT_LOG", "WEBHOOK", "DATABASE_QUERY", "THIRD_PARTY_CONFIRMATION"]);
91
+ if (!allowedMethods.has(raw?.observationMethod) || raw?.observationSource !== config.probeId) throw new Error("probe_observation_binding_invalid");
92
+ const references = raw.evidenceReferences === undefined ? undefined : strings(raw.evidenceReferences, 20);
93
+ const confidence = raw.confidence === undefined ? undefined : Number(raw.confidence);
94
+ if (confidence !== undefined && (!Number.isFinite(confidence) || confidence < 0 || confidence > 1)) throw new Error("probe_confidence_invalid");
95
+ respond({ ok: true, observation: {
96
+ observedState,
97
+ observationMethod: raw.observationMethod,
98
+ observationSource: raw.observationSource,
99
+ ...(references ? { evidenceReferences: references } : {}),
100
+ ...(confidence === undefined ? {} : { confidence }),
101
+ } });
102
+ } else {
103
+ throw new Error("probe_mode_invalid");
104
+ }
105
+ } catch (error) {
106
+ respond({ ok: false, error: error instanceof Error ? error.message.slice(0, 200) : "probe_failed" }, 1);
107
+ }
108
+
109
+ function record(value, name) {
110
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(name + "_invalid");
111
+ return value;
112
+ }
113
+ function strings(value, maximum) {
114
+ if (!Array.isArray(value) || value.length > maximum || value.some((item) => typeof item !== "string" || item.length > 512)) throw new Error("probe_evidence_references_invalid");
115
+ return value;
116
+ }
117
+ function rejectSecrets(value, depth = 0) {
118
+ if (depth > 8) throw new Error("probe_observation_too_deep");
119
+ for (const [key, child] of Object.entries(value)) {
120
+ if (/(authorization|cookie|password|secret|token|credential|api.?key|private.?key)/i.test(key)) throw new Error("probe_observation_contains_secret_field");
121
+ if (typeof child === "string" && child.length > 4096) throw new Error("probe_observation_string_too_large");
122
+ if (child && typeof child === "object") rejectSecrets(child, depth + 1);
123
+ }
124
+ }
125
+ function respond(value, code = 0) {
126
+ const output = JSON.stringify(value);
127
+ if (Buffer.byteLength(output) > 65536) {
128
+ process.stdout.write(JSON.stringify({ ok: false, error: "probe_output_too_large" }));
129
+ process.exitCode = 1;
130
+ return;
131
+ }
132
+ process.stdout.write(output);
133
+ process.exitCode = code;
134
+ }
135
+ `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "witnora",
3
- "version": "0.13.2",
3
+ "version": "0.13.4",
4
4
  "description": "Independent assurance for covered agent action paths across models and frameworks.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -41,8 +41,8 @@
41
41
  "import": "./dist/vendor/onegent-runtime/browser-adapter-kit.js"
42
42
  },
43
43
  "./deployment-enforcement": {
44
- "types": "./dist/vendor/agentcert-sdk/deployment-enforcement.d.ts",
45
- "import": "./dist/vendor/agentcert-sdk/deployment-enforcement.js"
44
+ "types": "./dist/internal/control-client/deployment-enforcement.d.ts",
45
+ "import": "./dist/internal/control-client/deployment-enforcement.js"
46
46
  }
47
47
  },
48
48
  "files": [
@@ -66,8 +66,5 @@
66
66
  },
67
67
  "optionalDependencies": {
68
68
  "pg": "^8.16.3"
69
- },
70
- "dependencies": {
71
- "agentcert-sdk": "0.5.0"
72
69
  }
73
70
  }