dna-sdk 0.1.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 (240) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -0
  3. package/dist/adapters/embedding/onnx.d.ts +32 -0
  4. package/dist/adapters/embedding/onnx.js +51 -0
  5. package/dist/adapters/filesystem/cache.d.ts +16 -0
  6. package/dist/adapters/filesystem/cache.js +113 -0
  7. package/dist/adapters/filesystem/index.d.ts +2 -0
  8. package/dist/adapters/filesystem/index.js +2 -0
  9. package/dist/adapters/filesystem/source.d.ts +82 -0
  10. package/dist/adapters/filesystem/source.js +357 -0
  11. package/dist/adapters/postgres/index.d.ts +2 -0
  12. package/dist/adapters/postgres/index.js +2 -0
  13. package/dist/adapters/postgres/migrations.d.ts +14 -0
  14. package/dist/adapters/postgres/migrations.js +88 -0
  15. package/dist/adapters/postgres/source.d.ts +172 -0
  16. package/dist/adapters/postgres/source.js +569 -0
  17. package/dist/adapters/resolvers/github.d.ts +12 -0
  18. package/dist/adapters/resolvers/github.js +47 -0
  19. package/dist/adapters/resolvers/http.d.ts +22 -0
  20. package/dist/adapters/resolvers/http.js +145 -0
  21. package/dist/adapters/resolvers/index.d.ts +3 -0
  22. package/dist/adapters/resolvers/index.js +3 -0
  23. package/dist/adapters/resolvers/local.d.ts +24 -0
  24. package/dist/adapters/resolvers/local.js +132 -0
  25. package/dist/adapters/search/driver.d.ts +45 -0
  26. package/dist/adapters/search/driver.js +138 -0
  27. package/dist/adapters/search/migrations.d.ts +26 -0
  28. package/dist/adapters/search/migrations.js +58 -0
  29. package/dist/adapters/search/rrf.d.ts +23 -0
  30. package/dist/adapters/search/rrf.js +42 -0
  31. package/dist/adapters/search/sqlite-vec.d.ts +66 -0
  32. package/dist/adapters/search/sqlite-vec.js +294 -0
  33. package/dist/bootstrap.d.ts +45 -0
  34. package/dist/bootstrap.js +134 -0
  35. package/dist/extensions/agentskills/DOCS.md +18 -0
  36. package/dist/extensions/agentskills.d.ts +28 -0
  37. package/dist/extensions/agentskills.js +252 -0
  38. package/dist/extensions/agentsmd/DOCS.md +18 -0
  39. package/dist/extensions/agentsmd.d.ts +22 -0
  40. package/dist/extensions/agentsmd.js +170 -0
  41. package/dist/extensions/audit/kinds/audit-log.kind.yaml +148 -0
  42. package/dist/extensions/audit.d.ts +25 -0
  43. package/dist/extensions/audit.js +62 -0
  44. package/dist/extensions/automation/kinds/automation.kind.yaml +296 -0
  45. package/dist/extensions/automation/query.d.ts +49 -0
  46. package/dist/extensions/automation/query.js +77 -0
  47. package/dist/extensions/automation/write-guards.d.ts +59 -0
  48. package/dist/extensions/automation/write-guards.js +153 -0
  49. package/dist/extensions/automation.d.ts +41 -0
  50. package/dist/extensions/automation.js +54 -0
  51. package/dist/extensions/collab.d.ts +16 -0
  52. package/dist/extensions/collab.js +79 -0
  53. package/dist/extensions/doc/kinds/doc.kind.yaml +118 -0
  54. package/dist/extensions/doc.d.ts +25 -0
  55. package/dist/extensions/doc.js +10 -0
  56. package/dist/extensions/eval/kinds/eval-baseline.kind.yaml +65 -0
  57. package/dist/extensions/eval/kinds/eval-case.kind.yaml +140 -0
  58. package/dist/extensions/eval/kinds/eval-run.kind.yaml +134 -0
  59. package/dist/extensions/eval/kinds/eval-suite.kind.yaml +87 -0
  60. package/dist/extensions/eval.d.ts +24 -0
  61. package/dist/extensions/eval.js +31 -0
  62. package/dist/extensions/evidence/kinds/evidence.kind.yaml +92 -0
  63. package/dist/extensions/evidence.d.ts +17 -0
  64. package/dist/extensions/evidence.js +94 -0
  65. package/dist/extensions/federation.d.ts +22 -0
  66. package/dist/extensions/federation.js +173 -0
  67. package/dist/extensions/guardrails/DOCS.md +20 -0
  68. package/dist/extensions/guardrails.d.ts +31 -0
  69. package/dist/extensions/guardrails.js +228 -0
  70. package/dist/extensions/helix/DOCS-Actor.md +20 -0
  71. package/dist/extensions/helix/DOCS-Agent.md +20 -0
  72. package/dist/extensions/helix/DOCS-UseCase.md +20 -0
  73. package/dist/extensions/helix/layers.d.ts +12 -0
  74. package/dist/extensions/helix/layers.js +18 -0
  75. package/dist/extensions/helix/write-guards.d.ts +70 -0
  76. package/dist/extensions/helix/write-guards.js +172 -0
  77. package/dist/extensions/helix.d.ts +29 -0
  78. package/dist/extensions/helix.js +963 -0
  79. package/dist/extensions/helix_extras.d.ts +124 -0
  80. package/dist/extensions/helix_extras.js +328 -0
  81. package/dist/extensions/hooks.d.ts +31 -0
  82. package/dist/extensions/hooks.js +247 -0
  83. package/dist/extensions/kinddef/DOCS.md +24 -0
  84. package/dist/extensions/kinddef.d.ts +40 -0
  85. package/dist/extensions/kinddef.js +171 -0
  86. package/dist/extensions/lesson.d.ts +6 -0
  87. package/dist/extensions/lesson.js +194 -0
  88. package/dist/extensions/modelreg/kinds/model-profile.kind.yaml +155 -0
  89. package/dist/extensions/modelreg.d.ts +30 -0
  90. package/dist/extensions/modelreg.js +38 -0
  91. package/dist/extensions/recognizer.d.ts +34 -0
  92. package/dist/extensions/recognizer.js +272 -0
  93. package/dist/extensions/research.d.ts +32 -0
  94. package/dist/extensions/research.js +292 -0
  95. package/dist/extensions/safety.d.ts +33 -0
  96. package/dist/extensions/safety.js +230 -0
  97. package/dist/extensions/sdlc/kinds/adr.kind.yaml +136 -0
  98. package/dist/extensions/sdlc/kinds/archive-proposal.kind.yaml +107 -0
  99. package/dist/extensions/sdlc/kinds/changelog.kind.yaml +108 -0
  100. package/dist/extensions/sdlc/kinds/cognitive-policy.kind.yaml +470 -0
  101. package/dist/extensions/sdlc/kinds/forecast.kind.yaml +125 -0
  102. package/dist/extensions/sdlc/kinds/insight.kind.yaml +87 -0
  103. package/dist/extensions/sdlc/kinds/kaizen.kind.yaml +106 -0
  104. package/dist/extensions/sdlc/kinds/lesson-learned.kind.yaml +270 -0
  105. package/dist/extensions/sdlc/kinds/narrative.kind.yaml +189 -0
  106. package/dist/extensions/sdlc/kinds/pattern-insight.kind.yaml +204 -0
  107. package/dist/extensions/sdlc/kinds/postmortem.kind.yaml +168 -0
  108. package/dist/extensions/sdlc/kinds/pre-mortem.kind.yaml +145 -0
  109. package/dist/extensions/sdlc/kinds/prompt-template.kind.yaml +109 -0
  110. package/dist/extensions/sdlc/kinds/retrospective.kind.yaml +186 -0
  111. package/dist/extensions/sdlc/kinds/risk-register.kind.yaml +178 -0
  112. package/dist/extensions/sdlc/kinds/saved-view.kind.yaml +127 -0
  113. package/dist/extensions/sdlc/kinds/status-report.kind.yaml +129 -0
  114. package/dist/extensions/sdlc/kinds/synthesis-run.kind.yaml +279 -0
  115. package/dist/extensions/sdlc/kinds/synthesizer-state.kind.yaml +196 -0
  116. package/dist/extensions/sdlc/kinds/workflow-event.kind.yaml +195 -0
  117. package/dist/extensions/sdlc.d.ts +32 -0
  118. package/dist/extensions/sdlc.js +1318 -0
  119. package/dist/extensions/soulspec/DOCS.md +19 -0
  120. package/dist/extensions/soulspec.d.ts +22 -0
  121. package/dist/extensions/soulspec.js +287 -0
  122. package/dist/extensions/tenant.d.ts +6 -0
  123. package/dist/extensions/tenant.js +291 -0
  124. package/dist/extensions/testkit.d.ts +24 -0
  125. package/dist/extensions/testkit.js +211 -0
  126. package/dist/index.d.ts +80 -0
  127. package/dist/index.js +102 -0
  128. package/dist/kernel/_text.d.ts +28 -0
  129. package/dist/kernel/_text.js +42 -0
  130. package/dist/kernel/bundle-handle.d.ts +83 -0
  131. package/dist/kernel/bundle-handle.js +184 -0
  132. package/dist/kernel/capabilities.d.ts +157 -0
  133. package/dist/kernel/capabilities.js +109 -0
  134. package/dist/kernel/collaborator-ports.d.ts +91 -0
  135. package/dist/kernel/collaborator-ports.js +1 -0
  136. package/dist/kernel/composition-resolver.d.ts +215 -0
  137. package/dist/kernel/composition-resolver.js +558 -0
  138. package/dist/kernel/composition.d.ts +10 -0
  139. package/dist/kernel/composition.js +1 -0
  140. package/dist/kernel/descriptor-loader.d.ts +13 -0
  141. package/dist/kernel/descriptor-loader.js +48 -0
  142. package/dist/kernel/document.d.ts +43 -0
  143. package/dist/kernel/document.js +71 -0
  144. package/dist/kernel/embedding.d.ts +28 -0
  145. package/dist/kernel/embedding.js +78 -0
  146. package/dist/kernel/errors.d.ts +62 -0
  147. package/dist/kernel/errors.js +82 -0
  148. package/dist/kernel/events.d.ts +6 -0
  149. package/dist/kernel/events.js +21 -0
  150. package/dist/kernel/evidence-capture.d.ts +69 -0
  151. package/dist/kernel/evidence-capture.js +138 -0
  152. package/dist/kernel/fs.d.ts +20 -0
  153. package/dist/kernel/fs.js +128 -0
  154. package/dist/kernel/generic-rw.d.ts +33 -0
  155. package/dist/kernel/generic-rw.js +235 -0
  156. package/dist/kernel/hooks.d.ts +172 -0
  157. package/dist/kernel/hooks.js +256 -0
  158. package/dist/kernel/index.d.ts +624 -0
  159. package/dist/kernel/index.js +1638 -0
  160. package/dist/kernel/instance.d.ts +211 -0
  161. package/dist/kernel/instance.js +487 -0
  162. package/dist/kernel/kind-registry.d.ts +183 -0
  163. package/dist/kernel/kind-registry.js +710 -0
  164. package/dist/kernel/kind_base.d.ts +106 -0
  165. package/dist/kernel/kind_base.js +244 -0
  166. package/dist/kernel/layer-resolver.d.ts +48 -0
  167. package/dist/kernel/layer-resolver.js +284 -0
  168. package/dist/kernel/lock-manager.d.ts +21 -0
  169. package/dist/kernel/lock-manager.js +43 -0
  170. package/dist/kernel/lock.d.ts +38 -0
  171. package/dist/kernel/lock.js +115 -0
  172. package/dist/kernel/meta.d.ts +135 -0
  173. package/dist/kernel/meta.js +817 -0
  174. package/dist/kernel/models.d.ts +3089 -0
  175. package/dist/kernel/models.js +764 -0
  176. package/dist/kernel/navigator.d.ts +36 -0
  177. package/dist/kernel/navigator.js +132 -0
  178. package/dist/kernel/port-surface.d.ts +33 -0
  179. package/dist/kernel/port-surface.js +180 -0
  180. package/dist/kernel/preview.d.ts +60 -0
  181. package/dist/kernel/preview.js +54 -0
  182. package/dist/kernel/prompt-budget.d.ts +39 -0
  183. package/dist/kernel/prompt-budget.js +43 -0
  184. package/dist/kernel/prompt-builder.d.ts +25 -0
  185. package/dist/kernel/prompt-builder.js +237 -0
  186. package/dist/kernel/protocols.d.ts +751 -0
  187. package/dist/kernel/protocols.js +493 -0
  188. package/dist/kernel/reports.d.ts +26 -0
  189. package/dist/kernel/reports.js +215 -0
  190. package/dist/kernel/resolver.d.ts +146 -0
  191. package/dist/kernel/resolver.js +232 -0
  192. package/dist/kernel/resource.d.ts +57 -0
  193. package/dist/kernel/resource.js +97 -0
  194. package/dist/kernel/runtime.d.ts +16 -0
  195. package/dist/kernel/runtime.js +18 -0
  196. package/dist/kernel/safety-scanner.d.ts +38 -0
  197. package/dist/kernel/safety-scanner.js +228 -0
  198. package/dist/kernel/serialize-to-files.d.ts +29 -0
  199. package/dist/kernel/serialize-to-files.js +23 -0
  200. package/dist/kernel/spec-access.d.ts +23 -0
  201. package/dist/kernel/spec-access.js +40 -0
  202. package/dist/kernel/studio_ui.d.ts +88 -0
  203. package/dist/kernel/studio_ui.js +135 -0
  204. package/dist/kernel/templates.d.ts +77 -0
  205. package/dist/kernel/templates.js +116 -0
  206. package/dist/kernel/tool-registry.d.ts +52 -0
  207. package/dist/kernel/tool-registry.js +89 -0
  208. package/dist/kernel/write-pipeline.d.ts +65 -0
  209. package/dist/kernel/write-pipeline.js +271 -0
  210. package/dist/kernel/writer-helpers.d.ts +24 -0
  211. package/dist/kernel/writer-helpers.js +42 -0
  212. package/dist/memory/decay.d.ts +31 -0
  213. package/dist/memory/decay.js +97 -0
  214. package/dist/memory/ecphory.d.ts +25 -0
  215. package/dist/memory/ecphory.js +178 -0
  216. package/dist/memory/encodingContext.d.ts +21 -0
  217. package/dist/memory/encodingContext.js +62 -0
  218. package/dist/memory/index.d.ts +23 -0
  219. package/dist/memory/index.js +23 -0
  220. package/dist/memory/memoryType.d.ts +7 -0
  221. package/dist/memory/memoryType.js +24 -0
  222. package/dist/memory/policy.d.ts +35 -0
  223. package/dist/memory/policy.js +35 -0
  224. package/dist/memory/retrieval.d.ts +21 -0
  225. package/dist/memory/retrieval.js +145 -0
  226. package/dist/testing/index.d.ts +8 -0
  227. package/dist/testing/index.js +8 -0
  228. package/dist/testing/recordSearchConformance.d.ts +43 -0
  229. package/dist/testing/recordSearchConformance.js +147 -0
  230. package/dist/viz/ascii.d.ts +7 -0
  231. package/dist/viz/ascii.js +60 -0
  232. package/dist/viz/health.d.ts +8 -0
  233. package/dist/viz/health.js +161 -0
  234. package/dist/viz/index.d.ts +10 -0
  235. package/dist/viz/index.js +10 -0
  236. package/dist/viz/matrix.d.ts +8 -0
  237. package/dist/viz/matrix.js +84 -0
  238. package/dist/viz/mermaid.d.ts +38 -0
  239. package/dist/viz/mermaid.js +706 -0
  240. package/package.json +82 -0
@@ -0,0 +1,256 @@
1
+ /**
2
+ * HookRegistry — middleware (use) + events (on / onAsync) for kernel lifecycle.
3
+ *
4
+ * 1:1 parity with Python dna.v3.kernel.hooks.
5
+ *
6
+ * Dual-channel events:
7
+ * - Sync listeners (legacy + in-process metrics/logging):
8
+ * k.on("post_build_prompt", fn) // fn: (ctx) => void
9
+ * k.hooks.emit("post_build_prompt", ctx)
10
+ * - Async listeners (I/O-heavy: event bus publishers, HTTP, DB writes):
11
+ * k.onAsync("post_save", fn) // fn: (ctx) => Promise<void>
12
+ * await k.hooks.emitAsync("post_save", ctx)
13
+ *
14
+ * Sync `on()` autodetects based on whether the handler returns a Promise.
15
+ * Callers that invoke `emit` (sync) skip async listeners — callers that
16
+ * invoke `emitAsync` run both channels. The skip is LOUD
17
+ * (s-kernel-fail-soft-audit, Py twin semantics): `emit` counts every skip
18
+ * in `skippedAsyncEmits` and console.warns ONCE per (hook, listener);
19
+ * `emit(hook, ctx, { strict: true })` throws instead — for call sites
20
+ * where skipping a listener would be a bug, not lost telemetry. The
21
+ * kernel's own sync emits (parse_error / kinddef_conflict /
22
+ * extension_error / sync-path post_build_prompt) are observability
23
+ * funnels and stay non-strict.
24
+ *
25
+ * Veto hooks (s-write-path-despecialize): ordered listeners that can BLOCK
26
+ * the operation by throwing. Unlike `emit`/`emitAsync` (fire-and-forget,
27
+ * errors swallowed), `emitVeto` runs listeners in ascending `priority`
28
+ * order and lets exceptions PROPAGATE — a throw IS the veto. Listeners may
29
+ * also mutate the typed context in place (e.g. fields on `ctx.raw`) — the
30
+ * write proceeds with the mutated payload.
31
+ *
32
+ * k.hooks.onVeto("pre_save", fn, { priority: 20, key: "helix.rule" });
33
+ * await k.hooks.emitVeto("pre_save", ctx);
34
+ *
35
+ * `key` makes registration idempotent: re-registering the same key REPLACES
36
+ * the previous listener (extensions may be re-loaded onto a shared registry).
37
+ */
38
+ // ---------------------------------------------------------------------------
39
+ // Types
40
+ // ---------------------------------------------------------------------------
41
+ /**
42
+ * The full vocabulary of hook names the SDK itself emits/consumes
43
+ * (s-dna-typed-hook-names). Py twin: `HookName` / `KNOWN_HOOK_NAMES` in
44
+ * dna/kernel/hooks.py. Both sides are locked to the shared fixture
45
+ * `tests/parity-fixtures/port-surface-parity.json` (section `hook_names`)
46
+ * by tests/hook-names.test.ts + tests/test_hook_names.py — vocabulary
47
+ * drift turns both suites red. Add a name HERE + fixture + Py, never ad hoc.
48
+ */
49
+ export const KNOWN_HOOK_NAMES = [
50
+ "pre_build_prompt",
51
+ "post_build_prompt",
52
+ "pre_save",
53
+ "post_save",
54
+ "post_delete",
55
+ "kinddef_conflict",
56
+ "parse_error",
57
+ "extension_error",
58
+ ];
59
+ const KNOWN_HOOK_SET = new Set(KNOWN_HOOK_NAMES);
60
+ // ---------------------------------------------------------------------------
61
+ // Registry
62
+ // ---------------------------------------------------------------------------
63
+ export class HookRegistry {
64
+ _middleware = new Map();
65
+ _events = new Map();
66
+ _asyncEvents = new Map();
67
+ /** Veto channel — hook → list of {priority, seq, key, fn}. `seq` keeps
68
+ * registration order stable among equal priorities. */
69
+ _veto = new Map();
70
+ _vetoSeq = 0;
71
+ /** s-kernel-fail-soft-audit — sync-emit async-listener skip tracking:
72
+ * warn ONCE per (hook, listener) + count every skip per hook so a
73
+ * mis-wired listener is visible instead of an invisible no-op. */
74
+ _asyncSkipWarned = new Set();
75
+ skippedAsyncEmits = new Map();
76
+ /** s-dna-typed-hook-names — unknown-name warn-once tracking (per
77
+ * registry, per name; same posture as _asyncSkipWarned). */
78
+ _unknownHookWarned = new Set();
79
+ /** Warn (once per registry+name) on a name outside `HookName`.
80
+ * Fail-loud, NOT fail-closed: custom hook names keep working
81
+ * (back-compat / third-party channels), but a typo of a builtin name —
82
+ * the listener that never fires — is now visible. Py twin semantics:
83
+ * `UnknownHookNameWarning`. */
84
+ _checkHookName(hook) {
85
+ if (KNOWN_HOOK_SET.has(hook) || this._unknownHookWarned.has(hook))
86
+ return;
87
+ this._unknownHookWarned.add(hook);
88
+ console.warn(`unknown hook name ${JSON.stringify(hook)} — known: ` +
89
+ `${JSON.stringify([...KNOWN_HOOK_NAMES].sort())}. A listener on (or ` +
90
+ `emit of) a misspelled hook never fires; custom names still work ` +
91
+ `(warning only).`);
92
+ }
93
+ /** Register middleware. Called in order, each receives output of previous. */
94
+ use(hook, fn) {
95
+ this._checkHookName(hook);
96
+ const list = this._middleware.get(hook) ?? [];
97
+ list.push(fn);
98
+ this._middleware.set(hook, list);
99
+ }
100
+ /**
101
+ * Register event subscriber. Fire-and-forget, errors logged but not raised.
102
+ *
103
+ * Autodetects sync vs async via `fn.constructor.name === "AsyncFunction"`.
104
+ * Async functions are routed to the async channel — callers using `emit`
105
+ * (sync) skip them; `emitAsync` runs both.
106
+ */
107
+ on(hook, fn) {
108
+ this._checkHookName(hook);
109
+ // AsyncFunction detection: works for `async function`. For regular
110
+ // functions that happen to return Promises, prefer `onAsync` explicitly.
111
+ if (fn.constructor?.name === "AsyncFunction") {
112
+ const list = this._asyncEvents.get(hook) ?? [];
113
+ list.push(fn);
114
+ this._asyncEvents.set(hook, list);
115
+ }
116
+ else {
117
+ const list = this._events.get(hook) ?? [];
118
+ list.push(fn);
119
+ this._events.set(hook, list);
120
+ }
121
+ }
122
+ /** Explicit async-only registration. Prefer `on()` — this is here for
123
+ * symmetry and for non-`async function` callables (arrow with Promise
124
+ * body, partials) where AsyncFunction detection returns false. */
125
+ onAsync(hook, fn) {
126
+ this._checkHookName(hook);
127
+ const list = this._asyncEvents.get(hook) ?? [];
128
+ list.push(fn);
129
+ this._asyncEvents.set(hook, list);
130
+ }
131
+ /** Run middleware chain. Returns modified context. */
132
+ runMiddleware(hook, ctx) {
133
+ this._checkHookName(hook);
134
+ for (const fn of this._middleware.get(hook) ?? []) {
135
+ ctx = fn(ctx);
136
+ }
137
+ return ctx;
138
+ }
139
+ /**
140
+ * Sync emit. Runs only sync listeners. If async listeners exist for this
141
+ * hook they CANNOT run here — the caller must use `emitAsync` to reach
142
+ * them. The skip is loud (s-kernel-fail-soft-audit):
143
+ *
144
+ * - default: each skipped listener is counted in `skippedAsyncEmits` and
145
+ * a console.warn fires once per (hook, listener) — a mis-wired async
146
+ * listener is visible, not an invisible no-op.
147
+ * - `strict: true`: throw instead — for emit sites where skipping a
148
+ * listener would be a bug, not lost telemetry.
149
+ */
150
+ emit(hook, ctx, opts) {
151
+ this._checkHookName(hook);
152
+ for (const fn of this._events.get(hook) ?? []) {
153
+ try {
154
+ fn(ctx);
155
+ }
156
+ catch (e) {
157
+ console.warn(`Hook ${hook} subscriber error:`, e);
158
+ }
159
+ }
160
+ const skipped = this._asyncEvents.get(hook) ?? [];
161
+ if (skipped.length > 0) {
162
+ const total = (this.skippedAsyncEmits.get(hook) ?? 0) + skipped.length;
163
+ this.skippedAsyncEmits.set(hook, total);
164
+ if (opts?.strict) {
165
+ const names = skipped.map((fn) => fn.name || "<anonymous>");
166
+ throw new Error(`Hook ${JSON.stringify(hook)} was emitted via sync emit(strict) ` +
167
+ `but has ${skipped.length} async listener(s) ` +
168
+ `${JSON.stringify(names)} that cannot run — use emitAsync() at ` +
169
+ `this call site.`);
170
+ }
171
+ for (const fn of skipped) {
172
+ const key = `${hook}\0${fn.name || "<anonymous>"}`;
173
+ if (this._asyncSkipWarned.has(key))
174
+ continue;
175
+ this._asyncSkipWarned.add(key);
176
+ console.warn(`Hook ${hook} has async listener ${fn.name || "<anonymous>"} but ` +
177
+ `was fired via sync emit(); the listener was SKIPPED (total ` +
178
+ `skips for this hook: ${total}). Use emitAsync() to reach it. ` +
179
+ `(warned once per (hook, listener))`);
180
+ }
181
+ }
182
+ }
183
+ /**
184
+ * Async emit. Runs sync listeners inline, then awaits each async listener.
185
+ * Use from async call sites (kernel.writeDocument, kernel.deleteDocument).
186
+ * Errors in any listener are logged and do NOT prevent other listeners.
187
+ */
188
+ async emitAsync(hook, ctx) {
189
+ this._checkHookName(hook);
190
+ for (const fn of this._events.get(hook) ?? []) {
191
+ try {
192
+ fn(ctx);
193
+ }
194
+ catch (e) {
195
+ console.warn(`Hook ${hook} (sync) subscriber error:`, e);
196
+ }
197
+ }
198
+ for (const fn of this._asyncEvents.get(hook) ?? []) {
199
+ try {
200
+ await fn(ctx);
201
+ }
202
+ catch (e) {
203
+ console.warn(`Hook ${hook} (async) subscriber error:`, e);
204
+ }
205
+ }
206
+ }
207
+ // -- Veto channel (s-write-path-despecialize) ------------------------------
208
+ /**
209
+ * Register a veto listener on `hook`. Listeners run in ascending
210
+ * `priority` (ties keep registration order). Throwing from a listener
211
+ * PROPAGATES to the emitter — that is the veto. Sync and async callables
212
+ * both work.
213
+ *
214
+ * `key` (recommended: `"<extension>.<rule>"`) makes registration
215
+ * idempotent — a second `onVeto` with the same key REPLACES the earlier
216
+ * listener instead of stacking a duplicate.
217
+ */
218
+ onVeto(hook, fn, opts) {
219
+ this._checkHookName(hook);
220
+ const listeners = this._veto.get(hook) ?? [];
221
+ const key = opts?.key ?? null;
222
+ const filtered = key !== null
223
+ ? listeners.filter((entry) => entry.key !== key)
224
+ : listeners;
225
+ this._vetoSeq += 1;
226
+ filtered.push({
227
+ priority: opts?.priority ?? 0, seq: this._vetoSeq, key, fn,
228
+ });
229
+ filtered.sort((a, b) => a.priority - b.priority || a.seq - b.seq);
230
+ this._veto.set(hook, filtered);
231
+ }
232
+ /**
233
+ * Run veto listeners for `hook` in priority order. Unlike
234
+ * `emit`/`emitAsync`, exceptions are NOT swallowed — the first throw
235
+ * aborts the chain and propagates to the caller (the operation is
236
+ * vetoed). Listeners may mutate `ctx` in place.
237
+ */
238
+ async emitVeto(hook, ctx) {
239
+ this._checkHookName(hook);
240
+ for (const entry of this._veto.get(hook) ?? []) {
241
+ await entry.fn(ctx);
242
+ }
243
+ }
244
+ /** Check if any veto listeners are registered for `hook`. */
245
+ hasVeto(hook) {
246
+ return (this._veto.get(hook)?.length ?? 0) > 0;
247
+ }
248
+ /** Check if any middleware, events (sync or async) or veto listeners are
249
+ * registered. */
250
+ has(hook) {
251
+ return ((this._middleware.get(hook)?.length ?? 0) > 0 ||
252
+ (this._events.get(hook)?.length ?? 0) > 0 ||
253
+ (this._asyncEvents.get(hook)?.length ?? 0) > 0 ||
254
+ (this._veto.get(hook)?.length ?? 0) > 0);
255
+ }
256
+ }