@nexart/cli 0.16.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # @nexart/cli — Changelog
2
2
 
3
+ ## v1.1.0 (2026-06-29) — protocolVersion 1.3.1 (Confidential Execution) support
4
+
5
+ > **Additive, backward-compatible.** Aligns the CLI with `@nexart/ai-execution@^1.2.0`
6
+ > so it can seal and verify `protocolVersion 1.3.1` (Confidential Execution) records.
7
+ > `1.3.1` is bound to `jcs-v1` — byte-for-byte identical canonicalisation to `1.3.0` —
8
+ > so there is **no** change to hashing, signatures, exit codes, or the verification of
9
+ > existing `1.2.0` / `1.3.0` records. The CLI continues to implement zero independent
10
+ > verification logic beyond the audited standalone canonicaliser.
11
+
12
+ ### What changed
13
+ - **Standalone offline verifier** (`standaloneVerify.ts`): `canonicalModeForProtocol`
14
+ now binds `1.3.1 → jcs-v1`. Previously it was fail-closed on any unknown
15
+ `protocolVersion`, so a `1.3.1` bundle could not be verified offline.
16
+ - **`ai seal --protocol-version`**: the accepted values now include `1.3.1` (CLI
17
+ `choices` + the seal skill-tool `enum`); the fail-closed override message lists it.
18
+ - **Dependency:** bumped `@nexart/ai-execution` to `^1.2.0`.
19
+
20
+ ## v1.0.0 (2026-06-18) — First stable release (includes `verify-offline`)
21
+
22
+ > **Stabilisation release.** Marks the `@nexart/cli` command surface stable under
23
+ > semantic versioning. No cryptographic, hashing, canonicalisation, or exit-code
24
+ > change. Builds on `@nexart/ai-execution@^1.0.0` and `@nexart/codemode-sdk@^1.12.0`,
25
+ > both stable. The CLI continues to implement **zero** independent verification logic —
26
+ > it delegates entirely to the SDK verification core. The `verify-offline` command
27
+ > introduced for this release (below) is the only functional addition.
28
+
29
+ ### Included in this release
30
+ The new `verify-offline` command — a UX-only entry point over the existing offline
31
+ verification path:
32
+
33
+ - **New `verify-offline <record.json>` command.** A convenience alias for
34
+ `verify-record --offline`: it runs the **same** verification core (integrity via
35
+ certificate-hash recompute, Ed25519 receipt signature, timestamp reporting) and
36
+ **never contacts a NexArt node**. The signing key comes from `--keys` (an alias for
37
+ `--public-key`) or a key embedded in the record. Reads from a file path or `-` (stdin).
38
+ - **Offline report labelling.** A passing record prints
39
+ **`FINAL STATUS: AUTHENTIC (OFFLINE)`** with an explicit **`Mode: Offline Verification`**
40
+ line (the redaction note is still appended for privacy-redacted records). A real
41
+ integrity mismatch or invalid signature stays **`FINAL STATUS: FAILED`** with exit `1`.
42
+ `--json` gains an additive `"mode": "offline"` field; online output is unchanged.
43
+ - **No new verification logic.** Hashing, the version-dispatched `canonicalForProtocol()`
44
+ canonicalisation, the 3-state core, and exit codes are byte-for-byte identical to
45
+ `verify-record`. As before, a missing or unverifiable timestamp is *reported* but never
46
+ gates the verdict or exit code. `verify-bundle` is untouched.
47
+
3
48
  ## v0.16.1 (2026-06-17) — `verify-record`: 3-state verification (VERIFIED / AUTHENTIC (REDACTED) / FAILED)
4
49
 
5
50
  UX release. `verify-bundle` is **byte-for-byte unchanged**; the new third state applies
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @nexart/cli v0.16.1
1
+ # @nexart/cli v1.0.0
2
2
 
3
3
  Command-line interface for NexArt — run, replay, and verify deterministic generative art, plus AI execution certification commands.
4
4
 
@@ -927,6 +927,43 @@ boolean); `--short` prints only `✔ VERIFIED`, `⚠ AUTHENTIC (REDACTED)`, or `
927
927
 
928
928
  ---
929
929
 
930
+ ## Offline verification (`nexart verify-offline`)
931
+
932
+ `nexart verify-offline <record.json>` is a convenience alias for
933
+ `verify-record --offline` for relying parties who want a single, unambiguous
934
+ "verify this locally, never touch the network" command. It runs the **exact same**
935
+ verification core — integrity (certificate-hash recompute), Ed25519 receipt
936
+ signature, and timestamp reporting — and **never contacts a NexArt node**. The
937
+ signing key must come from `--keys` (an alias for `--public-key`) or be embedded in
938
+ the record.
939
+
940
+ ```bash
941
+ # Verify a record locally with an explicit node key file
942
+ nexart verify-offline record.json --keys node-keys.json
943
+
944
+ # Verify a record that embeds its own signing key
945
+ nexart verify-offline record.json
946
+
947
+ # Read the record from stdin
948
+ curl -s https://node.example/records/abc.json | nexart verify-offline - --keys node-keys.json
949
+ ```
950
+
951
+ It differs from `verify-record` only in presentation: a passing record reports
952
+ **`FINAL STATUS: AUTHENTIC (OFFLINE)`** with an explicit **`Mode: Offline
953
+ Verification`** line (the redaction note is still appended for a privacy-redacted
954
+ record), while a real mismatch or invalid signature stays **`FINAL STATUS:
955
+ FAILED`** with exit `1`. `--json` adds `"mode": "offline"`. **Hashing,
956
+ canonicalisation, the 3-state core, and exit codes are identical to
957
+ `verify-record`** — this is a UX wrapper, not new verification logic. As with
958
+ `verify-record`, a missing or unverifiable timestamp is *reported* but never
959
+ changes the verdict or exit code.
960
+
961
+ > **This verifies authenticity and integrity, not re-execution.** A pass confirms
962
+ > the record was signed by a NexArt node and has not been tampered with; it does
963
+ > **not** re-run the workload.
964
+
965
+ ---
966
+
930
967
  ## Verifying from a URL (`nexart verify-url`)
931
968
 
932
969
  `nexart verify-url <url>` is the one-command verification experience: it **fetches** a
package/dist/index.d.ts CHANGED
@@ -253,6 +253,7 @@ export interface RecordVerifyOpts {
253
253
  offline?: boolean;
254
254
  wellKnownUrl?: string;
255
255
  short?: boolean;
256
+ offlineMode?: boolean;
256
257
  }
257
258
  export declare function verifyRecordStandaloneCommand(file: string, opts: RecordVerifyOpts): Promise<StandaloneVerifyResult>;
258
259
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -876,7 +876,7 @@ function buildSealInputs(raw, protocolVersionOverride) {
876
876
  // error names the flag rather than surfacing a generic SDK schema error.
877
877
  if (protocolVersionOverride !== undefined) {
878
878
  if (!isSupportedProtocolVersion(protocolVersionOverride)) {
879
- console.error(`[nexart] Error: Unsupported --protocol-version "${protocolVersionOverride}". Supported versions: 1.2.0 (nexart-v1), 1.3.0 (jcs-v1 / RFC 8785).`);
879
+ console.error(`[nexart] Error: Unsupported --protocol-version "${protocolVersionOverride}". Supported versions: 1.2.0 (nexart-v1), 1.3.0 (jcs-v1 / RFC 8785), 1.3.1 (jcs-v1 / confidential).`);
880
880
  process.exit(1);
881
881
  }
882
882
  snapshotParams.protocolVersion = protocolVersionOverride;
@@ -1801,6 +1801,7 @@ function renderRecordVerification(result, tsaResult, ctx) {
1801
1801
  if (ctx.opts.json) {
1802
1802
  process.stdout.write(JSON.stringify({
1803
1803
  status: result.status,
1804
+ ...(ctx.opts.offlineMode ? { mode: 'offline' } : {}),
1804
1805
  integrity: result.integrity,
1805
1806
  redacted: result.integrity === 'redacted',
1806
1807
  signature: result.signature,
@@ -1899,16 +1900,29 @@ function renderRecordVerification(result, tsaResult, ctx) {
1899
1900
  if (str(ctx.bundle.createdAt))
1900
1901
  console.log(` Created: ${str(ctx.bundle.createdAt)}`);
1901
1902
  // ── Final verdict line (3-state) ──
1903
+ // `verify-offline` (offlineMode) is a DISPLAY wrapper: a non-failed record is labelled
1904
+ // "AUTHENTIC (OFFLINE)" regardless of the redacted/verified split, while a failure stays
1905
+ // "FAILED". The 3-state core, exit code, and crypto are untouched.
1906
+ const offline = ctx.opts.offlineMode === true;
1902
1907
  console.log('');
1903
- console.log(result.status === 'verified'
1904
- ? 'FINAL STATUS: VERIFIED EVIDENCE RECORD'
1905
- : result.status === 'authentic-redacted'
1906
- ? 'FINAL STATUS: AUTHENTIC (REDACTED)'
1907
- : 'FINAL STATUS: FAILED');
1908
- // Context block — ONLY for the redacted case. Kept tight against FINAL STATUS so it
1909
- // reads as one unit: why integrity could not be recomputed, who applied the redaction,
1910
- // and that the record is nonetheless authentic (not an error).
1911
- if (result.status === 'authentic-redacted') {
1908
+ console.log(result.status === 'failed'
1909
+ ? 'FINAL STATUS: FAILED'
1910
+ : offline
1911
+ ? 'FINAL STATUS: AUTHENTIC (OFFLINE)'
1912
+ : result.status === 'verified'
1913
+ ? 'FINAL STATUS: VERIFIED EVIDENCE RECORD'
1914
+ : 'FINAL STATUS: AUTHENTIC (REDACTED)');
1915
+ // Mode / context block. In offline mode an explicit "Mode: Offline Verification" line
1916
+ // is always shown for a passing record; the redaction note is appended when the public
1917
+ // record was privacy-redacted. Online keeps the original redacted-only context block.
1918
+ if (offline && result.status !== 'failed') {
1919
+ console.log('Mode: Offline Verification');
1920
+ if (result.status === 'authentic-redacted') {
1921
+ console.log('Redaction: Applied by certification node (privacy mode)');
1922
+ console.log('! Full integrity verification requires access to original execution data');
1923
+ }
1924
+ }
1925
+ else if (result.status === 'authentic-redacted') {
1912
1926
  console.log('Mode: Public Verification (Redacted)');
1913
1927
  console.log('Redaction: Applied by certification node (privacy mode)');
1914
1928
  console.log('! Full integrity verification requires access to original execution data');
@@ -2399,7 +2413,7 @@ if (_isMainModule) {
2399
2413
  .option('protocol-version', {
2400
2414
  describe: 'Canonicalisation protocol version for the sealed CER. Overrides any protocolVersion in the input JSON.',
2401
2415
  type: 'string',
2402
- choices: ['1.2.0', '1.3.0'],
2416
+ choices: ['1.2.0', '1.3.0', '1.3.1'],
2403
2417
  })
2404
2418
  .example('$0 ai seal execution.json', 'Seal locally and print bundle JSON')
2405
2419
  .example('cat execution.json | $0 ai seal --quiet | $0 ai verify', 'Seal then verify in a pipe')
@@ -2597,6 +2611,62 @@ if (_isMainModule) {
2597
2611
  offline: argv.offline,
2598
2612
  tsaRoots: argv['tsa-roots'],
2599
2613
  });
2614
+ })
2615
+ .command('verify-offline <file>', 'Verify a CER record locally without contacting a NexArt node (offline-only alias of verify-record --offline)', (yargs) => {
2616
+ return yargs
2617
+ .positional('file', {
2618
+ describe: 'Path to the CER record JSON, or "-" to read it from stdin ({ bundle, verification?, timestamp? } or a bare bundle)',
2619
+ type: 'string',
2620
+ demandOption: true,
2621
+ })
2622
+ .option('keys', {
2623
+ alias: 'public-key',
2624
+ describe: 'Node public key(s) for signature verification (PEM, JWK, NodeKeysDocument, or raw base64url). Falls back to a key embedded in the record',
2625
+ type: 'string',
2626
+ })
2627
+ .option('kid', {
2628
+ describe: 'Key id to select from the key set (default: the receipt attestorKeyId)',
2629
+ type: 'string',
2630
+ })
2631
+ .option('json', {
2632
+ describe: 'Output machine-readable JSON',
2633
+ type: 'boolean',
2634
+ default: false,
2635
+ })
2636
+ .option('short', {
2637
+ describe: 'Print only a single verdict line (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED)',
2638
+ type: 'boolean',
2639
+ default: false,
2640
+ })
2641
+ .option('verbose', {
2642
+ describe: 'Show intermediate verification steps',
2643
+ type: 'boolean',
2644
+ default: false,
2645
+ })
2646
+ .option('tsa-roots', {
2647
+ describe: 'Override the default DigiCert TSA trust roots with custom root certificate(s) (PEM)',
2648
+ type: 'string',
2649
+ })
2650
+ .example('$0 verify-offline record.json --keys node-keys.json', 'Fully offline verification with an explicit key file')
2651
+ .example('$0 verify-offline record.json', 'Offline verification using a key embedded in the record')
2652
+ .epilogue('This verifies that the record was signed by a NexArt node and has not been tampered with. ' +
2653
+ 'It does not re-execute the workload, and never contacts a node.');
2654
+ }, async (argv) => {
2655
+ // yargs collapses a bare "-" positional to an empty string; restore the stdin sentinel.
2656
+ const file = argv.file === '' ? '-' : argv.file;
2657
+ await verifyRecordStandaloneCommand(file, {
2658
+ // `keys` is the alias for `public-key`; yargs populates both, prefer either.
2659
+ publicKey: argv.keys ?? argv['public-key'],
2660
+ kid: argv.kid,
2661
+ json: argv.json,
2662
+ short: argv.short,
2663
+ verbose: argv.verbose,
2664
+ tsaRoots: argv['tsa-roots'],
2665
+ // verify-offline is offline by definition: no network for keys or anything else.
2666
+ offline: true,
2667
+ // Display-only: label the report as an offline verification.
2668
+ offlineMode: true,
2669
+ });
2600
2670
  })
2601
2671
  .command('verify-hash <certificate_hash>', 'Resolve a certificate hash to its public CER record and verify it — one-command verification', (yargs) => {
2602
2672
  return yargs