@@ -224,6 +224,10 @@ export const FunctionDebugMetadataSchema = z.object({
224224 acir_locations : z . record ( z . number ( ) ) ,
225225 brillig_locations : z . record ( z . record ( z . number ( ) ) ) ,
226226 } ) ,
227+ // `function_locations` is required in the static `DebugFileMap` type (noir-types compat), but legacy v4-next
228+ // artifacts were compiled before this field existed — see {@link fillMissingFunctionLocations} for the full
229+ // rationale. The preprocessor injects `[]` for missing entries so parsing succeeds; the cast restores the
230+ // output type since `z.preprocess` widens the schema's input type to `unknown`.
227231 files : z . preprocess (
228232 fillMissingFunctionLocations ,
229233 z . record (
@@ -336,8 +340,28 @@ export type DebugFileMap = Record<
336340> ;
337341
338342/**
339- * Fills missing `function_locations` on each entry of a file map with an empty array.
340- * Kept for backwards compatibility with artifacts compiled before `function_locations` was introduced.
343+ * Preprocessor for {@link FunctionDebugMetadataSchema} / {@link ContractArtifactSchema } that injects an empty
344+ * `function_locations` array on any file map entry missing it. Invoked via `z.preprocess(...)` before the strict
345+ * object schema runs, so legacy inputs do not fail validation.
346+ *
347+ * Why this exists on v4-next (and not on `next` / v5):
348+ *
349+ * - The Noir submodule on `next` was bumped past nightly-2026-03-19, which added `function_locations` to the
350+ * debug file map in the Noir `@aztec/noir-types` package and to every freshly-compiled artifact's `file_map`
351+ * entries. The matching stdlib change (making `DebugFileMap.function_locations` required) was done in one
352+ * go on `next`, so there `function_locations` is always present both at compile time and at runtime.
353+ *
354+ * - On v4-next we had to cherry-pick the Noir bump (chore: Update Noir to nightly-2026-04-10 (#22393)) without
355+ * re-running the full noir-projects compilation pipeline, so the on-disk contract and protocol-circuit
356+ * artifacts (e.g. `yarn-project/protocol-contracts/artifacts/AuthRegistry.json`) still carry the pre-update
357+ * `file_map` shape with only `source` + `path`. Separately, `ivc-integration` compares our `DebugFileMap`
358+ * against the Noir-types `DebugFileMap`, which *does* require `function_locations`, so we cannot simply make
359+ * our own field optional — that would break the structural compatibility check.
360+ *
361+ * - To satisfy both constraints at once we keep `function_locations` required in the static TypeScript type
362+ * (so `ivc-integration` keeps compiling) but relax the runtime input shape via this preprocessor (so legacy
363+ * artifacts keep parsing). Once all v4-next artifacts get regenerated with the new Noir compiler, this
364+ * helper — and both `z.preprocess(...)` wrappers — can be removed.
341365 */
342366function fillMissingFunctionLocations ( val : unknown ) : unknown {
343367 if ( val && typeof val === 'object' ) {
@@ -405,6 +429,10 @@ export const ContractArtifactSchema = zodFor<ContractArtifact>()(
405429 globals : z . record ( z . array ( AbiValueSchema ) ) ,
406430 } ) ,
407431 storageLayout : z . record ( z . object ( { slot : schemas . Fr } ) ) ,
432+ // Legacy v4-next contract artifacts (e.g. `protocol-contracts/artifacts/AuthRegistry.json`) were emitted
433+ // before Noir started including `function_locations` in their `file_map` entries — see
434+ // {@link fillMissingFunctionLocations } for the full rationale. The preprocessor injects `[]` for missing
435+ // entries so parsing succeeds without weakening the static `DebugFileMap` type.
408436 fileMap : z . preprocess (
409437 fillMissingFunctionLocations ,
410438 z . record (
0 commit comments