koala73/worldmonitor · error · Error
Compact health pending entries must be objects
Error message
Compact health pending entries must be objects
What it means
validateCompactHealthPayload also requires every value inside `pending` to be a non-null, non-array object — one entry per pending seed with its own fields. It throws when any entry is null, an array, or a scalar, because downstream freshness logic reads per-seed properties from each entry and cannot do so safely otherwise.
Solutions
- Fix the producer so each pending value is an object with the expected per-seed fields
- Update fixtures/mocks to use full entry objects
- If the wire format intentionally changed, update validateCompactHealthPayload and downstream readers together in one deploy
- Add a schema test on the worker output so entry shape drift is caught before the checker runs
Example fix
// before
{"pending":{"seed:fx": 1719000000000}}
// after
{"pending":{"seed:fx":{"updatedAt":1719000000000,"status":"PENDING"}}} Defensive patterns
Strategy: type-guard
Validate before calling
const entries = Object.values(payload.pending ?? {});
if (entries.some((e) => e === null || typeof e !== 'object' || Array.isArray(e))) throw new Error('each pending entry must be an object'); Type guard
const pendingEntriesAreObjects = (p) => Object.values(p.pending ?? {}).every((e) => e !== null && typeof e === 'object' && !Array.isArray(e)); Try / catch
try {
validateCompactHealthPayload(payload);
} catch (e) {
if (e.message === 'Compact health pending entries must be objects') {
console.error('A pending seed entry lost its object shape — check the producer');
} else throw e;
} Prevention
- Give every pending seed a full entry object with its fields
- Validate producer output against the schema in worker tests
- Deploy worker and checker contract changes atomically
- Keep mock fixtures shape-identical to production output
When it happens
Trigger: A pending map whose values are scalars (e.g. {"seed:foo": 123} timestamps instead of objects) or nulls; manual fixture construction with wrong entry shape; API change flattening entries into plain values.
Common situations: Producer simplifies entries to timestamps/strings to save payload size; test mocks built with placeholder primitives; JSON schema drift between worker and checker versions deployed at different times.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Compact health payload must be an object
- Compact health pending must be an object
- Physical divergence snapshot must contain gold and silver re
- ${name} cronSchedule must be a string or null
- Convex embed key validation unavailable: invalid-payload
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/3f652c4cd1f7a2aa.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/check-seed-freshness.mjs:24
const DEFAULT_HEALTH_URL = 'https://api.worldmonitor.app/api/health?compact=1';
const BASELINE_URL = new URL('./seed-freshness-baseline.json', import.meta.url);
// api/health.js only serves a cached verdict for 60 seconds. Allow its maximum
// 20-second request timeout too, so a valid snapshot cannot be rejected solely
// because the response arrived at the end of the monitor's fetch window.
export const MAX_HEALTH_OBSERVATION_AGE_MS = 80 * 1000;
export function validateCompactHealthPayload(payload) {
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
throw new Error('Compact health payload must be an object');
}
if (Object.hasOwn(payload, 'pending')) {
if (!payload.pending || typeof payload.pending !== 'object' || Array.isArray(payload.pending)) {
throw new Error('Compact health pending must be an object');
}
for (const entry of Object.values(payload.pending)) {
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
throw new Error('Compact health pending entries must be objects');
}
}
}
// Compact health omits `problems` entirely when every check is healthy.
if (payload.problems == null && payload.status === 'HEALTHY') return payload;
if (!payload.problems || typeof payload.problems !== 'object' || Array.isArray(payload.problems)) {
throw new Error('Compact health payload must contain a problems object');
}
return payload;
}
// The ONLY states being on-demand actually explains: nothing has requested the
// key yet, or the producer has not run for the first time. Absence is expected
// for an RPC-populated cache or a deployment-order bridge, so it must not page.
//
// Everything else must stay strict even for an on-demand source. `SEED_ERROR`
// means the producer ran and failed; a long `STALE_SEED` means it stopped
// running. Neither is explained by "nobody asked for it yet", and softeningView on GitHub (pinned to 7d06c8633d)