humanish 0.23.0 → 0.25.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.
@@ -1,5 +1,5 @@
1
1
  // A VENDOR-NEUTRAL loopback catch server for email-send APIs (#297). An app hardwired to a hosted
2
- // email provider is redirected into the faux bus with ONE env var and no code change: point the app's
2
+ // email provider is redirected into the fake bus with ONE env var and no code change: point the app's
3
3
  // API base URL at this server. The catch does not depend on, or name itself after, any one vendor —
4
4
  // it normalizes each provider's distinct wire shape (Resend's flat body, SendGrid's nested
5
5
  // personalizations, Postmark's TitleCase, a custom app's own JSON) to one shape via pluggable
@@ -6,7 +6,7 @@ export declare function extractLinks(body: string): string[];
6
6
  * "verification code 8A3F2K") are high-precision and preferred; if none are labeled, fall back to an
7
7
  * isolated 4–8 digit run (a bare OTP). Pure; tokens are runtime-only literal-scrub targets. */
8
8
  export declare function extractOtpCodes(body: string): string[];
9
- export interface FauxInboxOptions {
9
+ export interface FakeInboxOptions {
10
10
  /** "email" (default) or "sms" — the address shape + surface differ; machinery is identical. */
11
11
  channel?: CommsChannelKind;
12
12
  /** Email domain for minted addresses. Default example.test (an RFC 6761 reserved, unroutable test
@@ -15,18 +15,26 @@ export interface FauxInboxOptions {
15
15
  /** Injected clock (ms) for deterministic tests. Default Date.now. */
16
16
  now?: () => number;
17
17
  }
18
- /** The in-process faux adapter. Implements the same CommsChannel port a real provider adapter would. */
19
- export declare class FauxInbox implements CommsChannel {
18
+ /** The in-process fake adapter. Implements the same CommsChannel port a real provider adapter would. */
19
+ export declare class FakeInbox implements CommsChannel {
20
20
  readonly channel: CommsChannelKind;
21
- readonly kind: "faux";
21
+ readonly kind: "fake";
22
22
  private readonly domain;
23
23
  private readonly clock;
24
24
  private readonly byActor;
25
25
  private readonly byValue;
26
26
  private readonly queues;
27
27
  private counter;
28
- constructor(options?: FauxInboxOptions);
28
+ constructor(options?: FakeInboxOptions);
29
29
  provision(actorId: string): Promise<CommsAddress>;
30
+ /**
31
+ * Provision an inbox for `actorId` at an EXPLICIT address (a lab-declared recipient), so the
32
+ * app-under-test's send to that literal address resolves in `deliverRaw` (which drops recipients
33
+ * with no provisioned inbox). Same value-collision guard as `provision`; if `actorId` already held
34
+ * a different auto-generated address, the declared address supersedes it (the lab's declaration
35
+ * wins). Idempotent: re-declaring the same address returns the existing inbox without clearing it.
36
+ */
37
+ provisionAddress(actorId: string, value: string): Promise<CommsAddress>;
30
38
  private route;
31
39
  send(message: OutboundMessage): Promise<CommsMessage>;
32
40
  deliverRaw(inbound: InboundRaw): Promise<CommsMessage[]>;
@@ -1,4 +1,7 @@
1
- // The FAUX in-process email/SMS bus (#297 Stage 2). Deterministic, $0, offline, public-safe: a
1
+ // The FAKE in-process email/SMS bus (#297 Stage 2). "Fake" in the precise test-double sense (Fowler):
2
+ // a working, in-memory implementation of the CommsChannel port that takes a production shortcut — the
3
+ // same slot as Kubernetes' `fake` clientset, distinct from a mock/stub. Deterministic, $0, offline,
4
+ // public-safe: a
2
5
  // message an app-under-test "sends" (via an ingress like the vendor-neutral email catch) is routed to the
3
6
  // addressed actor inbox and read back through the same CommsChannel port a real provider adapter
4
7
  // would implement. Nothing leaves the process. See comms-types.ts for the port + public-safety notes.
@@ -73,10 +76,10 @@ function smsAddressFor(actorId) {
73
76
  const digits = digestText(actorId, 16).replace(/[a-f]/g, (c) => String(c.charCodeAt(0) % 10)).slice(0, 7);
74
77
  return `+1555${digits}`;
75
78
  }
76
- /** The in-process faux adapter. Implements the same CommsChannel port a real provider adapter would. */
77
- export class FauxInbox {
79
+ /** The in-process fake adapter. Implements the same CommsChannel port a real provider adapter would. */
80
+ export class FakeInbox {
78
81
  channel;
79
- kind = "faux";
82
+ kind = "fake";
80
83
  domain;
81
84
  clock;
82
85
  byActor = new Map();
@@ -95,7 +98,7 @@ export class FauxInbox {
95
98
  const value = this.channel === "sms" ? smsAddressFor(actorId) : `${sanitizeLocalPart(actorId)}@${this.domain}`;
96
99
  // Address collision guard: two distinct actor ids can sanitize to the same local part. Reuse the
97
100
  // existing inbox rather than resetting its queue (which would drop already-delivered mail). Both
98
- // actors then share it — a faux-world edge; declare distinct addresses to avoid it.
101
+ // actors then share it — a fake-world edge; declare distinct addresses to avoid it.
99
102
  const prior = this.byValue.get(value.toLowerCase());
100
103
  if (prior) {
101
104
  this.byActor.set(actorId, prior);
@@ -107,6 +110,27 @@ export class FauxInbox {
107
110
  this.queues.set(value.toLowerCase(), []);
108
111
  return address;
109
112
  }
113
+ /**
114
+ * Provision an inbox for `actorId` at an EXPLICIT address (a lab-declared recipient), so the
115
+ * app-under-test's send to that literal address resolves in `deliverRaw` (which drops recipients
116
+ * with no provisioned inbox). Same value-collision guard as `provision`; if `actorId` already held
117
+ * a different auto-generated address, the declared address supersedes it (the lab's declaration
118
+ * wins). Idempotent: re-declaring the same address returns the existing inbox without clearing it.
119
+ */
120
+ async provisionAddress(actorId, value) {
121
+ const normalized = value.trim();
122
+ const key = normalized.toLowerCase();
123
+ const prior = this.byValue.get(key);
124
+ if (prior) {
125
+ this.byActor.set(actorId, prior);
126
+ return prior;
127
+ }
128
+ const address = { channel: this.channel, actorId, value: normalized, digest: digestText(normalized, 16) };
129
+ this.byActor.set(actorId, address);
130
+ this.byValue.set(key, address);
131
+ this.queues.set(key, []);
132
+ return address;
133
+ }
110
134
  route(from, to, subject, body) {
111
135
  const at = this.clock();
112
136
  const message = {
@@ -154,4 +178,4 @@ export class FauxInbox {
154
178
  return [...this.byActor.values()];
155
179
  }
156
180
  }
157
- //# sourceMappingURL=comms-faux-inbox.js.map
181
+ //# sourceMappingURL=comms-fake-inbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comms-fake-inbox.js","sourceRoot":"","sources":["../src/comms-fake-inbox.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import type { CommsChannel } from "./comms-types.js";
1
+ import type { CommsAddress, CommsChannel } from "./comms-types.js";
2
+ import { type CommsThreadArtifact } from "./comms-evidence.js";
2
3
  import { type EmailSendProfile } from "./comms-email-catch.js";
3
4
  import { type DetachedTimers } from "./e2b-detached.js";
4
5
  import type { E2BDesktopSandbox } from "./e2b-desktop-launch.js";
@@ -57,7 +58,37 @@ export declare function drainCommsCatch(desktop: E2BDesktopSandbox, deployed: Pi
57
58
  }>;
58
59
  /**
59
60
  * Parse drained raw sends with the host profiles and route them into the CommsChannel (the host-side
60
- * FauxInbox). Returns the number of inbox deliveries made. Same profiles as the host catch, so the
61
+ * FakeInbox). Returns the number of inbox deliveries made. Same profiles as the host catch, so the
61
62
  * in-sandbox and in-process routes normalize identically.
62
63
  */
63
64
  export declare function routeCapturedSends(sends: RawCapturedSend[], channel: CommsChannel, profiles?: EmailSendProfile[]): Promise<number>;
65
+ /** Outcome of a whole-run comms collect. `artifact` is present only when captured mail matched a
66
+ * provisioned inbox; `captured > 0 && artifact === undefined` means the app sent mail to an address
67
+ * no declared recipient covers (captured but unevidenced) — the caller should surface that, not drop
68
+ * it silently. */
69
+ export interface CommsThreadCollection {
70
+ artifact?: CommsThreadArtifact;
71
+ /** Raw sends the in-sandbox catch captured this run (all POSTed paths). */
72
+ captured: number;
73
+ /** Distinct messages that matched a provisioned recipient inbox (drives whether an artifact exists). */
74
+ matched: number;
75
+ }
76
+ /**
77
+ * End of a run's comms funnel: drain everything the in-sandbox catch captured, route it into the
78
+ * host `channel`, poll the provisioned `inboxes`, and build the digest-only thread artifact. The
79
+ * `artifact` is omitted when nothing was captured OR nothing matched a provisioned inbox (an empty
80
+ * file would be a false claim of a delivered thread) — but `captured`/`matched` are always reported so
81
+ * the caller can warn on captured-but-unevidenced mail rather than lose it silently. Composes the
82
+ * tested drain/route/build pieces so the CUA and shared-world routes collect evidence identically. The
83
+ * full NDJSON is drained from cursor 0 (a whole-run collect), so it is idempotent to call once at
84
+ * teardown.
85
+ */
86
+ export declare function collectCommsThread(args: {
87
+ desktop: E2BDesktopSandbox;
88
+ deployed: Pick<DeployedCommsCatch, "deliveriesPath">;
89
+ channel: CommsChannel;
90
+ /** The inboxes provisioned for this run (declared recipients). Only mail to these is evidenced. */
91
+ inboxes: CommsAddress[];
92
+ profiles?: EmailSendProfile[];
93
+ requestTimeoutMs?: number;
94
+ }): Promise<CommsThreadCollection>;
@@ -6,6 +6,7 @@
6
6
  // each poll `cat` its append-only NDJSON of captured sends back to the host, where the real profiles
7
7
  // parse them and route into the CommsChannel. A FIXED loopback port is chosen up front so the app's
8
8
  // injected base-URL env (`http://127.0.0.1:<port>`) is known before the sandbox is created.
9
+ import { buildCommsThreadArtifact } from "./comms-evidence.js";
9
10
  import { DEFAULT_EMAIL_PROFILES } from "./comms-email-catch.js";
10
11
  import { startDetachedProcess } from "./e2b-detached.js";
11
12
  /** The default in-sandbox loopback port for the catch. Fixed (not ephemeral) so the injected base-URL
@@ -126,7 +127,7 @@ export async function drainCommsCatch(desktop, deployed, cursor = 0, requestTime
126
127
  }
127
128
  /**
128
129
  * Parse drained raw sends with the host profiles and route them into the CommsChannel (the host-side
129
- * FauxInbox). Returns the number of inbox deliveries made. Same profiles as the host catch, so the
130
+ * FakeInbox). Returns the number of inbox deliveries made. Same profiles as the host catch, so the
130
131
  * in-sandbox and in-process routes normalize identically.
131
132
  */
132
133
  export async function routeCapturedSends(sends, channel, profiles = DEFAULT_EMAIL_PROFILES) {
@@ -154,4 +155,35 @@ export async function routeCapturedSends(sends, channel, profiles = DEFAULT_EMAI
154
155
  }
155
156
  return delivered;
156
157
  }
158
+ /**
159
+ * End of a run's comms funnel: drain everything the in-sandbox catch captured, route it into the
160
+ * host `channel`, poll the provisioned `inboxes`, and build the digest-only thread artifact. The
161
+ * `artifact` is omitted when nothing was captured OR nothing matched a provisioned inbox (an empty
162
+ * file would be a false claim of a delivered thread) — but `captured`/`matched` are always reported so
163
+ * the caller can warn on captured-but-unevidenced mail rather than lose it silently. Composes the
164
+ * tested drain/route/build pieces so the CUA and shared-world routes collect evidence identically. The
165
+ * full NDJSON is drained from cursor 0 (a whole-run collect), so it is idempotent to call once at
166
+ * teardown.
167
+ */
168
+ export async function collectCommsThread(args) {
169
+ const { sends } = await drainCommsCatch(args.desktop, args.deployed, 0, args.requestTimeoutMs);
170
+ if (sends.length === 0)
171
+ return { captured: 0, matched: 0 };
172
+ await routeCapturedSends(sends, args.channel, args.profiles);
173
+ const seen = new Set();
174
+ const messages = [];
175
+ for (const inbox of args.inboxes) {
176
+ for (const message of await args.channel.poll(inbox, 0)) {
177
+ // A single send addressed to several provisioned inboxes must appear once in the thread.
178
+ if (seen.has(message.id))
179
+ continue;
180
+ seen.add(message.id);
181
+ messages.push(message);
182
+ }
183
+ }
184
+ if (messages.length === 0)
185
+ return { captured: sends.length, matched: 0 };
186
+ messages.sort((a, b) => a.deliveredAt - b.deliveredAt || a.id.localeCompare(b.id));
187
+ return { artifact: buildCommsThreadArtifact(messages), captured: sends.length, matched: messages.length };
188
+ }
157
189
  //# sourceMappingURL=comms-sandbox-catch.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"comms-sandbox-catch.js","sourceRoot":"","sources":["../src/comms-sandbox-catch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"comms-sandbox-catch.js","sourceRoot":"","sources":["../src/comms-sandbox-catch.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,wGAAwG;AACxG,uGAAuG;AACvG,qGAAqG;AACrG,qGAAqG;AACrG,qGAAqG;AACrG,oGAAoG;AACpG,4FAA4F;AAG5F,OAAO,EAAE,wBAAwB,EAA4B,MAAM,qBAAqB,CAAC;AACzF,OAAO,EAAE,sBAAsB,EAAyB,MAAM,wBAAwB,CAAC;AACvF,OAAO,EAAE,oBAAoB,EAAuB,MAAM,mBAAmB,CAAC;AAG9E;;8EAE8E;AAC9E,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC;AAC/C,MAAM,iBAAiB,GAAG,qBAAqB,CAAC;AAEhD,iDAAiD;AACjD,SAAS,GAAG,CAAC,KAAa;IACxB,OAAO,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC;AAC/C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,2CAA2C;IAC3C,sDAAsD;IACtD,sCAAsC;IACtC,+CAA+C;IAC/C,6EAA6E;IAC7E,oEAAoE;IACpE,8BAA8B;IAC9B,8BAA8B;IAC9B,gDAAgD;IAChD,qNAAqN;IACrN,uJAAuJ;IACvJ,oCAAoC;IACpC,6GAA6G;IAC7G,yBAAyB;IACzB,0DAA0D;IAC1D,sGAAsG;IACtG,sGAAsG;IACtG,8FAA8F;IAC9F,oJAAoJ;IACpJ,2GAA2G;IAC3G,OAAO;IACP,+EAA+E;IAC/E,+BAA+B;CAChC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AA0Bb;0GAC0G;AAC1G,KAAK,UAAU,YAAY,CACzB,OAA0B,EAC1B,IAAY,EACZ,OAAyE;IAEzE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACzG,MAAM,QAAQ,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC;IAC3C,SAAS,CAAC;QACR,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ;aAClC,GAAG,CAAC,yCAAyC,IAAI,6BAA6B,EAAE,EAAE,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,EAAE,CAAC;aAC/H,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,sBAAsB,CAAC;YAAE,OAAO,IAAI,CAAC;QACxE,IAAI,GAAG,EAAE,IAAI,QAAQ;YAAE,OAAO,KAAK,CAAC;QACpC,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AASD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAA0B,EAC1B,UAAmC,EAAE;IAErC,kGAAkG;IAClG,sGAAsG;IACtG,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,0BAA0B,CAAC,CAAC,CAAC;IAC5E,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,GAAG,MAAM,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,kCAAkC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,iBAAiB,CAAC;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,aAAa,CAAC;IAC3C,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,MAAM,CAAC;IAC5D,MAAM,UAAU,GAAG,GAAG,GAAG,YAAY,CAAC;IACtC,MAAM,cAAc,GAAG,GAAG,GAAG,oBAAoB,CAAC;IAElD,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACzE,MAAM,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,oBAAoB,CAAC,CAAC;IAC5D,MAAM,oBAAoB,CAAC,OAAO,EAAE;QAClC,IAAI;QACJ,OAAO,EAAE,QAAQ,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,IAAI,GAAG,CAAC,cAAc,CAAC,EAAE;QACjE,gBAAgB;KACjB,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE,IAAI,EAAE;QAC9C,SAAS,EAAE,OAAO,CAAC,cAAc,IAAI,MAAM;QAC3C,gBAAgB;QAChB,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;KAC1B,CAAC,CAAC;IACH,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,oBAAoB,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;AAC9E,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAA0B,EAC1B,QAAoD,EACpD,MAAM,GAAG,CAAC,EACV,gBAAgB,GAAG,MAAM;IAEzB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,sBAAsB,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAC3H,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;IACnC,IAAI,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACxE,oGAAoG;IACpG,oGAAoG;IACpG,sGAAsG;IACtG,wEAAwE;IACxE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3E,MAAM,KAAK,GAAsB,EAAE,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;YAC3D,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,MAAM,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACvG,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAwB,EACxB,OAAqB,EACrB,WAA+B,sBAAsB;IAErD,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC;QACrG,IAAI,OAAO,KAAK,SAAS;YAAE,SAAS;QACpC,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;YAC1D,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC;gBACxC,IAAI,EAAE,UAAU,CAAC,IAAI;gBACrB,EAAE,EAAE,UAAU,CAAC,EAAE;gBACjB,GAAG,CAAC,UAAU,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC;gBAC5E,IAAI,EAAE,UAAU,CAAC,IAAI;aACtB,CAAC,CAAC;YACH,SAAS,IAAI,QAAQ,CAAC,MAAM,CAAC;QAC/B,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAcD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAQxC;IACC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC/F,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAC3D,MAAM,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,KAAK,MAAM,OAAO,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC;YACxD,yFAAyF;YACzF,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBAAE,SAAS;YACnC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YACrB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IACzE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACnF,OAAO,EAAE,QAAQ,EAAE,wBAAwB,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;AAC5G,CAAC"}
@@ -18,7 +18,7 @@ export interface CommsMessage {
18
18
  /** Resolved recipient inboxes this message was delivered to. */
19
19
  to: CommsAddress[];
20
20
  subject?: string;
21
- /** Runtime-only for real; local-only for faux (never a share path without redaction — #108). */
21
+ /** Runtime-only for real; local-only for fake (never a share path without redaction — #108). */
22
22
  body: string;
23
23
  /** Actionable links extracted from the body (magic-link / invite / reset). Runtime-only. */
24
24
  links: string[];
@@ -43,12 +43,12 @@ export interface InboundRaw {
43
43
  body: string;
44
44
  }
45
45
  /**
46
- * The port. Faux (in-process) + real-email + real-sms adapters implement it identically. Async so a
47
- * real (network) adapter fits without changing callers; the faux adapter just resolves immediately.
46
+ * The port. Fake (in-process) + real-email + real-sms adapters implement it identically. Async so a
47
+ * real (network) adapter fits without changing callers; the fake adapter just resolves immediately.
48
48
  */
49
49
  export interface CommsChannel {
50
50
  readonly channel: CommsChannelKind;
51
- readonly kind: "faux" | "real";
51
+ readonly kind: "fake" | "real";
52
52
  /** Mint/allocate an inbox for an actor (address auto-generated). Idempotent per actor. */
53
53
  provision(actorId: string): Promise<CommsAddress>;
54
54
  /** Route a composed message from one actor to addressed inboxes. Returns the delivered record. */
@@ -1,6 +1,6 @@
1
1
  // The addressed message bus (#297) — the seam that makes "off-app" comms (email/SMS the persona
2
2
  // actually lives in) a first-class, persona-driven testable surface. A single port, addressed by
3
- // actor; faux (in-process) and real (provider-backed) adapters implement it identically, so the
3
+ // actor; fake (in-process) and real (provider-backed) adapters implement it identically, so the
4
4
  // persona surface + evidence writer consume it without knowing which is behind it.
5
5
  //
6
6
  // PUBLIC-SAFETY: raw address values, message bodies, links, and codes are RUNTIME-ONLY. Only the
@@ -919,7 +919,7 @@ export async function runConcurrentSharedWorld(options) {
919
919
  //
920
920
  // TEMPORARY SHIM (tracked by #296): this CDP URL-relay handoff — reading the host's /lobby/CODE off
921
921
  // its own browser and threading it into the follower missions — is a temporary coordination shim.
922
- // It is to be augmented/replaced by the actor message bus (faux SMS/email invite) in #297: the
922
+ // It is to be augmented/replaced by the actor message bus (fake SMS/email invite) in #297: the
923
923
  // human-realistic version is the HOST SENDING the invite link and followers RECEIVING and tapping
924
924
  // it, rather than the orchestrator relaying the code out-of-band.
925
925
  const lobbyCodeLatch = deferred();
@@ -512,6 +512,10 @@ export interface LaneRunOutcome {
512
512
  harnessError: boolean;
513
513
  failureCode?: CuaActorLabErrorCode;
514
514
  entryKind?: "local-app";
515
+ /** Relative run-dir path of the digest-only comms-thread evidence artifact this lane wrote
516
+ * (humanish.comms-thread.v1), when a comms lab captured mail into its in-sandbox catch. Registered
517
+ * in the lane's stream artifacts. Absent when no comms lab ran or nothing was captured. */
518
+ commsArtifactPath?: string;
515
519
  }
516
520
  /** Build a lane's writeScreenshot closure: writes under screenshots/<screenshotDir>/ and records
517
521
  * the relative path the trace references (screenshots/<name> at N=1; screenshots/<laneId>/<name>
@@ -733,6 +737,9 @@ export declare function buildCuaBundle(args: {
733
737
  isMobile?: boolean;
734
738
  runId: string;
735
739
  screenshots: string[];
740
+ /** Relative run-dir path of the digest-only comms-thread evidence artifact (humanish.comms-thread.v1),
741
+ * when a comms lab captured mail; registered as a "log" stream artifact. */
742
+ commsArtifactPath?: string;
736
743
  /**
737
744
  * Capture-time screenshot policy ("blurred" when policies.redactScreenshots, else "raw").
738
745
  * When a session ran, its trace's `redaction.screenshots` is the evidence-of-record and
@@ -31,7 +31,8 @@ import { CHROMIUM_EVIDENCE_HYGIENE_FLAGS, chromiumEvidenceProfilePreferencesJson
31
31
  import { DEFAULT_OPENAI_CU_MODEL } from "./openai-responses-cu.js";
32
32
  import { createDesktopSandbox, loadE2BDesktopModule } from "./e2b-desktop-launch.js";
33
33
  import { probeUrl, readDetachedLog, runDetachedStep, startDetachedProcess } from "./e2b-detached.js";
34
- import { DEFAULT_SANDBOX_CATCH_PORT, deployCommsCatch } from "./comms-sandbox-catch.js";
34
+ import { DEFAULT_SANDBOX_CATCH_PORT, collectCommsThread, deployCommsCatch } from "./comms-sandbox-catch.js";
35
+ import { FakeInbox } from "./comms-fake-inbox.js";
35
36
  import { DEFAULT_DEVICE_PRESET, isDevicePresetName, resolveDevicePreset } from "./device-presets.js";
36
37
  import { cuaLaneValidationReason, isHttpUrl, isLoopbackUrl, MAX_CUA_LANES, subjectStateInvalidReason } from "./lab-config.js";
37
38
  import { mapWithConcurrency } from "./concurrency.js";
@@ -994,6 +995,10 @@ export async function runCuaLane(spec, deps) {
994
995
  // sandbox-create (below, so the app reads it at boot); the catch is started right after create.
995
996
  const commsEmail = (cloneRoute || localTreeRoute) ? config.comms?.email : undefined;
996
997
  const commsPort = commsEmail ? (commsEmail.port ?? DEFAULT_SANDBOX_CATCH_PORT) : undefined;
998
+ // Hoisted so the finally can drain the catch before teardown; `commsArtifactPath` is the written
999
+ // evidence path folded into the lane outcome.
1000
+ let deployedComms;
1001
+ let commsArtifactPath;
997
1002
  const commsEnv = commsEmail && commsPort !== undefined
998
1003
  ? { [commsEmail.injectEnv]: `http://127.0.0.1:${commsPort}` }
999
1004
  : {};
@@ -1082,7 +1087,7 @@ export async function runCuaLane(spec, deps) {
1082
1087
  // into its env at create) resolves the moment it boots. A comms-declared lab that can't stand the
1083
1088
  // catch up is a setup failure (fail closed) rather than silently sending real mail.
1084
1089
  if (commsEmail && commsPort !== undefined) {
1085
- const deployedComms = await deployCommsCatch(desktop, { port: commsPort, requestTimeoutMs: deps.requestTimeoutMs });
1090
+ deployedComms = await deployCommsCatch(desktop, { port: commsPort, requestTimeoutMs: deps.requestTimeoutMs });
1086
1091
  if (!deployedComms.ready) {
1087
1092
  throw new Error(`comms email catch did not become ready on 127.0.0.1:${commsPort} in the subject sandbox`);
1088
1093
  }
@@ -1284,6 +1289,42 @@ export async function runCuaLane(spec, deps) {
1284
1289
  : { warnings: [...(desktopGeometry.warnings ?? []), ...geometryWarnings] })
1285
1290
  };
1286
1291
  }
1292
+ // Off-app comms evidence (#297): before this lane's sandbox is torn down, drain everything the
1293
+ // in-sandbox catch captured, route it into a host fake inbox addressed to the declared
1294
+ // recipients, and write the digest-only thread artifact. Wrapped so a drain failure NEVER
1295
+ // breaks teardown — the sandbox must still be killed either way. Runs only for a ready catch.
1296
+ if (commsEmail && deployedComms?.ready) {
1297
+ try {
1298
+ const commsChannel = new FakeInbox();
1299
+ const commsInboxes = [];
1300
+ for (const recipient of commsEmail.recipients ?? []) {
1301
+ if (recipient.address !== undefined) {
1302
+ commsInboxes.push(await commsChannel.provisionAddress(recipient.lane, recipient.address));
1303
+ }
1304
+ }
1305
+ const collected = await collectCommsThread({
1306
+ desktop,
1307
+ deployed: deployedComms,
1308
+ channel: commsChannel,
1309
+ inboxes: commsInboxes,
1310
+ requestTimeoutMs: deps.requestTimeoutMs
1311
+ });
1312
+ if (collected.artifact) {
1313
+ const path = deps.laneCount === 1 ? "comms/thread.json" : `comms/${spec.streamId}.thread.json`;
1314
+ await writeContainedOutputFile(deps.artifactRoot, path, `${JSON.stringify(collected.artifact, null, 2)}\n`, "utf8");
1315
+ commsArtifactPath = path;
1316
+ }
1317
+ else if (collected.captured > 0) {
1318
+ // Captured mail that matched no declared recipient must not vanish silently (invariant 6:
1319
+ // honest signals): tell the operator to declare comms.email.recipients[].address to match
1320
+ // the address the app actually sends to (e.g. the one the persona surface will sign up with).
1321
+ warnings.push(`Comms catch captured ${collected.captured} email send(s) but none matched a declared recipient inbox — no comms evidence written. Declare comms.email.recipients[].address to match the address the app sends to.`);
1322
+ }
1323
+ }
1324
+ catch (error) {
1325
+ warnings.push(`Comms evidence collection failed (run continues; sandbox still torn down): ${redactText(deps.scrubKnownValues(toErrorMessage(error)))}`);
1326
+ }
1327
+ }
1287
1328
  const failed = sessionError !== undefined || session === undefined;
1288
1329
  // Each route's own keep flag gates its own lane only: a clone.keep can never leak into
1289
1330
  // a local-tree lane's teardown decision, and vice versa.
@@ -1363,7 +1404,8 @@ export async function runCuaLane(spec, deps) {
1363
1404
  noEngagement,
1364
1405
  selfReportedBlocker,
1365
1406
  harnessError,
1366
- ...(failureCode === undefined ? {} : { failureCode })
1407
+ ...(failureCode === undefined ? {} : { failureCode }),
1408
+ ...(commsArtifactPath === undefined ? {} : { commsArtifactPath })
1367
1409
  };
1368
1410
  }
1369
1411
  /** Run the single IN-PROCESS lane (a custom executor + provider; NO E2B). Always one lane. */
@@ -2252,6 +2294,7 @@ function buildSingleLaneBundle(args) {
2252
2294
  }),
2253
2295
  ...(args.localAppSubject || args.inProcessRoute ? { entryKind: "local-app" } : {}),
2254
2296
  ...(outcome?.session ? { traceArtifactPath: spec.traceArtifactPath } : {}),
2297
+ ...(outcome?.commsArtifactPath === undefined ? {} : { commsArtifactPath: outcome.commsArtifactPath }),
2255
2298
  ...(desktopSpanToMinutes(outcome?.desktopDurationMs) === undefined
2256
2299
  ? {}
2257
2300
  : { desktopMinutes: desktopSpanToMinutes(outcome?.desktopDurationMs) }),
@@ -2768,6 +2811,9 @@ export function buildCuaBundle(args) {
2768
2811
  ...(args.traceArtifactPath
2769
2812
  ? [{ label: "actor trace", path: args.traceArtifactPath, kind: "trace" }]
2770
2813
  : []),
2814
+ ...(args.commsArtifactPath
2815
+ ? [{ label: "comms thread", path: args.commsArtifactPath, kind: "log" }]
2816
+ : []),
2771
2817
  ...args.screenshots.map((screenshot, index) => ({
2772
2818
  label: `screenshot ${String(index + 1).padStart(2, "0")} (${screenshotMode})`,
2773
2819
  path: screenshot,
@@ -3115,6 +3161,9 @@ export function buildCuaFanoutBundle(args) {
3115
3161
  ...(session
3116
3162
  ? [{ label: `lane ${spec.laneId} actor trace`, path: spec.traceArtifactPath, kind: "trace" }]
3117
3163
  : []),
3164
+ ...(outcome?.commsArtifactPath
3165
+ ? [{ label: `lane ${spec.laneId} comms thread`, path: outcome.commsArtifactPath, kind: "log" }]
3166
+ : []),
3118
3167
  ...screenshots.map((screenshot, screenshotIndex) => ({
3119
3168
  label: `lane ${spec.laneId} screenshot ${String(screenshotIndex + 1).padStart(2, "0")} (${screenshotMode})`,
3120
3169
  path: screenshot,