Repository navigation
fix(hooks): render nested hook metadata as plain JSON - #3211
Abhishek Acharya (abhishekacharya200) wants to merge 9 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The canonical thawing path lacks its required static architecture guard, and the changelog entry omits the PR reference.
2 open findings
What changed in this PR
Fixes nested hook metadata serialization while preserving immutable hook IR.
Changes:
- Adds recursive metadata thawing at native render boundaries.
- Adds regression coverage across supported renderers.
- Updates documentation and changelog.
| File | Description |
|---|---|
src/apm_cli/hook_contract.py |
Adds recursive metadata thawing. |
src/apm_cli/integration/hook_native_formats.py |
Thaws metadata before native rendering. |
tests/integration/test_architecture_contract_guards.py |
Tests serialization and IR independence. |
docs/src/content/docs/producer/author-primitives/hooks-and-commands.md |
Documents nested metadata preservation. |
CHANGELOG.md |
Records the bug fix. |
🧠 Review effort: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
@microsoft-github-policy-service agree |
hook_contract freezes hook metadata recursively, but the native renderers only copied the top level. Nested mappings such as env stayed MappingProxyType and json.dumps failed during install and update. Add _thaw next to _freeze and use it for handler metadata, binding metadata and raw entries in the nested and flat renderers. The IR stays frozen; rendered output is an independent copy. Fixes microsoft#3167
Extend the neutral-hook-contract architecture rule so native hook renderers must thaw frozen metadata through hook_contract._thaw, with mutation tests that restore each shallow dict(...)/raw_entry copy and expect the rule to fail. Add the PR reference to the changelog entry.
f4422d4 to
832a0c8
Compare
Preserve upstream README link corrections and cleanup diagnostic changes before validating microsoft#3211. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Address the panel coverage follow-up for microsoft#3211 and accepted scope microsoft#3167. Exercise real HookIntegrator writes across Claude, Codex, Gemini and Antigravity, preserving metadata, matcher, command and timeout units. Keep packaged hook guidance aligned. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
APM Review Panel: prior
|
| Workflow | Observed result |
|---|---|
| CodeQL | completed / success |
| Merge Gate | completed / success |
| NOTICE Drift Check | completed / success |
| CI | completed / success |
| Deploy Docs | completed / success |
| Spec conformance | completed / success |
Docs deployment was skipped for this PR; its build passed. Earlier-head CI is retained as history, not substituted for these observed runs. Pending jobs are not treated as passed.
Disposition
The prior ship_now code/test recommendation is retained as history, not a claim that the current PR is ready. The required matrix failure blocks readiness despite the narrower passing evidence. Existing reviewer requests are unchanged; no reviewer was requested or review approved. #3199 remains untouched and open.
Per the release wind-down, #3211 is left open and non-draft. No merge, queue, auto-merge, new PR, readiness automation or protection change was performed. Platform admission remains separate from technical readiness. This recovery ends here without further optional work or a release commitment.
Generated by autopilot-pr-merge-worker. This comment is AI-generated and may contain errors.
Preserve upstream fixes and both existing release-note sections without rewriting the contributor history. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Select nonredundant regression assets from microsoft#3199 commit b36ed0f for the approved microsoft#3167 scope. Keep microsoft#3211 as the sole implementation and its canonical _thaw unchanged. Prove binding/raw/source independence and normal approved local install/update without executing hooks. Co-authored-by: Mohammed Alkindi <alkndymhmd692@gmail.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Faithfully preserve main through f8e59dc without changing the reviewed hook implementation or tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Preserve current main through microsoft#3190 without altering the reviewed canonical hook rendering or consolidated regression assets. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Resolve only overlapping CHANGELOG additions by retaining both entries. Preserve the existing hook JSON repair, contributor history and regression coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a


fix(hooks): preserve nested metadata in native JSON
Description
TL;DR
Warning
Conflict recovery is published, but readiness is blocked by an inherited main registry-matrix defect:
test_matrix_covers_every_registry_guard_exactly_oncelacksregistry-delegation-build-id-verification(1 failed in 0.82s on the exact head). The matrix and architecture registry are unchanged from main. Under the release wind-down, no out-of-scope repair or further work will be started; this PR remains open.Restore lossless native hook JSON during
apm installandapm updatewithout changing the immutable hook IR or supported adapter behavior. #3211 remains the sole implementation; selected, nonredundant regression tests from #3199 are incorporated with author credit.Problem (WHY)
Object of type mappingproxy is not JSON serializableduring hook integration (#3167)._freezerecursively snapshots metadata, but shallowdict(...)copies and raw-entry passthrough left nested mappings frozen.Approach (WHAT)
_thawbeside_freezeinhook_contract.py.Implementation (HOW)
src/apm_cli/hook_contract.pysrc/apm_cli/integration/hook_native_formats.pyscripts/architecture_linter/checks/mutation_hook_contract.pytests/integration/test_architecture_contract_guards.pytests/unit/integration/test_hook_native_metadata.pytests/integration/test_hook_nested_metadata_install.pydocs/src/content/docs/producer/author-primitives/hooks-and-commands.mdpackages/apm-guide/.apm/skills/apm-usage/package-authoring.mdCHANGELOG.mdDiagrams
The dashed stage materializes independent native output; the source and immutable IR remain unchanged.
flowchart LR subgraph Source["Source"] S["Nested hook JSON"] end subgraph Contract["Canonical contract"] I["Immutable HookDocument"] T["hook_contract._thaw"] end subgraph Native["Existing native adapters"] O["Claude / Codex / Gemini / Antigravity JSON"] end S --> I I --> T T --> O classDef new stroke-dasharray: 5 5 class T newTrade-offs
_thawfrom fix(hooks): serialize nested hook metadata in native hook files #3199; combining both full implementations would shadow the canonical import.apm approve, not fix(hooks): serialize nested hook metadata in native hook files #3199's test-only--no-policyflag. No production policy bypass was present in fix(hooks): serialize nested hook metadata in native hook files #3199.update --yes, exercising materialization rather than an unchanged-ref no-op.--yesaccepts the update plan; hook consent remains explicit.Benefits
default=str.Issue and approved scope
Issue: #3167
Human scope-approval comment: #3167 (comment)
This PR completes that bounded scope. The maintainer subsequently selected #3211 as the sole landing vehicle, with useful #3199 tests retained and credited. No action closing or merging #3199 is part of this PR.
Fixes #3167
Note
Original Abhishek Acharya (@abhishekacharya200) commits are preserved. The selected tests from Mohammed Alkindi (@MohammedAlkindi)'s #3199 commit
b36ed0ff2a60ee1974172d4d40bf21760ada7f7aare credited in commitc26ea4976b8591673e2067a92f525d0894c28bebusing the original commit's author identity.Type of change
Testing
The full local repository suite was not run. The focused functional suite and all local lint gates below were run on exact head
2b794ed5f7a80ef834cc925e20d2ba01b4b440ad, including main throughb71cb47cd99e665d285a497ba6d05e810ae42ea4.Validation
uv run --frozen --extra dev pytest tests/integration/test_architecture_contract_guards.py tests/unit/integration/test_hook_native_metadata.py tests/integration/test_hook_nested_metadata_install.py -q --basetemp=../pr-3211-artifacts/consolidation/conflict-b71/pytest-exact --junitxml=../pr-3211-artifacts/consolidation/conflict-b71/tests-exact.xml:Historical mutation proof on
bc941e0a4cd55f2b610107fede64d29fb17012bd(not rerun for this faithful merge): the same files with-k 'metadata or shallow or raw_entries or independent or nested_env', after replacing_thawwith identity and unregistering_nhc_native_metadata_thaw:All 28 intended traps failed; the control passed. Both guards were restored byte-for-byte. The recovery changed neither these regression-trap files nor the thaw/static guard; the fresh 46-case run above passed on the new head. Historical mutation execution is not presented as a new run.
Exact local lint and consumer-path evidence
uv run --frozen --extra dev ruff check src/ tests/ scripts/lint_architecture_boundaries.py scripts/architecture_linter/ scripts/windows_native_symlink_probe_entry.pyuv run --frozen --extra dev ruff format --check src/ tests/ scripts/lint_architecture_boundaries.py scripts/architecture_linter/ scripts/windows_native_symlink_probe_entry.pyuv 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.pybash scripts/lint-auth-signals.shbash scripts/lint-architecture-boundaries.shuv run --frozen python scripts/check_test_assertions.pyuv run --frozen python scripts/check_exact_test_duplicates.pyThe CI-equivalent YAML I/O, 2100-line and portable-relative-path guards also passed, using equivalent Python regex/file scans on macOS.
The installed
.venv/bin/apmconsole script independently passedinstallwith no hook approval (no native hooks emitted),approve ../hookpkg, approvedinstall, andupdate --yeswith fixture lock/config regeneration. Generated Claude/Codex metadata and original source bytes were checked; the hook-execution sentinel remained absent. These are real subprocesses, separate from the in-process CliRunner regression.Scenario Evidence
tests/integration/test_hook_nested_metadata_install.py::test_install_update_write_nested_hook_metadata(regression-trap for #3167)tests/integration/test_architecture_contract_guards.py::test_integrator_writes_nested_hook_metadata_to_native_jsontests/unit/integration/test_hook_native_metadata.pytests/unit/integration/test_hook_native_metadata.py::test_rendered_binding_handler_and_raw_output_are_independent_of_ir_and_sourceConflict recovery
Faithful merge
2b794ed5f7a80ef834cc925e20d2ba01b4b440adhas parentsbc941e0a4cd55f2b610107fede64d29fb17012bdandb71cb47cd99e665d285a497ba6d05e810ae42ea4. The only conflict wasCHANGELOG.md; both #3211 and #3155 entries remain. All nine PR files have identical added/removed lines relative to the new base as the previously reviewed consolidation. Contributor commits, canonical thaw, consent checks and tests are preserved. The prior genuine delta/CEO review is carried forward; no new panel was run and no unlanded #3153 work was imported.Technical CI
Observed standard technical workflows for exact head
2b794ed5f7a80ef834cc925e20d2ba01b4b440adand tested baseb71cb47cd99e665d285a497ba6d05e810ae42ea4:Docs build passed; deployment was skipped for the PR. Owner evidence version 2 includes fresh deterministic detection and five exact-head consumer test IDs. The blocked completion passes schema validation; the verifier's blocked-return shortcut is not presented as ready-state owner certification. The prior run's checks and preview are not used as current-head execution evidence.
The PR remains open and non-draft under the release wind-down. No merge, queue, auto-merge, reviewer request or readiness automation was enabled. Technical readiness does not grant platform admission or a release commitment.
Additional required gate:
uv run --frozen --extra dev pytest tests/integration/test_architecture_owner_rule_mutations.py::test_matrix_covers_every_registry_guard_exactly_once -q --basetemp=../pr-3211-artifacts/consolidation/conflict-b71/pytest-matrix --junitxml=../pr-3211-artifacts/consolidation/conflict-b71/registry-matrix.xmlfailed:1 failed in 0.82s, missingregistry-delegation-build-id-verification. This blocks readiness regardless of narrower passing checks. No baseline fix was attempted.How to test
uv run --frozen --extra dev pytest tests/integration/test_architecture_contract_guards.py tests/unit/integration/test_hook_native_metadata.py tests/integration/test_hook_nested_metadata_install.py -q; expect 46 passing cases.bash scripts/lint-architecture-boundaries.sh; expect exit 0.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 restores existing supported metadata serialization; it introduces no normative schema or requirement.
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com