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/LICENSE +23 -0
- package/README.md +20 -4
- package/RULES.md +1 -1
- package/build-browser.js +90 -187
- package/cap-fab-renderer.js +26 -50
- package/capdag.js +327 -184
- package/formal/index.d.ts +123 -0
- package/formal/index.js +316 -0
- package/formal/program.wasm +0 -0
- package/machine-parser.js +8 -5
- package/package.json +14 -5
- package/planner.js +1 -3
- package/capdag.test.js +0 -8003
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
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
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
|
-
*
|
|
601
|
-
*
|
|
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(
|
|
604
|
-
if (!
|
|
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
|
-
*
|
|
656
|
-
* Equivalent to cap.accepts(this).
|
|
674
|
+
* Whether this cap fits `pattern`: `pattern.accepts(this)`.
|
|
657
675
|
*
|
|
658
|
-
* @param {CapUrn}
|
|
659
|
-
* @returns {boolean}
|
|
676
|
+
* @param {CapUrn} pattern
|
|
677
|
+
* @returns {boolean}
|
|
660
678
|
*/
|
|
661
|
-
conformsTo(
|
|
662
|
-
return
|
|
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
|
-
|
|
693
|
-
|
|
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.
|
|
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.
|
|
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
|
-
*
|
|
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.
|
|
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
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
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
|
-
|
|
879
|
-
|
|
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
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
*
|
|
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
|
|
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.
|
|
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
|
|
5523
|
+
* Get every cartridge with a cap that can SERVE the requested URN.
|
|
5369
5524
|
*
|
|
5370
|
-
* The request URN is parsed via CapUrn.fromString
|
|
5371
|
-
* cartridge cap is parsed and
|
|
5372
|
-
*
|
|
5373
|
-
*
|
|
5374
|
-
*
|
|
5375
|
-
*
|
|
5376
|
-
*
|
|
5377
|
-
*
|
|
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.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 } =
|
|
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 =
|
|
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 =
|
|
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
|
-
*
|
|
8030
|
-
*
|
|
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
|
-
|
|
8037
|
-
|
|
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
|
-
|
|
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
|
|
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';
|