@sanity/ailf 6.0.0 → 6.1.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 (59) hide show
  1. package/config/airbyte/ai_literacy_framework.connector.yaml +276 -0
  2. package/config/bigquery/views/synthesis_parse_failure_rate_7d.sql +42 -0
  3. package/dist/_vendor/ailf-core/artifact-registry.d.ts +17 -0
  4. package/dist/_vendor/ailf-core/artifact-registry.js +14 -0
  5. package/dist/_vendor/ailf-core/ports/context.d.ts +22 -0
  6. package/dist/_vendor/ailf-core/schemas/eval-config.d.ts +7 -0
  7. package/dist/_vendor/ailf-core/schemas/eval-config.js +8 -0
  8. package/dist/_vendor/ailf-core/services/diagnosis/cards/__tests__/failure-mode-summary.test.js +59 -0
  9. package/dist/_vendor/ailf-core/services/diagnosis/cards/doc-attribution-spotlight.js +5 -1
  10. package/dist/_vendor/ailf-core/services/diagnosis/cards/failure-mode-summary.js +47 -3
  11. package/dist/_vendor/ailf-core/services/diagnosis/cards/index.d.ts +10 -0
  12. package/dist/_vendor/ailf-core/services/diagnosis/cards/index.js +13 -0
  13. package/dist/_vendor/ailf-core/services/diagnosis/cards/low-confidence-attribution.js +17 -1
  14. package/dist/_vendor/ailf-core/services/diagnosis/cards/no-issues.js +1 -1
  15. package/dist/_vendor/ailf-core/services/diagnosis/cards/regression-vs-baseline.js +5 -1
  16. package/dist/_vendor/ailf-core/services/diagnosis/cards/top-recommendations.js +5 -1
  17. package/dist/_vendor/ailf-core/services/diagnosis/cards/weakest-area.js +5 -1
  18. package/dist/_vendor/ailf-core/services/diagnosis/prompt-builders.js +15 -2
  19. package/dist/_vendor/ailf-core/services/diagnosis/prompts/weakest-area.system.d.ts +5 -3
  20. package/dist/_vendor/ailf-core/services/diagnosis/prompts/weakest-area.system.js +19 -31
  21. package/dist/_vendor/ailf-core/services/index.d.ts +1 -1
  22. package/dist/_vendor/ailf-core/services/index.js +1 -1
  23. package/dist/_vendor/ailf-core/types/diagnosis.d.ts +3 -0
  24. package/dist/_vendor/ailf-core/types/index.d.ts +7 -0
  25. package/dist/_vendor/ailf-core/types/repo-config.d.ts +16 -0
  26. package/dist/_vendor/ailf-core/types/synthesis-telemetry.d.ts +101 -0
  27. package/dist/_vendor/ailf-core/types/synthesis-telemetry.js +18 -0
  28. package/dist/adapters/config-sources/file-config-adapter.js +8 -6
  29. package/dist/adapters/llm/index.d.ts +1 -1
  30. package/dist/adapters/llm/index.js +1 -1
  31. package/dist/adapters/llm/openai-llm-client.js +7 -2
  32. package/dist/adapters/llm/retry.d.ts +18 -0
  33. package/dist/adapters/llm/retry.js +21 -0
  34. package/dist/adapters/synthesis/synthesis-telemetry-schema.d.ts +49 -0
  35. package/dist/adapters/synthesis/synthesis-telemetry-schema.js +55 -0
  36. package/dist/adapters/task-sources/content-lake-task-source.js +10 -5
  37. package/dist/adapters/task-sources/repo-schemas.d.ts +7 -0
  38. package/dist/adapters/task-sources/repo-schemas.js +10 -0
  39. package/dist/artifact-capture/api-gateway-artifact-writer.d.ts +11 -1
  40. package/dist/artifact-capture/api-gateway-artifact-writer.js +3 -1
  41. package/dist/artifact-capture/batching-api-gateway-artifact-writer.d.ts +11 -1
  42. package/dist/artifact-capture/batching-api-gateway-artifact-writer.js +3 -1
  43. package/dist/artifact-capture/gcs-artifact-writer.d.ts +11 -1
  44. package/dist/artifact-capture/gcs-artifact-writer.js +6 -3
  45. package/dist/artifact-capture/local-fs-artifact-writer.d.ts +11 -1
  46. package/dist/artifact-capture/local-fs-artifact-writer.js +6 -3
  47. package/dist/commands/interpret.d.ts +21 -1
  48. package/dist/commands/interpret.js +13 -4
  49. package/dist/commands/pipeline-action.d.ts +44 -0
  50. package/dist/commands/pipeline-action.js +193 -1
  51. package/dist/commands/run.d.ts +2 -0
  52. package/dist/commands/run.js +2 -0
  53. package/dist/composition-root.d.ts +22 -5
  54. package/dist/composition-root.js +78 -8
  55. package/dist/orchestration/pipeline-orchestrator.js +3 -0
  56. package/dist/orchestration/steps/gap-analysis-step.js +0 -1
  57. package/dist/report-store.d.ts +40 -0
  58. package/dist/report-store.js +88 -0
  59. package/package.json +1 -1
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Zod adapter schema for SynthesisCostTelemetry at the trust boundary.
3
+ *
4
+ * This schema sits at `packages/eval/src/adapters/**` and is therefore
5
+ * scanned by `pnpm check-trust-boundary-satisfies` (D0045). The
6
+ * `satisfies z.ZodType<SynthesisCostTelemetry>` clause makes schema/type
7
+ * drift a build error, not a runtime bug.
8
+ *
9
+ * Used by:
10
+ * - Plan 06-04 `ReportStore.patchSynthesis` — validates telemetry before
11
+ * writing to Sanity (process memory → Sanity write boundary, T-06-04).
12
+ * - Any future Sanity-side reader of `summary.synthesis.diagnosis.*`
13
+ * (Sanity Content Lake → eval process boundary, T-06-04).
14
+ *
15
+ * Security constraints:
16
+ * - No `.passthrough()` — schema is closed to prevent PII leakage from
17
+ * card body text into the telemetry shape (T-06-05).
18
+ * - Satisfies clause is load-bearing (T-06-06); no exemption marker.
19
+ *
20
+ * @see packages/core/src/types/synthesis-telemetry.ts — independently authored domain types
21
+ * @see docs/decisions/D0045-type-architecture-and-contract-enforcement.md
22
+ * @see .planning/phases/06-post-run-integration-cost-telemetry/06-CONTEXT.md §D6-09
23
+ */
24
+ import { z } from "zod";
25
+ /**
26
+ * Enum of all valid card types — mirrors `CardType` from diagnosis.ts.
27
+ * Using `z.enum()` (not `z.string()`) so the schema satisfies
28
+ * `z.ZodType<SynthesisPerCardTelemetry>` (which requires `cardType: CardType`).
29
+ */
30
+ const CardTypeSchema = z.enum([
31
+ "area-summary",
32
+ "failure-mode-summary",
33
+ "no-issues",
34
+ "top-recommendations",
35
+ "weakest-area",
36
+ "low-confidence-attribution",
37
+ "doc-attribution-spotlight",
38
+ "regression-vs-baseline",
39
+ ]);
40
+ const SynthesisPerCardSchema = z.object({
41
+ cardType: CardTypeSchema,
42
+ cost: z.number().nonnegative().optional(),
43
+ parseFailed: z.boolean(),
44
+ latencyMs: z.number().int().nonnegative().optional(),
45
+ tokenInput: z.number().int().nonnegative().optional(),
46
+ tokenOutput: z.number().int().nonnegative().optional(),
47
+ cardVersion: z.string(),
48
+ generatedAt: z.string().datetime({ offset: false }), // ISO 8601 UTC required
49
+ });
50
+ export const SynthesisCostTelemetrySchema = z.object({
51
+ cost: z.number().nonnegative(),
52
+ parseFailureCount: z.number().int().nonnegative(),
53
+ parseFailureRate: z.number().min(0).max(1),
54
+ perCard: z.array(SynthesisPerCardSchema),
55
+ });
@@ -286,16 +286,21 @@ function mapAssertions(raw) {
286
286
  .map((c) => ({ id: c.id, text: c.text })),
287
287
  template: a.template,
288
288
  type: "llm-rubric",
289
- ...(a.weight !== undefined ? { weight: a.weight } : {}),
289
+ // Use `!= null` (loose) so we drop both `undefined` AND `null`.
290
+ // GROQ projects missing scalar fields as `null`, but the domain
291
+ // schema's `z.number().optional()` accepts `T | undefined`, not
292
+ // `T | null` — a strict `!== undefined` check would forward
293
+ // `weight: null` and trigger Zod's "Invalid input" on assertions.
294
+ ...(a.weight != null ? { weight: a.weight } : {}),
290
295
  };
291
296
  }
292
- // Value-based assertion
297
+ // Value-based assertion — same null-vs-undefined hazard as above.
293
298
  const result = { type: a.type };
294
- if (a.value !== undefined)
299
+ if (a.value != null)
295
300
  result.value = a.value;
296
- if (a.threshold !== undefined)
301
+ if (a.threshold != null)
297
302
  result.threshold = a.threshold;
298
- if (a.weight !== undefined)
303
+ if (a.weight != null)
299
304
  result.weight = a.weight;
300
305
  return result;
301
306
  });
@@ -1561,6 +1561,13 @@ export declare const RepoConfigSchema: z.ZodObject<{
1561
1561
  dir: z.ZodOptional<z.ZodString>;
1562
1562
  exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
1563
1563
  }, z.core.$strip>>;
1564
+ summary: z.ZodOptional<z.ZodObject<{
1565
+ onRun: z.ZodOptional<z.ZodEnum<{
1566
+ never: "never";
1567
+ always: "always";
1568
+ auto: "auto";
1569
+ }>>;
1570
+ }, z.core.$strip>>;
1564
1571
  taskSource: z.ZodOptional<z.ZodObject<{
1565
1572
  type: z.ZodOptional<z.ZodEnum<{
1566
1573
  "content-lake": "content-lake";
@@ -646,6 +646,15 @@ const OwnerConfigSchema = z
646
646
  individual: z.string().min(1).optional(),
647
647
  })
648
648
  .optional();
649
+ /**
650
+ * Post-run diagnosis summary policy (Phase 6 / DIAG-06).
651
+ * Sits in the W0077 Phase-6a auto-load pathway.
652
+ */
653
+ const SummaryConfigSchema = z
654
+ .object({
655
+ onRun: z.enum(["auto", "always", "never"]).optional(),
656
+ })
657
+ .optional();
649
658
  /**
650
659
  * Agentic-mode configuration (W0077 Phase 6f). Replaces the retired
651
660
  * `--header` and `--allowed-origin` CLI flags. `headers` is a key/value
@@ -694,6 +703,7 @@ export const RepoConfigSchema = z.object({
694
703
  owner: OwnerConfigSchema,
695
704
  agentic: AgenticConfigSchema,
696
705
  artifacts: ArtifactsConfigSchema,
706
+ summary: SummaryConfigSchema,
697
707
  taskSource: TaskSourceConfigSchema,
698
708
  triggers: z
699
709
  .object({
@@ -27,7 +27,7 @@
27
27
  * @see docs/decisions/D0032-run-anchored-artifact-store.md
28
28
  * @see docs/decisions/D0033-unified-run-anchored-artifact-capture.md
29
29
  */
30
- import { type ArtifactEntry, type ArtifactRef, type ArtifactType, type ArtifactWriter, type AssociationValues, type RunId, type RunManifest } from "../_vendor/ailf-core/index.d.ts";
30
+ import { type ArtifactEntry, type ArtifactRef, type ArtifactType, type ArtifactWriter, type AssociationValues, type RunId, type RunManifest, type WriteSource } from "../_vendor/ailf-core/index.d.ts";
31
31
  import { type UploadMetricsSink } from "./upload-metrics.js";
32
32
  export interface ApiGatewayArtifactWriterOptions {
33
33
  /** Base URL of the API gateway (e.g., "https://ailf-api.sanity.build"). */
@@ -41,10 +41,20 @@ export interface ApiGatewayArtifactWriterOptions {
41
41
  * Defaults to a no-op so the hot path stays free when metrics are off.
42
42
  */
43
43
  metrics?: UploadMetricsSink;
44
+ /**
45
+ * Identifies what kind of execution context owns this writer instance.
46
+ * The D0050 guard refuses to emit a descriptor whose `writePolicy` is
47
+ * different from this value. `"pipeline"` (the default) is for writers
48
+ * driven by an `ailf run` pipeline; `"post-hoc"` is for writers driven
49
+ * by deferred commands like `ailf interpret` that emit descriptors
50
+ * declared `writePolicy: "post-hoc"` (D0050).
51
+ */
52
+ writerSource?: WriteSource;
44
53
  }
45
54
  export declare class ApiGatewayArtifactWriter implements ArtifactWriter {
46
55
  private readonly options;
47
56
  private readonly metrics;
57
+ private readonly writerSource;
48
58
  constructor(options: ApiGatewayArtifactWriterOptions);
49
59
  emit<T extends ArtifactType>(type: T, association: AssociationValues, payload: unknown): Promise<ArtifactRef | null>;
50
60
  appendNdjson(): Promise<ArtifactRef | null>;
@@ -33,14 +33,16 @@ import { NO_OP_UPLOAD_METRICS, } from "./upload-metrics.js";
33
33
  export class ApiGatewayArtifactWriter {
34
34
  options;
35
35
  metrics;
36
+ writerSource;
36
37
  constructor(options) {
37
38
  this.options = options;
38
39
  this.metrics = options.metrics ?? NO_OP_UPLOAD_METRICS;
40
+ this.writerSource = options.writerSource ?? "pipeline";
39
41
  }
40
42
  // ---- Canonical W0049 API ------------------------------------------------
41
43
  async emit(type, association, payload) {
42
44
  const descriptor = ARTIFACT_REGISTRY[type];
43
- assertWritePolicyMatches("pipeline", descriptor);
45
+ assertWritePolicyMatches(this.writerSource, descriptor);
44
46
  const runId = association.run;
45
47
  if (!runId) {
46
48
  console.warn(` ⚠️ emit("${type}"): association.run is required, skipping`);
@@ -25,7 +25,7 @@
25
25
  * does this writer. Traces flow through the GCS-direct writer when ADC
26
26
  * credentials are present.
27
27
  */
28
- import { type ArtifactEntry, type ArtifactRef, type ArtifactType, type ArtifactWriter, type AssociationValues, type RunId, type RunManifest } from "../_vendor/ailf-core/index.d.ts";
28
+ import { type ArtifactEntry, type ArtifactRef, type ArtifactType, type ArtifactWriter, type AssociationValues, type RunId, type RunManifest, type WriteSource } from "../_vendor/ailf-core/index.d.ts";
29
29
  import { type UploadMetricsSink } from "./upload-metrics.js";
30
30
  export interface BatchingApiGatewayArtifactWriterOptions {
31
31
  /** Base URL of the API gateway (e.g., "https://ailf-api.sanity.build"). */
@@ -46,12 +46,22 @@ export interface BatchingApiGatewayArtifactWriterOptions {
46
46
  putConcurrency?: number;
47
47
  /** Optional metrics sink; defaults to no-op. */
48
48
  metrics?: UploadMetricsSink;
49
+ /**
50
+ * Identifies what kind of execution context owns this writer instance.
51
+ * The D0050 guard refuses to emit a descriptor whose `writePolicy` is
52
+ * different from this value. `"pipeline"` (the default) is for writers
53
+ * driven by an `ailf run` pipeline; `"post-hoc"` is for writers driven
54
+ * by deferred commands like `ailf interpret` that emit descriptors
55
+ * declared `writePolicy: "post-hoc"` (D0050).
56
+ */
57
+ writerSource?: WriteSource;
49
58
  }
50
59
  export declare class BatchingApiGatewayArtifactWriter implements ArtifactWriter {
51
60
  private readonly options;
52
61
  private readonly pending;
53
62
  private flushing;
54
63
  private microtaskScheduled;
64
+ private readonly writerSource;
55
65
  constructor(options: BatchingApiGatewayArtifactWriterOptions);
56
66
  emit<T extends ArtifactType>(type: T, association: AssociationValues, payload: unknown): Promise<ArtifactRef | null>;
57
67
  appendNdjson(): Promise<ArtifactRef | null>;
@@ -51,6 +51,7 @@ export class BatchingApiGatewayArtifactWriter {
51
51
  pending = [];
52
52
  flushing = null;
53
53
  microtaskScheduled = false;
54
+ writerSource;
54
55
  constructor(options) {
55
56
  this.options = {
56
57
  apiBaseUrl: options.apiBaseUrl,
@@ -60,11 +61,12 @@ export class BatchingApiGatewayArtifactWriter {
60
61
  putConcurrency: options.putConcurrency ?? DEFAULT_PUT_CONCURRENCY,
61
62
  metrics: options.metrics ?? NO_OP_UPLOAD_METRICS,
62
63
  };
64
+ this.writerSource = options.writerSource ?? "pipeline";
63
65
  }
64
66
  // ---- ArtifactWriter surface --------------------------------------------
65
67
  async emit(type, association, payload) {
66
68
  const descriptor = ARTIFACT_REGISTRY[type];
67
- assertWritePolicyMatches("pipeline", descriptor);
69
+ assertWritePolicyMatches(this.writerSource, descriptor);
68
70
  const runId = association.run;
69
71
  if (!runId) {
70
72
  console.warn(` ⚠️ emit("${type}"): association.run is required, skipping`);
@@ -28,7 +28,7 @@
28
28
  * @see docs/decisions/D0033-unified-run-anchored-artifact-capture.md
29
29
  */
30
30
  import { Storage } from "@google-cloud/storage";
31
- import { type ArtifactEntry, type ArtifactRef, type ArtifactType, type ArtifactWriter, type ArtifactWriterProgressOptions, type AssociationValues, type RunId, type RunManifest } from "../_vendor/ailf-core/index.d.ts";
31
+ import { type ArtifactEntry, type ArtifactRef, type ArtifactType, type ArtifactWriter, type ArtifactWriterProgressOptions, type AssociationValues, type RunId, type RunManifest, type WriteSource } from "../_vendor/ailf-core/index.d.ts";
32
32
  import { type UploadMetricsSink } from "./upload-metrics.js";
33
33
  export interface GcsArtifactWriterOptions {
34
34
  /** GCS bucket name (e.g., "ailf-artifacts") */
@@ -51,6 +51,15 @@ export interface GcsArtifactWriterOptions {
51
51
  * Defaults to a no-op so the hot path stays free when metrics are off.
52
52
  */
53
53
  metrics?: UploadMetricsSink;
54
+ /**
55
+ * Identifies what kind of execution context owns this writer instance.
56
+ * The D0050 guard refuses to emit a descriptor whose `writePolicy` is
57
+ * different from this value. `"pipeline"` (the default) is for writers
58
+ * driven by an `ailf run` pipeline; `"post-hoc"` is for writers driven
59
+ * by deferred commands like `ailf interpret` that emit descriptors
60
+ * declared `writePolicy: "post-hoc"` (D0050).
61
+ */
62
+ writerSource?: WriteSource;
54
63
  }
55
64
  export declare class GcsArtifactWriter implements ArtifactWriter {
56
65
  private client;
@@ -68,6 +77,7 @@ export declare class GcsArtifactWriter implements ArtifactWriter {
68
77
  * measured safe, without forcing the producer to own that knob.
69
78
  */
70
79
  private readonly limiter;
80
+ private readonly writerSource;
71
81
  constructor(options: GcsArtifactWriterOptions);
72
82
  private reportProgress;
73
83
  emit<T extends ArtifactType>(type: T, association: AssociationValues, payload: unknown): Promise<ArtifactRef | null>;
@@ -57,9 +57,11 @@ export class GcsArtifactWriter {
57
57
  * measured safe, without forcing the producer to own that knob.
58
58
  */
59
59
  limiter;
60
+ writerSource;
60
61
  constructor(options) {
61
62
  this.options = options;
62
63
  this.metrics = options.metrics ?? NO_OP_UPLOAD_METRICS;
64
+ this.writerSource = options.writerSource ?? "pipeline";
63
65
  if (options.storage) {
64
66
  this.client = options.storage;
65
67
  }
@@ -79,7 +81,7 @@ export class GcsArtifactWriter {
79
81
  // ---- Canonical W0049 API ------------------------------------------------
80
82
  async emit(type, association, payload) {
81
83
  const descriptor = ARTIFACT_REGISTRY[type];
82
- assertWritePolicyMatches("pipeline", descriptor);
84
+ assertWritePolicyMatches(this.writerSource, descriptor);
83
85
  const runId = association.run;
84
86
  if (!runId) {
85
87
  console.warn(` ⚠️ emit("${type}"): association.run is required, skipping`);
@@ -92,7 +94,8 @@ export class GcsArtifactWriter {
92
94
  const { body } = prepareUploadBody(payload, descriptor.mime);
93
95
  const preview = buildManifestPreview(descriptor, payload);
94
96
  if (descriptor.layout === "bulk") {
95
- const path = descriptor.objectPath(runId);
97
+ const extra = descriptor.extractPathArgs?.(association, payload) ?? [];
98
+ const path = descriptor.objectPath(runId, ...extra);
96
99
  const ref = await this.putBody(path, body, {
97
100
  layout: "bulk",
98
101
  mime: descriptor.mime,
@@ -133,7 +136,7 @@ export class GcsArtifactWriter {
133
136
  }
134
137
  async appendNdjson(type, association, rows) {
135
138
  const descriptor = ARTIFACT_REGISTRY[type];
136
- assertWritePolicyMatches("pipeline", descriptor);
139
+ assertWritePolicyMatches(this.writerSource, descriptor);
137
140
  if (descriptor.mime !== "application/x-ndjson") {
138
141
  console.warn(` ⚠️ appendNdjson("${type}"): descriptor mime is ${descriptor.mime}, not application/x-ndjson — skipping`);
139
142
  return null;
@@ -36,7 +36,7 @@
36
36
  * @see docs/decisions/D0033-unified-run-anchored-artifact-capture.md (§ M4)
37
37
  * @see packages/eval/src/artifact-capture/gcs-artifact-writer.ts (mirror)
38
38
  */
39
- import { type ArtifactEntry, type ArtifactRef, type ArtifactType, type ArtifactWriter, type ArtifactWriterProgressOptions, type AssociationValues, type RunId, type RunManifest } from "../_vendor/ailf-core/index.d.ts";
39
+ import { type ArtifactEntry, type ArtifactRef, type ArtifactType, type ArtifactWriter, type ArtifactWriterProgressOptions, type AssociationValues, type RunId, type RunManifest, type WriteSource } from "../_vendor/ailf-core/index.d.ts";
40
40
  export interface LocalFilesystemArtifactWriterOptions {
41
41
  /**
42
42
  * Absolute or cwd-relative root directory under which `runs/{runId}/…`
@@ -56,10 +56,20 @@ export interface LocalFilesystemArtifactWriterOptions {
56
56
  * render per-batch updates during long export phases.
57
57
  */
58
58
  progress?: ArtifactWriterProgressOptions;
59
+ /**
60
+ * Identifies what kind of execution context owns this writer instance.
61
+ * The D0050 guard refuses to emit a descriptor whose `writePolicy` is
62
+ * different from this value. `"pipeline"` (the default) is for writers
63
+ * driven by an `ailf run` pipeline; `"post-hoc"` is for writers driven
64
+ * by deferred commands like `ailf interpret` that emit descriptors
65
+ * declared `writePolicy: "post-hoc"` (D0050).
66
+ */
67
+ writerSource?: WriteSource;
59
68
  }
60
69
  export declare class LocalFilesystemArtifactWriter implements ArtifactWriter {
61
70
  private readonly options;
62
71
  private readonly excludeSet;
72
+ private readonly writerSource;
63
73
  constructor(options: LocalFilesystemArtifactWriterOptions);
64
74
  private reportProgress;
65
75
  emit<T extends ArtifactType>(type: T, association: AssociationValues, payload: unknown): Promise<ArtifactRef | null>;
@@ -46,9 +46,11 @@ import { redactArtifactData } from "./redact-artifact.js";
46
46
  export class LocalFilesystemArtifactWriter {
47
47
  options;
48
48
  excludeSet;
49
+ writerSource;
49
50
  constructor(options) {
50
51
  this.options = options;
51
52
  this.excludeSet = new Set(options.exclude ?? []);
53
+ this.writerSource = options.writerSource ?? "pipeline";
52
54
  }
53
55
  reportProgress(ref) {
54
56
  const progress = this.options.progress;
@@ -66,7 +68,7 @@ export class LocalFilesystemArtifactWriter {
66
68
  if (this.excludeSet.has(type))
67
69
  return null;
68
70
  const descriptor = ARTIFACT_REGISTRY[type];
69
- assertWritePolicyMatches("pipeline", descriptor);
71
+ assertWritePolicyMatches(this.writerSource, descriptor);
70
72
  const runId = association.run;
71
73
  if (!runId) {
72
74
  console.warn(` ⚠️ emit("${type}"): association.run is required, skipping`);
@@ -81,7 +83,8 @@ export class LocalFilesystemArtifactWriter {
81
83
  // descriptor's capBytes.
82
84
  const preview = buildManifestPreview(descriptor, payload);
83
85
  if (descriptor.layout === "bulk") {
84
- const relPath = descriptor.objectPath(runId);
86
+ const extra = descriptor.extractPathArgs?.(association, payload) ?? [];
87
+ const relPath = descriptor.objectPath(runId, ...extra);
85
88
  const absPath = this.resolve(relPath);
86
89
  const wrote = await this.writeAtomic(absPath, body);
87
90
  if (!wrote)
@@ -128,7 +131,7 @@ export class LocalFilesystemArtifactWriter {
128
131
  if (this.excludeSet.has(type))
129
132
  return null;
130
133
  const descriptor = ARTIFACT_REGISTRY[type];
131
- assertWritePolicyMatches("pipeline", descriptor);
134
+ assertWritePolicyMatches(this.writerSource, descriptor);
132
135
  if (descriptor.mime !== "application/x-ndjson") {
133
136
  console.warn(` ⚠️ appendNdjson("${type}"): descriptor mime is ${descriptor.mime}, not application/x-ndjson — skipping`);
134
137
  return null;
@@ -16,7 +16,7 @@
16
16
  * @see .planning/phases/05-diagnosis-engine-cli-llm-cards/05-AI-SPEC.md §6
17
17
  */
18
18
  import { Command } from "commander";
19
- import type { DiagnosisRunner, VersionedInputs } from "../_vendor/ailf-core/index.d.ts";
19
+ import { type DiagnosisCard, type DiagnosisRunner, type VersionedInputs } from "../_vendor/ailf-core/index.d.ts";
20
20
  interface MinimalReportStore {
21
21
  read(id: string): Promise<unknown | null>;
22
22
  latest(): Promise<unknown | null>;
@@ -39,6 +39,26 @@ export interface InterpretCommandOptions {
39
39
  */
40
40
  readonly versionsFromReport?: (report: unknown) => VersionedInputs;
41
41
  }
42
+ /**
43
+ * Visual status markers — locked visual contract per plan Test 7:
44
+ * ready: "✓", degraded: "⚠", missing: "—"
45
+ *
46
+ * Exported so Plan 06-04's post-run hook imports the SAME object and
47
+ * D6-04's "single formatter, single visual contract" is physically
48
+ * enforced — no copy/paste drift possible.
49
+ */
50
+ export declare const STATUS_ICONS: Record<DiagnosisCard["status"], string>;
51
+ /**
52
+ * Format a single card as a one-line summary string.
53
+ *
54
+ * Format: `<icon> <cardType>: <summary>`
55
+ * Per AI-SPEC §6: distinct icons for ready / degraded / missing.
56
+ *
57
+ * Exported so Plan 06-04's post-run hook imports the SAME function and
58
+ * D6-04's "single formatter, single visual contract" is physically
59
+ * enforced — no copy/paste drift possible.
60
+ */
61
+ export declare function formatCardSummaryLine(card: DiagnosisCard): string;
42
62
  /**
43
63
  * Create the `ailf interpret <reportId>` Commander command.
44
64
  *
@@ -18,6 +18,7 @@
18
18
  import { dirname, resolve } from "path";
19
19
  import { fileURLToPath } from "url";
20
20
  import { Command } from "commander";
21
+ import { CARD_REGISTRY_VERSION, diagnosisVersion, } from "../_vendor/ailf-core/index.js";
21
22
  import { addOutputDirOption } from "./shared/options.js";
22
23
  import { resolveOutputDir } from "./shared/resolve-output-dir.js";
23
24
  // ---------------------------------------------------------------------------
@@ -31,8 +32,12 @@ const ROOT = resolve(__dirname, "..", "..");
31
32
  /**
32
33
  * Visual status markers — locked visual contract per plan Test 7:
33
34
  * ready: "✓", degraded: "⚠", missing: "—"
35
+ *
36
+ * Exported so Plan 06-04's post-run hook imports the SAME object and
37
+ * D6-04's "single formatter, single visual contract" is physically
38
+ * enforced — no copy/paste drift possible.
34
39
  */
35
- const STATUS_ICONS = {
40
+ export const STATUS_ICONS = {
36
41
  ready: "✓",
37
42
  degraded: "⚠",
38
43
  missing: "—",
@@ -52,8 +57,12 @@ function getCardSummaryText(card) {
52
57
  *
53
58
  * Format: `<icon> <cardType>: <summary>`
54
59
  * Per AI-SPEC §6: distinct icons for ready / degraded / missing.
60
+ *
61
+ * Exported so Plan 06-04's post-run hook imports the SAME function and
62
+ * D6-04's "single formatter, single visual contract" is physically
63
+ * enforced — no copy/paste drift possible.
55
64
  */
56
- function formatCardSummaryLine(card) {
65
+ export function formatCardSummaryLine(card) {
57
66
  const icon = STATUS_ICONS[card.status];
58
67
  const text = getCardSummaryText(card);
59
68
  return `${icon} ${card.cardType}: ${text}`;
@@ -82,10 +91,10 @@ function defaultVersionsFromReport(report) {
82
91
  : "unknown",
83
92
  diagnosisVersion: typeof versions?.diagnosisVersion === "string"
84
93
  ? versions.diagnosisVersion
85
- : "0.1.0",
94
+ : diagnosisVersion,
86
95
  cardVersion: typeof versions?.cardVersion === "string"
87
96
  ? versions.cardVersion
88
- : "0.1.0",
97
+ : CARD_REGISTRY_VERSION,
89
98
  };
90
99
  }
91
100
  // ---------------------------------------------------------------------------
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import { type ImpactSummary } from "../pipeline/reverse-mapping.js";
14
14
  import type { DebugOptions, EvalMode } from "../pipeline/types.js";
15
+ import { type Diagnosis, type ReportStorePort, type SynthesisCostTelemetry } from "../_vendor/ailf-core/index.d.ts";
15
16
  import type { PipelineCliOptions } from "./run.js";
16
17
  export interface ResolvedOptions {
17
18
  allowedOriginArgs: string[];
@@ -63,6 +64,12 @@ export interface ResolvedOptions {
63
64
  studioOriginOverride?: string;
64
65
  remote: boolean;
65
66
  repoTasksPath?: string;
67
+ /** Phase 6 / DIAG-06: post-run diagnosis summary policy. Precedence
68
+ * resolution (CLI flag > env > config > auto) lives in
69
+ * shouldRunPostSummary() — this field carries only the config-file
70
+ * signal so the helper has a single typed input.
71
+ */
72
+ summaryOnRun?: "auto" | "always" | "never";
66
73
  taskOption?: string;
67
74
  tagOption?: string[];
68
75
  taskSourceType?: "content-lake" | "repo";
@@ -88,6 +95,43 @@ export interface ResolvedOptions {
88
95
  * Exported so the plan builder can call it independently.
89
96
  */
90
97
  export declare function computeResolvedOptions(opts: PipelineCliOptions): ResolvedOptions;
98
+ /**
99
+ * Determine whether the post-run diagnosis summary hook should fire.
100
+ *
101
+ * 4-level precedence chain (D6-20):
102
+ * Level 1 — CLI flag (absolute): if `cliOpts.summary` is boolean, use it.
103
+ * Level 2 — AILF_INTERPRET_ON_RUN env var (absolute): strict "1"/"0" parse;
104
+ * anything else falls through (T-06-11 spoofing mitigation).
105
+ * Level 3 — config `summary.onRun` (absolute): "always" → true; "never" → false;
106
+ * "auto" or absent falls through to level 4.
107
+ * Level 4 — default auto: TTY && !CI (SC1 default-off in CI).
108
+ */
109
+ export declare function shouldRunPostSummary(cliOpts: PipelineCliOptions, resolvedOnRun: "auto" | "always" | "never" | undefined): boolean;
110
+ export declare function buildSynthesisTelemetry(diagnosis: Diagnosis): SynthesisCostTelemetry;
111
+ /**
112
+ * Run post-pipeline hooks after the pipeline completes.
113
+ *
114
+ * Fires after orchestratePipeline() + writePipelineResult() (D6-02).
115
+ * Hook failure prints to stderr but does NOT change exit code (D6-03).
116
+ * CI default-off: fires only when shouldRunPostSummary returns true (D6-20).
117
+ *
118
+ * @param ctx - App context (composition root wiring)
119
+ * @param result - Pipeline result (includes reportId when published)
120
+ * @param args - Hook options (cliOpts, summaryOnRun from config, optional runnerFactory for tests)
121
+ */
122
+ export declare function runPostPipelineHooks(ctx: {
123
+ reportStore?: ReportStorePort;
124
+ }, result: {
125
+ success: boolean;
126
+ reportId?: string;
127
+ }, args: {
128
+ cliOpts: PipelineCliOptions;
129
+ summaryOnRun?: "auto" | "always" | "never";
130
+ /** Override runner factory for tests — avoids vi.mock of composition root */
131
+ runnerFactory?: (ctx: unknown) => {
132
+ run(opts: unknown): Promise<Diagnosis>;
133
+ };
134
+ }): Promise<void>;
91
135
  /**
92
136
  * Execute the evaluation pipeline.
93
137
  *