capdag 1.311.160 → 1.363.10

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/capdag.js CHANGED
@@ -1,12 +1,36 @@
1
1
  // Cap URN JavaScript Implementation
2
2
  // Follows the exact same rules as Rust, Go, and Objective-C implementations
3
-
4
- // Import TaggedUrn from the tagged-urn package
5
- const {
6
- TaggedUrn,
7
- valuesMatch: taggedUrnValuesMatch,
8
- scoreTagValue
9
- } = require('tagged-urn');
3
+ //
4
+ // Dispatch, acceptance, equivalence and specificity are decided by code
5
+ // generated from the proved model in ../formal (Lean) by lungo, in ./formal,
6
+ // not written here. It runs as WebAssembly, instantiated once when this module
7
+ // is first imported.
8
+
9
+ import { TaggedUrn } from 'tagged-urn';
10
+ import * as machineParser from './machine-parser.js';
11
+ import { load } from './formal/index.js';
12
+
13
+ const model = await load();
14
+
15
+ // Node's modules, for what works with the file system and with processes:
16
+ // cartridge discovery, installation records and directory hashes. They exist
17
+ // only under Node; elsewhere this module loads without them, and asking for
18
+ // them there is an error naming what needed them.
19
+ const node = (typeof process !== 'undefined' && process.versions && process.versions.node)
20
+ ? {
21
+ fs: await import('node:fs'),
22
+ path: await import('node:path'),
23
+ crypto: await import('node:crypto'),
24
+ childProcess: await import('node:child_process'),
25
+ }
26
+ : null;
27
+
28
+ function nodeModules(what) {
29
+ if (node === null) {
30
+ throw new Error(`capdag: ${what} needs Node's file system and processes, which this runtime does not have`);
31
+ }
32
+ return node;
33
+ }
10
34
 
11
35
  /**
12
36
  * Error types for Cap URN operations
@@ -83,7 +107,7 @@ function processDirectionTag(taggedUrn, tagName) {
83
107
  */
84
108
  function canonicalizeDirectionSpec(spec, tagName) {
85
109
  if (spec === 'media:' || spec === '*') {
86
- return spec;
110
+ return 'media:';
87
111
  }
88
112
 
89
113
  try {
@@ -106,7 +130,7 @@ function canonicalizeDirectionSpec(spec, tagName) {
106
130
  */
107
131
  function validatePreservedDirectionSpec(spec, tagName) {
108
132
  if (spec === 'media:' || spec === '*') {
109
- return spec;
133
+ return 'media:';
110
134
  }
111
135
 
112
136
  try {
@@ -195,6 +219,27 @@ function validateNonStructuralTags(tags) {
195
219
  }
196
220
  }
197
221
 
222
+ /**
223
+ * A tagged URN as capdag's model takes it. Each program generated for
224
+ * JavaScript runs in a WebAssembly instance of its own, so tagged-urn's model
225
+ * value of a URN cannot pass here: this program makes its own, from the same
226
+ * tags, with the same function of the same model.
227
+ */
228
+ function modelUrn(urn) {
229
+ const made = model.make(urn.prefix, urn.modelTags());
230
+ if (made === null) {
231
+ throw new Error(`capdag: the model refused ${urn.toString()}, whose keys are sorted`);
232
+ }
233
+ return made;
234
+ }
235
+
236
+ const MODEL_EFFECTS = Object.freeze({
237
+ declared: Object.freeze({ kind: 'declared' }),
238
+ none: Object.freeze({ kind: 'none' }),
239
+ patch: Object.freeze({ kind: 'patch' }),
240
+ '?': Object.freeze({ kind: 'unspecified' }),
241
+ });
242
+
198
243
  class CapUrn {
199
244
  // Per-axis weights for cap-URN specificity. Two orders of
200
245
  // magnitude separate each axis to keep them in distinct digit
@@ -224,6 +269,20 @@ class CapUrn {
224
269
  }
225
270
  validateNonStructuralTags(this.tags);
226
271
  this._validateAdmissible();
272
+ // The same cap on the proved model's side: its three URNs and its effect.
273
+ // Dispatch, acceptance, equivalence and specificity are asked of it. Made
274
+ // here, from the same fields, and the cap is frozen, so the two can never
275
+ // describe different caps; it is not enumerable, so it is not part of what
276
+ // a cap serializes or compares as.
277
+ const formal = {
278
+ input: modelUrn(MediaUrn.fromString(this.inSpec)._urn),
279
+ output: modelUrn(MediaUrn.fromString(this.outSpec)._urn),
280
+ other: modelUrn(new TaggedUrn('cap', this.tags)),
281
+ effect: MODEL_EFFECTS[this.effectValue],
282
+ };
283
+ Object.defineProperty(this, 'formal', { value: formal, enumerable: false });
284
+ Object.freeze(this.tags);
285
+ Object.freeze(this);
227
286
  }
228
287
 
229
288
  /**
@@ -587,79 +646,38 @@ class CapUrn {
587
646
  }
588
647
 
589
648
  /**
590
- * Check if this cap (pattern/handler) accepts a request (instance).
649
+ * Whether `cap` fits this cap read as a PATTERN over caps — what a search
650
+ * asks: `cap`'s input is within this pattern's, its output covers the
651
+ * pattern's, its effect is the pattern's (a pattern's `?effect` fits any),
652
+ * and its cap-tags — complete: a cap has the tags it has — satisfy the
653
+ * pattern's.
591
654
  *
592
- * Direction (in/out) uses TaggedUrn.accepts()/conformsTo() (via MediaUrn matching):
593
- * - Input: capIn.accepts(requestIn) — cap's input spec is pattern, request's input is instance
594
- * - Output: capOut.conformsTo(requestOut) — cap's output is instance, request's output is pattern
595
- * For other tags:
596
- * - For each tag in the request: cap has same value, wildcard (*), or missing tag
597
- * - For each tag in the cap: if request is missing that tag, that's fine (cap is more specific)
598
- * Missing tags (except in/out) are treated as wildcards (less specific, can handle any value).
655
+ * A side the pattern leaves open is not asked about. `cap:candle` fits every
656
+ * cap tagged `candle`, whatever it takes and gives: its open output is "not
657
+ * established", not the type "anything".
599
658
  *
600
- * @param {CapUrn} request - The request cap to check
601
- * @returns {boolean} Whether this cap accepts the request
659
+ * Decided by the proved model (CapDAG.Exec.accepts, which is CapDAG.fits).
660
+ * For a question no cap URN can spell — "what gives this, whatever it
661
+ * takes" — ask a CapQuery.
662
+ *
663
+ * @param {CapUrn} cap - The cap to test against this pattern
664
+ * @returns {boolean} Whether the cap fits
602
665
  */
603
- accepts(request) {
604
- if (!request) {
666
+ accepts(cap) {
667
+ if (!cap) {
605
668
  return true;
606
669
  }
607
-
608
- // Input direction: pattern accepts instance. `media:` on the pattern side is
609
- // the wildcard top and skips the check.
610
- if (this.inSpec !== 'media:' && this.inSpec !== '*') {
611
- const capIn = MediaUrn.fromString(this.inSpec);
612
- const requestIn = MediaUrn.fromString(request.inSpec);
613
- if (!capIn.accepts(requestIn)) {
614
- return false;
615
- }
616
- }
617
-
618
- // Output direction: candidate output must conform to requested output.
619
- // `media:` on the pattern side is wildcard top and skips the check.
620
- if (this.outSpec !== 'media:' && this.outSpec !== '*') {
621
- const capOut = MediaUrn.fromString(this.outSpec);
622
- const requestOut = MediaUrn.fromString(request.outSpec);
623
- if (!capOut.conformsTo(requestOut)) {
624
- return false;
625
- }
626
- }
627
-
628
- if (this.effectValue !== CapEffect.ANY && this.effectValue !== request.effectValue) {
629
- return false;
630
- }
631
-
632
- // Y-axis: every tag's per-key match runs through the six-form
633
- // truth table (taggedUrnValuesMatch). Walk the union of all keys
634
- // appearing on either side so missing-on-pattern and
635
- // missing-on-instance cells both get evaluated.
636
- const allKeys = new Set([
637
- ...Object.keys(this.tags),
638
- ...Object.keys(request.tags),
639
- ]);
640
- for (const key of allKeys) {
641
- const patt = Object.prototype.hasOwnProperty.call(this.tags, key)
642
- ? this.tags[key]
643
- : undefined;
644
- const inst = Object.prototype.hasOwnProperty.call(request.tags, key)
645
- ? request.tags[key]
646
- : undefined;
647
- if (!taggedUrnValuesMatch(inst, patt)) {
648
- return false;
649
- }
650
- }
651
- return true;
670
+ return model.accepts(this.formal, cap.formal);
652
671
  }
653
672
 
654
673
  /**
655
- * Check if this cap (instance) conforms to another cap (pattern).
656
- * Equivalent to cap.accepts(this).
674
+ * Whether this cap fits `pattern`: `pattern.accepts(this)`.
657
675
  *
658
- * @param {CapUrn} cap - The cap to check conformance against
659
- * @returns {boolean} Whether this cap conforms to the given cap
676
+ * @param {CapUrn} pattern
677
+ * @returns {boolean}
660
678
  */
661
- conformsTo(cap) {
662
- return cap.accepts(this);
679
+ conformsTo(pattern) {
680
+ return pattern.accepts(this);
663
681
  }
664
682
 
665
683
  /**
@@ -689,16 +707,8 @@ class CapUrn {
689
707
  * @returns {number} The specificity score
690
708
  */
691
709
  specificity() {
692
- const inUrn = MediaUrn.fromString(this.inSpec);
693
- const outUrn = MediaUrn.fromString(this.outSpec);
694
-
695
- let yScore = 0;
696
- for (const value of Object.values(this.tags)) {
697
- yScore += scoreTagValue(value);
698
- }
699
- return CapUrn.WEIGHT_OUT * outUrn.specificity()
700
- + CapUrn.WEIGHT_IN * inUrn.specificity()
701
- + yScore;
710
+ // Computed by the proved model (CapDAG.Exec.specificity).
711
+ return Number(model.specificity(this.formal));
702
712
  }
703
713
 
704
714
  /**
@@ -781,7 +791,7 @@ class CapUrn {
781
791
  * @returns {boolean}
782
792
  */
783
793
  isComparable(other) {
784
- return this.accepts(other) || other.accepts(this);
794
+ return model.comparable(this.formal, other.formal);
785
795
  }
786
796
 
787
797
  /**
@@ -792,7 +802,7 @@ class CapUrn {
792
802
  * @returns {boolean}
793
803
  */
794
804
  isEquivalent(other) {
795
- return this.accepts(other) && other.accepts(this);
805
+ return model.equivalent(this.formal, other.formal);
796
806
  }
797
807
 
798
808
  /**
@@ -851,51 +861,62 @@ class CapUrn {
851
861
  }
852
862
 
853
863
  /**
854
- * Check if this candidate can dispatch the given request.
864
+ * Whether this candidate SERVES `request` — the predicate routing and
865
+ * dispatch act on.
866
+ *
867
+ * The candidate takes at least what the request sends, gives at least what
868
+ * the request needs, has the effect asked for unless the request says
869
+ * `?effect`, and has the cap-tags asked for — its own tags being complete, so
870
+ * a request for `!x` is served by a candidate that does not mention `x`, and
871
+ * a candidate may carry tags the request does not ask about.
872
+ *
873
+ * An input the request leaves open is not established: the caller has not
874
+ * said what it will send, and every candidate passes that side. That is
875
+ * "some input", not "any input" — `media:` on a CANDIDATE's input does mean
876
+ * it takes anything.
877
+ *
878
+ * This is a guarantee. What only could serve does not; see mayDispatch and
879
+ * CapQuery.grade. Decided by the proved model (CapDAG.Exec.dispatch, which is
880
+ * CapDAG.serves). Not symmetric.
855
881
  *
856
882
  * @param {CapUrn} request
857
883
  * @returns {boolean}
858
884
  */
859
885
  isDispatchable(request) {
860
- return this._inputDispatchable(request)
861
- && this._outputDispatchable(request)
862
- && this._effectDispatchable(request)
863
- && this._capTagsDispatchable(request);
864
- }
865
-
866
- _inputDispatchable(request) {
867
- if (request.inSpec === 'media:') return true;
868
- if (this.inSpec === 'media:') return true;
869
- return MediaUrn.fromString(request.inSpec).conformsTo(MediaUrn.fromString(this.inSpec));
886
+ return model.dispatch(this.formal, request.formal);
870
887
  }
871
888
 
872
- _outputDispatchable(request) {
873
- if (request.outSpec === 'media:') return true;
874
- if (this.outSpec === 'media:') return false;
875
- return MediaUrn.fromString(this.outSpec).conformsTo(MediaUrn.fromString(request.outSpec));
889
+ /**
890
+ * Whether this candidate COULD serve `request`: not guaranteed, not
891
+ * excluded. For exploring what the fabric might do — never for routing a
892
+ * call, which must be served.
893
+ *
894
+ * @param {CapUrn} request
895
+ * @returns {boolean}
896
+ */
897
+ mayDispatch(request) {
898
+ return model.mayDispatch(this.formal, request.formal);
876
899
  }
877
900
 
878
- _effectDispatchable(request) {
879
- return request.effectValue === CapEffect.ANY || this.effectValue === request.effectValue;
901
+ /**
902
+ * Whether what this cap gives, `next` takes: the edge of a route.
903
+ *
904
+ * @param {CapUrn} next
905
+ * @returns {boolean}
906
+ */
907
+ flowsInto(next) {
908
+ return model.flows(this.formal, next.formal);
880
909
  }
881
910
 
882
- _capTagsDispatchable(request) {
883
- const allKeys = new Set([
884
- ...Object.keys(this.tags),
885
- ...Object.keys(request.tags),
886
- ]);
887
- for (const key of allKeys) {
888
- const patt = Object.prototype.hasOwnProperty.call(request.tags, key)
889
- ? request.tags[key]
890
- : undefined;
891
- const inst = Object.prototype.hasOwnProperty.call(this.tags, key)
892
- ? this.tags[key]
893
- : undefined;
894
- if (!taggedUrnValuesMatch(inst, patt)) {
895
- return false;
896
- }
897
- }
898
- return true;
911
+ /**
912
+ * Whether what this cap gives COULD be something `next` takes: an edge a
913
+ * search may explore and a run has to check.
914
+ *
915
+ * @param {CapUrn} next
916
+ * @returns {boolean}
917
+ */
918
+ mayFlowInto(next) {
919
+ return model.mayFlow(this.formal, next.formal);
899
920
  }
900
921
 
901
922
  /**
@@ -908,7 +929,9 @@ class CapUrn {
908
929
  const declaredIn = this.inMediaUrn();
909
930
  const declaredOut = this.outMediaUrn();
910
931
 
911
- if (!runtimeInput.conformsTo(declaredIn)) {
932
+ // A runtime media URN is the media of a value that exists, so it is read
933
+ // complete: it SATISFIES the declared type, or does not.
934
+ if (!runtimeInput.satisfies(declaredIn)) {
912
935
  throw new CapUrnError(
913
936
  ErrorCodes.INVALID_EFFECT_APPLICATION,
914
937
  `Runtime input '${runtimeInput}' does not conform to declared input '${declaredIn}'`
@@ -940,7 +963,7 @@ class CapUrn {
940
963
  );
941
964
  }
942
965
 
943
- if (!runtimeOut.conformsTo(declaredOut)) {
966
+ if (!runtimeOut.satisfies(declaredOut)) {
944
967
  throw new CapUrnError(
945
968
  ErrorCodes.INVALID_EFFECT_APPLICATION,
946
969
  `Inferred runtime output '${runtimeOut}' does not conform to declared output '${declaredOut}'`
@@ -984,7 +1007,7 @@ class CapUrn {
984
1007
  case CapEffect.PATCH:
985
1008
  return runtimeOutput.isEquivalent(inferred);
986
1009
  case CapEffect.DECLARED:
987
- return runtimeOutput.conformsTo(inferred);
1010
+ return runtimeOutput.satisfies(inferred);
988
1011
  default:
989
1012
  throw new CapUrnError(
990
1013
  ErrorCodes.INVALID_EFFECT_APPLICATION,
@@ -1128,6 +1151,109 @@ class CapUrnBuilder {
1128
1151
  }
1129
1152
  }
1130
1153
 
1154
+ /**
1155
+ * How a registered cap answers a CapQuery.
1156
+ *
1157
+ * - EXACT: guaranteed, and exactly what was asked on every side that was asked.
1158
+ * - GUARANTEED: whatever the cap takes and gives, it is what was asked.
1159
+ * - POSSIBLE: not guaranteed and not excluded — only running it tells. For
1160
+ * exploring; a call is never routed on it.
1161
+ * - NONE: excluded.
1162
+ */
1163
+ const MatchGrade = Object.freeze({
1164
+ EXACT: 'exact',
1165
+ GUARANTEED: 'guaranteed',
1166
+ POSSIBLE: 'possible',
1167
+ NONE: 'none',
1168
+ });
1169
+
1170
+ /** Whether routing may act on a grade. */
1171
+ function gradeIsGuaranteed(grade) {
1172
+ return grade === MatchGrade.EXACT || grade === MatchGrade.GUARANTEED;
1173
+ }
1174
+
1175
+ /** The cap-tag pattern a query asks for: `cap:` with these tags (none asks for none). */
1176
+ function queryTagPattern(tags) {
1177
+ return modelUrn(new TaggedUrn('cap', { ...(tags || {}) }));
1178
+ }
1179
+
1180
+ /**
1181
+ * A question about caps.
1182
+ *
1183
+ * A request is not a cap. A cap takes this and gives that; a request is a
1184
+ * QUESTION about caps, and may leave a side unasked: "what gives me this file,
1185
+ * whatever it takes?", "what can this input become?". Those are not cap URNs
1186
+ * with `media:` on a side — `media:` is the type "anything", a claim about
1187
+ * every input — they are queries with that side unknown.
1188
+ *
1189
+ * A cap URN read as a request or as a pattern is one (fromRequest, fromPattern)
1190
+ * — which is what CapUrn.isDispatchable and CapUrn.accepts ask — and so are the
1191
+ * questions no cap URN can spell (producing, consuming, between). Every answer
1192
+ * is decided by the proved model (formal/CapDAG/Query.lean).
1193
+ */
1194
+ class CapQuery {
1195
+ constructor(formal, asked) {
1196
+ Object.defineProperty(this, 'formal', { value: formal, enumerable: false });
1197
+ this.asked = asked;
1198
+ Object.freeze(this);
1199
+ }
1200
+
1201
+ toString() {
1202
+ return this.asked;
1203
+ }
1204
+
1205
+ /** The cap URN `request`, read as a request: an input it leaves open is not established. */
1206
+ static fromRequest(request) {
1207
+ return new CapQuery(model.queryOfRequest(request.formal), `request ${request}`);
1208
+ }
1209
+
1210
+ /** The cap URN `pattern`, read as a pattern over caps: an output it leaves open is not established. */
1211
+ static fromPattern(pattern) {
1212
+ return new CapQuery(model.queryOfPattern(pattern.formal), `pattern ${pattern}`);
1213
+ }
1214
+
1215
+ /** Caps that GIVE `output`, whatever they take, with the cap-tags `tags` asks for. */
1216
+ static producing(output, tags) {
1217
+ return new CapQuery(
1218
+ model.queryProducing(modelUrn(output._urn), queryTagPattern(tags)),
1219
+ `anything giving ${output}`);
1220
+ }
1221
+
1222
+ /** Caps that TAKE `input`, whatever they give: what this input can become in one step. */
1223
+ static consuming(input, tags) {
1224
+ return new CapQuery(
1225
+ model.queryConsuming(modelUrn(input._urn), queryTagPattern(tags)),
1226
+ `anything taking ${input}`);
1227
+ }
1228
+
1229
+ /** Caps that take `input` and give `output`: nothing unknown. */
1230
+ static between(input, output, tags) {
1231
+ return new CapQuery(
1232
+ model.queryBetween(modelUrn(input._urn), modelUrn(output._urn), queryTagPattern(tags)),
1233
+ `anything taking ${input} and giving ${output}`);
1234
+ }
1235
+
1236
+ /** Whether `cap` is guaranteed to be what is asked. */
1237
+ admits(cap) {
1238
+ return model.queryAdmits(this.formal, cap.formal);
1239
+ }
1240
+
1241
+ /** Whether `cap` could be what is asked. */
1242
+ mayAdmit(cap) {
1243
+ return model.queryMayAdmit(this.formal, cap.formal);
1244
+ }
1245
+
1246
+ /** How `cap` answers: one of MatchGrade. */
1247
+ grade(cap) {
1248
+ const answer = model.queryGrade(this.formal, cap.formal);
1249
+ const grade = { exact: MatchGrade.EXACT, guaranteed: MatchGrade.GUARANTEED, possible: MatchGrade.POSSIBLE, none: MatchGrade.NONE }[answer.kind];
1250
+ if (grade === undefined) {
1251
+ throw new Error(`capdag: the model answered with a grade this mirror does not know: ${JSON.stringify(answer)}`);
1252
+ }
1253
+ return grade;
1254
+ }
1255
+ }
1256
+
1131
1257
  /**
1132
1258
  * Cap Matcher utility class
1133
1259
  */
@@ -1637,6 +1763,34 @@ class MediaUrn {
1637
1763
  */
1638
1764
  accepts(instance) { return this._urn.accepts(instance._urn); }
1639
1765
 
1766
+ /**
1767
+ * Whether this media type and `other` COULD describe the same value: not a
1768
+ * guarantee (conformsTo), and not excluded. `media:ext` meets
1769
+ * `media:ext=pdf`; `media:ext=pdf` does not meet `media:ext=png`.
1770
+ * @param {MediaUrn} other
1771
+ * @returns {boolean}
1772
+ */
1773
+ meets(other) { return this._urn.meets(other._urn); }
1774
+
1775
+ /**
1776
+ * Whether a VALUE whose media this is satisfies the type `pattern`.
1777
+ *
1778
+ * conformsTo compares two types, and a type that does not mention a key
1779
+ * says nothing about it. A value that exists is complete: the tags it does
1780
+ * not have, it does not have. Use this where the receiver is the media of
1781
+ * actual data — a stream that arrived, an output that was produced.
1782
+ * @param {MediaUrn} pattern
1783
+ * @returns {boolean}
1784
+ */
1785
+ satisfies(pattern) { return this._urn.satisfies(pattern._urn); }
1786
+
1787
+ /**
1788
+ * Whether a value whose media this is COULD satisfy `pattern`.
1789
+ * @param {MediaUrn} pattern
1790
+ * @returns {boolean}
1791
+ */
1792
+ maySatisfy(pattern) { return this._urn.maySatisfy(pattern._urn); }
1793
+
1640
1794
  /** @returns {number} Specificity score (tag count based) */
1641
1795
  specificity() { return this._urn.specificity(); }
1642
1796
 
@@ -2742,13 +2896,14 @@ class Cap {
2742
2896
  }
2743
2897
 
2744
2898
  /**
2745
- * Check if this capability accepts a request string
2899
+ * Whether this capability, as a candidate, can serve the request
2900
+ * (CapUrn.isDispatchable).
2746
2901
  * @param {string} request - The request string
2747
- * @returns {boolean} Whether this capability accepts the request
2902
+ * @returns {boolean} Whether this capability serves the request
2748
2903
  */
2749
2904
  acceptsRequest(request) {
2750
2905
  const requestUrn = CapUrn.fromString(request);
2751
- return this.urn.accepts(requestUrn);
2906
+ return this.urn.isDispatchable(requestUrn);
2752
2907
  }
2753
2908
 
2754
2909
  /**
@@ -5365,16 +5520,17 @@ class CartridgeRepoServer {
5365
5520
  }
5366
5521
 
5367
5522
  /**
5368
- * Get cartridges that provide a specific cap.
5523
+ * Get every cartridge with a cap that can SERVE the requested URN.
5369
5524
  *
5370
- * The request URN is parsed via CapUrn.fromString. Each declared
5371
- * cartridge cap is parsed and matched with `conformsTo`: cap dispatch
5372
- * is the partial-order question "does the declared cap conform to
5373
- * (i.e. refine, equal, or be more specific than) the requested
5374
- * pattern?". Only `in` and `out` tags are semantically meaningful —
5375
- * no string comparison, no special role for the `op` tag. A malformed
5376
- * input URN throws; a malformed declared URN in the registry also
5377
- * throws (registry corruption is not a fallback condition).
5525
+ * The request URN is parsed via CapUrn.fromString; each declared
5526
+ * cartridge cap is parsed too and asked `isDispatchable` — "can this
5527
+ * declared candidate serve the request?" (it takes at least what is
5528
+ * sent, gives at least what is needed, has the tags asked for; a side the
5529
+ * request leaves open is not asked about). This is deliberately looser
5530
+ * than the equivalence used to resolve an alias to its exact cap: here
5531
+ * everything capable is enumerated. A malformed input URN throws; a
5532
+ * malformed declared URN in the registry also throws (registry corruption
5533
+ * is not a fallback condition).
5378
5534
  */
5379
5535
  getCartridgesByCap(capUrn) {
5380
5536
  const requested = CapUrn.fromString(capUrn);
@@ -5383,7 +5539,7 @@ class CartridgeRepoServer {
5383
5539
  (p.cap_groups || []).some(g =>
5384
5540
  (g.caps || []).some(c => {
5385
5541
  const declared = CapUrn.fromString(c.urn);
5386
- return declared.conformsTo(requested);
5542
+ return declared.isDispatchable(requested);
5387
5543
  })
5388
5544
  )
5389
5545
  );
@@ -6172,8 +6328,7 @@ class CartridgeJson {
6172
6328
  * @throws {CartridgeJsonError}
6173
6329
  */
6174
6330
  static readFromDir(versionDir, expectedSlug) {
6175
- const fs = require('fs');
6176
- const path = require('path');
6331
+ const { fs, path } = nodeModules('reading cartridge.json');
6177
6332
  const jsonPath = path.join(versionDir, 'cartridge.json');
6178
6333
 
6179
6334
  if (!fs.existsSync(jsonPath)) {
@@ -6255,7 +6410,7 @@ class CartridgeJson {
6255
6410
  * @returns {string}
6256
6411
  */
6257
6412
  resolveEntryPoint(versionDir) {
6258
- const path = require('path');
6413
+ const { path } = nodeModules('resolving an entry point');
6259
6414
  return path.join(versionDir, this.entry);
6260
6415
  }
6261
6416
  }
@@ -6272,9 +6427,7 @@ class CartridgeJson {
6272
6427
  * @returns {string} Lowercase hex SHA256.
6273
6428
  */
6274
6429
  function hashCartridgeDirectory(dir) {
6275
- const fs = require('fs');
6276
- const path = require('path');
6277
- const crypto = require('crypto');
6430
+ const { fs, path, crypto } = nodeModules('hashing a cartridge directory');
6278
6431
 
6279
6432
  const files = [];
6280
6433
  const collect = (current) => {
@@ -6577,7 +6730,7 @@ function encodeHelloFrame() {
6577
6730
  * @throws {Error} when the handshake/probe fails.
6578
6731
  */
6579
6732
  function probeCartridgeCapGroups(entryPath) {
6580
- const { spawn } = require('child_process');
6733
+ const { spawn } = nodeModules('probing a cartridge').childProcess;
6581
6734
  return new Promise((resolve, reject) => {
6582
6735
  let child;
6583
6736
  try {
@@ -6714,8 +6867,7 @@ function probeCartridgeCapGroups(entryPath) {
6714
6867
  * @returns {Promise<Array<DiscoveredCartridge>>}
6715
6868
  */
6716
6869
  async function discoverCartridges(cartridgesRoot, identity) {
6717
- const fs = require('fs');
6718
- const path = require('path');
6870
+ const { fs, path } = nodeModules('discovering cartridges');
6719
6871
  const discovered = [];
6720
6872
 
6721
6873
  let rootStat;
@@ -6755,8 +6907,7 @@ async function discoverCartridges(cartridgesRoot, identity) {
6755
6907
  * Mirrors capdag::cartridge_discovery::scan_channel_root.
6756
6908
  */
6757
6909
  async function scanChannelRoot(scanRoot, expectedSlug, identity, discovered) {
6758
- const fs = require('fs');
6759
- const path = require('path');
6910
+ const { fs, path } = nodeModules('discovering cartridges');
6760
6911
 
6761
6912
  let nameEntries;
6762
6913
  try {
@@ -7645,7 +7796,6 @@ class Machine {
7645
7796
  // ============================================================================
7646
7797
 
7647
7798
  // Load the Peggy-generated parser
7648
- const machineParser = require('./machine-parser.js');
7649
7799
 
7650
7800
  /**
7651
7801
  * Assign a media URN to a node, or check consistency if already assigned.
@@ -8026,22 +8176,13 @@ class MediaRegistryEntry {
8026
8176
  * implementations (capdag, capdag-js, capdag-py, capdag-go, capdag-objc)
8027
8177
  * use this same algorithm so a URN's key is identical across languages.
8028
8178
  *
8029
- * Runtime detection: `crypto.subtle` is available in browsers and
8030
- * modern Node (≥ 16, exposed via `globalThis.crypto`); CommonJS Node
8031
- * also has the synchronous `crypto` module. We prefer subtle for
8032
- * portability and fall back to the Node module when subtle is absent.
8179
+ * `crypto.subtle` is on `globalThis` in browsers and in every Node this
8180
+ * package supports.
8033
8181
  */
8034
8182
  async function sha256Hex(s) {
8035
8183
  const utf8 = new TextEncoder().encode(s);
8036
- if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.subtle) {
8037
- const buf = await globalThis.crypto.subtle.digest('SHA-256', utf8);
8038
- return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
8039
- }
8040
- // Node CommonJS fallback. require() may throw in strict ESM contexts;
8041
- // we let it propagate because every supported runtime has a crypto API.
8042
- // eslint-disable-next-line global-require
8043
- const nodeCrypto = require('crypto');
8044
- return nodeCrypto.createHash('sha256').update(utf8).digest('hex');
8184
+ const buf = await globalThis.crypto.subtle.digest('SHA-256', utf8);
8185
+ return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
8045
8186
  }
8046
8187
 
8047
8188
  /**
@@ -8445,12 +8586,7 @@ class Manifest {
8445
8586
  }
8446
8587
  }
8447
8588
 
8448
- // Unified configurable planner vocabulary (mirrors Rust plan_space.rs and the
8449
- // PlanMachines/DiscoverConvergentTargets proto surface).
8450
- const planner = require('./planner.js');
8451
-
8452
- // Export for CommonJS
8453
- module.exports = {
8589
+ export {
8454
8590
  // Registry trust vocabulary — one verdict per registry, shared by every
8455
8591
  // cartridge that claims provenance from it.
8456
8592
  RegistryVerdict,
@@ -8466,24 +8602,6 @@ module.exports = {
8466
8602
  registryVerdictRemedy,
8467
8603
  MANIFEST_SIG_FORMAT,
8468
8604
  RELEASE_KEY_CERT_FORMAT,
8469
- // Planner plan-space vocabulary
8470
- PlanStateError: planner.PlanStateError,
8471
- ConvergencePresence: planner.ConvergencePresence,
8472
- ConvergenceLocation: planner.ConvergenceLocation,
8473
- ConvergenceMechanism: planner.ConvergenceMechanism,
8474
- ConvergenceArity: planner.ConvergenceArity,
8475
- DivergencePresence: planner.DivergencePresence,
8476
- DivergenceLocation: planner.DivergenceLocation,
8477
- RankPolicy: planner.RankPolicy,
8478
- PlanMode: planner.PlanMode,
8479
- PlanRequest: planner.PlanRequest,
8480
- parsePlanApex: planner.parsePlanApex,
8481
- parsePlanProfile: planner.parsePlanProfile,
8482
- parsePlanCost: planner.parsePlanCost,
8483
- parsePlanCandidate: planner.parsePlanCandidate,
8484
- parsePlanCandidates: planner.parsePlanCandidates,
8485
- parseConvergentTarget: planner.parseConvergentTarget,
8486
- parseConvergentTargets: planner.parseConvergentTargets,
8487
8605
  ALIAS_TARGET_CAP,
8488
8606
  ALIAS_TARGET_MEDIA,
8489
8607
  tokenIsUrn,
@@ -8498,6 +8616,9 @@ module.exports = {
8498
8616
  CapEffect,
8499
8617
  CapUrnBuilder,
8500
8618
  CapMatcher,
8619
+ CapQuery,
8620
+ MatchGrade,
8621
+ gradeIsGuaranteed,
8501
8622
  CapUrnError,
8502
8623
  ErrorCodes,
8503
8624
  MediaUrn,
@@ -8541,7 +8662,7 @@ module.exports = {
8541
8662
  MEDIA_OBJECT,
8542
8663
  // List types
8543
8664
  MEDIA_LIST,
8544
- MEDIA_TEXTABLE_LIST: MEDIA_STRING_LIST,
8665
+ MEDIA_STRING_LIST as MEDIA_TEXTABLE_LIST,
8545
8666
  MEDIA_STRING_LIST,
8546
8667
  MEDIA_INTEGER_LIST,
8547
8668
  MEDIA_NUMBER_LIST,
@@ -8705,3 +8826,25 @@ module.exports = {
8705
8826
  MediaRegistryEntry,
8706
8827
  FabricRegistryClient,
8707
8828
  };
8829
+
8830
+ // Unified configurable planner vocabulary (mirrors Rust plan_space.rs and the
8831
+ // PlanMachines/DiscoverConvergentTargets proto surface).
8832
+ export {
8833
+ PlanStateError,
8834
+ ConvergencePresence,
8835
+ ConvergenceLocation,
8836
+ ConvergenceMechanism,
8837
+ ConvergenceArity,
8838
+ DivergencePresence,
8839
+ DivergenceLocation,
8840
+ RankPolicy,
8841
+ PlanMode,
8842
+ PlanRequest,
8843
+ parsePlanApex,
8844
+ parsePlanProfile,
8845
+ parsePlanCost,
8846
+ parsePlanCandidate,
8847
+ parsePlanCandidates,
8848
+ parseConvergentTarget,
8849
+ parseConvergentTargets,
8850
+ } from './planner.js';