@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.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +114 -36
- package/dist/resources/extensions/gsd/activity-log.js +1 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +16 -10
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +53 -15
- package/dist/resources/extensions/gsd/auto-model-selection.js +28 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +29 -117
- package/dist/resources/extensions/gsd/auto-start.js +53 -30
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +12 -2
- package/dist/resources/extensions/gsd/auto-timers.js +9 -2
- package/dist/resources/extensions/gsd/auto.js +97 -31
- package/dist/resources/extensions/gsd/compat/compat-marker.js +44 -0
- package/dist/resources/extensions/gsd/config-overlay.js +7 -2
- package/dist/resources/extensions/gsd/db/engine.js +33 -0
- package/dist/resources/extensions/gsd/db-adapter.js +30 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/dist/resources/extensions/gsd/journal.js +1 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +34 -91
- package/dist/resources/extensions/gsd/metrics.js +2 -2
- package/dist/resources/extensions/gsd/paths.js +70 -49
- package/dist/resources/extensions/gsd/post-unit-hooks.js +27 -0
- package/dist/resources/extensions/gsd/preferences-models.js +66 -15
- package/dist/resources/extensions/gsd/preferences-validation.js +66 -10
- package/dist/resources/extensions/gsd/rule-registry.js +106 -10
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +1 -10
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +3 -24
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +10 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -5
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +16 -10
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -8
- package/dist/resources/extensions/gsd/uat-dispatch.js +94 -0
- package/dist/resources/extensions/gsd/uok/audit-toggle.js +31 -2
- package/dist/resources/extensions/gsd/uok/audit.js +3 -0
- package/dist/resources/extensions/gsd/uok/gate-runner.js +22 -30
- package/dist/resources/extensions/gsd/uok/gitops.js +18 -15
- package/dist/resources/extensions/gsd/uok/kernel.js +120 -46
- package/dist/resources/extensions/gsd/uok/model-policy.js +17 -14
- package/dist/resources/extensions/gsd/workflow-event-ledger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-projections.js +9 -26
- package/dist/resources/extensions/gsd/workspace.js +5 -14
- package/dist/resources/extensions/shared/gsd-browser-cli.js +29 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
- package/dist/web/standalone/.next/server/chunks/{1128.js → 1991.js} +1 -1
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/worktree-cli-create.d.ts +10 -0
- package/dist/worktree-cli-create.js +11 -0
- package/dist/worktree-cli-format.d.ts +12 -0
- package/dist/worktree-cli-format.js +38 -0
- package/dist/worktree-cli-plan.d.ts +16 -0
- package/dist/worktree-cli-plan.js +19 -0
- package/dist/worktree-cli-session.d.ts +13 -0
- package/dist/worktree-cli-session.js +18 -0
- package/dist/worktree-cli-status.d.ts +35 -0
- package/dist/worktree-cli-status.js +54 -0
- package/dist/worktree-cli.d.ts +1 -16
- package/dist/worktree-cli.js +28 -122
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/dist/orchestrator-agent.d.ts +21 -0
- package/packages/daemon/dist/orchestrator-agent.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator-agent.js +113 -0
- package/packages/daemon/dist/orchestrator-agent.js.map +1 -0
- package/packages/daemon/dist/orchestrator-tools.d.ts +12 -0
- package/packages/daemon/dist/orchestrator-tools.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator-tools.js +137 -0
- package/packages/daemon/dist/orchestrator-tools.js.map +1 -0
- package/packages/daemon/dist/orchestrator.d.ts +4 -56
- package/packages/daemon/dist/orchestrator.d.ts.map +1 -1
- package/packages/daemon/dist/orchestrator.js +13 -303
- package/packages/daemon/dist/orchestrator.js.map +1 -1
- package/packages/daemon/dist/orchestrator.test.js +11 -1
- package/packages/daemon/dist/orchestrator.test.js.map +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +146 -33
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +25 -0
- package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +93 -0
- package/src/resources/extensions/gsd/activity-log.ts +1 -1
- package/src/resources/extensions/gsd/auto/orchestrator.ts +17 -11
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +59 -14
- package/src/resources/extensions/gsd/auto-model-selection.ts +33 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +4 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +53 -113
- package/src/resources/extensions/gsd/auto-start.ts +57 -28
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -2
- package/src/resources/extensions/gsd/auto-timers.ts +9 -2
- package/src/resources/extensions/gsd/auto.ts +103 -30
- package/src/resources/extensions/gsd/compat/compat-marker.ts +47 -0
- package/src/resources/extensions/gsd/config-overlay.ts +7 -1
- package/src/resources/extensions/gsd/db/engine.ts +36 -0
- package/src/resources/extensions/gsd/db-adapter.ts +29 -3
- package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/src/resources/extensions/gsd/journal.ts +1 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +52 -103
- package/src/resources/extensions/gsd/metrics.ts +2 -2
- package/src/resources/extensions/gsd/paths.ts +87 -48
- package/src/resources/extensions/gsd/post-unit-hooks.ts +32 -0
- package/src/resources/extensions/gsd/preferences-models.ts +74 -20
- package/src/resources/extensions/gsd/preferences-types.ts +24 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +77 -9
- package/src/resources/extensions/gsd/rule-registry.ts +111 -9
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +1 -9
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +2 -22
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +14 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +41 -2
- package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +48 -1
- package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/auto-timeout-recovery-supervisor-inflight-guard.test.ts +159 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/compat-marker.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/db-adapter.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/db-writable-probe.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +72 -32
- package/src/resources/extensions/gsd/tests/integration/paths.test.ts +88 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +269 -0
- package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +95 -1
- package/src/resources/extensions/gsd/tests/sibling-model-fallbacks.test.ts +189 -0
- package/src/resources/extensions/gsd/tests/single-field-model-thinking.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/supervisor-model-swap-inflight-guard.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/supervisor-model-swap-thinking-clamp.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/uat-dispatch.test.ts +211 -0
- package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +226 -2
- package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +213 -2
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +9 -5
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +19 -8
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +13 -9
- package/src/resources/extensions/gsd/types.ts +23 -4
- package/src/resources/extensions/gsd/uat-dispatch.ts +176 -0
- package/src/resources/extensions/gsd/uok/audit-toggle.ts +32 -2
- package/src/resources/extensions/gsd/uok/audit.ts +2 -0
- package/src/resources/extensions/gsd/uok/gate-runner.ts +26 -36
- package/src/resources/extensions/gsd/uok/gitops.ts +21 -18
- package/src/resources/extensions/gsd/uok/kernel.ts +169 -48
- package/src/resources/extensions/gsd/uok/model-policy.ts +20 -17
- package/src/resources/extensions/gsd/workflow-event-ledger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-projections.ts +9 -24
- package/src/resources/extensions/gsd/workspace.ts +5 -21
- package/src/resources/extensions/shared/gsd-browser-cli.ts +29 -0
- /package/dist/web/standalone/.next/static/{gXoWec4UNqcR0tkuHwYCr → NPMNi2HV8BuK3baQsEUhk}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{gXoWec4UNqcR0tkuHwYCr → NPMNi2HV8BuK3baQsEUhk}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supervisor model swap honors the in-flight-tool guard (#1229 follow-up).
|
|
3
|
+
*
|
|
4
|
+
* The wrap-up soft-timeout timer gates `triggerTurn` on there being no tools
|
|
5
|
+
* in flight — sending a turn mid-tool-call skips tool results and trips
|
|
6
|
+
* provider errors (#3512). The supervisor model swap
|
|
7
|
+
* (`applySupervisorModelIfConfigured`) must honor the identical guard: swapping
|
|
8
|
+
* the session model while tool calls are still open risks the same provider
|
|
9
|
+
* errors, so it must only run when the timer will actually trigger a turn.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import test from "node:test";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { mock } from "node:test";
|
|
15
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
|
|
19
|
+
import { startUnitSupervision, type SupervisionContext } from "../auto-timers.ts";
|
|
20
|
+
import { clearGSDPreferencesCache } from "../preferences.ts";
|
|
21
|
+
import {
|
|
22
|
+
markToolStart,
|
|
23
|
+
markToolEnd,
|
|
24
|
+
clearInFlightTools,
|
|
25
|
+
} from "../auto-tool-tracking.ts";
|
|
26
|
+
|
|
27
|
+
const SUPERVISOR_PREFS = [
|
|
28
|
+
"auto_supervisor:",
|
|
29
|
+
" soft_timeout_minutes: 1",
|
|
30
|
+
" idle_timeout_minutes: 100",
|
|
31
|
+
" hard_timeout_minutes: 100",
|
|
32
|
+
" model:",
|
|
33
|
+
" model: supervisor-primary",
|
|
34
|
+
" fallbacks: [supervisor-fb]",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
interface Harness {
|
|
38
|
+
home: string;
|
|
39
|
+
base: string;
|
|
40
|
+
ctx: any;
|
|
41
|
+
pi: any;
|
|
42
|
+
s: any;
|
|
43
|
+
sctx: SupervisionContext;
|
|
44
|
+
setModelCalls: unknown[][];
|
|
45
|
+
sendMessageCalls: unknown[][];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function makeHarness(): Harness {
|
|
49
|
+
const home = mkdtempSync(join(tmpdir(), "gsd-supervisor-guard-home-"));
|
|
50
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-supervisor-guard-base-"));
|
|
51
|
+
process.env.GSD_HOME = home;
|
|
52
|
+
writeFileSync(join(home, "preferences.md"), ["---", ...SUPERVISOR_PREFS, "---", ""].join("\n"));
|
|
53
|
+
clearGSDPreferencesCache();
|
|
54
|
+
|
|
55
|
+
const setModelCalls: unknown[][] = [];
|
|
56
|
+
const sendMessageCalls: unknown[][] = [];
|
|
57
|
+
|
|
58
|
+
const ctx = {
|
|
59
|
+
ui: { notify: () => {} },
|
|
60
|
+
model: { provider: "anthropic" },
|
|
61
|
+
modelRegistry: {
|
|
62
|
+
getAvailable: () => [
|
|
63
|
+
{ id: "supervisor-primary", provider: "anthropic" },
|
|
64
|
+
{ id: "supervisor-fb", provider: "anthropic" },
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
} as any;
|
|
68
|
+
|
|
69
|
+
const pi = {
|
|
70
|
+
sendMessage: (...args: unknown[]) => {
|
|
71
|
+
sendMessageCalls.push(args);
|
|
72
|
+
},
|
|
73
|
+
setModel: async (...args: unknown[]) => {
|
|
74
|
+
setModelCalls.push(args);
|
|
75
|
+
return true;
|
|
76
|
+
},
|
|
77
|
+
getThinkingLevel: () => "off",
|
|
78
|
+
setThinkingLevel: () => {},
|
|
79
|
+
} as any;
|
|
80
|
+
|
|
81
|
+
const s = {
|
|
82
|
+
active: true,
|
|
83
|
+
verbose: false,
|
|
84
|
+
basePath: base,
|
|
85
|
+
currentUnit: { type: "task", id: "T01", startedAt: 1234 },
|
|
86
|
+
cmdCtx: undefined,
|
|
87
|
+
wrapupWarningHandle: null,
|
|
88
|
+
idleWatchdogHandle: null,
|
|
89
|
+
unitTimeoutHandle: null,
|
|
90
|
+
continueHereHandle: null,
|
|
91
|
+
} as any;
|
|
92
|
+
|
|
93
|
+
const sctx: SupervisionContext = {
|
|
94
|
+
s,
|
|
95
|
+
ctx,
|
|
96
|
+
pi,
|
|
97
|
+
unitType: "task",
|
|
98
|
+
unitId: "T01",
|
|
99
|
+
prefs: undefined,
|
|
100
|
+
buildSnapshotOpts: () => ({}) as any,
|
|
101
|
+
buildRecoveryContext: () => ({}) as any,
|
|
102
|
+
pauseAuto: async () => {},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return { home, base, ctx, pi, s, sctx, setModelCalls, sendMessageCalls };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function cleanup(h: Harness): void {
|
|
109
|
+
clearInFlightTools();
|
|
110
|
+
clearGSDPreferencesCache();
|
|
111
|
+
delete process.env.GSD_HOME;
|
|
112
|
+
rmSync(h.home, { recursive: true, force: true });
|
|
113
|
+
rmSync(h.base, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Neutralize every timer except the soft-timeout wrap-up so only it fires. */
|
|
117
|
+
function isolateSoftTimeout(s: any): void {
|
|
118
|
+
if (s.idleWatchdogHandle) clearInterval(s.idleWatchdogHandle);
|
|
119
|
+
if (s.unitTimeoutHandle) clearTimeout(s.unitTimeoutHandle);
|
|
120
|
+
if (s.continueHereHandle) clearInterval(s.continueHereHandle);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Flush the soft-timeout callback's chained awaits. */
|
|
124
|
+
async function flush(): Promise<void> {
|
|
125
|
+
for (let i = 0; i < 10; i++) await Promise.resolve();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
test("soft-timeout skips the supervisor model swap while a tool is in flight", async () => {
|
|
129
|
+
const h = makeHarness();
|
|
130
|
+
mock.timers.enable({ apis: ["setTimeout", "setInterval", "Date"], now: 0 });
|
|
131
|
+
try {
|
|
132
|
+
startUnitSupervision(h.sctx);
|
|
133
|
+
isolateSoftTimeout(h.s);
|
|
134
|
+
|
|
135
|
+
// A tool is still executing when the soft timeout fires.
|
|
136
|
+
markToolStart("call-1", true, "some_tool");
|
|
137
|
+
|
|
138
|
+
mock.timers.tick(60_000);
|
|
139
|
+
await flush();
|
|
140
|
+
|
|
141
|
+
assert.equal(
|
|
142
|
+
h.setModelCalls.length,
|
|
143
|
+
0,
|
|
144
|
+
"model must NOT swap while a tool is in flight",
|
|
145
|
+
);
|
|
146
|
+
assert.equal(h.sendMessageCalls.length, 1, "wrap-up message still sent (queued)");
|
|
147
|
+
const opts = h.sendMessageCalls[0][1] as { triggerTurn?: boolean };
|
|
148
|
+
assert.equal(opts.triggerTurn, false, "turn is not triggered mid-tool-call");
|
|
149
|
+
|
|
150
|
+
markToolEnd("call-1");
|
|
151
|
+
} finally {
|
|
152
|
+
mock.timers.reset();
|
|
153
|
+
cleanup(h);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("soft-timeout applies the supervisor model swap when no tools are in flight", async () => {
|
|
158
|
+
const h = makeHarness();
|
|
159
|
+
mock.timers.enable({ apis: ["setTimeout", "setInterval", "Date"], now: 0 });
|
|
160
|
+
try {
|
|
161
|
+
startUnitSupervision(h.sctx);
|
|
162
|
+
isolateSoftTimeout(h.s);
|
|
163
|
+
|
|
164
|
+
// No tools in flight — the timer will trigger a turn, so the model swaps.
|
|
165
|
+
mock.timers.tick(60_000);
|
|
166
|
+
await flush();
|
|
167
|
+
|
|
168
|
+
assert.equal(h.setModelCalls.length, 1, "model swaps to the supervisor model");
|
|
169
|
+
assert.deepEqual(
|
|
170
|
+
h.setModelCalls[0][0],
|
|
171
|
+
{ id: "supervisor-primary", provider: "anthropic" },
|
|
172
|
+
"swaps to the configured supervisor primary",
|
|
173
|
+
);
|
|
174
|
+
assert.equal(h.sendMessageCalls.length, 1);
|
|
175
|
+
const opts = h.sendMessageCalls[0][1] as { triggerTurn?: boolean };
|
|
176
|
+
assert.equal(opts.triggerTurn, true, "turn is triggered when no tools are open");
|
|
177
|
+
} finally {
|
|
178
|
+
mock.timers.reset();
|
|
179
|
+
cleanup(h);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supervisor model swap reclamps the reasoning level (#1229 follow-up).
|
|
3
|
+
*
|
|
4
|
+
* Hook overrides in `unit-phase.ts` reapply `applyThinkingLevelForModel` after a
|
|
5
|
+
* model swap so the session's reasoning effort is re-clamped to what the newly
|
|
6
|
+
* selected model supports (ADR-026). `applySupervisorModelIfConfigured` swaps
|
|
7
|
+
* the session model for supervisor interventions (wrap-up, context-budget,
|
|
8
|
+
* timeout recovery) and must do the same — otherwise a level that was valid for
|
|
9
|
+
* the prior model can reach the provider on the steered turn.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import test from "node:test";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
|
|
18
|
+
import { applySupervisorModelIfConfigured } from "../auto-model-selection.ts";
|
|
19
|
+
import { clearGSDPreferencesCache } from "../preferences.ts";
|
|
20
|
+
|
|
21
|
+
const SUPERVISOR_PREFS = [
|
|
22
|
+
"auto_supervisor:",
|
|
23
|
+
" model:",
|
|
24
|
+
" model: supervisor-primary",
|
|
25
|
+
" fallbacks: [supervisor-fb]",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
interface Harness {
|
|
29
|
+
home: string;
|
|
30
|
+
base: string;
|
|
31
|
+
ctx: any;
|
|
32
|
+
pi: any;
|
|
33
|
+
setModelCalls: unknown[][];
|
|
34
|
+
setThinkingCalls: unknown[];
|
|
35
|
+
order: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function makeHarness(): Harness {
|
|
39
|
+
const home = mkdtempSync(join(tmpdir(), "gsd-supervisor-clamp-home-"));
|
|
40
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-supervisor-clamp-base-"));
|
|
41
|
+
process.env.GSD_HOME = home;
|
|
42
|
+
writeFileSync(join(home, "preferences.md"), ["---", ...SUPERVISOR_PREFS, "---", ""].join("\n"));
|
|
43
|
+
clearGSDPreferencesCache();
|
|
44
|
+
|
|
45
|
+
const setModelCalls: unknown[][] = [];
|
|
46
|
+
const setThinkingCalls: unknown[] = [];
|
|
47
|
+
const order: string[] = [];
|
|
48
|
+
|
|
49
|
+
const ctx = {
|
|
50
|
+
ui: { notify: () => {} },
|
|
51
|
+
model: { provider: "anthropic" },
|
|
52
|
+
modelRegistry: {
|
|
53
|
+
getAvailable: () => [
|
|
54
|
+
{ id: "supervisor-primary", provider: "anthropic" },
|
|
55
|
+
{ id: "supervisor-fb", provider: "anthropic" },
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
} as any;
|
|
59
|
+
|
|
60
|
+
const pi = {
|
|
61
|
+
setModel: async (...args: unknown[]) => {
|
|
62
|
+
setModelCalls.push(args);
|
|
63
|
+
order.push("setModel");
|
|
64
|
+
return true;
|
|
65
|
+
},
|
|
66
|
+
getThinkingLevel: () => "high",
|
|
67
|
+
setThinkingLevel: (level: unknown) => {
|
|
68
|
+
setThinkingCalls.push(level);
|
|
69
|
+
order.push("setThinkingLevel");
|
|
70
|
+
},
|
|
71
|
+
} as any;
|
|
72
|
+
|
|
73
|
+
return { home, base, ctx, pi, setModelCalls, setThinkingCalls, order };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function cleanup(h: Harness): void {
|
|
77
|
+
clearGSDPreferencesCache();
|
|
78
|
+
delete process.env.GSD_HOME;
|
|
79
|
+
rmSync(h.home, { recursive: true, force: true });
|
|
80
|
+
rmSync(h.base, { recursive: true, force: true });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
test("supervisor swap reapplies the thinking level after selecting the model", async () => {
|
|
84
|
+
const h = makeHarness();
|
|
85
|
+
try {
|
|
86
|
+
await applySupervisorModelIfConfigured(h.ctx, h.pi, h.base);
|
|
87
|
+
|
|
88
|
+
assert.equal(h.setModelCalls.length, 1, "model swaps to the supervisor model");
|
|
89
|
+
assert.deepEqual(
|
|
90
|
+
h.setModelCalls[0][0],
|
|
91
|
+
{ id: "supervisor-primary", provider: "anthropic" },
|
|
92
|
+
"swaps to the configured supervisor primary",
|
|
93
|
+
);
|
|
94
|
+
assert.equal(
|
|
95
|
+
h.setThinkingCalls.length,
|
|
96
|
+
1,
|
|
97
|
+
"thinking level is reclamped exactly once after the swap",
|
|
98
|
+
);
|
|
99
|
+
assert.deepEqual(
|
|
100
|
+
h.order,
|
|
101
|
+
["setModel", "setThinkingLevel"],
|
|
102
|
+
"reclamp happens after setModel, matching the hook-override pattern",
|
|
103
|
+
);
|
|
104
|
+
} finally {
|
|
105
|
+
cleanup(h);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("supervisor swap does not touch the thinking level when no model is configured", async () => {
|
|
110
|
+
const h = makeHarness();
|
|
111
|
+
// Overwrite prefs with no auto_supervisor.model.
|
|
112
|
+
writeFileSync(join(h.home, "preferences.md"), ["---", "auto_supervisor:", " soft_timeout_minutes: 1", "---", ""].join("\n"));
|
|
113
|
+
clearGSDPreferencesCache();
|
|
114
|
+
try {
|
|
115
|
+
await applySupervisorModelIfConfigured(h.ctx, h.pi, h.base);
|
|
116
|
+
|
|
117
|
+
assert.equal(h.setModelCalls.length, 0, "no swap without a configured supervisor model");
|
|
118
|
+
assert.equal(h.setThinkingCalls.length, 0, "thinking level untouched when nothing swaps");
|
|
119
|
+
} finally {
|
|
120
|
+
cleanup(h);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Tests for run-uat dispatch discovery boundaries.
|
|
3
|
+
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { test } from "node:test";
|
|
9
|
+
|
|
10
|
+
import { checkNeedsRunUat as checkNeedsRunUatFromPrompts } from "../auto-prompts.ts";
|
|
11
|
+
import {
|
|
12
|
+
closeDatabase,
|
|
13
|
+
insertMilestone,
|
|
14
|
+
insertSlice,
|
|
15
|
+
isDbAvailable,
|
|
16
|
+
openDatabase,
|
|
17
|
+
} from "../gsd-db.ts";
|
|
18
|
+
import { checkNeedsRunUat } from "../uat-dispatch.ts";
|
|
19
|
+
import type { GSDState } from "../types.ts";
|
|
20
|
+
|
|
21
|
+
function createFixtureBase(): string {
|
|
22
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-uat-dispatch-test-"));
|
|
23
|
+
mkdirSync(join(base, ".gsd", "milestones"), { recursive: true });
|
|
24
|
+
return base;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function writeRoadmap(base: string, milestoneId: string): void {
|
|
28
|
+
const dir = join(base, ".gsd", "milestones", milestoneId);
|
|
29
|
+
mkdirSync(dir, { recursive: true });
|
|
30
|
+
writeFileSync(
|
|
31
|
+
join(dir, `${milestoneId}-ROADMAP.md`),
|
|
32
|
+
[
|
|
33
|
+
`# ${milestoneId}: UAT dispatch`,
|
|
34
|
+
"",
|
|
35
|
+
"## Slices",
|
|
36
|
+
"",
|
|
37
|
+
"- [x] **S01: First slice** `risk:low` `depends:[]`",
|
|
38
|
+
"- [ ] **S02: Next slice** `risk:low` `depends:[S01]`",
|
|
39
|
+
"",
|
|
40
|
+
"## Boundary Map",
|
|
41
|
+
"",
|
|
42
|
+
].join("\n"),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function writeSliceFile(
|
|
47
|
+
base: string,
|
|
48
|
+
milestoneId: string,
|
|
49
|
+
sliceId: string,
|
|
50
|
+
suffix: string,
|
|
51
|
+
content: string,
|
|
52
|
+
): void {
|
|
53
|
+
const dir = join(base, ".gsd", "milestones", milestoneId, "slices", sliceId);
|
|
54
|
+
mkdirSync(dir, { recursive: true });
|
|
55
|
+
writeFileSync(join(dir, `${sliceId}-${suffix}.md`), content);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
test("checkNeedsRunUat resolves runtime harness dispatch from UAT plus summary context", async (t) => {
|
|
59
|
+
const base = createFixtureBase();
|
|
60
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
61
|
+
|
|
62
|
+
writeRoadmap(base, "M001");
|
|
63
|
+
writeSliceFile(
|
|
64
|
+
base,
|
|
65
|
+
"M001",
|
|
66
|
+
"S01",
|
|
67
|
+
"UAT",
|
|
68
|
+
[
|
|
69
|
+
"# S01 UAT",
|
|
70
|
+
"",
|
|
71
|
+
"## UAT Type",
|
|
72
|
+
"- UAT mode: browser-executable",
|
|
73
|
+
"",
|
|
74
|
+
"## Preconditions",
|
|
75
|
+
"- Start the dev server with `npm run test:server`.",
|
|
76
|
+
].join("\n"),
|
|
77
|
+
);
|
|
78
|
+
writeSliceFile(
|
|
79
|
+
base,
|
|
80
|
+
"M001",
|
|
81
|
+
"S01",
|
|
82
|
+
"SUMMARY",
|
|
83
|
+
[
|
|
84
|
+
"# S01 Summary",
|
|
85
|
+
"",
|
|
86
|
+
"Verification: `npm run test:uat` passed and exercises the browser harness end-to-end.",
|
|
87
|
+
].join("\n"),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
assert.deepEqual(await checkNeedsRunUat(base, "M001", { uat_dispatch: true }, [{ sliceId: "S01" }]), {
|
|
91
|
+
sliceId: "S01",
|
|
92
|
+
uatType: "runtime-executable",
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("checkNeedsRunUat skips slices that already have an ASSESSMENT verdict", async (t) => {
|
|
97
|
+
const base = createFixtureBase();
|
|
98
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
99
|
+
|
|
100
|
+
writeRoadmap(base, "M001");
|
|
101
|
+
writeSliceFile(
|
|
102
|
+
base,
|
|
103
|
+
"M001",
|
|
104
|
+
"S01",
|
|
105
|
+
"UAT",
|
|
106
|
+
[
|
|
107
|
+
"# S01 UAT",
|
|
108
|
+
"",
|
|
109
|
+
"## UAT Type",
|
|
110
|
+
"- UAT mode: artifact-driven",
|
|
111
|
+
].join("\n"),
|
|
112
|
+
);
|
|
113
|
+
writeSliceFile(base, "M001", "S01", "ASSESSMENT", "---\nverdict: PASS\n---\n# UAT Assessment\n");
|
|
114
|
+
|
|
115
|
+
assert.equal(await checkNeedsRunUat(base, "M001", { uat_dispatch: true }, [{ sliceId: "S01" }]), null);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("auto-prompts keeps the compatibility checkNeedsRunUat wrapper", async (t) => {
|
|
119
|
+
const base = createFixtureBase();
|
|
120
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
121
|
+
|
|
122
|
+
writeRoadmap(base, "M001");
|
|
123
|
+
writeSliceFile(
|
|
124
|
+
base,
|
|
125
|
+
"M001",
|
|
126
|
+
"S01",
|
|
127
|
+
"UAT",
|
|
128
|
+
[
|
|
129
|
+
"# S01 UAT",
|
|
130
|
+
"",
|
|
131
|
+
"## UAT Type",
|
|
132
|
+
"- UAT mode: human-experience",
|
|
133
|
+
].join("\n"),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const legacyState: GSDState = {
|
|
137
|
+
activeMilestone: { id: "M001", title: "UAT dispatch" },
|
|
138
|
+
activeSlice: { id: "S02", title: "Next slice" },
|
|
139
|
+
activeTask: null,
|
|
140
|
+
phase: "planning",
|
|
141
|
+
recentDecisions: [],
|
|
142
|
+
blockers: [],
|
|
143
|
+
nextAction: "Plan S02",
|
|
144
|
+
registry: [],
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
assert.deepEqual(
|
|
148
|
+
await checkNeedsRunUatFromPrompts(base, "M001", legacyState, { uat_dispatch: true }),
|
|
149
|
+
{ sliceId: "S01", uatType: "human-experience" },
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("checkNeedsRunUat treats the DB as authoritative and ignores roadmap fallback when DB slices exist but none are complete", async (t) => {
|
|
154
|
+
const base = createFixtureBase();
|
|
155
|
+
t.after(() => {
|
|
156
|
+
closeDatabase();
|
|
157
|
+
rmSync(base, { recursive: true, force: true });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// DB knows this milestone's slices, but none are complete.
|
|
161
|
+
openDatabase(":memory:");
|
|
162
|
+
assert.ok(isDbAvailable());
|
|
163
|
+
insertMilestone({ id: "M001", title: "UAT dispatch", status: "active" });
|
|
164
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "First slice", status: "active", risk: "low", depends: [] });
|
|
165
|
+
insertSlice({ id: "S02", milestoneId: "M001", title: "Next slice", status: "pending", risk: "low", depends: [] });
|
|
166
|
+
|
|
167
|
+
// The roadmap shows S01 completed and a dispatchable UAT file exists, so the
|
|
168
|
+
// roadmap fallback *would* dispatch S01 if it were (incorrectly) consulted.
|
|
169
|
+
writeRoadmap(base, "M001");
|
|
170
|
+
writeSliceFile(
|
|
171
|
+
base,
|
|
172
|
+
"M001",
|
|
173
|
+
"S01",
|
|
174
|
+
"UAT",
|
|
175
|
+
["# S01 UAT", "", "## UAT Type", "- UAT mode: human-experience"].join("\n"),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// DB is authoritative: no completed slices means no dispatch, and the roadmap
|
|
179
|
+
// fallback candidate must NOT be consulted (regression for #1268).
|
|
180
|
+
assert.equal(
|
|
181
|
+
await checkNeedsRunUat(base, "M001", { uat_dispatch: true }, [{ sliceId: "S01" }]),
|
|
182
|
+
null,
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("checkNeedsRunUat uses roadmap fallback candidates when the DB has no slice rows for the milestone", async (t) => {
|
|
187
|
+
const base = createFixtureBase();
|
|
188
|
+
t.after(() => {
|
|
189
|
+
closeDatabase();
|
|
190
|
+
rmSync(base, { recursive: true, force: true });
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// DB is available but has no rows for M001, so it has no authoritative view.
|
|
194
|
+
openDatabase(":memory:");
|
|
195
|
+
assert.ok(isDbAvailable());
|
|
196
|
+
|
|
197
|
+
writeRoadmap(base, "M001");
|
|
198
|
+
writeSliceFile(
|
|
199
|
+
base,
|
|
200
|
+
"M001",
|
|
201
|
+
"S01",
|
|
202
|
+
"UAT",
|
|
203
|
+
["# S01 UAT", "", "## UAT Type", "- UAT mode: human-experience"].join("\n"),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// With no DB slice rows, the roadmap-derived fallback candidate is honored.
|
|
207
|
+
assert.deepEqual(
|
|
208
|
+
await checkNeedsRunUat(base, "M001", { uat_dispatch: true }, [{ sliceId: "S01" }]),
|
|
209
|
+
{ sliceId: "S01", uatType: "human-experience" },
|
|
210
|
+
);
|
|
211
|
+
});
|
|
@@ -3,14 +3,20 @@ import assert from "node:assert/strict";
|
|
|
3
3
|
|
|
4
4
|
import { closeDatabase, openDatabase, _getAdapter } from "../gsd-db.ts";
|
|
5
5
|
import { UokGateRunner } from "../uok/gate-runner.ts";
|
|
6
|
+
import {
|
|
7
|
+
clearUnifiedAuditOverrideForTests,
|
|
8
|
+
setUnifiedAuditEnabled,
|
|
9
|
+
} from "../uok/audit-toggle.ts";
|
|
6
10
|
|
|
7
11
|
test.beforeEach(() => {
|
|
12
|
+
setUnifiedAuditEnabled(true);
|
|
8
13
|
closeDatabase();
|
|
9
14
|
const ok = openDatabase(":memory:");
|
|
10
15
|
assert.equal(ok, true);
|
|
11
16
|
});
|
|
12
17
|
|
|
13
18
|
test.afterEach(() => {
|
|
19
|
+
clearUnifiedAuditOverrideForTests();
|
|
14
20
|
closeDatabase();
|
|
15
21
|
});
|
|
16
22
|
|
|
@@ -69,6 +75,39 @@ test("uok gate runner returns manual-attention for unknown gate id", async () =>
|
|
|
69
75
|
assert.equal(result.failureClass, "unknown");
|
|
70
76
|
});
|
|
71
77
|
|
|
78
|
+
test("uok gate runner emits audit by default for standalone callers", async () => {
|
|
79
|
+
clearUnifiedAuditOverrideForTests();
|
|
80
|
+
const runner = new UokGateRunner();
|
|
81
|
+
await runner.run("standalone-missing-gate", {
|
|
82
|
+
basePath: process.cwd(),
|
|
83
|
+
traceId: "trace-standalone",
|
|
84
|
+
turnId: "turn-standalone",
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const adapter = _getAdapter();
|
|
88
|
+
const rows = adapter?.prepare(
|
|
89
|
+
"SELECT type FROM audit_events WHERE trace_id = 'trace-standalone'",
|
|
90
|
+
).all() ?? [];
|
|
91
|
+
assert.equal(rows.length, 1);
|
|
92
|
+
assert.equal(rows[0]?.["type"], "gate-run");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("uok gate runner skips audit when unified audit is explicitly disabled", async () => {
|
|
96
|
+
setUnifiedAuditEnabled(false);
|
|
97
|
+
const runner = new UokGateRunner();
|
|
98
|
+
await runner.run("disabled-audit-missing-gate", {
|
|
99
|
+
basePath: process.cwd(),
|
|
100
|
+
traceId: "trace-disabled-audit",
|
|
101
|
+
turnId: "turn-disabled-audit",
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const adapter = _getAdapter();
|
|
105
|
+
const rows = adapter?.prepare(
|
|
106
|
+
"SELECT type FROM audit_events WHERE trace_id = 'trace-disabled-audit'",
|
|
107
|
+
).all() ?? [];
|
|
108
|
+
assert.equal(rows.length, 0);
|
|
109
|
+
});
|
|
110
|
+
|
|
72
111
|
// Regression tests for #4950
|
|
73
112
|
|
|
74
113
|
test("uok gate runner: gate.execute throws — outcome is fail, audit emitted, DB row written, no exception escapes", async () => {
|