instar 1.3.656 → 1.3.657
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/LiveTestHarness.d.ts +25 -0
- package/dist/core/LiveTestHarness.d.ts.map +1 -1
- package/dist/core/LiveTestHarness.js +40 -4
- package/dist/core/LiveTestHarness.js.map +1 -1
- package/dist/core/RealChannelDriver.d.ts +15 -0
- package/dist/core/RealChannelDriver.d.ts.map +1 -1
- package/dist/core/RealChannelDriver.js +16 -0
- package/dist/core/RealChannelDriver.js.map +1 -1
- package/dist/core/SlackLiveSender.d.ts +29 -0
- package/dist/core/SlackLiveSender.d.ts.map +1 -1
- package/dist/core/SlackLiveSender.js +68 -3
- package/dist/core/SlackLiveSender.js.map +1 -1
- package/dist/core/TelegramLiveSender.d.ts +18 -0
- package/dist/core/TelegramLiveSender.d.ts.map +1 -1
- package/dist/core/TelegramLiveSender.js +60 -0
- package/dist/core/TelegramLiveSender.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.657.md +32 -0
- package/upgrades/side-effects/real-channel-absence-collect.md +73 -0
|
@@ -54,6 +54,31 @@ export interface ChannelDriver {
|
|
|
54
54
|
afterMessageId?: string;
|
|
55
55
|
}): Promise<ReplyResult[]>;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Thrown by a driver whose `collectMessages` exists at the object level but cannot
|
|
59
|
+
* serve a SPECIFIC surface (e.g. RealChannelDriver implements collectMessages, but the
|
|
60
|
+
* sender wired for `dashboard` has no history to collect). The harness maps THIS error
|
|
61
|
+
* to BLOCKED (driver-unsupported) rather than FAIL — an absence assertion the driver
|
|
62
|
+
* genuinely cannot make on this surface is unverifiable, not a failure. Any OTHER throw
|
|
63
|
+
* remains a loud FAIL (a real driver error must not be silently downgraded).
|
|
64
|
+
*/
|
|
65
|
+
export declare class DriverCapabilityError extends Error {
|
|
66
|
+
constructor(message: string);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Thrown by a SENDER's `collectMessages` when it read the channel but CANNOT guarantee
|
|
70
|
+
* it saw every message in the window — a paginated/truncated history read (a full page
|
|
71
|
+
* came back, so more may exist) or a failed read. An absence proof over an incomplete
|
|
72
|
+
* read would silently false-PASS (the spurious nudge could be on the page we never
|
|
73
|
+
* fetched), so the harness maps THIS to BLOCKED, never PASS. Distinct from
|
|
74
|
+
* DriverCapabilityError (which is the capability LAYER saying a surface has no collector
|
|
75
|
+
* at all): senders raise THIS for an incomplete read; only RealChannelDriver raises
|
|
76
|
+
* DriverCapabilityError for a missing method. A real transport error stays a plain Error
|
|
77
|
+
* (→ FAIL).
|
|
78
|
+
*/
|
|
79
|
+
export declare class AbsenceUnverifiableError extends Error {
|
|
80
|
+
constructor(message: string);
|
|
81
|
+
}
|
|
57
82
|
export type Volatility = 'safe' | 'volatile' | 'permission';
|
|
58
83
|
export interface HarnessScenario {
|
|
59
84
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LiveTestHarness.d.ts","sourceRoot":"","sources":["../../src/core/LiveTestHarness.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"LiveTestHarness.d.ts","sourceRoot":"","sources":["../../src/core/LiveTestHarness.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,31 @@
|
|
|
19
19
|
* platform), not a fuzzy judgment. (An optional Tier-1 semantic supervisor for
|
|
20
20
|
* natural-language expectations is a follow-on; the core encodes exact checks.)
|
|
21
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* Thrown by a driver whose `collectMessages` exists at the object level but cannot
|
|
24
|
+
* serve a SPECIFIC surface (e.g. RealChannelDriver implements collectMessages, but the
|
|
25
|
+
* sender wired for `dashboard` has no history to collect). The harness maps THIS error
|
|
26
|
+
* to BLOCKED (driver-unsupported) rather than FAIL — an absence assertion the driver
|
|
27
|
+
* genuinely cannot make on this surface is unverifiable, not a failure. Any OTHER throw
|
|
28
|
+
* remains a loud FAIL (a real driver error must not be silently downgraded).
|
|
29
|
+
*/
|
|
30
|
+
export class DriverCapabilityError extends Error {
|
|
31
|
+
constructor(message) { super(message); this.name = 'DriverCapabilityError'; }
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Thrown by a SENDER's `collectMessages` when it read the channel but CANNOT guarantee
|
|
35
|
+
* it saw every message in the window — a paginated/truncated history read (a full page
|
|
36
|
+
* came back, so more may exist) or a failed read. An absence proof over an incomplete
|
|
37
|
+
* read would silently false-PASS (the spurious nudge could be on the page we never
|
|
38
|
+
* fetched), so the harness maps THIS to BLOCKED, never PASS. Distinct from
|
|
39
|
+
* DriverCapabilityError (which is the capability LAYER saying a surface has no collector
|
|
40
|
+
* at all): senders raise THIS for an incomplete read; only RealChannelDriver raises
|
|
41
|
+
* DriverCapabilityError for a missing method. A real transport error stays a plain Error
|
|
42
|
+
* (→ FAIL).
|
|
43
|
+
*/
|
|
44
|
+
export class AbsenceUnverifiableError extends Error {
|
|
45
|
+
constructor(message) { super(message); this.name = 'AbsenceUnverifiableError'; }
|
|
46
|
+
}
|
|
22
47
|
export class HarnessVolatileChannelError extends Error {
|
|
23
48
|
constructor(scenarioId, surface, channelId) {
|
|
24
49
|
super(`refusing volatile/permission scenario "${scenarioId}" on non-demo channel ${surface}:${channelId} (§5.3 — volatile scenarios run only on demo channels)`);
|
|
@@ -40,9 +65,13 @@ export class LiveTestHarness {
|
|
|
40
65
|
*/
|
|
41
66
|
async run(matrix, opts = {}) {
|
|
42
67
|
// §5.3 PRE-FLIGHT: refuse the whole run if a volatile/permission scenario points
|
|
43
|
-
// at a non-demo channel — before a single message is sent.
|
|
68
|
+
// at a non-demo channel — before a single message is sent. An ABSENCE scenario
|
|
69
|
+
// (absenceWindowMs != null) is ALSO demo-only regardless of its `safe` tag: it polls
|
|
70
|
+
// the channel's whole agent-outbound history into the harness/logs, so a `safe` tag
|
|
71
|
+
// must not let it read a live (operator) channel's content (security review FD-2).
|
|
44
72
|
for (const s of matrix.scenarios) {
|
|
45
|
-
|
|
73
|
+
const requiresDemo = s.volatility !== 'safe' || s.absenceWindowMs != null;
|
|
74
|
+
if (requiresDemo && !this.d.driver.isDemoChannel(s.surface, s.channelId)) {
|
|
46
75
|
throw new HarnessVolatileChannelError(s.id, s.surface, s.channelId);
|
|
47
76
|
}
|
|
48
77
|
}
|
|
@@ -92,8 +121,15 @@ export class LiveTestHarness {
|
|
|
92
121
|
return { ...base, verdict: 'PASS', evidence };
|
|
93
122
|
}
|
|
94
123
|
catch (err) {
|
|
95
|
-
|
|
96
|
-
|
|
124
|
+
if (err instanceof DriverCapabilityError || err instanceof AbsenceUnverifiableError) {
|
|
125
|
+
// Either the surface has no collector (DriverCapabilityError) or the read could
|
|
126
|
+
// not be proven complete (AbsenceUnverifiableError — truncated/paginated/failed
|
|
127
|
+
// history). Both make the absence assertion unverifiable → BLOCKED (named),
|
|
128
|
+
// never a silent PASS over an incomplete read and never a misleading FAIL.
|
|
129
|
+
return { ...base, verdict: 'BLOCKED', blockedKind: 'platform-error', blockedReason: `absence unverifiable on surface "${s.surface}": ${err.message}` };
|
|
130
|
+
}
|
|
131
|
+
// @silent-fallback-ok: not a silent fallback — a real driver error is SURFACED as
|
|
132
|
+
// a FAIL verdict (with the error in blockedReason) that the LiveTestGate consumes
|
|
97
133
|
// and VETOES on. The error is escalated, not swallowed; this is the loud path.
|
|
98
134
|
return { ...base, verdict: 'FAIL', blockedReason: `driver error: ${err instanceof Error ? err.message : String(err)}` };
|
|
99
135
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LiveTestHarness.js","sourceRoot":"","sources":["../../src/core/LiveTestHarness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;
|
|
1
|
+
{"version":3,"file":"LiveTestHarness.js","sourceRoot":"","sources":["../../src/core/LiveTestHarness.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,17 @@ export interface SurfaceSender {
|
|
|
32
32
|
timeoutMs: number;
|
|
33
33
|
afterMessageId?: string;
|
|
34
34
|
}): Promise<Omit<ReplyResult, 'responderMachineId'> | null>;
|
|
35
|
+
/**
|
|
36
|
+
* OPTIONAL — collect EVERY agent-authored message on the channel within `windowMs`
|
|
37
|
+
* after `afterMessageId` (the surface's OUTBOUND, the class a spurious background
|
|
38
|
+
* nudge belongs to). Backs the harness ABSENCE assertion over a REAL channel. A
|
|
39
|
+
* sender that omits this makes its surface unverifiable for absence (the driver
|
|
40
|
+
* raises DriverCapabilityError → harness BLOCKED, never a false PASS).
|
|
41
|
+
*/
|
|
42
|
+
collectMessages?(channelId: string, opts: {
|
|
43
|
+
windowMs: number;
|
|
44
|
+
afterMessageId?: string;
|
|
45
|
+
}): Promise<Array<Omit<ReplyResult, 'responderMachineId'>>>;
|
|
35
46
|
}
|
|
36
47
|
export interface RealChannelDriverDeps {
|
|
37
48
|
/** Per-surface real senders. A surface absent here throws if a scenario targets it. */
|
|
@@ -57,5 +68,9 @@ export declare class RealChannelDriver implements ChannelDriver {
|
|
|
57
68
|
timeoutMs: number;
|
|
58
69
|
afterMessageId?: string;
|
|
59
70
|
}): Promise<ReplyResult | null>;
|
|
71
|
+
collectMessages(surface: Surface, channelId: string, opts: {
|
|
72
|
+
windowMs: number;
|
|
73
|
+
afterMessageId?: string;
|
|
74
|
+
}): Promise<ReplyResult[]>;
|
|
60
75
|
}
|
|
61
76
|
//# sourceMappingURL=RealChannelDriver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RealChannelDriver.d.ts","sourceRoot":"","sources":["../../src/core/RealChannelDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;
|
|
1
|
+
{"version":3,"file":"RealChannelDriver.d.ts","sourceRoot":"","sources":["../../src/core/RealChannelDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,kGAAkG;AAClG,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3D,kHAAkH;IAClH,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,GAAG,IAAI,CAAC,CAAC;IAC7I;;;;;;OAMG;IACH,eAAe,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC;CACnJ;AAED,MAAM,WAAW,qBAAqB;IACpC,uFAAuF;IACvF,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC;IACjD,mCAAmC;IACnC,YAAY,EAAE,IAAI,CAAC,mBAAmB,EAAE,eAAe,CAAC,CAAC;IACzD;;;;OAIG;IACH,uBAAuB,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzF,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,qBAAa,iBAAkB,YAAW,aAAa;IACrD,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAwB;gBAC9B,IAAI,EAAE,qBAAqB;IAEvC,OAAO,CAAC,GAAG;IAEX,OAAO,CAAC,SAAS;IAUjB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAIrD,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAI5E,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAgBlI,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAexI"}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* failures degrade to `undefined` (the harness then simply can't assert on responder)
|
|
21
21
|
* rather than throwing the whole scenario — the SEND/REPLY evidence is still recorded.
|
|
22
22
|
*/
|
|
23
|
+
import { DriverCapabilityError } from './LiveTestHarness.js';
|
|
23
24
|
export class RealChannelDriver {
|
|
24
25
|
d;
|
|
25
26
|
constructor(deps) { this.d = deps; }
|
|
@@ -56,5 +57,20 @@ export class RealChannelDriver {
|
|
|
56
57
|
}
|
|
57
58
|
return { ...reply, responderMachineId };
|
|
58
59
|
}
|
|
60
|
+
async collectMessages(surface, channelId, opts) {
|
|
61
|
+
const sender = this.senderFor(surface);
|
|
62
|
+
if (!sender.collectMessages) {
|
|
63
|
+
// The surface is wired, but its sender can't collect history — the absence
|
|
64
|
+
// assertion is unverifiable HERE. Raise the typed capability error so the harness
|
|
65
|
+
// records BLOCKED (driver-unsupported), never a false PASS and never a FAIL that
|
|
66
|
+
// would wrongly read as "the agent sent a spurious message".
|
|
67
|
+
throw new DriverCapabilityError(`sender for surface "${surface}" does not implement collectMessages`);
|
|
68
|
+
}
|
|
69
|
+
const msgs = await sender.collectMessages(channelId, opts);
|
|
70
|
+
// The absence assertion checks message TEXT only; responder-machine attribution is
|
|
71
|
+
// unnecessary here (and would cost one placement read per message). Return the
|
|
72
|
+
// agent-authored messages as ReplyResult with responderMachineId left undefined.
|
|
73
|
+
return msgs.map(m => ({ ...m }));
|
|
74
|
+
}
|
|
59
75
|
}
|
|
60
76
|
//# sourceMappingURL=RealChannelDriver.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RealChannelDriver.js","sourceRoot":"","sources":["../../src/core/RealChannelDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;
|
|
1
|
+
{"version":3,"file":"RealChannelDriver.js","sourceRoot":"","sources":["../../src/core/RealChannelDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAmC7D,MAAM,OAAO,iBAAiB;IACX,CAAC,CAAwB;IAC1C,YAAY,IAA2B,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAEnD,GAAG,CAAC,CAAS,IAAU,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,yBAAyB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAEvE,SAAS,CAAC,OAAgB;QAChC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,8EAA8E;YAC9E,kEAAkE;YAClE,MAAM,IAAI,KAAK,CAAC,0CAA0C,OAAO,qBAAqB,CAAC,CAAC;QAC1F,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,aAAa,CAAC,OAAgB,EAAE,SAAiB;QAC/C,OAAO,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAgB,EAAE,SAAiB,EAAE,IAAY;QAC1D,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,OAAgB,EAAE,SAAiB,EAAE,IAAoD;QACxG,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACxE,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,kBAAsC,CAAC;QAC3C,IAAI,CAAC;YACH,kBAAkB,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,uBAAuB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,IAAI,SAAS,CAAC;QAC/F,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,gFAAgF;YAChF,2EAA2E;YAC3E,6DAA6D;YAC7D,IAAI,CAAC,GAAG,CAAC,wCAAwC,OAAO,IAAI,SAAS,kCAAkC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3J,kBAAkB,GAAG,SAAS,CAAC;QACjC,CAAC;QACD,OAAO,EAAE,GAAG,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,OAAgB,EAAE,SAAiB,EAAE,IAAmD;QAC5G,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;YAC5B,2EAA2E;YAC3E,kFAAkF;YAClF,iFAAiF;YACjF,6DAA6D;YAC7D,MAAM,IAAI,qBAAqB,CAAC,uBAAuB,OAAO,sCAAsC,CAAC,CAAC;QACxG,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAC3D,mFAAmF;QACnF,+EAA+E;QAC/E,iFAAiF;QACjF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACnC,CAAC;CACF"}
|
|
@@ -41,6 +41,14 @@ export interface SlackLiveSenderDeps {
|
|
|
41
41
|
api: SlackCaller;
|
|
42
42
|
/** The agent's (Echo's) Slack bot user id — awaitReply waits for a reply authored by THIS id. */
|
|
43
43
|
agentBotUserId: string;
|
|
44
|
+
/**
|
|
45
|
+
* OPTIONAL — the agent's Slack app `bot_id`. A message posted by the agent's BACKGROUND
|
|
46
|
+
* path (incoming-webhook / app post) can carry `bot_id` with NO `user` field; matching
|
|
47
|
+
* on `agentBotUserId` alone would then SKIP it → a spurious background nudge would be
|
|
48
|
+
* missed by the absence proof (false PASS). When set, a message authored by EITHER the
|
|
49
|
+
* agent's user id OR this bot_id counts as agent-outbound. (Lessons-review FD-3.)
|
|
50
|
+
*/
|
|
51
|
+
agentBotId?: string;
|
|
44
52
|
/** Poll cadence while awaiting a reply. Default 2000ms. */
|
|
45
53
|
pollIntervalMs?: number;
|
|
46
54
|
/** Injected for tests; defaults to real timers / clock. */
|
|
@@ -59,6 +67,27 @@ export declare class SlackLiveSender implements SurfaceSender {
|
|
|
59
67
|
timeoutMs: number;
|
|
60
68
|
afterMessageId?: string;
|
|
61
69
|
}): Promise<Omit<ReplyResult, 'responderMachineId'> | null>;
|
|
70
|
+
/** A message is agent-outbound if it is from the agent's user id OR its app bot_id. */
|
|
71
|
+
private isAgentAuthored;
|
|
72
|
+
/**
|
|
73
|
+
* Collect EVERY agent-authored message strictly after `after`, polling across the
|
|
74
|
+
* window so a nudge landing LATE (after a legitimate reply) is still seen. Returned
|
|
75
|
+
* oldest-first. Backs the absence assertion over a real Slack channel.
|
|
76
|
+
*
|
|
77
|
+
* Soundness for an ABSENCE proof (an under-collection here is a silent false-PASS):
|
|
78
|
+
* - **Failed read:** `ok === false` (auth revoked / not_in_channel) throws — never a
|
|
79
|
+
* vacuous PASS over a read that returned nothing because it FAILED.
|
|
80
|
+
* - **Truncation guard:** a `next_cursor` (more pages exist) or a full page
|
|
81
|
+
* (>= HISTORY_LIMIT) means the read may be incomplete → AbsenceUnverifiableError →
|
|
82
|
+
* harness BLOCKED, never a false PASS over an unread page.
|
|
83
|
+
* - **Anti-laundering:** ALL text VERSIONS per ts are kept (an edit can't launder a
|
|
84
|
+
* nudge out). **Identity:** user id OR app bot_id (a background nudge may have only
|
|
85
|
+
* bot_id). **Bounded window:** clamped to MAX_WINDOW_MS.
|
|
86
|
+
*/
|
|
87
|
+
collectMessages(channelId: string, opts: {
|
|
88
|
+
windowMs: number;
|
|
89
|
+
afterMessageId?: string;
|
|
90
|
+
}): Promise<Array<Omit<ReplyResult, 'responderMachineId'>>>;
|
|
62
91
|
/** Find the FIRST agent-authored message strictly after `after` (oldest-first). */
|
|
63
92
|
private findAgentReply;
|
|
64
93
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SlackLiveSender.d.ts","sourceRoot":"","sources":["../../src/core/SlackLiveSender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"SlackLiveSender.d.ts","sourceRoot":"","sources":["../../src/core/SlackLiveSender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAMpE,oFAAoF;AACpF,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAC9D,EAAE,CAAC,EAAE,OAAO,CAAC;QACb,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,QAAQ,CAAC,EAAE,KAAK,CAAC;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAClG,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;KACtB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,mBAAmB;IAClC,6FAA6F;IAC7F,GAAG,EAAE,WAAW,CAAC;IACjB,iGAAiG;IACjG,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,qBAAa,eAAgB,YAAW,aAAa;IACnD,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAsB;gBAC5B,IAAI,EAAE,mBAAmB;IAErC,OAAO,CAAC,GAAG;YACG,KAAK;IAGnB,OAAO,CAAC,GAAG;IAEL,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAU1D,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,GAAG,IAAI,CAAC;IAelJ,uFAAuF;IACvF,OAAO,CAAC,eAAe;IAMvB;;;;;;;;;;;;;;OAcG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAmCtJ,mFAAmF;YACrE,cAAc;CAkB7B"}
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
* the RealChannelDriver's injected placement reader; this sender only returns the
|
|
20
20
|
* reply text + id.
|
|
21
21
|
*/
|
|
22
|
+
import { AbsenceUnverifiableError } from './LiveTestHarness.js';
|
|
23
|
+
/** History page size + the absolute ceiling on an absence-collection window. */
|
|
24
|
+
const HISTORY_LIMIT = 100;
|
|
25
|
+
const MAX_WINDOW_MS = 300_000;
|
|
22
26
|
export class SlackLiveSender {
|
|
23
27
|
d;
|
|
24
28
|
constructor(deps) { this.d = deps; }
|
|
@@ -52,12 +56,73 @@ export class SlackLiveSender {
|
|
|
52
56
|
this.log(`no agent reply in ${channelId} within ${opts.timeoutMs}ms`);
|
|
53
57
|
return null;
|
|
54
58
|
}
|
|
59
|
+
/** A message is agent-outbound if it is from the agent's user id OR its app bot_id. */
|
|
60
|
+
isAgentAuthored(m) {
|
|
61
|
+
if (m.user === this.d.agentBotUserId)
|
|
62
|
+
return true;
|
|
63
|
+
if (this.d.agentBotId && m.bot_id === this.d.agentBotId)
|
|
64
|
+
return true;
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Collect EVERY agent-authored message strictly after `after`, polling across the
|
|
69
|
+
* window so a nudge landing LATE (after a legitimate reply) is still seen. Returned
|
|
70
|
+
* oldest-first. Backs the absence assertion over a real Slack channel.
|
|
71
|
+
*
|
|
72
|
+
* Soundness for an ABSENCE proof (an under-collection here is a silent false-PASS):
|
|
73
|
+
* - **Failed read:** `ok === false` (auth revoked / not_in_channel) throws — never a
|
|
74
|
+
* vacuous PASS over a read that returned nothing because it FAILED.
|
|
75
|
+
* - **Truncation guard:** a `next_cursor` (more pages exist) or a full page
|
|
76
|
+
* (>= HISTORY_LIMIT) means the read may be incomplete → AbsenceUnverifiableError →
|
|
77
|
+
* harness BLOCKED, never a false PASS over an unread page.
|
|
78
|
+
* - **Anti-laundering:** ALL text VERSIONS per ts are kept (an edit can't launder a
|
|
79
|
+
* nudge out). **Identity:** user id OR app bot_id (a background nudge may have only
|
|
80
|
+
* bot_id). **Bounded window:** clamped to MAX_WINDOW_MS.
|
|
81
|
+
*/
|
|
82
|
+
async collectMessages(channelId, opts) {
|
|
83
|
+
const after = opts.afterMessageId;
|
|
84
|
+
const deadline = this.now() + Math.min(Math.max(0, opts.windowMs), MAX_WINDOW_MS);
|
|
85
|
+
const pollMs = this.d.pollIntervalMs ?? 2000;
|
|
86
|
+
const seen = new Map(); // ts → every text version observed
|
|
87
|
+
for (let first = true; first || this.now() < deadline; first = false) {
|
|
88
|
+
const res = await this.d.api.call('conversations.history', {
|
|
89
|
+
channel: channelId,
|
|
90
|
+
...(after ? { oldest: after, inclusive: false } : {}),
|
|
91
|
+
limit: HISTORY_LIMIT,
|
|
92
|
+
});
|
|
93
|
+
if (res.ok === false) {
|
|
94
|
+
throw new AbsenceUnverifiableError(`conversations.history failed for ${channelId} (ok=false) — cannot prove absence over a failed read`);
|
|
95
|
+
}
|
|
96
|
+
const messages = res.messages ?? [];
|
|
97
|
+
const nextCursor = res.response_metadata?.next_cursor;
|
|
98
|
+
if ((nextCursor && nextCursor.length > 0) || messages.length >= HISTORY_LIMIT) {
|
|
99
|
+
throw new AbsenceUnverifiableError(`${channelId} history read is paginated/truncated (cursor or full ${HISTORY_LIMIT}-page) — cannot prove completeness`);
|
|
100
|
+
}
|
|
101
|
+
for (const m of messages) {
|
|
102
|
+
if (!m.ts)
|
|
103
|
+
continue; // skip a malformed entry
|
|
104
|
+
if (after && !(m.ts > after))
|
|
105
|
+
continue; // strictly after the prompt
|
|
106
|
+
if (!this.isAgentAuthored(m))
|
|
107
|
+
continue; // agent OUTBOUND only (user id OR bot_id)
|
|
108
|
+
const set = seen.get(m.ts) ?? new Set();
|
|
109
|
+
set.add(m.text ?? '');
|
|
110
|
+
seen.set(m.ts, set);
|
|
111
|
+
}
|
|
112
|
+
if (this.now() >= deadline)
|
|
113
|
+
break;
|
|
114
|
+
await this.sleep(pollMs);
|
|
115
|
+
}
|
|
116
|
+
return [...seen.entries()]
|
|
117
|
+
.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
|
|
118
|
+
.flatMap(([ts, texts]) => [...texts].map(text => ({ messageId: ts, text })));
|
|
119
|
+
}
|
|
55
120
|
/** Find the FIRST agent-authored message strictly after `after` (oldest-first). */
|
|
56
121
|
async findAgentReply(channelId, after) {
|
|
57
122
|
const res = await this.d.api.call('conversations.history', {
|
|
58
123
|
channel: channelId,
|
|
59
124
|
...(after ? { oldest: after, inclusive: false } : {}),
|
|
60
|
-
limit:
|
|
125
|
+
limit: HISTORY_LIMIT,
|
|
61
126
|
});
|
|
62
127
|
const messages = res.messages ?? [];
|
|
63
128
|
// conversations.history returns newest-first; scan oldest-first so we return the
|
|
@@ -66,8 +131,8 @@ export class SlackLiveSender {
|
|
|
66
131
|
for (const m of oldestFirst) {
|
|
67
132
|
if (after && !(m.ts > after))
|
|
68
133
|
continue; // strictly-after guard (belt + suspenders vs `oldest`)
|
|
69
|
-
if (
|
|
70
|
-
continue; // only the AGENT's reply
|
|
134
|
+
if (!this.isAgentAuthored(m))
|
|
135
|
+
continue; // only the AGENT's reply (user id OR bot_id)
|
|
71
136
|
const text = m.text ?? '';
|
|
72
137
|
return { text, messageId: m.ts };
|
|
73
138
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SlackLiveSender.js","sourceRoot":"","sources":["../../src/core/SlackLiveSender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;
|
|
1
|
+
{"version":3,"file":"SlackLiveSender.js","sourceRoot":"","sources":["../../src/core/SlackLiveSender.ts"],"names":[],"mappings":"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"}
|
|
@@ -48,6 +48,24 @@ export declare class TelegramLiveSender implements SurfaceSender {
|
|
|
48
48
|
timeoutMs: number;
|
|
49
49
|
afterMessageId?: string;
|
|
50
50
|
}): Promise<Omit<ReplyResult, 'responderMachineId'> | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Collect EVERY agent-authored message in the topic strictly after `afterMessageId`,
|
|
53
|
+
* polling across the window so a nudge that lands LATE (after a legitimate reply) is
|
|
54
|
+
* still seen. Returned oldest-first. Backs the absence assertion.
|
|
55
|
+
*
|
|
56
|
+
* Soundness for an ABSENCE proof (an under-collection here is a silent false-PASS):
|
|
57
|
+
* - **Anti-laundering:** ALL distinct text VERSIONS of a messageId are kept (not
|
|
58
|
+
* last-write-wins), so an edit that rewrites a spurious nudge to benign text cannot
|
|
59
|
+
* launder it out of the proof — every observed version is returned and matched.
|
|
60
|
+
* - **Truncation guard:** a poll returning a FULL page (>= HISTORY_LIMIT) means the
|
|
61
|
+
* read may be truncated (a nudge could be on an unread page) → AbsenceUnverifiableError
|
|
62
|
+
* → the harness records BLOCKED, never a false PASS over an incomplete read.
|
|
63
|
+
* - **Bounded window:** windowMs is clamped to MAX_WINDOW_MS (caps real-API poll count).
|
|
64
|
+
*/
|
|
65
|
+
collectMessages(channelId: string, opts: {
|
|
66
|
+
windowMs: number;
|
|
67
|
+
afterMessageId?: string;
|
|
68
|
+
}): Promise<Array<Omit<ReplyResult, 'responderMachineId'>>>;
|
|
51
69
|
private findAgentReply;
|
|
52
70
|
}
|
|
53
71
|
//# sourceMappingURL=TelegramLiveSender.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TelegramLiveSender.d.ts","sourceRoot":"","sources":["../../src/core/TelegramLiveSender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"TelegramLiveSender.d.ts","sourceRoot":"","sources":["../../src/core/TelegramLiveSender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAMpE,kFAAkF;AAClF,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,4FAA4F;IAC5F,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,uGAAuG;IACvG,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClF,4FAA4F;IAC5F,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,oBAAoB,EAAE,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IAC1G,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,qBAAa,kBAAmB,YAAW,aAAa;IACtD,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAyB;gBAC/B,IAAI,EAAE,sBAAsB;IAExC,OAAO,CAAC,GAAG;YACG,KAAK;IAGnB,OAAO,CAAC,GAAG;IAEX,OAAO,CAAC,QAAQ;IAMV,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAQ1D,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,GAAG,IAAI,CAAC;IAelJ;;;;;;;;;;;;;OAaG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC,CAAC;YAuCxI,cAAc;CAW7B"}
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
* sent. Null on timeout. The responder-MACHINE attribution (the cross-machine proof) is
|
|
16
16
|
* the RealChannelDriver's injected placement reader, not this sender.
|
|
17
17
|
*/
|
|
18
|
+
import { AbsenceUnverifiableError } from './LiveTestHarness.js';
|
|
19
|
+
/** History page size + the absolute ceiling on an absence-collection window. */
|
|
20
|
+
const HISTORY_LIMIT = 100;
|
|
21
|
+
const MAX_WINDOW_MS = 300_000;
|
|
18
22
|
export class TelegramLiveSender {
|
|
19
23
|
d;
|
|
20
24
|
constructor(deps) { this.d = deps; }
|
|
@@ -52,6 +56,62 @@ export class TelegramLiveSender {
|
|
|
52
56
|
this.log(`no agent reply in topic ${topic} within ${opts.timeoutMs}ms`);
|
|
53
57
|
return null;
|
|
54
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Collect EVERY agent-authored message in the topic strictly after `afterMessageId`,
|
|
61
|
+
* polling across the window so a nudge that lands LATE (after a legitimate reply) is
|
|
62
|
+
* still seen. Returned oldest-first. Backs the absence assertion.
|
|
63
|
+
*
|
|
64
|
+
* Soundness for an ABSENCE proof (an under-collection here is a silent false-PASS):
|
|
65
|
+
* - **Anti-laundering:** ALL distinct text VERSIONS of a messageId are kept (not
|
|
66
|
+
* last-write-wins), so an edit that rewrites a spurious nudge to benign text cannot
|
|
67
|
+
* launder it out of the proof — every observed version is returned and matched.
|
|
68
|
+
* - **Truncation guard:** a poll returning a FULL page (>= HISTORY_LIMIT) means the
|
|
69
|
+
* read may be truncated (a nudge could be on an unread page) → AbsenceUnverifiableError
|
|
70
|
+
* → the harness records BLOCKED, never a false PASS over an incomplete read.
|
|
71
|
+
* - **Bounded window:** windowMs is clamped to MAX_WINDOW_MS (caps real-API poll count).
|
|
72
|
+
*/
|
|
73
|
+
async collectMessages(channelId, opts) {
|
|
74
|
+
const topic = this.topicNum(channelId);
|
|
75
|
+
const afterId = opts.afterMessageId != null ? Number(opts.afterMessageId) : -Infinity;
|
|
76
|
+
const deadline = this.now() + Math.min(Math.max(0, opts.windowMs), MAX_WINDOW_MS);
|
|
77
|
+
const pollMs = this.d.pollIntervalMs ?? 2000;
|
|
78
|
+
const seen = new Map(); // messageId → every text version observed
|
|
79
|
+
for (let first = true; first || this.now() < deadline; first = false) {
|
|
80
|
+
const entries = await this.d.getHistory(topic, HISTORY_LIMIT);
|
|
81
|
+
// Telegram getTopicHistory returns the most-recent HISTORY_LIMIT entries of the
|
|
82
|
+
// topic's WHOLE lifetime (a tail, NOT bounded to the marker like Slack's `oldest`).
|
|
83
|
+
// So a full page only means the read is TRUNCATED when its OLDEST entry is still
|
|
84
|
+
// AFTER the marker — i.e. the marker scrolled off the page, so post-marker messages
|
|
85
|
+
// between the marker and the page's oldest may be unread. If the oldest in-page
|
|
86
|
+
// entry is <= the marker, the marker (or older) is in-page → every post-marker
|
|
87
|
+
// message is present → complete, even on a long-lived demo topic with >100 lifetime
|
|
88
|
+
// messages. (Round-2 review: a bare `length >= LIMIT` wrongly blocked any reused topic.)
|
|
89
|
+
if (entries.length >= HISTORY_LIMIT) {
|
|
90
|
+
const ids = entries.map(e => e.messageId).filter(n => Number.isFinite(n));
|
|
91
|
+
const oldestInPage = ids.length ? Math.min(...ids) : -Infinity;
|
|
92
|
+
if (oldestInPage > afterId) {
|
|
93
|
+
throw new AbsenceUnverifiableError(`topic ${topic} full ${HISTORY_LIMIT}-entry page is entirely after the marker — the marker scrolled off, post-marker messages may be unread (cannot prove completeness)`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const e of entries) {
|
|
97
|
+
if (!Number.isFinite(e.messageId))
|
|
98
|
+
continue; // skip a malformed entry, never a junk key
|
|
99
|
+
if (e.messageId <= afterId)
|
|
100
|
+
continue; // strictly after the prompt we sent
|
|
101
|
+
if (e.fromUser)
|
|
102
|
+
continue; // agent OUTBOUND only (the background-nudge class)
|
|
103
|
+
const set = seen.get(e.messageId) ?? new Set();
|
|
104
|
+
set.add(e.text ?? '');
|
|
105
|
+
seen.set(e.messageId, set);
|
|
106
|
+
}
|
|
107
|
+
if (this.now() >= deadline)
|
|
108
|
+
break;
|
|
109
|
+
await this.sleep(pollMs);
|
|
110
|
+
}
|
|
111
|
+
return [...seen.entries()]
|
|
112
|
+
.sort((a, b) => a[0] - b[0])
|
|
113
|
+
.flatMap(([messageId, texts]) => [...texts].map(text => ({ messageId: String(messageId), text })));
|
|
114
|
+
}
|
|
55
115
|
async findAgentReply(topicId, afterId) {
|
|
56
116
|
const entries = await this.d.getHistory(topicId, 100);
|
|
57
117
|
// Oldest-first by messageId so we return the EARLIEST agent reply after the prompt.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TelegramLiveSender.js","sourceRoot":"","sources":["../../src/core/TelegramLiveSender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;
|
|
1
|
+
{"version":3,"file":"TelegramLiveSender.js","sourceRoot":"","sources":["../../src/core/TelegramLiveSender.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-06-24T22:34:53.907Z",
|
|
5
|
+
"instarVersion": "1.3.657",
|
|
6
6
|
"entryCount": 202,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The live-test harness gained the ability to run its **absence proof** — "after this conversation, prove the agent sent NO unwanted background message (e.g. the false throttle nudge)" — over a **real** Telegram or Slack channel. Previously that assertion only worked against a simulated driver; the production channel driver had no way to collect a window of channel messages, so a real-channel absence scenario was reported as unverifiable.
|
|
9
|
+
|
|
10
|
+
A new `collectMessages` step (on the Telegram and Slack live senders, exposed through `RealChannelDriver`) polls the channel's history over a window and gathers every agent-authored message, so the harness can prove none of them matches a forbidden pattern. Because the only dangerous failure of an absence proof is a silent **false PASS**, every way the read could be incomplete is forced to BLOCK ("can't verify") instead of passing:
|
|
11
|
+
|
|
12
|
+
- a **paginated/truncated** read (more messages than one page) → BLOCKED;
|
|
13
|
+
- an **edited** message can't launder its earlier text away (every version is kept);
|
|
14
|
+
- a **Slack background post** under a `bot_id` (not a user id) is now matched;
|
|
15
|
+
- a **failed** history read → BLOCKED;
|
|
16
|
+
- a reused, long-lived demo channel no longer trips a false "too busy" alarm (the Telegram check is bounded to where the test message sits in history).
|
|
17
|
+
|
|
18
|
+
## What to Tell Your User
|
|
19
|
+
|
|
20
|
+
Nothing changes in day-to-day use — this is testing infrastructure that runs before code ships, not something on the live conversation path. The benefit is indirect: the "no surprise background message" guarantee can now be proven over a real channel, and it refuses to give a green light it can't actually justify — one more way a feature side-effect is caught before it reaches the fleet.
|
|
21
|
+
|
|
22
|
+
## Summary of New Capabilities
|
|
23
|
+
|
|
24
|
+
- `RealChannelDriver.collectMessages` + `SurfaceSender.collectMessages` on the Telegram and Slack live senders — collect a window of agent-authored channel messages for the harness absence assertion.
|
|
25
|
+
- A second typed harness signal, `AbsenceUnverifiableError`: a sender raises it for an incomplete/failed read so the harness records BLOCKED (never a false PASS); `DriverCapabilityError` stays the capability-layer "no collector on this surface" signal; a plain error stays a FAIL.
|
|
26
|
+
- Absence scenarios are demo-channel-only (a whole-history read never touches a live operator channel).
|
|
27
|
+
|
|
28
|
+
## Evidence
|
|
29
|
+
|
|
30
|
+
- **Reproduction (the gap):** before this change, an absence scenario pointed at a real Telegram/Slack channel returned `BLOCKED — driver does not support collectMessages`; the proof could only be exercised against the fake driver in `rate-limit-false-positive-prevention.test.ts`.
|
|
31
|
+
- **Before → after (the false-PASS holes, found in review):** a naive `collectMessages` that returned a single 100-entry page, kept last-write-wins text, matched Slack by user id only, and ignored failed reads would PASS an absence scenario even when a spurious message existed (on a paginated read, an edited message, a `bot_id` post, or a failed read). After: each of those four paths now produces BLOCKED, proven by dedicated tests (`Telegram: a FULL page … → AbsenceUnverifiableError`, `Telegram: … marker IN-PAGE does NOT block`, `Slack: ok:false → AbsenceUnverifiableError`, `Slack: next_cursor → AbsenceUnverifiableError`, `Slack: bot_id … IS collected`, `harness: a SAFE absence scenario on a NON-demo channel is refused`).
|
|
32
|
+
- **Verified:** 22 unit tests across the collect path (both sides of every guard boundary), no regression across the existing harness/sender/gate suites, clean typecheck. Multi-angle spec review (6 internal lenses + 2 non-Claude models, 3 rounds) converged with zero material findings.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Side-Effects Review — Real-channel collectMessages (absence proof over a live channel)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `real-channel-absence-collect`
|
|
4
|
+
**Date:** `2026-06-24`
|
|
5
|
+
**Author:** Echo (autonomous, 8-hour run)
|
|
6
|
+
**Spec:** `docs/specs/real-channel-absence-collect.md` (review-convergence + approved)
|
|
7
|
+
**Second-pass reviewer:** REQUIRED (touches the LiveTestHarness verdict path) — verdict appended below.
|
|
8
|
+
|
|
9
|
+
## Summary of the change
|
|
10
|
+
|
|
11
|
+
The `LiveTestHarness` absence assertion (PR #1262) could only run against a fake driver,
|
|
12
|
+
because the production `RealChannelDriver` had no `collectMessages`. This adds it, so the
|
|
13
|
+
"no spurious background message" proof runs over a REAL Telegram/Slack channel. The whole
|
|
14
|
+
risk of an absence proof is a silent **false PASS** (reporting "no spurious message" over
|
|
15
|
+
an incomplete read), so every under-collection path is forced to BLOCK, never PASS.
|
|
16
|
+
|
|
17
|
+
Files modified:
|
|
18
|
+
- `src/core/LiveTestHarness.ts` — new `AbsenceUnverifiableError` (a sender's
|
|
19
|
+
read-incompleteness signal) + the absence-path catch now maps BOTH it and
|
|
20
|
+
`DriverCapabilityError` to **BLOCKED**; a plain `Error` stays a FAIL. The §5.3
|
|
21
|
+
pre-flight now treats any `absenceWindowMs != null` scenario as demo-only regardless of
|
|
22
|
+
its `safe` tag (a whole-history read must never touch a live channel).
|
|
23
|
+
- `src/core/RealChannelDriver.ts` — new `collectMessages(surface, channelId, opts)`
|
|
24
|
+
delegating to the surface sender; raises `DriverCapabilityError` for a surface whose
|
|
25
|
+
sender has no collector; new optional `SurfaceSender.collectMessages`.
|
|
26
|
+
- `src/core/TelegramLiveSender.ts` — `collectMessages`: polls `getHistory` across the
|
|
27
|
+
window, keeps ALL text versions per messageId (anti edit-laundering), skips non-finite
|
|
28
|
+
ids, clamps the window to 300s, and BLOCKS (AbsenceUnverifiableError) on a
|
|
29
|
+
marker-bounded full-page truncation (full page whose oldest entry is still after the
|
|
30
|
+
marker — so a reused demo topic is not wrongly blocked).
|
|
31
|
+
- `src/core/SlackLiveSender.ts` — `collectMessages` (mirror): `oldest`-bounded read,
|
|
32
|
+
BLOCKS on `ok:false` (failed read) or `next_cursor`/full-page (truncation); new
|
|
33
|
+
`isAgentAuthored` helper matches `user` OR an injected `agentBotId` (a background nudge
|
|
34
|
+
may carry only `bot_id`) — applied to `collectMessages` AND `awaitReply` for parity;
|
|
35
|
+
optional `agentBotId` dep.
|
|
36
|
+
|
|
37
|
+
Files added:
|
|
38
|
+
- `tests/unit/realchannel-collect-messages.test.ts` — 22 unit tests: collect semantics,
|
|
39
|
+
late-nudge polling, anti-laundering, truncation→BLOCK (marker-bounded), reused-topic
|
|
40
|
+
no-mis-fire, Slack bot_id / ok:false / next_cursor, §5.3 absence-demo guard, and the
|
|
41
|
+
end-to-end harness PASS/FAIL/BLOCKED over a RealChannelDriver.
|
|
42
|
+
- `docs/specs/real-channel-absence-collect.md` (+ `.eli16.md`, convergence report).
|
|
43
|
+
|
|
44
|
+
## Blast radius
|
|
45
|
+
|
|
46
|
+
- **Production runtime:** none. `collectMessages` is reachable ONLY via
|
|
47
|
+
`LiveTestHarness.run` → `RealChannelDriver`, constructed per-request in the live-test
|
|
48
|
+
route and the `instar dev` runner. No sentinel, gate, scheduler, or request path calls
|
|
49
|
+
it (verified by the scalability + integration reviewers).
|
|
50
|
+
- **Existing callers:** unchanged. `RealChannelDriver`'s constructor signature is
|
|
51
|
+
untouched; `collectMessages` is a new instance method. The harness's `!driver.collectMessages`
|
|
52
|
+
BLOCKED path still fires for FAKE drivers (RealChannelDriver now routes per-surface
|
|
53
|
+
"unsupported" through the typed error instead). `awaitReply`'s new `bot_id` matching is
|
|
54
|
+
additive — unset `agentBotId` → byte-identical prior behavior (existing tests green).
|
|
55
|
+
- **Multi-machine:** machine-local-by-design — a live channel read happens on the serving
|
|
56
|
+
machine against that channel's live history; no replicated/proxied state.
|
|
57
|
+
|
|
58
|
+
## Rollback
|
|
59
|
+
|
|
60
|
+
Fully additive and revertible: drop the three `collectMessages` method bodies, the
|
|
61
|
+
`AbsenceUnverifiableError` class + its catch clause, the §5.3 `absenceWindowMs` predicate,
|
|
62
|
+
and the `agentBotId` dep. No config defaults, hooks, CLAUDE.md template, migration, or
|
|
63
|
+
dashboard surface touched, so no Migration Parity obligation. Reverting the commit is
|
|
64
|
+
sufficient; nothing ships dark or irreversible.
|
|
65
|
+
|
|
66
|
+
## Second-pass reviewer verdict
|
|
67
|
+
|
|
68
|
+
Multi-angle spec-converge (6 internal lenses + codex-cli:gpt-5.5 + gemini-2.5-pro across
|
|
69
|
+
3 rounds) converged with zero material findings in the final round. Round 1 surfaced 5
|
|
70
|
+
material false-PASS holes (truncation, edit-laundering, Slack `bot_id`, Slack `ok:false`,
|
|
71
|
+
§5.3 safe-bypass) — all fixed + tested. Round 2 surfaced 1 material (Telegram full-page
|
|
72
|
+
guard mis-firing on a reused demo topic) — fixed marker-bounded + tested. Round 3
|
|
73
|
+
verified resolution. Standards-Conformance Gate: 0 flags all rounds. Verdict: APPROVED.
|