capdag 1.205.540 → 1.218.588

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.
@@ -2087,7 +2087,7 @@ function buildRunGraphData(data) {
2087
2087
  // labels the anchor→entry edge on the first body.
2088
2088
  //
2089
2089
  // Without such a preceding sequence cap, the source itself is
2090
- // already a list (e.g. `media:pdf;list` source_media_urn) and the
2090
+ // already a list (e.g. `media:ext=pdf;list` source_media_urn) and the
2091
2091
  // ForEach iterates it directly.
2092
2092
  let seqProducerStepIdx = -1;
2093
2093
  let seqProducerStep = null;
package/capdag.js CHANGED
@@ -6069,6 +6069,14 @@ function probeCartridgeCapGroups(entryPath) {
6069
6069
  child.on('error', (e) => finish(new Error(`cartridge ${entryPath} spawn error: ${e.message}`)));
6070
6070
  child.on('close', () => finish(new Error(`cartridge ${entryPath} HELLO failed: connection closed before receiving HELLO`)));
6071
6071
 
6072
+ // A cartridge that exits or closes its stdin before reading our HELLO
6073
+ // surfaces the broken pipe as an ASYNCHRONOUS 'error' event on the pipe
6074
+ // (EPIPE), not as a synchronous throw from write(). Without a listener,
6075
+ // Node treats that as an unhandled 'error' and aborts the whole host
6076
+ // process. Catch it here and route it through finish() so a single
6077
+ // misbehaving cartridge fails its own probe instead of killing discovery.
6078
+ child.stdin.on('error', (e) => finish(new Error(`cartridge ${entryPath} HELLO failed: ${e.message}`)));
6079
+
6072
6080
  timer = setTimeout(() => finish(new Error(`cartridge ${entryPath} HELLO failed: timeout`)), 5000);
6073
6081
 
6074
6082
  // Send our HELLO first (host side).
@@ -6120,6 +6128,9 @@ function probeCartridgeCapGroups(entryPath) {
6120
6128
  buf = Buffer.concat([buf, chunk]);
6121
6129
  tryParse();
6122
6130
  });
6131
+ // Guard the read side too: an error on the child's stdout (e.g. the pipe
6132
+ // breaking mid-read) must not become an unhandled 'error' event.
6133
+ child.stdout.on('error', (e) => finish(new Error(`cartridge ${entryPath} HELLO failed: ${e.message}`)));
6123
6134
  });
6124
6135
  }
6125
6136
 
@@ -7540,8 +7551,139 @@ class FabricRegistryClient {
7540
7551
  }
7541
7552
  }
7542
7553
 
7554
+ // ===========================================================================
7555
+ // Aliases (the DNS-analogue translation layer over URNs)
7556
+ // ===========================================================================
7557
+ //
7558
+ // An alias is a first-class fabric definition: a short, contiguous,
7559
+ // case-insensitive name that resolves to exactly one cap or media URN. This
7560
+ // lightweight JS mirror provides the pure alias primitives (name rules,
7561
+ // URN-vs-alias detection, target classification), the StoredAlias wire shape,
7562
+ // and Manifest (de)serialization of the aliases map — the pieces the web UIs
7563
+ // and LSP need to recognize and present aliases. Full registry-side alias
7564
+ // resolution lives in the heavier mirrors (Rust/Go/Py/ObjC).
7565
+
7566
+ const ALIAS_TARGET_CAP = 'cap';
7567
+ const ALIAS_TARGET_MEDIA = 'media';
7568
+ const ALIAS_NAME_RE = /^[a-z0-9._-]+$/;
7569
+
7570
+ /**
7571
+ * A contiguous token "looks like a URN" iff it contains ':'. Every tagged URN
7572
+ * has the shape prefix:..., so the presence of ':' is the unambiguous
7573
+ * discriminator between a URN and an alias name.
7574
+ */
7575
+ function tokenIsUrn(token) {
7576
+ return typeof token === 'string' && token.includes(':');
7577
+ }
7578
+
7579
+ /** Complement of tokenIsUrn: a colon-free token is an alias candidate. */
7580
+ function isAliasToken(token) {
7581
+ return !tokenIsUrn(token);
7582
+ }
7583
+
7584
+ /**
7585
+ * Normalize and validate an alias name. Lowercases the input, then requires it
7586
+ * non-empty, free of ':' (so it can never look like a tagged URN), free of
7587
+ * whitespace, and matching [a-z0-9._-]+. Returns the canonical lowercased
7588
+ * name or throws — there is no lenient path.
7589
+ */
7590
+ function normalizeAliasName(name) {
7591
+ if (typeof name !== 'string' || name.length === 0) {
7592
+ throw new Error('alias name is empty');
7593
+ }
7594
+ if (name.includes(':')) {
7595
+ throw new Error(`alias name '${name}' contains ':' — aliases must never look like a tagged URN`);
7596
+ }
7597
+ if (/\s/.test(name)) {
7598
+ throw new Error(`alias name '${name}' contains whitespace`);
7599
+ }
7600
+ const lowered = name.toLowerCase();
7601
+ if (!ALIAS_NAME_RE.test(lowered)) {
7602
+ throw new Error(`alias name '${name}' contains invalid characters; allowed: lowercase letters, digits, '.', '_', '-'`);
7603
+ }
7604
+ return lowered;
7605
+ }
7606
+
7607
+ /**
7608
+ * Classify an alias target URN by prefix. Returns 'cap', 'media', or null
7609
+ * (not a cap/media URN).
7610
+ */
7611
+ function classifyAliasTarget(target) {
7612
+ try {
7613
+ CapUrn.fromString(target);
7614
+ return ALIAS_TARGET_CAP;
7615
+ } catch (_) { /* not a cap URN */ }
7616
+ try {
7617
+ MediaUrn.fromString(target);
7618
+ return ALIAS_TARGET_MEDIA;
7619
+ } catch (_) { /* not a media URN */ }
7620
+ return null;
7621
+ }
7622
+
7623
+ /**
7624
+ * The published wire/cache shape of a single fabric alias. Mirrors
7625
+ * fabric/alias.schema.json: { name, target, version }.
7626
+ */
7627
+ class StoredAlias {
7628
+ constructor(name, target, version) {
7629
+ this.name = name;
7630
+ this.target = target;
7631
+ this.version = version;
7632
+ }
7633
+ toJSON() {
7634
+ return { name: this.name, target: this.target, version: this.version };
7635
+ }
7636
+ static fromJSON(obj) {
7637
+ return new StoredAlias(obj.name, obj.target, obj.version);
7638
+ }
7639
+ }
7640
+
7641
+ /**
7642
+ * A versioned registry snapshot. Mirrors fabric/manifest.schema.json:
7643
+ * { version, previous, caps, media, aliases } where each map is
7644
+ * name/urn -> defver.
7645
+ */
7646
+ class Manifest {
7647
+ constructor(version, previous, caps = {}, media = {}, aliases = {}) {
7648
+ this.version = version;
7649
+ this.previous = previous;
7650
+ this.caps = caps;
7651
+ this.media = media;
7652
+ this.aliases = aliases;
7653
+ }
7654
+ static empty(version) {
7655
+ return new Manifest(version, Math.max(0, version - 1), {}, {}, {});
7656
+ }
7657
+ toJSON() {
7658
+ return {
7659
+ version: this.version,
7660
+ previous: this.previous,
7661
+ caps: this.caps,
7662
+ media: this.media,
7663
+ aliases: this.aliases,
7664
+ };
7665
+ }
7666
+ static fromJSON(obj) {
7667
+ return new Manifest(
7668
+ obj.version,
7669
+ obj.previous,
7670
+ obj.caps || {},
7671
+ obj.media || {},
7672
+ obj.aliases || {},
7673
+ );
7674
+ }
7675
+ }
7676
+
7543
7677
  // Export for CommonJS
7544
7678
  module.exports = {
7679
+ ALIAS_TARGET_CAP,
7680
+ ALIAS_TARGET_MEDIA,
7681
+ tokenIsUrn,
7682
+ isAliasToken,
7683
+ normalizeAliasName,
7684
+ classifyAliasTarget,
7685
+ StoredAlias,
7686
+ Manifest,
7545
7687
  CapUrn,
7546
7688
  CapKind,
7547
7689
  CapEffect,