apcore-js 0.19.0 → 0.20.0

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.
Files changed (186) hide show
  1. package/README.md +110 -9
  2. package/dist/acl.d.ts +18 -0
  3. package/dist/acl.d.ts.map +1 -1
  4. package/dist/acl.js +54 -17
  5. package/dist/acl.js.map +1 -1
  6. package/dist/async-task.d.ts +70 -16
  7. package/dist/async-task.d.ts.map +1 -1
  8. package/dist/async-task.js +212 -72
  9. package/dist/async-task.js.map +1 -1
  10. package/dist/builtin-steps.d.ts +16 -5
  11. package/dist/builtin-steps.d.ts.map +1 -1
  12. package/dist/builtin-steps.js +45 -28
  13. package/dist/builtin-steps.js.map +1 -1
  14. package/dist/config.d.ts +38 -0
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +163 -33
  17. package/dist/config.js.map +1 -1
  18. package/dist/context.d.ts.map +1 -1
  19. package/dist/context.js +12 -1
  20. package/dist/context.js.map +1 -1
  21. package/dist/errors.d.ts +32 -10
  22. package/dist/errors.d.ts.map +1 -1
  23. package/dist/errors.js +55 -16
  24. package/dist/errors.js.map +1 -1
  25. package/dist/events/circuit-breaker.d.ts +45 -0
  26. package/dist/events/circuit-breaker.d.ts.map +1 -0
  27. package/dist/events/circuit-breaker.js +115 -0
  28. package/dist/events/circuit-breaker.js.map +1 -0
  29. package/dist/events/emitter.d.ts +22 -1
  30. package/dist/events/emitter.d.ts.map +1 -1
  31. package/dist/events/emitter.js +66 -2
  32. package/dist/events/emitter.js.map +1 -1
  33. package/dist/events/index.d.ts +4 -2
  34. package/dist/events/index.d.ts.map +1 -1
  35. package/dist/events/index.js +3 -2
  36. package/dist/events/index.js.map +1 -1
  37. package/dist/events/subscribers.d.ts +33 -1
  38. package/dist/events/subscribers.d.ts.map +1 -1
  39. package/dist/events/subscribers.js +124 -1
  40. package/dist/events/subscribers.js.map +1 -1
  41. package/dist/executor.d.ts +10 -2
  42. package/dist/executor.d.ts.map +1 -1
  43. package/dist/executor.js +134 -51
  44. package/dist/executor.js.map +1 -1
  45. package/dist/generated/version.d.ts +1 -1
  46. package/dist/generated/version.js +1 -1
  47. package/dist/index.d.ts +35 -25
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +23 -17
  50. package/dist/index.js.map +1 -1
  51. package/dist/middleware/base.d.ts +25 -3
  52. package/dist/middleware/base.d.ts.map +1 -1
  53. package/dist/middleware/base.js +24 -0
  54. package/dist/middleware/base.js.map +1 -1
  55. package/dist/middleware/circuit-breaker.d.ts +54 -0
  56. package/dist/middleware/circuit-breaker.d.ts.map +1 -0
  57. package/dist/middleware/circuit-breaker.js +168 -0
  58. package/dist/middleware/circuit-breaker.js.map +1 -0
  59. package/dist/middleware/context-namespace.d.ts +30 -0
  60. package/dist/middleware/context-namespace.d.ts.map +1 -0
  61. package/dist/middleware/context-namespace.js +38 -0
  62. package/dist/middleware/context-namespace.js.map +1 -0
  63. package/dist/middleware/index.d.ts +7 -1
  64. package/dist/middleware/index.d.ts.map +1 -1
  65. package/dist/middleware/index.js +4 -1
  66. package/dist/middleware/index.js.map +1 -1
  67. package/dist/middleware/manager.d.ts +11 -4
  68. package/dist/middleware/manager.d.ts.map +1 -1
  69. package/dist/middleware/manager.js +25 -9
  70. package/dist/middleware/manager.js.map +1 -1
  71. package/dist/middleware/platform-notify.d.ts +8 -4
  72. package/dist/middleware/platform-notify.d.ts.map +1 -1
  73. package/dist/middleware/platform-notify.js +11 -7
  74. package/dist/middleware/platform-notify.js.map +1 -1
  75. package/dist/middleware/tracing.d.ts +50 -0
  76. package/dist/middleware/tracing.d.ts.map +1 -0
  77. package/dist/middleware/tracing.js +89 -0
  78. package/dist/middleware/tracing.js.map +1 -0
  79. package/dist/observability/batch-span-processor.d.ts +48 -0
  80. package/dist/observability/batch-span-processor.d.ts.map +1 -0
  81. package/dist/observability/batch-span-processor.js +89 -0
  82. package/dist/observability/batch-span-processor.js.map +1 -0
  83. package/dist/observability/context-logger.d.ts +54 -1
  84. package/dist/observability/context-logger.d.ts.map +1 -1
  85. package/dist/observability/context-logger.js +270 -6
  86. package/dist/observability/context-logger.js.map +1 -1
  87. package/dist/observability/error-history.d.ts +36 -7
  88. package/dist/observability/error-history.d.ts.map +1 -1
  89. package/dist/observability/error-history.js +169 -50
  90. package/dist/observability/error-history.js.map +1 -1
  91. package/dist/observability/index.d.ts +16 -5
  92. package/dist/observability/index.d.ts.map +1 -1
  93. package/dist/observability/index.js +8 -3
  94. package/dist/observability/index.js.map +1 -1
  95. package/dist/observability/metrics.d.ts +14 -1
  96. package/dist/observability/metrics.d.ts.map +1 -1
  97. package/dist/observability/metrics.js +23 -2
  98. package/dist/observability/metrics.js.map +1 -1
  99. package/dist/observability/prometheus-exporter.d.ts +37 -0
  100. package/dist/observability/prometheus-exporter.d.ts.map +1 -0
  101. package/dist/observability/prometheus-exporter.js +135 -0
  102. package/dist/observability/prometheus-exporter.js.map +1 -0
  103. package/dist/observability/storage.d.ts +43 -0
  104. package/dist/observability/storage.d.ts.map +1 -0
  105. package/dist/observability/storage.js +58 -0
  106. package/dist/observability/storage.js.map +1 -0
  107. package/dist/observability/store.d.ts +29 -0
  108. package/dist/observability/store.d.ts.map +1 -0
  109. package/dist/observability/store.js +36 -0
  110. package/dist/observability/store.js.map +1 -0
  111. package/dist/observability/usage-exporter.d.ts +58 -0
  112. package/dist/observability/usage-exporter.d.ts.map +1 -0
  113. package/dist/observability/usage-exporter.js +86 -0
  114. package/dist/observability/usage-exporter.js.map +1 -0
  115. package/dist/observability/usage.d.ts +18 -1
  116. package/dist/observability/usage.d.ts.map +1 -1
  117. package/dist/observability/usage.js +25 -3
  118. package/dist/observability/usage.js.map +1 -1
  119. package/dist/pipeline-config.d.ts +11 -0
  120. package/dist/pipeline-config.d.ts.map +1 -1
  121. package/dist/pipeline-config.js +36 -10
  122. package/dist/pipeline-config.js.map +1 -1
  123. package/dist/pipeline.d.ts +123 -2
  124. package/dist/pipeline.d.ts.map +1 -1
  125. package/dist/pipeline.js +249 -50
  126. package/dist/pipeline.js.map +1 -1
  127. package/dist/registry/index.d.ts +2 -0
  128. package/dist/registry/index.d.ts.map +1 -1
  129. package/dist/registry/index.js +1 -0
  130. package/dist/registry/index.js.map +1 -1
  131. package/dist/registry/multi-class.d.ts +57 -0
  132. package/dist/registry/multi-class.d.ts.map +1 -0
  133. package/dist/registry/multi-class.js +120 -0
  134. package/dist/registry/multi-class.js.map +1 -0
  135. package/dist/registry/registry.d.ts +91 -3
  136. package/dist/registry/registry.d.ts.map +1 -1
  137. package/dist/registry/registry.js +181 -11
  138. package/dist/registry/registry.js.map +1 -1
  139. package/dist/schema/constants.d.ts +9 -0
  140. package/dist/schema/constants.d.ts.map +1 -0
  141. package/dist/schema/constants.js +9 -0
  142. package/dist/schema/constants.js.map +1 -0
  143. package/dist/schema/index.d.ts +1 -1
  144. package/dist/schema/index.d.ts.map +1 -1
  145. package/dist/schema/index.js +1 -1
  146. package/dist/schema/index.js.map +1 -1
  147. package/dist/schema/loader.d.ts +27 -3
  148. package/dist/schema/loader.d.ts.map +1 -1
  149. package/dist/schema/loader.js +137 -32
  150. package/dist/schema/loader.js.map +1 -1
  151. package/dist/schema/types.d.ts +4 -0
  152. package/dist/schema/types.d.ts.map +1 -1
  153. package/dist/schema/types.js.map +1 -1
  154. package/dist/schema/validator.d.ts +9 -0
  155. package/dist/schema/validator.d.ts.map +1 -1
  156. package/dist/schema/validator.js +153 -4
  157. package/dist/schema/validator.js.map +1 -1
  158. package/dist/sys-modules/audit.d.ts +50 -0
  159. package/dist/sys-modules/audit.d.ts.map +1 -0
  160. package/dist/sys-modules/audit.js +89 -0
  161. package/dist/sys-modules/audit.js.map +1 -0
  162. package/dist/sys-modules/control.d.ts +32 -4
  163. package/dist/sys-modules/control.d.ts.map +1 -1
  164. package/dist/sys-modules/control.js +196 -25
  165. package/dist/sys-modules/control.js.map +1 -1
  166. package/dist/sys-modules/index.d.ts +7 -2
  167. package/dist/sys-modules/index.d.ts.map +1 -1
  168. package/dist/sys-modules/index.js +3 -1
  169. package/dist/sys-modules/index.js.map +1 -1
  170. package/dist/sys-modules/overrides.d.ts +58 -0
  171. package/dist/sys-modules/overrides.d.ts.map +1 -0
  172. package/dist/sys-modules/overrides.js +106 -0
  173. package/dist/sys-modules/overrides.js.map +1 -0
  174. package/dist/sys-modules/registration.d.ts +17 -12
  175. package/dist/sys-modules/registration.d.ts.map +1 -1
  176. package/dist/sys-modules/registration.js +115 -23
  177. package/dist/sys-modules/registration.js.map +1 -1
  178. package/dist/sys-modules/toggle.d.ts +7 -2
  179. package/dist/sys-modules/toggle.d.ts.map +1 -1
  180. package/dist/sys-modules/toggle.js +61 -5
  181. package/dist/sys-modules/toggle.js.map +1 -1
  182. package/dist/trace-context.d.ts +47 -9
  183. package/dist/trace-context.d.ts.map +1 -1
  184. package/dist/trace-context.js +139 -16
  185. package/dist/trace-context.js.map +1 -1
  186. package/package.json +1 -1
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Multi-class module discovery: opt-in scanner, snake_case ID derivation, conflict detection.
3
+ *
4
+ * Implements PROTOCOL_SPEC §2.1.1 (Multi-Module Discovery).
5
+ */
6
+ import { IdTooLongError, InvalidSegmentError, ModuleIdConflictError } from '../errors.js';
7
+ const SEGMENT_RE = /^[a-z][a-z0-9_]*$/;
8
+ const CANONICAL_ID_RE = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/;
9
+ const MAX_MODULE_ID_LEN = 192;
10
+ /**
11
+ * Convert a class name to a snake_case segment per PROTOCOL_SPEC §2.1.1.
12
+ *
13
+ * Algorithm:
14
+ * 1. Insert boundary at ALLCAPS→CamelCase transitions (HTTPSender → HTTP_Sender).
15
+ * 2. Insert boundary at lowercase/digit→uppercase transitions (MathOps → Math_Ops).
16
+ * 3. Replace every non-alphanumeric character with `_`.
17
+ * 4. Lowercase.
18
+ * 5. Collapse consecutive `_` to a single `_`.
19
+ * 6. Strip leading and trailing `_`.
20
+ */
21
+ export function classNameToSegment(className) {
22
+ let s = className.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2');
23
+ s = s.replace(/([a-z\d])([A-Z])/g, '$1_$2');
24
+ s = s.replace(/[^a-zA-Z0-9]/g, '_');
25
+ s = s.toLowerCase();
26
+ s = s.replace(/_+/g, '_');
27
+ return s.replace(/^_+|_+$/g, '');
28
+ }
29
+ function computeBaseId(filePath, extensionsRoot) {
30
+ const normalized = filePath.replace(/\\/g, '/');
31
+ const parts = normalized.split('/');
32
+ const rootIdx = parts.findIndex(p => p === extensionsRoot);
33
+ const relParts = rootIdx === -1 ? [parts[parts.length - 1]] : parts.slice(rootIdx + 1);
34
+ // Strip file extension from the last segment
35
+ relParts[relParts.length - 1] = relParts[relParts.length - 1].replace(/\.[^.]+$/, '');
36
+ return relParts.join('.');
37
+ }
38
+ /**
39
+ * Discover module IDs for classes in a single file under multi-class mode.
40
+ *
41
+ * Implements the Registry.discover_multi_class contract from PROTOCOL_SPEC §2.1.1.
42
+ *
43
+ * When multiClassEnabled is false (the default — multi-class mode is opt-in),
44
+ * only the first qualifying class is used and the bare base_id is returned.
45
+ *
46
+ * When multiClassEnabled is true and exactly one class qualifies:
47
+ * - If the class segment matches the file's last path segment (class named after
48
+ * the file), the bare base_id is returned to preserve existing module IDs.
49
+ * - Otherwise the class segment is appended: base_id.class_segment.
50
+ *
51
+ * @internal Prefer `Registry.discoverMultiClass` (D-15) for the canonical
52
+ * cross-language API surface; this free function is retained for backwards
53
+ * compatibility and direct use by the scanner.
54
+ *
55
+ * @throws ModuleIdConflictError — two classes produce the same class_segment
56
+ * @throws InvalidSegmentError — a segment does not match ^[a-z][a-z0-9_]*$
57
+ * @throws IdTooLongError — a derived module_id exceeds 192 characters
58
+ */
59
+ export function discoverMultiClass(filePath, classes, extensionsRoot = 'extensions', multiClassEnabled = false) {
60
+ const qualifying = classes.filter(c => c.implementsModule);
61
+ if (qualifying.length === 0)
62
+ return [];
63
+ const baseId = computeBaseId(filePath, extensionsRoot);
64
+ if (!multiClassEnabled) {
65
+ return [{ moduleId: baseId, className: qualifying[0].name }];
66
+ }
67
+ if (qualifying.length === 1) {
68
+ const segment = classNameToSegment(qualifying[0].name);
69
+ const lastBaseSegment = baseId.split('.').pop();
70
+ // Identity guarantee: when the class segment matches the file's last segment
71
+ // (class named after the file), preserve the bare base_id so existing IDs
72
+ // are not broken when multi-class mode is enabled.
73
+ if (segment === lastBaseSegment) {
74
+ return [{ moduleId: baseId, className: qualifying[0].name }];
75
+ }
76
+ // Class has a distinct name from the file — append the class segment.
77
+ if (!SEGMENT_RE.test(segment))
78
+ throw new InvalidSegmentError(filePath, qualifying[0].name, segment);
79
+ const moduleId = `${baseId}.${segment}`;
80
+ if (!CANONICAL_ID_RE.test(moduleId))
81
+ throw new InvalidSegmentError(filePath, qualifying[0].name, segment);
82
+ if (moduleId.length > MAX_MODULE_ID_LEN)
83
+ throw new IdTooLongError(filePath, moduleId);
84
+ return [{ moduleId, className: qualifying[0].name }];
85
+ }
86
+ // Multi-class path: derive IDs, detect conflicts, validate
87
+ const seenSegments = new Map(); // segment → className
88
+ const results = [];
89
+ for (const cls of qualifying) {
90
+ const segment = classNameToSegment(cls.name);
91
+ if (!SEGMENT_RE.test(segment)) {
92
+ throw new InvalidSegmentError(filePath, cls.name, segment);
93
+ }
94
+ if (seenSegments.has(segment)) {
95
+ console.warn(`[apcore:multi-class] MODULE_ID_CONFLICT in '${filePath}': ` +
96
+ `classes '${seenSegments.get(segment)}' and '${cls.name}' both produce segment '${segment}'`);
97
+ throw new ModuleIdConflictError(filePath, [seenSegments.get(segment), cls.name], segment);
98
+ }
99
+ seenSegments.set(segment, cls.name);
100
+ const moduleId = `${baseId}.${segment}`;
101
+ if (!CANONICAL_ID_RE.test(moduleId)) {
102
+ throw new InvalidSegmentError(filePath, cls.name, segment);
103
+ }
104
+ if (moduleId.length > MAX_MODULE_ID_LEN) {
105
+ throw new IdTooLongError(filePath, moduleId);
106
+ }
107
+ results.push({ moduleId, className: cls.name });
108
+ }
109
+ return results;
110
+ }
111
+ /**
112
+ * Internal alias of {@link discoverMultiClass}. Use {@link Registry.discoverMultiClass}
113
+ * instead — this name is preserved so the scanner and other internal callers
114
+ * have a stable reference that signals the surface is not part of the public
115
+ * API.
116
+ *
117
+ * @internal
118
+ */
119
+ export const _discoverMultiClass = discoverMultiClass;
120
+ //# sourceMappingURL=multi-class.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-class.js","sourceRoot":"","sources":["../../src/registry/multi-class.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1F,MAAM,UAAU,GAAG,mBAAmB,CAAC;AACvC,MAAM,eAAe,GAAG,uCAAuC,CAAC;AAChE,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAY9B;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,IAAI,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;IAC5D,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IACpB,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC1B,OAAO,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AACnC,CAAC;AAED,SAAS,aAAa,CAAC,QAAgB,EAAE,cAAsB;IAC7D,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,cAAc,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;IACvF,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACtF,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAgB,EAChB,OAAmC,EACnC,iBAAyB,YAAY,EACrC,oBAA6B,KAAK;IAElC,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;IAC3D,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEvC,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IAEvD,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,eAAe,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC;QACjD,6EAA6E;QAC7E,0EAA0E;QAC1E,mDAAmD;QACnD,IAAI,OAAO,KAAK,eAAe,EAAE,CAAC;YAChC,OAAO,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD,sEAAsE;QACtE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,MAAM,IAAI,mBAAmB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACpG,MAAM,QAAQ,GAAG,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC;QACxC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,MAAM,IAAI,mBAAmB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC1G,IAAI,QAAQ,CAAC,MAAM,GAAG,iBAAiB;YAAE,MAAM,IAAI,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACtF,OAAO,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,2DAA2D;IAC3D,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC,CAAC,sBAAsB;IACtE,MAAM,OAAO,GAAsB,EAAE,CAAC;IAEtC,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAE7C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,mBAAmB,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC7D,CAAC;QAED,IAAI,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,CACV,+CAA+C,QAAQ,KAAK;gBAC5D,YAAY,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,GAAG,CAAC,IAAI,2BAA2B,OAAO,GAAG,CAC7F,CAAC;YACF,MAAM,IAAI,qBAAqB,CAAC,QAAQ,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAE,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;QAC7F,CAAC;QACD,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QAEpC,MAAM,QAAQ,GAAG,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC;QAExC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,mBAAmB,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC7D,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;YACxC,MAAM,IAAI,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC/C,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,kBAAkB,CAAC"}
@@ -77,12 +77,48 @@ export declare class Registry {
77
77
  setValidator(validator: ModuleValidator): void;
78
78
  discover(): Promise<number>;
79
79
  private _discoverCustom;
80
+ /**
81
+ * Default discovery pipeline (D-32 — 8 canonical stages, mirroring
82
+ * apcore-rust `default_discoverer.rs`):
83
+ *
84
+ * 1. _ensureIdMap — lazy-load the optional id_map.json
85
+ * 2. _scanRoots — walk extension roots
86
+ * 3. _applyIdMapOverrides — rewrite canonical IDs from the map
87
+ * 4. _loadAllMetadata — load each module's `module.yaml`
88
+ * 5. _resolveAllEntryPoints — import the JS/TS entry point
89
+ * 6. _validateAll — run module/custom validators
90
+ * 7. _filterIdConflicts — batch-drop conflicting / invalid IDs
91
+ * 8. _resolveLoadOrder + _registerInOrder
92
+ * — topological sort then register.
93
+ */
80
94
  private _discoverDefault;
81
95
  private _scanRoots;
82
96
  private _applyIdMapOverrides;
83
97
  private _loadAllMetadata;
84
98
  private _resolveAllEntryPoints;
85
99
  private _validateAll;
100
+ /**
101
+ * Stage 7 (D-32) — batch-drop modules with invalid or conflicting IDs.
102
+ *
103
+ * Mirrors apcore-python `_filter_id_conflicts` and apcore-rust
104
+ * `default_discoverer::filter_id_conflicts`. Two failure modes drop a
105
+ * module here (warn + skip) rather than aborting the whole batch:
106
+ * - PROTOCOL_SPEC §2.7 ID validation (empty / pattern / length /
107
+ * reserved-word first segment), and
108
+ * - Algorithm A03 conflict detection (duplicate against an existing
109
+ * registration, lowercase collision, reserved-word collision).
110
+ *
111
+ * Soft-severity conflicts (e.g. case-insensitive match against an
112
+ * already-registered ID at `warn` level) are NOT dropped here — the
113
+ * warning is logged and the module flows through to registration so
114
+ * existing behaviour is preserved. Only `error`-severity conflicts and
115
+ * invalid IDs are filtered out.
116
+ *
117
+ * `rawMetadata` is accepted for cross-language signature parity with
118
+ * the Rust/Python helpers (which may inspect metadata for additional
119
+ * checks); the TS implementation reads only the module ID.
120
+ */
121
+ private _filterIdConflicts;
86
122
  private _resolveLoadOrder;
87
123
  private _registerInOrder;
88
124
  /**
@@ -91,12 +127,31 @@ export declare class Registry {
91
127
  * Validation order (PROTOCOL_SPEC §2.7, aligned with apcore-python and
92
128
  * apcore-rust): empty → pattern → length → reserved (per-segment) →
93
129
  * duplicate.
130
+ *
131
+ * The optional `version` and `metadata` parameters mirror apcore-python's
132
+ * `Registry.register(module_id, module, version=None, metadata=None)` for
133
+ * cross-language signature parity (sync finding A-001). Multi-version
134
+ * coexistence is not yet implemented in this SDK — when supplied, both
135
+ * fields are merged into the module's metadata so callers can read them
136
+ * back via `getDefinition()` and `list({tags})`. See PROTOCOL_SPEC §5.4.
94
137
  */
95
- register(moduleId: string, module: unknown): void;
138
+ register(moduleId: string, module: unknown, version?: string | null, metadata?: Record<string, unknown> | null): void;
96
139
  /** Inner registration — no validator, no ID validation. Used by discover() paths that run their own checks. */
97
140
  private _registerImpl;
98
141
  unregister(moduleId: string): boolean;
99
- get(moduleId: string): unknown | null;
142
+ /**
143
+ * Look up a registered module by ID.
144
+ *
145
+ * @param moduleId - Module identifier (must be non-empty).
146
+ * @param _versionHint - Optional semver range for multi-version coexistence
147
+ * (PROTOCOL_SPEC §5.4). This SDK currently exposes a single-version
148
+ * registry, so the hint is accepted for cross-language API parity with
149
+ * apcore-python (sync finding A-002) but does NOT participate in
150
+ * resolution: the latest registered module for `moduleId` is returned
151
+ * regardless of the hint. When multi-version registration lands, this
152
+ * parameter will gate semver-range matching.
153
+ */
154
+ get(moduleId: string, _versionHint?: string | null): unknown | null;
100
155
  has(moduleId: string): boolean;
101
156
  list(options?: {
102
157
  tags?: string[];
@@ -105,12 +160,35 @@ export declare class Registry {
105
160
  iter(): IterableIterator<[string, unknown]>;
106
161
  get count(): number;
107
162
  get moduleIds(): string[];
108
- getDefinition(moduleId: string): ModuleDescriptor | null;
163
+ getDefinition(moduleId: string, _versionHint?: string | null): ModuleDescriptor | null;
109
164
  describe(moduleId: string): string;
110
165
  on(event: string, callback: EventCallback): void;
111
166
  off(event: string, callback: EventCallback): boolean;
112
167
  reload(): Promise<number>;
113
168
  private _triggerEvent;
169
+ /**
170
+ * Watch the configured extension roots for filesystem changes and
171
+ * unregister any module whose source file is modified or deleted.
172
+ *
173
+ * **Cross-language divergence (sync finding A-D-104):** unlike apcore-python
174
+ * (which re-imports the file via `importlib.reload`) and apcore-rust (which
175
+ * triggers full rediscovery), the TypeScript SDK is **event-only**. On a
176
+ * file change the registry:
177
+ * 1. unregisters the previously-loaded module (calling its `onUnload`),
178
+ * 2. emits a `'file_changed'` event with `{ filePath }` payload.
179
+ *
180
+ * Consumers are expected to subscribe and re-register the module
181
+ * themselves (e.g. by calling `registry.discover()` or registering a fresh
182
+ * import). ES module specifiers are immutable in Node — there is no
183
+ * portable "reload from disk" primitive — so a transparent dynamic
184
+ * `import()` would silently return the cached old module on every
185
+ * invocation. A workaround using a cache-busting query (`?v=Date.now()`)
186
+ * leaks the old module each reload and breaks browser bundlers, so it is
187
+ * intentionally **not** offered here.
188
+ *
189
+ * If your application needs Python-style hot-reload semantics, listen for
190
+ * `'file_changed'` and re-discover or re-import explicitly.
191
+ */
114
192
  watch(): Promise<void>;
115
193
  unwatch(): void;
116
194
  private _handleFileChange;
@@ -137,6 +215,16 @@ export declare class Registry {
137
215
  */
138
216
  exportSchema(moduleId: string, strict?: boolean): Record<string, unknown> | null;
139
217
  clearCache(): void;
218
+ /**
219
+ * Discover module IDs for the classes in a single file under multi-class
220
+ * mode (PROTOCOL_SPEC §2.1.1).
221
+ *
222
+ * D-15: cross-language alignment with Python `Registry.discover_multi_class`
223
+ * and the Rust trait method. Internally delegates to the free function
224
+ * {@link discoverMultiClass} (re-exported as `_discoverMultiClass` for
225
+ * scanner internals), so behaviour is identical.
226
+ */
227
+ discoverMultiClass(filePath: string, classes: readonly import('./multi-class.js').ClassDescriptor[], extensionsRoot?: string, multiClassEnabled?: boolean): import('./multi-class.js').MultiClassEntry[];
140
228
  /**
141
229
  * Number of in-flight executions per module.
142
230
  */
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry/registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry/registry.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ import { detectIdConflicts } from './conflicts.js';
7
7
  import { resolveDependencies } from './dependencies.js';
8
8
  import { resolveEntryPoint } from './entry-point.js';
9
9
  import { mergeModuleMetadata, parseDependencies } from './metadata.js';
10
+ import { _discoverMultiClass } from './multi-class.js';
10
11
  import { getSchema } from './schema-export.js';
11
12
  import { toStrictSchema } from '../schema/strict.js';
12
13
  import { deepCopy } from '../utils/index.js';
@@ -197,6 +198,17 @@ export class Registry {
197
198
  continue;
198
199
  }
199
200
  }
201
+ // PROTOCOL_SPEC §2.7 ID validation — sync finding A-D-102.
202
+ // Mirrors apcore-python `Registry._discover_custom` which calls
203
+ // `_validate_module_id` before registration. Invalid IDs are skipped
204
+ // with a warning rather than aborting the whole discover run.
205
+ try {
206
+ validateModuleId(moduleId, false);
207
+ }
208
+ catch (e) {
209
+ console.warn(`[apcore:registry] Skipping custom-discovered module with invalid ID '${moduleId}': ${e.message}`);
210
+ continue;
211
+ }
200
212
  try {
201
213
  this._registerImpl(moduleId, mod);
202
214
  count++;
@@ -207,6 +219,20 @@ export class Registry {
207
219
  }
208
220
  return count;
209
221
  }
222
+ /**
223
+ * Default discovery pipeline (D-32 — 8 canonical stages, mirroring
224
+ * apcore-rust `default_discoverer.rs`):
225
+ *
226
+ * 1. _ensureIdMap — lazy-load the optional id_map.json
227
+ * 2. _scanRoots — walk extension roots
228
+ * 3. _applyIdMapOverrides — rewrite canonical IDs from the map
229
+ * 4. _loadAllMetadata — load each module's `module.yaml`
230
+ * 5. _resolveAllEntryPoints — import the JS/TS entry point
231
+ * 6. _validateAll — run module/custom validators
232
+ * 7. _filterIdConflicts — batch-drop conflicting / invalid IDs
233
+ * 8. _resolveLoadOrder + _registerInOrder
234
+ * — topological sort then register.
235
+ */
210
236
  async _discoverDefault() {
211
237
  await this._ensureIdMap();
212
238
  const discovered = await this._scanRoots();
@@ -214,8 +240,9 @@ export class Registry {
214
240
  const rawMetadata = await this._loadAllMetadata(discovered);
215
241
  const resolvedModules = await this._resolveAllEntryPoints(discovered, rawMetadata);
216
242
  const validModules = await this._validateAll(resolvedModules);
217
- const loadOrder = this._resolveLoadOrder(validModules, rawMetadata);
218
- return this._registerInOrder(loadOrder, validModules, rawMetadata);
243
+ const filteredModules = this._filterIdConflicts(validModules, rawMetadata);
244
+ const loadOrder = this._resolveLoadOrder(filteredModules, rawMetadata);
245
+ return this._registerInOrder(loadOrder, filteredModules, rawMetadata);
219
246
  }
220
247
  async _scanRoots() {
221
248
  let maxDepth = 8;
@@ -295,6 +322,56 @@ export class Registry {
295
322
  }
296
323
  return validModules;
297
324
  }
325
+ /**
326
+ * Stage 7 (D-32) — batch-drop modules with invalid or conflicting IDs.
327
+ *
328
+ * Mirrors apcore-python `_filter_id_conflicts` and apcore-rust
329
+ * `default_discoverer::filter_id_conflicts`. Two failure modes drop a
330
+ * module here (warn + skip) rather than aborting the whole batch:
331
+ * - PROTOCOL_SPEC §2.7 ID validation (empty / pattern / length /
332
+ * reserved-word first segment), and
333
+ * - Algorithm A03 conflict detection (duplicate against an existing
334
+ * registration, lowercase collision, reserved-word collision).
335
+ *
336
+ * Soft-severity conflicts (e.g. case-insensitive match against an
337
+ * already-registered ID at `warn` level) are NOT dropped here — the
338
+ * warning is logged and the module flows through to registration so
339
+ * existing behaviour is preserved. Only `error`-severity conflicts and
340
+ * invalid IDs are filtered out.
341
+ *
342
+ * `rawMetadata` is accepted for cross-language signature parity with
343
+ * the Rust/Python helpers (which may inspect metadata for additional
344
+ * checks); the TS implementation reads only the module ID.
345
+ */
346
+ _filterIdConflicts(validModules, _rawMetadata) {
347
+ const filtered = new Map();
348
+ // Track within-batch IDs (case-insensitive) so two newly-discovered
349
+ // modules whose IDs collide on lowercase don't both slip through.
350
+ const batchLowercase = new Map(this._lowercaseMap);
351
+ const batchIds = new Set(this._modules.keys());
352
+ for (const [modId, mod] of validModules.entries()) {
353
+ try {
354
+ validateModuleId(modId, false);
355
+ }
356
+ catch (e) {
357
+ console.warn(`[apcore:registry] Skipping discovered module with invalid ID '${modId}': ${e.message}`);
358
+ continue;
359
+ }
360
+ const conflict = detectIdConflicts(modId, batchIds, RESERVED_WORDS, batchLowercase);
361
+ if (conflict !== null) {
362
+ if (conflict.severity === 'error') {
363
+ console.warn(`[apcore:registry] Skipping discovered module '${modId}' due to ID conflict: ${conflict.message}`);
364
+ continue;
365
+ }
366
+ // Soft severity — log but keep the module.
367
+ console.warn(`[apcore:registry] ID conflict: ${conflict.message}`);
368
+ }
369
+ filtered.set(modId, mod);
370
+ batchIds.add(modId);
371
+ batchLowercase.set(modId.toLowerCase(), modId);
372
+ }
373
+ return filtered;
374
+ }
298
375
  _resolveLoadOrder(validModules, rawMetadata) {
299
376
  const modulesWithDeps = [];
300
377
  const moduleVersions = new Map();
@@ -326,9 +403,13 @@ export class Registry {
326
403
  return resolveDependencies(modulesWithDeps, knownIds, moduleVersions);
327
404
  }
328
405
  _registerInOrder(loadOrder, validModules, rawMetadata) {
406
+ // Stage 8 (D-32). Conflict detection / ID validation already happened in
407
+ // `_filterIdConflicts` (stage 7), so this loop is purely a register pass.
329
408
  let count = 0;
330
409
  for (const modId of loadOrder) {
331
410
  const mod = validModules.get(modId);
411
+ if (mod === undefined)
412
+ continue;
332
413
  const modObj = mod;
333
414
  const mergedMeta = mergeModuleMetadata(modObj, rawMetadata.get(modId) ?? {});
334
415
  this._modules.set(modId, mod);
@@ -357,8 +438,15 @@ export class Registry {
357
438
  * Validation order (PROTOCOL_SPEC §2.7, aligned with apcore-python and
358
439
  * apcore-rust): empty → pattern → length → reserved (per-segment) →
359
440
  * duplicate.
441
+ *
442
+ * The optional `version` and `metadata` parameters mirror apcore-python's
443
+ * `Registry.register(module_id, module, version=None, metadata=None)` for
444
+ * cross-language signature parity (sync finding A-001). Multi-version
445
+ * coexistence is not yet implemented in this SDK — when supplied, both
446
+ * fields are merged into the module's metadata so callers can read them
447
+ * back via `getDefinition()` and `list({tags})`. See PROTOCOL_SPEC §5.4.
360
448
  */
361
- register(moduleId, module) {
449
+ register(moduleId, module, version, metadata) {
362
450
  validateModuleId(moduleId, false);
363
451
  if (this._customValidator !== null) {
364
452
  const result = this._customValidator.validate(module);
@@ -369,10 +457,14 @@ export class Registry {
369
457
  throw new InvalidInputError(`Custom validator rejected module '${moduleId}': ${result.join('; ')}`);
370
458
  }
371
459
  }
372
- this._registerImpl(moduleId, module);
460
+ const overrides = { ...(metadata ?? {}) };
461
+ if (version !== undefined && version !== null) {
462
+ overrides['version'] = version;
463
+ }
464
+ this._registerImpl(moduleId, module, overrides);
373
465
  }
374
466
  /** Inner registration — no validator, no ID validation. Used by discover() paths that run their own checks. */
375
- _registerImpl(moduleId, module) {
467
+ _registerImpl(moduleId, module, metadataOverrides = {}) {
376
468
  // Algorithm A03: detect ID conflicts (exact duplicate, reserved word, case collision)
377
469
  const conflict = detectIdConflicts(moduleId, new Set(this._modules.keys()), RESERVED_WORDS, this._lowercaseMap);
378
470
  if (conflict !== null) {
@@ -385,9 +477,10 @@ export class Registry {
385
477
  }
386
478
  this._modules.set(moduleId, module);
387
479
  this._lowercaseMap.set(moduleId.toLowerCase(), moduleId);
388
- // Populate metadata from the module object
480
+ // Populate metadata from the module object, layering any explicit overrides
481
+ // (e.g. the `version` / `metadata` args passed to `register()`) on top.
389
482
  const modObj = module;
390
- this._moduleMeta.set(moduleId, mergeModuleMetadata(modObj, {}));
483
+ this._moduleMeta.set(moduleId, mergeModuleMetadata(modObj, metadataOverrides));
391
484
  // Call onLoad if available
392
485
  if (typeof modObj['onLoad'] === 'function') {
393
486
  try {
@@ -423,7 +516,19 @@ export class Registry {
423
516
  this._triggerEvent(REGISTRY_EVENTS.UNREGISTER, moduleId, module);
424
517
  return true;
425
518
  }
426
- get(moduleId) {
519
+ /**
520
+ * Look up a registered module by ID.
521
+ *
522
+ * @param moduleId - Module identifier (must be non-empty).
523
+ * @param _versionHint - Optional semver range for multi-version coexistence
524
+ * (PROTOCOL_SPEC §5.4). This SDK currently exposes a single-version
525
+ * registry, so the hint is accepted for cross-language API parity with
526
+ * apcore-python (sync finding A-002) but does NOT participate in
527
+ * resolution: the latest registered module for `moduleId` is returned
528
+ * regardless of the hint. When multi-version registration lands, this
529
+ * parameter will gate semver-range matching.
530
+ */
531
+ get(moduleId, _versionHint) {
427
532
  if (moduleId === '') {
428
533
  throw new ModuleNotFoundError('');
429
534
  }
@@ -465,7 +570,18 @@ export class Registry {
465
570
  get moduleIds() {
466
571
  return [...this._modules.keys()].sort();
467
572
  }
468
- getDefinition(moduleId) {
573
+ getDefinition(moduleId, _versionHint) {
574
+ // `_versionHint` accepted for cross-language API parity with apcore-python
575
+ // (sync finding A-002 / §5.4). Ignored under the single-version registry;
576
+ // see `get()` for the rationale.
577
+ //
578
+ // D10-011: spec registry-system.md:382 says any error that `get(module_id)`
579
+ // raises is propagated. The empty-string guard mirrors `get()` (line 669)
580
+ // so callers using getDefinition see the same ModuleNotFoundError as
581
+ // get(), matching apcore-python where getDefinition routes through get().
582
+ if (moduleId === '') {
583
+ throw new ModuleNotFoundError('');
584
+ }
469
585
  const module = this._modules.get(moduleId);
470
586
  if (module == null)
471
587
  return null;
@@ -564,6 +680,29 @@ export class Registry {
564
680
  }
565
681
  }
566
682
  }
683
+ /**
684
+ * Watch the configured extension roots for filesystem changes and
685
+ * unregister any module whose source file is modified or deleted.
686
+ *
687
+ * **Cross-language divergence (sync finding A-D-104):** unlike apcore-python
688
+ * (which re-imports the file via `importlib.reload`) and apcore-rust (which
689
+ * triggers full rediscovery), the TypeScript SDK is **event-only**. On a
690
+ * file change the registry:
691
+ * 1. unregisters the previously-loaded module (calling its `onUnload`),
692
+ * 2. emits a `'file_changed'` event with `{ filePath }` payload.
693
+ *
694
+ * Consumers are expected to subscribe and re-register the module
695
+ * themselves (e.g. by calling `registry.discover()` or registering a fresh
696
+ * import). ES module specifiers are immutable in Node — there is no
697
+ * portable "reload from disk" primitive — so a transparent dynamic
698
+ * `import()` would silently return the cached old module on every
699
+ * invocation. A workaround using a cache-busting query (`?v=Date.now()`)
700
+ * leaks the old module each reload and breaks browser bundlers, so it is
701
+ * intentionally **not** offered here.
702
+ *
703
+ * If your application needs Python-style hot-reload semantics, listen for
704
+ * `'file_changed'` and re-discover or re-import explicitly.
705
+ */
567
706
  async watch() {
568
707
  if (this._watchers && this._watchers.length > 0) {
569
708
  return; // Already watching
@@ -685,12 +824,30 @@ export class Registry {
685
824
  */
686
825
  registerInternal(moduleId, module) {
687
826
  validateModuleId(moduleId, true);
688
- if (this._modules.has(moduleId)) {
689
- throw new InvalidInputError(`Module ID '${moduleId}' is already registered`);
827
+ // D11-007: route duplicate detection through detectIdConflicts (with an
828
+ // empty reserved-words set so the bypass for system.* prefixes is
829
+ // preserved). This restores the case-collision branch present in
830
+ // apcore-python (registry.py:1674) and apcore-rust (registry.rs:727).
831
+ // The lowercase-only EBNF in validateModuleId makes case collisions
832
+ // unreachable today, but the contract surface stays aligned across SDKs.
833
+ const conflict = detectIdConflicts(moduleId, new Set(this._modules.keys()), new Set(), this._lowercaseMap);
834
+ if (conflict !== null) {
835
+ if (conflict.severity === 'error') {
836
+ throw new InvalidInputError(conflict.message);
837
+ }
838
+ else {
839
+ console.warn(`[apcore:registry] ID conflict: ${conflict.message}`);
840
+ }
690
841
  }
691
842
  this._modules.set(moduleId, module);
692
843
  const modObj = module;
693
844
  this._moduleMeta.set(moduleId, mergeModuleMetadata(modObj, {}));
845
+ // Mirror apcore-python register_internal and apcore-rust register_core:
846
+ // every registration site (including sys/internal) populates the lowercase
847
+ // index. The lowercase-only EBNF pattern enforced by validateModuleId makes
848
+ // case collisions unreachable today, but keeping _lowercaseMap consistent
849
+ // with _modules preserves the invariant for downstream conflict detection.
850
+ this._lowercaseMap.set(moduleId.toLowerCase(), moduleId);
694
851
  if (typeof modObj['onLoad'] === 'function') {
695
852
  try {
696
853
  modObj['onLoad']();
@@ -698,6 +855,7 @@ export class Registry {
698
855
  catch (e) {
699
856
  this._modules.delete(moduleId);
700
857
  this._moduleMeta.delete(moduleId);
858
+ this._lowercaseMap.delete(moduleId.toLowerCase());
701
859
  throw e;
702
860
  }
703
861
  }
@@ -728,6 +886,18 @@ export class Registry {
728
886
  this._schemaCache.clear();
729
887
  }
730
888
  // ── Safe Hot-Reload (F09 / Algorithm A21) ───────────────────────
889
+ /**
890
+ * Discover module IDs for the classes in a single file under multi-class
891
+ * mode (PROTOCOL_SPEC §2.1.1).
892
+ *
893
+ * D-15: cross-language alignment with Python `Registry.discover_multi_class`
894
+ * and the Rust trait method. Internally delegates to the free function
895
+ * {@link discoverMultiClass} (re-exported as `_discoverMultiClass` for
896
+ * scanner internals), so behaviour is identical.
897
+ */
898
+ discoverMultiClass(filePath, classes, extensionsRoot = 'extensions', multiClassEnabled = false) {
899
+ return _discoverMultiClass(filePath, classes, extensionsRoot, multiClassEnabled);
900
+ }
731
901
  /**
732
902
  * Number of in-flight executions per module.
733
903
  */