@fidacy/openclaw-plugin 0.1.7 → 0.1.9

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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.9 — 2026-07-03
4
+
5
+ - New tools: `anchor_artifact` and `check_artifact` (hash-only artifact anchoring
6
+ on the Bitcoin-checkpointed audit chain; files never leave the machine).
7
+
3
8
  ## 0.1.1 — 2026-07-02
4
9
 
5
10
  - README: animated demo (BEC lookalike-payee DENY → legit ALLOW with signed
package/dist/index.js CHANGED
@@ -4671,7 +4671,7 @@ function resolveMandateRules(cfg) {
4671
4671
  }
4672
4672
 
4673
4673
  // ../mcp/src/telemetry.ts
4674
- var CLIENT_VERSION = true ? "0.1.7" : "dev";
4674
+ var CLIENT_VERSION = true ? "0.1.9" : "dev";
4675
4675
  var currentShell = "mcp";
4676
4676
  function setTelemetryShell(shell) {
4677
4677
  currentShell = shell;
@@ -4695,8 +4695,13 @@ var flushTimer = null;
4695
4695
  function resultOf(status, violatedRule) {
4696
4696
  if (status === "ALLOW") return "allow";
4697
4697
  const r = violatedRule ?? "";
4698
- if (r.startsWith("per_tx_cap") || r.startsWith("total_cap")) return "deny_cap";
4698
+ if (r.startsWith("payee_lookalike")) return "deny_lookalike";
4699
4699
  if (r.startsWith("payee_not_in_allowlist")) return "deny_payee";
4700
+ if (r.startsWith("duplicate_invoice")) return "deny_duplicate";
4701
+ if (r.startsWith("per_tx_cap") || r.startsWith("total_cap")) return "deny_cap";
4702
+ if (r.startsWith("mandate_revoked") || r.startsWith("before_mandate") || r.startsWith("after_mandate"))
4703
+ return "deny_window";
4704
+ if (r.startsWith("non_positive_amount") || r.startsWith("invalid_")) return "deny_invalid";
4700
4705
  return "deny_scope";
4701
4706
  }
4702
4707
  function record(type, result) {
@@ -4794,7 +4799,7 @@ function requestUpgrade() {
4794
4799
  }
4795
4800
 
4796
4801
  // ../mcp/src/provision.ts
4797
- var CLIENT_VERSION2 = true ? "0.1.7" : "dev";
4802
+ var CLIENT_VERSION2 = true ? "0.1.9" : "dev";
4798
4803
  function provisionEnabled() {
4799
4804
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
4800
4805
  return !(v === "1" || v === "true" || v === "yes");
@@ -4983,6 +4988,70 @@ async function postOnce(fetchImpl, url, headers, payload, timeoutMs) {
4983
4988
  return parsed;
4984
4989
  }
4985
4990
 
4991
+ // ../mcp/src/artifacts.ts
4992
+ import { createHash } from "node:crypto";
4993
+ import { createReadStream } from "node:fs";
4994
+ import { pipeline } from "node:stream/promises";
4995
+ var ARTIFACT_KINDS = [
4996
+ "contract",
4997
+ "invoice",
4998
+ "prescription",
4999
+ "claim",
5000
+ "document",
5001
+ "image",
5002
+ "audio",
5003
+ "video",
5004
+ "custom"
5005
+ ];
5006
+ async function hashFile(path) {
5007
+ const hash = createHash("sha256");
5008
+ await pipeline(createReadStream(path), async function* (source) {
5009
+ for await (const chunk of source) {
5010
+ hash.update(chunk);
5011
+ yield;
5012
+ }
5013
+ });
5014
+ return hash.digest("hex");
5015
+ }
5016
+ async function engineFetch(method, pathAndQuery, cfg, body) {
5017
+ const base = requireSafeBaseUrl(cfg.engineUrl);
5018
+ const controller = new AbortController();
5019
+ const timer = setTimeout(() => controller.abort(), 1e4);
5020
+ let res;
5021
+ try {
5022
+ res = await fetch(`${base}${pathAndQuery}`, {
5023
+ method,
5024
+ headers: {
5025
+ authorization: `Bearer ${cfg.apiKey}`,
5026
+ ...body !== void 0 ? { "content-type": "application/json" } : {}
5027
+ },
5028
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
5029
+ signal: controller.signal
5030
+ });
5031
+ } catch {
5032
+ throw new AssessError({ type: "network_error", status: 0 });
5033
+ } finally {
5034
+ clearTimeout(timer);
5035
+ }
5036
+ let json;
5037
+ try {
5038
+ json = await res.json();
5039
+ } catch {
5040
+ throw new AssessError({ type: "bad_response", status: res.status });
5041
+ }
5042
+ if (!res.ok) {
5043
+ const type = typeof json?.error === "string" ? String(json.error) : "engine_error";
5044
+ throw new AssessError({ type, status: res.status });
5045
+ }
5046
+ return json;
5047
+ }
5048
+ async function anchorArtifact(params, cfg) {
5049
+ return await engineFetch("POST", "/v1/artifacts", cfg, params);
5050
+ }
5051
+ async function findArtifacts(sha2562, cfg) {
5052
+ return await engineFetch("GET", `/v1/artifacts?sha256=${sha2562}`, cfg);
5053
+ }
5054
+
4986
5055
  // src/index.ts
4987
5056
  var core;
4988
5057
  function boot() {
@@ -5124,6 +5193,105 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
5124
5193
  }
5125
5194
  }
5126
5195
  }),
5196
+ // ARTIFACT ANCHORING. Hash-only integrity/existence proof: the file is hashed
5197
+ // locally (streaming) and never uploaded; the SHA-256 joins the same
5198
+ // Bitcoin-checkpointed audit chain as the verdicts. Receipt = offline JWS.
5199
+ tool({
5200
+ name: "anchor_artifact",
5201
+ label: "Anchor Artifact (Bitcoin-anchored integrity proof)",
5202
+ description: "Prove an artifact existed exactly as-is at this moment, and make any later tampering detectable. Give a file `path` (hashed locally with SHA-256 \u2014 the file itself is NEVER uploaded) or a precomputed `sha256`. The hash is registered on Fidacy's tamper-evident audit chain, which is checkpointed to the Bitcoin blockchain, and you get a signed receipt (JWS) verifiable offline against the engine JWKS. Use it for contracts, invoices, medical prescriptions, insurance claims, images, audio, video. `kind` defaults to document; optional `label` is a short reference (no PII).",
5203
+ parameters: typebox_exports.Object({
5204
+ path: typebox_exports.Optional(typebox_exports.String()),
5205
+ sha256: typebox_exports.Optional(typebox_exports.String({ pattern: "^[0-9a-f]{64}$" })),
5206
+ kind: typebox_exports.Optional(typebox_exports.Union(ARTIFACT_KINDS.map((k) => typebox_exports.Literal(k)))),
5207
+ label: typebox_exports.Optional(typebox_exports.String({ maxLength: 120 })),
5208
+ subject: typebox_exports.Optional(typebox_exports.String({ maxLength: 120 }))
5209
+ }),
5210
+ async execute(params, config) {
5211
+ boot();
5212
+ const engineUrl = config.engineUrl ?? process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com";
5213
+ const apiKey = (config.engineApiKey ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim();
5214
+ if (!apiKey) {
5215
+ throw new Error(
5216
+ "anchor_artifact requires an engine API key (an fky_live_/fky_test_ key with assess:write). Set plugins.entries.fidacy.config.engineApiKey or FIDACY_ENGINE_API_KEY."
5217
+ );
5218
+ }
5219
+ if (!params.path && !params.sha256) {
5220
+ throw new Error("Give a file `path` (hashed locally) or a precomputed `sha256`.");
5221
+ }
5222
+ let hash = params.sha256 ?? "";
5223
+ if (!hash && params.path) {
5224
+ try {
5225
+ hash = await hashFile(params.path);
5226
+ } catch {
5227
+ throw new Error("Could not read that file locally (check the path and permissions). Nothing was sent anywhere.");
5228
+ }
5229
+ }
5230
+ try {
5231
+ const r = await anchorArtifact(
5232
+ { sha256: hash, kind: params.kind ?? "document", ...params.label ? { label: params.label } : {}, ...params.subject ? { subject: params.subject } : {} },
5233
+ { engineUrl, apiKey }
5234
+ );
5235
+ return {
5236
+ summary: `ANCHORED ${r.kind} \xB7 sha256 ${hash.slice(0, 16)}\u2026 \xB7 audit seq ${r.audit.seq} \xB7 Bitcoin checkpoint: ${r.anchor.status}. The file never left this machine.`,
5237
+ ...r
5238
+ };
5239
+ } catch (e) {
5240
+ if (e instanceof AssessError) throw new Error(`ANCHOR ${e.status}: ${e.type}`);
5241
+ throw new Error("ANCHOR failed: unexpected error");
5242
+ }
5243
+ }
5244
+ }),
5245
+ // Verification half: was this exact content anchored, and did it reach Bitcoin?
5246
+ tool({
5247
+ name: "check_artifact",
5248
+ label: "Check Artifact (was this hash anchored?)",
5249
+ description: "Check whether an artifact was anchored by this account and the state of its Bitcoin checkpoint. Give a file `path` (hashed locally, never uploaded) or a `sha256`. If the current hash of a file does NOT match an anchored record you expected, the file changed since anchoring \u2014 that is the tampering signal.",
5250
+ parameters: typebox_exports.Object({
5251
+ path: typebox_exports.Optional(typebox_exports.String()),
5252
+ sha256: typebox_exports.Optional(typebox_exports.String({ pattern: "^[0-9a-f]{64}$" }))
5253
+ }),
5254
+ async execute(params, config) {
5255
+ boot();
5256
+ const engineUrl = config.engineUrl ?? process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com";
5257
+ const apiKey = (config.engineApiKey ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim();
5258
+ if (!apiKey) {
5259
+ throw new Error(
5260
+ "check_artifact requires an engine API key. Set plugins.entries.fidacy.config.engineApiKey or FIDACY_ENGINE_API_KEY."
5261
+ );
5262
+ }
5263
+ if (!params.path && !params.sha256) {
5264
+ throw new Error("Give a file `path` (hashed locally) or a `sha256`.");
5265
+ }
5266
+ let hash = params.sha256 ?? "";
5267
+ if (!hash && params.path) {
5268
+ try {
5269
+ hash = await hashFile(params.path);
5270
+ } catch {
5271
+ throw new Error("Could not read that file locally (check the path and permissions). Nothing was sent anywhere.");
5272
+ }
5273
+ }
5274
+ try {
5275
+ const r = await findArtifacts(hash, { engineUrl, apiKey });
5276
+ if (!r.artifacts.length) {
5277
+ return {
5278
+ summary: `NOT FOUND \xB7 sha256 ${hash.slice(0, 16)}\u2026 has no anchored record in this account. If you expected a match, the file changed since anchoring.`,
5279
+ sha256: hash,
5280
+ artifacts: []
5281
+ };
5282
+ }
5283
+ const first = r.artifacts[0];
5284
+ return {
5285
+ summary: `FOUND ${r.artifacts.length} record(s) \xB7 newest: ${String(first.kind)} anchored ${String(first.createdAt)} (audit seq ${String(first.auditSeq)}). Content matches the anchored hash byte for byte.`,
5286
+ sha256: hash,
5287
+ ...r
5288
+ };
5289
+ } catch (e) {
5290
+ if (e instanceof AssessError) throw new Error(`CHECK ${e.status}: ${e.type}`);
5291
+ throw new Error("CHECK failed: unexpected error");
5292
+ }
5293
+ }
5294
+ }),
5127
5295
  // Upgrade funnel. Records the intent signal and returns the real-account link;
5128
5296
  // the anon_id travels in the URL so the engine binds past usage to the tenant.
5129
5297
  tool({
@@ -3,13 +3,15 @@
3
3
  "name": "Fidacy \u2014 Payment Firewall",
4
4
  "description": "A signed, independently-verifiable verdict on every money-moving agent action. Blocks wrong/lookalike payee, over-cap, and duplicate-invoice fraud before money moves. Non-custodial, local-first, deny-by-default.",
5
5
  "icon": "https://fidacy.com/logo.png",
6
- "version": "0.1.7",
6
+ "version": "0.1.9",
7
7
  "contracts": {
8
8
  "tools": [
9
9
  "request_payment",
10
10
  "verify_mandate",
11
11
  "get_audit_proof",
12
12
  "assess_action",
13
+ "anchor_artifact",
14
+ "check_artifact",
13
15
  "fidacy_upgrade"
14
16
  ]
15
17
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fidacy/openclaw-plugin",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Fidacy payment firewall as a native OpenClaw plugin: signed, verifiable verdicts on every money-moving agent action, in-process (no MCP subprocess).",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://fidacy.com",
@@ -46,20 +46,20 @@
46
46
  "npmSpec": "@fidacy/openclaw-plugin"
47
47
  }
48
48
  },
49
+ "scripts": {
50
+ "build": "node scripts/bundle.mjs",
51
+ "typecheck": "tsc --noEmit"
52
+ },
49
53
  "engines": {
50
54
  "node": ">=20"
51
55
  },
52
56
  "devDependencies": {
57
+ "@fidacy/firewall": "workspace:*",
58
+ "@fidacy/mcp": "workspace:*",
53
59
  "@types/node": "^22.10.0",
54
60
  "esbuild": "^0.24.0",
55
61
  "openclaw": "2026.6.11",
56
62
  "typebox": "1.1.39",
57
- "typescript": "^5.6.3",
58
- "@fidacy/firewall": "0.1.0",
59
- "@fidacy/mcp": "0.1.13"
60
- },
61
- "scripts": {
62
- "build": "node scripts/bundle.mjs",
63
- "typecheck": "tsc --noEmit"
63
+ "typescript": "^5.6.3"
64
64
  }
65
65
  }