Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 46 additions & 10 deletions docs/ado-script.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,26 +273,62 @@ non-zero:

### Env-var contract

The compiler injects these on the `node exec-context-pr-synth.js`
step; the predefined `SYSTEM_PULLREQUEST_*` variables are auto-mapped
into the process env by ADO at runtime:
The compiler injects only the non-auto-injected `SYSTEM_ACCESSTOKEN` bearer
(via the shared bundle-auth applier — see [Bundle env contract](#bundle-env-contract))
plus the computed `PR_SYNTH_SPEC` on the `node exec-context-pr-synth.js` step.
Every predefined `System.*` / `Build.*` variable the bundle reads
(`SYSTEM_TEAMPROJECT`, `BUILD_REPOSITORY_ID`, `BUILD_REASON`,
`BUILD_REPOSITORY_PROVIDER`, `BUILD_SOURCEBRANCH`, `SYSTEM_PULLREQUEST_*`) is
auto-mapped into the process env by ADO at runtime, so it is **not**
re-projected on the step:

| Env var | Source | Purpose |
|---|---|---|
| `PR_SYNTH_SPEC` | compiled inline (base64) | The branch/path filter spec |
| `SYSTEM_ACCESSTOKEN` | `$(System.AccessToken)` | ADO REST auth |
| `SYSTEM_ACCESSTOKEN` | `$(System.AccessToken)` (bundle-auth applier) | ADO REST auth |
| `SYSTEM_COLLECTIONURI` | ADO auto-injected | ADO org base URL (read via `getWebApi()`; falls back to `SYSTEM_TEAMFOUNDATIONCOLLECTIONURI`) |
| `ADO_PROJECT` | `$(System.TeamProject)` | ADO project for the PR lookup |
| `ADO_REPO_ID` | `$(Build.Repository.ID)` | Repository id for the PR lookup |
| `BUILD_REASON` | `$(Build.Reason)` | Distinguishes CI from PR builds |
| `BUILD_REPOSITORY_PROVIDER` | `$(Build.Repository.Provider)` | Detects GitHub-typed repos |
| `BUILD_SOURCEBRANCH` | `$(Build.SourceBranch)` | Source ref matched against active PRs |
| `SYSTEM_PULLREQUEST_*` | ADO-injected | Real-PR identifiers propagated verbatim |
| `SYSTEM_TEAMPROJECT` | ADO auto-injected | ADO project for the PR lookup |
| `BUILD_REPOSITORY_ID` | ADO auto-injected | Repository id for the PR lookup |
| `BUILD_REASON` | ADO auto-injected | Distinguishes CI from PR builds |
| `BUILD_REPOSITORY_PROVIDER` | ADO auto-injected | Detects GitHub-typed repos |
| `BUILD_SOURCEBRANCH` | ADO auto-injected | Source ref matched against active PRs |
| `SYSTEM_PULLREQUEST_*` | ADO auto-injected | Real-PR identifiers read verbatim |

The bundle lazy-imports `azure-devops-node-api` only when it needs to
call the PR REST endpoints (steps 4 and 7); real PR builds and
GitHub-typed repos return before any SDK load.

## Bundle env contract

Every compiler-emitted step that runs an ado-script bundle has an implicit
environment contract — which `process.env` keys the bundle reads. That contract
is modelled in [`src/compile/ado_bundle.rs`](../src/compile/ado_bundle.rs):

- **`Bundle`** enumerates every bundle with its on-disk `path()` and its
`auth()` requirement (`BundleAuth::Bearer` for bundles that read
`SYSTEM_ACCESSTOKEN` — for ADO REST via `getWebApi()` and/or git bearer auth
via `bearerEnv` — `BundleAuth::None` otherwise).
- **`apply_bundle_auth(step, bundle, token)`** is the single chokepoint that
projects `SYSTEM_ACCESSTOKEN` (from a `TokenSource`) into every
bearer-requiring bundle step. `SYSTEM_ACCESSTOKEN` is the one ADO predefined variable that is
**not** auto-injected — ADO maps it only when a step explicitly references
it — so it must be projected. This applier is why a step can no longer ship
without a bearer (the regression behind #1307).
- **`token_source_for(write_service_connection)`** unifies the
`System.AccessToken` vs `SC_WRITE_TOKEN` selection shared by the Conclusion
job and the Stage 3 executor.

Every other ADO predefined variable (`System.*`, `Build.*`) is auto-injected
into every script step's env under its SCREAMING_SNAKE name, so bundle steps
**must not** re-project them (e.g. `SYSTEM_TEAMPROJECT: $(System.TeamProject)`
is a redundant mirror). `is_redundant_ado_mirror` identifies such entries and
the contract tests — plus the compiled-YAML churn guard in
`tests/compiler_tests.rs` — assert migrated steps do not emit them. The ADO
collection URI is likewise read from the auto-injected `SYSTEM_COLLECTIONURI`
(`scripts/ado-script/src/shared/auth.ts::getWebApi`, see #1307), so it
is never part of a step's env contract.


## End-to-end data flow

```
Expand Down
20 changes: 19 additions & 1 deletion docs/extending.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,25 @@ let step = Step::Bash(

`BashStep::script` is the raw bash body. Do not include `- bash: |` or YAML indentation; the lowerer and serializer own YAML formatting.

### Task steps
### Steps that run an ado-script bundle

If your step invokes an ado-script Node bundle (`/tmp/ado-aw-scripts/ado-script/*.js`),
model its env contract via [`src/compile/ado_bundle.rs`](../src/compile/ado_bundle.rs)
rather than hand-writing the auth env:

- Add a `Bundle` variant with its `path()` and `auth()` (`BundleAuth::Bearer`
if the bundle reads `SYSTEM_ACCESSTOKEN` — for ADO REST via `getWebApi()`
and/or git bearer auth — else `BundleAuth::None`).
- Project the bearer with `apply_bundle_auth(step, Bundle::X, token)` — never
`.with_env("SYSTEM_ACCESSTOKEN", …)` by hand. Use `token_source_for(write_sc)`
to pick the token source.
- Do **not** re-project ADO predefined variables (`System.*` / `Build.*`): ADO
auto-injects them into every script step's env under their SCREAMING_SNAKE
names, so the bundle reads them directly. Only genuinely computed inputs
(base64 specs, `AW_*` config, `PARAM_*`) belong in `.with_env`. The contract
tests and the churn guard in `tests/compiler_tests.rs` enforce this.



```rust
use crate::compile::ir::step::Step;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export function makeEnv(overrides: Record<string, string>): NodeJS.ProcessEnv {
BUILD_REASON: "IndividualCI",
BUILD_REPOSITORY_PROVIDER: "TfsGit",
BUILD_SOURCEBRANCH: "refs/heads/feature/x",
ADO_PROJECT: "MyProject",
ADO_REPO_ID: "00000000-0000-0000-0000-000000000000",
SYSTEM_TEAMPROJECT: "MyProject",
BUILD_REPOSITORY_ID: "00000000-0000-0000-0000-000000000000",
...overrides,
};
}
Expand Down
6 changes: 3 additions & 3 deletions scripts/ado-script/src/exec-context-pr-synth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,11 @@ export async function main(env: NodeJS.ProcessEnv = process.env): Promise<number
return 0;
}

const project = env.ADO_PROJECT ?? "";
const repoId = env.ADO_REPO_ID ?? "";
const project = env.SYSTEM_TEAMPROJECT ?? "";
const repoId = env.BUILD_REPOSITORY_ID ?? "";
if (project.length === 0 || repoId.length === 0) {
logError(
"[synth-pr] required env vars ADO_PROJECT and ADO_REPO_ID must be set by the compiler",
"[synth-pr] required env vars SYSTEM_TEAMPROJECT and BUILD_REPOSITORY_ID must be auto-injected by ADO",
);
return 1;
}
Expand Down
Loading
Loading