@opengsd/gsd-pi 1.5.0-dev.4fb2d460 → 1.5.0-dev.c4c8885f

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 (234) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +114 -36
  3. package/dist/resources/extensions/gsd/activity-log.js +1 -1
  4. package/dist/resources/extensions/gsd/auto/orchestrator.js +16 -10
  5. package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
  6. package/dist/resources/extensions/gsd/auto-dispatch.js +53 -15
  7. package/dist/resources/extensions/gsd/auto-model-selection.js +28 -1
  8. package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
  9. package/dist/resources/extensions/gsd/auto-prompts.js +29 -117
  10. package/dist/resources/extensions/gsd/auto-start.js +53 -30
  11. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +12 -2
  12. package/dist/resources/extensions/gsd/auto-timers.js +9 -2
  13. package/dist/resources/extensions/gsd/auto.js +97 -31
  14. package/dist/resources/extensions/gsd/compat/compat-marker.js +44 -0
  15. package/dist/resources/extensions/gsd/config-overlay.js +7 -2
  16. package/dist/resources/extensions/gsd/db/engine.js +33 -0
  17. package/dist/resources/extensions/gsd/db-adapter.js +30 -3
  18. package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  19. package/dist/resources/extensions/gsd/journal.js +1 -1
  20. package/dist/resources/extensions/gsd/markdown-renderer.js +34 -91
  21. package/dist/resources/extensions/gsd/metrics.js +2 -2
  22. package/dist/resources/extensions/gsd/paths.js +70 -49
  23. package/dist/resources/extensions/gsd/post-unit-hooks.js +27 -0
  24. package/dist/resources/extensions/gsd/preferences-models.js +66 -15
  25. package/dist/resources/extensions/gsd/preferences-validation.js +66 -10
  26. package/dist/resources/extensions/gsd/rule-registry.js +106 -10
  27. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +1 -10
  28. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +3 -24
  29. package/dist/resources/extensions/gsd/state-reconciliation/index.js +10 -0
  30. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -5
  31. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +16 -10
  32. package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -8
  33. package/dist/resources/extensions/gsd/uat-dispatch.js +94 -0
  34. package/dist/resources/extensions/gsd/uok/audit-toggle.js +31 -2
  35. package/dist/resources/extensions/gsd/uok/audit.js +3 -0
  36. package/dist/resources/extensions/gsd/uok/gate-runner.js +22 -30
  37. package/dist/resources/extensions/gsd/uok/gitops.js +18 -15
  38. package/dist/resources/extensions/gsd/uok/kernel.js +120 -46
  39. package/dist/resources/extensions/gsd/uok/model-policy.js +17 -14
  40. package/dist/resources/extensions/gsd/workflow-event-ledger.js +1 -1
  41. package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
  42. package/dist/resources/extensions/gsd/workflow-projections.js +9 -26
  43. package/dist/resources/extensions/gsd/workspace.js +5 -14
  44. package/dist/resources/extensions/shared/gsd-browser-cli.js +29 -0
  45. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  46. package/dist/web/standalone/.next/BUILD_ID +1 -1
  47. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  48. package/dist/web/standalone/.next/build-manifest.json +2 -2
  49. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  50. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  67. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  68. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  69. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  70. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  71. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  72. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  73. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  74. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  75. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  76. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  77. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  78. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  79. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  80. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  81. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  82. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  83. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  84. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  85. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  86. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  87. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  88. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  89. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  90. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  91. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  92. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  93. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  94. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  95. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  96. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  97. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  98. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  99. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  100. package/dist/web/standalone/.next/server/app/index.html +1 -1
  101. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  104. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  108. package/dist/web/standalone/.next/server/chunks/{1128.js → 1991.js} +1 -1
  109. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  110. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  112. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  113. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  114. package/dist/worktree-cli-create.d.ts +10 -0
  115. package/dist/worktree-cli-create.js +11 -0
  116. package/dist/worktree-cli-format.d.ts +12 -0
  117. package/dist/worktree-cli-format.js +38 -0
  118. package/dist/worktree-cli-plan.d.ts +16 -0
  119. package/dist/worktree-cli-plan.js +19 -0
  120. package/dist/worktree-cli-session.d.ts +13 -0
  121. package/dist/worktree-cli-session.js +18 -0
  122. package/dist/worktree-cli-status.d.ts +35 -0
  123. package/dist/worktree-cli-status.js +54 -0
  124. package/dist/worktree-cli.d.ts +1 -16
  125. package/dist/worktree-cli.js +28 -122
  126. package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
  127. package/integrations/hermes/pyproject.toml +1 -1
  128. package/package.json +1 -1
  129. package/packages/cloud-mcp-gateway/package.json +2 -2
  130. package/packages/contracts/package.json +1 -1
  131. package/packages/daemon/dist/orchestrator-agent.d.ts +21 -0
  132. package/packages/daemon/dist/orchestrator-agent.d.ts.map +1 -0
  133. package/packages/daemon/dist/orchestrator-agent.js +113 -0
  134. package/packages/daemon/dist/orchestrator-agent.js.map +1 -0
  135. package/packages/daemon/dist/orchestrator-tools.d.ts +12 -0
  136. package/packages/daemon/dist/orchestrator-tools.d.ts.map +1 -0
  137. package/packages/daemon/dist/orchestrator-tools.js +137 -0
  138. package/packages/daemon/dist/orchestrator-tools.js.map +1 -0
  139. package/packages/daemon/dist/orchestrator.d.ts +4 -56
  140. package/packages/daemon/dist/orchestrator.d.ts.map +1 -1
  141. package/packages/daemon/dist/orchestrator.js +13 -303
  142. package/packages/daemon/dist/orchestrator.js.map +1 -1
  143. package/packages/daemon/dist/orchestrator.test.js +11 -1
  144. package/packages/daemon/dist/orchestrator.test.js.map +1 -1
  145. package/packages/daemon/package.json +4 -4
  146. package/packages/gsd-agent-core/package.json +5 -5
  147. package/packages/gsd-agent-modes/package.json +7 -7
  148. package/packages/mcp-server/package.json +4 -4
  149. package/packages/native/package.json +1 -1
  150. package/packages/pi-agent-core/package.json +1 -1
  151. package/packages/pi-ai/package.json +1 -1
  152. package/packages/pi-coding-agent/package.json +7 -7
  153. package/packages/pi-tui/package.json +2 -2
  154. package/packages/rpc-client/package.json +2 -2
  155. package/pkg/package.json +1 -1
  156. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +146 -33
  157. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +25 -0
  158. package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +93 -0
  159. package/src/resources/extensions/gsd/activity-log.ts +1 -1
  160. package/src/resources/extensions/gsd/auto/orchestrator.ts +17 -11
  161. package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -1
  162. package/src/resources/extensions/gsd/auto-dispatch.ts +59 -14
  163. package/src/resources/extensions/gsd/auto-model-selection.ts +33 -1
  164. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -2
  165. package/src/resources/extensions/gsd/auto-prompts.ts +53 -113
  166. package/src/resources/extensions/gsd/auto-start.ts +57 -28
  167. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -2
  168. package/src/resources/extensions/gsd/auto-timers.ts +9 -2
  169. package/src/resources/extensions/gsd/auto.ts +103 -30
  170. package/src/resources/extensions/gsd/compat/compat-marker.ts +47 -0
  171. package/src/resources/extensions/gsd/config-overlay.ts +7 -1
  172. package/src/resources/extensions/gsd/db/engine.ts +36 -0
  173. package/src/resources/extensions/gsd/db-adapter.ts +29 -3
  174. package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  175. package/src/resources/extensions/gsd/journal.ts +1 -1
  176. package/src/resources/extensions/gsd/markdown-renderer.ts +52 -103
  177. package/src/resources/extensions/gsd/metrics.ts +2 -2
  178. package/src/resources/extensions/gsd/paths.ts +87 -48
  179. package/src/resources/extensions/gsd/post-unit-hooks.ts +32 -0
  180. package/src/resources/extensions/gsd/preferences-models.ts +74 -20
  181. package/src/resources/extensions/gsd/preferences-types.ts +24 -1
  182. package/src/resources/extensions/gsd/preferences-validation.ts +77 -9
  183. package/src/resources/extensions/gsd/rule-registry.ts +111 -9
  184. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +1 -9
  185. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +2 -22
  186. package/src/resources/extensions/gsd/state-reconciliation/index.ts +14 -0
  187. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +41 -2
  188. package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +48 -1
  189. package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +29 -1
  190. package/src/resources/extensions/gsd/tests/auto-timeout-recovery-supervisor-inflight-guard.test.ts +159 -0
  191. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +3 -1
  192. package/src/resources/extensions/gsd/tests/compat-marker.test.ts +85 -0
  193. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +45 -0
  194. package/src/resources/extensions/gsd/tests/db-adapter.test.ts +39 -0
  195. package/src/resources/extensions/gsd/tests/db-writable-probe.test.ts +66 -0
  196. package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +22 -1
  197. package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +72 -32
  198. package/src/resources/extensions/gsd/tests/integration/paths.test.ts +88 -1
  199. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +2 -1
  200. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
  201. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +269 -0
  202. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +8 -1
  203. package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +54 -0
  204. package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +49 -1
  205. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +95 -1
  206. package/src/resources/extensions/gsd/tests/sibling-model-fallbacks.test.ts +189 -0
  207. package/src/resources/extensions/gsd/tests/single-field-model-thinking.test.ts +156 -0
  208. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +6 -1
  209. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +32 -0
  210. package/src/resources/extensions/gsd/tests/supervisor-model-swap-inflight-guard.test.ts +181 -0
  211. package/src/resources/extensions/gsd/tests/supervisor-model-swap-thinking-clamp.test.ts +122 -0
  212. package/src/resources/extensions/gsd/tests/uat-dispatch.test.ts +211 -0
  213. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +39 -0
  214. package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +226 -2
  215. package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +6 -0
  216. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +213 -2
  217. package/src/resources/extensions/gsd/tools/complete-milestone.ts +9 -5
  218. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +19 -8
  219. package/src/resources/extensions/gsd/tools/validate-milestone.ts +13 -9
  220. package/src/resources/extensions/gsd/types.ts +23 -4
  221. package/src/resources/extensions/gsd/uat-dispatch.ts +176 -0
  222. package/src/resources/extensions/gsd/uok/audit-toggle.ts +32 -2
  223. package/src/resources/extensions/gsd/uok/audit.ts +2 -0
  224. package/src/resources/extensions/gsd/uok/gate-runner.ts +26 -36
  225. package/src/resources/extensions/gsd/uok/gitops.ts +21 -18
  226. package/src/resources/extensions/gsd/uok/kernel.ts +169 -48
  227. package/src/resources/extensions/gsd/uok/model-policy.ts +20 -17
  228. package/src/resources/extensions/gsd/workflow-event-ledger.ts +1 -1
  229. package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
  230. package/src/resources/extensions/gsd/workflow-projections.ts +9 -24
  231. package/src/resources/extensions/gsd/workspace.ts +5 -21
  232. package/src/resources/extensions/shared/gsd-browser-cli.ts +29 -0
  233. /package/dist/web/standalone/.next/static/{gXoWec4UNqcR0tkuHwYCr → NPMNi2HV8BuK3baQsEUhk}/_buildManifest.js +0 -0
  234. /package/dist/web/standalone/.next/static/{gXoWec4UNqcR0tkuHwYCr → NPMNi2HV8BuK3baQsEUhk}/_ssgManifest.js +0 -0
@@ -2,6 +2,11 @@
2
2
  // Types consumed by state derivation, file parsing, and status display.
3
3
  // Pure interfaces — no logic, no runtime dependencies.
4
4
 
5
+ // Type-only import (erased at compile time — no runtime dependency): sibling
6
+ // single-model fields share the phase-bucket object form so they honor
7
+ // `fallbacks[]` (#1229).
8
+ import type { GSDPhaseModelConfig } from "./preferences-types.js";
9
+
5
10
  // ─── Enums & Literal Unions ────────────────────────────────────────────────
6
11
 
7
12
  export type RiskLevel = "low" | "medium" | "high";
@@ -303,8 +308,13 @@ export interface PostUnitHookConfig {
303
308
  prompt: string;
304
309
  /** Max times this hook can fire for the same trigger unit. Default 1, max 10. */
305
310
  max_cycles?: number;
306
- /** Model override for hook sessions. */
307
- model?: string;
311
+ /**
312
+ * Model override for hook sessions. Accepts a bare model ID or the extended
313
+ * `{ model, provider?, fallbacks? }` object form so a transient trip of the
314
+ * primary provider falls back to the next entry instead of hard-failing the
315
+ * hook (#1229).
316
+ */
317
+ model?: string | GSDPhaseModelConfig;
308
318
  /** Expected output file name (relative to task/slice dir). Used for idempotency — skip if exists. */
309
319
  artifact?: string;
310
320
  /** Whether the hook is advisory or blocks unit advancement. Default advisory. */
@@ -505,6 +515,11 @@ export interface PreDispatchResult {
505
515
  export interface PersistedHookState {
506
516
  /** Cycle counts keyed as "hookName/triggerUnitType/triggerUnitId". */
507
517
  cycleCounts: Record<string, number>;
518
+ /**
519
+ * Cycle keys already granted a one-shot re-dispatch after a lost/interrupted
520
+ * dispatch, so the refund is bounded to one per gate across resumes.
521
+ */
522
+ redispatchedGateKeys?: string[];
508
523
  /** In-flight hook, persisted so blocking gates cannot be skipped after resume. */
509
524
  activeHook?: HookExecutionState | null;
510
525
  /** Remaining hook queue by hook name and trigger unit. */
@@ -607,8 +622,12 @@ export interface ReactiveExecutionConfig {
607
622
  max_parallel: number;
608
623
  /** Isolation mode for parallel tasks within a slice. Currently only "same-tree" is supported. */
609
624
  isolation_mode: "same-tree";
610
- /** Optional model override for subagents spawned during parallel execution. */
611
- subagent_model?: string;
625
+ /**
626
+ * Optional model override for subagents spawned during parallel execution.
627
+ * Accepts a bare model ID or the extended `{ model, provider?, fallbacks? }`
628
+ * object form for parity with phase buckets (#1229).
629
+ */
630
+ subagent_model?: string | GSDPhaseModelConfig;
612
631
  }
613
632
 
614
633
  /** Per-slice reactive execution runtime state, persisted to disk. */
@@ -0,0 +1,176 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Resolve whether a completed slice needs run-uat dispatch.
3
+
4
+ import { loadFile } from "./files.js";
5
+ import type { GSDPreferences } from "./preferences.js";
6
+ import { resolveSliceFile } from "./paths.js";
7
+ import {
8
+ classifyUatContentForRun,
9
+ shouldDispatchUatForContent,
10
+ type UatType,
11
+ } from "./uat-policy.js";
12
+ import { hasVerdict } from "./verdict-parser.js";
13
+ import { logWarning } from "./workflow-logger.js";
14
+
15
+ export interface UatDispatchCandidate {
16
+ sliceId: string;
17
+ }
18
+
19
+ export type RunUatDispatch = { sliceId: string; uatType: UatType };
20
+
21
+ async function loadSliceFileContent(
22
+ base: string,
23
+ milestoneId: string,
24
+ sliceId: string,
25
+ kind: "UAT" | "ASSESSMENT" | "SUMMARY",
26
+ ): Promise<string> {
27
+ const filePath = resolveSliceFile(base, milestoneId, sliceId, kind);
28
+ return filePath ? ((await loadFile(filePath)) ?? "") : "";
29
+ }
30
+
31
+ async function resolveRunUatEffectiveType(
32
+ base: string,
33
+ milestoneId: string,
34
+ sliceId: string,
35
+ uatContent: string,
36
+ ): Promise<UatType> {
37
+ let summaryContent = "";
38
+ try {
39
+ summaryContent = await loadSliceFileContent(
40
+ base,
41
+ milestoneId,
42
+ sliceId,
43
+ "SUMMARY",
44
+ );
45
+ } catch (err) {
46
+ logWarning(
47
+ "prompt",
48
+ `resolveRunUatEffectiveType SUMMARY load failed: ${err instanceof Error ? err.message : String(err)}`,
49
+ );
50
+ }
51
+ return classifyUatContentForRun(uatContent, summaryContent).effectiveType;
52
+ }
53
+
54
+ async function resolveCandidateRunUatDispatch(
55
+ base: string,
56
+ milestoneId: string,
57
+ candidate: UatDispatchCandidate,
58
+ prefs: GSDPreferences | undefined,
59
+ ): Promise<RunUatDispatch | null> {
60
+ const uatContent = await loadSliceFileContent(
61
+ base,
62
+ milestoneId,
63
+ candidate.sliceId,
64
+ "UAT",
65
+ );
66
+ if (!uatContent) return null;
67
+ if (hasVerdict(uatContent)) return null;
68
+
69
+ const assessmentContent = await loadSliceFileContent(
70
+ base,
71
+ milestoneId,
72
+ candidate.sliceId,
73
+ "ASSESSMENT",
74
+ );
75
+ if (assessmentContent && hasVerdict(assessmentContent)) return null;
76
+ if (!shouldDispatchUatForContent(uatContent, prefs)) return null;
77
+
78
+ return {
79
+ sliceId: candidate.sliceId,
80
+ uatType: await resolveRunUatEffectiveType(
81
+ base,
82
+ milestoneId,
83
+ candidate.sliceId,
84
+ uatContent,
85
+ ),
86
+ };
87
+ }
88
+
89
+ async function getDbCompletedSliceCandidates(
90
+ milestoneId: string,
91
+ ): Promise<UatDispatchCandidate[] | null> {
92
+ const { isDbAvailable, getMilestoneSlices } = await import("./gsd-db.js");
93
+ if (!isDbAvailable()) return null;
94
+
95
+ const slices = getMilestoneSlices(milestoneId);
96
+ // No DB slice rows for this milestone: the DB has no authoritative view, so
97
+ // return null to let the caller defer to the roadmap fallback.
98
+ if (slices.length === 0) return null;
99
+
100
+ // The DB has slice rows and is therefore authoritative for this milestone.
101
+ // Return a (possibly empty) array rather than null: an empty result must NOT
102
+ // fall through to the roadmap fallback, because the DB, not stale roadmap
103
+ // checkboxes, is the source of truth for which slices are complete.
104
+ return slices
105
+ .filter((slice) => slice.status === "complete")
106
+ .map((slice) => ({ sliceId: slice.id }))
107
+ .reverse();
108
+ }
109
+
110
+ export async function findRunUatDispatchFromCandidates(
111
+ base: string,
112
+ milestoneId: string,
113
+ candidates: readonly UatDispatchCandidate[],
114
+ prefs: GSDPreferences | undefined,
115
+ ): Promise<RunUatDispatch | null> {
116
+ for (const candidate of candidates) {
117
+ const dispatch = await resolveCandidateRunUatDispatch(
118
+ base,
119
+ milestoneId,
120
+ candidate,
121
+ prefs,
122
+ );
123
+ if (dispatch) return dispatch;
124
+ }
125
+ return null;
126
+ }
127
+
128
+ /**
129
+ * Check if the most recently completed slice needs a UAT run.
130
+ * Returns { sliceId, uatType } if UAT should be dispatched, null otherwise.
131
+ *
132
+ * When the DB has slice rows for the milestone it is authoritative: only its
133
+ * completed slices are considered and the `fallbackCandidates` (roadmap-derived)
134
+ * are ignored, even when no DB slice is complete. The fallback candidates are
135
+ * only consulted when the DB has no slice rows for the milestone, is
136
+ * unavailable, or errors.
137
+ *
138
+ * Skips when:
139
+ * - No completed slices exist in DB or the caller-provided fallback candidates
140
+ * - uat_dispatch is not enabled and the UAT spec does not require runtime/browser evidence
141
+ * - No UAT file exists for the slice
142
+ * - UAT result already exists in the UAT or ASSESSMENT file
143
+ */
144
+ export async function checkNeedsRunUat(
145
+ base: string,
146
+ milestoneId: string,
147
+ prefs: GSDPreferences | undefined,
148
+ fallbackCandidates: readonly UatDispatchCandidate[] = [],
149
+ ): Promise<RunUatDispatch | null> {
150
+ try {
151
+ const dbCandidates = await getDbCompletedSliceCandidates(milestoneId);
152
+ // A non-null result (including an empty array) means the DB is authoritative
153
+ // for this milestone; only fall through to the roadmap fallback when the DB
154
+ // has no slice rows at all (null).
155
+ if (dbCandidates !== null) {
156
+ return findRunUatDispatchFromCandidates(
157
+ base,
158
+ milestoneId,
159
+ dbCandidates,
160
+ prefs,
161
+ );
162
+ }
163
+ } catch (err) {
164
+ logWarning(
165
+ "prompt",
166
+ `checkNeedsRunUat DB lookup failed: ${err instanceof Error ? err.message : String(err)}`,
167
+ );
168
+ }
169
+
170
+ return findRunUatDispatchFromCandidates(
171
+ base,
172
+ milestoneId,
173
+ fallbackCandidates,
174
+ prefs,
175
+ );
176
+ }
@@ -1,9 +1,39 @@
1
1
  const AUDIT_ENV_KEY = "GSD_UOK_AUDIT_UNIFIED";
2
+ const suppressedBasePaths = new Set<string>();
2
3
 
3
4
  export function setUnifiedAuditEnabled(enabled: boolean): void {
4
5
  process.env[AUDIT_ENV_KEY] = enabled ? "1" : "0";
5
6
  }
6
7
 
7
- export function isUnifiedAuditEnabled(): boolean {
8
- return process.env[AUDIT_ENV_KEY] === "1";
8
+ export function setUnifiedAuditSuppressedForBasePath(basePath: string, suppressed: boolean): void {
9
+ if (suppressed) {
10
+ suppressedBasePaths.add(basePath);
11
+ return;
12
+ }
13
+ suppressedBasePaths.delete(basePath);
14
+ }
15
+
16
+ export function isUnifiedAuditEnabled(basePath?: string): boolean {
17
+ if (basePath && suppressedBasePaths.has(basePath)) return false;
18
+ return process.env[AUDIT_ENV_KEY] !== "0";
19
+ }
20
+
21
+ export function getUnifiedAuditOverride(): boolean | undefined {
22
+ const raw = process.env[AUDIT_ENV_KEY];
23
+ if (raw === "0") return false;
24
+ if (raw === "1") return true;
25
+ return undefined;
26
+ }
27
+
28
+ export function restoreUnifiedAuditOverride(override: boolean | undefined): void {
29
+ if (override === undefined) {
30
+ delete process.env[AUDIT_ENV_KEY];
31
+ return;
32
+ }
33
+ setUnifiedAuditEnabled(override);
34
+ }
35
+
36
+ export function clearUnifiedAuditOverrideForTests(): void {
37
+ delete process.env[AUDIT_ENV_KEY];
38
+ suppressedBasePaths.clear();
9
39
  }
@@ -9,6 +9,7 @@ import { withFileLockSync } from "../file-lock.js";
9
9
  import { gsdRoot } from "../paths.js";
10
10
  import { isDbAvailable, insertAuditEvent } from "../gsd-db.js";
11
11
  import { CURRENT_UOK_CONTRACT_VERSION, validateAuditEvent, type AuditEventEnvelope } from "./contracts.js";
12
+ import { isUnifiedAuditEnabled } from "./audit-toggle.js";
12
13
 
13
14
  function auditLogPath(basePath: string): string {
14
15
  return join(gsdRoot(basePath), "audit", "events.jsonl");
@@ -42,6 +43,7 @@ export function buildAuditEnvelope(args: {
42
43
  export function emitUokAuditEvent(basePath: string, event: AuditEventEnvelope): void {
43
44
  // Drop writes from a turn superseded by timeout recovery / cancellation.
44
45
  if (isStaleWrite("uok-audit")) return;
46
+ if (!isUnifiedAuditEnabled(basePath)) return;
45
47
  const validation = validateAuditEvent(event);
46
48
  if (!validation.ok) {
47
49
  throw new Error(`Invalid UOK audit event: ${validation.issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`);
@@ -1,6 +1,7 @@
1
1
  import type { FailureClass, GateResult } from "./contracts.js";
2
2
  import { insertGateRun } from "../gsd-db.js";
3
3
  import { buildAuditEnvelope, emitUokAuditEvent } from "./audit.js";
4
+ import { isUnifiedAuditEnabled } from "./audit-toggle.js";
4
5
 
5
6
  export interface GateRunnerContext {
6
7
  basePath: string;
@@ -38,6 +39,29 @@ const RETRY_MATRIX: Record<FailureClass, number> = {
38
39
  unknown: 0,
39
40
  };
40
41
 
42
+ function emitGateAuditIfEnabled(ctx: GateRunnerContext, result: GateResult): void {
43
+ if (!isUnifiedAuditEnabled(ctx.basePath)) return;
44
+
45
+ emitUokAuditEvent(
46
+ ctx.basePath,
47
+ buildAuditEnvelope({
48
+ traceId: ctx.traceId,
49
+ turnId: ctx.turnId,
50
+ category: "gate",
51
+ type: "gate-run",
52
+ payload: {
53
+ gateId: result.gateId,
54
+ gateType: result.gateType,
55
+ outcome: result.outcome,
56
+ failureClass: result.failureClass,
57
+ attempt: result.attempt,
58
+ maxAttempts: result.maxAttempts,
59
+ retryable: result.retryable,
60
+ },
61
+ }),
62
+ );
63
+ }
64
+
41
65
  export class UokGateRunner {
42
66
  private readonly registry = new Map<string, GateExecutionInput>();
43
67
 
@@ -85,24 +109,7 @@ export class UokGateRunner {
85
109
  evaluatedAt: unknownResult.evaluatedAt,
86
110
  });
87
111
 
88
- emitUokAuditEvent(
89
- ctx.basePath,
90
- buildAuditEnvelope({
91
- traceId: ctx.traceId,
92
- turnId: ctx.turnId,
93
- category: "gate",
94
- type: "gate-run",
95
- payload: {
96
- gateId: unknownResult.gateId,
97
- gateType: unknownResult.gateType,
98
- outcome: unknownResult.outcome,
99
- failureClass: unknownResult.failureClass,
100
- attempt: unknownResult.attempt,
101
- maxAttempts: unknownResult.maxAttempts,
102
- retryable: unknownResult.retryable,
103
- },
104
- }),
105
- );
112
+ emitGateAuditIfEnabled(ctx, unknownResult);
106
113
 
107
114
  return unknownResult;
108
115
  }
@@ -170,24 +177,7 @@ export class UokGateRunner {
170
177
  evaluatedAt: final.evaluatedAt,
171
178
  });
172
179
 
173
- emitUokAuditEvent(
174
- ctx.basePath,
175
- buildAuditEnvelope({
176
- traceId: ctx.traceId,
177
- turnId: ctx.turnId,
178
- category: "gate",
179
- type: "gate-run",
180
- payload: {
181
- gateId: final.gateId,
182
- gateType: final.gateType,
183
- outcome: final.outcome,
184
- failureClass: final.failureClass,
185
- attempt: final.attempt,
186
- maxAttempts: final.maxAttempts,
187
- retryable: final.retryable,
188
- },
189
- }),
190
- );
180
+ emitGateAuditIfEnabled(ctx, final);
191
181
 
192
182
  if (!retryable) break;
193
183
  }
@@ -1,6 +1,7 @@
1
1
  import { isDbAvailable, upsertTurnGitTransaction } from "../gsd-db.js";
2
2
  import type { TurnCloseoutRecord } from "./contracts.js";
3
3
  import { buildAuditEnvelope, emitUokAuditEvent } from "./audit.js";
4
+ import { isUnifiedAuditEnabled } from "./audit-toggle.js";
4
5
 
5
6
  export type TurnGitStage = "turn-start" | "stage" | "checkpoint" | "publish" | "record";
6
7
 
@@ -34,24 +35,26 @@ export function writeTurnGitTransaction(args: GitTxArgs): void {
34
35
  updatedAt: new Date().toISOString(),
35
36
  });
36
37
 
37
- emitUokAuditEvent(
38
- args.basePath,
39
- buildAuditEnvelope({
40
- traceId: args.traceId,
41
- turnId: args.turnId,
42
- category: "gitops",
43
- type: `turn-git-${args.stage}`,
44
- payload: {
45
- unitType: args.unitType,
46
- unitId: args.unitId,
47
- action: args.action,
48
- push: args.push,
49
- status: args.status,
50
- error: args.error,
51
- ...(args.metadata ?? {}),
52
- },
53
- }),
54
- );
38
+ if (isUnifiedAuditEnabled(args.basePath)) {
39
+ emitUokAuditEvent(
40
+ args.basePath,
41
+ buildAuditEnvelope({
42
+ traceId: args.traceId,
43
+ turnId: args.turnId,
44
+ category: "gitops",
45
+ type: `turn-git-${args.stage}`,
46
+ payload: {
47
+ unitType: args.unitType,
48
+ unitId: args.unitId,
49
+ action: args.action,
50
+ push: args.push,
51
+ status: args.status,
52
+ error: args.error,
53
+ ...(args.metadata ?? {}),
54
+ },
55
+ }),
56
+ );
57
+ }
55
58
  }
56
59
 
57
60
  export function writeTurnCloseoutGitRecord(
@@ -8,10 +8,16 @@ import type { AutoSession } from "../auto/session.js";
8
8
  import type { LoopDeps } from "../auto/loop-deps.js";
9
9
  import { gsdRoot } from "../paths.js";
10
10
  import { buildAuditEnvelope, emitUokAuditEvent } from "./audit.js";
11
- import { setUnifiedAuditEnabled } from "./audit-toggle.js";
12
- import { resolveUokFlags } from "./flags.js";
11
+ import {
12
+ getUnifiedAuditOverride,
13
+ restoreUnifiedAuditOverride,
14
+ setUnifiedAuditEnabled,
15
+ setUnifiedAuditSuppressedForBasePath,
16
+ } from "./audit-toggle.js";
17
+ import { resolveUokFlags, type UokFlags } from "./flags.js";
13
18
  import { createTurnObserver } from "./loop-adapter.js";
14
19
  import { incrementLegacyTelemetry } from "../legacy-telemetry.js";
20
+ import { logWarning } from "../workflow-logger.js";
15
21
 
16
22
  interface RunAutoLoopWithUokArgs {
17
23
  ctx: ExtensionContext;
@@ -45,80 +51,195 @@ function writeParityEvent(basePath: string, event: Record<string, unknown>): voi
45
51
  }
46
52
  }
47
53
 
54
+ type UokKernelPathLabel = "uok-kernel" | "legacy-wrapper" | "legacy-fallback";
55
+
56
+ interface UokKernelRunPlan {
57
+ flags: UokFlags;
58
+ pathLabel: UokKernelPathLabel;
59
+ traceId: string;
60
+ useKernel: boolean;
61
+ }
62
+
48
63
  function resolveKernelPathLabel(
49
- flags: ReturnType<typeof resolveUokFlags>,
50
- ): "uok-kernel" | "legacy-wrapper" | "legacy-fallback" {
64
+ flags: UokFlags,
65
+ ): UokKernelPathLabel {
51
66
  if (flags.legacyFallback) return "legacy-fallback";
52
67
  return flags.enabled ? "uok-kernel" : "legacy-wrapper";
53
68
  }
54
69
 
55
- export async function runAutoLoopWithUok(args: RunAutoLoopWithUokArgs): Promise<void> {
56
- const { ctx, pi, s, deps, runKernelLoop, runLegacyLoop } = args;
57
- const prefs = deps.loadEffectiveGSDPreferences()?.preferences;
58
- const flags = resolveUokFlags(prefs);
59
- setUnifiedAuditEnabled(flags.auditUnified);
70
+ function createUokKernelRunPlan(input: {
71
+ preferences: ReturnType<LoopDeps["loadEffectiveGSDPreferences"]> | undefined;
72
+ autoStartTime?: unknown;
73
+ }): UokKernelRunPlan {
74
+ const flags = resolveUokFlags(input.preferences?.preferences);
60
75
  const pathLabel = resolveKernelPathLabel(flags);
61
- if (pathLabel !== "uok-kernel") {
62
- incrementLegacyTelemetry("legacy.uokFallbackUsed");
63
- }
64
-
65
- writeParityEvent(s.basePath, {
66
- ts: new Date().toISOString(),
67
- path: pathLabel,
76
+ return {
68
77
  flags,
69
- phase: "enter",
70
- });
78
+ pathLabel,
79
+ traceId: `session:${String(input.autoStartTime || Date.now())}`,
80
+ useKernel: flags.enabled,
81
+ };
82
+ }
83
+
84
+ interface KernelAuditStateController {
85
+ apply(enabled: boolean): void;
86
+ restore(): void;
87
+ }
88
+
89
+ function createKernelAuditStateController(basePath: string): KernelAuditStateController {
90
+ const previousAuditOverride = getUnifiedAuditOverride();
91
+ return {
92
+ apply(enabled): void {
93
+ setUnifiedAuditSuppressedForBasePath(basePath, !enabled);
94
+ setUnifiedAuditEnabled(enabled);
95
+ },
96
+ restore(): void {
97
+ restoreUnifiedAuditOverride(previousAuditOverride);
98
+ },
99
+ };
100
+ }
71
101
 
72
- if (flags.auditUnified) {
102
+ function emitKernelEnterAudit(input: {
103
+ basePath: string;
104
+ plan: UokKernelRunPlan;
105
+ sessionId?: string;
106
+ }): boolean {
107
+ if (!input.plan.flags.auditUnified) return true;
108
+
109
+ try {
73
110
  emitUokAuditEvent(
74
- s.basePath,
111
+ input.basePath,
75
112
  buildAuditEnvelope({
76
- traceId: `session:${String(s.autoStartTime || Date.now())}`,
113
+ traceId: input.plan.traceId,
77
114
  category: "orchestration",
78
115
  type: "uok-kernel-enter",
79
116
  payload: {
80
- flags,
81
- sessionId: ctx.sessionManager?.getSessionId?.(),
117
+ flags: input.plan.flags,
118
+ sessionId: input.sessionId,
82
119
  },
83
120
  }),
84
121
  );
122
+ return true;
123
+ } catch (err) {
124
+ const errorMessage = err instanceof Error ? err.message : String(err);
125
+ writeParityEvent(input.basePath, {
126
+ ts: new Date().toISOString(),
127
+ path: input.plan.pathLabel,
128
+ flags: input.plan.flags,
129
+ phase: "telemetry-error",
130
+ telemetry: "uok-kernel-enter",
131
+ error: errorMessage,
132
+ });
133
+ logWarning("db", `uok-kernel-enter audit emit failed (non-fatal): ${errorMessage}`);
134
+ return false;
85
135
  }
136
+ }
86
137
 
87
- const decoratedDeps: LoopDeps = flags.enabled
88
- ? {
89
- ...deps,
90
- uokObserver: createTurnObserver({
91
- basePath: s.basePath,
92
- gitAction: flags.gitopsTurnAction,
93
- gitPush: flags.gitopsTurnPush,
94
- enableAudit: flags.auditUnified,
95
- enableGitops: flags.gitops,
96
- }),
97
- }
98
- : deps;
138
+ function decorateLoopDepsForKernel(input: {
139
+ deps: LoopDeps;
140
+ basePath: string;
141
+ plan: UokKernelRunPlan;
142
+ auditHealthy: boolean;
143
+ }): LoopDeps {
144
+ if (!input.plan.useKernel) return input.deps;
145
+
146
+ return {
147
+ ...input.deps,
148
+ uokObserver: createTurnObserver({
149
+ basePath: input.basePath,
150
+ gitAction: input.plan.flags.gitopsTurnAction,
151
+ gitPush: input.plan.flags.gitopsTurnPush,
152
+ enableAudit: input.plan.flags.auditUnified && input.auditHealthy,
153
+ enableGitops: input.plan.flags.gitops,
154
+ }),
155
+ };
156
+ }
157
+
158
+ async function executeKernelRunPlan(input: {
159
+ plan: UokKernelRunPlan;
160
+ ctx: ExtensionContext;
161
+ pi: ExtensionAPI;
162
+ s: AutoSession;
163
+ deps: LoopDeps;
164
+ kernelDeps: LoopDeps;
165
+ runKernelLoop: RunAutoLoopWithUokArgs["runKernelLoop"];
166
+ runLegacyLoop: RunAutoLoopWithUokArgs["runLegacyLoop"];
167
+ }): Promise<void> {
168
+ if (input.plan.useKernel) {
169
+ await input.runKernelLoop(input.ctx, input.pi, input.s, input.kernelDeps);
170
+ return;
171
+ }
172
+ await input.runLegacyLoop(input.ctx, input.pi, input.s, input.deps);
173
+ }
174
+
175
+ export async function runAutoLoopWithUok(args: RunAutoLoopWithUokArgs): Promise<void> {
176
+ const { ctx, pi, s, deps, runKernelLoop, runLegacyLoop } = args;
177
+ const auditState = createKernelAuditStateController(s.basePath);
178
+ let plan: UokKernelRunPlan | null = null;
99
179
 
100
180
  try {
101
- if (flags.enabled) {
102
- await runKernelLoop(ctx, pi, s, decoratedDeps);
103
- } else {
104
- await runLegacyLoop(ctx, pi, s, deps);
181
+ plan = createUokKernelRunPlan({
182
+ preferences: deps.loadEffectiveGSDPreferences(),
183
+ autoStartTime: s.autoStartTime,
184
+ });
185
+ auditState.apply(plan.flags.auditUnified);
186
+
187
+ if (plan.pathLabel !== "uok-kernel") {
188
+ incrementLegacyTelemetry("legacy.uokFallbackUsed");
105
189
  }
190
+
106
191
  writeParityEvent(s.basePath, {
107
192
  ts: new Date().toISOString(),
108
- path: pathLabel,
109
- flags,
110
- phase: "exit",
111
- status: "ok",
193
+ path: plan.pathLabel,
194
+ flags: plan.flags,
195
+ phase: "enter",
112
196
  });
113
- } catch (err) {
197
+
198
+ const auditHealthy = emitKernelEnterAudit({
199
+ basePath: s.basePath,
200
+ plan,
201
+ sessionId: ctx.sessionManager?.getSessionId?.(),
202
+ });
203
+ auditState.apply(plan.flags.auditUnified && auditHealthy);
204
+
205
+ const kernelDeps = decorateLoopDepsForKernel({
206
+ deps,
207
+ basePath: s.basePath,
208
+ plan,
209
+ auditHealthy,
210
+ });
211
+
212
+ await executeKernelRunPlan({
213
+ plan,
214
+ ctx,
215
+ pi,
216
+ s,
217
+ deps,
218
+ kernelDeps,
219
+ runKernelLoop,
220
+ runLegacyLoop,
221
+ });
222
+
114
223
  writeParityEvent(s.basePath, {
115
224
  ts: new Date().toISOString(),
116
- path: pathLabel,
117
- flags,
225
+ path: plan.pathLabel,
226
+ flags: plan.flags,
118
227
  phase: "exit",
119
- status: "error",
120
- error: err instanceof Error ? err.message : String(err),
228
+ status: "ok",
121
229
  });
230
+ } catch (err) {
231
+ if (plan) {
232
+ writeParityEvent(s.basePath, {
233
+ ts: new Date().toISOString(),
234
+ path: plan.pathLabel,
235
+ flags: plan.flags,
236
+ phase: "exit",
237
+ status: "error",
238
+ error: err instanceof Error ? err.message : String(err),
239
+ });
240
+ }
122
241
  throw err;
242
+ } finally {
243
+ auditState.restore();
123
244
  }
124
245
  }