Merge pull request #1693 from pikasTech/codex/hwlab-1691-runtime-read-cache

fix: reuse runtime readiness for workbench reads
This commit is contained in:
Lyon
2026-06-20 12:18:43 +08:00
committed by GitHub
3 changed files with 61 additions and 0 deletions
+21
View File
@@ -27,6 +27,7 @@ export function createWorkbenchFactsStore(options = {}, actor = null) {
persistence: result?.persistence ?? null persistence: result?.persistence ?? null
}; };
} catch (error) { } catch (error) {
logWorkbenchFactsQueryFailure(options.logger ?? console, params, error);
return { return {
facts: emptyWorkbenchFacts(), facts: emptyWorkbenchFacts(),
count: 0, count: 0,
@@ -137,6 +138,26 @@ export function createWorkbenchFactsStore(options = {}, actor = null) {
}; };
} }
function logWorkbenchFactsQueryFailure(logger, params = {}, error = null) {
const families = Array.isArray(params.families) ? params.families : [];
const event = {
event: "workbench_facts_query_failed",
ok: false,
families,
hasSessionId: Boolean(params.sessionId),
hasTraceId: Boolean(params.traceId),
hasOwnerUserId: Boolean(params.ownerUserId),
errorName: error?.name ?? "Error",
errorCode: error?.code ?? null,
message: error instanceof Error ? error.message : String(error ?? "unknown"),
valuesRedacted: true
};
try {
if (typeof logger?.warn === "function") logger.warn(JSON.stringify(event));
else if (typeof logger?.log === "function") logger.log(JSON.stringify(event));
} catch {}
}
async function durableTraceSnapshot(runtimeStore, traceId) { async function durableTraceSnapshot(runtimeStore, traceId) {
if (!runtimeStore || typeof runtimeStore.queryAgentTraceEvents !== "function") return null; if (!runtimeStore || typeof runtimeStore.queryAgentTraceEvents !== "function") return null;
const safeId = safeTraceId(traceId); const safeId = safeTraceId(traceId);
+33
View File
@@ -806,6 +806,33 @@ test("configured postgres runtime persists and queries Workbench durable facts",
assert.ok(queryClient.calls.some((call) => call.sql.startsWith("SELECT session_json FROM workbench_sessions WHERE session_id = $1"))); assert.ok(queryClient.calls.some((call) => call.sql.startsWith("SELECT session_json FROM workbench_sessions WHERE session_id = $1")));
}); });
test("configured postgres runtime reuses proven readiness for Workbench durable reads", async () => {
const queryClient = createFakePostgresClient({ migrationReady: true });
const store = createConfiguredCloudRuntimeStore({
env: {
HWLAB_CLOUD_RUNTIME_ADAPTER: "postgres",
HWLAB_CLOUD_RUNTIME_DURABLE: "true"
},
dbUrl: "postgres://hwlab_redacted@db.example.invalid:5432/hwlab",
queryClient,
now: () => "2026-06-20T10:00:00.000Z"
});
await store.writeWorkbenchFacts({
facts: {
sessions: [{ sessionId: "ses_ready_cached", ownerUserId: "usr_ready", status: "running", updatedAt: "2026-06-20T10:00:01.000Z" }]
}
});
queryClient.calls.length = 0;
const first = await store.queryWorkbenchFacts({ families: ["sessions"], limit: 10 });
const second = await store.queryWorkbenchFacts({ families: ["sessions"], limit: 10 });
assert.equal(first.count, 1);
assert.equal(second.count, 1);
assert.equal(queryClient.calls.filter((call) => isReadinessQuery(call.sql)).length, 0);
assert.equal(queryClient.calls.filter((call) => call.sql.startsWith("SELECT session_json FROM workbench_sessions")).length, 2);
});
test("configured postgres runtime queries requested workbench fact families concurrently", async () => { test("configured postgres runtime queries requested workbench fact families concurrently", async () => {
const expectedReads = 4; const expectedReads = 4;
let started = 0; let started = 0;
@@ -930,6 +957,12 @@ test("configured postgres runtime pushes trace event query filters into SQL", as
assert.deepEqual(select.params, ["ses_filter_b"]); assert.deepEqual(select.params, ["ses_filter_b"]);
}); });
function isReadinessQuery(sql) {
return sql.includes("information_schema.columns")
|| sql.startsWith("SELECT id, schema_version FROM hwlab_schema_migrations")
|| sql.startsWith("SELECT COUNT(*)::int AS count FROM ");
}
function createFakePostgresClient({ function createFakePostgresClient({
migrationReady = true, migrationReady = true,
migrationErrorCode = null, migrationErrorCode = null,
+7
View File
@@ -1372,7 +1372,10 @@ export class PostgresCloudRuntimeStore {
} }
async assertReadyForDurableReads(method) { async assertReadyForDurableReads(method) {
const current = this.summary();
if (isReadyForDurableRuntimeRead(current)) return;
const readiness = await this.readiness(); const readiness = await this.readiness();
if (isReadyForDurableRuntimeRead(readiness)) return;
if (readiness.ready !== true || readiness.durable !== true || readiness.liveRuntimeEvidence !== true) { if (readiness.ready !== true || readiness.durable !== true || readiness.liveRuntimeEvidence !== true) {
throw durableRuntimeReadBlockedError(method, readiness); throw durableRuntimeReadBlockedError(method, readiness);
} }
@@ -1697,6 +1700,10 @@ export class PostgresCloudRuntimeStore {
} }
} }
function isReadyForDurableRuntimeRead(readiness = {}) {
return readiness.ready === true && readiness.durable === true && readiness.liveRuntimeEvidence === true;
}
function normalizeCapabilityInputs(params) { function normalizeCapabilityInputs(params) {
const value = params.capabilities ?? params.capability ?? params; const value = params.capabilities ?? params.capability ?? params;
if (Array.isArray(value)) { if (Array.isArray(value)) {