Skip to content

fix(hooks): admit and stage hook-only Git-path packages - #3282

Merged
Daniel Meppiel (danielmeppiel) merged 12 commits into
mainfrom
danielmeppiel-hook-path-packages
Oct 19, 2026
Merged

Daniel Meppiel (danielmeppiel) merged 12 commits into
mainfrom
danielmeppiel-hook-path-packages

Conversation

@danielmeppiel

@danielmeppiel Daniel Meppiel (danielmeppiel) commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Description

fix(hooks): admit and stage existing hook-only Git-path packages

TL;DR: Restore resolver admission for the already-supported hooks/*.json and .apm/hooks/*.json package 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)

  • Canonical validation recognized hook packages, but resolver admission fell through to Downloaded package has no apm.yml or SKILL.md.
  • Hook validation constructed metadata without checking the neutral descriptor grammar.
  • Literal-branch updates could bypass staged resolution and delete the live package cache when replacement validation failed, even while preserving the lock and deployed hooks.

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)

  • Extend the resolver's existing canonical package-type/validation route to HOOK_PACKAGE.
  • Reuse HookIntegrator.find_hook_files and parse_hook_source before producing package metadata.
  • Extend the shared recheck decision so existing hook packages use the existing staged replacement owner on update.
  • Keep executable approval, target restriction, integrity, resource selection and native hook compilation in their existing owners.

Implementation (HOW)

File Bounded change
src/apm_cli/deps/apm_resolver.py Admit validated hook metadata and supply the installed path to shared recheck policy.
src/apm_cli/models/validation.py Validate discovered descriptors before metadata construction; preserve source provenance.
src/apm_cli/drift.py Re-enter staged admission for updates of existing canonical hook packages.
src/apm_cli/install/phases/resolve.py Pass the same installed path to the shared callback decision.
scripts/architecture_linter/checks/marketplace_catalog_and_contract.py Extend the registered package-format guard against bypassing classification, discovery and grammar owners.
tests/unit/deps/test_hook_package_admission.py Cover admission, rejected candidates, activation ordering and update rechecks for both layouts.
tests/integration/test_architecture_hook_package_admission.py Six source mutations prove the registered static guard rejects owner bypasses.
tests/integration/test_hook_path_package_lifecycle.py Real installed-CLI lifecycle, invalid replacements, approval, target restrictions and non-hook controls.
docs/src/content/docs/reference/package-types.md Correct the supported layouts, Git-path selection, native output and lifecycle guarantees.
packages/apm-guide/.apm/skills/apm-usage/package-authoring.md Ship the same concise author guidance.
CHANGELOG.md Add one numbered Unreleased fix entry, preserving the released v0.34.0 block.

The 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 changed
Loading

Trade-offs

  • Reuse the current staging owner for hook updates rather than redesigning all package-cache acquisition.
  • Validate the existing neutral grammar rather than adding recursive discovery or a second format classifier. Previously accepted malformed hook-only descriptors now fail explicitly.
  • Native configuration and referenced resources are asserted without executing package hooks during installation or claiming live harness compatibility.
  • Leave three independently reproduced baseline model-test failures outside this issue's bounded scope.

Benefits

  1. Both existing hook layouts install without generated apm.yml or SKILL.md.
  2. Rejected replacements preserve installed source bytes as well as manifest, lock, deployed files and ownership.
  3. Locked restore, update, approval, target narrowing and removal are covered through the installed CLI.

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

  • Bug fix
  • New feature
  • Documentation
  • Maintenance / refactor

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality (if applicable)

Validation evidence

Base 4d35e74f1801b1ed2424569f55c927da0d18eccd is merged into code-tested head 051ac1d8dbdc666bc936614d76a8f555031d46f0. Final head 78b9af3dd9affc687650af0464d56be914cd39b6 adds 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=short
20 passed in 38.83s
Exact 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=short
92 passed in 149.03s (0:02:29)
Canonical lint mirror: all checks passed on the final head
uv run --frozen --extra dev ruff check src/ tests/ scripts/lint_architecture_boundaries.py scripts/architecture_linter/ scripts/windows_native_symlink_probe_entry.py
uv run --frozen --extra dev ruff format --check src/ tests/ scripts/lint_architecture_boundaries.py scripts/architecture_linter/ scripts/windows_native_symlink_probe_entry.py
uv run --frozen --extra dev python -m pylint --disable=all --enable=R0801 --min-similarity-lines=10 --fail-on=R0801 src/apm_cli/ scripts/lint_architecture_boundaries.py scripts/architecture_linter/ scripts/windows_native_symlink_probe_entry.py
bash scripts/lint-auth-signals.sh
bash scripts/lint-architecture-boundaries.sh

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 GNU grep -P and shell rg were unavailable.

All checks passed!
1963 files already formatted
YAML I/O and portable-path guards passed (Python regex equivalent).
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)
[+] auth-signal lint clean
Broader pre-merge evidence, mutation kills and disclosed baseline failures
  • Before the main merge, 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=short passed: 654 passed, 9 subtests passed.
  • uv run --extra dev pytest -p no:cacheprovider -q tests/quality --tb=short passed: 64 passed. uv run --frozen python scripts/check_test_assertions.py and uv run --frozen python scripts/check_exact_test_duplicates.py both passed.
  • Removing hook admission: uv run --extra dev pytest -q tests/unit/deps/test_hook_package_admission.py -k uses_validated_metadata --tb=short failed 2/2, reproducing the missing-manifest error.
  • Bypassing the neutral parser: uv run --extra dev pytest -q tests/unit/deps/test_hook_package_admission.py -k malformed --tb=short failed 6, passed 2; JSON decoding still rejected syntactically malformed input.
  • Disabling hook staged rechecks: 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=short failed at installed.exists() after malformed replacement. All mutations were restored.
  • On the merged head, uv run --extra dev pytest -q tests/test_apm_package_models.py --tb=short reported 3 failed, 163 passed. The same three failures reproduce on a clean 4d35e74f18 worktree using python -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 legacy vscode normalization, not hook admission.
  • mmdc -i hook-admission.mmd -o hook-admission.svg --quiet passed for the exact diagram above.

Scenario Evidence

# Scenario (user promise) Principle(s) Test(s) proving it Type
1 Install either documented hook layout without inventing a manifest or skill. DevX (pragmatic as npm); Vendor-neutral tests/integration/test_hook_path_package_lifecycle.py::test_hook_path_install_restore_update_remove (regression-trap for #2980) e2e
2 Restore the locked revision after upstream advances; update resources and remove only managed package state. Governed by policy; DevX (pragmatic as npm) Same two-layout installed lifecycle test e2e
3 Reject malformed JSON, invalid grammar and arbitrary-directory replacements without losing the previous source, lock or ownership. Secure by default; Governed by policy Same two-layout installed lifecycle test; tests/unit/deps/test_hook_package_admission.py::test_malformed_hook_candidate_never_activates e2e / integration
4 Keep hooks unapproved until explicit approval, respect a Claude-only dependency restriction, and retain valid Claude/Codex resources and registrations. Secure by default; Multi-harness support Same two-layout installed lifecycle test e2e
5 Continue installing a root skill while rejecting arbitrary, recursively nested and malformed hook package controls. DevX (pragmatic as npm); Secure by default Same two-layout installed lifecycle test e2e

How to test

  • Run uv run --extra dev apm --version to materialize the installed Python entrypoint.
  • Run 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.
  • Run uv run --extra dev pytest -q tests/unit/deps/test_hook_package_admission.py tests/integration/test_architecture_hook_package_admission.py and bash 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-XXX covers,
confirm the three-step ritual in the
development guide:

  • Spec edit: docs/src/content/docs/specs/openapm-v0.1.md updated
    (new/changed <a id="req-XXX"></a> anchor + prose + Appendix C
    row).
  • Manifest edit: docs/src/content/docs/specs/manifests/openapm-v0.1.requirements.yml
    updated.
  • Test edit: a @pytest.mark.req("req-XXX") test under
    tests/spec_conformance/ added or extended.
  • CONFORMANCE.{md,json} regenerated via
    uv run --extra dev python -m tests.spec_conformance.gen_statement
    and committed.
  • N/A -- this PR does not change OpenAPM-observable behaviour.

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

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

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.

🟡 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

Comment thread src/apm_cli/drift.py Resolved
Comment thread src/apm_cli/drift.py Resolved
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
@danielmeppiel

Daniel Meppiel (danielmeppiel) commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator Author

APM Review Panel: ship_now

Hook-only Git path packages retain canonical validation, and rejected versioned marketplace refreshes now preserve installed source as well as installation state.

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

Reviewed head: 966132942b, incorporating published main cb06ec21ba.

Compatibility testing after landed #3156 exposed a real failure: malformed structured marketplace hook refresh preserved the manifest/lock/deployment snapshot but deleted the prior installed source. The separately authorized supplement routes early selected keys and late selected identities through the existing interpreted-reference owner. Existing staging, validation, publication, rollback, executable consent, target restrictions, and update/refresh-only receipt eligibility remain intact.

The six-person affected-only delta and CEO synthesis leave no actionable follow-ups. Deterministic integration proof preserves every reviewed PR production file and all incoming version/entrypoint changes. The installed console script binds the combined tree through apm_cli.entrypoint:main.

Authorization and budget: The human expressly authorized this focused supplement after the original immutable 4 outer / 2 Copilot / 3 CI-recovery budget was exhausted. Those counters were not reset. The supplement used one repair pass, one focused panel plus CEO, three distinct restored mutations, one lease-pinned push, and zero additional CI recoveries.

Evidence

  • 159 exact-head affected/integrity tests passed, plus 11 post-restoration selector/architecture checks. Both hook layouts retain exact source bytes, complete lifecycle state, nonempty resource deployment records, and target configuration after malformed replacement.
  • Removing the corrected early interpreted-reference input fails both real installed-source assertions after state equality succeeds. Removing the registered static condition breaks its matching architecture assertion. Bypassing direct interpreted keys fails all three new ref cases. Every mutation was restored.
  • Four detected owners / 14 passing functional mappings; canonical schema and semantic owner verification passed.
  • Full canonical lint passed, including architecture paths, R0801, YAML/path/2100-line guards, and auth/architecture boundaries.
  • All seven final-head technical workflows succeeded: CI, Spec conformance, CodeQL, Docs, NOTICE, CRLF, and required-check aggregation. The queued macOS probe was awaited, not waived.

Panel summary

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 integrated 966132942b with 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
@danielmeppiel
Daniel Meppiel (danielmeppiel) merged commit 8974961 into main Oct 19, 2026
27 checks passed
@danielmeppiel
Daniel Meppiel (danielmeppiel) deleted the danielmeppiel-hook-path-packages branch October 19, 2026 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants