All files / core/semantic-log/src/service facets.ts

100% Statements 93/93
100% Branches 7/7
100% Functions 2/2
100% Lines 93/93

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 941x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 9x 9x 1x 1x 1x 1x 1x 1x 1x 1x 18x 18x 18x 18x 18x 18x 18x 5x 5x 5x 5x 5x 5x 5x 5x 18x 9x 9x 9x 9x 9x 9x 9x 9x 18x 4x 4x 4x 4x 4x 4x 4x 18x 18x  
/**
 * Facet projection at read time (PRD R16).
 *
 * One recorded fact, several audiences: the `TemplateEntry` the registry
 * already holds is reshaped here, at answer time, into what one consumer needs.
 * Nothing is duplicated at emit time, so a new audience is a new function in
 * this file — no emitter change, no new record type, no migration — which is
 * R16's acceptance criterion ("adding a new consumer view requires no emitter
 * change and no new record type").
 *
 * **What `diagnostic.drifted` means.** It is *not* "this template drifted".
 * That reading is not derivable and never will be: a template's embedding is
 * keyed by the same fingerprint that identifies it, so its vector is the same
 * every time and a per-template distance is always zero (D4, ruled 2026-09-13;
 * R6c makes drift a property of `flow.kind`, observed by `FlowDriftHistory`).
 * `drifted` instead means **"this template was the trigger of a drift
 * observation"**: the flow drifted, and this template is the step the anomaly
 * was attributed to (`onAnomaly` pairs every anomaly with the template whose
 * event raised it). It is derived from the anomaly collection the caller
 * supplies — the bounded set of anomalies this read is made against — and is
 * `false` when that collection holds no `drift` anomaly naming this template.
 * There is no stored per-template drift marker, and one must not be
 * reintroduced (`alerts.driftAt` was deleted as dead state).
 *
 * Projections are **copies**: the returned `intents`, `exemplars` and `alerts`
 * are fresh, so a caller cannot change the registry entry through the view it
 * was handed (the ownership rule `lineage.ts`, `centroid.ts` and
 * `FlowDriftHistory.inWindow` already keep).
 */
 
import type {AnomalyKind} from './detectors.ts';
import type {TemplateEntry} from './registry.ts';
 
export const FACETS = ['ops', 'diagnostic', 'compliance'] as const;
export type Facet = (typeof FACETS)[number];
 
/**
 * The slice of a published anomaly a drift attribution reads: which detector
 * raised it and which template's event triggered it (`templateRef`). These are
 * exactly the names the digest publishes under its `anomaly` kind —
 * `anomalyRef` is deliberately not read here, because for a drift it is the
 * flow *kind*, not a template (see `digest.ts`).
 */
export interface FacetAnomaly {
    kind: AnomalyKind;
    templateRef?: string;
}
 
export function isFacet(value: string): value is Facet {
    return (FACETS as readonly string[]).includes(value);
}
 
/**
 * Project a stored template into the shape one audience needs.
 *
 * `anomalies` is the bounded collection the caller is reading against — the
 * digest's retained anomalies for the route — and only `diagnostic` reads it.
 */
export function project(
    entry: TemplateEntry,
    facet: Facet,
    anomalies: readonly FacetAnomaly[] = [],
): Record<string, unknown> {
    const base = {ref: entry.ref, service: entry.service, levelName: entry.levelName};
    switch (facet) {
        case 'ops':
            return {
                ...base,
                count: entry.count,
                firstSeen: entry.firstSeen,
                lastSeen: entry.lastSeen,
                retiredAt: entry.retiredAt,
                message: entry.signature.replace(/^.*\[MSG: /, '').replace(/\]$/, ''),
            };
        case 'diagnostic':
            return {
                ...base,
                fingerprint: entry.fingerprint,
                signature: entry.signature,
                alerts: {...entry.alerts},
                drifted: anomalies.some(anomaly => anomaly.kind === 'drift' && anomaly.templateRef === entry.ref),
                novelty: entry.alerts.noveltyAt !== undefined,
            };
        case 'compliance':
            return {
                ...base,
                intents: [...entry.intents],
                exemplars: [...entry.exemplars],
                firstSeen: entry.firstSeen,
                lastSeen: entry.lastSeen,
            };
    }
}