@sanity/ailf 4.0.4 → 4.0.6

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.
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @sanity/ailf-core — Cross-package sentinel constants.
3
+ *
4
+ * Sentinel strings shared between the init template (which writes them
5
+ * into `.github/workflows/ailf-eval.yml`) and the consumers that must
6
+ * recognize them (run-context builder, caller envelope assembly, CLI
7
+ * pre-flight checks). Centralizing here keeps the placeholder definition
8
+ * in exactly one place — duplicating the literal across producers and
9
+ * consumers historically caused the literal to leak into Sanity reports
10
+ * (W0143).
11
+ */
12
+ /**
13
+ * The literal string `ailf init` writes into the scaffolded GitHub Actions
14
+ * workflow as the value of `AILF_OWNER_TEAM`. Consumers treat this string
15
+ * as semantically unset — it must never end up persisted as a real team
16
+ * slug on a run report.
17
+ *
18
+ * @see packages/core/examples/ailf-eval-workflow.yml — the canonical source
19
+ * @see packages/eval/src/pipeline/run-context.ts — sanitizes on read
20
+ * @see packages/eval/src/adapters/api-client/build-request.ts — drops on the wire
21
+ */
22
+ export declare const PLACEHOLDER_OWNER_TEAM = "<REPLACE-WITH-YOUR-TEAM-SLUG>";
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @sanity/ailf-core — Cross-package sentinel constants.
3
+ *
4
+ * Sentinel strings shared between the init template (which writes them
5
+ * into `.github/workflows/ailf-eval.yml`) and the consumers that must
6
+ * recognize them (run-context builder, caller envelope assembly, CLI
7
+ * pre-flight checks). Centralizing here keeps the placeholder definition
8
+ * in exactly one place — duplicating the literal across producers and
9
+ * consumers historically caused the literal to leak into Sanity reports
10
+ * (W0143).
11
+ */
12
+ /**
13
+ * The literal string `ailf init` writes into the scaffolded GitHub Actions
14
+ * workflow as the value of `AILF_OWNER_TEAM`. Consumers treat this string
15
+ * as semantically unset — it must never end up persisted as a real team
16
+ * slug on a run report.
17
+ *
18
+ * @see packages/core/examples/ailf-eval-workflow.yml — the canonical source
19
+ * @see packages/eval/src/pipeline/run-context.ts — sanitizes on read
20
+ * @see packages/eval/src/adapters/api-client/build-request.ts — drops on the wire
21
+ */
22
+ export const PLACEHOLDER_OWNER_TEAM = "<REPLACE-WITH-YOUR-TEAM-SLUG>";
@@ -17,6 +17,7 @@ export * from "./services/index.js";
17
17
  export * from "./examples/index.js";
18
18
  export * from "./artifact-registry.js";
19
19
  export * from "./batch-signing.js";
20
+ export * from "./constants.js";
20
21
  export { defineCanaryTasks, defineConfig, defineFeatures, defineModeBase, defineModels, definePricingTable, definePreset, definePrompts, defineRubrics, defineSchedules, defineSinks, defineSources, defineTask, defineTestBudgets, defineThresholds, } from "./config-helpers.js";
21
22
  export type { PricingEntry, PromptEntry, SourceEntry, } from "./config-helpers.js";
22
23
  export { env } from "./env-helper.js";
@@ -17,6 +17,7 @@ export * from "./services/index.js";
17
17
  export * from "./examples/index.js";
18
18
  export * from "./artifact-registry.js";
19
19
  export * from "./batch-signing.js";
20
+ export * from "./constants.js";
20
21
  // ---------------------------------------------------------------------------
21
22
  // Architecture overhaul — Phase 0 helpers
22
23
  // ---------------------------------------------------------------------------
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { existsSync } from "fs";
16
16
  import { resolve } from "path";
17
- import { PipelineRequestSchema, } from "../../_vendor/ailf-core/index.js";
17
+ import { PLACEHOLDER_OWNER_TEAM, PipelineRequestSchema, } from "../../_vendor/ailf-core/index.js";
18
18
  import { LEGACY_EVAL_MODE_ALIASES, isRunClassification, } from "../../_vendor/ailf-shared/index.js";
19
19
  import { RepoTaskSource } from "../task-sources/repo-task-source.js";
20
20
  const LEGACY_LITERACY_VARIANT_SET = new Set(LEGACY_EVAL_MODE_ALIASES);
@@ -316,7 +316,12 @@ export function buildCallerEnvelope(config) {
316
316
  }
317
317
  }
318
318
  // Owner: flag > env. Team required, individual optional.
319
- const team = config.ownerTeamOption ?? process.env.AILF_OWNER_TEAM?.trim() ?? undefined;
319
+ // W0143: drop the init-template placeholder when a consumer hasn't
320
+ // filled in their team slug yet we treat it as unset rather than
321
+ // shipping the literal `<REPLACE-WITH-YOUR-TEAM-SLUG>` across the wire
322
+ // and into the report's Provenance card.
323
+ const rawTeam = config.ownerTeamOption ?? process.env.AILF_OWNER_TEAM?.trim() ?? undefined;
324
+ const team = rawTeam === PLACEHOLDER_OWNER_TEAM ? undefined : rawTeam;
320
325
  const individual = config.ownerIndividualOption ??
321
326
  process.env.AILF_OWNER_INDIVIDUAL?.trim() ??
322
327
  process.env.GITHUB_ACTOR?.trim() ??
@@ -376,12 +376,16 @@ export function parseCanonicalTaskFile(raw, filename) {
376
376
  // (featureArea, canonicalDocs, assert, vars), surface a helpful error
377
377
  // message telling them what the canonical names are.
378
378
  // ---------------------------------------------------------------------------
379
+ // Phrasing avoids literal `{` and `}` characters on purpose. GitHub Actions
380
+ // registers each line of a multi-line secret as a mask, so a pretty-printed
381
+ // JSON secret introduces standalone `{` / `}` masks that then redact every
382
+ // `{` / `}` in subsequent log output. See W0144.
379
383
  /** Old field names from @sanity/ailf-tasks → canonical equivalents */
380
384
  const LEGACY_FIELD_MAP = {
381
385
  featureArea: "area",
382
- canonicalDocs: "context.docs (nested under context: { docs: [...] })",
386
+ canonicalDocs: "context.docs (move the docs array under a context parent)",
383
387
  assert: "assertions",
384
- vars: "prompt (nested under prompt: { text: ... })",
388
+ vars: "prompt (move the text string under a prompt parent)",
385
389
  };
386
390
  /**
387
391
  * Detect legacy field names in raw task data and return helpful messages.
@@ -20,6 +20,7 @@ import { buildAppContext, parseArtifactUploadEnv, } from "../orchestration/build
20
20
  import { buildStepSequence } from "../orchestration/build-step-sequence.js";
21
21
  import { orchestratePipeline } from "../orchestration/pipeline-orchestrator.js";
22
22
  import { load } from "js-yaml";
23
+ import { PLACEHOLDER_OWNER_TEAM } from "../_vendor/ailf-core/index.js";
23
24
  import { parseRepoConfig, } from "../adapters/task-sources/repo-schemas.js";
24
25
  import { getCallerCwd, resolveOutputDir } from "./shared/resolve-output-dir.js";
25
26
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -406,6 +407,7 @@ function resolveRepoTasksPath(callerCwd, explicitPath, taskSourceType) {
406
407
  * 4. Delegate to the PipelineOrchestrator
407
408
  */
408
409
  export async function executePipeline(cliOpts) {
410
+ warnIfPlaceholderOwnerTeam();
409
411
  // When --config is provided, resolve config from file instead of CLI flags
410
412
  if (cliOpts.config) {
411
413
  const { existsSync } = await import("fs");
@@ -493,6 +495,20 @@ export async function executePipeline(cliOpts) {
493
495
  // ---------------------------------------------------------------------------
494
496
  // Internal helpers
495
497
  // ---------------------------------------------------------------------------
498
+ /**
499
+ * W0143: warn once on stderr when the consumer is still running with the
500
+ * unedited init-template placeholder for AILF_OWNER_TEAM. The run
501
+ * continues — provenance just gets attributed as unset rather than the
502
+ * literal placeholder.
503
+ */
504
+ function warnIfPlaceholderOwnerTeam() {
505
+ if (process.env.AILF_OWNER_TEAM?.trim() !== PLACEHOLDER_OWNER_TEAM)
506
+ return;
507
+ console.warn(` ⚠️ AILF_OWNER_TEAM is still set to the init-template placeholder ` +
508
+ `"${PLACEHOLDER_OWNER_TEAM}". Provenance will be logged as unset. ` +
509
+ `Set a real team slug in .github/workflows/ailf-eval.yml (or unset ` +
510
+ `AILF_OWNER_TEAM) to attribute this run.`);
511
+ }
496
512
  /**
497
513
  * Resolve CLI options into typed ResolvedOptions.
498
514
  */
@@ -34,11 +34,16 @@ export class FetchDocsStep {
34
34
  // a mismatch where configs reference context files that were never
35
35
  // fetched.
36
36
  //
37
- // Content Lake path: use ctx.taskSource (ContentLakeTaskSource) which
38
- // loads Studio-owned ailf.task documents via GROQ.
39
- // Filesystem path: load from .task.ts files (repo/inline tasks).
37
+ // Adapter path: ctx.taskSource handles both content-lake and repo modes.
38
+ // The composition root wires the right adapter (ContentLakeTaskSource
39
+ // or RepoTaskSource) per taskSourceType. RepoTaskSource loads BOTH
40
+ // .yaml and .task.ts files — necessary for external-consumer evals
41
+ // that materialize inline tasks as YAML (W0148).
42
+ // Filesystem path: load from .task.ts files (legacy unset path —
43
+ // AILF defaults from tasks/${mode}/ + optional repoTasksPath augment).
40
44
  let allTasks;
41
- if (ctx.config.taskSourceType === "content-lake") {
45
+ if (ctx.config.taskSourceType === "content-lake" ||
46
+ ctx.config.taskSourceType === "repo") {
42
47
  const filter = {
43
48
  ...(ctx.config.areas?.length ? { areas: ctx.config.areas } : {}),
44
49
  ...(ctx.config.tasks?.length ? { taskIds: ctx.config.tasks } : {}),
@@ -19,13 +19,14 @@ export declare class GenerateConfigsStep implements PipelineStep {
19
19
  private compileSingleMode;
20
20
  private loadTasks;
21
21
  /**
22
- * Load tasks from the Content Lake via ctx.taskSource.
22
+ * Load tasks via ctx.taskSource (the composition-root-wired adapter).
23
23
  *
24
- * The ContentLakeTaskSource adapter handles area/task/tag filtering
25
- * in the GROQ query itself, so we build a FilterOptions and pass it
26
- * through rather than filtering in-memory after loading.
24
+ * Used for both `taskSourceType === "content-lake"` (ContentLakeTaskSource)
25
+ * and `taskSourceType === "repo"` (RepoTaskSource). Filtering by
26
+ * area/task/tag is delegated to the adapter — ContentLakeTaskSource
27
+ * pushes it into the GROQ query, RepoTaskSource applies it in-memory.
27
28
  */
28
- private loadTasksFromContentLake;
29
+ private loadTasksFromAdapter;
29
30
  /**
30
31
  * Load tasks from filesystem .task.ts files.
31
32
  *
@@ -209,23 +209,30 @@ export class GenerateConfigsStep {
209
209
  // Task loading — unified for all modes
210
210
  // ---------------------------------------------------------------------------
211
211
  async loadTasks(ctx, mode, state) {
212
- // Content Lake path — use ctx.taskSource (ContentLakeTaskSource) which
213
- // loads ailf.task documents via GROQ. This is the only path that sees
214
- // Studio-owned tasks (ownership: "studio").
215
- if (ctx.config.taskSourceType === "content-lake") {
216
- return this.loadTasksFromContentLake(ctx, state);
212
+ // Adapter path — use ctx.taskSource. The composition root wires the
213
+ // right adapter for each taskSourceType:
214
+ // - "content-lake" → ContentLakeTaskSource (Studio-owned ailf.task docs)
215
+ // - "repo" → RepoTaskSource (loads .yaml AND .task.ts from repoTasksPath)
216
+ // Routing both through ctx.taskSource keeps the orchestration step
217
+ // file-format-agnostic (W0148: external-consumer evals materialize
218
+ // inline tasks as .yaml, which loadPipelineTasks can't read).
219
+ if (ctx.config.taskSourceType === "content-lake" ||
220
+ ctx.config.taskSourceType === "repo") {
221
+ return this.loadTasksFromAdapter(ctx, state);
217
222
  }
218
- // Filesystem path — load from .task.ts files (repo tasks, inline tasks).
223
+ // Filesystem path — load from .task.ts files (legacy unset path:
224
+ // AILF defaults from tasks/${mode}/ + optional repoTasksPath augment).
219
225
  return this.loadTasksFromFilesystem(ctx, mode, state);
220
226
  }
221
227
  /**
222
- * Load tasks from the Content Lake via ctx.taskSource.
228
+ * Load tasks via ctx.taskSource (the composition-root-wired adapter).
223
229
  *
224
- * The ContentLakeTaskSource adapter handles area/task/tag filtering
225
- * in the GROQ query itself, so we build a FilterOptions and pass it
226
- * through rather than filtering in-memory after loading.
230
+ * Used for both `taskSourceType === "content-lake"` (ContentLakeTaskSource)
231
+ * and `taskSourceType === "repo"` (RepoTaskSource). Filtering by
232
+ * area/task/tag is delegated to the adapter — ContentLakeTaskSource
233
+ * pushes it into the GROQ query, RepoTaskSource applies it in-memory.
227
234
  */
228
- async loadTasksFromContentLake(ctx, state) {
235
+ async loadTasksFromAdapter(ctx, state) {
229
236
  const filter = {
230
237
  ...(ctx.config.areas?.length ? { areas: ctx.config.areas } : {}),
231
238
  ...(ctx.config.tasks?.length ? { taskIds: ctx.config.tasks } : {}),
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * @see docs/decisions/D0032-run-anchored-artifact-store.md (§ Move 5 — Drift Prevention)
14
14
  */
15
- import type { Logger, RunContext } from "../_vendor/ailf-core/index.d.ts";
15
+ import { type Logger, type RunContext } from "../_vendor/ailf-core/index.d.ts";
16
16
  import { type RunClassification, type RunExecutor, type RunExecutorSurface, type RunHost, type RunLineage, type RunOwner, type RunTool } from "../_vendor/ailf-shared/index.d.ts";
17
17
  import type { ResolvedSourceConfig } from "../sources.js";
18
18
  import type { EvalMode } from "./types.js";
@@ -92,6 +92,11 @@ export declare function detectClassification(log: Logger): RunClassification;
92
92
  /**
93
93
  * Resolve `owner` from `AILF_OWNER_TEAM` (+ optional
94
94
  * `AILF_OWNER_INDIVIDUAL`). `team` is free-form; default is `"unknown"`.
95
+ *
96
+ * The init-template placeholder (`PLACEHOLDER_OWNER_TEAM`) is treated as
97
+ * if the env var were unset — consumers that haven't filled it in yet
98
+ * end up with `team: "unknown"` instead of the literal placeholder being
99
+ * persisted onto the report (W0143).
95
100
  */
96
101
  export declare function detectOwner(): RunOwner;
97
102
  /**
@@ -15,6 +15,7 @@
15
15
  import { execSync } from "node:child_process";
16
16
  import { createRequire } from "node:module";
17
17
  import * as os from "node:os";
18
+ import { PLACEHOLDER_OWNER_TEAM, } from "../_vendor/ailf-core/index.js";
18
19
  import { isRunClassification, } from "../_vendor/ailf-shared/index.js";
19
20
  import { ConsoleLogger } from "../adapters/loggers/index.js";
20
21
  import { tryLoadConfigFile } from "./compiler/config-loader.js";
@@ -45,7 +46,15 @@ export function buildRunContext(input) {
45
46
  // preservation across the --remote boundary.
46
47
  const envelope = input.callerEnvelope;
47
48
  const classification = envelope?.classification ?? detectClassification(log);
48
- const owner = envelope?.owner ?? detectOwner();
49
+ // W0143: a caller-supplied owner whose team is the init-template
50
+ // placeholder is treated as "user hasn't filled this in yet" — drop the
51
+ // envelope owner and fall through to env detection (which performs the
52
+ // same sanitization on AILF_OWNER_TEAM). Avoids persisting the literal
53
+ // placeholder verbatim on the report.
54
+ const sanitizedEnvelopeOwner = envelope?.owner && envelope.owner.team !== PLACEHOLDER_OWNER_TEAM
55
+ ? envelope.owner
56
+ : undefined;
57
+ const owner = sanitizedEnvelopeOwner ?? detectOwner();
49
58
  const executor = envelope?.executor ?? detectExecutor();
50
59
  // `tool` and `host` are server-environment facts — they always reflect
51
60
  // where this pipeline is actually running, never what a caller claimed.
@@ -184,9 +193,15 @@ export function detectClassification(log) {
184
193
  /**
185
194
  * Resolve `owner` from `AILF_OWNER_TEAM` (+ optional
186
195
  * `AILF_OWNER_INDIVIDUAL`). `team` is free-form; default is `"unknown"`.
196
+ *
197
+ * The init-template placeholder (`PLACEHOLDER_OWNER_TEAM`) is treated as
198
+ * if the env var were unset — consumers that haven't filled it in yet
199
+ * end up with `team: "unknown"` instead of the literal placeholder being
200
+ * persisted onto the report (W0143).
187
201
  */
188
202
  export function detectOwner() {
189
- const team = process.env.AILF_OWNER_TEAM?.trim() || "unknown";
203
+ const rawTeam = process.env.AILF_OWNER_TEAM?.trim();
204
+ const team = rawTeam && rawTeam !== PLACEHOLDER_OWNER_TEAM ? rawTeam : "unknown";
190
205
  const individual = process.env.AILF_OWNER_INDIVIDUAL?.trim() || undefined;
191
206
  return individual ? { individual, team } : { team };
192
207
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/ailf",
3
- "version": "4.0.4",
3
+ "version": "4.0.6",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"