Files
orca/src/main/daemon/daemon-audit-eligibility-event.ts
T
mirror 34fa2cd5ca
Skill update round trip / roundtrip (false, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, ubuntu-latest, symlink, latest) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (false, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, macos-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, ubuntu-latest, symlink, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, copy, 1.5.17) (push) Canceled after 0s
Skill update round trip / roundtrip (true, windows-latest, symlink, 1.5.17) (push) Canceled after 0s
source snapshot 2026-09-02
2026-09-02 02:03:16 +08:00

128 lines
4.9 KiB
TypeScript

import { createHash } from 'node:crypto'
import { track } from '../telemetry/client'
import type { EventProps } from '../../shared/telemetry-events'
import type { DaemonAuditObservation } from './daemon-audit-classifier'
import { PROTOCOL_VERSION } from './daemon-protocol-version'
// Why: a steady daemon repeats a byte-identical observation on every listProcesses call, so
// repeats are re-sent only as an occasional heartbeat — the shared per-session telemetry
// ceiling is 1,000 events for the whole app and audit data must not crowd it out.
const REPEATED_OBSERVATION_INTERVAL_MS = 5 * 60_000
const INCARNATION_CORRELATION_DOMAIN = 'orca:daemon-audit-eligibility:incarnation:v1'
type AuditEligibilityCommonProperties = Omit<
Extract<EventProps<'daemon_audit_eligibility'>, { exact_incarnation: 'unavailable' }>,
'exact_incarnation'
>
export function trackDaemonAuditEligibility(observation: DaemonAuditObservation): void {
try {
track('daemon_audit_eligibility', auditEligibilityProperties(observation, hashLaunchNonce))
} catch {
// Audit telemetry cannot affect daemon availability.
}
}
export function createDaemonAuditEligibilityTracker(
// Why: the window must be measured on a monotonic clock — a backward wall-clock jump (NTP
// correction, VM resume) would otherwise park the next heartbeat in the future.
monotonicNowMs: () => number = () => performance.now()
): (observation: DaemonAuditObservation) => void {
let lastProperties: string | null = null
let lastTrackedAtMs = 0
const correlateLaunchNonce = createLaunchNonceCorrelationCache()
return (observation) => {
// Why: the rate-limit bookkeeping runs inside the daemon's inventory path, so it is guarded
// together with the emit — audit telemetry cannot affect daemon availability.
try {
const eventProperties = auditEligibilityProperties(observation, correlateLaunchNonce)
const properties = JSON.stringify(eventProperties)
const observedAtMs = monotonicNowMs()
const elapsedMs = observedAtMs - lastTrackedAtMs
if (
properties === lastProperties &&
elapsedMs >= 0 &&
elapsedMs < REPEATED_OBSERVATION_INTERVAL_MS
) {
return
}
lastProperties = properties
lastTrackedAtMs = observedAtMs
track('daemon_audit_eligibility', eventProperties)
} catch {
// Audit telemetry cannot affect daemon availability.
}
}
}
function auditEligibilityProperties(
observation: DaemonAuditObservation,
correlateLaunchNonce: (launchNonce: string) => string
): EventProps<'daemon_audit_eligibility'> {
const generationRole = generationRoleForProtocol(observation.context.protocolGeneration)
const commonProperties: AuditEligibilityCommonProperties = {
state: observation.state,
reason: observation.reason,
trigger: observation.trigger,
evidence_sources: [...observation.evidenceSources],
protocol_generation: observation.context.protocolGeneration,
generation_role: generationRole,
provider: observation.context.provider,
endpoint_kind: observation.context.endpointKind,
profile_scope: observation.context.profileScope ? 'configured' : 'unspecified',
reachability: observation.reachability,
inventory_authority: observation.inventoryAuthority,
process_liveness: observation.processLiveness,
process_reason: observation.processReason,
endpoint_state: observation.endpointState
}
if (!observation.exactIncarnation) {
return { ...commonProperties, exact_incarnation: 'unavailable' }
}
return {
...commonProperties,
exact_incarnation: exactIncarnationKind(observation.exactIncarnation),
exact_incarnation_correlation: correlateLaunchNonce(
observation.exactIncarnation.identity.launchNonce
)
}
}
function generationRoleForProtocol(protocolGeneration: number): 'current' | 'legacy' {
if (protocolGeneration === PROTOCOL_VERSION) {
return 'current'
}
if (protocolGeneration > 0 && protocolGeneration < PROTOCOL_VERSION) {
return 'legacy'
}
throw new Error('unsupported_daemon_audit_protocol_generation')
}
function exactIncarnationKind(
exactIncarnation: NonNullable<DaemonAuditObservation['exactIncarnation']>
): 'endpoint-identity' | 'endpoint-identity-linux-ticks' {
return exactIncarnation.linuxStartTicks && exactIncarnation.bootId
? 'endpoint-identity-linux-ticks'
: 'endpoint-identity'
}
function createLaunchNonceCorrelationCache(): (launchNonce: string) => string {
let cachedNonce: string | null = null
let cachedCorrelation = ''
return (launchNonce) => {
if (launchNonce !== cachedNonce) {
cachedNonce = launchNonce
cachedCorrelation = hashLaunchNonce(launchNonce)
}
return cachedCorrelation
}
}
function hashLaunchNonce(launchNonce: string): string {
const digest = createHash('sha256')
.update(INCARNATION_CORRELATION_DOMAIN)
.update('\0')
.update(launchNonce)
.digest('hex')
return `v1:${digest.slice(0, 32)}`
}