From a024e8818d7597505fc674f28876dbddb560a7a0 Mon Sep 17 00:00:00 2001 From: oddkit-agent Date: Mon, 20 Apr 2026 12:10:11 +0000 Subject: [PATCH 1/2] fix(telemetry_public): add server_time + full envelope to match tool convention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit telemetry_public was the sole tool returning a bare {action, result} envelope. Every other oddkit tool (including telemetry_policy per PR #108) returns the full {action, result, server_time, assistant_text, debug} shape. Canon reference: klappy://docs/appendices/epoch-8-2 — server_time in every response. Precedent: PR #108 — telemetry_policy envelope conformance. Changes: - workers/src/index.ts: telemetry_public handler emits full envelope on both success and not-configured error paths - workers/test/canon-tool-envelope.smoke.mjs: add telemetry_public assertion via expectFullEnvelope Caught by v0.21.0 regression test sweep (2026-04-20). --- workers/src/index.ts | 19 +++++++++++++++++++ workers/test/canon-tool-envelope.smoke.mjs | 9 ++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/workers/src/index.ts b/workers/src/index.ts index 58fb122..045fd92 100644 --- a/workers/src/index.ts +++ b/workers/src/index.ts @@ -449,6 +449,8 @@ Time filter example: WHERE timestamp > NOW() - INTERVAL '30' DAY`, openWorldHint: true, }, async ({ sql }) => { + const startTime = Date.now(); + if (!env.CF_ACCOUNT_ID || !env.CF_API_TOKEN) { return { content: [{ @@ -456,6 +458,9 @@ Time filter example: WHERE timestamp > NOW() - INTERVAL '30' DAY`, text: JSON.stringify({ action: "telemetry_public", result: { error: "Telemetry queries not configured. CF_ACCOUNT_ID and CF_API_TOKEN required." }, + server_time: new Date().toISOString(), + assistant_text: "Telemetry queries not configured. Set CF_ACCOUNT_ID and CF_API_TOKEN.", + debug: { duration_ms: Date.now() - startTime }, }, null, 2), }], }; @@ -482,12 +487,26 @@ Time filter example: WHERE timestamp > NOW() - INTERVAL '30' DAY`, result = { error: "Query execution failed." }; } + // Derive row count for assistant_text when the result carries data rows + let assistantText = "Telemetry query completed."; + if (typeof result === 'object' && result !== null) { + if ('error' in result) { + assistantText = "Telemetry query completed with errors."; + } else if ('data' in result && Array.isArray((result as Record).data)) { + const rows = ((result as Record).data as unknown[]).length; + assistantText = `Telemetry query returned ${rows} row${rows === 1 ? "" : "s"}.`; + } + } + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "telemetry_public", result: { data: result, generated_at: new Date().toISOString() }, + server_time: new Date().toISOString(), + assistant_text: assistantText, + debug: { duration_ms: Date.now() - startTime }, }, null, 2), }], }; diff --git a/workers/test/canon-tool-envelope.smoke.mjs b/workers/test/canon-tool-envelope.smoke.mjs index a73eb3d..2474fa8 100644 --- a/workers/test/canon-tool-envelope.smoke.mjs +++ b/workers/test/canon-tool-envelope.smoke.mjs @@ -129,7 +129,14 @@ async function run() { `got: ${policyOverride.debug?.knowledge_base_url}`, ); - // Tool 4: oddkit_encode — canon-driven, DOLCHEO-aware. Full envelope + + // Tool 4: telemetry_public — should have full envelope (no governance_source) + console.log("\n─── Testing: telemetry_public ───"); + const telemetryPublicResult = await callTool("telemetry_public", { + sql: "SELECT 1 AS probe FROM oddkit_telemetry WHERE timestamp > NOW() - INTERVAL '1' HOUR LIMIT 1" + }); + expectFullEnvelope("telemetry_public", telemetryPublicResult); + + // Tool 5: oddkit_encode — canon-driven, DOLCHEO-aware. Full envelope + // governance_source + DOLCHEO prefix-tag batch mode + Open facet + back- // compat for unprefixed input. console.log(`\n─── oddkit_encode: envelope + governance_source ───`); From 4ce5d1a8c729711ad127a6121600729b43979642 Mon Sep 17 00:00:00 2001 From: oddkit-agent Date: Mon, 20 Apr 2026 12:28:28 +0000 Subject: [PATCH 2/2] fix(catalog): debug.generated_at is response time, not cached index timestamp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit runCatalog returned index.generated_at as debug.generated_at — the cached index build timestamp, not the current response time. Every other handler uses new Date().toISOString() for this field. Observed during v0.21.0 regression testing: debug.generated_at lagged server_time by 48 minutes in a single response envelope. Changes: - workers/src/orchestrate.ts: runCatalog now sets debug.generated_at to new Date().toISOString(). Cached index build timestamp preserved as debug.index_built_at for consumers wanting the freshness signal. - workers/test/canon-tool-envelope.smoke.mjs: add oddkit_catalog coverage. Bug slipped because catalog had no smoke assertion. Canon references: - klappy://canon/values/axioms (Axiom 1 — Reality Is Sovereign) - klappy://canon/principles/consistency-same-pattern-every-time - klappy://canon/principles/cache-fetches-and-parses (PR #125) --- workers/src/orchestrate.ts | 3 ++- workers/test/canon-tool-envelope.smoke.mjs | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/workers/src/orchestrate.ts b/workers/src/orchestrate.ts index 96c55b5..f2ba00b 100644 --- a/workers/src/orchestrate.ts +++ b/workers/src/orchestrate.ts @@ -1742,7 +1742,8 @@ async function runCatalog( debug: { knowledge_base_url: knowledgeBaseUrl, baseline_url: index.baseline_url, - generated_at: index.generated_at, + generated_at: new Date().toISOString(), // response time — consistent with all other handlers + index_built_at: index.generated_at, // preserve cache-freshness diagnostic under accurate name duration_ms: Date.now() - startMs, }, }; diff --git a/workers/test/canon-tool-envelope.smoke.mjs b/workers/test/canon-tool-envelope.smoke.mjs index 2474fa8..89ce465 100644 --- a/workers/test/canon-tool-envelope.smoke.mjs +++ b/workers/test/canon-tool-envelope.smoke.mjs @@ -603,6 +603,28 @@ async function run() { `got: met=${prereqPass.result?.prerequisites?.required_met} total=${prereqPass.result?.prerequisites?.required_total}`, ); + // Tool: oddkit_catalog — debug.generated_at must be response time, not cached index time + console.log("\n─── Testing: oddkit_catalog ───"); + const catalogResult = await callTool("oddkit", { action: "catalog", input: "recent" }); + expectFullEnvelope("oddkit_catalog", catalogResult); + + // debug.generated_at must be recent (under 60s) — not the cached index build time + const generatedAt = catalogResult.debug?.generated_at; + ok(`oddkit_catalog: debug.generated_at present`, + typeof generatedAt === "string" && /^\d{4}-\d{2}-\d{2}T/.test(generatedAt), + `got: ${generatedAt}`); + if (typeof generatedAt === "string") { + const ageMs = Date.now() - Date.parse(generatedAt); + ok(`oddkit_catalog: debug.generated_at is response time (age < 60s)`, + ageMs < 60_000 && ageMs >= 0, + `ageMs=${ageMs}`); + } + + // index_built_at is the separate, accurately-named cache-freshness diagnostic + ok(`oddkit_catalog: debug.index_built_at present`, + typeof catalogResult.debug?.index_built_at === "string", + `got: ${catalogResult.debug?.index_built_at}`); + console.log(`\n${passed} passed, ${failed} failed`); process.exit(failed === 0 ? 0 : 1); }