Skip to content

Support package resources with scoped ownership - #54120

Open
pelikhan with Copilot wants to merge 5 commits into
mainfrom
copilot/support-declarative-repo-assets
Open

Support package resources with scoped ownership#54120
pelikhan with Copilot wants to merge 5 commits into
mainfrom
copilot/support-declarative-repo-assets

Conversation

Copilot AI commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Repository packages could install workflows, skills, and agents, but not related repository assets such as Issue Forms, CODEOWNERS, or policy files. This adds manifest-declared resources plus package-scoped provenance so optional packages can coexist without overwriting or removing each other’s files.

  • Manifest resources
    • Adds resources: mappings with package-relative source and repository-relative destination.
    • Restricts destinations to .github/ISSUE_TEMPLATE/*.yml|*.yaml, .github/CODEOWNERS, and .github/aw/**.
    • Rejects absolute paths, traversal, duplicate destinations, symlinked local sources, and invalid destination namespaces.
resources:
  - source: templates/bug.yml
    destination: .github/ISSUE_TEMPLATE/bug.yml
  - source: policy/controls.json
    destination: .github/aw/policy/controls.json
  • Install and ownership

    • Copies resources as inert content from the selected package ref.
    • Records package-scoped ownership metadata under .github/aw/packages/*.json.
    • Tracks package source, resolved ref, destination paths, source paths, and SHA-256 digests.
  • Update/remove safety

    • Refuses to overwrite locally modified owned resources unless --force is used.
    • Removes only stale files owned by the same package and unchanged since installation.
    • Preserves unrelated and cross-package files.
  • Docs and schema

    • Extends the manifest JSON schema.
    • Documents resource syntax, allowed destinations, provenance records, and ownership behavior.

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 19.7 AIC · ⌖ 6.17 AIC · ⊞ 6K ·
Comment /souschef to run again

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Support declarative repository assets and composable package ownership Support package resources with scoped ownership Aug 20, 2026
Copilot AI requested a review from pelikhan August 20, 2026 02:19
@pelikhan
pelikhan marked this pull request as ready for review August 20, 2026 04:44
Copilot AI balanced review requested due to automatic review settings August 20, 2026 04:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds manifest-declared repository resources with package-scoped ownership, update, and removal support.

Changes:

  • Adds resource parsing, validation, resolution, and installation.
  • Introduces ownership records and drift detection.
  • Extends update/remove behavior, tests, schema, and documentation.
Show a summary per file
File Description
pkg/parser/schemas/aw_manifest_schema.json Defines resource mappings.
pkg/cli/update_manifest.go Synchronizes resources during updates.
pkg/cli/spec.go Marks package resource specs.
pkg/cli/remove_command.go Initiates owned-file cleanup.
pkg/cli/bootstrap_profile_inference.go Excludes resources from inference.
pkg/cli/bootstrap_profile_helpers.go Excludes resources from auth detection.
pkg/cli/add_workflow_resolution.go Resolves local and remote resources.
pkg/cli/add_package_ownership.go Implements ownership and synchronization.
pkg/cli/add_package_manifest.go Extends the resolved package model.
pkg/cli/add_package_manifest_test.go Tests manifest resource resolution.
pkg/cli/add_package_manifest_resources.go Validates and normalizes resources.
pkg/cli/add_package_manifest_resolve.go Adds remote resource resolution.
pkg/cli/add_package_manifest_parse.go Parses resource declarations.
pkg/cli/add_command.go Installs resources and records ownership.
pkg/cli/add_command_test.go Tests installation and drift handling.
package.md Adds package resource guidance.
docs/src/content/docs/specs/repository-package-manifest-specification.md Specifies resource lifecycle and safeguards.

Review details

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 17/17 changed files
  • Comments generated: 14
  • Review effort level: Balanced

Comment on lines +81 to +85
case strings.HasPrefix(destination, constants.GithubDir+"aw/"):
remaining := strings.TrimPrefix(destination, constants.GithubDir+"aw/")
if remaining == "" || strings.HasPrefix(remaining, "../") {
return errorsForResourceDestination()
}
Comment on lines +110 to +112
absolutePath := filepath.Clean(filepath.Join(packageDir, filepath.FromSlash(resource.Source)))
if err := validateLocalPackageMappingSource(absolutePath, packageDir, resource.Source); err != nil {
return nil, err
Comment thread pkg/cli/add_command.go
tracker.TrackCreated(destFile)
}
}
if err := os.WriteFile(destFile, resolved.Content, constants.FilePermPublic); err != nil {
Comment thread pkg/cli/add_package_ownership.go Outdated
Comment on lines +278 to +281
path := filepath.Join(gitRoot, filepath.FromSlash(destination))
current, digestErr := fileSHA256(path)
if digestErr == nil && current == entry.SHA256 {
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
Comment thread pkg/cli/add_package_ownership.go Outdated
if err := os.MkdirAll(filepath.Dir(destPath), constants.DirPermPublic); err != nil {
return fmt.Errorf("failed to create package resource directory: %w", err)
}
if err := os.WriteFile(destPath, content, constants.FilePermPublic); err != nil {
Comment thread pkg/cli/add_package_ownership.go Outdated
Comment on lines +389 to +391
if strings.HasPrefix(destination, constants.WorkflowsDirSlash) {
continue
}
Comment thread pkg/cli/add_command.go
Comment on lines +499 to +502
fileExists := fileutil.FileExists(destFile)
if fileExists && !opts.Force {
packageSource := packageSourceForSpec(resolved.Spec, resolved.SourceInfo)
if owned, drifted := packageOwnershipAllowsOverwrite(gitRoot, rel, packageSource); !owned || drifted {
Comment on lines +56 to +57
if len(installationSources) == 0 && len(resourceFiles) == 0 && len(extensionFiles.skillFiles) == 0 && len(extensionFiles.agentFiles) == 0 {
return nil, fmt.Errorf("repository %q does not contain any installable workflows, resources, skills, or agents (either explicitly declared or auto-discovered). Add workflows under 'workflows/', resources in aw.yml, skills under 'skills/', or agents under 'agents/', or declare them explicitly in aw.yml", repositoryPackageIdentifier(repoSpec.RepoSlug, packagePath))
Comment on lines +294 to +295
if fileutil.FileExists(destPath) && !opts.Force {
if owned, drifted := packageOwnershipAllowsOverwrite(gitRoot, destination, packageBase); !owned || drifted {
Comment on lines +92 to +94
if spec.RepoSlug == "" {
return ""
}
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Ponytail Reviewer completed successfully!

Generated by Ponytail Reviewer for #54120

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Test Quality Sentinel skipped because pre-fetch PR data was unavailable: unable to fetch test file diff

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

reviewed_at: 2026-08-20T00:00:00Z
review_event: REQUEST_CHANGES
top_themes:
  - non-atomic resource updates
  - inconsistent success/failure accounting during update
  - package-owned resource cleanup misses resource-only and action-only packages
files_reviewed:
  - docs/src/content/docs/specs/repository-package-manifest-specification.md
  - package.md
  - pkg/cli/add_command.go
  - pkg/cli/add_command_test.go
  - pkg/cli/add_package_manifest.go
  - pkg/cli/add_package_manifest_parse.go
  - pkg/cli/add_package_manifest_resolve.go
  - pkg/cli/add_package_manifest_resources.go
  - pkg/cli/add_package_manifest_test.go
  - pkg/cli/add_package_ownership.go
  - pkg/cli/add_workflow_resolution.go
  - pkg/cli/bootstrap_profile_helpers.go
  - pkg/cli/bootstrap_profile_inference.go
  - pkg/cli/remove_command.go
  - pkg/cli/spec.go
  - pkg/cli/update_manifest.go
  - pkg/parser/schemas/aw_manifest_schema.json
comment_count: 3

Note

This comment is managed by comment memory.

It stores persistent context for this thread in the code block at the top of this comment.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

🔎 Code quality review by PR Code Quality Reviewer · gpt54 · 11.2 AIC · ⌖ 8.33 AIC · ⊞ 4.6K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

REQUEST_CHANGES

Resource support adds useful capability, but the current implementation leaves the package updater in an inconsistent state and the removal path does not correctly clean up package-owned assets.

Blocking themes
  • resource updates are not atomic: stale assets are deleted before replacement work is known to succeed
  • package update reporting can mark the same workflow as both success and failure
  • package-owned resource cleanup keys off *.md workflows only, so resource-only / action-only packages leak owned files

🔎 Code quality review by PR Code Quality Reviewer · gpt54 · 11.2 AIC · ⌖ 8.33 AIC · ⊞ 4.6K
Comment /review to run again

successes = append(successes, name)
}

if err := syncManifestManagedResources(ctx, repoSpec, latestPkg, latestRef, opts); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This update can report the same workflow as both updated and failed, which breaks the caller’s result accounting and can turn a partial resource sync failure into a misleading success summary.

💡 Why this is a real bug `successes` is populated before `syncManifestManagedResources` runs. If resource sync then fails, lines 195-198 append a failure entry for every workflow in `grouped` without removing the already-recorded successes. The same workflow name is therefore returned in both lists, so higher-level reporting cannot tell whether that workflow actually succeeded.

A safer pattern is to treat resource sync as part of the package update transaction: either run it before appending workflow successes, or clear/reclassify the package’s successes when resource sync fails.

Comment thread pkg/cli/add_package_ownership.go Outdated
desired[filepath.ToSlash(filepath.Clean(resource.DestinationPath))] = resource
}

var kept []packageOwnershipFileEntry

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The updater deletes stale resource files before it knows the new package state can be installed, so any later download/write error leaves the repository partially updated with files already removed.

💡 Why this should block merge The loop at lines 271-289 eagerly removes resources that disappeared from the manifest, but the subsequent loop still has multiple failure points: download failure, mkdir failure, write failure, and digest failure. Hitting any of those exits early after destructive deletions have already happened, which violates the atomicity guarantee described in the spec update and makes `gh aw update` non-rollbackable for resources.

Please stage removals until all downloads and writes succeed, or write into a temp area and swap/update the ownership record only after the whole resource set has been applied successfully.

return os.WriteFile(recordPath, data, constants.FilePermPublic)
}

func packageHasRemainingWorkflows(gitRoot, packageBase string) bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This cleanup path never removes package-owned resources when a package installs only action workflows or only resources, so uninstalling the last remaining package content leaves .github/aw / issue-template assets orphaned forever.

💡 Why this is broken `removePackageOwnedFilesIfUnused` decides whether a package is still present by scanning only `constants.GetWorkflowDir()/*.md`. That misses two cases introduced or already supported here:
  1. packages that install only resources and no markdown workflows, and
  2. packages whose remaining managed content is action workflows copied as .yml.

In both cases RemoveWorkflows has no surviving .md marker to inspect, so resource ownership cleanup is never triggered correctly. The new feature depends on ownership records for removal, so the presence check needs to account for all package-managed artifacts, not just markdown workflows.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /diagnosing-bugs, /tdd, and /codebase-design — requesting changes on security and correctness gaps. The inline comments already identify 14 concrete issues; this summary groups the most critical themes.

📋 Key Themes & Risks

🔴 Security — Must Fix

  1. Trust-store self-poisoning (add_package_manifest_resources.go:85): Allowed destinations include .github/aw/packages/** — a malicious package can install a forged ownership record for another package. Exclude the trust-store path from the destination allowlist entirely.

  2. Symlink traversal — source path (add_package_manifest_resources.go:112): Lstat only checks the leaf; a source like linked-dir/secret.json where linked-dir is a symlink escapes the package root. Call filepath.EvalSymlinks on the full resolved source before the containment check.

  3. Symlink write-through — destination (add_command.go:519, add_package_ownership.go:309): os.WriteFile follows existing destination symlinks; --force then allows writing outside the repository. Lstat the final destination before writing and reject symlinks.

  4. Unvalidated deletes from ownership records (add_package_ownership.go:395): Paths loaded from JSON are deleted without namespace or containment checks. A forged record with ../../ can delete arbitrary files. Apply the same validation used for incoming manifest destinations.

🟡 Correctness — Should Fix

  1. Non-atomic stale-delete then write (add_package_ownership.go:281): Stale files are removed before new ones are written; a failed download leaves the installation in a partial state. Buffer writes first, then delete staleness only after all writes succeed.

  2. Tag vs. commit SHA (add_package_ownership.go:260): latestRef may be a mutable release tag stored as ResolvedCommit. Resolve tags to their underlying commit SHA before recording.

  3. Success/failure ambiguity (update_manifest.go:199): Workflows enter successes before resource sync; a resource failure produces entries in both lists.

  4. Resource-only packages invisible to update/remove (add_package_manifest_resolve.go:57): Update and remove discovery scans installed *.md files; a resources-only package leaves no marker and becomes unmanageable.

  5. Stale workflow metadata in ownership records (add_package_ownership.go:264): The update path copies the previous Files list without re-deriving added/removed/updated workflows.

✅ Positive Highlights

  • Clean layered validation in add_package_manifest_resources.go (absolute path, traversal, duplicate destination, namespace checks)
  • SHA-256 digest tracking is the right foundation for ownership safety
  • Destination allowlist is a sound security boundary — the self-poisoning issue is a gap in the list, not a gap in the approach

The security items (1–4) are blockers; they allow a malicious package or forged record to escape the repository boundary. I'd recommend a focused pass on those before merging.

@copilot please address the review comments above.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 17.5 AIC · ⌖ 11.2 AIC · ⊞ 7.8K
Comment /matt to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: Support package resources with scoped ownership

This PR adds declarative resources support to the aw.yml package manifest, enabling packages to install inert repository assets (issue templates, CODEOWNERS, .github/aw/** files) alongside workflows.

The implementation is well-structured: destination validation runs at manifest-parse time (validateManifestResourceDestination), the allowlist is enforced, path-traversal guards are in place, and ownership records are written per-package.

Existing review comments already cover the blocking issues:

The 14 existing inline comments identify the high-signal issues:

  • Trust-store self-poisoning (add_package_manifest_resources.go:85) — the .github/aw/packages/** allowlist entry lets a package overwrite ownership records for other packages
  • Symlink traversal on source (add_package_manifest_resources.go:112) — Lstat only checks the final path; symlinked parent directories in the source pass through
  • Symlink traversal on destination write (add_command.go:519, add_package_ownership.go:309) — os.WriteFile follows existing destination symlinks; --force becomes a write-anywhere primitive
  • Non-atomic update (add_package_ownership.go:281) — stale resource files are deleted before replacement downloads succeed, leaving inconsistent state on failure
  • Path traversal in ownership record destinations (add_package_ownership.go:395) — destinations in the JSON record are used for deletion without re-running the allowlist check
  • Stale ownership metadata (add_package_ownership.go:264) — only upserting resources leaves prior entries for updated/removed workflows intact
  • Case-sensitive collision gap (add_command.go:502, add_package_ownership.go:295) — exact-match FileExists misses differently-cased names on case-insensitive filesystems
  • Success recorded before resource sync (update_manifest.go:199) — resource sync failures append to failures after successes is already populated
  • .github/workflows/ resources silently skipped (add_package_ownership.go:391) — action workflows installed as resources bypass ownership tracking
  • Local-only resources lack ownership records (add_package_ownership.go:94) — local-manifest resources skip ownership metadata entirely

No new blocking issues found beyond those already flagged. The overall design and validation chain are sound; addressing the existing comments — particularly the trust-store self-poisoning and symlink traversal issues — is needed before merging.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 38.9 AIC · ⌖ 8.99 AIC · ⊞ 5.7K

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ponytail review — cut opportunities only (correctness/security are out of scope for this pass; existing Copilot comments already flag those).

net: -20 lines possible.

Generated by ✂️ Ponytail Reviewer for #54120 · auto · 44.6 AIC · ⌖ 6.34 AIC · ⊞ 7.3K
Comment /ponytail to run again

tracker.TrackCreated(recordPath)
}
}
data, err := json.MarshalIndent(record, "", " ")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

L64-70/331-337/411-416: yagni: marshal-indent-append-newline-write JSON record is duplicated 3x across this file. Extract one writeOwnershipRecordFile(path string, record *packageOwnershipRecord) error helper.

}
}

func errorsForResourceDestination() error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

L92-94: yagni: errorsForResourceDestination() wraps a single static error with one caller pattern used 3x. Inline errors.New(...) at each call site or return it once from validateManifestResourceDestination.

…rship

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

ADR Required — Architecture Decision Record Missing

Status: 🔴 Blocked — a draft ADR has been committed to this branch for your review before merging.

This PR adds 856 new lines in business-logic directories (pkg/), which exceeds the 100-line threshold for ADR enforcement.

A draft ADR has been generated and committed to the branch at:
docs/adr/54120-package-resources-with-scoped-ownership.md

What to do next
  1. Review the draft ADR at docs/adr/54120-package-resources-with-scoped-ownership.md in this PR's file changes.
  2. Fill in any [TODO: verify] gaps — the draft was inferred from the PR diff and may be missing context only you have.
  3. Update the Status from Draft to Accepted once the team has reviewed the decision.
  4. The ADR will merge as part of this PR — no separate PR is needed.
Why ADRs Matter

Architecture Decision Records capture why a significant design choice was made — not just what was built. They help future contributors understand constraints, avoid re-litigating settled decisions, and safely evolve the system. See the ADR index for examples.

ADR sections required

The draft follows the Michael Nygard template and must contain:

  • Context — the situation and problem
  • Decision — what was decided and why (active voice)
  • Alternatives Considered — ≥2 genuine options evaluated
  • Consequences — positive and negative outcomes

Merge is blocked until the ADR status is changed from Draft to Accepted.

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 · 70.3 AIC · ⌖ 27.6 AIC · ⊞ 9.1K ·
Comment /review to run again

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot This PR still needs a maintainer-facing finish pass.

Current blockers to address in one pass:

  • Review the unresolved maintainer-visible feedback from github-actions and reply on-thread where needed.
  • Failed/action-required checks currently visible on this head: CWI, Doc Build - Deploy, and CGO require maintainer follow-up/retrigger after your code changes.
  • A branch refresh was requested by sous-chef.
  • Then run the pr-finisher skill and leave a concise maintainer handoff with validation and remaining human actions.

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 19.7 AIC · ⌖ 6.17 AIC · ⊞ 6K ·
Comment /souschef to run again

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@copilot This PR still needs a maintainer-facing finish pass.

Current blockers to address in one pass:...

Done in a4bd724f24.

I completed the finish pass items I could action in-agent:

  • Addressed maintainer-visible update/resource lifecycle feedback with code changes in pkg/cli/update_manifest.go and pkg/cli/add_package_ownership.go (resource sync failure accounting, transactional rollback behavior, stale-resource cleanup behavior).
  • Ran local validation (make fmt, make lint, targeted go test for affected package-resource/update paths, make recompile, make agent-report-progress).
  • Ran code_review and addressed the reported rollback issues.
  • Ran codeql_checker (no alerts; analyzer reported DB size skip).

Maintainer follow-up still required:

  • Re-trigger CI checks (CWI, Doc Build - Deploy, CGO) since agent pushes do not trigger workflows.
  • Review/resolve remaining open review threads on the PR UI as needed.
  • Branch refresh from main is already present (2a39bba).

@github-actions

Copy link
Copy Markdown
Contributor

PR Triage

Category: feature | Risk: high | Priority: medium (score 50/100)
Score breakdown: impact 28 + urgency 12 + quality 10
Recommended action: batch_review
Substantial new feature (package resources w/ scoped ownership). Automated reviewers requested changes 3x — needs human design review before merge, not auto-mergeable given size/risk.

Generated by 🔧 PR Triage Agent · auto · 62.9 AIC · ⌖ 7.05 AIC · ⊞ 8.3K ·

@github-actions

Copy link
Copy Markdown
Contributor

PR Triage

Category: feature · Risk: high · Score: 45/100 · Action: batch_review

Large feature addition (1026 additions, 18 files). Multiple unresolved CHANGES_REQUESTED reviews; mergeable_state blocked. Needs human review.

Automated triage — see the [triage report issue] for full context.

Generated by 🔧 PR Triage Agent · auto · 67 AIC · ⌖ 10.2 AIC · ⊞ 8.3K ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support declarative repository assets and composable package ownership

4 participants