@groundnuty/macf-channel-server 0.2.36 → 0.2.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/comms-ledger-record.d.ts +88 -0
- package/dist/comms-ledger-record.d.ts.map +1 -0
- package/dist/comms-ledger-record.js +74 -0
- package/dist/comms-ledger-record.js.map +1 -0
- package/dist/comms-ledger.d.ts +198 -0
- package/dist/comms-ledger.d.ts.map +1 -0
- package/dist/comms-ledger.js +224 -0
- package/dist/comms-ledger.js.map +1 -0
- package/dist/https.d.ts +47 -0
- package/dist/https.d.ts.map +1 -1
- package/dist/https.js +146 -1
- package/dist/https.js.map +1 -1
- package/dist/metrics.d.ts +26 -0
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +34 -0
- package/dist/metrics.js.map +1 -1
- package/dist/notify-peer.d.ts +12 -0
- package/dist/notify-peer.d.ts.map +1 -1
- package/dist/notify-peer.js +73 -21
- package/dist/notify-peer.js.map +1 -1
- package/dist/server.js +29 -0
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* comms-ledger-record — the LOUD-BUT-PROCEEDS policy slot for the
|
|
3
|
+
* comms-ledger (macf#473 piece 2; operator decision 2026-06-08).
|
|
4
|
+
*
|
|
5
|
+
* The library writer `appendEdge` (comms-ledger.ts) is deliberately
|
|
6
|
+
* FAIL-LOUD — it throws on any write failure (I/O error) or schema-parse
|
|
7
|
+
* rejection, because an authoritative DR-025 edge must never silently
|
|
8
|
+
* disappear at the WRITE layer. But the channel-server's three coordination
|
|
9
|
+
* EDGE SITES (inbound /notify recv, inbound A2A message/send recv, outbound
|
|
10
|
+
* notify_peer send) sit on the delivery hot path: a ledger write failure
|
|
11
|
+
* there must NOT abort the delivery the agent depends on. The observability
|
|
12
|
+
* layer can never be allowed to cause a coordination outage.
|
|
13
|
+
*
|
|
14
|
+
* `recordEdge` reconciles those two requirements. It is append-first, and on
|
|
15
|
+
* ANY failure it CATCHES, emits a LOUD signal on BOTH channels (a
|
|
16
|
+
* `logger.error('comms_ledger_write_failed', …)` line carrying the edge
|
|
17
|
+
* inline + a dedicated `comms_ledger_write_failed` metric carrying enough
|
|
18
|
+
* label dimensions to reconstruct the edge class), then RETURNS normally.
|
|
19
|
+
* It never re-throws and never silently swallows. The caller then proceeds
|
|
20
|
+
* with delivery regardless.
|
|
21
|
+
*
|
|
22
|
+
* The WHOLE loud-signal path is guarded so it can never make delivery fatal.
|
|
23
|
+
* The dominant cause of `appendEdge` throwing is a disk-full / read-only
|
|
24
|
+
* volume — but the ledger is a SIBLING of `channel.log`, and `logger.error`
|
|
25
|
+
* lands in `channel.log` via the same `appendFileSync`. A disk-full failure
|
|
26
|
+
* therefore makes the loud-signal emitter (`logger.error`) throw the SAME
|
|
27
|
+
* errno, which — without the outermost guard — would escape `recordEdge` and,
|
|
28
|
+
* at the recv sites (`try { record; await onNotify; 200 } catch { 500 }`),
|
|
29
|
+
* skip the delivery and 500 the sender. So the entire catch-block body is
|
|
30
|
+
* wrapped in an OUTERMOST try/catch. If even that fails, a best-effort
|
|
31
|
+
* `process.stderr.write` is the last channel left; once that path is
|
|
32
|
+
* exhausted the only correct action is to swallow — there is no louder
|
|
33
|
+
* channel remaining and delivery MUST proceed.
|
|
34
|
+
*
|
|
35
|
+
* This is the structural inverse of `appendEdge`: the WRITE is fail-loud
|
|
36
|
+
* (it must surface the failure to *someone*); the POLICY around it at the
|
|
37
|
+
* hot-path sites is loud-but-proceeds (it must surface AND keep going). The
|
|
38
|
+
* library stays pure; the policy lives here.
|
|
39
|
+
*/
|
|
40
|
+
import type { Logger } from '@groundnuty/macf-core';
|
|
41
|
+
import type { CommsLedger, CommsLedgerEdge } from './comms-ledger.js';
|
|
42
|
+
/**
|
|
43
|
+
* Sink for the machine-readable half of the loud signal. Injected (not
|
|
44
|
+
* imported directly from `metrics.ts`) so `recordEdge` degrades gracefully
|
|
45
|
+
* when metrics are off — server.ts wires the real `getCommsLedgerWriteFailedCounter`
|
|
46
|
+
* increment; tests inject a spy; omitting it entirely still logs.
|
|
47
|
+
*
|
|
48
|
+
* Receives the edge that failed to write so the recorder can derive whatever
|
|
49
|
+
* label dimensions it wants (channel / direction / agent) from a single
|
|
50
|
+
* source — keeping the label set decided at the metrics layer, not here.
|
|
51
|
+
*/
|
|
52
|
+
export type CommsLedgerWriteFailedRecorder = (failedEdge: CommsLedgerEdge) => void;
|
|
53
|
+
export interface RecordEdgeDeps {
|
|
54
|
+
readonly ledger: CommsLedger;
|
|
55
|
+
readonly logger: Logger;
|
|
56
|
+
/**
|
|
57
|
+
* Optional metric recorder for the write-failed signal. When absent,
|
|
58
|
+
* `recordEdge` still emits the `logger.error` loud signal — the metric is
|
|
59
|
+
* a complement, not a precondition (OTEL is opt-in; the log is always on).
|
|
60
|
+
*/
|
|
61
|
+
readonly recordWriteFailed?: CommsLedgerWriteFailedRecorder;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Append `edge` to the comms-ledger under the loud-but-proceeds policy.
|
|
65
|
+
*
|
|
66
|
+
* Behavior (macf#473 operator decision):
|
|
67
|
+
* 1. append-first: call `ledger.appendEdge(edge)`.
|
|
68
|
+
* 2. on ANY throw (I/O failure OR the Zod schema-parse rejection inside
|
|
69
|
+
* appendEdge) → CATCH it.
|
|
70
|
+
* 3. emit the LOUD signal on both channels:
|
|
71
|
+
* - `logger.error('comms_ledger_write_failed', { edge, error })`
|
|
72
|
+
* — the edge is carried INLINE so the lost authoritative record is
|
|
73
|
+
* reconstructable from the log alone.
|
|
74
|
+
* - `recordWriteFailed(edge)` (if wired) — increments the dedicated
|
|
75
|
+
* metric carrying enough label dimensions to reconstruct the edge
|
|
76
|
+
* CLASS for alerting / dashboards.
|
|
77
|
+
* 4. RETURN normally. NEVER re-throw, NEVER silently swallow.
|
|
78
|
+
*
|
|
79
|
+
* The caller proceeds with delivery afterward regardless of outcome. This
|
|
80
|
+
* guarantees the observability layer can never cause a coordination outage,
|
|
81
|
+
* while never being silent about a lost edge.
|
|
82
|
+
*
|
|
83
|
+
* Note: the no-op ledger (no MACF_LOG_PATH) never throws, so this is a clean
|
|
84
|
+
* no-op there too — same as the rest of the observability surface when
|
|
85
|
+
* unconfigured.
|
|
86
|
+
*/
|
|
87
|
+
export declare function recordEdge(deps: RecordEdgeDeps, edge: CommsLedgerEdge): void;
|
|
88
|
+
//# sourceMappingURL=comms-ledger-record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comms-ledger-record.d.ts","sourceRoot":"","sources":["../src/comms-ledger-record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEtE;;;;;;;;;GASG;AACH,MAAM,MAAM,8BAA8B,GAAG,CAAC,UAAU,EAAE,eAAe,KAAK,IAAI,CAAC;AAEnF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,8BAA8B,CAAC;CAC7D;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,eAAe,GAAG,IAAI,CA4C5E"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append `edge` to the comms-ledger under the loud-but-proceeds policy.
|
|
3
|
+
*
|
|
4
|
+
* Behavior (macf#473 operator decision):
|
|
5
|
+
* 1. append-first: call `ledger.appendEdge(edge)`.
|
|
6
|
+
* 2. on ANY throw (I/O failure OR the Zod schema-parse rejection inside
|
|
7
|
+
* appendEdge) → CATCH it.
|
|
8
|
+
* 3. emit the LOUD signal on both channels:
|
|
9
|
+
* - `logger.error('comms_ledger_write_failed', { edge, error })`
|
|
10
|
+
* — the edge is carried INLINE so the lost authoritative record is
|
|
11
|
+
* reconstructable from the log alone.
|
|
12
|
+
* - `recordWriteFailed(edge)` (if wired) — increments the dedicated
|
|
13
|
+
* metric carrying enough label dimensions to reconstruct the edge
|
|
14
|
+
* CLASS for alerting / dashboards.
|
|
15
|
+
* 4. RETURN normally. NEVER re-throw, NEVER silently swallow.
|
|
16
|
+
*
|
|
17
|
+
* The caller proceeds with delivery afterward regardless of outcome. This
|
|
18
|
+
* guarantees the observability layer can never cause a coordination outage,
|
|
19
|
+
* while never being silent about a lost edge.
|
|
20
|
+
*
|
|
21
|
+
* Note: the no-op ledger (no MACF_LOG_PATH) never throws, so this is a clean
|
|
22
|
+
* no-op there too — same as the rest of the observability surface when
|
|
23
|
+
* unconfigured.
|
|
24
|
+
*/
|
|
25
|
+
export function recordEdge(deps, edge) {
|
|
26
|
+
try {
|
|
27
|
+
deps.ledger.appendEdge(edge);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
// OUTERMOST guard around the ENTIRE loud-signal path. The ledger is a
|
|
31
|
+
// sibling of channel.log, so the dominant failure cause (disk-full /
|
|
32
|
+
// read-only volume) makes `logger.error` throw the SAME errno. Without
|
|
33
|
+
// this guard that throw escapes recordEdge and 500s the sender at the
|
|
34
|
+
// recv sites. Nothing in here may escape — delivery must proceed.
|
|
35
|
+
try {
|
|
36
|
+
// LOUD signal — human-readable half (always on). The edge is carried
|
|
37
|
+
// inline so the lost authoritative record can be reconstructed from the
|
|
38
|
+
// log without the ledger file.
|
|
39
|
+
deps.logger.error('comms_ledger_write_failed', {
|
|
40
|
+
edge,
|
|
41
|
+
error: err instanceof Error ? err.message : String(err),
|
|
42
|
+
});
|
|
43
|
+
// LOUD signal — machine-readable half (when metrics are wired).
|
|
44
|
+
// Guard the recorder ITSELF so a misbehaving metric sink can't turn the
|
|
45
|
+
// loud-but-proceeds policy back into a fatal path. Last-ditch only.
|
|
46
|
+
if (deps.recordWriteFailed !== undefined) {
|
|
47
|
+
try {
|
|
48
|
+
deps.recordWriteFailed(edge);
|
|
49
|
+
}
|
|
50
|
+
catch (metricErr) {
|
|
51
|
+
deps.logger.error('comms_ledger_write_failed_metric_error', {
|
|
52
|
+
error: metricErr instanceof Error ? metricErr.message : String(metricErr),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// The loud-signal path itself failed (e.g. logger.error threw the same
|
|
59
|
+
// disk-full errno that broke appendEdge — they share the log volume).
|
|
60
|
+
// process.stderr is the last channel that doesn't touch that volume;
|
|
61
|
+
// best-effort, guarded, then SWALLOW. This is the one place a true
|
|
62
|
+
// swallow is correct: no louder channel remains and delivery must
|
|
63
|
+
// proceed. recordEdge NEVER escapes.
|
|
64
|
+
try {
|
|
65
|
+
process.stderr.write('comms_ledger_write_failed (loud-signal emit also failed)\n');
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
/* nothing left to do */
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// RETURN normally — the caller proceeds with delivery.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=comms-ledger-record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comms-ledger-record.js","sourceRoot":"","sources":["../src/comms-ledger-record.ts"],"names":[],"mappings":"AAiEA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,UAAU,CAAC,IAAoB,EAAE,IAAqB;IACpE,IAAI,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,sEAAsE;QACtE,qEAAqE;QACrE,uEAAuE;QACvE,sEAAsE;QACtE,kEAAkE;QAClE,IAAI,CAAC;YACH,qEAAqE;YACrE,wEAAwE;YACxE,+BAA+B;YAC/B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,EAAE;gBAC7C,IAAI;gBACJ,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;YACH,gEAAgE;YAChE,wEAAwE;YACxE,oEAAoE;YACpE,IAAI,IAAI,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;gBACzC,IAAI,CAAC;oBACH,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;gBAC/B,CAAC;gBAAC,OAAO,SAAS,EAAE,CAAC;oBACnB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wCAAwC,EAAE;wBAC1D,KAAK,EAAE,SAAS,YAAY,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC;qBAC1E,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uEAAuE;YACvE,sEAAsE;YACtE,qEAAqE;YACrE,mEAAmE;YACnE,kEAAkE;YAClE,qCAAqC;YACrC,IAAI,CAAC;gBACH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4DAA4D,CAAC,CAAC;YACrF,CAAC;YAAC,MAAM,CAAC;gBACP,wBAAwB;YAC1B,CAAC;QACH,CAAC;QACD,uDAAuD;IACzD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* comms-ledger — the write-ahead, per-agent, authoritative record of every
|
|
4
|
+
* coordination edge the channel-server sends or receives.
|
|
5
|
+
*
|
|
6
|
+
* Implements DR-025 (observable coordination substrate). The invariant: any
|
|
7
|
+
* channel carrying agent-to-agent coordination MUST preserve a durable,
|
|
8
|
+
* graph-reconstructable, fleet-analyzable record. GitHub gives this for free;
|
|
9
|
+
* A2A / direct channels must earn it deliberately or the diagnose→redesign
|
|
10
|
+
* instrument (and the paper's evidence) goes blind. This is macf#444 one layer
|
|
11
|
+
* up; a Tempo-only design would reproduce silent-fallback Instance 8 (OTLP
|
|
12
|
+
* silent-drop) as the methodology's foundation.
|
|
13
|
+
*
|
|
14
|
+
* Resilience shape: a write-ahead log + a downstream rebuildable index. The
|
|
15
|
+
* JSONL ledger is AUTHORITATIVE (local disk, synchronous, fail-loud); the Tempo
|
|
16
|
+
* span is a DERIVED best-effort central index over the same data. The durable
|
|
17
|
+
* write happens BEFORE the lossy network hop, independent of Tempo's health.
|
|
18
|
+
*
|
|
19
|
+
* This module is the library layer (#473 piece 1): the schema, the unified
|
|
20
|
+
* event taxonomy, the fail-loud writer, the `processed` backfill join, the
|
|
21
|
+
* multi-host gather, and the rotation policy. Wiring it into the three edge
|
|
22
|
+
* sites (inbound `/notify`, inbound A2A `message/send`, outbound notify_peer)
|
|
23
|
+
* is #473 piece 2.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Unified coordination-event taxonomy (DR-025 §"Channel unification").
|
|
27
|
+
* One enum spanning the A2A events and the router events, so a ledger edge
|
|
28
|
+
* analyzes identically regardless of which channel carried it.
|
|
29
|
+
*/
|
|
30
|
+
export declare const CommsEventSchema: z.ZodEnum<{
|
|
31
|
+
mention: "mention";
|
|
32
|
+
error: "error";
|
|
33
|
+
custom: "custom";
|
|
34
|
+
"session-end": "session-end";
|
|
35
|
+
"turn-complete": "turn-complete";
|
|
36
|
+
"issue-routed": "issue-routed";
|
|
37
|
+
"pr-review-state": "pr-review-state";
|
|
38
|
+
}>;
|
|
39
|
+
export type CommsEvent = z.infer<typeof CommsEventSchema>;
|
|
40
|
+
export declare const CommsChannelSchema: z.ZodEnum<{
|
|
41
|
+
a2a: "a2a";
|
|
42
|
+
"github-route": "github-route";
|
|
43
|
+
}>;
|
|
44
|
+
export type CommsChannel = z.infer<typeof CommsChannelSchema>;
|
|
45
|
+
export declare const CommsDirectionSchema: z.ZodEnum<{
|
|
46
|
+
send: "send";
|
|
47
|
+
recv: "recv";
|
|
48
|
+
}>;
|
|
49
|
+
export type CommsDirection = z.infer<typeof CommsDirectionSchema>;
|
|
50
|
+
/**
|
|
51
|
+
* One coordination edge — one JSONL line per exchange (DR-025 §"The edge schema").
|
|
52
|
+
*
|
|
53
|
+
* - `delivered` is known at edge-write (the byte sequence was accepted/pushed).
|
|
54
|
+
* - `processed` is the macf#444 distinction (delivery ≠ a turn actually happening).
|
|
55
|
+
* It is NULLABLE at edge-write — the peer hasn't necessarily taken a turn yet —
|
|
56
|
+
* and is backfilled later via the receipt join (`backfillProcessed`). The
|
|
57
|
+
* edge-write must never block on it.
|
|
58
|
+
* - `trace_id` cross-references the Tempo span. It is captured synchronously from
|
|
59
|
+
* `span.spanContext().traceId` (OTel api ≥1.9.1: synchronous, available pre-export)
|
|
60
|
+
* BEFORE this write; the span export (`span.end()`) happens after, async, best-effort.
|
|
61
|
+
* - `github_anchor` stitches an off-GitHub edge back to its GitHub object so the
|
|
62
|
+
* on-GitHub and off-GitHub graphs join into one; `null` for a pure nudge.
|
|
63
|
+
*/
|
|
64
|
+
export declare const CommsLedgerEdgeSchema: z.ZodObject<{
|
|
65
|
+
ts: z.ZodString;
|
|
66
|
+
from: z.ZodString;
|
|
67
|
+
to: z.ZodString;
|
|
68
|
+
channel: z.ZodEnum<{
|
|
69
|
+
a2a: "a2a";
|
|
70
|
+
"github-route": "github-route";
|
|
71
|
+
}>;
|
|
72
|
+
event: z.ZodEnum<{
|
|
73
|
+
mention: "mention";
|
|
74
|
+
error: "error";
|
|
75
|
+
custom: "custom";
|
|
76
|
+
"session-end": "session-end";
|
|
77
|
+
"turn-complete": "turn-complete";
|
|
78
|
+
"issue-routed": "issue-routed";
|
|
79
|
+
"pr-review-state": "pr-review-state";
|
|
80
|
+
}>;
|
|
81
|
+
direction: z.ZodEnum<{
|
|
82
|
+
send: "send";
|
|
83
|
+
recv: "recv";
|
|
84
|
+
}>;
|
|
85
|
+
msg_id: z.ZodString;
|
|
86
|
+
intent_summary: z.ZodString;
|
|
87
|
+
github_anchor: z.ZodNullable<z.ZodString>;
|
|
88
|
+
delivered: z.ZodBoolean;
|
|
89
|
+
processed: z.ZodNullable<z.ZodBoolean>;
|
|
90
|
+
trace_id: z.ZodString;
|
|
91
|
+
}, z.core.$strip>;
|
|
92
|
+
export type CommsLedgerEdge = z.infer<typeof CommsLedgerEdgeSchema>;
|
|
93
|
+
/** Max length of the deterministic intent-summary clip. */
|
|
94
|
+
export declare const INTENT_SUMMARY_MAX = 120;
|
|
95
|
+
/**
|
|
96
|
+
* Cheap, deterministic intent summary: the first non-empty line of the message,
|
|
97
|
+
* trimmed and clipped to `max` chars.
|
|
98
|
+
*
|
|
99
|
+
* Explicitly NOT an LLM summarize (#473 AC): keep a model dependency and its
|
|
100
|
+
* latency out of the delivery hot path. This runs synchronously on every edge.
|
|
101
|
+
*/
|
|
102
|
+
export declare function intentSummary(text: string | null | undefined, max?: number): string;
|
|
103
|
+
/** Canonical per-agent ledger filename, kept as a sibling of `channel.log`. */
|
|
104
|
+
export declare const COMMS_LEDGER_FILENAME = "comms-ledger.jsonl";
|
|
105
|
+
/**
|
|
106
|
+
* Derive the ledger path from the channel-server's `logPath` (`MACF_LOG_PATH`),
|
|
107
|
+
* as a sibling file in the same `.macf/logs/` directory. Returns `undefined`
|
|
108
|
+
* when no log path is configured (the ledger is then a no-op, mirroring how the
|
|
109
|
+
* `logger` is a no-op without `MACF_LOG_PATH` — the real fleet always sets it).
|
|
110
|
+
*/
|
|
111
|
+
export declare function ledgerPathFromLog(logPath: string | undefined): string | undefined;
|
|
112
|
+
export interface CommsLedger {
|
|
113
|
+
/**
|
|
114
|
+
* Append one coordination edge — SYNCHRONOUS and FAIL-LOUD.
|
|
115
|
+
*
|
|
116
|
+
* Throws if the write fails: an authoritative edge would otherwise be lost,
|
|
117
|
+
* and DR-025 names this the one operation that must NOT silently degrade.
|
|
118
|
+
* This is the structural distinction from the best-effort `logger`
|
|
119
|
+
* (`channel.log`), whose `logger.info` must stay non-fatal. Callers append to
|
|
120
|
+
* the ledger BEFORE the (async, best-effort) Tempo span export, so a Tempo or
|
|
121
|
+
* network failure can never cost an edge.
|
|
122
|
+
*/
|
|
123
|
+
readonly appendEdge: (edge: CommsLedgerEdge) => void;
|
|
124
|
+
/** The resolved ledger path, or `undefined` if the ledger is a no-op. */
|
|
125
|
+
readonly path: string | undefined;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Create the per-agent write-ahead comms-ledger writer.
|
|
129
|
+
*
|
|
130
|
+
* Pass the channel-server's `logPath`; the ledger is written to a sibling
|
|
131
|
+
* `comms-ledger.jsonl` in the same directory. A distinct writer from `logger`
|
|
132
|
+
* by design — fail-loud, authoritative — never the best-effort log.
|
|
133
|
+
*
|
|
134
|
+
* Durability note (research-confirmed: OTel api 1.9.1 / Node `fs`): `appendFileSync`
|
|
135
|
+
* is synchronous and throws on write error (the fail-loud guarantee), but does
|
|
136
|
+
* NOT `fsync`. The threat model is a Tempo/network failure with the edge already
|
|
137
|
+
* on local disk — not power-loss — so per-write `fsync` (latency on every
|
|
138
|
+
* exchange) is deliberately skipped.
|
|
139
|
+
*/
|
|
140
|
+
export declare function createCommsLedger(opts: {
|
|
141
|
+
readonly logPath?: string | undefined;
|
|
142
|
+
/** Override the derived path (mainly for tests). */
|
|
143
|
+
readonly ledgerPath?: string | undefined;
|
|
144
|
+
}): CommsLedger;
|
|
145
|
+
/**
|
|
146
|
+
* Backfill `processed` on edges from a set of receipt keys (DR-025 / macf#444).
|
|
147
|
+
*
|
|
148
|
+
* Edges are written with `processed: null` (unknown-at-write); a receipt — proof
|
|
149
|
+
* the peer actually took a turn — resolves it. This is a PURE function: it does
|
|
150
|
+
* NOT mutate the append-only ledger, it produces a derived view (the same shape
|
|
151
|
+
* as `reconciler/reconcile.ts`, which joins delivered routes ⋈ turn receipts).
|
|
152
|
+
*
|
|
153
|
+
* Channel split for the `processed` (delivery ≠ turn) join:
|
|
154
|
+
* - **a2a edges** join on `msg_id` via THIS function — `keyOf` maps the edge
|
|
155
|
+
* to its `msg_id` and `receiptKeys` carries the observed receipts.
|
|
156
|
+
* - **github-route edges** are tracked SEPARATELY by the macf#444 reconciler,
|
|
157
|
+
* which is run_id-keyed off the prompt `[macf-route:RUN:AGENT]` marker. The
|
|
158
|
+
* github-route recv edge intentionally does NOT carry that run_id (it is
|
|
159
|
+
* absent from CommsLedgerEdge, NotifyPayload, and the `type:num:ts` msg_id),
|
|
160
|
+
* so the `(run_id, agent)` join is structurally impossible HERE. A
|
|
161
|
+
* github-route edge's `processed` therefore stays `null` in the ledger BY
|
|
162
|
+
* DESIGN; its delivery≠turn distinction lives in the reconciler's view, not
|
|
163
|
+
* this one. `backfillProcessed` is the a2a-side join.
|
|
164
|
+
*
|
|
165
|
+
* `keyOf` is left channel-agnostic on purpose (it just reads `msg_id` for the
|
|
166
|
+
* a2a join); edges whose `processed` is already non-null are left untouched
|
|
167
|
+
* (idempotent).
|
|
168
|
+
*/
|
|
169
|
+
export declare function backfillProcessed(edges: readonly CommsLedgerEdge[], receiptKeys: ReadonlySet<string>, keyOf: (edge: CommsLedgerEdge) => string): CommsLedgerEdge[];
|
|
170
|
+
/**
|
|
171
|
+
* Gather + merge per-agent ledgers into one fleet view, ordered by `ts`.
|
|
172
|
+
*
|
|
173
|
+
* The per-agent ledger is the durable floor; Tempo is the central convenience
|
|
174
|
+
* index. When Tempo is down, fleet-graph analysis falls back to merging the
|
|
175
|
+
* per-agent ledgers. On the single-host substrate this is trivial — all the
|
|
176
|
+
* `comms-ledger.jsonl` files are local. For a MULTI-HOST fleet the gather step
|
|
177
|
+
* is explicit and operator-defined: collect each host's ledger (e.g.
|
|
178
|
+
* `rsync <agent-host>:.../comms-ledger.jsonl ./gathered/<agent>.jsonl`) into one
|
|
179
|
+
* place, then call this. There is deliberately NO central durable sink — that
|
|
180
|
+
* would reintroduce the single point of failure DR-025 exists to avoid.
|
|
181
|
+
*
|
|
182
|
+
* Malformed lines are skipped (a corrupt line must not blind the whole gather);
|
|
183
|
+
* this is the one place a parse error is tolerated, because gather is a derived
|
|
184
|
+
* read, not the authoritative write.
|
|
185
|
+
*/
|
|
186
|
+
export declare function mergeLedgers(contents: readonly string[]): CommsLedgerEdge[];
|
|
187
|
+
/**
|
|
188
|
+
* Rotation/retention policy (DR-025 §"Costs", "no silent caps"):
|
|
189
|
+
*
|
|
190
|
+
* The ledger is the PERMANENT record, so rotation is deliberate and must NEVER
|
|
191
|
+
* silently truncate. The writer here only ever appends — it has no size cap and
|
|
192
|
+
* no truncation path by construction. If an operator rotates the file for size,
|
|
193
|
+
* the canonical action is archive-on-rotate (move the old file aside, e.g.
|
|
194
|
+
* `comms-ledger.jsonl.<date>`), never `> truncate` or head/tail clipping. Size
|
|
195
|
+
* management is an operator concern, intentionally outside this hot-path writer.
|
|
196
|
+
*/
|
|
197
|
+
export declare const ROTATION_POLICY: "archive-on-rotate; never silent truncation";
|
|
198
|
+
//# sourceMappingURL=comms-ledger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comms-ledger.d.ts","sourceRoot":"","sources":["../src/comms-ledger.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;EAU3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,kBAAkB;;;EAAkC,CAAC;AAClE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,oBAAoB;;;EAA2B,CAAC;AAC7D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAahC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,2DAA2D;AAC3D,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC/B,GAAG,GAAE,MAA2B,GAC/B,MAAM,CAUR;AAED,+EAA+E;AAC/E,eAAO,MAAM,qBAAqB,uBAAuB,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGjF;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;;;;;OASG;IACH,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAC;IACrD,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AASD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,oDAAoD;IACpD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C,GAAG,WAAW,CAkBd;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,eAAe,EAAE,EACjC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,EAChC,KAAK,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,MAAM,GACvC,eAAe,EAAE,CAInB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,eAAe,EAAE,CAW3E;AAYD;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAG,4CAAqD,CAAC"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
/**
|
|
5
|
+
* comms-ledger — the write-ahead, per-agent, authoritative record of every
|
|
6
|
+
* coordination edge the channel-server sends or receives.
|
|
7
|
+
*
|
|
8
|
+
* Implements DR-025 (observable coordination substrate). The invariant: any
|
|
9
|
+
* channel carrying agent-to-agent coordination MUST preserve a durable,
|
|
10
|
+
* graph-reconstructable, fleet-analyzable record. GitHub gives this for free;
|
|
11
|
+
* A2A / direct channels must earn it deliberately or the diagnose→redesign
|
|
12
|
+
* instrument (and the paper's evidence) goes blind. This is macf#444 one layer
|
|
13
|
+
* up; a Tempo-only design would reproduce silent-fallback Instance 8 (OTLP
|
|
14
|
+
* silent-drop) as the methodology's foundation.
|
|
15
|
+
*
|
|
16
|
+
* Resilience shape: a write-ahead log + a downstream rebuildable index. The
|
|
17
|
+
* JSONL ledger is AUTHORITATIVE (local disk, synchronous, fail-loud); the Tempo
|
|
18
|
+
* span is a DERIVED best-effort central index over the same data. The durable
|
|
19
|
+
* write happens BEFORE the lossy network hop, independent of Tempo's health.
|
|
20
|
+
*
|
|
21
|
+
* This module is the library layer (#473 piece 1): the schema, the unified
|
|
22
|
+
* event taxonomy, the fail-loud writer, the `processed` backfill join, the
|
|
23
|
+
* multi-host gather, and the rotation policy. Wiring it into the three edge
|
|
24
|
+
* sites (inbound `/notify`, inbound A2A `message/send`, outbound notify_peer)
|
|
25
|
+
* is #473 piece 2.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Unified coordination-event taxonomy (DR-025 §"Channel unification").
|
|
29
|
+
* One enum spanning the A2A events and the router events, so a ledger edge
|
|
30
|
+
* analyzes identically regardless of which channel carried it.
|
|
31
|
+
*/
|
|
32
|
+
export const CommsEventSchema = z.enum([
|
|
33
|
+
// A2A / peer_notification (notify_peer `event`, message/send)
|
|
34
|
+
'turn-complete',
|
|
35
|
+
'session-end',
|
|
36
|
+
'error',
|
|
37
|
+
'custom',
|
|
38
|
+
// GitHub router (the macf-actions route-by-* blocks)
|
|
39
|
+
'issue-routed',
|
|
40
|
+
'mention',
|
|
41
|
+
'pr-review-state',
|
|
42
|
+
]);
|
|
43
|
+
export const CommsChannelSchema = z.enum(['a2a', 'github-route']);
|
|
44
|
+
export const CommsDirectionSchema = z.enum(['send', 'recv']);
|
|
45
|
+
/**
|
|
46
|
+
* One coordination edge — one JSONL line per exchange (DR-025 §"The edge schema").
|
|
47
|
+
*
|
|
48
|
+
* - `delivered` is known at edge-write (the byte sequence was accepted/pushed).
|
|
49
|
+
* - `processed` is the macf#444 distinction (delivery ≠ a turn actually happening).
|
|
50
|
+
* It is NULLABLE at edge-write — the peer hasn't necessarily taken a turn yet —
|
|
51
|
+
* and is backfilled later via the receipt join (`backfillProcessed`). The
|
|
52
|
+
* edge-write must never block on it.
|
|
53
|
+
* - `trace_id` cross-references the Tempo span. It is captured synchronously from
|
|
54
|
+
* `span.spanContext().traceId` (OTel api ≥1.9.1: synchronous, available pre-export)
|
|
55
|
+
* BEFORE this write; the span export (`span.end()`) happens after, async, best-effort.
|
|
56
|
+
* - `github_anchor` stitches an off-GitHub edge back to its GitHub object so the
|
|
57
|
+
* on-GitHub and off-GitHub graphs join into one; `null` for a pure nudge.
|
|
58
|
+
*/
|
|
59
|
+
export const CommsLedgerEdgeSchema = z.object({
|
|
60
|
+
ts: z.string(), // ISO 8601
|
|
61
|
+
from: z.string(),
|
|
62
|
+
to: z.string(),
|
|
63
|
+
channel: CommsChannelSchema,
|
|
64
|
+
event: CommsEventSchema,
|
|
65
|
+
direction: CommsDirectionSchema,
|
|
66
|
+
msg_id: z.string(),
|
|
67
|
+
intent_summary: z.string(),
|
|
68
|
+
github_anchor: z.string().nullable(),
|
|
69
|
+
delivered: z.boolean(),
|
|
70
|
+
processed: z.boolean().nullable(),
|
|
71
|
+
trace_id: z.string(),
|
|
72
|
+
});
|
|
73
|
+
/** Max length of the deterministic intent-summary clip. */
|
|
74
|
+
export const INTENT_SUMMARY_MAX = 120;
|
|
75
|
+
/**
|
|
76
|
+
* Cheap, deterministic intent summary: the first non-empty line of the message,
|
|
77
|
+
* trimmed and clipped to `max` chars.
|
|
78
|
+
*
|
|
79
|
+
* Explicitly NOT an LLM summarize (#473 AC): keep a model dependency and its
|
|
80
|
+
* latency out of the delivery hot path. This runs synchronously on every edge.
|
|
81
|
+
*/
|
|
82
|
+
export function intentSummary(text, max = INTENT_SUMMARY_MAX) {
|
|
83
|
+
if (!text)
|
|
84
|
+
return '';
|
|
85
|
+
const firstLine = text
|
|
86
|
+
.split('\n')
|
|
87
|
+
.map((l) => l.trim())
|
|
88
|
+
.find((l) => l.length > 0) ?? '';
|
|
89
|
+
if (firstLine.length <= max)
|
|
90
|
+
return firstLine;
|
|
91
|
+
// Clip on a char boundary; the ellipsis signals truncation (never a silent cap).
|
|
92
|
+
return firstLine.slice(0, max - 1) + '…';
|
|
93
|
+
}
|
|
94
|
+
/** Canonical per-agent ledger filename, kept as a sibling of `channel.log`. */
|
|
95
|
+
export const COMMS_LEDGER_FILENAME = 'comms-ledger.jsonl';
|
|
96
|
+
/**
|
|
97
|
+
* Derive the ledger path from the channel-server's `logPath` (`MACF_LOG_PATH`),
|
|
98
|
+
* as a sibling file in the same `.macf/logs/` directory. Returns `undefined`
|
|
99
|
+
* when no log path is configured (the ledger is then a no-op, mirroring how the
|
|
100
|
+
* `logger` is a no-op without `MACF_LOG_PATH` — the real fleet always sets it).
|
|
101
|
+
*/
|
|
102
|
+
export function ledgerPathFromLog(logPath) {
|
|
103
|
+
if (!logPath)
|
|
104
|
+
return undefined;
|
|
105
|
+
return join(dirname(logPath), COMMS_LEDGER_FILENAME);
|
|
106
|
+
}
|
|
107
|
+
const NOOP_LEDGER = {
|
|
108
|
+
appendEdge: () => {
|
|
109
|
+
/* no path configured → no-op (mirrors logger when MACF_LOG_PATH unset) */
|
|
110
|
+
},
|
|
111
|
+
path: undefined,
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Create the per-agent write-ahead comms-ledger writer.
|
|
115
|
+
*
|
|
116
|
+
* Pass the channel-server's `logPath`; the ledger is written to a sibling
|
|
117
|
+
* `comms-ledger.jsonl` in the same directory. A distinct writer from `logger`
|
|
118
|
+
* by design — fail-loud, authoritative — never the best-effort log.
|
|
119
|
+
*
|
|
120
|
+
* Durability note (research-confirmed: OTel api 1.9.1 / Node `fs`): `appendFileSync`
|
|
121
|
+
* is synchronous and throws on write error (the fail-loud guarantee), but does
|
|
122
|
+
* NOT `fsync`. The threat model is a Tempo/network failure with the edge already
|
|
123
|
+
* on local disk — not power-loss — so per-write `fsync` (latency on every
|
|
124
|
+
* exchange) is deliberately skipped.
|
|
125
|
+
*/
|
|
126
|
+
export function createCommsLedger(opts) {
|
|
127
|
+
const ledgerPath = opts.ledgerPath ?? ledgerPathFromLog(opts.logPath);
|
|
128
|
+
if (!ledgerPath)
|
|
129
|
+
return NOOP_LEDGER;
|
|
130
|
+
const dir = dirname(ledgerPath);
|
|
131
|
+
if (!existsSync(dir))
|
|
132
|
+
mkdirSync(dir, { recursive: true });
|
|
133
|
+
if (!existsSync(ledgerPath))
|
|
134
|
+
writeFileSync(ledgerPath, '');
|
|
135
|
+
return {
|
|
136
|
+
path: ledgerPath,
|
|
137
|
+
appendEdge: (edge) => {
|
|
138
|
+
// Validate the shape (cheap, catches a malformed edge at the source),
|
|
139
|
+
// then append exactly one JSONL line. appendFileSync throws on any write
|
|
140
|
+
// error → propagates to the caller (fail-loud, per DR-025).
|
|
141
|
+
const line = JSON.stringify(CommsLedgerEdgeSchema.parse(edge));
|
|
142
|
+
appendFileSync(ledgerPath, line + '\n');
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Backfill `processed` on edges from a set of receipt keys (DR-025 / macf#444).
|
|
148
|
+
*
|
|
149
|
+
* Edges are written with `processed: null` (unknown-at-write); a receipt — proof
|
|
150
|
+
* the peer actually took a turn — resolves it. This is a PURE function: it does
|
|
151
|
+
* NOT mutate the append-only ledger, it produces a derived view (the same shape
|
|
152
|
+
* as `reconciler/reconcile.ts`, which joins delivered routes ⋈ turn receipts).
|
|
153
|
+
*
|
|
154
|
+
* Channel split for the `processed` (delivery ≠ turn) join:
|
|
155
|
+
* - **a2a edges** join on `msg_id` via THIS function — `keyOf` maps the edge
|
|
156
|
+
* to its `msg_id` and `receiptKeys` carries the observed receipts.
|
|
157
|
+
* - **github-route edges** are tracked SEPARATELY by the macf#444 reconciler,
|
|
158
|
+
* which is run_id-keyed off the prompt `[macf-route:RUN:AGENT]` marker. The
|
|
159
|
+
* github-route recv edge intentionally does NOT carry that run_id (it is
|
|
160
|
+
* absent from CommsLedgerEdge, NotifyPayload, and the `type:num:ts` msg_id),
|
|
161
|
+
* so the `(run_id, agent)` join is structurally impossible HERE. A
|
|
162
|
+
* github-route edge's `processed` therefore stays `null` in the ledger BY
|
|
163
|
+
* DESIGN; its delivery≠turn distinction lives in the reconciler's view, not
|
|
164
|
+
* this one. `backfillProcessed` is the a2a-side join.
|
|
165
|
+
*
|
|
166
|
+
* `keyOf` is left channel-agnostic on purpose (it just reads `msg_id` for the
|
|
167
|
+
* a2a join); edges whose `processed` is already non-null are left untouched
|
|
168
|
+
* (idempotent).
|
|
169
|
+
*/
|
|
170
|
+
export function backfillProcessed(edges, receiptKeys, keyOf) {
|
|
171
|
+
return edges.map((e) => e.processed === null ? { ...e, processed: receiptKeys.has(keyOf(e)) } : e);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Gather + merge per-agent ledgers into one fleet view, ordered by `ts`.
|
|
175
|
+
*
|
|
176
|
+
* The per-agent ledger is the durable floor; Tempo is the central convenience
|
|
177
|
+
* index. When Tempo is down, fleet-graph analysis falls back to merging the
|
|
178
|
+
* per-agent ledgers. On the single-host substrate this is trivial — all the
|
|
179
|
+
* `comms-ledger.jsonl` files are local. For a MULTI-HOST fleet the gather step
|
|
180
|
+
* is explicit and operator-defined: collect each host's ledger (e.g.
|
|
181
|
+
* `rsync <agent-host>:.../comms-ledger.jsonl ./gathered/<agent>.jsonl`) into one
|
|
182
|
+
* place, then call this. There is deliberately NO central durable sink — that
|
|
183
|
+
* would reintroduce the single point of failure DR-025 exists to avoid.
|
|
184
|
+
*
|
|
185
|
+
* Malformed lines are skipped (a corrupt line must not blind the whole gather);
|
|
186
|
+
* this is the one place a parse error is tolerated, because gather is a derived
|
|
187
|
+
* read, not the authoritative write.
|
|
188
|
+
*/
|
|
189
|
+
export function mergeLedgers(contents) {
|
|
190
|
+
const edges = [];
|
|
191
|
+
for (const content of contents) {
|
|
192
|
+
for (const raw of content.split('\n')) {
|
|
193
|
+
const line = raw.trim();
|
|
194
|
+
if (!line)
|
|
195
|
+
continue;
|
|
196
|
+
const parsed = CommsLedgerEdgeSchema.safeParse(JSON.parse(safeJson(line)));
|
|
197
|
+
if (parsed.success)
|
|
198
|
+
edges.push(parsed.data);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return edges.sort((a, b) => (a.ts < b.ts ? -1 : a.ts > b.ts ? 1 : 0));
|
|
202
|
+
}
|
|
203
|
+
/** Parse-guard: return the line if it is JSON-parseable, else an empty object literal. */
|
|
204
|
+
function safeJson(line) {
|
|
205
|
+
try {
|
|
206
|
+
JSON.parse(line);
|
|
207
|
+
return line;
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return '{}';
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Rotation/retention policy (DR-025 §"Costs", "no silent caps"):
|
|
215
|
+
*
|
|
216
|
+
* The ledger is the PERMANENT record, so rotation is deliberate and must NEVER
|
|
217
|
+
* silently truncate. The writer here only ever appends — it has no size cap and
|
|
218
|
+
* no truncation path by construction. If an operator rotates the file for size,
|
|
219
|
+
* the canonical action is archive-on-rotate (move the old file aside, e.g.
|
|
220
|
+
* `comms-ledger.jsonl.<date>`), never `> truncate` or head/tail clipping. Size
|
|
221
|
+
* management is an operator concern, intentionally outside this hot-path writer.
|
|
222
|
+
*/
|
|
223
|
+
export const ROTATION_POLICY = 'archive-on-rotate; never silent truncation';
|
|
224
|
+
//# sourceMappingURL=comms-ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comms-ledger.js","sourceRoot":"","sources":["../src/comms-ledger.ts"],"names":[],"mappings":"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"}
|