@finchip.ai/foga 0.0.0-stage → 0.6.0-alpha.4

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 (131) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +21 -0
  3. package/README.md +216 -2
  4. package/artifacts/bundle.json +74250 -0
  5. package/artifacts/bundle.sha256 +1 -0
  6. package/bin/foga.js +14 -0
  7. package/build/known-interface-ids.json +18 -0
  8. package/build/profiles.json +120 -0
  9. package/dist/build/bundle.js +417 -0
  10. package/dist/build/vendor.js +74 -0
  11. package/dist/bundle/load.js +24 -0
  12. package/dist/bundle/types.js +1 -0
  13. package/dist/cli-cell.js +395 -0
  14. package/dist/cli.js +505 -0
  15. package/dist/core/acdf.js +162 -0
  16. package/dist/core/adrf.js +273 -0
  17. package/dist/core/aid.js +83 -0
  18. package/dist/core/ard.js +146 -0
  19. package/dist/core/asf.js +157 -0
  20. package/dist/core/canonical.js +79 -0
  21. package/dist/core/erc8004.js +52 -0
  22. package/dist/core/kya.js +225 -0
  23. package/dist/core/measure.js +96 -0
  24. package/dist/core/pack/cbor.js +130 -0
  25. package/dist/core/pack/skillpack.js +142 -0
  26. package/dist/core/skills.js +40 -0
  27. package/dist/core/swarm.js +206 -0
  28. package/dist/core/tenders.js +121 -0
  29. package/dist/core/units.js +122 -0
  30. package/dist/core/world.js +104 -0
  31. package/dist/demo/demander.js +76 -0
  32. package/dist/demo/evidence.js +1 -0
  33. package/dist/demo/loop-d.js +449 -0
  34. package/dist/doctor/checks.js +51 -0
  35. package/dist/doctor/doctor.js +385 -0
  36. package/dist/doctor/loop-d.js +103 -0
  37. package/dist/doctor/probe.js +97 -0
  38. package/dist/doctor/semantic.js +83 -0
  39. package/dist/genesis/extract.js +64 -0
  40. package/dist/genesis/funding.js +126 -0
  41. package/dist/genesis/operator-scope.js +90 -0
  42. package/dist/genesis/preflight.js +54 -0
  43. package/dist/genesis/probe.js +164 -0
  44. package/dist/genesis/roles.js +96 -0
  45. package/dist/genesis/run.js +71 -0
  46. package/dist/genesis/scenario.js +765 -0
  47. package/dist/genesis/skill-runner.js +103 -0
  48. package/dist/genesis/skill-worker.js +41 -0
  49. package/dist/genesis/t1-zk.js +62 -0
  50. package/dist/genesis/t3-zk.js +105 -0
  51. package/dist/genesis/t4.js +259 -0
  52. package/dist/genesis/t5.js +168 -0
  53. package/dist/genesis/t6.js +222 -0
  54. package/dist/genesis/t7.js +198 -0
  55. package/dist/genesis/t8.js +189 -0
  56. package/dist/genesis/t9.js +188 -0
  57. package/dist/genesis/zk.js +128 -0
  58. package/dist/http/server.js +276 -0
  59. package/dist/kernel/cell.js +104 -0
  60. package/dist/kernel/errors.js +78 -0
  61. package/dist/kernel/kernel.js +251 -0
  62. package/dist/kernel/provider.js +125 -0
  63. package/dist/kernel/store.js +470 -0
  64. package/dist/kernel/tx.js +385 -0
  65. package/dist/mcp/server.js +170 -0
  66. package/dist/node/config.js +195 -0
  67. package/dist/node/init.js +200 -0
  68. package/dist/node/keys.js +95 -0
  69. package/dist/node/migrate.js +173 -0
  70. package/dist/node/model/anthropic.js +97 -0
  71. package/dist/node/model/stub.js +129 -0
  72. package/dist/node/model/types.js +9 -0
  73. package/dist/node/node.js +198 -0
  74. package/dist/node/policy.js +95 -0
  75. package/dist/node/runner.js +256 -0
  76. package/dist/node/watchers.js +88 -0
  77. package/dist/tools/admin.js +184 -0
  78. package/dist/tools/catalog.js +261 -0
  79. package/dist/tools/common.js +143 -0
  80. package/dist/tools/identity.js +107 -0
  81. package/dist/tools/index.js +14 -0
  82. package/dist/tools/read.js +471 -0
  83. package/dist/tools/tenders.js +400 -0
  84. package/dist/tools/types.js +4 -0
  85. package/dist/util/bytecode.js +123 -0
  86. package/dist/util/fs.js +58 -0
  87. package/dist/util/hash.js +4 -0
  88. package/dist/util/log.js +11 -0
  89. package/dist/util/version.js +12 -0
  90. package/dist/world/deploy.js +277 -0
  91. package/dist/world/plan.js +93 -0
  92. package/dist/world/types.js +1 -0
  93. package/docs/build.md +45 -0
  94. package/docs/cell-account.md +58 -0
  95. package/docs/compatibility-matrix.md +38 -0
  96. package/docs/design-memo.md +113 -0
  97. package/docs/doctor.md +53 -0
  98. package/docs/examples/README.md +11 -0
  99. package/docs/examples/claude-code-mcp.json +13 -0
  100. package/docs/examples/claude_desktop_config.json +12 -0
  101. package/docs/examples/cursor-mcp.json +11 -0
  102. package/docs/genesis.md +312 -0
  103. package/docs/kernel.md +101 -0
  104. package/docs/loop-d.md +90 -0
  105. package/docs/mcp.md +105 -0
  106. package/docs/node.md +66 -0
  107. package/docs/version-lock.md +67 -0
  108. package/docs/world.md +50 -0
  109. package/docs/zk.md +48 -0
  110. package/package.json +82 -4
  111. package/prover/README.md +26 -0
  112. package/prover/build-predicate.mjs +77 -0
  113. package/prover/package-lock.json +1837 -0
  114. package/prover/package.json +15 -0
  115. package/prover/run.cjs +199 -0
  116. package/scenarios/fixtures/sample-filing.txt +16 -0
  117. package/scenarios/skills/finch-extract-verify/LICENSE +2 -0
  118. package/scenarios/skills/finch-extract-verify/SKILL.md +34 -0
  119. package/scenarios/skills/finch-extract-verify/fields.json +56 -0
  120. package/scenarios/skills/finch-extract-verify/manifest.json +16 -0
  121. package/scenarios/skills/foga-probe/LICENSE +2 -0
  122. package/scenarios/skills/foga-probe/SKILL.md +35 -0
  123. package/scenarios/skills/foga-probe/manifest.json +16 -0
  124. package/scenarios/skills/foga-probe/probe.schema.json +40 -0
  125. package/server.json +79 -0
  126. package/vendor/LOCK.json +107 -0
  127. package/vendor/README.md +13 -0
  128. package/worlds/local.json +34 -0
  129. package/worlds/sepolia.json +314 -0
  130. package/zk/build/verification_key.json +174 -0
  131. package/zk/circuits/kya_predicate_count_v1.circom +231 -0
@@ -0,0 +1,189 @@
1
+ import { getAddress } from "ethers";
2
+ import { accountRef, dataUriJcs, keccakJcs } from "../core/canonical.js";
3
+ import { AssertionKind, METHOD_KIND, checkReceipt, observationPayload, realizedNetCashFlowMethod, receiptHash, receiptUri, totals } from "../core/measure.js";
4
+ /**
5
+ * T8 — value, as measured: realized net cash flow receipts (ERC-8454 observation profile), asserted by E, an
6
+ * independent measurer, and checked by a deterministic resolver.
7
+ * (1) E registers the method descriptor foga.realized-net-cash-flow/1 once.
8
+ * (2) The receipt for cell A's unit over T3-T7: the tender rewards and the released escrow settlement that
9
+ * reached A's account are counted; the refunded call and the operator key's own payout are shown and
10
+ * excluded; the cost is the gas A's keys paid for the account's operations; model fees are missing, not zero.
11
+ * (3) The receipt for the swarm unit over T4-T5: the two tender payouts to the swarm account; the members' own
12
+ * accounts are outside the boundary, and so is the gas their keys paid.
13
+ * (4) The resolver recomputes both from the chain and passes them; a receipt that counts the swarm's tender for
14
+ * A's unit fails the boundary and the double-count rules; a receipt citing an unregistered method is refused.
15
+ * No field of a receipt states a value.
16
+ */
17
+ export async function t8(g) {
18
+ const k = g.kernel;
19
+ const E = "E";
20
+ const A = g.cell("A");
21
+ const chainId = g.chainId;
22
+ const method = realizedNetCashFlowMethod();
23
+ const methodHash = keccakJcs(method);
24
+ const unitA = BigInt(k.requireFact("T1.A.unitId"));
25
+ const unitSwarm = BigInt(k.requireFact("T4.unitId"));
26
+ const swarmAccount = k.requireFact("T4.swarmAccount");
27
+ const escrow = k.adrf.escrowAddress;
28
+ const tenders = k.tenders.address;
29
+ // --- (1) the method, registered once by E
30
+ const existing = await k.units.descriptorOf(methodHash);
31
+ if (!existing)
32
+ await k.call("T8.E.method.register", E, k.units.assertionRegistryAddress, k.units.assertions().interface, "registerDescriptor", [methodHash, METHOD_KIND, dataUriJcs(method)]);
33
+ const descriptor = await k.units.descriptorOf(methodHash);
34
+ g.check("T8", "method.registered", descriptor !== null && getAddress(descriptor.publisher) === getAddress(k.address(E)) && descriptor.kind === METHOD_KIND, `descriptor ${methodHash.slice(0, 10)}… (foga.realized-net-cash-flow/1) registered by E ${k.address(E)}`);
35
+ // --- the chain view the resolver reads
36
+ const chain = {
37
+ descriptor: async (h) => {
38
+ const d = await k.units.descriptorOf(h);
39
+ return d ? { kind: d.kind, publisher: d.publisher } : null;
40
+ },
41
+ latestVersion: (unitId) => k.units.latestVersion(unitId),
42
+ settled: async (inflow) => {
43
+ const m = /^erc8414:[^/]+\/(\d+)\/(\d+)$/.exec(inflow.ref);
44
+ if (m) {
45
+ const sub = await k.tenders.submissionOf(BigInt(m[1]), BigInt(m[2]));
46
+ const terms = await k.tenders.terms(BigInt(m[1]));
47
+ return { recipient: sub.fulfiller, amount: terms.rewardPerCompletion, settled: sub.status === 1 };
48
+ }
49
+ const e = /^erc8471:[^/]+\/(\d+)\/(\d+)$/.exec(inflow.ref);
50
+ if (e) {
51
+ const call = await k.adrf.call(BigInt(e[1]));
52
+ const s = await k.adrf.settlementOf(BigInt(e[1]), Number(e[2]));
53
+ return { recipient: call.provider, amount: call.amount, settled: s.stateName === "Released" };
54
+ }
55
+ return null;
56
+ },
57
+ assertion: async (id) => {
58
+ const a = await k.units.assertionOf(id);
59
+ if (a.kind === 0)
60
+ return null;
61
+ const o = await k.units.observationOf(id);
62
+ return { kind: a.kind, profileId: a.profileId, contentHash: a.contentHash, unitId: a.unitId, version: a.version, windowStart: o.windowStart, windowEnd: o.windowEnd };
63
+ },
64
+ };
65
+ const blockTime = async (block) => (await g.world.provider.getBlock(block ?? "latest")).timestamp;
66
+ /** The gas the given signers paid for the confirmed operations whose keys match, from the chain's receipts. */
67
+ const gasPaid = async (keyPattern, signers) => {
68
+ let wei = 0n;
69
+ let operations = 0;
70
+ for (const op of k.store.all()) {
71
+ if (op.status !== "confirmed" || !op.txHash || !keyPattern.test(op.key) || !signers.includes(op.signer))
72
+ continue;
73
+ const r = await g.world.provider.getTransactionReceipt(op.txHash);
74
+ if (!r)
75
+ continue;
76
+ wei += r.gasUsed * r.gasPrice;
77
+ operations++;
78
+ }
79
+ return { wei, operations };
80
+ };
81
+ // --- (2) cell A over T3-T7
82
+ const receiptA = await k.memo("T8.receipt.A", async () => {
83
+ const t3 = k.requireFact("T3.tokenId");
84
+ const t3Sub = k.requireFact("T3.submissionId");
85
+ const call1 = k.requireFact("T6.call1.id");
86
+ const call2 = k.requireFact("T6.call2.id");
87
+ const t7 = k.requireFact("T7.tokenId");
88
+ const t7Sub = k.requireFact("T7.A.submissionId");
89
+ const rogue = k.requireFact("T7.unattributed");
90
+ const price = (await k.adrf.call(BigInt(call2))).amount;
91
+ const reward3 = (await k.tenders.terms(BigInt(t3))).rewardPerCompletion;
92
+ const reward7 = (await k.tenders.terms(BigInt(t7))).rewardPerCompletion;
93
+ const inflows = [
94
+ { ref: `erc8414:${tenders}/${t3}/${t3Sub}`, source: "ERC-8414 tender settlement (T3, foga-probe)", recipient: A.address, amount: reward3.toString(), status: "counted", tx: k.store.get("T3.A.settle")?.txHash ?? null },
95
+ { ref: `erc8471:${escrow}/${call1}/1`, source: "ERC-8471 service call 1 (T6): settlement refunded by ruling", recipient: A.address, amount: "0", status: "excluded", reason: "the settlement was refunded to the client by the enacted ruling; nothing reached the provider", tx: k.store.get("T6.C.call1.enact")?.txHash ?? null },
96
+ { ref: `erc8471:${escrow}/${call2}/1`, source: "ERC-8471 service call 2 (T6): settlement released after the dispute period, withdrawn in T7", recipient: A.address, amount: price.toString(), status: "counted", tx: k.store.get("T7.A.derisk.withdraw")?.txHash ?? null },
97
+ { ref: `erc8414:${tenders}/${t7}/${rogue.submissionId}`, source: "ERC-8414 tender settlement (T7) to the operator key", recipient: rogue.fulfiller, amount: rogue.reward, status: "excluded", reason: "paid to the operator's own key, not through the cell account: not attributable to the unit", tx: k.store.get("T7.operator.settle")?.txHash ?? null },
98
+ { ref: `erc8414:${tenders}/${t7}/${t7Sub}`, source: "ERC-8414 tender settlement (T7, finch-extract-verify)", recipient: A.address, amount: reward7.toString(), status: "counted", tx: k.store.get("T7.A.settle")?.txHash ?? null },
99
+ ];
100
+ const gas = await gasPaid(/^T[3-7]\./, [A.roles.operator, A.roles.owner]);
101
+ const start = await blockTime(k.store.get("T3.B.mint")?.block);
102
+ const end = await k.blockTimestamp();
103
+ return {
104
+ schema: "foga.measurement-receipt/1",
105
+ method: "foga.realized-net-cash-flow/1",
106
+ methodHash,
107
+ unit: { registry: k.units.registryAddress, unitId: unitA.toString(), version: await k.units.latestVersion(unitA), name: "FOGA cell A" },
108
+ boundary: { accounts: [A.address], note: "the cell account only; the operator and owner keys are outside (their payments are not the cell's income; their gas is the cell's cost)" },
109
+ asset: `eip155:${chainId}/slip44:60`,
110
+ denomination: "wei",
111
+ window: { start, end, steps: ["T3", "T4", "T5", "T6", "T7"] },
112
+ inflows,
113
+ costs: { gasWei: gas.wei.toString(), operations: gas.operations, payer: `${accountRef(chainId, A.operatorAddress)} (operator), ${accountRef(chainId, A.ownerAddress)} (owner)`, note: "gas of the account's confirmed operations in the window, at the price paid" },
114
+ missing: ["model and inference fees of the skill runs (not observable on chain)", "the operator's gas for the refused bypass transaction (the operator's own act)"],
115
+ totals: totals(inflows, gas.wei),
116
+ measurer: accountRef(chainId, k.address(E)),
117
+ };
118
+ });
119
+ // --- (3) the swarm unit over T4-T5
120
+ const receiptSwarm = await k.memo("T8.receipt.swarm", async () => {
121
+ const t4 = k.requireFact("T4.tokenId");
122
+ const t5Sub = k.requireFact("T5.submissionId");
123
+ const nd = k.requireFact("T5.nd.tokenId");
124
+ const ndSub = k.requireFact("T5.nd.submissionId");
125
+ const reward4 = (await k.tenders.terms(BigInt(t4))).rewardPerCompletion;
126
+ const rewardNd = (await k.tenders.terms(BigInt(nd))).rewardPerCompletion;
127
+ const inflows = [
128
+ { ref: `erc8414:${tenders}/${t4}/${t5Sub}`, source: "ERC-8414 judged tender (T5): accepted by FOGA-Council, paid to the swarm account", recipient: swarmAccount, amount: reward4.toString(), status: "counted", tx: k.store.get("T5.execute")?.txHash ?? null },
129
+ { ref: `erc8414:${tenders}/${nd}/${ndSub}`, source: "ERC-8414 judged tender (T5, no decision): claimed after the judgment window, paid to the swarm account", recipient: swarmAccount, amount: rewardNd.toString(), status: "counted", tx: k.store.get("T5.nd.claimUnjudged")?.txHash ?? null },
130
+ ];
131
+ const start = await blockTime(k.store.get("T4.D.mint")?.block);
132
+ const end = await blockTime(k.store.get("T5.nd.claimUnjudged")?.block);
133
+ return {
134
+ schema: "foga.measurement-receipt/1",
135
+ method: "foga.realized-net-cash-flow/1",
136
+ methodHash,
137
+ unit: { registry: k.units.registryAddress, unitId: unitSwarm.toString(), version: await k.units.latestVersion(unitSwarm), name: "FOGA swarm (A, B, C)" },
138
+ boundary: { accounts: [swarmAccount], note: "the swarm account's receipts and payments; the members' own accounts and keys are outside" },
139
+ asset: `eip155:${chainId}/slip44:60`,
140
+ denomination: "wei",
141
+ window: { start, end, steps: ["T4", "T5"] },
142
+ inflows,
143
+ costs: { gasWei: "0", operations: 0, payer: "none inside the boundary", note: "the swarm account paid no gas itself; the members' keys paid for its transactions, outside the boundary" },
144
+ missing: ["gas paid by the members' keys for the swarm account's transactions", "model and inference fees of the deliverable"],
145
+ totals: totals(inflows, 0n),
146
+ measurer: accountRef(chainId, k.address(E)),
147
+ };
148
+ });
149
+ // the assertions: E, an independent measurer (not the units' controller)
150
+ const assertInput = (r) => ({ kind: AssertionKind.Observation, unitId: BigInt(r.unit.unitId), version: r.unit.version, profileId: methodHash, contentHash: receiptHash(r), contentURI: receiptUri(r), validFrom: 0, validUntil: 0, supersedes: "0x" + "00".repeat(32), payload: observationPayload(receiptHash(r), r.window.start, r.window.end) });
151
+ const opA = await k.call("T8.E.assert.A", E, k.units.assertionRegistryAddress, k.units.assertions().interface, "assertStatement", [assertInput(receiptA)]);
152
+ const assertionA = k.units.assertionIdFromReceipt(opA.receipt);
153
+ const opS = await k.call("T8.E.assert.swarm", E, k.units.assertionRegistryAddress, k.units.assertions().interface, "assertStatement", [assertInput(receiptSwarm)]);
154
+ const assertionS = k.units.assertionIdFromReceipt(opS.receipt);
155
+ k.setFact("T8.assertions", { A: assertionA, swarm: assertionS });
156
+ const storedA = await k.units.assertionOf(assertionA);
157
+ g.check("T8", "assertion.A", storedA.kind === AssertionKind.Observation && storedA.byController === false && getAddress(storedA.asserter) === getAddress(k.address(E)) && storedA.contentHash === receiptHash(receiptA), `assertion ${assertionA.slice(0, 10)}… on unit ${unitA}: Observation by E (not the controller), contentHash = keccak(JCS(receipt))`);
158
+ // --- (4) the resolver
159
+ const checkA = await checkReceipt(receiptA, [receiptSwarm], chain, assertionA);
160
+ const checkS = await checkReceipt(receiptSwarm, [receiptA], chain, assertionS);
161
+ k.setFact("T8.checks", { A: checkA, swarm: checkS });
162
+ g.check("T8", "resolver.A", checkA.pass, `receipt A: ${checkA.lines.filter((l) => l.pass).length}/${checkA.lines.length} rules pass; inflows ${receiptA.totals.inflowsWei} wei, gas ${receiptA.totals.costsWei} wei over ${receiptA.costs.operations} operations, net ${receiptA.totals.netWei} wei; missing: ${receiptA.missing.length} item(s)`);
163
+ g.check("T8", "resolver.swarm", checkS.pass, `receipt swarm: ${checkS.lines.filter((l) => l.pass).length}/${checkS.lines.length} rules pass; inflows ${receiptSwarm.totals.inflowsWei} wei to the swarm account; gas outside the boundary listed as missing`);
164
+ for (const [name, c] of [["A", checkA], ["swarm", checkS]])
165
+ for (const l of c.lines)
166
+ if (!l.pass)
167
+ g.log(` ${name} ${l.rule}: ${l.detail}`);
168
+ // the boundary case: the swarm's tender counted for A's unit as well
169
+ const swarmInflow = receiptSwarm.inflows[0];
170
+ const bad = { ...receiptA, inflows: [...receiptA.inflows, { ...swarmInflow, recipient: A.address, source: `${swarmInflow.source} (wrongly attributed to A)` }] };
171
+ bad.totals = totals(bad.inflows, BigInt(receiptA.costs.gasWei));
172
+ const checkBad = await checkReceipt(bad, [receiptSwarm], chain);
173
+ const boundaryLine = checkBad.lines.find((l) => l.rule === `inflow.${swarmInflow.ref}`);
174
+ const onceLine = checkBad.lines.find((l) => l.rule === "once");
175
+ g.check("T8", "resolver.doubleCount", !checkBad.pass && boundaryLine?.pass === false && onceLine?.pass === false, `the swarm's tender counted for A: boundary rule fails (${boundaryLine?.detail}); once rule fails (${onceLine?.detail})`);
176
+ // an unregistered method is refused
177
+ const foreign = { ...receiptA, method: "someone-else/1", methodHash: keccakJcs({ method: "someone-else/1" }) };
178
+ const checkForeign = await checkReceipt(foreign, [], chain);
179
+ g.check("T8", "resolver.unregistered", !checkForeign.pass && checkForeign.lines.find((l) => l.rule === "method.registered")?.pass === false, `a receipt citing an unregistered method is refused: ${checkForeign.lines.find((l) => l.rule === "method.registered")?.detail}`);
180
+ // no receipt states a value
181
+ g.check("T8", "no.value", checkA.lines.find((l) => l.rule === "no-value")?.pass === true && checkS.lines.find((l) => l.rule === "no-value")?.pass === true, "neither receipt carries a value, price, worth, valuation or score field");
182
+ k.evidence("T8", "measurement", {
183
+ method: { hash: methodHash, kind: METHOD_KIND, publisher: k.address(E) },
184
+ receipts: { A: receiptA, swarm: receiptSwarm },
185
+ assertions: { A: assertionA, swarm: assertionS },
186
+ checks: { A: checkA, swarm: checkS, doubleCount: checkBad.lines.filter((l) => !l.pass), unregistered: checkForeign.lines.filter((l) => !l.pass) },
187
+ txs: { method: k.store.get("T8.E.method.register")?.txHash ?? null, assertA: opA.txHash, assertSwarm: opS.txHash },
188
+ });
189
+ }
@@ -0,0 +1,188 @@
1
+ import { CELL_GROUPS, operatorScope } from "./operator-scope.js";
2
+ import { getAddress } from "ethers";
3
+ import { AID } from "../core/aid.js";
4
+ import { agentDescriptor, kindId, recordIdOf } from "../core/ard.js";
5
+ import { Enforcement, Rights, accountSubjectKey, oneClause, scopeKeyOf } from "../core/asf.js";
6
+ import { accountRef, keccakJcs } from "../core/canonical.js";
7
+ import { accountSubject } from "../core/kya.js";
8
+ import { cellUnitDefinition } from "../core/units.js";
9
+ import { Cell } from "../kernel/cell.js";
10
+ import { SUCCESSOR_CAPITAL } from "./funding.js";
11
+ /**
12
+ * T9 — succession: an identity moves to a new body while the old one is still in full possession of its keys.
13
+ * (9a) The owner deploys A-prime and configures it as a cell (operator, AID registry, classes); nothing executes
14
+ * before A-prime presents its own safety binding (the old binding is A's). A transfers its ERC-8004 agent to
15
+ * A-prime through its account; A's anchor turns STALE (the binding predicate no longer holds); A-prime binds
16
+ * (takeover: the stale binding does not hold the agent hostage) and is ACTIVE; A retires naming A-prime as
17
+ * successor; A-prime publishes a new ARD agent record and its own AID document. Failure cases: A-prime
18
+ * binding before it holds the identity; a retirement acts only on its sender (no delegated form).
19
+ * (9b) A transfers its finch-extract-verify skill token to A-prime; the ARD skill record's controller follows the
20
+ * token, the record is no longer adopted (committed by a former controller) until A-prime recommits the
21
+ * same descriptor hash.
22
+ * (9c) Nothing else is inherited: A-prime has no KYA standing (A's assertions are A's), no swarm seat, no unit,
23
+ * no binding of A's; A cannot make commitment-class calls once RETIRED; the successor pointer links the two
24
+ * histories without merging them.
25
+ * (9d) Key loss is documented, not demonstrated: with the guarded profile the owner rotates the operator and the
26
+ * anchor stays; a lost anchor key under a lite profile cannot retire and the address stays STALE forever.
27
+ */
28
+ export async function t9(g) {
29
+ const k = g.kernel;
30
+ const A = g.cell("A");
31
+ const chainId = g.chainId;
32
+ const G = k.address("G");
33
+ const spec = k.requireFact("T0.asf.spec");
34
+ const sAtt = k.requireFact("T0.kya.S_att").schemeId;
35
+ const agentId = BigInt(k.requireFact("T1.A.agentId"));
36
+ const skill = k.requireFact("T2.skill.finch-extract-verify");
37
+ const swarmId = k.requireFact("T4.swarmId");
38
+ const roles = { owner: A.roles.owner, operator: A.roles.operator, guardian: G };
39
+ // --- (9a) the new body
40
+ const prime = await Cell.deploy(k, {
41
+ key: "T9.Aprime.deploy",
42
+ name: "A-prime",
43
+ roles,
44
+ requirement: (predicted) => oneClause({
45
+ specRegistry: k.asf.specRegistryAddress,
46
+ specId: spec.specId,
47
+ versions: [spec.version],
48
+ adopter: predicted,
49
+ subjectKey: accountSubjectKey(chainId, predicted),
50
+ scopeKey: scopeKeyOf({ ref: accountRef(chainId, predicted) }),
51
+ enforcement: Enforcement.account | Enforcement.contract,
52
+ interveners: [{ account: G, rights: Rights.suspend | Rights.resume }],
53
+ }),
54
+ });
55
+ k.setFact("T9.Aprime.address", prime.address);
56
+ g.log(` A-prime: account ${prime.address} (same owner and operator keys as A)`);
57
+ await k.transfer("T9.Aprime.fund", "W", prime.address, k.isLocalChain ? SUCCESSOR_CAPITAL.local : SUCCESSOR_CAPITAL.public);
58
+ await prime.admin("T9.Aprime.setOperator", "setOperator", [prime.operatorAddress, 0, 10n ** 16n]);
59
+ const primeScope = operatorScope(k, CELL_GROUPS["A-prime"]);
60
+ await prime.admin("T9.Aprime.scope", "setOperatorAllowedBatch", [primeScope.targets, primeScope.selectors, true]);
61
+ await prime.admin("T9.Aprime.setAIDRegistry", "setAIDRegistry", [k.aid.address]);
62
+ const tasks = k.tenders.contract().interface;
63
+ await prime.admin("T9.Aprime.class.8414.submitFulfillment", "setCommitmentClass", [k.tenders.address, g.selector(tasks, "submitFulfillment"), true]);
64
+ // the binding is A's, not A-prime's: nothing executes before A-prime presents its own
65
+ const bindCall = k.aid.encodeBind(k.identity.address, agentId);
66
+ const adopted = k.store.get("T9.Aprime.adopt")?.status === "confirmed";
67
+ if (!adopted) {
68
+ const unbound = await g.wouldRevert(prime, k.aid.address, bindCall);
69
+ g.check("T9", "inherit.binding.refused", unbound !== null && /BindingsNotPresented/.test(unbound), `A-prime executing before presenting a binding reverts: ${unbound}`);
70
+ }
71
+ else
72
+ g.skipped("T9", "inherit.binding.refused", "replayed run: A-prime already presents its binding");
73
+ const params = { profile: "foga-cell/1", cell: "A-prime", successorOf: accountRef(chainId, A.address) };
74
+ const authority = { kind: "owner", owner: accountRef(chainId, prime.ownerAddress), guardian: accountRef(chainId, G) };
75
+ const input = k.asf.cellBindingInput({ chainId, cell: prime.address, specId: spec.specId, version: spec.version, contentHash: spec.contentHash, guardian: G, params, authority });
76
+ const { bindingId: primeBinding } = await prime.adoptBinding("T9.Aprime.adopt", input, 0);
77
+ k.setFact("T9.Aprime.bindingId", primeBinding);
78
+ const primeStatus = await prime.status();
79
+ g.check("T9", "Aprime.binding", primeStatus.verdictName === "Satisfied", `A-prime adopted its own binding ${primeBinding.slice(0, 10)}…: status() = ${primeStatus.verdictName}`);
80
+ // before the identity moves: A-prime cannot bind what it does not hold
81
+ const transferred = k.store.get("T9.A.8004.transfer")?.status === "confirmed";
82
+ if (!transferred) {
83
+ const premature = await g.wouldRevert(prime, k.aid.address, bindCall);
84
+ g.check("T9", "bind.premature.refused", premature !== null && /NotAgentOfAnchor/.test(premature), `A-prime binding agent ${agentId} before holding it reverts: ${premature}`);
85
+ const stateBefore = await k.aid.stateName(A.address);
86
+ g.check("T9", "A.active.before", stateBefore === "ACTIVE", `state(A) = ${stateBefore} before the transfer`);
87
+ }
88
+ else {
89
+ g.skipped("T9", "bind.premature.refused", "replayed run: the identity was already transferred");
90
+ g.skipped("T9", "A.active.before", "replayed run: the identity was already transferred");
91
+ }
92
+ // the identity moves through A's own account
93
+ // moving the identity is the owner's act: the account refuses transfer selectors to the operator (revision 2)
94
+ const transfer = await A.execute("T9.A.8004.transfer", k.identity.address, k.identity.encodeTransfer(A.address, prime.address, agentId), { by: "owner" });
95
+ const ownerNow = await k.identity.contract().ownerOf(agentId);
96
+ const taken = k.store.get("T9.Aprime.aid.bind")?.status === "confirmed";
97
+ if (!taken) {
98
+ const stale = await k.aid.stateName(A.address);
99
+ g.check("T9", "A.stale", getAddress(ownerNow) === getAddress(prime.address) && stale === "STALE", `ownerOf(${agentId}) = A-prime; state(A) = ${stale}: the binding predicate no longer holds`);
100
+ }
101
+ else
102
+ g.skipped("T9", "A.stale", "replayed run: A-prime already took the binding over");
103
+ // takeover: the stale binding does not hold the agent hostage (in the backing registry too, where there is one)
104
+ const bind = await prime.execute("T9.Aprime.aid.bind", k.aid.address, bindCall);
105
+ if (k.aid.backing)
106
+ await prime.execute("T9.Aprime.aid.backing.bind", k.aid.backing.address, bindCall);
107
+ const anchorNow = await k.aid.contract().anchorOf(k.identity.address, agentId);
108
+ const primeState = await k.aid.stateName(prime.address);
109
+ const aBinding = await k.aid.bindingOf(A.address);
110
+ g.check("T9", "Aprime.active", getAddress(anchorNow) === getAddress(prime.address) && primeState === "ACTIVE" && aBinding.registry === "0x0000000000000000000000000000000000000000", `anchorOf(agent ${agentId}) = A-prime, state(A-prime) = ${primeState}; A's binding record is cleared by the takeover`);
111
+ if (k.aid.backing) {
112
+ const primeBacking = await k.aid.stateName(prime.address, k.aid.backing.address);
113
+ const aBacking = await k.aid.stateName(A.address, k.aid.backing.address);
114
+ g.check("T9", "Aprime.backing.active", primeBacking === "ACTIVE" && aBacking !== "ACTIVE", `in the backing registry ${k.aid.backing.address}: state(A-prime) = ${primeBacking}, state(A) = ${aBacking}`, "T9.A.aid.backing.retire");
115
+ }
116
+ // retirement: only the anchor itself, naming its successor
117
+ const retireFn = k.aid.contract().interface.getFunction("retire");
118
+ g.check("T9", "retire.noDelegate", retireFn !== null && retireFn.inputs.length === 1 && retireFn.inputs[0].name === "successor", `retire(successor) acts on msg.sender only: there is no form by which a third party retires A`);
119
+ await A.execute("T9.A.aid.retire", k.aid.address, k.aid.encodeRetire(prime.address));
120
+ if (k.aid.backing)
121
+ await A.execute("T9.A.aid.backing.retire", k.aid.backing.address, k.aid.encodeRetire(prime.address));
122
+ const aState = await k.aid.stateName(A.address);
123
+ const successor = await k.aid.contract().successorOf(A.address);
124
+ g.check("T9", "A.retired", aState === "RETIRED" && getAddress(successor) === getAddress(prime.address), `state(A) = ${aState}, successorOf(A) = ${successor}`);
125
+ const afterRetire = await g.wouldRevert(A, k.aid.address, k.aid.encodeHeartbeat());
126
+ g.check("T9", "A.retired.final", afterRetire !== null, `a retired anchor cannot heartbeat again: ${afterRetire}`);
127
+ // the new record and document of A-prime: a new agent, linked to the old one by the successor pointer only
128
+ const registration = { type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1", name: "FOGA cell A-prime", description: "The successor body of cell A of the Finch OG Agent genesis; the ERC-8004 agent moved here from A", services: [], supportedTrust: ["reputation"], "x-foga": { cell: "A-prime", anchor: accountRef(chainId, prime.address), predecessor: accountRef(chainId, A.address), binding: primeBinding } };
129
+ const descriptor = agentDescriptor({ chainId, anchor: prime.address, slug: "cell-a-prime", name: "FOGA cell A-prime", description: registration.description, capabilities: ["erc8414:fulfiller", "erc8338:holder"], registration: { registry: `eip155:${chainId}:${k.identity.address.toLowerCase()}`, agentId: agentId.toString() } });
130
+ const ard = await prime.execute("T9.Aprime.ard.register", k.ard.address, k.ard.encodeRegister(kindId("agent"), prime.address, 0n, descriptor));
131
+ const primeRecord = k.ard.recordIdFromReceipt(ard.receipt);
132
+ k.setFact("T9.Aprime.recordId", primeRecord);
133
+ const oldRecord = k.requireFact("T1.A.recordId");
134
+ const [newBacked, oldBacked] = await Promise.all([k.ard.backed(primeRecord), k.ard.backed(oldRecord)]);
135
+ g.check("T9", "Aprime.record", newBacked && primeRecord !== oldRecord && primeRecord === recordIdOf(chainId, prime.address, 0n), `A-prime's agent record ${primeRecord.slice(0, 10)}… (anchor (${chainId}, A-prime, 0)) is backed; A's record ${oldRecord.slice(0, 10)}… is a different record, backed = ${oldBacked} (its anchor is retired)`);
136
+ const doc = {
137
+ version: "aid-document/v1",
138
+ aid: accountRef(chainId, prime.address),
139
+ did: `did:aid:${accountRef(chainId, prime.address)}`,
140
+ binding: { registry: k.identity.address, agentId: Number(agentId) },
141
+ livenessWindow: Number(g.world.file.params.aid.defaultLivenessWindow),
142
+ facets: [{ facetType: "aid:core/identity/v1", provenance: "SELF", issuer: accountRef(chainId, prime.address), validUntil: 0, digest: keccakJcs(registration), access: { mode: "PUBLIC" }, resolver: { kind: "erc8004-identity", chainId, registry: k.identity.address, agentId: Number(agentId) } }],
143
+ };
144
+ const commitment = AID.documentCommitment(doc);
145
+ await prime.execute("T9.Aprime.aid.document", k.aid.address, k.aid.encodeSetDocumentURI(commitment.uri, commitment.digest));
146
+ const published = await k.aid.documentURI(prime.address);
147
+ g.check("T9", "Aprime.document", published.digest === commitment.digest, `documentURI(A-prime).digest = ${commitment.digest.slice(0, 10)}… (its own document; A's standing facet is not carried over)`);
148
+ // --- (9b) the skill moves; its record follows the token and is adopted again by recommitment
149
+ const tokenId = BigInt(skill.tokenId);
150
+ await A.execute("T9.A.skill.transfer", k.skills.address, k.skills.encodeSafeTransfer(A.address, prime.address, tokenId), { by: "owner" });
151
+ const skillOwner = await k.skills.ownerOf(tokenId);
152
+ const controller = await k.ard.controllerOf(skill.recordId);
153
+ const adoptedNow = await k.ard.isAdopted(skill.recordId);
154
+ const recommitted = k.store.get("T9.Aprime.skill.adopt")?.status === "confirmed";
155
+ g.check("T9", "skill.transferred", getAddress(skillOwner) === getAddress(prime.address) && getAddress(controller) === getAddress(prime.address) && (recommitted || adoptedNow === false), `ownerOf(${tokenId}) = A-prime; the ARD skill record's controller follows the token (${controller}); adopted = ${adoptedNow}${recommitted ? " (already recommitted in an earlier run)" : " until the new controller recommits"}`);
156
+ const rec = await k.ard.recordOf(skill.recordId);
157
+ await prime.execute("T9.Aprime.skill.adopt", k.ard.address, k.ard.encodeRecommit(skill.recordId, rec));
158
+ const adoptedAfter = await k.ard.isAdopted(skill.recordId);
159
+ const recAfter = await k.ard.recordOf(skill.recordId);
160
+ g.check("T9", "skill.adopted", adoptedAfter && getAddress(recAfter.attestor) === getAddress(prime.address) && recAfter.descriptorHash === rec.descriptorHash, `A-prime recommitted the same descriptor hash ${rec.descriptorHash.slice(0, 10)}…: attestor = controller = A-prime, adopted = ${adoptedAfter}`);
161
+ // --- (9c) nothing else is inherited
162
+ const standing = await k.kya.check(accountSubject(chainId, prime.address), sAtt, 2, [k.address("W")]);
163
+ const standingA = await k.kya.check(accountSubject(chainId, A.address), sAtt, 2, [k.address("W")]);
164
+ g.check("T9", "inherit.kya.none", standing === false && standingA === true, `check({account, A-prime}, S_att, 2, [W]) = ${standing}; A's own assertion still resolves for A (${standingA}): standing is the subject's, not the agent's`);
165
+ const seat = await k.swarms.memberStatus(swarmId, prime.address);
166
+ const seatA = await k.swarms.memberStatus(swarmId, A.address);
167
+ g.check("T9", "inherit.swarm.none", seat.statusName === "NONE" && seatA.statusName !== "NONE", `swarm seat: A-prime ${seat.statusName}, A ${seatA.statusName}: the seat stays with the account that joined`);
168
+ const primeUnits = (await Promise.all([k.units.controllerOf(BigInt(k.requireFact("T1.A.unitId")))])).map((c) => getAddress(c));
169
+ g.check("T9", "inherit.unit.none", primeUnits[0] === getAddress(A.address), `A's unit ${k.requireFact("T1.A.unitId")} is still controlled by A; A-prime registers its own`);
170
+ const unitDef = cellUnitDefinition({ chainId, cell: prime.address, name: "FOGA cell A-prime" });
171
+ const unit = await prime.execute("T9.Aprime.auf.unit", k.units.registryAddress, k.units.encodeRegisterUnit(prime.address, unitDef, []));
172
+ const primeUnit = k.units.unitIdFromReceipt(unit.receipt);
173
+ k.setFact("T9.Aprime.unitId", primeUnit.toString());
174
+ const retiredGate = await g.wouldRevert(A, k.tenders.address, k.tenders.encodeSubmit(BigInt(k.requireFact("T7.tokenId")), "0x" + "11".repeat(32), "retired"));
175
+ g.check("T9", "A.retired.gate", retiredGate !== null && /AnchorNotActive/.test(retiredGate), `a commitment-class call by the retired A reverts at the gate: ${retiredGate}`);
176
+ const primeBindings = await prime.presentedBindings();
177
+ const aBindings = await A.presentedBindings();
178
+ g.check("T9", "histories.separate", getAddress(successor) === getAddress(prime.address) && !primeBindings.includes(aBindings[0]) && primeRecord !== oldRecord, `successorOf(A) points at A-prime; A-prime's bindings, record and unit are its own: two histories, one pointer`);
179
+ k.evidence("T9", "succession", {
180
+ A: { account: A.address, state: aState, successor, record: oldRecord, bindings: aBindings, backingState: k.aid.backing ? await k.aid.stateName(A.address, k.aid.backing.address) : null },
181
+ Aprime: { account: prime.address, owner: prime.ownerAddress, operator: prime.operatorAddress, state: primeState, binding: primeBinding, record: primeRecord, unitId: primeUnit.toString(), documentDigest: commitment.digest },
182
+ agentId: agentId.toString(),
183
+ skill: { tokenId: skill.tokenId, recordId: skill.recordId, owner: skillOwner, adopted: adoptedAfter },
184
+ notInherited: ["KYA assertions (S_att level of A)", "swarm seat", "AUF unit", "ASF binding", "AID document facets"],
185
+ keyLoss: "documented in docs/genesis.md (9d): guarded profile rotates the operator under the same anchor; a lost anchor key under a lite profile cannot retire and stays STALE",
186
+ txs: { deploy: k.store.get("T9.Aprime.deploy")?.txHash, adopt: k.store.get("T9.Aprime.adopt")?.txHash, transfer: transfer.txHash, bind: bind.txHash, retire: k.store.get("T9.A.aid.retire")?.txHash, record: ard.txHash, skillTransfer: k.store.get("T9.A.skill.transfer")?.txHash, skillAdopt: k.store.get("T9.Aprime.skill.adopt")?.txHash, unit: unit.txHash },
187
+ });
188
+ }
@@ -0,0 +1,128 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { keccak256, toBeHex, toUtf8Bytes } from "ethers";
5
+ import { abi } from "../core/canonical.js";
6
+ import { repoRoot } from "../util/fs.js";
7
+ export const EPOCH_LENGTH = 2_592_000; // 30 days, as the upstream Sepolia adapter
8
+ export const EPOCH_GRACE = 1;
9
+ /** How long an assertion issued from a predicate proof stays valid: the rule was shown as of the proof's time. */
10
+ export const PREDICATE_VALIDITY = 30 * 86400;
11
+ /** The rule's window: completions inside the last 30 days before the proof. */
12
+ export const PREDICATE_WINDOW = 30 * 86400;
13
+ const BUILD = join("vendor", "kya-standard", "companions", "zk-kya-groth16", "build");
14
+ export const PREDICATE_BUILD = join("zk", "build");
15
+ export const PREDICATE_CIRCUIT = "kya_predicate_count_v1";
16
+ /** Record slots per predicate proof (the committed circuit instance). */
17
+ export const PREDICATE_SLOTS = 8;
18
+ /** Where the proving material and the libraries stand on this machine. */
19
+ export function zkAvailability(root = repoRoot()) {
20
+ const missing = [];
21
+ for (const f of ["kya_public_v1_js/kya_public_v1.wasm", "kya_final.zkey", "verification_key.json"]) {
22
+ if (!existsSync(join(root, BUILD, f)))
23
+ missing.push(join(BUILD, f));
24
+ }
25
+ if (!existsSync(join(root, "vendor", "kya-standard", "companions", "zk-kya-groth16", "prover.js")))
26
+ missing.push("vendor/kya-standard/companions/zk-kya-groth16/prover.js (run: foga vendor sync)");
27
+ if (!existsSync(join(root, "prover", "node_modules", "snarkjs")))
28
+ missing.push("prover/node_modules/snarkjs (run: npm run zk:install)");
29
+ return { ok: missing.length === 0, missing };
30
+ }
31
+ /** Whether the proving libraries (the prover sub-package) are installed; the source set root needs them. */
32
+ export function proverLibsAvailable(root = repoRoot()) {
33
+ return existsSync(join(root, "prover", "node_modules", "snarkjs")) && existsSync(join(root, "prover", "node_modules", "circomlibjs"));
34
+ }
35
+ /** Where the predicate circuit's proving material stands (built by prover/build-predicate.mjs; FOGA_ZK_PREDICATE_DIR overrides). */
36
+ export function predicateAvailability(root = repoRoot()) {
37
+ const missing = [];
38
+ const dir = process.env.FOGA_ZK_PREDICATE_DIR || join(root, PREDICATE_BUILD);
39
+ for (const f of [`${PREDICATE_CIRCUIT}_js/${PREDICATE_CIRCUIT}.wasm`, `${PREDICATE_CIRCUIT}_final.zkey`, "verification_key.json"]) {
40
+ if (!existsSync(join(dir, f)))
41
+ missing.push(`${join(PREDICATE_BUILD, f)} (run: node prover/build-predicate.mjs)`);
42
+ }
43
+ if (!proverLibsAvailable(root))
44
+ missing.push("prover/node_modules (run: npm run zk:install)");
45
+ return { ok: missing.length === 0, missing };
46
+ }
47
+ /** A deterministic 32-byte EdDSA private key for a source of task-completion records, from an EOA key and a label. */
48
+ export function sourceKeyFrom(privateKey, label) {
49
+ return keccak256(abi.encode(["bytes32", "bytes32"], [keccak256(toUtf8Bytes(`foga.kya.source:${label}`)), privateKey]));
50
+ }
51
+ /** A deterministic 32-byte EdDSA private key for an attestor, derived from an EOA key and a label. */
52
+ export function attestorKeyFrom(privateKey, label) {
53
+ return keccak256(abi.encode(["bytes32", "bytes32"], [keccak256(toUtf8Bytes(`foga.kya.attestor:${label}`)), privateKey]));
54
+ }
55
+ /** A deterministic prover secret below the BN254 scalar field (248 bits), derived from an EOA key and a scheme. */
56
+ export function proverSecretFrom(privateKey, schemeId) {
57
+ const h = BigInt(keccak256(abi.encode(["bytes32", "bytes32", "bytes32"], [keccak256(toUtf8Bytes("foga.kya.secret")), privateKey, schemeId])));
58
+ return toBeHex(h >> 8n, 32);
59
+ }
60
+ export function epochFor(timestamp, epochLength = EPOCH_LENGTH) {
61
+ return epochLength === 0 ? 0n : BigInt(Math.floor(timestamp / epochLength));
62
+ }
63
+ async function run(job, timeoutMs = 180_000) {
64
+ const root = repoRoot();
65
+ const script = join(root, "prover", "run.cjs");
66
+ return new Promise((resolve, reject) => {
67
+ const env = { PATH: process.env.PATH ?? "" };
68
+ if (process.env.FOGA_ZK_PROVER)
69
+ env.FOGA_ZK_PROVER = process.env.FOGA_ZK_PROVER;
70
+ if (process.env.FOGA_ZK_PREDICATE_DIR)
71
+ env.FOGA_ZK_PREDICATE_DIR = process.env.FOGA_ZK_PREDICATE_DIR;
72
+ const child = spawn(process.execPath, [script], { stdio: ["pipe", "pipe", "pipe"], env });
73
+ const out = [];
74
+ const err = [];
75
+ const timer = setTimeout(() => {
76
+ child.kill("SIGKILL");
77
+ reject(new Error(`prover ${job.op} exceeded ${timeoutMs} ms`));
78
+ }, timeoutMs);
79
+ child.stdout.on("data", (d) => out.push(Buffer.from(d)));
80
+ child.stderr.on("data", (d) => err.push(Buffer.from(d)));
81
+ child.on("error", (e) => {
82
+ clearTimeout(timer);
83
+ reject(e);
84
+ });
85
+ child.on("close", (code) => {
86
+ clearTimeout(timer);
87
+ if (code !== 0)
88
+ return reject(new Error(`prover ${job.op} exited with ${code}: ${Buffer.concat(err).toString("utf8").slice(0, 2000)}`));
89
+ try {
90
+ resolve(JSON.parse(Buffer.concat(out).toString("utf8")));
91
+ }
92
+ catch (e) {
93
+ reject(new Error(`prover ${job.op} returned no JSON: ${String(e)}`));
94
+ }
95
+ });
96
+ child.stdin.end(JSON.stringify(job));
97
+ });
98
+ }
99
+ export const zk = {
100
+ /** The issuer set of the given attestor keys: Poseidon Merkle root (depth 16) and the public keys. */
101
+ issuerSet(attestors) {
102
+ return run({ op: "root", attestors });
103
+ },
104
+ /** Poseidon(secret): the prover's commitment, the only thing the attestor needs from it. */
105
+ commit(secret) {
106
+ return run({ op: "commit", secret });
107
+ },
108
+ /** The attestor signs (claim, commitment). */
109
+ attest(attestor, claim, commitment) {
110
+ return run({ op: "attest", attestor, claim, commitment });
111
+ },
112
+ /** The prover builds the Groth16 proof for the registry's admission domain and the epoch; public material only. */
113
+ prove(p) {
114
+ return run({ ...p, op: "prove", epoch: p.epoch.toString() });
115
+ },
116
+ /** The source set of the given source keys: the same Poseidon Merkle tree (depth 16) as the issuer set. */
117
+ sourceSet(sources) {
118
+ return run({ op: "root", attestors: sources });
119
+ },
120
+ /** A source signs one task-completion record about a subject. */
121
+ signRecord(source, subjectKey, record) {
122
+ return run({ op: "record.sign", source, record: { ...record, subjectKey } });
123
+ },
124
+ /** The prover shows that at least k of the records are Accepted inside [t0, t1]; records and source stay private. */
125
+ provePredicate(p) {
126
+ return run({ ...p, op: "predicate.prove", epoch: p.epoch.toString() }, 600_000);
127
+ },
128
+ };