fix(safe-outputs): create build attachments via DistributedTask timeline attachment API#1433
Conversation
The upload-build-attachment executor PUT to
/_apis/build/builds/{id}/attachments/{type}/{name}, but ADO's
Build>Attachments REST API is read-only (Get/List) — that create route
never existed, so every live upload 404'd with "controller ... does not
implement IController" (executor-e2e build 619362).
Build attachments are created via the DistributedTask timeline
attachment API — the same mechanism as `#`#vso[task.addattachment] —
which targets the current job's timeline record and is therefore
current-run only.
- Attach via PUT .../distributedtask/hubs/build/plans/{planId}
/timelines/{timelineId}/records/{recordId}/attachments/{type}/{name}.
- Add plan_id/timeline_id/job_id to ExecutionContext from SYSTEM_PLANID/
SYSTEM_TIMELINEID/SYSTEM_JOBID (auto-injected; no compiler change).
- Reject build_id that differs from the current run with a clear message.
- Remove the now-meaningless allowed-build-ids config; codemod 0005
auto-strips it from source with a compile warning (leaves
upload-pipeline-artifact untouched).
- Update MCP tool description and docs (safe-outputs, codemods).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The DistributedTask timeline attachment route is project-scoped (like the Build area), not collection-scoped. Empirically verified: the project-scoped route matches (structured error) while the collection-scoped one 404s as an unmatched route (executor-e2e build 619387). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The DistributedTask timeline-attachment route's {scopeIdentifier} is the
project GUID (SYSTEM_TEAMPROJECTID), not the project name — the name
routes but is rejected HTTP 400. Use api-version 7.1 (released) and read
the attachment URL from _links.self.href (no top-level url field).
Empirically verified against build 619390's plan/timeline/record.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Looks good — correct fix for a real API bug, well-documented, follows project conventions throughout. Findings
|
…ject_id path - test_executor_rejects_sha256_mismatch: use two genuinely 17-byte strings so the 'same size, wrong hash' claim holds and the test truly exercises the SHA-256 check independent of the size check. - Add test_executor_fails_when_project_id_missing covering the SYSTEM_TEAMPROJECTID .context()? path (scope identifier) directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Thanks for the review. Addressed in 129377e:
|
🔍 Rust PR ReviewSummary: Looks good — correct fix for a real production bug, well-tested, with minor notes below. Findings✅ What Looks Good
|
Summary
Fixes
upload-build-attachment, which failed against the live ADO server in thedeterministic executor-e2e pipeline (build 619362):
Root cause: the executor
PUTs to/_apis/build/builds/{id}/attachments/{type}/{name}, but ADO's Build ▸Attachments REST API is read-only (
Get/Listonly) — that create routenever existed, so every live upload 404'd as an unmatched route.
Fix
Build attachments are created via the DistributedTask timeline attachment
API — the same mechanism as the
##vso[task.addattachment]logging command. Theresulting object is a build attachment (same
{type}/{name}, read back viathe Build ▸ Attachments Get/List API); only the write endpoint changes. Because a
timeline attachment targets the current job's record, it is current-run only.
Verified working endpoint (empirically confirmed):
SYSTEM_TEAMPROJECTID) — the projectname routes but is rejected
HTTP 400; a collection-scoped URL404s._links.self.href(there is no top-levelurlfield).Changes
result.rs: addplan_id/timeline_id/job_idtoExecutionContextfrom
SYSTEM_PLANID/SYSTEM_TIMELINEID/SYSTEM_JOBID(auto-injectedpredefined vars — no compiler change needed).
upload_build_attachment.rs: switch to the timeline-attachment endpoint;reject a
build_idthat differs from the current run with a clear message;remove the now-meaningless
allowed-build-idsconfig; parse_links.self.href; rewrite the module doc.0005_drop_build_attachment_allowed_build_ids: auto-stripssafe-outputs.upload-build-attachment.allowed-build-ids(with a compilewarning). Leaves
upload-pipeline-artifact.allowed-build-idsuntouched.mcp.rstool description +docs/safe-outputs.md/docs/codemods.md.Semantic note
There is no user-facing difference in the resulting attachment — a timeline
attachment is a build attachment, and it is read back through the same Build ▸
Attachments API. The only behavioural change is that attaching to an arbitrary
other build is no longer offered (it was never actually possible).
Testing
cargo testfull suite (2511 unit + all integration),cargo clippy --all-targets --all-features— clean. (Nocargo fmt: local rustfmt 1.9.0reformats unrelated committed code and CI has no fmt gate.)
this branch (build 619391) passes: 20 total, 19 passed, 0 failed, 1
skipped, with
[PASS] upload-build-attachment.Fixes the failure tracked in
jamesadevine/ado-aw-issues#8.