Repository navigation
fix(hooks): render nested hook metadata as plain JSON - #3211
Daniel Meppiel (danielmeppiel) merged 10 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:
|
| Workflow | Observed result |
|---|---|
| Deploy Docs | completed / success |
| CI | completed / success |
| Spec conformance | completed / success |
| Merge Gate | completed / success |
| NOTICE Drift Check | completed / success |
| CodeQL | completed / success |
Docs deployment was skipped for the PR; the build passed. Workflow execution approval was restricted to these reviewed-head standard workflows, byte-identical to trusted main.
Disposition
Current code/test recommendation is ship_now on proven faithful integration; platform admission and any landing remain the parent's independent responsibility. The PR is open and non-draft. No merge, auto-merge, queue, new PR, reviewer request, assignment, original/competing closure, automation or policy change was made.
Push: git push --force-with-lease=fix-hook-metadata-json-rendering:2b794ed5f7a80ef834cc925e20d2ba01b4b440ad https://github.com/abhishekacharya200/apm.git HEAD:fix-hook-metadata-json-rendering (fast-forward history, explicit concurrency lease).
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
Faithfully integrate released main and its registry mutation matrix repair. Preserve the dated release text, original contributor history, and the sole microsoft#3211 hook-rendering implementation with credited microsoft#3199 tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
4d35e74
into
microsoft:main


fix(hooks): preserve nested metadata in native JSON
Description
TL;DR
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 hook consumer suite and full registry mutation matrix passed at exact head
247d486801b73a29a70c0380533f89a43bc10bcb, including main65b3b9f4172feaae5be9118e9a8b672f7a527481.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 tests/integration/test_architecture_owner_rule_mutations.py -q --basetemp=../pr-3211-artifacts/consolidation/restart-65b3/pytest-exact --junitxml=../pr-3211-artifacts/consolidation/restart-65b3/tests-exact.xmlThis includes all 46 retained hook contract/metadata/CLI cases and the full registry matrix, including
test_matrix_covers_every_registry_guard_exactly_onceand theregistry-delegation-build-id-verificationmutation case repaired on main by #3251.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 passed using equivalent Python scans on macOS. The owner-v2 schema and full semantic verifier passed with five exact-head consumer test IDs; no blocked-return shortcut is used.
The installed
.venv/bin/apmindependently passed unapprovedinstall(no native hooks emitted),approve ../hookpkg, approvedinstall, andupdate --yesafter fixture lock/config removal. Claude/Codex JSON and original source bytes were checked; the hook-execution sentinel remained absent. No--no-policyor--forcewas used.Historical mutation proof remains attributed to
bc941e0a4cd55f2b610107fede64d29fb17012bd: replacing_thawwith identity and unregistering_nhc_native_metadata_thawproduced28 failed, 1 passed, 17 deselected in 28.58s. Guards were restored byte-for-byte. The protected implementation, static guard and PR regression files are unchanged in this recovery; no new execution of that historical mutation is claimed.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_sourcePost-release recovery and authorship
Normal merge
247d486801b73a29a70c0380533f89a43bc10bcbretains published parent2b794ed5f7a80ef834cc925e20d2ba01b4b440adand main65b3b9f4172feaae5be9118e9a8b672f7a527481. All eight non-changelog PR files and their semantic diff are byte-identical to the previously reviewed head. Git auto-merged the changelog but misplaced the undelivered fix and restored obsolete duplicate release entries; the correction retains main's dated v0.34 release bytes exactly and places only the identical #3211 entry under Unreleased.Original Abhishek Acharya (@abhishekacharya200) commits and Mohammed Alkindi (@MohammedAlkindi)'s selected-test credit in
c26ea4976b8591673e2067a92f525d0894c28bebremain ancestors. No duplicate thaw implementation, trust bypass or unlanded #3153 change is imported. The genuine prior specialist/CEOship_nowcarries forward on this proven faithful integration and refreshed exact-head evidence, not on a historical badge. No broad panel was rerun.Retained budgets: 4/4 convergence iterations, 2/2 Copilot rounds and 0/3 CI recovery iterations. The two prior Copilot findings remain resolved by the original author. No budget was reset.
Technical CI
Observed standard workflows for exact head
247d486801b73a29a70c0380533f89a43bc10bcb:Docs build passed; deployment is skipped for PRs. Main's former registry-matrix blocker is fixed and the full matrix now passes locally. The PR remains open and non-draft. No merge, auto-merge, queue, assignment, reviewer request, competing-PR closure or policy change was performed. The parent independently verifies and serializes any landing.
How to test
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