@dharma-ai-labs/agent-fabric 0.2.132 → 0.2.133

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.
@@ -10,8 +10,19 @@ test('the installed onboarding contract forbids product-code workarounds', async
10
10
  assert.match(contract, /do not bypass enrollment/i);
11
11
  assert.match(contract, /exact failed stage, non-sensitive error, and correlation ID/i);
12
12
  assert.match(contract, /supported retry, reconciliation, rollback, or browser-authorized re-enrollment/i);
13
- assert.match(contract, /one retry of the unchanged command is permitted before grant expiry/i);
14
- assert.match(contract, /Never retry a grant after redemption/i);
13
+ assert.match(contract, /retry the unchanged command once before expiry when the receipt explicitly proves non-redemption/i);
14
+ assert.match(contract, /After redemption, approval or another error, use only the supported cause-specific grant-free recovery; never replay the grant/i);
15
+ });
16
+ test('the installed contract separates agent instructions from recipient-only private terminal entry', async () => {
17
+ const contract = await readFile(contractUrl, 'utf8');
18
+ assert.match(contract, /short-lived grant is a separate recipient-only private field, not part of the agent prompt/i);
19
+ assert.match(contract, /exact generated grant-free `--grant-prompt` command directly in their own private, unrecorded interactive terminal/i);
20
+ assert.match(contract, /Never receive, print, save, commit, place in argv or environment, send through agent tools or transport, or record that grant/i);
21
+ assert.match(contract, /cannot detect an external recorder or prove the human owns a TTY/i);
22
+ assert.match(contract, /no supplementary manual setup command/i);
23
+ assert.match(contract, /Join existing[\s\S]*No GitHub permission or source checkout is needed/i);
24
+ assert.match(contract, /legacy Demo command contract does not establish `--grant-prompt` support/i);
25
+ assert.doesNotMatch(contract, /only routine human action|setup envelope contains[\s\S]*one-time grant/i);
15
26
  });
16
27
  test('the installed guide names the supported knowledge and peer workflow', async () => {
17
28
  const contract = await readFile(contractUrl, 'utf8');
@@ -1 +1 @@
1
- {"version":3,"file":"onboardingContract.test.js","sourceRoot":"","sources":["../src/onboardingContract.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,+BAA+B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE9E,IAAI,CAAC,oEAAoE,EAAE,KAAK,IAAI,EAAE;IACpF,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAErD,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,gDAAgD,CAAC,CAAC;IACzE,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,0CAA0C,CAAC,CAAC;IACnE,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,4BAA4B,CAAC,CAAC;IACrD,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,2BAA2B,CAAC,CAAC;IACpD,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,8DAA8D,CAAC,CAAC;IACvF,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,iFAAiF,CAAC,CAAC;IAC1G,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,sEAAsE,CAAC,CAAC;IAC/F,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,uCAAuC,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;IACrF,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAErD,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,kHAAkH,CAAC,CAAC;IAC3I,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,gFAAgF,CAAC,CAAC;IACzG,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,oHAAoH,CAAC,CAAC;IAC7I,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,8EAA8E,CAAC,CAAC;IACvG,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,qDAAqD,CAAC,CAAC;IAC9E,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,iDAAiD,CAAC,CAAC;IAC1E,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,sDAAsD,CAAC,CAAC;IAC/E,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,wDAAwD,CAAC,CAAC;IACjF,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,mCAAmC,CAAC,CAAC;IAC5D,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,4EAA4E,CAAC,CAAC;IACrG,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,qBAAqB,CAAC,CAAC;AACvD,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"onboardingContract.test.js","sourceRoot":"","sources":["../src/onboardingContract.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,+BAA+B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE9E,IAAI,CAAC,oEAAoE,EAAE,KAAK,IAAI,EAAE;IACpF,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAErD,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,gDAAgD,CAAC,CAAC;IACzE,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,0CAA0C,CAAC,CAAC;IACnE,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,4BAA4B,CAAC,CAAC;IACrD,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,2BAA2B,CAAC,CAAC;IACpD,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,8DAA8D,CAAC,CAAC;IACvF,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,iFAAiF,CAAC,CAAC;IAC1G,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,mGAAmG,CAAC,CAAC;IAC5H,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,iIAAiI,CAAC,CAAC;AAC5J,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gGAAgG,EAAE,KAAK,IAAI,EAAE;IAChH,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACrD,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,6FAA6F,CAAC,CAAC;IACtH,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,qHAAqH,CAAC,CAAC;IAC9I,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,gIAAgI,CAAC,CAAC;IACzJ,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,mEAAmE,CAAC,CAAC;IAC5F,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,wCAAwC,CAAC,CAAC;IACjE,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,wEAAwE,CAAC,CAAC;IACjG,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,2EAA2E,CAAC,CAAC;IACpG,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,yEAAyE,CAAC,CAAC;AAC3G,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;IACrF,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAErD,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,kHAAkH,CAAC,CAAC;IAC3I,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,gFAAgF,CAAC,CAAC;IACzG,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,oHAAoH,CAAC,CAAC;IAC7I,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,8EAA8E,CAAC,CAAC;IACvG,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,qDAAqD,CAAC,CAAC;IAC9E,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,iDAAiD,CAAC,CAAC;IAC1E,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,sDAAsD,CAAC,CAAC;IAC/E,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,wDAAwD,CAAC,CAAC;IACjF,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,mCAAmC,CAAC,CAAC;IAC5D,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,4EAA4E,CAAC,CAAC;IACrG,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,qBAAqB,CAAC,CAAC;AACvD,CAAC,CAAC,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { EventEmitter } from 'node:events';
2
+ type TerminalInput = Pick<EventEmitter, 'on' | 'removeListener' | 'listenerCount'> & {
3
+ isTTY: boolean;
4
+ isRaw: boolean;
5
+ fd: number;
6
+ readableFlowing: boolean | null;
7
+ setRawMode(raw: boolean): unknown;
8
+ resume(): unknown;
9
+ pause(): unknown;
10
+ };
11
+ type TerminalOutput = {
12
+ isTTY: boolean;
13
+ fd: number;
14
+ write(value: string): unknown;
15
+ };
16
+ type Signals = Pick<EventEmitter, 'on' | 'removeListener'>;
17
+ interface PrivateGrantTerminal {
18
+ input: TerminalInput;
19
+ output: TerminalOutput;
20
+ signals: Signals;
21
+ isTerminal: (fd: number) => boolean;
22
+ }
23
+ export declare function bootstrapGrantMode(flags: Map<string, string | boolean>): 'resume' | 'argument' | 'prompt';
24
+ /** Read only from the user's own terminal. No pipe, environment, file or echo fallback. */
25
+ export declare function readPrivateBootstrapGrant(terminal?: PrivateGrantTerminal): Promise<string>;
26
+ export {};
27
+ //# sourceMappingURL=privateGrantInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privateGrantInput.d.ts","sourceRoot":"","sources":["../src/privateGrantInput.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,KAAK,aAAa,GAAG,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,gBAAgB,GAAG,eAAe,CAAC,GAAG;IACnF,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5E,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;IAAC,MAAM,IAAI,OAAO,CAAC;IAAC,KAAK,IAAI,OAAO,CAAC;CACxE,CAAC;AACF,KAAK,cAAc,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AACpF,KAAK,OAAO,GAAG,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,gBAAgB,CAAC,CAAC;AAC3D,UAAU,oBAAoB;IAC5B,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,EAAE,cAAc,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;CACrC;AAKD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAYzG;AAED,2FAA2F;AAC3F,wBAAsB,yBAAyB,CAAC,QAAQ,GAAE,oBAGzD,GAAG,OAAO,CAAC,MAAM,CAAC,CAmFlB"}
@@ -0,0 +1,159 @@
1
+ import { isatty } from 'node:tty';
2
+ const MAX_GRANT_BYTES = 16_384;
3
+ const CANCEL_SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT', 'SIGTSTP'];
4
+ export function bootstrapGrantMode(flags) {
5
+ const grant = flags.get('grant');
6
+ const prompt = flags.get('grant-prompt');
7
+ if ((flags.has('resume') && (flags.has('grant') || flags.has('grant-prompt')))
8
+ || (flags.has('grant') && flags.has('grant-prompt'))
9
+ || (flags.has('grant-prompt') && prompt !== true)) {
10
+ throw new Error('bootstrap_grant_options_invalid: --grant-prompt takes no value and cannot be combined with --grant or --resume.');
11
+ }
12
+ if (flags.has('resume'))
13
+ return 'resume';
14
+ if (prompt === true)
15
+ return 'prompt';
16
+ if (typeof grant === 'string' && grant.length > 0)
17
+ return 'argument';
18
+ throw new Error('bootstrap_grant_options_invalid: use --grant-prompt for private terminal entry, or an existing supported --grant invocation.');
19
+ }
20
+ /** Read only from the user's own terminal. No pipe, environment, file or echo fallback. */
21
+ export async function readPrivateBootstrapGrant(terminal = {
22
+ input: process.stdin, output: process.stderr,
23
+ signals: process, isTerminal: isatty,
24
+ }) {
25
+ const { input, output, signals, isTerminal } = terminal;
26
+ if (!input.isTTY || !output.isTTY || !isTerminal(input.fd) || !isTerminal(output.fd)
27
+ || input.isRaw || input.listenerCount('data') !== 0) {
28
+ throw new Error('private_grant_terminal_required: run this command directly in your own interactive terminal; do not pipe, redirect or send the grant through an agent.');
29
+ }
30
+ const wasFlowing = input.readableFlowing;
31
+ const bytes = Buffer.alloc(MAX_GRANT_BYTES);
32
+ let length = 0;
33
+ let finished = false;
34
+ let escape = '';
35
+ let pasting = false;
36
+ let escapeTimer;
37
+ let inputTimer;
38
+ return new Promise((resolve, reject) => {
39
+ const restore = () => {
40
+ clearTimeout(escapeTimer);
41
+ clearTimeout(inputTimer);
42
+ input.removeListener('data', onData);
43
+ input.removeListener('end', onFailure);
44
+ input.removeListener('close', onFailure);
45
+ input.removeListener('error', onFailure);
46
+ for (const signal of CANCEL_SIGNALS)
47
+ signals.removeListener(signal, onCancel);
48
+ signals.removeListener('exit', onExit);
49
+ bytes.fill(0);
50
+ length = 0;
51
+ try {
52
+ if (input.isRaw)
53
+ input.setRawMode(false);
54
+ if (wasFlowing === true)
55
+ input.resume();
56
+ else
57
+ input.pause();
58
+ return true;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ };
64
+ const finish = (code, value) => {
65
+ if (finished)
66
+ return;
67
+ finished = true;
68
+ const restored = restore();
69
+ try {
70
+ output.write('\n');
71
+ }
72
+ catch { /* Never print input or an underlying error. */ }
73
+ if (code || !restored)
74
+ reject(new Error(code || 'private_grant_input_failed'));
75
+ else
76
+ resolve(value);
77
+ };
78
+ const onCancel = () => finish('private_grant_cancelled');
79
+ const onFailure = () => finish('private_grant_input_failed');
80
+ const onExit = () => { finished = true; restore(); };
81
+ const onData = (data) => {
82
+ const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data);
83
+ if (chunk.length > MAX_GRANT_BYTES + 14) {
84
+ finish('private_grant_too_large');
85
+ return;
86
+ }
87
+ for (let index = 0; index < chunk.length; index += 1) {
88
+ const byte = chunk[index];
89
+ if (escape || byte === 27) {
90
+ escape += String.fromCharCode(byte);
91
+ const start = '\x1b[200~';
92
+ const end = '\x1b[201~';
93
+ if (escape === start && !pasting && length === 0) {
94
+ pasting = true;
95
+ escape = '';
96
+ clearTimeout(escapeTimer);
97
+ }
98
+ else if (escape === end && pasting) {
99
+ pasting = false;
100
+ escape = '';
101
+ clearTimeout(escapeTimer);
102
+ }
103
+ else if (!start.startsWith(escape) && !end.startsWith(escape)) {
104
+ onCancel();
105
+ return;
106
+ }
107
+ else {
108
+ clearTimeout(escapeTimer);
109
+ escapeTimer = setTimeout(onCancel, 1_000);
110
+ }
111
+ continue;
112
+ }
113
+ if ([3, 4, 26].includes(byte)) {
114
+ onCancel();
115
+ return;
116
+ }
117
+ if (byte === 10 || byte === 13) {
118
+ if (pasting || length === 0 || chunk.subarray(index + 1).some((next) => next !== 10 && next !== 13)) {
119
+ finish('private_grant_invalid');
120
+ return;
121
+ }
122
+ finish(undefined, bytes.subarray(0, length).toString('ascii'));
123
+ return;
124
+ }
125
+ if (byte === 8 || byte === 127) {
126
+ if (length > 0)
127
+ bytes[--length] = 0;
128
+ continue;
129
+ }
130
+ if (byte < 33 || byte > 126) {
131
+ finish('private_grant_invalid');
132
+ return;
133
+ }
134
+ if (length === MAX_GRANT_BYTES) {
135
+ finish('private_grant_too_large');
136
+ return;
137
+ }
138
+ bytes[length++] = byte;
139
+ }
140
+ };
141
+ input.on('data', onData);
142
+ input.on('end', onFailure);
143
+ input.on('close', onFailure);
144
+ input.on('error', onFailure);
145
+ for (const signal of CANCEL_SIGNALS)
146
+ signals.on(signal, onCancel);
147
+ signals.on('exit', onExit);
148
+ try {
149
+ input.setRawMode(true);
150
+ output.write('Paste your short-lived setup grant here, then press Enter (input hidden; Ctrl+C cancels): ');
151
+ inputTimer = setTimeout(() => finish('private_grant_cancelled'), 300_000);
152
+ input.resume();
153
+ }
154
+ catch {
155
+ onFailure();
156
+ }
157
+ });
158
+ }
159
+ //# sourceMappingURL=privateGrantInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privateGrantInput.js","sourceRoot":"","sources":["../src/privateGrantInput.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=privateGrantInput.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privateGrantInput.test.d.ts","sourceRoot":"","sources":["../src/privateGrantInput.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,136 @@
1
+ import assert from 'node:assert/strict';
2
+ import { EventEmitter } from 'node:events';
3
+ import test from 'node:test';
4
+ import { bootstrapGrantMode, readPrivateBootstrapGrant } from './privateGrantInput.js';
5
+ class Terminal extends EventEmitter {
6
+ isTTY = true;
7
+ isRaw = false;
8
+ readableFlowing = false;
9
+ fd = 0;
10
+ modes = [];
11
+ setRawMode(raw) { this.isRaw = raw; this.modes.push(raw); return this; }
12
+ resume() { this.readableFlowing = true; return this; }
13
+ pause() { this.readableFlowing = false; return this; }
14
+ }
15
+ function fixture() {
16
+ const input = new Terminal();
17
+ const signals = new EventEmitter();
18
+ const writes = [];
19
+ const output = { isTTY: true, fd: 2, write(value) { writes.push(value); return true; } };
20
+ return { input, output, signals, writes, isTerminal: () => true };
21
+ }
22
+ const fakeGrant = 'synthetic-not-a-real-grant';
23
+ function restored(io) {
24
+ assert.equal(io.input.isRaw, false);
25
+ assert.equal(io.input.readableFlowing, false);
26
+ assert.equal(io.input.listenerCount('data'), 0);
27
+ for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT', 'SIGTSTP', 'exit'])
28
+ assert.equal(io.signals.listenerCount(signal), 0);
29
+ assert.equal(io.writes.join('').includes(fakeGrant), false);
30
+ }
31
+ test('private grant succeeds with raw input, no echo and terminal restoration', async () => {
32
+ const io = fixture();
33
+ const pending = readPrivateBootstrapGrant(io);
34
+ assert.equal(io.input.isRaw, true);
35
+ io.input.emit('data', Buffer.from(fakeGrant + '\r'));
36
+ assert.equal(await pending, fakeGrant);
37
+ assert.deepEqual(io.input.modes, [true, false]);
38
+ restored(io);
39
+ });
40
+ test('prompt rejects pipe/redirected output and already-owned terminal before changing it', async () => {
41
+ for (const change of [(io) => { io.input.isTTY = false; },
42
+ (io) => { io.output.isTTY = false; },
43
+ (io) => { io.input.isRaw = true; },
44
+ (io) => { io.input.on('data', () => { }); }]) {
45
+ const io = fixture();
46
+ change(io);
47
+ await assert.rejects(readPrivateBootstrapGrant(io), /private_grant_terminal_required/);
48
+ assert.deepEqual(io.input.modes, []);
49
+ }
50
+ const io = fixture();
51
+ io.isTerminal = () => false;
52
+ await assert.rejects(readPrivateBootstrapGrant(io), /private_grant_terminal_required/);
53
+ });
54
+ test('cancel, EOF, signals, stream failure and oversize restore without exposing input', async () => {
55
+ for (const event of ['ctrl-c', 'ctrl-d', 'escape', 'end', 'close', 'error', 'SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT', 'SIGTSTP', 'oversize']) {
56
+ const io = fixture();
57
+ const pending = readPrivateBootstrapGrant(io);
58
+ io.input.emit('data', Buffer.from(fakeGrant));
59
+ if (event === 'ctrl-c')
60
+ io.input.emit('data', Buffer.from([3]));
61
+ else if (event === 'ctrl-d')
62
+ io.input.emit('data', Buffer.from([4]));
63
+ else if (event === 'escape')
64
+ io.input.emit('data', Buffer.from([27]));
65
+ else if (event === 'oversize')
66
+ io.input.emit('data', Buffer.alloc(16_385, 65));
67
+ else if (['end', 'close', 'error'].includes(event))
68
+ io.input.emit(event, new Error(fakeGrant));
69
+ else
70
+ io.signals.emit(event);
71
+ await assert.rejects(pending, /private_grant_(cancelled|input_failed|too_large)/);
72
+ restored(io);
73
+ }
74
+ });
75
+ test('private entry rejects empty, multiline, whitespace, non-ASCII and control content', async () => {
76
+ for (const text of ['\r', fakeGrant + '\nsecond-command', 'bad token\r', 'é\r', '\x00\r']) {
77
+ const io = fixture();
78
+ const pending = readPrivateBootstrapGrant(io);
79
+ io.input.emit('data', Buffer.from(text));
80
+ await assert.rejects(pending, /private_grant_invalid/);
81
+ restored(io);
82
+ }
83
+ });
84
+ test('private entry handles backspace and split paste without echo', async () => {
85
+ const io = fixture();
86
+ const pending = readPrivateBootstrapGrant(io);
87
+ io.input.emit('data', Buffer.from('synthetic-not-a-real-grantX\x7f'));
88
+ io.input.emit('data', Buffer.from('\r\n'));
89
+ assert.equal(await pending, fakeGrant);
90
+ restored(io);
91
+ });
92
+ test('bracketed paste boundaries may be split without accepting a pasted command or newline', async () => {
93
+ const io = fixture();
94
+ const pending = readPrivateBootstrapGrant(io);
95
+ for (const piece of ['\x1b', '[20', '0~', fakeGrant, '\x1b[2', '01~', '\r']) {
96
+ io.input.emit('data', Buffer.from(piece));
97
+ }
98
+ assert.equal(await pending, fakeGrant);
99
+ restored(io);
100
+ const bad = fixture();
101
+ const rejected = readPrivateBootstrapGrant(bad);
102
+ bad.input.emit('data', Buffer.from('\x1b[200~' + fakeGrant + '\ncommand\x1b[201~'));
103
+ await assert.rejects(rejected, /private_grant_invalid/);
104
+ restored(bad);
105
+ });
106
+ test('process exit restores terminal without emitting private input', () => {
107
+ const io = fixture();
108
+ void readPrivateBootstrapGrant(io);
109
+ io.input.emit('data', Buffer.from(fakeGrant));
110
+ io.signals.emit('exit');
111
+ restored(io);
112
+ });
113
+ test('raw-mode setup and prompt-write failures are sanitized and restored', async () => {
114
+ for (const where of ['raw', 'write']) {
115
+ const io = fixture();
116
+ if (where === 'raw')
117
+ io.input.setRawMode = () => { throw new Error(fakeGrant); };
118
+ else
119
+ io.output.write = () => { throw new Error(fakeGrant); };
120
+ await assert.rejects(readPrivateBootstrapGrant(io), /private_grant_input_failed/);
121
+ assert.equal(io.input.listenerCount('data'), 0);
122
+ assert.equal(io.input.isRaw, false);
123
+ }
124
+ });
125
+ test('supported grant modes preserve legacy and resume while rejecting conflict/value confusion', () => {
126
+ const flags = (...values) => new Map(values);
127
+ assert.equal(bootstrapGrantMode(flags(['grant-prompt', true])), 'prompt');
128
+ assert.equal(bootstrapGrantMode(flags(['grant', fakeGrant])), 'argument');
129
+ assert.equal(bootstrapGrantMode(flags(['resume', true])), 'resume');
130
+ for (const values of [[['grant', fakeGrant], ['grant-prompt', true]],
131
+ [['resume', true], ['grant-prompt', true]], [['resume', true], ['grant', fakeGrant]],
132
+ [['grant-prompt', fakeGrant]], [['grant-prompt', 'false']], [['grant', true]], []]) {
133
+ assert.throws(() => bootstrapGrantMode(flags(...values)), /bootstrap_grant_options_invalid/);
134
+ }
135
+ });
136
+ //# sourceMappingURL=privateGrantInput.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privateGrantInput.test.js","sourceRoot":"","sources":["../src/privateGrantInput.test.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ import { runInNewContext } from 'node:vm';
7
7
  import test from 'node:test';
8
8
  import ts from 'typescript';
9
9
  import { appendRecoveredWorkspace, resolveRegistryRecoveryProjection } from './workspaceRegistryRecovery.js';
10
+ import { bootstrapGrantMode } from './privateGrantInput.js';
10
11
  // Execute the actual function body, parsed by TypeScript rather than extracted
11
12
  // by text matching. All I/O boundaries are injected: no enrollment, paid work,
12
13
  // secret-store writes, daemon processes, or external requests can occur.
@@ -28,6 +29,8 @@ function bootstrapDependencies(onboarding) {
28
29
  const calls = [];
29
30
  const record = async (name) => { calls.push(name); };
30
31
  const dependencies = {
32
+ bootstrapGrantMode,
33
+ readPrivateBootstrapGrant: async () => { await record('private_grant'); return 'fixture_private_grant'; },
31
34
  normalizeHqUrl: (value) => value,
32
35
  portalUrl: () => 'https://fixture.invalid',
33
36
  required: (flags, key) => {
@@ -367,6 +370,63 @@ function bootstrapFlags(complete = false) {
367
370
  flags.set('complete', true);
368
371
  return flags;
369
372
  }
373
+ test('private grant entry follows preflight, uses the unchanged redemption scope and stays out of onboard flags', async () => {
374
+ const f = bootstrapDependencies({ ok: true, stage: 'ready', localStage: 'ready', sharedRepositoryReady: true });
375
+ const flags = bootstrapFlags();
376
+ flags.delete('grant');
377
+ flags.set('grant-prompt', true);
378
+ const originalOnboard = f.dependencies.onboard;
379
+ f.dependencies.onboard = async (onboardFlags) => {
380
+ assert.equal(onboardFlags.has('grant'), false);
381
+ assert.equal(onboardFlags.has('grant-prompt'), false);
382
+ return originalOnboard(onboardFlags);
383
+ };
384
+ f.dependencies.redeemBootstrapGrant = async (input) => {
385
+ assert.equal(input.bootstrapToken, 'fixture_private_grant');
386
+ assert.equal(input.organizationId, 'org_fixture');
387
+ assert.equal(input.hqUrl, 'https://fixture.invalid');
388
+ assert.equal(input.repositoryFingerprint, `sha256:${'e'.repeat(64)}`);
389
+ assert.equal(typeof input.onRecipientApprovalRequired, 'function');
390
+ f.calls.push('redeem');
391
+ return { deviceId: 'fixture_device', serverPublicKeyEd25519: 'fixture_server_key',
392
+ relayUrl: 'wss://fixture.invalid', organizationApiToken: 'fixture_token', organizationApiTokenScopes: [] };
393
+ };
394
+ await (await caller('bootstrap', f.dependencies))(flags);
395
+ assert.ok(f.calls.indexOf('preflight') < f.calls.indexOf('private_grant'));
396
+ assert.ok(f.calls.indexOf('private_grant') < f.calls.indexOf('redeem'));
397
+ });
398
+ test('foreign enrollment and grant-option conflict stop before private entry or redemption', async () => {
399
+ for (const conflict of [false, true]) {
400
+ const f = bootstrapDependencies({ ok: true });
401
+ const flags = bootstrapFlags();
402
+ flags.set('grant-prompt', true);
403
+ if (!conflict) {
404
+ flags.delete('grant');
405
+ f.dependencies.readDeviceConfig = async () => ({ organizationId: 'org_other', hqUrl: 'https://fixture.invalid' });
406
+ }
407
+ f.dependencies.redeemBootstrapGrant = async () => { throw new Error('must not redeem'); };
408
+ await assert.rejects((await caller('bootstrap', f.dependencies))(flags), conflict
409
+ ? /bootstrap_grant_options_invalid/ : /another organization or portal/);
410
+ assert.equal(f.calls.includes('private_grant'), false);
411
+ }
412
+ });
413
+ test('redemption failure never reflects private terminal input into an error', async () => {
414
+ for (const message of ['reflected fixture_private_grant', 'grant_expired']) {
415
+ const f = bootstrapDependencies({ ok: true });
416
+ const flags = bootstrapFlags();
417
+ flags.delete('grant');
418
+ flags.set('grant-prompt', true);
419
+ f.dependencies.redeemBootstrapGrant = async () => { throw new Error(message); };
420
+ // The actual function runs in a separate VM realm; the Error need not share its constructor.
421
+ await assert.rejects((await caller('bootstrap', f.dependencies))(flags), (error) => {
422
+ const text = String(error);
423
+ assert.equal(text.includes('fixture_private_grant'), false);
424
+ assert.match(text, message === 'grant_expired' ? /grant_expired/ : /bootstrap_redemption_failed/);
425
+ return true;
426
+ });
427
+ assert.equal(f.calls.includes('save_device'), false);
428
+ }
429
+ });
370
430
  async function resumeFixture() {
371
431
  const f = bootstrapDependencies({ ok: true, stage: 'ready', localStage: 'ready',
372
432
  sharedRepositoryReady: true, workspaceId: 'workspace_fixture' });