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.
Files changed (63) hide show
  1. package/README.md +7 -0
  2. package/dist/bounded-input.d.ts +8 -0
  3. package/dist/bounded-input.js +21 -0
  4. package/dist/bounded-input.test.d.ts +1 -0
  5. package/dist/bounded-input.test.js +24 -0
  6. package/dist/claims.js +119 -31
  7. package/dist/doctor.d.ts +1 -1
  8. package/dist/doctor.js +1 -1
  9. package/dist/doctor.test.js +2 -0
  10. package/dist/evidence.d.ts +21 -0
  11. package/dist/evidence.js +14 -0
  12. package/dist/feedback.d.ts +17 -0
  13. package/dist/feedback.js +24 -0
  14. package/dist/identity.d.ts +3 -0
  15. package/dist/identity.js +25 -1
  16. package/dist/index.js +1842 -523
  17. package/dist/init.d.ts +6 -2
  18. package/dist/init.js +16 -3
  19. package/dist/init.test.js +18 -4
  20. package/dist/lock.js +47 -9
  21. package/dist/lock.test.d.ts +1 -0
  22. package/dist/lock.test.js +26 -0
  23. package/dist/observed-outcome-pilot.d.ts +16 -0
  24. package/dist/observed-outcome-pilot.js +59 -0
  25. package/dist/observed-outcome-pilot.test.d.ts +1 -0
  26. package/dist/observed-outcome-pilot.test.js +25 -0
  27. package/dist/paybox-link.d.ts +34 -0
  28. package/dist/paybox-link.js +52 -0
  29. package/dist/paybox-link.test.d.ts +1 -0
  30. package/dist/paybox-link.test.js +36 -0
  31. package/dist/private-json.d.ts +2 -0
  32. package/dist/private-json.js +39 -0
  33. package/dist/protocol.test.js +23 -0
  34. package/dist/receipt-metadata.d.ts +8 -0
  35. package/dist/receipt-metadata.js +12 -0
  36. package/dist/receipt-metadata.test.js +18 -1
  37. package/dist/receipt-verifier.d.ts +13 -0
  38. package/dist/receipt-verifier.js +125 -0
  39. package/dist/receipt-verifier.test.d.ts +1 -0
  40. package/dist/receipt-verifier.test.js +54 -0
  41. package/dist/receipts.js +11 -2
  42. package/dist/settlement-driver.d.ts +3 -0
  43. package/dist/settlement-driver.js +120 -0
  44. package/dist/settlement-driver.test.d.ts +1 -0
  45. package/dist/settlement-driver.test.js +78 -0
  46. package/dist/settlement-runner.d.ts +34 -0
  47. package/dist/settlement-runner.js +40 -0
  48. package/dist/settlement-runner.test.d.ts +1 -0
  49. package/dist/settlement-runner.test.js +128 -0
  50. package/dist/status.js +2 -0
  51. package/dist/store.d.ts +2 -0
  52. package/dist/store.js +33 -9
  53. package/dist/technocore-link.d.ts +34 -0
  54. package/dist/technocore-link.js +49 -0
  55. package/dist/technocore-link.test.d.ts +1 -0
  56. package/dist/technocore-link.test.js +36 -0
  57. package/dist/tool-metadata.test.d.ts +1 -0
  58. package/dist/tool-metadata.test.js +86 -0
  59. package/dist/types.d.ts +6 -0
  60. package/dist/usage-recovery.test.d.ts +1 -0
  61. package/dist/usage-recovery.test.js +121 -0
  62. package/package.json +4 -4
  63. 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
- providerEvidence: receipt.providerEvidence
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.map((item) => `${item.receiptId.slice(0, 10)}…: ${item.reason}`).join("; ");
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
- const receipts = eligible.slice(0, maxReceipts);
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 publish = await claims.publishRoot(root);
129
- const publishReceipt = await publish.wait();
130
- let rootApproved = false;
131
- for (let attempt = 0; attempt < 10 && !rootApproved; attempt += 1) {
132
- rootApproved = await claims.approvedRoots(root);
133
- if (!rootApproved)
134
- await new Promise((resolve) => setTimeout(resolve, 3_000));
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
- if (!rootApproved)
137
- throw new Error("Published Merkle root was not visible through the RPC after 30 seconds");
138
- const claim = await claims.claimBatch(root, accounts, amounts, receiptIds, proofs);
139
- const claimReceipt = await claim.wait();
140
- if (!publishReceipt || !claimReceipt)
141
- throw new Error("Settlement transaction receipt is unavailable");
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
- publishTransaction: publishReceipt.hash,
146
- claimTransaction: claimReceipt.hash,
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
- const message = error instanceof Error ? error.message : String(error);
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)).length;
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.map((batch) => batch.publishTransaction),
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
@@ -1,4 +1,4 @@
1
- type CheckStatus = "pass" | "fail" | "warning";
1
+ type CheckStatus = "pass" | "fail" | "warning" | "not_checked";
2
2
  export interface DoctorCheck {
3
3
  status: CheckStatus;
4
4
  detail: string;
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 = warning("Key file exists; Windows ACLs were not changed or re-applied by this read-only diagnostic.");
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.");
@@ -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;
@@ -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
+ }>;
@@ -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
+ }
@@ -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 { ...input, wallet: wallet.address, nonce, issuedAt, walletAuthorization };
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 = {