pactkit-codex 3.0.4__tar.gz → 3.1.0__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_codex-3.0.4/src/pactkit_codex.egg-info → pactkit_codex-3.1.0}/PKG-INFO +2 -2
- {pactkit_codex-3.0.4 → pactkit_codex-3.1.0}/pyproject.toml +2 -2
- pactkit_codex-3.1.0/src/pactkit_codex/declarations.py +141 -0
- {pactkit_codex-3.0.4 → pactkit_codex-3.1.0}/src/pactkit_codex/deployer.py +9 -2
- {pactkit_codex-3.0.4 → pactkit_codex-3.1.0/src/pactkit_codex.egg-info}/PKG-INFO +2 -2
- {pactkit_codex-3.0.4 → pactkit_codex-3.1.0}/src/pactkit_codex.egg-info/SOURCES.txt +4 -1
- {pactkit_codex-3.0.4 → pactkit_codex-3.1.0}/src/pactkit_codex.egg-info/requires.txt +1 -1
- pactkit_codex-3.1.0/tests/test_capability_contract.py +112 -0
- pactkit_codex-3.1.0/tests/test_profile_contract.py +186 -0
- {pactkit_codex-3.0.4 → pactkit_codex-3.1.0}/LICENSE +0 -0
- {pactkit_codex-3.0.4 → pactkit_codex-3.1.0}/README.md +0 -0
- {pactkit_codex-3.0.4 → pactkit_codex-3.1.0}/setup.cfg +0 -0
- {pactkit_codex-3.0.4 → pactkit_codex-3.1.0}/src/pactkit_codex/__init__.py +0 -0
- {pactkit_codex-3.0.4 → pactkit_codex-3.1.0}/src/pactkit_codex/enforcement.py +0 -0
- {pactkit_codex-3.0.4 → pactkit_codex-3.1.0}/src/pactkit_codex.egg-info/dependency_links.txt +0 -0
- {pactkit_codex-3.0.4 → pactkit_codex-3.1.0}/src/pactkit_codex.egg-info/entry_points.txt +0 -0
- {pactkit_codex-3.0.4 → pactkit_codex-3.1.0}/src/pactkit_codex.egg-info/top_level.txt +0 -0
- {pactkit_codex-3.0.4 → pactkit_codex-3.1.0}/tests/test_host_body_snapshot.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pactkit-codex
|
|
3
|
-
Version: 3.0
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: PactKit adapter for OpenAI Codex CLI
|
|
5
5
|
Author: Slim
|
|
6
6
|
License: MIT
|
|
@@ -21,7 +21,7 @@ Classifier: Topic :: Software Development :: Quality Assurance
|
|
|
21
21
|
Requires-Python: >=3.10
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE
|
|
24
|
-
Requires-Dist: pactkit<3.
|
|
24
|
+
Requires-Dist: pactkit<3.2.0,>=3.0.1
|
|
25
25
|
Requires-Dist: tomli>=1.1.0; python_version < "3.11"
|
|
26
26
|
Dynamic: license-file
|
|
27
27
|
|
|
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pactkit-codex"
|
|
7
|
-
version = "3.0
|
|
7
|
+
version = "3.1.0"
|
|
8
8
|
description = "PactKit adapter for OpenAI Codex CLI"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
11
11
|
requires-python = ">=3.10"
|
|
12
12
|
dependencies = [
|
|
13
|
-
"pactkit>=3.0.1,<3.
|
|
13
|
+
"pactkit>=3.0.1,<3.2.0",
|
|
14
14
|
"tomli>=1.1.0; python_version < '3.11'",
|
|
15
15
|
]
|
|
16
16
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Codex's own declarations (SPEC-4.0-22 Step 6).
|
|
2
|
+
|
|
3
|
+
These lived in Core's transitional seed (`_CORE_CAPABILITIES`, `_CORE_SECURITY`,
|
|
4
|
+
`_CORE_PRETOOL_SURFACES`, `_CORE_DEPLOYMENT_CLAIMS`) until Step 6. While they
|
|
5
|
+
did, this adapter could not answer for itself: `import pactkit_codex` read Core's table
|
|
6
|
+
for its own identity, so removing the seed broke the import rather than the
|
|
7
|
+
deployment.
|
|
8
|
+
|
|
9
|
+
Core still owns the **vocabulary** (`capability.CAPABILITY_VOCABULARY`), the
|
|
10
|
+
four levels, and the hole-filling rule in `resolve_capabilities`. What a given
|
|
11
|
+
Host scores on them is the Host's own declaration — that is the split
|
|
12
|
+
SPEC-4.0-22 R1 draws, and this module is the Codex side of it.
|
|
13
|
+
|
|
14
|
+
Every value here was materialised from the seed Core was carrying, so this is a
|
|
15
|
+
move, not a re-measurement. The `Capability.evidence` strings are preserved
|
|
16
|
+
verbatim for the same reason: they are what the seed said, and re-wording them
|
|
17
|
+
here would quietly change what the registry reports.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from pactkit.capability import Capability, CapabilityLevel
|
|
23
|
+
from pactkit.profiles import (
|
|
24
|
+
CLIPolicy,
|
|
25
|
+
DeploymentClaim,
|
|
26
|
+
FormatProfile,
|
|
27
|
+
InputTranslation,
|
|
28
|
+
SecurityDeclaration,
|
|
29
|
+
SecurityFacts,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"CAPABILITIES",
|
|
34
|
+
"DEPLOYMENT_CLAIM",
|
|
35
|
+
"INPUT_TRANSLATION",
|
|
36
|
+
"PRETOOL_SURFACE",
|
|
37
|
+
"PROFILE",
|
|
38
|
+
"SECURITY",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _cap(name: str, level: CapabilityLevel, implemented: bool, evidence: str) -> Capability:
|
|
43
|
+
return Capability(name=name, level=level, implemented=implemented, evidence=evidence)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
#: The Host's profile. Core no longer owns it; this module is the source.
|
|
47
|
+
PROFILE = FormatProfile(
|
|
48
|
+
name='codex',
|
|
49
|
+
display_name='Codex CLI',
|
|
50
|
+
global_config_dir='~/.codex',
|
|
51
|
+
project_config_dir='.codex',
|
|
52
|
+
skills_dir='~/.codex/skills',
|
|
53
|
+
agents_dir='~/.codex/agents',
|
|
54
|
+
commands_dir='~/.codex/prompts',
|
|
55
|
+
rules_dir='~/.codex/rules',
|
|
56
|
+
project_instructions_file='AGENTS.md',
|
|
57
|
+
global_instructions_file='AGENTS.md',
|
|
58
|
+
pactkit_yaml_path='.codex/pactkit.yaml',
|
|
59
|
+
agent_format='md',
|
|
60
|
+
rules_import_style='inline',
|
|
61
|
+
excluded_agent_fields=frozenset({'memory', 'permissionMode', 'skills', 'hooks'}),
|
|
62
|
+
excluded_commands=frozenset(),
|
|
63
|
+
supports_model_routing=False,
|
|
64
|
+
skills_path_var='~/.codex/skills',
|
|
65
|
+
command_deploy_form='skill',
|
|
66
|
+
agent_dialect=None,
|
|
67
|
+
command_dialect=None,
|
|
68
|
+
core_bundled=False,
|
|
69
|
+
cli_policy=CLIPolicy.PREFERRED,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
#: What this Host scores on the capability vocabulary.
|
|
73
|
+
CAPABILITIES: dict[str, Capability] = {
|
|
74
|
+
'completion_hook': _cap('completion_hook', CapabilityLevel.DEGRADED, False,
|
|
75
|
+
"unverified — not measured on this Host; absence of the capability is NOT established (SPEC-4.0-04 R2: "
|
|
76
|
+
"unverified is not unavailable)",
|
|
77
|
+
),
|
|
78
|
+
'git_hook': _cap('git_hook', CapabilityLevel.NATIVE, True, 'shipped (commit_gate pre-commit chain)',
|
|
79
|
+
),
|
|
80
|
+
'global_instruction': _cap('global_instruction', CapabilityLevel.EQUIVALENT, True, 'shipped (~/.codex/AGENTS.md)',
|
|
81
|
+
),
|
|
82
|
+
'native_command': _cap('native_command', CapabilityLevel.DEGRADED, False,
|
|
83
|
+
"unverified — not measured on this Host; absence of the capability is NOT established (SPEC-4.0-04 R2: "
|
|
84
|
+
"unverified is not unavailable)",
|
|
85
|
+
),
|
|
86
|
+
'native_skill': _cap('native_skill', CapabilityLevel.DEGRADED, False,
|
|
87
|
+
"unverified — not measured on this Host; absence of the capability is NOT established (SPEC-4.0-04 R2: "
|
|
88
|
+
"unverified is not unavailable)",
|
|
89
|
+
),
|
|
90
|
+
'persistent_session': _cap('persistent_session', CapabilityLevel.DEGRADED, False,
|
|
91
|
+
"unverified — not measured on this Host; absence of the capability is NOT established (SPEC-4.0-04 R2: "
|
|
92
|
+
"unverified is not unavailable)",
|
|
93
|
+
),
|
|
94
|
+
'post_tool_hook': _cap('post_tool_hook', CapabilityLevel.DEGRADED, False,
|
|
95
|
+
"unverified — not measured on this Host; absence of the capability is NOT established (SPEC-4.0-04 R2: "
|
|
96
|
+
"unverified is not unavailable)",
|
|
97
|
+
),
|
|
98
|
+
'pre_tool_hook': _cap('pre_tool_hook', CapabilityLevel.NATIVE, True,
|
|
99
|
+
'shipped (doctor.check_codex_hook_capability; native hooks engine)',
|
|
100
|
+
),
|
|
101
|
+
'project_instruction': _cap('project_instruction', CapabilityLevel.EQUIVALENT, True,
|
|
102
|
+
'shipped (AGENTS.md at project root)',
|
|
103
|
+
),
|
|
104
|
+
'rule_import': _cap('rule_import', CapabilityLevel.EQUIVALENT, True, 'shipped as AGENTS.md (generators.deployer)',
|
|
105
|
+
),
|
|
106
|
+
'session_resume': _cap('session_resume', CapabilityLevel.DEGRADED, False,
|
|
107
|
+
"unverified — not measured on this Host; absence of the capability is NOT established (SPEC-4.0-04 R2: "
|
|
108
|
+
"unverified is not unavailable)",
|
|
109
|
+
),
|
|
110
|
+
'shell_execution': _cap('shell_execution', CapabilityLevel.NATIVE, True, 'shipped (native shell tool)',
|
|
111
|
+
),
|
|
112
|
+
'structured_tool_result': _cap('structured_tool_result', CapabilityLevel.DEGRADED, False,
|
|
113
|
+
"unverified — not measured on this Host; absence of the capability is NOT established (SPEC-4.0-04 R2: "
|
|
114
|
+
"unverified is not unavailable)",
|
|
115
|
+
),
|
|
116
|
+
'subagent': _cap('subagent', CapabilityLevel.DEGRADED, False,
|
|
117
|
+
"unverified — not measured on this Host; absence of the capability is NOT established (SPEC-4.0-04 R2: "
|
|
118
|
+
"unverified is not unavailable)",
|
|
119
|
+
),
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#: Files a gate must treat specially on this Host (SPEC-4.0-22 R8).
|
|
123
|
+
SECURITY = SecurityDeclaration(
|
|
124
|
+
env_capable=SecurityFacts.declared(['.codex/config.toml']),
|
|
125
|
+
protected=SecurityFacts.declared(['.codex/hooks.json']),
|
|
126
|
+
conditional=SecurityFacts.empty(),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
#: Which mechanism this Host offers for pre-tool interception, when one
|
|
130
|
+
#: has been measured. `None` means unverified — not "cannot".
|
|
131
|
+
PRETOOL_SURFACE = 'native hooks engine'
|
|
132
|
+
|
|
133
|
+
#: How this Host executes and where its verification comes from.
|
|
134
|
+
DEPLOYMENT_CLAIM = DeploymentClaim(
|
|
135
|
+
execution_mode='portable',
|
|
136
|
+
verification_source='native_codex_session',
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
#: Read-side wire-format data, when the Host has one. `None` means it
|
|
140
|
+
#: declares none — the ladder then never asks it to recognise a payload.
|
|
141
|
+
INPUT_TRANSLATION: InputTranslation | None = None
|
|
@@ -46,7 +46,6 @@ from pactkit.generators.deployer import (
|
|
|
46
46
|
_enforce_deploy_integrity,
|
|
47
47
|
)
|
|
48
48
|
from pactkit.generators.spi import render_prompt, render_skill_md
|
|
49
|
-
from pactkit.profiles import get_profile
|
|
50
49
|
from pactkit.prompts.guides import GUIDES_FILES
|
|
51
50
|
from pactkit.prompts.rules import (
|
|
52
51
|
COMMAND_CONDITIONAL_RULES_MAP,
|
|
@@ -57,6 +56,8 @@ from pactkit.prompts.rules import (
|
|
|
57
56
|
)
|
|
58
57
|
from pactkit.utils import atomic_write
|
|
59
58
|
|
|
59
|
+
from pactkit_codex import declarations
|
|
60
|
+
|
|
60
61
|
# All canonical commands deploy. Sprint degrades to sequential execution when
|
|
61
62
|
# Codex does not expose team orchestration primitives.
|
|
62
63
|
CODEX_EXCLUDED_COMMANDS: frozenset[str] = frozenset()
|
|
@@ -111,7 +112,13 @@ class CodexDeployer(DeployerBase):
|
|
|
111
112
|
- config.toml (not JSON/YAML)
|
|
112
113
|
"""
|
|
113
114
|
|
|
114
|
-
|
|
115
|
+
# SPEC-4.0-22 Step 6 — the Host's own declarations (see declarations.py).
|
|
116
|
+
profile = declarations.PROFILE
|
|
117
|
+
capabilities = declarations.CAPABILITIES
|
|
118
|
+
security = declarations.SECURITY
|
|
119
|
+
pretool_surface = declarations.PRETOOL_SURFACE
|
|
120
|
+
deployment_claim = declarations.DEPLOYMENT_CLAIM
|
|
121
|
+
input_translation = declarations.INPUT_TRANSLATION
|
|
115
122
|
|
|
116
123
|
@staticmethod
|
|
117
124
|
def continuation_capabilities(codex_root=None):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pactkit-codex
|
|
3
|
-
Version: 3.0
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: PactKit adapter for OpenAI Codex CLI
|
|
5
5
|
Author: Slim
|
|
6
6
|
License: MIT
|
|
@@ -21,7 +21,7 @@ Classifier: Topic :: Software Development :: Quality Assurance
|
|
|
21
21
|
Requires-Python: >=3.10
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE
|
|
24
|
-
Requires-Dist: pactkit<3.
|
|
24
|
+
Requires-Dist: pactkit<3.2.0,>=3.0.1
|
|
25
25
|
Requires-Dist: tomli>=1.1.0; python_version < "3.11"
|
|
26
26
|
Dynamic: license-file
|
|
27
27
|
|
|
@@ -2,6 +2,7 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
4
|
src/pactkit_codex/__init__.py
|
|
5
|
+
src/pactkit_codex/declarations.py
|
|
5
6
|
src/pactkit_codex/deployer.py
|
|
6
7
|
src/pactkit_codex/enforcement.py
|
|
7
8
|
src/pactkit_codex.egg-info/PKG-INFO
|
|
@@ -10,4 +11,6 @@ src/pactkit_codex.egg-info/dependency_links.txt
|
|
|
10
11
|
src/pactkit_codex.egg-info/entry_points.txt
|
|
11
12
|
src/pactkit_codex.egg-info/requires.txt
|
|
12
13
|
src/pactkit_codex.egg-info/top_level.txt
|
|
13
|
-
tests/
|
|
14
|
+
tests/test_capability_contract.py
|
|
15
|
+
tests/test_host_body_snapshot.py
|
|
16
|
+
tests/test_profile_contract.py
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""SPEC-4.0-22 Step 7 — 本宿主的 capability / deployment claim / pretool 契约。
|
|
2
|
+
|
|
3
|
+
**为什么这份测试在本仓。** 这三张表(能力声明、部署声明、pre-tool 机制名)
|
|
4
|
+
自 Step 6/7 起由本 adapter 自带并经 entry-point 注册。core 侧只保留它随包发布
|
|
5
|
+
的 `classic`。所以「本宿主能做什么、怎么执行、pre-tool 机制叫什么」的断言属于
|
|
6
|
+
**本仓**——生产里的事实归谁,断言就归谁。
|
|
7
|
+
|
|
8
|
+
core 侧对应断言(`test_spec40_22_capability_registry.py` 的宿主值行)已随之删除。
|
|
9
|
+
**是搬迁,不是复制**:两处各留一份就是给同一个事实造两个真相源。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from pactkit.capability import resolve_capabilities
|
|
15
|
+
from pactkit.generators.deploy_base import _ensure_entry_point_deployers
|
|
16
|
+
from pactkit.profiles import registered_adapters
|
|
17
|
+
|
|
18
|
+
HOST = "codex"
|
|
19
|
+
PRETOOL = 'native hooks engine'
|
|
20
|
+
CLAIM = ('portable', 'native_codex_session')
|
|
21
|
+
ENV_CAPABLE = {'.codex/config.toml'}
|
|
22
|
+
PROTECTED = {'.codex/hooks.json'}
|
|
23
|
+
CONDITIONAL = set()
|
|
24
|
+
PRESENT = ['git_hook', 'global_instruction', 'pre_tool_hook', 'project_instruction', 'rule_import', 'shell_execution']
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _registration():
|
|
28
|
+
_ensure_entry_point_deployers()
|
|
29
|
+
return registered_adapters()[HOST]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestTheDeclarationReachesTheRegistry:
|
|
33
|
+
def test_the_registry_carries_this_hosts_claim(self):
|
|
34
|
+
claim = _registration().deployment_claim
|
|
35
|
+
assert (claim.execution_mode, claim.verification_source) == CLAIM
|
|
36
|
+
|
|
37
|
+
def test_the_registry_carries_this_hosts_pretool_mechanism(self):
|
|
38
|
+
assert _registration().pretool_surface == PRETOOL
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestTheCapabilityDeclaration:
|
|
42
|
+
def test_the_present_set_is_exactly_this(self):
|
|
43
|
+
"""**逐项**钉住,不是只钉个数——多一项少一项都要红。
|
|
44
|
+
|
|
45
|
+
迁移前基线是 6 项;这条断言的是同一批名字,所以它既防漏声明,
|
|
46
|
+
也防「为了让表看起来完整」而虚报一项。
|
|
47
|
+
"""
|
|
48
|
+
resolved = resolve_capabilities(HOST)
|
|
49
|
+
assert resolved.known
|
|
50
|
+
present = sorted(n for n, e in resolved.entries.items() if e.present)
|
|
51
|
+
assert present == PRESENT
|
|
52
|
+
|
|
53
|
+
def test_every_declared_entry_carries_evidence(self):
|
|
54
|
+
for name, entry in resolve_capabilities(HOST).entries.items():
|
|
55
|
+
assert entry.evidence, f"{name} 没有证据来源——那就是一个断言,不是声明"
|
|
56
|
+
|
|
57
|
+
def test_the_declaration_is_not_empty(self):
|
|
58
|
+
assert _registration().capabilities, (
|
|
59
|
+
"本宿主没有声明任何能力——registry 会把每一项都填成 Degraded"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TestTheContractHasTeeth:
|
|
64
|
+
"""反证:改坏本仓的声明,上面必须有测试变红。"""
|
|
65
|
+
|
|
66
|
+
def test_mutating_the_claim_moves_the_answer(self):
|
|
67
|
+
import dataclasses
|
|
68
|
+
|
|
69
|
+
from pactkit.profiles import register_adapter
|
|
70
|
+
import pactkit_codex.declarations as decl
|
|
71
|
+
|
|
72
|
+
entry = _registration()
|
|
73
|
+
broken = dataclasses.replace(
|
|
74
|
+
entry.deployment_claim, verification_source="mutated"
|
|
75
|
+
)
|
|
76
|
+
register_adapter(HOST, deployer=entry.deployer, profile=entry.profile,
|
|
77
|
+
deployment_claim=broken)
|
|
78
|
+
try:
|
|
79
|
+
assert _registration().deployment_claim.verification_source == "mutated", (
|
|
80
|
+
"改掉本仓的声明后 registry 没变——core 侧还有第二份真相源"
|
|
81
|
+
)
|
|
82
|
+
finally:
|
|
83
|
+
register_adapter(HOST, deployer=entry.deployer, profile=entry.profile,
|
|
84
|
+
deployment_claim=decl.DEPLOYMENT_CLAIM)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
# 安全事实(SPEC-4.0-22 R8 / Step 7)
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TestTheSecurityFacts:
|
|
93
|
+
"""这三类的定义域是 **PactKit gate-relative**:`env` 能到达 gate 进程的文件、
|
|
94
|
+
gate 不许任何人编辑的文件、移除 gate 条目算篡改的文件。
|
|
95
|
+
|
|
96
|
+
所以它们是**本宿主的声明**——core 侧那张跨宿主表已随 Step 7 删除,这里的
|
|
97
|
+
断言跟事实走。
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def test_the_declared_files_are_exactly_these(self):
|
|
101
|
+
s = _registration().security
|
|
102
|
+
assert set(s.env_capable.files) == ENV_CAPABLE
|
|
103
|
+
assert set(s.protected.files) == PROTECTED
|
|
104
|
+
assert set(s.conditional.files) == CONDITIONAL
|
|
105
|
+
|
|
106
|
+
def test_the_declaration_is_complete(self):
|
|
107
|
+
"""`missing`(宿主没说话)与 `empty`(宿主说了它没有)是两件事。
|
|
108
|
+
|
|
109
|
+
声明不完整时,需要 tamper_guard 授权的写操作会 fail closed——那是
|
|
110
|
+
R8 的设计,不是缺陷。所以「完整」本身是要被钉住的事实。
|
|
111
|
+
"""
|
|
112
|
+
assert _registration().security.complete
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""SPEC-4.0-22 Step 7 — 本宿主的 profile 契约。
|
|
2
|
+
|
|
3
|
+
**为什么这份测试在本仓。** codex 的 `FormatProfile` 自 Step 6/7 起由本 adapter
|
|
4
|
+
自带并经 entry-point 注册;core 侧只保留它随包发布的 `classic`。所以「codex 的
|
|
5
|
+
全局配置目录是什么」「它的命令以什么形态落盘」这类断言属于**本仓**——生产里的
|
|
6
|
+
事实在谁那里,断言就在谁那里。
|
|
7
|
+
|
|
8
|
+
core 侧对应的 `test_story_slim060_codex_profile.py` / `test_story059_codex_removal.py`
|
|
9
|
+
已随之删除。**是搬迁,不是复制**:两处各留一份,就是给同一个事实造两个真相源,
|
|
10
|
+
而漂移的那天两边都会绿。
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import cast
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from pactkit.profiles import (
|
|
20
|
+
CLIPolicy,
|
|
21
|
+
FormatProfile,
|
|
22
|
+
get_profile,
|
|
23
|
+
)
|
|
24
|
+
from pactkit_codex.declarations import PROFILE
|
|
25
|
+
|
|
26
|
+
SPEC = cast(FormatProfile, PROFILE)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TestTheProfileIsReachable:
|
|
30
|
+
"""注册真的发生,且生产读路径(registry)能拿到它。"""
|
|
31
|
+
|
|
32
|
+
def test_it_is_registered(self):
|
|
33
|
+
from pactkit.generators.deploy_base import _ensure_entry_point_deployers
|
|
34
|
+
|
|
35
|
+
_ensure_entry_point_deployers()
|
|
36
|
+
assert get_profile("codex").name == "codex"
|
|
37
|
+
|
|
38
|
+
def test_get_profile_returns_the_same_object(self):
|
|
39
|
+
from pactkit.generators.deploy_base import _ensure_entry_point_deployers
|
|
40
|
+
from pactkit.profiles import get_registration
|
|
41
|
+
|
|
42
|
+
_ensure_entry_point_deployers()
|
|
43
|
+
assert get_registration("codex").profile is SPEC, (
|
|
44
|
+
"registry 里那份不是本仓声明的这个对象——说明还有第二个真相源"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def test_it_is_a_format_profile(self):
|
|
48
|
+
assert isinstance(SPEC, FormatProfile)
|
|
49
|
+
|
|
50
|
+
def test_it_is_frozen(self):
|
|
51
|
+
with pytest.raises(AttributeError):
|
|
52
|
+
SPEC.name = "modified"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TestThePathFacts:
|
|
56
|
+
"""本宿主的目录布局——**实测值**,不是从 core 抄来的默认值。"""
|
|
57
|
+
|
|
58
|
+
@pytest.mark.parametrize(
|
|
59
|
+
"field,expected",
|
|
60
|
+
[
|
|
61
|
+
("name", "codex"),
|
|
62
|
+
("display_name", "Codex CLI"),
|
|
63
|
+
("global_config_dir", "~/.codex"),
|
|
64
|
+
("project_config_dir", ".codex"),
|
|
65
|
+
("skills_dir", "~/.codex/skills"),
|
|
66
|
+
("agents_dir", "~/.codex/agents"),
|
|
67
|
+
("rules_dir", "~/.codex/rules"),
|
|
68
|
+
("project_instructions_file", "AGENTS.md"),
|
|
69
|
+
("pactkit_yaml_path", ".codex/pactkit.yaml"),
|
|
70
|
+
("agent_format", "md"),
|
|
71
|
+
("rules_import_style", "inline"),
|
|
72
|
+
("skills_path_var", "~/.codex/skills"),
|
|
73
|
+
],
|
|
74
|
+
)
|
|
75
|
+
def test_field(self, field, expected):
|
|
76
|
+
assert getattr(SPEC, field) == expected
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class TestTheMeasuredLayoutFact:
|
|
80
|
+
def test_commands_deploy_as_skills(self):
|
|
81
|
+
"""**实测**(2026-09-22):codex 的命令落在 `skills/{name}/SKILL.md`,
|
|
82
|
+
不是 `commands_dir` 指的 `~/.codex/prompts`(那个目录根本不存在)。
|
|
83
|
+
|
|
84
|
+
这一条是本仓最该守的布局事实之一:`commands_dir` 是一个**没人写的路径**,
|
|
85
|
+
把它当布局用会得到一份永远不会被发现的产物。
|
|
86
|
+
"""
|
|
87
|
+
assert SPEC.command_deploy_form == "skill", (
|
|
88
|
+
"codex 的命令以 skill 形态落盘(adapter 自己的 docstring 与实测一致);"
|
|
89
|
+
"改成 `flat`/`prompt` 会让产物落到一个没人读的目录"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def test_commands_dir_is_not_the_layout_fact(self):
|
|
93
|
+
"""`commands_dir` 仍在(字段合法性),但它**不是**布局事实。"""
|
|
94
|
+
assert SPEC.commands_dir == "~/.codex/prompts"
|
|
95
|
+
assert SPEC.command_deploy_form != "flat", (
|
|
96
|
+
"若两者相等,说明有人把 `commands_dir` 当成了落盘位置"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class TestTheCLIPolicy:
|
|
101
|
+
def test_codex_prefers_the_pactkit_cli(self):
|
|
102
|
+
assert SPEC.cli_policy is CLIPolicy.PREFERRED
|
|
103
|
+
|
|
104
|
+
def test_has_pactkit_cli_is_derived_from_the_policy(self):
|
|
105
|
+
assert SPEC.has_pactkit_cli is True
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class TestTheDeclaredDialects:
|
|
109
|
+
"""codex 自带 writer、不调共享 API,所以方言字段**没有消费者**。
|
|
110
|
+
|
|
111
|
+
为了让表看起来完整而填 `"claude"`,就是造一条死通道。
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
@pytest.mark.parametrize("field", ["agent_dialect", "command_dialect"])
|
|
115
|
+
def test_it_declares_nothing(self, field):
|
|
116
|
+
assert getattr(SPEC, field) is None, (
|
|
117
|
+
f"{field} 被填了值——codex 不调共享 writer,这个值没有消费者"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TestTheLiveViewsSeeIt:
|
|
122
|
+
"""core 的活视图(`VALID_FORMATS` / `PACTKIT_YAML_CANDIDATES`)在注册后
|
|
123
|
+
必须包含本宿主——**这是 core 的机制**,但只有本仓能把它端到端证明。"""
|
|
124
|
+
|
|
125
|
+
def test_it_is_in_valid_formats(self):
|
|
126
|
+
from pactkit.generators.deploy_base import _ensure_entry_point_deployers
|
|
127
|
+
from pactkit.profiles import VALID_FORMATS
|
|
128
|
+
|
|
129
|
+
_ensure_entry_point_deployers()
|
|
130
|
+
assert "codex" in VALID_FORMATS
|
|
131
|
+
|
|
132
|
+
def test_its_config_path_is_a_discovery_candidate(self):
|
|
133
|
+
from pactkit.generators.deploy_base import _ensure_entry_point_deployers
|
|
134
|
+
from pactkit.profiles import PACTKIT_YAML_CANDIDATES
|
|
135
|
+
|
|
136
|
+
_ensure_entry_point_deployers()
|
|
137
|
+
assert SPEC.pactkit_yaml_path in PACTKIT_YAML_CANDIDATES
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class TestTheContractHasTeeth:
|
|
141
|
+
"""反证:改坏声明,上面必须有测试变红。
|
|
142
|
+
|
|
143
|
+
没有这一条,「搬过来了」只是两个仓各自为绿的巧合。
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
def test_the_profile_is_what_get_profile_answers(self):
|
|
147
|
+
"""把本仓的声明换掉,`get_profile` 必须跟着变。
|
|
148
|
+
|
|
149
|
+
若它不变,说明 core 还留着一份——那正是「复制一份、core 还留一份」的形态。
|
|
150
|
+
"""
|
|
151
|
+
import dataclasses
|
|
152
|
+
|
|
153
|
+
import pactkit_codex.declarations as decl
|
|
154
|
+
from pactkit.generators.deploy_base import _ensure_entry_point_deployers
|
|
155
|
+
from pactkit.profiles import get_registration, register_adapter
|
|
156
|
+
|
|
157
|
+
_ensure_entry_point_deployers()
|
|
158
|
+
broken = dataclasses.replace(SPEC, display_name="Not Codex")
|
|
159
|
+
register_adapter(
|
|
160
|
+
"codex",
|
|
161
|
+
deployer=get_registration("codex").deployer,
|
|
162
|
+
profile=broken,
|
|
163
|
+
)
|
|
164
|
+
try:
|
|
165
|
+
assert get_profile("codex").display_name == "Not Codex", (
|
|
166
|
+
"改掉本仓的声明后 get_profile 没变——core 侧还有第二份真相源"
|
|
167
|
+
)
|
|
168
|
+
finally:
|
|
169
|
+
register_adapter(
|
|
170
|
+
"codex",
|
|
171
|
+
deployer=get_registration("codex").deployer,
|
|
172
|
+
profile=decl.PROFILE,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class TestTheCLIPolicyIsDeclaredHere:
|
|
177
|
+
"""`cli_policy` 是**本宿主的**声明:它决定部署产物要不要依赖 PactKit CLI。
|
|
178
|
+
|
|
179
|
+
core 侧对应断言(`test_story_slim084.py::TestHasPactkitCli`)已随之删除。
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
def test_the_policy(self):
|
|
183
|
+
assert SPEC.cli_policy is CLIPolicy.PREFERRED
|
|
184
|
+
|
|
185
|
+
def test_has_pactkit_cli_is_derived(self):
|
|
186
|
+
assert SPEC.has_pactkit_cli is True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|