@cat-factory/kernel 0.271.0 → 0.273.0

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.
Files changed (45) hide show
  1. package/dist/domain/agent-capabilities.js +51 -12
  2. package/dist/domain/agent-capabilities.js.map +1 -1
  3. package/dist/domain/binary-store-registry.d.ts +98 -0
  4. package/dist/domain/binary-store-registry.d.ts.map +1 -0
  5. package/dist/domain/binary-store-registry.js +79 -0
  6. package/dist/domain/binary-store-registry.js.map +1 -0
  7. package/dist/domain/notification-audience.d.ts +27 -0
  8. package/dist/domain/notification-audience.d.ts.map +1 -0
  9. package/dist/domain/notification-audience.js +40 -0
  10. package/dist/domain/notification-audience.js.map +1 -0
  11. package/dist/domain/types.d.ts +1 -1
  12. package/dist/domain/types.d.ts.map +1 -1
  13. package/dist/domain/vcs-errors.d.ts +25 -0
  14. package/dist/domain/vcs-errors.d.ts.map +1 -1
  15. package/dist/domain/vcs-errors.js +41 -0
  16. package/dist/domain/vcs-errors.js.map +1 -1
  17. package/dist/domain/workspace-cascade.d.ts +1 -1
  18. package/dist/domain/workspace-cascade.d.ts.map +1 -1
  19. package/dist/domain/workspace-cascade.js +1 -0
  20. package/dist/domain/workspace-cascade.js.map +1 -1
  21. package/dist/index.d.ts +4 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +8 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/ports/binary-artifacts.d.ts +44 -2
  26. package/dist/ports/binary-artifacts.d.ts.map +1 -1
  27. package/dist/ports/binary-artifacts.js +41 -0
  28. package/dist/ports/binary-artifacts.js.map +1 -1
  29. package/dist/ports/index.d.ts +4 -3
  30. package/dist/ports/index.d.ts.map +1 -1
  31. package/dist/ports/index.js +2 -2
  32. package/dist/ports/index.js.map +1 -1
  33. package/dist/ports/notification-channel.d.ts +98 -4
  34. package/dist/ports/notification-channel.d.ts.map +1 -1
  35. package/dist/ports/notification-channel.js +129 -2
  36. package/dist/ports/notification-channel.js.map +1 -1
  37. package/dist/ports/notification-settings-repositories.d.ts +16 -0
  38. package/dist/ports/notification-settings-repositories.d.ts.map +1 -0
  39. package/dist/ports/notification-settings-repositories.js +2 -0
  40. package/dist/ports/notification-settings-repositories.js.map +1 -0
  41. package/dist/shared/post-mortem.logic.d.ts +35 -0
  42. package/dist/shared/post-mortem.logic.d.ts.map +1 -0
  43. package/dist/shared/post-mortem.logic.js +72 -0
  44. package/dist/shared/post-mortem.logic.js.map +1 -0
  45. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"notification-channel.d.ts","sourceRoot":"","sources":["../../src/ports/notification-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAE7F;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,gBAAgB,CAAA;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAA;CACrC;AAcD,MAAM,WAAW,mBAAmB;IAClC,mFAAmF;IACnF,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACxE;AAED,0FAA0F;AAC1F,qBAAa,4BAA6B,YAAW,mBAAmB;IAC1D,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAArC,YAA6B,QAAQ,EAAE,mBAAmB,EAAE,EAAI;IAE1D,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAU5E;CACF;AAED,2FAA2F;AAC3F,qBAAa,uBAAwB,YAAW,mBAAmB;IAC3D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;CAClC"}
1
+ {"version":3,"file":"notification-channel.d.ts","sourceRoot":"","sources":["../../src/ports/notification-channel.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,2BAA2B,EAC3B,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,oBAAoB,CAAA;AAE3B;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,gBAAgB,CAAA;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAA;CACrC;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,6BAA6B,YAAI,QAAQ,EAAE,WAAW,EAAE,SAAS,CAAU,CAAA;AAExF,MAAM,MAAM,0BAA0B,GAAG,CAAC,OAAO,6BAA6B,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvF,kFAAkF;AAClF,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,0BAA0B,CAEhG;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAU9E;AAMD,MAAM,WAAW,mBAAmB;IAClC,2FAA2F;IAC3F,OAAO,CACL,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CAAA;CACjB;AAED,0FAA0F;AAC1F,qBAAa,4BAA6B,YAAW,mBAAmB;IAC1D,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAArC,YAA6B,QAAQ,EAAE,mBAAmB,EAAE,EAAI;IAE1D,OAAO,CACX,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CAUf;CACF;AAED,2FAA2F;AAC3F,qBAAa,uBAAwB,YAAW,mBAAmB;IAC3D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;CAClC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CACN,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,OAAO,CAAC,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,yBAA0B,YAAW,mBAAmB;IAEjE,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAJ3B,YACmB,OAAO,EAAE,2BAA2B,EACpC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,mBAAmB,EAC1B,OAAO,CAAC,GAAE,CACzB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE;QACP,WAAW,EAAE,MAAM,CAAA;QACnB,cAAc,EAAE,MAAM,CAAA;QACtB,OAAO,EAAE,2BAA2B,CAAA;KACrC,KACE,IAAI,aAAA,EACP;IAEE,OAAO,CACX,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,0BAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CAGf;YAEa,MAAM;CAYrB"}
@@ -1,13 +1,78 @@
1
+ import { defaultNotificationRoute } from '@cat-factory/contracts';
2
+ // Port for *delivering* a notification to humans. The NotificationService owns
3
+ // the canonical persistence + lifecycle (raise / list / resolve); a channel is
4
+ // purely "how a human is told". This is the extension seam for future delivery
5
+ // mechanisms: in-app (push the `notification` WorkspaceEvent to the board) is the
6
+ // only channel today, but an EmailNotificationChannel / SlackNotificationChannel
7
+ // implement the same port and are composed in via CompositeNotificationChannel —
8
+ // no change to the call sites that raise notifications.
9
+ //
10
+ // All deliveries are best-effort: a channel failure must never break the state
11
+ // transition that raised the notification (the row is already persisted). Channels
12
+ // swallow their own errors, exactly like the event publisher.
13
+ /**
14
+ * WHICH lifecycle edge a delivery reports. The NotificationService re-delivers a card on
15
+ * every transition it makes, and the transports split hard on what that means:
16
+ *
17
+ * - A STATE transport (the in-app push, the outbound webhook) carries the card's current
18
+ * value, so it wants every edge: a board holding an open card has to see it settle, or
19
+ * it renders an already-dismissed decision as still actionable until the next reload.
20
+ * - An ALERT transport (email, Slack) interrupts a human, so it wants the FIRST edge only.
21
+ * Mailing a board "Decision needed: …" after the decision was made is not a stale render,
22
+ * it is a wrong statement that cannot be taken back.
23
+ *
24
+ * Without this, a channel can only guess from `notification.status`, and the two edges that
25
+ * matter most are indistinguishable there: an escalation and a fresh raise are both `open`.
26
+ * It is a required parameter so a new call site fails to typecheck rather than silently
27
+ * picking whichever reading the channel it happens to reach assumes.
28
+ *
29
+ * The members, in lifecycle order:
30
+ * - `raised`: a human is being asked something they have NOT been asked. A new card, or one
31
+ * whose user-visible content changed.
32
+ * - `refreshed`: the same open card moved without a new ask. The escalation sweep flipped it
33
+ * red; a failed action put it back.
34
+ * - `settled`: the card is no longer actionable. Acted on, resolved, or auto-dismissed.
35
+ *
36
+ * Declared as a LIST rather than a bare union because one consumer has to check it at runtime:
37
+ * the mothership relay reads this off the wire from a node that may be a different build, and a
38
+ * predicate derived from the vocabulary's own members cannot fall out of step with it.
39
+ */
40
+ export const NOTIFICATION_DELIVERY_REASONS = ['raised', 'refreshed', 'settled'];
41
+ /** Whether an arbitrary value is one of {@link NOTIFICATION_DELIVERY_REASONS}. */
42
+ export function isNotificationDeliveryReason(value) {
43
+ return NOTIFICATION_DELIVERY_REASONS.includes(value);
44
+ }
45
+ /**
46
+ * Whether this edge is a NEW ask, and so the one an alert transport delivers on.
47
+ *
48
+ * The single place that judgement lives: three transports would otherwise each restate it,
49
+ * and a fourth would arrive stating it slightly differently. Exhaustive over the union, so
50
+ * adding an edge fails the build here rather than defaulting to "interrupt everyone".
51
+ */
52
+ export function isAlertingDelivery(reason) {
53
+ switch (reason) {
54
+ case 'raised':
55
+ return true;
56
+ case 'refreshed':
57
+ case 'settled':
58
+ return false;
59
+ default:
60
+ return assertNeverDeliveryReason(reason);
61
+ }
62
+ }
63
+ function assertNeverDeliveryReason(reason) {
64
+ throw new Error(`unhandled notification delivery reason: ${String(reason)}`);
65
+ }
1
66
  /** Fan a notification out to every configured channel, isolating per-channel failures. */
2
67
  export class CompositeNotificationChannel {
3
68
  channels;
4
69
  constructor(channels) {
5
70
  this.channels = channels;
6
71
  }
7
- async deliver(workspaceId, notification) {
72
+ async deliver(workspaceId, notification, reason) {
8
73
  await Promise.all(this.channels.map(async (channel) => {
9
74
  try {
10
- await channel.deliver(workspaceId, notification);
75
+ await channel.deliver(workspaceId, notification, reason);
11
76
  }
12
77
  catch {
13
78
  // Best-effort: one channel failing must not block the others or the caller.
@@ -19,4 +84,66 @@ export class CompositeNotificationChannel {
19
84
  export class NoopNotificationChannel {
20
85
  async deliver() { }
21
86
  }
87
+ /**
88
+ * Gates one channel's deliveries on the workspace's routing for that channel.
89
+ *
90
+ * A DECORATOR rather than a filter inside {@link CompositeNotificationChannel}, because
91
+ * only some transports are routed here: Slack and the outbound webhooks answer "which
92
+ * types" where their destination is declared (a Slack route's channel, a webhook
93
+ * endpoint's own `types` filter), so wrapping them would be a second switch for a
94
+ * question already decided. A facade wraps exactly the channels the manager owns, and
95
+ * what it wrapped is legible at the wiring site.
96
+ *
97
+ * It gates the ALERTING edge ONLY (see {@link NotificationDeliveryReason}). Two things
98
+ * follow from that, and both are the point rather than a concession:
99
+ *
100
+ * - A mute stops the interruption, never a correction. The card is persisted whatever the
101
+ * routing says and reaches every board on its next snapshot, so withholding the SETTLED
102
+ * push would leave those boards rendering a decision that was already made as still
103
+ * waiting for one, curable only by a reload.
104
+ * - The routing read happens on the raise and nowhere else. The escalation sweep re-delivers
105
+ * every overdue card in a workspace in one loop; a gate that consulted the store per card
106
+ * would be a read per card per channel, and one mothership round trip each.
107
+ *
108
+ * What remains is one read per raised card per routed channel. That is deliberate rather
109
+ * than un-batched: the two routed channels land in DIFFERENT channel sets (the in-app push is
110
+ * local, email is external and mothership-delivered), so there is no single point that could
111
+ * ask once for both, and a raise is a human-scale event.
112
+ *
113
+ * A router failure means the decision is UNKNOWN, and the honest reading of that is the
114
+ * SHIPPED DEFAULT rather than silence: a settings read failing must not be the reason a
115
+ * human never hears that their run parked, and it must not turn every muted type into a
116
+ * mailshot either. So a throw falls back to the same default a workspace that never
117
+ * configured anything gets, and is reported through `onError` instead of swallowed.
118
+ */
119
+ export class RoutedNotificationChannel {
120
+ channel;
121
+ router;
122
+ inner;
123
+ onError;
124
+ constructor(channel, router, inner, onError) {
125
+ this.channel = channel;
126
+ this.router = router;
127
+ this.inner = inner;
128
+ this.onError = onError;
129
+ }
130
+ async deliver(workspaceId, notification, reason) {
131
+ if (isAlertingDelivery(reason) && !(await this.routes(workspaceId, notification)))
132
+ return;
133
+ await this.inner.deliver(workspaceId, notification, reason);
134
+ }
135
+ async routes(workspaceId, notification) {
136
+ try {
137
+ return await this.router.isRouted(workspaceId, notification.type, this.channel);
138
+ }
139
+ catch (error) {
140
+ this.onError?.(error, {
141
+ workspaceId,
142
+ notificationId: notification.id,
143
+ channel: this.channel,
144
+ });
145
+ return defaultNotificationRoute(notification.type, this.channel);
146
+ }
147
+ }
148
+ }
22
149
  //# sourceMappingURL=notification-channel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"notification-channel.js","sourceRoot":"","sources":["../../src/ports/notification-channel.ts"],"names":[],"mappings":"AAmCA,0FAA0F;AAC1F,MAAM,OAAO,4BAA4B;IACV,QAAQ;IAArC,YAA6B,QAA+B;wBAA/B,QAAQ;IAA0B,CAAC;IAEhE,KAAK,CAAC,OAAO,CAAC,WAAmB,EAAE,YAA0B;QAC3D,MAAM,OAAO,CAAC,GAAG,CACf,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;YAClC,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,YAAY,CAAC,CAAA;YAClD,CAAC;YAAC,MAAM,CAAC;gBACP,4EAA4E;YAC9E,CAAC;QACH,CAAC,CAAC,CACH,CAAA;IACH,CAAC;CACF;AAED,2FAA2F;AAC3F,MAAM,OAAO,uBAAuB;IAClC,KAAK,CAAC,OAAO,KAAmB,CAAC;CAClC"}
1
+ {"version":3,"file":"notification-channel.js","sourceRoot":"","sources":["../../src/ports/notification-channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { NotificationRoutingMatrix } from '../domain/types.js';
2
+ export interface NotificationSettingsRecord {
3
+ workspaceId: string;
4
+ /** The {@link NotificationRoutingMatrix}, serialized as JSON. */
5
+ matrixJson: string;
6
+ updatedAt: number;
7
+ }
8
+ export interface NotificationSettingsRepository {
9
+ /** A workspace's routing overrides, or null when it has never configured any. */
10
+ getByWorkspace(workspaceId: string): Promise<NotificationSettingsRecord | null>;
11
+ /** Create or replace a workspace's routing overrides. */
12
+ upsert(record: NotificationSettingsRecord): Promise<void>;
13
+ }
14
+ /** Re-exported for repository implementations that map the persisted matrix. */
15
+ export type { NotificationRoutingMatrix };
16
+ //# sourceMappingURL=notification-settings-repositories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-settings-repositories.d.ts","sourceRoot":"","sources":["../../src/ports/notification-settings-repositories.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAA;AAYnE,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,MAAM,CAAA;IACnB,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,8BAA8B;IAC7C,iFAAiF;IACjF,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC,CAAA;IAC/E,yDAAyD;IACzD,MAAM,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1D;AAED,gFAAgF;AAChF,YAAY,EAAE,yBAAyB,EAAE,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=notification-settings-repositories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-settings-repositories.js","sourceRoot":"","sources":["../../src/ports/notification-settings-repositories.ts"],"names":[],"mappings":""}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The cap on a composed post-mortem, matching the debug API's own `MAX_EVICTION_DETAIL_CHARS`
3
+ * read budget: past it the text is not merely long, it is text no reader can be served in one
4
+ * response anyway.
5
+ */
6
+ export declare const MAX_POST_MORTEM_CHARS = 4000;
7
+ /**
8
+ * Join a post-mortem's parts into the scrubbed, bounded `detail` a transport reports beside an
9
+ * eviction. Empty/blank parts are dropped, so a caller composes with plain optionals rather than
10
+ * building an array conditionally; everything empty ⇒ `undefined`, which is the honest answer
11
+ * ("nothing could be read") and the one the eviction view omits the field for.
12
+ *
13
+ * ORDER MATTERS: the cap keeps the HEAD, so the caller's own one-line verdict ("the container
14
+ * exited with code 137") must come first and any bulk material (a log tail, a kubelet message)
15
+ * after it. A caller passing bulk is expected to have bounded it already, with
16
+ * {@link tailPostMortemMaterial}: this cap is the backstop, not the sizing decision, and it says
17
+ * what it dropped rather than ending mid-word and reading like the whole of what was there.
18
+ */
19
+ export declare function composePostMortem(parts: Array<string | undefined>): string | undefined;
20
+ /**
21
+ * Bound BULK post-mortem material (a container's captured output, a harness's stderr) to its
22
+ * LAST `maxChars` characters, saying what it dropped.
23
+ *
24
+ * The TAIL, and that direction is the whole reason this exists beside {@link composePostMortem}.
25
+ * The compose cap keeps the head because what it bounds is a composed detail whose one-line
26
+ * verdict comes first; a log is the opposite shape, and its value is at the end, where the crash
27
+ * is. Let bulk reach the compose cap unbounded and the two rules combine into the worst possible
28
+ * one: the boot chatter is kept and the death is dropped.
29
+ *
30
+ * A LINE bound (`docker logs --tail 50`, a stderr ring) is not this bound. Fifty lines of an
31
+ * agent echoing a base64 payload is not a short tail, and a producer that only counts lines has
32
+ * no idea how many characters it just handed over.
33
+ */
34
+ export declare function tailPostMortemMaterial(text: string, maxChars: number): string;
35
+ //# sourceMappingURL=post-mortem.logic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-mortem.logic.d.ts","sourceRoot":"","sources":["../../src/shared/post-mortem.logic.ts"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,OAAQ,CAAA;AAE1C;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG,SAAS,CAYtF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI7E"}
@@ -0,0 +1,72 @@
1
+ import { redactSecrets } from './redact-secrets.logic.js';
2
+ // Composing the one thing a dead container leaves behind.
3
+ //
4
+ // A transport that discovers its backend is gone gets exactly one chance to say WHY: the
5
+ // `RunnerJobView.detail` it mints beside the eviction, which the engine persists as the step's
6
+ // `firstEvictionDetail` and renders on the run. Every producer of that text has the same two
7
+ // obligations, and each was being restated per transport:
8
+ //
9
+ // - **Scrub it.** The material is a container's own output, a kubelet message, a harness's
10
+ // stderr: free text that routinely echoes a token it was handed. It is persisted and
11
+ // rendered to a person, so it goes through `redactSecrets` before either happens.
12
+ // - **Bound it.** A post-mortem is a diagnostic, not a log sink, and it rides a row every
13
+ // read of the run pays for.
14
+ //
15
+ // Both live here so a new transport inherits them by calling this rather than by someone
16
+ // remembering. Sibling of `describeProcessExit`, which owns the other half of the same job:
17
+ // how the death is WORDED.
18
+ /**
19
+ * The cap on a composed post-mortem, matching the debug API's own `MAX_EVICTION_DETAIL_CHARS`
20
+ * read budget: past it the text is not merely long, it is text no reader can be served in one
21
+ * response anyway.
22
+ */
23
+ export const MAX_POST_MORTEM_CHARS = 4_000;
24
+ /**
25
+ * Join a post-mortem's parts into the scrubbed, bounded `detail` a transport reports beside an
26
+ * eviction. Empty/blank parts are dropped, so a caller composes with plain optionals rather than
27
+ * building an array conditionally; everything empty ⇒ `undefined`, which is the honest answer
28
+ * ("nothing could be read") and the one the eviction view omits the field for.
29
+ *
30
+ * ORDER MATTERS: the cap keeps the HEAD, so the caller's own one-line verdict ("the container
31
+ * exited with code 137") must come first and any bulk material (a log tail, a kubelet message)
32
+ * after it. A caller passing bulk is expected to have bounded it already, with
33
+ * {@link tailPostMortemMaterial}: this cap is the backstop, not the sizing decision, and it says
34
+ * what it dropped rather than ending mid-word and reading like the whole of what was there.
35
+ */
36
+ export function composePostMortem(parts) {
37
+ const joined = parts
38
+ .map((part) => part?.trim())
39
+ .filter((part) => !!part)
40
+ .join('\n');
41
+ if (!joined)
42
+ return undefined;
43
+ // Scrub BEFORE the cap, so the two can't disagree about where a redacted span started.
44
+ const scrubbed = redactSecrets(joined);
45
+ if (!scrubbed)
46
+ return undefined;
47
+ if (scrubbed.length <= MAX_POST_MORTEM_CHARS)
48
+ return scrubbed;
49
+ const dropped = scrubbed.length - MAX_POST_MORTEM_CHARS;
50
+ return `${scrubbed.slice(0, MAX_POST_MORTEM_CHARS)}\n…(${dropped} more characters of post-mortem detail dropped)`;
51
+ }
52
+ /**
53
+ * Bound BULK post-mortem material (a container's captured output, a harness's stderr) to its
54
+ * LAST `maxChars` characters, saying what it dropped.
55
+ *
56
+ * The TAIL, and that direction is the whole reason this exists beside {@link composePostMortem}.
57
+ * The compose cap keeps the head because what it bounds is a composed detail whose one-line
58
+ * verdict comes first; a log is the opposite shape, and its value is at the end, where the crash
59
+ * is. Let bulk reach the compose cap unbounded and the two rules combine into the worst possible
60
+ * one: the boot chatter is kept and the death is dropped.
61
+ *
62
+ * A LINE bound (`docker logs --tail 50`, a stderr ring) is not this bound. Fifty lines of an
63
+ * agent echoing a base64 payload is not a short tail, and a producer that only counts lines has
64
+ * no idea how many characters it just handed over.
65
+ */
66
+ export function tailPostMortemMaterial(text, maxChars) {
67
+ if (text.length <= maxChars)
68
+ return text;
69
+ const dropped = text.length - maxChars;
70
+ return `…(${dropped} earlier characters dropped)\n${text.slice(-maxChars)}`;
71
+ }
72
+ //# sourceMappingURL=post-mortem.logic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-mortem.logic.js","sourceRoot":"","sources":["../../src/shared/post-mortem.logic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAEzD,0DAA0D;AAC1D,EAAE;AACF,yFAAyF;AACzF,+FAA+F;AAC/F,6FAA6F;AAC7F,0DAA0D;AAC1D,EAAE;AACF,6FAA6F;AAC7F,yFAAyF;AACzF,sFAAsF;AACtF,4FAA4F;AAC5F,gCAAgC;AAChC,EAAE;AACF,yFAAyF;AACzF,4FAA4F;AAC5F,2BAA2B;AAE3B;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAA;AAE1C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAgC;IAChE,MAAM,MAAM,GAAG,KAAK;SACjB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;SAC3B,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SACxC,IAAI,CAAC,IAAI,CAAC,CAAA;IACb,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAC7B,uFAAuF;IACvF,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;IACtC,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAA;IAC/B,IAAI,QAAQ,CAAC,MAAM,IAAI,qBAAqB;QAAE,OAAO,QAAQ,CAAA;IAC7D,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,GAAG,qBAAqB,CAAA;IACvD,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAqB,CAAC,OAAO,OAAO,iDAAiD,CAAA;AACnH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY,EAAE,QAAgB;IACnE,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ;QAAE,OAAO,IAAI,CAAA;IACxC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAA;IACtC,OAAO,KAAK,OAAO,iCAAiC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAA;AAC7E,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/kernel",
3
- "version": "0.271.0",
3
+ "version": "0.273.0",
4
4
  "description": "Shared vocabulary, pure logic, and port interfaces for the Agent Architecture Board.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,7 +26,7 @@
26
26
  "dependencies": {
27
27
  "ai": "^7.0.51",
28
28
  "yaml": "^2.9.0",
29
- "@cat-factory/contracts": "0.273.0"
29
+ "@cat-factory/contracts": "0.275.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@stryker-mutator/core": "9.6.1",