Skip to content

fix(install): deploy skills through home directory aliases - #2876

Merged
Daniel Meppiel (danielmeppiel) merged 20 commits into
mainfrom
danielmeppiel-fix-symlink-home-deployment
Sep 30, 2026
Merged

Daniel Meppiel (danielmeppiel) merged 20 commits into
mainfrom
danielmeppiel-fix-symlink-home-deployment

Conversation

@danielmeppiel

@danielmeppiel Daniel Meppiel (danielmeppiel) commented Sep 7, 2026 •

Copy link
Copy Markdown
Collaborator

fix(install): complete HOME-alias lifecycle repair standalone against main

TL;DR

Global skills deploy through HOME/APM_HOME directory aliases without authorizing linked descendants. This standalone repair preserves the original history and fixes the directly coupled ownership, matching-lock freshness, frozen identity and alias-reader defects exposed by connected lifecycle execution. Fresh exact-head proof covers the retained nine-witness contract; source-Python proof remains distinct from hosted platform checks and independent acceptance.

Closes #2867. Thanks to Dave Mead (@DaveMeadAdjust) for the reproduction and proposed fix.

Important

Candidate 2e819da58835436be1c936fbfe6e5c38856f7baf, tree c71d8a9cfac6126c40698bf6a2fbdc5aab846b43, targets main@6db0c11dfe4aa603889e7216babf0c0fd2b7e036. Driver and independent parent acceptance pass; all 22 hosted checks conclude successfully or with expected neutral/skipped status. Final delta recommendation: ship_now, no technical follow-ups. GitHub reports MERGEABLE / BLOCKED: required code-owner and last-push approval plus merge-queue handling remain human-controlled. CLA passes; no merge or auto-merge occurred.

Problem (WHY)

  • Alias-root install exited zero with empty skill directories because authorized canonical roots and lexical copy paths differed.
  • Global update read ownership from the deployment root instead of user metadata, skipped owned skills as collisions, and could remove them during cleanup.
  • Unmatched/sibling lock entries allowed stale mutable-ref answers; unclassifiable historical provider hints prevented valid current declarations from recovering.
  • Frozen checks missed declaration/full-pin/provider changes, while unchanged semver constraints bypassed transport comparison.
  • Retained global trajectories exposed readers skipping valid one-component aliases; physical mutations exposed missing execution/launcher negatives.
  • [!] The first hosted successor completed lifecycle assertions but exceeded the unchanged six-minute job budget with two workers; measured four-worker scheduling preserves the exact selection.

Qualification follows the Agent Skills guidance "Read agent execution traces, not just final outputs.". This anchors the validation method; the failure descriptions above come from observed regressions, not hypothetical policy claims.

Approach (WHAT)

  • Admit plugins first, shallow-copy metadata and resolve only the package root; keep descendant authorization and caller metadata unchanged.
  • Read USER ownership from the selected metadata root; reuse immutable durable maps only during integration, retaining live collision checks.
  • Separate dependency/provider/ref-scoped lock seeds from fresh remote answers and receipts; discard invalid historical hints without relaxing current declarations.
  • Route frozen provider/transport/full-pin checks and reader placement through existing canonical authorities.
  • Execute eight original P8 witnesses plus the ninth real-tag semver witness with exact source/head/tree/interpreter/digest binding and independent execution.
  • Keep the existing lifecycle-smoke job, selection and six-minute cap; use exactly four workers with loadgroup, print actual runner CPUs, and reject scheduling drift.

Implementation (HOW)

Surface Change and boundary
src/apm_cli/integration/skill_integrator.py, integration/skill_support.py, install/pipeline.py Root-only normalization after plugin admission, USER-root forwarding and one destination-keyed ownership builder with lazy immutable integrate-phase snapshots. Preserve managed-file precedence and the mutable same-run overlay; lockfile-only still integrates without the snapshot.
src/apm_cli/deps/tiered_ref_resolver.py, install/helpers/ref_seed.py, core/host_providers.py Matching-only dependency/provider/ref seeds and effective-provider identity. Historical classification failure discards the hint; current declarations stay strict. Remote receipts and fresh-result coalescing remain separate.
src/apm_cli/drift.py, install/plan.py, commands/install.py Frozen declaration/provider/full-pin comparison, transport-before-semver ordering and truthful help. No credential-resolution redesign.
src/apm_cli/commands/deps/cli.py Extend the existing reader index through declared/locked get_install_path() authority; refuse escape and stay in the selected store. No parallel discovery map.
scripts/check_lifecycle_evidence.py, scripts/lifecycle_contracts.py, tests/utils/lifecycle_evidence.py Fresh execution, immutable nine-witness obligations, real runner/Hypothesis observation and strict source/launcher binding. Validate loaded witness contents; persist failed independent reports without masking original errors.
tests/fixtures/lifecycle_bug_ledger.json, lifecycle_completion.schema.json Preserve inherited gaps; add bounded #2867 contract, exact witnesses and 85-registration/22-applicable command assessment. Native sidecar remains separate from the generic merge-worker schema.
Required/generated lifecycle modules and existing isolation/state helpers Connected project/global-canonical/global-aliased trajectories, 25 guarded rules plus initializer, independent destinations, actual real-tag transport refusal/recovery, and full-pin-only corruption/refusal/recovery in both required globals.
Ownership, freshness, frozen-host, reader, security and cache test families External-root update/compile, cold frozen replay, sibling-seed isolation, four source layouts, receipt validity and manifest/lock alias escape. Both new historical-provider variants use real ordinary installs, local Git rewrites and exact locked/deployed B bytes.
Snapshot/pipeline and native-provider component families Actual consumer/load counts, immutable lifetime, live collisions, exception/retry/lockfile-only behavior and no intermediate durable writes. Reject foreign/prefix-lookalike interpreters while accepting only canonical long-path exec trampolines. Existing target-phase mocks now provide the required integrator.
Architecture owner JSON shards, boundary checks and corresponding integration matrices Extend existing owners, add the bounded native owner and retain exhaustive guard equality. Reuse the parent-approved upstream marketplace testcase correction; no missing native/reader guard cases.
tests/spec_conformance/test_lockfile_reqs.py, CONFORMANCE.json, CONFORMANCE.md Exact frozen full-SHA coverage under existing req-lk-003; regenerate inventory without a new normative requirement.
.github/workflows/ci.yml, tests/quality/test_ci_topology.py Lifecycle-smoke workers 2 to 4 on the public Ubuntu runner, same loadgroup and budget. Strict positive/negative topology coverage, including physical guard removal. No new jobs, permissions or selector changes.
Install/deps references, contributing integration-testing page, packaged commands/dependencies guides, CHANGELOG Document replay/freshness/aliases and independent evidence handoff; consolidate duplicated prose and document bounded scheduling. Preserve contributor credit; README unchanged.
Standalone scope, provenance and review folds

Direct human authorization made this same PR standalone against main, replacing only necessary obsolete prerequisites. Original published 171a3ecbe and selected main 6db0c11 are ancestors; the protected original local branch remains at 70e52a03.

GitHub required removing stack grouping 2883 before retargeting. Explicitly authorized metadata-only removal preserved PRs, refs, content and reviewers; #2882 remains closed/unmerged. Only #2876 was retargeted and normally fast-forwarded. No force-push, replacement PR, reviewer request or merge occurred.

Historical successful reports remain bound to their actual heads. Final driver and parent qualification each require their own clean checkout and interpreter.

Diagrams

Global trajectories advance installed bytes, commit identity and ownership together; generated global variants additionally require fresh C despite a sibling-only seed.

stateDiagram-v2
    direction LR
    [*] --> InstalledA: install A
    InstalledA --> UpstreamB: publish B
    UpstreamB --> InstalledB: update / compile B
    note right of InstalledB
        NEW assertions: bytes, commit and ownership advance together
        Replay and refusal leave the installed revision unchanged
    end note
    InstalledB --> DamagedB: tamper or remove sources
    DamagedB --> InstalledB: install repairs / audit passes
    InstalledB --> Removed: uninstall / audit empty
    Removed --> Reinstalled: restore declaration / install
    note right of Reinstalled
        Required globals retain B
        Global models publish C with only a sibling lock seed
        NEW assertion: unseeded dependency installs C
    end note
    Reinstalled --> Removed: audit / uninstall / audit empty
    Removed --> [*]
Loading

Trade-offs

  • Resolve the package root, not descendants. HOME aliases and explicit dependency placement aliases remain distinct authorization contracts.
  • Cache durable ownership reads only during integration, not across commands. Existence, managed files, content comparisons and current-run owners remain live; this is not a transaction against external writers.
  • Matching-lock replay is not blanket offline support. Unseeded mutable refs require upstream truth; frozen mode validates identity rather than network freshness. Review intended drift and regenerate with apm install --update.
  • Source-Python qualification is not packaged-platform parity. Local Git fixtures and mutations supplement real subprocess trajectories and hosted Linux/Windows/binary checks.
  • Use the public runner's bounded four-worker capacity rather than raise the budget or remove scenarios. Preserve independent parent execution and human review rather than substituting driver or historical approval.

Benefits

  1. Canonical and aliased HOME roots deploy/update real skill bytes while preserving unrelated user content.
  2. Used ownership roots load once per phase, unused roots zero times; N-to-10N record visits stay within 15x. No general wall-clock speedup is claimed.
  3. Matching dependencies replay their locks; unrelated/sibling/invalid historical seeds cannot authorize stale answers or poison a valid current declaration.
  4. All three real model variants retain the original P8 obligations; separate real-tag and full-pin refusal/recovery paths remain exercised.

Validation

Exact 2e819d proof, commands and qualification boundaries

Full functional/owner-matrix/quality/composed integration invocation, including tests/integration/test_ref_freshness_lifecycle.py (python -m pytest -p no:cacheprovider -o addopts= -q, exact argv/environment retained in the driver artifact):

1426 passed, 2 subtests passed in 555.38s (0:09:15)

python scripts/check_lifecycle_evidence.py --base 6db0c11dfe4aa603889e7216babf0c0fd2b7e036 --head 2e819da58835436be1c936fbfe6e5c38856f7baf --lane full --report <external-driver.json> --completion-output <external-completion.json>:

9 passed in 733.19s (0:12:13)

Driver report SHA256: 14a3929876a6d7ca4b2cc9d42e7319408635275fe616b4123df1beddb4a7b837; sidecar SHA256: 53604cdff9ff0489999867bee9e7cf0c4a6a9b2633c8e0f3a7017040d8b3a088. Canonical schema/execution validation and clean postflight identity pass.

75 physical mutation recipes killed and restored: 44 component, 30 provider and one fixed-worker topology guard. Both new real-CLI historical variants fail at ordinary reinstall when the historical-admission guard is removed. Full-pin removal loses the early diagnostic while a downstream immutable-conflict guard still refuses; this is not a claim that the mutant succeeds.

Fresh detection finds seven canonical owners; nine actual consumer nodes cover all seven. Version-2 schema and strict semantic preflight pass. Complete owner matrix equality, all newly registered guards, source safety/2100-line guards, Ruff/format, pylint R0801, auth/architecture boundaries and assertion/duplicate ratchets pass.

Conformance: 274 passed with two existing waivers, neither a P8 skip. npm --prefix docs run check:links:

[+] Checked 1057 relative link(s) across generated pages. No broken relative links found.

Independent parent fresh --completion, in its own checkout/venv/disposable roots:

9 passed in 618.54s (0:10:18)

Parent report SHA256: 057e631e4fbccc4c74400e5f31b3cb45fe6ea7e12397dabd0d0009a8244d38f9. Actual process exit 0 and parent postflight revalidate all nine witnesses/734 events, every setup/call/teardown, clean exact source and unchanged profile. The driver separately revalidated the completion and distinct source/venv bindings.

CI run 36740776748 and required gate 36740776727 succeed. Lifecycle Smoke reports actual four CPUs, 231 passed, 1 skipped in 184.01s; whole-job duration is 210s within the unchanged 360s budget. Linux shards/binary smoke, Windows checks, architecture ratchets, spec, docs, CodeQL and CLA succeed. Native nine have no skips.

Four final GPT-6 Astra delta specialists report no findings; the final advisory recommends ship_now. Cumulative counters remain outer 4, Copilot 2/2 and CI recovery 2. Earlier 541 acceptance and 541 scheduling measurements are not rebound to this head. Human review/last-push approval and queue handling remain required.

The unchanged lifecycle diagram was freshly validated with mmdc 11.15.0; GitHub visual rendering is not independently verified.

Scenario Evidence

# Scenario (user promise) Principle(s) Test(s) proving it Type
1 Both HOME spellings install, update, compile, repair and remove without touching unrelated content DevX (pragmatic as npm); Multi-harness support tests/integration/test_required_lifecycle_state_machine.py::test_required_global_audit_rule_matrix_for_external_roots (both variants; regression-trap for #2867) e2e
2 Project and both global layouts execute the mandatory connected lifecycle Portability by manifest; Governed by policy tests/integration/test_generated_lifecycle_state_machine.py mandatory replay and Hypothesis sequences, all three variants e2e
3 External owned skills update at their explicit alias and compile B Multi-harness support; DevX (pragmatic as npm) tests/integration/test_ownership_invariant_lifecycle.py::test_global_update_preserves_owned_external_skill_targets e2e
4 Cold frozen replay retains A; unmatched and invalid historical hints permit current B Governed by policy; Vendor-neutral tests/integration/test_ref_freshness_lifecycle.py including [historical-matching] and [historical-unrelated] e2e
5 Frozen semver rejects both transport flips; full-pin corruption preserves durable state and recovers Secure by default; Governed by policy tests/integration/test_required_lifecycle_state_machine.py separate real-tag witness and both required globals e2e
6 Root aliases never authorize escaping descendants; readers stay in the selected store Secure by default tests/unit/install/test_security_scan_scope.py, tests/unit/commands/test_deps_alias_readers.py unit
7 Ownership reuse preserves live collisions and fresh command epochs Secure by default; DevX (pragmatic as npm) tests/unit/integration/test_skill_ownership_snapshot.py, tests/unit/install/test_pipeline_ownership_snapshot.py integration
8 Lock replay does not manufacture remote freshness receipts Governed by policy tests/unit/deps/test_github_downloader_phase3.py, tests/unit/cache/test_git_cache.py receipt cases integration
9 Native evidence rejects replayed, skipped, foreign-source and altered witness claims OSS / community-driven; Secure by default tests/unit/scripts/test_lifecycle_evidence.py, tests/integration/test_architecture_native_lifecycle_evidence.py integration

How to test

  • Use a clean candidate checkout with its own installed source/interpreter and disposable HOME/APM_HOME/config/cache roots. Verify exact head/tree and imported apm_cli source.
  • Run the full native command above with new, distinct absolute output paths outside the checkout; require all nine witnesses without skips. The contributing integration-testing page gives the isolation setup.
  • Run ownership, pipeline, reader, security, frozen, resolver/receipt and provider families; repeat the real historical-provider and fixed-worker guard-removal negatives, then restore and require passing controls.
  • Independently execute the same candidate in another checkout/venv with --completion /absolute/driver/completion.json --report /absolute/new/parent.json; require fresh execution, not reuse of the driver result.
  • Inspect current hosted checks, actual runner CPU count, unchanged six-minute lifecycle budget and final advisory. Preserve human review and CLA requirements; do not merge or auto-merge.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Normalize the skill source root on a shallow metadata copy while preserving source-plan authorization and descendant symlink rejection. Add global audit and skill layout regression coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

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.

Copilot review overview

🟡 Changes recommended

The new CHANGELOG entry references the closed issue number instead of ending with the PR number per the repo’s changelog format contract.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 2 Low severity

New issues introduced by this change (2)
Severity Finding
Low severity CHANGELOG.md — The new changelog bullet ends with #2867, but changelog entries are required to end with the pull…
Low severity tests/​unit/​install/​test_security_scan_scope.py — In the MARKETPLACE_PLUGIN branch the test writes .claude-plugin/plugin.json, but the subsequent…
What changed in this PR

Fixes a global skill-install edge case where deploying skill files could be silently skipped when HOME/APM_HOME is a directory-symlink alias, by canonicalizing the package root spelling used during skill integration while preserving existing authorization and descendant-link protections.

Changes:

  • Canonicalize PackageInfo.install_path on a shallow copy inside SkillIntegrator.integrate_package_skill() to align with DeployableSourcePlan root resolution.
  • Add regression coverage for aliased home paths in the global install/audit integration test and add a multi-layout unit/component test ensuring authorization is preserved under root aliases.
  • Document the root-alias behavior in CLI install docs and add an Unreleased changelog entry.
File Description
src/​apm_cli/​integration/​skill_integrator.py Shallow-copies package metadata and resolves the package root before skill routing/copy so source-plan authorization matches copy/discovery paths.
tests/​unit/​install/​test_security_scan_scope.py Adds a multi-layout test asserting root-alias normalization does not broaden authorization and still rejects symlink escapes.
tests/​integration/​test_global_audit_deploy_root.py Adds a parametric regression test for global install + audit when HOME/APM_HOME uses a directory symlink alias.
docs/​src/​content/​docs/​reference/​cli/​install.md Documents that global skill installation supports home directory alias spellings while keeping in-package symlink rejection unchanged.
CHANGELOG.md Records the fix under Unreleased.

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

Comment thread CHANGELOG.md Outdated
Comment thread tests/unit/install/test_security_scan_scope.py Outdated
@danielmeppiel

Daniel Meppiel (danielmeppiel) commented Sep 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

APM Review Panel: needs_rework

PR #2876 repairs HOME-alias skill deployment and scoped lock replay; declared aliases select placement, while bounded ownership reuse and combined native qualification remain subject to their existing holds.

cc Sergio Sisternes (@sergio-sisternes-epam) -- a fresh advisory pass is ready for your review.

This is a bounded continuation and scope clarification of the existing iteration 1, not a new panel, outer iteration or counter reset. The original nine returns cover local a42fe808128d5866c17d93fe63ed4b57d0426ea0 against 1030295. Preserve their canonical boundaries: plugin admission precedes root-only normalization, caller metadata and descendant authorization remain intact, global ownership comes from user-scope metadata, and locked seeds remain dependency-scoped while fresh results remain repository-shared. The settled #2901 handoff at published c5b4a86 corrects our earlier placement framing: HOME/APM_HOME symlink spelling is distinct from a declared dependency alias. DependencyReference.get_install_path/build_materialization_path selects validated explicit-alias placement; LockedDependency.alias round-trips that placement, not source identity, and an absent alias is not inferred from an inventory name. Canonical materialization means this authority-selected destination, not obligatorily a non-alias repository layout. Preserve .safe, foo..bar and my-skill.v2, strict resolved containment, repository/ref/host identity, original declaring-source coordinates and declaration-relative SOURCE ../ anchoring. VP-approved documentation/fixture reconciliation is FOLD within the existing repair; frozen alias-drift detection, automatic alias relocation, graph-wide alias-conflict policy and global root-local persistence remain excluded. P4/P6/P7 reservations and Dave Mead (@DaveMeadAdjust)'s credit remain operative. The canonical Git-semver transport correction and refusal/recovery fold are unchanged. Its reported unit red/green and mutation progress is supplemented by the repaired real-tag, nine-command project CLI fixture: 1 test passed in 30.93 seconds under quiet-2876-semver-1 with own-workspace binary/source identity. The exact dirty-head qualification remains in the ship recommendation; this interim pass does not establish combined or final P8 acceptance.

The historical evidence remains passed, not absent: the prior panel-verified native-full-a42fe8081.json records 8/8 full/source-python witnesses for that exact range. In tests/integration/test_required_lifecycle_state_machine.py::test_required_global_audit_rule_matrix_for_external_roots[True], assert compiled_path.read_bytes() != compiled_a checks changed compiled content; in tests/integration/test_generated_lifecycle_state_machine.py::test_generated_lifecycle_sequences_preserve_reference_model[global-aliased], assert self.skill_path.read_bytes() == expected, ( checks deployed bytes. These are meaningful historical assertions, not future acceptance. The model has 25 guarded rules plus its mandatory initializer, across three scope variants; it is bounded rather than exhaustive. Preserve all eight witness selectors and independent reference expectations while reconciling declared-alias destinations and lock alias round-trips in the existing global, prune/uninstall and generated fixtures identified in compatibility-2901.json; retain exact SHA, bytes, ownership, HOME alias and unrelated-user-file assertions. No production-derived expected-path tautology or selector deletion is authorized. The caller reports the four documentation-table rows and concise explanation committed at6a0a95edd75431bfd22ce6d758d7c4666c3b3a09, and the fixture reconciliation is planned, not executed. Actual stable #2882/#2901 runtime integration and native qualification remain pending, including #2882's #2879 prerequisite. The published review base remains coherent #2882; no direct #2901 merge or independent runtime-composite push is authorized. Combined qualification must be a named unpublished stable-parent diagnostic until human prerequisite landings and an actual-main repin; diagnostic results cannot substitute for final integrated-head evidence. P8 remains mandatory: unknown completion evidence, historical passes and the 186-pass aggregate cannot replace candidate-bound driver and independent parent execution. Preserve #2901's exclusive ownership and #2813's lifecycle handoff.

Performance disposition remains FOLD only for the integrate-phase ownership snapshot, not an install-wide cache. The exact-revision microprobe gives identical results at baseline 1030295 and candidate a42fe808: P=5,D=5 yields 5 loads/parses and 25 dependency/record visits; P=50,D=50 yields 50 loads/parses and 2,500 visits, a 100x traversal increase. Both cross-controls yield 250 visits; absent-lock controls yield zero loads/visits. Correct global-root selection exposes a previously skipped lockfile, not a like-for-like builder regression or measured wall-time regression. The pre-fold shared SkillIntegrator and frozen IntegratorBundle did not themselves provide immutable ownership. Existing sequential phase ordering supplies the bounded lifetime: integration updates deployment/context state, while dependency lock persistence and applicable project-local persistence occur after exit. The native collision overlay remains mutable; separate scratch replay keeps fresh integrators. Source inspection confirmed the committed nullcontext lockfile-only correction and the budget-safe relocation at 84f13a102c2396df3809bd0c700b9d5c8684ec09: one executable builder in existing skill_support.py, exposed by _build_ownership_maps = staticmethod(build_skill_ownership_maps), preserving consumer monkeypatch lookup, lazy imports, outputs and phase lifetime. Reported executable-body AST identity and 2099 integrator/123 support lines under the unchanged 2100 cap are structural evidence, not runtime qualification. Provenance selectors retain the support/pipeline owner separately from the existing skill_integrator owner, without duplicated guard IDs. The USER persistence expectation identified in the dd3ac38bd review is corrected at 5ace7461935a89b4ed2d36327f4c9cde7bea7894: project-local persistence remains project-only; the user case checks dependency ownership without inventing global root-local persistence. The committed project/user epoch and other snapshot tests remain unexecuted. These narrow corrections preserve the original FOLD and every lifetime, mutation, root and P8 obligation; this amendment runs no tests, native campaign or new panel.

Dissent. Retain the original bounded integrate-phase FOLD, excluding cross-command caching or a general writer-invalidation policy; the single-builder relocation and corrected USER expectation stay inside that boundary and do not establish runtime acceptance. The settled published #2901 interface supersedes our earlier display-only/non-alias-layout framing: declared aliases select placement, independently of source identity and HOME/APM_HOME symlink spelling; this correction preserves the existing folds, exclusions, counters and P8 holds.

Aligned with: Portable By Manifest: P5/P6: validated declared aliases select canonical-authority placement and round-trip in LockedDependency.alias, without changing source identity or declaration-relative source anchoring; HOME/APM_HOME symlink spelling is a separate dimension. Secure By Default: Preserve plugin admission, descendant-link restrictions and credential boundaries; close semver transport drift in the existing authority without claiming a demonstrated credential bypass. Governed By Policy: P6/P8: declared-identity refusal must match its documentation, and final candidate-bound lifecycle, mutation and independent completion evidence remains mandatory. Multi Harness Multi Host: Retain equivalent explicit/inferred provider forms and external deployment-root support without pinning historical credential configuration or adding a host-specific exception. Oss Community Driven: P7: complete the reported #2867 repair with original contributor credit and bounded follow-ups; do not appropriate #2901 or turn the fix into an unrelated redesign. Pragmatic As Npm: P4/P6: preserve existing commands, including lockfile-only execution, and reuse durable ownership only within the existing integration write boundary; make no unmeasured speed claim.

Panel summary

Persona B R N Takeaway
Python Architect 0 1 0 Canonical owners are reused, but frozen semver transport drift escapes the shared predicate. Historical P8 passes do not establish final integration acceptance.
Cli Logging Expert 0 0 0 At a42 vs 103, frozen help/refusals retain ASCII and CommandLogger routing; ownership and freshness changes add no output noise. No CLI findings; execution evidence was not refreshed.
Devx Ux Expert 0 1 0 Alias ownership, canonical paths, scoped lock replay and removal/reinstall align with user expectations; complete the advertised frozen transport check for Git semver ranges.
Supply Chain Security Expert 0 1 0 Root/source authorization remains intact; fix frozen semver transport comparison. Verified historical a42 lifecycle results do not certify a future merged candidate.
Oss Growth Hacker 0 1 0 The alias reliability story is grounded; the changelog should also disclose changed frozen, freshness, and dependency-alias behavior.
Auth Expert 0 0 0 No auth regression found at a42fe8081: canonical provider/credential identity preserves equivalence and scoped lock replay. Focused checks: 165 passed, 4 skipped; native/CI acceptance pending.
Doc Writer 0 1 0 Changed install docs match the candidate's scope; qualify one remaining unconditional lock-replay promise in the packaged dependency guide.
Test Coverage Expert 1 1 0 Existing lifecycle assertions and historical a42 native execution are verified; final integrated driver/parent evidence and exact owner-evidence bindings remain pending.
Performance Expert 0 1 0 Scoped lock seeds preserve replay without stale sibling reuse; fresh-result coalescing remains intact. Repeated global ownership indexing is a non-blocking scaling follow-up, not speed evidence.

B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.

Top 5 follow-ups

  1. [Test Coverage Expert] (blocking-severity) Mandatory before completion: obtain final integrated native, owner, mutation and independent acceptance evidence. -- After the human prerequisite landings, actual-main repin, stable release-adapted harden(governance): require native lifecycle evidence before shipping #2882 through fix(release): preserve prereleases and qualify exact native candidates #2879, permitted fix(alias): reject path-traversing dependency aliases — fixes #2900 #2901 integration and all committed folds, run scripts/check_lifecycle_evidence.py --base --head --lane full --report from the clean candidate. Until then, combined qualification is restricted to a named unpublished stable-parent diagnostic: no direct fix(alias): reject path-traversing dependency aliases — fixes #2900 #2901 merge or independent runtime-composite push, and no borrowed upstream acceptance. The parent must independently rerun the full gate using --completion and a separate fresh report. Revalidate the executable command map and preserve all eight witness selectors, three scope variants and 25 rules plus the mandatory initializer through Copilot/apmlifecycle phase2 interactions #2813. Reconcile the existing global, prune/uninstall and generated fixtures identified in compatibility-2901.json with validated explicit-alias placement and lock alias round-trip, independently of source identity and HOME symlink spelling. Preserve exact SHA, deployed bytes, source-key uninstall selection, ownership, hooks, audit and unrelated-user-file checks; keep independent reference expectations rather than computing them from the production path helper. Actual stable harden(governance): require native lifecycle evidence before shipping #2882/fix(alias): reject path-traversing dependency aliases — fixes #2900 #2901 runtime integration, current-remote receipt/locked-seed compatibility and native execution remain pending. Bind every detected exact touched_owners[].decision values to executed consumer rows with head_sha, invocation/report and passing run_evidence. Supply mutation-failure/restoration passes, static/lint, final panel/docs-sync, exact hosted CI and real branch approvals. This is required evidence, not a post-merge deferral; historical a42 success, the interim project-only native pass and aggregate counts cannot discharge it.

  2. [Python Architect] Fold the semver transport correction into drift.detect_ref_change and extend existing native refusal/recovery coverage. -- Compare declared Git HTTP/HTTPS transport before the unchanged-semver return, preserving update_refs behavior, unchanged-range replay and equivalent providers. Cover both transport directions in drift/plan tests and the existing hermetic command/lifecycle fixture: refuse without durable mutation, restore the declaration and succeed. Add the canonical-owner regression guard and mutation/restoration evidence rather than a frozen-only identity helper. This closes the shared structural omission identified independently by architecture, DevX and supply-chain reviewers without inventing a credential exploit.

  3. [Doc Writer] Replace the unconditional locked-SHA sentence in packages/apm-guide/.apm/skills/apm-usage/dependencies.md:672. -- State that plain installs replay matching lock entries while mutable Git refs without a matching entry resolve upstream when materialized. Link commands.md for frozen/refresh details instead of duplicating the policy. Also reconcile the four placement-table rows and concise explanation with published fix(alias): reject path-traversing dependency aliases — fixes #2900 #2901: HOME/APM_HOME symlink spelling is separate from declared dependency alias; get_install_path/build_materialization_path selects validated explicit-alias placement, and LockedDependency.alias records placement rather than source identity. Remove claims that explicit aliases affect only logs/display or that canonical layout necessarily excludes aliases. Preserve safe dotted names and original declaring-source anchoring. These prose edits are committed at6a0a95edd75431bfd22ce6d758d7c4666c3b3a09, not completed combined qualification. This remains an in-PR truthfulness correction, not authorization for new alias-drift, relocation or persistence policy.

  4. [Oss Growth Hacker] Expand the existing fix(install): deploy skills through home directory aliases #2876 changelog entry and add concise recovery/migration guidance. -- Disclose changed frozen source-identity/full-commit checks and upstream resolution for unseeded mutable refs, while retaining the HOME-alias repair and Dave Mead (@DaveMeadAdjust)'s credit. Describe canonical materialization as placement selected by get_install_path/build_materialization_path, including a validated explicit dependency alias; distinguish that placement from HOME/APM_HOME symlink resolution and source identity. LockedDependency.alias round-trips placement. For intentional source/ref changes, retain the existing guidance to regenerate and review the lock with apm install --update, and link the install reference. Do not promise automatic relocation when an alias changes, frozen alias-drift detection, global root-local persistence, network freshness under --frozen, historical credential pinning, universal offline operation, or a ratified release.

  5. [Performance Expert] Retain FOLD for committed integrate-phase ownership reuse and its single-builder relocation; execute the unchanged qualification obligations when authorized. -- Exact seam, confirmed at dd3ac38bd1c0e0600ceba2bf194d0dcec1a11e3c: src/apm_cli/install/pipeline.py:_run_integration_phase (line 102 there) encloses only _run_phase('integrate', _integrate_phase, ctx), after target initialization/download and before failure handling, cleanup, LockfileBuilder.build_and_save, post_deps_local and audit. The committed contextlib.nullcontext branch for ctx.lockfile_only fixes the earlier unconditional skill lookup. Retain this branch: targets initialization is skipped in lockfile-only mode, and materialization must still feed lock construction without creating integrators or skipping the whole phase.

The relocation at 84f13a102c2396df3809bd0c700b9d5c8684ec09 remains inside this FOLD. Existing skill_support.py:build_skill_ownership_maps is the single executable builder, exposed through SkillIntegrator._build_ownership_maps = staticmethod(build_skill_ownership_maps). Its executable body is unchanged; lazy imports, outputs and consumer class/instance monkeypatch lookup are preserved. Keep the support/pipeline provenance selectors and legacy skill_integrator selector with their respective existing owners, without duplicate guard IDs. The static guard and source-override mutations follow both the alias and support root lookup. The reported executable-body AST identity and integrator 2099/support 123 lines respect the unchanged 2100 cap; they are not runtime results. Native, bundle and standalone-subskill consumers still use the instance _ownership_maps accessor and that class seam. Lazily retain the two returned dictionaries behind MappingProxyType for each resolved metadata root; no full-index copy, alternative builder, path/stat memo, process-global cache or fallback to an earlier ctx lock representation. One first-use snapshot per root is sufficient here; it is not a simultaneous multi-root transaction. Keep uncached standalone entry points fresh. Normal command execution creates a new integrator; finally drops durable maps on every context exit, and nested contexts remain rejected rather than silently sharing another lifetime.

Required qualification guards remain unexecuted: (1) Drive native, bundle and standalone-subskill package integration through this seam with real populated lockfiles and forwarding counters, including global metadata roots different from deployment roots. Require one canonical load/build per used root per phase, zero for an unused root, and at most 15x N-to-10N ownership-record visit growth; check exact owner identities and observable collision/deployment results, not counts alone. (2) Run consecutive commands through fresh InstallContexts, including reinstall/update, replace/delete/create the lock between epochs, and verify new ownership; also exit and re-enter the context on the same integrator to prove durable maps are not retained. Exercise exceptional exit after maps exist, a per-package failure caught inside the phase, retry, and nested-context refusal. Preserve the current-run overlay throughout a batch; a new command receives a fresh integrator, not an inherited overlay. (3) Verify two distinct resolved metadata roots cannot reuse each other's durable maps, while two symlink spellings of one physical metadata root may share a build. These filesystem aliases are not declared dependency placement aliases. Test separate project/user commands with the same skill names and different owners; do not infer that the skill-name-keyed session overlay supports mixing independent deployment roots in one run. (4) Guard the actual integrate phase's write set: after dependency deployments and again after root primitives, durable lockfile bytes or absence must be unchanged while deployed files and ctx package/local records change. Preserve root-local collision precedence through its augmented managed-files set. The project/user consecutive-pipeline test added at dd3ac38bd retains these shared observations; its persisted-ownership expectation is corrected at 5ace7461935a89b4ed2d36327f4c9cde7bea7894 to distinguish project-local persistence from user dependency ownership. Do not require or introduce global root-local persistence: post_deps_local explicitly skips USER scope. Verify later same-run native collisions consult mutable _native_skill_session_owners when durable ownership is absent, retaining durable-map-first precedence. Do not cache target existence, content comparisons, managed_files or session owners. If a permitted on-disk ownership writer appears between package calls, delimit a new epoch before subsequent reads; the guard must fail rather than bless stale maps. Test replacement at valid phase boundaries and freshness afterward, including initially absent locks. (5) Preserve project/global lockfile-only behavior, no-target/no-skill paths and zero new deployment side effects. Verify drift replay creates fresh integrators and leaves live lock/governed roots unchanged; do not wrap replay or uninstall in this context. Preserve applicable deterministic/generated lifecycle coverage and final P8 execution, subject to the VP hold.

Intermediate-mutation basis is unchanged: integrate prepares materializations, sequentially deploys packages and root primitives, and records package/local outcomes in ctx. Root-local integration uses the same skill instance without persisting the lock. Hook/primitive/config writes are not inputs to the durable maps; the approval prompt may save personal executable consent, not lock ownership. The primitive registry does not dispatch MCP/LSP lock writers. Dependency lock persistence and applicable PROJECT-local persistence remain after snapshot exit. Drift replay is a separate scratch operation with fresh integrators and a live-project read-only guard, not an intermediate writer. InstallTransaction and install/locking.py serialize cooperating mutating commands; this is not protection against arbitrary external filesystem edits or permission to broaden concurrency semantics.

Measured limits and scope: ownership-builder-measurement.log establishes identical builder counts at 1030295 and a42fe808128d5866c17d93fe63ed4b57d0426ea0, not a full-install profile or post-relocation result. P counts calls reaching the builder, not every declared package. Accounting for get_all_dependencies sorting, the fuller preexisting comparison-based bound is O(P*(B + D log D + F)), excluding path/identity-string work; one stable root/epoch could reduce that to O(B + D log D + F + P). For K epochs pay K builds. No dominant phase, elapsed-time regression or end-to-end speedup is established. Absence of a new regression alone is not a rubric-compliant deferral reason. scope_boundary_crossed for excluded work: reuse beyond this ordered integration phase into replay/uninstall, persistent cross-command caches or general writer invalidation would add a new cache-lifetime domain to this HOME-alias ownership repair. Frozen alias-drift, automatic alias relocation, graph-wide alias-conflict policy and global root-local persistence remain outside this fold. Published #2901 owns validated placement and source identity; this amendment authorizes only the stated documentation/fixture reconciliation, not an independent compatibility implementation or merge. The committed relocation, corrected expectations and queued tests do not qualify the candidate for shipping.

Architecture

### Before: 103029508
```mermaid
classDiagram
  direction LR
  class InstallService {
    +run(request)
    +enforce_frozen(request)
  }
  class install_plan {
    <<Module>>
    +lockfile_satisfies_manifest()
  }
  class DependencyReference {
    +get_unique_key()
    +get_install_path()
  }
  class LockFile {
    +read(path)
  }
  class LockedDependency {
    +to_dependency_ref()
  }
  class RefFreshnessPolicy {
    <<Strategy>>
    REPRODUCIBLE
    CURRENT_REMOTE
  }
  class TieredRefResolver {
    +seed(repo_ref, ref, sha)
    +resolve(repo_ref)
  }
  class RefResolutionTier {
    <<Protocol>>
    +try_resolve(dep_ref, ref)
  }
  class PerRunRefCache
  class host_providers {
    <<Module>>
    +classify_host_provider()
  }
  class HostProviderDescriptor {
    <<ValueObject>>
    kind
    credential_purpose
  }
  class drift {
    <<Module>>
    +detect_ref_change()
  }
  class BaseIntegrator {
    <<Base>>
  }
  class SkillIntegrator {
    <<Subclass>>
    +integrate_package_skill()
    -_build_ownership_maps(project_root)
  }
  class PackageInfo
  class DeployableSourcePlan {
    <<ValueObject>>
    +copy_ignore()
  }
  class _ModelFixture {
    <<ValueObject>>
    +create(root, binary)
  }
  class RuleBasedStateMachine
  class _LifecycleReferenceModel
  class LifecycleStateSnapshot {
    <<ValueObject>>
    +capture()
  }
  InstallService ..> install_plan
  install_plan ..> DependencyReference : membership keys only
  install_plan ..> LockFile
  LockFile *-- LockedDependency
  LockedDependency ..> DependencyReference
  TieredRefResolver o-- RefResolutionTier
  TieredRefResolver *-- PerRunRefCache : seeds and fresh answers share cache
  TieredRefResolver ..> RefFreshnessPolicy
  TieredRefResolver ..> DependencyReference
  host_providers ..> HostProviderDescriptor
  BaseIntegrator <|-- SkillIntegrator
  SkillIntegrator ..> PackageInfo : lexical install_path
  SkillIntegrator ..> DeployableSourcePlan
  SkillIntegrator ..> LockFile : deployment-root lookup
  RuleBasedStateMachine <|-- _LifecycleReferenceModel
  _LifecycleReferenceModel *-- _ModelFixture
  _LifecycleReferenceModel ..> LifecycleStateSnapshot
  note for TieredRefResolver "Chain of Responsibility: L0PerRunCache -> L1CommitsAPI -> L2BareRevParse -> L3LegacyClone; policy selects eligible tiers"
  note for _ModelFixture "Factory: create builds the project fixture"
  note for _LifecycleReferenceModel "Project-only generated model; no mandatory initializer spine"
Loading

After: a42fe808

classDiagram
  direction LR
  class InstallService {
    +run(request)
    +enforce_frozen(request)
  }
  class install_plan {
    <<Module>>
    +lockfile_satisfies_manifest()
  }
  class DependencyReference {
    +get_unique_key()
    +get_install_path()
  }
  class LockFile {
    +read(path)
  }
  class LockedDependency {
    +to_dependency_ref()
  }
  class RefFreshnessPolicy {
    <<Strategy>>
    REPRODUCIBLE
    LOCKED_OR_CURRENT
    CURRENT_REMOTE
  }
  class TieredRefResolver {
    +seed(repo_ref, ref, sha)
    +resolve(repo_ref)
    -_lock_seed_key(dep_ref, ref)
    -_lock_seeds
  }
  class RefResolutionTier {
    <<Protocol>>
    +try_resolve(dep_ref, ref)
  }
  class PerRunRefCache
  class host_providers {
    <<Module>>
    +classify_host_provider()
    +effective_host_provider_identity()
  }
  class HostProviderDescriptor {
    <<ValueObject>>
    kind
    credential_purpose
  }
  class drift {
    <<Module>>
    +detect_ref_change()
  }
  class BaseIntegrator {
    <<Base>>
  }
  class SkillIntegrator {
    <<Subclass>>
    +integrate_package_skill()
    -_build_ownership_maps(lockfile_root)
  }
  class PackageInfo
  class DeployableSourcePlan {
    <<ValueObject>>
    +copy_ignore()
  }
  class _ModelFixture {
    <<ValueObject>>
    +create(root, binary, variant)
  }
  class RuleBasedStateMachine
  class _LifecycleReferenceModel {
    +mandatory_spine()
    +durable_state_matches_reference_model()
  }
  class LifecycleStateSnapshot {
    <<ValueObject>>
    +capture()
  }
  InstallService ..> install_plan
  install_plan ..> DependencyReference : canonical key and full pin
  install_plan ..> drift : declared identity predicate
  install_plan ..> LockFile
  LockFile *-- LockedDependency
  LockedDependency ..> DependencyReference
  drift ..> host_providers
  TieredRefResolver o-- RefResolutionTier
  TieredRefResolver *-- PerRunRefCache : fresh repository answers only
  TieredRefResolver ..> RefFreshnessPolicy
  TieredRefResolver ..> DependencyReference : scoped seed identity
  TieredRefResolver ..> host_providers : scoped seed provider
  host_providers ..> HostProviderDescriptor
  BaseIntegrator <|-- SkillIntegrator
  SkillIntegrator ..> PackageInfo : shallow copy then root resolve
  SkillIntegrator ..> DeployableSourcePlan : unchanged descendant authority
  SkillIntegrator ..> LockFile : canonical scope metadata root
  RuleBasedStateMachine <|-- _LifecycleReferenceModel
  _LifecycleReferenceModel *-- _ModelFixture
  _LifecycleReferenceModel ..> LifecycleStateSnapshot
  note for TieredRefResolver "Chain of Responsibility retained; matching dependency seed precedes fresh L0 and remote tiers; LOCKED_OR_CURRENT excludes L2BareRevParse"
  note for _ModelFixture "Factory: create constructs project, global-canonical or global-aliased fixture"
  note for _LifecycleReferenceModel "Hypothesis initialize calls _mandatory_replay before bounded generated tails"
  note for SkillIntegrator "Base + subclass retained; plugin admission precedes copy; scope.get_apm_dir selects ownership root"
  note for DependencyReference "Download and integrate now both delegate to get_install_path -> materialization.build_materialization_path"
  class install_plan:::touched
  class RefFreshnessPolicy:::touched
  class TieredRefResolver:::touched
  class host_providers:::touched
  class drift:::touched
  class SkillIntegrator:::touched
  class _ModelFixture:::touched
  class _LifecycleReferenceModel:::touched
  class LifecycleStateSnapshot:::touched
  classDef touched fill:#fff3b0,stroke:#d47600
Loading

```mermaid
### Before: 103029508
Paths below are under src/apm_cli/. The flow focuses on Git skill installation; existing policy, executable and MCP gates remain in force.
```mermaid
flowchart TD
  A["commands/install.py: install --global, optional --frozen"] --> B{"install/service.py: InstallService.run; frozen?"}
  B -->|yes| C["[I/O] InstallService.enforce_frozen: LockFile.read; install/plan.py: lockfile_satisfies_manifest checks direct keys"]
  C --> D{"Structural and applicable MCP checks satisfied?"}
  D -->|no| X["commands/install.py: FrozenInstallError -> exit 1"]
  D -->|yes| E
  B -->|no| E["[I/O] install/phases/resolve.py: run -> _load_lockfile -> _setup_downloader"]
  E --> F{"deps/tiered_ref_resolver.py: ref_freshness_policy_for_install"}
  F -->|ordinary install| G["REPRODUCIBLE: permit lock seeds and persistent L2"]
  F -->|update or refresh| H["CURRENT_REMOTE: no lock seeds or L2"]
  G --> I["[LOCK] install/helpers/ref_seed.py: seed_ref_resolver_from_lockfile -> TieredRefResolver.seed -> PerRunRefCache.put"]
  I --> J{"TieredRefResolver.resolve: ref kind?"}
  H --> J
  J -->|full SHA| K["TieredRefResolver._build_result: SHA passthrough"]
  J -->|omitted or Artifactory| L["[NET] [EXEC] L3LegacyClone.resolve_full"]
  J -->|named| M{"[LOCK] PerRunRefCache.get: repository/ref hit?"}
  M -->|yes, including lock seed| K
  M -->|no| N["[NET] [EXEC] TieredRefResolver._dispatch: L1CommitsAPI, eligible L2BareRevParse, L3LegacyClone"]
  N --> K
  L --> K
  K --> O["[I/O] [FS] install/phases/download.py: run; alias join or get_install_path"]
  O --> P["[I/O] [FS] install/phases/integrate.py: run; alias join or get_install_path; install/services.py: integrate_package_primitives"]
  P --> Q["[I/O] integration/skill_integrator.py: integrate_package_skill; enforce_agent_plugin_deployment_boundary"]
  Q -->|admitted| R["PackageInfo.install_path remains lexical"]
  R --> S["[I/O] _build_ownership_maps(project_root): LockFile.read from deployment root"]
  S --> T["[I/O] [FS] _integrate_native_skill / _integrate_skill_bundle / _promote_sub_skills_standalone; shutil.copytree with DeployableSourcePlan.copy_ignore"]
  T --> U["[FS] [LOCK] install/pipeline.py: cleanup.run -> LockfileBuilder.build_and_save -> audit.run -> finalize.run"]
  U --> V["InstallResult: success exit 0 or failure exit 1; filtered-out skill bytes could still leave exit 0"]

After: a42fe808

flowchart TD
  A["commands/install.py: install --global, optional --frozen"] --> B{"install/service.py: InstallService.run; frozen?"}
  B -->|yes| C["[I/O] InstallService.enforce_frozen: LockFile.read; install/plan.py: lockfile_satisfies_manifest"]
  C --> C1{"Direct key present and full revision pin equals resolved_commit?"}
  C1 -->|no| X["commands/install.py: FrozenInstallError -> exit 1"]
  C1 -->|yes| C2["drift.py: detect_ref_change -> host_providers.effective_host_provider_identity; source/ref/provider/HTTP comparison"]
  C2 --> D{"No drift and applicable MCP checks satisfied?"}
  D -->|no| X
  D -->|yes| E
  C2 -.-> W["Review correction: matching Git-semver constraint currently returns before HTTP-transport comparison"]
  B -->|no| E["[I/O] install/phases/resolve.py: run -> _load_lockfile -> _setup_downloader"]
  E --> F{"deps/tiered_ref_resolver.py: ref_freshness_policy_for_install"}
  F -->|lock exists, ordinary install| G["LOCKED_OR_CURRENT: matching seeds allowed; no persistent L2"]
  F -->|no lock, update or refresh| H["CURRENT_REMOTE: no lock seeds or L2"]
  G --> I["[LOCK] install/helpers/ref_seed.py: seed_ref_resolver_from_lockfile -> TieredRefResolver.seed -> _lock_seeds using _lock_seed_key"]
  I --> J{"TieredRefResolver.resolve: ref kind?"}
  H --> J
  J -->|full SHA| K["TieredRefResolver._build_result: SHA passthrough"]
  J -->|omitted or Artifactory| L["[NET] [EXEC] L3LegacyClone.resolve_full"]
  J -->|named| M{"[LOCK] _lock_seeds.get: dependency/repository/provider/ref match?"}
  M -->|yes| K
  M -->|no| M2{"[LOCK] PerRunRefCache.get: fresh repository/ref answer?"}
  M2 -->|yes| K
  M2 -->|no| N["[NET] [EXEC] TieredRefResolver._dispatch: L1CommitsAPI -> L3LegacyClone; remote failure is not replaced by L2"]
  N --> K
  L --> K
  K --> O["[I/O] [FS] install/phases/download.py: run -> DependencyReference.get_install_path"]
  O --> P["[I/O] [FS] install/phases/integrate.py: run -> DependencyReference.get_install_path; install/services.py: integrate_package_primitives"]
  P --> Q["[I/O] integration/skill_integrator.py: integrate_package_skill; enforce_agent_plugin_deployment_boundary"]
  Q -->|admitted| R["[I/O] copy(package_info); install_path.resolve root only"]
  R --> S["[I/O] scope.get_apm_dir(USER) -> _build_ownership_maps(lockfile_root); independent of deployment root"]
  S --> T["[I/O] [FS] _integrate_native_skill / _integrate_skill_bundle / _promote_sub_skills_standalone; shutil.copytree with unchanged DeployableSourcePlan.copy_ignore"]
  T --> U["[FS] [LOCK] install/pipeline.py: cleanup.run -> LockfileBuilder.build_and_save -> audit.run -> finalize.run"]
  U --> V["InstallResult: success exit 0 or failure exit 1; lifecycle assertions check bytes, ownership and unrelated files"]
  classDef touched fill:#fff3b0,stroke:#d47600
  class C,C1,C2,G,I,M,O,P,R,S,W touched
Loading

### Recommendation

Keep all original FOLDs and P8 holds in the same panel/outer iteration. The phase snapshot, nullcontext correction, single-builder relocation and scope-correct USER expectation are committed but runtime qualification remains queued. Fold the approved documentation/fixture reconciliation with #2901's published placement contract: declared alias is placement, not source identity; HOME/APM_HOME symlink spelling is separate. The repaired project semver fixture has a reported interim pass of 1 test/nine real CLI commands in 30.93 seconds under quiet-2876-semver-1, with explicit own-workspace APM_BINARY_PATH and apm_cli.__file__ identity. It ran at dirty HEAD 843386ea1073373f95d5d8bc588fb057cd8747ab plus git-diff SHA256 b86517479bf9afe414fd4e6c317f3a70ecaceffb68a49286f23a6b57a91aabfe with the native fixture and ownership prototype dirty, before helper commit 60a9a5ca85829d0f83989f5aa5fc5b04cb0ae815; it certifies neither that later commit nor final P8. Actual stable #2882/#2901 runtime integration, the two global native variants and full-provider execution remain pending. No direct #2901 merge or independent runtime-composite push is authorized: combined qualification remains a named unpublished stable-parent diagnostic until human prerequisite landings and actual-main repin. Resume execution only in an authorized VP slot and obtain all final-head evidence, including the independent parent --completion rerun. Historical passes remain historical; this amendment is not merge approval, CI certification or a final ship claim.

---

<details>
<summary>Full per-persona findings</summary>

#### Python Architect

- **[recommended]** Apply transport drift before the Git-semver early return in the canonical predicate at `src/apm_cli/install/plan.py:502`
  The new frozen identity gate correctly delegates to drift.detect_ref_change, but that existing predicate returns immediately for ref_kind == 'semver' at src/apm_cli/drift.py:190-191, before its is_insecure comparison at lines 199-201. On exact candidate a42fe808, real DependencyReference.parse_from_dict inputs for owner/package with ref '^1.0.0' and a lock with constraint '^1.0.0', resolved_ref 'v1.2.0', and a full commit produce detect_ref_change=False and lockfile_satisfies_manifest=(True, []) for BOTH HTTPS-to-HTTP and HTTP-to-HTTPS changes. Equivalent branch-ref controls produce drift=True and frozen=False. Thus the newly strengthened frozen contract still accepts a changed declared HTTP transport for Git-semver dependencies. This is an inherited predicate limitation exposed by the new caller, not a newly introduced credential bypass; recommended is appropriate because the bounded probe establishes a structural-contract omission, not an end-to-end unauthorized download. Extend the existing decision owner instead of duplicating transport logic inside install/plan.py.
  
  **Design patterns**
  - Used in this PR: Base + subclass -- SkillIntegrator retains BaseIntegrator infrastructure while changing root selection and forwarding.
  - Used in this PR: Strategy + Chain of Responsibility -- RefFreshnessPolicy selects cache eligibility for the TieredRefResolver tier chain; dependency-scoped lock seeds no longer masquerade as repository-wide fresh answers.
  - Used in this PR: Dataclass-as-value-object + Factory -- DeployableSourcePlan preserves immutable source authorization, and the frozen _ModelFixture.create factory constructs explicit project/global lifecycle variants.
  - Pragmatic suggestion: none -- the existing owners are the right boundaries; correcting the common drift predicate is simpler than adding another identity abstraction.
  *Suggested:* Move the Git HTTP-transport comparison in drift.detect_ref_change ahead of the Git-semver return, preserving existing update_refs and source semantics. Add bidirectional HTTP/HTTPS semver cases to tests/unit/test_drift_detection.py and tests/unit/install/test_plan.py, then prove frozen refusal, unchanged durable state, and recovery through the existing command/lifecycle fixture. Extend the existing architecture guard so the transport decision remains in the shared drift owner rather than acquiring a frozen-only sibling.
  *Proof (manual):* -- proves: A frozen Git-semver declaration can currently change between HTTP and HTTPS without being rejected by the structural identity check. [governed-by-policy, portability-by-manifest]

#### Cli Logging Expert

No findings.

#### Devx Ux Expert

- **[recommended]** Apply the frozen transport check to Git semver ranges too at `src/apm_cli/install/plan.py:502`
  At a42fe808128d5866c17d93fe63ed4b57d0426ea0 against 103029508a8fbf863c55fdd224be63461e115a2c, the new frozen preflight delegates declared-identity comparison to detect_ref_change(). For a Git dependency with ref_kind='semver' and an unchanged constraint such as '^1.2.0', drift.py:190-191 returns False before the is_insecure comparison at lines 199-201. The dependency key does not distinguish HTTP from HTTPS, so changing only that declared transport can still satisfy this structural check. This conflicts with the updated install help and reference promising refusal of declared transport changes. Recommended rather than blocking: the helper's early return predates this PR, and this review establishes an incomplete newly advertised contract by static tracing, not a reproduced CLI regression or a security judgment. No native execution was performed; the historical eight passing lifecycle witnesses were inspected and are not being called absent or failed.
  *Suggested:* Keep detect_ref_change as the shared authority, but compare the existing declared transport identity before the Git-semver early return. Preserve unchanged-range replay and equivalent inferred/explicit providers. Add a focused frozen CLI fixture covering an unchanged Git semver constraint with an HTTP/HTTPS declaration flip, asserting refusal and unchanged durable state. This needs no new flag, lock schema, or persistence of historical host configuration.
  *Proof (unknown):* -- proves: The new frozen structural check has a statically visible path that accepts a declared transport change when the Git semver constraint is unchanged; CLI execution of that combination was not verified. [devx, governed-by-policy]

#### Supply Chain Security Expert

- **[recommended]** Check transport drift before the Git semver early return. at `src/apm_cli/install/plan.py:503`
  At a42fe808128d5866c17d93fe63ed4b57d0426ea0 against 103029508a8fbf863c55fdd224be63461e115a2c, the new frozen check delegates declared identity validation to detect_ref_change. For a Git dependency with reference '^1.2.0' and a lock with constraint '^1.2.0', drift.py:190-191 returns False before comparing is_insecure at lines 199-201. Changing the declaration between HTTP and HTTPS therefore passes this structural check when host, provider and package key otherwise match; the key deliberately excludes transport. InstallService.enforce_frozen adds MCP checks but no independent transport comparison. The early return predates this PR, but its newly expanded frozen contract now inherits the gap, contrary to the transport-refusal promise in reference/cli/install.md. Recommended rather than blocking: explicit HTTP consent and commit-integrity controls remain separate safeguards, and this review has not demonstrated a network/authentication bypass. This is static candidate-bound evidence, not an executed CLI failure.
  *Suggested:* Have the existing detect_ref_change authority compare Git transport before returning for an unchanged semver constraint. Add a frozen regression for both HTTP-to-HTTPS and HTTPS-to-HTTP with identical constraints, asserting refusal before mutation and acceptance after restoring the declaration. Coordinate any shared identity edits with the exclusive #2901 owner; no new identity helper or lockfile schema is needed.
  *Proof (manual):* -- proves: The frozen structural check can accept changed Git transport when the declared semver constraint is unchanged. [secure-by-default, governed-by-policy]

#### Oss Growth Hacker

- **[recommended]** Disclose the broader install behavior changes in the release-facing entry at `CHANGELOG.md:12`
  CHANGELOG.md:12 describes only global home-directory aliases, but this exact candidate also changes behavior for users without home aliases. install/plan.py now refuses mismatched manifest-declared identity and full commit pins under --frozen; tiered_ref_resolver.py distinguishes matching dependency-scoped lock seeds from unseeded mutable refs; the changed registry and packaged dependency guides replace the documented alias-controlled directory with canonical materialization. These changes affect CI refusal, upstream access on unlocked materialization, and expectations about installed paths. The detailed install reference explains frozen and freshness behavior, but an upgrader reading the changelog would not learn that this PR affects them. This is recommended change communication, not a correctness finding, a demand for new functionality, or a semver verdict.
  *Suggested:* Extend the existing #2876 entry with concise user-facing clauses: frozen installs reject changed declared Git identity or mismatched full commit pins; plain installs replay matching lock entries but resolve unseeded mutable refs upstream; dependency aliases retain their selection/display role while materialization uses canonical paths. Link the install reference for recovery guidance and retain @DaveMeadAdjust's credit. Do not describe frozen as network freshness, historical credential-configuration pinning, or universal offline installation.

#### Auth Expert

No findings.

#### Doc Writer

- **[recommended]** Qualify the packaged dependency guide's unconditional locked-SHA promise at `packages/apm-guide/.apm/skills/apm-usage/dependencies.md:672`
  At a42fe808, dependencies.md still says install without --update 'always uses the locked SHA'. This contradicts the revised commands.md and install reference: ordinary installs replay only matching lock entries, changed declarations can require resolution, and unseeded mutable refs resolve upstream even with unrelated or sibling lock entries. The next sentence also lists --refresh as an upstream operation, although --refresh does not require --update. The candidate's ref_freshness_policy_for_install and dependency-scoped TieredRefResolver seeds make this distinction explicit. This is recommended documentation drift, not a demonstrated runtime regression or a missing-test claim.
  *Suggested:* Replace the unconditional sentence in place: 'Plain installs replay matching lock entries; mutable Git refs without a matching entry resolve upstream when materialized. See commands.md for frozen and refresh behavior.' Keep the detailed command policy in commands.md rather than maintaining a second exhaustive list here.
  *Proof (manual):* `tests/unit/deps/test_tiered_ref_resolver.py::test_install_lock_presence_cannot_authorize_unseeded_bare_refs` -- proves: The inspected candidate distinguishes matching locked commits from upstream resolution for dependencies without matching lock entries; this review did not execute the test. [devx, governed-by-policy]
  `assert result.resolved_commit == (SHA_A if matching_lock else SHA_B)     stale_l2.assert_not_called()`

#### Test Coverage Expert

- **[blocking]** P8 completion evidence remains pending, not missing lifecycle tests. at `tests/integration/test_required_lifecycle_state_machine.py:2180`
  The reviewed a42 tree exactly matches the historical native report, whose digest, eight passing witnesses, command inventory and ordered runtime trajectories were verified. This is genuine source-Python execution, not static proof, and must retain its historical passed status. However, recovery-state.json explicitly records zero recovery native campaigns and pending corrected-provider integration. No final integrated driver report or independent parent --completion execution is available. P8 requires both at the final candidate; the historical report and fresh 186-test aggregate cannot certify a future merged tree. This finding concerns unavailable completion evidence, not an observed runtime failure or absent scenarios. Native execution was prohibited for this reviewer.
  *Suggested:* Reuse the adjusted required and generated tests; do not add a duplicate module. After the authorized provider integration produces a clean committed candidate, the driver must execute scripts/check_lifecycle_evidence.py --base <actual-comparison-base> --head <final-candidate> --lane full --report <fresh-external-report>. The parent must independently execute the full gate with --completion <driver-return-json> and a separate fresh report. Preserve all eight witnesses, the three scope variants and the 26 model entrypoints through the planned framework handoff. Keep completion blocked until required execution and mutation/restoration evidence are current.
  *Proof (unknown):* `tests/integration/test_required_lifecycle_state_machine.py::test_required_global_audit_rule_matrix_for_external_roots[True]` -- proves: Install, update, compile, repair and remove through home aliases must remain safe on the final integrated candidate. [devx, governed-by-policy, secure-by-default]
  `assert_snapshot_set_unchanged(before, ArtifactSnapshotSet.capture(artifact_roots))`

- **[recommended]** Bind the five reported owner decisions to exact executed consumer evidence. at `tests/integration/test_generated_lifecycle_state_machine.py:687`
  recovered-owner-report.json identifies five decisions at a42 versus 103. The supplied recovery JSON artifacts contain no functional evidence rows carrying owner_decisions and head_sha together with passing run_evidence. recovered-functional.log records 186 passed in 41.10s but contains neither an invocation nor node identifiers. Historical native witnesses exercise canonical materialization, user scope, provider refusal/recovery and sibling-seed freshness; the four-layout authorization test also exists and uses real files. These are useful tests, not absent coverage. Nevertheless, their exact decision-to-execution bindings are not established by the supplied owner report or aggregate log. This is advisory evidence traceability; shepherd-driver remains the enforcement owner.
  *Suggested:* Attach executed consumer rows for each exact touched_owners[].decision from recovered-owner-report.json, retaining its reviewed head_sha and a verifiable invocation/report reference. Reuse existing lifecycle, authorization and frozen tests rather than introducing owner-only tests. Mark static architecture guards as static and mocked resolver tests as unit; neither replaces functional consumer evidence. Regenerate bindings at the final integrated head.
  *Proof (unknown):* `tests/integration/test_generated_lifecycle_state_machine.py::test_generated_lifecycle_sequences_preserve_reference_model[global-aliased]` -- proves: Frozen installs refuse a changed provider without altering installed files and recover after the declaration is restored. [governed-by-policy, secure-by-default]
  `assert_snapshot_set_unchanged(before, self._capture())`

#### Performance Expert

- **[recommended]** Reuse the ownership index across packages within one immutable lockfile snapshot. at `src/apm_cli/integration/skill_integrator.py:1106`
  At skill_integrator.py:878-891, each ownership-map construction reads/parses the complete lockfile and visits every package and deployed path. Native skills call it at line 1106, bundles at 1313, and standalone sub-skills through lines 953 and 897. services.py:602 calls skill integration per package. For P such package integrations, B lockfile bytes, D locked packages, and F deployed-path records, this performs P lockfile reads and O(P*(B+D+F)) processing, ignoring path-string lengths. With one deployed record per package, increasing P and F together from N to 10*N produces 100 times the ownership-record visits. The traversal already existed at base 1030295; this PR correctly makes global calls read the real user lockfile rather than an unrelated deployment-root lockfile. This is an exposed existing scaling cost, not grounds to revert the ownership-root correction or a measured wall-time regression. It conflicts with the integrator rule to partition once and pay only for the current package.
  *Suggested:* As a bounded follow-up, reuse the existing canonical ownership-map builder's result for a single install's immutable lockfile snapshot, with explicit replacement/invalidation when that snapshot changes. Keep project and user scopes separate; do not introduce a process-global path-only cache or a second ownership authority. This reduces ownership work to O(B+D+F+P). Add deterministic N/10*N integration guards counting LockFile.read calls and ownership-record visits, requiring at most 15x visit growth, plus update/reinstall and separate-root cases to catch stale indexes. Coordinate any shared identity changes with #2901 rather than taking over its implementation.

</details>

<sub>This panel is advisory. It does not block merge. Re-apply the
`panel-review` label after addressing feedback to re-run.</sub>

Addresses the panel lifecycle follow-up and Copilot plugin fixture comment. Run global install and audit through the installed CLI with aliased HOME/APM_HOME while preserving physical-root snapshots and user-owned sentinels; pass the written plugin manifest explicitly.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@danielmeppiel

Copy link
Copy Markdown
Collaborator Author

Shepherd driver: converged for maintainer review

The final full panel recommends ship_now at 70e52a03dc40b971fed2e5e94c10e7c72287b011. No actionable follow-ups remain. The single panel recommendation was updated in place above.

Reservations carried from strategic-alignment

  • Preserve descendant symlink and source-authorization checks; verify alias-path deployment and escape rejection without broadening trusted paths. -- Addressed by root-only normalization after plugin admission, unchanged source-plan authorization, and real-filesystem descendant-link/escape assertions.
  • Name and document root-alias handling in CHANGELOG; require no new flags or changes to user HOME/APM_HOME. -- Addressed by the Unreleased entry and install reference note; both home spellings work with unchanged commands.

Folded in this run

Copilot signals reviewed

  • CHANGELOG.md -- LEGIT: changelog trailer requires the PR number; corrected and thread resolved.
  • tests/unit/install/test_security_scan_scope.py -- LEGIT: normalization needs the explicit non-root manifest path; corrected and thread resolved.
  • Second fetch found no additional Copilot findings.

Regression-trap evidence (mutation-break gate)

  • Removed only the root-resolution assignment: aliased global install and three non-plugin layout cases failed; canonical home and parser-normalized plugin controls passed.
  • Removed the same assignment for the added real-CLI alias lifecycle case: missing SKILL.md caused the expected failure. Assignment restored before the final suite.
  • Final-head suite: 345 passed, 3 subtests passed in 30.21s; separately executed declared-plugin owner case: 1 passed in 0.69s.

Canonical-owner evidence

Exact base/head detection reports the legacy plugin membership/placeholder owner through the integrator selector. Classification: owner-extension, no centralization or authority rerouting, dual_guardrail_required=false. Exact-head executed declared-plugin functional evidence and clean admission-first boundary lint cover the detected owner. The version 2 completion and semantic owner verifier passed.

Lint contract

The complete current canonical mirror passed before push: Ruff check/format on runtime, tests and architecture scripts; YAML I/O, 2100-line and portable-path guards; pylint R0801; auth signals and architecture boundaries.

CI

Final-head CI passed, including both Linux shards, Windows Compatibility, lifecycle, binary smoke, architecture ratchets and lint. Merge Gate also passed. Full rollup: 17 successful checks and one intentional docs-deploy skip; zero CI recovery iterations.

Mergeability status

PR head SHA CEO stance iters folds defers Copilot rounds CI mergeable mergeStateStatus notes
#2876 70e52a0 ship_now 1 3 0 2 green MERGEABLE BLOCKED awaiting maintainer/protection requirements

Convergence

One outer iteration; two Copilot rounds; full initial and final specialist panels. No deferrals. This is a landing-ready advisory, not an approval or merge. No issue closure or merge was performed.

@danielmeppiel
Daniel Meppiel (danielmeppiel) marked this pull request as draft September 7, 2026 14:41
Exercise connected global canonical and aliased command traces and deterministic spines inside generated models. Preserve global skill ownership and canonical module paths across update, validate frozen identity, and refresh mutable refs when no lock exists.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…imits

Exercise deps info, cache info/prune, project target inspection, and global absolute-file find refusal without inventing unsupported flags or weakening durable-state checks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@danielmeppiel

Copy link
Copy Markdown
Collaborator Author

Docs sync advisory

Verdict: no residual docs changes * Pages affected: 2 already updated * LLM calls: 3/15

At 9eeaf3785d06c70238628ca55dfbc7c81e57e79d, the install reference and packaged command guide cover alias-root ownership, canonical module materialization, frozen declared-identity checks, and no-lock mutable-ref freshness. The index-only classifier nominated compile/lock/outdated/update references; page-level localization found those pages already truthful, and CDO agreed. No structural change or companion PR is needed. This is advisory and does not change draft status or claim hosted CI acceptance.

Bind frozen full-SHA validation to req-lk-003 and extend canonical/aliased trajectories with real lock exports and cache/source maintenance.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Extend the existing identity owner selectors and provenance consumer checks for canonical module paths, frozen drift validation, and user-scope lockfile roots, with focused bypass mutations.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Regenerate the conformance statements after adding the req-lk-003 frozen full-SHA equality/refusal test.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@sergio-sisternes-epam

Copy link
Copy Markdown
Collaborator

PR triage recommendation

ready-for-review

Recommendation only, not merge or scope approval. A responsible
human maintainer must approve. Labels, automated advice, and
silence are not approval.

Linked issue

#2867 (open). Live labels include status/accepted, type/bug,
theme/security, area/content-security, and triage/recommended.
This PR body closes #2867.

Record on that issue: automated triage applied status/accepted on
2026-09-09; Daniel Meppiel (@danielmeppiel) removed it on 2026-09-12 as part of the
roadmap reset (apm-governance-reset:2026-09-12:acceptance);
Sergio Sisternes (@sergio-sisternes-epam) re-applied status/accepted on 2026-09-13.
The last issue comment is still the 2026-09-12 withdrawal. This
worker does not treat the PR-triage comment as a new acceptance
record.

Proposed classification

type/bug, theme/security, area/content-security (same useful
axes as #2867; the PR currently has no labels). No classification
conflicts to preserve.

Suggested next action

Wait for a human CODEOWNERS review. Do not merge from this
recommendation. Do not start autopilot-pr-review-worker or
autopilot-pr-merge-worker from this comment.

CODEOWNERS for * are Daniel Meppiel (@danielmeppiel) and Sergio Sisternes (@sergio-sisternes-epam).
GitHub already has a review requested of Sergio Sisternes (@sergio-sisternes-epam).
This comment does not add or remove review requests.

Note: base branch is danielmeppiel-bug-assessment, not main.
Head is 171a3ecbe. Existing conversation includes Copilot inline
notes (changelog trailer and plugin-manifest fixture; author
replied), a review-panel comment later edited, a shepherd
convergence note, and a docs-sync advisory. Those are not this
triage pass.

Suggested PR comment

Thank you for this pull request and for closing #2867.

This is advisory classification only. A responsible human
maintainer still has to review. Linked #2867 is labelled
status/accepted, so this PR is not auto-deferred.

CODEOWNERS owners are @danielmeppiel and @sergio-sisternes-epam.
A review is already requested of @sergio-sisternes-epam; this
comment does not change review requests or assignment.

@sergio-sisternes-epam Sergio Sisternes (sergio-sisternes-epam) added triage/recommended Automated advice completed; not human scope approval. type/bug Something does not work as documented. theme/security Secure by default. Content scanning, lockfile integrity, MCP trust boundaries. area/content-security Unicode scanning, Glassworm, apm audit content checks, SARIF output. status/accepted Human scope approval; verify the issue's approval record and review contact before work. labels Sep 17, 2026
Preserve original HOME-alias repair history and current destination-scoped ownership, alias placement and lifecycle coverage. Adapt documented conflicts without merging obsolete prerequisite branches.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adapt matching-lock freshness and provider-scoped seeds from de82061b7f66fe149ef240575d16165ca3ab631d and e30e4e59ef90f679309334ba87c4173ecbcbe09b; transport-before-semver from 843386ea1073373f95d5d8bc588fb057cd8747ab; connected real-tag trajectory from 60a9a5ca85829d0f83989f5aa5fc5b04cb0ae815.

Adapt integration-phase ownership snapshots from 40c53a8ae1cdd0ca8ae675e5ea7c7770ff123461 while retaining current-main destination collision authority. Reuse narrowly scoped epoch, alias and receipt tests with provenance in the approved standalone plan. Native evidence mechanics adapted from be73ed139f3a73c19437a895dfd6d68e41df8bfc without restoring the closed governance stack. Fix the alias reader defect exposed by retained global lifecycle witnesses through existing placement authority.

Original published repair and authorship remain in merge ancestry. No historical qualification results are reused.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Preserve landed Claude discovery and selected-store authority from #2952, OpenCode enabled semantics from #3102, and package-remote marketplace tags from #3044. Union existing dependency-identity selectors with the standalone reader/frozen routes; regenerate combined conformance. No unmerged prerequisite branch is imported.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Exercise same-cardinality witness substitution, skipped calls behind successful pytest exits, realistic foreign launchers, and manifest-only alias escape. Admit only canonical Python exec trampolines while preserving exact environment and source checks, including foreign virtualenvs sharing a base interpreter. Remove unrelated prototype ratchet metadata; ordinary required unit coverage remains unchanged. Align inherited scoped-lock fixture with actual bare-ref serialization without relaxing frozen admission.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Require actual APM Popen argv and isolated cwd/environment rather than counting identity probes. Reject executable-prefix lookalikes alongside foreign virtualenvs. Correct the external-global ownership test to independently expect its explicit alias path, lock alias and absence of the non-alias cache while retaining deployed bytes and compile assertions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@danielmeppiel
Daniel Meppiel (danielmeppiel) removed this pull request from stack #2883 September 30, 2026 13:17
@danielmeppiel
Daniel Meppiel (danielmeppiel) changed the base branch from danielmeppiel-bug-assessment to main September 30, 2026 13:17
@danielmeppiel

Copy link
Copy Markdown
Collaborator Author

APM Review Panel: needs_rework

PR #2876 repairs HOME-alias lifecycles with nine fresh native passes; completion validation, stale-seed recovery and a frozen full-pin regression trap still need in-scope folds.

panel-mode=full; personas=python-architect,test-coverage-expert,auth-expert,supply-chain-security-expert,performance-expert,devx-ux-expert,doc-writer,oss-growth-hacker,cli-logging-expert,apm-ceo

cc Sergio Sisternes (@sergio-sisternes-epam) -- a fresh advisory pass is ready for your review.

At 25299d0 against d3c5cd9, the updated driver evidence reports all nine fresh native trajectories passed in 752.00s, with report digest 2c55d4daab81c81e394ad6f69ef8c1ee9faabfdeec78418e01dc6e24e467a5f1. This supersedes the panelists' statements that the run was pending, not the discovered completion-validation gap or pending independent parent acceptance. Architecture preserves canonical owners and bounded integration snapshots; security found no introduced bypass in its scoped review; performance reports one build per used root, zero for unused roots and 10x ownership visits at 10N, not a measured wall-time speedup. These are bounded findings, not certification of every final-head obligation.

The missing real-CLI full-pin refusal/recovery trap is a critical-promise coverage gap despite the passing native run. Existing unit evidence in tests/unit/install/test_plan.py::TestLockfileSatisfiesManifest::test_frozen_compares_declared_identity_without_resolving_upstream passed eight cases with assertions "assert ok is satisfied", "assert bool(reasons) is not satisfied" and "assert lock.to_yaml() == before"; that proves the comparison contract, not command-path enforcement. Separately, the architect's manual probe shows completion validation accepting digest-bound records with skipped, uncollected witnesses and missing execution identity. A genuine successful run does not test refusal of such records. Treat that probe as a concrete finding needing an automated regression, not as durable qualification. Consolidate architect/auth stale-provider seed recovery into one repair: discard an unclassifiable historical seed while preserving strict current-manifest validation. The two provider diagnostic failures and two documentation clarity/concision items are also concrete, bounded folds; no specialist disagreement warrants waiving them.

All five curated follow-up groups fit the already authorized coupled scope and should be folded by default without asking for fresh permission. The approved eight-line upstream matrix test commit 0aad2c1 remains a separate, known import: it has not been cherry-picked or qualified here and is not a new PR-specific finding. After any new-head fold, the nine-pass report is historical evidence; qualify the resulting head with fresh native execution, independent parent completion, exact-head owner evidence and hosted CI. Preserve Sergio (Sergio Sisternes (@sergio-sisternes-epam)) as the responsible human reviewer and Dave Mead (@DaveMeadAdjust)'s contributor credit. The earlier needs_rework assessment is superseded only by genuine qualification, never by restoring the older ship_now statement.

Aligned with: Keep canonical dependency identity and HOME/APM_HOME path aliases distinct; exercise full-pin refusal and recovery through the real CLI. Preserve containment and current-manifest validation while discarding unusable historical seeds; validate execution witnesses rather than trusting digest binding alone. Retain strict frozen refusal and durable-state preservation, backed by command-path regressions and independently checked final-head evidence. Preserve provider and transport boundaries and distinguish source-Python qualification from packaged parity and hosted checks. Retain Dave's credit, Sergio's responsible-human review and clear contributor instructions without overstating qualification. Keep recovery actionable, preserve original failure diagnostics and state each install behavior once.

Growth signal. Retain the existing Dave Mead (@DaveMeadAdjust) credit and concise apm install --update recovery guidance. Distinguish filesystem HOME/APM_HOME aliases from dependency alias placement, and do not turn frozen replay into a promise of remote freshness, offline operation or automatic relocation. No additional marketing or README work is needed.

Panel summary

Persona B R N Takeaway

| python-architect | 0 | 2 | 0 | Ownership snapshots preserve the canonical builder and phase lifetime. Two recommended fixes remain: stale-provider seed recovery and validation of driver witness contents. |

| test-coverage-expert | 0 | 1 | 0 | Full-pin mismatch has unit coverage but lacks a native frozen refusal/recovery trap. Alias lifecycle coverage is present; exact-head native completion remains pending. |

| auth-expert | 0 | 1 | 0 | Scoped seeds preserve provider and transport boundaries; unclassifiable historical provider hints need a nonfatal seed-rejection path. Native execution remains pending. |

| supply-chain-security-expert | 0 | 0 | 0 | No introduced containment, ownership, or identity bypass found at 25299d vs d3c5cd9. Scoped tests: 344 passed, 2 subtests passed. Native9 and independent completion remain pending. |

| performance-expert | 0 | 0 | 0 | Original performance FOLD satisfied: 101 scoped tests pass; one build per used root, zero unused, 10x ownership visits at 10N. Native9 remains pending; no wall-time speedup claimed. |

| devx-ux-expert | 0 | 0 | 0 | No actionable UX regression found in the scoped 25299d diff. Frozen recovery, selected-store readers, and freshness guidance align. Review was static; disclosed native validation remains pending. |

| doc-writer | 0 | 2 | 0 | Scoped behavior claims align with the changed runtime. Clarify lifecycle-run prerequisites and consolidate duplicate install guidance; no blocking docs finding. |

| oss-growth-hacker | 0 | 0 | 0 | The original growth fold is addressed: contributor credit, concise recovery, distinct alias concepts, and bounded freshness claims. No new growth findings. |

| cli-logging-expert | 0 | 2 | 0 | Install help distinguishes frozen identity from freshness. Two native-provider failure paths obscure actionable diagnostics; review was static and no native suite was rerun. |

B = blocking-severity findings, R = recommended, N = nits. Counts are signal strength, not gates. The maintainer ships.

Top 5 follow-ups

  1. [test-coverage-expert] Extend the existing global external-root trajectory with real-CLI full-pin-only lock corruption, frozen refusal and recovery for both HOME variants. -- This missing secure-by-default/governed-by-policy regression ranks above opinion-only recommendations. Keep manifest.ref and lock.resolved_ref at real commit A, change only lock.resolved_commit to real commit B, assert exit 1 and the commit-mismatch diagnostic with unchanged durable snapshots, then restore the lock and prove frozen replay preserves pinned bytes. Retain all nine selectors and the initializer-backed 25-rule model; passing comparison units and today's native nine do not cover this transition.

  2. [python-architect] Validate driver witness contents and required source/interpreter/launcher identity through the existing execution-validation owner at completion verification and emission. -- The manual probe accepted nine uncollected/skipped witnesses with no events/models or execution identity after digest binding. Reuse validate_execution and the current candidate contract, explicitly allowing appropriate checkout-relative differences; add digest-recomputed invalid-witness/profile regressions and extend the existing static guard. The fresh nine-pass run does not refute this independent verification defect.

  3. [python-architect, auth-expert] Discard unclassifiable historical provider seeds without aborting resolution of a valid current manifest. -- These duplicate findings describe one recovery regression: obsolete GitLab hints on a now-GHES host can raise during seeding, including for lock-only entries, before valid declarations resolve. Return False without recording the unusable seed, keep current-manifest validation strict, and add matching-entry and unrelated stale-entry recovery regressions plus the existing freshness-boundary check.

  4. [cli-logging-expert] Preserve the independent run's failure report and original diagnostic; compare postflight source identity only against a captured baseline. -- Two distinct error paths obscure the actionable cause: completion status comparison can prevent persistence of a failed fresh run's report, and early contract-validation failure can be overwritten by a false source-change message. Use the existing exclusive report writer before completion comparison on execution failure, print the original reason and report path, and preserve initial errors separately from genuine postflight errors. Add focused regressions for both paths without emitting completion success.

  5. [doc-writer] Clarify lifecycle qualification prerequisites and consolidate duplicate install freshness/HOME-alias explanations. -- Define BASE_SHA as a full, locally present, distinct ancestor of HEAD; identify distinct new external report/sidecar paths; explicitly replace --completion-output with --completion for independent execution on the same base/head and keep the driver's report accessible at its recorded path. State replay/freshness and HOME-alias behavior once in install.md, retaining recovery links, frozen refusal details, the separate user lock root and containment qualifications.

Architecture

Class Diagram

Before

classDiagram
    direction LR
    class InstallPipeline {
      <<Module>>
      +run_install_pipeline()
    }
    class BaseIntegrator {
      +check_collision()
    }
    class SkillIntegrator {
      +integrate_package_skill()
      +_build_ownership_maps(project_root)
    }
    class LockFile {
      +read(path)
      +get_package_dependencies()
    }
    class LockedDependency {
      +get_unique_key()
      +to_dependency_ref()
    }
    class DependencyReference {
      +get_unique_key()
      +get_install_path()
    }
    class TieredRefResolver {
      <<ChainOfResponsibility>>
      +seed()
      +resolve()
    }
    class RefResolutionTier {
      <<Protocol>>
      +try_resolve()
    }
    class PerRunRefCache {
      +get()
      +put()
    }
    class RefFreshnessPolicy {
      <<Enumeration>>
      REPRODUCIBLE
      CURRENT_REMOTE
    }
    InstallPipeline ..> SkillIntegrator : sequential integration
    BaseIntegrator <|-- SkillIntegrator
    SkillIntegrator ..> LockFile : rebuild per consumer
    LockFile o-- LockedDependency
    LockedDependency ..> DependencyReference : reconstructs
    TieredRefResolver *-- RefResolutionTier
    TieredRefResolver *-- PerRunRefCache : lock seeds and fresh results
    TieredRefResolver ..> RefFreshnessPolicy
    TieredRefResolver ..> DependencyReference
    note for TieredRefResolver "Chain of Responsibility: L0 -> L1 -> L2 -> L3"
Loading

After

classDiagram
    direction LR
    class InstallPipeline {
      <<Module>>
      +run_install_pipeline()
      +_run_integration_phase()
    }
    class BaseIntegrator {
      +check_collision()
    }
    class SkillIntegrator {
      <<ContextManager>>
      +ownership_snapshot()
      +_ownership_maps(lockfile_root)
      +integrate_package_skill()
    }
    class SkillSupport {
      <<Module>>
      +build_skill_ownership_maps(lockfile_root)
    }
    class LockFile {
      +read(path)
      +get_package_dependencies()
    }
    class LockedDependency {
      +get_unique_key()
      +to_dependency_ref()
    }
    class DependencyReference {
      +get_unique_key()
      +get_install_path()
    }
    class TieredRefResolver {
      <<ChainOfResponsibility>>
      +seed()
      +_lock_seed_key()
      +resolve()
    }
    class RefResolutionTier {
      <<Protocol>>
      +try_resolve()
    }
    class PerRunRefCache {
      +get()
      +put()
    }
    class RefFreshnessPolicy {
      <<Enumeration>>
      REPRODUCIBLE
      LOCKED_OR_CURRENT
      CURRENT_REMOTE
    }
    class HostProviders {
      <<Module>>
      +classify_host_provider()
      +effective_host_provider_identity()
    }
    class LifecycleEvidencePlugin {
      <<Observer>>
      +pytest_sessionstart()
      +pytest_runtest_logreport()
    }
    class LifecycleContracts {
      <<Module>>
      +candidate_contract()
      +validate_execution()
    }
    class CheckLifecycleEvidence {
      <<Module>>
      +execute()
      +validate_completion()
    }
    InstallPipeline ..> SkillIntegrator : integration-only context
    BaseIntegrator <|-- SkillIntegrator
    SkillIntegrator ..> SkillSupport : canonical builder seam
    SkillSupport ..> LockFile : once per used physical root
    LockFile o-- LockedDependency
    LockedDependency ..> DependencyReference : reconstructs
    TieredRefResolver *-- RefResolutionTier
    TieredRefResolver *-- PerRunRefCache : fresh repository results
    TieredRefResolver ..> RefFreshnessPolicy
    TieredRefResolver ..> DependencyReference : scoped lock-seed identity
    TieredRefResolver ..> HostProviders
    CheckLifecycleEvidence ..> LifecycleEvidencePlugin : fresh pytest observations
    CheckLifecycleEvidence ..> LifecycleContracts : execution validation
    note for SkillIntegrator "Context manager: lazy MappingProxyType maps; finally clears the epoch; live collision overlay remains separate"
    note for TieredRefResolver "Chain of Responsibility: matching lock seed first; unseeded install refs use L0 -> L1 -> L3"
    class InstallPipeline:::touched
    class SkillIntegrator:::touched
    class SkillSupport:::touched
    class TieredRefResolver:::touched
    class RefFreshnessPolicy:::touched
    class HostProviders:::touched
    class LifecycleEvidencePlugin:::touched
    class LifecycleContracts:::touched
    class CheckLifecycleEvidence:::touched
    classDef touched fill:#fff3b0,stroke:#d47600
Loading

Component

Before

flowchart TD
    A["apm install: install/pipeline.py run_install_pipeline"] --> B["install/phases/resolve.py run"]
    B --> C["install/helpers/ref_seed.py seed_ref_resolver_from_lockfile"]
    C --> D["deps/tiered_ref_resolver.py seed: PerRunRefCache.put repository/ref"]
    D --> E["resolve: L0 repository/ref hit?"]
    E -->|yes| G["[FS] install/phases/download.py run"]
    E -->|no| F["[NET] _dispatch: commits API; [I/O] bare cache; [EXEC] legacy clone"]
    F --> G
    G --> H["[FS] _run_phase integrate: install/phases/integrate.py run"]
    H --> I{"ctx.lockfile_only?"}
    I -->|no| J["integration/skill_integrator.py integrate_package_skill"]
    J --> K["[I/O] _build_ownership_maps(project_root): LockFile.read per consumer"]
    K --> L["[FS] _integrate_native_skill / _integrate_skill_bundle / _promote_sub_skills_standalone"]
    L --> M{"pipeline result_from_install_context: FAILED?"}
    I -->|yes| M
    M -->|yes| N["Return failed InstallResult: exit_code 1"]
    M -->|no| O["[LOCK] install/phases/lockfile.py LockfileBuilder.build_and_save after cleanup"]
Loading

After

flowchart TD
    A["apm install: install/pipeline.py run_install_pipeline"] --> B["install/phases/resolve.py run"]
    B --> C["install/helpers/ref_seed.py seed_ref_resolver_from_lockfile"]
    C --> D["deps/tiered_ref_resolver.py seed: _lock_seed_key"]
    D --> P{"core/host_providers.py effective_host_provider_identity succeeds?"}
    P -->|no| Q["ValueError escapes before _resolve_dependencies"]
    P -->|yes| E{"resolve: matching dependency/provider/transport/ref lock seed?"}
    E -->|yes| G["[FS] install/phases/download.py run"]
    E -->|no| F["[NET] _dispatch: current upstream for unseeded install refs; fresh L0 reuse allowed"]
    F --> G
    G --> H["install/pipeline.py _run_integration_phase"]
    H --> I{"ctx.lockfile_only?"}
    I -->|yes| J["contextlib.nullcontext: integrate.run without skill lookup"]
    I -->|no| K["SkillIntegrator.ownership_snapshot: empty lazy epoch"]
    K --> L["[FS] integrate.run: integrate_package_skill; plugin boundary before root resolution"]
    L --> R["[I/O] _ownership_maps: USER get_apm_dir or project_root; resolve physical root"]
    R --> S{"Physical root already cached?"}
    S -->|no| T["[I/O] skill_support.build_skill_ownership_maps: LockFile.read; freeze maps"]
    S -->|yes| U["Reuse immutable maps; retain live managed set and session owners"]
    T --> U
    U --> V["[FS] _integrate_native_skill / _integrate_skill_bundle / _promote_sub_skills_standalone"]
    V --> W["ownership_snapshot finally: clear maps on success or exception"]
    L -->|uncaught exception| W
    W --> X{"Integration raised?"}
    X -->|yes| Y["Propagate exception without lockfile persistence"]
    X -->|no| M{"pipeline result_from_install_context: FAILED?"}
    J --> M
    M -->|yes| N["Return failed InstallResult: exit_code 1"]
    M -->|no| O["[LOCK] install/phases/lockfile.py LockfileBuilder.build_and_save after cleanup"]
    AA["scripts/check_lifecycle_evidence.py main --lane full"] --> AB["[I/O] execute: candidate and source_profile"]
    AB --> AC["[EXEC] pytest.main fixed nine nodes; LifecycleEvidencePlugin observes runner calls"]
    AC --> AD["scripts/lifecycle_contracts.py validate_execution"]
    AD --> AE["[I/O] execute finally: candidate and source_profile recheck"]
    AE --> AF{"Passed and --completion supplied?"}
    AF -->|yes| AG["[I/O] validate_completion: digest, headers, witness keys and CLI digest"]
    AF -->|passed without completion| AH["[FS] _write_new report; optional completion_summary sidecar"]
    AG --> AH
    AF -->|failed| AI["main returns 1"]
    AG -->|EvidenceError| AI
    AH --> AJ["main returns 0"]
Loading

Recommendation

Fold the concrete in-scope repairs and coverage/doc improvements, import the parent-approved exact upstream matrix test commit, and qualify the resulting head before seeking a fresh ship recommendation. Highest-signal work is the real-CLI frozen full-pin regression and driver-witness completion validation. The nine fresh passes are meaningful evidence for 25299d/d3, not proof for a later head or completed independent parent acceptance. This is advisory input to Sergio's responsible-human review; it does not authorize a merge or substitute for human review.


Full per-persona findings

python-architect

  • [recommended] Reject an unclassifiable lock seed without aborting valid manifest resolution at src/apm_cli/deps/tiered_ref_resolver.py:466
    The new _lock_seed_key calls effective_host_provider_identity on every seed, but seed does not handle that owner's ValueError/RuntimeError. A previously valid lock for code.example.com with host_type='gitlab' becomes unclassifiable when GITHUB_HOST identifies that host as GHES and the manifest removes the hint. detect_ref_change correctly returns True, but resolve.run calls seed_ref_resolver_from_lockfile before _resolve_dependencies, so the old lock aborts the command before the valid manifest can recover. The in-memory probe confirmed that old reconstruction and _repository_cache_identity succeed while the new seed key raises ValueError. Seeding also visits lock-only dependencies, so a stale unused entry can affect otherwise unrelated work. This is a bounded error-path regression, not evidence that ordinary matching installs fail.

Design patterns

  • Used in this PR: Context manager -- SkillIntegrator.ownership_snapshot bounds lazy immutable ownership maps to integration and clears them in finally.

  • Used in this PR: Chain of Responsibility -- TieredRefResolver retains the tier waterfall while separating dependency-scoped lock seeds from fresh repository results.

  • Pragmatic suggestion: none -- the existing owners are sufficient; a narrow seed-rejection path is simpler than another policy abstraction.
    Suggested: Treat canonical provider-classification failure for a locked seed as seed rejection, returning False without recording it. Keep invalid current-manifest resolution fail-closed. Add matching and unrelated stale-lock recovery regressions, and extend the registered freshness boundary check to retain that distinction.

  • [recommended] Validate driver witness records, not only their names and report digest at scripts/check_lifecycle_evidence.py:204
    validate_completion checks the driver's header, witness key set, profile kind and CLI digest, but never validates the witness values or the remaining source/interpreter/launcher profile. A read-only probe supplied a correctly digest-bound driver with all nine witnesses collected=False, setup='skipped', events=[], models=0 and no source/interpreter/launcher identity; validate_completion accepted it. The current _completion test fixture similarly treats empty witness objects as an acceptable driver. This does not bypass execute() or the parent's fresh run. It means successful independent verification cannot also substantiate the claimed driver execution, which is a separate obligation in the brief. A hash establishes which bytes were supplied, not whether those bytes contain valid execution evidence.
    Suggested: Route loaded driver records through the existing validate_execution owner using the current candidate contract, and validate required source-profile fields with checkout-relative differences explicitly allowed. Reuse that validation at completion emission. Add a digest-recomputed negative case for skipped/empty driver witnesses and extend the native-evidence static guard to require the owner call; do not duplicate trajectory logic.

test-coverage-expert

  • [recommended] Exercise the new full-pin mismatch rejection through a real frozen install at tests/integration/test_required_lifecycle_state_machine.py:
    Scenario Evidence row 7 claims that frozen mode rejects mismatched full commit pins. The new branch in install/plan.py compares a manifest full pin against resolved_commit independently of resolved_ref. Its eight-case unit test passes in this review, and the conformance test also contains the mismatch assertion, but neither invokes the CLI. I searched tests/integration for full-pin, manifest-commit, mismatch and frozen/provider cases and read the matching tests. The required/global generated trajectories reject a changed declaration or provider; they do not keep manifest.ref == lock.resolved_ref == A while changing only lock.resolved_commit to B. test_hermetic_lifecycle_foundation.py tests that corruption through audit, not install --frozen. Thus existing native trajectories would not detect a command-path regression that bypasses this particular preflight rejection. This is a tier-floor gap, not evidence of a failing implementation or a claim that the pending native nine are absent.
    Suggested: Extend test_required_global_audit_rule_matrix_for_external_roots with a short full-pin subtrajectory for both existing home variants: install a real full commit A; change only the locked resolved_commit to another real commit B while retaining resolved_ref=A and manifest ref=A; snapshot the durable roots; run install --global --frozen and require exit 1 plus the manifest-commit/locked-commit diagnostic and unchanged snapshots; restore the matching lock and require frozen replay with the original deployed bytes. Extend the existing trajectory instead of adding another module or witness selector, preserving all nine selectors and the 25-rule initializer-backed model.

auth-expert

  • [recommended] Reject unclassifiable lock seeds without aborting ordinary resolution at src/apm_cli/deps/tiered_ref_resolver.py:464
    The new seed identity calculation can raise ValueError for historical provider metadata that drift detection explicitly treats as requiring re-resolution. For example, a locked dependency with host='code.example.com' and host_type='gitlab' becomes unclassifiable when GITHUB_HOST is subsequently set to code.example.com. classify_host_provider rejects that hint, and seed() propagates the exception. seed_ref_resolver_from_lockfile seeds every locked entry without handling this failure, including entries no longer declared. Thus an obsolete seed can prevent ordinary installation instead of being discarded so the valid current declaration resolves upstream. This is a static call-path finding; no tests were executed.
    Suggested: Handle unclassifiable historical provider identity during seed admission by returning False, consistently with rejected seed inputs and detect_ref_change's treatment of unclassifiable locks. Keep current manifest validation strict. Add a regression using a formerly GitLab-hinted lock entry and a currently GHES-classified host, asserting that the seed is rejected and resolution of the valid current declaration can proceed.

supply-chain-security-expert

No findings.

performance-expert

No findings.

devx-ux-expert

No findings.

doc-writer

  • [recommended] Specify the lifecycle driver's base revision and completion-mode prerequisites. at docs/src/content/docs/contributing/integration-testing.md:79
    The new example uses BASE_SHA without defining its required form or relationship to HEAD. scripts/check_lifecycle_evidence.py:94-111 requires a full commit SHA, rejects an abbreviated SHA or branch name, and requires a distinct ancestor of the checked-out head. The independent-run paragraph also does not explicitly say to replace --completion-output with --completion, although argparse makes them mutually exclusive. These omitted prerequisites can stop a contributor before the documented qualification runs.
    Suggested: Define BASE_SHA as the full SHA of the intended comparison base, which must be a distinct ancestor present locally; identify REPORT_PATH and COMPLETION_PATH as distinct new external paths. In the independent-run paragraph, explicitly replace --completion-output with --completion, retain the same base/head, and keep the driver's report available at the path recorded by its sidecar.

  • [recommended] Consolidate the newly duplicated install behavior explanations. at docs/src/content/docs/reference/cli/install.md:125
    The new 'Lock replay and freshness' bullet at lines 125-131 repeats the existing, also-expanded 'Lockfile replay and Git ref freshness' bullet at line 180. 'Home aliases' at lines 132-135 repeats the new 'User-scope skill paths' bullet at line 185. This creates multiple maintenance locations for the same promises within one page, contrary to state-once documentation composition. The five scoped documentation files grow by 641 whitespace-delimited words, including 194 in install.md; consolidating these bullets offsets growth without removing user guidance.
    Suggested: Keep one replay/freshness bullet containing matching-lock versus unseeded-ref behavior and the existing update/lockfile links; put structural refusal details in the existing Frozen mode bullet. Merge the two home-alias bullets, preserving the separate user lock root and package/destination containment qualifications.

oss-growth-hacker

No findings.

cli-logging-expert

  • [recommended] Preserve the independent run's failure report before comparing completion claims at scripts/check_lifecycle_evidence.py:334
    In --completion mode, a fresh failed execution reaches validate_completion before its report is written. A successful driver sidecar then disagrees on status, replacing the actual execution error with 'Completion disagrees with fresh execution: status'. The command exits without saving the requested independent report, losing its structured witness diagnostics precisely when the independent run fails.
    Suggested: Handle a failed fresh execution before completion comparison: persist its failed report through the existing exclusive-write path, print the report location and original error, and return nonzero without emitting completion success. Add a --completion regression case asserting that the fresh failure reason and report survive.

  • [recommended] Do not replace contract-validation errors with a false source-change diagnostic at scripts/check_lifecycle_evidence.py:291
    If candidate_contract rejects the ledger before line 241, report['profile'] still contains only the initial kind field. The finally block compares a complete source profile against that placeholder and overwrites the original actionable error with 'Source executable identity changed during execution', even when no source changed and execution never started.
    Suggested: Track whether an initial source profile was successfully captured and compare profiles only when that baseline exists. Preserve the original failure, recording any genuine postflight failure separately. Add a case where candidate_contract raises an EvidenceError and assert that its diagnostic remains in the failed report.


Generated by autopilot-pr-review-worker. This comment is AI-generated and may contain errors.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
(cherry picked from commit 0aad2c1)
Validate driver witness contents and execution identity, preserve failure reports and first causes, reject unclassifiable historical provider seeds, and exercise real frozen full-pin refusal/recovery in both global trajectories. Preserve initialized integrator contracts in pipeline unit fixtures.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Compose 6db0c11 (#3103). Preserve every existing provider/seed identity guard and add the incoming MCP AuthResolver/provenance guards as a faithful union. Incoming production auth files are unchanged from main; the already-imported marketplace mutation case remains single.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Deep isolated paths can make Rich wrap the existing schema warning between words. Preserve the exact warning phrase, successful install, and deployed-byte assertions while comparing whitespace-normalized output. The unchanged b6 test fails on real wrapped output; the corrected ownership suite passes in the same deep environment.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@danielmeppiel

Copy link
Copy Markdown
Collaborator Author

APM Review Panel: needs_rework

The lifecycle delta preserves authority and recovery contracts; one critical-surface fixture gap and a confirmed CI time-budget failure still need focused work.

panel-mode=delta; personas=python-architect,test-coverage-expert,auth-expert,supply-chain-security-expert,performance-expert,cli-logging-expert,doc-writer,apm-ceo

cc Sergio Sisternes (@sergio-sisternes-epam) -- a fresh advisory pass is ready for your review.

This is one actual delta panel at 541e583, cumulative outer pass 3, not a new full panel. Six specialists report no remaining substantive finding across the five prior fold groups. Coverage identifies one missing fixture-backed promise: ordinary install must recover from unclassifiable historical lock provider hints while keeping current-declaration classification strict. Its narrower inspection confirmed the same gap, not another finding or review pass. The passing real-resolver/mock-downloader units remain valid evidence at their boundary; they do not establish real-CLI installation, deployment and lock-rewrite behavior. This is a critical portability regression-trap gap, not a demonstrated runtime bug. Prefer the bounded extension of the existing ref_freshness fixture over another lifecycle framework.

The orchestrator's live CI update supersedes the brief's pending status. Both actual-head runs, 36733011383 and 36733010855, were cancelled with the annotation 'The job has exceeded the maximum execution time of 6m0s' for required Lifecycle Smoke (Linux); check 36733011171 failed consequentially. Run 36733011383 reported 229 passed and 1 skipped in 365.65 seconds, with roughly 30 seconds of setup in addition. Successful test assertions therefore did not produce successful CI. This is an observed execution-budget failure supplied by the orchestrator, not a panelist finding or a transient rerun opportunity. Profile and remove unnecessary execution cost while preserving witnesses, contracts, required selection and the existing timeout.

The clean, source-bound 541 evidence remains meaningful: 9 native source-Python witnesses, 74 killed and restored physical mutants, and 1419 functional passes plus 2 subtests. It does not establish packaged-platform parity, and the invalidated historical dirty run contributes no acceptance evidence. At synthesis the parent run was pending. Post-synthesis execution update: the independent parent full --completion passed all 9 witnesses in 629.25s, using its own clean source and environment; report SHA256 355d3ead9c207b09c01eb805a41f4e7808dc72789efaac6fd8b04c8fc0139b53. This qualifies only 541, not CI success or a successor. Any source fold needs fresh successor-bound proof. The panel remains advisory, Sergio's responsible human review remains unchanged, and DaveMeadAdjust's contribution credit should be preserved.

Aligned with: Prove that valid current declarations recover from obsolete historical provider metadata through ordinary install, without weakening strict current identity. Preserve containment, provider and transport authority, full-pin refusal, receipt separation and immutable ownership snapshots throughout the follow-up. Reuse an existing real-CLI fixture and reduce redundant execution cost rather than adding another expensive test framework. Keep evidence limitations explicit, preserve contributor credit and leave the final decision with responsible human reviewers.

Panel summary

Persona B R N Takeaway

| python-architect | 0 | 0 | 0 | Delta folds close the prior architecture defects; canonical validators and incoming guard union are preserved. No remaining substantive finding. Independent acceptance and hosted CI remain pending. |

| test-coverage-expert | 0 | 1 | 0 | Both full-pin lifecycle trajectories are strengthened; stale-provider seed recovery still lacks fixture-backed install coverage. |

| auth-expert | 0 | 0 | 0 | Historical provider refusals are discarded without cache or receipt admission; unexpected TypeError propagates. Current classification remains strict, and auth guards compose without regression. |

| supply-chain-security-expert | 0 | 0 | 0 | No concrete in-scope regression found in historical seed rejection, strict current identity, full-pin refusal, or fresh-report binding. Read-only assessment; independent acceptance and CI remain pending. |

| performance-expert | 0 | 0 | 0 | Static delta: seed refusal (467-474) adds no I/O or loops; shared fresh-answer caching/coalescing (526-566) remains unchanged. No broader per-dependency work. Tests not run. |

| cli-logging-expert | 0 | 0 | 0 | Scoped delta preserves failed reports and original errors, records postflight failures separately, and keeps seed debug and whitespace assertions truthful. Parent acceptance and CI remain pending. |

| doc-writer | 0 | 0 | 0 | No new substantive docs defects in 25299d..541e583. Lifecycle instructions match the verifier; install consolidation preserves root, containment, freshness and recovery boundaries. |

B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.

Top 2 follow-ups

  1. [test-coverage-expert] Extend the existing ref_freshness real-CLI fixture with matching and unrelated unclassifiable historical provider hints. -- The narrower inspection confirmed the original integration-floor gap. Reuse test_reinstall_uses_current_ref_without_a_matching_lock_entry to verify current installed and deployed bytes, rewritten lock identity, preserved sentinels and stable replay. Keep current-declaration rejection strict; this adds a missing portability safeguard rather than repairing a proven runtime failure.

  2. [orchestrator] Profile Lifecycle Smoke (Linux) and reduce unnecessary execution cost within the unchanged six-minute job budget. -- Both 541 CI runs exceeded the job cap despite passing test assertions, and the dependent check failed. Preserve all witnesses, contracts and required selection; do not raise the timeout or rely on unchanged reruns. Obtain successful CI and fresh successor-bound acceptance evidence after any source fold.

Recommendation

Use the existing fold-by-default authorization for the bounded recovery fixture and contract-preserving CI cost reduction. Independent acceptance has now passed for 541; obtain fresh successor proof for any source changes before presenting the updated evidence to Sergio and the maintainer. This is advisory guidance, not a waiver or authorization to merge, change reviewers or alter PR state.


Full per-persona findings

python-architect

No findings.

test-coverage-expert

  • [recommended] Exercise stale-provider seed recovery through a real install at tests/unit/install/phases/test_resolve_seed_from_lockfile.py:82

The new matching and unrelated historical-seed cases exercise real classification and resolver admission, but construct the lockfile in memory and mock the downloader. They defend the function-level fix, not the install promise that an obsolete provider hint cannot prevent a valid current dependency from being installed. Targeted searches of the owned integration tests for historical, unclassifiable, seed, host_type, GITHUB_HOST and provider found no corresponding recovery transition. The generated frozen_refusal rule instead changes the current manifest's provider, expects refusal, and restores the manifest; it does not exercise rejecting stale lock metadata while continuing a non-frozen install. This is a tier-floor follow-up, not evidence of a correctness regression or a reason to discount the recorded passing unit controls.

Suggested: Extend test_required_global_audit_rule_matrix_for_external_roots rather than adding another expensive lifecycle module or changing the nine selectors. Reuse its local remote and sentinels: install A, publish B, arrange an obsolete historical provider hint with a valid current declaration, run ordinary install, verify B's installed and deployed bytes plus the rewritten lock, then replay and verify unchanged state. Cover matching and unrelated historical entries within the existing fixture. Keep current-declaration rejection strict.

Proof (missing at integration-with-fixtures): tests/integration/test_required_lifecycle_state_machine.py::test_required_global_audit_rule_matrix_for_external_roots -- A stale provider hint in my lockfile does not prevent installing my valid current dependency or preserve obsolete package bytes.

Proposed assertion: assert installed_skill.read_bytes() == revision_b_bytes assert locked_commit == commit_b.sha assert sentinel.read_bytes() == original_sentinel_bytes

auth-expert

No findings.

supply-chain-security-expert

No findings.

performance-expert

No findings.

cli-logging-expert

No findings.

doc-writer

No findings.

This panel is advisory. It does not block merge. Re-apply the
panel-review label after addressing feedback to re-run.


Generated by autopilot-pr-review-worker. This comment is AI-generated and may contain errors.

Exercise matching and unrelated stale provider hints through ordinary CLI installs. Use four bounded smoke workers without changing selection, grouping or the six-minute cap.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@danielmeppiel

Copy link
Copy Markdown
Collaborator Author

APM Review Panel: ship_now

The lifecycle recovery delta closes the real-CLI coverage gap and restores CI timing within the unchanged budget, with exact-head independent acceptance now passing.

panel-mode=delta; personas=python-architect,test-coverage-expert,performance-expert,doc-writer,apm-ceo

cc Sergio Sisternes (@sergio-sisternes-epam) -- a fresh advisory pass is ready for your review.

Post-synthesis acceptance update: The independent parent has now delivered its actual process/postflight receipt: exit 0, all nine witnesses and 734 events revalidated, every setup/call/teardown passed, clean exact source and unchanged profile, distinct parent checkout/venv, and no postflight errors. Report SHA256 057e631e4fbccc4c74400e5f31b3cb45fe6ea7e12397dabd0d0009a8244d38f9. The receipt handoff mentioned as pending in the synthesis below is complete; no source change or new review pass occurred.

Final outer4 delta synthesis at 2e819da: all four specialists returned zero findings, with no disagreement to arbitrate. Both bounded corrections from outer3 are supported. The test-coverage expert identifies real-CLI historical-matching and historical-unrelated cases in tests/integration/test_ref_freshness_lifecycle.py::test_reinstall_uses_current_ref_without_a_matching_lock_entry, including assert _deployed_bytes(scenario) == _skill_document("commit-b").encode() and assert _module_skill_bytes(scenario) == _skill_document("commit-b").encode(). Recorded physical mutation evidence exercises both cases negatively and restores passing controls. The four-file delta reuses existing fixtures and changes bounded same-job scheduling; no runtime source, scripts or manifests changed from 541e583. Selection, loadgroup grouping, permissions and the six-minute cap remain intact.

The independent parent report now exists and passes, superseding the pending-at-launch caveats in specialist returns. I inspected parent-native.log, which records '9 passed in 618.54s (0:10:18)' and 'Lifecycle evidence: passed', and verified parent-native.json SHA256 057e631e4fbccc4c74400e5f31b3cb45fe6ea7e12397dabd0d0009a8244d38f9. The report identifies the exact reviewed head and base 6db0c11; the driver reports successful canonical validate_completion and verification that parent source_root and python_environment differ from its own. This is independent source-Python acceptance, not packaged-platform parity. Driver evidence separately records nine witnesses passing in 733.19s, 75 physical mutations killed and restored, 1426 functional passes plus two subtests, seven-owner/nine-consumer validation, and passing lint, specification and documentation checks. Reviewers inspected evidence read-only; they did not rerun these campaigns. The parent's final process/postflight receipt is still being relayed, and no receipt identifier or completed handoff is asserted.

Exact-head hosted evidence records 22 checks concluding SUCCESS, NEUTRAL or SKIPPED, including successful required status and CLA checks. Lifecycle Smoke used four actual CPUs: 231 passed and one skipped in 184.01s, with the whole job completing in 210s against the unchanged 360s cap. That demonstrates recovery from the observed budget defect, not statistical tail-latency reliability. The 541 local two-versus-four-worker comparison remains historical evidence, not a 2e hosted performance comparison. Earlier invalidated shared-root execution contributes no acceptance evidence. Source freeze remains unchanged; this is cumulative outer4, with Copilot 2/2 and CI recovery 2, not a cap reset or another planned panel. Main ruleset 9294522 still requires code-owner review, last-push approval and the merge queue. Preserve Sergio's existing review request and DaveMeadAdjust's credit.

Aligned with: Real-CLI recovery cases now defend current declared identity, installed and deployed bytes, rewritten lock state, preserved manifest and sentinel, and stable replay despite obsolete historical provider hints. The bounded delta preserves existing authority and isolation contracts, read-only CI permissions and strict current-declaration handling. Passing technical evidence does not replace code-owner review, last-push approval or the repository's merge-queue process. Existing fixtures and measured four-worker scheduling address the two demonstrated gaps without another framework, reduced selection or a longer timeout. Preserve Sergio's review request and DaveMeadAdjust's credit while distinguishing historical evidence, current results and remaining human responsibilities.

Panel summary

Persona B R N Takeaway
python-architect 0 0 0 No substantive architecture defect in the four-file delta. Existing fixture authority and isolation are preserved; four-worker loadgroup retains selection. Independent full9 remains pending.
test-coverage-expert 0 0 0 Adjusted ApmLifecycle cases close both historical-provider recovery gaps in recorded real-CLI evidence; exact-four-worker topology has mutation-backed negative protection. No new findings.
performance-expert 0 0 0 Measured 2e hosted smoke completes in 210s under the unchanged 360s cap; four workers retain loadgroup and selection. No substantive performance finding.
doc-writer 0 0 0 No substantive docs drift in this delta. Four bounded workers, public Ubuntu, loadgroup grouping and the unchanged six-minute limit match the workflow and worker guard.

B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.

Recommendation

Recommend shipping this exact frozen revision through the repository's normal human review and merge-queue process: the two previously demonstrated deficiencies now have affirmative exact-head evidence, and the delta panel identifies no further technical changes. Complete the pending parent process/postflight receipt handoff without inventing its identifier or conflating it with the already-passing report. This stance follows the evidence, not exhausted iteration caps; it is neither human approval nor permission to merge or enter the queue.


Full per-persona findings

python-architect

No findings.

test-coverage-expert

No findings.

performance-expert

No findings.

doc-writer

No findings.

This panel is advisory. It does not block merge. Re-apply the
panel-review label after addressing feedback to re-run.


Generated by autopilot-pr-review-worker. This comment is AI-generated and may contain errors.

@danielmeppiel
Daniel Meppiel (danielmeppiel) merged commit 186b3ee into main Sep 30, 2026
23 checks passed
@danielmeppiel
Daniel Meppiel (danielmeppiel) deleted the danielmeppiel-fix-symlink-home-deployment branch September 30, 2026 18:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/content-security Unicode scanning, Glassworm, apm audit content checks, SARIF output. status/accepted Human scope approval; verify the issue's approval record and review contact before work. theme/security Secure by default. Content scanning, lockfile integrity, MCP trust boundaries. triage/recommended Automated advice completed; not human scope approval. type/bug Something does not work as documented.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Global install succeeds with empty skill directories when HOME/APM_HOME uses a symlink alias

3 participants