@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,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"}
|