capdag 1.248.10 → 1.255.27
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 +37 -104
- package/cap-fab-renderer.js +1 -2
- package/capdag.js +26 -10
- package/capdag.test.js +35 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,131 +1,64 @@
|
|
|
1
|
-
#
|
|
1
|
+
# CapDAG for JavaScript
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This public package is CapDAG's JavaScript planning and notation mirror. Use it
|
|
4
|
+
in browsers or Node.js for Tagged, Media, and Cap URNs, capability definitions,
|
|
5
|
+
dispatch, Machine Notation, planning, and graph rendering.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
JavaScript intentionally does not implement the cartridge runtime, host, relay,
|
|
8
|
+
or Bifaci process surface. That boundary is part of the package design, not an
|
|
9
|
+
unreported parity gap. Rust remains the behavioral reference for shared
|
|
10
|
+
features.
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
- **Media URN Validation** - Validates direction spec values are valid Media URNs
|
|
9
|
-
- **Special Pattern Values** - `*` (must-have-any), `?` (unspecified), `!` (must-not-have)
|
|
10
|
-
- **Graded Specificity** - Exact values score higher than wildcards
|
|
11
|
-
- **Cross-Language Compatible** - Identical behavior to Rust, Go, and Objective-C implementations
|
|
12
|
-
- **Production Ready** - No fallbacks, fails hard on invalid input
|
|
13
|
-
|
|
14
|
-
## Installation
|
|
12
|
+
## Install the package
|
|
15
13
|
|
|
16
14
|
```bash
|
|
17
15
|
npm install capdag
|
|
18
16
|
```
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
Node.js 14 or newer is required by the package manifest.
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
const { CapUrn, CapUrnBuilder, CapMatcher } = require('capdag');
|
|
20
|
+
## Parse and build Cap URNs
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
console.log(cap.toString());
|
|
22
|
+
```javascript
|
|
23
|
+
const { CapUrn, CapUrnBuilder } = require("capdag");
|
|
28
24
|
|
|
29
|
-
|
|
25
|
+
const parsed = CapUrn.fromString(
|
|
26
|
+
'cap:disbind;in="media:ext=pdf";out="media:enc=utf-8;page"'
|
|
27
|
+
);
|
|
30
28
|
const built = new CapUrnBuilder()
|
|
31
|
-
.inSpec(
|
|
32
|
-
.outSpec(
|
|
33
|
-
.
|
|
34
|
-
.tag('target', 'thumbnail')
|
|
35
|
-
.build();
|
|
36
|
-
|
|
37
|
-
// Matching
|
|
38
|
-
const request = CapUrn.fromString('cap:in="media:binary";extract;out="media:object"');
|
|
39
|
-
console.log(cap.accepts(request)); // true
|
|
40
|
-
|
|
41
|
-
// Find best match by specificity
|
|
42
|
-
const caps = [
|
|
43
|
-
CapUrn.fromString('cap:in=*;extract;out=*'),
|
|
44
|
-
CapUrn.fromString('cap:in="media:binary";extract;out="media:object"'),
|
|
45
|
-
CapUrn.fromString('cap:ext=pdf;in="media:binary";extract;out="media:object"')
|
|
46
|
-
];
|
|
47
|
-
const best = CapMatcher.findBestMatch(caps, request);
|
|
48
|
-
console.log(best.toString()); // Most specific match
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## API Reference
|
|
52
|
-
|
|
53
|
-
### CapUrn Class
|
|
54
|
-
|
|
55
|
-
#### Static Methods
|
|
56
|
-
- `CapUrn.fromString(s)` - Parse Cap URN from string
|
|
57
|
-
- Throws `CapUrnError` on invalid format or missing direction specifiers
|
|
58
|
-
|
|
59
|
-
#### Instance Methods
|
|
60
|
-
- `toString()` - Get canonical string representation
|
|
61
|
-
- `getTag(key)` - Get tag value (case-insensitive)
|
|
62
|
-
- `getInSpec()` - Get input media type
|
|
63
|
-
- `getOutSpec()` - Get output media type
|
|
64
|
-
- `hasTag(key, value)` - Check if tag exists with value
|
|
65
|
-
- `withTag(key, value)` - Add/update tag (returns new instance)
|
|
66
|
-
- `withoutTag(key)` - Remove tag (returns new instance)
|
|
67
|
-
- `accepts(request)` - Check if this cap (as pattern) accepts a request
|
|
68
|
-
- `specificity()` - Get specificity score for matching
|
|
69
|
-
- `isMoreSpecificThan(other)` - Compare specificity
|
|
70
|
-
- `equals(other)` - Check equality
|
|
71
|
-
|
|
72
|
-
### CapUrnBuilder Class
|
|
73
|
-
|
|
74
|
-
Fluent builder for constructing Cap URNs:
|
|
75
|
-
|
|
76
|
-
```javascript
|
|
77
|
-
const cap = new CapUrnBuilder()
|
|
78
|
-
.inSpec('media:binary')
|
|
79
|
-
.outSpec('media:object')
|
|
80
|
-
.tag('op', 'extract')
|
|
81
|
-
.tag('target', 'metadata')
|
|
29
|
+
.inSpec("media:ext=pdf")
|
|
30
|
+
.outSpec("media:enc=utf-8;page")
|
|
31
|
+
.marker("disbind")
|
|
82
32
|
.build();
|
|
83
|
-
```
|
|
84
33
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Utility for matching sets of caps:
|
|
88
|
-
|
|
89
|
-
- `CapMatcher.findBestMatch(caps, request)` - Find most specific match
|
|
90
|
-
- `CapMatcher.findAllMatches(caps, request)` - Find all matches (sorted by specificity)
|
|
91
|
-
|
|
92
|
-
### Error Handling
|
|
93
|
-
|
|
94
|
-
```javascript
|
|
95
|
-
const { CapUrnError, ErrorCodes } = require('capdag');
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const cap = CapUrn.fromString('cap:extract;in=media:;out=media:'); // Missing in/out
|
|
99
|
-
} catch (error) {
|
|
100
|
-
if (error instanceof CapUrnError) {
|
|
101
|
-
console.log(`Error code: ${error.code}`); // MISSING_IN_SPEC
|
|
102
|
-
}
|
|
103
|
-
}
|
|
34
|
+
console.log(parsed.toString() === built.toString());
|
|
104
35
|
```
|
|
105
36
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
- `ErrorCodes.MISSING_OUT_SPEC` - Missing required `out` tag
|
|
109
|
-
- `ErrorCodes.INVALID_MEDIA_URN` - Invalid Media URN in direction spec
|
|
37
|
+
Treat URNs as opaque parsed values. Use the package's predicates for
|
|
38
|
+
equivalence, conformance, dispatch, and ranking instead of string surgery.
|
|
110
39
|
|
|
111
|
-
|
|
40
|
+
## Find the relevant API
|
|
112
41
|
|
|
113
|
-
|
|
42
|
+
- `capdag.js` provides URNs, definitions, dispatch, and matching.
|
|
43
|
+
- `machine-parser.js` is generated from `machine.pegjs`.
|
|
44
|
+
- `planner.js` provides planning and Machine Notation structures.
|
|
45
|
+
- `cap-fab-renderer.js` and the browser build support graph presentation.
|
|
46
|
+
- [`RULES.md`](RULES.md) records package-specific construction rules.
|
|
114
47
|
|
|
115
|
-
|
|
116
|
-
|
|
48
|
+
The normative semantics and terminology live in the
|
|
49
|
+
[CapDAG specification](https://github.com/machinefabric/capdag/blob/main/docs/01-overview.md). Source comments and
|
|
50
|
+
exports are the JavaScript API reference.
|
|
117
51
|
|
|
118
|
-
##
|
|
52
|
+
## Build and verify changes
|
|
119
53
|
|
|
120
54
|
```bash
|
|
55
|
+
npm run build:parser
|
|
121
56
|
npm test
|
|
122
57
|
```
|
|
123
58
|
|
|
124
|
-
|
|
59
|
+
`npm test` runs the parser build first. Shared behavior changes require the
|
|
60
|
+
applicable reference test with the same substantive number and assertions.
|
|
125
61
|
|
|
126
|
-
|
|
127
|
-
- [Rust reference implementation](https://github.com/machinefabric/capdag)
|
|
128
|
-
- [Go implementation](https://github.com/machinefabric/capdag-go)
|
|
129
|
-
- [Objective-C implementation](https://github.com/machinefabric/capdag-objc)
|
|
62
|
+
## License
|
|
130
63
|
|
|
131
|
-
|
|
64
|
+
MIT
|
package/cap-fab-renderer.js
CHANGED
|
@@ -2234,8 +2234,7 @@ function buildRunGraphData(data) {
|
|
|
2234
2234
|
};
|
|
2235
2235
|
}
|
|
2236
2236
|
|
|
2237
|
-
// Expanding. The plan
|
|
2238
|
-
// `machfab-mac/.scrap/working_graph/.../RunGraphViewer.html`:
|
|
2237
|
+
// Expanding. The plan uses the renderer's established fan-out layout:
|
|
2239
2238
|
//
|
|
2240
2239
|
// pre-foreach backbone ... → [anchor]
|
|
2241
2240
|
// │
|
package/capdag.js
CHANGED
|
@@ -5603,9 +5603,12 @@ function slugForSync(registryUrl) {
|
|
|
5603
5603
|
}
|
|
5604
5604
|
|
|
5605
5605
|
/**
|
|
5606
|
-
* How a cartridge was installed.
|
|
5607
|
-
*
|
|
5608
|
-
*
|
|
5606
|
+
* How a cartridge was installed. Exactly ONE value is semantic: BUNDLE
|
|
5607
|
+
* selects the bundled-cartridge integrity path at discovery. Every other
|
|
5608
|
+
* value is provenance telemetry, and the vocabulary GROWS as installers do —
|
|
5609
|
+
* so an unrecognized spelling is preserved verbatim on the record and
|
|
5610
|
+
* round-trips, never failing the cartridge.json parse. Mirrors Rust
|
|
5611
|
+
* CartridgeInstallSource; the on-disk JSON uses snake_case values.
|
|
5609
5612
|
*/
|
|
5610
5613
|
const CartridgeInstallSource = Object.freeze({
|
|
5611
5614
|
REGISTRY: 'registry',
|
|
@@ -5744,10 +5747,12 @@ class CartridgeJson {
|
|
|
5744
5747
|
}
|
|
5745
5748
|
let installedFrom = null;
|
|
5746
5749
|
if (obj.installed_from !== undefined && obj.installed_from !== null) {
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
throw new CartridgeJsonError(CartridgeJsonErrorKind.INVALID_JSON, `cartridge.json unknown installed_from: ${obj.installed_from}`);
|
|
5750
|
+
if (typeof obj.installed_from !== 'string') {
|
|
5751
|
+
throw new CartridgeJsonError(CartridgeJsonErrorKind.INVALID_JSON, 'cartridge.json installed_from must be a string');
|
|
5750
5752
|
}
|
|
5753
|
+
// An unknown spelling is preserved VERBATIM: the provenance vocabulary
|
|
5754
|
+
// grows as installers do, and a telemetry hint must never fail the
|
|
5755
|
+
// parse and take the cartridge down with it. Only BUNDLE is semantic.
|
|
5751
5756
|
installedFrom = obj.installed_from;
|
|
5752
5757
|
}
|
|
5753
5758
|
return new CartridgeJson({
|
|
@@ -6150,7 +6155,6 @@ function encodeHelloFrame() {
|
|
|
6150
6155
|
['max_reorder_buffer', BIFACI_DEFAULT_MAX_REORDER_BUFFER],
|
|
6151
6156
|
['initial_credit', BIFACI_DEFAULT_INITIAL_CREDIT],
|
|
6152
6157
|
['version', BIFACI_PROTOCOL_VERSION],
|
|
6153
|
-
['handler_capacity', 0],
|
|
6154
6158
|
],
|
|
6155
6159
|
};
|
|
6156
6160
|
const frame = {
|
|
@@ -6265,9 +6269,21 @@ function probeCartridgeCapGroups(entryPath) {
|
|
|
6265
6269
|
finish(new Error(`cartridge ${entryPath} HELLO failed: meta.version ${metaVersion} does not match required version ${BIFACI_PROTOCOL_VERSION}`));
|
|
6266
6270
|
return;
|
|
6267
6271
|
}
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6272
|
+
// The concurrency-pool state map is MANDATORY on a cartridge HELLO
|
|
6273
|
+
// (bifaci pools, protocol v4): JSON bytes under meta key "pools". A
|
|
6274
|
+
// missing or malformed map is a protocol violation, never a default.
|
|
6275
|
+
const poolsVal = meta.get('pools');
|
|
6276
|
+
if (!poolsVal || poolsVal.__bytes === undefined) {
|
|
6277
|
+
finish(new Error(`cartridge ${entryPath} HELLO failed: missing required concurrency-pool state map`));
|
|
6278
|
+
return;
|
|
6279
|
+
}
|
|
6280
|
+
try {
|
|
6281
|
+
const poolStates = JSON.parse(Buffer.from(poolsVal.__bytes).toString('utf8'));
|
|
6282
|
+
if (poolStates === null || typeof poolStates !== 'object' || Array.isArray(poolStates)) {
|
|
6283
|
+
throw new Error('pool-state map must be a JSON object');
|
|
6284
|
+
}
|
|
6285
|
+
} catch (e) {
|
|
6286
|
+
finish(new Error(`cartridge ${entryPath} HELLO failed: malformed pool-state map: ${e.message}`));
|
|
6271
6287
|
return;
|
|
6272
6288
|
}
|
|
6273
6289
|
const manifestVal = meta.get('manifest');
|
package/capdag.test.js
CHANGED
|
@@ -6965,6 +6965,40 @@ function assertThrowsPlanState(fn, message) {
|
|
|
6965
6965
|
|
|
6966
6966
|
// TEST1450: a defaults-only request is AUTO with every knob at its default,
|
|
6967
6967
|
// and the proto JSON carries the documented wire shape.
|
|
6968
|
+
// TEST1514: the provenance vocabulary grows with installers. A workspace
|
|
6969
|
+
// build install parses to its named value; a spelling this build does not
|
|
6970
|
+
// know parses, is preserved VERBATIM, round-trips, and is not BUNDLE (the
|
|
6971
|
+
// one semantic value) — an unknown telemetry hint can never fail the
|
|
6972
|
+
// cartridge.json parse and take the cartridge down with it.
|
|
6973
|
+
function test1514_installSourceVocabularyTolerance() {
|
|
6974
|
+
const base = {
|
|
6975
|
+
name: 'candlecartridge',
|
|
6976
|
+
version: '1.227.800',
|
|
6977
|
+
channel: 'nightly',
|
|
6978
|
+
registry_url: 'https://cartridges-staging.machinefabric.com/v1/manifest',
|
|
6979
|
+
entry: 'candlecartridge',
|
|
6980
|
+
installed_at: '2026-08-14T22:26:59Z',
|
|
6981
|
+
fabric_manifest_version: 4,
|
|
6982
|
+
};
|
|
6983
|
+
// A drifted installer's spelling is tolerated but NOT blessed: the
|
|
6984
|
+
// protocol's vocabulary is registry/dev/bundle/app_installer, and a
|
|
6985
|
+
// writer's mistake never becomes a named value.
|
|
6986
|
+
const built = CartridgeJson.fromObject({ ...base, installed_from: 'build' });
|
|
6987
|
+
assert(built.installedFrom === 'build',
|
|
6988
|
+
`build is preserved verbatim like any unknown, got ${built.installedFrom}`);
|
|
6989
|
+
assert(!Object.values(CartridgeInstallSource).includes('build'),
|
|
6990
|
+
'build is not part of the protocol vocabulary');
|
|
6991
|
+
|
|
6992
|
+
const unknown = CartridgeJson.fromObject({ ...base, installed_from: 'quantum_courier' });
|
|
6993
|
+
assert(unknown.installedFrom === 'quantum_courier',
|
|
6994
|
+
`unknown spellings are preserved verbatim, got ${unknown.installedFrom}`);
|
|
6995
|
+
assert(unknown.installedFrom !== CartridgeInstallSource.BUNDLE,
|
|
6996
|
+
'an unknown spelling is never the semantic bundle value');
|
|
6997
|
+
const rewritten = unknown.toObject ? unknown.toObject() : JSON.parse(JSON.stringify(unknown));
|
|
6998
|
+
assert((rewritten.installed_from || rewritten.installedFrom) === 'quantum_courier',
|
|
6999
|
+
'unknown spellings round-trip');
|
|
7000
|
+
}
|
|
7001
|
+
|
|
6968
7002
|
function test1450_planRequestDefaults() {
|
|
6969
7003
|
const request = new plannerNS.PlanRequest({
|
|
6970
7004
|
sources: [{ mediaUrn: 'media:ext=pdf', isSequence: false }],
|
|
@@ -7589,6 +7623,7 @@ async function runTests() {
|
|
|
7589
7623
|
|
|
7590
7624
|
// planner.js: unified plan-space vocabulary
|
|
7591
7625
|
console.log('--- planner.js ---');
|
|
7626
|
+
runTest('TEST1514: install_source_vocabulary_tolerance', test1514_installSourceVocabularyTolerance);
|
|
7592
7627
|
runTest('TEST1450: plan_request_defaults', test1450_planRequestDefaults);
|
|
7593
7628
|
runTest('TEST1451: plan_request_knob_validation', test1451_planRequestKnobValidation);
|
|
7594
7629
|
runTest('TEST1452: knob_proto_round_trip', test1452_knobProtoRoundTrip);
|
package/package.json
CHANGED