75 lines
3.6 KiB
TypeScript
75 lines
3.6 KiB
TypeScript
export const observationKinds = [
|
|
'host.inspect',
|
|
'instance.inspect',
|
|
'rcon.status-batch',
|
|
'files.read',
|
|
'files.list',
|
|
'files.stat',
|
|
'files.manifest',
|
|
'logs.read'
|
|
];
|
|
const commands = ['status', 'getstatus', 'version'];
|
|
|
|
export function safeOperationTarget(kind: string, value: string | null): string | null {
|
|
if (!value || value.length > 1024) return null;
|
|
const paths = kind === 'files.manifest' ? value.split(', ') : [value];
|
|
return paths.every(
|
|
(path) =>
|
|
path === '.' ||
|
|
// eslint-disable-next-line no-control-regex -- Reject control characters in observed paths.
|
|
(!/[\\\x00-\x1f\x7f]/.test(path) &&
|
|
path.split('/').every((part) => part !== '' && part !== '.' && part !== '..'))
|
|
)
|
|
? value
|
|
: null;
|
|
}
|
|
|
|
export function successfulObservation(
|
|
kind: string,
|
|
payload: Record<string, unknown>,
|
|
result: unknown
|
|
): boolean {
|
|
const routine =
|
|
observationKinds.includes(kind) ||
|
|
(kind === 'rcon.execute' && commands.includes(String(payload.command)));
|
|
const value = result && typeof result === 'object' ? (result as Record<string, unknown>) : {};
|
|
// Older agents set ok=false when any target fails, despite completing the batch.
|
|
const completedBatch = kind === 'rcon.status-batch' && Array.isArray(value.results);
|
|
return (
|
|
routine &&
|
|
(completedBatch || value.ok !== false) &&
|
|
value.healthy !== false &&
|
|
(value.error == null || value.error === '')
|
|
);
|
|
}
|
|
|
|
/** Uses the operation alias o; serialized payloads may live outside the routing row. */
|
|
export const routineObservationSql = `(CASE WHEN o.kind IN (${observationKinds.map((kind) => `'${kind}'`).join(',')}) THEN 1
|
|
WHEN o.kind='rcon.execute' THEN json_extract(COALESCE(
|
|
(SELECT p.payload FROM ec_host_operation_payloads p WHERE p.operation_id=o.id),o.payload),'$.command') IN ('status','getstatus','version')
|
|
ELSE 0 END)`;
|
|
|
|
export const successfulObservationSql = `(o.state='completed' AND COALESCE(o.error,'')='' AND ${routineObservationSql}
|
|
AND CASE WHEN o.result IS NULL THEN 1 WHEN json_valid(o.result)
|
|
THEN (json_extract(o.result,'$.ok') IS NOT 0
|
|
OR (o.kind='rcon.status-batch' AND json_type(o.result,'$.results')='array'))
|
|
AND json_extract(o.result,'$.healthy') IS NOT 0
|
|
AND COALESCE(json_extract(o.result,'$.error'),'')='' ELSE 0 END)`;
|
|
|
|
export const importantOperationSql = `(o.routine=0 OR o.state IN ('failed','unknown')
|
|
OR (o.state='completed' AND o.observation_success=0) OR COALESCE(o.error,'')!='')`;
|
|
|
|
const payloadSql =
|
|
'COALESCE((SELECT p.payload FROM ec_host_operation_payloads p WHERE p.operation_id=o.id),o.payload)';
|
|
/** Deliberately exclude arbitrary RCON commands and all file contents/credentials. */
|
|
export const operationTargetSql = `CASE
|
|
WHEN o.kind='plutonium.prepare' THEN CASE
|
|
WHEN json_type(${payloadSql},'$.revision')='integer' AND json_extract(${payloadSql},'$.revision')>0
|
|
THEN 'Production revision r' || json_extract(${payloadSql},'$.revision')
|
|
ELSE 'Latest production revision' END
|
|
WHEN o.kind IN ('files.read','files.stat','files.list','logs.read') THEN
|
|
CASE WHEN json_type(${payloadSql},'$.path')='text' THEN substr(json_extract(${payloadSql},'$.path'),1,1024) END
|
|
WHEN o.kind='files.manifest' THEN substr((SELECT group_concat(value,', ') FROM json_each(${payloadSql},'$.roots') WHERE type='text'),1,1024)
|
|
WHEN o.kind='rcon.execute' AND ${routineObservationSql} THEN json_extract(${payloadSql},'$.command')
|
|
ELSE NULL END`;
|