@@ -117,194 +117,67 @@ stages:
117117 env:
118118 AZLDEV_COMMIT: $(AzldevCommit)
119119
120+ # Verify lock files are current. --check-only validates without
121+ # writing, exits nonzero if any lock would change.
120122 - script : |
121123 set -euo pipefail
122-
123- # Workaround for an ADO git config error.
124- # The config key may not be present on every agent image, so tolerate its absence.
125124 git config --unset extensions.worktreeConfig || true
126-
127- # Full history is needed for lock resolution and spec rendering.
128125 if [ "$(git rev-parse --is-shallow-repository)" = "true" ]; then
129126 echo "##[group]Fetching full git history"
130127 git fetch --unshallow
131128 echo "##[endgroup]"
132129 fi
133-
134- echo "##[group]Updating lock files"
135- lock_update_file="$ARTIFACT_STAGING_DIR/lock-update.json"
136- azldev component update -a -q -O json > "$lock_update_file"
137- echo "##[endgroup]"
138-
139- # Also copy into ob_outputDirectory so OneBranch auto-publishes the
140- # lock-update artifact for post-mortem triage.
141- ob_lock_dir="$OB_OUTPUT_DIR/lock-update"
142- mkdir -p "$ob_lock_dir"
143- cp "$lock_update_file" "$ob_lock_dir/"
144-
145- # Check if any locks drifted -- azldev tells us directly.
146- # If locks are not valid, we can't guarantee that any further steps are valid, so fail the PR check immediately.
147- # azldev emits a JSON 'null' when nothing changed; '. // []' coerces that to an
148- # empty array so the downstream '[.[] | ...]' pipeline is well-typed.
149- drifted=$(jq '(. // []) | [.[] | select(.changed == true)] | length' "$lock_update_file")
150- if [[ "$drifted" -gt 0 ]]; then
151- echo "##[error]$drifted lock file(s) are not up to date. Run 'azldev component update -a' and commit the result."
152- echo "##[group]Drifted components"
153- jq -r '(. // []) | .[] | select(.changed == true) | .component' "$lock_update_file"
154- echo "##[endgroup]"
155- exit 1
156- fi
130+ azldev component update --check-only -a -q
157131 displayName: "Verify lock files are up to date"
158132 env:
159- ARTIFACT_STAGING_DIR: $(Build.ArtifactStagingDirectory)
160- # OneBranch containers run as root. azldev refuses to run as root
161- # by default, disable the root security check for this step.
162133 AZLDEV_ALLOW_ROOT: "1"
163- OB_OUTPUT_DIR: $(ob_outputDirectory)
164134
135+ # Detect changed components. azldev hard-fails if any component has
136+ # sourcesChange == true without a corresponding identity change
137+ # (supply-chain drift protection). --include-unchanged ensures the
138+ # full component list is available for downstream consumers.
165139 - script : |
166140 set -euo pipefail
167-
168141 artifact_dir="$ARTIFACT_STAGING_DIR/changed-components"
169142 json_file="$artifact_dir/changed-components.json"
170143 mkdir -p "$artifact_dir"
171144
172- # 'azldev component changed' compares the source and target commits and emits one
173- # entry per component with its 'changeType' ('added', 'changed', 'unchanged', or
174- # 'deleted') plus a 'sourcesChange' flag indicating whether the rendered 'sources'
175- # file differs between commits. Only components with sourcesChange == true AND
176- # changeType != 'deleted' are forwarded to Control Tower for upload.
177-
178- echo "##[group]Computing changed components"
179- azldev component changed --from "$TARGET_COMMIT" --to "$SOURCE_COMMIT" -a --include-unchanged -O json > "$json_file"
180- echo "##[endgroup]"
145+ azldev component changed --from "$TARGET_COMMIT" --to "$SOURCE_COMMIT" \
146+ -a --include-unchanged -O json > "$json_file"
181147
182148 echo "##[group]Changed components (non-unchanged only)"
183- jq '(. // []) | [.[] | select(.changeType != "unchanged")]' "$json_file"
149+ jq '[.[] | select(.changeType != "unchanged")]' "$json_file"
184150 echo "##[endgroup]"
185151
186152 echo "##[group]Upload set (sourcesChange == true, changeType in {added, changed})"
187- upload_count=$(jq -r '(. // []) | [.[] | select(.sourcesChange == true and (.changeType | IN("added", "changed")))] | length' "$json_file")
188- jq -r '(. // []) | .[] | select(.sourcesChange == true and (.changeType | IN("added", "changed"))) | .component' "$json_file" | sort
153+ upload_count=$(jq -r '[.[] | select(.sourcesChange == true and (.changeType | IN("added", "changed")))] | length' "$json_file")
154+ jq -r '.[] | select(.sourcesChange == true and (.changeType | IN("added", "changed"))) | .component' "$json_file" | sort
189155 echo "Total: $upload_count component(s) to upload."
190156 echo "##[endgroup]"
191157
192- # Also write into ob_outputDirectory so OneBranch auto-publishes it
193- # as a build artifact (explicit PublishPipelineArtifact is forbidden).
194158 ob_dir="$OB_OUTPUT_DIR/changed-components"
195159 mkdir -p "$ob_dir"
196160 cp "$json_file" "$ob_dir/"
197161
198162 echo "##vso[task.setvariable variable=changedComponentsFile;isreadonly=true]$json_file"
199163 env:
200164 ARTIFACT_STAGING_DIR: $(Build.ArtifactStagingDirectory)
201- # OneBranch containers run as root. azldev refuses to run as root
202- # by default, disable the root security check for this step.
203165 AZLDEV_ALLOW_ROOT: "1"
204166 OB_OUTPUT_DIR: $(ob_outputDirectory)
205167 SOURCE_COMMIT: $(sourceCommit)
206168 TARGET_COMMIT: $(targetCommit)
207169 displayName: "Compute changed components"
208170
171+ # Render all specs to a staging area and diff against on-disk output.
172+ # --check-only exits nonzero if any component's rendered output would
173+ # change. --clean-stale detects orphan spec directories from deleted
174+ # components.
209175 - script : |
210176 set -euo pipefail
211-
212- # Source/identity consistency tripwire. The rendered 'sources' file
213- # describes the lookaside tarballs Control Tower will fetch and serve to
214- # builds. If a PR mutates that file but the component's input fingerprint
215- # is unchanged, we cannot tell from this side whether that's a hostile
216- # supply-chain swap or an accidental hand-edit / renderer non-determinism
217- # bug. We treat any such record as hostile-by-default and hard-fail, so
218- # the new bytes never reach the lookaside under an existing identity.
219- #
220- # The legitimate cases are explicitly enumerated as an allow-list (added,
221- # changed, deleted) so any future 'changeType' value -- e.g. 'renamed'
222- # that doesn't pull the sources blob into the input fingerprint -- fails
223- # closed until the policy here is reviewed.
224- bogus_count=$(jq -r '(. // []) | [.[] | select(.sourcesChange == true and (.changeType | IN("added", "changed", "deleted") | not))] | length' "$CHANGED_COMPONENTS_FILE")
225- if [[ "$bogus_count" -gt 0 ]]; then
226- echo "##[error]$bogus_count component(s) report a sources change without an accompanying identity change. Refusing to forward to Control Tower."
227- jq -r '(. // []) | .[] | select(.sourcesChange == true and (.changeType | IN("added", "changed", "deleted") | not)) | " - " + .component + " (changeType=" + (.changeType // "null") + ")"' "$CHANGED_COMPONENTS_FILE"
228- exit 1
229- fi
230- echo "OK: every sources change is accompanied by an identity change."
231- # When this step fails, subsequent steps in the job are skipped -- the
232- # bad data never reaches Control Tower. However, the job-level
233- # continueOnError: true means the PR check still reports green to
234- # GitHub. The consistency tripwire becomes truly blocking once ADO
235- # task 19179 removes the job-level flag.
236- env:
237- CHANGED_COMPONENTS_FILE: $(changedComponentsFile)
238- displayName: "Source/identity consistency check"
239-
240- - script : |
241- set -euo pipefail
242-
243- # azldev's renderedSpecsDir is absolute. Translate to repo-relative
244- # so it matches git's output ('git diff --name-only' always emits
245- # repo-relative paths regardless of the path arg form).
246- specs_dir_abs="$(azldev config dump -q -f json | jq -r '.project.renderedSpecsDir')"
247- specs_dir="$(realpath --relative-to="$(pwd)" "$specs_dir_abs")"
248-
249- # Capture git diff under the specs tree so the render set can
250- # include components whose specs were edited directly (which
251- # azldev's input-fingerprint view of "changed" would miss).
252- # --no-renames prevents collapse of delete+add into a rename
253- # entry which would lose the old path. The Python script
254- # filters out deleted/unknown components using the full
255- # changed-components JSON.
256- specs_diff_file="$ARTIFACT_STAGING_DIR/specs-diff.txt"
257- git diff --no-renames --name-only "$TARGET_COMMIT" "$SOURCE_COMMIT" -- "$specs_dir" > "$specs_diff_file"
258-
259- # Render set is the union of:
260- # - components flagged by 'azldev component changed' (inputs differ)
261- # - components whose spec tree was touched directly in the PR
262- changed=$(python3 .github/workflows/scripts/control-tower/compute_render_set.py \
263- --changed-components-file "$CHANGED_COMPONENTS_FILE" \
264- --specs-diff-file "$specs_diff_file" \
265- --specs-dir "$specs_dir")
266-
267- if [[ -z "$changed" ]]; then
268- echo "No changed components -- skipping render."
269- else
270- changed_count=$(echo "$changed" | wc -l)
271- echo "Rendering $changed_count component(s) (azldev dedupes internally)..."
272- echo "##[group]Render set"
273- echo "$changed" | sed 's/^/ - /'
274- echo "##[endgroup]"
275- echo "##[group]Specs rendering"
276- printf '%s\n' "$changed" | xargs -d '\n' azldev component render -q --
277- echo "##[endgroup]"
278- fi
279-
280- # Quick sanity check: no modified or untracked files under specs/.
281- drift=0
282- if ! git diff --quiet -- "$specs_dir"; then
283- echo "##[group]Git diff"
284- git --no-pager diff -- "$specs_dir"
285- echo "##[endgroup]"
286- drift=1
287- fi
288- untracked=$(git ls-files --others -- "$specs_dir")
289- if [[ -n "$untracked" ]]; then
290- echo "##[group]Untracked files"
291- echo "$untracked"
292- echo "##[endgroup]"
293- drift=1
294- fi
295- if [[ "$drift" -ne 0 ]]; then
296- echo "##[error]Specs are not up to date. Run 'azldev component render -q -a --clean-stale' and try again."
297- exit 1
298- fi
177+ azldev component render --check-only -a --clean-stale -q
178+ displayName: "Verify rendered specs are up to date"
299179 env:
300- ARTIFACT_STAGING_DIR: $(Build.ArtifactStagingDirectory)
301- # OneBranch containers run as root. azldev refuses to run as root
302- # by default, disable the root security check for this step.
303180 AZLDEV_ALLOW_ROOT: "1"
304- CHANGED_COMPONENTS_FILE: $(changedComponentsFile)
305- SOURCE_COMMIT: $(sourceCommit)
306- TARGET_COMMIT: $(targetCommit)
307- displayName: "Render changed specs and verify rendered tree"
308181
309182 - task : AzureCLI@2
310183 displayName : " Call Control Tower 'prcheck' API"
0 commit comments