instar 1.3.594 → 1.3.595

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.
@@ -0,0 +1,73 @@
1
+ /**
2
+ * DemoChannelRegistry — the §5.3 isolation backbone of the live-user-channel-proof
3
+ * standard (docs/specs/live-user-channel-proof-standard.md §5.3 "Demo Channel
4
+ * Isolation").
5
+ *
6
+ * A volatile / permission / dangerous live-test scenario must NEVER touch the live
7
+ * operator channel — it runs only on a registered DEMO channel (a throwaway Slack
8
+ * workspace, a demo Telegram group). The LiveTestHarness enforces this structurally
9
+ * by calling `isDemoChannel(surface, channelId)` and refusing the whole run (a throw,
10
+ * not a convention) if a non-safe scenario points at a channel this registry does not
11
+ * vouch for.
12
+ *
13
+ * The bindings are SIGNED and read once. A binding set that fails signature
14
+ * verification (tampered, unsigned, wrong signer) is FAIL-CLOSED: every channel
15
+ * resolves to NOT-a-demo-channel, so the harness's §5.3 guard refuses every volatile
16
+ * scenario rather than trusting an unverified "this is a safe demo channel" claim.
17
+ * Fail-closed is the safe direction here — the cost of a false "not demo" is a refused
18
+ * test; the cost of a false "is demo" is a destructive scenario on the live channel.
19
+ *
20
+ * This module is pure/injectable: it takes the raw bindings document + a `verify`
21
+ * function (Ed25519, same signer surface the LiveTestArtifactStore uses) so it is
22
+ * unit-testable with no filesystem or crypto host.
23
+ */
24
+ import type { Surface } from './LiveTestArtifactStore.js';
25
+ /** One registered demo channel. `workspaceId` is Slack-only (the demo workspace). */
26
+ export interface DemoChannelBinding {
27
+ surface: Surface;
28
+ /** Slack channel id, Telegram chat/topic id (as a string), or dashboard id. */
29
+ channelId: string;
30
+ /** Slack workspace / team id — REQUIRED for slack, ignored otherwise. */
31
+ workspaceId?: string;
32
+ /** Human label for audit (e.g. "SageMind Live Test"). */
33
+ label?: string;
34
+ }
35
+ /** The on-disk, signed bindings document. */
36
+ export interface DemoChannelBindingsDoc {
37
+ version: 1;
38
+ /** The machine that authored these bindings (audit only). */
39
+ machineId: string;
40
+ bindings: DemoChannelBinding[];
41
+ signedAt: string;
42
+ /** Ed25519 signature over the canonical payload (see canonicalBindingsPayload). */
43
+ signature: string;
44
+ }
45
+ export interface DemoChannelRegistryDeps {
46
+ /** The raw bindings document (e.g. parsed from state/demo-channel-bindings.json), or null when none exist. */
47
+ doc: DemoChannelBindingsDoc | null;
48
+ /** Verify a signature over a canonical payload string. Returns true iff valid. */
49
+ verify: (payload: string, signature: string) => boolean;
50
+ logger?: (m: string) => void;
51
+ }
52
+ /**
53
+ * The exact bytes the signature covers. Stable, order-independent across the binding
54
+ * LIST (sorted) so a re-serialization can't change the signed payload, but the
55
+ * signature is otherwise over the full meaningful content (version + machineId +
56
+ * signedAt + every binding field). Any field change invalidates the signature.
57
+ */
58
+ export declare function canonicalBindingsPayload(doc: Omit<DemoChannelBindingsDoc, 'signature'>): string;
59
+ export declare class DemoChannelRegistry {
60
+ private readonly verified;
61
+ private readonly set;
62
+ private readonly d;
63
+ constructor(deps: DemoChannelRegistryDeps);
64
+ private key;
65
+ private log;
66
+ /** True iff (surface, channelId) is a verified, registered demo channel. */
67
+ isDemoChannel(surface: Surface, channelId: string): boolean;
68
+ /** Whether a present bindings doc verified (false when none exist OR signature failed). */
69
+ get isVerified(): boolean;
70
+ /** Count of registered demo channels (0 when unverified/absent). */
71
+ get size(): number;
72
+ }
73
+ //# sourceMappingURL=DemoChannelRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DemoChannelRegistry.d.ts","sourceRoot":"","sources":["../../src/core/DemoChannelRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAE1D,qFAAqF;AACrF,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,6CAA6C;AAC7C,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,CAAC,CAAC;IACX,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,8GAA8G;IAC9G,GAAG,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACnC,kFAAkF;IAClF,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IACxD,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,IAAI,CAAC,sBAAsB,EAAE,WAAW,CAAC,GAAG,MAAM,CAkB/F;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAA0B;gBAEhC,IAAI,EAAE,uBAAuB;IA6BzC,OAAO,CAAC,GAAG;IAMX,OAAO,CAAC,GAAG;IAEX,4EAA4E;IAC5E,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAI3D,2FAA2F;IAC3F,IAAI,UAAU,IAAI,OAAO,CAA0B;IAEnD,oEAAoE;IACpE,IAAI,IAAI,IAAI,MAAM,CAA0B;CAC7C"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * DemoChannelRegistry — the §5.3 isolation backbone of the live-user-channel-proof
3
+ * standard (docs/specs/live-user-channel-proof-standard.md §5.3 "Demo Channel
4
+ * Isolation").
5
+ *
6
+ * A volatile / permission / dangerous live-test scenario must NEVER touch the live
7
+ * operator channel — it runs only on a registered DEMO channel (a throwaway Slack
8
+ * workspace, a demo Telegram group). The LiveTestHarness enforces this structurally
9
+ * by calling `isDemoChannel(surface, channelId)` and refusing the whole run (a throw,
10
+ * not a convention) if a non-safe scenario points at a channel this registry does not
11
+ * vouch for.
12
+ *
13
+ * The bindings are SIGNED and read once. A binding set that fails signature
14
+ * verification (tampered, unsigned, wrong signer) is FAIL-CLOSED: every channel
15
+ * resolves to NOT-a-demo-channel, so the harness's §5.3 guard refuses every volatile
16
+ * scenario rather than trusting an unverified "this is a safe demo channel" claim.
17
+ * Fail-closed is the safe direction here — the cost of a false "not demo" is a refused
18
+ * test; the cost of a false "is demo" is a destructive scenario on the live channel.
19
+ *
20
+ * This module is pure/injectable: it takes the raw bindings document + a `verify`
21
+ * function (Ed25519, same signer surface the LiveTestArtifactStore uses) so it is
22
+ * unit-testable with no filesystem or crypto host.
23
+ */
24
+ /**
25
+ * The exact bytes the signature covers. Stable, order-independent across the binding
26
+ * LIST (sorted) so a re-serialization can't change the signed payload, but the
27
+ * signature is otherwise over the full meaningful content (version + machineId +
28
+ * signedAt + every binding field). Any field change invalidates the signature.
29
+ */
30
+ export function canonicalBindingsPayload(doc) {
31
+ // Each binding is encoded as an ORDERED JSON tuple, NOT a delimiter-free
32
+ // concatenation. A concatenation like `${surface}${channelId}${workspaceId}` is
33
+ // ambiguous at field boundaries ({channelId:'C1',workspaceId:'W2'} vs
34
+ // {channelId:'C1W2'} would serialize identically and share one signature — a
35
+ // real bypass that promotes an unvouched channelId to a demo channel). A JSON
36
+ // tuple makes every field boundary explicit and distinguishes an absent field
37
+ // (null) from a present empty string (''), so one signature covers exactly one
38
+ // binding set.
39
+ const norm = (b) => JSON.stringify([b.surface, b.channelId, b.workspaceId ?? null, b.label ?? null]);
40
+ const lines = [...doc.bindings].map(norm).sort();
41
+ return [
42
+ `v=${doc.version}`,
43
+ `machine=${doc.machineId}`,
44
+ `signedAt=${doc.signedAt}`,
45
+ ...lines.map((l) => `b=${l}`),
46
+ ].join('\n');
47
+ }
48
+ export class DemoChannelRegistry {
49
+ verified;
50
+ set;
51
+ d;
52
+ constructor(deps) {
53
+ this.d = deps;
54
+ const { doc } = deps;
55
+ if (!doc) {
56
+ // No bindings at all is a legitimate state (safe-only test runs). It is NOT an
57
+ // error — it simply means there are zero demo channels, so isDemoChannel is
58
+ // always false and only safe scenarios can run.
59
+ this.verified = false;
60
+ this.set = new Set();
61
+ return;
62
+ }
63
+ const { signature, ...unsigned } = doc;
64
+ let ok = false;
65
+ try {
66
+ ok = this.d.verify(canonicalBindingsPayload(unsigned), signature);
67
+ }
68
+ catch (err) {
69
+ this.log(`verify threw (treating as unverified): ${err instanceof Error ? err.message : String(err)}`);
70
+ ok = false;
71
+ }
72
+ this.verified = ok;
73
+ if (!ok) {
74
+ // Fail-closed: a present-but-unverifiable bindings doc grants ZERO demo channels.
75
+ this.log(`bindings present but signature INVALID — fail-closed (0 demo channels; volatile scenarios will be refused)`);
76
+ this.set = new Set();
77
+ return;
78
+ }
79
+ this.set = new Set(doc.bindings.map((b) => this.key(b.surface, b.channelId)));
80
+ }
81
+ key(surface, channelId) {
82
+ // Unambiguous (JSON tuple) for the same reason canonicalBindingsPayload uses one —
83
+ // so a lookup key can never alias across the surface/channelId boundary. (Safe
84
+ // today since surface is a closed enum, but encoded explicitly for defense-in-depth.)
85
+ return JSON.stringify([surface, channelId]);
86
+ }
87
+ log(m) { this.d.logger?.(`[demo-channel-registry] ${m}`); }
88
+ /** True iff (surface, channelId) is a verified, registered demo channel. */
89
+ isDemoChannel(surface, channelId) {
90
+ return this.set.has(this.key(surface, channelId));
91
+ }
92
+ /** Whether a present bindings doc verified (false when none exist OR signature failed). */
93
+ get isVerified() { return this.verified; }
94
+ /** Count of registered demo channels (0 when unverified/absent). */
95
+ get size() { return this.set.size; }
96
+ }
97
+ //# sourceMappingURL=DemoChannelRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DemoChannelRegistry.js","sourceRoot":"","sources":["../../src/core/DemoChannelRegistry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * RealChannelDriver — the production `ChannelDriver` for the LiveTestHarness
3
+ * (docs/specs/live-user-channel-proof-standard.md §5.4 "Platform-Sanctioned
4
+ * Automation"). It drives a feature end-to-end through the REAL user surfaces by
5
+ * composing one `SurfaceSender` per surface (Telegram, Slack, dashboard) plus:
6
+ *
7
+ * - a DemoChannelRegistry → `isDemoChannel` (§5.3 isolation), and
8
+ * - a `resolveResponderMachine` reader → the `responderMachineId` stamped on every
9
+ * reply, which is the DETERMINISTIC cross-machine proof the harness asserts on
10
+ * (e.g. "after the transfer, the reply was served FROM the Mini").
11
+ *
12
+ * The driver itself is pure transport composition: it knows nothing about HTTP,
13
+ * Telegram, or Slack — each surface sender + the placement reader are injected, so
14
+ * this module is fully unit-testable with fakes. The real senders (a demo-bot/user
15
+ * Telegram sender, a Slack user-token sender) and the real placement reader (a
16
+ * GET /pool/placement call) are wired at construction in server.ts.
17
+ *
18
+ * Safety: a surface with no registered sender is a hard error on use (never a silent
19
+ * skip that would fabricate a "no reply" FAIL), and `responderMachineId` resolution
20
+ * failures degrade to `undefined` (the harness then simply can't assert on responder)
21
+ * rather than throwing the whole scenario — the SEND/REPLY evidence is still recorded.
22
+ */
23
+ import type { ChannelDriver, SendResult, ReplyResult } from './LiveTestHarness.js';
24
+ import type { Surface } from './LiveTestArtifactStore.js';
25
+ import type { DemoChannelRegistry } from './DemoChannelRegistry.js';
26
+ /** One real surface transport. `channelId` is the surface-native id (topic id, Slack channel). */
27
+ export interface SurfaceSender {
28
+ /** Send a USER-role message on the real surface. */
29
+ send(channelId: string, text: string): Promise<SendResult>;
30
+ /** Await the agent's reply after `afterMessageId` (null on timeout). No responder id — the driver stamps that. */
31
+ awaitReply(channelId: string, opts: {
32
+ timeoutMs: number;
33
+ afterMessageId?: string;
34
+ }): Promise<Omit<ReplyResult, 'responderMachineId'> | null>;
35
+ }
36
+ export interface RealChannelDriverDeps {
37
+ /** Per-surface real senders. A surface absent here throws if a scenario targets it. */
38
+ senders: Partial<Record<Surface, SurfaceSender>>;
39
+ /** §5.3 demo-channel isolation. */
40
+ demoRegistry: Pick<DemoChannelRegistry, 'isDemoChannel'>;
41
+ /**
42
+ * Resolve WHICH machine served (owns) the given channel at reply time — the
43
+ * cross-machine proof. Returns the machine id (or nickname-resolvable id), or null
44
+ * if it can't be determined. MUST NOT throw on a transient read error (return null).
45
+ */
46
+ resolveResponderMachine: (surface: Surface, channelId: string) => Promise<string | null>;
47
+ logger?: (m: string) => void;
48
+ }
49
+ export declare class RealChannelDriver implements ChannelDriver {
50
+ private readonly d;
51
+ constructor(deps: RealChannelDriverDeps);
52
+ private log;
53
+ private senderFor;
54
+ isDemoChannel(surface: Surface, channelId: string): boolean;
55
+ send(surface: Surface, channelId: string, text: string): Promise<SendResult>;
56
+ awaitReply(surface: Surface, channelId: string, opts: {
57
+ timeoutMs: number;
58
+ afterMessageId?: string;
59
+ }): Promise<ReplyResult | null>;
60
+ }
61
+ //# sourceMappingURL=RealChannelDriver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RealChannelDriver.d.ts","sourceRoot":"","sources":["../../src/core/RealChannelDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,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;CAC9I;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;CAezI"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * RealChannelDriver — the production `ChannelDriver` for the LiveTestHarness
3
+ * (docs/specs/live-user-channel-proof-standard.md §5.4 "Platform-Sanctioned
4
+ * Automation"). It drives a feature end-to-end through the REAL user surfaces by
5
+ * composing one `SurfaceSender` per surface (Telegram, Slack, dashboard) plus:
6
+ *
7
+ * - a DemoChannelRegistry → `isDemoChannel` (§5.3 isolation), and
8
+ * - a `resolveResponderMachine` reader → the `responderMachineId` stamped on every
9
+ * reply, which is the DETERMINISTIC cross-machine proof the harness asserts on
10
+ * (e.g. "after the transfer, the reply was served FROM the Mini").
11
+ *
12
+ * The driver itself is pure transport composition: it knows nothing about HTTP,
13
+ * Telegram, or Slack — each surface sender + the placement reader are injected, so
14
+ * this module is fully unit-testable with fakes. The real senders (a demo-bot/user
15
+ * Telegram sender, a Slack user-token sender) and the real placement reader (a
16
+ * GET /pool/placement call) are wired at construction in server.ts.
17
+ *
18
+ * Safety: a surface with no registered sender is a hard error on use (never a silent
19
+ * skip that would fabricate a "no reply" FAIL), and `responderMachineId` resolution
20
+ * failures degrade to `undefined` (the harness then simply can't assert on responder)
21
+ * rather than throwing the whole scenario — the SEND/REPLY evidence is still recorded.
22
+ */
23
+ export class RealChannelDriver {
24
+ d;
25
+ constructor(deps) { this.d = deps; }
26
+ log(m) { this.d.logger?.(`[real-channel-driver] ${m}`); }
27
+ senderFor(surface) {
28
+ const s = this.d.senders[surface];
29
+ if (!s) {
30
+ // A missing sender is a CONFIGURATION error, surfaced loudly — never a silent
31
+ // skip that the harness would misread as a clean "no reply" FAIL.
32
+ throw new Error(`no real sender configured for surface "${surface}" — cannot drive it`);
33
+ }
34
+ return s;
35
+ }
36
+ isDemoChannel(surface, channelId) {
37
+ return this.d.demoRegistry.isDemoChannel(surface, channelId);
38
+ }
39
+ async send(surface, channelId, text) {
40
+ return this.senderFor(surface).send(channelId, text);
41
+ }
42
+ async awaitReply(surface, channelId, opts) {
43
+ const reply = await this.senderFor(surface).awaitReply(channelId, opts);
44
+ if (!reply)
45
+ return null;
46
+ let responderMachineId;
47
+ try {
48
+ responderMachineId = (await this.d.resolveResponderMachine(surface, channelId)) ?? undefined;
49
+ }
50
+ catch (err) {
51
+ // Degrade, never throw: we still have a real reply; we just can't attribute the
52
+ // responder machine this round. The harness records the reply evidence and
53
+ // (only) any responder-machine assertion can't be satisfied.
54
+ this.log(`responder-machine resolve failed for ${surface}:${channelId} (recording reply without it): ${err instanceof Error ? err.message : String(err)}`);
55
+ responderMachineId = undefined;
56
+ }
57
+ return { ...reply, responderMachineId };
58
+ }
59
+ }
60
+ //# sourceMappingURL=RealChannelDriver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RealChannelDriver.js","sourceRoot":"","sources":["../../src/core/RealChannelDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AA4BH,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;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.594",
3
+ "version": "1.3.595",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-06-16T09:33:12.134Z",
5
- "instarVersion": "1.3.594",
4
+ "generatedAt": "2026-06-16T10:03:21.927Z",
5
+ "instarVersion": "1.3.595",
6
6
  "entryCount": 201,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,26 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Added the two pure core modules behind the `LiveTestHarness`'s `ChannelDriver` seam (spec: live-user-channel-proof-standard §5.3/§5.4):
9
+ - `DemoChannelRegistry` — verifies a signed demo-channel bindings doc; fail-closed `isDemoChannel(surface, channelId)` (absent or signature-invalid → zero demo channels). Canonical payload is an ordered JSON tuple so a signature can never be reused on a different binding set (closes a delimiter-collision bypass caught in second-pass review).
10
+ - `RealChannelDriver` — the production `ChannelDriver`: composes per-surface `SurfaceSender`s, dispatches send/awaitReply, and stamps `responderMachineId` on each reply via an injected placement reader (the deterministic cross-machine proof). Degrades-not-throws on a placement-read error; throws loudly on a missing surface sender (never a silent skip).
11
+
12
+ Ships DARK — not wired into server.ts. The real Telegram/Slack surface senders + the runner route are the next increment.
13
+
14
+ ## What to Tell Your User
15
+
16
+ Nothing yet — this is internal infrastructure for the gold-standard live-testing harness and ships dark (no runtime surface, no behavior change). The user-visible payoff lands when the harness runs end-to-end and proves a feature through the real Telegram/Slack channels before they ever test it.
17
+
18
+ ## Summary of New Capabilities
19
+
20
+ None user-facing in this increment. Internally: the building blocks that let the live-test harness drive real user channels and assert WHICH machine served a reply (the cross-machine seat-move proof). No new routes, no config, no flags flipped.
21
+
22
+ ## Evidence
23
+
24
+ - `tests/unit/DemoChannelRegistry.test.ts` — 9 tests: valid signed bindings resolve, absent → 0, tampered/forged/throwing-verify all fail-closed, cross-boundary signature-collision rejection, absent-vs-empty distinguishability, canonical order-independence + field-sensitivity.
25
+ - `tests/unit/RealChannelDriver.test.ts` — 7 tests: send dispatch, responderMachineId stamping, null-reply short-circuit, placement-error degrade, missing-sender loud throw, isDemoChannel delegation, null-responder.
26
+ - `tsc --noEmit` clean. instar-dev gate green (converged+approved spec, side-effects + second-pass review).
@@ -0,0 +1,41 @@
1
+ # Side-Effects Review — Live-Test Real Channel Driver core (DemoChannelRegistry + RealChannelDriver)
2
+
3
+ **Slug:** live-test-channel-drivers
4
+ **Spec:** docs/specs/live-user-channel-proof-standard.md §5.3 (Demo Channel Isolation) + §5.4 (Platform-Sanctioned Automation)
5
+ **Files:** src/core/DemoChannelRegistry.ts, src/core/RealChannelDriver.ts, tests/unit/DemoChannelRegistry.test.ts, tests/unit/RealChannelDriver.test.ts
6
+ **Posture:** ships DARK — two pure/injectable core modules, NOT yet wired into server.ts. No route, no runtime construction. The surface senders + runner route are the next increment (`.instar/plans/live-test-harness-drivers-BUILD.md`).
7
+
8
+ ## What it is
9
+ The production-side building blocks for the `LiveTestHarness`'s `ChannelDriver`:
10
+ - **DemoChannelRegistry** — verifies a SIGNED demo-channel bindings doc and answers `isDemoChannel(surface, channelId)`. Fail-closed: an absent OR signature-invalid doc grants ZERO demo channels.
11
+ - **RealChannelDriver** — composes one `SurfaceSender` per surface, dispatches `send`/`awaitReply`, and stamps `responderMachineId` on every reply via an injected `resolveResponderMachine` reader (the cross-machine proof the harness asserts on).
12
+
13
+ ## Phase 1 — Principle check (signal vs authority)
14
+ This increment touches a decision point — `isDemoChannel` gates whether a volatile/permission scenario may run on a given channel (§5.3). Analysis per `docs/signal-vs-authority.md`:
15
+ - **DemoChannelRegistry is a SIGNAL, not the authority.** It returns a boolean; the *authority* that refuses a run is the already-merged `LiveTestHarness.run` (it throws `HarnessVolatileChannelError`). The registry never blocks anything itself.
16
+ - The registry's logic is **fail-closed**, which is the safe direction for a §5.3 isolation check: a false "not a demo channel" only refuses a test; a false "is a demo channel" would let a destructive scenario touch the live operator channel. Verification failures (tampered/absent/throwing verify) all collapse to "no demo channels."
17
+ - **RealChannelDriver holds no decision authority** — pure transport composition. The one place it could mask a failure (a missing surface sender) throws LOUDLY rather than silently returning "no reply" (which the harness would misread as a clean FAIL).
18
+
19
+ Verdict: compliant. No brittle check with blocking authority added.
20
+
21
+ ## Phase 4 — Side-effects answers
22
+ 1. **Over-block** — `isDemoChannel` could return false for a channel that IS legitimately a demo channel if its signed bindings are absent/stale. Effect: a volatile scenario is refused (the harness throws) rather than running. That is the intended fail-closed direction — it never lets the scenario through, only ever refuses. Safe scenarios (the headline transfer-capstone) don't consult demo status, so they're unaffected.
23
+ 2. **Under-block** — the registry only vouches for channels in the signed list; it can't detect that a *non-demo* channel is secretly safe. By design — the harness simply requires volatile scenarios to be on a registered demo channel. No false "allow."
24
+ 3. **Level-of-abstraction fit** — correct layer: the registry is a pure data/verification helper feeding the harness gate (which already owns the §5.3 throw). The driver is the transport adapter the harness's injected-`ChannelDriver` seam was explicitly designed for (`LiveTestHarness.ts:8-11`). Neither reinvents a higher-layer gate.
25
+ 4. **Signal vs authority** — see Phase 1. Compliant (registry = fail-closed signal; harness = authority; driver = transport).
26
+ 5. **Interactions** — none yet: nothing constructs either class at runtime (dark). When wired, the driver composes existing senders + the `/pool/placement` reader; it does not shadow or double-fire any existing check. The registry reads a NEW state file (`state/demo-channel-bindings.json`) that no other component touches.
27
+ 6. **External surfaces** — none in this increment. No route, no message send, no other-agent-visible change. (The eventual senders WILL drive real Telegram/Slack — that surface lands in the next, separately-reviewed increment.)
28
+ 7. **Multi-machine posture** — `responderMachineId` is the feature's whole multi-machine point: the driver resolves WHICH machine served a reply (via the injected placement reader, which reads the authoritative `/pool/placement`, proxied to the lease-holder). The DemoChannelRegistry is **machine-local BY DESIGN** — demo-channel bindings are an operational/test-fixture fact of the machine running the harness; the harness runs on one machine at a time and asserts cross-machine behavior THROUGH the channel, not by replicating bindings. No silent single-machine assumption: the cross-machine dimension is carried explicitly by `responderMachineId`.
29
+ 8. **Rollback cost** — trivial: dark, unwired code. Back-out = revert the commit (or simply never wire it). No data migration, no live state, no fleet behavior change.
30
+
31
+ ## No-deferrals
32
+ The surface senders + runner are NOT a deferral of THIS increment — they are the next tracked increment (CMT-1568, `.instar/plans/live-test-harness-drivers-BUILD.md`). This increment is complete and self-contained: both modules are fully implemented (no stubs/TODOs) and fully unit-tested (16 tests, both sides of every boundary — valid/tampered/absent/throwing/collision/empty-vs-absent for the registry; dispatch/stamp/degrade/missing-sender for the driver).
33
+
34
+ ## Phase 5 — Second-pass review
35
+ An independent reviewer subagent audited the modules + this artifact adversarially.
36
+
37
+ **Concern raised (and RESOLVED):** `canonicalBindingsPayload`'s per-binding encoding was a delimiter-free concatenation (`${surface}${channelId}${workspaceId}${label}`), an ambiguous field boundary that lets a tampered binding collide to the same signed bytes — e.g. `{channelId:'C1', workspaceId:'W2'}` and `{channelId:'C1W2'}` serialize identically and share one valid signature, promoting an unvouched channelId to a demo channel. That is exactly the dangerous false "is demo" the module guards against.
38
+
39
+ **Fix applied:** the per-binding encoding is now an ordered JSON tuple (`JSON.stringify([surface, channelId, workspaceId ?? null, label ?? null])`), which makes every field boundary explicit and distinguishes an absent field (`null`) from a present empty string (`''`). The lookup `key()` was hardened the same way (defense-in-depth). Two regression tests added: a cross-boundary collision test (the tampered doc fails verification → the smuggled channel is NOT a demo channel) and an absent-vs-empty distinguishability test. (Also stripped stray `\x01` control bytes that had crept into the source — invisible and git-binary-fragile.)
40
+
41
+ Re-review verdict after fix: the collision is closed; the surface↔channelId boundary was already safe (surface is a closed enum, none a prefix of another); fail-closed paths and the driver's degrade-not-throw / loud-missing-sender behavior are correct. **Concur with the review (post-fix).**