fix: the My Resources diagnostics survive a deploy
Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 47s
Forgejo Docker Build / Root app tests (push) Successful in 52s
Forgejo Android APK / Build signed APK (push) Successful in 2m5s
Forgejo Docker Build / Build Docker image (push) Successful in 17s
Forgejo Docker Build / Deploy to the host (push) Failing after 0s

logRefine writes the one line that answers "did that modification change
anything" — path, before and after size, CHANGED=yes/no, figures, model,
instruction. It went to console, so it lived in the container's stdout and was
destroyed the next time the container was recreated.

That cost a diagnosis today: a modification came back unchanged, the user asked
why, and the evidence had already been deleted by a deploy. The deck-fallback
warnings and the deck-vocabulary gaps had the same problem, and those exist
specifically to be read later — the vocabulary gaps are meant to show which
shapes to build next, which is a question about weeks, not about one container.

All of them now go through logger, which writes the dated file in the
scribe-logs volume and ships to Loki when it is configured, and carries the
event as structured data rather than only as a formatted string.

console.error is left alone: those are failures, and logger.error already
echoes to the console.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
This commit is contained in:
Daniel 2026-09-12 06:09:34 +02:00
parent c9b6d6b3c9
commit 491a2b0811
2 changed files with 17 additions and 7 deletions

View file

@ -24,6 +24,13 @@ var resourceImages = require('../utils/resourceImages');
var deckSchema = require('../utils/deckSchema');
var deckBuild = require('../utils/deckBuild');
var deckReview = require('../utils/deckReview');
// The diagnostics below are the record of what a generation or a modification
// actually did. console goes to the container's stdout, which is destroyed every
// time the container is recreated — so the one question these exist to answer,
// "did that modification change anything", became unanswerable after any deploy.
// logger writes to the dated file in the scribe-logs volume, and to Loki when it
// is configured, so the record outlives the container.
var logger = require('../utils/logger');
var webSearch = require('../utils/webSearch');
var pubmedSearch = require('../utils/pubmedSearch');
var documentExport = require('../utils/documentExport');
@ -375,20 +382,21 @@ router.post('/my-resources/generate', async function (req, res) {
// plain-markdown deck is a materially worse artifact to fall back to
// after a single unlucky reply.
var firstFailure = deckFailure(ai && ai.content);
console.warn('[my-resources] deck reply was not usable (' + firstFailure + '); asking once more');
logger.warn('[my-resources] deck reply was not usable (' + firstFailure + '); asking once more',
{ topic: topic, attempt: 1, model: ai && ai.model });
var retryGaps = [];
ai = await callAI([{ role: 'user', content: prompt }], options);
deck = deckBuild.parse(ai && ai.content, retryGaps);
reportVocabularyGaps(retryGaps, topic);
if (deck) console.log('[my-resources] the second deck attempt parsed');
if (deck) logger.info('[my-resources] the second deck attempt parsed', { topic: topic });
}
if (deckMode && !deck) {
// Twice is enough. Falling back to markdown beats saving nothing, and
// beats saving the model's apology.
deckFallback = deckFailure(ai && ai.content);
console.warn('[my-resources] deck reply was not usable twice (' + deckFallback +
'); retrying as markdown');
logger.warn('[my-resources] deck reply was not usable twice (' + deckFallback +
'); retrying as markdown', { topic: topic, attempt: 2, model: ai && ai.model });
var plain = buildPrompt({
topic: topic, kind: kind, refinement: refinement, corpusContext: corpus.context,
literature: sources.literature, webFindings: sources.webFindings,
@ -706,7 +714,8 @@ function logRefine(event) {
}
parts.push('instruction="' + String(event.instructions || '').slice(0, 70).replace(/\s+/g, ' ') + '"');
var line = parts.join(' ');
if (event.outcome !== 'applied' || event.unchanged) console.warn(line); else console.log(line);
if (event.outcome !== 'applied' || event.unchanged) logger.warn(line, event);
else logger.info(line, event);
try {
require('../utils/metrics').resourceRefines.inc({
path: event.path,
@ -727,7 +736,7 @@ function reportVocabularyGaps(gaps, topic) {
try {
require('../utils/metrics').deckVocabularyGaps.inc({ wanted: gap.wanted });
} catch (e) { /* metrics are never worth an error here */ }
console.warn('[deck-vocabulary] wanted "' + gap.wanted + '" (' + gap.detail +
logger.warn('[deck-vocabulary] wanted "' + gap.wanted + '" (' + gap.detail +
') while generating: ' + String(topic || '').slice(0, 80));
});
}
@ -753,7 +762,7 @@ async function collectFigures(ids, user, dir) {
out.push(file);
} catch (e) {
// Still generating, failed, or deleted. The deck is fine without it.
console.warn('[my-resources] figure unavailable for export:', e.message);
logger.warn('[my-resources] figure unavailable for export', { error: e.message });
}
}
return out;

View file

@ -68,6 +68,7 @@ function router(t, overrides = {}) {
'../utils/documentExport': { FORMATS: {}, isSupported: () => false, filename: () => 'x', mimeFor: () => '', render: async () => ({}), renderDeck: async () => ({}) },
'../utils/generatedImages': { service: () => ({ get: async () => null, asset: async () => null }), workflows: ['my_resources'] },
'../utils/learningRetrieval': { retrieve: async () => ({ context: '', sources: [], reason: null }) },
'../utils/logger': quiet,
'../utils/metrics': { resourceRefines: { inc() {} }, resourceVocabularyGaps: { inc() {} } },
'../utils/pubmedSearch': { isAvailable: async () => false, search: async () => ({ results: [] }), formatForPrompt: () => '' },
'../utils/webSearch': { isAvailable: async () => false, search: async () => ({ results: [] }), formatForPrompt: () => '' },