capdag 1.361.331 → 1.368.19
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 +2 -2
- package/capdag.js +226 -45
- package/formal/index.d.ts +296 -6
- package/formal/index.js +887 -15
- package/formal/program.wasm +0 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ npm install capdag
|
|
|
17
17
|
|
|
18
18
|
The package is an ES module and needs Node.js 20 or newer. Dispatch,
|
|
19
19
|
acceptance, equivalence and specificity are decided by code generated from the
|
|
20
|
-
proved model ([formal foundations](https://
|
|
20
|
+
proved model ([formal foundations](https://machinefabric.com/capdag/docs/02-formal-foundations/)),
|
|
21
21
|
which runs as WebAssembly: it is instantiated when the package is first
|
|
22
22
|
imported, so importing it waits for that (top-level `await`), and a CommonJS
|
|
23
23
|
`require` cannot load it.
|
|
@@ -53,7 +53,7 @@ equivalence, conformance, dispatch, and ranking instead of string surgery.
|
|
|
53
53
|
- [`RULES.md`](RULES.md) records package-specific construction rules.
|
|
54
54
|
|
|
55
55
|
The normative semantics and terminology live in the
|
|
56
|
-
[CapDAG specification](https://
|
|
56
|
+
[CapDAG specification](https://machinefabric.com/capdag/docs/01-overview/). Source comments and
|
|
57
57
|
exports are the JavaScript API reference.
|
|
58
58
|
|
|
59
59
|
## Use it in a browser
|
package/capdag.js
CHANGED
|
@@ -646,38 +646,38 @@ class CapUrn {
|
|
|
646
646
|
}
|
|
647
647
|
|
|
648
648
|
/**
|
|
649
|
-
*
|
|
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.
|
|
650
654
|
*
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
*
|
|
654
|
-
* For other tags:
|
|
655
|
-
* - For each tag in the request: cap has same value, wildcard (*), or missing tag
|
|
656
|
-
* - For each tag in the cap: if request is missing that tag, that's fine (cap is more specific)
|
|
657
|
-
* 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".
|
|
658
658
|
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
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
|
|
661
665
|
*/
|
|
662
|
-
accepts(
|
|
663
|
-
if (!
|
|
666
|
+
accepts(cap) {
|
|
667
|
+
if (!cap) {
|
|
664
668
|
return true;
|
|
665
669
|
}
|
|
666
|
-
|
|
667
|
-
// opposite to isDispatchable's: this is the pattern relation, dispatch is
|
|
668
|
-
// the routing one.
|
|
669
|
-
return model.accepts(this.formal, request.formal);
|
|
670
|
+
return model.accepts(this.formal, cap.formal);
|
|
670
671
|
}
|
|
671
672
|
|
|
672
673
|
/**
|
|
673
|
-
*
|
|
674
|
-
* Equivalent to cap.accepts(this).
|
|
674
|
+
* Whether this cap fits `pattern`: `pattern.accepts(this)`.
|
|
675
675
|
*
|
|
676
|
-
* @param {CapUrn}
|
|
677
|
-
* @returns {boolean}
|
|
676
|
+
* @param {CapUrn} pattern
|
|
677
|
+
* @returns {boolean}
|
|
678
678
|
*/
|
|
679
|
-
conformsTo(
|
|
680
|
-
return
|
|
679
|
+
conformsTo(pattern) {
|
|
680
|
+
return pattern.accepts(this);
|
|
681
681
|
}
|
|
682
682
|
|
|
683
683
|
/**
|
|
@@ -861,21 +861,64 @@ class CapUrn {
|
|
|
861
861
|
}
|
|
862
862
|
|
|
863
863
|
/**
|
|
864
|
-
*
|
|
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.
|
|
865
881
|
*
|
|
866
882
|
* @param {CapUrn} request
|
|
867
883
|
* @returns {boolean}
|
|
868
884
|
*/
|
|
869
885
|
isDispatchable(request) {
|
|
870
|
-
// Decided by the proved model (CapDAG.Exec.dispatch): every axis is a type.
|
|
871
|
-
// The request's input refines the candidate's, the candidate's output
|
|
872
|
-
// refines the request's, the effect matches unless the request says
|
|
873
|
-
// ?effect, and the candidate's cap-tags refine the request's. `media:` on
|
|
874
|
-
// a request's input is a type — "may send anything" — so only a candidate
|
|
875
|
-
// that accepts anything serves it, which is what makes dispatch compose.
|
|
876
886
|
return model.dispatch(this.formal, request.formal);
|
|
877
887
|
}
|
|
878
888
|
|
|
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);
|
|
899
|
+
}
|
|
900
|
+
|
|
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);
|
|
909
|
+
}
|
|
910
|
+
|
|
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);
|
|
920
|
+
}
|
|
921
|
+
|
|
879
922
|
/**
|
|
880
923
|
* Infer the runtime output media for a concrete runtime input.
|
|
881
924
|
*
|
|
@@ -886,7 +929,9 @@ class CapUrn {
|
|
|
886
929
|
const declaredIn = this.inMediaUrn();
|
|
887
930
|
const declaredOut = this.outMediaUrn();
|
|
888
931
|
|
|
889
|
-
|
|
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)) {
|
|
890
935
|
throw new CapUrnError(
|
|
891
936
|
ErrorCodes.INVALID_EFFECT_APPLICATION,
|
|
892
937
|
`Runtime input '${runtimeInput}' does not conform to declared input '${declaredIn}'`
|
|
@@ -918,7 +963,7 @@ class CapUrn {
|
|
|
918
963
|
);
|
|
919
964
|
}
|
|
920
965
|
|
|
921
|
-
if (!runtimeOut.
|
|
966
|
+
if (!runtimeOut.satisfies(declaredOut)) {
|
|
922
967
|
throw new CapUrnError(
|
|
923
968
|
ErrorCodes.INVALID_EFFECT_APPLICATION,
|
|
924
969
|
`Inferred runtime output '${runtimeOut}' does not conform to declared output '${declaredOut}'`
|
|
@@ -962,7 +1007,7 @@ class CapUrn {
|
|
|
962
1007
|
case CapEffect.PATCH:
|
|
963
1008
|
return runtimeOutput.isEquivalent(inferred);
|
|
964
1009
|
case CapEffect.DECLARED:
|
|
965
|
-
return runtimeOutput.
|
|
1010
|
+
return runtimeOutput.satisfies(inferred);
|
|
966
1011
|
default:
|
|
967
1012
|
throw new CapUrnError(
|
|
968
1013
|
ErrorCodes.INVALID_EFFECT_APPLICATION,
|
|
@@ -1106,6 +1151,109 @@ class CapUrnBuilder {
|
|
|
1106
1151
|
}
|
|
1107
1152
|
}
|
|
1108
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
|
+
|
|
1109
1257
|
/**
|
|
1110
1258
|
* Cap Matcher utility class
|
|
1111
1259
|
*/
|
|
@@ -1615,6 +1763,34 @@ class MediaUrn {
|
|
|
1615
1763
|
*/
|
|
1616
1764
|
accepts(instance) { return this._urn.accepts(instance._urn); }
|
|
1617
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
|
+
|
|
1618
1794
|
/** @returns {number} Specificity score (tag count based) */
|
|
1619
1795
|
specificity() { return this._urn.specificity(); }
|
|
1620
1796
|
|
|
@@ -2720,13 +2896,14 @@ class Cap {
|
|
|
2720
2896
|
}
|
|
2721
2897
|
|
|
2722
2898
|
/**
|
|
2723
|
-
*
|
|
2899
|
+
* Whether this capability, as a candidate, can serve the request
|
|
2900
|
+
* (CapUrn.isDispatchable).
|
|
2724
2901
|
* @param {string} request - The request string
|
|
2725
|
-
* @returns {boolean} Whether this capability
|
|
2902
|
+
* @returns {boolean} Whether this capability serves the request
|
|
2726
2903
|
*/
|
|
2727
2904
|
acceptsRequest(request) {
|
|
2728
2905
|
const requestUrn = CapUrn.fromString(request);
|
|
2729
|
-
return this.urn.
|
|
2906
|
+
return this.urn.isDispatchable(requestUrn);
|
|
2730
2907
|
}
|
|
2731
2908
|
|
|
2732
2909
|
/**
|
|
@@ -5343,16 +5520,17 @@ class CartridgeRepoServer {
|
|
|
5343
5520
|
}
|
|
5344
5521
|
|
|
5345
5522
|
/**
|
|
5346
|
-
* Get
|
|
5523
|
+
* Get every cartridge with a cap that can SERVE the requested URN.
|
|
5347
5524
|
*
|
|
5348
|
-
* The request URN is parsed via CapUrn.fromString
|
|
5349
|
-
* cartridge cap is parsed and
|
|
5350
|
-
*
|
|
5351
|
-
*
|
|
5352
|
-
*
|
|
5353
|
-
*
|
|
5354
|
-
*
|
|
5355
|
-
*
|
|
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).
|
|
5356
5534
|
*/
|
|
5357
5535
|
getCartridgesByCap(capUrn) {
|
|
5358
5536
|
const requested = CapUrn.fromString(capUrn);
|
|
@@ -5361,7 +5539,7 @@ class CartridgeRepoServer {
|
|
|
5361
5539
|
(p.cap_groups || []).some(g =>
|
|
5362
5540
|
(g.caps || []).some(c => {
|
|
5363
5541
|
const declared = CapUrn.fromString(c.urn);
|
|
5364
|
-
return declared.
|
|
5542
|
+
return declared.isDispatchable(requested);
|
|
5365
5543
|
})
|
|
5366
5544
|
)
|
|
5367
5545
|
);
|
|
@@ -8438,6 +8616,9 @@ export {
|
|
|
8438
8616
|
CapEffect,
|
|
8439
8617
|
CapUrnBuilder,
|
|
8440
8618
|
CapMatcher,
|
|
8619
|
+
CapQuery,
|
|
8620
|
+
MatchGrade,
|
|
8621
|
+
gradeIsGuaranteed,
|
|
8441
8622
|
CapUrnError,
|
|
8442
8623
|
ErrorCodes,
|
|
8443
8624
|
MediaUrn,
|