dsh-realtime 0.2.0 → 0.2.2

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/lib/index.d.ts CHANGED
@@ -9,9 +9,12 @@
9
9
  * @module dsh-realtime
10
10
  */
11
11
  import { Service, type Context } from '@deepseek-ai/cordis';
12
+ import { Journal } from './journal.ts';
12
13
  import type { RealtimeDelegation, RealtimeModelInfo, RealtimeProviderInfo, RealtimeSession, RealtimeSessionHandlers, RealtimeSessionOptions } from './types.ts';
13
14
  export * from './types.ts';
14
15
  export * from './error.ts';
16
+ export * from './redact.ts';
17
+ export * from './journal.ts';
15
18
  export { RealtimeError, REALTIME_ERROR_CODES } from './error.ts';
16
19
  declare module '@deepseek-ai/cordis' {
17
20
  interface Context {
@@ -81,6 +84,14 @@ export declare abstract class RealtimeAdapter {
81
84
  */
82
85
  export declare class RealtimeRuntime extends Service {
83
86
  private readonly adapters;
87
+ /**
88
+ * The journal every plugin in this bundle writes to, and the diagnostics route serves.
89
+ *
90
+ * Owned here because this is the only object all of them already hold: the responder knows the
91
+ * delegation, the audio route knows the socket, and the seam is where those two meet. One journal
92
+ * per process — a reader should not be left correlating three partial ones.
93
+ */
94
+ readonly journal: Journal;
84
95
  /**
85
96
  * @param ctx - the Cordis context this service is mounted on.
86
97
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE3D,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,uBAAuB,EACvB,sBAAsB,EACvB,MAAM,YAAY,CAAA;AAEnB,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEhE,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,QAAQ,EAAE,eAAe,CAAA;KAC1B;CACF;AAED;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,6DAA6D;IAC7D,IAAI,IAAI,CAAA;IACR;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;CACnC;AAED;;;;;;GAMG;AACH,8BAAsB,eAAe;IACnC;;;;OAIG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,CAEnD;IAED;;;;;;;OAOG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAEnE;IAED;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;CAC5E;AAQD;;;;;GAKG;AACH,qBAAa,eAAgB,SAAQ,OAAO;IAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyC;IAElE;;OAEG;IACH,YAAY,GAAG,EAAE,OAAO,EAEvB;IAED;;;;;;;;OAQG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,eAAe,GAAG,yBAAyB,CAiCxF;IAED;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,OAAO;IAgCf;;;;;OAKG;IACH,OAAO,CAAC,MAAM;IASd;;;OAGG;IACH,aAAa,IAAI,oBAAoB,EAAE,CAEtC;IAED;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAQpB;;;;OAIG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAe/D;IAED;;;;;;;;OAQG;IACG,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,eAAe,CAAC,CAgBvE;IAED;;;;;;;;;;OAUG;IACH,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAWjE;CACF;AAED,qFAAqF;AACrF,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,CAAA;eAE5C,eAAe"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE3D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,uBAAuB,EACvB,sBAAsB,EACvB,MAAM,YAAY,CAAA;AAEnB,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEhE,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,QAAQ,EAAE,eAAe,CAAA;KAC1B;CACF;AAED;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,6DAA6D;IAC7D,IAAI,IAAI,CAAA;IACR;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;CACnC;AAED;;;;;;GAMG;AACH,8BAAsB,eAAe;IACnC;;;;OAIG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,CAEnD;IAED;;;;;;;OAOG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAEnE;IAED;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;CAC5E;AAQD;;;;;GAKG;AACH,qBAAa,eAAgB,SAAQ,OAAO;IAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyC;IAElE;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,UAAgB;IAEhC;;OAEG;IACH,YAAY,GAAG,EAAE,OAAO,EAEvB;IAED;;;;;;;;OAQG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,eAAe,GAAG,yBAAyB,CAiCxF;IAED;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,OAAO;IAgCf;;;;;OAKG;IACH,OAAO,CAAC,MAAM;IASd;;;OAGG;IACH,aAAa,IAAI,oBAAoB,EAAE,CAEtC;IAED;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAQpB;;;;OAIG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAe/D;IAED;;;;;;;;OAQG;IACG,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,eAAe,CAAC,CAgBvE;IAED;;;;;;;;;;OAUG;IACH,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAWjE;CACF;AAED,qFAAqF;AACrF,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,CAAA;eAE5C,eAAe"}
package/lib/index.js CHANGED
@@ -10,8 +10,11 @@
10
10
  */
11
11
  import { Service } from '@deepseek-ai/cordis';
12
12
  import { REALTIME_ERROR_CODES, RealtimeError } from './error.js';
13
+ import { Journal } from './journal.js';
13
14
  export * from './types.js';
14
15
  export * from './error.js';
16
+ export * from './redact.js';
17
+ export * from './journal.js';
15
18
  export { RealtimeError, REALTIME_ERROR_CODES } from './error.js';
16
19
  /**
17
20
  * Provider-wire adapter for the realtime session vocabulary.
@@ -49,6 +52,14 @@ export class RealtimeAdapter {
49
52
  */
50
53
  export class RealtimeRuntime extends Service {
51
54
  adapters = new Map();
55
+ /**
56
+ * The journal every plugin in this bundle writes to, and the diagnostics route serves.
57
+ *
58
+ * Owned here because this is the only object all of them already hold: the responder knows the
59
+ * delegation, the audio route knows the socket, and the seam is where those two meet. One journal
60
+ * per process — a reader should not be left correlating three partial ones.
61
+ */
62
+ journal = new Journal();
52
63
  /**
53
64
  * @param ctx - the Cordis context this service is mounted on.
54
65
  */
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * The journal: a bounded, redaction-safe record of what the plugin did.
3
+ *
4
+ * S1 story 2, and the substrate for the rest of the sprint — the diagnostics route serves it, the
5
+ * spoken failures narrate from it, and the fault-injection matrix asserts against it. It exists
6
+ * because every failure this project has spent hours on was *unobservable* rather than wrong: the
7
+ * delegation that produced nothing, the append nobody heard, the socket the host refused. A journal
8
+ * turns each of those into a line.
9
+ *
10
+ * Three properties are load-bearing, and each is a test rather than a promise:
11
+ *
12
+ * - **Redaction happens on write, not on read.** A journal that stores the raw text and redacts when
13
+ * serving it has already leaked: the secret is in memory, in a crash dump, and in whatever later
14
+ * edit forgets the second step. Every string is passed through {@link redact} *before* it is
15
+ * retained, so there is no unredacted copy to forget about. (That is design.md invariant 3 —
16
+ * credentials proven safe by a test that fails on a planted string.)
17
+ * - **Bounded, with eviction visible.** The buffer keeps the last `capacity` entries. Every entry
18
+ * carries a monotonic {@link JournalEntry.seq}, so a reader can tell that earlier entries were
19
+ * dropped rather than never recorded — a ring buffer that silently truncates is how "the log is
20
+ * empty" becomes indistinguishable from "nothing happened".
21
+ * - **Ack is not delivery.** Invariant 6: an acknowledgement proves injection, not that anyone heard
22
+ * it. `append.acknowledged` and `speech.played` are separate kinds with separate entries, and
23
+ * nothing here merges them into one "sent" line.
24
+ *
25
+ * @module dsh-realtime/journal
26
+ */
27
+ /** Entries retained when a caller does not say otherwise. */
28
+ export declare const DEFAULT_JOURNAL_CAPACITY = 200;
29
+ /**
30
+ * What a journal entry can describe.
31
+ *
32
+ * The list is deliberately small and closed: an entry kind is a contract with whatever reads the
33
+ * journal later, so adding one is a considered act rather than a free-form log line.
34
+ */
35
+ export type JournalKind =
36
+ /** A voice session opened, or was asked to and could not. */
37
+ 'session.opened' | 'session.closed' | 'session.failed'
38
+ /**
39
+ * The audio route accepted a socket, refused one and said why, or watched one leave.
40
+ *
41
+ * A close is recorded rather than left to be inferred from the absence of later entries: a reader
42
+ * looking at a journal that simply stops cannot tell a client that hung up from a process that died,
43
+ * and that difference is the whole reason anybody opens the journal.
44
+ */
45
+ | 'socket.accepted' | 'socket.rejected' | 'socket.closed'
46
+ /** The model raised a delegation, and the id it will be correlated by. */
47
+ | 'delegation.seen'
48
+ /** A delegated turn was admitted to the session, refused, declined, or ran out of window. */
49
+ | 'prompt.admitted' | 'prompt.refused' | 'prompt.declined' | 'window.elapsed'
50
+ /** A turn produced an answer. */
51
+ | 'answer.received'
52
+ /** A context append was acknowledged on the wire. Not delivery — see invariant 6. */
53
+ | 'append.acknowledged'
54
+ /** Audio was handed to the transport. **Not** a claim that anyone heard it. */
55
+ | 'speech.sent'
56
+ /** The configuration as resolved, so a journal can be read without the profile beside it. */
57
+ | 'config.resolved';
58
+ /** One retained entry. */
59
+ export interface JournalEntry {
60
+ /**
61
+ * Monotonic sequence number, never reused and never reset within one journal instance.
62
+ *
63
+ * A gap between the oldest retained `seq` and the newest means entries were evicted, which is the
64
+ * difference between "nothing happened" and "the buffer rolled over".
65
+ */
66
+ readonly seq: number;
67
+ /** Milliseconds since the epoch, as the injecting caller saw it. */
68
+ readonly at: number;
69
+ /** What happened. */
70
+ readonly kind: JournalKind;
71
+ /** Redacted detail fields; keys are the caller's, values are already safe to serve. */
72
+ readonly detail: Readonly<Record<string, string>>;
73
+ }
74
+ /** How a journal is constructed. */
75
+ export interface JournalOptions {
76
+ /** Entries retained before the oldest is evicted. */
77
+ capacity?: number;
78
+ /**
79
+ * Values that must never appear in an entry, however they are spelled.
80
+ *
81
+ * The route's capability token is 32 random bytes of base64url: it has no prefix and no structure,
82
+ * so no pattern can catch it and only a caller holding it can name it. The provider key is
83
+ * normally caught by shape, but pass it here too — a seam that relies on shape alone fails the
84
+ * moment a vendor changes its prefix.
85
+ */
86
+ secrets?: readonly string[];
87
+ }
88
+ /**
89
+ * A bounded, redaction-safe journal of plugin activity.
90
+ *
91
+ * Not a service: it is a plain object so it can be constructed in a test, handed to a route, and
92
+ * read without a Cordis context. The seam owns one instance and lends it to the plugins that write
93
+ * to it.
94
+ */
95
+ export declare class Journal {
96
+ private readonly capacity;
97
+ private readonly secrets;
98
+ private readonly entries;
99
+ private nextSeq;
100
+ /**
101
+ * @param options - capacity and the values that must never be retained.
102
+ * @throws RangeError when `capacity` is not a positive integer — a journal that retains nothing
103
+ * is a failure that looks like silence, which is the defect this module exists to remove.
104
+ */
105
+ constructor(options?: JournalOptions);
106
+ /**
107
+ * Add values that must never be retained, from a plugin that has only just learned them.
108
+ *
109
+ * Additive rather than a constructor argument because secrets arrive with the plugins that hold
110
+ * them, and plugins load in no guaranteed order: the route's capability token does not exist until
111
+ * the plugin that mints it applies, and a provider key arrives with whichever plugin reads the
112
+ * environment. A journal that could only be seeded at construction would have to be replaced to
113
+ * learn either — which is how a sink ends up with two instances and a reader with half a story.
114
+ * @param values - secrets to add; empty and duplicate values are ignored.
115
+ */
116
+ addSecrets(values: readonly string[]): void;
117
+ /**
118
+ * Redact and retain one entry, evicting the oldest when the buffer is full.
119
+ * @param kind - what happened.
120
+ * @param detail - string fields to retain; every value is redacted before it is stored.
121
+ * @param at - epoch milliseconds; defaults to now, and is injectable so a test can pin a time.
122
+ * @returns the entry exactly as it was retained, so a caller can correlate on `seq`.
123
+ */
124
+ record(kind: JournalKind, detail?: Readonly<Record<string, string>>, at?: number): JournalEntry;
125
+ /**
126
+ * Every retained entry, oldest first.
127
+ *
128
+ * Detached: mutating the result cannot reach the journal, so a route serving this cannot be used
129
+ * to tamper with the record it is reporting.
130
+ * @returns a detached, chronologically ordered copy.
131
+ */
132
+ snapshot(): JournalEntry[];
133
+ /** Entries currently retained. */
134
+ get size(): number;
135
+ /** Sequence number of the oldest retained entry, or `undefined` when empty. */
136
+ get oldestSeq(): number | undefined;
137
+ }
138
+ //# sourceMappingURL=journal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,6DAA6D;AAC7D,eAAO,MAAM,wBAAwB,MAAM,CAAA;AAE3C;;;;;GAKG;AACH,MAAM,MAAM,WAAW;AACrB,6DAA6D;AAC3D,gBAAgB,GAAG,gBAAgB,GAAG,gBAAgB;AACxD;;;;;;GAMG;GACD,iBAAiB,GAAG,iBAAiB,GAAG,eAAe;AACzD,0EAA0E;GACxE,iBAAiB;AACnB,6FAA6F;GAC3F,iBAAiB,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,gBAAgB;AAC7E,iCAAiC;GAC/B,iBAAiB;AACnB,qFAAqF;GACnF,qBAAqB;AACvB,+EAA+E;GAC7E,aAAa;AACf,6FAA6F;GAC3F,iBAAiB,CAAA;AAErB,0BAA0B;AAC1B,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,qBAAqB;IACrB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;IAC1B,uFAAuF;IACvF,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CAClD;AAED,oCAAoC;AACpC,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC5B;AAED;;;;;;GAMG;AACH,qBAAa,OAAO;IAClB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,OAAO,CAAI;IAEnB;;;;OAIG;IACH,YAAY,OAAO,GAAE,cAAmB,EAOvC;IAED;;;;;;;;;OASG;IACH,UAAU,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAK1C;IAED;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,EAAE,EAAE,GAAE,MAAmB,GAAG,YAAY,CAgB9G;IAED;;;;;;OAMG;IACH,QAAQ,IAAI,YAAY,EAAE,CAEzB;IAED,kCAAkC;IAClC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,+EAA+E;IAC/E,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;CACF"}
package/lib/journal.js ADDED
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The journal: a bounded, redaction-safe record of what the plugin did.
3
+ *
4
+ * S1 story 2, and the substrate for the rest of the sprint — the diagnostics route serves it, the
5
+ * spoken failures narrate from it, and the fault-injection matrix asserts against it. It exists
6
+ * because every failure this project has spent hours on was *unobservable* rather than wrong: the
7
+ * delegation that produced nothing, the append nobody heard, the socket the host refused. A journal
8
+ * turns each of those into a line.
9
+ *
10
+ * Three properties are load-bearing, and each is a test rather than a promise:
11
+ *
12
+ * - **Redaction happens on write, not on read.** A journal that stores the raw text and redacts when
13
+ * serving it has already leaked: the secret is in memory, in a crash dump, and in whatever later
14
+ * edit forgets the second step. Every string is passed through {@link redact} *before* it is
15
+ * retained, so there is no unredacted copy to forget about. (That is design.md invariant 3 —
16
+ * credentials proven safe by a test that fails on a planted string.)
17
+ * - **Bounded, with eviction visible.** The buffer keeps the last `capacity` entries. Every entry
18
+ * carries a monotonic {@link JournalEntry.seq}, so a reader can tell that earlier entries were
19
+ * dropped rather than never recorded — a ring buffer that silently truncates is how "the log is
20
+ * empty" becomes indistinguishable from "nothing happened".
21
+ * - **Ack is not delivery.** Invariant 6: an acknowledgement proves injection, not that anyone heard
22
+ * it. `append.acknowledged` and `speech.played` are separate kinds with separate entries, and
23
+ * nothing here merges them into one "sent" line.
24
+ *
25
+ * @module dsh-realtime/journal
26
+ */
27
+ import { redact } from './redact.js';
28
+ /** Entries retained when a caller does not say otherwise. */
29
+ export const DEFAULT_JOURNAL_CAPACITY = 200;
30
+ /**
31
+ * A bounded, redaction-safe journal of plugin activity.
32
+ *
33
+ * Not a service: it is a plain object so it can be constructed in a test, handed to a route, and
34
+ * read without a Cordis context. The seam owns one instance and lends it to the plugins that write
35
+ * to it.
36
+ */
37
+ export class Journal {
38
+ capacity;
39
+ secrets;
40
+ entries = [];
41
+ nextSeq = 1;
42
+ /**
43
+ * @param options - capacity and the values that must never be retained.
44
+ * @throws RangeError when `capacity` is not a positive integer — a journal that retains nothing
45
+ * is a failure that looks like silence, which is the defect this module exists to remove.
46
+ */
47
+ constructor(options = {}) {
48
+ const capacity = options.capacity ?? DEFAULT_JOURNAL_CAPACITY;
49
+ if (!Number.isInteger(capacity) || capacity < 1) {
50
+ throw new RangeError(`journal capacity must be a positive integer, received ${String(capacity)}`);
51
+ }
52
+ this.capacity = capacity;
53
+ this.secrets = [...options.secrets ?? []];
54
+ }
55
+ /**
56
+ * Add values that must never be retained, from a plugin that has only just learned them.
57
+ *
58
+ * Additive rather than a constructor argument because secrets arrive with the plugins that hold
59
+ * them, and plugins load in no guaranteed order: the route's capability token does not exist until
60
+ * the plugin that mints it applies, and a provider key arrives with whichever plugin reads the
61
+ * environment. A journal that could only be seeded at construction would have to be replaced to
62
+ * learn either — which is how a sink ends up with two instances and a reader with half a story.
63
+ * @param values - secrets to add; empty and duplicate values are ignored.
64
+ */
65
+ addSecrets(values) {
66
+ for (const value of values) {
67
+ if (typeof value !== 'string' || value.length === 0)
68
+ continue;
69
+ if (!this.secrets.includes(value))
70
+ this.secrets.push(value);
71
+ }
72
+ }
73
+ /**
74
+ * Redact and retain one entry, evicting the oldest when the buffer is full.
75
+ * @param kind - what happened.
76
+ * @param detail - string fields to retain; every value is redacted before it is stored.
77
+ * @param at - epoch milliseconds; defaults to now, and is injectable so a test can pin a time.
78
+ * @returns the entry exactly as it was retained, so a caller can correlate on `seq`.
79
+ */
80
+ record(kind, detail = {}, at = Date.now()) {
81
+ const safe = {};
82
+ for (const [key, value] of Object.entries(detail)) {
83
+ safe[key] = redact(value, this.secrets);
84
+ }
85
+ const entry = Object.freeze({
86
+ seq: this.nextSeq++,
87
+ at,
88
+ kind,
89
+ detail: Object.freeze(safe),
90
+ });
91
+ this.entries.push(entry);
92
+ // `shift()` on a bounded array: the buffer is capped, so this stays O(capacity) at worst and
93
+ // the memory ceiling is a property of the type rather than of the caller's discipline.
94
+ while (this.entries.length > this.capacity)
95
+ this.entries.shift();
96
+ return entry;
97
+ }
98
+ /**
99
+ * Every retained entry, oldest first.
100
+ *
101
+ * Detached: mutating the result cannot reach the journal, so a route serving this cannot be used
102
+ * to tamper with the record it is reporting.
103
+ * @returns a detached, chronologically ordered copy.
104
+ */
105
+ snapshot() {
106
+ return this.entries.map(entry => ({ ...entry, detail: { ...entry.detail } }));
107
+ }
108
+ /** Entries currently retained. */
109
+ get size() {
110
+ return this.entries.length;
111
+ }
112
+ /** Sequence number of the oldest retained entry, or `undefined` when empty. */
113
+ get oldestSeq() {
114
+ return this.entries[0]?.seq;
115
+ }
116
+ }
117
+ //# sourceMappingURL=journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEpC,6DAA6D;AAC7D,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAA;AAgE3C;;;;;;GAMG;AACH,MAAM,OAAO,OAAO;IACD,QAAQ,CAAQ;IAChB,OAAO,CAAU;IACjB,OAAO,GAAmB,EAAE,CAAA;IACrC,OAAO,GAAG,CAAC,CAAA;IAEnB;;;;OAIG;IACH,YAAY,OAAO,GAAmB,EAAE;QACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,wBAAwB,CAAA;QAC7D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,UAAU,CAAC,yDAAyD,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QACnG,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACxB,IAAI,CAAC,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAA;IAC3C,CAAC;IAED;;;;;;;;;OASG;IACH,UAAU,CAAC,MAAyB;QAClC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAQ;YAC7D,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC7D,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,IAAiB,EAAE,MAAM,GAAqC,EAAE,EAAE,EAAE,GAAW,IAAI,CAAC,GAAG,EAAE;QAC9F,MAAM,IAAI,GAA2B,EAAE,CAAA;QACvC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;QACzC,CAAC;QACD,MAAM,KAAK,GAAiB,MAAM,CAAC,MAAM,CAAC;YACxC,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE;YACnB,EAAE;YACF,IAAI;YACJ,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;SAC5B,CAAC,CAAA;QACF,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACxB,6FAA6F;QAC7F,uFAAuF;QACvF,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;QAChE,OAAO,KAAK,CAAA;IACd,CAAC;IAED;;;;;;OAMG;IACH,QAAQ;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAA;IAC/E,CAAC;IAED,kCAAkC;IAClC,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAA;IAC5B,CAAC;IAED,+EAA+E;IAC/E,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAA;IAC7B,CAAC;CACF"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Redaction: the one place provider text becomes text a human or a log may see.
3
+ *
4
+ * The plugin's job is to hold a live session, so credentials flow through every path it has. Three
5
+ * surfaces introduced for diagnostics — a journal, a route and spoken failures — are three new places
6
+ * a provider key or the route's capability token could escape into, and a disclosure outlives the
7
+ * debugging session that introduced it. Review does not scale to that; a primitive that every sink
8
+ * passes through, with a test that fails on a planted string, does.
9
+ *
10
+ * Two arms, because the two kinds of secret cannot be caught the same way:
11
+ *
12
+ * - **Shape.** A key with a distinctive prefix (`sk-…`), a bearer header, a private-key block, a JWT,
13
+ * a recorded key fingerprint. These are unambiguous by construction and match anywhere.
14
+ * - **Value.** The route's capability token is 32 random bytes in base64url — it has no prefix, no
15
+ * `=` padding and no structure, so no pattern can ever catch it. A caller that *holds* the secret
16
+ * must name it. That is why {@link redact} takes a list.
17
+ *
18
+ * There is deliberately **no allow-list and no "safe" mode**: a check with a compliant path relocates
19
+ * the work instead of preventing it. Naming the setting is more useful to a reader than a literal
20
+ * anyway.
21
+ *
22
+ * @module dsh-realtime/redact
23
+ */
24
+ /** What replaces a value that must not travel. One marker, so a reader can count the removals. */
25
+ export declare const REDACTED = "[redacted]";
26
+ /**
27
+ * Make text safe to journal, serve or speak.
28
+ *
29
+ * @param text - candidate text; returned unchanged when it is empty or not a string.
30
+ * @param secrets - values the caller knows must not appear. Omit when the caller holds none — the
31
+ * shape rules still apply.
32
+ * @returns the text with every matched secret replaced.
33
+ */
34
+ export declare function redact(text: string, secrets?: readonly string[]): string;
35
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,kGAAkG;AAClG,eAAO,MAAM,QAAQ,eAAe,CAAA;AAyCpC;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,SAAS,MAAM,EAAO,GAAG,MAAM,CAK5E"}
package/lib/redact.js ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Redaction: the one place provider text becomes text a human or a log may see.
3
+ *
4
+ * The plugin's job is to hold a live session, so credentials flow through every path it has. Three
5
+ * surfaces introduced for diagnostics — a journal, a route and spoken failures — are three new places
6
+ * a provider key or the route's capability token could escape into, and a disclosure outlives the
7
+ * debugging session that introduced it. Review does not scale to that; a primitive that every sink
8
+ * passes through, with a test that fails on a planted string, does.
9
+ *
10
+ * Two arms, because the two kinds of secret cannot be caught the same way:
11
+ *
12
+ * - **Shape.** A key with a distinctive prefix (`sk-…`), a bearer header, a private-key block, a JWT,
13
+ * a recorded key fingerprint. These are unambiguous by construction and match anywhere.
14
+ * - **Value.** The route's capability token is 32 random bytes in base64url — it has no prefix, no
15
+ * `=` padding and no structure, so no pattern can ever catch it. A caller that *holds* the secret
16
+ * must name it. That is why {@link redact} takes a list.
17
+ *
18
+ * There is deliberately **no allow-list and no "safe" mode**: a check with a compliant path relocates
19
+ * the work instead of preventing it. Naming the setting is more useful to a reader than a literal
20
+ * anyway.
21
+ *
22
+ * @module dsh-realtime/redact
23
+ */
24
+ /** What replaces a value that must not travel. One marker, so a reader can count the removals. */
25
+ export const REDACTED = '[redacted]';
26
+ /**
27
+ * Secrets whose *shape* is unambiguous, and the marker that replaces each.
28
+ *
29
+ * Every entry must be unmistakable on its own: a pattern that could match ordinary prose would
30
+ * corrupt the diagnostic it was meant to protect, which is the same failure as leaking — a report
31
+ * nobody can act on.
32
+ */
33
+ const SHAPES = Object.freeze([
34
+ // A PEM block, before the generic rules can chew holes in it.
35
+ [/-----BEGIN[^\n]*PRIVATE KEY-----[\s\S]*?-----END[^\n]*PRIVATE KEY-----/g, '[redacted private key]'],
36
+ // A named API key: `sk-`, `rk-`, `pk-` and the vendor suffix they share.
37
+ [/\b(?:sk|rk|pk)-[A-Za-z0-9_-]{8,}/g, REDACTED],
38
+ // An Authorization header carried in a message.
39
+ [/\bBearer[ \t]+[A-Za-z0-9._~+/=-]{8,}/gi, `Bearer ${REDACTED}`],
40
+ // A JSON Web Token — three base64url segments, which is a credential or contains one.
41
+ [/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g, REDACTED],
42
+ // A recorded key fingerprint: stable, org-identifying, and a leak in its own right.
43
+ [/\bfp=[0-9a-f]{16,}/gi, `fp=${REDACTED}`],
44
+ ]);
45
+ /**
46
+ * Remove every occurrence of a secret the caller holds.
47
+ *
48
+ * Longest first: one secret can contain another (a fingerprint inside a key), and replacing the
49
+ * shorter one first would leave a recognisable fragment of the longer one behind.
50
+ *
51
+ * @param text - candidate text.
52
+ * @param secrets - values that must not appear, in any form.
53
+ * @returns the text with each secret replaced, and no partial key material left.
54
+ */
55
+ function stripKnown(text, secrets) {
56
+ const ordered = secrets
57
+ .filter(secret => typeof secret === 'string' && secret.length > 0)
58
+ .sort((a, b) => b.length - a.length);
59
+ let out = text;
60
+ for (const secret of ordered)
61
+ out = out.split(secret).join(REDACTED);
62
+ return out;
63
+ }
64
+ /**
65
+ * Make text safe to journal, serve or speak.
66
+ *
67
+ * @param text - candidate text; returned unchanged when it is empty or not a string.
68
+ * @param secrets - values the caller knows must not appear. Omit when the caller holds none — the
69
+ * shape rules still apply.
70
+ * @returns the text with every matched secret replaced.
71
+ */
72
+ export function redact(text, secrets = []) {
73
+ if (typeof text !== 'string' || text.length === 0)
74
+ return text;
75
+ let out = text;
76
+ for (const [shape, replacement] of SHAPES)
77
+ out = out.replace(shape, replacement);
78
+ return secrets.length === 0 ? out : stripKnown(out, secrets);
79
+ }
80
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,kGAAkG;AAClG,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAA;AAEpC;;;;;;GAMG;AACH,MAAM,MAAM,GAA2C,MAAM,CAAC,MAAM,CAAC;IACnE,8DAA8D;IAC9D,CAAC,yEAAyE,EAAE,wBAAwB,CAAC;IACrG,yEAAyE;IACzE,CAAC,mCAAmC,EAAE,QAAQ,CAAC;IAC/C,gDAAgD;IAChD,CAAC,wCAAwC,EAAE,UAAU,QAAQ,EAAE,CAAC;IAChE,sFAAsF;IACtF,CAAC,+DAA+D,EAAE,QAAQ,CAAC;IAC3E,oFAAoF;IACpF,CAAC,sBAAsB,EAAE,MAAM,QAAQ,EAAE,CAAC;CAClC,CAAC,CAAA;AAEX;;;;;;;;;GASG;AACH,SAAS,UAAU,CAAC,IAAY,EAAE,OAA0B;IAC1D,MAAM,OAAO,GAAG,OAAO;SACpB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;SACjE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAA;IACtC,IAAI,GAAG,GAAG,IAAI,CAAA;IACd,KAAK,MAAM,MAAM,IAAI,OAAO;QAAE,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACpE,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,MAAM,CAAC,IAAY,EAAE,OAAO,GAAsB,EAAE;IAClE,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAC9D,IAAI,GAAG,GAAG,IAAI,CAAA;IACd,KAAK,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,IAAI,MAAM;QAAE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,WAAW,CAAC,CAAA;IAChF,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;AAC9D,CAAC"}
package/lib/types.d.ts CHANGED
@@ -187,4 +187,30 @@ export interface RealtimeSession {
187
187
  * halfway through a live conversation. Exact token accounting belongs to the adapter.
188
188
  */
189
189
  export declare const MAX_APPEND_CHARS = 2000;
190
+ /**
191
+ * How one delegated turn ended, when the answer was not an answer.
192
+ *
193
+ * Carried so a refusal can be *described* rather than only counted. The four cases are the four
194
+ * different things a person would do about it — nothing was asked, the controller refused (and said
195
+ * why), or it was admitted and nothing came back in time — and collapsing them into one `undefined`
196
+ * is what made the plugin's failures indistinguishable from outside it.
197
+ */
198
+ export type RealtimeDelegationOutcome = 'answered' | 'declined' | 'refused' | 'timeout';
199
+ /**
200
+ * One delegated turn, settled, reported for diagnostics.
201
+ *
202
+ * `reason` is present only on `refused`, and is **redacted** before it is emitted: this is the first
203
+ * surface on which text the plugin did not author reaches a human, so it is safe by construction
204
+ * rather than by a later pass.
205
+ */
206
+ export interface RealtimeDelegationSettlement {
207
+ /** The delegation this settles. Correlates with the ask, the answer and the journal. */
208
+ readonly id: string;
209
+ /** The session that raised it. */
210
+ readonly sessionId: string;
211
+ /** How it ended. */
212
+ readonly outcome: RealtimeDelegationOutcome;
213
+ /** The controller's own reason, redacted, when the outcome is `refused`. */
214
+ readonly reason?: string;
215
+ }
190
216
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-realtime",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Realtime voice capability seam for DeepSeek Harness: a provider registry plus the session adapter base.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -18,6 +18,10 @@
18
18
  "types": "./lib/error.d.ts",
19
19
  "default": "./lib/error.js"
20
20
  },
21
+ "./redact": {
22
+ "types": "./lib/redact.d.ts",
23
+ "default": "./lib/redact.js"
24
+ },
21
25
  "./src/*": "./src/*",
22
26
  "./package.json": "./package.json"
23
27
  },
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@
11
11
 
12
12
  import { Service, type Context } from '@deepseek-ai/cordis'
13
13
  import { REALTIME_ERROR_CODES, RealtimeError } from './error.ts'
14
+ import { Journal } from './journal.ts'
14
15
  import type {
15
16
  RealtimeDelegation,
16
17
  RealtimeModelInfo,
@@ -22,6 +23,8 @@ import type {
22
23
 
23
24
  export * from './types.ts'
24
25
  export * from './error.ts'
26
+ export * from './redact.ts'
27
+ export * from './journal.ts'
25
28
  export { RealtimeError, REALTIME_ERROR_CODES } from './error.ts'
26
29
 
27
30
  declare module '@deepseek-ai/cordis' {
@@ -108,6 +111,15 @@ interface AdapterRegistration {
108
111
  export class RealtimeRuntime extends Service {
109
112
  private readonly adapters = new Map<string, AdapterRegistration>()
110
113
 
114
+ /**
115
+ * The journal every plugin in this bundle writes to, and the diagnostics route serves.
116
+ *
117
+ * Owned here because this is the only object all of them already hold: the responder knows the
118
+ * delegation, the audio route knows the socket, and the seam is where those two meet. One journal
119
+ * per process — a reader should not be left correlating three partial ones.
120
+ */
121
+ readonly journal = new Journal()
122
+
111
123
  /**
112
124
  * @param ctx - the Cordis context this service is mounted on.
113
125
  */
package/src/journal.ts ADDED
@@ -0,0 +1,184 @@
1
+ /**
2
+ * The journal: a bounded, redaction-safe record of what the plugin did.
3
+ *
4
+ * S1 story 2, and the substrate for the rest of the sprint — the diagnostics route serves it, the
5
+ * spoken failures narrate from it, and the fault-injection matrix asserts against it. It exists
6
+ * because every failure this project has spent hours on was *unobservable* rather than wrong: the
7
+ * delegation that produced nothing, the append nobody heard, the socket the host refused. A journal
8
+ * turns each of those into a line.
9
+ *
10
+ * Three properties are load-bearing, and each is a test rather than a promise:
11
+ *
12
+ * - **Redaction happens on write, not on read.** A journal that stores the raw text and redacts when
13
+ * serving it has already leaked: the secret is in memory, in a crash dump, and in whatever later
14
+ * edit forgets the second step. Every string is passed through {@link redact} *before* it is
15
+ * retained, so there is no unredacted copy to forget about. (That is design.md invariant 3 —
16
+ * credentials proven safe by a test that fails on a planted string.)
17
+ * - **Bounded, with eviction visible.** The buffer keeps the last `capacity` entries. Every entry
18
+ * carries a monotonic {@link JournalEntry.seq}, so a reader can tell that earlier entries were
19
+ * dropped rather than never recorded — a ring buffer that silently truncates is how "the log is
20
+ * empty" becomes indistinguishable from "nothing happened".
21
+ * - **Ack is not delivery.** Invariant 6: an acknowledgement proves injection, not that anyone heard
22
+ * it. `append.acknowledged` and `speech.played` are separate kinds with separate entries, and
23
+ * nothing here merges them into one "sent" line.
24
+ *
25
+ * @module dsh-realtime/journal
26
+ */
27
+
28
+ import { redact } from './redact.ts'
29
+
30
+ /** Entries retained when a caller does not say otherwise. */
31
+ export const DEFAULT_JOURNAL_CAPACITY = 200
32
+
33
+ /**
34
+ * What a journal entry can describe.
35
+ *
36
+ * The list is deliberately small and closed: an entry kind is a contract with whatever reads the
37
+ * journal later, so adding one is a considered act rather than a free-form log line.
38
+ */
39
+ export type JournalKind =
40
+ /** A voice session opened, or was asked to and could not. */
41
+ | 'session.opened' | 'session.closed' | 'session.failed'
42
+ /**
43
+ * The audio route accepted a socket, refused one and said why, or watched one leave.
44
+ *
45
+ * A close is recorded rather than left to be inferred from the absence of later entries: a reader
46
+ * looking at a journal that simply stops cannot tell a client that hung up from a process that died,
47
+ * and that difference is the whole reason anybody opens the journal.
48
+ */
49
+ | 'socket.accepted' | 'socket.rejected' | 'socket.closed'
50
+ /** The model raised a delegation, and the id it will be correlated by. */
51
+ | 'delegation.seen'
52
+ /** A delegated turn was admitted to the session, refused, declined, or ran out of window. */
53
+ | 'prompt.admitted' | 'prompt.refused' | 'prompt.declined' | 'window.elapsed'
54
+ /** A turn produced an answer. */
55
+ | 'answer.received'
56
+ /** A context append was acknowledged on the wire. Not delivery — see invariant 6. */
57
+ | 'append.acknowledged'
58
+ /** Audio was handed to the transport. **Not** a claim that anyone heard it. */
59
+ | 'speech.sent'
60
+ /** The configuration as resolved, so a journal can be read without the profile beside it. */
61
+ | 'config.resolved'
62
+
63
+ /** One retained entry. */
64
+ export interface JournalEntry {
65
+ /**
66
+ * Monotonic sequence number, never reused and never reset within one journal instance.
67
+ *
68
+ * A gap between the oldest retained `seq` and the newest means entries were evicted, which is the
69
+ * difference between "nothing happened" and "the buffer rolled over".
70
+ */
71
+ readonly seq: number
72
+ /** Milliseconds since the epoch, as the injecting caller saw it. */
73
+ readonly at: number
74
+ /** What happened. */
75
+ readonly kind: JournalKind
76
+ /** Redacted detail fields; keys are the caller's, values are already safe to serve. */
77
+ readonly detail: Readonly<Record<string, string>>
78
+ }
79
+
80
+ /** How a journal is constructed. */
81
+ export interface JournalOptions {
82
+ /** Entries retained before the oldest is evicted. */
83
+ capacity?: number
84
+ /**
85
+ * Values that must never appear in an entry, however they are spelled.
86
+ *
87
+ * The route's capability token is 32 random bytes of base64url: it has no prefix and no structure,
88
+ * so no pattern can catch it and only a caller holding it can name it. The provider key is
89
+ * normally caught by shape, but pass it here too — a seam that relies on shape alone fails the
90
+ * moment a vendor changes its prefix.
91
+ */
92
+ secrets?: readonly string[]
93
+ }
94
+
95
+ /**
96
+ * A bounded, redaction-safe journal of plugin activity.
97
+ *
98
+ * Not a service: it is a plain object so it can be constructed in a test, handed to a route, and
99
+ * read without a Cordis context. The seam owns one instance and lends it to the plugins that write
100
+ * to it.
101
+ */
102
+ export class Journal {
103
+ private readonly capacity: number
104
+ private readonly secrets: string[]
105
+ private readonly entries: JournalEntry[] = []
106
+ private nextSeq = 1
107
+
108
+ /**
109
+ * @param options - capacity and the values that must never be retained.
110
+ * @throws RangeError when `capacity` is not a positive integer — a journal that retains nothing
111
+ * is a failure that looks like silence, which is the defect this module exists to remove.
112
+ */
113
+ constructor(options: JournalOptions = {}) {
114
+ const capacity = options.capacity ?? DEFAULT_JOURNAL_CAPACITY
115
+ if (!Number.isInteger(capacity) || capacity < 1) {
116
+ throw new RangeError(`journal capacity must be a positive integer, received ${String(capacity)}`)
117
+ }
118
+ this.capacity = capacity
119
+ this.secrets = [...options.secrets ?? []]
120
+ }
121
+
122
+ /**
123
+ * Add values that must never be retained, from a plugin that has only just learned them.
124
+ *
125
+ * Additive rather than a constructor argument because secrets arrive with the plugins that hold
126
+ * them, and plugins load in no guaranteed order: the route's capability token does not exist until
127
+ * the plugin that mints it applies, and a provider key arrives with whichever plugin reads the
128
+ * environment. A journal that could only be seeded at construction would have to be replaced to
129
+ * learn either — which is how a sink ends up with two instances and a reader with half a story.
130
+ * @param values - secrets to add; empty and duplicate values are ignored.
131
+ */
132
+ addSecrets(values: readonly string[]): void {
133
+ for (const value of values) {
134
+ if (typeof value !== 'string' || value.length === 0) continue
135
+ if (!this.secrets.includes(value)) this.secrets.push(value)
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Redact and retain one entry, evicting the oldest when the buffer is full.
141
+ * @param kind - what happened.
142
+ * @param detail - string fields to retain; every value is redacted before it is stored.
143
+ * @param at - epoch milliseconds; defaults to now, and is injectable so a test can pin a time.
144
+ * @returns the entry exactly as it was retained, so a caller can correlate on `seq`.
145
+ */
146
+ record(kind: JournalKind, detail: Readonly<Record<string, string>> = {}, at: number = Date.now()): JournalEntry {
147
+ const safe: Record<string, string> = {}
148
+ for (const [key, value] of Object.entries(detail)) {
149
+ safe[key] = redact(value, this.secrets)
150
+ }
151
+ const entry: JournalEntry = Object.freeze({
152
+ seq: this.nextSeq++,
153
+ at,
154
+ kind,
155
+ detail: Object.freeze(safe),
156
+ })
157
+ this.entries.push(entry)
158
+ // `shift()` on a bounded array: the buffer is capped, so this stays O(capacity) at worst and
159
+ // the memory ceiling is a property of the type rather than of the caller's discipline.
160
+ while (this.entries.length > this.capacity) this.entries.shift()
161
+ return entry
162
+ }
163
+
164
+ /**
165
+ * Every retained entry, oldest first.
166
+ *
167
+ * Detached: mutating the result cannot reach the journal, so a route serving this cannot be used
168
+ * to tamper with the record it is reporting.
169
+ * @returns a detached, chronologically ordered copy.
170
+ */
171
+ snapshot(): JournalEntry[] {
172
+ return this.entries.map(entry => ({ ...entry, detail: { ...entry.detail } }))
173
+ }
174
+
175
+ /** Entries currently retained. */
176
+ get size(): number {
177
+ return this.entries.length
178
+ }
179
+
180
+ /** Sequence number of the oldest retained entry, or `undefined` when empty. */
181
+ get oldestSeq(): number | undefined {
182
+ return this.entries[0]?.seq
183
+ }
184
+ }
package/src/redact.ts ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Redaction: the one place provider text becomes text a human or a log may see.
3
+ *
4
+ * The plugin's job is to hold a live session, so credentials flow through every path it has. Three
5
+ * surfaces introduced for diagnostics — a journal, a route and spoken failures — are three new places
6
+ * a provider key or the route's capability token could escape into, and a disclosure outlives the
7
+ * debugging session that introduced it. Review does not scale to that; a primitive that every sink
8
+ * passes through, with a test that fails on a planted string, does.
9
+ *
10
+ * Two arms, because the two kinds of secret cannot be caught the same way:
11
+ *
12
+ * - **Shape.** A key with a distinctive prefix (`sk-…`), a bearer header, a private-key block, a JWT,
13
+ * a recorded key fingerprint. These are unambiguous by construction and match anywhere.
14
+ * - **Value.** The route's capability token is 32 random bytes in base64url — it has no prefix, no
15
+ * `=` padding and no structure, so no pattern can ever catch it. A caller that *holds* the secret
16
+ * must name it. That is why {@link redact} takes a list.
17
+ *
18
+ * There is deliberately **no allow-list and no "safe" mode**: a check with a compliant path relocates
19
+ * the work instead of preventing it. Naming the setting is more useful to a reader than a literal
20
+ * anyway.
21
+ *
22
+ * @module dsh-realtime/redact
23
+ */
24
+
25
+ /** What replaces a value that must not travel. One marker, so a reader can count the removals. */
26
+ export const REDACTED = '[redacted]'
27
+
28
+ /**
29
+ * Secrets whose *shape* is unambiguous, and the marker that replaces each.
30
+ *
31
+ * Every entry must be unmistakable on its own: a pattern that could match ordinary prose would
32
+ * corrupt the diagnostic it was meant to protect, which is the same failure as leaking — a report
33
+ * nobody can act on.
34
+ */
35
+ const SHAPES: readonly (readonly [RegExp, string])[] = Object.freeze([
36
+ // A PEM block, before the generic rules can chew holes in it.
37
+ [/-----BEGIN[^\n]*PRIVATE KEY-----[\s\S]*?-----END[^\n]*PRIVATE KEY-----/g, '[redacted private key]'],
38
+ // A named API key: `sk-`, `rk-`, `pk-` and the vendor suffix they share.
39
+ [/\b(?:sk|rk|pk)-[A-Za-z0-9_-]{8,}/g, REDACTED],
40
+ // An Authorization header carried in a message.
41
+ [/\bBearer[ \t]+[A-Za-z0-9._~+/=-]{8,}/gi, `Bearer ${REDACTED}`],
42
+ // A JSON Web Token — three base64url segments, which is a credential or contains one.
43
+ [/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}/g, REDACTED],
44
+ // A recorded key fingerprint: stable, org-identifying, and a leak in its own right.
45
+ [/\bfp=[0-9a-f]{16,}/gi, `fp=${REDACTED}`],
46
+ ] as const)
47
+
48
+ /**
49
+ * Remove every occurrence of a secret the caller holds.
50
+ *
51
+ * Longest first: one secret can contain another (a fingerprint inside a key), and replacing the
52
+ * shorter one first would leave a recognisable fragment of the longer one behind.
53
+ *
54
+ * @param text - candidate text.
55
+ * @param secrets - values that must not appear, in any form.
56
+ * @returns the text with each secret replaced, and no partial key material left.
57
+ */
58
+ function stripKnown(text: string, secrets: readonly string[]): string {
59
+ const ordered = secrets
60
+ .filter(secret => typeof secret === 'string' && secret.length > 0)
61
+ .sort((a, b) => b.length - a.length)
62
+ let out = text
63
+ for (const secret of ordered) out = out.split(secret).join(REDACTED)
64
+ return out
65
+ }
66
+
67
+ /**
68
+ * Make text safe to journal, serve or speak.
69
+ *
70
+ * @param text - candidate text; returned unchanged when it is empty or not a string.
71
+ * @param secrets - values the caller knows must not appear. Omit when the caller holds none — the
72
+ * shape rules still apply.
73
+ * @returns the text with every matched secret replaced.
74
+ */
75
+ export function redact(text: string, secrets: readonly string[] = []): string {
76
+ if (typeof text !== 'string' || text.length === 0) return text
77
+ let out = text
78
+ for (const [shape, replacement] of SHAPES) out = out.replace(shape, replacement)
79
+ return secrets.length === 0 ? out : stripKnown(out, secrets)
80
+ }
package/src/types.ts CHANGED
@@ -206,3 +206,31 @@ export interface RealtimeSession {
206
206
  * halfway through a live conversation. Exact token accounting belongs to the adapter.
207
207
  */
208
208
  export const MAX_APPEND_CHARS = 2000
209
+
210
+ /**
211
+ * How one delegated turn ended, when the answer was not an answer.
212
+ *
213
+ * Carried so a refusal can be *described* rather than only counted. The four cases are the four
214
+ * different things a person would do about it — nothing was asked, the controller refused (and said
215
+ * why), or it was admitted and nothing came back in time — and collapsing them into one `undefined`
216
+ * is what made the plugin's failures indistinguishable from outside it.
217
+ */
218
+ export type RealtimeDelegationOutcome = 'answered' | 'declined' | 'refused' | 'timeout'
219
+
220
+ /**
221
+ * One delegated turn, settled, reported for diagnostics.
222
+ *
223
+ * `reason` is present only on `refused`, and is **redacted** before it is emitted: this is the first
224
+ * surface on which text the plugin did not author reaches a human, so it is safe by construction
225
+ * rather than by a later pass.
226
+ */
227
+ export interface RealtimeDelegationSettlement {
228
+ /** The delegation this settles. Correlates with the ask, the answer and the journal. */
229
+ readonly id: string
230
+ /** The session that raised it. */
231
+ readonly sessionId: string
232
+ /** How it ended. */
233
+ readonly outcome: RealtimeDelegationOutcome
234
+ /** The controller's own reason, redacted, when the outcome is `refused`. */
235
+ readonly reason?: string
236
+ }