feat(gsd-uok): flip default to UOK with emergency legacy fallback

This commit is contained in:
Jeremy McSpadden 2026-04-14 20:41:57 -05:00
parent 5a6a13eb39
commit f9f712098d
9 changed files with 77 additions and 8 deletions

View file

@ -191,8 +191,10 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
- `hooks`: boolean — enable routing hooks. Default: `true`.
- `capability_routing`: boolean — enable capability-profile scoring for model selection within a tier. Requires `enabled: true`. Default: `false`.
- `uok`: Unified Orchestration Kernel controls (all flags default to `false` during migration). Keys:
- `enabled`: boolean — enable kernel wrappers and contract observers.
- `uok`: Unified Orchestration Kernel controls. Keys:
- `enabled`: boolean — enable kernel wrappers and contract observers. Default: `true`.
- `legacy_fallback.enabled`: boolean — emergency release fallback that forces legacy orchestration behavior even when `uok.enabled` is `true`. Default: `false`.
- Runtime override: set `GSD_UOK_FORCE_LEGACY=1` (or `GSD_UOK_LEGACY_FALLBACK=1`) to force legacy behavior for the current process.
- `gates.enabled`: boolean — route checks through the unified gate runner and persist `gate_runs`.
- `model_policy.enabled`: boolean — enforce policy filtering before model capability scoring.
- `execution_graph.enabled`: boolean — enable DAG scheduler facade/adapters for execution.

View file

@ -213,6 +213,9 @@ export type UokTurnActionMode = "commit" | "snapshot" | "status-only";
export interface UokPreferences {
enabled?: boolean;
legacy_fallback?: {
enabled?: boolean;
};
gates?: {
enabled?: boolean;
};

View file

@ -178,7 +178,7 @@ export function validatePreferences(preferences: GSDPreferences): {
}
const parseEnabledBlock = (
key: "gates" | "model_policy" | "execution_graph" | "audit_unified" | "plan_v2",
key: "legacy_fallback" | "gates" | "model_policy" | "execution_graph" | "audit_unified" | "plan_v2",
): void => {
const value = raw[key];
if (value === undefined) return;
@ -201,6 +201,7 @@ export function validatePreferences(preferences: GSDPreferences): {
}
};
parseEnabledBlock("legacy_fallback");
parseEnabledBlock("gates");
parseEnabledBlock("model_policy");
parseEnabledBlock("execution_graph");
@ -243,6 +244,7 @@ export function validatePreferences(preferences: GSDPreferences): {
const knownUokKeys = new Set([
"enabled",
"legacy_fallback",
"gates",
"model_policy",
"execution_graph",

View file

@ -383,6 +383,9 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
uok: (base.uok || override.uok)
? {
enabled: override.uok?.enabled ?? base.uok?.enabled,
legacy_fallback: (base.uok?.legacy_fallback || override.uok?.legacy_fallback)
? { ...(base.uok?.legacy_fallback ?? {}), ...(override.uok?.legacy_fallback ?? {}) }
: undefined,
gates: (base.uok?.gates || override.uok?.gates)
? { ...(base.uok?.gates ?? {}), ...(override.uok?.gates ?? {}) }
: undefined,

View file

@ -40,7 +40,9 @@ dynamic_routing:
cross_provider:
hooks:
uok:
enabled: false
enabled: true
legacy_fallback:
enabled: false
gates:
enabled: false
model_policy:

View file

@ -0,0 +1,39 @@
import test from "node:test";
import assert from "node:assert/strict";
import { resolveUokFlags } from "../uok/flags.ts";
test("uok flags default to enabled when preference is unset", () => {
const flags = resolveUokFlags(undefined);
assert.equal(flags.enabled, true);
assert.equal(flags.legacyFallback, false);
});
test("uok legacy fallback preference forces legacy path", () => {
const flags = resolveUokFlags({
uok: {
enabled: true,
legacy_fallback: { enabled: true },
},
});
assert.equal(flags.enabled, false);
assert.equal(flags.legacyFallback, true);
});
test("uok legacy fallback env var forces legacy path", () => {
const previous = process.env.GSD_UOK_FORCE_LEGACY;
process.env.GSD_UOK_FORCE_LEGACY = "1";
try {
const flags = resolveUokFlags({
uok: {
enabled: true,
},
});
assert.equal(flags.enabled, false);
assert.equal(flags.legacyFallback, true);
} finally {
if (previous === undefined) delete process.env.GSD_UOK_FORCE_LEGACY;
else process.env.GSD_UOK_FORCE_LEGACY = previous;
}
});

View file

@ -7,6 +7,7 @@ test("uok preferences validate nested flags and turn_action", () => {
const input = {
uok: {
enabled: true,
legacy_fallback: { enabled: false },
gates: { enabled: true },
model_policy: { enabled: true },
execution_graph: { enabled: false },
@ -23,6 +24,7 @@ test("uok preferences validate nested flags and turn_action", () => {
const result = validatePreferences(input as never);
assert.equal(result.errors.length, 0);
assert.equal(result.preferences.uok?.enabled, true);
assert.equal(result.preferences.uok?.legacy_fallback?.enabled, false);
assert.equal(result.preferences.uok?.gitops?.turn_action, "status-only");
assert.equal(result.preferences.uok?.plan_v2?.enabled, true);
});

View file

@ -3,6 +3,7 @@ import { loadEffectiveGSDPreferences } from "../preferences.js";
export interface UokFlags {
enabled: boolean;
legacyFallback: boolean;
gates: boolean;
modelPolicy: boolean;
executionGraph: boolean;
@ -13,10 +14,20 @@ export interface UokFlags {
planV2: boolean;
}
function envForcesLegacyFallback(): boolean {
const raw = process.env.GSD_UOK_FORCE_LEGACY ?? process.env.GSD_UOK_LEGACY_FALLBACK;
if (!raw) return false;
const normalized = raw.trim().toLowerCase();
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
}
export function resolveUokFlags(prefs: GSDPreferences | undefined): UokFlags {
const uok = prefs?.uok;
const legacyFallback = uok?.legacy_fallback?.enabled === true || envForcesLegacyFallback();
const enabledByPreference = uok?.enabled ?? true;
return {
enabled: uok?.enabled === true,
enabled: enabledByPreference && !legacyFallback,
legacyFallback,
gates: uok?.gates?.enabled === true,
modelPolicy: uok?.model_policy?.enabled === true,
executionGraph: uok?.execution_graph?.enabled === true,

View file

@ -36,6 +36,11 @@ function writeParityEvent(basePath: string, event: Record<string, unknown>): voi
}
}
function resolveKernelPathLabel(flags: ReturnType<typeof resolveUokFlags>): "uok-wrapper" | "legacy-wrapper" | "legacy-fallback" {
if (flags.legacyFallback) return "legacy-fallback";
return flags.enabled ? "uok-wrapper" : "legacy-wrapper";
}
export async function runAutoLoopWithUok(args: RunAutoLoopWithUokArgs): Promise<void> {
const { ctx, pi, s, deps, runLegacyLoop } = args;
const prefs = deps.loadEffectiveGSDPreferences()?.preferences;
@ -44,7 +49,7 @@ export async function runAutoLoopWithUok(args: RunAutoLoopWithUokArgs): Promise<
writeParityEvent(s.basePath, {
ts: new Date().toISOString(),
path: flags.enabled ? "uok-wrapper" : "legacy-wrapper",
path: resolveKernelPathLabel(flags),
flags,
phase: "enter",
});
@ -81,7 +86,7 @@ export async function runAutoLoopWithUok(args: RunAutoLoopWithUokArgs): Promise<
await runLegacyLoop(ctx, pi, s, decoratedDeps);
writeParityEvent(s.basePath, {
ts: new Date().toISOString(),
path: flags.enabled ? "uok-wrapper" : "legacy-wrapper",
path: resolveKernelPathLabel(flags),
flags,
phase: "exit",
status: "ok",
@ -89,7 +94,7 @@ export async function runAutoLoopWithUok(args: RunAutoLoopWithUokArgs): Promise<
} catch (err) {
writeParityEvent(s.basePath, {
ts: new Date().toISOString(),
path: flags.enabled ? "uok-wrapper" : "legacy-wrapper",
path: resolveKernelPathLabel(flags),
flags,
phase: "exit",
status: "error",