Skip to content

Commit 1b33db4

Browse files
docs(spec): cite Cursor-native hook fail-closed validation and Claude-import coexistence as req-tg-016/017
Adds Section 8.5.9 to the OpenAPM v0.1 spec documenting the already-shipped Cursor-native hook installation behavior from this PR: fail-closed conversion validation (req-tg-016) and Claude-import-coexistence rejection (req-tg-017, both install orders). Narrowly bound to the accepted Cursor-native(+Claude-import) capability only, not a universal target-native obligation. Includes vendor-grounded editorial note (cursor.com/docs/hooks, cursor.com/docs/reference/third-party-hooks) distinguishing APM's own conservative conversion/safety policy from vendor-documented defaults (vendor default is merge-not-reject; vendor top-level shape is not documented as closed). Updates Section 8.7 and 11.3.2 enumerations, Appendix C (2 new rows, total 127 statements / 122 MUST), the requirements manifest, the 0.1.44 (proposed) revision-history row, regenerated CONFORMANCE artifacts, and new drift-sentinel conformance tests (tests/spec_conformance/test_cursor_hook_reqs.py) citing the already-existing, already-passing behavioral integration tests. Folds 4 round-1 findings from the real apm-spec-guardian 4-persona panel (spec-swagger-editor, spec-oci-editor, spec-pkgmgr-editor, spec-tag-architect; synthesizer ship_decision=fold_and_ship, shocked_meter_avg=8.0, 0 blockers across all 4 panels): - req-tg-017: defines the "observable overlap" predicate normatively (non-empty hook-event-identifier intersection after alias normalization), closing a 4/4-panel-convergent second-implementer reproducibility gap. - req-tg-016: adds a SHOULD-level sentence requiring implementations to document/expose their accepted source-format vocabulary, so conformance claims are independently verifiable. - Editorial note: drops the stale "eight" alias count (staleness magnet in informative text). - 0.1.44 revision row: adds a one-line clarification that revision label 0.1.43 and requirement id req-tg-015 are reserved by a concurrent sibling unit (PR #3150) on its own branch, not an unintentional gap. Deferred to v0.1.1 (not folded here, too heavy for a surgical mechanical fold): a machine-readable accepted-vocabulary artifact, and a stale- partial-artifact disposition clause for req-tg-016. Rejected: the stale cursor_preflight_done cache-bypass surface (out of scope, requires a src/apm_cli/** change this unit does not authorize) and the full vocabulary-artifact proposal (superseded by the lighter SHOULD-sentence folded above). No src/apm_cli/** changes. No spec waiver. Statement count unchanged at 127 (122 MUST, 5 SHOULD) -- this fold is prose-only, no new anchors. tests/spec_conformance/ re-verified: 293 passed, 2 skipped (orphan 4-way invariant intact). Closes the Spec conformance gate gap for PR #3149 / issue #3129. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent ae9aae3 commit 1b33db4

6 files changed

Lines changed: 161 additions & 6 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
### Fixed
1515

1616
- Codex agent conversion preserves native `model` and `model_reasoning_effort` settings and warns about dropped metadata instead of silently losing it. (#3150)
17+
### Added
18+
19+
- OpenAPM v0.1 spec: documented the already-shipped Cursor-native hook installation fail-closed conversion validation and Claude-import-coexistence rejection as [req-tg-016] and [req-tg-017] (Section 8.5.9). (#3149)
1720

1821
## [0.33.0] - 2026-10-02
1922

‎CONFORMANCE.json‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,12 +1555,34 @@
15551555
"tests": [
15561556
"tests/spec_conformance/test_manifest_reqs.py::test_codex_native_model_settings_preserved_and_dropped_metadata_bounded"
15571557
]
1558+
},
1559+
{
1560+
"conformance_class": "consumer",
1561+
"id": "req-tg-016",
1562+
"keyword": "MUST",
1563+
"section": "8.5.9",
1564+
"status": "active",
1565+
"test_count": 1,
1566+
"tests": [
1567+
"tests/spec_conformance/test_cursor_hook_reqs.py::test_cursor_native_fail_closed_conversion_clause_persists_in_spec"
1568+
]
1569+
},
1570+
{
1571+
"conformance_class": "consumer",
1572+
"id": "req-tg-017",
1573+
"keyword": "MUST",
1574+
"section": "8.5.9",
1575+
"status": "active",
1576+
"test_count": 1,
1577+
"tests": [
1578+
"tests/spec_conformance/test_cursor_hook_reqs.py::test_cursor_claude_import_overlap_rejection_clause_persists_in_spec"
1579+
]
15581580
}
15591581
],
15601582
"spec_version": "v0.1.1",
15611583
"summary_by_class": {
15621584
"consumer": {
1563-
"active": 92,
1585+
"active": 94,
15641586
"skipped": 1,
15651587
"unbound": 0,
15661588
"xfail": 0
@@ -1584,5 +1606,5 @@
15841606
"xfail": 0
15851607
}
15861608
},
1587-
"total_requirements": 126
1609+
"total_requirements": 128
15881610
}

‎CONFORMANCE.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ APM claims the proposed deployed-prompt-audit capability (req-pl-019, req-pl-020
3333
| Class | Active | Skipped | Xfail | Unbound |
3434
|-------|-------:|--------:|------:|--------:|
3535
| Producer | 12 | 0 | 0 | 0 |
36-
| Consumer | 92 | 1 | 0 | 0 |
36+
| Consumer | 94 | 1 | 0 | 0 |
3737
| Registry | 1 | 0 | 0 | 0 |
3838
| Governance | 20 | 0 | 0 | 0 |
3939

@@ -167,6 +167,8 @@ APM claims the proposed deployed-prompt-audit capability (req-pl-019, req-pl-020
167167
| [req-tg-013](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-013) | MUST | 8.5.7 | consumer | active | 7 | - |
168168
| [req-tg-014](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-014) | MUST | 8.5.8 | consumer | active | 1 | - |
169169
| [req-tg-015](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-015) | MUST | 8.5.1 | consumer | active | 1 | - |
170+
| [req-tg-016](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-016) | MUST | 8.5.9 | consumer | active | 1 | - |
171+
| [req-tg-017](docs/src/content/docs/specs/openapm-v0.1.md#req-tg-017) | MUST | 8.5.9 | consumer | active | 1 | - |
170172

171173
## Waivers
172174

‎docs/public/specs/manifests/openapm-v0.1.requirements.yml‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,16 @@ requirements:
465465
section: "8.5.1"
466466
conformance_class: consumer
467467
notes: "a conforming consumer providing the Codex-native agent conversion capability preserves a source-declared model or model_reasoning_effort string value in its native top-level placement, leaves either field absent when absent from the source, diagnoses a non-string source value by field name without including that value, and emits a diagnostic bounded on dropped-field count (at least one shown when any are dropped), per-field name length, and ASCII-sanitized field-name display (never the value) for any other dropped, non-capability-restriction frontmatter field"
468+
- id: req-tg-016
469+
keyword: MUST
470+
section: "8.5.9"
471+
conformance_class: consumer
472+
notes: "a Cursor-native hook installation implementation fails closed (no partial write) when converting a source-declared hook configuration whose event, top-level key, or handler field falls outside the accepted Cursor-native vocabulary, emitting an actionable diagnostic naming the unrecognized value(s)"
473+
- id: req-tg-017
474+
keyword: MUST
475+
section: "8.5.9"
476+
conformance_class: consumer
477+
notes: "a Cursor-native hook installation implementation detects, before writing, an observable overlap with an in-effect Claude-settings hook import for either install order, and rejects the write with an actionable diagnostic rather than merging, redirecting, or broadening accepted input, without altering the Claude import's own settings"
468478
- id: req-pr-006
469479
keyword: MUST
470480
section: "8.1"

‎docs/src/content/docs/specs/openapm-v0.1.md‎

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ between the companion corpus and the implementation.
136136

137137
### 1.3 Document conventions
138138

139-
- OpenAPM v0.1 carries **126 normative statements (121 MUST, 5 SHOULD)** indexed in
139+
- OpenAPM v0.1 carries **128 normative statements (123 MUST, 5 SHOULD)** indexed in
140140
[Appendix C](#appendix-c-index-of-normative-statements).
141141
- All on-disk files defined by this specification are **YAML 1.2**
142142
parsed under the safe subset defined in
@@ -2977,6 +2977,58 @@ manifest restriction, it MUST serialize only the supported subset using target
29772977
identifiers whose replay selects the same runtimes; it MUST NOT persist an
29782978
unsupported member or remap one to a different supported runtime.
29792979

2980+
#### 8.5.9 Cursor-native hook installation: fail-closed validation and Claude-import coexistence
2981+
2982+
<a id="req-tg-016"></a>
2983+
**[req-tg-016]** A conforming consumer implementation that provides Cursor-native
2984+
hook installation MUST fail closed when converting a source-declared hook
2985+
configuration into that Cursor-native format: if any source-declared event,
2986+
top-level configuration key, or handler field falls outside the vocabulary of
2987+
events, top-level keys, and handler fields the implementation accepts for
2988+
Cursor-native hooks, the implementation MUST NOT write the Cursor-native hook
2989+
artifact (zero bytes, no partial file) and MUST emit an actionable diagnostic
2990+
naming the unrecognized value(s). Implementations SHOULD document or
2991+
programmatically expose the set of accepted source-format identifiers so that
2992+
conformance claims are independently verifiable.
2993+
2994+
<a id="req-tg-017"></a>
2995+
**[req-tg-017]** A conforming consumer implementation that provides Cursor-native
2996+
hook installation, when the project or user scope also has a Claude-settings
2997+
hook import in effect for the same scope, MUST detect before writing whether
2998+
installing or retaining the Cursor-native hook configuration would create or
2999+
extend an observable overlap with that Claude-imported hook configuration, for
3000+
both install orders (Claude-import-first and Cursor-native-first). On a
3001+
detected overlap the implementation MUST reject the write with an actionable
3002+
diagnostic rather than merge, redirect, or broaden either side's accepted
3003+
input, and MUST NOT alter the Claude import's own settings. For the purposes
3004+
of this requirement, "observable overlap" is defined as a non-empty
3005+
intersection between the set of hook-event identifiers claimed by the
3006+
Cursor-native configuration and those claimed by the in-effect Claude-settings
3007+
import, evaluated after alias normalization.
3008+
3009+
> **Editorial note.** The event vocabulary, the Claude-to-Cursor event
3010+
> aliases, and the two-key (`version`, `hooks`) top-level shape this
3011+
> implementation validates are drawn from Cursor's own hook reference and
3012+
> third-party-hook reference as published at
3013+
> `https://cursor.com/docs/hooks` and
3014+
> `https://cursor.com/docs/reference/third-party-hooks` (fetched 2026-10-03).
3015+
> As with [req-tg-009]'s target-native capability encodings, these concrete
3016+
> vendor-specific vocabularies are intentionally kept out of the normative
3017+
> text above; a future harness implementing the same Cursor-native capability
3018+
> may accept a different, equally fail-closed vocabulary as the vendor
3019+
> surface evolves. The vendor reference also documents a `prompt` handler
3020+
> kind alongside `command`; this note does not assert that the accepted
3021+
> vocabulary covers every hook feature Cursor documents, only that whatever
3022+
> vocabulary an implementation does accept is enforced fail-closed. The
3023+
> rejection in [req-tg-017] is an APM consumer-side safety policy, not a
3024+
> reflection of Cursor's own documented default: the vendor's third-party-hook
3025+
> reference states that when hooks exist in multiple locations "All matching
3026+
> hooks from every source run" (merge, not reject). Likewise, the unknown-key
3027+
> rejection in [req-tg-016] is this implementation's own conservative
3028+
> conversion policy; the cited vendor example shows only `version` and
3029+
> `hooks` as top-level keys but does not itself document the top-level shape
3030+
> as closed to extension.
3031+
29803032
### 8.6 Per-target primitive support (informational)
29813033

29823034
The matrix of which primitive types each target supports is
@@ -2995,7 +3047,9 @@ without a spec revision. The current matrix is in the companion
29953047
[req-tg-010](#req-tg-010), [req-tg-011](#req-tg-011),
29963048
[req-tg-012](#req-tg-012), [req-tg-013](#req-tg-013),
29973049
[req-tg-014](#req-tg-014), [req-tg-015](#req-tg-015),
2998-
[req-pr-006](#req-pr-006), [req-pr-007](#req-pr-007).
3050+
[req-pr-006](#req-pr-006),
3051+
[req-pr-007](#req-pr-007), [req-tg-016](#req-tg-016),
3052+
[req-tg-017](#req-tg-017).
29993053

30003054
---
30013055

@@ -3642,6 +3696,7 @@ conformance statement identifying:
36423696
[req-tg-010](#req-tg-010), [req-tg-011](#req-tg-011),
36433697
[req-tg-012](#req-tg-012), [req-tg-013](#req-tg-013),
36443698
[req-tg-014](#req-tg-014), [req-tg-015](#req-tg-015),
3699+
[req-tg-016](#req-tg-016), [req-tg-017](#req-tg-017),
36453700
[req-sc-001](#req-sc-001),
36463701
[req-sc-002](#req-sc-002), [req-sc-003](#req-sc-003),
36473702
[req-sc-004](#req-sc-004), [req-sc-005](#req-sc-005),
@@ -4105,6 +4160,8 @@ renumbering of conformance classes.
41054160
| [req-tg-013](#req-tg-013) | MUST | 8.5.7 | consumer |
41064161
| [req-tg-014](#req-tg-014) | MUST | 8.5.8 | consumer |
41074162
| [req-tg-015](#req-tg-015) | MUST | 8.5.1 | consumer |
4163+
| [req-tg-016](#req-tg-016) | MUST | 8.5.9 | consumer |
4164+
| [req-tg-017](#req-tg-017) | MUST | 8.5.9 | consumer |
41084165
| [req-sc-001](#req-sc-001) | MUST | 10.4 | consumer |
41094166
| [req-sc-002](#req-sc-002) | MUST | 10.9 | consumer |
41104167
| [req-sc-003](#req-sc-003) | MUST | 10.3 | consumer |
@@ -4124,7 +4181,7 @@ renumbering of conformance classes.
41244181
| [req-cf-001](#req-cf-001) | MUST | 12.5 | consumer |
41254182
| [req-cf-002](#req-cf-002) | MUST | 12.3 | consumer |
41264183

4127-
**Total normative statements: 126** (121 MUST, 5 SHOULD).
4184+
**Total normative statements: 128** (123 MUST, 5 SHOULD).
41284185

41294186
---
41304187

@@ -4177,6 +4234,7 @@ renumbering of conformance classes.
41774234
| 0.1.41 | 2026-09-09 | Alias containment and lock-replay contract for PR #2901. Added [req-mf-025] (Section 4.3.2, consumer MUST), the optional lock-entry `alias` field, and conformance coverage. Under Section 9.2 this is an additive optional field and a defensive definition of previously unspecified alias behavior, not behavior-neutral errata: unsafe or reserved aliases can newly fail; valid dotted aliases remain accepted; surrounding whitespace is canonicalized; recorded aliases determine replay placement; absent aliases retain the unaliased layout. Source identity and permitted local source paths are unchanged. Older readers preserving the unknown field do not thereby implement placement support. Selects distinct 0.1.41 schema publication identities without changing published v0.1 URLs or bytes; Section 9.3 remains pending (see Appendix A). Sections 1.3, 4.9, 5.2, 10.7, 10.11, 11.3.2, and Appendix C updated. Statement count: 122 -> 123 (118 MUST, 5 SHOULD). |
41784235
| 0.1.42 (proposed) | 2026-09-30 | Optional deployed-prompt audit capability for PR #2962. Added conditional governance [req-pl-019] and [req-pl-020] in Section 6.8.1, both enumerations, Appendix C, requirements manifest and behavioral conformance coverage. Under Section 9.2 this is a new opt-in conformance capability, not behavior-neutral errata or a reinterpretation of an existing obligation: implementations not claiming it acquire no new required feature. APM claims it; newly discovered prompt findings and incomplete native coverage can newly fail default and CI audits, command-only content remains non-failing, and protected remediation is refused before writes. No schema, lockfile version, existing mandatory feature, drift-policy or ownership rule changes. Section 9.3 reviewer approvals and public comment period remain pending; this proposal is not evidence of adoption. Statement count: 123 -> 125 (120 MUST, 5 SHOULD). |
41794236
| 0.1.43 (proposed) | 2026-10-03 | Codex-native agent model preservation and bounded dropped-metadata diagnostic for PR #3150 (closes #3126). Added [req-tg-015] (Section 8.5.1, consumer MUST): a conforming consumer providing the Codex-native agent conversion capability MUST preserve a source-declared `model` or `model_reasoning_effort` string value in its native top-level placement, MUST leave either field absent when absent from the source, MUST diagnose a non-string value by field name without including that value, and MUST emit a diagnostic bounded on both dropped-field count and per-field name length (never the value) for any other dropped, non-capability-restriction frontmatter field. Under Section 9.2 this is a new opt-in conformance capability scoped to consumers providing the Codex-native agent conversion capability, not behavior-neutral errata: it does not require or imply preservation of any other `config.toml`-native key and does not define behavior for any other conversion target. Implementations not providing Codex-native agent conversion acquire no new required feature. No schema, lockfile version, existing mandatory feature, drift-policy or ownership rule changes. Section 9.3 reviewer approvals and public comment period remain pending; this proposal is not evidence of adoption. Section 8.7, Section 11.3.2, and Appendix C updated. Statement count: 125 -> 126 (121 MUST, 5 SHOULD). |
4237+
| 0.1.44 (proposed) | 2026-10-03 | Spec-citation fold for already-accepted Cursor-native hook installation (PR #3149, closes issue #3129's conformance gap). Added [req-tg-016] (Section 8.5.9, consumer MUST): fail-closed validation of out-of-vocabulary events, top-level keys, and handler fields when converting into the Cursor-native hook format. Added [req-tg-017] (Section 8.5.9, consumer MUST): pre-write detection and rejection of a Cursor-native-plus-Claude-import hook overlap, for both install orders, without altering the Claude import's own settings. Both anchors are bound specifically to the already-accepted Cursor-native (and Cursor-native-plus-Claude-import) capability, not a universal target-native or cross-target obligation. Section 8.7, Section 11.3.2, and Appendix C updated. Under Section 9.2 this is a defensive citation of previously unspecified fail-closed/coexistence behavior already shipped and tested in PR #3149, not a new mandatory feature for implementations that do not provide this capability. Section 9.3 reviewer approvals and public comment period remain pending; this proposal is not evidence of adoption. Statement count: 125 -> 127 (122 MUST, 5 SHOULD). Revision label `0.1.43` and requirement id [req-tg-015] are reserved by a concurrent sibling unit on a separate branch and are not assigned here. |
41804238

41814239
Errata (none at publication).
41824240

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""Cursor-native hook installation conformance -- sec.8.5.9.
2+
3+
req-tg-016 and req-tg-017 are the spec-citation fold for PR #3149's
4+
already-shipped Cursor-native hook capability: fail-closed conversion
5+
validation and Claude-import-coexistence rejection. These are
6+
silent-deletion detectors that pin the normative phrasing in place;
7+
the actual behavioral proof is the already-existing, already-passing
8+
integration suite named in the editorial note below (not duplicated
9+
here), consistent with this directory's drift-sentinel pattern for
10+
requirements backed by a real shipped implementation rather than a
11+
schema or fixture.
12+
"""
13+
14+
from __future__ import annotations
15+
16+
import pytest
17+
18+
from tests.spec_conformance._helpers import assert_spec_contains
19+
20+
21+
@pytest.mark.req("req-tg-016")
22+
def test_cursor_native_fail_closed_conversion_clause_persists_in_spec() -> None:
23+
"""Silent-deletion detector for the req-tg-016 normative clause.
24+
25+
Behavioral proof (not duplicated here):
26+
tests/unit/integration/test_cursor_hook_native_contract.py::
27+
test_unrepresentable_hooks_fail_without_native_or_script_writes,
28+
::test_cursor_install_emits_native_events_and_flat_handlers.
29+
"""
30+
assert_spec_contains(
31+
"MUST fail closed when converting a source-declared hook\n"
32+
"configuration into that Cursor-native format",
33+
"the implementation MUST NOT write the Cursor-native hook\n"
34+
"artifact (zero bytes, no partial file)",
35+
"MUST emit an actionable diagnostic\nnaming the unrecognized value(s)",
36+
)
37+
38+
39+
@pytest.mark.req("req-tg-017")
40+
def test_cursor_claude_import_overlap_rejection_clause_persists_in_spec() -> None:
41+
"""Silent-deletion detector for the req-tg-017 normative clause.
42+
43+
Behavioral proof (not duplicated here):
44+
tests/unit/integration/test_cursor_hook_native_contract.py::
45+
test_import_overlap_refused_in_either_install_order,
46+
::test_import_locations_preserved_and_overlap_rejected,
47+
::test_unapproved_hooks_do_not_enter_cursor_preflight;
48+
tests/integration/test_package_target_hook_routing_e2e.py::
49+
test_package_target_transition_repairs_cursor_and_uninstall_preserves_user_hook,
50+
::test_failed_restricted_update_preserves_existing_hook_state;
51+
tests/integration/test_cursor_hook_lifecycle.py::
52+
test_cursor_installed_cli_contract.
53+
"""
54+
assert_spec_contains(
55+
"for\nboth install orders (Claude-import-first and Cursor-native-first)",
56+
"MUST reject the write with an actionable\n"
57+
"diagnostic rather than merge, redirect, or broaden either side's accepted\n"
58+
"input",
59+
"MUST NOT alter the Claude import's own settings",
60+
)

0 commit comments

Comments
 (0)