Repository navigation
fix(hooks): admit and stage hook-only Git-path packages - #3282
Conversation
Preserve validated hook layouts through resolver admission and stage replacement updates before touching the installed source. Cover both layouts with real install, restore, update, consent, target and removal lifecycle regression tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 82e9592a-494f-4318-864b-5f8e1877372f
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 82e9592a-494f-4318-864b-5f8e1877372f
There was a problem hiding this comment.
🟡 Changes recommended
Selective updates can replace unselected packages without updating their lock records, and advancing hook updates trigger redundant downloads.
2 open issues
What changed in this PR
Repairs hook-only Git-path package installation for issue #2980 using existing validation and replacement staging.
Changes:
- Admits both supported hook layouts after descriptor validation.
- Routes hook updates through staged replacement.
- Adds lifecycle and architecture regression tests and updates author guidance.
| File | Description |
|---|---|
| tests/unit/deps/test_hook_package_admission.py | Tests validation, admission, and update rechecks. |
| tests/integration/test_hook_path_package_lifecycle.py | Exercises installed-CLI hook lifecycle and failure preservation. |
| tests/integration/test_architecture_hook_package_admission.py | Tests guards against bypassing canonical owners. |
| src/apm_cli/models/validation.py | Validates hook descriptors and preserves provenance. |
| src/apm_cli/install/phases/resolve.py | Supplies installed paths to recheck policy. |
| src/apm_cli/drift.py | Forces staged rechecks for hook updates. |
| src/apm_cli/deps/apm_resolver.py | Admits validated hook packages. |
| scripts/architecture_linter/checks/marketplace_catalog_and_contract.py | Guards canonical hook admission routes. |
| packages/apm-guide/.apm/skills/apm-usage/package-authoring.md | Documents hook-only authoring and lifecycle. |
| docs/src/content/docs/reference/package-types.md | Clarifies supported layouts and deployment behavior. |
Review effort: Balanced
Keep selective refreshes inside their selected dependency subtree; bind callback reuse to validated source bytes and the published path. Cover rejected replacements, shared parser and scope mutations, and repair valid-hook CI fixtures. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Propagate scoped keys while discovering edges and revisit already-loaded shared nodes once when newly selected. Preserve installed source bytes on invalid shared-child updates and cover the scope gate directly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Normalize selected dependency specs through canonical identity and reuse validated callback receipts for refresh as well as update. Cover both layouts and all receipt integrity controls. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Exercise existing req-rs-012 through installed hook lifecycles rather than waive the critical-path gate. Keep normalized selection in the existing package-selection helper and preserve invalid-selector fallback. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Name the canonical delegate and verify both parse failure types without losing valid selected keys. No runtime behavior change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Restrict validated callback receipts to update and refresh modes. Preserve ordinary and frozen lock restoration, with explicit negative mode coverage and matching architecture guard mutations. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
APM Review Panel:
|
| Persona | B | R | N | Takeaway |
|---|---|---|---|---|
| Python Architect | 0 | 1 | 0 | Shared interpreted-reference ownership is sound. |
| Supply Chain Security | 0 | 0 | 0 | No new trust surface or integrity bypass. |
| DevX UX | 0 | 0 | 0 | Existing CLI and fallback behavior preserved. |
| Test Coverage | 0 | 2 | 0 | Pending lifecycle evidence satisfied; unit/static evidence confirmed. |
| Documentation | 0 | 0 | 0 | Starlight and packaged guidance match the bounded behavior. |
| Performance | 0 | 1 | 0 | Canonical-reference scan accepted; no current change requested. |
The original finding classifications include positive observations and a now-satisfied evidence request. No panel-originated work remains.
Recommendation
Ship this head on the completed technical evidence. The CEO's final-hosted-CI condition is now satisfied; landing remains the maintainer's decision. This worker has not merged, enabled auto-merge, assigned anyone, requested reviewers, or changed repository policy.
Native limit: Evidence covers the real installed Python CLI and target configuration/resource contracts, not hook execution, an actual native hook harness, or hook-specific frozen PyInstaller acceptance. Frozen-install checks establish lock integrity, not frozen-executable coverage.
Per-persona dispositions and provenance
- Architecture: Positive consolidation finding retained. Early selection produces keys; late filtering produces identities. Parse failure falls back to the raw selector; it does not successfully parse a different key.
- Supply chain: No findings. Existing trust, consent, validation, and ordinary/frozen receipt boundaries remain unchanged.
- DevX: No findings. The repair does not broaden dotted raw-shorthand grammar or add commands/flags.
- Test coverage: The initial resource-free fixture could not satisfy the nonempty-deployment assertion. The resource-bearing fixture retains that assertion and passes both layouts. An earlier old-head catalog-registration timeout passed on unchanged serial retry; all final integrated cases passed.
- Documentation: No findings in the actual independent Starlight/packaged prose review. The prior successful documentation assessment is retained. A supplemental tool-less classifier attempt failed; no new classifier success is claimed.
- Performance: The additional in-memory interpreted-reference lookup was accepted. Unmeasured latency estimates were not adopted; speculative indexing is not required work for this repair.
- Review carry: Specialists reviewed repair
04e7a0fb86; strict integration preserved those production bytes. The CEO reviewed integrated966132942bwith exact-head functional evidence. Original failures, historical mutation executions, and supplemental proof remain separately preserved.
Generated by autopilot-pr-merge-worker. This comment is AI-generated and may contain errors.
Preserve landed 3156/3157 changes and reviewed hook-package work; resolve only additive Unreleased entries. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Repair the human-authorized 3156/3282 versioned marketplace hook refresh interaction without changing parser, staging, receipt or trust semantics. Preserve installed source and full lifecycle state on rejected replacements. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Preserve reviewed supplemental hook selection repair while integrating published main cb06ec2 for final exact-tree validation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a


Description
fix(hooks): admit and stage existing hook-only Git-path packages
TL;DR: Restore resolver admission for the already-supported
hooks/*.jsonand.apm/hooks/*.jsonpackage layouts without adding a fake manifest or skill. Validate descriptors before activation, and route hook updates through existing replacement staging so a rejected candidate cannot delete the installed source needed by restore and native references.Important
The installed-CLI lifecycle passes for both layouts. This exercises the real Python entrypoint, Git transport, lockfiles, native configuration output and resources; it does not execute hook payloads, launch native harnesses, or test a frozen PyInstaller binary.
Problem (WHY)
Downloaded package has no apm.yml or SKILL.md.These are executed failures within the approved scope, not proposed format changes. The method follows Agent Skills' "Effective skills are grounded in real expertise.": the failing installed lifecycle, including source-cache preservation, is retained as regression evidence.
Approach (WHAT)
HOOK_PACKAGE.HookIntegrator.find_hook_filesandparse_hook_sourcebefore producing package metadata.Implementation (HOW)
src/apm_cli/deps/apm_resolver.pysrc/apm_cli/models/validation.pysrc/apm_cli/drift.pysrc/apm_cli/install/phases/resolve.pyscripts/architecture_linter/checks/marketplace_catalog_and_contract.pytests/unit/deps/test_hook_package_admission.pytests/integration/test_architecture_hook_package_admission.pytests/integration/test_hook_path_package_lifecycle.pydocs/src/content/docs/reference/package-types.mdpackages/apm-guide/.apm/skills/apm-usage/package-authoring.mdCHANGELOG.mdThe shared update decision is the routing repair; the staging journal and hook resource-copy owners are unchanged.
Diagram
Legend: green nodes are the changed routing/validation decisions; invalid candidates stop before live activation and existing consent/target gates still precede deployment.
flowchart LR update["apm update"] --> recheck["should_force_ref_recheck"] fresh["apm install"] --> staged["ResolutionStagingSession candidate"] recheck -->|"Existing HOOK_PACKAGE"| staged staged --> classify["detect_package_type"] classify --> validate["validate_apm_package + parse_hook_source"] validate -->|"Invalid"| discard["Discard candidate; retain installed source, lock and ownership"] validate -->|"Valid HOOK_PACKAGE"| admit["Resolver validated admission"] admit --> activate["Activate staged candidate"] activate --> consent["Existing executable approval and target restrictions"] consent --> deploy["DeployableSourcePlan + HookSourceSelection"] deploy --> native["Write managed hook registrations and resources"] classDef changed fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px class recheck,validate,admit changedTrade-offs
Benefits
apm.ymlorSKILL.md.Issue and approved scope
Issue: #2980
Human scope-approval comment: #2980 (comment)
Completes the accepted admission/lifecycle slice. Current human implementation authorization was supplied by the parent scheduler; review and merge remain with the parent/maintainer. No new package formats, permission grants, native-runtime expansion, resolver redesign or workflow changes.
Type of change
Testing
Validation evidence
Base
4d35e74f1801b1ed2424569f55c927da0d18eccdis merged into code-tested head051ac1d8dbdc666bc936614d76a8f555031d46f0. Final head78b9af3dd9affc687650af0464d56be914cd39b6adds only the numbered changelog entry; the complete direct regression and canonical lint were rerun there. These are local results, not a claim that hosted CI is green.Final-head direct lifecycle and owner regression
APM_BINARY_PATH="$PWD/.venv/bin/apm" uv run --extra dev pytest -q tests/integration/test_hook_path_package_lifecycle.py tests/unit/deps/test_hook_package_admission.py tests/integration/test_architecture_hook_package_admission.py --tb=shortExact merged-head test command and result
APM_BINARY_PATH="$PWD/.venv/bin/apm" uv run --extra dev pytest -q tests/integration/test_hook_path_package_lifecycle.py tests/integration/test_hook_nested_metadata_install.py tests/unit/deps/test_hook_package_admission.py tests/integration/test_architecture_hook_package_admission.py tests/unit/integration/test_hook_native_metadata.py tests/unit/deps/test_apm_resolver_staging_paths.py tests/unit/install/test_resolve_resolution_paths.py tests/unit/test_install_update_refs.py tests/test_apm_package_models.py::TestHookPackageValidation --tb=shortCanonical lint mirror: all checks passed on the final head
All commands exited 0. The 2100-line guard also passed. YAML I/O and raw
str(relative_to)guards passed using the same regex patterns via Python on macOS, where GNUgrep -Pand shellrgwere unavailable.Broader pre-merge evidence, mutation kills and disclosed baseline failures
uv run --extra dev pytest -q tests/unit/deps/test_hook_package_admission.py tests/test_apm_resolver.py tests/unit/deps/test_apm_resolver_staging_paths.py tests/unit/install/test_resolve_resolution_paths.py tests/unit/test_install_update_refs.py tests/unit/install/test_branch_ref_drift.py tests/unit/install/test_drift.py tests/test_apm_package_models.py::TestHookPackageValidation tests/integration/test_architecture_hook_package_admission.py tests/integration/test_architecture_owner_rule_mutations.py --tb=shortpassed: 654 passed, 9 subtests passed.uv run --extra dev pytest -p no:cacheprovider -q tests/quality --tb=shortpassed: 64 passed.uv run --frozen python scripts/check_test_assertions.pyanduv run --frozen python scripts/check_exact_test_duplicates.pyboth passed.uv run --extra dev pytest -q tests/unit/deps/test_hook_package_admission.py -k uses_validated_metadata --tb=shortfailed 2/2, reproducing the missing-manifest error.uv run --extra dev pytest -q tests/unit/deps/test_hook_package_admission.py -k malformed --tb=shortfailed 6, passed 2; JSON decoding still rejected syntactically malformed input.APM_BINARY_PATH="$PWD/.venv/bin/apm" uv run --extra dev pytest -q 'tests/integration/test_hook_path_package_lifecycle.py::test_hook_path_install_restore_update_remove[hooks]' --tb=shortfailed atinstalled.exists()after malformed replacement. All mutations were restored.uv run --extra dev pytest -q tests/test_apm_package_models.py --tb=shortreported 3 failed, 163 passed. The same three failures reproduce on a clean4d35e74f18worktree usingpython -m pytest -q tests/test_apm_package_models.py -k 'test_parse_with_reference_and_alias_shorthand_not_parsed or test_csv_string_in_apm_yml_parses_like_cli or test_yaml_list_target_still_parses' --tb=short: 3 failed, 163 deselected. They concern alias shorthand and legacyvscodenormalization, not hook admission.mmdc -i hook-admission.mmd -o hook-admission.svg --quietpassed for the exact diagram above.Scenario Evidence
tests/integration/test_hook_path_package_lifecycle.py::test_hook_path_install_restore_update_remove(regression-trap for #2980)tests/unit/deps/test_hook_package_admission.py::test_malformed_hook_candidate_never_activatesHow to test
uv run --extra dev apm --versionto materialize the installed Python entrypoint.APM_BINARY_PATH="$PWD/.venv/bin/apm" uv run --extra dev pytest -q tests/integration/test_hook_path_package_lifecycle.py; both layouts must pass.uv run --extra dev pytest -q tests/unit/deps/test_hook_package_admission.py tests/integration/test_architecture_hook_package_admission.pyandbash scripts/lint-architecture-boundaries.sh; behavioral and static owner guards must pass.Spec conformance (OpenAPM v0.1)
If this PR changes behaviour that an OpenAPM v0.1
req-XXXcovers,confirm the three-step ritual in the
development guide:
docs/src/content/docs/specs/openapm-v0.1.mdupdated(new/changed
<a id="req-XXX"></a>anchor + prose + Appendix Crow).
docs/src/content/docs/specs/manifests/openapm-v0.1.requirements.ymlupdated.
@pytest.mark.req("req-XXX")test undertests/spec_conformance/added or extended.CONFORMANCE.{md,json}regenerated viauv run --extra dev python -m tests.spec_conformance.gen_statementand committed.
This repairs admission of existing implementation-supported manifestless layouts; it does not revise normative OpenAPM manifest, lockfile, target or hook semantics.
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com