@skillcap/gdh 0.13.3 → 0.14.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 (30) hide show
  1. package/INSTALL-BUNDLE.json +1 -1
  2. package/README.md +4 -4
  3. package/RELEASE-SPAN-UPDATE-CONTRACTS.json +79 -0
  4. package/node_modules/@gdh/adapters/package.json +8 -8
  5. package/node_modules/@gdh/authoring/package.json +2 -2
  6. package/node_modules/@gdh/cli/package.json +10 -10
  7. package/node_modules/@gdh/core/dist/index.d.ts +37 -2
  8. package/node_modules/@gdh/core/dist/index.d.ts.map +1 -1
  9. package/node_modules/@gdh/core/dist/index.js +2 -2
  10. package/node_modules/@gdh/core/dist/index.js.map +1 -1
  11. package/node_modules/@gdh/core/package.json +1 -1
  12. package/node_modules/@gdh/docs/package.json +2 -2
  13. package/node_modules/@gdh/mcp/package.json +8 -8
  14. package/node_modules/@gdh/observability/dist/runtime-bundles.d.ts.map +1 -1
  15. package/node_modules/@gdh/observability/dist/runtime-bundles.js +28 -2
  16. package/node_modules/@gdh/observability/dist/runtime-bundles.js.map +1 -1
  17. package/node_modules/@gdh/observability/package.json +2 -2
  18. package/node_modules/@gdh/runtime/dist/bridge-surface.js +173 -0
  19. package/node_modules/@gdh/runtime/dist/bridge-surface.js.map +1 -1
  20. package/node_modules/@gdh/runtime/dist/index.d.ts.map +1 -1
  21. package/node_modules/@gdh/runtime/dist/index.js +387 -17
  22. package/node_modules/@gdh/runtime/dist/index.js.map +1 -1
  23. package/node_modules/@gdh/runtime/package.json +2 -2
  24. package/node_modules/@gdh/scan/package.json +3 -3
  25. package/node_modules/@gdh/verify/dist/scenarios.d.ts +3 -1
  26. package/node_modules/@gdh/verify/dist/scenarios.d.ts.map +1 -1
  27. package/node_modules/@gdh/verify/dist/scenarios.js +425 -36
  28. package/node_modules/@gdh/verify/dist/scenarios.js.map +1 -1
  29. package/node_modules/@gdh/verify/package.json +7 -7
  30. package/package.json +11 -11
@@ -1552,6 +1552,24 @@ const HOST_RUNTIME_BRIDGE_ENTRIES = [
1552
1552
  executionRoute: null,
1553
1553
  safetyLevel: "read_only",
1554
1554
  },
1555
+ {
1556
+ id: "state.node_presence.await",
1557
+ summary: "Wait for one exact node path to become present or absent through bounded polling.",
1558
+ kind: "waiter",
1559
+ shape: "composite",
1560
+ source: "core",
1561
+ executionRoute: null,
1562
+ safetyLevel: "read_only",
1563
+ },
1564
+ {
1565
+ id: "state.signal.await",
1566
+ summary: "Wait for one observed signal emission to occur after an optional observed-count baseline.",
1567
+ kind: "waiter",
1568
+ shape: "composite",
1569
+ source: "core",
1570
+ executionRoute: null,
1571
+ safetyLevel: "read_only",
1572
+ },
1555
1573
  ];
1556
1574
  export function createRuntimeBridgeManager() {
1557
1575
  const sessions = new Map();
@@ -1816,6 +1834,7 @@ export function createRuntimeBridgeManager() {
1816
1834
  summary: `Runtime bridge session "${sessionId}" was not found.`,
1817
1835
  reasons: ["bridge_session_not_found"],
1818
1836
  result: null,
1837
+ waiterEvidence: null,
1819
1838
  transcriptPath: null,
1820
1839
  };
1821
1840
  }
@@ -1827,6 +1846,7 @@ export function createRuntimeBridgeManager() {
1827
1846
  summary: `Runtime bridge session "${sessionId}" is no longer active.`,
1828
1847
  reasons: ["bridge_session_not_active"],
1829
1848
  result: null,
1849
+ waiterEvidence: null,
1830
1850
  transcriptPath: session.transcriptPath,
1831
1851
  };
1832
1852
  }
@@ -1838,6 +1858,7 @@ export function createRuntimeBridgeManager() {
1838
1858
  summary: `Runtime bridge session "${sessionId}" is already handling another invocation.`,
1839
1859
  reasons: ["bridge_session_busy"],
1840
1860
  result: null,
1861
+ waiterEvidence: null,
1841
1862
  transcriptPath: session.transcriptPath,
1842
1863
  };
1843
1864
  }
@@ -1864,6 +1885,7 @@ export function createRuntimeBridgeManager() {
1864
1885
  : `Bridge entry "${entryId}" returned state "${syntheticResponse.state}".`,
1865
1886
  reasons: syntheticResponse.error ? [syntheticResponse.error] : [],
1866
1887
  result: syntheticResponse.result,
1888
+ waiterEvidence: syntheticResponse.waiterEvidence,
1867
1889
  transcriptPath: session.transcriptPath,
1868
1890
  };
1869
1891
  }
@@ -1883,6 +1905,7 @@ export function createRuntimeBridgeManager() {
1883
1905
  : `Bridge entry "${entryId}" returned state "${response.state}".`,
1884
1906
  reasons: response.error ? [response.error] : [],
1885
1907
  result: response.result,
1908
+ waiterEvidence: null,
1886
1909
  transcriptPath: session.transcriptPath,
1887
1910
  };
1888
1911
  }
@@ -1898,6 +1921,7 @@ export function createRuntimeBridgeManager() {
1898
1921
  summary: `Bridge entry "${entryId}" failed: ${formatBridgeError(error)}.`,
1899
1922
  reasons: ["bridge_invoke_failed"],
1900
1923
  result: null,
1924
+ waiterEvidence: null,
1901
1925
  transcriptPath: session.transcriptPath,
1902
1926
  };
1903
1927
  }
@@ -2001,6 +2025,12 @@ async function invokeSyntheticBridgeEntry(session, entryId, inputValue) {
2001
2025
  if (entryId === "state.node_property.await") {
2002
2026
  return invokeNodePropertyAwaitEntry(session, inputValue);
2003
2027
  }
2028
+ if (entryId === "state.node_presence.await") {
2029
+ return invokeNodePresenceAwaitEntry(session, inputValue);
2030
+ }
2031
+ if (entryId === "state.signal.await") {
2032
+ return invokeSignalAwaitEntry(session, inputValue);
2033
+ }
2004
2034
  return null;
2005
2035
  }
2006
2036
  async function invokeNodePropertyAwaitEntry(session, inputValue) {
@@ -2016,6 +2046,7 @@ async function invokeNodePropertyAwaitEntry(session, inputValue) {
2016
2046
  state: "failed",
2017
2047
  result: null,
2018
2048
  error: "state.node_property.await requires nodePath and property.",
2049
+ waiterEvidence: null,
2019
2050
  };
2020
2051
  }
2021
2052
  if (!hasExpected) {
@@ -2023,12 +2054,20 @@ async function invokeNodePropertyAwaitEntry(session, inputValue) {
2023
2054
  state: "failed",
2024
2055
  result: null,
2025
2056
  error: "state.node_property.await requires an expected value.",
2057
+ waiterEvidence: null,
2026
2058
  };
2027
2059
  }
2028
- const startedAt = Date.now();
2060
+ const target = {
2061
+ nodePath,
2062
+ property,
2063
+ expected,
2064
+ };
2065
+ const startedAt = new Date().toISOString();
2066
+ const startedAtMs = Date.now();
2067
+ recordWaiterStarted(session, "state.node_property.await", target, timeoutMs, pollIntervalMs);
2029
2068
  let attempts = 0;
2030
2069
  let lastValue = null;
2031
- while (Date.now() - startedAt <= timeoutMs) {
2070
+ while (Date.now() - startedAtMs <= timeoutMs) {
2032
2071
  attempts += 1;
2033
2072
  recordBridgeEvent(session, "invoke_expanded_leaf_step", {
2034
2073
  parentEntryId: "state.node_property.await",
@@ -2056,37 +2095,326 @@ async function invokeNodePropertyAwaitEntry(session, inputValue) {
2056
2095
  state: response.state,
2057
2096
  result: response.result,
2058
2097
  error: response.error,
2098
+ waiterEvidence: null,
2059
2099
  };
2060
2100
  }
2061
2101
  const payload = toJsonRecord(response.result);
2062
2102
  lastValue = (payload["value"] ?? null);
2063
2103
  if (jsonValuesEqual(lastValue, expected)) {
2104
+ const result = {
2105
+ nodePath,
2106
+ property,
2107
+ expected,
2108
+ value: lastValue,
2109
+ attempts,
2110
+ elapsedMs: Date.now() - startedAtMs,
2111
+ };
2112
+ const waiterEvidence = buildWaiterEvidence({
2113
+ waiterId: "state.node_property.await",
2114
+ outcome: "satisfied",
2115
+ startedAt,
2116
+ timeoutMs,
2117
+ pollCount: attempts,
2118
+ target,
2119
+ result,
2120
+ });
2121
+ recordWaiterSatisfied(session, "state.node_property.await", target, attempts, result);
2064
2122
  return {
2065
2123
  state: "ok",
2066
- result: {
2067
- nodePath,
2068
- property,
2069
- expected,
2070
- value: lastValue,
2071
- attempts,
2072
- elapsedMs: Date.now() - startedAt,
2073
- },
2124
+ result,
2074
2125
  error: null,
2126
+ waiterEvidence,
2075
2127
  };
2076
2128
  }
2077
2129
  await waitMs(pollIntervalMs);
2078
2130
  }
2131
+ const result = {
2132
+ nodePath,
2133
+ property,
2134
+ expected,
2135
+ value: lastValue,
2136
+ attempts,
2137
+ elapsedMs: Date.now() - startedAtMs,
2138
+ };
2139
+ const waiterEvidence = buildWaiterEvidence({
2140
+ waiterId: "state.node_property.await",
2141
+ outcome: "timed_out",
2142
+ startedAt,
2143
+ timeoutMs,
2144
+ pollCount: attempts,
2145
+ target,
2146
+ result,
2147
+ });
2148
+ recordWaiterTimedOut(session, "state.node_property.await", target, attempts, result);
2079
2149
  return {
2080
2150
  state: "unavailable",
2081
- result: {
2151
+ result,
2152
+ error: "Timed out waiting for the requested node property value.",
2153
+ waiterEvidence,
2154
+ };
2155
+ }
2156
+ async function invokeNodePresenceAwaitEntry(session, inputValue) {
2157
+ const input = toJsonRecord(inputValue);
2158
+ const nodePath = typeof input["nodePath"] === "string" ? input["nodePath"] : "";
2159
+ const expected = typeof input["expected"] === "string" ? input["expected"] : "";
2160
+ const timeoutMs = clampNumber(input["timeoutMs"], 1500, 100, 5000);
2161
+ const pollIntervalMs = clampNumber(input["pollIntervalMs"], 50, 25, 250);
2162
+ if (nodePath.length === 0) {
2163
+ return {
2164
+ state: "failed",
2165
+ result: null,
2166
+ error: "state.node_presence.await requires nodePath.",
2167
+ waiterEvidence: null,
2168
+ };
2169
+ }
2170
+ if (expected !== "present" && expected !== "absent") {
2171
+ return {
2172
+ state: "failed",
2173
+ result: null,
2174
+ error: 'state.node_presence.await requires expected = "present" or "absent".',
2175
+ waiterEvidence: null,
2176
+ };
2177
+ }
2178
+ const target = {
2179
+ nodePath,
2180
+ expected,
2181
+ };
2182
+ recordWaiterStarted(session, "state.node_presence.await", target, timeoutMs, pollIntervalMs);
2183
+ const startedAt = new Date().toISOString();
2184
+ const startedAtMs = Date.now();
2185
+ let attempts = 0;
2186
+ let lastSnapshot = null;
2187
+ while (Date.now() - startedAtMs <= timeoutMs) {
2188
+ attempts += 1;
2189
+ recordBridgeEvent(session, "invoke_expanded_leaf_step", {
2190
+ parentEntryId: "state.node_presence.await",
2191
+ leafEntryId: "state.node_presence.get",
2192
+ attempt: attempts,
2193
+ input: {
2194
+ nodePath,
2195
+ },
2196
+ });
2197
+ const response = await invokeBridgeEntryOverSocket(session.ws, session.sessionId, "state.node_presence.get", {
2082
2198
  nodePath,
2083
- property,
2084
- expected,
2085
- value: lastValue,
2086
- attempts,
2087
- elapsedMs: Date.now() - startedAt,
2199
+ });
2200
+ recordBridgeEvent(session, "invoke_expanded_leaf_response", {
2201
+ parentEntryId: "state.node_presence.await",
2202
+ leafEntryId: "state.node_presence.get",
2203
+ attempt: attempts,
2204
+ state: response.state,
2205
+ result: response.result,
2206
+ error: response.error,
2207
+ });
2208
+ if (response.state !== "ok") {
2209
+ return {
2210
+ state: response.state,
2211
+ result: response.result,
2212
+ error: response.error,
2213
+ waiterEvidence: null,
2214
+ };
2215
+ }
2216
+ const payload = toJsonRecord(response.result);
2217
+ lastSnapshot = payload;
2218
+ const present = payload["present"] === true;
2219
+ const matches = expected === "present" ? present : !present;
2220
+ if (matches) {
2221
+ const result = {
2222
+ nodePath,
2223
+ expected,
2224
+ present,
2225
+ name: (payload["name"] ?? null),
2226
+ className: (payload["className"] ?? null),
2227
+ attempts,
2228
+ elapsedMs: Date.now() - startedAtMs,
2229
+ };
2230
+ const waiterEvidence = buildWaiterEvidence({
2231
+ waiterId: "state.node_presence.await",
2232
+ outcome: "satisfied",
2233
+ startedAt,
2234
+ timeoutMs,
2235
+ pollCount: attempts,
2236
+ target,
2237
+ result,
2238
+ });
2239
+ recordWaiterSatisfied(session, "state.node_presence.await", target, attempts, result);
2240
+ return {
2241
+ state: "ok",
2242
+ result,
2243
+ error: null,
2244
+ waiterEvidence,
2245
+ };
2246
+ }
2247
+ await waitMs(pollIntervalMs);
2248
+ }
2249
+ const result = {
2250
+ nodePath,
2251
+ expected,
2252
+ present: lastSnapshot?.["present"] === true,
2253
+ name: (lastSnapshot?.["name"] ?? null),
2254
+ className: (lastSnapshot?.["className"] ?? null),
2255
+ attempts,
2256
+ elapsedMs: Date.now() - startedAtMs,
2257
+ };
2258
+ const waiterEvidence = buildWaiterEvidence({
2259
+ waiterId: "state.node_presence.await",
2260
+ outcome: "timed_out",
2261
+ startedAt,
2262
+ timeoutMs,
2263
+ pollCount: attempts,
2264
+ target,
2265
+ result,
2266
+ });
2267
+ recordWaiterTimedOut(session, "state.node_presence.await", target, attempts, result);
2268
+ return {
2269
+ state: "unavailable",
2270
+ result,
2271
+ error: "Timed out waiting for the requested node presence state.",
2272
+ waiterEvidence,
2273
+ };
2274
+ }
2275
+ async function invokeSignalAwaitEntry(session, inputValue) {
2276
+ const input = toJsonRecord(inputValue);
2277
+ const nodePath = typeof input["nodePath"] === "string" ? input["nodePath"] : "";
2278
+ const signalName = typeof input["signalName"] === "string" ? input["signalName"] : "";
2279
+ const afterCount = clampCount(input["afterCount"], 0);
2280
+ const timeoutMs = clampNumber(input["timeoutMs"], 1500, 100, 5000);
2281
+ const pollIntervalMs = clampNumber(input["pollIntervalMs"], 50, 25, 250);
2282
+ if (nodePath.length === 0 || signalName.length === 0) {
2283
+ return {
2284
+ state: "failed",
2285
+ result: null,
2286
+ error: "state.signal.await requires nodePath and signalName.",
2287
+ waiterEvidence: null,
2288
+ };
2289
+ }
2290
+ const target = {
2291
+ nodePath,
2292
+ signalName,
2293
+ afterCount,
2294
+ };
2295
+ recordWaiterStarted(session, "state.signal.await", target, timeoutMs, pollIntervalMs);
2296
+ recordBridgeEvent(session, "invoke_expanded_leaf_step", {
2297
+ parentEntryId: "state.signal.await",
2298
+ leafEntryId: "state.signal_observation.start",
2299
+ attempt: 1,
2300
+ input: {
2301
+ nodePath,
2302
+ signalName,
2088
2303
  },
2089
- error: "Timed out waiting for the requested node property value.",
2304
+ });
2305
+ const startResponse = await invokeBridgeEntryOverSocket(session.ws, session.sessionId, "state.signal_observation.start", {
2306
+ nodePath,
2307
+ signalName,
2308
+ });
2309
+ recordBridgeEvent(session, "invoke_expanded_leaf_response", {
2310
+ parentEntryId: "state.signal.await",
2311
+ leafEntryId: "state.signal_observation.start",
2312
+ attempt: 1,
2313
+ state: startResponse.state,
2314
+ result: startResponse.result,
2315
+ error: startResponse.error,
2316
+ });
2317
+ if (startResponse.state !== "ok") {
2318
+ return {
2319
+ state: startResponse.state,
2320
+ result: startResponse.result,
2321
+ error: startResponse.error,
2322
+ waiterEvidence: null,
2323
+ };
2324
+ }
2325
+ const startedAt = new Date().toISOString();
2326
+ const startedAtMs = Date.now();
2327
+ let attempts = 0;
2328
+ let lastSnapshot = null;
2329
+ while (Date.now() - startedAtMs <= timeoutMs) {
2330
+ attempts += 1;
2331
+ recordBridgeEvent(session, "invoke_expanded_leaf_step", {
2332
+ parentEntryId: "state.signal.await",
2333
+ leafEntryId: "state.signal_observation.get",
2334
+ attempt: attempts,
2335
+ input: {
2336
+ nodePath,
2337
+ signalName,
2338
+ },
2339
+ });
2340
+ const response = await invokeBridgeEntryOverSocket(session.ws, session.sessionId, "state.signal_observation.get", {
2341
+ nodePath,
2342
+ signalName,
2343
+ });
2344
+ recordBridgeEvent(session, "invoke_expanded_leaf_response", {
2345
+ parentEntryId: "state.signal.await",
2346
+ leafEntryId: "state.signal_observation.get",
2347
+ attempt: attempts,
2348
+ state: response.state,
2349
+ result: response.result,
2350
+ error: response.error,
2351
+ });
2352
+ if (response.state !== "ok") {
2353
+ return {
2354
+ state: response.state,
2355
+ result: response.result,
2356
+ error: response.error,
2357
+ waiterEvidence: null,
2358
+ };
2359
+ }
2360
+ const payload = toJsonRecord(response.result);
2361
+ lastSnapshot = payload;
2362
+ const count = clampCount(payload["count"], 0);
2363
+ if (count > afterCount) {
2364
+ const result = {
2365
+ nodePath,
2366
+ signalName,
2367
+ afterCount,
2368
+ count,
2369
+ lastObservedAt: (payload["lastObservedAt"] ?? null),
2370
+ lastPayload: (payload["lastPayload"] ?? null),
2371
+ attempts,
2372
+ elapsedMs: Date.now() - startedAtMs,
2373
+ };
2374
+ const waiterEvidence = buildWaiterEvidence({
2375
+ waiterId: "state.signal.await",
2376
+ outcome: "satisfied",
2377
+ startedAt,
2378
+ timeoutMs,
2379
+ pollCount: attempts,
2380
+ target,
2381
+ result,
2382
+ });
2383
+ recordWaiterSatisfied(session, "state.signal.await", target, attempts, result);
2384
+ return {
2385
+ state: "ok",
2386
+ result,
2387
+ error: null,
2388
+ waiterEvidence,
2389
+ };
2390
+ }
2391
+ await waitMs(pollIntervalMs);
2392
+ }
2393
+ const result = {
2394
+ nodePath,
2395
+ signalName,
2396
+ afterCount,
2397
+ count: clampCount(lastSnapshot?.["count"], 0),
2398
+ lastObservedAt: (lastSnapshot?.["lastObservedAt"] ?? null),
2399
+ lastPayload: (lastSnapshot?.["lastPayload"] ?? null),
2400
+ attempts,
2401
+ elapsedMs: Date.now() - startedAtMs,
2402
+ };
2403
+ const waiterEvidence = buildWaiterEvidence({
2404
+ waiterId: "state.signal.await",
2405
+ outcome: "timed_out",
2406
+ startedAt,
2407
+ timeoutMs,
2408
+ pollCount: attempts,
2409
+ target,
2410
+ result,
2411
+ });
2412
+ recordWaiterTimedOut(session, "state.signal.await", target, attempts, result);
2413
+ return {
2414
+ state: "unavailable",
2415
+ result,
2416
+ error: "Timed out waiting for the requested signal emission.",
2417
+ waiterEvidence,
2090
2418
  };
2091
2419
  }
2092
2420
  function toJsonRecord(value) {
@@ -2101,6 +2429,12 @@ function clampNumber(value, fallback, minimum, maximum) {
2101
2429
  }
2102
2430
  return Math.max(minimum, Math.min(maximum, Math.trunc(value)));
2103
2431
  }
2432
+ function clampCount(value, fallback) {
2433
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2434
+ return fallback;
2435
+ }
2436
+ return Math.max(0, Math.trunc(value));
2437
+ }
2104
2438
  function jsonValuesEqual(left, right) {
2105
2439
  return JSON.stringify(left) === JSON.stringify(right);
2106
2440
  }
@@ -2125,6 +2459,42 @@ function recordBridgeEvent(session, event, payload) {
2125
2459
  ...payload,
2126
2460
  });
2127
2461
  }
2462
+ function recordWaiterStarted(session, entryId, target, timeoutMs, pollIntervalMs) {
2463
+ recordBridgeEvent(session, "invoke_waiter_started", {
2464
+ entryId,
2465
+ target,
2466
+ timeoutMs,
2467
+ pollIntervalMs,
2468
+ });
2469
+ }
2470
+ function recordWaiterSatisfied(session, entryId, target, attempts, result) {
2471
+ recordBridgeEvent(session, "invoke_waiter_satisfied", {
2472
+ entryId,
2473
+ target,
2474
+ attempts,
2475
+ result,
2476
+ });
2477
+ }
2478
+ function recordWaiterTimedOut(session, entryId, target, attempts, result) {
2479
+ recordBridgeEvent(session, "invoke_waiter_timed_out", {
2480
+ entryId,
2481
+ target,
2482
+ attempts,
2483
+ result,
2484
+ });
2485
+ }
2486
+ function buildWaiterEvidence(input) {
2487
+ return {
2488
+ waiterId: input.waiterId,
2489
+ outcome: input.outcome,
2490
+ startedAt: input.startedAt,
2491
+ finishedAt: new Date().toISOString(),
2492
+ timeoutMs: input.timeoutMs,
2493
+ pollCount: input.pollCount,
2494
+ target: input.target,
2495
+ result: input.result,
2496
+ };
2497
+ }
2128
2498
  function bridgeMissing(reason) {
2129
2499
  return {
2130
2500
  required: true,