@fidacy/openclaw-plugin 0.1.6 → 0.1.8

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
@@ -30,3 +30,24 @@ First release. Fidacy payment firewall as a native OpenClaw plugin:
30
30
 
31
31
  - Same upgrade micro-trigger system as @fidacy/mcp 0.1.11 (once-per-install,
32
32
  value-proven moments only), routed through the fidacy_upgrade tool.
33
+
34
+ ## 0.1.4 — 2026-07-02
35
+
36
+ - Inherits @fidacy/mcp 0.1.12's restart-durable firewall state: the
37
+ duplicate-invoice (BEC) guard and the spend cap now rehydrate from the local
38
+ audit log at boot, so restarting the agent no longer re-opens a paid invoice
39
+ or resets the running total. Torn-tail audit logs (crash mid-append) are
40
+ salvaged instead of quarantined.
41
+
42
+ ## 0.1.5 — 2026-07-02
43
+
44
+ - Version sync only: `openclaw.plugin.json` had shipped 0.1.4 with a stale
45
+ manifest version (Plugin Inspector `package-manifest-version-drift` warning).
46
+ No code changes over 0.1.4.
47
+
48
+ ## 0.1.7 — 2026-07-03
49
+
50
+ - RESTORES the restart-durable firewall state from 0.1.4/0.1.5 (the 0.1.6 icon
51
+ release was built against a stale @fidacy/firewall dist and silently dropped
52
+ the rehydration — caught by the restart behavioral test, now part of the
53
+ release ritual). Keeps the 0.1.6 ClawHub card icon.
package/dist/index.js CHANGED
@@ -4259,66 +4259,9 @@ function sha256(input) {
4259
4259
 
4260
4260
  // ../firewall/dist/audit-store.js
4261
4261
  import fs from "node:fs";
4262
- var FileAuditStore = class {
4263
- path;
4264
- chain = [];
4265
- constructor(path) {
4266
- this.path = path;
4267
- this.load();
4268
- }
4269
- load() {
4270
- if (!fs.existsSync(this.path))
4271
- return;
4272
- try {
4273
- const raw = fs.readFileSync(this.path, "utf8");
4274
- const parsed = [];
4275
- for (const line of raw.split("\n")) {
4276
- if (!line.trim())
4277
- continue;
4278
- parsed.push(JSON.parse(line));
4279
- }
4280
- this.chain = parsed;
4281
- if (!this.intact())
4282
- throw new Error("audit chain integrity broken");
4283
- } catch (err) {
4284
- this.chain = [];
4285
- try {
4286
- const quarantine = `${this.path}.corrupt-${Date.now()}`;
4287
- fs.renameSync(this.path, quarantine);
4288
- console.error(`[fidacy] audit log at ${this.path} is unreadable/tampered (${err.message}); quarantined to ${quarantine}, starting a fresh chain.`);
4289
- } catch {
4290
- console.error(`[fidacy] audit log at ${this.path} is unreadable/tampered and could not be quarantined; starting a fresh in-memory chain.`);
4291
- }
4292
- }
4293
- }
4294
- head() {
4295
- return this.chain.length ? this.chain[this.chain.length - 1].hash : "GENESIS";
4296
- }
4297
- append(decision) {
4298
- const prevHash = this.head();
4299
- const seq = this.chain.length;
4300
- const ts = decision.ts;
4301
- const digest = sha256(stableStringify({ decisionId: decision.decisionId, status: decision.status, request: decision.request, violatedRule: decision.violatedRule ?? null }));
4302
- const hash = sha256(`${prevHash}|${digest}|${seq}|${ts}`);
4303
- const record2 = { seq, decisionId: decision.decisionId, status: decision.status, subject: decision.subject, digest, prevHash, hash, ts };
4304
- fs.appendFileSync(this.path, JSON.stringify(record2) + "\n");
4305
- this.chain.push(record2);
4306
- return record2;
4307
- }
4308
- find(decisionId) {
4309
- return this.chain.find((r) => r.decisionId === decisionId);
4310
- }
4311
- intact() {
4312
- let prev = "GENESIS";
4313
- for (const r of this.chain) {
4314
- const expected = sha256(`${prev}|${r.digest}|${r.seq}|${r.ts}`);
4315
- if (expected !== r.hash || r.prevHash !== prev)
4316
- return false;
4317
- prev = r.hash;
4318
- }
4319
- return true;
4320
- }
4321
- };
4262
+
4263
+ // ../firewall/dist/core.js
4264
+ import { randomUUID } from "node:crypto";
4322
4265
 
4323
4266
  // ../firewall/dist/evaluate.js
4324
4267
  function fold(s) {
@@ -4395,7 +4338,6 @@ function evaluate(mandate, req, spentSoFar) {
4395
4338
  }
4396
4339
 
4397
4340
  // ../firewall/dist/core.js
4398
- import { randomUUID } from "node:crypto";
4399
4341
  function canonInvoice(ref) {
4400
4342
  if (typeof ref !== "string")
4401
4343
  return "";
@@ -4443,6 +4385,35 @@ var DevFidacyCore = class {
4443
4385
  this.mandate = opts.mandate;
4444
4386
  this.store = new FileAuditStore(opts.auditLogPath ?? "./fidacy-audit.log");
4445
4387
  this.onDecision = opts.onDecision;
4388
+ this.rehydrate();
4389
+ }
4390
+ /**
4391
+ * Rebuild the in-memory decision state from the persisted audit log, so a process
4392
+ * restart cannot re-open a claimed invoice (BEC dedup) or reset the maxTotal
4393
+ * counter. One O(records) pass at boot over the already-loaded chain:
4394
+ * - claimedInvoices: every ALLOW that carried an invoiceRef, regardless of age —
4395
+ * a paid invoice stays paid.
4396
+ * - spent: ALLOW amounts whose timestamp falls inside the CURRENT mandate window
4397
+ * and whose currency matches the mandate (the cap is defined per window/currency).
4398
+ * Records from older versions lack the rehydration fields and are skipped.
4399
+ */
4400
+ rehydrate() {
4401
+ const notBefore = Date.parse(this.mandate.window.notBefore);
4402
+ const notAfter = Date.parse(this.mandate.window.notAfter);
4403
+ for (const r of this.store.records()) {
4404
+ if (r.status !== "ALLOW")
4405
+ continue;
4406
+ if (r.invoiceRef)
4407
+ this.claimedInvoices.add(`${r.subject}|${r.invoiceRef}`);
4408
+ if (typeof r.amount !== "number" || !Number.isFinite(r.amount))
4409
+ continue;
4410
+ if (r.currency !== this.mandate.allow.currency)
4411
+ continue;
4412
+ const t = Date.parse(r.ts);
4413
+ if (!Number.isFinite(t) || t < notBefore || t > notAfter)
4414
+ continue;
4415
+ this.spent += r.amount;
4416
+ }
4446
4417
  }
4447
4418
  async getMandate() {
4448
4419
  return this.mandate;
@@ -4529,6 +4500,96 @@ var HttpFidacyCore = class {
4529
4500
  }
4530
4501
  };
4531
4502
 
4503
+ // ../firewall/dist/audit-store.js
4504
+ var FileAuditStore = class {
4505
+ path;
4506
+ chain = [];
4507
+ constructor(path) {
4508
+ this.path = path;
4509
+ this.load();
4510
+ }
4511
+ load() {
4512
+ if (!fs.existsSync(this.path))
4513
+ return;
4514
+ try {
4515
+ const raw = fs.readFileSync(this.path, "utf8");
4516
+ const lines = raw.split("\n");
4517
+ const parsed = [];
4518
+ let torn = false;
4519
+ for (let i = 0; i < lines.length; i++) {
4520
+ if (!lines[i].trim())
4521
+ continue;
4522
+ try {
4523
+ parsed.push(JSON.parse(lines[i]));
4524
+ } catch {
4525
+ if (lines.slice(i + 1).some((l) => l.trim()))
4526
+ throw new Error(`unparseable record at line ${i + 1}`);
4527
+ torn = true;
4528
+ break;
4529
+ }
4530
+ }
4531
+ this.chain = parsed;
4532
+ if (!this.intact())
4533
+ throw new Error("audit chain integrity broken");
4534
+ if (torn) {
4535
+ const clean = parsed.map((r) => JSON.stringify(r)).join("\n") + (parsed.length ? "\n" : "");
4536
+ fs.writeFileSync(this.path, clean);
4537
+ console.error(`[fidacy] audit log at ${this.path} had a torn trailing line (crash during append?); salvaged ${parsed.length} intact records and truncated the tail.`);
4538
+ }
4539
+ } catch (err) {
4540
+ this.chain = [];
4541
+ try {
4542
+ const quarantine = `${this.path}.corrupt-${Date.now()}`;
4543
+ fs.renameSync(this.path, quarantine);
4544
+ console.error(`[fidacy] audit log at ${this.path} is unreadable/tampered (${err.message}); quarantined to ${quarantine}, starting a fresh chain.`);
4545
+ } catch {
4546
+ console.error(`[fidacy] audit log at ${this.path} is unreadable/tampered and could not be quarantined; starting a fresh in-memory chain.`);
4547
+ }
4548
+ }
4549
+ }
4550
+ head() {
4551
+ return this.chain.length ? this.chain[this.chain.length - 1].hash : "GENESIS";
4552
+ }
4553
+ append(decision) {
4554
+ const prevHash = this.head();
4555
+ const seq = this.chain.length;
4556
+ const ts = decision.ts;
4557
+ const digest = sha256(stableStringify({ decisionId: decision.decisionId, status: decision.status, request: decision.request, violatedRule: decision.violatedRule ?? null }));
4558
+ const hash = sha256(`${prevHash}|${digest}|${seq}|${ts}`);
4559
+ const record2 = { seq, decisionId: decision.decisionId, status: decision.status, subject: decision.subject, digest, prevHash, hash, ts };
4560
+ if (decision.status === "ALLOW") {
4561
+ const req = decision.request;
4562
+ if (typeof req?.amount === "number" && Number.isFinite(req.amount))
4563
+ record2.amount = req.amount;
4564
+ if (typeof req?.currency === "string")
4565
+ record2.currency = req.currency;
4566
+ const invoice = canonInvoice(req?.invoiceRef);
4567
+ if (invoice)
4568
+ record2.invoiceRef = invoice;
4569
+ }
4570
+ fs.appendFileSync(this.path, JSON.stringify(record2) + "\n");
4571
+ this.chain.push(record2);
4572
+ return record2;
4573
+ }
4574
+ find(decisionId) {
4575
+ return this.chain.find((r) => r.decisionId === decisionId);
4576
+ }
4577
+ /** The full loaded chain, oldest first. Read-only; used to rehydrate core state at boot. */
4578
+ records() {
4579
+ return this.chain;
4580
+ }
4581
+ intact() {
4582
+ let prev = "GENESIS";
4583
+ for (const r of this.chain) {
4584
+ const expected = sha256(`${prev}|${r.digest}|${r.seq}|${r.ts}`);
4585
+ if (expected !== r.hash || r.prevHash !== prev)
4586
+ return false;
4587
+ prev = r.hash;
4588
+ }
4589
+ return true;
4590
+ }
4591
+ };
4592
+
4532
4593
  // ../mcp/src/config.ts
4533
4594
  import { randomUUID as randomUUID2 } from "node:crypto";
4534
4595
  import { homedir } from "node:os";
@@ -4610,7 +4671,7 @@ function resolveMandateRules(cfg) {
4610
4671
  }
4611
4672
 
4612
4673
  // ../mcp/src/telemetry.ts
4613
- var CLIENT_VERSION = true ? "0.1.6" : "dev";
4674
+ var CLIENT_VERSION = true ? "0.1.8" : "dev";
4614
4675
  var currentShell = "mcp";
4615
4676
  function setTelemetryShell(shell) {
4616
4677
  currentShell = shell;
@@ -4634,8 +4695,13 @@ var flushTimer = null;
4634
4695
  function resultOf(status, violatedRule) {
4635
4696
  if (status === "ALLOW") return "allow";
4636
4697
  const r = violatedRule ?? "";
4637
- if (r.startsWith("per_tx_cap") || r.startsWith("total_cap")) return "deny_cap";
4698
+ if (r.startsWith("payee_lookalike")) return "deny_lookalike";
4638
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";
4639
4705
  return "deny_scope";
4640
4706
  }
4641
4707
  function record(type, result) {
@@ -4733,7 +4799,7 @@ function requestUpgrade() {
4733
4799
  }
4734
4800
 
4735
4801
  // ../mcp/src/provision.ts
4736
- var CLIENT_VERSION2 = true ? "0.1.6" : "dev";
4802
+ var CLIENT_VERSION2 = true ? "0.1.8" : "dev";
4737
4803
  function provisionEnabled() {
4738
4804
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
4739
4805
  return !(v === "1" || v === "true" || v === "yes");
@@ -3,7 +3,7 @@
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.6",
6
+ "version": "0.1.8",
7
7
  "contracts": {
8
8
  "tools": [
9
9
  "request_payment",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fidacy/openclaw-plugin",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
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",
@@ -56,7 +56,7 @@
56
56
  "typebox": "1.1.39",
57
57
  "typescript": "^5.6.3",
58
58
  "@fidacy/firewall": "0.1.0",
59
- "@fidacy/mcp": "0.1.11"
59
+ "@fidacy/mcp": "0.1.14"
60
60
  },
61
61
  "scripts": {
62
62
  "build": "node scripts/bundle.mjs",