@ttctl/core 0.0.0 → 0.1.0-rc.1

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 (195) hide show
  1. package/README.md +49 -9
  2. package/dist/__generated__/gateway.d.ts +4546 -0
  3. package/dist/__generated__/gateway.d.ts.map +1 -0
  4. package/dist/__generated__/gateway.js +9 -0
  5. package/dist/__generated__/gateway.js.map +1 -0
  6. package/dist/__generated__/talent-profile-zod-schemas.d.ts +1187 -0
  7. package/dist/__generated__/talent-profile-zod-schemas.d.ts.map +1 -0
  8. package/dist/__generated__/talent-profile-zod-schemas.js +1136 -0
  9. package/dist/__generated__/talent-profile-zod-schemas.js.map +1 -0
  10. package/dist/__generated__/talent-profile.d.ts +1397 -0
  11. package/dist/__generated__/talent-profile.d.ts.map +1 -0
  12. package/dist/__generated__/talent-profile.js +9 -0
  13. package/dist/__generated__/talent-profile.js.map +1 -0
  14. package/dist/__generated__/zod-schemas.d.ts +2895 -0
  15. package/dist/__generated__/zod-schemas.d.ts.map +1 -0
  16. package/dist/__generated__/zod-schemas.js +3121 -0
  17. package/dist/__generated__/zod-schemas.js.map +1 -0
  18. package/dist/__tests__/fixtures/profile/builders.d.ts +74 -0
  19. package/dist/__tests__/fixtures/profile/builders.d.ts.map +1 -0
  20. package/dist/__tests__/fixtures/profile/builders.js +196 -0
  21. package/dist/__tests__/fixtures/profile/builders.js.map +1 -0
  22. package/dist/__tests__/fixtures/profile/data.d.ts +39 -0
  23. package/dist/__tests__/fixtures/profile/data.d.ts.map +1 -0
  24. package/dist/__tests__/fixtures/profile/data.js +230 -0
  25. package/dist/__tests__/fixtures/profile/data.js.map +1 -0
  26. package/dist/__tests__/fixtures/profile/index.d.ts +9 -0
  27. package/dist/__tests__/fixtures/profile/index.d.ts.map +1 -0
  28. package/dist/__tests__/fixtures/profile/index.js +10 -0
  29. package/dist/__tests__/fixtures/profile/index.js.map +1 -0
  30. package/dist/__tests__/fixtures/profile/types.d.ts +53 -0
  31. package/dist/__tests__/fixtures/profile/types.d.ts.map +1 -0
  32. package/dist/__tests__/fixtures/profile/types.js +4 -0
  33. package/dist/__tests__/fixtures/profile/types.js.map +1 -0
  34. package/dist/auth/errors.d.ts +82 -0
  35. package/dist/auth/errors.d.ts.map +1 -0
  36. package/dist/auth/errors.js +68 -0
  37. package/dist/auth/errors.js.map +1 -0
  38. package/dist/auth.d.ts +192 -0
  39. package/dist/auth.d.ts.map +1 -0
  40. package/dist/auth.js +294 -0
  41. package/dist/auth.js.map +1 -0
  42. package/dist/config.d.ts +212 -0
  43. package/dist/config.d.ts.map +1 -0
  44. package/dist/config.js +349 -0
  45. package/dist/config.js.map +1 -0
  46. package/dist/configLock.d.ts +50 -0
  47. package/dist/configLock.d.ts.map +1 -0
  48. package/dist/configLock.js +88 -0
  49. package/dist/configLock.js.map +1 -0
  50. package/dist/configWriter.d.ts +97 -0
  51. package/dist/configWriter.d.ts.map +1 -0
  52. package/dist/configWriter.js +687 -0
  53. package/dist/configWriter.js.map +1 -0
  54. package/dist/index.d.ts +37 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +28 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/kill-switch.d.ts +161 -0
  59. package/dist/kill-switch.d.ts.map +1 -0
  60. package/dist/kill-switch.js +235 -0
  61. package/dist/kill-switch.js.map +1 -0
  62. package/dist/lib/date.d.ts +58 -0
  63. package/dist/lib/date.d.ts.map +1 -0
  64. package/dist/lib/date.js +104 -0
  65. package/dist/lib/date.js.map +1 -0
  66. package/dist/lib/diagnostic-log.d.ts +159 -0
  67. package/dist/lib/diagnostic-log.d.ts.map +1 -0
  68. package/dist/lib/diagnostic-log.js +186 -0
  69. package/dist/lib/diagnostic-log.js.map +1 -0
  70. package/dist/lib/package-version.d.ts +19 -0
  71. package/dist/lib/package-version.d.ts.map +1 -0
  72. package/dist/lib/package-version.js +38 -0
  73. package/dist/lib/package-version.js.map +1 -0
  74. package/dist/lib/redact.d.ts +153 -0
  75. package/dist/lib/redact.d.ts.map +1 -0
  76. package/dist/lib/redact.js +207 -0
  77. package/dist/lib/redact.js.map +1 -0
  78. package/dist/lib/text.d.ts +14 -0
  79. package/dist/lib/text.d.ts.map +1 -0
  80. package/dist/lib/text.js +21 -0
  81. package/dist/lib/text.js.map +1 -0
  82. package/dist/lib/wire-shape.d.ts +131 -0
  83. package/dist/lib/wire-shape.d.ts.map +1 -0
  84. package/dist/lib/wire-shape.js +376 -0
  85. package/dist/lib/wire-shape.js.map +1 -0
  86. package/dist/onepassword.d.ts +29 -0
  87. package/dist/onepassword.d.ts.map +1 -0
  88. package/dist/onepassword.js +112 -0
  89. package/dist/onepassword.js.map +1 -0
  90. package/dist/services/_shared/transport.d.ts +148 -0
  91. package/dist/services/_shared/transport.d.ts.map +1 -0
  92. package/dist/services/_shared/transport.js +102 -0
  93. package/dist/services/_shared/transport.js.map +1 -0
  94. package/dist/services/applications/index.d.ts +210 -0
  95. package/dist/services/applications/index.d.ts.map +1 -0
  96. package/dist/services/applications/index.js +240 -0
  97. package/dist/services/applications/index.js.map +1 -0
  98. package/dist/services/availability/index.d.ts +254 -0
  99. package/dist/services/availability/index.d.ts.map +1 -0
  100. package/dist/services/availability/index.js +310 -0
  101. package/dist/services/availability/index.js.map +1 -0
  102. package/dist/services/contracts/index.d.ts +132 -0
  103. package/dist/services/contracts/index.d.ts.map +1 -0
  104. package/dist/services/contracts/index.js +211 -0
  105. package/dist/services/contracts/index.js.map +1 -0
  106. package/dist/services/engagements/index.d.ts +504 -0
  107. package/dist/services/engagements/index.d.ts.map +1 -0
  108. package/dist/services/engagements/index.js +613 -0
  109. package/dist/services/engagements/index.js.map +1 -0
  110. package/dist/services/jobs/index.d.ts +490 -0
  111. package/dist/services/jobs/index.d.ts.map +1 -0
  112. package/dist/services/jobs/index.js +753 -0
  113. package/dist/services/jobs/index.js.map +1 -0
  114. package/dist/services/payments/index.d.ts +415 -0
  115. package/dist/services/payments/index.d.ts.map +1 -0
  116. package/dist/services/payments/index.js +636 -0
  117. package/dist/services/payments/index.js.map +1 -0
  118. package/dist/services/profile/__tests__/fixtures.d.ts +214 -0
  119. package/dist/services/profile/__tests__/fixtures.d.ts.map +1 -0
  120. package/dist/services/profile/__tests__/fixtures.js +176 -0
  121. package/dist/services/profile/__tests__/fixtures.js.map +1 -0
  122. package/dist/services/profile/basic/index.d.ts +390 -0
  123. package/dist/services/profile/basic/index.d.ts.map +1 -0
  124. package/dist/services/profile/basic/index.js +1007 -0
  125. package/dist/services/profile/basic/index.js.map +1 -0
  126. package/dist/services/profile/certifications/index.d.ts +74 -0
  127. package/dist/services/profile/certifications/index.d.ts.map +1 -0
  128. package/dist/services/profile/certifications/index.js +169 -0
  129. package/dist/services/profile/certifications/index.js.map +1 -0
  130. package/dist/services/profile/education/index.d.ts +73 -0
  131. package/dist/services/profile/education/index.d.ts.map +1 -0
  132. package/dist/services/profile/education/index.js +168 -0
  133. package/dist/services/profile/education/index.js.map +1 -0
  134. package/dist/services/profile/employment/index.d.ts +111 -0
  135. package/dist/services/profile/employment/index.d.ts.map +1 -0
  136. package/dist/services/profile/employment/index.js +202 -0
  137. package/dist/services/profile/employment/index.js.map +1 -0
  138. package/dist/services/profile/external/index.d.ts +219 -0
  139. package/dist/services/profile/external/index.d.ts.map +1 -0
  140. package/dist/services/profile/external/index.js +560 -0
  141. package/dist/services/profile/external/index.js.map +1 -0
  142. package/dist/services/profile/index.d.ts +24 -0
  143. package/dist/services/profile/index.d.ts.map +1 -0
  144. package/dist/services/profile/index.js +26 -0
  145. package/dist/services/profile/index.js.map +1 -0
  146. package/dist/services/profile/industries/index.d.ts +130 -0
  147. package/dist/services/profile/industries/index.d.ts.map +1 -0
  148. package/dist/services/profile/industries/index.js +292 -0
  149. package/dist/services/profile/industries/index.js.map +1 -0
  150. package/dist/services/profile/portfolio/index.d.ts +352 -0
  151. package/dist/services/profile/portfolio/index.d.ts.map +1 -0
  152. package/dist/services/profile/portfolio/index.js +833 -0
  153. package/dist/services/profile/portfolio/index.js.map +1 -0
  154. package/dist/services/profile/resume/index.d.ts +60 -0
  155. package/dist/services/profile/resume/index.d.ts.map +1 -0
  156. package/dist/services/profile/resume/index.js +212 -0
  157. package/dist/services/profile/resume/index.js.map +1 -0
  158. package/dist/services/profile/reviews/index.d.ts +137 -0
  159. package/dist/services/profile/reviews/index.d.ts.map +1 -0
  160. package/dist/services/profile/reviews/index.js +431 -0
  161. package/dist/services/profile/reviews/index.js.map +1 -0
  162. package/dist/services/profile/shared.d.ts +127 -0
  163. package/dist/services/profile/shared.d.ts.map +1 -0
  164. package/dist/services/profile/shared.js +155 -0
  165. package/dist/services/profile/shared.js.map +1 -0
  166. package/dist/services/profile/skills/index.d.ts +212 -0
  167. package/dist/services/profile/skills/index.d.ts.map +1 -0
  168. package/dist/services/profile/skills/index.js +461 -0
  169. package/dist/services/profile/skills/index.js.map +1 -0
  170. package/dist/services/profile/visas/index.d.ts +74 -0
  171. package/dist/services/profile/visas/index.d.ts.map +1 -0
  172. package/dist/services/profile/visas/index.js +306 -0
  173. package/dist/services/profile/visas/index.js.map +1 -0
  174. package/dist/services/timesheet/index.d.ts +326 -0
  175. package/dist/services/timesheet/index.d.ts.map +1 -0
  176. package/dist/services/timesheet/index.js +324 -0
  177. package/dist/services/timesheet/index.js.map +1 -0
  178. package/dist/services/translations.d.ts +79 -0
  179. package/dist/services/translations.d.ts.map +1 -0
  180. package/dist/services/translations.js +136 -0
  181. package/dist/services/translations.js.map +1 -0
  182. package/dist/transport-resilience.d.ts +136 -0
  183. package/dist/transport-resilience.d.ts.map +1 -0
  184. package/dist/transport-resilience.js +247 -0
  185. package/dist/transport-resilience.js.map +1 -0
  186. package/dist/transport.d.ts +408 -0
  187. package/dist/transport.d.ts.map +1 -0
  188. package/dist/transport.js +691 -0
  189. package/dist/transport.js.map +1 -0
  190. package/dist/types.d.ts +41 -0
  191. package/dist/types.d.ts.map +1 -0
  192. package/dist/types.js +18 -0
  193. package/dist/types.js.map +1 -0
  194. package/package.json +40 -12
  195. package/index.js +0 -7
@@ -0,0 +1,408 @@
1
+ import type { BrowserProfile } from "node-wreq";
2
+ import { TtctlError } from "./auth/errors.js";
3
+ import type { GraphQLRequest, ToptalSurface } from "./types.js";
4
+ /**
5
+ * Thrown when an impersonated surface returns HTTP 403.
6
+ *
7
+ * Empirically, Chrome TLS impersonation alone passes Cloudflare on the
8
+ * surfaces TTCtl currently uses (`talent-profile`, `scheduler`) — see
9
+ * `hq/engineering/adr/ADR-005-auth-model.md`. A 403 here therefore
10
+ * means Cloudflare has flipped a feature flag (e.g. activated a Turnstile
11
+ * challenge or a new bot-management heuristic) that we don't currently
12
+ * handle. There is no documented manual workaround; the user is asked to
13
+ * file an issue so we can investigate.
14
+ *
15
+ * Refined under issue #77 to extend `TtctlError`. Carries the stable
16
+ * `code = 'CF_403_CLEARANCE'` and a short `recovery` hint that the CLI /
17
+ * MCP surfaces render alongside the existing multi-line `message`.
18
+ *
19
+ * See also `Cf403PersistentError` (defined for future use when an explicit
20
+ * retry-with-fresh-clearance heuristic is added — currently TTCtl cannot
21
+ * distinguish "clearance expired" from "persistent block" at runtime, so
22
+ * `Cf403Error` is the only class actually thrown by the transport).
23
+ */
24
+ export declare class Cf403Error extends TtctlError {
25
+ readonly surface: ToptalSurface;
26
+ readonly endpoint: string;
27
+ readonly name = "Cf403Error";
28
+ readonly code = "CF_403_CLEARANCE";
29
+ readonly recovery: string;
30
+ constructor(surface: ToptalSurface, endpoint: string);
31
+ static formatMessage(surface: ToptalSurface, endpoint: string): string;
32
+ }
33
+ /**
34
+ * Thrown when Cloudflare is *persistently* blocking an impersonated surface
35
+ * — i.e. clearance refresh and re-attempts have failed and the only
36
+ * remaining recovery is the cookie-jar break-glass path documented in
37
+ * `SECURITY.md`.
38
+ *
39
+ * **Currently defined for future use.** TTCtl's transport has no automated
40
+ * retry-with-fresh-clearance heuristic, so a single 403 cannot be
41
+ * distinguished from a persistent block at runtime. The transport throws
42
+ * the more general `Cf403Error`. When a future iteration adds retry +
43
+ * clearance refresh, that layer will re-classify a confirmed-persistent
44
+ * block to `Cf403PersistentError`. See issue #77 § Out of Scope.
45
+ */
46
+ export declare class Cf403PersistentError extends TtctlError {
47
+ readonly surface: ToptalSurface;
48
+ readonly endpoint: string;
49
+ readonly name = "Cf403PersistentError";
50
+ readonly code = "CF_403_PERSISTENT";
51
+ readonly recovery: string;
52
+ constructor(surface: ToptalSurface, endpoint: string, message?: string);
53
+ }
54
+ /**
55
+ * Thrown when the scheduler bearer token has expired.
56
+ *
57
+ * **Scaffolded for post-v1 scheduler-surface coverage.** TTCtl currently
58
+ * has no scheduler operations wired in; transport routes scheduler →
59
+ * impersonated, but no service module issues scheduler GraphQL calls. When
60
+ * scheduler coverage lands (post-v1), this class will be thrown by the
61
+ * scheduler service module on bearer-expiry detection.
62
+ *
63
+ * `autoRecover = true` signals to the transport layer that an automated
64
+ * re-mint via `GetTopSchedulerToken` should be attempted once before
65
+ * surfacing this error. The auto-recovery contract is intentionally
66
+ * defined here so callers can plan against it; the actual re-mint
67
+ * orchestration ships with the scheduler surface implementation.
68
+ */
69
+ export declare class SchedulerBearerExpired extends TtctlError {
70
+ readonly name = "SchedulerBearerExpired";
71
+ readonly code = "SCHEDULER_BEARER_EXPIRED";
72
+ readonly recovery = "Scheduler bearer token expired; will be re-minted automatically on next call.";
73
+ readonly autoRecover = true;
74
+ constructor(message?: string);
75
+ }
76
+ /**
77
+ * Thrown when a transport receives an HTTP 3xx redirect carrying a
78
+ * `Location` header.
79
+ *
80
+ * TTCtl talks to fixed GraphQL endpoints; none of them legitimately
81
+ * redirect. A 3xx is therefore an anomaly — most plausibly Cloudflare or
82
+ * Toptal's edge flipping an infrastructure flag (mirror of the
83
+ * {@link Cf403Error} case), or a misconfigured DNS / edge change.
84
+ *
85
+ * Defense-in-depth posture (issue #268): every transport entry point has a
86
+ * no-follow redirect policy — `redirect: "manual"` pinned explicitly on
87
+ * `node-wreq`, and structurally on `undici` (its `request()` on the
88
+ * default dispatcher never follows redirects; redirect following is an
89
+ * opt-in interceptor TTCtl does not install). A 3xx is therefore returned
90
+ * verbatim rather than followed, and {@link executeWithResilience}
91
+ * inspects the status and throws this typed error rather than handing the
92
+ * redirect body back to a caller that would not know how to interpret it.
93
+ *
94
+ * Refusing to follow is the security-relevant property: a followed
95
+ * cross-origin redirect would leak the request body (operation name +
96
+ * variables) to the redirect target even though `node-wreq` strips the
97
+ * `authorization` header on cross-origin hops. Pinning `node-wreq`'s
98
+ * policy keeps that guarantee from depending on a transitive library
99
+ * default (it ships `redirect: "follow"` by default).
100
+ *
101
+ * Carries `surface`, `endpoint`, `status`, and `location` — the
102
+ * `Location` header value is a URL, not a credential, so it is safe to
103
+ * surface in the message and in diagnostic traces for operator triage.
104
+ */
105
+ export declare class RedirectError extends TtctlError {
106
+ readonly surface: ToptalSurface;
107
+ readonly endpoint: string;
108
+ readonly status: number;
109
+ readonly location: string;
110
+ readonly name = "RedirectError";
111
+ readonly code = "REDIRECT_REFUSED";
112
+ readonly recovery: string;
113
+ constructor(surface: ToptalSurface, endpoint: string, status: number, location: string);
114
+ static formatMessage(surface: ToptalSurface, endpoint: string, status: number, location: string): string;
115
+ }
116
+ /**
117
+ * Return the `Location` value if `status` + `headers` describe an HTTP
118
+ * redirect — a {@link REDIRECT_STATUS_CODES} status carrying a `Location`
119
+ * header — and `undefined` otherwise.
120
+ *
121
+ * Shared by {@link executeWithResilience} and the photo-upload path's
122
+ * hand-rolled `node-wreq` fetch (`multipartImpersonatedFetch` in
123
+ * `services/profile/basic/index.ts`) so every transport path enforces the
124
+ * same no-follow posture (issue #268). A 3xx WITHOUT a `Location` header
125
+ * is not a redirect — there is nothing to follow — so it returns
126
+ * `undefined` and the caller returns the response verbatim.
127
+ *
128
+ * The caller decides what to do with a positive result: throw a
129
+ * {@link RedirectError}. Detection and reaction are split so each call
130
+ * site can emit its own diagnostic-trace record before throwing, matching
131
+ * how each already handles the {@link Cf403Error} case.
132
+ */
133
+ export declare function getRedirectLocation(status: number, headers: Record<string, string>): string | undefined;
134
+ /**
135
+ * TLS-impersonation profile. Pinned as a coupled pair with `USER_AGENT` —
136
+ * see the `tls-fingerprinting` skill on identity-catalog freshness: WAFs
137
+ * cross-validate the User-Agent string against the JA4 hash, so the profile
138
+ * and UA must both name the same Chrome version. Bump them together when
139
+ * `node-wreq` publishes a newer profile.
140
+ *
141
+ * Currently `chrome_145` because that is the freshest profile published in
142
+ * `node-wreq@2.2.1`. The Rust upstream `wreq` crate has `chrome_146` in
143
+ * its release-candidate stream but the Node bindings have not yet shipped a
144
+ * matching release. Track upstream and bump.
145
+ */
146
+ export declare const IMPERSONATE_PROFILE: BrowserProfile;
147
+ export interface TransportRequest {
148
+ surface: ToptalSurface;
149
+ body: GraphQLRequest;
150
+ /**
151
+ * Bearer-style session token captured from `EmailPasswordSignIn`'s
152
+ * `SignInPayload.token`. When present, sent as
153
+ * `Authorization: Token token=<X>` — the canonical Rails
154
+ * `ActionController::HttpAuthentication::Token` format that Toptal's
155
+ * GraphQL services use to authenticate. Empirically validated as the
156
+ * sole auth mechanism on both the mobile gateway and the
157
+ * Cloudflare-protected `talent-profile` surface (see issue #59 and
158
+ * `hq/engineering/adr/ADR-005-auth-model.md`).
159
+ */
160
+ authToken?: string;
161
+ /**
162
+ * Caller-supplied abort signal. When the signal aborts, an in-flight
163
+ * request is cancelled and any pending retry backoff sleep returns
164
+ * immediately. Wired through to the MCP server's per-tool-call
165
+ * cancellation so a client that revokes a tool invocation actually
166
+ * tears down the upstream socket (issue #229).
167
+ *
168
+ * The transport composes this signal with a per-attempt internal
169
+ * timeout (default 30 s, overridable via `TTCTL_TRANSPORT_TIMEOUT_MS`)
170
+ * via `AbortSignal.any`, so a wedged Cloudflare connection times out
171
+ * even when no caller signal is supplied.
172
+ */
173
+ signal?: AbortSignal;
174
+ }
175
+ export interface TransportResponse {
176
+ status: number;
177
+ headers: Record<string, string>;
178
+ body: unknown;
179
+ }
180
+ /**
181
+ * Token redaction marker used by {@link buildDryRunPreview} when an
182
+ * `authToken` is present on the source request. Exposed as a constant so
183
+ * tests assert on the exact wire shape and so future code never reaches
184
+ * for the bearer literal by mistake.
185
+ */
186
+ export declare const DRY_RUN_REDACTED_AUTHORIZATION: "Token token=<redacted>";
187
+ /**
188
+ * Structured "would-have-sent" preview returned by mutation entry points
189
+ * when invoked with `dryRun: true` (issue #52). Mirrors the shape an
190
+ * actual {@link stockTransport} or {@link impersonatedTransport} call
191
+ * would build internally, with the bearer token replaced by
192
+ * {@link DRY_RUN_REDACTED_AUTHORIZATION} so the preview can be safely
193
+ * emitted to stdout / piped to `jq` without leaking session credentials.
194
+ *
195
+ * The variable shape, operation name, and surface match the request that
196
+ * WOULD have been sent had `dryRun` been false — including any
197
+ * placeholder fields (e.g. `profileId`) that would have been resolved via
198
+ * a sibling read call at execution time. Mutation entry points
199
+ * substitute placeholder strings rather than firing the read side, so
200
+ * the dry-run path has zero network I/O — every transport (read or
201
+ * write) stays uncalled. See `set()` in `services/profile/basic/index.ts`
202
+ * for the canonical pattern.
203
+ *
204
+ * Wire shape (`{ operation, variables, transport, surface, headers }`)
205
+ * is locked under the v0.4 envelope contract (#128) — see
206
+ * {@link DryRunEnvelope} on the CLI side.
207
+ */
208
+ export interface DryRunPreview {
209
+ /**
210
+ * Logical surface that would have been called. Drives the transport
211
+ * choice (stock vs impersonated) and is surfaced verbatim in the wire
212
+ * payload for downstream tooling.
213
+ */
214
+ surface: ToptalSurface;
215
+ /**
216
+ * Transport classification — `"stock"` for the mobile gateway,
217
+ * `"impersonated"` for Cloudflare-protected surfaces. Derived from
218
+ * {@link SURFACES_REQUIRING_IMPERSONATION} so the preview always
219
+ * reflects the actual transport that would have been used.
220
+ */
221
+ transport: "stock" | "impersonated";
222
+ /**
223
+ * Concrete URL for the surface — read off {@link SURFACE_ENDPOINTS}.
224
+ * Useful for piping into curl-style replay tooling (post-AC future
225
+ * work; tracked in #52 § Out of Scope).
226
+ */
227
+ endpoint: string;
228
+ /** GraphQL operation name (e.g. `"UPDATE_BASIC_INFO"`). */
229
+ operationName: string;
230
+ /**
231
+ * GraphQL variables payload that would have been sent. Mutation entry
232
+ * points substitute placeholder strings for fields that would be
233
+ * resolved via sibling read calls at execution time — callers reading
234
+ * the preview should NOT treat placeholder values as real ids.
235
+ */
236
+ variables: Record<string, unknown>;
237
+ /**
238
+ * Headers as they would be sent on the wire, with the `authorization`
239
+ * value replaced by {@link DRY_RUN_REDACTED_AUTHORIZATION} when an
240
+ * `authToken` was set on the source request. All other headers
241
+ * (accept, accept-language, content-type, origin, referer, etc.)
242
+ * surface verbatim — they carry no session-bound material.
243
+ */
244
+ headers: Record<string, string>;
245
+ }
246
+ /**
247
+ * Build a {@link DryRunPreview} from a {@link TransportRequest} without
248
+ * invoking any transport. Pure — no I/O, no allocations beyond the
249
+ * returned object.
250
+ *
251
+ * The headers projection mirrors what {@link stockTransport} and
252
+ * {@link impersonatedTransport} would have set (`COMMON_HEADERS` plus
253
+ * `authorization` when `authToken` is present), with the bearer value
254
+ * redacted to {@link DRY_RUN_REDACTED_AUTHORIZATION}. The transport
255
+ * classification is derived from
256
+ * {@link SURFACES_REQUIRING_IMPERSONATION} so changes to that set
257
+ * propagate automatically.
258
+ *
259
+ * Mutation entry points call this AFTER they've populated their
260
+ * `body.variables` with placeholder substitutions for any fields that
261
+ * would have been resolved at execution time (e.g. `profileId`) —
262
+ * keeping the read-side transport call out of the dry-run path entirely.
263
+ * See `set()` in `services/profile/basic/index.ts` for the pattern.
264
+ */
265
+ export declare function buildDryRunPreview(req: TransportRequest): DryRunPreview;
266
+ /**
267
+ * Choose transport per surface. The mobile gateway accepts stock TLS;
268
+ * `talent-profile` and `scheduler` require Chrome TLS-fingerprint impersonation
269
+ * to clear Cloudflare's bot-management.
270
+ */
271
+ export declare function callSurface(req: TransportRequest): Promise<TransportResponse>;
272
+ /**
273
+ * Stock HTTP via undici. Used for the mobile gateway endpoint, which doesn't
274
+ * gate on TLS fingerprint.
275
+ *
276
+ * Resilience (#229): wraps the network call in a retry loop that handles
277
+ * HTTP 429 (with `Retry-After` honoring) and 5xx with bounded exponential
278
+ * backoff, applies a per-attempt timeout, and propagates the caller's
279
+ * `AbortSignal` so an MCP client cancel actually tears down the in-flight
280
+ * request. Final failures surface as a typed {@link TransportError}.
281
+ */
282
+ export declare function stockTransport(req: TransportRequest): Promise<TransportResponse>;
283
+ /**
284
+ * Impersonated HTTP via `node-wreq` (Rust + BoringSSL). Used for the
285
+ * Cloudflare-protected `talent-profile` and `scheduler` surfaces.
286
+ *
287
+ * The `browser` option drives node-wreq's TLS ClientHello and HTTP/2
288
+ * SETTINGS frame to match the bundled Chrome profile (see `IMPERSONATE_PROFILE`).
289
+ * Empirically this fingerprint alone clears Cloudflare on the surfaces TTCtl
290
+ * uses; no `cf_clearance` cookie is required in the happy path.
291
+ *
292
+ * Header-tuple ordering is left as a future tightening — currently we pass
293
+ * a plain `Record<string, string>` matching `stockTransport`'s shape so the
294
+ * two transports stay symmetric. JA4H header-name ordering is a secondary
295
+ * detection vector relative to JA4 / Akamai HTTP/2; revisit if empirical
296
+ * blocks indicate it matters.
297
+ *
298
+ * Resilience (#229): wraps the network call in the same retry / timeout /
299
+ * abort loop as {@link stockTransport}. `Cf403Error` propagates as a
300
+ * non-retryable typed error — the 403 is signalled to Cloudflare's
301
+ * bot-management WAF, not a transient condition.
302
+ */
303
+ export declare function impersonatedTransport(req: TransportRequest): Promise<TransportResponse>;
304
+ /**
305
+ * One file slot in a GraphQL multipart request — the binary content plus the
306
+ * filename and (optional) content-type the server will see in the
307
+ * `Content-Disposition` of the corresponding form part. The variable path
308
+ * the file binds to is supplied separately via the `map` argument of
309
+ * {@link buildGraphQLMultipart}, keeping this struct purely about file
310
+ * material.
311
+ */
312
+ export interface MultipartFile {
313
+ filename: string;
314
+ content: Buffer | Uint8Array;
315
+ contentType?: string;
316
+ }
317
+ /**
318
+ * Multipart variant of {@link TransportRequest} for GraphQL operations that
319
+ * carry one or more `Upload`-typed variables. The caller supplies the file
320
+ * material and a `map` describing where each file slots into the
321
+ * `variables` tree (per the GraphQL multipart request spec —
322
+ * https://github.com/jaydenseric/graphql-multipart-request-spec).
323
+ *
324
+ * The `body` carries the operation envelope as in JSON requests; the
325
+ * variables it sends include `null` placeholders at the file slots, and the
326
+ * `map` form field tells the server how to reconstitute them from the
327
+ * trailing file parts.
328
+ */
329
+ export interface MultipartTransportRequest {
330
+ surface: ToptalSurface;
331
+ body: GraphQLRequest;
332
+ authToken?: string;
333
+ /**
334
+ * Files keyed by an arbitrary slot label. The label is what the spec
335
+ * calls the "file part name" — appears as the form-part name (`"0"`,
336
+ * `"1"`, …) and as the key in `map` that points to the variable path(s)
337
+ * the file binds to.
338
+ */
339
+ files: Record<string, MultipartFile>;
340
+ /**
341
+ * GraphQL multipart map: `slotLabel → [variablePath, ...]`. Variable
342
+ * paths are dotted strings (e.g. `"variables.input.file"`). The same file
343
+ * may bind to multiple variable paths (the spec allows it) but TTCtl's
344
+ * services use a 1:1 mapping today.
345
+ */
346
+ map: Record<string, string[]>;
347
+ /**
348
+ * Caller-supplied abort signal. See {@link TransportRequest.signal} for
349
+ * full semantics — wired identically into the multipart upload path so
350
+ * an MCP `cancel` request actually tears down the file upload mid-flight.
351
+ */
352
+ signal?: AbortSignal;
353
+ }
354
+ /**
355
+ * Build a `globalThis.FormData` payload conforming to the GraphQL multipart
356
+ * request spec (https://github.com/jaydenseric/graphql-multipart-request-spec).
357
+ * The wire layout is:
358
+ *
359
+ * ```
360
+ * --boundary
361
+ * Content-Disposition: form-data; name="operations"
362
+ * <JSON-encoded { operationName, query, variables }>
363
+ *
364
+ * --boundary
365
+ * Content-Disposition: form-data; name="map"
366
+ * <JSON-encoded { "0": ["variables.input.file"], ... }>
367
+ *
368
+ * --boundary
369
+ * Content-Disposition: form-data; name="0"; filename="<filename>"
370
+ * Content-Type: <contentType>
371
+ * <binary>
372
+ *
373
+ * --boundary--
374
+ * ```
375
+ *
376
+ * `node-wreq`'s `BodyInit` accepts `FormData` directly (verified against
377
+ * `node-wreq@2.2.1`'s `dist/types/shared.d.ts` — `BodyInit` includes
378
+ * `FormData`). When the body is a `FormData`, the runtime sets the
379
+ * `Content-Type: multipart/form-data; boundary=...` header automatically;
380
+ * the caller should NOT pre-set a JSON content-type or it will be
381
+ * overwritten with the boundary-aware multipart one.
382
+ *
383
+ * Pure function — no I/O. Tests construct expected `FormData` instances
384
+ * and inspect the entries via `for-of` iteration.
385
+ */
386
+ export declare function buildGraphQLMultipart(body: GraphQLRequest, files: Record<string, MultipartFile>, map: Record<string, string[]>): FormData;
387
+ /**
388
+ * Multipart variant of {@link impersonatedTransport}. Sends a
389
+ * GraphQL-multipart-spec request through the impersonated transport so
390
+ * file-upload mutations (`uploadResume`, `uploadPortfolioCover`,
391
+ * `uploadPortfolioFile`) clear Cloudflare on the `talent-profile` surface.
392
+ *
393
+ * Why a separate function rather than overloading `impersonatedTransport`:
394
+ * the JSON path sets `content-type: application/json` and stringifies the
395
+ * body; the multipart path lets the runtime supply the multipart
396
+ * content-type with its own boundary and passes the `FormData` through
397
+ * unchanged. The two paths have different body wire formats and different
398
+ * header expectations, so they are kept as separate functions for clarity.
399
+ *
400
+ * Errors:
401
+ * - `Cf403Error` on HTTP 403 (Cloudflare bot-management has tightened — see
402
+ * the `Cf403Error` doc-comment for the recovery hint).
403
+ * - All other transport-level failures propagate as the underlying
404
+ * `node-wreq` error; service callers wrap them in their domain-specific
405
+ * `*Error` with `code: 'NETWORK_ERROR'`.
406
+ */
407
+ export declare function impersonatedMultipartTransport(req: MultipartTransportRequest): Promise<TransportResponse>;
408
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}