diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 07dd71a..c4394b8 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to the `nirium-cli` package are documented here. ## Unreleased +## 3.2.0 - unreleased + +### Changed + +- `nirium report export anchors` prints each anchor's date and network (tab-separated) instead of its CID, and never prints a CID even if an older node sends one. The node's public Reporting export stopped returning CIDs and payroll run ids on 8 Nov 2026: an anchor's CID belongs to whoever made it. Piping `export anchors` into `nirium verify` no longer applies; verify the CIDs you anchored yourself with `nirium verify `. +- `nirium report summary` shows the date of the last anchor (`last: YYYY-MM-DD`) when the node sends `lastAnchoredAt`, and falls back to `latest: ` for older nodes. + ## 3.1.1 - 2026-11-07 ### Fixed diff --git a/packages/cli/README.md b/packages/cli/README.md index 4d44361..f373f83 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -341,15 +341,12 @@ receipts by gateway, and anchor counts for the period. `report export ` pulls the underlying rows: - Without `--csv`, rows print one per line on stdout. For `export - anchors` specifically, that line is **just the CID** - nothing else - - so it pipes straight into `nirium verify`: + anchors`, each line is the anchor's date and network, tab-separated. + There is no CID: the public Reporting export doesn't return them (an + anchor's CID belongs to whoever made it). If you anchored something, + you already have its CID - check it with `nirium verify `. - ```bash - nirium report export anchors --from 2026-10-01 | xargs -n1 nirium verify - ``` - - The row count and disclaimer print to **stderr**, not stdout, so they - never end up in that pipe. + The row count and disclaimer print to **stderr**, not stdout. - With `--csv `, the server's CSV is written to that file instead (nothing on stdout but a confirmation). - `--json` prints the full response (`type`, `period`, `rowCount`, diff --git a/packages/cli/bin/nirium.js b/packages/cli/bin/nirium.js index dcf08e7..97e6add 100755 --- a/packages/cli/bin/nirium.js +++ b/packages/cli/bin/nirium.js @@ -2015,7 +2015,12 @@ async function executeReportSummaryCommand(options) { console.log(` ${asset}: ${amount}`); } console.log(` Payments: ${data.payments?.count ?? 0} receipt(s), ${data.payments?.totalUsdc ?? '0'} USDC total`); - console.log(` Anchors: ${data.anchors?.count ?? 0} (latest: ${data.anchors?.latestCid || 'none'})`); + // Nodes since 8 Nov 2026 send lastAnchoredAt instead of latestCid (the latest + // anchor is usually someone else's evidence). Older nodes still send latestCid. + const lastAnchor = data.anchors?.lastAnchoredAt + ? `last: ${String(data.anchors.lastAnchoredAt).slice(0, 10)}` + : `latest: ${data.anchors?.latestCid || 'none'}`; + console.log(` Anchors: ${data.anchors?.count ?? 0} (${lastAnchor})`); if (Array.isArray(data.warnings) && data.warnings.length > 0) { console.log(' ⚠ Warnings:'); for (const w of data.warnings) console.log(` - ${w}`); @@ -2090,12 +2095,13 @@ async function executeReportExportCommand(type, options) { return; } - // `export anchors` prints exactly one CID per line on stdout - nothing - // else - so it pipes straight into `nirium verify` (xargs -n1 or a - // while-read loop). Row count and the disclaimer go to stderr instead - // of stdout for the same reason: stdout must stay pure for piping. + // `export anchors` prints one line per anchor: when and on which network, + // tab-separated. No CID: the public Reporting export stopped returning them + // on 8 Nov 2026 (an anchor's CID belongs to whoever made it), and this never + // prints one even if an older node still sends it. Row count and the + // disclaimer go to stderr so stdout stays clean for piping. for (const row of data.rows || []) { - console.log(type === 'anchors' ? (row.cid || '') : JSON.stringify(row)); + console.log(type === 'anchors' ? `${row.anchored_at || ''}\t${row.network || ''}` : JSON.stringify(row)); } console.error(`# ${data.rowCount ?? (data.rows || []).length} row(s). ${data.disclaimer || ''}`); } diff --git a/packages/cli/package.json b/packages/cli/package.json index d59985d..8891c85 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "nirium-cli", - "version": "3.1.1", + "version": "3.2.0", "description": "Nirium CLI \u2014 scaffold Stellar agent projects and check agent status", "main": "bin/nirium.js", "type": "module", diff --git a/packages/cli/test/report.test.js b/packages/cli/test/report.test.js index cd685ad..568c8c3 100644 --- a/packages/cli/test/report.test.js +++ b/packages/cli/test/report.test.js @@ -25,14 +25,15 @@ const SUMMARY = { network: 'all', payroll: { settledRuns: 2, recipientsPaid: 5, totalsByAsset: { USDC: '120.0000000' }, lastSettledAt: '2026-11-01T00:00:00.000Z' }, payments: { count: 10, totalUsdc: '1.23', byGateway: { x402: { count: 8, totalUsdc: 1.0 }, mpp: { count: 2, totalUsdc: 0.23 } } }, - anchors: { count: 3, latestCid: 'QmLatestFake' }, + anchors: { count: 3, lastAnchoredAt: '2026-11-01T00:00:00.000Z' }, generatedAt: '2026-11-08T00:00:00.000Z', disclaimer: DISCLAIMER, }; const ANCHOR_ROWS = [ + // An older node may still send cid; the CLI must never print it. { anchored_at: '2026-11-01T00:00:00.000Z', source: 'AUDIT_NODE', cid: 'QmAnchorOne', network: 'testnet' }, - { anchored_at: '2026-10-31T00:00:00.000Z', source: 'AUDIT_NODE', cid: 'QmAnchorTwo', network: 'testnet' }, + { anchored_at: '2026-10-31T00:00:00.000Z', source: 'AUDIT_NODE', network: 'mainnet' }, ]; let lastRequest = null; @@ -62,7 +63,7 @@ const server = http.createServer((req, res) => { const format = u.searchParams.get('format') || 'csv'; if (format === 'csv') { res.writeHead(200, { 'content-type': 'text/csv; charset=utf-8', 'content-disposition': `attachment; filename="nirium-${type}.csv"` }); - res.end('anchored_at,source,cid,network\r\n2026-11-01T00:00:00.000Z,AUDIT_NODE,QmAnchorOne,testnet\r\n'); + res.end('anchored_at,source,network\r\n2026-11-01T00:00:00.000Z,AUDIT_NODE,testnet\r\n'); return; } res.writeHead(200, { 'content-type': 'application/json' }); @@ -117,7 +118,7 @@ test('summary human output reports payroll, payments, anchors, and the disclaime assert.match(r.stdout, /2 settled run\(s\), 5 recipient\(s\) paid/); assert.match(r.stdout, /USDC: 120\.0000000/); assert.match(r.stdout, /10 receipt\(s\), 1\.23 USDC total/); - assert.match(r.stdout, /3 \(latest: QmLatestFake\)/); + assert.match(r.stdout, /3 \(last: 2026-11-01\)/); assert.match(r.stdout, /not certified regulatory reporting/); }); @@ -140,11 +141,11 @@ test('summary surfaces a server error as a failure', async () => { // ─── export ──────────────────────────────────────────────────── -test('export anchors prints exactly one CID per line on stdout, nothing else', async () => { +test('export anchors prints one line per anchor (date and network), nothing else', async () => { const r = await run(['export', 'anchors', '--url', base]); assert.equal(r.code, 0, r.stdout + r.stderr); const lines = r.stdout.trim().split('\n'); - assert.deepEqual(lines, ['QmAnchorOne', 'QmAnchorTwo']); + assert.deepEqual(lines, ['2026-11-01T00:00:00.000Z\ttestnet', '2026-10-31T00:00:00.000Z\tmainnet']); }); test('export anchors footer (row count + disclaimer) goes to stderr, not stdout', async () => { @@ -155,11 +156,10 @@ test('export anchors footer (row count + disclaimer) goes to stderr, not stdout' assert.match(r.stderr, /not certified regulatory reporting/); }); -test('export anchors pipes straight into `nirium verify` without extra parsing', async () => { +test('export anchors never prints a CID, even if the node sends one', async () => { const r = await run(['export', 'anchors', '--url', base]); - const cids = r.stdout.trim().split('\n').filter(Boolean); - assert.equal(cids.length, 2); - for (const cid of cids) assert.match(cid, /^Qm[A-Za-z0-9]+$/); + assert.equal(r.code, 0, r.stdout + r.stderr); + assert.doesNotMatch(r.stdout + r.stderr, /Qm[A-Za-z0-9]{10,}/); }); test('export payments/payroll print one JSON row per line (not just anchors)', async () => { @@ -185,8 +185,8 @@ test('export --csv writes the file, not stdout', async () => { assert.equal(r.code, 0, r.stdout + r.stderr); assert.match(r.stdout, /Wrote/); const content = fs.readFileSync(out, 'utf8'); - assert.match(content, /anchored_at,source,cid,network/); - assert.match(content, /QmAnchorOne/); + assert.match(content, /anchored_at,source,network/); + assert.doesNotMatch(content, /Qm[A-Za-z0-9]{10,}/); } finally { fs.rmSync(out, { force: true }); } diff --git a/packages/sdk-python/src/nirium/client.py b/packages/sdk-python/src/nirium/client.py index d65ea91..45d8bc1 100644 --- a/packages/sdk-python/src/nirium/client.py +++ b/packages/sdk-python/src/nirium/client.py @@ -361,7 +361,10 @@ async def submit_payout_onboard(self, signed_xdr: str) -> Dict[str, Any]: return await self._post("/api/payroll/onboard/submit", {"signedXdr": signed_xdr}) async def get_payout_runs(self) -> Dict[str, Any]: - """Payout history for the current network, with tx hashes and IPFS receipt CIDs.""" + """Public payout history for the current network: amount, asset, recipient count and tx hash. + + It does not include run ids or IPFS receipt CIDs; your own run's CID comes back + from submit_payout().""" return await self._get("/api/payroll/runs") async def get_payout_terms(self) -> Dict[str, Any]: diff --git a/packages/sdk/CHANGELOG.md b/packages/sdk/CHANGELOG.md index 3448c0c..3338573 100644 --- a/packages/sdk/CHANGELOG.md +++ b/packages/sdk/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to the `nirium` package are documented here. ## Unreleased +## 0.19.0 - unreleased + +### Changed + +- `getPayoutRuns()` now returns `{ runs: PublicPayoutRun[] }` instead of `PayoutRun[]`. The node's public `GET /api/payroll/runs` no longer includes `runId` or the IPFS receipt `cid`: a run's receipt is the paying company's evidence, so the public history keeps only what is already public on-chain (amount, asset, recipient count, tx hash). Your own run's CID still comes back from `submitPayout()`. TypeScript code that read `runId` or `cid` from `getPayoutRuns()` will no longer compile; at runtime those fields are `undefined` once a node runs the new version. + ## 0.18.0 - 2026-11-07 ### Added diff --git a/packages/sdk/package.json b/packages/sdk/package.json index afcd234..0bcc408 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "nirium", - "version": "0.18.0", + "version": "0.19.0", "description": "Autonomous treasury and agentic-payments infrastructure for Stellar (x402)", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 0574cab..201caf2 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -327,6 +327,22 @@ export interface PayoutRun { [key: string]: unknown; } +/** + * One entry of the public payout history (getPayoutRuns). It carries what is + * already public on-chain - amount, asset, recipient count and the tx - and + * never the run id or the IPFS receipt CID: the receipt is the paying + * company's evidence, returned only to whoever runs the payout (submitPayout). + */ +export interface PublicPayoutRun { + network?: string; + asset?: string; + totalAmount?: string; + recipientCount?: number; + txHash?: string; + explorer?: string; + signed?: boolean; +} + export interface AgentAttestationInput { /** ed25519 public key of the agent, as a Stellar address (G...). */ key: string; @@ -924,8 +940,12 @@ export class Agent { return this.request('POST', '/api/payroll/onboard/submit', { signedXdr }); } - /** Payout history for the current network, each with tx hash and IPFS receipt CID. */ - async getPayoutRuns(): Promise<{ runs: PayoutRun[] }> { + /** + * Public payout history for the current network: amount, asset, recipient + * count and tx hash. It does not include run ids or IPFS receipt CIDs; your + * own run's CID comes back from submitPayout(). + */ + async getPayoutRuns(): Promise<{ runs: PublicPayoutRun[] }> { return this.request('GET', '/api/payroll/runs'); }