aipou-mcp-server 0.5.0 → 0.6.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.
- package/README.md +7 -0
- package/dist/bounded-input.d.ts +8 -0
- package/dist/bounded-input.js +21 -0
- package/dist/bounded-input.test.d.ts +1 -0
- package/dist/bounded-input.test.js +24 -0
- package/dist/claims.js +119 -31
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +1 -1
- package/dist/doctor.test.js +2 -0
- package/dist/evidence.d.ts +21 -0
- package/dist/evidence.js +14 -0
- package/dist/feedback.d.ts +17 -0
- package/dist/feedback.js +24 -0
- package/dist/identity.d.ts +3 -0
- package/dist/identity.js +25 -1
- package/dist/index.js +1842 -523
- package/dist/init.d.ts +6 -2
- package/dist/init.js +16 -3
- package/dist/init.test.js +18 -4
- package/dist/lock.js +47 -9
- package/dist/lock.test.d.ts +1 -0
- package/dist/lock.test.js +26 -0
- package/dist/observed-outcome-pilot.d.ts +16 -0
- package/dist/observed-outcome-pilot.js +59 -0
- package/dist/observed-outcome-pilot.test.d.ts +1 -0
- package/dist/observed-outcome-pilot.test.js +25 -0
- package/dist/paybox-link.d.ts +34 -0
- package/dist/paybox-link.js +52 -0
- package/dist/paybox-link.test.d.ts +1 -0
- package/dist/paybox-link.test.js +36 -0
- package/dist/private-json.d.ts +2 -0
- package/dist/private-json.js +39 -0
- package/dist/protocol.test.js +23 -0
- package/dist/receipt-metadata.d.ts +8 -0
- package/dist/receipt-metadata.js +12 -0
- package/dist/receipt-metadata.test.js +18 -1
- package/dist/receipt-verifier.d.ts +13 -0
- package/dist/receipt-verifier.js +125 -0
- package/dist/receipt-verifier.test.d.ts +1 -0
- package/dist/receipt-verifier.test.js +54 -0
- package/dist/receipts.js +11 -2
- package/dist/settlement-driver.d.ts +3 -0
- package/dist/settlement-driver.js +120 -0
- package/dist/settlement-driver.test.d.ts +1 -0
- package/dist/settlement-driver.test.js +78 -0
- package/dist/settlement-runner.d.ts +34 -0
- package/dist/settlement-runner.js +40 -0
- package/dist/settlement-runner.test.d.ts +1 -0
- package/dist/settlement-runner.test.js +128 -0
- package/dist/status.js +2 -0
- package/dist/store.d.ts +2 -0
- package/dist/store.js +33 -9
- package/dist/technocore-link.d.ts +34 -0
- package/dist/technocore-link.js +49 -0
- package/dist/technocore-link.test.d.ts +1 -0
- package/dist/technocore-link.test.js +36 -0
- package/dist/tool-metadata.test.d.ts +1 -0
- package/dist/tool-metadata.test.js +86 -0
- package/dist/types.d.ts +6 -0
- package/dist/usage-recovery.test.d.ts +1 -0
- package/dist/usage-recovery.test.js +121 -0
- package/package.json +4 -4
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# AIPOU MCP Server
|
|
2
2
|
|
|
3
|
+
Version 0.6.0: see the [release notes](https://github.com/0xddneto/AI-Proof-of-Us/blob/main/CHANGELOG.md) and [independent pilot guide](https://github.com/0xddneto/AI-Proof-of-Us/blob/main/docs/independent-pilot.md).
|
|
4
|
+
|
|
5
|
+
`--init` uses the current absolute entrypoint from a source checkout and pins this package version when installed from npm. Windows `--doctor` marks uninspected ACLs as `not_checked`. `complete_ai_task` accepts optional `tokenUsageMeasurement` (`measured`, `unmeasured`, `unspecified`); unmeasured counts must be zero. Legacy receipts remain verifiable. Invalid verification results expose only an unverified `claimedWorkReceiptId`, with `workReceiptId: null`.
|
|
6
|
+
|
|
7
|
+
After a lost completion response, recover the persisted receipt with `export_ai_receipts` by nonce. `--feedback` prints optional local aggregate counts without sending anything. See [claim recovery and operating limits](https://github.com/0xddneto/AI-Proof-of-Us/blob/main/docs/settlement-recovery.md) before validator operation.
|
|
8
|
+
|
|
3
9
|
`aipou-mcp-server` records private, signed receipts for authorized AI tasks. It supports local receipt collection, reward status checks, and optional claims for validated work on Base.
|
|
4
10
|
|
|
5
11
|
It does not detect hidden AI use, prove task quality, replace payment rails, or require raw prompts and outputs to leave the user's machine.
|
|
@@ -107,6 +113,7 @@ Publication details: [docs/npm-publication.md](https://github.com/0xddneto/AI-Pr
|
|
|
107
113
|
- [Local Receipt Mode](https://github.com/0xddneto/AI-Proof-of-Us/tree/main/examples/local-receipt-mode)
|
|
108
114
|
- [Lifecycle Adapter Example](https://github.com/0xddneto/AI-Proof-of-Us/tree/main/examples/lifecycle-adapter)
|
|
109
115
|
- [Evidence Boundaries](https://github.com/0xddneto/AI-Proof-of-Us/blob/main/docs/evidence-boundaries.md)
|
|
116
|
+
- [Flop and Technocore Interoperability](https://github.com/0xddneto/AI-Proof-of-Us/blob/main/docs/flop-technocore-interoperability.md)
|
|
110
117
|
- [Claim Validation Policy](https://github.com/0xddneto/AI-Proof-of-Us/blob/main/docs/claim-validation-policy.md)
|
|
111
118
|
- [Tokenomics](https://github.com/0xddneto/AI-Proof-of-Us/blob/main/docs/tokenomics.md)
|
|
112
119
|
- [Security Policy](https://github.com/0xddneto/AI-Proof-of-Us/blob/main/SECURITY.md)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Transform, type TransformCallback } from "node:stream";
|
|
2
|
+
/** Bound each JSON-RPC line before the SDK parses or validates it. */
|
|
3
|
+
export declare class BoundedStdioInput extends Transform {
|
|
4
|
+
private readonly maxLineBytes;
|
|
5
|
+
private bytes;
|
|
6
|
+
constructor(maxLineBytes?: number);
|
|
7
|
+
_transform(chunk: Buffer, _encoding: BufferEncoding, callback: TransformCallback): void;
|
|
8
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Transform } from "node:stream";
|
|
2
|
+
/** Bound each JSON-RPC line before the SDK parses or validates it. */
|
|
3
|
+
export class BoundedStdioInput extends Transform {
|
|
4
|
+
maxLineBytes;
|
|
5
|
+
bytes = 0;
|
|
6
|
+
constructor(maxLineBytes = 1024 * 1024) {
|
|
7
|
+
super();
|
|
8
|
+
this.maxLineBytes = maxLineBytes;
|
|
9
|
+
}
|
|
10
|
+
_transform(chunk, _encoding, callback) {
|
|
11
|
+
for (const byte of chunk) {
|
|
12
|
+
if (byte === 10)
|
|
13
|
+
this.bytes = 0;
|
|
14
|
+
else if (++this.bytes > this.maxLineBytes) {
|
|
15
|
+
callback(new Error("AIPOU_INPUT_TOO_LARGE: JSON-RPC line exceeds 1 MiB"));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
callback(null, chunk);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { BoundedStdioInput } from "./bounded-input.js";
|
|
4
|
+
test("stdio accepts a bounded fragmented line and resets its byte count at newline", async () => {
|
|
5
|
+
const stream = new BoundedStdioInput(6);
|
|
6
|
+
const chunks = [];
|
|
7
|
+
stream.on("data", (b) => chunks.push(b));
|
|
8
|
+
stream.write(Buffer.from("123"));
|
|
9
|
+
stream.write(Buffer.from("456\n123456\n"));
|
|
10
|
+
stream.end();
|
|
11
|
+
await new Promise((resolve, reject) => {
|
|
12
|
+
stream.on("end", resolve);
|
|
13
|
+
stream.on("error", reject);
|
|
14
|
+
});
|
|
15
|
+
assert.equal(Buffer.concat(chunks).toString(), "123456\n123456\n");
|
|
16
|
+
});
|
|
17
|
+
test("oversized fragmented JSON is rejected before the offending chunk is parsed", async () => {
|
|
18
|
+
const stream = new BoundedStdioInput(4);
|
|
19
|
+
const error = new Promise((resolve) => stream.once("error", resolve));
|
|
20
|
+
stream.resume();
|
|
21
|
+
stream.write(Buffer.from("123"));
|
|
22
|
+
stream.end(Buffer.from("45"));
|
|
23
|
+
assert.match((await error).message, /INPUT_TOO_LARGE/);
|
|
24
|
+
});
|
package/dist/claims.js
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { MerkleTree } from "merkletreejs";
|
|
2
|
-
import { AbiCoder, Contract, JsonRpcProvider, Wallet, concat, getAddress, getBytes, keccak256, parseUnits } from "ethers";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { AbiCoder, Contract, JsonRpcProvider, Transaction, Wallet, concat, getAddress, getBytes, keccak256, parseUnits, } from "ethers";
|
|
3
|
+
import { mkdir, readFile, rm } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { collectorFingerprint, verifyCollectorPayload } from "./collector.js";
|
|
6
6
|
import { verifyTaskAuthorization } from "./identity.js";
|
|
7
|
-
import { DAY_MS, filterEligibleReceipts, settlementPolicyFromEnv } from "./policy.js";
|
|
7
|
+
import { DAY_MS, filterEligibleReceipts, settlementPolicyFromEnv, } from "./policy.js";
|
|
8
8
|
import { deriveTrustTier } from "./providers.js";
|
|
9
|
-
import { markBatchSettled, settledReceiptsSince, unsettledReceipts } from "./store.js";
|
|
9
|
+
import { exportStoredReceipts, markBatchSettled, settledReceiptsSince, unsettledReceipts, } from "./store.js";
|
|
10
|
+
import { withFileLock } from "./lock.js";
|
|
11
|
+
import { readOptionalJson, writePrivateJson } from "./private-json.js";
|
|
12
|
+
import { executeSettlementPlan, } from "./settlement-runner.js";
|
|
13
|
+
import { createSettlementDriver } from "./settlement-driver.js";
|
|
14
|
+
import { canonicalJson } from "./canonical.js";
|
|
15
|
+
import { verifyAipouReceipt } from "./receipt-verifier.js";
|
|
10
16
|
const claimsAbi = [
|
|
11
17
|
"function token() view returns (address)",
|
|
12
18
|
"function validator() view returns (address)",
|
|
13
19
|
"function approvedRoots(bytes32 root) view returns (bool)",
|
|
20
|
+
"function claimedReceipts(bytes32 receiptId) view returns (bool)",
|
|
14
21
|
"function publishRoot(bytes32 root)",
|
|
15
|
-
"function claimBatch(bytes32 root, address[] accounts, uint256[] amounts, bytes32[] receiptIds, bytes32[][] proofs)"
|
|
22
|
+
"function claimBatch(bytes32 root, address[] accounts, uint256[] amounts, bytes32[] receiptIds, bytes32[][] proofs)",
|
|
16
23
|
];
|
|
17
24
|
const tokenAbi = ["function emissionController() view returns (address)"];
|
|
18
25
|
function claimsAddress() {
|
|
@@ -51,6 +58,8 @@ async function trustedCollectorFingerprints() {
|
|
|
51
58
|
return new Set(content.fingerprints);
|
|
52
59
|
}
|
|
53
60
|
async function verifyReceipt(receipt, trustedCollectors) {
|
|
61
|
+
if (!verifyAipouReceipt(receipt).valid)
|
|
62
|
+
throw new Error("Invalid receipt integrity or signature; settlement stopped before submission");
|
|
54
63
|
const fingerprint = collectorFingerprint(receipt.collectorPublicKey);
|
|
55
64
|
if (!trustedCollectors.has(fingerprint)) {
|
|
56
65
|
throw new Error(`Untrusted collector ${fingerprint} for ${receipt.receiptId}`);
|
|
@@ -70,7 +79,7 @@ async function verifyReceipt(receipt, trustedCollectors) {
|
|
|
70
79
|
chainId: receipt.authorizationChainId,
|
|
71
80
|
verifyingContract: receipt.authorizationContract,
|
|
72
81
|
walletAuthorization: receipt.walletAuthorization,
|
|
73
|
-
completedAt: receipt.recordedAt
|
|
82
|
+
completedAt: receipt.recordedAt,
|
|
74
83
|
};
|
|
75
84
|
if (!verifyTaskAuthorization(session))
|
|
76
85
|
throw new Error(`Invalid wallet authorization for ${receipt.receiptId}`);
|
|
@@ -80,23 +89,61 @@ async function verifyReceipt(receipt, trustedCollectors) {
|
|
|
80
89
|
outputTokens: receipt.outputTokens,
|
|
81
90
|
durationSeconds: receipt.durationSeconds,
|
|
82
91
|
outputHash: receipt.outputHash,
|
|
83
|
-
|
|
92
|
+
tokenUsageMeasurement: receipt.tokenUsageMeasurement,
|
|
93
|
+
providerEvidence: receipt.providerEvidence,
|
|
84
94
|
}, receipt.providerEvidence);
|
|
85
95
|
if (derivedTier !== receipt.trustTier)
|
|
86
96
|
throw new Error(`Invalid trust tier for ${receipt.receiptId}`);
|
|
87
97
|
}
|
|
88
98
|
export async function settleRewards(maxReceipts = 25) {
|
|
99
|
+
if (!Number.isInteger(maxReceipts) || maxReceipts < 1 || maxReceipts > 100)
|
|
100
|
+
throw new Error("Batch size must be 1 through 100");
|
|
101
|
+
const directory = path.resolve(process.env.AIPOU_DATA_DIR || ".aipou");
|
|
102
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
103
|
+
try {
|
|
104
|
+
return await withFileLock(path.join(directory, "settlement.lock"), () => settleRewardsLocked(maxReceipts, directory));
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
const message = error instanceof Error ? error.message : "";
|
|
108
|
+
if (message.startsWith("SETTLEMENT_") ||
|
|
109
|
+
message === "There are no unsettled receipts" ||
|
|
110
|
+
message.startsWith("No receipts pass the settlement policy"))
|
|
111
|
+
throw error;
|
|
112
|
+
throw new Error("SETTLEMENT_FAILED: inspect local configuration and saved settlement state before retrying; private RPC and transaction details are withheld.");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function settleRewardsLocked(maxReceipts, directory) {
|
|
116
|
+
const journalPath = path.join(directory, "settlement-pending.json");
|
|
117
|
+
const pending = await readOptionalJson(journalPath);
|
|
89
118
|
const candidates = await unsettledReceipts(Number.MAX_SAFE_INTEGER);
|
|
90
|
-
if (candidates.length === 0)
|
|
119
|
+
if (candidates.length === 0 && !pending)
|
|
91
120
|
throw new Error("There are no unsettled receipts");
|
|
92
121
|
const policy = settlementPolicyFromEnv();
|
|
93
122
|
const recentlySettled = await settledReceiptsSince(Date.now() - DAY_MS);
|
|
94
123
|
const { eligible, skipped } = filterEligibleReceipts(candidates, recentlySettled, policy);
|
|
95
|
-
if (eligible.length === 0) {
|
|
96
|
-
const reasons = skipped
|
|
124
|
+
if (eligible.length === 0 && !pending) {
|
|
125
|
+
const reasons = skipped
|
|
126
|
+
.map((item) => `${item.receiptId.slice(0, 10)}…: ${item.reason}`)
|
|
127
|
+
.join("; ");
|
|
97
128
|
throw new Error(`No receipts pass the settlement policy. Skipped: ${reasons}`);
|
|
98
129
|
}
|
|
99
|
-
|
|
130
|
+
let receipts = eligible.slice(0, maxReceipts);
|
|
131
|
+
if (pending) {
|
|
132
|
+
if (pending.version !== 1 ||
|
|
133
|
+
pending.chainId !== 8453 ||
|
|
134
|
+
!Array.isArray(pending.receiptIds) ||
|
|
135
|
+
pending.receiptIds.length < 1 ||
|
|
136
|
+
pending.receiptIds.length > 100) {
|
|
137
|
+
throw new Error("SETTLEMENT_JOURNAL_INVALID: invalid saved batch");
|
|
138
|
+
}
|
|
139
|
+
const stored = await exportStoredReceipts();
|
|
140
|
+
receipts = pending.receiptIds.map((id) => {
|
|
141
|
+
const receipt = stored.find((item) => item.receiptId === id);
|
|
142
|
+
if (!receipt)
|
|
143
|
+
throw new Error("SETTLEMENT_JOURNAL_INVALID: saved receipt is missing");
|
|
144
|
+
return receipt;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
100
147
|
const trustedCollectors = await trustedCollectorFingerprints();
|
|
101
148
|
for (const receipt of receipts)
|
|
102
149
|
await verifyReceipt(receipt, trustedCollectors);
|
|
@@ -115,6 +162,8 @@ export async function settleRewards(maxReceipts = 25) {
|
|
|
115
162
|
const proofs = leaves.map((leaf) => tree.getHexProof(leaf));
|
|
116
163
|
const signer = await validatorWallet();
|
|
117
164
|
const address = claimsAddress();
|
|
165
|
+
if ((await signer.provider.getNetwork()).chainId !== 8453n)
|
|
166
|
+
throw new Error("Settlement requires Base mainnet (8453)");
|
|
118
167
|
const claims = new Contract(address, claimsAbi, signer);
|
|
119
168
|
const configuredValidator = getAddress(await claims.validator());
|
|
120
169
|
if (configuredValidator !== signer.address) {
|
|
@@ -125,28 +174,59 @@ export async function settleRewards(maxReceipts = 25) {
|
|
|
125
174
|
if (getAddress(await token.emissionController()) !== address) {
|
|
126
175
|
throw new Error("AIPOUClaims is not the token emission controller");
|
|
127
176
|
}
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
177
|
+
const expected = {
|
|
178
|
+
version: 1,
|
|
179
|
+
chainId: 8453,
|
|
180
|
+
contract: address,
|
|
181
|
+
root,
|
|
182
|
+
accounts,
|
|
183
|
+
amounts: amounts.map(String),
|
|
184
|
+
receiptIds,
|
|
185
|
+
proofs,
|
|
186
|
+
};
|
|
187
|
+
if (pending) {
|
|
188
|
+
const { publish: _publish, claim: _claim, ...saved } = pending;
|
|
189
|
+
if (canonicalJson(saved) !== canonicalJson(expected))
|
|
190
|
+
throw new Error("SETTLEMENT_JOURNAL_INVALID: batch does not match verified receipts and configured contract");
|
|
191
|
+
}
|
|
192
|
+
const plan = pending ?? expected;
|
|
193
|
+
for (const kind of ["publish", "claim"]) {
|
|
194
|
+
const saved = plan[kind];
|
|
195
|
+
if (!saved)
|
|
196
|
+
continue;
|
|
197
|
+
const transaction = Transaction.from(saved.raw);
|
|
198
|
+
const expectedData = kind === "publish"
|
|
199
|
+
? claims.interface.encodeFunctionData("publishRoot", [root])
|
|
200
|
+
: claims.interface.encodeFunctionData("claimBatch", [
|
|
201
|
+
root,
|
|
202
|
+
accounts,
|
|
203
|
+
amounts,
|
|
204
|
+
receiptIds,
|
|
205
|
+
proofs,
|
|
206
|
+
]);
|
|
207
|
+
if (transaction.hash !== saved.hash ||
|
|
208
|
+
transaction.from !== signer.address ||
|
|
209
|
+
transaction.chainId !== 8453n ||
|
|
210
|
+
transaction.to?.toLowerCase() !== address.toLowerCase() ||
|
|
211
|
+
transaction.value !== 0n ||
|
|
212
|
+
transaction.data !== expectedData) {
|
|
213
|
+
throw new Error("SETTLEMENT_JOURNAL_INVALID: saved transaction does not match the verified batch");
|
|
214
|
+
}
|
|
135
215
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
if (!
|
|
141
|
-
throw new Error("
|
|
216
|
+
const persist = (value) => writePrivateJson(journalPath, value);
|
|
217
|
+
await persist(plan);
|
|
218
|
+
const transactions = await executeSettlementPlan(plan, createSettlementDriver(signer, address), persist);
|
|
219
|
+
const confirmations = await Promise.all(receiptIds.map((id) => claims.claimedReceipts(id)));
|
|
220
|
+
if (!confirmations.every(Boolean))
|
|
221
|
+
throw new Error("SETTLEMENT_CLAIMS_UNCONFIRMED: receipt flags are not visible; journal retained for reconciliation");
|
|
142
222
|
const batch = {
|
|
143
223
|
root,
|
|
144
224
|
receiptIds,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
settledAt: new Date().toISOString()
|
|
225
|
+
...transactions,
|
|
226
|
+
settledAt: new Date().toISOString(),
|
|
148
227
|
};
|
|
149
228
|
await markBatchSettled(batch);
|
|
229
|
+
await rm(journalPath, { force: true });
|
|
150
230
|
return { ...batch, skippedReceipts: skipped };
|
|
151
231
|
}
|
|
152
232
|
export async function settleAllRewards(batchSize = 100, maxBatches = 20) {
|
|
@@ -162,7 +242,12 @@ export async function settleAllRewards(batchSize = 100, maxBatches = 20) {
|
|
|
162
242
|
}
|
|
163
243
|
}
|
|
164
244
|
catch (error) {
|
|
165
|
-
|
|
245
|
+
// Never return signed raw transactions, RPC credentials or local collector paths.
|
|
246
|
+
const raw = error instanceof Error ? error.message : "Settlement failed";
|
|
247
|
+
const message = raw.startsWith("SETTLEMENT_") ||
|
|
248
|
+
raw === "There are no unsettled receipts"
|
|
249
|
+
? raw.slice(0, 400)
|
|
250
|
+
: "SETTLEMENT_FAILED: inspect local diagnostics and saved settlement state before retrying; no private transaction payload is returned.";
|
|
166
251
|
if (message === "There are no unsettled receipts")
|
|
167
252
|
break;
|
|
168
253
|
stoppedReason = message;
|
|
@@ -172,7 +257,8 @@ export async function settleAllRewards(batchSize = 100, maxBatches = 20) {
|
|
|
172
257
|
if (!stoppedReason && batches.length === maxBatches) {
|
|
173
258
|
stoppedReason = `Stopped after maxBatches=${maxBatches}; run settle_all_ai_rewards again for any remaining receipts`;
|
|
174
259
|
}
|
|
175
|
-
const pendingReceiptCount = (await unsettledReceipts(Number.MAX_SAFE_INTEGER))
|
|
260
|
+
const pendingReceiptCount = (await unsettledReceipts(Number.MAX_SAFE_INTEGER))
|
|
261
|
+
.length;
|
|
176
262
|
const skippedReceipts = [...skippedByReceiptId.values()];
|
|
177
263
|
return {
|
|
178
264
|
batches,
|
|
@@ -180,9 +266,11 @@ export async function settleAllRewards(batchSize = 100, maxBatches = 20) {
|
|
|
180
266
|
settledReceiptCount: batches.reduce((total, batch) => total + batch.receiptIds.length, 0),
|
|
181
267
|
skippedReceipts,
|
|
182
268
|
pendingReceiptCount,
|
|
183
|
-
publishTransactions: batches
|
|
269
|
+
publishTransactions: batches
|
|
270
|
+
.map((batch) => batch.publishTransaction)
|
|
271
|
+
.filter(Boolean),
|
|
184
272
|
claimTransactions: batches.map((batch) => batch.claimTransaction),
|
|
185
273
|
stoppedReason,
|
|
186
|
-
settledAt: new Date().toISOString()
|
|
274
|
+
settledAt: new Date().toISOString(),
|
|
187
275
|
};
|
|
188
276
|
}
|
package/dist/doctor.d.ts
CHANGED
package/dist/doctor.js
CHANGED
|
@@ -60,7 +60,7 @@ export async function runInstallationDoctor(dataDirInput) {
|
|
|
60
60
|
try {
|
|
61
61
|
const keyStat = await stat(keyFile);
|
|
62
62
|
if (process.platform === "win32") {
|
|
63
|
-
keyPermissions =
|
|
63
|
+
keyPermissions = { status: "not_checked", detail: "Windows ACLs are not inspected by this read-only doctor. This is not a detected permission failure; init reports whether it successfully applied access restrictions." };
|
|
64
64
|
}
|
|
65
65
|
else if ((keyStat.mode & 0o077) === 0) {
|
|
66
66
|
keyPermissions = pass("Key file has no group or other permission bits.");
|
package/dist/doctor.test.js
CHANGED
|
@@ -24,6 +24,8 @@ test("installation doctor validates init output without exposing or changing the
|
|
|
24
24
|
assert.equal(result.ready, true);
|
|
25
25
|
assert.equal(result.wallet, initialized.wallet);
|
|
26
26
|
assert.equal(result.keySource, "key_file");
|
|
27
|
+
if (process.platform === "win32")
|
|
28
|
+
assert.equal(result.checks.keyPermissions.status, "not_checked");
|
|
27
29
|
assert.equal(result.version.length > 0, true);
|
|
28
30
|
assert.equal(Object.values(result.checks).every((check) => check.status !== "fail"), true);
|
|
29
31
|
assert.equal(privateKeyAfter, privateKeyBefore);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type EvidenceState = "VERIFIED" | "OBSERVED" | "UNKNOWN" | "ABSENT";
|
|
2
|
+
/**
|
|
3
|
+
* A compact result vocabulary for displaying receipt-adjacent facts without
|
|
4
|
+
* letting any one fact become a universal completion claim.
|
|
5
|
+
*/
|
|
6
|
+
export interface EvidenceReport {
|
|
7
|
+
receiptIntegrity: EvidenceState;
|
|
8
|
+
walletAuthorization: EvidenceState;
|
|
9
|
+
collectorSignature: EvidenceState;
|
|
10
|
+
hostExecution: EvidenceState;
|
|
11
|
+
delivery: EvidenceState;
|
|
12
|
+
businessAcceptance: EvidenceState;
|
|
13
|
+
settlement: EvidenceState;
|
|
14
|
+
externalEffect: EvidenceState;
|
|
15
|
+
}
|
|
16
|
+
export declare function receiptEvidenceReport(input: {
|
|
17
|
+
receiptIntegrity: boolean;
|
|
18
|
+
walletAuthorization: boolean;
|
|
19
|
+
collectorSignature: boolean;
|
|
20
|
+
hostExecution?: EvidenceState;
|
|
21
|
+
}): EvidenceReport;
|
package/dist/evidence.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function receiptEvidenceReport(input) {
|
|
2
|
+
return {
|
|
3
|
+
receiptIntegrity: input.receiptIntegrity ? "VERIFIED" : "UNKNOWN",
|
|
4
|
+
walletAuthorization: input.walletAuthorization ? "VERIFIED" : "UNKNOWN",
|
|
5
|
+
collectorSignature: input.collectorSignature ? "VERIFIED" : "UNKNOWN",
|
|
6
|
+
hostExecution: input.hostExecution ?? "UNKNOWN",
|
|
7
|
+
// These require their own artifacts and verifiers. AIPOU receipts do not
|
|
8
|
+
// infer them from a completed local task.
|
|
9
|
+
delivery: "UNKNOWN",
|
|
10
|
+
businessAcceptance: "ABSENT",
|
|
11
|
+
settlement: "ABSENT",
|
|
12
|
+
externalEffect: "UNKNOWN"
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Explicit local export only. No identifiers, task hashes, keys, paths or uploads. */
|
|
2
|
+
export declare function localFeedbackSummary(): Promise<{
|
|
3
|
+
schema: string;
|
|
4
|
+
version: string;
|
|
5
|
+
platform: NodeJS.Platform;
|
|
6
|
+
node: string;
|
|
7
|
+
localReceiptCount: number;
|
|
8
|
+
localActiveDays: number;
|
|
9
|
+
completedSessions: number;
|
|
10
|
+
activeSessions: number;
|
|
11
|
+
expiredSessions: number;
|
|
12
|
+
measuredUsageReceipts: number;
|
|
13
|
+
unmeasuredUsageReceipts: number;
|
|
14
|
+
unspecifiedUsageReceipts: number;
|
|
15
|
+
transmission: string;
|
|
16
|
+
interpretation: string;
|
|
17
|
+
}>;
|
package/dist/feedback.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getPackageVersion } from "./version.js";
|
|
2
|
+
import { exportStoredReceipts, protocolStateCounts } from "./store.js";
|
|
3
|
+
/** Explicit local export only. No identifiers, task hashes, keys, paths or uploads. */
|
|
4
|
+
export async function localFeedbackSummary() {
|
|
5
|
+
const receipts = await exportStoredReceipts();
|
|
6
|
+
const counts = await protocolStateCounts();
|
|
7
|
+
const activeDays = new Set(receipts.map((r) => r.recordedAt.slice(0, 10)));
|
|
8
|
+
return {
|
|
9
|
+
schema: "aipou-local-feedback-v1",
|
|
10
|
+
version: await getPackageVersion(),
|
|
11
|
+
platform: process.platform,
|
|
12
|
+
node: process.versions.node,
|
|
13
|
+
localReceiptCount: receipts.length,
|
|
14
|
+
localActiveDays: activeDays.size,
|
|
15
|
+
completedSessions: counts.completedSessions,
|
|
16
|
+
activeSessions: counts.activeSessions,
|
|
17
|
+
expiredSessions: counts.expiredSessions,
|
|
18
|
+
measuredUsageReceipts: receipts.filter((r) => r.tokenUsageMeasurement === "measured").length,
|
|
19
|
+
unmeasuredUsageReceipts: receipts.filter((r) => r.tokenUsageMeasurement === "unmeasured").length,
|
|
20
|
+
unspecifiedUsageReceipts: receipts.filter((r) => !r.tokenUsageMeasurement || r.tokenUsageMeasurement === "unspecified").length,
|
|
21
|
+
transmission: "none; review before sharing voluntarily",
|
|
22
|
+
interpretation: "one local installation; not unique-user, organic-adoption or revenue metrics",
|
|
23
|
+
};
|
|
24
|
+
}
|
package/dist/identity.d.ts
CHANGED
|
@@ -6,6 +6,9 @@ export declare const authorizationTypes: {
|
|
|
6
6
|
type: string;
|
|
7
7
|
}[];
|
|
8
8
|
};
|
|
9
|
+
export declare const DEFAULT_TASK_SESSION_TTL_SECONDS: number;
|
|
10
|
+
export declare function taskSessionExpiry(session: Pick<TaskSession, "issuedAt" | "expiresAt">): number;
|
|
11
|
+
export declare function isTaskSessionExpired(session: Pick<TaskSession, "issuedAt" | "expiresAt">, nowSeconds?: number): boolean;
|
|
9
12
|
export declare function agentWallet(): Wallet;
|
|
10
13
|
export declare function createTaskSession(input: {
|
|
11
14
|
provider: string;
|
package/dist/identity.js
CHANGED
|
@@ -12,6 +12,23 @@ export const authorizationTypes = {
|
|
|
12
12
|
{ name: "issuedAt", type: "uint256" }
|
|
13
13
|
]
|
|
14
14
|
};
|
|
15
|
+
export const DEFAULT_TASK_SESSION_TTL_SECONDS = 24 * 60 * 60;
|
|
16
|
+
function taskSessionTtlSeconds() {
|
|
17
|
+
const configured = process.env.AIPOU_TASK_SESSION_TTL_SECONDS;
|
|
18
|
+
if (!configured)
|
|
19
|
+
return DEFAULT_TASK_SESSION_TTL_SECONDS;
|
|
20
|
+
const value = Number.parseInt(configured, 10);
|
|
21
|
+
if (!Number.isInteger(value) || value < 60 || value > 7 * DEFAULT_TASK_SESSION_TTL_SECONDS) {
|
|
22
|
+
throw new Error("AIPOU_TASK_SESSION_TTL_SECONDS must be an integer from 60 through 604800");
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
export function taskSessionExpiry(session) {
|
|
27
|
+
return session.expiresAt ?? session.issuedAt + taskSessionTtlSeconds();
|
|
28
|
+
}
|
|
29
|
+
export function isTaskSessionExpired(session, nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
30
|
+
return nowSeconds >= taskSessionExpiry(session);
|
|
31
|
+
}
|
|
15
32
|
function readKeyFile(keyFile) {
|
|
16
33
|
try {
|
|
17
34
|
return readFileSync(path.resolve(keyFile), "utf8").trim();
|
|
@@ -52,7 +69,14 @@ export async function createTaskSession(input) {
|
|
|
52
69
|
issuedAt
|
|
53
70
|
};
|
|
54
71
|
const walletAuthorization = await wallet.signTypedData(domain, authorizationTypes, value);
|
|
55
|
-
return {
|
|
72
|
+
return {
|
|
73
|
+
...input,
|
|
74
|
+
wallet: wallet.address,
|
|
75
|
+
nonce,
|
|
76
|
+
issuedAt,
|
|
77
|
+
expiresAt: issuedAt + taskSessionTtlSeconds(),
|
|
78
|
+
walletAuthorization
|
|
79
|
+
};
|
|
56
80
|
}
|
|
57
81
|
export function verifyTaskAuthorization(session) {
|
|
58
82
|
const domain = {
|