From d8570d059e499e6a83df895c0e3528c731b468a3 Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Wed, 6 May 2026 14:48:15 +0200 Subject: [PATCH] sf snapshot: uncommitted changes after 38m inactivity --- .gitignore | 1 + biome.json | 1 + src/resources/extensions/sf/sf-db.js | 30 ++++++------- .../sf/tests/uok-gate-runner.test.mjs | 30 ++++++++++--- .../sf/tests/uok-unit-runtime.test.mjs | 10 +++++ .../extensions/sf/uok/metrics-exposition.js | 42 ++++++++++++++----- .../extensions/sf/uok/unit-runtime.js | 13 +++--- 7 files changed, 92 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index d19dd5bf1..51fe6fdc5 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ Thumbs.db *~ .idea/ .vscode/ +.vtcode/ *.code-workspace .env .env.* diff --git a/biome.json b/biome.json index 711bb6704..840247708 100644 --- a/biome.json +++ b/biome.json @@ -8,6 +8,7 @@ "files": { "includes": [ "**/*.{js,cjs,mjs,ts,tsx,json,jsonc,css,html}", + "!!.vtcode", "!!.sf", "!!.omg", "!!**/dist", diff --git a/src/resources/extensions/sf/sf-db.js b/src/resources/extensions/sf/sf-db.js index 66773dedc..54d694587 100644 --- a/src/resources/extensions/sf/sf-db.js +++ b/src/resources/extensions/sf/sf-db.js @@ -4115,9 +4115,9 @@ export function getGateCircuitBreaker(gateId) { */ export function updateGateCircuitBreaker(gateId, updates) { if (!currentDb) return; - currentDb - .prepare( - `INSERT INTO gate_circuit_breakers ( + currentDb + .prepare( + `INSERT INTO gate_circuit_breakers ( gate_id, state, failure_streak, last_failure_at, opened_at, half_open_attempts, updated_at ) VALUES ( :gate_id, :state, :failure_streak, :last_failure_at, :opened_at, :half_open_attempts, :updated_at @@ -4129,23 +4129,25 @@ export function updateGateCircuitBreaker(gateId, updates) { opened_at = COALESCE(excluded.opened_at, gate_circuit_breakers.opened_at), half_open_attempts = excluded.half_open_attempts, updated_at = excluded.updated_at`, - ) - .run({ - ":gate_id": gateId, - ":state": updates.state ?? "closed", - ":failure_streak": updates.failureStreak ?? 0, - ":last_failure_at": updates.lastFailureAt ?? null, - ":opened_at": updates.openedAt ?? null, - ":half_open_attempts": updates.halfOpenAttempts ?? 0, - ":updated_at": new Date().toISOString(), - }); + ) + .run({ + ":gate_id": gateId, + ":state": updates.state ?? "closed", + ":failure_streak": updates.failureStreak ?? 0, + ":last_failure_at": updates.lastFailureAt ?? null, + ":opened_at": updates.openedAt ?? null, + ":half_open_attempts": updates.halfOpenAttempts ?? 0, + ":updated_at": new Date().toISOString(), + }); return { total: 0, avgMs: 0, p50Ms: 0, p95Ms: 0, maxMs: 0 }; } export function getGateLatencyStats(gateId, windowHours = 24) { if (!currentDb) { return { total: 0, avgMs: 0, p50Ms: 0, p95Ms: 0, maxMs: 0 }; } - const cutoff = new Date(Date.now() - windowHours * 60 * 60 * 1000).toISOString(); + const cutoff = new Date( + Date.now() - windowHours * 60 * 60 * 1000, + ).toISOString(); try { const row = currentDb .prepare( diff --git a/src/resources/extensions/sf/tests/uok-gate-runner.test.mjs b/src/resources/extensions/sf/tests/uok-gate-runner.test.mjs index 2911ef4ea..0830bc500 100644 --- a/src/resources/extensions/sf/tests/uok-gate-runner.test.mjs +++ b/src/resources/extensions/sf/tests/uok-gate-runner.test.mjs @@ -228,7 +228,11 @@ test("run_records_every_attempt_to_gate_runs", async () => { type: "verification", execute: async () => { calls++; - return { outcome: "fail", failureClass: "execution", rationale: `attempt ${calls}` }; + return { + outcome: "fail", + failureClass: "execution", + rationale: `attempt ${calls}`, + }; }, }); await runner.run("audit-gate", makeCtx()); @@ -311,6 +315,7 @@ test("circuitBreaker_when_fails_incrementally_opens_after_threshold", async () = assert.equal(breaker.state, "open"); assert.equal(breaker.failureStreak, 5); assert.ok(breaker.openedAt != null); + assert.equal(calls, 10); }); test("circuitBreaker_when_open_blocks_execution", async () => { @@ -366,7 +371,11 @@ test("circuitBreaker_half_open_fail_reopens", async () => { runner.register({ id: "cb-reopen", type: "verification", - execute: async () => ({ outcome: "fail", failureClass: "execution", rationale: "nope" }), + execute: async () => ({ + outcome: "fail", + failureClass: "execution", + rationale: "nope", + }), }); updateGateCircuitBreaker("cb-reopen", { state: "half-open", @@ -425,7 +434,11 @@ test("validateGate_when_missing_id_returns_false", () => { }); test("validateGate_when_empty_id_returns_false", () => { - const result = validateGate({ id: "", type: "policy", execute: async () => ({}) }); + const result = validateGate({ + id: "", + type: "policy", + execute: async () => ({}), + }); assert.equal(result.valid, false); assert.ok(result.reason.includes("id")); }); @@ -450,8 +463,15 @@ test("validateGate_when_execute_not_function_returns_false", () => { test("runner_register_when_invalid_gate_throws", () => { const runner = new UokGateRunner(); - assert.throws(() => runner.register({ id: "", type: "policy", execute: async () => ({}) }), /id/); - assert.throws(() => runner.register({ id: "x", type: "", execute: async () => ({}) }), /type/); + assert.throws( + () => + runner.register({ id: "", type: "policy", execute: async () => ({}) }), + /id/, + ); + assert.throws( + () => runner.register({ id: "x", type: "", execute: async () => ({}) }), + /type/, + ); assert.throws(() => runner.register({ id: "x", type: "policy" }), /execute/); assert.throws(() => runner.register(null), /object/); }); diff --git a/src/resources/extensions/sf/tests/uok-unit-runtime.test.mjs b/src/resources/extensions/sf/tests/uok-unit-runtime.test.mjs index a20ed1c18..ab282052f 100644 --- a/src/resources/extensions/sf/tests/uok-unit-runtime.test.mjs +++ b/src/resources/extensions/sf/tests/uok-unit-runtime.test.mjs @@ -160,6 +160,16 @@ test("decide_when_stale_with_budget_returns_retry", () => { assert.equal(d.action, "retry"); }); +test("decide_when_runaway_recovered_requires_explicit_reset", () => { + const d = decideUnitRuntimeDispatch({ + status: "runaway-recovered", + retryCount: 0, + maxRetries: 1, + }); + assert.equal(d.action, "block"); + assert.equal(d.reasonCode, "runaway-recovery-reset-required"); +}); + test("decide_when_blocked_returns_notify", () => { const d = decideUnitRuntimeDispatch({ status: "blocked" }); assert.equal(d.action, "notify"); diff --git a/src/resources/extensions/sf/uok/metrics-exposition.js b/src/resources/extensions/sf/uok/metrics-exposition.js index a9ec78d4c..5a5318312 100644 --- a/src/resources/extensions/sf/uok/metrics-exposition.js +++ b/src/resources/extensions/sf/uok/metrics-exposition.js @@ -41,16 +41,32 @@ function collectGateMetrics() { const lines = []; for (const gateId of GATE_NAMES) { const stats = getGateRunStats(gateId, 24); - lines.push(fmtCounter("uok_gate_runs_total", stats.total, { gate_id: gateId })); - lines.push(fmtCounter("uok_gate_runs_passed_total", stats.pass, { gate_id: gateId })); - lines.push(fmtCounter("uok_gate_runs_failed_total", stats.fail, { gate_id: gateId })); - lines.push(fmtCounter("uok_gate_runs_retry_total", stats.retry, { gate_id: gateId })); + lines.push( + fmtCounter("uok_gate_runs_total", stats.total, { gate_id: gateId }), + ); + lines.push( + fmtCounter("uok_gate_runs_passed_total", stats.pass, { gate_id: gateId }), + ); + lines.push( + fmtCounter("uok_gate_runs_failed_total", stats.fail, { gate_id: gateId }), + ); + lines.push( + fmtCounter("uok_gate_runs_retry_total", stats.retry, { gate_id: gateId }), + ); const latency = getGateLatencyStats(gateId, 24); - lines.push(fmtGauge("uok_gate_latency_avg_ms", latency.avgMs, { gate_id: gateId })); - lines.push(fmtGauge("uok_gate_latency_p50_ms", latency.p50Ms, { gate_id: gateId })); - lines.push(fmtGauge("uok_gate_latency_p95_ms", latency.p95Ms, { gate_id: gateId })); - lines.push(fmtGauge("uok_gate_latency_max_ms", latency.maxMs, { gate_id: gateId })); + lines.push( + fmtGauge("uok_gate_latency_avg_ms", latency.avgMs, { gate_id: gateId }), + ); + lines.push( + fmtGauge("uok_gate_latency_p50_ms", latency.p50Ms, { gate_id: gateId }), + ); + lines.push( + fmtGauge("uok_gate_latency_p95_ms", latency.p95Ms, { gate_id: gateId }), + ); + lines.push( + fmtGauge("uok_gate_latency_max_ms", latency.maxMs, { gate_id: gateId }), + ); const breaker = getGateCircuitBreaker(gateId); const stateMap = { closed: 0, "half-open": 1, open: 2 }; @@ -61,9 +77,13 @@ function collectGateMetrics() { }), ); lines.push( - fmtGauge("uok_gate_circuit_breaker_failure_streak", breaker.failureStreak, { - gate_id: gateId, - }), + fmtGauge( + "uok_gate_circuit_breaker_failure_streak", + breaker.failureStreak, + { + gate_id: gateId, + }, + ), ); } return lines; diff --git a/src/resources/extensions/sf/uok/unit-runtime.js b/src/resources/extensions/sf/uok/unit-runtime.js index 0a779cd5b..d7c6a3b69 100644 --- a/src/resources/extensions/sf/uok/unit-runtime.js +++ b/src/resources/extensions/sf/uok/unit-runtime.js @@ -98,11 +98,7 @@ export const UNIT_RUNTIME_TRANSITIONS = { notified: ["queued"], }; const DEFAULT_UNIT_RUNTIME_MAX_RETRIES = 1; -const RETRYABLE_TERMINAL_STATUSES = new Set([ - "failed", - "stale", - "runaway-recovered", -]); +const RETRYABLE_TERMINAL_STATUSES = new Set(["failed", "stale"]); function hasUpdate(updates, key) { return Object.hasOwn(updates, key); } @@ -242,6 +238,13 @@ export function decideUnitRuntimeDispatch(record, options = {}) { ...common, }; } + if (state.status === "runaway-recovered") { + return { + action: "block", + reasonCode: "runaway-recovery-reset-required", + ...common, + }; + } if (RETRYABLE_TERMINAL_STATUSES.has(state.status)) { if (remaining > 0) { return {