@aarmos/avar-core 1.1.0 → 1.2.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/README.md CHANGED
@@ -53,4 +53,4 @@ Apache-2.0. See [`LICENSE`](./LICENSE) and [`NOTICE`](./NOTICE).
53
53
 
54
54
  ## Spec revision
55
55
 
56
- Tracks `avar/1` spec revision `1.0`. See the [AVAR spec](https://github.com/Aarmatix/avar-spec/blob/main/SPEC.md) for the current revision and changelog.
56
+ Tracks `avar/1` spec revision `1.1` (release candidate; backward-compatible with `1.0-rc1`). See the [AVAR spec](https://github.com/Aarmatix/avar-spec/blob/main/SPEC.md) for the current revision and changelog.
package/dist/index.d.ts CHANGED
@@ -194,80 +194,43 @@ declare function signedBodyOf(entry: AvarEntry): Omit<AvarEntry, "signature" | "
194
194
 
195
195
  declare function verifyBundle(bundle: AvarBundle): Promise<VerificationReport>;
196
196
 
197
- type DiffOp = {
198
- op: "add";
199
- path: string;
200
- value: unknown;
201
- } | {
202
- op: "remove";
203
- path: string;
204
- oldValue: unknown;
205
- } | {
206
- op: "replace";
207
- path: string;
208
- oldValue: unknown;
209
- value: unknown;
210
- };
211
- type CanonicalDiff = {
212
- equal: boolean;
213
- ops: DiffOp[];
214
- };
215
- /** Structural, canonical diff of two JSON values. */
216
- declare function diffCanonical(a: unknown, b: unknown): CanonicalDiff;
217
- type ReceiptEntryChange = {
218
- id: string;
219
- indexA: number;
220
- indexB: number;
221
- ops: DiffOp[];
222
- signatureChanged: boolean;
223
- entryHashChanged: boolean;
224
- stepsChanged: boolean;
225
- };
226
- type ReceiptDiff = {
227
- equal: boolean;
228
- kind: "bundle" | "entry";
229
- specVersion: {
230
- from: string;
231
- to: string;
232
- } | null;
233
- chainHead: {
234
- from: {
235
- entryHash: string;
236
- index: number;
237
- };
238
- to: {
239
- entryHash: string;
240
- index: number;
241
- };
242
- extended: boolean;
243
- } | null;
244
- devicePublicKeys: {
245
- added: string[];
246
- removed: string[];
247
- };
248
- entries: {
249
- added: Array<{
250
- id: string;
251
- index: number;
252
- }>;
253
- removed: Array<{
254
- id: string;
255
- index: number;
256
- }>;
257
- modified: ReceiptEntryChange[];
258
- unchanged: number;
259
- };
260
- /** Raw canonical diff of manifest + pubkeys (entries handled above). */
261
- ops: DiffOp[];
262
- };
197
+ type AarmosErrorCode = "BUNDLE_OVERSIZED" | "BUNDLE_NOT_ZIP" | "BUNDLE_MISSING_FILES" | "BUNDLE_INVALID_JSON" | "BUNDLE_INVALID_NDJSON" | "BUNDLE_SPEC_UNSUPPORTED" | "BUNDLE_EMPTY" | "SPEC_VERSION_MISMATCH" | "ENTRIES_SHA256_MISMATCH" | "SIGNATURE_MISMATCH" | "FINGERPRINT_MISMATCH" | "CHAIN_BROKEN" | "STEP_CHAIN_BROKEN" | "MANIFEST_INVALID" | "FILE_NOT_FOUND" | "FILE_UNREADABLE" | "UNKNOWN";
198
+ interface AarmosError {
199
+ code: AarmosErrorCode;
200
+ message: string;
201
+ hint: string;
202
+ docsUrl: string;
203
+ }
204
+ declare const AARMOS_ERROR_TEMPLATES: Record<AarmosErrorCode, Omit<AarmosError, "code">>;
205
+ declare function aarmosError(code: AarmosErrorCode, overrides?: Partial<Omit<AarmosError, "code">>): AarmosError;
206
+ /** One-line human-readable format used by the CLI on stderr / bridge diagnostics. */
207
+ declare function formatAarmosErrorLine(err: AarmosError): string;
208
+ /**
209
+ * Report-level classifier. Given an invalid VerificationReport, return the
210
+ * single most-important AarmosError to surface. Priority reflects severity:
211
+ * envelope > signature > fingerprint > chain > step-chain > format.
212
+ */
213
+ declare function classifyReport(report: {
214
+ formatOk: boolean;
215
+ entriesSha256Ok: boolean;
216
+ signaturesOk: boolean;
217
+ fingerprintsOk: boolean;
218
+ chainOk: boolean;
219
+ perStepChainOk: boolean;
220
+ verdict: "valid" | "invalid" | "valid-with-warnings";
221
+ issues: {
222
+ kind: string;
223
+ detail?: string;
224
+ }[];
225
+ }): AarmosError | null;
263
226
  /**
264
- * Compare two AVAR receipts. Accepts full bundles OR single entries.
265
- * Entries are matched by `id`, not by index — reordering is not a diff.
227
+ * Optional Error subclass so throw/catch sites can propagate an AarmosError
228
+ * through code that expects `Error`. Consumers can detect via
229
+ * `err instanceof AarmosErrorException` or `err && (err as any).aarmos`.
266
230
  */
267
- declare function diffReceipts(a: unknown, b: unknown): ReceiptDiff;
268
- /** Canonical diff of two policy documents. Schema-agnostic. */
269
- declare function diffPolicies(a: unknown, b: unknown): CanonicalDiff;
270
- /** Canonical diff of two tool manifests. Schema-agnostic. */
271
- declare function diffToolManifests(a: unknown, b: unknown): CanonicalDiff;
231
+ declare class AarmosErrorException extends Error {
232
+ readonly aarmos: AarmosError;
233
+ constructor(err: AarmosError);
234
+ }
272
235
 
273
- export { type AvarBundle, type AvarEntry, type BundleManifest, type BundlePubKeys, type CanonicalDiff, type DecisionStep, type DiffOp, GENESIS_PREV_HASH, GENESIS_PREV_STEP_HASH, type PolicyHit, type ReceiptDiff, type ReceiptEntryChange, type TextStep, type ToolStep, type TraceStep, type VerificationIssue, type VerificationReport, canonicalize, computeDeviceFingerprint, computeEntryHash, computeStepHash, diffCanonical, diffPolicies, diffReceipts, diffToolManifests, sha256Hex, signedBodyOf, utf8, verifyBundle, verifySignature };
236
+ export { AARMOS_ERROR_TEMPLATES, type AarmosError, type AarmosErrorCode, AarmosErrorException, type AvarBundle, type AvarEntry, type BundleManifest, type BundlePubKeys, type DecisionStep, GENESIS_PREV_HASH, GENESIS_PREV_STEP_HASH, type PolicyHit, type TextStep, type ToolStep, type TraceStep, type VerificationIssue, type VerificationReport, aarmosError, canonicalize, classifyReport, computeDeviceFingerprint, computeEntryHash, computeStepHash, formatAarmosErrorLine, sha256Hex, signedBodyOf, utf8, verifyBundle, verifySignature };
package/dist/index.js CHANGED
@@ -295,6 +295,138 @@ function computeChainHead(entries) {
295
295
  return { entryHash: "", index: -1 };
296
296
  }
297
297
 
298
+ // src/errors.ts
299
+ var SPEC_URL = "https://aarmos.io/docs/avar-spec";
300
+ var VERIFY_URL = "https://aarmos.io/trust/verify";
301
+ var QUICKSTART_URL = "https://aarmos.io/docs/quickstart";
302
+ var AARMOS_ERROR_TEMPLATES = {
303
+ BUNDLE_OVERSIZED: {
304
+ message: "That bundle is larger than the 25 MB browser limit.",
305
+ hint: "Run `aarmos verify <bundle>` locally instead \u2014 the CLI has no size cap.",
306
+ docsUrl: QUICKSTART_URL
307
+ },
308
+ BUNDLE_NOT_ZIP: {
309
+ message: "That file is not a valid .avar.zip.",
310
+ hint: "Bundles are zip archives. Rename or re-export the file, then try again.",
311
+ docsUrl: SPEC_URL
312
+ },
313
+ BUNDLE_MISSING_FILES: {
314
+ message: "Bundle is missing one or more required files.",
315
+ hint: "A valid AVAR bundle contains SPEC-VERSION, manifest.json, entries.ndjson, and pubkeys.json.",
316
+ docsUrl: SPEC_URL
317
+ },
318
+ BUNDLE_INVALID_JSON: {
319
+ message: "manifest.json or pubkeys.json is not valid JSON.",
320
+ hint: "The file exists but cannot be parsed. Re-export from the CLI to regenerate a clean bundle.",
321
+ docsUrl: SPEC_URL
322
+ },
323
+ BUNDLE_INVALID_NDJSON: {
324
+ message: "entries.ndjson contains a malformed line.",
325
+ hint: "Each line must be a self-contained JSON entry. One corrupt line usually means the file was edited by hand.",
326
+ docsUrl: SPEC_URL
327
+ },
328
+ BUNDLE_SPEC_UNSUPPORTED: {
329
+ message: "This bundle was produced by a spec version this verifier does not support.",
330
+ hint: "Update to the latest verifier (`npm i -g @aarmos/avar-core`) or re-export with a matching spec version.",
331
+ docsUrl: SPEC_URL
332
+ },
333
+ BUNDLE_EMPTY: {
334
+ message: "Bundle contains no entries.",
335
+ hint: "An empty ledger is unusual \u2014 check that the run actually produced receipts before exporting.",
336
+ docsUrl: SPEC_URL
337
+ },
338
+ SPEC_VERSION_MISMATCH: {
339
+ message: "This bundle uses a spec version this verifier does not recognize.",
340
+ hint: "Update the verifier (`npm i -g @aarmos/avar-core`) or re-export the bundle with matching spec version.",
341
+ docsUrl: SPEC_URL
342
+ },
343
+ ENTRIES_SHA256_MISMATCH: {
344
+ message: "entries.ndjson has been modified since the bundle was signed.",
345
+ hint: "The envelope hash in manifest.json no longer matches the file. Do not trust this bundle \u2014 re-export from the CLI.",
346
+ docsUrl: VERIFY_URL
347
+ },
348
+ SIGNATURE_MISMATCH: {
349
+ message: "One or more entry signatures do not match the embedded public keys.",
350
+ hint: "The bundle was modified after signing, or the wrong pubkeys.json is bundled.",
351
+ docsUrl: VERIFY_URL
352
+ },
353
+ FINGERPRINT_MISMATCH: {
354
+ message: "Device fingerprint on one or more entries does not match its public key.",
355
+ hint: "The entry claims a device whose public key does not hash to the declared fingerprint. Treat this bundle as untrusted.",
356
+ docsUrl: VERIFY_URL
357
+ },
358
+ CHAIN_BROKEN: {
359
+ message: "The per-entry hash chain is broken.",
360
+ hint: "An entry was inserted, removed, or edited after signing. The break index is shown in the issues list.",
361
+ docsUrl: VERIFY_URL
362
+ },
363
+ STEP_CHAIN_BROKEN: {
364
+ message: "The per-step hash chain inside one or more entries is broken.",
365
+ hint: "A decision or tool step was tampered with. The affected entry index is in the issues list.",
366
+ docsUrl: VERIFY_URL
367
+ },
368
+ MANIFEST_INVALID: {
369
+ message: "manifest.json is missing required fields or uses an unsupported format.",
370
+ hint: "Re-export the bundle from the CLI \u2014 do not edit manifest.json by hand.",
371
+ docsUrl: SPEC_URL
372
+ },
373
+ FILE_NOT_FOUND: {
374
+ message: "Receipt not found at the given path.",
375
+ hint: "Check the path \u2014 a common cause is running from a different working directory than expected.",
376
+ docsUrl: QUICKSTART_URL
377
+ },
378
+ FILE_UNREADABLE: {
379
+ message: "Receipt exists but could not be read.",
380
+ hint: "Check filesystem permissions on the file.",
381
+ docsUrl: QUICKSTART_URL
382
+ },
383
+ UNKNOWN: {
384
+ message: "Something went wrong while verifying that bundle.",
385
+ hint: "Try again with a fresh export. If the error persists, run `aarmos verify` locally to see the raw error.",
386
+ docsUrl: QUICKSTART_URL
387
+ }
388
+ };
389
+ function aarmosError(code, overrides = {}) {
390
+ const base = AARMOS_ERROR_TEMPLATES[code];
391
+ return {
392
+ code,
393
+ message: overrides.message ?? base.message,
394
+ hint: overrides.hint ?? base.hint,
395
+ docsUrl: overrides.docsUrl ?? base.docsUrl
396
+ };
397
+ }
398
+ function formatAarmosErrorLine(err) {
399
+ return `\u2717 ${err.code}: ${err.message}
400
+ Next: ${err.hint}
401
+ Docs: ${err.docsUrl}`;
402
+ }
403
+ function classifyReport(report) {
404
+ if (report.verdict !== "invalid") return null;
405
+ if (!report.entriesSha256Ok) return aarmosError("ENTRIES_SHA256_MISMATCH");
406
+ if (!report.signaturesOk) return aarmosError("SIGNATURE_MISMATCH");
407
+ if (!report.fingerprintsOk) return aarmosError("FINGERPRINT_MISMATCH");
408
+ if (!report.chainOk) return aarmosError("CHAIN_BROKEN");
409
+ if (!report.perStepChainOk) return aarmosError("STEP_CHAIN_BROKEN");
410
+ if (!report.formatOk) {
411
+ const specIssue = report.issues.find((i) => i.kind === "spec-version-mismatch");
412
+ if (specIssue) {
413
+ return aarmosError("SPEC_VERSION_MISMATCH", {
414
+ message: specIssue.detail ? `Spec mismatch \u2014 ${specIssue.detail}` : void 0
415
+ });
416
+ }
417
+ return aarmosError("MANIFEST_INVALID");
418
+ }
419
+ return aarmosError("UNKNOWN");
420
+ }
421
+ var AarmosErrorException = class extends Error {
422
+ aarmos;
423
+ constructor(err) {
424
+ super(`${err.code}: ${err.message}`);
425
+ this.name = "AarmosErrorException";
426
+ this.aarmos = err;
427
+ }
428
+ };
429
+
298
430
  // src/diff.ts
299
431
  function isPlainObject(v) {
300
432
  return typeof v === "object" && v !== null && !Array.isArray(v);
@@ -454,9 +586,13 @@ function diffToolManifests(a, b) {
454
586
  return diffCanonical(a, b);
455
587
  }
456
588
  export {
589
+ AARMOS_ERROR_TEMPLATES,
590
+ AarmosErrorException,
457
591
  GENESIS_PREV_HASH,
458
592
  GENESIS_PREV_STEP_HASH,
593
+ aarmosError,
459
594
  canonicalize,
595
+ classifyReport,
460
596
  computeDeviceFingerprint,
461
597
  computeEntryHash,
462
598
  computeStepHash,
@@ -464,6 +600,7 @@ export {
464
600
  diffPolicies,
465
601
  diffReceipts,
466
602
  diffToolManifests,
603
+ formatAarmosErrorLine,
467
604
  sha256Hex,
468
605
  signedBodyOf,
469
606
  utf8,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aarmos/avar-core",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Reference implementation of the AVAR (Aarmos Verifiable Action Record) spec — canonical JSON, Ed25519 signature verification, hash-chain math, and bundle verification. Pure, zero-dep, browser + Node.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",