@opengsd/gsd-pi 1.9.0-dev.e10b60d4 → 1.10.0-dev.a5e42e99

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 (251) hide show
  1. package/README.md +10 -9
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/loop.js +24 -4
  4. package/dist/resources/extensions/gsd/auto/run-unit.js +19 -1
  5. package/dist/resources/extensions/gsd/auto-model-selection.js +1 -0
  6. package/dist/resources/extensions/gsd/auto-start.js +30 -10
  7. package/dist/resources/extensions/gsd/auto-verification.js +25 -12
  8. package/dist/resources/extensions/gsd/auto-worktree-sync.js +7 -3
  9. package/dist/resources/extensions/gsd/auto.js +12 -1
  10. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +4 -3
  11. package/dist/resources/extensions/gsd/docs/preferences-reference.md +12 -3
  12. package/dist/resources/extensions/gsd/doctor-engine-checks.js +37 -6
  13. package/dist/resources/extensions/gsd/flat-phase-migration.js +12 -0
  14. package/dist/resources/extensions/gsd/gsd-db.js +75 -31
  15. package/dist/resources/extensions/gsd/model-cost-table.js +6 -3
  16. package/dist/resources/extensions/gsd/model-router.js +10 -5
  17. package/dist/resources/extensions/gsd/planning-subagent-registry.js +12 -4
  18. package/dist/resources/extensions/gsd/preferences-models.js +50 -10
  19. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  20. package/dist/resources/extensions/gsd/preferences-validation.js +41 -6
  21. package/dist/resources/extensions/gsd/preferences.js +7 -0
  22. package/dist/resources/extensions/gsd/templates/PREFERENCES.md +3 -0
  23. package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -1
  24. package/dist/resources/extensions/gsd/verification-evidence.js +5 -3
  25. package/dist/resources/extensions/gsd/worktree-state-projection.js +20 -18
  26. package/dist/resources/extensions/ollama/ollama-chat-provider.js +2 -2
  27. package/dist/resources/extensions/subagent/index.js +1 -1
  28. package/dist/startup-model-validation.d.ts +1 -1
  29. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  30. package/dist/web/standalone/.next/BUILD_ID +1 -1
  31. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  32. package/dist/web/standalone/.next/build-manifest.json +2 -2
  33. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  34. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/index.html +1 -1
  51. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  58. package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
  59. package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
  60. package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
  61. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  63. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  64. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  65. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  66. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  67. package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
  68. package/integrations/hermes/pyproject.toml +1 -1
  69. package/package.json +7 -6
  70. package/packages/cloud-mcp-gateway/package.json +2 -2
  71. package/packages/contracts/dist/rpc.d.ts +1 -1
  72. package/packages/contracts/dist/rpc.d.ts.map +1 -1
  73. package/packages/contracts/dist/rpc.js +1 -1
  74. package/packages/contracts/dist/rpc.js.map +1 -1
  75. package/packages/contracts/dist/rpc.test.js +1 -1
  76. package/packages/contracts/dist/rpc.test.js.map +1 -1
  77. package/packages/contracts/package.json +1 -1
  78. package/packages/daemon/package.json +4 -4
  79. package/packages/gsd-agent-core/package.json +5 -5
  80. package/packages/gsd-agent-modes/dist/cli/args.js +2 -2
  81. package/packages/gsd-agent-modes/dist/cli/args.js.map +1 -1
  82. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  83. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +1 -0
  84. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
  85. package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  86. package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.js +1 -0
  87. package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  88. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.d.ts.map +1 -1
  89. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.js +1 -0
  90. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
  91. package/packages/gsd-agent-modes/package.json +7 -7
  92. package/packages/gsd-cloud/README.md +21 -5
  93. package/packages/gsd-cloud/dist/background-cli.test.d.ts +2 -0
  94. package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +1 -0
  95. package/packages/gsd-cloud/dist/background-cli.test.js +325 -0
  96. package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -0
  97. package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
  98. package/packages/gsd-cloud/dist/cli.js +146 -16
  99. package/packages/gsd-cloud/dist/cli.js.map +1 -1
  100. package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
  101. package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
  102. package/packages/gsd-cloud/dist/cloud-config.js +7 -1
  103. package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
  104. package/packages/gsd-cloud/dist/cloud-runtime.d.ts +7 -4
  105. package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
  106. package/packages/gsd-cloud/dist/cloud-runtime.js +115 -31
  107. package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
  108. package/packages/gsd-cloud/dist/cloud-runtime.test.js +224 -59
  109. package/packages/gsd-cloud/dist/cloud-runtime.test.js.map +1 -1
  110. package/packages/gsd-cloud/dist/runtime-artifacts.d.ts +3 -0
  111. package/packages/gsd-cloud/dist/runtime-artifacts.d.ts.map +1 -0
  112. package/packages/gsd-cloud/dist/runtime-artifacts.js +36 -0
  113. package/packages/gsd-cloud/dist/runtime-artifacts.js.map +1 -0
  114. package/packages/gsd-cloud/dist/runtime-process.d.ts +32 -0
  115. package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -0
  116. package/packages/gsd-cloud/dist/runtime-process.js +640 -0
  117. package/packages/gsd-cloud/dist/runtime-process.js.map +1 -0
  118. package/packages/gsd-cloud/dist/runtime-process.test.d.ts +2 -0
  119. package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +1 -0
  120. package/packages/gsd-cloud/dist/runtime-process.test.js +543 -0
  121. package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -0
  122. package/packages/gsd-cloud/dist/runtime-telemetry.d.ts +117 -0
  123. package/packages/gsd-cloud/dist/runtime-telemetry.d.ts.map +1 -0
  124. package/packages/gsd-cloud/dist/runtime-telemetry.js +291 -0
  125. package/packages/gsd-cloud/dist/runtime-telemetry.js.map +1 -0
  126. package/packages/gsd-cloud/dist/runtime-telemetry.test.d.ts +2 -0
  127. package/packages/gsd-cloud/dist/runtime-telemetry.test.d.ts.map +1 -0
  128. package/packages/gsd-cloud/dist/runtime-telemetry.test.js +434 -0
  129. package/packages/gsd-cloud/dist/runtime-telemetry.test.js.map +1 -0
  130. package/packages/gsd-cloud/package.json +2 -2
  131. package/packages/mcp-server/dist/cli-runner.d.ts +3 -0
  132. package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
  133. package/packages/mcp-server/dist/cli-runner.js +119 -33
  134. package/packages/mcp-server/dist/cli-runner.js.map +1 -1
  135. package/packages/mcp-server/package.json +4 -4
  136. package/packages/native/package.json +1 -1
  137. package/packages/pi-agent-core/dist/types.d.ts +2 -2
  138. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  139. package/packages/pi-agent-core/dist/types.js.map +1 -1
  140. package/packages/pi-agent-core/package.json +1 -1
  141. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  142. package/packages/pi-ai/dist/models.generated.d.ts +130 -2
  143. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  144. package/packages/pi-ai/dist/models.generated.js +101 -9
  145. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  146. package/packages/pi-ai/dist/models.js +18 -6
  147. package/packages/pi-ai/dist/models.js.map +1 -1
  148. package/packages/pi-ai/dist/providers/amazon-bedrock.js +3 -2
  149. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  150. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts +1 -1
  151. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  152. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  153. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  154. package/packages/pi-ai/dist/providers/google.js.map +1 -1
  155. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +1 -1
  156. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  157. package/packages/pi-ai/dist/providers/openai-codex-responses.js +1 -1
  158. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  159. package/packages/pi-ai/dist/providers/openai-completions.d.ts +1 -1
  160. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  161. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  162. package/packages/pi-ai/dist/providers/openai-responses.d.ts +1 -1
  163. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  164. package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
  165. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  166. package/packages/pi-ai/dist/providers/simple-options.d.ts +1 -1
  167. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  168. package/packages/pi-ai/dist/providers/simple-options.js +1 -1
  169. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  170. package/packages/pi-ai/dist/types.d.ts +9 -1
  171. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  172. package/packages/pi-ai/dist/types.js.map +1 -1
  173. package/packages/pi-ai/package.json +1 -1
  174. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  175. package/packages/pi-coding-agent/dist/core/model-registry.js +1 -0
  176. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -1
  178. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  179. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -3
  180. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  181. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  182. package/packages/pi-coding-agent/dist/core/skills.d.ts +5 -0
  183. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/skills.js +54 -0
  185. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/theme/theme.d.ts +1 -1
  187. package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/theme/theme.js +1 -0
  189. package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
  190. package/packages/pi-coding-agent/package.json +7 -7
  191. package/packages/pi-tui/package.json +2 -2
  192. package/packages/rpc-client/package.json +2 -2
  193. package/pkg/dist/theme/theme.d.ts +1 -1
  194. package/pkg/dist/theme/theme.d.ts.map +1 -1
  195. package/pkg/dist/theme/theme.js +1 -0
  196. package/pkg/dist/theme/theme.js.map +1 -1
  197. package/pkg/package.json +1 -1
  198. package/src/resources/extensions/gsd/auto/loop.ts +48 -4
  199. package/src/resources/extensions/gsd/auto/run-unit.ts +20 -1
  200. package/src/resources/extensions/gsd/auto-model-selection.ts +1 -0
  201. package/src/resources/extensions/gsd/auto-start.ts +32 -9
  202. package/src/resources/extensions/gsd/auto-verification.ts +46 -12
  203. package/src/resources/extensions/gsd/auto-worktree-sync.ts +10 -3
  204. package/src/resources/extensions/gsd/auto.ts +13 -1
  205. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +4 -3
  206. package/src/resources/extensions/gsd/docs/preferences-reference.md +12 -3
  207. package/src/resources/extensions/gsd/doctor-engine-checks.ts +42 -6
  208. package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -0
  209. package/src/resources/extensions/gsd/gsd-db.ts +85 -33
  210. package/src/resources/extensions/gsd/model-cost-table.ts +14 -3
  211. package/src/resources/extensions/gsd/model-router.ts +10 -5
  212. package/src/resources/extensions/gsd/planning-subagent-registry.ts +18 -4
  213. package/src/resources/extensions/gsd/preferences-models.ts +54 -10
  214. package/src/resources/extensions/gsd/preferences-types.ts +10 -2
  215. package/src/resources/extensions/gsd/preferences-validation.ts +49 -6
  216. package/src/resources/extensions/gsd/preferences.ts +13 -0
  217. package/src/resources/extensions/gsd/templates/PREFERENCES.md +3 -0
  218. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +156 -0
  219. package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +21 -1
  220. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +74 -1
  221. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +147 -0
  222. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +8 -9
  223. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +32 -0
  224. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +13 -0
  225. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +22 -7
  226. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +55 -0
  227. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  228. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +24 -0
  229. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +38 -1
  230. package/src/resources/extensions/gsd/tests/preferences.test.ts +71 -4
  231. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +3 -0
  232. package/src/resources/extensions/gsd/tests/service-tier.test.ts +2 -2
  233. package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +1 -1
  234. package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +12 -0
  235. package/src/resources/extensions/gsd/tests/workflow-authority-faults.test.ts +238 -0
  236. package/src/resources/extensions/gsd/tests/workflow-authority-fixture.test.ts +48 -0
  237. package/src/resources/extensions/gsd/tests/workflow-authority-fixture.ts +141 -0
  238. package/src/resources/extensions/gsd/tests/workflow-authority-projection-conflict.test.ts +177 -0
  239. package/src/resources/extensions/gsd/tests/workflow-fault-harness.test.ts +39 -0
  240. package/src/resources/extensions/gsd/tests/workflow-fault-harness.ts +72 -0
  241. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
  242. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +60 -0
  243. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +40 -0
  244. package/src/resources/extensions/gsd/tools/plan-slice.ts +5 -1
  245. package/src/resources/extensions/gsd/verification-evidence.ts +5 -2
  246. package/src/resources/extensions/gsd/worktree-state-projection.ts +21 -24
  247. package/src/resources/extensions/ollama/ollama-chat-provider.ts +2 -2
  248. package/src/resources/extensions/ollama/tests/ollama-chat-provider-think.test.ts +4 -0
  249. package/src/resources/extensions/subagent/index.ts +1 -1
  250. /package/dist/web/standalone/.next/static/{V9MAVyYVPboQUB9KTTAMi → xwUaOqui04jIIFVsIBLiL}/_buildManifest.js +0 -0
  251. /package/dist/web/standalone/.next/static/{V9MAVyYVPboQUB9KTTAMi → xwUaOqui04jIIFVsIBLiL}/_ssgManifest.js +0 -0
@@ -0,0 +1,238 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawnSync, type SpawnSyncReturns } from "node:child_process";
3
+ import { mkdirSync, writeFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import test from "node:test";
7
+
8
+ import type { CompleteSliceParams } from "../types.js";
9
+ import {
10
+ closeDatabase,
11
+ getSlice,
12
+ getTask,
13
+ insertSlice,
14
+ insertTask,
15
+ updateTaskStatus,
16
+ } from "../gsd-db.js";
17
+ import { relSliceFile } from "../paths.js";
18
+ import { handleCompleteSlice } from "../tools/complete-slice.js";
19
+ import { createWorkflowAuthorityFixture } from "./workflow-authority-fixture.js";
20
+ import {
21
+ createWorkflowFaultHarness,
22
+ type WorkflowFaultHarness,
23
+ type WorkflowFaultPoint,
24
+ } from "./workflow-fault-harness.js";
25
+
26
+ interface FaultScenario {
27
+ point: WorkflowFaultPoint;
28
+ committed: boolean;
29
+ }
30
+
31
+ interface AuthoritySnapshot {
32
+ pid: number;
33
+ taskStatus: string | null;
34
+ sliceStatus: string | null;
35
+ activeSlice: string | null;
36
+ }
37
+
38
+ const SCENARIOS: FaultScenario[] = [
39
+ { point: "before-transaction-commit", committed: false },
40
+ { point: "after-db-commit-before-render", committed: true },
41
+ { point: "during-projection-write", committed: true },
42
+ { point: "before-independent-reopen", committed: true },
43
+ { point: "after-independent-reopen", committed: true },
44
+ ];
45
+
46
+ const COMPLETE_SLICE_PARAMS: CompleteSliceParams = {
47
+ milestoneId: "M001",
48
+ sliceId: "S02",
49
+ sliceTitle: "Ready dependent slice",
50
+ oneLiner: "Complete the dependent slice",
51
+ narrative: "The database records the completed slice before projections are refreshed.",
52
+ verification: "The focused authority matrix passed.",
53
+ uatContent: "## UAT Type\n\n- UAT mode: runtime-executable\n\n## Result\n\nPassed.",
54
+ };
55
+
56
+ function seedCompletionBoundary(): void {
57
+ updateTaskStatus("M001", "S02", "T01", "complete", "2026-07-11T00:00:00.000Z");
58
+ insertSlice({
59
+ id: "S03",
60
+ milestoneId: "M001",
61
+ title: "Blocked dependent slice",
62
+ status: "pending",
63
+ depends: ["S02"],
64
+ sequence: 3,
65
+ });
66
+ insertTask({
67
+ id: "T01",
68
+ milestoneId: "M001",
69
+ sliceId: "S03",
70
+ title: "Blocked task",
71
+ status: "pending",
72
+ sequence: 1,
73
+ });
74
+ }
75
+
76
+ function writeProjection(root: string, relativePath: string, content: string): void {
77
+ const path = join(root, ".gsd", relativePath);
78
+ mkdirSync(dirname(path), { recursive: true });
79
+ writeFileSync(path, content, "utf8");
80
+ }
81
+
82
+ function writeContradictoryProjection(root: string, committed: boolean): void {
83
+ const s02Checked = committed ? " " : "x";
84
+ const s03Checked = committed ? "x" : " ";
85
+ writeProjection(
86
+ root,
87
+ "milestones/M001/M001-ROADMAP.md",
88
+ [
89
+ "# M001: Contradictory projection",
90
+ "",
91
+ "## Slices",
92
+ "- [x] **S01: Completed prerequisite** `risk:low` `depends:[]`",
93
+ `- [${s02Checked}] **S02: Ready dependent slice** \`risk:medium\` \`depends:[S01]\``,
94
+ `- [${s03Checked}] **S03: Blocked dependent slice** \`risk:low\` \`depends:[S02]\``,
95
+ ].join("\n"),
96
+ );
97
+ writeProjection(
98
+ root,
99
+ "STATE.md",
100
+ [
101
+ "# GSD State",
102
+ "",
103
+ `**Active Slice:** ${committed ? "S02" : "S03"}`,
104
+ "**Phase:** executing",
105
+ ].join("\n"),
106
+ );
107
+ }
108
+
109
+ function armProductionFault(
110
+ point: WorkflowFaultPoint,
111
+ harness: WorkflowFaultHarness,
112
+ root: string,
113
+ ): void {
114
+ if (point === "before-transaction-commit") {
115
+ harness.armDatabaseAbort("status", "NEW.status = 'complete' AND OLD.status <> 'complete'");
116
+ } else if (point === "after-db-commit-before-render") {
117
+ harness.armDatabaseAbort("full_summary_md", "NEW.full_summary_md IS NOT OLD.full_summary_md");
118
+ } else if (point === "during-projection-write") {
119
+ const summaryPath = join(root, relSliceFile(root, "M001", "S02", "SUMMARY"));
120
+ harness.obstructProjection(summaryPath);
121
+ }
122
+ }
123
+
124
+ function runAuthorityProcess(
125
+ root: string,
126
+ dbPath: string,
127
+ faultPoint?: WorkflowFaultPoint,
128
+ ): SpawnSyncReturns<string> {
129
+ const resolver = join(process.cwd(), "src/resources/extensions/gsd/tests/resolve-ts.mjs");
130
+ const databaseModule = pathToFileURL(join(process.cwd(), "src/resources/extensions/gsd/gsd-db.ts")).href;
131
+ const stateModule = pathToFileURL(join(process.cwd(), "src/resources/extensions/gsd/state.ts")).href;
132
+ const faultHarnessModule = pathToFileURL(join(
133
+ process.cwd(),
134
+ "src/resources/extensions/gsd/tests/workflow-fault-harness.ts",
135
+ )).href;
136
+ const script = `
137
+ const [
138
+ { openDatabase, closeDatabase, getSlice, getTask },
139
+ { deriveStateFromDb },
140
+ { createWorkflowFaultHarness },
141
+ ] = await Promise.all([
142
+ import(${JSON.stringify(databaseModule)}),
143
+ import(${JSON.stringify(stateModule)}),
144
+ import(${JSON.stringify(faultHarnessModule)}),
145
+ ]);
146
+ const [root, dbPath, faultPoint] = process.argv.slice(-3);
147
+ if (!openDatabase(dbPath)) throw new Error("fresh process could not open workflow database");
148
+ if (faultPoint) {
149
+ process.stderr.write("DATABASE_OPENED_BEFORE_FAULT=" + process.pid + "\\n");
150
+ createWorkflowFaultHarness(faultPoint).hit(faultPoint, "fresh-process-reopen");
151
+ }
152
+ const state = await deriveStateFromDb(root);
153
+ const snapshot = {
154
+ pid: process.pid,
155
+ taskStatus: getTask("M001", "S02", "T01")?.status ?? null,
156
+ sliceStatus: getSlice("M001", "S02")?.status ?? null,
157
+ activeSlice: state.activeSlice?.id ?? null,
158
+ };
159
+ closeDatabase();
160
+ process.stdout.write("AUTHORITY_SNAPSHOT=" + JSON.stringify(snapshot) + "\\n");
161
+ `;
162
+ return spawnSync(
163
+ process.execPath,
164
+ [
165
+ "--import",
166
+ resolver,
167
+ "--experimental-strip-types",
168
+ "--input-type=module",
169
+ "--eval",
170
+ script,
171
+ root,
172
+ dbPath,
173
+ faultPoint ?? "",
174
+ ],
175
+ { cwd: process.cwd(), encoding: "utf8" },
176
+ );
177
+ }
178
+
179
+ function readAuthorityInFreshProcess(root: string, dbPath: string): AuthoritySnapshot {
180
+ const child = runAuthorityProcess(root, dbPath);
181
+
182
+ assert.equal(child.status, 0, child.stderr || child.stdout);
183
+ const line = child.stdout.split("\n").find((entry) => entry.startsWith("AUTHORITY_SNAPSHOT="));
184
+ assert.ok(line, `fresh process did not return an authority snapshot: ${child.stdout}`);
185
+ return JSON.parse(line.slice("AUTHORITY_SNAPSHOT=".length)) as AuthoritySnapshot;
186
+ }
187
+
188
+ for (const scenario of SCENARIOS) {
189
+ test(`database authority remains coherent at ${scenario.point}`, async (t) => {
190
+ const fixture = await createWorkflowAuthorityFixture();
191
+ t.after(() => fixture.cleanup());
192
+ seedCompletionBoundary();
193
+ const harness = createWorkflowFaultHarness(scenario.point);
194
+ armProductionFault(scenario.point, harness, fixture.root);
195
+
196
+ let completionError: unknown;
197
+ let completionStale = false;
198
+ try {
199
+ const result = await handleCompleteSlice(COMPLETE_SLICE_PARAMS, fixture.root);
200
+ assert.ok(!("error" in result), "production completion must reach its mutation boundary");
201
+ completionStale = result.stale === true;
202
+ harness.hit("before-independent-reopen", "complete-dependent-slice");
203
+ } catch (error) {
204
+ completionError = error;
205
+ }
206
+
207
+ if (scenario.point === "during-projection-write") {
208
+ assert.equal(completionStale, true, "the production renderer must surface a stale projection");
209
+ } else if (scenario.point === "after-independent-reopen") {
210
+ assert.equal(completionError, undefined, "completion must succeed before the reopen fault");
211
+ assert.equal(completionStale, false, "completion must not be stale before the reopen fault");
212
+ } else {
213
+ assert.match(String(completionError), new RegExp(scenario.point));
214
+ }
215
+
216
+ writeContradictoryProjection(fixture.root, scenario.committed);
217
+ closeDatabase();
218
+ if (scenario.point === "after-independent-reopen") {
219
+ const faultedChild = runAuthorityProcess(fixture.root, fixture.dbPath, scenario.point);
220
+ assert.notEqual(faultedChild.status, 0, "fresh process must fault after opening the database");
221
+ assert.match(faultedChild.stderr, /DATABASE_OPENED_BEFORE_FAULT=/);
222
+ assert.match(faultedChild.stderr, /after-independent-reopen/);
223
+ }
224
+ const snapshot = readAuthorityInFreshProcess(fixture.root, fixture.dbPath);
225
+ fixture.reopen();
226
+
227
+ const expectedStatus = scenario.committed ? "complete" : "pending";
228
+ const { pid, ...authority } = snapshot;
229
+ assert.notEqual(pid, process.pid, "authority must be verified by another process");
230
+ assert.deepEqual(authority, {
231
+ taskStatus: "complete",
232
+ sliceStatus: expectedStatus,
233
+ activeSlice: scenario.committed ? "S03" : "S02",
234
+ });
235
+ assert.equal(getTask("M001", "S02", "T01")?.status, "complete");
236
+ assert.equal(getSlice("M001", "S02")?.status, expectedStatus);
237
+ });
238
+ }
@@ -0,0 +1,48 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import { getMilestoneSlices, getSliceTasks } from "../gsd-db.ts";
5
+ import { queryDecisionsFromMemories, queryRequirements } from "../context-store.ts";
6
+ import { deriveStateFromDb } from "../state.ts";
7
+ import { createWorkflowAuthorityFixture } from "./workflow-authority-fixture.ts";
8
+
9
+ test("workflow authority fixture seeds a reopenable DB-backed workflow", async (t) => {
10
+ const fixture = await createWorkflowAuthorityFixture();
11
+ t.after(() => fixture.cleanup());
12
+ fixture.reopen();
13
+
14
+ const state = await deriveStateFromDb(fixture.root);
15
+ const slices = getMilestoneSlices(fixture.ids.milestone);
16
+ const completedTasks = getSliceTasks(fixture.ids.milestone, fixture.ids.completedSlice);
17
+ const readyTasks = getSliceTasks(fixture.ids.milestone, fixture.ids.readySlice);
18
+ const requirements = queryRequirements({
19
+ milestoneId: fixture.ids.milestone,
20
+ sliceId: fixture.ids.readySlice,
21
+ });
22
+ const decisions = queryDecisionsFromMemories({ milestoneId: fixture.ids.milestone });
23
+
24
+ assert.equal(state.phase, "executing");
25
+ assert.equal(state.activeMilestone?.id, fixture.ids.milestone);
26
+ assert.equal(state.activeSlice?.id, fixture.ids.readySlice);
27
+ assert.equal(state.activeTask?.id, fixture.ids.readyTask);
28
+ assert.deepEqual(
29
+ slices.map((slice) => ({ id: slice.id, status: slice.status, depends: slice.depends })),
30
+ [
31
+ { id: fixture.ids.completedSlice, status: "complete", depends: [] },
32
+ { id: fixture.ids.readySlice, status: "pending", depends: [fixture.ids.completedSlice] },
33
+ ],
34
+ );
35
+ assert.deepEqual(
36
+ completedTasks.map((task) => ({ id: task.id, status: task.status })),
37
+ [{ id: fixture.ids.completedTask, status: "complete" }],
38
+ );
39
+ assert.deepEqual(
40
+ readyTasks.map((task) => ({ id: task.id, status: task.status })),
41
+ [{ id: fixture.ids.readyTask, status: "pending" }],
42
+ );
43
+ assert.ok(state.requirements);
44
+ assert.equal(state.requirements.active, 1);
45
+ assert.equal(state.requirements.total, 1);
46
+ assert.deepEqual(requirements.map((requirement) => requirement.id), [fixture.ids.requirement]);
47
+ assert.deepEqual(decisions.map((decision) => decision.id), [fixture.ids.decision]);
48
+ });
@@ -0,0 +1,141 @@
1
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+
5
+ import {
6
+ closeDatabase,
7
+ insertMilestone,
8
+ insertSlice,
9
+ insertTask,
10
+ openDatabase,
11
+ transaction,
12
+ } from "../gsd-db.ts";
13
+ import { saveDecisionToDb, saveRequirementToDb } from "../db-writer.ts";
14
+ import { invalidateStateCache } from "../state.ts";
15
+
16
+ export interface WorkflowAuthorityFixture {
17
+ root: string;
18
+ dbPath: string;
19
+ ids: {
20
+ milestone: string;
21
+ completedSlice: string;
22
+ readySlice: string;
23
+ completedTask: string;
24
+ readyTask: string;
25
+ requirement: string;
26
+ decision: string;
27
+ };
28
+ reopen(): void;
29
+ cleanup(): void;
30
+ }
31
+
32
+ function openFixtureDatabase(dbPath: string): void {
33
+ if (!openDatabase(dbPath)) {
34
+ throw new Error(`Could not open workflow authority fixture database: ${dbPath}`);
35
+ }
36
+ }
37
+
38
+ export async function createWorkflowAuthorityFixture(): Promise<WorkflowAuthorityFixture> {
39
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-workflow-authority-")));
40
+ const dbPath = join(root, ".gsd", "gsd.db");
41
+ let cleanedUp = false;
42
+
43
+ mkdirSync(join(root, ".gsd"), { recursive: true });
44
+ openFixtureDatabase(dbPath);
45
+
46
+ try {
47
+ transaction(() => {
48
+ insertMilestone({ id: "M001", title: "Authority Fixture", status: "active" });
49
+ insertSlice({
50
+ id: "S01",
51
+ milestoneId: "M001",
52
+ title: "Completed prerequisite",
53
+ status: "complete",
54
+ risk: "low",
55
+ depends: [],
56
+ sequence: 1,
57
+ });
58
+ insertSlice({
59
+ id: "S02",
60
+ milestoneId: "M001",
61
+ title: "Ready dependent slice",
62
+ status: "pending",
63
+ risk: "medium",
64
+ depends: ["S01"],
65
+ sequence: 2,
66
+ });
67
+ insertTask({
68
+ id: "T01",
69
+ milestoneId: "M001",
70
+ sliceId: "S01",
71
+ title: "Completed task",
72
+ status: "complete",
73
+ sequence: 1,
74
+ });
75
+ insertTask({
76
+ id: "T01",
77
+ milestoneId: "M001",
78
+ sliceId: "S02",
79
+ title: "Ready task",
80
+ status: "pending",
81
+ sequence: 1,
82
+ });
83
+ });
84
+
85
+ const requirement = await saveRequirementToDb(
86
+ {
87
+ class: "core-capability",
88
+ status: "active",
89
+ description: "SQLite is authoritative",
90
+ why: "Readable projections must not control workflow state.",
91
+ source: "workflow-authority-fixture",
92
+ primary_owner: "M001/S02",
93
+ validation: "DB-derived state remains canonical",
94
+ },
95
+ root,
96
+ );
97
+ const decision = await saveDecisionToDb(
98
+ {
99
+ when_context: "M001/S02",
100
+ scope: "architecture",
101
+ decision: "Choose the workflow authority",
102
+ choice: "SQLite is authoritative",
103
+ rationale: "Projections may lag without losing saved intent.",
104
+ revisable: "No",
105
+ made_by: "human",
106
+ source: "discussion",
107
+ },
108
+ root,
109
+ );
110
+
111
+ return {
112
+ root,
113
+ dbPath,
114
+ ids: {
115
+ milestone: "M001",
116
+ completedSlice: "S01",
117
+ readySlice: "S02",
118
+ completedTask: "T01",
119
+ readyTask: "T01",
120
+ requirement: requirement.id,
121
+ decision: decision.id,
122
+ },
123
+ reopen() {
124
+ closeDatabase();
125
+ openFixtureDatabase(dbPath);
126
+ invalidateStateCache();
127
+ },
128
+ cleanup() {
129
+ if (cleanedUp) return;
130
+ cleanedUp = true;
131
+ closeDatabase();
132
+ invalidateStateCache();
133
+ rmSync(root, { recursive: true, force: true });
134
+ },
135
+ };
136
+ } catch (error) {
137
+ closeDatabase();
138
+ rmSync(root, { recursive: true, force: true });
139
+ throw error;
140
+ }
141
+ }
@@ -0,0 +1,177 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdirSync, writeFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+
6
+ import { getMilestoneSlices, getSliceTasks } from "../gsd-db.ts";
7
+ import { queryDecisionsFromMemories, queryRequirements } from "../context-store.ts";
8
+ import { deriveStateFromDb } from "../state.ts";
9
+ import {
10
+ createWorkflowAuthorityFixture,
11
+ type WorkflowAuthorityFixture,
12
+ } from "./workflow-authority-fixture.ts";
13
+
14
+ function writeProjection(root: string, path: string, content: string): void {
15
+ const fullPath = join(root, ".gsd", path);
16
+ mkdirSync(dirname(fullPath), { recursive: true });
17
+ writeFileSync(fullPath, content, "utf8");
18
+ }
19
+
20
+ function writeContradictoryProjections(root: string): void {
21
+ writeProjection(
22
+ root,
23
+ "STATE.md",
24
+ [
25
+ "# GSD State",
26
+ "",
27
+ "**Active Milestone:** M999",
28
+ "**Active Slice:** S99",
29
+ "**Phase:** complete",
30
+ "**Requirements Status:** 0 active · 1 validated",
31
+ ].join("\n"),
32
+ );
33
+ writeProjection(
34
+ root,
35
+ "PROJECT.md",
36
+ [
37
+ "# Projection-only project",
38
+ "",
39
+ "## Milestone Sequence",
40
+ "- [x] M001: Falsely complete",
41
+ "- [ ] M999: Projection-only active milestone",
42
+ ].join("\n"),
43
+ );
44
+ writeProjection(
45
+ root,
46
+ "REQUIREMENTS.md",
47
+ [
48
+ "# Requirements",
49
+ "",
50
+ "## Validated",
51
+ "### R999 — Markdown controls workflow state",
52
+ "- Status: validated",
53
+ "- Primary owning slice: M999/S99",
54
+ ].join("\n"),
55
+ );
56
+ writeProjection(
57
+ root,
58
+ "DECISIONS.md",
59
+ [
60
+ "# Decisions Register",
61
+ "",
62
+ "| # | When | Scope | Decision | Choice | Rationale | Revisable? | Made By |",
63
+ "|---|------|-------|----------|--------|-----------|------------|---------|",
64
+ "| D999 | M999/S99 | architecture | Choose authority | Markdown is authoritative | projection conflict | No | human |",
65
+ ].join("\n"),
66
+ );
67
+ writeProjection(
68
+ root,
69
+ "milestones/M001/M001-ROADMAP.md",
70
+ [
71
+ "# M001: Falsely complete",
72
+ "",
73
+ "## Slices",
74
+ "- [ ] **S01: Falsely reopened** `risk:low` `depends:[]`",
75
+ "- [x] **S02: Falsely complete** `risk:low` `depends:[]`",
76
+ ].join("\n"),
77
+ );
78
+ writeProjection(
79
+ root,
80
+ "milestones/M001/slices/S02/S02-PLAN.md",
81
+ [
82
+ "# S02: Falsely complete",
83
+ "",
84
+ "## Tasks",
85
+ "- [x] **T99: Projection-only task** `est:1m`",
86
+ ].join("\n"),
87
+ );
88
+ }
89
+
90
+ async function readDirectDbAuthoritySnapshot(fixture: WorkflowAuthorityFixture) {
91
+ const state = await deriveStateFromDb(fixture.root);
92
+ return {
93
+ state: {
94
+ phase: state.phase,
95
+ milestone: state.activeMilestone?.id ?? null,
96
+ slice: state.activeSlice?.id ?? null,
97
+ task: state.activeTask?.id ?? null,
98
+ requirements: state.requirements,
99
+ },
100
+ registry: state.registry.map((milestone) => ({
101
+ id: milestone.id,
102
+ status: milestone.status,
103
+ })),
104
+ slices: getMilestoneSlices(fixture.ids.milestone).map((slice) => ({
105
+ id: slice.id,
106
+ status: slice.status,
107
+ depends: slice.depends,
108
+ })),
109
+ tasks: {
110
+ completed: getSliceTasks(fixture.ids.milestone, fixture.ids.completedSlice).map((task) => ({
111
+ id: task.id,
112
+ status: task.status,
113
+ })),
114
+ ready: getSliceTasks(fixture.ids.milestone, fixture.ids.readySlice).map((task) => ({
115
+ id: task.id,
116
+ status: task.status,
117
+ })),
118
+ },
119
+ requirements: queryRequirements({
120
+ milestoneId: fixture.ids.milestone,
121
+ sliceId: fixture.ids.readySlice,
122
+ }).map((requirement) => ({
123
+ id: requirement.id,
124
+ status: requirement.status,
125
+ owner: requirement.primary_owner,
126
+ })),
127
+ decisions: queryDecisionsFromMemories().map((decision) => ({
128
+ id: decision.id,
129
+ choice: decision.choice,
130
+ })),
131
+ };
132
+ }
133
+
134
+ test("direct DB authority queries ignore contradictory Markdown projections", async (t) => {
135
+ const fixture = await createWorkflowAuthorityFixture();
136
+ t.after(() => fixture.cleanup());
137
+ fixture.reopen();
138
+ const before = await readDirectDbAuthoritySnapshot(fixture);
139
+
140
+ assert.deepEqual(before, {
141
+ state: {
142
+ phase: "executing",
143
+ milestone: fixture.ids.milestone,
144
+ slice: fixture.ids.readySlice,
145
+ task: fixture.ids.readyTask,
146
+ requirements: {
147
+ active: 1,
148
+ validated: 0,
149
+ deferred: 0,
150
+ outOfScope: 0,
151
+ blocked: 0,
152
+ total: 1,
153
+ },
154
+ },
155
+ registry: [{ id: fixture.ids.milestone, status: "active" }],
156
+ slices: [
157
+ { id: fixture.ids.completedSlice, status: "complete", depends: [] },
158
+ { id: fixture.ids.readySlice, status: "pending", depends: [fixture.ids.completedSlice] },
159
+ ],
160
+ tasks: {
161
+ completed: [{ id: fixture.ids.completedTask, status: "complete" }],
162
+ ready: [{ id: fixture.ids.readyTask, status: "pending" }],
163
+ },
164
+ requirements: [
165
+ { id: fixture.ids.requirement, status: "active", owner: "M001/S02" },
166
+ ],
167
+ decisions: [
168
+ { id: fixture.ids.decision, choice: "SQLite is authoritative" },
169
+ ],
170
+ });
171
+
172
+ writeContradictoryProjections(fixture.root);
173
+ fixture.reopen();
174
+
175
+ const after = await readDirectDbAuthoritySnapshot(fixture);
176
+ assert.deepEqual(after, before);
177
+ });
@@ -0,0 +1,39 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import {
5
+ createWorkflowFaultHarness,
6
+ type WorkflowFaultPoint,
7
+ } from "./workflow-fault-harness.js";
8
+
9
+ test("fault harness throws once at the armed named boundary", () => {
10
+ const harness = createWorkflowFaultHarness("after-db-commit-before-render");
11
+
12
+ harness.hit("before-transaction-commit", "complete-task");
13
+ assert.throws(
14
+ () => harness.hit("after-db-commit-before-render", "complete-task"),
15
+ /complete-task.*after-db-commit-before-render.*hit 1/,
16
+ );
17
+
18
+ assert.equal(harness.count("before-transaction-commit"), 1);
19
+ assert.equal(harness.count("after-db-commit-before-render"), 1);
20
+ });
21
+
22
+ test("fault harness automatically disarms after its first matching hit", () => {
23
+ const point: WorkflowFaultPoint = "during-projection-write";
24
+ const harness = createWorkflowFaultHarness(point);
25
+
26
+ assert.throws(() => harness.hit(point, "complete-slice"));
27
+ assert.doesNotThrow(() => harness.hit(point, "complete-slice"));
28
+ assert.equal(harness.count(point), 2);
29
+ });
30
+
31
+ test("fault harness instances never share armed state or counts", () => {
32
+ const first = createWorkflowFaultHarness("before-independent-reopen");
33
+ const second = createWorkflowFaultHarness("before-independent-reopen");
34
+
35
+ assert.throws(() => first.hit("before-independent-reopen", "resume"));
36
+ assert.equal(first.count("before-independent-reopen"), 1);
37
+ assert.equal(second.count("before-independent-reopen"), 0);
38
+ assert.throws(() => second.hit("before-independent-reopen", "resume"));
39
+ });