pactkit-opencode 3.0.1__tar.gz → 3.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/PKG-INFO +1 -1
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/pyproject.toml +1 -1
- pactkit_opencode-3.0.4/src/pactkit_opencode/__init__.py +20 -0
- pactkit_opencode-3.0.4/src/pactkit_opencode/enforcement.py +59 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/src/pactkit_opencode.egg-info/PKG-INFO +1 -1
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/src/pactkit_opencode.egg-info/SOURCES.txt +2 -0
- pactkit_opencode-3.0.4/tests/test_opencode_enforcement_channel.py +115 -0
- pactkit_opencode-3.0.1/src/pactkit_opencode/__init__.py +0 -8
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/README.md +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/setup.cfg +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/src/pactkit_opencode/deployer.py +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/src/pactkit_opencode.egg-info/dependency_links.txt +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/src/pactkit_opencode.egg-info/entry_points.txt +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/src/pactkit_opencode.egg-info/requires.txt +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/src/pactkit_opencode.egg-info/top_level.txt +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/tests/test_a1_config_recovery.py +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/tests/test_a2_mcp_retired.py +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/tests/test_bug035_opencode_dual_layer.py +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/tests/test_deploy_opencode_parity.py +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/tests/test_gate_plugin_deploy.py +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/tests/test_host_body_snapshot.py +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/tests/test_story069_opencode_format.py +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/tests/test_story070_opencode_compliance.py +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/tests/test_story071_opencode_config_parity.py +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/tests/test_story073_command_model_routing.py +0 -0
- {pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/tests/test_story_work_units.py +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""PactKit adapter for OpenCode IDE.
|
|
2
|
+
|
|
3
|
+
Auto-registers OpenCodeDeployer via entry_points when imported.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pactkit_opencode.deployer import OpenCodeDeployer
|
|
7
|
+
|
|
8
|
+
# The second import is for its side effect: it registers OpenCode's
|
|
9
|
+
# enforcement channel (SPEC-4.0-06 R3), and registration IS Core's
|
|
10
|
+
# availability signal — an adapter that is not installed cannot register, so
|
|
11
|
+
# `format == "all"` never installs a channel for a host that never deployed.
|
|
12
|
+
#
|
|
13
|
+
# This adapter's channel is the one exception to that sentence: Core installs
|
|
14
|
+
# nothing for opencode, because `_deploy_gate_plugin()` installs the plugin
|
|
15
|
+
# itself, globally. Registering still matters — without it Core answers "no
|
|
16
|
+
# channel" for a host that has one, and `gate_coverage` reports the pre-tool
|
|
17
|
+
# gates as unenforced while the plugin is on disk and blocking.
|
|
18
|
+
from pactkit_opencode.enforcement import opencode_channel # noqa: F401
|
|
19
|
+
|
|
20
|
+
__all__ = ["OpenCodeDeployer"]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""OpenCode's enforcement channel, stated by its owner (SPEC-4.0-06 R3).
|
|
2
|
+
|
|
3
|
+
OpenCode's veto surface is a plugin hook (``tool.execute.before``, throwing
|
|
4
|
+
denies the call), not a PreToolUse hook. Unlike Codex's — and unlike the
|
|
5
|
+
classic host's — **Core does not install it**: ``_deploy_gate_plugin()``
|
|
6
|
+
writes it, globally, from this adapter. There is no project-relative config
|
|
7
|
+
file for Core to merge into, and the plugin lives outside the deploy root, so
|
|
8
|
+
Core can neither install nor verify it.
|
|
9
|
+
|
|
10
|
+
So the provider returns None. That is the registry's word for "this host has
|
|
11
|
+
no channel for Core to install": ``_selected_channels`` yields nothing for
|
|
12
|
+
opencode and ``deploy("all")`` skips it, both correct — the plugin is this
|
|
13
|
+
adapter's to deploy and always was.
|
|
14
|
+
|
|
15
|
+
What registering buys is the *other* reader. ``gate_matrix.host_channel_registered``
|
|
16
|
+
asks "did this host register?", and until this module existed the answer was
|
|
17
|
+
no, so ``gate_coverage("opencode")`` reported ``spec_guard`` and
|
|
18
|
+
``tamper_guard`` as not enforced — while the plugin was on disk and blocking.
|
|
19
|
+
Measured 2026-09-22: those two gates flipped from ``not_enforced`` to
|
|
20
|
+
``enforced`` with the registration and nothing else, and no deploy behaviour
|
|
21
|
+
changed.
|
|
22
|
+
|
|
23
|
+
The looseness is the same one Codex already carries, accepted deliberately
|
|
24
|
+
(owner ruling 2026-09-22): registration is evidence that the *adapter* is
|
|
25
|
+
installed, not that the plugin reached disk. A project that installed this
|
|
26
|
+
adapter and never ran ``pactkit init --format opencode`` is reported as
|
|
27
|
+
covered. Making that claim verifiable needs a channel shape for
|
|
28
|
+
adapter-installed plugins; it is not this module's job to fake it with a
|
|
29
|
+
``HookChannel`` whose ``relative_path`` would make Core write a
|
|
30
|
+
project-relative file for a channel that is global by design.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
|
|
37
|
+
from pactkit.commit_gate import HookChannel, register_enforcement_channel
|
|
38
|
+
|
|
39
|
+
#: MUST equal the string `gate_matrix.channels_for("opencode")` advertises, or
|
|
40
|
+
#: the coverage notice names a channel that is not the one installed — the
|
|
41
|
+
#: divergence SPEC-4.0-06 R4 records as `HookChannel`'s standing tension.
|
|
42
|
+
#: There is no `HookChannel` here to carry it (Core installs nothing), so the
|
|
43
|
+
#: agreement is asserted by `test_opencode_enforcement_channel.py` instead of
|
|
44
|
+
#: being structural.
|
|
45
|
+
ADVERTISED_IDENTITY = "tool.execute.before plugin (veto by throwing)"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def opencode_channel(root: Path) -> HookChannel | None:
|
|
49
|
+
"""None: Core installs nothing for this host, and that is the whole point.
|
|
50
|
+
|
|
51
|
+
Returning a `HookChannel` would send Core's installer after a
|
|
52
|
+
project-relative file for a channel that is global by design. A None
|
|
53
|
+
provider is a registered host whose channel Core does not own — distinct
|
|
54
|
+
from a host that never registered at all.
|
|
55
|
+
"""
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
register_enforcement_channel("opencode", opencode_channel)
|
|
@@ -2,6 +2,7 @@ README.md
|
|
|
2
2
|
pyproject.toml
|
|
3
3
|
src/pactkit_opencode/__init__.py
|
|
4
4
|
src/pactkit_opencode/deployer.py
|
|
5
|
+
src/pactkit_opencode/enforcement.py
|
|
5
6
|
src/pactkit_opencode.egg-info/PKG-INFO
|
|
6
7
|
src/pactkit_opencode.egg-info/SOURCES.txt
|
|
7
8
|
src/pactkit_opencode.egg-info/dependency_links.txt
|
|
@@ -14,6 +15,7 @@ tests/test_bug035_opencode_dual_layer.py
|
|
|
14
15
|
tests/test_deploy_opencode_parity.py
|
|
15
16
|
tests/test_gate_plugin_deploy.py
|
|
16
17
|
tests/test_host_body_snapshot.py
|
|
18
|
+
tests/test_opencode_enforcement_channel.py
|
|
17
19
|
tests/test_story069_opencode_format.py
|
|
18
20
|
tests/test_story070_opencode_compliance.py
|
|
19
21
|
tests/test_story071_opencode_config_parity.py
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""OpenCode's enforcement channel is registered by its owner, not by Core.
|
|
2
|
+
|
|
3
|
+
OpenCode's veto surface is a plugin hook (`tool.execute.before`, throwing
|
|
4
|
+
denies the call). `_deploy_gate_plugin()` installs it, globally, from this
|
|
5
|
+
adapter — Core does not install it and cannot verify it, so the provider
|
|
6
|
+
returns None and `_selected_channels` yields nothing.
|
|
7
|
+
|
|
8
|
+
That makes this the one host whose channel is registered but not installable
|
|
9
|
+
by Core, and the distinction is the whole point of the file:
|
|
10
|
+
|
|
11
|
+
* registering is what makes `gate_matrix.host_channel_registered` answer yes,
|
|
12
|
+
which is what makes `gate_coverage("opencode")` credit `spec_guard` and
|
|
13
|
+
`tamper_guard` — measured 2026-09-22, those two flipped from `not_enforced`
|
|
14
|
+
to `enforced` with this registration and nothing else;
|
|
15
|
+
* it must NOT make Core write anything, because the plugin is already this
|
|
16
|
+
adapter's to deploy and is global by design.
|
|
17
|
+
|
|
18
|
+
The looseness carried here is the same one Codex already has, accepted
|
|
19
|
+
deliberately (owner ruling 2026-09-22): registration is evidence that the
|
|
20
|
+
*adapter* is installed, not that the plugin reached disk.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from pactkit import commit_gate, gate_matrix
|
|
26
|
+
from pactkit_opencode.enforcement import ADVERTISED_IDENTITY, opencode_channel
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def gate_coverage_for(fmt: str) -> dict:
|
|
30
|
+
"""The default (measured) path — `has_git=False` so only the host layer moves."""
|
|
31
|
+
from pactkit.gate_matrix import gate_coverage
|
|
32
|
+
|
|
33
|
+
return gate_coverage(fmt, has_git=False)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestRegistration:
|
|
37
|
+
"""The registry, not the deploy summary, is the availability signal."""
|
|
38
|
+
|
|
39
|
+
def test_importing_the_adapter_registers_a_channel(self):
|
|
40
|
+
import pactkit_opencode # noqa: F401 — the import IS the registration
|
|
41
|
+
|
|
42
|
+
assert gate_matrix.host_channel_registered("opencode"), (
|
|
43
|
+
"importing this adapter registered no opencode enforcement channel — "
|
|
44
|
+
"Core will then answer 'no channel' for a host whose plugin is on "
|
|
45
|
+
"disk and blocking"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def test_the_registration_is_what_credits_the_host_gates(self):
|
|
49
|
+
"""The observable consequence, and the reason this module exists.
|
|
50
|
+
|
|
51
|
+
Before it, `gate_coverage("opencode")` reported `spec_guard` and
|
|
52
|
+
`tamper_guard` as not enforced. Both halves are asserted: the default
|
|
53
|
+
(measured) path credits them, and taking the registration away puts
|
|
54
|
+
them back — so the credit is coming from the registry, not from the
|
|
55
|
+
channel list alone.
|
|
56
|
+
"""
|
|
57
|
+
import pactkit_opencode # noqa: F401
|
|
58
|
+
|
|
59
|
+
measured = gate_coverage_for("opencode")
|
|
60
|
+
assert measured["channel_basis"] == "registered"
|
|
61
|
+
for gate in ("spec_guard", "tamper_guard"):
|
|
62
|
+
assert gate in measured["enforced"], (
|
|
63
|
+
f"{gate} is not credited although the adapter registered — this "
|
|
64
|
+
"is the display defect the registration fixes"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
saved = dict(commit_gate._ENFORCEMENT_CHANNELS)
|
|
68
|
+
try:
|
|
69
|
+
commit_gate._ENFORCEMENT_CHANNELS.pop("opencode")
|
|
70
|
+
gap = gate_coverage_for("opencode")
|
|
71
|
+
for gate in ("spec_guard", "tamper_guard"):
|
|
72
|
+
assert gate in gap["not_enforced"], gate
|
|
73
|
+
assert "not registered" in gap["not_enforced"]["tamper_guard"], (
|
|
74
|
+
"the reason must name the missing registration, not blame the "
|
|
75
|
+
"host — opencode does offer a pre-tool surface"
|
|
76
|
+
)
|
|
77
|
+
finally:
|
|
78
|
+
commit_gate._ENFORCEMENT_CHANNELS.clear()
|
|
79
|
+
commit_gate._ENFORCEMENT_CHANNELS.update(saved)
|
|
80
|
+
|
|
81
|
+
def test_core_still_installs_nothing_for_opencode(self, tmp_path):
|
|
82
|
+
"""The negative: registering must not send Core's installer anywhere.
|
|
83
|
+
|
|
84
|
+
The plugin is global and lives outside the deploy root; a HookChannel
|
|
85
|
+
here would make Core write a project-relative file for it.
|
|
86
|
+
"""
|
|
87
|
+
import pactkit_opencode # noqa: F401
|
|
88
|
+
|
|
89
|
+
assert commit_gate._selected_channels(tmp_path, "opencode") == [], (
|
|
90
|
+
"Core selected a channel to install for opencode — it owns none"
|
|
91
|
+
)
|
|
92
|
+
assert opencode_channel(tmp_path) is None
|
|
93
|
+
|
|
94
|
+
def test_all_does_not_pick_opencode_up(self, tmp_path):
|
|
95
|
+
"""`all` traverses the registry, and opencode contributes no channel."""
|
|
96
|
+
import pactkit_opencode # noqa: F401
|
|
97
|
+
|
|
98
|
+
identities = [
|
|
99
|
+
channel.identity
|
|
100
|
+
for channel in commit_gate._selected_channels(tmp_path, "all")
|
|
101
|
+
]
|
|
102
|
+
assert ADVERTISED_IDENTITY not in identities
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TestTheAdvertisedName:
|
|
106
|
+
def test_the_identity_is_the_advertised_one(self):
|
|
107
|
+
"""R4's tension, asserted: `gate_matrix.channels_for` names this
|
|
108
|
+
channel in the coverage notice, and there is no `HookChannel` here to
|
|
109
|
+
carry the string — so nothing structural holds the two together."""
|
|
110
|
+
advertised = gate_matrix.channels_for("opencode", has_git=False)
|
|
111
|
+
assert advertised, "anti-vacuity: opencode advertises no channel at all"
|
|
112
|
+
assert ADVERTISED_IDENTITY in advertised, (
|
|
113
|
+
f"this module names the channel {ADVERTISED_IDENTITY!r} but the "
|
|
114
|
+
f"coverage notice advertises {advertised!r}"
|
|
115
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/src/pactkit_opencode.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/src/pactkit_opencode.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/src/pactkit_opencode.egg-info/requires.txt
RENAMED
|
File without changes
|
{pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/src/pactkit_opencode.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/tests/test_story070_opencode_compliance.py
RENAMED
|
File without changes
|
{pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/tests/test_story071_opencode_config_parity.py
RENAMED
|
File without changes
|
{pactkit_opencode-3.0.1 → pactkit_opencode-3.0.4}/tests/test_story073_command_model_routing.py
RENAMED
|
File without changes
|
|
File without changes
|