Skip to content

The merge policy reads the APM lockfile and base-owned deployments in Go #859

Description

@tbhb-agent

Problem

The merge policy decides which changed paths belong to APM deployments the base commit already owns, and it reads that from the pinned apm.lock.yaml with a hand-written parser in merge-policy.py (about 215 of its 816 lines: regex and policy validation, lock scalar, list and record parsing, and the base-owned deployment map). The Go lock parser is owned by #981 in internal/mergepolicy/lock.go; this issue consumes it and adds no second parser. It is Python run through uv, it is on the golden and owner path lists, and it is under the shell freeze in #588. The same program has to hold the policy and the merge it gates, so the first step is an agentrepo merge policy command group in Go that reads the lockfile through that parser and answers "is this path owned remotely". The frozen helper has no command that prints ownership (it is an internal map there), so this child defines the observable output.

Part of #324, within #588. Child 01 in #324's Children list. Replaces the lock and ownership half of packages/agents/source/skills/merge-pr/shared/scripts/merge-policy.py.

Acceptance criteria

  1. AC1: Given an apm.lock.yaml blob at a pinned base commit, when agentrepo merge policy owned <base> runs, then it prints one deployed path per line, sorted, for every path the lock marks as owned by a remote package, and none that a local dependency or an active local owner overrides.
  2. AC2: Given lock fixtures, when the command reads them, then ownership matches the frozen parser's through the Verify merge skill directories against base lock hashes in Go #981 types.
  3. AC3: Given a lockfile that is absent at the base, when the command runs, then it prints an empty set and exits 0.
  4. AC4: Given a lockfile that is malformed or names a deployment path that escapes the repository, when the command runs, then it exits 1 with merge policy unreadable: <reason> on stderr and prints nothing on stdout.
  5. AC5: Given a policy regular expression that does not compile, when the command validates its configuration, then it exits 1 and names the configuration key without printing its value. This is an intentional improvement: the frozen helper does not attach the key to a compile error.
  6. AC6: Given agentrepo merge policy owned <base> <path>..., when a path lies under a deployment the base lock marks as remotely owned (the most specific deployed owner wins), then it is printed as owned, including when the pull request edits the content of that path; given a deployment declared only in a lockfile that the pull request head adds or changes, then the paths it declares are not owned.
  7. AC7: Non-functional: reading the lock never executes repository content and never needs uv, and the new command runs on macOS, Linux and Windows in CI.

Scope and non-goals

Scope: the ownership layer and the agentrepo merge policy owned command with its two forms over the lock parser exported by #981 in internal/mergepolicy/lock.go, and table-driven tests with fixtures taken from policy.bats and pins.bats.

Non-goal: the loosened-gate scan, owner approval, required checks and cleanup, which are children 02, 03 and 12. Non-goal: changing any shell script; the frozen helper stays in place and is the parity oracle until child 14. Non-goal: the lock changes of #313, which stay parked and are re-landed as Go fixes after the port.

Approach

Meet AC1 to AC7 with failing cases first. The expected ownership for each fixture is derived once from the frozen helper by a throwaway harness that calls its ownership function and writes golden files; the Go command must reproduce them. Mechanism is the implementer's choice.

Dependencies

Blocked by: #333 and #981.

Prerequisites: #321 (configuration readers), #333 (binary distribution) and #981 (lock parser and integrity library).

Size

S. One pull request.

Verify plan

  • AC2: golden comparison of ownership read through the Verify merge skill directories against base lock hashes in Go #981 types against the frozen parser's on the same fixtures.
  • AC1, AC3, AC6: golden comparison against the frozen helper's recorded ownership on shared fixtures, including a fixture that edits an already owned path and a fixture that adds a deployment only on the head.
  • AC4, AC5: table-driven error cases checking exit status and stream.
  • AC7: the CI matrix run for the new code, a test that runs with uv absent from PATH, and a test whose fixture repository holds an executable hook and a script that would create a sentinel file, asserting the sentinel never appears.
  • Definition of done: D1 each AC is verified at the merged head, with the commands and results cited in the pull request's Verification. D2 an independent review approves the exact merged head, and every finding has a recorded disposition. D3 required checks pass with no gate loosened. D4 the leak scans the repository specifies are clean, and every post made for this work passes the per-post leak review before it is posted. D5 every commit is signed. D6 the documentation matches the change or the Verification says why there is no impact. D7 every commit has an Assisted-by trailer and the pull request has the provenance:agent-assisted label. D8 the pull request stays within the size limits. D9 follow-ups are filed as issues and listed under Related. D10 the merge closes the issue through a closing reference, the board shows Done, and the final Status: comment names the merge commit.

Risks

A parser that reads a lockfile differently from the Python helper could mark a path as owned that the base does not own, which would hide it from the loosened-gate scan. Golden comparison on the fixtures is the control, and nothing calls the Go command until child 14. Undo: revert; no caller depends on it yet.

Operator authority

None. No owner-tier path is edited; protection of the new Go files is settled by #753 before any rollout.

Activity

  1. tbhb-agent commented on Oct 27, 2026

    @tbhb-agent
    ContributorAuthor

    Refinement: READY — The merge policy reads the APM lockfile and base-owned deployments in Go
    Model: gpt-6-astra

    Child 01 The merge policy reads the APM lockfile and base-owned deployments in Go

    Verdict: READY

    Checked: The ownership output, oracle, base-versus-head distinction and execution-safety verification close the earlier findings. Ownership and local overrides match packages/agents/source/skills/merge-pr/shared/scripts/merge-policy.py:192; most-specific ownership matches packages/agents/source/skills/merge-pr/shared/scripts/merge-policy.py:230. Scope, dependencies, verification and undo are bounded.

    Gaps: None.

    Split: None needed.

    Definition of done

    The definition of done for this issue (Feature). Tick each item and give its evidence.

    • D1 The author verified each AC on the merged head, and the Verification section of the pull request cites each AC ID with its evidence: the command and its result, or a link.
    • D2 An independent reviewer approved the exact merged head, and the author resolved each finding or filed it as a follow-up.
    • D3 The required checks pass on that head, and any loosened gate has the owner's approval of that exact head.
    • D4 The leak scans come back clean: gitleaks, devtools' check-leaks.sh with the repository's private-strings list, and the leak review of each post.
    • D5 The author signed each commit, the squash included.
    • D6 The docs match the change (README, docs/, CHANGELOG or help text), or the Verification section says "No doc impact" and why.
    • D7 The commits and the squash have Assisted-by trailers, and the pull request has the provenance:agent-assisted label.
    • D8 The pull request stays within the size limits, or the owner approved its exact head over the hard limit.
    • D9 The author files follow-ups and deferred findings as issues with at least a Problem and numbered AC and lists them under the Related section of the pull request. Any work left in the code links its issue.
    • D10 The merge closes the issue through a closing reference and the board shows Done. The final Status: comment names the merge commit.
    • devtools-1 mise run check is green, and so are the coverage floors mise run cover-go-check and mise run cover-py-check, which check doesn't run.
    • devtools-2 A change to skill sources under packages/agents/source/ commits what mise run generate-skills, then mise run skill-tokens, then apm install rewrite, in that order, and check-generated-skills (part of lint) passes.
    • devtools-3 A consumer-visible change (the vendored payload, a skill, a reusable workflow, a task or a pin) has its consumer steps in docs/agent-skills-migration.md or the adoption docs, or the PR's Verification says "No consumer impact" and why.

    Round: 3 of 3

  2. added
    refinement:activeA refinement round is open; not ready for implementation
    and removed
    refinement:readyPassed the Definition of Ready; may be picked up
    on Oct 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    refinement:activeA refinement round is open; not ready for implementation

    Fields

    Priority

    None yet

    Work type

    None yet

    Projects

    • Status
      Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions