@fidacy/openclaw-plugin 0.1.4 → 0.1.6
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 +0 -8
- package/dist/index.js +63 -124
- package/openclaw.plugin.json +2 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -30,11 +30,3 @@ 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.
|
package/dist/index.js
CHANGED
|
@@ -4259,9 +4259,66 @@ function sha256(input) {
|
|
|
4259
4259
|
|
|
4260
4260
|
// ../firewall/dist/audit-store.js
|
|
4261
4261
|
import fs from "node:fs";
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
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
|
+
};
|
|
4265
4322
|
|
|
4266
4323
|
// ../firewall/dist/evaluate.js
|
|
4267
4324
|
function fold(s) {
|
|
@@ -4338,6 +4395,7 @@ function evaluate(mandate, req, spentSoFar) {
|
|
|
4338
4395
|
}
|
|
4339
4396
|
|
|
4340
4397
|
// ../firewall/dist/core.js
|
|
4398
|
+
import { randomUUID } from "node:crypto";
|
|
4341
4399
|
function canonInvoice(ref) {
|
|
4342
4400
|
if (typeof ref !== "string")
|
|
4343
4401
|
return "";
|
|
@@ -4385,35 +4443,6 @@ var DevFidacyCore = class {
|
|
|
4385
4443
|
this.mandate = opts.mandate;
|
|
4386
4444
|
this.store = new FileAuditStore(opts.auditLogPath ?? "./fidacy-audit.log");
|
|
4387
4445
|
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
|
-
}
|
|
4417
4446
|
}
|
|
4418
4447
|
async getMandate() {
|
|
4419
4448
|
return this.mandate;
|
|
@@ -4500,96 +4529,6 @@ var HttpFidacyCore = class {
|
|
|
4500
4529
|
}
|
|
4501
4530
|
};
|
|
4502
4531
|
|
|
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
|
-
|
|
4593
4532
|
// ../mcp/src/config.ts
|
|
4594
4533
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
4595
4534
|
import { homedir } from "node:os";
|
|
@@ -4671,7 +4610,7 @@ function resolveMandateRules(cfg) {
|
|
|
4671
4610
|
}
|
|
4672
4611
|
|
|
4673
4612
|
// ../mcp/src/telemetry.ts
|
|
4674
|
-
var CLIENT_VERSION = true ? "0.1.
|
|
4613
|
+
var CLIENT_VERSION = true ? "0.1.6" : "dev";
|
|
4675
4614
|
var currentShell = "mcp";
|
|
4676
4615
|
function setTelemetryShell(shell) {
|
|
4677
4616
|
currentShell = shell;
|
|
@@ -4794,7 +4733,7 @@ function requestUpgrade() {
|
|
|
4794
4733
|
}
|
|
4795
4734
|
|
|
4796
4735
|
// ../mcp/src/provision.ts
|
|
4797
|
-
var CLIENT_VERSION2 = true ? "0.1.
|
|
4736
|
+
var CLIENT_VERSION2 = true ? "0.1.6" : "dev";
|
|
4798
4737
|
function provisionEnabled() {
|
|
4799
4738
|
const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
|
|
4800
4739
|
return !(v === "1" || v === "true" || v === "yes");
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
"id": "fidacy",
|
|
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
7
|
"contracts": {
|
|
7
8
|
"tools": [
|
|
8
9
|
"request_payment",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fidacy/openclaw-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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.
|
|
59
|
+
"@fidacy/mcp": "0.1.11"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "node scripts/bundle.mjs",
|