@napplet/conformance-cli 0.2.18 → 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 napplet contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -136,7 +136,7 @@ var NappletConformanceHostBundle = (() => {
136
136
  // ── resource ─────────────────────────────────────────────────────────────
137
137
  "resource.info": { dir: "out", fields: { ...ID } },
138
138
  "resource.bytes": { dir: "out", fields: { ...ID, url: "string" } },
139
- "resource.bytesMany": { dir: "out", fields: { ...ID, urls: "array" } },
139
+ "resource.bytesMany": { dir: "out", fields: { ...ID, requests: "array" } },
140
140
  "resource.cancel": { dir: "out", fields: { ...ID } },
141
141
  "resource.info.result": { dir: "in" },
142
142
  "resource.info.error": { dir: "in" },
@@ -185,7 +185,7 @@ var NappletConformanceHostBundle = (() => {
185
185
  "intent.invoke": { dir: "out", fields: { ...ID, request: "object" } },
186
186
  "intent.available": { dir: "out", fields: { ...ID, archetype: "string" } },
187
187
  "intent.handlers": { dir: "out", fields: { ...ID } },
188
- "intent.invoke.result": { dir: "in" },
188
+ "intent.invoke.result": { dir: "in", fields: { ...ID, result: "object" } },
189
189
  "intent.available.result": { dir: "in" },
190
190
  "intent.handlers.result": { dir: "in" },
191
191
  "intent.changed": { dir: "in" },
@@ -336,6 +336,48 @@ var NappletConformanceHostBundle = (() => {
336
336
  });
337
337
  }
338
338
  }
339
+ function validateServerHints(value, field, errors) {
340
+ if (Array.isArray(value) && value.every((server) => typeof server === "string")) return;
341
+ errors.push({
342
+ code: "wrong-type",
343
+ message: `Field "${field}" must be an array of strings`,
344
+ field
345
+ });
346
+ }
347
+ function validateResourceRequestFields(record, errors) {
348
+ if (record.servers !== void 0) validateServerHints(record.servers, "servers", errors);
349
+ }
350
+ function validateResourceBytesManyRequests(requests, errors) {
351
+ if (!Array.isArray(requests)) return;
352
+ if (requests.length === 0) {
353
+ errors.push({
354
+ code: "invalid-resource-request",
355
+ message: 'Field "requests" must contain at least one resource request',
356
+ field: "requests"
357
+ });
358
+ return;
359
+ }
360
+ for (const [index, request] of requests.entries()) {
361
+ const field = `requests[${index}]`;
362
+ if (typeof request !== "object" || request === null || Array.isArray(request)) {
363
+ errors.push({
364
+ code: "invalid-resource-request",
365
+ message: `Field "${field}" must be a resource request object`,
366
+ field
367
+ });
368
+ continue;
369
+ }
370
+ const entry = request;
371
+ if (typeof entry.url !== "string") {
372
+ errors.push({
373
+ code: entry.url === void 0 ? "missing-field" : "wrong-type",
374
+ message: `Field "${field}.url" must be a string`,
375
+ field: `${field}.url`
376
+ });
377
+ }
378
+ if (entry.servers !== void 0) validateServerHints(entry.servers, `${field}.servers`, errors);
379
+ }
380
+ }
339
381
  function validateEnvelope(message) {
340
382
  const errors = [];
341
383
  if (typeof message !== "object" || message === null || Array.isArray(message)) {
@@ -396,6 +438,12 @@ var NappletConformanceHostBundle = (() => {
396
438
  if (type === "intent.invoke") {
397
439
  validateIntentInvokeRequest(record.request, errors);
398
440
  }
441
+ if (type === "resource.bytes") {
442
+ validateResourceRequestFields(record, errors);
443
+ }
444
+ if (type === "resource.bytesMany") {
445
+ validateResourceBytesManyRequests(record.requests, errors);
446
+ }
399
447
  return { ok: errors.length === 0, type, domain, direction: "out", errors };
400
448
  }
401
449
  var NAPPLET_KIND_SNAPSHOT = 5129;
@@ -413,14 +461,14 @@ var NappletConformanceHostBundle = (() => {
413
461
  return NAPPLET_MANIFEST_KINDS.includes(kind);
414
462
  }
415
463
  function isSha256Hex(value) {
416
- return typeof value === "string" && /^[0-9a-f]{64}$/i.test(value);
464
+ return typeof value === "string" && /^[0-9a-f]{64}$/.test(value);
417
465
  }
418
466
  function manifestDTag(event) {
419
- const d = firstTag(event, "d")?.[1]?.trim();
467
+ const d = firstTag(event, "d")?.[1];
420
468
  return d || void 0;
421
469
  }
422
470
  function manifestRequires(event) {
423
- return event.tags.filter((tag) => tag[0] === "requires").map((tag) => tag[1]?.trim() ?? "").filter(Boolean);
471
+ return event.tags.filter((tag) => tag[0] === "R").map((tag) => tag[1]?.trim() ?? "").filter(Boolean);
424
472
  }
425
473
  function validateManifestEvent(event) {
426
474
  const errors = [];
@@ -429,6 +477,7 @@ var NappletConformanceHostBundle = (() => {
429
477
  return {
430
478
  ok: false,
431
479
  requires: [],
480
+ optional: [],
432
481
  errors: [{ code: "missing-manifest-event", message: "No NIP-5D manifest event was resolved" }],
433
482
  warnings
434
483
  };
@@ -441,53 +490,58 @@ var NappletConformanceHostBundle = (() => {
441
490
  message: `Manifest event kind ${event.kind} is not a NIP-5D napplet kind`
442
491
  });
443
492
  }
444
- if (event.kind === NAPPLET_KIND_NAMED && !dTag) {
445
- errors.push({
446
- code: "missing-d-tag",
447
- message: "Named napplet manifest kind 35129 must include a non-empty d tag"
448
- });
449
- } else if ((event.kind === NAPPLET_KIND_ROOT || event.kind === NAPPLET_KIND_SNAPSHOT) && dTag) {
450
- errors.push({
451
- code: "unexpected-d-tag",
452
- message: `Napplet manifest kind ${event.kind} must not include a d tag`
453
- });
493
+ const identifiers = event.tags.filter((tag) => tag[0] === "d");
494
+ if (event.kind === NAPPLET_KIND_NAMED && (identifiers.length !== 1 || !dTag)) {
495
+ errors.push({ code: "missing-d-tag", message: "Named manifests require exactly one non-empty d tag" });
496
+ } else if (event.kind !== NAPPLET_KIND_NAMED && identifiers.length > 0) {
497
+ errors.push({ code: "unexpected-d-tag", message: "Only named manifests carry a d tag" });
454
498
  }
455
- const indexPath = event.tags.find((tag) => tag[0] === "path" && tag[1] === "/index.html");
456
- if (!indexPath) {
457
- errors.push({
458
- code: "missing-index-html",
459
- message: "Napplet manifest must include a path tag for /index.html"
460
- });
461
- } else if (!isSha256Hex(indexPath[2])) {
462
- errors.push({
463
- code: "invalid-index-html-hash",
464
- message: "The /index.html path tag must carry a 64-character sha256 hash"
465
- });
499
+ const hashes = event.tags.filter((tag) => tag[0] === "x");
500
+ if (hashes.length !== 1 || hashes[0].length !== 2 || !isSha256Hex(hashes[0][1])) {
501
+ errors.push({ code: "invalid-artifact-hash", message: "Manifest requires exactly one x tag with the lowercase sha256 of /index.html" });
466
502
  }
467
- for (const req of requires) {
468
- if (req.startsWith("nap:") || req.startsWith("NAP-")) {
469
- errors.push({
470
- code: "invalid-required-nap",
471
- message: `requires tag "${req}" must be a bare NAP domain such as "relay"`
472
- });
473
- continue;
474
- }
475
- if (!NAP_DOMAINS.includes(req)) {
476
- errors.push({
477
- code: "unknown-required-nap",
478
- message: `requires tag "${req}" is not a known NAP domain`
479
- });
480
- }
503
+ if (typeof event.content !== "string" || !event.content.trim()) {
504
+ errors.push({ code: "missing-description", message: "Manifest content must contain a non-empty plain-text description" });
481
505
  }
506
+ validateCapabilities(event, errors, warnings);
507
+ validateMetadata(event, errors, warnings);
482
508
  return {
483
509
  ok: errors.length === 0,
484
510
  kind: event.kind,
485
511
  dTag,
486
512
  requires,
513
+ optional: event.tags.filter((tag) => tag[0] === "O").map((tag) => tag[1]).filter(Boolean),
487
514
  errors,
488
515
  warnings
489
516
  };
490
517
  }
518
+ function validateCapabilities(event, errors, warnings) {
519
+ for (const tag of event.tags.filter((tag2) => tag2[0] === "R" || tag2[0] === "O")) {
520
+ const domain = tag[1];
521
+ if (tag.length !== 2 || !domain || /[.:\s]/.test(domain) || domain.startsWith("NAP-")) {
522
+ errors.push({ code: tag[0] === "R" ? "invalid-required-nap" : "invalid-optional-nap", message: `${tag[0]} must name one bare NAP domain` });
523
+ } else if (!NAP_DOMAINS.includes(domain)) {
524
+ warnings.push({ code: "unknown-required-nap", message: `Domain "${domain}" is not in this tool's registry; check its NAP and runtime availability` });
525
+ }
526
+ }
527
+ }
528
+ function validateMetadata(event, errors, warnings) {
529
+ for (const name of ["title", "source", "a", "A"]) {
530
+ const tags = event.tags.filter((tag) => tag[0] === name);
531
+ if (tags.length > 1 || tags.length && (name === "a" || name === "A") && event.kind !== NAPPLET_KIND_SNAPSHOT) {
532
+ errors.push({ code: "invalid-metadata", message: `Invalid ${name} tag cardinality for kind ${event.kind}` });
533
+ }
534
+ }
535
+ for (const tag of event.tags.filter((tag2) => tag2[0] === "i")) {
536
+ if (!tag[1]?.trim() || tag[1].includes("?")) {
537
+ errors.push({ code: "invalid-metadata", message: "Intent advertisements must be non-empty queryless identities" });
538
+ }
539
+ }
540
+ const icons = event.tags.filter((tag) => tag[0] === "icon");
541
+ if (icons.length > 1 || icons.some((tag) => tag.length !== 3 || !isSha256Hex(tag[1]) || !["image/png", "image/jpeg", "image/webp"].includes(tag[2]))) {
542
+ warnings.push({ code: "invalid-icon", message: "Ignore invalid icon metadata and use generic artwork (NIP-5D \xA7Icon)" });
543
+ }
544
+ }
491
545
  var REFERENCE_PUBKEY = "f".repeat(64);
492
546
  var REFERENCE_ENDPOINT = { dTag: "reference-source" };
493
547
  var REFERENCE_BLOB_URL = "https://reference.invalid/blob";
@@ -587,10 +641,11 @@ var NappletConformanceHostBundle = (() => {
587
641
  "resource.bytesMany": (e) => ok({
588
642
  type: "resource.bytesMany.result",
589
643
  id: e.id,
590
- items: Array.isArray(e.urls) ? e.urls.map((url) => {
644
+ items: Array.isArray(e.requests) ? e.requests.map((request) => {
645
+ const url = typeof request === "object" && request !== null && !Array.isArray(request) ? request.url : void 0;
591
646
  const decoded = dataUrlToBlob(url);
592
647
  return {
593
- url,
648
+ url: typeof url === "string" ? url : "",
594
649
  ok: true,
595
650
  blob: decoded?.blob ?? new Blob([]),
596
651
  mime: decoded?.mime ?? "application/octet-stream"
@@ -604,7 +659,8 @@ var NappletConformanceHostBundle = (() => {
604
659
  schemes: [
605
660
  { scheme: "data", enabled: true },
606
661
  { scheme: "https", enabled: true }
607
- ]
662
+ ],
663
+ maxServers: 8
608
664
  }
609
665
  }),
610
666
  "resource.cancel": none,
@@ -738,118 +794,133 @@ var NappletConformanceHostBundle = (() => {
738
794
  "fs.watch": (e) => ok({ type: "fs.watch.result", id: e.id, watchId: `watch-${String(e.id)}` }),
739
795
  "fs.unwatch": (e) => ok({ type: "fs.unwatch.result", id: e.id })
740
796
  };
741
- function createReferenceShell(options = {}) {
742
- const now = options.now ?? (() => Date.now());
743
- const records = [];
744
- const targetQueues = /* @__PURE__ */ new Map();
745
- function queueDelivery(target, delivery) {
746
- const queue = targetQueues.get(target);
747
- if (queue) {
748
- queue.push(delivery);
749
- return;
750
- }
751
- targetQueues.set(target, [delivery]);
752
- }
753
- function takeDeliveries(target) {
754
- const queue = targetQueues.get(target) ?? [];
755
- targetQueues.delete(target);
756
- return queue;
757
- }
758
- function unavailableIntent(id, archetype, action, error) {
759
- return ok({
760
- type: "intent.invoke.result",
761
- id,
762
- result: { ok: false, archetype, action, handled: false, error }
763
- });
764
- }
765
- function handleIntentInvoke(_endpoint, env) {
766
- const request = env.request;
767
- if (typeof request !== "object" || request === null || Array.isArray(request)) {
768
- return unavailableIntent(env.id, "", "open", "invalid intent request");
769
- }
770
- const intent = request;
771
- const archetype = typeof intent.archetype === "string" ? intent.archetype : "";
772
- const action = typeof intent.action === "string" ? intent.action : "open";
773
- if (!archetype) {
774
- return unavailableIntent(env.id, archetype, action, "intent request requires an archetype");
775
- }
776
- if (archetype !== "note") {
777
- return unavailableIntent(env.id, archetype, action, "no handler");
778
- }
779
- if (intent.convention !== void 0 && intent.convention !== REFERENCE_CONVENTION) {
780
- return unavailableIntent(env.id, archetype, action, "unsupported convention");
781
- }
782
- return ok({
783
- type: "intent.invoke.result",
784
- id: env.id,
785
- result: {
786
- ok: true,
787
- archetype,
788
- action,
789
- handled: true,
790
- handler: REFERENCE_HANDLER,
791
- windowId: "reference-window",
792
- convention: typeof intent.convention === "string" ? intent.convention : REFERENCE_CONVENTION
793
- }
794
- });
795
- }
796
- function intentAvailability(archetype) {
797
- if (archetype !== "note") {
798
- return { archetype, available: false, candidates: [], hasDefault: false };
799
- }
800
- return {
801
- archetype,
802
- available: true,
803
- candidates: [{
804
- dTag: REFERENCE_HANDLER,
805
- actions: ["open"],
806
- conventions: [REFERENCE_CONVENTION],
807
- isDefault: true
808
- }],
809
- hasDefault: true
810
- };
811
- }
812
- function handleFrom(endpoint, envelope) {
813
- const type = envelope && typeof envelope === "object" && typeof envelope.type === "string" ? envelope.type : void 0;
797
+ function createReferenceHandler(options) {
798
+ return (endpoint, envelope) => {
799
+ const type = getEnvelopeType(envelope);
814
800
  const verdict = validateEnvelope(envelope);
815
- records.push({ envelope, verdict, timestamp: now() });
801
+ options.records.push({ envelope, verdict, timestamp: options.now() });
816
802
  if (!type || !verdict.ok) return [];
817
803
  const env = envelope;
818
- if (type === "intent.invoke") return handleIntentInvoke(endpoint, env);
804
+ if (type === "intent.invoke") return options.intents.handleInvoke(endpoint, env);
819
805
  if (type === "intent.available") {
820
- return ok({ type: "intent.available.result", id: env.id, availability: intentAvailability(env.archetype) });
806
+ return ok({ type: "intent.available.result", id: env.id, availability: options.intents.availability(env.archetype) });
821
807
  }
822
808
  if (type === "intent.handlers") {
823
- return ok({ type: "intent.handlers.result", id: env.id, handlers: [intentAvailability("note")] });
824
- }
825
- if (type === "inc.emit") {
826
- if (env.topic === REFERENCE_CONVENTION) {
827
- const event = {
828
- type: "inc.event",
829
- topic: env.topic,
830
- sender: endpoint.dTag
831
- };
832
- if ("payload" in env) event.payload = env.payload;
833
- queueDelivery(REFERENCE_SUBSCRIBER, event);
834
- }
835
- return [];
809
+ return ok({ type: "intent.handlers.result", id: env.id, handlers: [options.intents.availability("note")] });
836
810
  }
811
+ if (type === "inc.emit") return handleIncEmit(endpoint, env, options.queueDelivery);
837
812
  const responder = RESPONDERS[type];
838
813
  return responder ? responder(env) : [];
814
+ };
815
+ }
816
+ function getEnvelopeType(envelope) {
817
+ if (!envelope || typeof envelope !== "object") return void 0;
818
+ const type = envelope.type;
819
+ return typeof type === "string" ? type : void 0;
820
+ }
821
+ function handleIncEmit(endpoint, env, queueDelivery) {
822
+ if (env.topic !== REFERENCE_CONVENTION) return [];
823
+ const event = {
824
+ type: "inc.event",
825
+ topic: env.topic,
826
+ sender: endpoint.dTag
827
+ };
828
+ if ("payload" in env) event.payload = env.payload;
829
+ queueDelivery(REFERENCE_SUBSCRIBER, event);
830
+ return [];
831
+ }
832
+ function unavailableIntent(id, archetype, action, error) {
833
+ return ok({
834
+ type: "intent.invoke.result",
835
+ id,
836
+ result: { ok: false, archetype, action, handled: false, error }
837
+ });
838
+ }
839
+ function createIntentHandlers() {
840
+ return { handleInvoke, availability };
841
+ }
842
+ function handleInvoke(_endpoint, env) {
843
+ const request = env.request;
844
+ if (typeof request !== "object" || request === null || Array.isArray(request)) {
845
+ return unavailableIntent(env.id, "", "open", "invalid intent request");
846
+ }
847
+ const intent = request;
848
+ const archetype = typeof intent.archetype === "string" ? intent.archetype : "";
849
+ const action = typeof intent.action === "string" ? intent.action : "open";
850
+ if (!archetype) {
851
+ return unavailableIntent(env.id, archetype, action, "intent request requires an archetype");
839
852
  }
840
- function handle(envelope) {
841
- return handleFrom(REFERENCE_ENDPOINT, envelope);
853
+ if (archetype !== "note") return unavailableIntent(env.id, archetype, action, "no handler");
854
+ if (intent.convention !== void 0 && intent.convention !== REFERENCE_CONVENTION) {
855
+ return unavailableIntent(env.id, archetype, action, "unsupported convention");
856
+ }
857
+ return ok({
858
+ type: "intent.invoke.result",
859
+ id: env.id,
860
+ result: {
861
+ ok: true,
862
+ archetype,
863
+ action,
864
+ handled: true,
865
+ handler: REFERENCE_HANDLER,
866
+ windowId: "reference-window",
867
+ convention: typeof intent.convention === "string" ? intent.convention : REFERENCE_CONVENTION
868
+ }
869
+ });
870
+ }
871
+ function availability(archetype) {
872
+ if (archetype !== "note") {
873
+ return { archetype, available: false, candidates: [], hasDefault: false };
842
874
  }
875
+ return {
876
+ archetype,
877
+ available: true,
878
+ candidates: [{
879
+ dTag: REFERENCE_HANDLER,
880
+ actions: ["open"],
881
+ conventions: [REFERENCE_CONVENTION],
882
+ isDefault: true
883
+ }],
884
+ hasDefault: true
885
+ };
886
+ }
887
+ function createReferenceShell(options = {}) {
888
+ const records = [];
889
+ const deliveries = createDeliveryQueue();
890
+ const handleFrom = createReferenceHandler({
891
+ now: options.now ?? Date.now,
892
+ records,
893
+ queueDelivery: deliveries.queue,
894
+ intents: createIntentHandlers()
895
+ });
843
896
  return {
844
897
  get records() {
845
898
  return records;
846
899
  },
847
- handle,
900
+ handle: (envelope) => handleFrom(REFERENCE_ENDPOINT, envelope),
848
901
  handleFrom,
849
- takeDeliveries,
902
+ takeDeliveries: deliveries.take,
850
903
  reset() {
851
904
  records.length = 0;
852
- targetQueues.clear();
905
+ deliveries.clear();
906
+ }
907
+ };
908
+ }
909
+ function createDeliveryQueue() {
910
+ const queues = /* @__PURE__ */ new Map();
911
+ return {
912
+ queue(target, delivery) {
913
+ const queue = queues.get(target);
914
+ if (queue) queue.push(delivery);
915
+ else queues.set(target, [delivery]);
916
+ },
917
+ take(target) {
918
+ const queue = queues.get(target) ?? [];
919
+ queues.delete(target);
920
+ return queue;
921
+ },
922
+ clear() {
923
+ queues.clear();
853
924
  }
854
925
  };
855
926
  }
@@ -902,11 +973,14 @@ var NappletConformanceHostBundle = (() => {
902
973
  napplet.resource.info = () =>
903
974
  request({ type: 'resource.info' }, 'resource.info.result', 'resource.info.error')
904
975
  .then((msg) => msg.info);
905
- napplet.resource.bytes = (url) =>
906
- request({ type: 'resource.bytes', url: String(url) }, 'resource.bytes.result', 'resource.bytes.error')
976
+ napplet.resource.bytes = (url, options) => {
977
+ const message = { type: 'resource.bytes', url: String(url) };
978
+ if (options && options.servers !== undefined) message.servers = options.servers;
979
+ return request(message, 'resource.bytes.result', 'resource.bytes.error')
907
980
  .then((msg) => msg.blob);
908
- napplet.resource.bytesMany = (urls) =>
909
- request({ type: 'resource.bytesMany', urls: Array.from(urls || []) }, 'resource.bytesMany.result', 'resource.bytesMany.error')
981
+ };
982
+ napplet.resource.bytesMany = (requests) =>
983
+ request({ type: 'resource.bytesMany', requests: Array.from(requests || []) }, 'resource.bytesMany.result', 'resource.bytesMany.error')
910
984
  .then((msg) => msg.items);
911
985
  napplet.resource.bytesAsObjectURL = (url) => {
912
986
  const handle = { url: '', revoke: () => {} };
@@ -1044,13 +1118,14 @@ var NappletConformanceHostBundle = (() => {
1044
1118
  var skip = (detail) => ({ status: "skip", detail });
1045
1119
  var result = { pass, fail, skip };
1046
1120
  function manifestErrors(ctx, codes) {
1047
- return validateManifestEvent(ctx.manifestEvent).errors.filter((e) => codes.includes(e.code));
1121
+ const verdict = validateManifestEvent(ctx.manifestEvent);
1122
+ return [...verdict.errors, ...verdict.warnings].filter((e) => codes.includes(e.code));
1048
1123
  }
1049
- function manifestCheck(id, title, codes) {
1124
+ function manifestCheck(id, title, codes, severity = "error") {
1050
1125
  return {
1051
1126
  id,
1052
1127
  area: "manifest",
1053
- severity: "error",
1128
+ severity,
1054
1129
  title,
1055
1130
  run: (ctx) => {
1056
1131
  if (!ctx.manifestEvent) return result.skip("No NIP-5D manifest event was resolved");
@@ -1067,13 +1142,18 @@ var NappletConformanceHostBundle = (() => {
1067
1142
  "unexpected-d-tag"
1068
1143
  ]),
1069
1144
  manifestCheck("manifest/index-html", "Manifest declares a hashed /index.html", [
1070
- "missing-index-html",
1071
- "invalid-index-html-hash"
1145
+ "invalid-artifact-hash",
1146
+ "missing-description",
1147
+ "invalid-metadata"
1072
1148
  ]),
1073
- manifestCheck("manifest/requires", "requires tags are bare known NAP domains", [
1149
+ manifestCheck("manifest/requires", "R/O tags name bare NAP domains", [
1074
1150
  "invalid-required-nap",
1075
- "unknown-required-nap"
1151
+ "invalid-optional-nap"
1076
1152
  ]),
1153
+ // Registry coverage is a tooling advisory, not a protocol requirement.
1154
+ manifestCheck("manifest/requires-known", "R/O domains are in this toolchain registry (advisory)", [
1155
+ "unknown-required-nap"
1156
+ ], "warning"),
1077
1157
  // ── boot ─────────────────────────────────────────────────────────────────────
1078
1158
  {
1079
1159
  id: "boot/sandbox-allow-scripts",