@productbrain/cli 0.1.0-beta.3508 → 0.1.0-beta.3516

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.
Files changed (29) hide show
  1. package/dist/__tests__/gateway-dry-run-timeout.test.d.ts +2 -0
  2. package/dist/__tests__/gateway-dry-run-timeout.test.d.ts.map +1 -0
  3. package/dist/__tests__/gateway-dry-run-timeout.test.js +70 -0
  4. package/dist/__tests__/gateway-dry-run-timeout.test.js.map +1 -0
  5. package/dist/__tests__/gateway-timeout-body-phase.test.d.ts +2 -0
  6. package/dist/__tests__/gateway-timeout-body-phase.test.d.ts.map +1 -0
  7. package/dist/__tests__/gateway-timeout-body-phase.test.js +121 -0
  8. package/dist/__tests__/gateway-timeout-body-phase.test.js.map +1 -0
  9. package/dist/__tests__/route-latency-budget.test.d.ts +2 -0
  10. package/dist/__tests__/route-latency-budget.test.d.ts.map +1 -0
  11. package/dist/__tests__/route-latency-budget.test.js +103 -0
  12. package/dist/__tests__/route-latency-budget.test.js.map +1 -0
  13. package/dist/generated/routeLatencyBudget.generated.d.ts +55 -0
  14. package/dist/generated/routeLatencyBudget.generated.d.ts.map +1 -0
  15. package/dist/generated/routeLatencyBudget.generated.js +348 -0
  16. package/dist/generated/routeLatencyBudget.generated.js.map +1 -0
  17. package/dist/lib/client.d.ts +11 -9
  18. package/dist/lib/client.d.ts.map +1 -1
  19. package/dist/lib/client.js +57 -16
  20. package/dist/lib/client.js.map +1 -1
  21. package/dist/lib/gatewayTimeout.d.ts +88 -0
  22. package/dist/lib/gatewayTimeout.d.ts.map +1 -0
  23. package/dist/lib/gatewayTimeout.js +118 -0
  24. package/dist/lib/gatewayTimeout.js.map +1 -0
  25. package/dist/lib/kernelEnvelope.d.ts +31 -0
  26. package/dist/lib/kernelEnvelope.d.ts.map +1 -0
  27. package/dist/lib/kernelEnvelope.js +52 -0
  28. package/dist/lib/kernelEnvelope.js.map +1 -0
  29. package/package.json +2 -2
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gateway-dry-run-timeout.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-dry-run-timeout.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/gateway-dry-run-timeout.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * gateway-dry-run-timeout.test.ts — PR #533 review, Codex P2 (WP-575, TEN-2917).
3
+ *
4
+ * The CLI half of "a dry run that times out did not write anything"; packages/mcp-server has
5
+ * the mirror. Both halves exist on purpose rather than one shared test: the failure mode this
6
+ * work package removed was the two connectors DISAGREEING, so each connector's own suite has
7
+ * to fail when ITS copy goes stale — and mcp-server cannot import from here anyway (DEC-701).
8
+ *
9
+ * `pb capture` never sends `preview: true` today and says so explicitly
10
+ * (../commands/capture.ts:445-450), so there is no live CLI defect here. That is exactly why
11
+ * the test exists: the derivation is kept identical on both sides BEFORE a CLI preview flag
12
+ * lands, rather than after the two connectors have already drifted apart again.
13
+ */
14
+ import { describe, expect, it } from 'vitest';
15
+ import { gatewayTimeoutError, isDryRunCall, mayHaveLandedOnTimeout, routeMayMutate } from '../lib/gatewayTimeout.js';
16
+ import { ErrorCode } from '../lib/errors.js';
17
+ /** Mirrored VERBATIM from packages/mcp-server/src/__tests__/gateway-dry-run-timeout.test.ts. */
18
+ const DRY_RUN_PARITY_CASES = [
19
+ ['chain.createEntry', { preview: true }, false],
20
+ ['chain.createEntry', {}, true],
21
+ ['chain.commitEntry', { preview: true }, false],
22
+ ['chain.commitEntry', {}, true],
23
+ ['chain.searchEntries', {}, false],
24
+ ['not.a.real.route', { preview: true }, false],
25
+ ['not.a.real.route', {}, true],
26
+ ];
27
+ describe('connector parity table — mirrored verbatim in the mcp-server suite', () => {
28
+ it.each(DRY_RUN_PARITY_CASES)('%s -> mayHaveLanded=%o', (fn, args, expected) => {
29
+ expect(mayHaveLandedOnTimeout(fn, args)).toBe(expected);
30
+ });
31
+ });
32
+ describe('isDryRunCall', () => {
33
+ it('recognises only an explicit preview:true', () => {
34
+ expect(isDryRunCall({ preview: true })).toBe(true);
35
+ expect(isDryRunCall({ preview: false })).toBe(false);
36
+ expect(isDryRunCall({})).toBe(false);
37
+ expect(isDryRunCall(undefined)).toBe(false);
38
+ expect(isDryRunCall(null)).toBe(false);
39
+ });
40
+ it('does not treat a truthy non-true preview as a dry run', () => {
41
+ // The server validator is v.optional(v.boolean()), so anything else is a caller bug — and
42
+ // guessing "dry run" from it would claim nothing landed when something might have.
43
+ expect(isDryRunCall({ preview: 'yes' })).toBe(false);
44
+ expect(isDryRunCall({ preview: 1 })).toBe(false);
45
+ });
46
+ });
47
+ describe('gatewayTimeoutError guidance', () => {
48
+ it('tells a dry run nothing was written, without the duplicate-write warning', () => {
49
+ const err = gatewayTimeoutError('chain.createEntry', 30_000, { preview: true });
50
+ expect(err.code).toBe(ErrorCode.NETWORK_TIMEOUT);
51
+ expect(err.guidance).toMatch(/preview/i);
52
+ expect(err.guidance).toMatch(/safe to retry/i);
53
+ expect(err.guidance).not.toMatch(/duplicate/i);
54
+ });
55
+ it('still warns on a real write — the fail-safe is unchanged', () => {
56
+ const err = gatewayTimeoutError('chain.createEntry', 30_000, {});
57
+ expect(err.guidance).toMatch(/may have\s+COMPLETED anyway/i);
58
+ expect(err.guidance).toMatch(/duplicate the write/i);
59
+ });
60
+ it('an unknown route with no args still fails safe to "may have landed"', () => {
61
+ expect(routeMayMutate('not.a.real.route')).toBe(true);
62
+ expect(gatewayTimeoutError('not.a.real.route', 30_000).guidance).toMatch(/duplicate the write/i);
63
+ });
64
+ it('a read route says nothing was written, and does not claim it was a preview', () => {
65
+ const err = gatewayTimeoutError('chain.searchEntries', 10_000, {});
66
+ expect(err.guidance).toMatch(/this is a read/i);
67
+ expect(err.guidance).not.toMatch(/preview/i);
68
+ });
69
+ });
70
+ //# sourceMappingURL=gateway-dry-run-timeout.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-dry-run-timeout.test.js","sourceRoot":"","sources":["../../src/__tests__/gateway-dry-run-timeout.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gateway-timeout-body-phase.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-timeout-body-phase.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/gateway-timeout-body-phase.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * gateway-timeout-body-phase.test.ts — WP-575 (TEN-2917, TEN-1126), PR #533 review round.
3
+ *
4
+ * THE DEFECT THIS PINS: `fetch` resolves as soon as HEADERS arrive, while the budget's
5
+ * AbortSignal is still live. A server that returns `200` and then stalls mid-body therefore
6
+ * aborts during `res.json()` — NOT during `fetch` — which put it outside the caller's
7
+ * timeout-classification `try`. The old blanket `.catch(() => ({}))` in `parseKernelEnvelope`
8
+ * swallowed that abort into an empty envelope, and a `200` status then read it as SUCCESS
9
+ * with `undefined` data. A timed-out write reported as completed: exactly the false-outcome
10
+ * this work package exists to remove, running in reverse.
11
+ *
12
+ * The mcp-server connector had the identical hole and it was fixed there earlier in this same
13
+ * PR (`throwClassifiedGatewayFailure`, mcp-server/src/gatewaySeam.ts). The CLI's three
14
+ * kernel-call variants were the unswept siblings — so all three are asserted here, not just
15
+ * the one the reviewer named.
16
+ */
17
+ import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest';
18
+ import { kernelCall, kernelCallEnvelope } from '../lib/client.js';
19
+ import { CLIError, ErrorCode } from '../lib/errors.js';
20
+ import { latencyBudgetMsForRoute } from '../generated/routeLatencyBudget.generated.js';
21
+ const realFetch = global.fetch;
22
+ /**
23
+ * A `Response` whose headers have already arrived but whose BODY rejects the way
24
+ * `AbortSignal.timeout` really does — a DOMException named "TimeoutError". The client
25
+ * discriminates on that `name`, so a plain `new Error()` would not exercise the branch.
26
+ */
27
+ function responseThatStallsMidBody(status = 200, abortName = 'TimeoutError') {
28
+ return {
29
+ ok: status >= 200 && status < 300,
30
+ status,
31
+ statusText: 'OK',
32
+ headers: { get: () => null },
33
+ json: async () => {
34
+ const err = new Error('The operation was aborted due to timeout');
35
+ err.name = abortName;
36
+ throw err;
37
+ },
38
+ };
39
+ }
40
+ describe('CLI budget abort during the RESPONSE BODY (WP-575, PR #533 review)', () => {
41
+ beforeEach(() => {
42
+ vi.clearAllMocks();
43
+ process.env.CONVEX_SITE_URL = 'https://example.convex.site';
44
+ process.env.PRODUCTBRAIN_API_KEY = 'pb_sk_test';
45
+ global.fetch = vi.fn(async () => responseThatStallsMidBody());
46
+ });
47
+ afterEach(() => {
48
+ global.fetch = realFetch;
49
+ });
50
+ it('kernelCall surfaces the timeout instead of returning undefined as success', async () => {
51
+ // The regression in one line: this used to RESOLVE with `undefined`.
52
+ const err = (await kernelCall('chain.createEntry', {}).catch((e) => e));
53
+ expect(err).toBeInstanceOf(CLIError);
54
+ expect(err.code).toBe(ErrorCode.NETWORK_TIMEOUT);
55
+ });
56
+ it('kernelCallEnvelope surfaces it too — the sibling variant, swept in the same round', async () => {
57
+ const err = (await kernelCallEnvelope('chain.createEntry', {}).catch((e) => e));
58
+ expect(err).toBeInstanceOf(CLIError);
59
+ expect(err.code).toBe(ErrorCode.NETWORK_TIMEOUT);
60
+ });
61
+ it('classifies an AbortError body abort too, matching the mcp-server twin', async () => {
62
+ // Depending on where in undici the budget signal is observed, a body-phase abort can
63
+ // surface as "AbortError" rather than "TimeoutError". Matching only the latter left that
64
+ // case falling through to an empty envelope, which a 200 reads as success with
65
+ // `undefined` data — this work package's own defect, surviving in one phase.
66
+ // `throwClassifiedGatewayFailure` (mcp-server/src/gatewaySeam.ts) has always accepted both.
67
+ global.fetch = vi.fn(async () => responseThatStallsMidBody(200, 'AbortError'));
68
+ const err = (await kernelCall('chain.createEntry', {}).catch((e) => e));
69
+ expect(err).toBeInstanceOf(CLIError);
70
+ expect(err.code).toBe(ErrorCode.NETWORK_TIMEOUT);
71
+ });
72
+ it('a NON-OK status keeps its own error — a stalled error body is not a budget timeout', async () => {
73
+ // The server reached a verdict and said so; only its error body stalled. Reporting a
74
+ // budget timeout here would trade a real status for "may have completed anyway, search
75
+ // before retrying" about a call the server explicitly failed (PR #533 review round 5).
76
+ global.fetch = vi.fn(async () => responseThatStallsMidBody(429));
77
+ const err = (await kernelCall('chain.createEntry', {}).catch((e) => e));
78
+ expect(err.code).not.toBe(ErrorCode.NETWORK_TIMEOUT);
79
+ expect(err.message).not.toMatch(/latency budget/i);
80
+ });
81
+ it('is a TIMEOUT, never NETWORK_UNREACHABLE — the host answered', async () => {
82
+ // The whole point of the distinction: the connection succeeded and the server began
83
+ // responding. Reporting "could not reach" would be a false statement about the server
84
+ // and would imply nothing ran.
85
+ const err = (await kernelCall('chain.createEntry', {}).catch((e) => e));
86
+ expect(err.code).not.toBe(ErrorCode.NETWORK_UNREACHABLE);
87
+ expect(err.message).not.toMatch(/could not reach/i);
88
+ });
89
+ it('reports the budget the call was actually judged by, not a hardcoded number', async () => {
90
+ const err = (await kernelCall('chain.createEntry', {}).catch((e) => e));
91
+ expect(err.message).toContain(String(latencyBudgetMsForRoute('chain.createEntry')));
92
+ });
93
+ it('says the write MAY HAVE COMPLETED — a body-phase abort leaves the outcome unknown', async () => {
94
+ // The server accepted the request and began responding; only the client stopped
95
+ // waiting. Telling the agent "it failed" is what invites the duplicating retry
96
+ // (TEN-1126's ghost entries).
97
+ const err = (await kernelCall('chain.createEntry', {}).catch((e) => e));
98
+ expect(err.guidance).toMatch(/may have\s+COMPLETED/i);
99
+ expect(err.guidance).toMatch(/duplicate/i);
100
+ });
101
+ it('a read route is told nothing was written — safe to retry, nothing to duplicate', async () => {
102
+ const err = (await kernelCall('resolveWorkspace', {}).catch((e) => e));
103
+ expect(err.guidance).toMatch(/nothing\s+was written/i);
104
+ expect(err.guidance).toMatch(/safe to retry/i);
105
+ });
106
+ it('a NON-timeout parse failure keeps the lenient empty-envelope path', async () => {
107
+ // Deliberately unchanged: a non-JSON or empty body is already handled by the ok/status
108
+ // checks every caller runs, and narrowing it here would change unrelated error paths.
109
+ global.fetch = vi.fn(async () => ({
110
+ ok: false,
111
+ status: 500,
112
+ statusText: 'Internal Server Error',
113
+ headers: { get: () => null },
114
+ json: async () => {
115
+ throw new SyntaxError('Unexpected token < in JSON at position 0');
116
+ },
117
+ }));
118
+ await expect(kernelCall('chain.createEntry', {})).rejects.toThrow(/Internal Server Error/);
119
+ });
120
+ });
121
+ //# sourceMappingURL=gateway-timeout-body-phase.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway-timeout-body-phase.test.js","sourceRoot":"","sources":["../../src/__tests__/gateway-timeout-body-phase.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=route-latency-budget.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-latency-budget.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/route-latency-budget.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * route-latency-budget.test.ts — WP-575 (TEN-2917, TEN-1126), commits_to DEC-64.
3
+ *
4
+ * WP-575's acceptance: "Unit coverage for budget derivation from route type, asserted for
5
+ * BOTH connectors so they cannot drift." This is the CLI half; packages/mcp-server has the
6
+ * mirror. Both halves exist on purpose rather than one shared test: the failure mode this
7
+ * work package removed was the two connectors disagreeing, so each connector's own suite
8
+ * has to fail when ITS copy goes stale — a single test in one package would let the other
9
+ * drift with a green suite.
10
+ *
11
+ * Drift is checked against the generator's own `resolveGeneratedContent`, imported from the
12
+ * generator itself so test and codegen can never disagree about what the rule is (INS-2177:
13
+ * fingerprint the rule, not the instance — and the concrete failure that lesson comes from,
14
+ * a test that hand-rebuilt the banner and silently stripped it on every run).
15
+ *
16
+ * On failure: `npm run route-budget:codegen` from the repo root, commit both regenerated
17
+ * files.
18
+ */
19
+ import { describe, it, expect } from 'vitest';
20
+ import { readFileSync } from 'node:fs';
21
+ import { join, dirname } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ // @ts-expect-error — plain .mjs module without type declarations
24
+ import { resolveGeneratedContent, DESTS } from '../../../../scripts/generate-route-latency-budget.mjs';
25
+ import { ROUTE_LATENCY_BUDGET_MS, ROUTE_LATENCY_BUDGET_OVERRIDE_MS, ROUTE_TYPE_BY_NAME, WIDEST_ROUTE_LATENCY_BUDGET_MS, latencyBudgetMsForRoute, latencyBudgetMsForRouteType, } from '../generated/routeLatencyBudget.generated.js';
26
+ const __dirname = dirname(fileURLToPath(import.meta.url));
27
+ const REPO_ROOT = join(__dirname, '..', '..', '..', '..');
28
+ describe('route latency budget — CLI generated copy (WP-575)', () => {
29
+ it('matches what the generator would emit right now', () => {
30
+ const expected = resolveGeneratedContent(REPO_ROOT);
31
+ const committed = readFileSync(join(REPO_ROOT, 'packages/cli/src/generated/routeLatencyBudget.generated.ts'), 'utf8');
32
+ expect(committed).toBe(expected);
33
+ });
34
+ it('is byte-identical to the mcp-server copy — the two connectors cannot disagree', () => {
35
+ const contents = DESTS.map((d) => readFileSync(join(REPO_ROOT, d), 'utf8'));
36
+ expect(contents).toHaveLength(2);
37
+ expect(contents[0]).toBe(contents[1]);
38
+ });
39
+ });
40
+ describe('route latency budget — derivation from route type (WP-575)', () => {
41
+ it('gives action routes DEC-64\'s 30s background anchor', () => {
42
+ // Actions are the only Convex function kind that can reach an external service, so
43
+ // they are the only ones that can be waiting on an LLM. That is why DEC-64's
44
+ // background anchor applies to them and not to the other two.
45
+ expect(latencyBudgetMsForRouteType('action')).toBe(30_000);
46
+ expect(ROUTE_LATENCY_BUDGET_MS.action).toBe(30_000);
47
+ });
48
+ it('keeps queries and mutations on the tight budget — neither can reach an LLM', () => {
49
+ expect(latencyBudgetMsForRouteType('query')).toBe(10_000);
50
+ expect(latencyBudgetMsForRouteType('mutation')).toBe(10_000);
51
+ });
52
+ it('gives chain.createEntry the action budget — the route the defect was reported on', () => {
53
+ // The bug: a blanket 10s abort on a route whose real round-trip is 14-20s, so MCP
54
+ // reported hard failures on writes that had SUCCEEDED. Its registry type is `action`,
55
+ // so the derived budget must now exceed that measured round-trip.
56
+ expect(ROUTE_TYPE_BY_NAME['chain.createEntry']).toBe('action');
57
+ expect(latencyBudgetMsForRoute('chain.createEntry')).toBe(30_000);
58
+ expect(latencyBudgetMsForRoute('chain.createEntry')).toBeGreaterThan(20_000);
59
+ });
60
+ it('fails SAFE on an unknown route name — widest budget, never the tightest, never a throw', () => {
61
+ // Guessing low on an unclassifiable route would abort a call still legitimately in
62
+ // flight and re-create the exact false-failure this work package removed.
63
+ expect(latencyBudgetMsForRoute('not.a.real.route')).toBe(WIDEST_ROUTE_LATENCY_BUDGET_MS);
64
+ expect(WIDEST_ROUTE_LATENCY_BUDGET_MS).toBe(Math.max(...Object.values(ROUTE_LATENCY_BUDGET_MS), ...Object.values(ROUTE_LATENCY_BUDGET_OVERRIDE_MS)));
65
+ });
66
+ it('resolves inherited Object.prototype names to the fail-safe, not a truthy member', () => {
67
+ // PR #533 review, Codex P2. A plain-object map returns a FUNCTION for these names, so
68
+ // the truthiness check took the "known route" branch and produced an undefined budget —
69
+ // `AbortSignal.timeout(undefined)` THROWS, turning the fail-safe into a hard failure
70
+ // before the call ever left the process. The SSOT scans an array and never had this
71
+ // hole, so the copy also DISAGREED with the source it is generated from.
72
+ for (const name of ['toString', 'constructor', 'valueOf', 'hasOwnProperty', '__proto__']) {
73
+ expect(latencyBudgetMsForRoute(name)).toBe(WIDEST_ROUTE_LATENCY_BUDGET_MS);
74
+ // The real consequence, asserted directly: this must not throw.
75
+ expect(() => AbortSignal.timeout(latencyBudgetMsForRoute(name))).not.toThrow();
76
+ }
77
+ });
78
+ it('gives the two long-deadline actions their declared per-route budgets', () => {
79
+ // PR #533 review, Codex P1 — the flat action budget was tighter than these routes'
80
+ // own server-side deadlines, so the connector would abort a call the server was still
81
+ // legitimately working on. See the SSOT for the citation behind each number.
82
+ expect(latencyBudgetMsForRoute('onboarding.chat')).toBeGreaterThan(45_000);
83
+ expect(latencyBudgetMsForRoute('quality.spineCheck')).toBeGreaterThan(5 * 60_000);
84
+ });
85
+ it('carries the whole route registry, not a sampled subset', () => {
86
+ const names = Object.keys(ROUTE_TYPE_BY_NAME);
87
+ expect(names.length).toBeGreaterThan(200);
88
+ // Every value must be one of the three real kinds — a partial or malformed map would
89
+ // silently hand action routes the tight budget.
90
+ for (const type of Object.values(ROUTE_TYPE_BY_NAME)) {
91
+ expect(['query', 'mutation', 'action']).toContain(type);
92
+ }
93
+ expect(names.some((n) => ROUTE_TYPE_BY_NAME[n] === 'action')).toBe(true);
94
+ expect(names.some((n) => ROUTE_TYPE_BY_NAME[n] === 'query')).toBe(true);
95
+ expect(names.some((n) => ROUTE_TYPE_BY_NAME[n] === 'mutation')).toBe(true);
96
+ });
97
+ it('every route resolves to a positive budget', () => {
98
+ for (const name of Object.keys(ROUTE_TYPE_BY_NAME)) {
99
+ expect(latencyBudgetMsForRoute(name)).toBeGreaterThan(0);
100
+ }
101
+ });
102
+ });
103
+ //# sourceMappingURL=route-latency-budget.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-latency-budget.test.js","sourceRoot":"","sources":["../../src/__tests__/route-latency-budget.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ export type GatewayRouteType = 'query' | 'mutation' | 'action';
2
+ /** The budget table, copied verbatim from the SSOT. Milliseconds. */
3
+ export declare const ROUTE_LATENCY_BUDGET_MS: {
4
+ readonly query: 10000;
5
+ readonly mutation: 10000;
6
+ readonly action: 30000;
7
+ };
8
+ /**
9
+ * PER-ROUTE OVERRIDES — routes whose OWN server-side deadline exceeds their type's budget
10
+ * (PR #533 review, Codex P1). Route type predicts LLM EXPOSURE, not LLM COUNT: a flat
11
+ * action budget assumed every action is one inline LLM call, and shipped actions declare
12
+ * far longer deadlines. Bounding those at the type budget aborts a call the server is
13
+ * still legitimately working on. Each value is derived from a deadline the SERVER
14
+ * declares — see the SSOT for the citation behind every number.
15
+ */
16
+ export declare const ROUTE_LATENCY_BUDGET_OVERRIDE_MS: {
17
+ readonly 'onboarding.chat': 60000;
18
+ readonly 'quality.spineCheck': 330000;
19
+ };
20
+ /**
21
+ * The widest declared budget — the fail-safe default for a route name this map does not
22
+ * know. Derived from the tables rather than restated, so it cannot drift if a value moves.
23
+ * Spans the overrides too, so the unknown-route default is never TIGHTER than a budget
24
+ * already granted to a known route.
25
+ */
26
+ export declare const WIDEST_ROUTE_LATENCY_BUDGET_MS: number;
27
+ /**
28
+ * NULL-PROTOTYPE, deliberately (PR #533 review, Codex P2).
29
+ *
30
+ * A plain object literal inherits Object.prototype, so an unknown route name that
31
+ * collides with an inherited member — `toString`, `constructor`, `valueOf` — resolves to a
32
+ * TRUTHY function instead of falling through to the widest-budget default below. That
33
+ * yielded `AbortSignal.timeout(undefined)`, which THROWS before the call ever reaches the
34
+ * gateway — turning the fail-safe default into a hard failure.
35
+ *
36
+ * The SSOT scans an array (`GATEWAY_ROUTES.find`) and has no such hole, so a plain-object
37
+ * copy DISAGREED with the source it is generated from — precisely the connector drift this
38
+ * file exists to end. A null prototype restores parity for every consumer of the map at
39
+ * once (`latencyBudgetMsForRoute` here, `routeMayMutate` in both connectors' seams) rather
40
+ * than guarding each lookup and leaving the next one to rediscover it.
41
+ */
42
+ export declare const ROUTE_TYPE_BY_NAME: Readonly<Record<string, GatewayRouteType>>;
43
+ /** Budget for a route TYPE. Total over the three kinds the registry can produce. */
44
+ export declare function latencyBudgetMsForRouteType(type: GatewayRouteType): number;
45
+ /**
46
+ * Budget for a route by NAME.
47
+ *
48
+ * FAILS SAFE, DELIBERATELY: an unknown name gets the WIDEST budget, not the tightest and
49
+ * not a throw. Guessing low on a route we cannot classify would abort a call still
50
+ * legitimately in flight and re-create the exact false-failure this work package removes.
51
+ * Guessing high only delays surfacing a genuinely stuck call — and an unknown name is
52
+ * almost always a caller bug the gateway rejects in milliseconds anyway.
53
+ */
54
+ export declare function latencyBudgetMsForRoute(routeName: string): number;
55
+ //# sourceMappingURL=routeLatencyBudget.generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routeLatencyBudget.generated.d.ts","sourceRoot":"","sources":["../../src/generated/routeLatencyBudget.generated.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE/D,qEAAqE;AACrE,eAAO,MAAM,uBAAuB;;;;CAI1B,CAAC;AAEX;;;;;;;GAOG;AACH,eAAO,MAAM,gCAAgC;;;CAWnC,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,EAAE,MAG5C,CAAC;AAmQF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAGzE,CAAC;AAEF,oFAAoF;AACpF,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAE1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CASjE"}