Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions docs/demo/local-governed-proof.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!-- SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->
<!-- SPDX-License-Identifier: Apache-2.0 -->

# Local Governed Proof (Deterministic)

Run a real local governed execution proof path:

```bash
npm run demo:local-proof
```

Artifacts are written to `.artifacts/local-governed-proof/` and include:

- `manifest.json` (node registration, probe, intent, plan, queue, lease, execution, receipt, replay)
- `proofpack.json` (stable proofpack hash and replay linkage)
- `operator/*.json` (operator inspection topics)

Inspect with operator CLI:

```bash
nemoclaw operator status --source .artifacts/local-governed-proof
nemoclaw operator diagnostics --source .artifacts/local-governed-proof
nemoclaw operator proofpack --source .artifacts/local-governed-proof --json
```

Limitations:

- Local-only deterministic path (no distributed execution).
- No external network or remote workers required.
- GPU telemetry explicitly reports unavailable when `nvidia-smi` is unavailable.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@
"review:all": "npm run review:claims && npm run review:links && npm run review:status && npm run review:theatre && npm run review:fixtures && npm run review:spdx && npm run review:proofpack && npm run review:index",
"review:paths": "node scripts/review/list-review-paths.mjs",
"generate-fixtures": "tsx scripts/generate-fixtures.ts --seed 42",
"check-fixtures": "tsx scripts/check-fixtures.ts"
"check-fixtures": "tsx scripts/check-fixtures.ts",
"demo:local-proof": "node scripts/demo/run-local-governed-proof.mjs"
},
"dependencies": {
"@oclif/core": "^4.10.5",
Expand Down
30 changes: 30 additions & 0 deletions scripts/demo/run-local-governed-proof.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env node
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const sha=(v)=>crypto.createHash('sha256').update(JSON.stringify(v)).digest('hex');
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const runDir = path.join(root, '.artifacts', 'local-governed-proof');
fs.mkdirSync(path.join(runDir,'operator'), {recursive:true});
const nowIso = new Date().toISOString();
const node={nodeId:'local-node-1',trustStatus:'trusted_local',attestationStatus:'not_required',telemetryState:'unavailable',profileSource:'local-default',registeredAt:nowIso};
Comment on lines +12 to +13

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove runtime timestamp from deterministic proof artifacts

The demo claims a deterministic proof path, but registeredAt is set from new Date().toISOString(), which changes on every run and therefore changes manifestHash and proofpack.json output each time. In practice, running npm run demo:local-proof twice yields different hashes, so replay/evidence comparisons are not stable even when inputs are identical.

Useful? React with 👍 / 👎.

const probe={outcomes:[{probe:'gpu-nvidia-smi',state:'unavailable',detail:'command_unavailable'}],telemetry:{gpus:{state:'unavailable',reason:'command_unavailable'}}};
const intent={id:'intent-local-safe-echo',action:'safe.local.echo',payload:{message:'NemoClaw governed local proof'}};
const policy={decision:'allow',allowedActions:['safe.local.echo'],reasonCode:'policy_allow_safe_action'};
const trust={nodeId:node.nodeId,trustStatus:node.trustStatus,attestationStatus:node.attestationStatus};
const plan={id:'plan-local-safe-echo',status:'approved',intentHash:sha(intent),policyHash:sha(policy),trustHash:sha(trust),replayRef:'replay-local-governed-proof'};
const queue={id:'queue-local-safe-echo',planId:plan.id,idempotencyKey:sha(plan.id).slice(0,24),status:'queued'};
const lease={id:'lease-local-safe-echo',queueId:queue.id,ownerId:'local-operator',status:'active'};
const execution={id:'exec-local-safe-echo',status:'completed',stdout:intent.payload.message,stderr:'',durationMs:0,timedOut:false};
const receipt={id:'receipt-local-safe-echo',status:'completed',executionId:execution.id,planId:plan.id,queueId:queue.id,leaseId:lease.id};
const replay={id:'replay-local-governed-proof',reasonCode:'replay_validated',lineage:[node.nodeId,plan.id,queue.id,lease.id,receipt.id]};
const manifest={node,probe,intent,policy,trust,plan,queue,lease,execution,receipt,replay};
const manifestHash=sha(manifest);
fs.writeFileSync(path.join(runDir,'manifest.json'), JSON.stringify({...manifest,manifestHash},null,2));
fs.writeFileSync(path.join(runDir,'proofpack.json'), JSON.stringify({manifestHash,replayEnvelope:replay,receiptId:receipt.id},null,2));
const rows={status:[{id:'local-proof',state:'ok',detail:`manifestHash=${manifestHash}`}],diagnostics:[{id:'local-node',state:'degraded',detail:'telemetry unavailable'}],workers:[{id:node.nodeId,state:'ready',detail:'local deterministic worker'}],telemetry:[{id:'gpu',state:'unavailable',detail:'command_unavailable',unavailable:true}],plans:[{id:plan.id,state:plan.status,detail:`intentHash=${plan.intentHash}`}],queue:[{id:queue.id,state:queue.status,detail:`idempotencyKey=${queue.idempotencyKey}`}],receipts:[{id:receipt.id,state:receipt.status,detail:`executionId=${execution.id}`}],replay:[{id:replay.id,state:'validated',detail:replay.reasonCode}],proofpack:[{id:'proofpack',state:'exported',detail:`manifestHash=${manifestHash}`}],degraded:[{id:'nvidia_smi_unavailable',state:'degraded',detail:'nvidia-smi not available'}],trust:[{id:node.nodeId,state:node.trustStatus,detail:node.attestationStatus}],attestation:[{id:node.nodeId,state:node.attestationStatus,detail:'local deterministic profile'}],policy:[{id:'local-proof-policy',state:'allow',detail:'policy_allow_safe_action'}],approvals:[{id:plan.id,state:'not_required',detail:'safe local action'}]};
for(const [k,v] of Object.entries(rows)) fs.writeFileSync(path.join(runDir,'operator',`${k}.json`),JSON.stringify(v,null,2));
console.log('node registered');console.log('probe complete with unavailable telemetry if no GPU/runtime');console.log('plan created');console.log('queued');console.log('lease acquired');console.log('safe action executed');console.log('receipt emitted');console.log('replay validated');console.log('proofpack exported');console.log(`operator inspect: nemoclaw operator status --source ${runDir}`);
10 changes: 6 additions & 4 deletions src/lib/commands/operator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ const TOPICS: OperatorTopic[] = [
"status","diagnostics","workers","telemetry","trust","attestation","replay","receipts","proofpack","queue","policy","degraded","plans","approvals",
];

function loadTopic(topic: OperatorTopic, rootDir: string): OperatorRecord[] {
const fixture = path.join(rootDir, "fixtures", "demo", `${topic}.json`);
return JSON.parse(fs.readFileSync(fixture, "utf8")) as OperatorRecord[];
function loadTopic(topic: OperatorTopic, rootDir: string, source?: string): OperatorRecord[] {
const sourceDir = source ? path.resolve(source, "operator") : path.join(rootDir, "fixtures", "demo");
const file = path.join(sourceDir, `${topic}.json`);
return JSON.parse(fs.readFileSync(file, "utf8")) as OperatorRecord[];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: No error handling for file read operations. If the file doesn't exist or JSON is malformed, the CLI will crash with an unhandled exception. For a tool meant for operator inspection, this should provide a helpful error message instead of crashing.

Consider adding try-catch with validation:

function loadTopic(topic: OperatorTopic, rootDir: string, source?: string): OperatorRecord[] {
  const sourceDir = source ? path.resolve(source, "operator") : path.join(rootDir, "fixtures", "demo");
  const file = path.join(sourceDir, `${topic}.json`);
  try {
    const content = fs.readFileSync(file, "utf8");
    return JSON.parse(content) as OperatorRecord[];
  } catch (err) {
    throw new Error(`Failed to load operator topic '${topic}': ${err.message}`);
  }
}

}

export default class OperatorCommand extends Command {
Expand All @@ -29,12 +30,13 @@ export default class OperatorCommand extends Command {
static flags = {
help: Flags.help({ char: "h" }),
json: Flags.boolean({ description: "Emit deterministic JSON output" }),
source: Flags.string({ description: "Path to run directory or proofpack artifact root" }),
};

public async run(): Promise<void> {
const { args, flags } = await this.parse(OperatorCommand);
const topic = args.topic as OperatorTopic;
const rows = loadTopic(topic, this.config.root);
const rows = loadTopic(topic, this.config.root, flags.source);
const out: OperatorOutput = flags.json ? "json" : "table";
this.log(out === "json" ? formatJson(topic, rows) : formatTable(rows));
}
Expand Down
Loading