@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.
- package/README.md +49 -9
- package/dist/__generated__/gateway.d.ts +4546 -0
- package/dist/__generated__/gateway.d.ts.map +1 -0
- package/dist/__generated__/gateway.js +9 -0
- package/dist/__generated__/gateway.js.map +1 -0
- package/dist/__generated__/talent-profile-zod-schemas.d.ts +1187 -0
- package/dist/__generated__/talent-profile-zod-schemas.d.ts.map +1 -0
- package/dist/__generated__/talent-profile-zod-schemas.js +1136 -0
- package/dist/__generated__/talent-profile-zod-schemas.js.map +1 -0
- package/dist/__generated__/talent-profile.d.ts +1397 -0
- package/dist/__generated__/talent-profile.d.ts.map +1 -0
- package/dist/__generated__/talent-profile.js +9 -0
- package/dist/__generated__/talent-profile.js.map +1 -0
- package/dist/__generated__/zod-schemas.d.ts +2895 -0
- package/dist/__generated__/zod-schemas.d.ts.map +1 -0
- package/dist/__generated__/zod-schemas.js +3121 -0
- package/dist/__generated__/zod-schemas.js.map +1 -0
- package/dist/__tests__/fixtures/profile/builders.d.ts +74 -0
- package/dist/__tests__/fixtures/profile/builders.d.ts.map +1 -0
- package/dist/__tests__/fixtures/profile/builders.js +196 -0
- package/dist/__tests__/fixtures/profile/builders.js.map +1 -0
- package/dist/__tests__/fixtures/profile/data.d.ts +39 -0
- package/dist/__tests__/fixtures/profile/data.d.ts.map +1 -0
- package/dist/__tests__/fixtures/profile/data.js +230 -0
- package/dist/__tests__/fixtures/profile/data.js.map +1 -0
- package/dist/__tests__/fixtures/profile/index.d.ts +9 -0
- package/dist/__tests__/fixtures/profile/index.d.ts.map +1 -0
- package/dist/__tests__/fixtures/profile/index.js +10 -0
- package/dist/__tests__/fixtures/profile/index.js.map +1 -0
- package/dist/__tests__/fixtures/profile/types.d.ts +53 -0
- package/dist/__tests__/fixtures/profile/types.d.ts.map +1 -0
- package/dist/__tests__/fixtures/profile/types.js +4 -0
- package/dist/__tests__/fixtures/profile/types.js.map +1 -0
- package/dist/auth/errors.d.ts +82 -0
- package/dist/auth/errors.d.ts.map +1 -0
- package/dist/auth/errors.js +68 -0
- package/dist/auth/errors.js.map +1 -0
- package/dist/auth.d.ts +192 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +294 -0
- package/dist/auth.js.map +1 -0
- package/dist/config.d.ts +212 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +349 -0
- package/dist/config.js.map +1 -0
- package/dist/configLock.d.ts +50 -0
- package/dist/configLock.d.ts.map +1 -0
- package/dist/configLock.js +88 -0
- package/dist/configLock.js.map +1 -0
- package/dist/configWriter.d.ts +97 -0
- package/dist/configWriter.d.ts.map +1 -0
- package/dist/configWriter.js +687 -0
- package/dist/configWriter.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/kill-switch.d.ts +161 -0
- package/dist/kill-switch.d.ts.map +1 -0
- package/dist/kill-switch.js +235 -0
- package/dist/kill-switch.js.map +1 -0
- package/dist/lib/date.d.ts +58 -0
- package/dist/lib/date.d.ts.map +1 -0
- package/dist/lib/date.js +104 -0
- package/dist/lib/date.js.map +1 -0
- package/dist/lib/diagnostic-log.d.ts +159 -0
- package/dist/lib/diagnostic-log.d.ts.map +1 -0
- package/dist/lib/diagnostic-log.js +186 -0
- package/dist/lib/diagnostic-log.js.map +1 -0
- package/dist/lib/package-version.d.ts +19 -0
- package/dist/lib/package-version.d.ts.map +1 -0
- package/dist/lib/package-version.js +38 -0
- package/dist/lib/package-version.js.map +1 -0
- package/dist/lib/redact.d.ts +153 -0
- package/dist/lib/redact.d.ts.map +1 -0
- package/dist/lib/redact.js +207 -0
- package/dist/lib/redact.js.map +1 -0
- package/dist/lib/text.d.ts +14 -0
- package/dist/lib/text.d.ts.map +1 -0
- package/dist/lib/text.js +21 -0
- package/dist/lib/text.js.map +1 -0
- package/dist/lib/wire-shape.d.ts +131 -0
- package/dist/lib/wire-shape.d.ts.map +1 -0
- package/dist/lib/wire-shape.js +376 -0
- package/dist/lib/wire-shape.js.map +1 -0
- package/dist/onepassword.d.ts +29 -0
- package/dist/onepassword.d.ts.map +1 -0
- package/dist/onepassword.js +112 -0
- package/dist/onepassword.js.map +1 -0
- package/dist/services/_shared/transport.d.ts +148 -0
- package/dist/services/_shared/transport.d.ts.map +1 -0
- package/dist/services/_shared/transport.js +102 -0
- package/dist/services/_shared/transport.js.map +1 -0
- package/dist/services/applications/index.d.ts +210 -0
- package/dist/services/applications/index.d.ts.map +1 -0
- package/dist/services/applications/index.js +240 -0
- package/dist/services/applications/index.js.map +1 -0
- package/dist/services/availability/index.d.ts +254 -0
- package/dist/services/availability/index.d.ts.map +1 -0
- package/dist/services/availability/index.js +310 -0
- package/dist/services/availability/index.js.map +1 -0
- package/dist/services/contracts/index.d.ts +132 -0
- package/dist/services/contracts/index.d.ts.map +1 -0
- package/dist/services/contracts/index.js +211 -0
- package/dist/services/contracts/index.js.map +1 -0
- package/dist/services/engagements/index.d.ts +504 -0
- package/dist/services/engagements/index.d.ts.map +1 -0
- package/dist/services/engagements/index.js +613 -0
- package/dist/services/engagements/index.js.map +1 -0
- package/dist/services/jobs/index.d.ts +490 -0
- package/dist/services/jobs/index.d.ts.map +1 -0
- package/dist/services/jobs/index.js +753 -0
- package/dist/services/jobs/index.js.map +1 -0
- package/dist/services/payments/index.d.ts +415 -0
- package/dist/services/payments/index.d.ts.map +1 -0
- package/dist/services/payments/index.js +636 -0
- package/dist/services/payments/index.js.map +1 -0
- package/dist/services/profile/__tests__/fixtures.d.ts +214 -0
- package/dist/services/profile/__tests__/fixtures.d.ts.map +1 -0
- package/dist/services/profile/__tests__/fixtures.js +176 -0
- package/dist/services/profile/__tests__/fixtures.js.map +1 -0
- package/dist/services/profile/basic/index.d.ts +390 -0
- package/dist/services/profile/basic/index.d.ts.map +1 -0
- package/dist/services/profile/basic/index.js +1007 -0
- package/dist/services/profile/basic/index.js.map +1 -0
- package/dist/services/profile/certifications/index.d.ts +74 -0
- package/dist/services/profile/certifications/index.d.ts.map +1 -0
- package/dist/services/profile/certifications/index.js +169 -0
- package/dist/services/profile/certifications/index.js.map +1 -0
- package/dist/services/profile/education/index.d.ts +73 -0
- package/dist/services/profile/education/index.d.ts.map +1 -0
- package/dist/services/profile/education/index.js +168 -0
- package/dist/services/profile/education/index.js.map +1 -0
- package/dist/services/profile/employment/index.d.ts +111 -0
- package/dist/services/profile/employment/index.d.ts.map +1 -0
- package/dist/services/profile/employment/index.js +202 -0
- package/dist/services/profile/employment/index.js.map +1 -0
- package/dist/services/profile/external/index.d.ts +219 -0
- package/dist/services/profile/external/index.d.ts.map +1 -0
- package/dist/services/profile/external/index.js +560 -0
- package/dist/services/profile/external/index.js.map +1 -0
- package/dist/services/profile/index.d.ts +24 -0
- package/dist/services/profile/index.d.ts.map +1 -0
- package/dist/services/profile/index.js +26 -0
- package/dist/services/profile/index.js.map +1 -0
- package/dist/services/profile/industries/index.d.ts +130 -0
- package/dist/services/profile/industries/index.d.ts.map +1 -0
- package/dist/services/profile/industries/index.js +292 -0
- package/dist/services/profile/industries/index.js.map +1 -0
- package/dist/services/profile/portfolio/index.d.ts +352 -0
- package/dist/services/profile/portfolio/index.d.ts.map +1 -0
- package/dist/services/profile/portfolio/index.js +833 -0
- package/dist/services/profile/portfolio/index.js.map +1 -0
- package/dist/services/profile/resume/index.d.ts +60 -0
- package/dist/services/profile/resume/index.d.ts.map +1 -0
- package/dist/services/profile/resume/index.js +212 -0
- package/dist/services/profile/resume/index.js.map +1 -0
- package/dist/services/profile/reviews/index.d.ts +137 -0
- package/dist/services/profile/reviews/index.d.ts.map +1 -0
- package/dist/services/profile/reviews/index.js +431 -0
- package/dist/services/profile/reviews/index.js.map +1 -0
- package/dist/services/profile/shared.d.ts +127 -0
- package/dist/services/profile/shared.d.ts.map +1 -0
- package/dist/services/profile/shared.js +155 -0
- package/dist/services/profile/shared.js.map +1 -0
- package/dist/services/profile/skills/index.d.ts +212 -0
- package/dist/services/profile/skills/index.d.ts.map +1 -0
- package/dist/services/profile/skills/index.js +461 -0
- package/dist/services/profile/skills/index.js.map +1 -0
- package/dist/services/profile/visas/index.d.ts +74 -0
- package/dist/services/profile/visas/index.d.ts.map +1 -0
- package/dist/services/profile/visas/index.js +306 -0
- package/dist/services/profile/visas/index.js.map +1 -0
- package/dist/services/timesheet/index.d.ts +326 -0
- package/dist/services/timesheet/index.d.ts.map +1 -0
- package/dist/services/timesheet/index.js +324 -0
- package/dist/services/timesheet/index.js.map +1 -0
- package/dist/services/translations.d.ts +79 -0
- package/dist/services/translations.d.ts.map +1 -0
- package/dist/services/translations.js +136 -0
- package/dist/services/translations.js.map +1 -0
- package/dist/transport-resilience.d.ts +136 -0
- package/dist/transport-resilience.d.ts.map +1 -0
- package/dist/transport-resilience.js +247 -0
- package/dist/transport-resilience.js.map +1 -0
- package/dist/transport.d.ts +408 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +691 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/package.json +40 -12
- 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":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAa9C,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,UAAW,SAAQ,UAAU;aAQtB,OAAO,EAAE,aAAa;aACtB,QAAQ,EAAE,MAAM;IARlC,SAAkB,IAAI,gBAAgB;IACtC,QAAQ,CAAC,IAAI,sBAAsB;IACnC,QAAQ,CAAC,QAAQ,SAEwE;gBAGvE,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,MAAM;IAKlC,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;CAWvE;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,oBAAqB,SAAQ,UAAU;aAShC,OAAO,EAAE,aAAa;aACtB,QAAQ,EAAE,MAAM;IATlC,SAAkB,IAAI,0BAA0B;IAChD,QAAQ,CAAC,IAAI,uBAAuB;IACpC,QAAQ,CAAC,QAAQ,SAGwD;gBAGvD,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,MAAM,EAChC,OAAO,GAAE,MAA8E;CAI1F;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,sBAAuB,SAAQ,UAAU;IACpD,SAAkB,IAAI,4BAA4B;IAClD,QAAQ,CAAC,IAAI,8BAA8B;IAC3C,QAAQ,CAAC,QAAQ,mFAAmF;IACpG,SAAkB,WAAW,QAAQ;gBAEzB,OAAO,GAAE,MAA0C;CAGhE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,aAAc,SAAQ,UAAU;aAUzB,OAAO,EAAE,aAAa;aACtB,QAAQ,EAAE,MAAM;aAChB,MAAM,EAAE,MAAM;aACd,QAAQ,EAAE,MAAM;IAZlC,SAAkB,IAAI,mBAAmB;IACzC,QAAQ,CAAC,IAAI,sBAAsB;IACnC,QAAQ,CAAC,QAAQ,SAIW;gBAGV,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM;IAKlC,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;CAYzG;AA2BD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,SAAS,CAGvG;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,EAAE,cAA6B,CAAC;AAsBhE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,aAAa,CAAC;IACvB,IAAI,EAAE,cAAc,CAAC;IACrB;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,EAAG,wBAAiC,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,OAAO,EAAE,aAAa,CAAC;IACvB;;;;;OAKG;IACH,SAAS,EAAE,OAAO,GAAG,cAAc,CAAC;IACpC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,gBAAgB,GAAG,aAAa,CAevE;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAKnF;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAsDtF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAsE7F;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,aAAa,CAAC;IACvB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACrC;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9B;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,EACpC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAC5B,QAAQ,CAWV;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,8BAA8B,CAAC,GAAG,EAAE,yBAAyB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA0E/G"}
|