@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,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized redaction primitives for diagnostic logging and pattern-leak
|
|
3
|
+
* detection.
|
|
4
|
+
*
|
|
5
|
+
* This module is the **single source of truth** for "what is a secret" across
|
|
6
|
+
* the project — both the runtime debug-log path (issue #139) and the static
|
|
7
|
+
* `check-secret-leakage` script consume the constants defined here. Any
|
|
8
|
+
* future addition to either redaction (runtime) or detection (lint) MUST
|
|
9
|
+
* extend these exports rather than redefining them locally.
|
|
10
|
+
*
|
|
11
|
+
* Consumers:
|
|
12
|
+
*
|
|
13
|
+
* - `packages/core/src/lib/diagnostic-log.ts` calls {@link redactHeaders} /
|
|
14
|
+
* {@link redactBody} before emitting any `--verbose` / `--debug` line
|
|
15
|
+
* to stderr.
|
|
16
|
+
* - `scripts/check-secret-leakage.ts` consumes {@link BEARER_PATTERN_SOURCE}
|
|
17
|
+
* to scan tracked source for accidentally committed Toptal session
|
|
18
|
+
* bearers.
|
|
19
|
+
*
|
|
20
|
+
* Wire shape is stable; downstream tools and tests identify redaction sites
|
|
21
|
+
* by exact match against {@link REDACTED}. Patterns are append-only — never
|
|
22
|
+
* remove an entry, only add. Tests assert that no `authToken` value or
|
|
23
|
+
* cookie value can appear verbatim in the output of either consumer.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Replacement marker for redacted scalar values. Stable wire-shape so
|
|
27
|
+
* downstream tools and tests can identify redaction sites by exact match.
|
|
28
|
+
*/
|
|
29
|
+
export declare const REDACTED: "***REDACTED***";
|
|
30
|
+
/**
|
|
31
|
+
* Source pattern for Toptal Talent's session bearer token, exported as a
|
|
32
|
+
* STRING (not a `RegExp`) so non-TypeScript consumers (the
|
|
33
|
+
* `scripts/check-secret-leakage.ts` lint script before its own
|
|
34
|
+
* `new RegExp(..., "g")` construction) can adopt the canonical pattern
|
|
35
|
+
* without paying for any module-load-time `g`-flag state. The TS-side
|
|
36
|
+
* {@link BEARER_PATTERN} below wraps this source in a fresh `RegExp` per
|
|
37
|
+
* import so callers do not share the `lastIndex` cursor (`/g` flag is
|
|
38
|
+
* stateful per-instance).
|
|
39
|
+
*
|
|
40
|
+
* The bearer shape is `user_<24hex>_<20alnum>` — the canonical output of
|
|
41
|
+
* Toptal's `EmailPasswordSignIn` mutation (per
|
|
42
|
+
* `hq/engineering/adr/ADR-005-auth-model.md`). Anchored to character
|
|
43
|
+
* class boundaries: the `user_` literal prefix is TTCtl-specific (no false
|
|
44
|
+
* positives against unrelated `user_` strings in test data), the 24-hex +
|
|
45
|
+
* 20-alphanumeric pair is the empirically observed shape.
|
|
46
|
+
*/
|
|
47
|
+
export declare const BEARER_PATTERN_SOURCE: "user_[0-9a-f]{24}_[A-Za-z0-9]{20}";
|
|
48
|
+
/**
|
|
49
|
+
* Compiled bearer-shape regex with the `g` (global) flag. A fresh `RegExp`
|
|
50
|
+
* instance constructed at module-load time so callers in this package can
|
|
51
|
+
* use `.test()` / `.exec()` against multiline strings without resetting
|
|
52
|
+
* `lastIndex` between unrelated callers.
|
|
53
|
+
*
|
|
54
|
+
* **Stateful flag warning**: the `g` flag makes `exec` / `test` advance an
|
|
55
|
+
* internal cursor. Callers iterating over multiple inputs must either
|
|
56
|
+
* reset `BEARER_PATTERN.lastIndex = 0` before each input, or construct a
|
|
57
|
+
* fresh instance via `new RegExp(BEARER_PATTERN_SOURCE, "g")`.
|
|
58
|
+
*/
|
|
59
|
+
export declare const BEARER_PATTERN: RegExp;
|
|
60
|
+
/**
|
|
61
|
+
* Header names whose VALUES carry session-bearing or otherwise
|
|
62
|
+
* authentication-sensitive material. Lowercased for case-insensitive
|
|
63
|
+
* matching — runtime code paths normalise the header key to lowercase
|
|
64
|
+
* before lookup. Includes both standard auth headers (`authorization`,
|
|
65
|
+
* `cookie`, `proxy-authorization`) and a small set of vendor-specific
|
|
66
|
+
* variants that historically carry session tokens.
|
|
67
|
+
*
|
|
68
|
+
* `set-cookie` is included because GraphQL responses occasionally echo
|
|
69
|
+
* session-bearing cookies on the response side; redacting it on the
|
|
70
|
+
* response trace keeps the symmetric request/response wire shape clean.
|
|
71
|
+
*/
|
|
72
|
+
export declare const SECRET_HEADER_NAMES: ReadonlySet<string>;
|
|
73
|
+
/**
|
|
74
|
+
* Field names (case-insensitive) whose VALUES carry secrets when they
|
|
75
|
+
* appear inside request or response bodies — GraphQL `variables`, JSON
|
|
76
|
+
* payloads, REST request bodies, error envelopes, etc. Runtime code
|
|
77
|
+
* paths normalise the field key to lowercase before lookup.
|
|
78
|
+
*
|
|
79
|
+
* Conservative on purpose: the cost of redacting a field that turns out
|
|
80
|
+
* to be benign is zero (one fewer scalar visible in debug output); the
|
|
81
|
+
* cost of NOT redacting an actual secret is a credential leak. Add new
|
|
82
|
+
* field names freely.
|
|
83
|
+
*/
|
|
84
|
+
export declare const SECRET_BODY_FIELD_NAMES: ReadonlySet<string>;
|
|
85
|
+
/**
|
|
86
|
+
* Return a redacted copy of `headers`. Keys are preserved verbatim; values
|
|
87
|
+
* for keys whose lowercase form is in {@link SECRET_HEADER_NAMES} are
|
|
88
|
+
* replaced with {@link REDACTED}. Non-secret headers pass through unchanged.
|
|
89
|
+
*
|
|
90
|
+
* For the `cookie` and `set-cookie` headers, the entire header value is
|
|
91
|
+
* replaced. TTCtl is bearer-only (no cookie jar is configured in either
|
|
92
|
+
* transport — see ADR-005), but a server may still emit `Set-Cookie` on
|
|
93
|
+
* responses (Cloudflare bot-management cookies, Toptal session hints
|
|
94
|
+
* TTCtl ignores). Whole-value redaction is the safe default for that
|
|
95
|
+
* case; cookie names carry no operational signal here because TTCtl
|
|
96
|
+
* never sends them back.
|
|
97
|
+
*
|
|
98
|
+
* Pure — does not mutate the input.
|
|
99
|
+
*/
|
|
100
|
+
export declare function redactHeaders(headers: Record<string, string>): Record<string, string>;
|
|
101
|
+
/**
|
|
102
|
+
* Deep-walk `body` and replace values of any field whose key (lowercased)
|
|
103
|
+
* is in {@link SECRET_BODY_FIELD_NAMES} with {@link REDACTED}. Returns a
|
|
104
|
+
* structurally cloned copy — does NOT mutate the input. Non-object inputs
|
|
105
|
+
* pass through unchanged (scalars, null, undefined).
|
|
106
|
+
*
|
|
107
|
+
* Walks both plain objects and arrays. Arrays inherit the parent's
|
|
108
|
+
* redaction rules — e.g., `{credentials: [{password: "..."}]}` redacts
|
|
109
|
+
* the `password` field inside the array element.
|
|
110
|
+
*
|
|
111
|
+
* Designed for GraphQL `variables` payloads (which are arbitrary JSON
|
|
112
|
+
* trees) and REST request bodies. Cycle detection is NOT implemented —
|
|
113
|
+
* GraphQL request/response payloads are tree-shaped by spec, so cycles
|
|
114
|
+
* cannot occur in valid inputs; defensive callers (e.g.,
|
|
115
|
+
* `diagnostic-log.ts`) parse JSON before invoking and pass the parsed
|
|
116
|
+
* tree.
|
|
117
|
+
*
|
|
118
|
+
* The implementation walks keys via `Object.entries` which preserves
|
|
119
|
+
* insertion order in V8 — useful for tests that snapshot the redacted
|
|
120
|
+
* output as JSON.
|
|
121
|
+
*/
|
|
122
|
+
export declare function redactBody(body: unknown): unknown;
|
|
123
|
+
/**
|
|
124
|
+
* True when `text` contains a Toptal Talent session bearer match per
|
|
125
|
+
* {@link BEARER_PATTERN_SOURCE}. Convenience wrapper that constructs a
|
|
126
|
+
* fresh `RegExp` per call so `lastIndex` state on the shared
|
|
127
|
+
* {@link BEARER_PATTERN} cannot leak into the test.
|
|
128
|
+
*
|
|
129
|
+
* Used by `scripts/check-secret-leakage.ts` as the authoritative
|
|
130
|
+
* "contains-bearer" check, replacing the previous in-script regex.
|
|
131
|
+
*/
|
|
132
|
+
export declare function containsBearerToken(text: string): boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Replace every Toptal Talent session bearer match in `text` with the
|
|
135
|
+
* canonical {@link REDACTED} marker. Returns the input unchanged when no
|
|
136
|
+
* match is present.
|
|
137
|
+
*
|
|
138
|
+
* Free-text counterpart to the structural {@link redactBody} /
|
|
139
|
+
* {@link redactHeaders} pair — designed for diagnostic output whose
|
|
140
|
+
* shape is a single string (error messages, stack traces, crash logs)
|
|
141
|
+
* rather than a parsed object graph. The bearer regex is the only
|
|
142
|
+
* pattern this scrubs; password and other unstructured secrets cannot
|
|
143
|
+
* be detected without a runtime registry of known values (out of scope
|
|
144
|
+
* for this module — see SECURITY.md § Crash-output secret invariant).
|
|
145
|
+
*
|
|
146
|
+
* Constructs a fresh `RegExp` per call so the shared
|
|
147
|
+
* {@link BEARER_PATTERN}'s `lastIndex` cursor cannot be advanced by
|
|
148
|
+
* unrelated callers.
|
|
149
|
+
*
|
|
150
|
+
* Pure — does not mutate the input string.
|
|
151
|
+
*/
|
|
152
|
+
export declare function redactString(text: string): string;
|
|
153
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/lib/redact.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAG,gBAAyB,CAAC;AAElD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,qBAAqB,EAAG,mCAA4C,CAAC;AAElF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,EAAE,MAA+C,CAAC;AAE7E;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CASlD,CAAC;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAetD,CAAC;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMrF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAcjD;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (C) 2026 Oleksii PELYKH
|
|
3
|
+
/**
|
|
4
|
+
* Centralized redaction primitives for diagnostic logging and pattern-leak
|
|
5
|
+
* detection.
|
|
6
|
+
*
|
|
7
|
+
* This module is the **single source of truth** for "what is a secret" across
|
|
8
|
+
* the project — both the runtime debug-log path (issue #139) and the static
|
|
9
|
+
* `check-secret-leakage` script consume the constants defined here. Any
|
|
10
|
+
* future addition to either redaction (runtime) or detection (lint) MUST
|
|
11
|
+
* extend these exports rather than redefining them locally.
|
|
12
|
+
*
|
|
13
|
+
* Consumers:
|
|
14
|
+
*
|
|
15
|
+
* - `packages/core/src/lib/diagnostic-log.ts` calls {@link redactHeaders} /
|
|
16
|
+
* {@link redactBody} before emitting any `--verbose` / `--debug` line
|
|
17
|
+
* to stderr.
|
|
18
|
+
* - `scripts/check-secret-leakage.ts` consumes {@link BEARER_PATTERN_SOURCE}
|
|
19
|
+
* to scan tracked source for accidentally committed Toptal session
|
|
20
|
+
* bearers.
|
|
21
|
+
*
|
|
22
|
+
* Wire shape is stable; downstream tools and tests identify redaction sites
|
|
23
|
+
* by exact match against {@link REDACTED}. Patterns are append-only — never
|
|
24
|
+
* remove an entry, only add. Tests assert that no `authToken` value or
|
|
25
|
+
* cookie value can appear verbatim in the output of either consumer.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Replacement marker for redacted scalar values. Stable wire-shape so
|
|
29
|
+
* downstream tools and tests can identify redaction sites by exact match.
|
|
30
|
+
*/
|
|
31
|
+
export const REDACTED = "***REDACTED***";
|
|
32
|
+
/**
|
|
33
|
+
* Source pattern for Toptal Talent's session bearer token, exported as a
|
|
34
|
+
* STRING (not a `RegExp`) so non-TypeScript consumers (the
|
|
35
|
+
* `scripts/check-secret-leakage.ts` lint script before its own
|
|
36
|
+
* `new RegExp(..., "g")` construction) can adopt the canonical pattern
|
|
37
|
+
* without paying for any module-load-time `g`-flag state. The TS-side
|
|
38
|
+
* {@link BEARER_PATTERN} below wraps this source in a fresh `RegExp` per
|
|
39
|
+
* import so callers do not share the `lastIndex` cursor (`/g` flag is
|
|
40
|
+
* stateful per-instance).
|
|
41
|
+
*
|
|
42
|
+
* The bearer shape is `user_<24hex>_<20alnum>` — the canonical output of
|
|
43
|
+
* Toptal's `EmailPasswordSignIn` mutation (per
|
|
44
|
+
* `hq/engineering/adr/ADR-005-auth-model.md`). Anchored to character
|
|
45
|
+
* class boundaries: the `user_` literal prefix is TTCtl-specific (no false
|
|
46
|
+
* positives against unrelated `user_` strings in test data), the 24-hex +
|
|
47
|
+
* 20-alphanumeric pair is the empirically observed shape.
|
|
48
|
+
*/
|
|
49
|
+
export const BEARER_PATTERN_SOURCE = "user_[0-9a-f]{24}_[A-Za-z0-9]{20}";
|
|
50
|
+
/**
|
|
51
|
+
* Compiled bearer-shape regex with the `g` (global) flag. A fresh `RegExp`
|
|
52
|
+
* instance constructed at module-load time so callers in this package can
|
|
53
|
+
* use `.test()` / `.exec()` against multiline strings without resetting
|
|
54
|
+
* `lastIndex` between unrelated callers.
|
|
55
|
+
*
|
|
56
|
+
* **Stateful flag warning**: the `g` flag makes `exec` / `test` advance an
|
|
57
|
+
* internal cursor. Callers iterating over multiple inputs must either
|
|
58
|
+
* reset `BEARER_PATTERN.lastIndex = 0` before each input, or construct a
|
|
59
|
+
* fresh instance via `new RegExp(BEARER_PATTERN_SOURCE, "g")`.
|
|
60
|
+
*/
|
|
61
|
+
export const BEARER_PATTERN = new RegExp(BEARER_PATTERN_SOURCE, "g");
|
|
62
|
+
/**
|
|
63
|
+
* Header names whose VALUES carry session-bearing or otherwise
|
|
64
|
+
* authentication-sensitive material. Lowercased for case-insensitive
|
|
65
|
+
* matching — runtime code paths normalise the header key to lowercase
|
|
66
|
+
* before lookup. Includes both standard auth headers (`authorization`,
|
|
67
|
+
* `cookie`, `proxy-authorization`) and a small set of vendor-specific
|
|
68
|
+
* variants that historically carry session tokens.
|
|
69
|
+
*
|
|
70
|
+
* `set-cookie` is included because GraphQL responses occasionally echo
|
|
71
|
+
* session-bearing cookies on the response side; redacting it on the
|
|
72
|
+
* response trace keeps the symmetric request/response wire shape clean.
|
|
73
|
+
*/
|
|
74
|
+
export const SECRET_HEADER_NAMES = new Set([
|
|
75
|
+
"authorization",
|
|
76
|
+
"cookie",
|
|
77
|
+
"set-cookie",
|
|
78
|
+
"proxy-authorization",
|
|
79
|
+
"x-auth-token",
|
|
80
|
+
"x-csrf-token",
|
|
81
|
+
"x-session-token",
|
|
82
|
+
"x-toptal-session",
|
|
83
|
+
]);
|
|
84
|
+
/**
|
|
85
|
+
* Field names (case-insensitive) whose VALUES carry secrets when they
|
|
86
|
+
* appear inside request or response bodies — GraphQL `variables`, JSON
|
|
87
|
+
* payloads, REST request bodies, error envelopes, etc. Runtime code
|
|
88
|
+
* paths normalise the field key to lowercase before lookup.
|
|
89
|
+
*
|
|
90
|
+
* Conservative on purpose: the cost of redacting a field that turns out
|
|
91
|
+
* to be benign is zero (one fewer scalar visible in debug output); the
|
|
92
|
+
* cost of NOT redacting an actual secret is a credential leak. Add new
|
|
93
|
+
* field names freely.
|
|
94
|
+
*/
|
|
95
|
+
export const SECRET_BODY_FIELD_NAMES = new Set([
|
|
96
|
+
"password",
|
|
97
|
+
"passwd",
|
|
98
|
+
"token",
|
|
99
|
+
"secret",
|
|
100
|
+
"apikey",
|
|
101
|
+
"api_key",
|
|
102
|
+
"authtoken",
|
|
103
|
+
"auth_token",
|
|
104
|
+
"accesstoken",
|
|
105
|
+
"access_token",
|
|
106
|
+
"refreshtoken",
|
|
107
|
+
"refresh_token",
|
|
108
|
+
"authorization",
|
|
109
|
+
"email",
|
|
110
|
+
]);
|
|
111
|
+
/**
|
|
112
|
+
* Return a redacted copy of `headers`. Keys are preserved verbatim; values
|
|
113
|
+
* for keys whose lowercase form is in {@link SECRET_HEADER_NAMES} are
|
|
114
|
+
* replaced with {@link REDACTED}. Non-secret headers pass through unchanged.
|
|
115
|
+
*
|
|
116
|
+
* For the `cookie` and `set-cookie` headers, the entire header value is
|
|
117
|
+
* replaced. TTCtl is bearer-only (no cookie jar is configured in either
|
|
118
|
+
* transport — see ADR-005), but a server may still emit `Set-Cookie` on
|
|
119
|
+
* responses (Cloudflare bot-management cookies, Toptal session hints
|
|
120
|
+
* TTCtl ignores). Whole-value redaction is the safe default for that
|
|
121
|
+
* case; cookie names carry no operational signal here because TTCtl
|
|
122
|
+
* never sends them back.
|
|
123
|
+
*
|
|
124
|
+
* Pure — does not mutate the input.
|
|
125
|
+
*/
|
|
126
|
+
export function redactHeaders(headers) {
|
|
127
|
+
const out = {};
|
|
128
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
129
|
+
out[key] = SECRET_HEADER_NAMES.has(key.toLowerCase()) ? REDACTED : value;
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Deep-walk `body` and replace values of any field whose key (lowercased)
|
|
135
|
+
* is in {@link SECRET_BODY_FIELD_NAMES} with {@link REDACTED}. Returns a
|
|
136
|
+
* structurally cloned copy — does NOT mutate the input. Non-object inputs
|
|
137
|
+
* pass through unchanged (scalars, null, undefined).
|
|
138
|
+
*
|
|
139
|
+
* Walks both plain objects and arrays. Arrays inherit the parent's
|
|
140
|
+
* redaction rules — e.g., `{credentials: [{password: "..."}]}` redacts
|
|
141
|
+
* the `password` field inside the array element.
|
|
142
|
+
*
|
|
143
|
+
* Designed for GraphQL `variables` payloads (which are arbitrary JSON
|
|
144
|
+
* trees) and REST request bodies. Cycle detection is NOT implemented —
|
|
145
|
+
* GraphQL request/response payloads are tree-shaped by spec, so cycles
|
|
146
|
+
* cannot occur in valid inputs; defensive callers (e.g.,
|
|
147
|
+
* `diagnostic-log.ts`) parse JSON before invoking and pass the parsed
|
|
148
|
+
* tree.
|
|
149
|
+
*
|
|
150
|
+
* The implementation walks keys via `Object.entries` which preserves
|
|
151
|
+
* insertion order in V8 — useful for tests that snapshot the redacted
|
|
152
|
+
* output as JSON.
|
|
153
|
+
*/
|
|
154
|
+
export function redactBody(body) {
|
|
155
|
+
if (body === null || body === undefined)
|
|
156
|
+
return body;
|
|
157
|
+
if (Array.isArray(body))
|
|
158
|
+
return body.map((item) => redactBody(item));
|
|
159
|
+
if (typeof body !== "object")
|
|
160
|
+
return body;
|
|
161
|
+
const input = body;
|
|
162
|
+
const out = {};
|
|
163
|
+
for (const [key, value] of Object.entries(input)) {
|
|
164
|
+
if (SECRET_BODY_FIELD_NAMES.has(key.toLowerCase())) {
|
|
165
|
+
out[key] = REDACTED;
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
out[key] = redactBody(value);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* True when `text` contains a Toptal Talent session bearer match per
|
|
175
|
+
* {@link BEARER_PATTERN_SOURCE}. Convenience wrapper that constructs a
|
|
176
|
+
* fresh `RegExp` per call so `lastIndex` state on the shared
|
|
177
|
+
* {@link BEARER_PATTERN} cannot leak into the test.
|
|
178
|
+
*
|
|
179
|
+
* Used by `scripts/check-secret-leakage.ts` as the authoritative
|
|
180
|
+
* "contains-bearer" check, replacing the previous in-script regex.
|
|
181
|
+
*/
|
|
182
|
+
export function containsBearerToken(text) {
|
|
183
|
+
return new RegExp(BEARER_PATTERN_SOURCE).test(text);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Replace every Toptal Talent session bearer match in `text` with the
|
|
187
|
+
* canonical {@link REDACTED} marker. Returns the input unchanged when no
|
|
188
|
+
* match is present.
|
|
189
|
+
*
|
|
190
|
+
* Free-text counterpart to the structural {@link redactBody} /
|
|
191
|
+
* {@link redactHeaders} pair — designed for diagnostic output whose
|
|
192
|
+
* shape is a single string (error messages, stack traces, crash logs)
|
|
193
|
+
* rather than a parsed object graph. The bearer regex is the only
|
|
194
|
+
* pattern this scrubs; password and other unstructured secrets cannot
|
|
195
|
+
* be detected without a runtime registry of known values (out of scope
|
|
196
|
+
* for this module — see SECURITY.md § Crash-output secret invariant).
|
|
197
|
+
*
|
|
198
|
+
* Constructs a fresh `RegExp` per call so the shared
|
|
199
|
+
* {@link BEARER_PATTERN}'s `lastIndex` cursor cannot be advanced by
|
|
200
|
+
* unrelated callers.
|
|
201
|
+
*
|
|
202
|
+
* Pure — does not mutate the input string.
|
|
203
|
+
*/
|
|
204
|
+
export function redactString(text) {
|
|
205
|
+
return text.replace(new RegExp(BEARER_PATTERN_SOURCE, "g"), REDACTED);
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../../src/lib/redact.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,oCAAoC;AAEpC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,gBAAyB,CAAC;AAElD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,mCAA4C,CAAC;AAElF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,cAAc,GAAW,IAAI,MAAM,CAAC,qBAAqB,EAAE,GAAG,CAAC,CAAC;AAE7E;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAwB,IAAI,GAAG,CAAC;IAC9D,eAAe;IACf,QAAQ;IACR,YAAY;IACZ,qBAAqB;IACrB,cAAc;IACd,cAAc;IACd,iBAAiB;IACjB,kBAAkB;CACnB,CAAC,CAAC;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAwB,IAAI,GAAG,CAAC;IAClE,UAAU;IACV,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,WAAW;IACX,YAAY;IACZ,aAAa;IACb,cAAc;IACd,cAAc;IACd,eAAe;IACf,eAAe;IACf,OAAO;CACR,CAAC,CAAC;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,aAAa,CAAC,OAA+B;IAC3D,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,GAAG,CAAC,GAAG,CAAC,GAAG,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;IAC3E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,UAAU,CAAC,IAAa;IACtC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACrD,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,KAAK,GAAG,IAA+B,CAAC;IAC9C,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,IAAI,uBAAuB,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YACnD,GAAG,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,IAAI,MAAM,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,qBAAqB,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;AACxE,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split a multi-paragraph string on blank lines into an array of trimmed
|
|
3
|
+
* paragraphs (drops empties). Mirrors the wire shape used by sub-domains
|
|
4
|
+
* whose mutation inputs accept paragraph arrays (e.g. `experienceItems`
|
|
5
|
+
* on `UpdateEmploymentInput` per
|
|
6
|
+
* `research/captures/web/inputs/UpdateEmploymentInput.json`).
|
|
7
|
+
*
|
|
8
|
+
* Pure — no I/O — so it's directly unit-testable.
|
|
9
|
+
*
|
|
10
|
+
* @param text - source string with paragraphs separated by blank lines
|
|
11
|
+
* @returns array of trimmed paragraphs (empty paragraphs dropped)
|
|
12
|
+
*/
|
|
13
|
+
export declare function splitParagraphs(text: string): string[];
|
|
14
|
+
//# sourceMappingURL=text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/lib/text.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAKtD"}
|
package/dist/lib/text.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (C) 2026 Oleksii PELYKH
|
|
3
|
+
/**
|
|
4
|
+
* Split a multi-paragraph string on blank lines into an array of trimmed
|
|
5
|
+
* paragraphs (drops empties). Mirrors the wire shape used by sub-domains
|
|
6
|
+
* whose mutation inputs accept paragraph arrays (e.g. `experienceItems`
|
|
7
|
+
* on `UpdateEmploymentInput` per
|
|
8
|
+
* `research/captures/web/inputs/UpdateEmploymentInput.json`).
|
|
9
|
+
*
|
|
10
|
+
* Pure — no I/O — so it's directly unit-testable.
|
|
11
|
+
*
|
|
12
|
+
* @param text - source string with paragraphs separated by blank lines
|
|
13
|
+
* @returns array of trimmed paragraphs (empty paragraphs dropped)
|
|
14
|
+
*/
|
|
15
|
+
export function splitParagraphs(text) {
|
|
16
|
+
return text
|
|
17
|
+
.split(/\r?\n\s*\r?\n/)
|
|
18
|
+
.map((p) => p.trim())
|
|
19
|
+
.filter((p) => p.length > 0);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/lib/text.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,oCAAoC;AAEpC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,IAAI;SACR,KAAK,CAAC,eAAe,CAAC;SACtB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wire-shape validation helper for the hybrid runtime
|
|
3
|
+
* field-level validation model (ADR-006). Used by every per-service
|
|
4
|
+
* `callGateway` / `callTalentProfile` helper to convert a
|
|
5
|
+
* {@link z.ZodError} from a failed `schema.parse(body.data)` call into
|
|
6
|
+
* the structured payload required by the project's `WIRE_SHAPE_ERROR`
|
|
7
|
+
* UX (see `docs/wire-validation-error-format.md`, M2 / #281).
|
|
8
|
+
*
|
|
9
|
+
* Operationally a `WIRE_SHAPE_ERROR` means Toptal changed the wire
|
|
10
|
+
* shape (the schemas were synthesized from APK decompilation + live
|
|
11
|
+
* captures and lag behind server-side updates). The CLI / MCP surfaces
|
|
12
|
+
* render the diff so an operator can file an actionable bug.
|
|
13
|
+
*
|
|
14
|
+
* Z-3 (#286) wires this helper through the call path. No production op
|
|
15
|
+
* has `schema` wired yet — that's Z-4 (#288)'s beachhead.
|
|
16
|
+
*
|
|
17
|
+
* Zod v4 note: in v4 the public `error.issues[]` does NOT carry the
|
|
18
|
+
* raw input value (`input` is stripped from the user-facing
|
|
19
|
+
* {@link z.core.$ZodIssue} despite being present on the internal
|
|
20
|
+
* {@link z.core.$ZodRawIssue}). To recover the wire value for the
|
|
21
|
+
* `actual` and `value` slots of a {@link WireShapeDiffEntry}, the
|
|
22
|
+
* caller passes the original wire data (`body.data`) as the second
|
|
23
|
+
* argument to {@link buildWireShapeError} / {@link projectZodErrorToDiff};
|
|
24
|
+
* we walk the issue's `path` against that snapshot. When the wire
|
|
25
|
+
* value can't be resolved (e.g., the schema rejected the entire
|
|
26
|
+
* top-level shape), the entry falls back to a path-only render.
|
|
27
|
+
*/
|
|
28
|
+
import type { z } from "zod";
|
|
29
|
+
/**
|
|
30
|
+
* One field-level diff entry in a {@link WireShapeErrorPayload}. Wire
|
|
31
|
+
* shape matches the JSON contract in `docs/wire-validation-error-format.md`:
|
|
32
|
+
*
|
|
33
|
+
* - `op` — `"+"` schema-unknown wire field (strict mode unrecognized
|
|
34
|
+
* keys) / `"-"` schema-required missing on wire / `"~"` type
|
|
35
|
+
* mismatch.
|
|
36
|
+
* - `path` — JSON path from operation root with zero-indexed bracket
|
|
37
|
+
* notation for arrays (`billingCycle.records[0].duration`) so it
|
|
38
|
+
* matches `jq` semantics.
|
|
39
|
+
* - `expected` — schema type as a short string (`"number"`,
|
|
40
|
+
* `"string"`, `"undefined"`, …).
|
|
41
|
+
* - `actual` — wire value type as a short string.
|
|
42
|
+
* - `value?` — raw wire value rendered to a string, truncated to
|
|
43
|
+
* {@link MAX_VALUE_LENGTH} chars with `…` on overflow. Omitted for
|
|
44
|
+
* `+` (key absent from schema, value undefined) and `-` (field
|
|
45
|
+
* absent from wire, value undefined). `body.data` never contains
|
|
46
|
+
* the bearer, so no redaction is needed.
|
|
47
|
+
*/
|
|
48
|
+
export interface WireShapeDiffEntry {
|
|
49
|
+
op: "+" | "-" | "~";
|
|
50
|
+
path: string;
|
|
51
|
+
expected: string;
|
|
52
|
+
actual: string;
|
|
53
|
+
value?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Structured payload returned by {@link buildWireShapeError}. Each
|
|
57
|
+
* service-level `WIRE_SHAPE_ERROR` carries this payload through its
|
|
58
|
+
* own domain-error class via the `cause` field. The CLI / MCP layers
|
|
59
|
+
* lift `message`, `hint`, and `diff` into the user-visible envelope.
|
|
60
|
+
*/
|
|
61
|
+
export interface WireShapeErrorPayload {
|
|
62
|
+
message: string;
|
|
63
|
+
hint: string;
|
|
64
|
+
diff: WireShapeDiffEntry[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Maximum rendered length of a {@link WireShapeDiffEntry.value} field
|
|
68
|
+
* (per `docs/wire-validation-error-format.md` § Diff entries).
|
|
69
|
+
* Truncation appends `…` (a single ellipsis character, not three
|
|
70
|
+
* dots) to mark overflow.
|
|
71
|
+
*/
|
|
72
|
+
export declare const MAX_VALUE_LENGTH = 32;
|
|
73
|
+
/**
|
|
74
|
+
* Verbatim hint string emitted alongside every `WIRE_SHAPE_ERROR`.
|
|
75
|
+
* Lifted into the CLI envelope's `hint` slot and the MCP error-text
|
|
76
|
+
* `Hint:` block; identical across all surfaces so an operator pattern-
|
|
77
|
+
* matches the message regardless of where it's encountered. The text
|
|
78
|
+
* comes directly from `docs/wire-validation-error-format.md` § Code +
|
|
79
|
+
* base fields (M2 / #281).
|
|
80
|
+
*/
|
|
81
|
+
export declare const WIRE_SHAPE_HINT = "wire shape doesn't match expected \u2014 this typically means Toptal changed the API; please file an issue at https://github.com/alexey-pelykh/ttctl/issues with the operation name and timestamp.";
|
|
82
|
+
/**
|
|
83
|
+
* Build the field-level diff from a {@link z.ZodError}. Issues are
|
|
84
|
+
* projected per {@link issueToDiffEntries} and sorted deterministically
|
|
85
|
+
* per {@link compareDiffEntries} so two runs against the same drifted
|
|
86
|
+
* wire response produce byte-identical output (load-bearing for
|
|
87
|
+
* snapshot tests and CI grep-fu).
|
|
88
|
+
*
|
|
89
|
+
* `wireData` is the original `body.data` snapshot that failed
|
|
90
|
+
* validation — walking it against each issue's `path` recovers the
|
|
91
|
+
* actual wire value (Zod v4 strips `input` from public issues; see
|
|
92
|
+
* file header). Pass `undefined` only in tests / migration paths
|
|
93
|
+
* where the snapshot is unavailable; entries will degrade to path-
|
|
94
|
+
* only render in that case.
|
|
95
|
+
*
|
|
96
|
+
* Exported for unit tests; callers should prefer
|
|
97
|
+
* {@link buildWireShapeError} which composes the full payload.
|
|
98
|
+
*/
|
|
99
|
+
export declare function projectZodErrorToDiff(error: z.ZodError, wireData?: unknown): WireShapeDiffEntry[];
|
|
100
|
+
/**
|
|
101
|
+
* Compose the `message` line of a `WIRE_SHAPE_ERROR` per
|
|
102
|
+
* `docs/wire-validation-error-format.md` § Code + base fields. The
|
|
103
|
+
* pluralisation is wire-stable (`1 field issue` vs `2 field issues`)
|
|
104
|
+
* so snapshot tests don't need locale-aware matchers.
|
|
105
|
+
*
|
|
106
|
+
* Exported for unit tests; callers should prefer
|
|
107
|
+
* {@link buildWireShapeError} which composes the full payload.
|
|
108
|
+
*/
|
|
109
|
+
export declare function buildWireShapeMessage(operationName: string, diffEntryCount: number): string;
|
|
110
|
+
/**
|
|
111
|
+
* Convert a {@link z.ZodError} from a failed `schema.parse(body.data)`
|
|
112
|
+
* call into the full {@link WireShapeErrorPayload}. The payload is
|
|
113
|
+
* stable across surfaces (CLI envelope, MCP error text) and round-
|
|
114
|
+
* trips through JSON without redaction (body.data never carries the
|
|
115
|
+
* bearer).
|
|
116
|
+
*
|
|
117
|
+
* Each service's `callGateway` / `callTalentProfile` helper wraps the
|
|
118
|
+
* payload's `message` into its own domain-error class with
|
|
119
|
+
* `code: "WIRE_SHAPE_ERROR"` and `cause: zodError`; the CLI / MCP
|
|
120
|
+
* layers reconstruct the diff from `cause` when rendering.
|
|
121
|
+
*
|
|
122
|
+
* Pass `wireData` (the original `body.data` snapshot) so the diff
|
|
123
|
+
* can recover actual wire values via path walk — Zod v4 strips the
|
|
124
|
+
* `input` field from public issues, so the snapshot is the only way
|
|
125
|
+
* to render `actual` / `value` for non-`unrecognized_keys` entries.
|
|
126
|
+
*
|
|
127
|
+
* Z-3 (#286) wires the mechanism; no production op has `schema`
|
|
128
|
+
* passed in yet (Z-4 / #288 ships the first beachhead).
|
|
129
|
+
*/
|
|
130
|
+
export declare function buildWireShapeError(operationName: string, error: z.ZodError, wireData?: unknown): WireShapeErrorPayload;
|
|
131
|
+
//# sourceMappingURL=wire-shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire-shape.d.ts","sourceRoot":"","sources":["../../src/lib/wire-shape.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,kBAAkB,EAAE,CAAC;CAC5B;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,uMACqK,CAAC;AAqRlM;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,kBAAkB,EAAE,CAOjG;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAG3F;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,CAAC,CAAC,QAAQ,EACjB,QAAQ,CAAC,EAAE,OAAO,GACjB,qBAAqB,CAOvB"}
|