@sanity/ailf 6.1.2 → 7.0.0
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/dist/_vendor/ailf-core/constants.d.ts +12 -0
- package/dist/_vendor/ailf-core/constants.js +12 -0
- package/dist/_vendor/ailf-shared/gcs-defaults.d.ts +16 -0
- package/dist/_vendor/ailf-shared/gcs-defaults.js +16 -0
- package/dist/_vendor/ailf-shared/generated/help-content.d.ts +2 -0
- package/dist/_vendor/ailf-shared/generated/help-content.js +140 -0
- package/dist/_vendor/ailf-shared/glossary.d.ts +318 -0
- package/dist/_vendor/ailf-shared/glossary.js +330 -0
- package/dist/_vendor/ailf-shared/help-content.d.ts +10 -0
- package/dist/_vendor/ailf-shared/help-content.js +10 -0
- package/dist/_vendor/ailf-shared/help-topics.d.ts +26 -0
- package/dist/_vendor/ailf-shared/help-topics.js +1 -0
- package/dist/_vendor/ailf-shared/index.d.ts +4 -0
- package/dist/_vendor/ailf-shared/index.js +3 -0
- package/dist/composition-root.js +7 -5
- package/dist/webhook/eval-request-handler.d.ts +32 -29
- package/dist/webhook/eval-request-handler.js +90 -50
- package/package.json +3 -3
|
@@ -10,21 +10,25 @@
|
|
|
10
10
|
* Designed to run in any HTTP environment: Cloudflare Workers, Vercel
|
|
11
11
|
* functions, Express, Hono, etc.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* The eval-request document carries a canonical `PipelineRequest` JSON
|
|
14
|
+
* blob in its `pipelineRequest` field (see W0239). The handler parses it
|
|
15
|
+
* via `PipelineRequestSchema` from `@sanity/ailf-core` and forwards it
|
|
16
|
+
* to the dispatcher as-is. Scoping (release-scoped via `perspective`,
|
|
17
|
+
* task-scoped via `tasks`) is asserted on the parsed `PipelineRequest`
|
|
18
|
+
* — at least one must be present.
|
|
18
19
|
*
|
|
19
20
|
* Flow:
|
|
20
21
|
* 1. Receive eval request payload (from Sanity webhook projection)
|
|
21
|
-
* 2. Validate: must be `ailf.evalRequest` type, `pending` status,
|
|
22
|
-
*
|
|
23
|
-
* 3.
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
* 2. Validate envelope: must be `ailf.evalRequest` type, `pending` status,
|
|
23
|
+
* `pipelineRequest` present
|
|
24
|
+
* 3. Parse + Zod-validate `pipelineRequest` against `PipelineRequestSchema`
|
|
25
|
+
* 4. Assert scoping: parsed request must have `perspective` or `tasks`
|
|
26
|
+
* 5. Dispatch evaluation to GitHub Actions via `repository_dispatch`
|
|
27
|
+
* with `external-eval` event type — the parsed `PipelineRequest`
|
|
28
|
+
* rides as `client_payload.request` unchanged
|
|
29
|
+
* 6. On success: PATCH the eval request document → `status: "dispatched"`
|
|
30
|
+
* 7. On failure: PATCH the eval request document → `status: "failed"` + error
|
|
31
|
+
* 8. Return a structured result
|
|
28
32
|
*
|
|
29
33
|
* ## Sanity Manage Webhook Configuration
|
|
30
34
|
*
|
|
@@ -45,6 +49,7 @@
|
|
|
45
49
|
* @see docs/design-docs/report-store/visibility-workflows.md
|
|
46
50
|
*/
|
|
47
51
|
import { createClient } from "@sanity/client";
|
|
52
|
+
import { PipelineRequestSchema } from "../_vendor/ailf-core/index.js";
|
|
48
53
|
// ---------------------------------------------------------------------------
|
|
49
54
|
// Constants
|
|
50
55
|
// ---------------------------------------------------------------------------
|
|
@@ -116,18 +121,33 @@ export async function handleEvalRequest(payload, config) {
|
|
|
116
121
|
requestId,
|
|
117
122
|
};
|
|
118
123
|
}
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
if (!payload.pipelineRequest) {
|
|
125
|
+
return markFailed("Missing required field: pipelineRequest. The eval-request document " +
|
|
126
|
+
"must carry a canonical PipelineRequest JSON serialization.");
|
|
127
|
+
}
|
|
128
|
+
let parsedRequest;
|
|
129
|
+
try {
|
|
130
|
+
parsedRequest = JSON.parse(payload.pipelineRequest);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
return markFailed(`pipelineRequest is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
134
|
+
}
|
|
135
|
+
const parseResult = PipelineRequestSchema.safeParse(parsedRequest);
|
|
136
|
+
if (!parseResult.success) {
|
|
137
|
+
return markFailed(`pipelineRequest failed PipelineRequestSchema validation: ${parseResult.error.message}`);
|
|
138
|
+
}
|
|
139
|
+
const request = reconcileCallerIdentity(parseResult.data, payload.requestedBy);
|
|
140
|
+
const hasPerspective = !!request.perspective;
|
|
141
|
+
const hasTasks = Array.isArray(request.tasks) && request.tasks.length > 0;
|
|
121
142
|
if (!hasPerspective && !hasTasks) {
|
|
122
|
-
return markFailed("
|
|
123
|
-
"
|
|
124
|
-
"or a tasks array for task-scoped evals.");
|
|
143
|
+
return markFailed("pipelineRequest must scope the evaluation: provide either " +
|
|
144
|
+
"`perspective` (release-scoped) or `tasks` (task-scoped).");
|
|
125
145
|
}
|
|
126
146
|
// -------------------------------------------------------------------------
|
|
127
147
|
// 3. Dispatch evaluation via GitHub Actions
|
|
128
148
|
// -------------------------------------------------------------------------
|
|
129
149
|
const repo = config.githubRepo ?? DEFAULT_REPO;
|
|
130
|
-
const dispatchResult = await dispatchGitHubEval(repo,
|
|
150
|
+
const dispatchResult = await dispatchGitHubEval(repo, request, config);
|
|
131
151
|
// -------------------------------------------------------------------------
|
|
132
152
|
// 4. Update eval request document status
|
|
133
153
|
// -------------------------------------------------------------------------
|
|
@@ -152,46 +172,66 @@ export async function handleEvalRequest(payload, config) {
|
|
|
152
172
|
// Dispatch failed — mark the document as failed
|
|
153
173
|
return markFailed(dispatchResult.error ?? "Unknown dispatch error");
|
|
154
174
|
}
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Internal helpers
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
/**
|
|
179
|
+
* Reconcile caller-claimed identity against the trustworthy Sanity write
|
|
180
|
+
* context.
|
|
181
|
+
*
|
|
182
|
+
* The `pipelineRequest` blob is authored by whoever wrote the Sanity
|
|
183
|
+
* document — a browser writer (App SDK dashboard) can set
|
|
184
|
+
* `executor.name` / `owner.individual` to any string, including
|
|
185
|
+
* someone else's. The webhook's only trustworthy identity signal is
|
|
186
|
+
* `payload.requestedBy` (the Sanity-session-authenticated writer).
|
|
187
|
+
*
|
|
188
|
+
* Per D0037, `owner.team` is caller-supplied (the caller knows their
|
|
189
|
+
* team); `executor.surface` / `executor.type` are caller-supplied.
|
|
190
|
+
* Identity fields (`executor.name`, `executor.githubActor`,
|
|
191
|
+
* `owner.individual`) are overwritten or stripped server-side here so
|
|
192
|
+
* downstream provenance reflects who actually wrote the document, not
|
|
193
|
+
* what they claimed.
|
|
194
|
+
*
|
|
195
|
+
* When `requestedBy` is missing (legacy documents), the executor/owner
|
|
196
|
+
* identity fields are stripped — the pipeline's server-side detection
|
|
197
|
+
* fills them as best it can.
|
|
198
|
+
*/
|
|
199
|
+
function reconcileCallerIdentity(request, requestedBy) {
|
|
200
|
+
const out = { ...request };
|
|
201
|
+
if (request.executor) {
|
|
202
|
+
out.executor = {
|
|
203
|
+
...request.executor,
|
|
204
|
+
...(requestedBy ? { name: requestedBy } : { name: undefined }),
|
|
205
|
+
githubActor: undefined,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
if (request.owner) {
|
|
209
|
+
out.owner = {
|
|
210
|
+
...request.owner,
|
|
211
|
+
...(requestedBy
|
|
212
|
+
? { individual: requestedBy }
|
|
213
|
+
: { individual: undefined }),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
155
218
|
/**
|
|
156
219
|
* Dispatch an evaluation via GitHub Actions repository_dispatch.
|
|
157
220
|
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
221
|
+
* Forwards the already-validated `PipelineRequest` as-is under
|
|
222
|
+
* `client_payload.request` — no field translation, no hardcoded
|
|
223
|
+
* overrides. The workflow passes the request to the CLI via `--config`.
|
|
224
|
+
*
|
|
225
|
+
* Workflow-level metadata (`caller_repo`) stays at the top level of
|
|
226
|
+
* `client_payload` for the workflow to read, separate from the
|
|
227
|
+
* pipeline-invocation contract.
|
|
162
228
|
*/
|
|
163
|
-
async function dispatchGitHubEval(repo,
|
|
229
|
+
async function dispatchGitHubEval(repo, request, config) {
|
|
164
230
|
const url = `${GITHUB_API}/repos/${repo}/dispatches`;
|
|
165
|
-
const hasPerspective = !!payload.perspective;
|
|
166
|
-
const hasTasks = Array.isArray(payload.tasks) && payload.tasks.length > 0;
|
|
167
|
-
const hasAreas = Array.isArray(payload.areas) && payload.areas.length > 0;
|
|
168
|
-
// Nest the PipelineRequest under `request` to stay within GitHub's
|
|
169
|
-
// 10-property limit on client_payload. Workflow-level metadata
|
|
170
|
-
// (caller_repo) stays at the top level for the workflow to read.
|
|
171
231
|
const body = {
|
|
172
232
|
client_payload: {
|
|
173
233
|
caller_repo: "sanity-io/www-sanity-io",
|
|
174
|
-
request
|
|
175
|
-
dataset: payload.dataset,
|
|
176
|
-
mode: payload.mode,
|
|
177
|
-
projectId: payload.projectId,
|
|
178
|
-
publish: true,
|
|
179
|
-
source: "production",
|
|
180
|
-
// Studio-initiated evals always use Content Lake as the task source.
|
|
181
|
-
// Without this, the pipeline only loads filesystem .task.ts files and
|
|
182
|
-
// Studio-owned tasks are invisible.
|
|
183
|
-
taskMode: "content-lake",
|
|
184
|
-
// Release-scoped fields
|
|
185
|
-
...(hasPerspective ? { perspective: payload.perspective } : {}),
|
|
186
|
-
// Task-scoped fields
|
|
187
|
-
...(hasTasks ? { tasks: payload.tasks } : {}),
|
|
188
|
-
...(hasAreas ? { areas: payload.areas } : {}),
|
|
189
|
-
...(payload.debug ? { debug: true } : {}),
|
|
190
|
-
...(payload.tag ? { publishTag: payload.tag } : {}),
|
|
191
|
-
...(payload.sourceReportId
|
|
192
|
-
? { sourceReportId: payload.sourceReportId }
|
|
193
|
-
: {}),
|
|
194
|
-
},
|
|
234
|
+
request,
|
|
195
235
|
},
|
|
196
236
|
event_type: "external-eval",
|
|
197
237
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/ailf",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"tsx": "^4.19.2",
|
|
57
57
|
"typescript": "^5.7.3",
|
|
58
58
|
"vitest": "^4.1.5",
|
|
59
|
-
"@sanity/ailf-
|
|
60
|
-
"@sanity/ailf-
|
|
59
|
+
"@sanity/ailf-shared": "0.1.0",
|
|
60
|
+
"@sanity/ailf-core": "0.1.0"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "tsc && tsc -p tsconfig.scripts.json && tsx scripts/bundle-workspace-deps.ts",
|