@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,431 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (C) 2026 Oleksii PELYKH
3
+ /**
4
+ * `profile.reviews` service module.
5
+ *
6
+ * Section reviews are the section/item review-and-approval flow: when the
7
+ * talent edits a sensitive section (basic info, skills, employments, etc.)
8
+ * the change goes into a "pending review" state; the talent must explicitly
9
+ * approve each pending item or section before it's published.
10
+ *
11
+ * Per issue #76 the v0 user-facing surface exposes 4 leaves:
12
+ *
13
+ * 1. {@link list} — list pending section reviews
14
+ * 2. {@link approveItem} — approve a single pending item within a section
15
+ * 3. {@link approveSection} — approve all pending items in a section
16
+ * 4. {@link submitForReview} — re-submit the profile for platform-side re-review
17
+ *
18
+ * ## Spec / API divergences (documented per #76 AC)
19
+ *
20
+ * The issue (#76) suggests `approve-item <id>` and `approve-section <id>` as
21
+ * single-positional CLI signatures. The actual GraphQL inputs require three
22
+ * fields each (`reviewId`, `itemId`, `itemKind` for item; `reviewId`,
23
+ * `section` for section — see
24
+ * `research/notes/10-mutation-input-patterns.md`). The CLI uses named flags
25
+ * (`--review-id`, `--item-id`, `--kind`, `--section`) to disambiguate.
26
+ *
27
+ * `submitForReview` is listed in
28
+ * `research/notes/10-mutation-input-patterns.md` § "non-obvious shapes"
29
+ * with no live capture. We follow Pattern 2 (`{ profileId: ID! }`) as the
30
+ * most plausible inferred shape; deviations would surface at runtime as
31
+ * `USER_ERROR`. Marked **INFERRED — UNVERIFIED** in the implementation.
32
+ *
33
+ * `ItemReviewKind` and `ReviewSection` enum values are not catalogued in
34
+ * the schema (`research/graphql/talent_profile/schema.graphql` types most
35
+ * scalars as `Unknown`). The CLI accepts these as plain strings and lets
36
+ * the server validate; the server's user-error response is rendered
37
+ * verbatim so the user sees the rejected value alongside the expected
38
+ * vocabulary.
39
+ *
40
+ * ## Wire-shape decisions
41
+ *
42
+ * All operations target the talent_profile surface and use
43
+ * {@link impersonatedTransport}. Same full-document strategy as the
44
+ * external sub-domain — see `services/profile/external/index.ts` § "Wire-
45
+ * shape decisions" for rationale.
46
+ */
47
+ import { AuthRevokedError, TtctlError } from "../../../auth/errors.js";
48
+ import { impersonatedTransport } from "../../../transport.js";
49
+ import { ProfileError } from "../basic/index.js";
50
+ import { extractProfileId, isAuthRevokedExtensionCode } from "../shared.js";
51
+ // Re-export the shared `ProfileError` / `ProfileErrorCode` so consumers can
52
+ // continue to write `profile.reviews.ProfileError`.
53
+ export { ProfileError };
54
+ function parseTalentProfileResponse(res, commandLabel, fallbackErrorCode = "UNKNOWN") {
55
+ if (res.status === 401) {
56
+ throw new AuthRevokedError("Session is invalid or expired.");
57
+ }
58
+ if (res.status < 200 || res.status >= 300) {
59
+ throw new ProfileError(fallbackErrorCode, `${commandLabel} returned HTTP ${res.status.toString()}`);
60
+ }
61
+ const body = res.body;
62
+ if (body && Array.isArray(body.errors) && body.errors.length > 0) {
63
+ const first = body.errors[0];
64
+ const message = first?.message ?? "GraphQL error";
65
+ if (isAuthRevokedExtensionCode(first?.extensions?.code)) {
66
+ throw new AuthRevokedError("Session is invalid or expired.");
67
+ }
68
+ throw new ProfileError("GRAPHQL_ERROR", `${commandLabel} failed: ${message}`);
69
+ }
70
+ if (!body?.data) {
71
+ throw new ProfileError(fallbackErrorCode, `${commandLabel} response had no \`data\` field`);
72
+ }
73
+ return body.data;
74
+ }
75
+ async function withNetworkErrorMapping(commandLabel, fn) {
76
+ try {
77
+ return await fn();
78
+ }
79
+ catch (err) {
80
+ if (err instanceof TtctlError)
81
+ throw err;
82
+ if (err instanceof ProfileError)
83
+ throw err;
84
+ throw new ProfileError("NETWORK_ERROR", `${commandLabel} request failed: ${err.message}`, {
85
+ cause: err,
86
+ });
87
+ }
88
+ }
89
+ const SECTION_REVIEWS_QUERY = `query sectionReviews($profileId: ID!) {
90
+ sectionReviews(id: $profileId) {
91
+ id
92
+ section
93
+ requestedAt
94
+ items {
95
+ id
96
+ itemId
97
+ requestedAt
98
+ }
99
+ }
100
+ }`;
101
+ function coerceString(value) {
102
+ return typeof value === "string" ? value : null;
103
+ }
104
+ /**
105
+ * Detect the talent_profile graphql-pro "field hidden due to permissions"
106
+ * response on the `sectionReviews` field. The server emits this when the
107
+ * signed-in user has no pending section reviews visible — the field is
108
+ * permission-gated rather than returning an empty array. The shape is:
109
+ *
110
+ * { data: null, errors: [{
111
+ * message: "An object of type SectionReview was hidden due to permissions",
112
+ * path: ["sectionReviews", 0],
113
+ * extensions: { code: "UNAUTHORIZED" },
114
+ * }] }
115
+ *
116
+ * `extensions.code` ALONE is ambiguous (same code is used for session-revoked
117
+ * bearer). Two discriminators are checked, both must hold:
118
+ *
119
+ * 1. **Primary** — `path[0] === "sectionReviews"`. GraphQL spec § 7.1.7 mandates
120
+ * `path` on field-level errors and prohibits it on errors raised before
121
+ * field resolution (session-revoked top-level errors fire BEFORE field
122
+ * resolution and therefore have no `path`). This is the structural
123
+ * discriminator and survives English-wording drift of the message.
124
+ * 2. **Confirmation** — message contains `"hidden due to permissions"`. Defense
125
+ * against the (unlikely) case where a path-bearing top-level error is
126
+ * emitted for some unrelated reason.
127
+ */
128
+ function isFieldHiddenSectionReviews(body) {
129
+ if (body === null || typeof body !== "object")
130
+ return false;
131
+ const envelope = body;
132
+ if (envelope.data !== null && envelope.data !== undefined)
133
+ return false;
134
+ if (!Array.isArray(envelope.errors) || envelope.errors.length === 0)
135
+ return false;
136
+ const first = envelope.errors[0];
137
+ if (first?.extensions?.code !== "UNAUTHORIZED")
138
+ return false;
139
+ if (!Array.isArray(first.path) || first.path[0] !== "sectionReviews")
140
+ return false;
141
+ return typeof first.message === "string" && first.message.includes("hidden due to permissions");
142
+ }
143
+ /**
144
+ * List pending section reviews for the signed-in user. Returns an empty
145
+ * array (not a `null` or thrown error) when no reviews are pending. This
146
+ * includes the talent_profile "field hidden due to permissions" response
147
+ * (see {@link isFieldHiddenSectionReviews}) — empirically the
148
+ * no-pending-reviews surface form for accounts that have no review activity.
149
+ *
150
+ * Errors: `AuthRevokedError`, `ProfileError(GRAPHQL_ERROR)`,
151
+ * `ProfileError(NETWORK_ERROR)`, `Cf403Error` (and other `TtctlError`
152
+ * subclasses) propagate verbatim.
153
+ */
154
+ export async function list(token) {
155
+ const profileId = await extractProfileId(token);
156
+ const res = await withNetworkErrorMapping("Section reviews list", () => impersonatedTransport({
157
+ surface: "talent-profile",
158
+ authToken: token,
159
+ body: {
160
+ operationName: "sectionReviews",
161
+ query: SECTION_REVIEWS_QUERY,
162
+ variables: { profileId },
163
+ },
164
+ }));
165
+ // talent_profile's graphql-pro layer overloads `extensions.code = "UNAUTHORIZED"`
166
+ // for two distinct conditions: (1) session-revoked bearer (handled globally by
167
+ // `isAuthRevokedExtensionCode` → AuthRevokedError); (2) field-level permission
168
+ // denial, surfaced with `data: null` + `message: "An object of type
169
+ // SectionReview was hidden due to permissions"`. For `sectionReviews` the
170
+ // field-hidden case empirically corresponds to "no pending reviews are visible
171
+ // to this user" (live capture 2026-05-16 against an account with no pending
172
+ // section reviews). Short-circuit to `[]` BEFORE the generic auth-revoked
173
+ // mapping fires, otherwise the user sees a misleading "Run `ttctl auth
174
+ // signin`" hint when the session is in fact valid for every other
175
+ // talent_profile op.
176
+ if (res.status === 200 && isFieldHiddenSectionReviews(res.body)) {
177
+ return [];
178
+ }
179
+ const data = parseTalentProfileResponse(res, "Section reviews list");
180
+ const rows = data.sectionReviews ?? [];
181
+ return rows
182
+ .filter((r) => r !== null && typeof r === "object")
183
+ .map((row) => {
184
+ const items = (row.items ?? [])
185
+ .filter((it) => it !== null && typeof it === "object")
186
+ .map((it) => ({
187
+ id: typeof it.id === "string" ? it.id : "",
188
+ itemId: typeof it.itemId === "string" ? it.itemId : "",
189
+ requestedAt: coerceString(it.requestedAt),
190
+ }))
191
+ .filter((it) => it.id.length > 0);
192
+ return {
193
+ id: typeof row.id === "string" ? row.id : "",
194
+ section: coerceString(row.section),
195
+ requestedAt: coerceString(row.requestedAt),
196
+ items,
197
+ };
198
+ })
199
+ .filter((row) => row.id.length > 0);
200
+ }
201
+ const APPROVE_ITEM_REVIEW_MUTATION = `mutation ApproveItemReview($input: ApproveItemReviewInput!) {
202
+ approveItemReview(input: $input) {
203
+ sectionReviews {
204
+ id
205
+ section
206
+ requestedAt
207
+ items {
208
+ id
209
+ itemId
210
+ requestedAt
211
+ }
212
+ }
213
+ errors {
214
+ code
215
+ key
216
+ message
217
+ }
218
+ notice
219
+ success
220
+ }
221
+ }`;
222
+ function rowsToSectionReviews(rows) {
223
+ return (rows ?? [])
224
+ .filter((r) => r !== null && typeof r === "object")
225
+ .map((row) => ({
226
+ id: typeof row.id === "string" ? row.id : "",
227
+ section: coerceString(row.section),
228
+ requestedAt: coerceString(row.requestedAt),
229
+ items: (row.items ?? [])
230
+ .filter((it) => it !== null && typeof it === "object")
231
+ .map((it) => ({
232
+ id: typeof it.id === "string" ? it.id : "",
233
+ itemId: typeof it.itemId === "string" ? it.itemId : "",
234
+ requestedAt: coerceString(it.requestedAt),
235
+ }))
236
+ .filter((it) => it.id.length > 0),
237
+ }))
238
+ .filter((row) => row.id.length > 0);
239
+ }
240
+ /**
241
+ * Approve a single pending item within a section review. Returns the
242
+ * server-confirmed updated list of pending section reviews.
243
+ *
244
+ * **Destructive**: approval is final per the platform's review semantics —
245
+ * once approved, the change is published and cannot be reverted from this
246
+ * surface. Callers are expected to confirm intent (the CLI does so by
247
+ * requiring explicit `--review-id` / `--item-id` / `--kind` flags rather
248
+ * than auto-discovering pending items).
249
+ *
250
+ * Errors:
251
+ * - `ProfileError("VALIDATION_ERROR")` when any of `reviewId / itemId /
252
+ * itemKind` is empty
253
+ * - `ProfileError("USER_ERROR")` when the server rejects the input (e.g.
254
+ * unknown item kind, item already approved)
255
+ * - `AuthRevokedError`, `Cf403Error`, other `TtctlError` subclasses
256
+ * propagate verbatim
257
+ */
258
+ export async function approveItem(token, args) {
259
+ if (args.reviewId.length === 0 || args.itemId.length === 0 || args.itemKind.length === 0) {
260
+ throw new ProfileError("VALIDATION_ERROR", "Approve-item review requires non-empty reviewId, itemId, and itemKind.");
261
+ }
262
+ // Destructive — approval is final per platform review semantics; once
263
+ // approved, the change is published and cannot be reverted from this
264
+ // surface. No safe round-trip on a live maintainer profile. Wire shape
265
+ // inferred from research/notes/10. The read-side sectionReviews query
266
+ // is covered in packages/e2e/src/40-profile-reviews.e2e.test.ts.
267
+ const res = await withNetworkErrorMapping("Approve item review", () => impersonatedTransport({
268
+ surface: "talent-profile",
269
+ authToken: token,
270
+ body: {
271
+ // e2e-exempt: destructive — see comment above the withNetworkErrorMapping call.
272
+ operationName: "ApproveItemReview",
273
+ query: APPROVE_ITEM_REVIEW_MUTATION,
274
+ variables: {
275
+ input: {
276
+ reviewId: args.reviewId,
277
+ itemId: args.itemId,
278
+ itemKind: args.itemKind,
279
+ },
280
+ },
281
+ },
282
+ }));
283
+ const data = parseTalentProfileResponse(res, "Approve item review");
284
+ const payload = data.approveItemReview;
285
+ if (!payload) {
286
+ throw new ProfileError("UNKNOWN", "Approve item review response had no payload");
287
+ }
288
+ if (Array.isArray(payload.errors) && payload.errors.length > 0) {
289
+ const first = payload.errors[0];
290
+ const fieldHint = first?.key ? ` (${first.key})` : "";
291
+ throw new ProfileError("USER_ERROR", `Approve item review rejected${fieldHint}: ${first?.message ?? "unknown error"}`);
292
+ }
293
+ if (payload.success === false) {
294
+ throw new ProfileError("USER_ERROR", `Approve item review reported success=false${payload.notice ? `: ${payload.notice}` : ""}`);
295
+ }
296
+ return {
297
+ sectionReviews: rowsToSectionReviews(payload.sectionReviews),
298
+ notice: payload.notice ?? null,
299
+ };
300
+ }
301
+ const APPROVE_SECTION_REVIEW_MUTATION = `mutation ApproveSectionReview($input: ApproveSectionReviewInput!) {
302
+ approveSectionReview(input: $input) {
303
+ sectionReviews {
304
+ id
305
+ section
306
+ requestedAt
307
+ items {
308
+ id
309
+ itemId
310
+ requestedAt
311
+ }
312
+ }
313
+ errors {
314
+ code
315
+ key
316
+ message
317
+ }
318
+ notice
319
+ success
320
+ }
321
+ }`;
322
+ /**
323
+ * Approve all pending items within a section review. Returns the
324
+ * server-confirmed updated list of pending section reviews.
325
+ *
326
+ * **Destructive**: same caveat as {@link approveItem} — approvals are final.
327
+ *
328
+ * Errors: same taxonomy as {@link approveItem}.
329
+ */
330
+ export async function approveSection(token, args) {
331
+ if (args.reviewId.length === 0 || args.section.length === 0) {
332
+ throw new ProfileError("VALIDATION_ERROR", "Approve-section review requires non-empty reviewId and section.");
333
+ }
334
+ // Destructive — section-level approval is final per platform review
335
+ // semantics; the entire section's pending items are published in one
336
+ // call. No safe round-trip on a live maintainer profile. Wire shape
337
+ // inferred from research/notes/10. The read-side sectionReviews query
338
+ // is covered in packages/e2e/src/40-profile-reviews.e2e.test.ts.
339
+ const res = await withNetworkErrorMapping("Approve section review", () => impersonatedTransport({
340
+ surface: "talent-profile",
341
+ authToken: token,
342
+ body: {
343
+ // e2e-exempt: destructive — see comment above the withNetworkErrorMapping call.
344
+ operationName: "ApproveSectionReview",
345
+ query: APPROVE_SECTION_REVIEW_MUTATION,
346
+ variables: {
347
+ input: {
348
+ reviewId: args.reviewId,
349
+ section: args.section,
350
+ },
351
+ },
352
+ },
353
+ }));
354
+ const data = parseTalentProfileResponse(res, "Approve section review");
355
+ const payload = data.approveSectionReview;
356
+ if (!payload) {
357
+ throw new ProfileError("UNKNOWN", "Approve section review response had no payload");
358
+ }
359
+ if (Array.isArray(payload.errors) && payload.errors.length > 0) {
360
+ const first = payload.errors[0];
361
+ const fieldHint = first?.key ? ` (${first.key})` : "";
362
+ throw new ProfileError("USER_ERROR", `Approve section review rejected${fieldHint}: ${first?.message ?? "unknown error"}`);
363
+ }
364
+ if (payload.success === false) {
365
+ throw new ProfileError("USER_ERROR", `Approve section review reported success=false${payload.notice ? `: ${payload.notice}` : ""}`);
366
+ }
367
+ return {
368
+ sectionReviews: rowsToSectionReviews(payload.sectionReviews),
369
+ notice: payload.notice ?? null,
370
+ };
371
+ }
372
+ const SUBMIT_FOR_REVIEW_MUTATION = `mutation submitForReview($input: SubmitForReviewInput!) {
373
+ submitForReview(input: $input) {
374
+ errors {
375
+ code
376
+ key
377
+ message
378
+ }
379
+ notice
380
+ success
381
+ }
382
+ }`;
383
+ /**
384
+ * Re-submit the talent's profile for platform-side re-review. Used after
385
+ * profile edits that require the platform reviewer to re-verify the
386
+ * content (skills, employments, etc.).
387
+ *
388
+ * **INFERRED — UNVERIFIED** input shape: `{ profileId: ID! }` per
389
+ * `research/notes/10-mutation-input-patterns.md` Pattern 2. No live curl
390
+ * capture exists for this mutation. Deviations would surface as
391
+ * `USER_ERROR` at runtime.
392
+ *
393
+ * Errors:
394
+ * - `ProfileError("USER_ERROR")` when the server rejects the submission
395
+ * (e.g. profile not yet ready per `getProfileReadiness`)
396
+ * - `AuthRevokedError`, `Cf403Error`, other `TtctlError` subclasses
397
+ * propagate verbatim
398
+ */
399
+ export async function submitForReview(token) {
400
+ const profileId = await extractProfileId(token);
401
+ // Triggers an actual platform-side re-review against the maintainer's
402
+ // profile when called on a submittable state. No safe reverse-trip.
403
+ // Wire shape inferred from research/notes/10 Pattern 2
404
+ // (`{ profileId: ID! }`). The read-side sectionReviews query is covered
405
+ // in packages/e2e/src/40-profile-reviews.e2e.test.ts.
406
+ const res = await withNetworkErrorMapping("Submit for review", () => impersonatedTransport({
407
+ surface: "talent-profile",
408
+ authToken: token,
409
+ body: {
410
+ // e2e-exempt: destructive — see comment above the withNetworkErrorMapping call.
411
+ operationName: "submitForReview",
412
+ query: SUBMIT_FOR_REVIEW_MUTATION,
413
+ variables: { input: { profileId } },
414
+ },
415
+ }));
416
+ const data = parseTalentProfileResponse(res, "Submit for review");
417
+ const payload = data.submitForReview;
418
+ if (!payload) {
419
+ throw new ProfileError("UNKNOWN", "Submit-for-review response had no payload");
420
+ }
421
+ if (Array.isArray(payload.errors) && payload.errors.length > 0) {
422
+ const first = payload.errors[0];
423
+ const keyHint = first?.key ? ` (${first.key})` : "";
424
+ throw new ProfileError("USER_ERROR", `Submit for review rejected${keyHint}: ${first?.message ?? "unknown error"}`);
425
+ }
426
+ if (payload.success === false) {
427
+ throw new ProfileError("USER_ERROR", `Submit for review reported success=false${payload.notice ? `: ${payload.notice}` : ""}`);
428
+ }
429
+ return { notice: payload.notice ?? null };
430
+ }
431
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/services/profile/reviews/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,127 @@
1
+ import type { z } from "zod";
2
+ import type { TransportResponse } from "../../transport.js";
3
+ /**
4
+ * Shape of an entry in the GraphQL `errors` array (top-level, not the
5
+ * mutation-payload `errors` field). Mirrors the inline definition in
6
+ * `services/profile/basic/index.ts`; declared here so the four sub-domains
7
+ * landing in #74 share a single source of truth.
8
+ */
9
+ export interface GraphQLErrorEntry {
10
+ message?: string | null;
11
+ extensions?: {
12
+ code?: string | null;
13
+ } | null;
14
+ }
15
+ /**
16
+ * Mutation-payload `UserError` shape (`{ code, key, message }`). Same name
17
+ * on the wire across `updateBasicInfo`, `createEducation`, `updateEducation`,
18
+ * etc. — keeping the interface here lets every sub-domain surface the
19
+ * same error fields uniformly.
20
+ *
21
+ * The canonical schema (`packages/core/src/__generated__/talent-profile.ts`)
22
+ * is the authority: `UserError { code, key, message }`. Selecting `field`
23
+ * (a stale name from earlier inferred drafts) returns a top-level GraphQL
24
+ * error from the server. See #248 for the wire-shape regression.
25
+ */
26
+ export interface UserError {
27
+ code?: string | null;
28
+ key?: string | null;
29
+ message?: string | null;
30
+ }
31
+ /**
32
+ * Returns `true` when `extensions.code` on a GraphQL error indicates the
33
+ * session token has been revoked or expired and the user must re-run
34
+ * `ttctl auth signin`.
35
+ *
36
+ * Recognized stable codes (accumulated across surfaces and history):
37
+ *
38
+ * - `'UNAUTHENTICATED'` — talent-profile (Cloudflare-protected, web-portal API)
39
+ * - `'AUTHENTICATION_REQUIRED'` — defensive carryover from #77 (provenance unverified;
40
+ * see "Empirical history" below)
41
+ * - `'UNAUTHORIZED'` — mobile-gateway (federated `talent_schema` subgraph,
42
+ * empirically observed 2026-05-07 for invalid bearer
43
+ * tokens; see `research/notes/14-auth-error-extensions-code.md`)
44
+ *
45
+ * All three collapse to `AuthRevokedError` so the CLI / MCP surfaces apply a
46
+ * uniform "Run `ttctl auth signin`" recovery hint regardless of which surface
47
+ * raised the failure.
48
+ *
49
+ * **Empirical history** (issue #89): the original predicate (#77) recognized
50
+ * only the first two codes. Live mobile-gateway capture for an invalid bearer
51
+ * token returned HTTP 200 with `errors[0].extensions.code = 'UNAUTHORIZED'`
52
+ * (NOT `'AUTHENTICATION_REQUIRED'` as documentation suggested). All token-
53
+ * shape variants (corrupted, malformed, no-auth, plausible-but-invalid) flow
54
+ * through the same code path with identical responses, so this single code
55
+ * covers every "gateway can't resolve the token to an account" case.
56
+ *
57
+ * Future drift: a server-side rename adds another `||` clause here. New codes
58
+ * MUST be empirically captured before being added — see the research note for
59
+ * the capture procedure.
60
+ */
61
+ export declare function isAuthRevokedExtensionCode(code: string | null | undefined): boolean;
62
+ /**
63
+ * Resolve the signed-in user's `profileId` for use in mutation inputs that
64
+ * follow Pattern 2 (`{ profileId, <entity>: <Entity>Input }`) — see
65
+ * `research/notes/10-mutation-input-patterns.md`. Lazily fetches the
66
+ * profile via `profile.basic.show()` against the mobile-gateway surface,
67
+ * then extracts `viewerRole.profileId`.
68
+ *
69
+ * Errors propagate verbatim — a write attempt that can't read its own
70
+ * profile is unrecoverable, and surfacing the read-side error gives the
71
+ * user the same actionable message as `ttctl profile show`.
72
+ */
73
+ export declare function extractProfileId(token: string): Promise<string>;
74
+ /**
75
+ * Fire a GraphQL request against the talent-profile surface and turn
76
+ * transport-level outcomes into typed errors consistent across the four
77
+ * sub-domains landing in #74.
78
+ *
79
+ * Error mapping:
80
+ * - Underlying typed errors (`TtctlError` subclasses — `Cf403Error`,
81
+ * `AuthRevokedError`, …) propagate as-is so the CLI / MCP surfaces can
82
+ * render their `recovery` hints.
83
+ * - Other transport throws become `ProfileError("NETWORK_ERROR")` with
84
+ * the underlying message tagged by `verb` (e.g. "education list").
85
+ * - HTTP 401 becomes `AuthRevokedError`.
86
+ * - Non-2xx becomes `ProfileError("UNKNOWN")`.
87
+ *
88
+ * Top-level GraphQL `errors` are NOT inspected here — callers handle
89
+ * those via {@link ensureNoTopLevelErrors} once they've narrowed the body
90
+ * to their expected shape.
91
+ *
92
+ * Optional `schema` parameter (Z-3 / #286): when provided, the
93
+ * response `body.data` is validated against the schema as a SIDE
94
+ * EFFECT before the helper returns; on `ZodError` the call throws
95
+ * `ProfileError("WIRE_SHAPE_ERROR")` with the original ZodError
96
+ * chained via `cause` and a field-level diff in the message per
97
+ * `docs/wire-validation-error-format.md`. The return shape is
98
+ * unchanged (`TransportResponse`) — callers still narrow `res.body`
99
+ * to their per-operation type. Schema validation is a guard rail; it
100
+ * does NOT mutate the response. When omitted, the existing pass-
101
+ * through behavior is preserved. No production op wires `schema` in
102
+ * Wave 3; Z-4 (#288) ships the first beachhead.
103
+ */
104
+ export declare function callTalentProfile(token: string, operationName: string, query: string, variables: Record<string, unknown>, verb: string, schema?: z.ZodType): Promise<TransportResponse>;
105
+ /**
106
+ * Throw a typed error when the GraphQL response carries a non-empty
107
+ * top-level `errors` array. Auth-revoked codes (per
108
+ * {@link isAuthRevokedExtensionCode}) become `AuthRevokedError`; anything
109
+ * else becomes `ProfileError("GRAPHQL_ERROR")` tagged by `verb`.
110
+ */
111
+ export declare function ensureNoTopLevelErrors(body: {
112
+ errors?: GraphQLErrorEntry[] | null;
113
+ } | null, verb: string): void;
114
+ /**
115
+ * Inspect a mutation payload for `success === false` or a non-empty
116
+ * `errors` array and convert either into `ProfileError("USER_ERROR")`. The
117
+ * common shape across the four sub-domains' mutation payloads is
118
+ * `{ success?, notice?, errors?: UserError[] }`; this helper handles
119
+ * exactly that subset and lets each sub-domain narrow the rest of the
120
+ * payload itself.
121
+ */
122
+ export declare function applyUserErrorsAndSuccess(payload: {
123
+ success?: boolean | null;
124
+ notice?: string | null;
125
+ errors?: UserError[] | null;
126
+ }, verb: string): void;
127
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/services/profile/shared.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAK7B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAG5D;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CAC9C;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAEnF;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAUrE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,GACjB,OAAO,CAAC,iBAAiB,CAAC,CA6B5B;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAQ/G;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,CAAA;CAAE,EAC1F,IAAI,EAAE,MAAM,GACX,IAAI,CAYN"}