feat(shared/apm): apps[] + matrix fan-out for multi-org App auth#982
feat(shared/apm): apps[] + matrix fan-out for multi-org App auth#982danielmeppiel merged 4 commits intomainfrom
Conversation
…ages Add app-id, private-key, owner, repositories inputs to the shared/apm.md gh-aw workflow. When app-id is set, mint an installation access token via actions/create-github-app-token before the apm-action pack step and use it in place of the default GH_AW_PLUGINS_TOKEN / GH_AW_GITHUB_TOKEN / GITHUB_TOKEN cascade. When app-id is empty, behavior is unchanged. Restores parity with the deprecated dependencies.github-app frontmatter form so users migrating from dependencies: to imports: shared/apm.md can keep fetching cross-org private APM packages with a GitHub App. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds GitHub App-based token minting support to the shared/apm.md gh-aw shared workflow so workflows can fetch cross-org private APM packages without forking the shared import, and records the new capability in the changelog.
Changes:
- Adds new optional inputs (
app-id,private-key,owner,repositories) toshared/apm.mdand wires them into a conditionalactions/create-github-app-tokenstep. - Updates the pack step to prefer the minted installation token ahead of the existing
GH_AW_PLUGINS_TOKEN || GH_AW_GITHUB_TOKEN || GITHUB_TOKENcascade. - Adds an Unreleased changelog entry describing the new auth path.
Show a summary per file
| File | Description |
|---|---|
CHANGELOG.md |
Documents the new GitHub App auth inputs for shared/apm.md under [Unreleased]. |
.github/workflows/shared/apm.md |
Extends the shared gh-aw import schema and job steps to mint and use a GitHub App installation token for package fetching. |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 4
| with: | ||
| app-id: ${{ github.aw.import-inputs.app-id }} | ||
| private-key: ${{ github.aw.import-inputs.private-key }} | ||
| owner: ${{ github.aw.import-inputs.owner || github.repository_owner }} | ||
| repositories: ${{ github.aw.import-inputs.repositories }} |
There was a problem hiding this comment.
The token-mint step runs based only on app-id being set, but private-key is described as required when app-id is set. Consider tightening the if: condition (or adding a small validation step) so misconfigurations fail fast with a clear message instead of failing inside actions/create-github-app-token.
| By default, packages are fetched using the cascading token fallback: | ||
| `GH_AW_PLUGINS_TOKEN || GH_AW_GITHUB_TOKEN || GITHUB_TOKEN`. | ||
|
|
||
| For cross-org private packages, supply GitHub App credentials via the | ||
| `app-id`, `private-key`, `owner`, and `repositories` inputs. When `app-id` | ||
| is set, an installation access token is minted with | ||
| `actions/create-github-app-token` and used in place of the default cascade. |
There was a problem hiding this comment.
This PR adds new authentication inputs to shared/apm.md, but the docs page that describes the shared/apm.md import still only mentions the token cascade and does not document GitHub App-based auth for cross-org private packages. Please update docs/src/content/docs/integrations/gh-aw.md (and any other relevant docs) to include the new app-id/private-key/owner/repositories inputs and recommended usage.
| ### Added | ||
|
|
||
| - `shared/apm.md` gh-aw workflow now accepts `app-id`, `private-key`, `owner`, and `repositories` inputs to mint a GitHub App installation token for fetching cross-org private APM packages, restoring parity with the deprecated `dependencies.github-app` form. The default `GH_AW_PLUGINS_TOKEN || GH_AW_GITHUB_TOKEN || GITHUB_TOKEN` cascade still applies when no app-id is supplied. |
There was a problem hiding this comment.
CHANGELOG entries in this repo consistently end with the PR number in parentheses (e.g. (#974)). This new Unreleased bullet is missing the PR reference; please add (#<PR_NUMBER>) and keep it to one line per PR per the project's changelog format.
| if: ${{ github.aw.import-inputs.app-id != '' }} | ||
| uses: actions/create-github-app-token@v3.1.1 | ||
| with: | ||
| app-id: ${{ github.aw.import-inputs.app-id }} | ||
| private-key: ${{ github.aw.import-inputs.private-key }} |
There was a problem hiding this comment.
github.aw.import-inputs.app-id / private-key use hyphenated keys with dot-notation in expressions. In GitHub Actions expressions, app-id is parsed as subtraction, so the if: gate and inputs will not evaluate as intended. Use bracket notation for hyphenated keys (e.g., github.aw.import-inputs['app-id'], ...['private-key']) or rename the schema keys to avoid - and update all references accordingly.
| if: ${{ github.aw.import-inputs.app-id != '' }} | |
| uses: actions/create-github-app-token@v3.1.1 | |
| with: | |
| app-id: ${{ github.aw.import-inputs.app-id }} | |
| private-key: ${{ github.aw.import-inputs.private-key }} | |
| if: ${{ github.aw.import-inputs['app-id'] != '' }} | |
| uses: actions/create-github-app-token@v3.1.1 | |
| with: | |
| app-id: ${{ github.aw.import-inputs['app-id'] }} | |
| private-key: ${{ github.aw.import-inputs['private-key'] }} |
Refactor shared/apm.md to implement the v3 design ratified in #983: - import-schema gains apps[] (array of GitHub App credential groups), each entry mints its own installation token and packs only its declared packages. - single-app top-level form (app-id, private-key, owner, repositories) stays first-class as the canonical shorthand for one-org users. - new apm-prep job normalises packages + single-app + apps[] into one canonical matrix of credential groups via jq (auto-derives apps[].id from slug(owner) when omitted; rejects duplicate ids and invalid id patterns). - apm job fans out one matrix replica per group; each replica conditionally mints, packs, and uploads apm-<group-id> as an artifact. The multi-bundle restore block is intentionally commented out behind a TODO(microsoft/apm-action bundles-file) marker: the upstream apm-action does not yet expose a bundles-file: input, so the matrix-restore cannot land. This commit is workflow-side-only; the diff is for design review and is NOT merge-ready until upstream PR-A ships per #983. Security: - apm-prep never echoes $groups / $matrix / any matrix.group.* value (S3 mitigation; private-key flows through env -> jq -> GITHUB_OUTPUT with GA secret masking; only id + package-count are printed). - artifact-count validation against the matrix manifest is included in the commented-out restore block (S4: defends against same-run artifact-name collision attacks); will activate alongside bundles-file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Upstream apm-action change is now open: microsoft/apm-action#30 (closes microsoft/apm-action#29). Both CI checks green, including a new self-contained integration workflow that exercises the multi-bundle loop end-to-end (3 packs -> 3 artifacts -> 1 Once #30 merges and is tagged
|
Uncomments the matrix-restore block that was previously emitted in commented-out form pending the upstream bundles-file: input. Bumps both apm-action references to @v1.5.0 (the release shipping the new input) and removes the doc-level 'blocked on upstream' notes. Land sequence: 1. Merge microsoft/apm-action#30 + tag v1.5.0 + advance v1 2. Wait for CI on this PR to flip green with @v1.5.0 live 3. Merge this PR Refs microsoft/apm-action#29 microsoft/apm-action#30 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pre-staged for apm-action v1.5.0 cutoverJust pushed Why CI on this push will be RED (and that's OK)
Land sequence (do not merge this PR until step 4)
Closes the upstream gap tracked in microsoft/apm-action#29. Diff summary
The |
…sibility, docs) Acts on the apm-review-panel verdict on PR #30 (APPROVE, 0 required, 23 nits). Fixes everything in scope NOW per maintainer guidance: 'great UX and silent magic stuff will come as a surprise to users; we don't defer what we can fix now.' Defence-in-depth (supply-chain expert): - TOKEN_ENV_DENYLIST: add GH_TOKEN, ACTIONS_RUNTIME_TOKEN, ACTIONS_ID_TOKEN_REQUEST_TOKEN. Future-proofs the strip against APM auto-detecting more aliases and against a malicious bundle ever attempting to exfiltrate runner-scoped tokens (cache write, OIDC). - parseBundleListFile: require '.tar.gz' suffix on every entry. Catches unexpanded globs ('/tmp/*'), wrong extensions ('.zip'), and accidental directory paths at parse time with a clear line number rather than a cryptic tar error mid-loop. - buildStrippedEnv: filter undefined-valued env entries up-front instead of using an unsafe 'as' cast that hid the type mismatch. Collision visibility (kills the 'silent last-wins' concern): - New logCollisionPolicy() emits a single core.warning before the restore loop runs when N>1 bundles, naming the count and stating the policy explicitly. Users are never surprised by silent overwrites. - Wire previewBundleFiles into runner.ts so the call site is real today (kills the architect's dead-code nit). Implementation remains a stub that returns empty CollisionReport; v1.6.0 ships the SHA-aware detection. The runner already surfaces sameSha as core.info and differentSha as core.warning whenever the implementation lands. CLI logging: - Per-bundle '[bundle K of N] OK' confirmation line so a stalled run is debuggable from the log alone. - Aggregate success line names the output directory. DevX UX: - Mutex error wording: 'specify exactly one of: ...' -> 'inputs ... are mutually exclusive (got: ...). Pick exactly one mode per step.' - action.yml bundles-file description: explicit '.tar.gz' requirement + 'globs are NOT expanded' note. - README: lead the multi-bundle section with WHY (multi-org / multi-app fan-out), bold backward-compat callout, drop 'agent job' jargon, promote bundles-restored output to prose, document collision policy explicitly. Add anchor (#multi-bundle-restore) and cross-link from the Private repo authentication section. CI integration test: - Add per-org marker file in each pack matrix replica; assert all 3 coexist in the merged workspace after restore (proves the loop is genuinely additive, not coincidentally identical). - New 'reject-traversal' job: writes a bundles-file containing '../escape.tar.gz' and asserts the action step fails (continue-on- error + outcome assertion). Locks B3 / B1 in CI. Tests: - Extend [B7] denylist tests (parser-side and buildStrippedEnv-side) to iterate TOKEN_ENV_DENYLIST so future additions auto-extend coverage. Add explicit assertion that the new tokens are present in the list. - Add parser tests for '.tar.gz' rejection (.zip extension, glob pattern '/tmp/bundles/*'). - Add tests for logCollisionPolicy (no-op for N<=1, warning for N>1). - Update 3 runner mutex tests to assert against the new wording. Deliberately deferred (out of PR scope; tracked for follow-up): - Full per-file SHA collision detection (v1.6.0 -- requires shelling out to 'apm unpack --dry-run' and aggregating; substantial new code). - Full multi-job matrix-pack + restore example in README (deserves its own docs PR; the existing example covers the consumer side). - Cross-link to microsoft/apm#982 in README (apm#982 not yet merged; premature link). - Release-note headline rewording (lives in the GitHub release UI, not in this PR body). Tests: 82 passed (was 79; +3 new assertions for collision policy and .tar.gz rejection). dist/ regenerated. Refs: #30 (comment) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Empirical finding from CI: 'apm bundle' only ships files attributable to dependencies declared in apm.yml, not arbitrary primitives sitting in .github/. The marker skill was packed locally (logged as part of 'skills/: marker-X, ...') but never made it into the .tar.gz because it isn't a registered dependency. Proving distinct-content merge across N orgs at apm-action's CI level would require a fleet of N genuinely distinct test packages -- overkill for action-side CI. The real distinct-content / per-App scenario is end-to-end tested by microsoft/apm#982 against real GitHub Apps. Document the test's scope explicitly in a NOTE block. The negative-test job (B3 traversal rejection) keeps real defensive value in CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(bundles-file): phase 1-2 - types and multibundle.ts implementation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(bundles-file): phase 3-4 - runner dispatcher and action.yml surface Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(bundles-file): phase 5 - unit tests for multibundle and 3-way mutex Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(bundles-file): phase 6 - integration CI workflow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(bundles-file): phase 7 - README documentation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(ci): pre-seed apm.lock.yaml in test fixture apm install with no remote deps does not auto-create a lockfile, but apm pack requires one. Provide a minimal lockfile declaring the local SKILL.md as a deployed file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(ci): use real public APM dep instead of local-only fixture apm unpack only deploys files from dependencies[].deployed_files, not local_deployed_files (verified in src/apm_cli/bundle/unpacker.py). Switch each matrix replica to install microsoft/apm-sample-package so the bundle has real deployable content. The test still validates the multi-bundle loop end-to-end: 3 pack jobs -> 3 artifacts -> 1 restore call with bundles-file -> asserts bundles-restored=3 and deployment dir is non-empty. Identical bundles across replicas additionally exercise the same-SHA collision path (no warnings expected). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * polish: address review-panel findings (defense-in-depth, collision visibility, docs) Acts on the apm-review-panel verdict on PR #30 (APPROVE, 0 required, 23 nits). Fixes everything in scope NOW per maintainer guidance: 'great UX and silent magic stuff will come as a surprise to users; we don't defer what we can fix now.' Defence-in-depth (supply-chain expert): - TOKEN_ENV_DENYLIST: add GH_TOKEN, ACTIONS_RUNTIME_TOKEN, ACTIONS_ID_TOKEN_REQUEST_TOKEN. Future-proofs the strip against APM auto-detecting more aliases and against a malicious bundle ever attempting to exfiltrate runner-scoped tokens (cache write, OIDC). - parseBundleListFile: require '.tar.gz' suffix on every entry. Catches unexpanded globs ('/tmp/*'), wrong extensions ('.zip'), and accidental directory paths at parse time with a clear line number rather than a cryptic tar error mid-loop. - buildStrippedEnv: filter undefined-valued env entries up-front instead of using an unsafe 'as' cast that hid the type mismatch. Collision visibility (kills the 'silent last-wins' concern): - New logCollisionPolicy() emits a single core.warning before the restore loop runs when N>1 bundles, naming the count and stating the policy explicitly. Users are never surprised by silent overwrites. - Wire previewBundleFiles into runner.ts so the call site is real today (kills the architect's dead-code nit). Implementation remains a stub that returns empty CollisionReport; v1.6.0 ships the SHA-aware detection. The runner already surfaces sameSha as core.info and differentSha as core.warning whenever the implementation lands. CLI logging: - Per-bundle '[bundle K of N] OK' confirmation line so a stalled run is debuggable from the log alone. - Aggregate success line names the output directory. DevX UX: - Mutex error wording: 'specify exactly one of: ...' -> 'inputs ... are mutually exclusive (got: ...). Pick exactly one mode per step.' - action.yml bundles-file description: explicit '.tar.gz' requirement + 'globs are NOT expanded' note. - README: lead the multi-bundle section with WHY (multi-org / multi-app fan-out), bold backward-compat callout, drop 'agent job' jargon, promote bundles-restored output to prose, document collision policy explicitly. Add anchor (#multi-bundle-restore) and cross-link from the Private repo authentication section. CI integration test: - Add per-org marker file in each pack matrix replica; assert all 3 coexist in the merged workspace after restore (proves the loop is genuinely additive, not coincidentally identical). - New 'reject-traversal' job: writes a bundles-file containing '../escape.tar.gz' and asserts the action step fails (continue-on- error + outcome assertion). Locks B3 / B1 in CI. Tests: - Extend [B7] denylist tests (parser-side and buildStrippedEnv-side) to iterate TOKEN_ENV_DENYLIST so future additions auto-extend coverage. Add explicit assertion that the new tokens are present in the list. - Add parser tests for '.tar.gz' rejection (.zip extension, glob pattern '/tmp/bundles/*'). - Add tests for logCollisionPolicy (no-op for N<=1, warning for N>1). - Update 3 runner mutex tests to assert against the new wording. Deliberately deferred (out of PR scope; tracked for follow-up): - Full per-file SHA collision detection (v1.6.0 -- requires shelling out to 'apm unpack --dry-run' and aggregating; substantial new code). - Full multi-job matrix-pack + restore example in README (deserves its own docs PR; the existing example covers the consumer side). - Cross-link to microsoft/apm#982 in README (apm#982 not yet merged; premature link). - Release-note headline rewording (lives in the GitHub release UI, not in this PR body). Tests: 82 passed (was 79; +3 new assertions for collision policy and .tar.gz rejection). dist/ regenerated. Refs: #30 (comment) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(ci): place per-org marker as a skill so the bundler includes it The previous approach wrote markers to .github/markers/, which is not a known APM primitive directory and so the bundler skipped them. Restore landed only the sample package's 6 files; the marker assertion failed because the markers were never bundled in the first place. Reshape the marker as a real APM skill at .github/skills/marker-<org>/SKILL.md so the bundler picks it up. The 'true merge' assertion now actually proves what it claims: all 3 distinct per-org skill directories must coexist in the merged restore workspace. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(ci): drop marker-skill assertion (apm bundle includes only deps) Empirical finding from CI: 'apm bundle' only ships files attributable to dependencies declared in apm.yml, not arbitrary primitives sitting in .github/. The marker skill was packed locally (logged as part of 'skills/: marker-X, ...') but never made it into the .tar.gz because it isn't a registered dependency. Proving distinct-content merge across N orgs at apm-action's CI level would require a fleet of N genuinely distinct test packages -- overkill for action-side CI. The real distinct-content / per-App scenario is end-to-end tested by microsoft/apm#982 against real GitHub Apps. Document the test's scope explicitly in a NOTE block. The negative-test job (B3 traversal rejection) keeps real defensive value in CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolves CHANGELOG.md conflict in the [Unreleased] section by combining both sides: - Added (HEAD): apps[]/app-id GitHub App auth entries for shared/apm.md - Changed/Fixed (origin/main): #820 target validation, #918 cleanup, #1008 marketplace build GHE host Both blocks are independent and additive. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Status update: apm-action v1.5.0 is live; PR rebased + ready for CI
The Once CI is green this is ready for final review + squash-merge. |
….5.0 (#1026) The apps[] + matrix fan-out PR (#982) merged with a stale docstring header ('STATUS: blocked... does not produce a working agent run') and lock files that still pinned microsoft/apm-action@v1.4.2 with the old single-bundle restore path. apm-action v1.5.0 has shipped with the bundles-file: input the shared workflow now relies on; the workflow IS production-ready. Changes: - shared/apm.md: drop obsolete STATUS banner; replace with version header pointing at canonical source + apm-action pin so vendored copies can self-check freshness via head -3 - pr-review-panel.lock.yml + triage-panel.lock.yml: regenerated with gh aw compile -- now pin microsoft/apm-action@v1.5.0 (SHA 454b8a1d) and use bundles-file: matrix-aware restore path that #982 designed - agentics-maintenance.yml: SHA-pin tightening for github/gh-aw-actions (no behavior change) - docs/integrations/gh-aw.md: add 'Vendor the canonical shared/apm.md' callout explaining the local-file model and how to fetch the current version, with comparison cue for vendored copies Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PR #982 added a Compute APM credential-group matrix step that feeds `${{ github.aw.import-inputs.packages }}` to `jq --argjson`. gh-aw substitutes that template at compile time using Go's default slice formatter, which emits `[microsoft/apm#main]` (space-separated, no quotes) instead of valid JSON `["microsoft/apm#main"]`. jq rejects the malformed input and apm-prep fails, blocking every PR run of the review panel and every triage-panel labelled issue. The bug shipped latent in #982 (locks not regenerated) and surfaced in #1026 when the locks were recompiled. Pinning gh-aw does not help: the same compiler version (v0.68.3) produced both shapes -- the difference was the new compute step that started routing the substituted value through `--argjson`. Fix: add a small Bash+Python repair_string_array helper in shared/apm.md that detects malformed Go-slice strings and rewrites them as JSON before they reach jq. Already-valid JSON and 'null' pass through untouched. apps[] (object arrays) is not repairable this way -- consumers must use the legacy single-app inputs until upstream gh-aw exposes a JSON-encoding helper for import-inputs (paper-cut filed upstream). - shared/apm.md: add repair_string_array helper for AW_APM_PACKAGES - pr-review-panel.lock.yml + triage-panel.lock.yml: recompile Verified locally with the live shared/apm.md run-block against four inputs ([single], [multi space-separated], null, already-valid JSON); all produce a correctly-typed matrix.group.packages array. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps pyproject.toml + uv.lock to 0.11.0. Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because this release ships one BREAKING removal (`apm marketplace build` -> exits 2, use `apm pack`) plus several net-new features (Dev Container Feature, Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack` unification, multi-org `apps[]`). Strict semver in 0.x: minor for features-with-break, patch only for bugfixes. Milestone admin (done out-of-band): - Renamed milestone #8 `0.10.1` -> `0.11.0` - Created milestone #9 `0.12.0` as next-up bucket - Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0` - 6 closed items stay in `0.11.0` PRs shipping in 0.11.0 (22 commits since v0.10.0): User-facing features: - #1042/#722 `apm pack` unifies bundle + marketplace.json (BREAKING: `apm marketplace build` removed) - #1038 `marketplace:` block in apm.yml + `apm marketplace migrate` - #803 /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives - #861 Dev Container Feature `ghcr.io/microsoft/apm/apm-cli` - #982/#984 shared/apm.md `apps:` array for cross-org private packages - #820 `target:` in apm.yml validates at parse time - #1032 `apm marketplace add` honors manifest.name (Claude Code parity) - #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms User-facing fixes: - #1015 ADO Entra ID auth + `apm install --update` pre-flight abort - #1019/#1020 GEMINI.md only created when target requested - #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms - #1018 POSIX paths in auto-discovery output (Windows compat) - #996 drop stray 'specify' from generated file footer Maintainer tooling: - #1043 NOTICE.md per CELA template - #1045/#1044 NOTICE drift gate + license-policy gate in CI - #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut) - #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1 - #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file - #1022 review-panel: true fan-out + binary verdict + label automation - #918 complexity audit + benchmarks suite - #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder) Files changed: - pyproject.toml: 0.10.0 -> 0.11.0 - uv.lock: regenerated (version field only) - CHANGELOG.md: [Unreleased] promoted to [0.11.0] - 2026-04-29 NOTICE drift check passes against the bumped lockfile. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps pyproject.toml + uv.lock to 0.11.0. Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because this release ships one BREAKING removal (`apm marketplace build` -> exits 2, use `apm pack`) plus several net-new features (Dev Container Feature, Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack` unification, multi-org `apps[]`). Strict semver in 0.x: minor for features-with-break, patch only for bugfixes. Milestone admin (done out-of-band): - Renamed milestone #8 `0.10.1` -> `0.11.0` - Created milestone #9 `0.12.0` as next-up bucket - Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0` - 6 closed items stay in `0.11.0` PRs shipping in 0.11.0 (22 commits since v0.10.0): User-facing features: - #1042/#722 `apm pack` unifies bundle + marketplace.json (BREAKING: `apm marketplace build` removed) - #1038 `marketplace:` block in apm.yml + `apm marketplace migrate` - #803 /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives - #861 Dev Container Feature `ghcr.io/microsoft/apm/apm-cli` - #982/#984 shared/apm.md `apps:` array for cross-org private packages - #820 `target:` in apm.yml validates at parse time - #1032 `apm marketplace add` honors manifest.name (Claude Code parity) - #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms User-facing fixes: - #1015 ADO Entra ID auth + `apm install --update` pre-flight abort - #1019/#1020 GEMINI.md only created when target requested - #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms - #1018 POSIX paths in auto-discovery output (Windows compat) - #996 drop stray 'specify' from generated file footer Maintainer tooling: - #1043 NOTICE.md per CELA template - #1045/#1044 NOTICE drift gate + license-policy gate in CI - #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut) - #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1 - #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file - #1022 review-panel: true fan-out + binary verdict + label automation - #918 complexity audit + benchmarks suite - #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder) Files changed: - pyproject.toml: 0.10.0 -> 0.11.0 - uv.lock: regenerated (version field only) - CHANGELOG.md: [Unreleased] promoted to [0.11.0] - 2026-04-29 NOTICE drift check passes against the bumped lockfile. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore(release): cut 0.11.0 Promotes [Unreleased] -> [0.11.0] - 2026-04-29 and bumps pyproject.toml + uv.lock to 0.11.0. Version-bump rationale: 0.11.0 (minor bump) chosen over 0.10.1 because this release ships one BREAKING removal (`apm marketplace build` -> exits 2, use `apm pack`) plus several net-new features (Dev Container Feature, Codex project-scoped MCP, `marketplace:` block in apm.yml, `apm pack` unification, multi-org `apps[]`). Strict semver in 0.x: minor for features-with-break, patch only for bugfixes. Milestone admin (done out-of-band): - Renamed milestone #8 `0.10.1` -> `0.11.0` - Created milestone #9 `0.12.0` as next-up bucket - Moved 43 open items (42 issues + 1 open PR #999) from `0.11.0` -> `0.12.0` - 6 closed items stay in `0.11.0` PRs shipping in 0.11.0 (22 commits since v0.10.0): User-facing features: - #1042/#722 `apm pack` unifies bundle + marketplace.json (BREAKING: `apm marketplace build` removed) - #1038 `marketplace:` block in apm.yml + `apm marketplace migrate` - #803 /#502 Codex project-scoped MCP (`.codex/config.toml`) + user-scope primitives - #861 Dev Container Feature `ghcr.io/microsoft/apm/apm-cli` - #982/#984 shared/apm.md `apps:` array for cross-org private packages - #820 `target:` in apm.yml validates at parse time - #1032 `apm marketplace add` honors manifest.name (Claude Code parity) - #1000/#998/#994 unified `--policy` / `--policy-source` accepted forms User-facing fixes: - #1015 ADO Entra ID auth + `apm install --update` pre-flight abort - #1019/#1020 GEMINI.md only created when target requested - #1008 marketplace producer respects GITHUB_HOST + multi-host URL forms - #1018 POSIX paths in auto-discovery output (Windows compat) - #996 drop stray 'specify' from generated file footer Maintainer tooling: - #1043 NOTICE.md per CELA template - #1045/#1044 NOTICE drift gate + license-policy gate in CI - #1033 shared/apm.md `[a b]` import-input repair (gh-aw#29076 paper-cut) - #1030 panel workflows skip-don't-fail on unmatched labels; gh-aw v0.71.1 - #1026 shared/apm.md recompiled to apm-action v1.5.0 + bundles-file - #1022 review-panel: true fan-out + binary verdict + label automation - #918 complexity audit + benchmarks suite - #1002 CodeQL clear-text-storage false-positive resolved (token -> placeholder) Files changed: - pyproject.toml: 0.10.0 -> 0.11.0 - uv.lock: regenerated (version field only) - CHANGELOG.md: [Unreleased] promoted to [0.11.0] - 2026-04-29 NOTICE drift check passes against the bumped lockfile. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(changelog): tighten 0.11.0 entries to lead with user impact Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(changelog): move Dev Container Feature to Maintainer tooling (not yet published) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(changelog): de-dupe within 0.11.0 (combine #722 Removed bullets, drop #820 Fixed pointer) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
feat(shared/apm): apps[] + matrix fan-out for multi-org App auth
TL;DR
Refactors
.github/workflows/shared/apm.mdto implement the v3 design ratified in #983:apps:array input — list of GitHub App credential groups (one installation token + one package list per group).app-id,private-key,owner,repositories) kept first-class as the canonical shorthand for one-org users — not deprecated (UX F1).apm-prepjob normalisespackages:+ single-app inputs +apps[]into one canonical matrix of credential groups viajq. Auto-derivesapps[].idfromslug(owner)when omitted (UX F3).apmjob fans out one matrix replica per group viastrategy.matrix; each replica conditionally mints a token, packs only its declared packages, uploadsapm-<group-id>as an artifact.TODO(microsoft/apm-action bundles-file)marker — the upstream action does not yet accept a list of bundles, so the workflow is not merge-ready until upstream PR-A ships.Three user-facing forms (all valid, additive)
Why
apm-prep+ matrix fan-outactions/create-github-app-tokenmints one installation token per call (single-owner scope), so multi-org cannot be a single mint step.pkg/parser/import_field_extractor.go:360-363), so importingshared/apm.mdtwice collides onjobs.apm. The fan-out has to live inside one shared workflow.import-schemasupportsarrayofobjectitems;${{ secrets.X }}strings inside user-supplied values pass through gh-aw substitution unevaluated and are evaluated by GA at runtime (pkg/parser/import_field_extractor.go:603-722,:798-812).Security highlights
apm-prepnever echoes$groups,$matrix, or anymatrix.group.*value. Onlyid+ package-count is printed.private-keyflows env → jq →$GITHUB_OUTPUTand benefits from GA secret masking.jq, notpython3, in the normaliser (architect iter-2: runner portability onubuntu-slim).What this PR does NOT do (gap)
microsoft/apm-action(v1.4.2) only accepts a singlebundle:path. The workflow uploads N artifacts but cannot restore them in one call. The restore steps are present in commented form with aTODO(microsoft/apm-action bundles-file)marker so the orchestration is reviewable.microsoft/apm-actionPR-A addsbundles-file:(newline-separated list of tarballs) with the security contract from S6 (path validation, extraction guards, deterministic conflict resolution).apm-actionrelease is tagged.<NEW_VERSION>placeholder in this workflow and uncomment the restore block.Validation evidence
yaml.safe_load) intoimport-schema,jobs: {apm-prep, apm}, top-levelsteps: [](intentionally empty until restore lands).apm-prepjq script across four shapes (packages-only, single-app legacy, multi-app + packages, same-owner duplicate detection) reproduced expected matrix output and the duplicate-id error path.actions/create-github-app-token@v3.1.1matches the pin used by upstreamgh-aw(pkg/workflow/action_pins.jsonSHA1b10c78c7865c340bc4f6099eb2f838309f1e8c3).Linked issues / sequencing
microsoft/apm-actionPR-A (bundles-file:input). Will close shared/apm.md: support multiple GitHub Apps per workflow for multi-org private packages #983 once that ships and this workflow's<NEW_VERSION>placeholder + restore-block uncommenting land.Limitations / future work
gh-aw/.github/workflows/shared/apm.mddeferred to a follow-up.apm-actionaccumulates 3+ multi-bundle quirks, extract amicrosoft/apm-awcomposite action wrapping the matrix machinery.