feat: T4b/T4c wave 8 - weather history, phenology, honey chain, Telegram base
- Weather history from Open-Meteo behind an adapter: worker loop phenology-weather with backoff, Retry-After, User-Agent, chunked writes and forecast-to-archive correction - Phenology: growing degree sum per DWD, phase from reports, GTS and calendar, calibration from previous years, daily job step, SeasonService reads the stored state - Honey chain: prefill for extractions and batches (proposals only), plausibility and withdrawal warnings, Extraction lock, chain events and batch.created webhook from HoneyBatchRecorded - Telegram base: schema, NotificationDelivery outbox, hardened bot client, startup check without crash, no-network-outside-loops guard - ARCHITECTURE.md: sections 11.11-11.13, assumptions 42-46, env table Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
22382d53a2
commit
1971b2d9c4
90 files changed
+8431
-155
No files matched your search
@@ -321,4 +321,43 @@ describe('assertStartupEnvironment', () => {
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('telegram bot token', () => {
|
||||
const TOKEN = '123456789:AAEhBOweik6ad9r_QXMENQjcrGbqCr4K-RM';
|
||||
|
||||
it('starts without a token in every role (Telegram stays disabled)', () => {
|
||||
for (const role of ['api', 'worker', 'job', 'all'] as const) {
|
||||
expect(() => assertStartupEnvironment(role, PROD_ENV)).not.toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
it('the worker accepts a well-formed token in production', () => {
|
||||
expect(() =>
|
||||
assertStartupEnvironment('worker', {
|
||||
...PROD_ENV,
|
||||
TELEGRAM_BOT_TOKEN: TOKEN,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('a malformed or placeholder token never stops the worker (Telegram stays off)', () => {
|
||||
for (const bad of ['garbage', 'change-me-telegram-token']) {
|
||||
expect(() =>
|
||||
assertStartupEnvironment('worker', {
|
||||
...PROD_ENV,
|
||||
TELEGRAM_BOT_TOKEN: bad,
|
||||
}),
|
||||
).not.toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
it('the API pod does not look at the token', () => {
|
||||
expect(() =>
|
||||
assertStartupEnvironment('api', {
|
||||
...PROD_ENV,
|
||||
TELEGRAM_BOT_TOKEN: 'garbage',
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
PREVIOUS_KEY_SUFFIX,
|
||||
readRingSecrets,
|
||||
} from '../common/crypto/key-ring';
|
||||
import { assertTelegramToken } from '../telegram/telegram-config';
|
||||
import type { ApicoreRole } from './role';
|
||||
|
||||
const REQUIRED_ENV_VARS = ['DATABASE_URL', 'JWT_ACCESS_SECRET'] as const;
|
||||
@@ -130,16 +131,17 @@ function assertEncryptionKeys(env: NodeJS.ProcessEnv): void {
|
||||
|
||||
/**
|
||||
* Startup checks shared by every entry point (API, worker, CLI job). All
|
||||
* roles load the same modules and secrets today, so the checks do not differ
|
||||
* per role yet; the role is part of the signature so role-specific checks can
|
||||
* be added without touching the callers.
|
||||
* roles load the same modules and secrets today, so the checks hardly differ
|
||||
* per role; the Telegram bot token is the first role-specific one (only the
|
||||
* worker holds it).
|
||||
*/
|
||||
export function assertStartupEnvironment(
|
||||
_role: ApicoreRole,
|
||||
role: ApicoreRole,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): void {
|
||||
assertRequiredEnvVars(env);
|
||||
assertProductionSettings(env);
|
||||
assertNoPlaceholderSecrets(env);
|
||||
assertEncryptionKeys(env);
|
||||
assertTelegramToken(role, env);
|
||||
}
|
||||
Reference in new issue
Block a user