Skip to content

feat(shared/apm): apps[] + matrix fan-out for multi-org App auth#982

Merged
danielmeppiel merged 4 commits intomainfrom
feat/shared-apm-github-app-auth
Apr 28, 2026
Merged

feat(shared/apm): apps[] + matrix fan-out for multi-org App auth#982
danielmeppiel merged 4 commits intomainfrom
feat/shared-apm-github-app-auth

Conversation

@danielmeppiel
Copy link
Copy Markdown
Collaborator

@danielmeppiel danielmeppiel commented Apr 27, 2026

feat(shared/apm): apps[] + matrix fan-out for multi-org App auth

Status: BLOCKED on upstream microsoft/apm-action PR-A (bundles-file: input). Workflow-side of v3 is in; merge-ready once upstream ships.

TL;DR

Refactors .github/workflows/shared/apm.md to implement the v3 design ratified in #983:

  • New apps: array input — list of GitHub App credential groups (one installation token + one package list per group).
  • Single-app top-level form (app-id, private-key, owner, repositories) kept first-class as the canonical shorthand for one-org users — not deprecated (UX F1).
  • New apm-prep job normalises packages: + single-app inputs + apps[] into one canonical matrix of credential groups via jq. Auto-derives apps[].id from slug(owner) when omitted (UX F3).
  • apm job fans out one matrix replica per group via strategy.matrix; each replica conditionally mints a token, packs only its declared packages, uploads apm-<group-id> as an artifact.
  • Multi-bundle restore block is emitted in commented-out form behind a 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)

# 1. Public + default-token packages (no App)
imports:
  - uses: shared/apm.md
    with:
      packages:
        - microsoft/apm-sample-package

# 2. Single GitHub App (one org) — canonical shorthand
imports:
  - uses: shared/apm.md
    with:
      app-id: ${{ vars.APP_ID }}
      private-key: ${{ secrets.APP_PRIVATE_KEY }}
      owner: my-org
      packages:
        - my-org/my-private-skills

# 3. Multiple GitHub Apps (cross-org)
imports:
  - uses: shared/apm.md
    with:
      packages:
        - microsoft/apm-sample-package
      apps:
        - id: acme
          app-id: ${{ vars.ACME_APP_ID }}
          private-key: ${{ secrets.ACME_KEY }}
          owner: acme-org
          packages:
            - acme-org/acme-skills/skills/code-review
        - app-id: ${{ vars.BETA_APP_ID }}    # id auto-derived: "auto-beta-org"
          private-key: ${{ secrets.BETA_KEY }}
          owner: beta-org
          packages:
            - beta-org/beta-pkg

Why apm-prep + matrix fan-out

  • actions/create-github-app-token mints one installation token per call (single-owner scope), so multi-org cannot be a single mint step.
  • gh-aw merges imported jobs by key (pkg/parser/import_field_extractor.go:360-363), so importing shared/apm.md twice collides on jobs.apm. The fan-out has to live inside one shared workflow.
  • gh-aw import-schema supports array of object items; ${{ 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-prep never echoes $groups, $matrix, or any matrix.group.* value. Only id + package-count is printed. private-key flows env → jq → $GITHUB_OUTPUT and benefits from GA secret masking.
  • jq, not python3, in the normaliser (architect iter-2: runner portability on ubuntu-slim).
  • The commented-out restore block includes an artifact-count validation against the matrix manifest (S4: defends against same-run artifact-name collision attacks). Activates when bundles-file lands.

What this PR does NOT do (gap)

  • Does not activate the multi-bundle restore. The current microsoft/apm-action (v1.4.2) only accepts a single bundle: path. The workflow uploads N artifacts but cannot restore them in one call. The restore steps are present in commented form with a TODO(microsoft/apm-action bundles-file) marker so the orchestration is reviewable.
  • This PR is therefore not merge-ready. Merging would silently regress the workflow (no restore happens). It is held until:
    1. Upstream microsoft/apm-action PR-A adds bundles-file: (newline-separated list of tarballs) with the security contract from S6 (path validation, extraction guards, deterministic conflict resolution).
    2. A new apm-action release is tagged.
    3. We update the <NEW_VERSION> placeholder in this workflow and uncomment the restore block.

Validation evidence

  • YAML frontmatter parses (yaml.safe_load) into import-schema, jobs: {apm-prep, apm}, top-level steps: [] (intentionally empty until restore lands).
  • Workflow file is fully ASCII-clean (cross-platform encoding rule).
  • Wire-level dry runs of the apm-prep jq 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.1 matches the pin used by upstream gh-aw (pkg/workflow/action_pins.json SHA 1b10c78c7865c340bc4f6099eb2f838309f1e8c3).

Linked issues / sequencing

Limitations / future work

  • Per-group cache key alignment with upstream gh-aw/.github/workflows/shared/apm.md deferred to a follow-up.
  • If apm-action accumulates 3+ multi-bundle quirks, extract a microsoft/apm-aw composite action wrapping the matrix machinery.

…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>
Copilot AI review requested due to automatic review settings April 27, 2026 08:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) to shared/apm.md and wires them into a conditional actions/create-github-app-token step.
  • Updates the pack step to prefer the minted installation token ahead of the existing GH_AW_PLUGINS_TOKEN || GH_AW_GITHUB_TOKEN || GITHUB_TOKEN cascade.
  • 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

Comment thread .github/workflows/shared/apm.md Outdated
Comment on lines +81 to +85
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 }}
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/shared/apm.md Outdated
Comment on lines +153 to +159
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.
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread CHANGELOG.md
Comment on lines +11 to +13
### 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.
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot generated this review using guidance from repository custom instructions.
Comment thread .github/workflows/shared/apm.md Outdated
Comment on lines +79 to +83
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 }}
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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'] }}

Copilot uses AI. Check for mistakes.
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>
@danielmeppiel danielmeppiel changed the title feat(shared/apm): support github-app token minting for cross-org packages feat(shared/apm): apps[] + matrix fan-out for multi-org App auth Apr 27, 2026
@danielmeppiel
Copy link
Copy Markdown
Collaborator Author

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 bundles-file: restore).

Once #30 merges and is tagged v1.5.0:

  1. Replace <NEW_VERSION> in this PR's matrix-restore block with @v1.5.0 (or @v1 once the floating tag advances)
  2. Uncomment the matrix-restore block in shared/apm.md
  3. CI here will then exercise the full multi-app -> multi-bundle pipeline

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>
@danielmeppiel
Copy link
Copy Markdown
Collaborator Author

Pre-staged for apm-action v1.5.0 cutover

Just pushed b63e3661 which replaces the commented-out matrix-restore placeholder with the real multi-bundle restore steps and pins both microsoft/apm-action references to @v1.5.0.

Why CI on this push will be RED (and that's OK)

microsoft/apm-action@v1.5.0 does not exist as a tag yet. The release is gated on microsoft/apm-action#30 which is open, has both CI checks green, and is awaiting merge + tag.

Land sequence (do not merge this PR until step 4)

  1. Merge microsoft/apm-action#30 (adds bundles-file: input)
  2. Tag v1.5.0 in apm-action and create the GitHub Release
  3. Advance the floating v1 tag in apm-action to point at v1.5.0
  4. Re-run CI on this PR — should flip green
  5. Merge this PR

Closes the upstream gap tracked in microsoft/apm-action#29.

Diff summary

  • .github/workflows/shared/apm.md: @v1.4.2@v1.5.0 (×2); uncomment matrix-restore block (~36 lines); drop TODO/blocked notes
  • CHANGELOG.md: drop the "blocked on upstream" NOTE; add cross-links to apm-action#29 / apm-action#30

The apps[] schema, matrix fan-out, manifest validation step, and per-group token minting are unchanged from cf2e9f66 — this is purely the unblock cutover.

danielmeppiel added a commit to microsoft/apm-action that referenced this pull request Apr 28, 2026
…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>
danielmeppiel added a commit to microsoft/apm-action that referenced this pull request Apr 28, 2026
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>
danielmeppiel added a commit to microsoft/apm-action that referenced this pull request Apr 28, 2026
* 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>
@danielmeppiel
Copy link
Copy Markdown
Collaborator Author

Status update: apm-action v1.5.0 is live; PR rebased + ready for CI

The @v1.5.0 references in shared/apm.md now resolve against a real published release. CI on ef65dba6 is running.

Once CI is green this is ready for final review + squash-merge.

@danielmeppiel danielmeppiel merged commit 4a0f83a into main Apr 28, 2026
7 checks passed
@danielmeppiel danielmeppiel deleted the feat/shared-apm-github-app-auth branch April 28, 2026 21:05
danielmeppiel added a commit that referenced this pull request Apr 28, 2026
….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>
danielmeppiel added a commit that referenced this pull request Apr 29, 2026
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>
danielmeppiel pushed a commit that referenced this pull request Apr 29, 2026
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>
@danielmeppiel danielmeppiel added this to the 0.11.0 milestone Apr 29, 2026
danielmeppiel pushed a commit that referenced this pull request Apr 29, 2026
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>
danielmeppiel added a commit that referenced this pull request Apr 29, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

shared/apm.md: support multiple GitHub Apps per workflow for multi-org private packages

2 participants