@jam-overture/ loom/ telemetry
The journal — proposal, provenance, disposition and outcome, recorded as they happen.
Everything below is exported from that import. The names, the signatures and the sentences are read from the package itself rather than written here, so this page says what the copy of Loom in your node_modules says — and it changes in the same pull request the code does.
72 exports, in 8 modules. Generated from ./dist/telemetry/index.d.ts, which is the declaration file this package publishes for @jam-overture/loom/telemetry.
Nothing to install first. Everything this import loads arrives with @jam-overture/loom itself.
No import here has everything behind it. @jam-overture/loom/telemetry publishes 72 of the 1,298 names this package publishes. The other 1,226 are behind one of the 16 other imports, and 15 of those 16 publish nothing this one does. The imports do not nest: a name that is not on this page is not a name that does not exist, and the search at the top of the page says which import it comes from.
Some of these names are published elsewhere too: 1 by @jam-overture/loom/signals — the same declaration reached through two doors, so either import gives you the same thing.
One name here means something else behind another door. horizonOf is published by @jam-overture/loom/signals as well, and it is declared differently there — the same name, not the same thing. Searching the name finds both, and only the signature tells them apart.
Calibration
telemetry/calibration
Confidence is self-graded and trusted on purpose, on one condition: that it be calibrated. This is the measurement half of that promise. It compares what the model claimed against what became of the claim, and it changes nothing — no threshold moves, no policy reads it.
CalibrationVerdicttype
What became of a proposal, once it is something a claim can be scored against.
type CalibrationVerdict = "survived" | "rejected";
UnjudgedReasontype
Why a proposal yielded no verdict. Kept apart from rejected on purpose: a commit that failed on the database says nothing about whether the model was right, and folding those into the rejections would make the runtime look overconfident every time infrastructure broke.
type UnjudgedReason = "awaiting-answer" | "failed" | "unsettled";
UNJUDGED_REASONSvalue
The buckets a fold has to open before the first proposal arrives, so a reason nobody hit still reads as zero rather than as missing.
const UNJUDGED_REASONS: readonly UnjudgedReason[]
verdictOffunction
A repair is scored as its own proposal, not merged into the one it replaced. Both graded themselves, and the refusal that prompted the repair is exactly the case where the grade was wrong — averaging it away would hide the datapoint calibration exists to collect.
const verdictOf: (proposal: ProposalEpisode) => CalibrationVerdict | UnjudgedReason
CALIBRATION_BUCKET_COUNTvalue
const CALIBRATION_BUCKET_COUNT = 10
CalibrationScoretype
type CalibrationScore = {
readonly judged: number;
readonly survived: number;
/** null when nothing was judged — zero out of zero is not zero. */
readonly observedRate: number | null;
readonly meanConfidence: number | null;
/**
* `meanConfidence - observedRate`. Positive means the model claimed more than
* it delivered. null when there is nothing to compare.
*/
readonly gap: number | null;
};ConfidenceBuckettype
Half-open [lower, upper), except the last bucket, which is closed so 1 has a home.
type ConfidenceBucket = CalibrationScore & {
readonly lower: number;
readonly upper: number;
};PolicyCalibrationtype
A calibration reading confined to the claims one policy judged.
type PolicyCalibration = {
/**
* Which policy judged these claims, as its own dispositions named it.
*
* `null` means this window never saw the judgment — the page opened after the
* disposition and before the commit, say. Distinct from the recorded string
* `UNATTRIBUTED_POLICY_ID`, which is a judgment that really was made before the
* Gate wrote down which policy made it. One is a gap in the reader, the other
* is a gap in the record, and merging them would let a fixed record look like a
* short page.
*/
readonly policyId: string | null;
readonly overall: CalibrationScore;
readonly buckets: readonly ConfidenceBucket[];
/**
* The distinct fingerprints the judgments in this segment named, sorted.
*
* A name is host-declared, and hosts are asked to rename a policy they edit.
* More than one fingerprint under one name means that did not happen, and this
* row is pooling gates that differ by more than what they are called — the same
* error the segments exist to correct, one level down. `rulesetContinuityOf`
* reads this list; the list itself is carried so a host can match a digest to
* the configuration it is looking at.
*/
readonly fingerprints: readonly string[];
/**
* Judged claims in this segment whose disposition named no fingerprint. Kept
* beside the list rather than folded into it: one fingerprint plus twelve
* unfingerprinted judgments is not a segment shown to be constant, and a list
* of length one would say it was.
*/
readonly unfingerprinted: number;
};CalibrationReporttype
type CalibrationReport = {
readonly overall: CalibrationScore;
readonly buckets: readonly ConfidenceBucket[];
/**
* The same claims again, split by the policy that judged each one. Every
* judged proposal appears in exactly one segment, so the segments sum to
* `overall` — the split is a partition, not a sample.
*
* Ordered by policy name, with the unrecorded segment last, so a reader
* comparing two windows is comparing rows in the same order.
*/
readonly byPolicy: readonly PolicyCalibration[];
/** Proposals that produced no verdict, by why. Never in a denominator. */
readonly unjudged: Readonly<Record<UnjudgedReason, number>>;
/**
* Records the fold could not attribute to a proposal, carried through rather
* than dropped — the same reason `EpisodeFold` returns them. A rate computed
* over a window that silently lost records is a rate over a denominator it
* changed without saying so.
*/
readonly unattributed: number;
/**
* Proposals the runtime authored rather than a model — the inverse behind a
* revert, say. Segmented rather than dropped: they carry a confidence because
* every proposal does, but nobody graded it, so scoring them would measure the
* constant the runtime stamps. Reporting the count keeps that visible instead
* of leaving a reader to wonder where the undos went.
*/
readonly runtimeAuthored: number;
};calibrationOffunction
Shown in use on What every ask leaves behind — Was the confidence worth anything?
const calibrationOf: (fold: EpisodeFold) => CalibrationReport
Episode
telemetry/episode
The fold that turns a stream of records into what §6 was built to answer: for each thing someone asked for, what the model proposed, what the Gate decided, and what became of it.
EpisodeAnswertype
type EpisodeAnswer = "confirmed" | "discarded";
FailureStagetype
type FailureStage = "interpretation" | "assessment" | "repair" | "application" | "custody" | "commit";
EpisodeFailuretype
type EpisodeFailure = TelemetryFailure & {
readonly stage: FailureStage;
};ProposalEpisodetype
type ProposalEpisode = {
readonly proposalId: ProposalId;
/** Set when this proposal replaced one the Gate refused. */
readonly repairOf?: ProposalId;
readonly provenance: Provenance;
readonly rationale: string;
readonly delta: TreeDelta;
readonly proposedAt: string;
readonly assessment?: AssessmentSummary;
readonly disposition?: Disposition;
/** The Gate held it and custody succeeded, so a human was asked. */
readonly held: boolean;
/** A refusal that was handed back for one more attempt. */
readonly repairRequested: boolean;
readonly answer?: EpisodeAnswer;
/**
* Who answered. Distinct from `provenance.actor`, which is who asked — a hold
* exists to put a second person in the way of a change, and an episode that
* showed only the asker would make the two look like one.
*/
readonly answeredBy?: string;
/** Applied in memory. Present without `committedRevision` means it did not persist. */
readonly appliedRevision?: number;
readonly committedRevision?: number;
readonly failure?: EpisodeFailure;
readonly settledAt?: string;
};EpisodeResolutiontype
type EpisodeResolution = {
readonly kind: "committed";
readonly proposalId: ProposalId;
readonly revision: number;
} | {
readonly kind: "refused";
readonly proposalId: ProposalId;
} | {
readonly kind: "awaiting-answer";
readonly proposalId: ProposalId;
} | {
readonly kind: "discarded";
readonly proposalId: ProposalId;
} | {
readonly kind: "not-interpreted";
readonly failure: EpisodeFailure;
} | {
readonly kind: "not-writable";
readonly failure: EpisodeFailure;
} | {
readonly kind: "failed";
readonly proposalId: ProposalId;
readonly failure: EpisodeFailure;
}
/** Nothing in this window settled it — still in flight, or the page ends mid-episode. */
| {
readonly kind: "open";
};EpisodeResolutionKindtype
type EpisodeResolutionKind = EpisodeResolution["kind"];
EPISODE_RESOLUTION_KINDSvalue
const EPISODE_RESOLUTION_KINDS: readonly EpisodeResolutionKind[]
IntentEpisodetype
type IntentEpisode = {
readonly intentId: IntentId;
readonly treeId: TreeId;
/** Absent when the window opened after the intent was received. */
readonly intent?: IntentSummary;
/**
* The policy this intent was last resolved to be judged under. Absent when
* the window opened after resolution, or when the records predate it.
*
* "Last" matters for a proposal that was held and later confirmed: the Gate
* resolves again on the second look, so a host that narrowed its policy in
* between shows two resolutions and this keeps the one that decided the
* outcome. Each proposal's disposition carries the policy that judged that
* proposal, which is the finer-grained truth.
*/
readonly policyId?: string;
readonly startedAt: string;
readonly proposals: readonly ProposalEpisode[];
readonly resolution: EpisodeResolution;
};EpisodeFoldtype
type EpisodeFold = {
readonly episodes: readonly IntentEpisode[];
/**
* Records that name a proposal this window never saw proposed. Returned
* rather than discarded: a fold that quietly dropped them would report a
* refusal rate over a denominator it had silently changed.
*/
readonly unattributed: readonly RecordedTelemetry[];
};episodesOffunction
Shown in use on What every ask leaves behind — A record is not the story. An episode is.
const episodesOf: (records: readonly RecordedTelemetry[]) => EpisodeFold
EpisodeTallytype
What a window of episodes adds up to.
type EpisodeTally = {
readonly episodes: number;
readonly proposals: number;
/** Proposals the Gate would not apply on its own. */
readonly held: number;
/** Proposals made to replace one the Gate refused. */
readonly repairs: number;
/**
* Every kind, including the ones that did not happen. A resolution absent from
* this map and one that occurred zero times are different claims, and only one
* of them is true.
*/
readonly byResolution: Readonly<Record<EpisodeResolutionKind, number>>;
};tallyEpisodesfunction
const tallyEpisodes: (episodes: readonly IntentEpisode[]) => EpisodeTally
Event
telemetry/event
What §6 keeps, and what it deliberately does not.
telemetryFailureSchemaschema
A failure, as telemetry stores it: the code, and a sentence the runtime wrote about it. code is an open string rather than a mirror of §1/§2/§5's error taxonomies on purpose — adding an error code somewhere else in the codebase must not make records written yesterday fail to parse today.
const telemetryFailureSchema: z.ZodObject<…>
TelemetryFailuretype
type TelemetryFailure = {
readonly code: string;
readonly detail: string;
};intentSummarySchemaschema
The intent, minus what was said. Origin, scope and the revision it was aimed at are what an analysis needs; utteranceLength keeps "a one-word ask" and "three paragraphs" distinguishable without storing either.
const intentSummarySchema: z.ZodObject<…>
IntentSummarytype
type IntentSummary = {
readonly intentId: IntentId;
readonly origin: IntentOrigin;
readonly actor?: string;
readonly baseRevision: number;
readonly scopeNodeId?: NodeId;
readonly utteranceLength: number;
readonly observedAt: string;
};assessmentSummarySchemaschema
What the Gate was looking at, in the shape a query can group by. The disposition that follows says stakes, reversibility and confidence; this says how big the change was and what it touched, which is what makes "the Gate refuses this class of change" measurable rather than anecdotal.
const assessmentSummarySchema: z.ZodObject<…>
AssessmentSummarytype
type AssessmentSummary = {
readonly proposalId: ProposalId;
readonly stakes: StakeLevel;
readonly reversible: boolean;
readonly operationCount: number;
readonly insertedNodeCount: number;
readonly removedNodeCount: number;
readonly movedNodeCount: number;
readonly configuredNodeCount: number;
readonly touchedPrimitiveTypes: readonly PrimitiveType[];
/** Absent on a record written before the field existed, never defaulted. */
readonly removedPrimitiveTypes?: readonly PrimitiveType[];
readonly relocatedPrimitiveTypes?: readonly PrimitiveType[];
readonly relocatedNodeCount?: number;
readonly shallowestAffectedDepth: number;
/** Absent on a record written before the field existed, never defaulted. */
readonly affectedNodeCount?: number;
/** Absent on a record written before the field existed, never defaulted. */
readonly configuredPropKeys?: readonly string[];
readonly retainedNodeCount: number;
readonly irreversibilityReasons: readonly string[];
/** Absent when no out-of-tree reason fired, and before the field existed. */
readonly outOfTreeEffectTypes?: readonly PrimitiveType[];
/** Absent on a record written before the field existed, never defaulted. */
readonly stakeFactorCodes?: readonly StakeFactorCode[];
};telemetryEventSchemaschema
const telemetryEventSchema: z.ZodDiscriminatedUnion<…>
TelemetryEventtype
type TelemetryEvent = {
readonly type: "intent-received";
readonly intent: IntentSummary;
} | {
readonly type: "policy-resolved";
readonly intentId: IntentId;
readonly policyId: string;
/** Absent on a record written before the Gate fingerprinted policies. */
readonly policyFingerprint?: string;
} | {
readonly type: "interpretation-failed";
readonly intentId: IntentId;
readonly failure: TelemetryFailure;
} | {
readonly type: "change-proposed";
readonly proposal: ProposedChange;
} | {
readonly type: "assessment-failed";
readonly proposalId: ProposalId;
readonly failure: TelemetryFailure;
} | {
readonly type: "change-assessed";
readonly assessment: AssessmentSummary;
} | {
readonly type: "disposition-decided";
readonly proposalId: ProposalId;
readonly disposition: Disposition;
} | {
readonly type: "repair-requested";
readonly refusedProposalId: ProposalId;
readonly reason: DispositionReason;
} | {
readonly type: "repair-failed";
readonly refusedProposalId: ProposalId;
readonly failure: TelemetryFailure;
} | {
readonly type: "change-applied";
readonly proposalId: ProposalId;
readonly revision: number;
} | {
readonly type: "application-failed";
readonly proposalId: ProposalId;
readonly failure: TelemetryFailure;
} | {
readonly type: "intent-not-writable";
readonly intentId: IntentId;
readonly failure: TelemetryFailure;
} | {
readonly type: "proposal-held";
readonly proposalId: ProposalId;
} | {
readonly type: "hold-failed";
readonly proposalId: ProposalId;
readonly failure: TelemetryFailure;
} | {
readonly type: "hold-confirmed";
readonly proposalId: ProposalId;
readonly actor?: string;
} | {
readonly type: "hold-discarded";
readonly proposalId: ProposalId;
readonly actor?: string;
} | {
readonly type: "change-committed";
readonly proposalId: ProposalId;
readonly revision: number;
} | {
readonly type: "commit-failed";
readonly proposalId: ProposalId;
readonly failure: TelemetryFailure;
};TelemetryEventTypetype
type TelemetryEventType = TelemetryEvent["type"];
TELEMETRY_EVENT_TYPESvalue
Every event the journal can hold, in the order the runtime writes them.
const TELEMETRY_EVENT_TYPES: readonly TelemetryEventType[]
telemetryRecordSchemaschema
const telemetryRecordSchema: z.ZodObject<…>
TelemetryRecordtype
type TelemetryRecord = {
readonly treeId: TreeId;
readonly occurredAt: string;
readonly event: TelemetryEvent;
};recordOffunction
const recordOf: (envelope: RuntimeEventEnvelope) => TelemetryRecord
proposalIdOffunction
The proposal an event is about, when it is about one. Used by the fold rather than stored as a column: a query that needs an index on it is a change with a reason behind it, and duplicating a field into a column is how two copies of the same fact start disagreeing.
const proposalIdOf: (event: TelemetryEvent) => ProposalId | undefined
intentIdOffunction
The intent an event names directly. Everything else reaches one via its proposal.
const intentIdOf: (event: TelemetryEvent) => IntentId | undefined
Journal
telemetry/journal
Where the narrated runtime goes to be read later.
TelemetryErrortype
The only way a journal fails. There is no not-found: reading a tree nothing has been recorded about is an empty page, not an error, because a journal makes no claim that a tree exists. There is no conflict either — appending an observation cannot collide with another observation.
type TelemetryError = {
readonly code: "unavailable";
readonly detail: string;
};describeTelemetryErrorfunction
const describeTelemetryError: (error: TelemetryError) => string
RecordedTelemetrytype
A record as it comes back out, with the position the journal gave it.
type RecordedTelemetry = TelemetryRecord & {
readonly seq: number;
/**
* When the journal learned of the record, which is not `occurredAt`.
*
* Both are here because they answer different questions and only one of them
* the journal can vouch for. `occurredAt` is what the host said, and a host
* that sets it wrongly — a skewed serverless clock, a replayed batch — is
* describing its own timeline. `recordedAt` is stamped on arrival, so it is
* the only timestamp retention may be measured against: an age a writer can
* choose is an age a writer can dodge.
*/
readonly recordedAt: string;
};TelemetryReadRequesttype
type TelemetryReadRequest = {
/** Absent reads every tree the handle can see, which is the scope rule. */
readonly treeId?: TreeId;
/** A cursor from a previous page's `older`/`newer`, passed back unread. */
readonly cursor?: string;
/**
* Default `newer`, which with no cursor is the oldest page. `older` with no
* cursor is the newest page — the read a reader almost always wants first.
*/
readonly direction?: PageDirection;
readonly limit?: number;
};TelemetryPagetype
type TelemetryPage = PageEnds & {
/**
* Always ascending by `seq`, whichever end the page was taken from.
*
* The direction is a property of *which* records a page contains, never of
* the order they arrive in. `episodesOf` folds a stream in arrival order, and
* a page that sometimes came back reversed would make every consumer
* responsible for knowing which — and silently wrong when it guessed.
*/
readonly records: readonly RecordedTelemetry[];
};DEFAULT_TELEMETRY_LIMITvalue
Larger than a tree listing's, because these are read to be folded rather than shown: an episode spans a dozen or so records, so a page of 200 is a handful of episodes and a page of 50 would routinely split one across a boundary.
const DEFAULT_TELEMETRY_LIMIT = 200
MAX_TELEMETRY_LIMITvalue
const MAX_TELEMETRY_LIMIT = 1000
clampTelemetryLimitfunction
const clampTelemetryLimit: (limit: number | undefined) => number
AssessmentLookuptype
What a consumer asks when it is holding a list of proposals and wants the Gate's reading of each one.
type AssessmentLookup = {
readonly proposalIds: readonly ProposalId[];
/** Absent looks across every tree the handle can see, as `read` does. */
readonly treeId?: TreeId;
};AssessmentLookupResulttype
The assessments that were found, and the ids this lookup did not reach.
type AssessmentLookupResult = {
readonly assessments: ReadonlyMap<ProposalId, AssessmentSummary>;
readonly unasked: readonly ProposalId[];
};MAX_ASSESSMENT_LOOKUPvalue
How many proposals one lookup answers, and it is a cap rather than a clamp.
const MAX_ASSESSMENT_LOOKUP = 400
ForgetRequesttype
What a journal is asked to forget, expressed as a position rather than a rule.
type ForgetRequest = {
readonly before: number;
};ForgetOutcometype
type ForgetOutcome = {
readonly removed: number;
};TelemetryJournalinterface
Four operations, and the asymmetry is deliberate: writes arrive in batches because a request narrates several stages and should pay for one round trip, while reads are paged because a journal only grows.
interface TelemetryJournal {
readonly record: (records: readonly TelemetryRecord[]) => Promise<Result<void, TelemetryError>>;
readonly read: (request?: TelemetryReadRequest) => Promise<Result<TelemetryPage, TelemetryError>>;
readonly assessments: (request: AssessmentLookup) => Promise<Result<AssessmentLookupResult, TelemetryError>>;
readonly forget: (request: ForgetRequest) => Promise<Result<ForgetOutcome, TelemetryError>>;
}TelemetryPrunertype
The halves of the journal its two in-package consumers actually use.
type TelemetryPruner = Pick<TelemetryJournal, "read" | "forget">;
TelemetryWritertype
type TelemetryWriter = Pick<TelemetryJournal, "record">;
Memory
telemetry/memory
The telemetry journal that keeps its records in the process.
memoryTelemetryJournalfunction
A journal in memory: the reference implementation of the contract, and the one pnpm dev runs on.
Shown in use on What every ask leaves behind — What this page is not showing you and Going to production — Three places state lives, and only one of them is your pages
const memoryTelemetryJournal: (clock?: Clock) => TelemetryJournal
Remeasure
telemetry/remeasure
What these stakes would have been, under a policy that was not in force.
RemeasuredFactortype
One rule's contribution, and whether it was run again or taken as it stood.
type RemeasuredFactor = {
readonly code: StakeFactorCode;
readonly level: StakeLevel;
/**
* `remeasured` — this rule was run against the policy given here.
* `recorded` — no field of a policy can move it, so its code is its level.
*/
readonly source: "remeasured" | "recorded";
};RemeasuredStakestype
type RemeasuredStakes = {
/** A floor, not an answer, whenever `unreadable` is not empty. */
readonly level: StakeLevel;
readonly factors: readonly RemeasuredFactor[];
/**
* Rules this record cannot answer under this policy, because a field they read
* was not written down. Empty is the claim that `level` is exact.
*/
readonly unreadable: readonly StakeFactorCode[];
};remeasureStakesfunction
const remeasureStakes: (summary: AssessmentSummary, policy: GatePolicy) => RemeasuredStakes
Retention
telemetry/retention
How a journal is allowed to forget.
MIN_RETENTION_MSvalue
The floor is not a style choice. A policy of zero would empty the journal on the next run, and the most likely way to write one is a units mistake — days where milliseconds were wanted. An hour is short enough to never obstruct a host that means it and long enough that maxAgeMs: 7 is refused rather than obeyed.
const MIN_RETENTION_MS: number
retentionPolicySchemaschema
const retentionPolicySchema: z.ZodObject<…>
RetentionPolicytype
type RetentionPolicy = z.infer<typeof retentionPolicySchema>;
DEFAULT_RETENTION_SCANvalue
How much of the journal one run will look at.
const DEFAULT_RETENTION_SCAN = 5000
MAX_RETENTION_SCANvalue
const MAX_RETENTION_SCAN = 50000
RetentionPlantype
type RetentionPlan = {
/**
* Forget every record below this position. `null` when nothing may go yet —
* either the journal holds nothing old enough, or the oldest thing in it is
* an episode still waiting on someone.
*/
readonly before: number | null;
readonly forgets: number;
/** Older than the horizon, kept because their own episode is unsettled. */
readonly keptUnsettled: number;
/**
* Older than the horizon and settled, kept only because an unsettled episode
* sits in front of them. Reported separately because it is the price of the
* prefix rule, and a host watching this number stay large has learned that
* something very old is still waiting on an answer.
*/
readonly keptBehind: number;
readonly unsettledEpisodes: number;
};horizonOffunction
const horizonOf: (policy: RetentionPolicy, now: string) => string | null
retentionPlanOffunction
What may be forgotten, as a pure function of records and an instant.
const retentionPlanOf: (records: readonly RecordedTelemetry[], horizon: string) => RetentionPlan
RetentionRequesttype
type RetentionRequest = {
readonly policy: RetentionPolicy;
readonly clock?: Clock;
readonly scanLimit?: number;
};RetentionOutcometype
What a run did, in the shape of SnapshotAudit: an outcome a host can log, show, or ignore, and never an exception. Nothing downstream depends on retention having happened, so a run that could not read the journal is a reported non-event rather than a fault anyone has to handle.
type RetentionOutcome = {
readonly outcome: "forgot";
readonly plan: RetentionPlan;
readonly removed: number;
} | {
readonly outcome: "nothing-to-forget";
readonly plan: RetentionPlan;
}
/** The policy or the clock was not usable. Nothing was read and nothing was deleted. */
| {
readonly outcome: "refused";
readonly reason: string;
} | {
readonly outcome: "unavailable";
readonly error: TelemetryError;
};applyRetentionfunction
Reads the oldest end of a journal, decides what may go, and drops it.
Shown in use on What every ask leaves behind — Forgetting is a feature, and it has rules
const applyRetention: (journal: TelemetryPruner, request: RetentionRequest) => Promise<RetentionOutcome>
describeRetentionfunction
One line for an operator, because an outcome nobody can read is an outcome nobody acts on.
const describeRetention: (outcome: RetentionOutcome) => string
Sink
telemetry/sink
The seam between a runtime narrating itself and a journal that outlives the request.
MAX_BUFFERED_RECORDSvalue
A ceiling on what one collector will hold. A host that forgets to flush leaks memory into the write path, and unbounded telemetry buffers are a worse failure than lost telemetry — so the buffer refuses to grow past this and says how much it dropped.
Shown in use on What every ask leaves behind — Turning it on is six lines and one decision
const MAX_BUFFERED_RECORDS = 1000
TelemetryCollectortype
type TelemetryCollector = {
readonly sink: EventSink;
/** Not yet written. Exposed so a host can decide a flush is worth the round trip. */
readonly pending: () => readonly TelemetryRecord[];
/** Records this collector could not hold or could not write. */
readonly dropped: () => number;
/**
* Writes what has been collected and empties the buffer, whether or not the
* write succeeded. Records are not retained for a retry: a journal that is
* failing will fail the retry too, and a queue that grows while it drains
* moves the outage into the write path.
*/
readonly flush: () => Promise<Result<void, TelemetryError>>;
};collectTelemetryfunction
Shown in use on What every ask leaves behind — Turning it on is six lines and one decision
const collectTelemetry: (journal: TelemetryWriter) => TelemetryCollector